This section will give you a walkthrough of the configurations & process for creating and executing Zenlink aggregation swap v2.
- Moonbeam
- Scroll
- Base
- Astar
- Arbitrum One (Waiting for upgrade from v1)
Network/Chain | Contract Address |
---|---|
Moonbeam | 0x603eF396029b5e89f9420b4192814aEC0664ADAb |
Scroll | 0xf5016C2DF297457a1f9b036990cc704306264B40 |
Base | 0x7BAe21fB8408D534aDfeFcB46371c3576a1D5717 |
Astar | 0x8f68eAA5DD8c43fdb9A236ed9C76DD6182D3060D |
Network/Chain | Contract Address |
---|---|
Moonbeam | 0x832B21FA3AA074Ee5328f653D9DB147Bcb155C7a |
Scroll | 0x4e231728d42565830157FFFaBBB9c78aD5152E94 |
Base | 0x4e231728d42565830157FFFaBBB9c78aD5152E94 |
Astar | 0x934AF6d0C4b6EaF259AcEEf3225827C3025B29c5 |
First things first, we need to configure some required user-specific things (including initialize viem client. Viem is a typeScript interface for Ethereum):
- The chainConfig of an Ethereum node to connect to
- The private key of the account to trade
import { createPublicClient, createWalletClient, http } from 'viem'
import { privateKeyToAccount } from 'viem/accounts'
import { moonbeam } from 'viem/chains'
import type { Address } from 'viem'
const account = privateKeyToAccount(process.env.PRIVATE_KEY as Address)
const chainConfig = {
chain: moonbeam,
transport: http(moonbeam.rpcUrls.default.http[0]),
}
const publicClient = createPublicClient(chainConfig)
const walletClient = createWalletClient({
account,
...chainConfig,
})
Next, define the parameters for the swap you want to perform.
Parameter name | Type | Description |
---|---|---|
chainId | number (Required) | chainId of the network(parachainId or ethereumId) |
fromTokenId | string (Required) | contract address of a token to sell (or Native) |
toTokenId | string (Required) | contract address of a token to buy (or Native) |
amount | string (Required) | amount of a token to sell, set in minimal divisible units (eg: 51.03 USDC set as 51030000) |
gasPrice | number (Required) | zenlink takes in account gas expenses to determine exchange route |
priceImpact | number (Optional) | limit of price slippage you are willing to accept in percentage (eg: 0.01 set as 1%) |
to | number (Optional) | receiver address (note that the routeParams will not be returned if it is not provided) |
const chainId = 2004 // can also use moonbeam ethereum chainId 1284
const fromTokenId = 'Native' // can also use '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE' as Native address
const toTokenId = '0x931715FEE2d06333043d11F658C8CE934aC61D0c' // USDC.wh address
const amount = BigInt(10000e18).toString() // 10000 GLMR
const gasPrice = await publicClient.getGasPrice()
const priceImpact = 0.01 // 1%
Now, define the API URLs and fetch route result including simple description, contract address and contract call parameters.
const {
routeHumanString,
routeParams,
routerAddress,
executorAddress,
} = await fetch(
`https://path-finder.zenlink.pro/v2?chainId=${
chainId
}&fromTokenId=${
fromTokenId
}&toTokenId=${
toTokenId
}&amount=${
amount
}&gasPrice=${
Number(gasPrice)
}&priceImpact=${
priceImpact
}&to=${account.address}`,
).then(res => res.json())
console.log(`Route Description: \n${routeHumanString}`)
const { tokenIn, amountIn, tokenOut, amountOutMin, to, routeCode, value } = routeParams
if (!routerAddress || !executorAddress)
console.error('Contract address not found')
Parameter name | Type | Description |
---|---|---|
routeHumanString | string | describe the entire route in human-readable form |
routerAddress | string | contract address of router |
executorAddress | String | contract address of executor |
routeParams | object | tokenIn (string) parameters of a token to selltokenOut (string) parameters of a token to buyamountIn (string) input amount of tokenIn in minimal divisible unitsamountOutMin (string) minimumal amount of a token to buyto (string) receiver that transaction will be sent torouteCode (string) bytes code required for the contract |
If srcToken(fromToken) is not Native currency of network(like GLMR), we need to check the allowance and create the token allowance (approval) transaction.
// check erc20 allowance
if (tokenIn !== '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE') {
const allowance = await publicClient.readContract({
address: tokenIn,
abi: erc20ABI,
functionName: 'allowance',
args: [account.address, routerAddress],
})
if (allowance < BigInt(amountIn)) {
const { request } = await publicClient.simulateContract({
account,
address: tokenIn,
abi: erc20ABI,
functionName: 'approve',
args: [routerAddress, BigInt(amountIn)],
})
const hash = await walletClient.writeContract(request)
console.log('Approving Aggregation Router: ', hash)
await publicClient.waitForTransactionReceipt({ hash })
console.log('Approved!')
}
}
Before proceeding, please confirm that your approval transaction has a status of Success!
AggregationRouterV2ABI
const aggregationRouterV2ABI = [
{
inputs: [
{
internalType: 'contract IAggregationExecutor',
name: 'executor',
type: 'address',
},
{
components: [
{
internalType: 'Currency',
name: 'srcToken',
type: 'address',
},
{
internalType: 'Currency',
name: 'dstToken',
type: 'address',
},
{
internalType: 'address',
name: 'dstReceiver',
type: 'address',
},
{
internalType: 'uint256',
name: 'amount',
type: 'uint256',
},
{
internalType: 'uint256',
name: 'minReturnAmount',
type: 'uint256',
},
],
internalType: 'struct AggregationRouter.SwapDescription',
name: 'desc',
type: 'tuple',
},
{
internalType: 'bytes',
name: 'route',
type: 'bytes',
},
],
name: 'swap',
outputs: [
{
internalType: 'uint256',
name: 'returnAmount',
type: 'uint256',
},
{
internalType: 'uint256',
name: 'spentAmount',
type: 'uint256',
},
],
stateMutability: 'payable',
type: 'function',
},
] as const
const {
result: [returnAmount, spentAmount],
request,
} = await publicClient.simulateContract({
address: routerAddress,
abi: aggregationRouterV2ABI,
functionName: 'swap',
account,
args: [
executorAddress,
{
srcToken: tokenIn,
dstToken: tokenOut,
amount: BigInt(amountIn),
dstReceiver: to,
minReturnAmount: BigInt(amountOutMin),
},
routeCode,
],
value: BigInt(value || '0'),
})
// check simulate result
if (returnAmount >= BigInt(amountOutMin) && spentAmount === BigInt(amount)) {
console.log('Simulate Completed!')
const hash = await walletClient.writeContract(request)
console.log('Transaction Signed and Sent: ', hash)
await publicClient.waitForTransactionReceipt({ hash })
console.log('Transaction Completed, Swap tx hash: ', hash)
}
else {
console.log('Simulate failed, please try again!')
}
After running this code in the console, you should see something like this
Transaction Completed, Swap tx hash: 0xe90955ddec325e26f274f6467e92bbd43753173a5bac877b30e0d835055f9d02
Let's check the result of the transaction on the explorer: https://moonbeam.moonscan.io/tx/0xe90955ddec325e26f274f6467e92bbd43753173a5bac877b30e0d835055f9d02