Skip to content

Latest commit

 

History

History
267 lines (225 loc) · 8.69 KB

File metadata and controls

267 lines (225 loc) · 8.69 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
  • Astar (Waiting for upgrade from v1)
  • Arbitrum One (Waiting for upgrade from v1)

Contracts

AggregationRouter

Network/Chain Contract Address
Moonbeam 0x3494764d3bE100BA489c8BC5C3438E7629c5e5E5

AggregationExecutor

Network/Chain Contract Address
Moonbeam 0x832B21FA3AA074Ee5328f653D9DB147Bcb155C7a

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