Exact input mode

In this example, we will conduct price query and swap with exact input amount in iZiSwap (Amount Mode).

Here, quoter with exact input amount means pre-query amount of token acquired given exact input token amount. For example, if you want to swap 1 ETH to N USDC, the step is used to determine N.

swap with exact input amount means to invoke the real swap with the amount.

Quoter and swap are called throw 2 different contracts.

Suppose we want to swap USDT (testA) token to BUSD (testB) token, where these two tokens are standard ERC-20 tokens deployed on BSC. The full example codes can be found here.

1. Some imports

These are some base modules used in most examples.

1import Web3 from 'web3';
2import {privateKey} from '../../.secret'
3import { BigNumber } from 'bignumber.js'
4import {BaseChain, ChainId, initialChainTable} from 'iziswap-sdk/lib/base/types'
5import { amount2Decimal, fetchToken, getErc20TokenContract } from 'iziswap-sdk/lib/base/token/token';

And these are some imports for quoter and swap.

1import { SwapChainWithExactInputParams } from 'iziswap-sdk/lib/swap/types';
2import { QuoterSwapChainWithExactInputParams } from 'iziswap-sdk/lib/quoter/types';
3import { getSwapChainWithExactInputCall, getSwapContract } from 'iziswap-sdk/lib/swap/funcs';
4import { getQuoterContract, quoterSwapChainWithExactInput } from 'iziswap-sdk/lib/quoter/funcs';

Here quoterSwapChainWithExactInput will return amount of token acquired (N). getSwapChainWithExactInputCall will return calling for swapping with exact input.

2. Initialization

 1const chain:BaseChain = initialChainTable[ChainId.BSC]
 2const rpc = 'https://bsc-dataseed2.defibit.io/' //BSC network rpc node
 3const web3 = new Web3(new Web3.providers.HttpProvider(rpc))
 4const account =  web3.eth.accounts.privateKeyToAccount(YOUR_PRIVATE_KEY)  //Fill with your sk, dangerous, never to share
 5console.log('address: ', account.address)
 6
 7const testAAddress = '0x55d398326f99059ff775485246999027b3197955' // USDT
 8const testBAddress = '0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56' // BUSD
 9
10// TokenInfoFormatted of token 'USDT' and token 'BUSD'
11const testA = await fetchToken(testAAddress, chain, web3)
12const testB = await fetchToken(testBAddress, chain, web3)
13const fee = 400 // 400 means 0.04%

We are going to pay token testA to acquire token testB.

3. Use Quoter to pre-query amount of token testB acquired

First, we use getQuoterContract to instantiate a quoter contract.

1const quoterAddress = '0x64b005eD986ed5D6aeD7125F49e61083c46b8e02' // Quoter address on BSC, more can be found in the deployed contracts section.
2const quoterContract = getQuoterContract(quoterAddress, web3)

Second, use quoterSwapChainWithExactInput to query.

 1// swap 50 USDT -> BUSD
 2const amountA = new BigNumber(50).times(10 ** testA.decimal)
 3
 4const params = {
 5    // pay testA to buy testB
 6    tokenChain: [testA, testB],
 7    feeChain: [fee],
 8    inputAmount: amountA.toFixed(0)
 9} as QuoterSwapChainWithExactInputParams
10
11const {outputAmount} = await quoterSwapChainWithExactInput(quoterContract, params)
12
13const amountB = outputAmount
14const amountBDecimal = amount2Decimal(new BigNumber(amountB), testB)
15
16console.log(' amountA to pay: ', 50)
17console.log(' amountB to acquire: ', amountBDecimal)

In the above code, we are ready to pay 50 testA (USDT, decimal amount). We simply call function quoterSwapChainWithExactInput to get the acquired amount of token testB (BUSD). The function quoterSwapChainWithExactInput need 2 params:

    • quoterContract: obtained through getQuoterContract before

    • a QuoterSwapChainWithExactInputParams instance: describes information such as swap chains and input amount

The fields of QuoterSwapChainWithExactInputParams is explained in the following code.

 1export interface QuoterSwapChainWithExactInputParams {
 2
 3    // input: tokenChain.first()
 4    // output: tokenChain.last()
 5    tokenChain: TokenInfoFormatted[];
 6
 7    // feeChain[i] / 1e6 is feeTier
 8    // 3000 means 0.3%
 9    // (tokenChain[i], feeChain[i], tokenChain[i+1]) means i-th iZi-swap-pool in the swap chain
10    // in that pool, tokenChain[i] is the token payed to the pool, tokenChain[i+1] is the token acquired from the pool
11    // ofcourse, feeChain.length + 1 === tokenChain.length
12    feeChain: number[];
13
14    // 10-decimal format number, like 100, 150000, ...
15    // or hex format number start with '0x'
16    // amount = inputAmount / (10 ** inputToken.decimal)
17    inputAmount: string;
18}

iZiSwap’s quoter and swap contracts support swap chain with multi swap pools. For example, if you have some token0, and wants to get token3 through the path token0 -> (token0, token1, 0.05%) -> token1 -> (token1, token2, 0.3%) -> token2 -> (token2, token3, 0.3%) -> token3, you should fill the tokenChain and feeChain fields with following code

1// here, token0..3 are TokenInfoFormatted
2params.tokenChain = [token0, token1, token2, token3]
3params.feeChain = [500, 3000, 3000]

Now we have finished the Quoter part.

4. Use Swap to actually pay token testA to get token testB

First, we use getSwapContract to get the Swap contract

1const swapAddress = '0xBd3bd95529e0784aD973FD14928eEDF3678cfad8' // Swap contract on BSC
2const swapContract = getSwapContract(swapAddress, web3)

Second, use getSwapChainWithExactInputCall to get calling (transaction handler) of swap:

 1const swapParams = {
 2    ...params,
 3    // slippery is 1.5%
 4    // amountB is the pre-query result from Quoter
 5    minOutputAmount: new BigNumber(amountB).times(0.985).toFixed(0)
 6} as SwapChainWithExactInputParams
 7
 8const gasPrice = '3000000000' //BSC default gas price
 9
10const tokenA = testA
11const tokenB = testB
12const tokenAContract = getErc20TokenContract(tokenA.address, web3)
13const tokenBContract = getErc20TokenContract(tokenB.address, web3)
14
15const tokenABalanceBeforeSwap = await tokenAContract.methods.balanceOf(account.address).call()
16const tokenBBalanceBeforeSwap = await tokenBContract.methods.balanceOf(account.address).call()
17
18console.log('tokenABalanceBeforeSwap: ', tokenABalanceBeforeSwap)
19console.log('tokenBBalanceBeforeSwap: ', tokenBBalanceBeforeSwap)
20
21const {swapCalling, options} = getSwapChainWithExactInputCall(
22    swapContract,
23    account.address,
24    chain,
25    swapParams,
26    gasPrice
27)

In the above code, we ready to pay 50 testA (decimal amount). We simply call function getSwapChainWithExactInputCall to get acquired amount of token testB. The params needed by function getSwapChainWithExactInputCall can be viewed in the following code:

 1/**
 2 * @param swapContract, swap contract, can be obtained through getSwapContract(...)
 3 * @param account, address of user
 4 * @param chain, object of BaseChain, describe which chain we are using
 5 * @param params, some settings of this swap, including swapchain, input amount, min required output amount
 6 * @param gasPrice, gas price of this swap transaction
 7 * @return swapCalling, calling of this swap transaction
 8 * @return options, options of this swap transaction, used in sending transaction
 9 */
10export const getSwapChainWithExactInputCall = (
11    swapContract: Contract,
12    account: string,
13    chain: BaseChain,
14    params: SwapChainWithExactInputParams,
15    gasPrice: number | string
16) : { swapCalling: any, options: any }

SwapChainWithExactInputParams has following fields

 1export interface SwapChainWithExactInputParams {
 2
 3    // input: tokenChain.first()
 4    // output: tokenChain.last()
 5    tokenChain: TokenInfoFormatted[];
 6
 7    // feeChain[i] / 1e6 is feeTier
 8    // 3000 means 0.3%
 9    // (tokenChain[i], feeChain[i], tokenChain[i+1]) means i-th iZi-swap-pool in the swap chain
10    // in that pool, tokenChain[i] is the token payed to the pool, tokenChain[i+1] is the token acquired from the pool
11    // ofcourse, feeChain.length + 1 === tokenChain.length
12    feeChain: number[];
13
14    // 10-decimal format number, like 100, 150000, ...
15    // or hex format number start with '0x'
16    // amount = inputAmount / (10 ** inputToken.decimal)
17    inputAmount: string;
18
19    // if actual acquired amount < minOutputAmount, the transaction will be revert
20    minOutputAmount: string;
21
22    // who will get outputToken, default is payer
23    recipient?: string;
24
25    // latest timestamp to execute this swap transaction, default is 0xffffffff,
26    // etc max number of uint32, which is larger than latest unix-time
27    deadline?: string;
28
29    // default is false
30    // when the input or output token is wbnb or weth or other wrapped chain-token
31    // user wants to pay bnb/eth directly (send the transaction with value > 0) or acquire bnb/eth directly
32    // if this field is undefined or false, user will send the swap calling with value > 0 or acquire bnb/eth directly
33    // if this field is true, user will send the swap calling with value===0 and pay eth/bnb through weth/wbnb
34    //    like other erc-20 tokens or acquire weth/wbnb like other erc-20 tokens
35    strictERC20Token?: boolean;
36}

Usually, we can fill SwapChainWithExactInputParams through following code

1const swapParams = {
2    ...params,
3    // slippery is 1.5%, here amountB is value returned from quoter
4    minOutputAmount: new BigNumber(amountB).times(0.985).toFixed(0)
5} as SwapChainWithExactInputParams

Notice that in this example, both tokens are ERC-20 compatible tokens and is the general case. However, if tokenX or tokenY is chain gas token (such as ETH on Ethereum or BNB on BSD), we should specify one or some fields in swapParams to indicate sdk paying/acquiring in form of Chain Token or paying/acquiring in form of Wrapped Chain Token (such as WETH on Ethereum or WBNB on BSC).

In that case, take testA to be BNB as example.

If you want to use BNB directly, just set testAAddress to be WBNB and strictERC20Token is false by default.

1...
2
3const testAAddress = '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c' // WBNB
4
5...

And the BNB need to pay (the value field in the transaction data) is set in the options return.

If you want to use WBNB, first to set testAAddress to be WBNB and then to set strictERC20Token as true.

 1...
 2
 3const testAAddress = '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c' // WBNB
 4
 5...
 6
 7const swapParams = {
 8    ...
 9    strictERC20Token: true
10    ...
11} as SwapChainWithExactInputParams
12
13...

Now the swap will use WBNB instead of BNB.

5. Approve (skip if you pay chain token directly)

Before sending transaction or estimating gas, you need to approve contract Swap to have authority to spend your token. Since the contract need to transfer some tokenA or tokenB to the pool.

If the allowance is enough or the input token is chain gas token, just skip this step.

 1// the approve interface abi of erc20 token
 2const erc20ABI = [{
 3  "inputs": [
 4    {
 5      "internalType": "address",
 6      "name": "spender",
 7      "type": "address"
 8    },
 9    {
10      "internalType": "uint256",
11      "name": "amount",
12      "type": "uint256"
13    }
14  ],
15  "name": "approve",
16  "outputs": [
17    {
18      "internalType": "bool",
19      "name": "",
20      "type": "bool"
21    }
22  ],
23  "stateMutability": "nonpayable",
24  "type": "function"
25}];
26// if tokenA is not chain token (BNB on BSC or ETH on Ethereum...), we need transfer tokenA to pool
27// otherwise we can skip following codes
28{
29    const tokenAContract = new web3.eth.Contract(erc20ABI, testAAddress);
30    // you could approve a very large amount (much more greater than amount to transfer),
31    // and don't worry about that because swapContract only transfer your token to pool with amount you specified and your token is safe
32    // then you do not need to approve next time for this user's address
33    const approveCalling = tokenAContract.methods.approve(
34        swapAddress,
35        "0xffffffffffffffffffffffffffffffff"
36    );
37    // estimate gas
38    const gasLimit = await mintCalling.estimateGas({from: account})
39    // then send transaction to approve
40    // you could simply use followiing line if you use metamask in your frontend code
41    // otherwise, you should use the function "web3.eth.accounts.signTransaction"
42    // notice that, sending transaction for approve may fail if you have approved the token to swapContract before
43    // if you want to enlarge approve amount, you should refer to interface of erc20 token
44    await approveCalling.send({gas: gasLimit})
45}

6. Estimate gas (optional)

Before actually send the transaction, this is double check (or user experience enhancement measures) to check whether the gas spending is normal.

1const gasLimit = await swapCalling.estimateGas(options)
2console.log('gas limit: ', gasLimit)

7. Send transaction!

Now, we can then send the transaction.

For metamask or other explorer’s wallet provider, you can easily write

1await swapCalling.send({...options, gas: gasLimit})

Otherwise, you could use following code

 1// sign transaction
 2// options is returned from getSwapChainWithExactInputCall
 3const signedTx = await web3.eth.accounts.signTransaction(
 4    {
 5        ...options,
 6        to: swapAddress,
 7        data: swapCalling.encodeABI(),
 8        gas: new BigNumber(gasLimit * 1.1).toFixed(0, 2),
 9    },
10    privateKey
11)
12// send transaction
13const tx = await web3.eth.sendSignedTransaction(signedTx.rawTransaction);
14console.log('tx: ', tx);

After sending transaction, we will successfully finish swapping with exact amount of input token (if no revert occurred).