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.
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.
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.
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.
// DISCLAIMER: Code snippets in this guide are just examples and you// should always do your own testing. If you have questions, visit our// https://t.me/KyberDeveloper.let maxGasPrice =awaitKyberNetworkProxyContract.maxGasPrice();
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.
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).
// Connecting to a providerconstNETWORK='ropsten';constPROJECT_ID='INFURA_PROJECT_ID'; // Replace this with your own Project IDconstprovider=newethers.getDefaultProvider(NETWORK, { infura:PROJECT_ID });
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.
// User DetailsconstPRIVATE_KEY='PRIVATE_KEY'; // Eg. 0x40ddbce3c7df9ab8d507d6b4af3861d224711b35299470ab7a217f780fe696cdconstUSER_WALLET=newethers.Wallet(PRIVATE_KEY, provider);
Note that we fix USER_WALLET as the sender of any transactions made here to these contracts.
// Instantiate contracts, using USER_WALLET as sender of txnsconstKyberNetworkProxyContract=newethers.Contract( IKyberNetworkProxy_ADDRESS, IKyberNetworkProxy_ABI,USER_WALLET,);constsrcTokenContract=newethers.Contract(SRC_TOKEN_ADDRESS,IERC20_ABI,USER_WALLET,);
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.
asyncfunctioncheckAndApproveTokenForTrade( srcTokenContract, userAddress, srcQty,) {if (srcTokenContract.address ==ETH_ADDRESS) {return; }// check existing allowance given to proxy contractlet existingAllowance =awaitsrcTokenContract.allowance( userAddress, IKyberNetworkProxy_ADDRESS, );// if zero allowance, just set to MAX_UINT256if (existingAllowance.eq(ZERO_BN)) {console.log('Approving KNP contract to max allowance');awaitsrcTokenContract.approve(IKyberNetworkProxy_ADDRESS,MAX_UINT256); } elseif (existingAllowance.lt(srcQty)) {// if existing allowance is insufficient, reset to zero, then set to MAX_UINT256console.log('Approving KNP contract to zero, then max allowance');awaitsrcTokenContract.approve(IKyberNetworkProxy_ADDRESS,ZERO_BN);awaitsrcTokenContract.approve(IKyberNetworkProxy_ADDRESS,MAX_UINT256); }return;}
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).
let minConversionRate =awaitKyberNetworkProxyContract.getExpectedRateAfterFee(SRC_TOKEN_ADDRESS,DEST_TOKEN_ADDRESS,SRC_QTY,PLATFORM_FEE, hint,);
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.
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.
let ethValue =SRC_TOKEN_ADDRESS==ETH_ADDRESS?SRC_QTY:ZERO_BN;awaitKyberNetworkProxyContract.tradeWithHintAndFee(SRC_TOKEN_ADDRESS,SRC_QTY,DEST_TOKEN_ADDRESS,USER_WALLET.address,// destAddressMAX_UINT256,// maxDestAmount: set to be arbitrarily large minConversionRate,PLATFORM_WALLET,PLATFORM_FEE, hint, { value: ethValue, gasLimit:gasConfig.gasLimit, gasPrice:gasConfig.gasPrice, },);
The main function will combine the different functions together to obtain the conversion rate and execute the trade.
asyncfunctionmain() {// Step 1: Check and approve allowance if neededawaitcheckAndApproveTokenForTrade( srcTokenContract,USER_WALLET.address,SRC_QTY, );let hint =EMPTY_HINT; // build hint here (see section on reserve routing)// Step 2: Get rate for tradelet minConversionRate =awaitKyberNetworkProxyContract.getExpectedRateAfterFee(SRC_TOKEN_ADDRESS,DEST_TOKEN_ADDRESS,SRC_QTY,PLATFORM_FEE, hint, );// Step 3: Get gas limit estimates and pricelet gasConfig =awaitgetGasConfig( KyberNetworkProxyContract, provider,SRC_TOKEN_ADDRESS,DEST_TOKEN_ADDRESS,SRC_QTY,USER_WALLET.address,MAX_UINT256, minConversionRate,PLATFORM_WALLET,PLATFORM_FEE, hint, );// Step 4: Execute tradelet ethValue =SRC_TOKEN_ADDRESS==ETH_ADDRESS?SRC_QTY:ZERO_BN;console.log('Executing Trade...');awaitKyberNetworkProxyContract.tradeWithHintAndFee(SRC_TOKEN_ADDRESS,SRC_QTY,DEST_TOKEN_ADDRESS,USER_WALLET.address,// destAddressMAX_UINT256,// maxDestAmount: set to be arbitrarily large minConversionRate,PLATFORM_WALLET,PLATFORM_FEE, hint, { value: ethValue, gasLimit:gasConfig.gasLimit, gasPrice:gasConfig.gasPrice, }, );// Quit the programprocess.exit(0);}