Exact output mode

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

Here, quoter with exact output amount means pre-query amount of token need to pay given exact amount of desired token. For example, if you want to swap N ETH to 2000 USDC, the step is used to determine N.

swap with exact output amount means to invoke the real swap with the desired 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 code of this chapter can be found here.

1. Some imports

These are some base modules used in most examples.

1import Web3 from 'web3';
2import { BigNumber } from 'bignumber.js'
3import {privateKey} from '../../.secret'
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 { getQuoterContract, quoterSwapChainWithExactOutput } from 'iziswap-sdk/lib/quoter/funcs';
2import { QuoterSwapChainWithExactOutputParams } from 'iziswap-sdk/lib/quoter/types';
3import { getSwapChainWithExactOutputCall, getSwapContract } from 'iziswap-sdk/lib/swap/funcs';
4import { SwapChainWithExactOutputParams } from 'iziswap-sdk/lib/swap/types';

Here quoterSwapChainWithExactOutput will return the amount of input token needed to pay (N), and getSwapChainWithExactOutputCall will return calling for swapping with exact amount of output (or we say desired) token.

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) // replace privateKey to your sk
 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 take example of paying token testA to acquire token testB

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

First, we use getQuoterContract to get the Quoter contract

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

Then, use quoterSwapChainWithExactOutput to query

 1const amountB = new BigNumber(10).times(10 ** testA.decimal)
 2
 3const params = {
 4    // pay testA to buy testB
 5    tokenChain: [testA, testB],
 6    feeChain: [fee],
 7    outputAmount: amountB.toFixed(0)
 8} as QuoterSwapChainWithExactOutputParams
 9
10const {inputAmount} = await quoterSwapChainWithExactOutput(quoterContract, params)
11
12const amountA = inputAmount
13const amountADecimal = amount2Decimal(new BigNumber(amountA), testA)
14
15console.log(' amountB to desired: ', 10)
16console.log(' amountA to pay: ', amountADecimal)

In the above code, we ready to buy 10 testB (decimal amount). We simply call function quoterSwapChainWithExactOutput to get acquired amount of token testB. The function quoterSwapChainWithExactOutput need 2 params:

    • quoterContract: obtained through getQuoterContract before

    • a QuoterSwapChainWithExactOuptutParams instance: describes information such as swap chains and output amount

The fields of QuoterSwapChainWithExactOutputParams is explained in the following code.

 1export interface QuoterSwapChainWithExactOutputParams {
 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 = outputAmount / (10 ** outputToken.decimal)
17    outputAmount: 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 getSwapChainWithExactOutputCall to get calling of swap

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

In the above code, we ready to buy 10 testB (decimal amount). We simply call function getSwapChainWithExactOutputCall to get acquired amount of token testA. The params needed by function getSwapChainWithExactOutputCall 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, output amount, max input 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 getSwapChainWithExactOutputCall = (
11    swapContract: Contract,
12    account: string,
13    chain: BaseChain,
14    params: SwapChainWithExactOutputParams,
15    gasPrice: number | string
16) : {swapCalling: any, options: any}

SwapChainWithExactOutputParams has following fields

 1export interface SwapChainWithExactOutputParams {
 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 = outputAmount / (10 ** outputToken.decimal)
17    outputAmount: string;
18    // if actual amount of input token > maxInputAmount, the transaction will be reverted
19    maxInputAmount: string;
20
21    // who will get outputToken, default is payer
22    recipient?: string;
23
24    // latest timestamp to execute this swap transaction, default is 0xffffffff,
25    // etc max number of uint32, which is larger than latest unix-time
26    deadline?: string;
27
28    // default is false
29    // when the input or output token is wbnb or weth or other wrapped chain-token
30    // user wants to pay bnb/eth directly (send the transaction with value > 0) or acquire bnb/eth directly
31    // if this field is undefined or false, user will send the swap calling with value > 0 or acquire bnb/eth directly
32    // if this field is true, user will send the swap calling with value===0 and pay eth/bnb through weth/wbnb
33    //    like other erc-20 tokens or acquire weth/wbnb like other erc-20 tokens
34    strictERC20Token?: boolean;
35}

Usually, we can fill SwapChainWithExactOutputParams through following code

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

Again, if one of the tokens is the chain gas token (e.g., ETH on Ethereum), please refer to the previous section to check how to set the strictERC20Token param.

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

before send transaction or estimate gas, you need to approve contract liquidityManager to have authority to spend yuor token, because you need transfer some tokenA and some tokenB to pool.

 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 chain or ETH on eth chain...), 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)

of course you can skip this step if you donot want to limit gas

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

7. send transaction!

now, we can then send transaction to swap

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 do swapping with exact amount of desired(or we say output) token (if no revert occured)