Skip to content

Latest commit

 

History

History
275 lines (233 loc) · 9.06 KB

V2AggregationSwap.md

File metadata and controls

275 lines (233 loc) · 9.06 KB

Documentation: V2 Aggregation Swap Examples

This section will give you a walkthrough of the configurations & process for creating and executing Zenlink aggregation swap v2.

full examples

Supported Networks/Chains

  • Moonbeam
  • Scroll
  • Base
  • Astar
  • Arbitrum One (Waiting for upgrade from v1)

Contracts

AggregationRouter

Network/Chain Contract Address
Moonbeam 0x603eF396029b5e89f9420b4192814aEC0664ADAb
Scroll 0xf5016C2DF297457a1f9b036990cc704306264B40
Base 0x7BAe21fB8408D534aDfeFcB46371c3576a1D5717
Astar 0x8f68eAA5DD8c43fdb9A236ed9C76DD6182D3060D

AggregationExecutor

Network/Chain Contract Address
Moonbeam 0x832B21FA3AA074Ee5328f653D9DB147Bcb155C7a
Scroll 0x4e231728d42565830157FFFaBBB9c78aD5152E94
Base 0x4e231728d42565830157FFFaBBB9c78aD5152E94
Astar 0x934AF6d0C4b6EaF259AcEEf3225827C3025B29c5

Guide

1. Configuration

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,
})

2. Define Your Swap Parameters

Next, define the parameters for the swap you want to perform.

Description of query parameters

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%

3 Define API URLs and Fetch Route Result

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')

Description of response parameters

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 sell
tokenOut (string) parameters of a token to buy
amountIn (string) input amount of tokenIn in minimal divisible units
amountOutMin (string) minimumal amount of a token to buy
to (string) receiver that transaction will be sent to
routeCode (string) bytes code required for the contract

4 Check Token Allowance

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!')
  }
}

5. Making the Swap

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