How to make your contract compatible with Relay
Relay allows a user to pay on Chain A for an arbitrary set of transactions to be executed on Chain B. These transactions are executed by a relayer, not the user themselves, which means you need to make special considerations to make sure your contract is compatible.
When Relayers execute transactions on behalf of users, they do so via a multicall contract (to be specific, we use Vectorized’s gas-optimized Multicaller contract). This protects the Relayer from malicious transactions.
One exception is simple bridge transactions, where the data
is empty. For these, the Relayer will execute them directly to avoid the gas overhead of using Multicaller.
Because transactions are executed via the Relayer / Multicaller, you can’t rely on msg.sender
for authentication. There are a couple of ways around this:
The simplest solution is if your contract allows one user to take an action on behalf of another user. This works great for actions that don’t require authentication, because they are purely net beneficial to the user:
You just need to allow passing the address that the action is being done on behalf of. Of course, you can’t do this for actions that potentially harm the user:
Some contracts don’t require authentication, but also don’t have native delegation built into them. In such scenarios, it might be possible to use a router contract. E.g. if an NFT only supported minting to msg.sender
, you could mint via a router contract then have it forwarded the NFT to the user.
For authenticated actions, you can do those by passing in proof that the user authenticated the action. Currently, you need to build custom signature authentication into your contract, however, in the near future we will be adding support for more standardized flows, such as:
Alternatively, you can simply bridge a small amount of gas and have the user execute the transaction themselves. This is viable because of how fast and cheap Relay is, and is backwards compatible with any contract.
We will soon add functionality to our SDK to handle this flow completely automatically, but until then, you can do it manually like this:
This might seem like a lot, but especially when Origin and Destination are both L2s, it can be quite fast and cheap. Arguably, much better than the user needing to worry about having a balance on many chains.