Cross-Chain Contract Calls and Deposits

Contract Calls allow developers to execute complex transactions on the same chain or across different blockchains as one single transaction. This enables deposits into staking, lending or liquidity protocols from any token as a single transaction which otherwise would have taken anywhere between 2-10 transactions.

Swing supports both same chain and cross chain contract calls. Swing seamlessly connects to bridges, DEXes and protocols to execute complex on-chain transactions.

Our contracts were audited on 31 May 2023, by Peckshield Audit. view audit report Monetize your integration by collecting partner fees up to 10% of the transaction volume. Learn More

Steps: Executing a Contract Call

The steps to executing a contract call are the same as executing a bridge or swap transaction, with the added exception that you’ll need to generate the callData for the contract where the funds will be deposited:

Making a Request

POST: https://swap.prod.swing.xyz/v0/transfer/send

Body Parameters:

KeyExampleDescription
fromChainethereumThe blockchain where the transaction originates.
fromTokenAddress0x0000000000000000000000000000000000000000Source Token Address
fromUserAddress0x018c15DA1239B84b08283799B89045CD476BBbBbSender's wallet address
tokenSymbolETHSource token slug
toTokenAddress0x0000000000000000000000000000000000000000Destination token address.
toChainpolygonDestination chain
toTokenAmount4376081Amount of the destination token to be received.
toTokenSymbolMATICDestination Chain slug
toUserAddress0x018c15DA1239B84b08283799B89045CD476BBbBbReceiver's wallet address
tokenAmount1000000000000000000Amount of the source token being sent (in wei for ETH).
projectIdreplugYour project's ID
routesee Get QouteSelected route
contractCallInfosee Generating Contract Calldata sectionTarget contract info for deposits

Sample Request

const result = await axios.post(
  'https://swap.prod.swing.xyz/v0/transfer/send',
  {
    fromChain: 'polygon',
    tokenSymbol: 'USDC',
    fromTokenAddress: '0xcbe56b00d173a26a5978ce90db2e33622fd95a28',
    fromUserAddress: '0x018c15DA1239B84b08283799B89045CD476BBbBb',
    toChain: 'ethereum',
    toTokenSymbol: 'ETH',
    toTokenAddress: '0x0000000000000000000000000000000000000000',
    toUserAddress: '0x018c15DA1239B84b08283799B89045CD476BBbBb',
    tokenAmount: '1000000000',
    projectId: 'replug',
    route: [
      {
        bridge: 'stargate',
        bridgeTokenAddress: '0xc2132d05d31c914a87c6611c10748aeb04b58e8f',
        steps: ['allowance', 'approve', 'send'],
        name: 'USDT',
        part: 100,
      },
    ],
    contractCallInfo: [
      {
        toContractAddress: '0x9ee91F9f426fA633d227f7a9b000E28b9dfd8599',
        toContractCallData:
          '0xa9059cbb0000000000000000000000001234567890abcdef1234567890abcdef12345678000000000000000000000000',
        outputTokenAddress: '0x018c15DA1239B84b08283799B89045CD476BBbBb',
      },
    ],
  },
);

Generating Contract Calldata

For Contract Calls, Swing's /send endpoint gives us the option to instruct the system on what to do with our funds post swap.

For example, we can instruct Swing to deposit our funds into a smart contract of our choosing. A more practical instruction would be to stake our funds into a staking contract, and then send us that staking contract's LP token. We can achieve this using the contractCallInfo array parameter in the /send endpoint.

To execute a contract call via Swing, we'll need three (3) parameters to be passed our contractCallInfo Array:

PropertyExampleDescription
toContractAddress0x9ee91F9f426fA633d227f7a9b000E28b9dfd8599requiredSmart Contract address we'll deposit your funds into
toContractCallData0xa9059cbb0000000000000000000000abcdef........requiredCalldata to be passed to Swing
outputTokenAddress0x018c15DA1239B84b08283799B89045CD476BBbBboptionalAddress of Liquidity Provider Token to be received by sender

When executing a contract call transaction, you are required to provide the contract calldata for the contract to which you wish to send your funds.

Let's say we've got some USDC on the Polygon chain and want to stake some ETH into LIDO's Ethereum staking contracts via Swing. The following will take place to make this transaction to be possible:

  • Swing will start the transaction by deducting the necessary funds from the fromUserAddress.
  • Swing will then bridge our USDC (tokenSymbol) from the Polygon chain (fromChain) to the Ethereum Chain (toChain).
  • Swing will then swap that USDC (tokenSymbol) to ETH (toTokenSymbol) on the destination chain (toChain) and send it to wallet address supplied to the toUserAddress.
  • Using the contractCallInfo parameter, Swing will then execute a function on a target Smart Contract and deposit your funds.
  • Optionally, If the target smart contract emits any token to the sender's wallet like an LP token, Swing can then send those token to the address supplied to the toUserAddress.

If you omit the toUserAddress parameter, Swing will default it's value to whatever value you supply to the fromUserAddress.

To get started, we'll first need to generate the necessary callData by using LIDO's staking contract ABI:

// Using partial ABI for simplicity
const LIDO_ABI = [
  {
    constant: false,
    inputs: [
      { name: '_amount', type: 'uint256' },
      { name: '_referral', type: 'address' },
    ],
    name: 'submit',
    outputs: [{ name: '', type: 'uint256' }],
    payable: true,
    stateMutability: 'payable',
    type: 'function',
  },
];

It's very important to remember what we're trying to achieve here. We want Swing to interact with LIDO's contract immediately after swapping our funds. When Swing is done swapping our assets, we want Swing to call the submit function on LIDO's contract.

For this example, we'll be using ethers.js to generate the callData required for our contract call example.

import { ethers } from 'ethers';

Using ethers to generate our callData is fairly straight forward. First we'll load up LIDO's staking smart contract using it's ABI and contract address:

const LIDO_ADDRESS = '0x9ee91F9f426fA633d227f7a9b000E28b9dfd8599';
const MY_REWARDS_ADDRESS = '0x184AbEfBdCa24Ce0Dd964a74f6d5E69CE44D9579';
const LIDO_OUTPUT_TOKEN_ADDRESS = '0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84'; // sETH token address
const toAmount = '2578306944516966';
 
const contract = new ethers.Contract(
  LIDO_ADDRESS,
  LIDO_ABI,
  ethers.getDefaultProvider(),
);

Next, we'll encode the function data by using the encodeFunctionData() method to generate the final callData for the submit() function in LIDO's staking contract.

LIDO's submit() function takes two parameters, namely:

  • _amount — the amount of tokens to be staked
  • _referral — an address for incentivisation reward
const interface = new ethers.utils.Interface(LIDO_ABI);
const callData = interface.encodeFunctionData('submit', [
  toAmount,
  MY_REWARDS_ADDRESS,
]);

Putting it all together into a function called generateCallData():

const LIDO_ADDRESS = '0x9ee91F9f426fA633d227f7a9b000E28b9dfd8599';
const MY_REWARDS_ADDRESS = '0x184AbEfBdCa24Ce0Dd964a74f6d5E69CE44D9579';
const LIDO_OUTPUT_TOKEN_ADDRESS = '0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84'; // sETH token address
const toAmount = '1000000000';
 
const LIDO_ABI = [
  {
    constant: false,
    inputs: [
      { name: '_amount', type: 'uint256' },
      { name: '_referral', type: 'address' },
    ],
    name: 'submit',
    outputs: [{ name: '', type: 'uint256' }],
    payable: true,
    stateMutability: 'payable',
    type: 'function',
  },
];
 
async function generateCallData() {
  const contract = new ethers.Contract(
    LIDO_ADDRESS,
    LIDO_ABI,
    ethers.getDefaultProvider(),
  );
 
  const interface = new ethers.utils.Interface(LIDO_ABI);
  const callData = await interface.encodeFunctionData('submit', [
    toAmount,
    MY_REWARDS_ADDRESS,
  ]);
 
  return callData;
}

Executing a Contract Call Transaction

Next, let's generate our callData and make a request to Swing to get our transaction ready to be sent over to the network:

const sendTransaction = async () => {
  const callData = await generateCallData(); //sample output: 0xa9059cbb0000000000000000000000001234567890abcdef1234567890abcdef12345678000000000000000000000000...
 
  const result = await axios.post(
    'https://swap.prod.swing.xyz/v0/transfer/send',
    {
      fromChain: 'polygon',
      tokenSymbol: 'USDC',
      fromTokenAddress: '0x3c499c542cef5e3811e1192ce70d8cc03d5c3359',
      fromUserAddress: '0x018c15DA1239B84b08283799B89045CD476BBbBb',
      toChain: 'ethereum',
      toTokenSymbol: 'ETH',
      toTokenAddress: '0x0000000000000000000000000000000000000000',
      tokenAmount: '1000000000',
      projectId: 'replug', // create your project here: https://platform.swing.xyz/
      route: [
        {
          bridge: 'stargate',
          bridgeTokenAddress: '0xc2132d05d31c914a87c6611c10748aeb04b58e8f',
          steps: ['allowance', 'approve', 'send'],
          name: 'USDT',
          part: 100,
        },
      ],
      contractCallInfo: [
        {
          toContractAddress: LIDO_ADDRESS,
          toContractCallData: callData,
          outputTokenAddress: LIDO_OUTPUT_TOKEN_ADDRESS, //Address of Liquidity Provider Token to be received by sender
        },
      ],
    },
  );
  return result.data;
};

Since executing a contract call will change the state of a user's wallet, the next step of this process requires invoking a contract-level function on the wallet address you supply to the fromUserAddress parameter. Swing only returns the necessary call-data that will need to be signed and executed by the local wallet (i.e. Metamask, WalletConnect, Coinbase Wallet).