Using a Custom Handler Contract
Creating an Transaction for an Aave Deposit (Example #1)
In this example we'll be walking through how to use this functionality to perform an Aave deposit on behalf of the user on the destination chain, using a custom handler contract.
It is reccommended for most use cases to use the Using the Generic Multicaller Handler Contract, but if your use case requires more complex logic, you'll need to implement a custom handleV3AcrossMessage
function and deploy a new contract, but creating the deposit message
is trivial.
Crafting the Message
Example in solidity:
Implementing the 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.
This contract has not been vetted whatsoever, so use this sample code at your own risk.
Generating the Deposit
The deposit creation process is nearly identical to the process described for initiating a deposit (Bridge Integration Guide). However, there are a few tweaks to that process to include a message.
When getting a quote, two additional query parameters need to be added.
recipient: the recipient for the deposit. In this use case this is not the end-user. It is the contract that implements the handler you would like to call.
message: the message you crafted above. In the above Aave example, the
outputAmount
needs to be set equal to theamount
to ensure that the amount expected to be received on the destination chain by the handler contract is equal to the amount to be deposited into Aave. However, usually thesuggested-fees
endpoint in the API is queried to get the suggestedoutputAmount
to set in order to make theinputAmount
profitable for fillers to relay. We recommend doing the following in cases like this Aave example:In the
message
you use as a parameter when querying thesuggested-fees
endpoint, setdepositAmount = outputAmount = inputAmount
which will return you a suggested output amount.When you call depositV3, set
outputAmount
to the newly suggested output amount, and keepinputAmount
equal todepositAmount
. This will ensure that your deposit is profitable for fillers.
When calling depositV3, you'll need to make a slight tweak to the parameters.
The recipient should be set to your custom handler contract.
The message field should be set to the message you generated above instead of
0x
. Make sure that you re-generate the message settinginputAmount = amount
andoutputAmount
equal to the suggested output amount.In the above example, the
outputAmount
needs to be set equal to theamount
to ensure that the amount expected to be received on the destination chain by the handler contract is equal to the amount to be deposited into Aave.
You can find this interface definition in the codebase here.
What happens after the deposit is received on the destination chain?
When the relay is filled, the destination SpokePool
calls handleV3AcrossMessage
on the recipient contract (your custom handler contract) with the message (and a few other fields). You can define any arbitrary logic in handleV3AcrossMessage
to fit your use case.
Message Constraints
Handler contracts only uses the funds that are sent to it. That means that the message is assumed to only have authority over those funds and, critically, no outside funds. This is important because relayers can send invalid relays. They will not be repaid if they attempt this, but if an invalid message could unlock other funds, then a relayer could spoof messages maliciously.
Conclusion
Now that you have a process for constructing a message, creating a deposit transaction, and you have a handler contract deployed on the destination chain, all you need to do is send the deposit to have the handler get executed on the destination.
Creating a Transaction for WrapChoice (Example #2)
Here's another contract example using a slightly different message format to allow users to choose whether they want to receive WETH or ETH. This example uses a custom handler contract. In this example, the message
should be contain an encoded address
and bool
value only:
The deposit recipient
should be set to your custom handler contract's address on the destination chain. Generating the Deposit is identical to process described above.
Reverting Transactions
If the
recipient
contract'shandleV3AcrossMessage
function reverts whenmessage
,tokenSent
,amount
, andrelayer
are passed to it, then the fill on destination will fail and cannot occur. In this case the deposit will expire when the destination SpokePool timestamp exceeds the deposit fillDeadline timestamp, the depositor will be refunded on theoriginChainId
. Ensure that the depositor address on the origin SpokePool is capable of receiving refunds.It is possible to update the message using
speedUpDepositV3
but it requires the originaldepositor
address to create a signature, which may not be possible if thedepositor
is not an EOA or a smart contract capable of creating ERC1271 signatures.If the
recipient
address is not a contract on the destination chain, then thefillRelay
transaction will not attempt to pass calldata to it.Consider implementing fallback logic in your custom handler contract to transfer funds in the case of a transaction revert based on your use case.
Summarized Requirements
The deposit
message
is not emptyThe
recipient
address is your custom handler contract on thedestinationChainId
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, the recipient's handler contract'shandleV3AcrossMessage
will be executedThe additional gas cost to execute the above function is compensated for in the deposit's
relayerFeePct
Security & Safety Considerations
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.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.
Last updated