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

# Integrate Permit3

> Sign, simulate, and submit a single-chain ERC-20 transfer using Permit3 and TypeScript.

This example transfers 1 USDC on Base using Permit3. It uses the single-chain signed-permit overload from the [current interface](https://github.com/eco/permit3/blob/ee41d83f499e3ce05dd4295d7aae4cab01644b93/src/interfaces/IPermit3.sol).

## Prerequisites

* A TypeScript project with `viem` installed.
* A Base RPC URL and a viem account that can sign transactions and typed data.
* At least 1 USDC and enough ETH on Base for gas.
* An existing USDC allowance of at least 1 USDC from the owner to Permit3. This token approval is separate from the signed permit.
* A recipient address you control or intend to pay.

The [Permit3 deployment record](https://github.com/eco/permit3#deployment-information) lists the address used below. Check the deployment and approval target before approving tokens.

## Sign and execute

The code reads `hashChainPermits` from the contract instead of recreating its hashing algorithm. The permit identifies Base (`8453`) as the execution chain, while the EIP-712 domain uses the fixed chain ID `1` required by Permit3.

```typescript theme={null}
import { randomBytes } from "node:crypto";
import {
  createPublicClient,
  createWalletClient,
  erc20Abi,
  http,
  pad,
  parseAbi,
  parseUnits,
  toHex,
  type Account,
  type Address,
} from "viem";
import { base } from "viem/chains";

const permit3 = "0xEc00030C0000245E27d1521Cc2EE88F071c2Ae34";
const usdc = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
const permit3Abi = parseAbi([
  "struct AllowanceOrTransfer { uint48 modeOrExpiration; bytes32 tokenKey; address account; uint160 amountDelta; }",
  "struct ChainPermits { uint64 chainId; AllowanceOrTransfer[] permits; }",
  "function hashChainPermits(ChainPermits chainPermits) pure returns (bytes32)",
  "function permit(address owner, bytes32 salt, uint48 deadline, uint48 timestamp, AllowanceOrTransfer[] permits, bytes signature)",
]);

export async function transferUsdc(
  account: Account,
  recipient: Address,
  rpcUrl: string,
) {
  const publicClient = createPublicClient({ chain: base, transport: http(rpcUrl) });
  const walletClient = createWalletClient({ account, chain: base, transport: http(rpcUrl) });
  const amount = parseUnits("1", 6);

  try {
    if ((await publicClient.getChainId()) !== base.id) {
      throw new Error("The RPC must connect to Base mainnet.");
    }
    const allowance = await publicClient.readContract({
      address: usdc,
      abi: erc20Abi,
      functionName: "allowance",
      args: [account.address, permit3],
    });
    if (allowance < amount) {
      throw new Error("Approve at least 1 USDC to Permit3 before continuing.");
    }

    const permits = [{
      modeOrExpiration: 0,
      tokenKey: pad(usdc, { size: 32 }),
      account: recipient,
      amountDelta: amount,
    }];
    const merkleRoot = await publicClient.readContract({
      address: permit3,
      abi: permit3Abi,
      functionName: "hashChainPermits",
      args: [{ chainId: BigInt(base.id), permits }],
    });
    const timestamp = Math.floor(Date.now() / 1000);
    const deadline = timestamp + 300;
    const salt = toHex(randomBytes(32));
    const signature = await walletClient.signTypedData({
      domain: { name: "Permit3", version: "1", chainId: 1, verifyingContract: permit3 },
      primaryType: "Permit3",
      types: {
        Permit3: [
          { name: "owner", type: "address" },
          { name: "salt", type: "bytes32" },
          { name: "deadline", type: "uint48" },
          { name: "timestamp", type: "uint48" },
          { name: "merkleRoot", type: "bytes32" },
        ],
      },
      message: { owner: account.address, salt, deadline, timestamp, merkleRoot },
    });
    const { request } = await publicClient.simulateContract({
      account,
      address: permit3,
      abi: permit3Abi,
      functionName: "permit",
      args: [account.address, salt, deadline, timestamp, permits, signature],
    });
    const gas = await publicClient.estimateContractGas(request);
    const hash = await walletClient.writeContract({ ...request, gas });
    const receipt = await publicClient.waitForTransactionReceipt({ hash });
    if (receipt.status !== "success") {
      throw new Error(`Permit3 transfer reverted: ${hash}`);
    }
    return { hash, receipt };
  } catch (error) {
    throw new Error("Could not complete the Permit3 transfer.", { cause: error });
  }
}
```

The function submits a mainnet transfer when you call it. Simulation and gas estimation run first, but state can change before the transaction is mined. Check the confirmed transfer and recipient balance in your application.

## Multi-chain permits and witnesses

For multi-chain authorization, hash each chain's permits, build the matching Merkle root and proofs, and use the overload that takes `ChainPermits` plus a proof array. Submit each chain's transaction separately. Use the [repository's cross-chain guide](https://github.com/eco/permit3/blob/ee41d83f499e3ce05dd4295d7aae4cab01644b93/docs/guides/cross-chain-permit.md) with the matching interface.

Witness permits add signed application data. Validate that data against the action your application performs; a signed hash alone does not enforce those conditions.

## Troubleshooting

| Problem | Check |
| - | - |
| Signature rejected | Owner, verifying contract, fixed domain chain ID `1`, and the contract-derived permit hash |
| Permit expired | Deadline and signing clock |
| Nonce already used | Use a new cryptographically random salt for each new authorization |
| Token transfer fails | Owner balance, token allowance to Permit3, and recipient |
| RPC or simulation fails | Chain ID, deployed code, and the underlying error before retrying |

## Next steps

Use [Permit3 API funding](/api-reference/v1/submit-permit3) when funding a Routes quote through the API. Read [Permit3 concepts](/routes/primitives/permit3/overview) for allowance modes and multi-chain behavior.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.