Across+ Integration
Product Description
Across+ is a bridge abstraction framework. A developer can use Across+ in their dApp to bundle bridging and protocol actions in the same transaction, abstracting it away from the user. This can be used to promote user onboarding and defragment user liquidity across chains.
Instead of users independently bridging assets to chains to use applications, Across+ enables your application to meet the user where they already are.
At a high level, Across+ works through the following process:
End-user (or intermediate contract) includes a message field in the deposit transaction.
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 contract with the message (and a few other fields).This function can do anything, meaning application-specific actions can be executed atomically.
Integrating Across+ into Your Application
This guide contains instructions and examples for calling the smart contract functions and constructing the message
and creating the destination handler contract.
If you have further questions or suggestions for this guide, please send a message to the #developer-questions
channel in the Across Discord.
Creating an Across+ Transaction
In this example we'll be walking through how to use Across+ to perform an AAVE deposit on behalf of the user on the destination chain.
Crafting the Message
Across+ requires that you send some nonempty message to your contract on the other side. This message allows you to pass arbitrary information to your recipient contract and it ensures that Across understands that you intend to trigger the handler function on the recipient (instead of just transferring tokens). A message is required if you want the handler to be called.
In this example, our message will be just the user's address since that's all our contract would need to know to generate an AAVE deposit. Here's an example for generating this in typescript:
Example in solidity:
Generating the Deposit
The deposit creation process is nearly identical to the process described for initiating a deposit (Initiating a Deposit (User Intent)). However, there are two tweaks to that process to include a message.
When getting a quote (Getting a Quote), two additional query parameters need to be added.
recipient: the recipient for the deposit. For Across+ transactions, this is not the end-user. It is the contract that implements the handler you would like to call (more on that later). Usually, this is a contract you have created to decode and handle the message.
message: the message you crafted above.
When calling deposit (Calling depositV3), you'll need to make a slight tweak to the parameters.
The recipient should be set to your handler contract.
The message field should be set to the message you generated above instead of
0x
.
Building a Handler Contract
You will need to implement a function matching the following interface in your handler contract to receive the message:
For this example, we're depositing the funds the user sent into AAVE on the user's behalf. Here's how that full contract implementation might look. Note: this contract has not been vetted whatsoever, so use this sample code at your own risk.
One note on this implementation: this contract only uses the funds that are sent to it. That means that the message is assumed to only have authority over those funds and no outside funds. This is important because relayers can send invalid relays. They will not be repaid the funds sent in, but if an invalid message could unlock other funds, then a relayer could use this maliciously.
You can find this interface definition in the codebase here.
Conclusion
Now that you have a process for constructing a message, creating a deposit transaction, and you have a handler contract built and deployed on the destination chain, all you need to do is send the deposit to have the handler get executed on the destination.
WrapChoice Example
Here's another contract example using a slightly different message format to allow users to choose whether they want to receive WETH or ETH. In this example, a bool
is passed along with the address of the user to allow the message to define the unwrapping behavior on the destination.
Summarized Requirements
The deposit
message
is not emptyThe
recipient
address is a contract on thedestinationChainId
that implements a publichandleV3AcrossMessage(address,uint256,address,bytes)
function. See Reverting Transactions for considerations.Construct your
message
Use the Across API to get an estimate of the
relayerFeePct
you should set for your message and recipient combinationCall
depositV3()
passing in your messageOnce the relayer calls
fillV3Relay()
on the destination, your recipient'shandleAcrossMessage
will be executedThe additional gas cost to execute the above function is compensated for in the deposit's
relayerFeePct
.
Security & Safety Considerations
It is recommended that recipient contracts require that
handleV3AcrossMessage()
is only callable by the Across SpokePool contract on the same chain.Avoid making unvalidated assumptions about the
message
data supplied tohandleV3AcrossMessage()
. Across+ does not guarantee message integrity, only that a relayer who spoofs a message will not be repaid by Across. If integrity is required, integrators should consider including a depositor signature in the message for additional verification. Message data should otherwise be treated as spoofable and untrusted for use beyond directing the funds passed along with it.Avoid embedding assumptions about the transfer token or transfer amount into their messages. Instead use the
tokenSent
andamount
variables supplied with to thehandleV3AcrossMessage()
function. These fields are enforced by the SpokePool contract and so can be assumed to be correct by the recipient contract.In the event that a deposit expires, it will be refunded to the depositor address. Ensure that the depositor address on the origin SpokePool is capable of receiving refunds.
The relayer(s) able to complete a fill can be restricted by storing an approved set of addresses in a mapping and validating the
handleV3AcrossMessage()
relayer
parameter. This implies a trust relationship with one or more relayers.
Reverting Transactions
If the
message
specifies a transaction that could revert when handled on the destination, the user will be refunded on the origin chain in the bundle following the expiry of the deposit.Note: Expiry occurs when the destination SpokePool timestamp exceeds the deposit fillDeadline timestamp.
If this is not desirable behavior it is recommended to include logic in the handler contract to simply transfer the funds to the user in the case of a reverting transaction.
Last updated