[Framework Tutorial 6] - Atomic operations in asset exchange

[Framework Tutorial 6] - Atomic operations in asset exchange

At the end of the tutorial, you will be able to create atomic orders for your assets on the Ethereum network.

To achieve this, we will use the following technologies:

  • Ethereum Ropsten testnet
  • TypeScript
  • Node.js
  • Express
  • 0xcert Framework
  • jQuery

It is assumed that you know the basics of JavaScript, Node.js, jQuery, HTML, and use of the terminal.

All the code used for the examples can be found on GitHub.

Build your own dapp

Atomic operations

To exchange digital assets (both ERC-721 NFTs and ERC-20 fungible tokens) with a third party in a decentralized way and without the risk of losing traded assets, atomic operation is an optimal way of swapping assets of both trading parties in a single go.

Atomic operation within the 0xcert Framework is a set of instructions for an order to be executed in a single mutation and with only two possible outcomes - either a successful exchange or an abolishment of the exchange process and return to its starting point.

How does atomic operation work

An atomic operation is an order between a Maker and a Taker. The Maker creates an order, and the Taker executes it, and multiple parties can join as participants.

The 0xcert Framework allows for multiple actions to be performed in such a manner. At this point, the atomic operation within the Framework supports the transfer of digital assets (ERC-721) and values (ERC-20), and the creation of new assets (ERC-721).

The benefits of atomic operation are trustlessness and security of a transaction and the exchanged assets. You may delegate paying storage fees to another user, as well as determine the exact time of the operation to be executed.

To ensure that a specific agreement between parties is reached to its fullness, the 0xcert Framework provides an automatized atomic swap. This way, an atomic transaction can only conclude in two possible ways:

a) Transaction is completed successfully for both trading parties or
b) Transaction is abolished and reverses the order to its starting point without any asset loss (in case there are issues in the order's settlement).

The steps of the atomic operation progress automatically. A new step can begin only once the previous one is completed thoroughly and successfully. When the final condition is met and confirmed, the atomic operation as a whole is settled automatically, no sooner and in no other way. By locking the transaction process from start to finish and making it atomic (indivisible), we remove the possibility for any third scenario where one party could take an unfair advantage over the other.

A trade agreement is set between two or more trading parties in an off-chain environment, while the agreement execution and settlement provided by the atomic operation is done entirely on-chain. This way, we can verify and track each step of the operation at any time.

To learn more about the process of atomic operation, please check this article.

Build your own dapp


Before you begin

The atomic operation in already deployed contract is performed via the Order Gateway structure which is deployed permanently on the platform and available to the public. The cryptographic mechanisms embedded in the Order Gateway structure guarantee protection for a fixed sequence of required steps and conditions.

You can see a live example for atomic operation here.

Installation

First, we need to install the order gateway package:

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

Usage overview

For this tutorial, we will transfer an existent asset (ERC-721) to a new digital wallet, plus create (mint) a new asset (ERC-721) and transfer it to our main digital wallet - all in a single operation.

First, we need to prepare the state so that we can execute our plan.

If you completed all the steps of the previous tutorial, this should be a piece of cake. For this example, we will need a deployed assetLedger with an existing asset with ID 100.

We will also need to set up an additional wallet to act as the Taker (recipient) of the atomic swap. If you forgot how to do this, check the Create a new account (wallet) section of the first tutorial.

Now, we import a module into the application. In this case, we import the OrderGateway class that represents a wrapper around a specific pre-deployed structure on the Ethereum network.

import { OrderGateway, Order, OrderActionKind } from '@0xcert/ethereum-order-gateway';

Next, let's add a new API endpoint called atomic-order.

app.post('/atomic-order', async (req, res) => {
});

In all previous tutorials, we always created a front-end where you specified all the input parameters. But since atomic orders are a bit more complex and dynamic, we will hard code all the parameters and just press a button on the front-end. In a real-world dapp, you would either handle this on the front-end similar to SwapMarket or generate the atomic order on the back-end yourself, depending on what you want to use it for. In this example, we only want to show how it works, so a primitive set-up like this is sufficient.

Next, we create a new instance of the OrderGateway class of an ID that points to a pre-deployed order gateway smart contract on the Ethereum Ropsten network.

You can find the orderGatewayId for your preferred network here. Since this tutorial is made to work on the Ethereum Ropsten network, we will copy OrderGateway address from the Ropsten section.

const orderGatewayId = '0x28dDb78095cf42081B9393F263E8b70BffCbF88F';
const orderGateway = OrderGateway.getInstance(provider, orderGatewayId);

Now, we can define an operation with two actions:

  1. The first action will transfer an asset (ERC-721) to our second wallet.
  2. The second action will create (mint) a new asset (ERC-721) of ID 200 and add it to our wallet.
  3. Every atomic operation has a Maker and a Taker. In this tutorial, we will act as both the Maker and the Taker of the order. For the makerId, we will use the Geth account we created in the previous tutorials, and for the takerId, we will use the one we created a few steps before.

This is how our order will look like.

const account1 = '0x...'; // 0x... - Account we used in previous tutorial.
const account2 = '0x...'; // 0x... - Account we just created.
const assetLedgerId = '0x...'; // Your already deployed asset ledger.
const order = {
    makerId: account1,
    takerId: account2,
    actions: [
        {
            kind: OrderActionKind.TRANSFER_ASSET,
            ledgerId: assetLedgerId,
            senderId: account1,
            receiverId: account2,
            assetId: '100',
        },
        {
            kind: OrderActionKind.CREATE_ASSET,
            ledgerId: assetLedgerId,
            receiverId: account1,
            assetId: '200',
            assetImprint: '0000000000000000000000000000000000000000000000000000000000000000', // check certification section in documentation on how to create a valid imprint
        },
    ],
    seed: Date.now(), // unique order identification
    expiration: Date.now() + 60 * 60 * 24, // 1 day
} as Order;

Atomic order in action

Atomic operations work in multiple steps and with multiple parties, like this:

1. Maker signs an atomic order.
2. Maker approves their operations.
3. Maker sends an order with a signature to Taker.
4. Taker approves their operations.
5. Taker performs the atomic order.

For this example, we will do all of this in one API call and therefore act as the Maker and the Taker, and we will do all the operations in a single go. So there is no need for Step 3 since we already have the needed information.

Let's begin with Step 1.

const signedClaim = await orderGateway.claim(order);

By calling the claim function, we sign the order. Next, we need to send this signature to the Taker, together with the order object via an agreed communication channel.

Steps 2 and 4 are described below (each party should do this on their side):

For this example, we don't have any actions that need approval from the Taker, so we only do the Maker's part.

All parties participating in the operation must unlock the assets to be transferred and allow the OrderGateway to manage them. This step should be done by every person that participates in a transfer within order operations. In the example below, we authorize the OrderGateway to transfer the asset of ID 100 to another wallet address and grant it the ability to create (mint) assets.

The API section of the 0xcert Framework Documentation provides instructions on how to authorize the Order Gateway for all the assets simultaneously, to avoid authorizing only one asset at a time.

Order Gateway is comprised of multiple smart contracts. To save you the hassle of having to know the exact addresses, we handle all of this under one roof. However, the instance of OrderGateway is needed so that we know how to manage it. You may also decide to do this manually by finding the exact proxy contracts for the Order Gateway, but we recommend using Order Gateway Instance and let the 0xcert Framework handle that for you.

// approve account for transfering asset
await assetLedger.approveAccount('100', orderGateway).then((mutation) => {
    return mutation.complete();
});
// assign ability to mint
await assetLedger.grantAbilities(orderGateway, [GeneralAssetLedgerAbility.CREATE_ASSET]).then((mutation) => {
    return mutation.complete();
});

Make sure you create an instance of assetLedger and import GeneralAssetLedgerAbility.

For Step 5, we perform the order as a Taker (who will pay the network fee).
To specify that we perform this as a Taker, we will need to create a new instance of Provider and a new instance of OrderGateway with that provider, like this:

const providerTaker = new HttpProvider({
    url: 'http://127.0.0.1:8545',
    accountId: account2,
    requiredConfirmations: 1
  });
const orderGatewayTaker = OrderGateway.getInstance(providerTaker, orderGatewayId);

Now, we can execute the order and return the result:

const mutation = await orderGatewayTaker.perform(order, signedClaim);
res.send(mutation.id);

To trigger the API, let's add a button to our webpage:

<h1>Atomic order</h1>
<button id="atomicOrder">Perform atomic order</button>
<p id="atomicOrderConsole"></p>

And JavaScript code for the API call:

$.ajax({
  contentType: 'application/json'
});
$(function(){
  $('#atomicOrder').click(function(){
    $.post(
      "http://localhost:3000/atomic-order",
      { },
      function (response) {
        $('#atomicOrderConsole').html('<a href="https://ropsten.etherscan.io/tx/' + response + '" target="_blank">Check transaction on etherscan</a>.');
      }
    );
  });
});

Now we can trigger and check if the atomic operations work as we intended. This is just a simple introduction to atomic operations. You can specify multiple actions and as such transfer multiple assets, create multiple new assets and transfer value.


Congrats, you've made it to the end!

If you followed the tutorial series, you should know how to set up an Express server with TypeScript and how to manage assets with the 0xcert Framework. To keep the tutorials and the overall series comprehensive, we only went through core asset management functionalities provided by the 0xcert Framework.

To learn more, please visit our GitHub and check the documentation. The whole code example is available on GitHub here.

Build your own dapp

If you have questions about the code or you experience trouble developing your dapp, you can ask our developers for help in our Gitter channel.

Framework tutorial #1: Run and prepare Geth node for back-end integration
Framework tutorial #2: Set up Express server with Typescript for dapp backend
Framework tutorial #3: Deploy asset ledger
Framework tutorial #4: Create new assets
Framework Tutorial #5: Transfer assets

Newsletter