Migration Steps
1
Deploy Your Own Trusted Forwarder
Choose between two forwarder types based on your needs:
Sequential Forwarder (Nonce-based)Contract:
| Type | Replay Protection | Use Case |
|---|---|---|
| Sequential | Nonce (0, 1, 2…) | Simple operations, ordered transactions |
| Concurrent | Random salt (hash-based) | Batch operations, parallel transactions |
TrusteForwarderERC2771.solConcurrent Forwarder (Hash-based)Contract: TrustedForwarderConcurrentERC2771.solSave your deployed forwarder address - you’ll need it for the next steps.
2
Whitelist the Trusted Forwarder in Your Contract
Your contract must trust the new forwarder address. How you do this depends on your contract’s architecture:If your contract has an updateable forwarder:If your contract has an immutable forwarder:You’ll need to redeploy your contract with the new forwarder address:
3
Update Frontend Encoding
Previously, Gelato handled the encoding to the trusted forwarder internally. Now you must encode the call to the forwarder yourself.
- Sequential Forwarder
- Concurrent Forwarder
Key Changes Summary
| What | Before (Gelato handled it) | After (You handle it) |
|---|---|---|
| Forwarder | Gelato’s forwarder | Your deployed forwarder |
| EIP-712 Domain | - | Sign for YOUR forwarder |
| Domain name | - | "TrustedForwarder" or "TrustedForwarderConcurrentERC2771" |
| Domain verifyingContract | - | Your forwarder address |
| Get nonce from | - | Your forwarder (sequential only) |
| Gelato target | Your contract | Your forwarder |
| Encoding | Just your function | Full forwarder call |
Sequential vs Concurrent
| Feature | Sequential | Concurrent |
|---|---|---|
| Replay protection | Nonce (0, 1, 2…) | Random salt |
| Transaction order | Must be in order | Any order |
| Parallel transactions | No | Yes |
| Failed tx blocks others | Yes | No |
| Get from forwarder | userNonce(address) | Nothing (generate salt) |
| EIP-712 type name | SponsoredCallERC2771 | SponsoredCallConcurrentERC2771 |
| Forwarder function | sponsoredCallERC2771() | sponsoredCallConcurrentERC2771() |
Migration Checklist
- Deploy trusted forwarder (sequential or concurrent)
- Whitelist forwarder in your contract (update address or redeploy)
- Update frontend to encode calls to your forwarder
- Test on testnet
- Deploy to production
Troubleshooting
Signature verification failed
Signature verification failed
- Ensure domain
verifyingContractis your forwarder address (not your contract) - Ensure domain
namematches exactly:"TrustedForwarder"or"TrustedForwarderConcurrentERC2771" - Ensure
chainIdmatches the network
Nonce mismatch (Sequential only)
Nonce mismatch (Sequential only)
- Get fresh nonce from forwarder before each signature:
forwarder.userNonce(user) - Don’t reuse old signatures
Replay error (Concurrent only)
Replay error (Concurrent only)
- Generate a new random
userSaltfor each transaction - Don’t reuse salts
Wrong user address in contract
Wrong user address in contract
- Ensure your contract uses
_msgSender()(fromERC2771Context) - Ensure the forwarder is whitelisted in your contract
Example Implementations
Sequential Forwarder
Nonce-based replay protection
Concurrent Forwarder
Salt-based replay protection
Sequential Example Script
Complete sequential implementation
Concurrent Example Script
Complete concurrent implementation
- SimpleCounterTrusted.sol - Sequential example
- SimpleCounterTrustedConcurrent.sol - Concurrent example