A Beginner’s Guide To Building A Relayer With 0x Protocol

How to build a relayer using 0x open protocol for decentralized exchange on the Ethereum blockchain.

What is the 0x project?

As 0x project’s whitepaper described “0x is an open protocol for decentralized exchange on the Ethereum blockchain.” In simple terms, 0x defines all the rules which an exchange need’s to perform trade functionality. Anyone can build a decentralized exchange using 0x protocol. Linda Xie wrote an awesome blog about 0x protocol which you can read here.

What is decentralize exchange (DeX)?

The main property of a decentralize exchange is giving custodianship to user/traders of their own asset. Unlike a centralized crypto exchange (ex — coinbase, binance) where the exchange stores a user’s private key, in decentralized exchange, private keys always remain with the the users, hence the user is always in control of his own asset. Isn’t it amazing?

Source

What Is A Relayer?

A Relayer hosts an off-chain order book. Using relayers, users can find, create, fill or cancel orders. Relayers helps traders discover counter-parties and move cryptographically orders between them. A relayer can talk to other relayers and create a pool of orders to increase liquidity.

How To Build A Relayer?

I’ve been following the 0x protocol since last year and they have one of the best developer wikis out there!

0x makes it so just about anyone can build a decentralized exchange and earn money. If an entire exchange is too much, you can start with just building a relayer. Today decentralized exchanges are in their infancy stage, but improved tech and UX will attract more people towards DeX in future. Now let’s start building our relayer.

Prerequisite-

  • Basic understanding of smart contracts.
  • Basic Understanding of node js
  • Basic Understanding of ganache
  • Basic understanding of exchange terminology

Installation

Clone the 0x starter project from GitHub.

git clone https://github.com/0xProject/0x-starter-project.git

Install all the dependencies (we use Yarn: brew install yarn --without-node):

yarn

Pull the latest Ganache 0x snapshot with all the 0x contracts pre-deployed:

yarn download_snapshot

In a separate terminal, navigate to the project directory and start Ganache:

yarn ganache-cli

Create, validate and fill an order

In this tutorial, we will perform three operations.

  • Create an order
  • Validate it
  • Fill it using ganache

Now we will understand how to create an order, validate it and fill it using 0x-starter-project.

Imports

We will import different utilities which will help us throughout this tutorial. We will also import configuration files. For now, we are using ganache with already 0x smart contracts are deployed privately.

import {
    assetDataUtils,
    BigNumber,
    ContractWrappers,
    generatePseudoRandomSalt,
    Order,
    orderHashUtils,
    signatureUtils,
} from '0x.js';
import { Web3Wrapper } from '@0x/web3-wrapper';
import { NETWORK_CONFIGS, TX_DEFAULTS } from '../configs';
import { DECIMALS, NULL_ADDRESS, ZERO } from '../constants';
import { getContractAddressesForNetwork, getContractWrappersConfig } from '../contracts';
import { PrintUtils } from '../print_utils';
import { providerEngine } from '../provider_engine';
import { getRandomFutureDateInSeconds } from '../utils';

You can find all these files the 0x-starter-project. Go check out them, look at configurations and settings.

Provider and constructor

We need to initialize contractWrappers. This provides helper functions around 0x contracts as well as ERC20/ERC721 token contracts on the blockchain. It takes provideEngine and network configuration of ganache, which will be running by default on http://localhost:8545.

const contractWrappers = new ContractWrappers(providerEngine, getContractWrappersConfig(NETWORK_CONFIGS.networkId));

Web3Provider - Web3 provider allows your application to communicate with an Ethereum Node. A provider can be any module or class instance that implements thesendAsync method (simply send in web3 V1.0 Beta). That's it. What this sendAsyncmethod does is take JSON RPC payload requests and handles them. Web3.js is an example of a javascript module that contains a Web3 HTTP Provider. Using a configured Web3 Provider, the application can request signatures, estimate gas and gas price, and submit transactions to an Ethereum node. The simplest example of a provider is:

const provider = new web3.providers.HttpProvider('http://localhost:8545');

This provider simply takes each JSON RPC payload it receives and sends it on to the Ethereum node running on port 8545.¹

Initialize web3wrapper and get address information

Initialize the Web3Wrapper using provideEngine, this provides helper functions around fetching account information.

const web3Wrapper = new Web3Wrapper(providerEngine);
const [maker, taker] = await web3Wrapper.getAvailableAddressesAsync();

Initializing address

For this tutorial, we are trading ZRX token with WETH (wrapped eth, erc20 compatible version of eth, eth itself is not ERC20 compatible token, Eth and weth has 1:1 exchange ratio). For that, we need to have the contract address of our 0x protocol to execute orders and ZRX token and WETH token’s ERC20 contract.

const contractAddresses = getContractAddressesForNetwork(NETWORK_CONFIGS.networkId);
const zrxTokenAddress = contractAddresses.zrxToken;
const etherTokenAddress = contractAddresses.etherToken;

Encoding Assets

For this tutorial, maker (seller/order creator) is creating an order of 5 ZRX tokens in exchange of .1 weth. We also need to take care of decimal points as different tokens can use different decimal points. In our case both weth/eth and zrx has 18 decimal points. 0x v2 uses Ethereum ABI encoding scheme to encode asset data into hex strings to encode all the information needed to identify an asset. That’s what we are doing here.

const makerAssetAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(5), DECIMALS);
const takerAssetAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(0.1), DECIMALS);
const makerAssetData = assetDataUtils.encodeERC20AssetData(zrxTokenAddress);
const takerAssetData = assetDataUtils.encodeERC20AssetData(etherTokenAddress);

Allowance for transacting assets

We need to allow 0x contract to move assets on the behalf or maker and taker. As we are transacting ETH so first we need to convert ETH into WETH by depositing ETH on WETH contract.

const makerZRXApprovalTxHash = await contractWrappers.erc20Token.setUnlimitedProxyAllowanceAsync(
        zrxTokenAddress,
        maker,
);
const takerWETHApprovalTxHash = await contractWrappers.erc20Token.setUnlimitedProxyAllowanceAsync(
        etherTokenAddress,
        taker,
);
const takerWETHDepositTxHash = await contractWrappers.etherToken.depositAsync(
        etherTokenAddress,
        takerAssetAmount,
        taker,
);

Creating an Order

0x protocol has below specific parameters which are needed to create an order. We will set these parameters and will create our order. We will use NULL_ADDRESS allowing anyone to fill our order.

  • **exchangeAddress** : The Exchange address.
  • **makerAddress** : Ethereum address of our Maker.
  • **takerAddress** : Ethereum address of our Taker.
  • **senderAddress** : Ethereum address of a required Sender (none for now).
  • **feeRecipientAddress** : Ethereum address of our Relayer (none for now).
  • **expirationTimeSeconds**: When will the order expire (in unix time).
  • **salt**: Random number to make the order (and therefore its hash) unique.
  • **makerAssetAmount**: The amount of token the Maker is offering.
  • **takerAssetAmount**: The amount of token the Maker is requesting from the Taker.
  • **makerAssetData**: The token address the Maker is offering.
  • **takerAssetData**: The token address the Maker is requesting from the Taker.
  • **makerFee**: How many ZRX the Maker will pay as a fee to the Relayer.
  • **takerFee** : How many ZRX the Taker will pay as a fee to the Relayer.
const randomExpiration = getRandomFutureDateInSeconds();const exchangeAddress = contractAddresses.exchange;
const order: Order = {
        exchangeAddress,
        makerAddress: maker,
        takerAddress: NULL_ADDRESS,
        senderAddress: NULL_ADDRESS,
        feeRecipientAddress: NULL_ADDRESS,
        expirationTimeSeconds: randomExpiration,
        salt: generatePseudoRandomSalt(),
        makerAssetAmount,
        takerAssetAmount,
        makerAssetData,
        takerAssetData,
        makerFee: ZERO,
        takerFee: ZERO,
};

Signing Order

Now we need to sign the order with maker’s private key so we can prove that specificed maker address is owned by maker. We will append this signature to our order. To do that we will perform these steps:

  • Generate a hash from our order.
  • Sign generated the hash using maker’s private key
  • append signature with the order
const orderHashHex = orderHashUtils.getOrderHashHex(order);
const signature = await signatureUtils.ecSignHashAsync(providerEngine, orderHashHex, maker);
const signedOrder = { ...order, signature };

Validating Order

We need to validate the order is Fillable before calling fillOrder This checks both the maker and taker balances and allowances to ensure it is fillable up to takerAssetAmount. Also if there is any order manipulation by anyone, we will also get to know by validating our order.

await contractWrappers.exchange.validateFillOrderThrowIfInvalidAsync
(signedOrder, takerAssetAmount, taker);

Filling Order

Now if everything worked fine then we can validate our order now using 0x protocol. We will also set the gas limit for executing our order.

txHash = await contractWrappers.exchange.fillOrderAsync(signedOrder, takerAssetAmount, taker, {
        gasLimit: TX_DEFAULTS.gas,
});

That’s it. We have filled our order.

yarn scenario:fill_order_erc20

You will see output something like below. If you see the fill_order_erc20.ts. The above code is available in the same file and it is using printUtils to print the output below.

yarn run v1.10.1
$ cross-env yarn build && node ./lib/scenarios/fill_order_erc20.js
$ cross-env yarn pre_build && tsc
$ run-s copy_artifacts
$ copyfiles -u 1 './src/artifacts/**/*' ./lib
┌────────────┐
│ Fill Order │
└────────────┘
 Accounts                                            
 Maker     0x5409ed021d9299bf6814279a6a1411a7e866a631
 Taker     0x6ecbe1db9ef729cbe972c83fb886247691fb6beb
 Setup                                                                                  
 Maker ZRX Approval   0xffa485e507252a439be2677a977e791d9b5ba829251481a3b43479326f55da45
 Taker WETH Approval  0x45cdef330d7d0a97cb058a8ba7b25735ee548aabcf00787d178e1fe3fd41200b
 Taker WETH Deposit   0xd57ac57521b8040f2a8cf8ee6d1109bd55c283ca4b818d0ca58599ba58625e7d
 Order                                                                                            
 exchangeAddress        0x48bacb9266a570d521063ef5dd96e61686dbe788                                
 makerAddress           0x5409ed021d9299bf6814279a6a1411a7e866a631                                
 takerAddress           0x0000000000000000000000000000000000000000                                
 senderAddress          0x0000000000000000000000000000000000000000                                
 feeRecipientAddress    0x0000000000000000000000000000000000000000                                
 expirationTimeSeconds  1540250008                                                                
 salt                   1030945504406868252934488830320408688272183043132497204228070027505852506…
 makerAssetAmount       5000000000000000000                                                       
 takerAssetAmount       100000000000000000                                                        
 makerAssetData         0xf47261b0000000000000000000000000871dd7c2b4b25e1aa18728e9d5f2af4c4e431f5c
 takerAssetData         0xf47261b00000000000000000000000000b1ba0af832d7c05fd64161e0db78e85978e8082
 makerFee               0                                                                         
 takerFee               0
Allowances
 Token  Maker     Taker   
 WETH   0         MAX_UINT
 ZRX    MAX_UINT  0
Balances
 Token  Maker      Taker
 WETH   0.5        0.1  
 ZRX    999999980  20
Transaction
┌───────────┬────────────────────────────────────────────────────────────────────┐
│ fillOrder │ 0x3dbe6243a61507aff8f60b88585baa2781bfc959c19334dd953c45a90cbadb28 │
├───────────┼────────────────────────────────────────────────────────────────────┤
│ orderHash │ 0x4fce1fd1a979119fdc4dc09a199a3bc3284bfabde3c455b24d222110dad2425c │
├───────────┼────────────────────────────────────────────────────────────────────┤
│ gasUsed   │ 111748                                                             │
├───────────┼────────────────────────────────────────────────────────────────────┤
│ status    │ Success                                                            │
└───────────┴────────────────────────────────────────────────────────────────────┘
Logs
 Fill                                                                                              
 contract                0x48bacb9266a570d521063ef5dd96e61686dbe788                                
 makerAddress            0x5409ed021d9299bf6814279a6a1411a7e866a631                                
 feeRecipientAddress     0x0000000000000000000000000000000000000000                                
 takerAddress            0x6ecbe1db9ef729cbe972c83fb886247691fb6beb                                
 senderAddress           0x6ecbe1db9ef729cbe972c83fb886247691fb6beb                                
 makerAssetFilledAmount  5000000000000000000                                                       
 takerAssetFilledAmount  100000000000000000                                                        
 makerFeePaid            0                                                                         
 takerFeePaid            0                                                                         
 orderHash               0x4fce1fd1a979119fdc4dc09a199a3bc3284bfabde3c455b24d222110dad2425c        
 makerAssetData          0xf47261b0000000000000000000000000871dd7c2b4b25e1aa18728e9d5f2af4c4e431f5c
 takerAssetData          0xf47261b00000000000000000000000000b1ba0af832d7c05fd64161e0db78e85978e8082
 Transfer                                            
 contract  0x871dd7c2b4b25e1aa18728e9d5f2af4c4e431f5c
 _from     0x5409ed021d9299bf6814279a6a1411a7e866a631
 _to       0x6ecbe1db9ef729cbe972c83fb886247691fb6beb
 _value    5000000000000000000                       
 Transfer                                            
 contract  0x0b1ba0af832d7c05fd64161e0db78e85978e8082
 _from     0x6ecbe1db9ef729cbe972c83fb886247691fb6beb
 _to       0x5409ed021d9299bf6814279a6a1411a7e866a631
 _value    100000000000000000
Balances
 Token  Maker      Taker
 WETH   0.6        0    
 ZRX    999999975  25   
Done in 8.25s.

Conclusion

So, we have learned how we can create and fill orders using 0x protocol.

We are using ganache but we can submit and fill the order to a relayer using 0x/connect (which is a wrapper of 0x protocol’s Standard Relayer API) We will check this in future tutorials.

Next, we’ll discuss different order book strategies. If you have any doubt you can refer to 0x project docs. They have awesome documentation.

Please comment below, if you have more questions about building your own decentralize exchange or relayer.

Thanks!

Originally published:

November 9, 2018

Related Articles