> ## Documentation Index
> Fetch the complete documentation index at: https://docs.gelato.cloud/llms.txt
> Use this file to discover all available pages before exploring further.

# ERC-2771 Migration Guide

> Migrate from Gelato's deprecated trusted forwarder to your own forwarder

<Warning>
  Gelato is **deprecating the trusted forwarder** contracts. The old forwarder will no longer be available, and Gelato will not provide a replacement. To continue using ERC-2771 meta-transactions, you must **deploy your own trusted forwarder** and update your integration.
</Warning>

<Card title="Migration Examples Repository" icon="github" href="https://github.com/gelatodigital/gelato-migration-erc2271-syncfee">
  Complete migration examples with contracts, deployment scripts, and step-by-step code.
</Card>

## Migration Steps

<Steps>
  <Step title="Deploy Your Own Trusted Forwarder">
    Choose between two forwarder types based on your needs:

    | Type           | Replay Protection        | Use Case                                |
    | -------------- | ------------------------ | --------------------------------------- |
    | **Sequential** | Nonce (0, 1, 2...)       | Simple operations, ordered transactions |
    | **Concurrent** | Random salt (hash-based) | Batch operations, parallel transactions |

    <Warning>
      **DISCLAIMER:** All Solidity contracts in the referenced repository are provided as **examples for educational purposes only**. They have **NOT been audited** and may contain bugs or security vulnerabilities. **USE AT YOUR OWN RISK.** For production use, please ensure proper security audits are conducted by qualified professionals.
    </Warning>

    **Sequential Forwarder (Nonce-based)**

    Contract: [`TrusteForwarderERC2771.sol`](https://github.com/gelatodigital/gelato-migration-erc2271-syncfee/blob/main/contracts/trustedForwarders/TrusteForwarderERC2771.sol)

    **Concurrent Forwarder (Hash-based)**

    Contract: [`TrustedForwarderConcurrentERC2771.sol`](https://github.com/gelatodigital/gelato-migration-erc2271-syncfee/blob/main/contracts/trustedForwarders/TrustedForwarderConcurrentERC2771.sol)

    <Note>Save your deployed forwarder address - you'll need it for the next steps.</Note>
  </Step>

  <Step title="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:**

    ```solidity theme={null}
    yourContract.setTrustedForwarder(newForwarderAddress);
    ```

    **If your contract has an immutable forwarder:**

    You'll need to redeploy your contract with the new forwarder address:

    ```solidity theme={null}
    contract YourContract is ERC2771Context {
        constructor(address trustedForwarder) ERC2771Context(trustedForwarder) {}

        function yourFunction() external {
            address user = _msgSender();  // Still works the same
            // ...
        }
    }
    ```

    <Warning>If your contract is not upgradeable and contains important state data, a migration strategy will be required to transfer the state to the new contract.</Warning>
  </Step>

  <Step title="Update Frontend Encoding">
    Previously, Gelato handled the encoding to the trusted forwarder internally. Now **you must encode the call to the forwarder yourself**.

    <Tabs>
      <Tab title="Sequential Forwarder">
        ```typescript theme={null}
        import { ethers } from "ethers";

        // 1. Encode your function call
        const functionData = yourContract.interface.encodeFunctionData("yourFunction", [args]);

        // 2. Get user's nonce from YOUR forwarder
        const userNonce = await trustedForwarder.userNonce(userAddress);

        // 3. Create EIP-712 domain for YOUR forwarder
        const domain = {
          name: "TrustedForwarder",
          version: "1",
          chainId: chainId,
          verifyingContract: trustedForwarderAddress  // YOUR forwarder address
        };

        // 4. Define the type structure
        const types = {
          SponsoredCallERC2771: [
            { name: "chainId", type: "uint256" },
            { name: "target", type: "address" },
            { name: "data", type: "bytes" },
            { name: "user", type: "address" },
            { name: "userNonce", type: "uint256" },
            { name: "userDeadline", type: "uint256" }
          ]
        };

        // 5. Create the message
        const message = {
          chainId: chainId,
          target: yourContractAddress,    // Your contract (the target)
          data: functionData,             // Your function call
          user: userAddress,
          userNonce: userNonce,           // From forwarder
          userDeadline: 0                 // 0 = no expiry
        };

        // 6. User signs the message
        const signature = await signer.signTypedData(domain, types, message);

        // 7. Encode the call to the forwarder
        const forwarderData = trustedForwarder.interface.encodeFunctionData(
          "sponsoredCallERC2771",
          [
            message,           // The CallWithERC2771 struct
            sponsorAddress,    // Who pays (can be same as user)
            feeToken,          // Fee token address
            oneBalanceChainId, // Chain ID for 1Balance
            signature,         // User's signature
            0,                 // nativeToFeeTokenXRateNumerator
            0,                 // nativeToFeeTokenXRateDenominator
            ethers.ZeroHash    // correlationId
          ]
        );

        // 8. Send to Gelato with FORWARDER as target
        await gelatoRelay.sponsoredCall({
          target: trustedForwarderAddress,  // YOUR forwarder, not your contract!
          data: forwarderData
        });
        ```
      </Tab>

      <Tab title="Concurrent Forwarder">
        ```typescript theme={null}
        import { ethers } from "ethers";

        // 1. Encode your function call
        const functionData = yourContract.interface.encodeFunctionData("yourFunction", [args]);

        // 2. Generate a unique salt (random bytes32)
        const userSalt = ethers.hexlify(ethers.randomBytes(32));

        // 3. Create EIP-712 domain for YOUR forwarder
        const domain = {
          name: "TrustedForwarderConcurrentERC2771",
          version: "1",
          chainId: chainId,
          verifyingContract: trustedForwarderAddress  // YOUR forwarder address
        };

        // 4. Define the type structure
        const types = {
          SponsoredCallConcurrentERC2771: [
            { name: "chainId", type: "uint256" },
            { name: "target", type: "address" },
            { name: "data", type: "bytes" },
            { name: "user", type: "address" },
            { name: "userSalt", type: "bytes32" },
            { name: "userDeadline", type: "uint256" }
          ]
        };

        // 5. Create the message
        const message = {
          chainId: chainId,
          target: yourContractAddress,    // Your contract (the target)
          data: functionData,             // Your function call
          user: userAddress,
          userSalt: userSalt,             // Random salt for replay protection
          userDeadline: 0                 // 0 = no expiry
        };

        // 6. User signs the message
        const signature = await signer.signTypedData(domain, types, message);

        // 7. Encode the call to the forwarder
        const forwarderData = trustedForwarder.interface.encodeFunctionData(
          "sponsoredCallConcurrentERC2771",
          [
            message,           // The CallWithConcurrentERC2771 struct
            sponsorAddress,    // Who pays
            feeToken,          // Fee token address
            oneBalanceChainId, // Chain ID for 1Balance
            signature,         // User's signature
            0,                 // nativeToFeeTokenXRateNumerator
            0,                 // nativeToFeeTokenXRateDenominator
            ethers.ZeroHash    // correlationId
          ]
        );

        // 8. Send to Gelato with FORWARDER as target
        await gelatoRelay.sponsoredCall({
          target: trustedForwarderAddress,  // YOUR forwarder, not your contract!
          data: forwarderData
        });
        ```
      </Tab>
    </Tabs>
  </Step>
</Steps>

## 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

<AccordionGroup>
  <Accordion title="Signature verification failed">
    * Ensure domain `verifyingContract` is your **forwarder address** (not your contract)
    * Ensure domain `name` matches exactly: `"TrustedForwarder"` or `"TrustedForwarderConcurrentERC2771"`
    * Ensure `chainId` matches the network
  </Accordion>

  <Accordion title="Nonce mismatch (Sequential only)">
    * Get fresh nonce from forwarder before each signature: `forwarder.userNonce(user)`
    * Don't reuse old signatures
  </Accordion>

  <Accordion title="Replay error (Concurrent only)">
    * Generate a new random `userSalt` for each transaction
    * Don't reuse salts
  </Accordion>

  <Accordion title="Wrong user address in contract">
    * Ensure your contract uses `_msgSender()` (from `ERC2771Context`)
    * Ensure the forwarder is whitelisted in your contract
  </Accordion>
</AccordionGroup>

## Example Implementations

<CardGroup cols={2}>
  <Card title="Sequential Forwarder" icon="arrow-down-1-9" href="https://github.com/gelatodigital/gelato-migration-erc2271-syncfee/blob/main/contracts/trustedForwarders/TrusteForwarderERC2771.sol">
    Nonce-based replay protection
  </Card>

  <Card title="Concurrent Forwarder" icon="shuffle" href="https://github.com/gelatodigital/gelato-migration-erc2271-syncfee/blob/main/contracts/trustedForwarders/TrustedForwarderConcurrentERC2771.sol">
    Salt-based replay protection
  </Card>

  <Card title="Sequential Example Script" icon="code" href="https://github.com/gelatodigital/gelato-migration-erc2271-syncfee/blob/main/scripts/testSponsoredCallTrusted.ts">
    Complete sequential implementation
  </Card>

  <Card title="Concurrent Example Script" icon="code" href="https://github.com/gelatodigital/gelato-migration-erc2271-syncfee/blob/main/scripts/testSponsoredCallTrustedConcurrent.ts">
    Complete concurrent implementation
  </Card>
</CardGroup>

**Example Contracts:**

* [SimpleCounterTrusted.sol](https://github.com/gelatodigital/gelato-migration-erc2271-syncfee/blob/main/contracts/SimpleCounterTrusted.sol) - Sequential example
* [SimpleCounterTrustedConcurrent.sol](https://github.com/gelatodigital/gelato-migration-erc2271-syncfee/blob/main/contracts/SimpleCounterTrustedConcurrent.sol) - Concurrent example
