# Atomic actions

If you want to exchange assets with someone in a trustless way, not depending on a third party nor accepting counterparty risk, atomic actions are the way to go.

Atomic order is a way of creating an atomic swap within the 0xcert Framework. It is a set of instructions for what you will execute in a single mutation and with only two possible results: either the mutation will succeed with all actions executed, or the mutation will fail and nothing will change. Atomic order leaves no middle way.

Atomic actions can be performed between multiple parties and can contain multiple actions.

List of available actions:

  • Transfer asset
  • Transfer value
  • Create new asset
  • Update existing asset imprint
  • Destroy an asset
  • Update account abilities

Since atomic actions can be performed between multiple parties, there are four different ways of how to interact with them. In this guide, we will go through all of them and explain each and every one. Here is a quick overview:

  • FixedActionsOrder - All participants are known and set in the order. Only the last defined participant can perform the order; others have to provide signatures.
  • SignedFixedActionsOrder - All participants are known and set in the order. All participants have to provide signatures. Anyone can perform the order.
  • DynamicActionsOrder - The last participant can be unknown or "any". All defined participants have to provide signatures, anyone can perform the order. The one that performs the order, automatically becomes the last participant.
  • SignedDynamicActionsOrder - The last participant can be unknown or "any". All defined participants have to provide signatures, as well as the last "any" participant. Anyone can then perform the order once all the signatures have been provided.

More about atomic swaps

For more information on the actual process of atomic operation, please check this article.

# Gateway

All atomic actions within the 0xcert Framework are performed through a Gateway. Gateway is, as the name suggests, the gateway to multiple smart contracts that already live (and are maintained by 0xcert) on the blockchain and that enable atomic swaps with a broad array of functionalities. Depending on what kind of atomic order we are performing, there are multiple main smart contracts, as well as multiple proxies that take care of specific actions. Here, you can see all the already deployed smart contracts and their addresses.

Since Gateway is a single point of entry for all kinds of atomic actions, each order is defined through an Order interface and has a unique OrderKind for the sake of differentiation.

For the beginning of this guide, we will create a FixedActionsOrder, the simplest type of atomic actions.

# Prerequisites

In this guide, we will assume you have gone through the Asset Management guide and have deployed an AssetLedger, as well created an asset with ID 100. You will also need three MetaMask accounts (create them through your MetaMask plug-in) with some ETH available.

# Installation

We recommend you employ the package as an NPM package in your application.

$ npm i --save @0xcert/ethereum-gateway

In our official GitHub repository, we also host a compiled and minimized JavaScript file that can be directly implemented in your website. Please refer to the API section to learn more about gateway.

# Initialization

As usual, we first import a module into the application. This time, we import the Gateway class, which represents a wrapper around a specific pre-deployed structure on the Ethereum network.

import { Gateway } from '@0xcert/ethereum-gateway';

Then, we create a new instance of the Gateway class with an ID that points to a pre-deployed gateway on the Ethereum Ropsten network (this option can also be configured in the provider).

const gateway = Gateway.getInstance(provider, buildGatewayConfig(NetworkKind.ROPSTEN));

buildGatewayConfig will always return the latest contract versions for a specific package version. You can also configure gateway config on your own with our already deployed addresses here or with your own addresses.

# Fixed actions order

Live example

Click here to check the live example for the fixed actions order.

We will have two participants; one will be the account we used for creating the AssetLedger; the other will be the second account from MetaMask. For simplicity, let's name them account1 and account2.

We will create a fixed actions order with two actions. The first action will be a transfer of our already created asset with ID 100 from account1 to account2. In the second action, we will create a new asset with ID 101 in account2.

First, we start by defining the order.

const order = {
  kind: OrderKind.FIXED_ACTIONS_ORDER, // defining order kind
  signers: ['0x...', '0x...'], // account1, account2
  seed: Date.now(), // unique order identification
  expiration: Date.now() + 86400000, // 1 day
  actions: [ // actions we want to perform in this order
    {
      kind: ActionsOrderActionKind.TRANSFER_ASSET, // transfer asset action
      ledgerId: '0x..', // assetLedgerId that we created in the previous guide
      senderId: '0x..', // account1
      receiverId: '0x..', // account2
      assetId: '100', // asset id
    },
    {
      kind: ActionsOrderActionKind.CREATE_ASSET,
      ledgerId: '0x..', // assetLedgerId that we created in the previous guide
      senderId: '0x..', // account1
      receiverId: '0x..', // account2
      assetId: '101', // asset id
      assetImprint: 'c6c14772f269bed1161d4350403f4c867c749b3cce7abe84c6d0605068cd8a87', // asset imprint
    }
  ]
} as FixedActionsOrder

In this order, we are creating a new asset and transferring an existing one. But these actions first require our approval. So we will first approve asset transfer. For this, we will make an instance of AssetLedger as we learned in the Asset Management guide and set gateway proxy as an operator.

const transferProxy = await gateway.getProxyAccountId(ProxyKind.TRANSFER_ASSET);
const mutation = await assetLedger.approveOperator(transferProxy);
await mutation.complete();

Then, we will grant permission to create proxy to create a new asset on our behalf.

const createProxy = await gateway.getProxyAccountId(ProxyKind.CREATE_ASSET);
const mutation = await assetLedger.grantAbilities(createProxy, [GeneralAssetLedgerAbility.CREATE_ASSET]);
await mutation.complete();

TIP

Don't forget to create an instance of assetLedger and to import GeneralAssetLedgerAbility.

Granting permissions and setting the operator is only needed the first time (per ledger).

Now, since we have two participants (signers), the first one has to sign the order while the second one can perform it. This means you have to select account1 in MetaMask when signing the order.

const signature = await gateway.sign(order); 

The first participant can now send their signature and the order definition to the second participant through arbitrary channels. Once the second participant (account2) receives this data, they can perform the order.

const mutation = await gateway.perform(order, [signature]);
await mutation.complete();

If we did everything correctly, the atomic swap would perform successfully; otherwise, an error will be thrown, specifying what went wrong.

# Signed fixed actions order

Live example

Click here to check the live example for signed fixed actions order.

Just like in the fixed actions order, we will have two participants. Also, we will create an asset with ID 101 and transfer an existing asset with ID 100.

TIP

If you already transferred and created an asset in the previous section on this specific AssetLedger, you will not be able to do the same action again since account1 is no longer the owner of the asset 100 and the asset 101 has already been created. You can either deploy a new AssetLedger and recreate the same starting conditions or send the asset 100 back to account1, and instead of creating the asset 101, you can create an asset with ID 102.

The order will be defined almost the same way as in the fixed order, however, the execution process will be different. We will only showcase the differences between the fixed action order and the signed fixed action order.

const order = {
  kind: OrderKind.SIGNED_FIXED_ACTIONS_ORDER, // defining order kind
  signers: ['0x...', '0x...'], // account1, account2
  seed: Date.now(), // unique order identification
  expiration: Date.now() + 86400000, // 1 day
  actions: [ // actions we want to perform in this order
    {
      kind: ActionsOrderActionKind.TRANSFER_ASSET, // transfer asset action
      ledgerId: '0x..', // assetLedgerId that we created in the previous guide
      senderId: '0x..', // account1
      receiverId: '0x..', // account2
      assetId: '100', // asset id
    },
    {
      kind: ActionsOrderActionKind.CREATE_ASSET,
      ledgerId: '0x..', // assetLedgerId that we created in the previous guide
      senderId: '0x..', // account1
      receiverId: '0x..', // account2
      assetId: '101', // asset id
      assetImprint: 'c6c14772f269bed1161d4350403f4c867c749b3cce7abe84c6d0605068cd8a87', // asset imprint
    }
  ]
} as SignedFixedActionsOrder

The way we grant permissions remains the same as in the guide above.

The difference now comes in signing the order. In the case above, only the first participant was needed to sign the order, and the second one could execute it. In this case, however, both participants need to sign the order, and any participant can execute the order.

const signatureFromAccount1 = await gateway.sign(order); // sign with account1 in metamask
const signatureFromAccount2 = await gateway.sign(order); // sign with account2 in metamask

TIP

If you are not using MetamaskProvider where you only need to change the account in the MetaMask extension, you will need to create two providers with different default accounts, as well as two gateways, each with a different provider for signatures to be made from different accounts.

Now anyone that has both signatures can execute this order:

const mutation = await gateway.perform(order, [signatureFromAccount1, signatureFromAccount2]); // perform with account3 in metamask
await mutation.complete();

# Dynamic actions order

Live example

Click here to check the live example for dynamic actions order.

The difference between the fixed and the dynamic actions order is in knowing the participants beforehand. In the fixed order at defining the order, we had to specify both participants, as well as the senderId and receiverId in both actions. On the other hand, dynamic orders allow for an undefined participant. This means that anyone can become that last participant and automatically become either the senderId or receiverId in actions if they are undefined. This allows us to make a marketplace-like order where, for example, you can say you are selling an asset to anyone willing to pay some value for it.

For this guide, however, we will be using the same scenario as in previous guides.

TIP

If you already transferred and created an asset in the previous section on this specific AssetLedger, you will not be able to do the same action again since account1 is no longer the owner of the asset 100 and the asset 101 has already been created. You can either deploy a new AssetLedger and recreate the same starting conditions or send the asset 100 back to account1, and instead of creating the asset 101, you can create an asset with ID 102.

Now let's define the order. Unlike the fixed order, we will define only one signer and will not define receiverId in actions.

const order = {
  kind: OrderKind.DYNAMIC_ACTIONS_ORDER, // defining order kind
  signers: ['0x...'], // account1
  seed: Date.now(), // unique order identification
  expiration: Date.now() + 86400000, // 1 day
  actions: [ // actions we want to perform in this order
    {
      kind: ActionsOrderActionKind.TRANSFER_ASSET, // transfer asset action
      ledgerId: '0x..', // assetLedgerId that we created in the previous guide
      senderId: '0x..', // account1
      assetId: '100', // asset id
    },
    {
      kind: ActionsOrderActionKind.CREATE_ASSET,
      ledgerId: '0x..', // assetLedgerId that we created in the previous guide
      senderId: '0x..', // account1
      assetId: '101', // asset id
      assetImprint: 'c6c14772f269bed1161d4350403f4c867c749b3cce7abe84c6d0605068cd8a87', // asset imprint
    }
  ]
} as DynamicActionsOrder

We still need to grant permission the same way as in previous guides, and the specified signer needs to sign the order.

const signature = await gateway.sign(order); 

Now, anyone that has the order definition and the order signature from account1 can perform the order and will automatically become the last participant and receiver of both assets.

const mutation = await gateway.perform(order, [signature]);
await mutation.complete();

# Signed dynamic actions order

Much like a signed fixed order differs from a fixed order, the signed dynamic order differs from a dynamic order. This means that instead of any participant being able to perform the order and become the receiver, any participant can provide the last signature, and anyone with both signatures can execute the order.

So let's define the order.

const order = {
  kind: OrderKind.SIGNED_DYNAMIC_ACTIONS_ORDER, // defining order kind
  signers: ['0x...'], // account1
  seed: Date.now(), // unique order identification
  expiration: Date.now() + 86400000, // 1 day
  actions: [ // actions we want to perform in this order
    {
      kind: ActionsOrderActionKind.TRANSFER_ASSET, // transfer asset action
      ledgerId: '0x..', // assetLedgerId that we created in the previous guide
      senderId: '0x..', // account1
      assetId: '100', // asset id
    },
    {
      kind: ActionsOrderActionKind.CREATE_ASSET,
      ledgerId: '0x..', // assetLedgerId that we created in the previous guide
      senderId: '0x..', // account1
      assetId: '101', // asset id
      assetImprint: 'c6c14772f269bed1161d4350403f4c867c749b3cce7abe84c6d0605068cd8a87', // asset imprint
    }
  ]
} as SignedDynamicActionsOrder

The way we grant permissions remains the same as in the guide above.

The difference now comes in signing the order. In the case above, only the first participant was needed to sign the order, and any participant could execute it (making them the recipient). In this case, however, both participants need to sign the order, and any participant can execute the order.

const signatureFromAccount1 = await gateway.sign(order); 
const signatureFromAnyone = await gateway.sign(order); 

TIP

If you are not using MetamaskProvider where you only need to change the account in the MetaMask extension, you will need to create two providers with different default accounts, as well as two gateways, each with a different provider for signatures to be made from different accounts.

Now, anyone that has both signatures can execute this order, and the participant with the address that created the second signature will become the receiver.

const mutation = await gateway.perform(order, [signatureFromAccount1, signatureFromAnyone]);
await mutation.complete();