Swapping
Integrations
Advanced Scenarios
Vault Swaps
EVM

EVM Vault swaps

Initiating a Vault Swap on an EVM chain (currently Ethereum or Arbitrum) requires making a smart contract call to Chainflip's Vault contract. The contract addresses can be found in Testnet addresses and Mainnet addresses.

EVM Vault Swaps follow the same pattern mentioned in the Vault Swaps overview:

  1. Request the swap parameter encoding.
  2. Use the return values to build a custom EVM transaction.
  3. Sign and broadcast the transaction to the target network.

Swap Parameter Encoding via Broker API

1. Request the encoded parameters via RPC

Example Request with refund parameters and a CCM message:

curl -H "Content-Type: application/json" -d '{
    "id": 1,
    "jsonrpc": "2.0",
    "method": "broker_request_swap_parameter_encoding",
    "params": {
        "source_asset": { "chain": "Ethereum", "asset": "ETH" },
        "destination_asset": { "chain": "Ethereum", "asset": "FLIP" },
        "destination_address": "0xcf0871027a5f984403aEfD2fb22831D4bEBc11Ef",
        "broker_commission": 0,
        "extra_parameters": {
            "chain": "Ethereum",
            "input_amount": 1000,
            "refund_parameters": {
                "retry_duration": 10,
                "refund_address": "0xcf0871027a5f984403aEfD2fb22831D4bEBc11Ef",
                "min_price": "0x0"
            }
        },
        "channel_metadata": {
            "message": "0x0011223344556677",
            "gas_budget": 1000,
            "ccm_additional_data": "0x"
        }
    }
}' http://localhost:10997

Example response:

{
  "jsonrpc": "2.0",
  "result": {
    "chain": "Ethereum",
    "calldata": "0x07933dd2000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000003e800000000000000000000000000000000000000000000000000000000000001400000000000000000000000000000000000000000000000000000000000000014cf0871027a5f984403aefd2fb22831d4bebc11ef00000000000000000000000000000000000000000000000000000000000000000000000000000000000000080011223344556677000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006000000a00000000cf0871027a5f984403aefd2fb22831d4bebc11ef000000000000000000000000000000000000000000000000000000000000000000009059e6d854b769a505d01148af212bf8cb7f8469a7153edce8dcaedd9d299125000000",
    "value": "0x3e8",
    "to": "0xb7a5bd0345ef1cc5e66bf61bdec17d2461fbd968"
  },
  "id": 1
}

Example response when the source asset is an ERC-20 token rather than native ETH or arbETH. Note the inclusion of the source_token_address:

{
  "jsonrpc": "2.0",
  "result": {
    "chain": "Ethereum",
    "calldata": "0xbbddc2fb000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000003e800000000000000000000000010c6e9530f1c1af873a391030a1d9e8ed0630d2600000000000000000000000000000000000000000000000000000000000003e800000000000000000000000000000000000000000000000000000000000001800000000000000000000000000000000000000000000000000000000000000014cf0871027a5f984403aefd2fb22831d4bebc11ef00000000000000000000000000000000000000000000000000000000000000000000000000000000000000080011223344556677000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005f00000a000000cf0871027a5f984403aefd2fb22831d4bebc11ef000000000000000000000000000000000000000000000000000000000000000000002efeb485320647a8d472503591f8fce9268cc3bf1bb8ad02efd2e905dcd1f31e00000000",
    "value": "0x3e8",
    "to": "0xb7a5bd0345ef1cc5e66bf61bdec17d2461fbd968",
    "source_token_address": "0x10c6e9530f1c1af873a391030a1d9e8ed0630d26"
  },
  "id": 1
}

2. Construct the Transaction

Note the return values from the above RPC call:

FieldDescription
calldataHex-encoded EVM calldata.
valueHex-encoded amount of native currency - (Eth for Ethereum or ArbEth or Arbitrum). Always returns 0 for ERC-20 tokens.
toChainflip's Vault Smart Contract address for either Ethereum or Arbitrum.
source_token_address(Optional) The token address requiring approval.

The first three fields map closely to common EVM transaction building APIs and need to be included in the transaction. Other values such as gas or gasPrice can be estimated and set by the user or wallet.

Note that if source_token_address is present, the returned address is the contract address of the ERC-20 token. This requires approval / whitelisting by the sender, before submitting the vault swap.

/*
Assume `calldata`, `value` and `to`;
*/
const web3 = new Web3("your-endpoint");
const tx = {
  to: to,
  data: calldata,
  value: new BigNumber(value.slice(2), 16).toString(),
  // .. possibly add gas details, or allow the API to set it.
};
 
// If `source_token_address` was returned from the RPC, the token requires approval.
// This is left as an excercise to the reader.

3. Sign and Send

The transaction needs to be finalised and signed by the sender.

As a basic example, using web3 and continuing from the previous code snippet:

// Assume a loaded `wallet`;
const signedTx = await web3.eth.accounts.signTransaction(tx, wallet.privateKey);
const receipt = await web3.eth.sendSignedTransaction(signedTx.rawTransaction as string);

Full Example

This is a basic Typescript example combining the above steps.

import BigNumber from 'bignumber.js';
import Web3 from 'web3';
 
    jsonrpc: "2.0",
    method: "broker_request_swap_parameter_encoding",
    params: {
      source_asset: { chain: "Ethereum", asset: "ETH" },
      destination_asset: { chain: "Bitcoin", asset: "BTC" },
      destination_address: "bcrt1pesng8qzx6wn7m2a5xq480mmtwcme964nx60f7sfpjha8n8lsup8s2vdvvf",
      broker_commission: 10,
      extra_parameters: {
        chain: "Ethereum",
        input_amount: "0x100000000",
        refund_parameters: {
            retry_duration: 10,
            refund_address: "0xcf0871127a5f984403aEfD2fb22831E4bEBc11Ef",
            min_price: "0xabc"
        }
      },
      dca_parameters: {
        number_of_chunks: 4,
        chunk_interval: 10
      }
    }
  })
});
 
const encodedVaultSwapDetails = await response.json() as {
  chain: 'Ethereum' | 'Arbitrum';
  calldata: string;
  value: string;
  to: string;
};
  chain: 'Ethereum' | 'Arbitrum';
  calldata: string;
  value: string;
  to: string;
};
 
const web3 = new Web3("your-endpoint");
const tx = {
  to: vaultSwapDetails.to,
  data: vaultSwapDetails.calldata,
  value: new BigNumber(vaultSwapDetails.value.slice(2), 16).toString(),
};
 
// Send and sign using the preferred method.
const signedTx = await web3.eth.accounts.signTransaction(tx, evmWallet.privateKey);
const receipt = await web3.eth.sendSignedTransaction(signedTx.rawTransaction as string);

Vault contract interface reference

Incorrectly encoded parameters may result in the loss of user funds.

Therefore, it's advised to use the provided RPC to encode Vault Swap parameters. Otherwise make sure to thoroughly test your logic before initiating a swap with real funds. You can test against a local instance of the Chainflip network. Reach out on our Discord community server if you wish to know more.

The Vault smart contract can be called directly without using the provided RPCs to encode the transaction. Here is the reference for the Vault smart contract interface.

In the case of swapping ERC-20 tokens corresponding token approval is required.

    // Swap native token
    function xSwapNative(
        uint32 dstChain,
        bytes calldata dstAddress,
        uint32 dstToken,
        bytes calldata cfParameters
    ) external payable;
 
    // Swap ERC20 token
    function xSwapToken(
        uint32 dstChain,
        bytes calldata dstAddress,
        uint32 dstToken,
        IERC20 srcToken,
        uint256 amount,
        bytes calldata cfParameters
    ) external;
 
    // Swap native token with CCM
    function xCallNative(
        uint32 dstChain,
        bytes calldata dstAddress,
        uint32 dstToken,
        bytes calldata message,
        uint256 gasAmount,
        bytes calldata cfParameters
    ) external payable;
 
    // Swap ERC20 token with CCM
    function xCallToken(
        uint32 dstChain,
        bytes calldata dstAddress,
        uint32 dstToken,
        bytes calldata message,
        uint256 gasAmount,
        IERC20 srcToken,
        uint256 amount,
        bytes calldata cfParameters
    ) external;

Parameter reference

ParamDescriptionData Type
dstChainDestination chain ID for the swap.uint32
dstAddressAddress where the swapped tokens will be sent to on the destination chain. Addresses must be encoded into a bytes type valid for the destination chain. You can check out a reference on how to do address encoding for each of the chains from our SDK (opens in a new tab).address
dstTokenDestination tokenuint32
srcTokenAddress of the token to be swapped from the source chain.address
amountAmount of the source token to be swapped. When swapping a native asset, the msg.value passed in the call will be used instead.uint
messageMessage that is passed to the destination address on the destination chain. It must be shorter than 15k bytes.bytes
gasAmountGas budget for the call on the destination chain in gas units or compute units. This amount should cover the execution of the receiver's logic.uint
cfParametersAdditional metadata for additional features. It cannot be empty and must be correctly encoded according to Chainflip Parameters.bytes