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:
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.
To be repaid for the fill, the relayer is required to include the same message field in their fill.
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 !
Before starting the integration, please ensure that you fill this form and get your integrator ID. This will allow us to support you better as you progress towards the production-release and accelerate co-marketing efforts from our side.
Once you fill out the form, we’ll get in touch and provide your integrator ID. In the meantime, you can proceed with the integration by following this guide and simply add the integrator ID once it’s shared with you.
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
}
]
}
when using tradeType=exactOutput
with the Swap API some routes may return more tokens than expected. So if you’re using exactOutput
with embedded actions, and you need those actions to execute with the exact amount you specify, you should not use dynamic balance, instead, set the values explicitly to the exact amount.
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 totrue
. Thetarget
will be the recipient's address.Are you calling a smart contract function? If so, set
isNativeTransfer
tofalse
. Thetarget
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
totrue
.
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 anargs
array. For each argument, decide if its value is static or dynamic:For a static value, provide it in the
value
field and ensurepopulateDynamically
isfalse
.For a dynamic value, set
populateDynamically
totrue
and specify which token's balance to use in thebalanceSourceToken
field. Thevalue
field will be ignored.
Examples
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 thedepositId
parameter.depositId
: The deposit id that is emitted from theDepositV3
function call as aV3FundsDeposited
event. Use this in conjunction with theoriginChainId
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