Embedded Crosschain Swap Actions

This guide helps developers understand the fundamentals of preparing for and constructing the request body for embedded crosschain swap actions. This enables developers to add custom actions (like mint, deposit, etc) that execute seamlessly on the destination chain immediately after a swap, all within a single transaction built using the /swap/approval API.


Executing Destination Chain Actions

At a high level, this is how the embedded crosschain swap actions system works:

  1. User submits the intent using the Swap API where they define the actions they want to execute on the destination chain along with the input/output tokens.

  2. To be repaid for the fill, the relayer is required to include the same message field in their fill.

  3. When this message field is non-empty and the recipient is a contract, the SpokePool calls a special handler function on the recipient handler contract with the message (and a few other fields).

To execute the destination chain actions, the entry point is the handleV3AcrossMessage() in the MulticallHandler.sol contract. But if there were any actions using the dynamic balance feature, then one of the calls executed by handleV3AcrossMessage will be to makeCallWithBalance() .

Now, let's dive a little deeper !


The Action Object Structure

The goal is to build an actions object that defines a "post-swap" operation. All actions are submitted as objects in an array, in the request body for the /swap/approval API. Here is the basic structure of a single action:

{
  "actions":[
    {
      "target": "0x...", // The contract or recipient address
      "functionSignature": "function transfer(address,uint256)", // The function to call, or "" for native transfers
      "args": [
        // Array of arguments for the function, or [] for native transfers
        {
          "value": "1000", // Static value for an argument
          "populateDynamically": false, // Whether to use the swap balance instead
          "balanceSourceToken": "0x..." // Token to get the balance from if dynamic
        }
      ],
      "value": "0", // Static msg.value to send, in wei
      "isNativeTransfer": false, // True for simple native currency transfers
      "populateCallValueDynamically": false // True to use the entire swapped native balance as msg.value
    }
  ]
}

Breaking Down the Action Object

To fill in the structure above, you must define its core components by making a series of decisions. Each decision corresponds to a field in the action object.

1. Set the Target, Function Signature, Action Type and Value

First, determine your goal. This defines the destination (target) and the type of action.

  • Is this a simple native token transfer? If so, set the isNativeTransfer flag to true. The target will be the recipient's address.

  • Are you calling a smart contract function? If so, set isNativeTransfer to false. The target will be the contract's address.

  • Native Value (value): If the action needs to send native currency (msg.value), decide how to set this amount:

    • For a static amount, provide it in the top-level value field.

    • To use the entire swapped balance, set populateCallValueDynamically to true.

2. Specify the Function

If you are calling a contract (isNativeTransfer is false), you must provide the full functionSignature string (e.g., "transfer(address,uint256)"). For native transfers, this must be an empty string ("").

3. Provide Parameters for the Function Signature

Finally, define the parameters for the action.

  • Function Arguments (args): For contract calls, you must provide an args array. For each argument, decide if its value is static or dynamic:

    • For a static value, provide it in the value field and ensure populateDynamically is false.

    • For a dynamic value, set populateDynamically to true and specify which token's balance to use in the balanceSourceToken field. The value field will be ignored.


Examples

All these examples work with the /swap/approval API endpoint.

Incase you want to use embedded crosschain actions with the /suggested-fees API, please refer to the Legacy Embedded Crosschain Actions guide.

This enables you to build bridge+action flows with the /suggested/fees API.


Tracking Deposits

To track the lifecycle of a deposit you can use the GET /deposit/status endpoint with the following parameters:

  • originChainId: chainId where the deposit originated from. Use this in conjunction with the depositId parameter.

  • depositId: The deposit id that is emitted from the DepositV3 function call as a V3FundsDeposited event. Use this in conjunction with the originChainId parameter.

  • depositTxHash : The deposit transaction hash that is emitted from the DepositV3 function call as a V3FundsDeposited event. If you are using this, you do not need the above parameters.

The recommended solution for tracking all Across deposits originating from your integration, for a single user, is to store the user's depositId and originChainId or the depositTxHash from each transaction originating from your app, and then get the status of each via the above endpoint.

In case you face any errors or need support, please feel free to reach out to us.

Last updated