Ethers JS

Introduction

This guide will walk you through on how you can interact with our protocol implementation using the ethers.js library. You may also use the Web3 library, with some syntax changes. The most common group of users that can benefit from this guide are wallets or vendors who want to use their own UI.

Risk Mitigation

There are some risks when utilising Kyber. To safeguard users, we kindly ask that you refer to the Slippage Rates Protection and Price Feed Security sections on what these risks are, and how to mitigate them.

Overview

We break this guide into 2 sections:

  1. Trading Tokens - The section covers what contract interfaces to import, and functions to call to fetch rates and perform a simple trade.

  2. Reserve Routing - This section covers the reserve routing feature to include / exclude reserves, or to split trades amongst multiple reserves.

Things to note

  1. If the source token is not ETH (ie. an ERC20 token), the user is required to first call the ERC20 approve function to give an allowance to the kyber proxy contract.

  2. To prevent front running, the contract limits the gas price trade transactions can have. The transaction will be reverted if the limit is exceeded. To query for the maximum gas limit, check the public variable maxGasPrice.

Trading Tokens

Suppose we want to convert 100 KNC to DAI tokens on Ropsten, which is a token to token conversion. In addition, we want to charge a platform fee of 0.25%. Note that ETH is used as the base pair i.e. KNC -> ETH -> DAI.

The code example will also work for token -> ether and ether -> token conversions.

Import Relevant Packages

  • We use ethers for connecting to the Ethereum blockchain

  • ethers includes a BN utils library for BigNumber variables, which we shall also instantiate for convenience

  • The node-fetch module is used for making API queries

Connect to an Ethereum Node

ethers provides a very simple method getDefaultProvider to easily connect to the Ethereum blockchain. While not necessary, it is recommended to provide an API key for the various providers offered (Eg. Alchemy, Infura and Etherscan).

Define Constants and Trade Details

Next, we will define the constants that we will be using for this scenario.

Universal Constants

  • ETH_ADDRESS used by Kyber to represent Ether

  • ZERO_BN: BigNumber instance of 0

  • MAX_UINT256: BigNumber instance of 2**256 - 1

  • EMPTY_HINT: The hint parameter is used for [reserve routing]. In this case, an empty hint would be 0x.

Tokens and Source Quantity

We define the source and destination tokens, as well as the source quantity to be used for the trade.

Contract ABIs and Proxy Address

The following ABIs are imported for these functionalities:

Sender

Replace the PRIVATE_KEY with a private key (including the 0x prefix) of the sender of the transaction. There are other import methods that ethers.js support, such as reading from Metamask, and mnemonic phrases.

Platform Wallet and Fees

Find out more about platform fees here.

Instantiate Contracts

Note that we fix USER_WALLET as the sender of any transactions made here to these contracts.

Core Steps For Trade Execution

Now that we have defined the trade details, we break down the process into a number of steps.

Step 1: Checking Token Allowance

We first check if there is sufficient allowance given to the proxy contract for the trade. Should it be insufficient, we will call the approve function of the source token contract.

Step 2: Hint

For simple trades, we can simply set the hint as the EMPTY_HINT. Otherwise, we can build hints to specify reserve routes.

Step 3: Fetching Rates

Next, we fetch the expected rate for the trade, which we can set as the minimum conversion rate. Should the actual rate fall below this, the trade will revert. You may choose to add a buffer (reduce the fetched expected rate by some percentage).

Understanding the rate

Divide the rate by 10**18 to get a 'readable' rate. For example, if the rate returned from a query of 1 WBTC -> KNC is 7980824281140923034320, then 1 WBTC can be exchanged for 7980824281140923034320 / 1e18 ~= 7980.824 KNC.

Step 3: Gas Configurations

We next define the gas limit and price to be used for the trade. There are a number of ways to go about this. We give 2 possible methods to determine each parameter, but this is definitely customisable to suit your needs.

Step 4: Executing Trade

We can finally make a call to execute the trade.

Tying Everything Together

The main function will combine the different functions together to obtain the conversion rate and execute the trade.

Full code example

Before running this code example, the following fields need to be modified:

  1. Change INFURA_PROJECT_ID to your Infura Project ID.

  2. Change PRIVATE_KEY to the private key (with 0x prefix) of the Ethereum wallet holding Ether.

  3. Change PLATFORM_WALLET to a wallet address for platform fees.

Reserve Routing

Overview

In previous network versions, the hint parameter was used to filter permissionless reserves. With Katalyst, we utilise this parameter for routing trades to specific reserves.

There are 4 optional routing rules:

  1. BestOfAll - This is the default routing rule when no hint is provided, and is the classic reserve matching algorithm used by the Kyber smart contracts since the beginning.

  2. MaskIn (Whitelist) - Specify a list of reserves to be included and perform the BestOfAll routing on them

  3. MaskOut (Blacklist) - Specify a list of reserves to be excluded and perform the BestOfAll routing on the remaining reserves

  4. Split - Specify a list of reserves and their respective percentages of the total srcQty that will be routed to each reserve.

For token -> token trades, you can specify a routing rule for each half. For example, a MaskIn route can be used for the token -> ether side, while a Split route can be used for the ether -> token side.

Fetching Reserve IDs

The contract to interact with for this functionality here is the KyberStorage contract.

Define Contract ABI and Address

We import the IKyberStorage_ABI to fetch reserve IDs, and define the kyberStorage address.

Methods

For the token -> ether side of the trade, call getReserveIdsPerTokenSrc of the kyberStorage contract. For the ether -> token side of the trade, call getReserveIdsPerTokenDest of the kyberStorage contract.

Examples

Get reserve IDs for WBTC -> ETH.

Get reserve IDs for ETH -> KNC.

Building Hints

The contract to interact with for this functionality here is the KyberHintHandler contract, which is inherited by the KyberMatchingEngine contract.

  • For token -> ether trades, call the buildTokenToEthHint function

  • For ether -> token trades, call the buildTokenToEthHint function

  • For token -> token trades, call the buildTokenToTokenHint function

Their input parameters are explained below:

Parameter
Type
Description

tokenSrc

IERC20

source ERC20 token contract address

tokenToEthType

uint256

0 = BestOfAll, 1 = MaskIn, 2 = MaskOut, 3 = Split

tokenToEthReserveIds

bytes32[]

list of reserve IDs for token -> ether trade

tokenToEthSplits

uint256[]

percentages (in basis points) for Split trade

tokenDest

IERC20

source ERC20 token contract address

ethToTokenType

uint256

0 = BestOfAll, 1 = MaskIn, 2 = MaskOut, 3 = Split

ethToTokenReserveIds

bytes32[]

list of reserve IDs for token -> ether trade

ethToTokenSplits

uint256[]

percentages (in basis points) for Split trade

Notes

  • The correct builder hint function must be used for the correct trade type. Otherwise, the hint will not be built correctly, and will result in transaction failure.

  • For token -> token trades, a combination of TradeTypes are allowed. For example, the token -> eth trade can be BestOfAll, while the eth -> token trade can be Split.

Define Contract ABI and Address

We import the IKyberHint_ABI to fetch reserve IDs, and define the kyberHintHandler address.

MaskIn TradeType

Note that the splits parameter must be empty, ie. [].

Example

Select the first reserve for a WBTC -> ETH trade.

MaskOut TradeType

Note that the splits parameter must be empty, ie. [].

Example

Exclude the first reserve from a WBTC -> ETH trade.

Split TradeType

Note that the splits values must add up to 10000 (100%).

Example

Split evenly among 2 reserves for a ETH -> KNC trade.

BestOfAll TradeType

  • The BestOfAll TradeType is primarily for specifying the BestOfAll behaviour for one side of token -> token trades. It is not needed for token -> ether and ether -> token trades.

  • The reserveIds and splits parameters must be empty.

Example

For a WBTC -> KNC trade, do a MaskIn route for WBTC -> ETH, and BestOfAll route for ETH -> KNC.

Full Code Example

Last updated

Was this helpful?