stargate

 avatar
Mike112
typescript
a year ago
19 kB
4
Indexable
Never
import { ethers } from 'ethers';

const BlockchainData = {
    "Ethereum": { chainID: 101, rpcProvider: 'https://eth.llamarpc.com', stargateRouter: "0x8731d54E9D02c286767d56ac03e8037C07e01e98", routerETH: "0x150f94B44927F078737562f0fcF3C95c01Cc2376"},
    "BnbChain": { chainID: 102, rpcProvider: 'https://bsc-dataseed.binance.org', stargateRouter: "0x4a364f8c717cAAD9A442737Eb7b8A55cc6cf18D8", routerETH: ""},
    "Avalanche": { chainID: 106, rpcProvider: 'https://avalanche.public-rpc.com', stargateRouter: "0x45A01E4e04F14f7A4a6702c74187c5F6222033cd", routerETH: ""},
    "Polygon": { chainID: 109, rpcProvider: 'https://polygon-rpc.com/', stargateRouter: "0x45A01E4e04F14f7A4a6702c74187c5F6222033cd", routerETH: ""},
    "Arbitrum": { chainID: 110, rpcProvider: 'https://arbitrum-one.public.blastapi.io', stargateRouter: "0x53Bf833A5d6c4ddA888F69c22C88C9f356a41614", routerETH: "0xbf22f0f184bCcbeA268dF387a49fF5238dD23E40"},
    "Optimism": { chainID: 111, rpcProvider: 'https://mainnet.optimism.io', stargateRouter: "0xB0D502E938ed5f4df2E681fE6E419ff29631d62b", routerETH: "0xB49c4e680174E331CB0A7fF3Ab58afC9738d5F8b"},
    "Fantom": { chainID: 112, rpcProvider: 'https://rpc.ftm.tools', stargateRouter: "0xAf5191B0De278C7286d6C7CC6ab6BB8A73bA2Cd6", routerETH: ""},
    "Metis": { chainID: 151, rpcProvider: 'https://andromeda.metis.io/?owner=1088', stargateRouter: "0x2F6F07CDcf3588944Bf4C42aC74ff24bF56e7590", routerETH: ""},
    "Base": { chainID: 184, rpcProvider: 'https://mainnet.base.org', stargateRouter: "0x45f1A95A4D3f3836523F5c83673c797f4d4d263B", routerETH: "0x50B6EbC2103BFEc165949CC946d739d5650d7ae4"},
    "Linea": { chainID: 183, rpcProvider: 'https://rpc.linea.build', stargateRouter: "0x2F6F07CDcf3588944Bf4C42aC74ff24bF56e7590", routerETH: "0x8731d54E9D02c286767d56ac03e8037C07e01e98"},
    "Kava": { chainID: 177, rpcProvider: 'https://evm.kava.io', stargateRouter: "0x2F6F07CDcf3588944Bf4C42aC74ff24bF56e7590", routerETH: ""},
}

// Pool IDs

const poolIDs = {
    "USDC": 1,
    "USDT": 2,
    "DAI": 3,
    "FRAX": 7,
    "USDD": 11,
    "ETH": 13,
    "sUSD": 14,
    "LUSD": 15,
    "MAI": 16,
    "METIS": 17,
    "metis.USDT": 19
}

// ABI for ERC20
const erc20ABI = [
    {
        "constant": true,
        "inputs": [
          { "name": "_owner", "type": "address" },
          { "name": "_spender", "type": "address" }
        ],
        "name": "allowance",
        "outputs": [{ "name": "", "type": "uint256" }],
        "type": "function"
    },
    {
        "constant": false,
        "inputs": [
            { "name": "_spender", "type": "address" },
            { "name": "_value", "type": "uint256" }
        ],
        "name": "approve",
        "outputs": [{ "name": "", "type": "bool" }],
        "type": "function"
    }
];

const stargateABI = [
    {"inputs":[{"internalType":"string","name":"_name","type":"string"},{"internalType":"string","name":"_symbol","type":"string"},{"internalType":"address","name":"_endpoint","type":"address"},{"internalType":"uint16","name":"_mainEndpointId","type":"uint16"},{"internalType":"uint256","name":"_initialSupplyOnMainEndpoint","type":"uint256"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bool","name":"isPaused","type":"bool"}],"name":"Paused","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint16","name":"srcChainId","type":"uint16"},{"indexed":false,"internalType":"uint64","name":"nonce","type":"uint64"},{"indexed":false,"internalType":"uint256","name":"qty","type":"uint256"}],"name":"ReceiveFromChain","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint16","name":"dstChainId","type":"uint16"},{"indexed":false,"internalType":"bytes","name":"to","type":"bytes"},{"indexed":false,"internalType":"uint256","name":"qty","type":"uint256"}],"name":"SendToChain","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"chainId","outputs":[{"internalType":"uint16","name":"","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"subtractedValue","type":"uint256"}],"name":"decreaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint16","name":"","type":"uint16"}],"name":"dstContractLookup","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"endpoint","outputs":[{"internalType":"contract ILayerZeroEndpoint","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint16","name":"_dstChainId","type":"uint16"},{"internalType":"bool","name":"_useZro","type":"bool"},{"internalType":"bytes","name":"txParameters","type":"bytes"}],"name":"estimateSendTokensFee","outputs":[{"internalType":"uint256","name":"nativeFee","type":"uint256"},{"internalType":"uint256","name":"zroFee","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint16","name":"_srcChainId","type":"uint16"},{"internalType":"bytes","name":"_srcAddress","type":"bytes"}],"name":"forceResumeReceive","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"addedValue","type":"uint256"}],"name":"increaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"isMain","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint16","name":"_srcChainId","type":"uint16"},{"internalType":"bytes","name":"_fromAddress","type":"bytes"},{"internalType":"uint64","name":"nonce","type":"uint64"},{"internalType":"bytes","name":"_payload","type":"bytes"}],"name":"lzReceive","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"_pause","type":"bool"}],"name":"pauseSendTokens","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"paused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint16","name":"_dstChainId","type":"uint16"},{"internalType":"bytes","name":"_to","type":"bytes"},{"internalType":"uint256","name":"_qty","type":"uint256"},{"internalType":"address","name":"zroPaymentAddress","type":"address"},{"internalType":"bytes","name":"adapterParam","type":"bytes"}],"name":"sendTokens","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint16","name":"_version","type":"uint16"},{"internalType":"uint16","name":"_chainId","type":"uint16"},{"internalType":"uint256","name":"_configType","type":"uint256"},{"internalType":"bytes","name":"_config","type":"bytes"}],"name":"setConfig","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint16","name":"_dstChainId","type":"uint16"},{"internalType":"bytes","name":"_destinationContractAddress","type":"bytes"}],"name":"setDestination","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint16","name":"version","type":"uint16"}],"name":"setReceiveVersion","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint16","name":"version","type":"uint16"}],"name":"setSendVersion","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"}
];

const routerABI = [
    {
        "inputs": [
          {
            "internalType": "uint16",
            "name": "_dstChainId",
            "type": "uint16"
          },
          {
            "internalType": "uint8",
            "name": "_functionType",
            "type": "uint8"
          },
          {
            "internalType": "bytes",
            "name": "_toAddress",
            "type": "bytes"
          },
          {
            "internalType": "bytes",
            "name": "_transferAndCallPayload",
            "type": "bytes"
          },
          {
            "components": [
              {
                "internalType": "uint256",
                "name": "dstGasForCall",
                "type": "uint256"
              },
              {
                "internalType": "uint256",
                "name": "dstNativeAmount",
                "type": "uint256"
              },
              {
                "internalType": "bytes",
                "name": "dstNativeAddr",
                "type": "bytes"
              }
            ],
            "internalType": "struct IStargateRouter.lzTxObj",
            "name": "_lzTxParams",
            "type": "tuple"
          }
        ],
        "name": "quoteLayerZeroFee",
        "outputs": [
          {
            "internalType": "uint256",
            "name": "",
            "type": "uint256"
          },
          {
            "internalType": "uint256",
            "name": "",
            "type": "uint256"
          }
        ],
        "stateMutability": "view",
        "type": "function"
    },
    {
        "inputs": [
          {
            "internalType": "uint16",
            "name": "_dstChainId",
            "type": "uint16"
          },
          {
            "internalType": "uint256",
            "name": "_srcPoolId",
            "type": "uint256"
          },
          {
            "internalType": "uint256",
            "name": "_dstPoolId",
            "type": "uint256"
          },
          {
            "internalType": "address payable",
            "name": "_refundAddress",
            "type": "address"
          },
          {
            "internalType": "uint256",
            "name": "_amountLD",
            "type": "uint256"
          },
          {
            "internalType": "uint256",
            "name": "_minAmountLD",
            "type": "uint256"
          },
          {
            "components": [
              {
                "internalType": "uint256",
                "name": "dstGasForCall",
                "type": "uint256"
              },
              {
                "internalType": "uint256",
                "name": "dstNativeAmount",
                "type": "uint256"
              },
              {
                "internalType": "bytes",
                "name": "dstNativeAddr",
                "type": "bytes"
              }
            ],
            "internalType": "struct IStargateRouter.lzTxObj",
            "name": "_lzTxParams",
            "type": "tuple"
          },
          {
            "internalType": "bytes",
            "name": "_to",
            "type": "bytes"
          },
          {
            "internalType": "bytes",
            "name": "_payload",
            "type": "bytes"
          }
        ],
        "name": "swap",
        "outputs": [],
        "stateMutability": "payable",
        "type": "function"
    },
];

const routerETHABI = [
    {"inputs":[{"internalType":"uint16","name":"_dstChainId","type":"uint16"},{"internalType":"address payable","name":"_refundAddress","type":"address"},{"internalType":"bytes","name":"_toAddress","type":"bytes"},{"internalType":"uint256","name":"_amountLD","type":"uint256"},{"internalType":"uint256","name":"_minAmountLD","type":"uint256"}],"name":"swapETH","outputs":[],"stateMutability":"payable","type":"function"},{"stateMutability":"payable","type":"receive"}
]

async function swapOnStargate(
    privateKey: string, 
    fromNetwork: string,  
    toNetwork: string, 
    tokenName: string,
    tokenAddress: string, // from token address
    amount: string,
    recipient: string 
) {
    let router : ethers.Contract;
    const fromNetworkId = BlockchainData[fromNetwork as keyof typeof BlockchainData].chainID;
    const toNetworkId = BlockchainData[toNetwork as keyof typeof BlockchainData].chainID;

    const provider = new ethers.providers.JsonRpcProvider(BlockchainData[fromNetwork as keyof typeof BlockchainData].rpcProvider);
    const providerFeeData = await provider.getFeeData();
    console.log('Fee Data: ', providerFeeData)

    const signer = new ethers.Wallet(privateKey, provider)
    
    if(tokenName === "STG") {
        const tokenContract = new ethers.Contract(tokenAddress, stargateABI, signer);
        // Check if we have enough allowance for Router contract
        // Approving spend if needed
        const allowance = await tokenContract.allowance(signer.address, BlockchainData[fromNetwork as keyof typeof BlockchainData].stargateRouter)

        if (allowance.lt(amount)) {
            await tokenContract.approve(BlockchainData[fromNetwork as keyof typeof BlockchainData].stargateRouter, amount, {
                gasPrice: providerFeeData.gasPrice,
            })
        }

        const feeData = await tokenContract.estimateSendTokensFee(
            toNetworkId,
            false,
            "0x"
        );

        const tx = await tokenContract.sendTokens(
            toNetworkId,
            recipient,
            amount,
            0,
            "0x",
            {
                value: feeData[0],
                gasLimit: 600000,
                gasPrice: providerFeeData.gasPrice,
            }
        );

        await tx.wait();

    } else if(tokenName !== "ETH") {
        // Check if we have enough balance for specified amount for swap
        const tokenFromContract = new ethers.Contract(tokenAddress, erc20ABI, signer);
        // Check if we have enough allowance for Router contract
        // Approving spend if needed
        const allowance = await tokenFromContract.allowance(signer.address, BlockchainData[fromNetwork as keyof typeof BlockchainData].stargateRouter)

        if (allowance.lt(amount)) {
            await tokenFromContract.approve(BlockchainData[fromNetwork as keyof typeof BlockchainData].stargateRouter, amount, {
                gasPrice: providerFeeData.gasPrice,
            })
        }
        // stargate router
        router = new ethers.Contract(BlockchainData[fromNetwork as keyof typeof BlockchainData].stargateRouter, routerABI, signer);

        // We initialize router on fromNetwork, as we will specify destination network during swap
        const quoteData = await router.quoteLayerZeroFee(
            toNetworkId,
            1,
            signer.address,
            "0x",
            ({
                dstGasForCall: 0,
                dstNativeAmount: 0,
                dstNativeAddr: "0x"
            })
        );

        const feeWei = quoteData[0];

        console.log('Quote data: ', quoteData)

        const tx = await router.swap(
            toNetworkId,
            poolIDs[tokenName],
            poolIDs[tokenName],
            signer.address,
            amount,
            0,
            {dstGasForCall: 0, dstNativeAmount: 0, dstNativeAddr: "0x"},
            recipient,
            "0x",
            {
                value: feeWei,
                gasLimit: 600000,
                gasPrice: providerFeeData.gasPrice,
            }
        );

        await tx.wait();

    } else {
        // stargate ETH router
        if(!BlockchainData[fromNetwork as keyof typeof BlockchainData].routerETH) {
            throw new Error('Unsupported Network');
        }

        router = new ethers.Contract(BlockchainData[fromNetwork as keyof typeof BlockchainData].routerETH, routerETHABI, signer);

        const tx = await router.swapETH(
            toNetworkId,
            signer.address,
            recipient,
            amount,
            0,
            {
                value: amount,
                gasLimit: 600000,
                gasPrice: providerFeeData.gasPrice,
            }
        );

        await tx.wait();
    }
    
}