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

# Funding methods

> Three ways to fund a Gateway deposit vault: ERC-3009 transferWithAuthorization, EIP-2612 Permit, and direct transfer

You have three ways to fund a Gateway deposit vault. The right choice depends on whether your users have gas and which token standards they support.

| Method                                                                      | User txs | User needs gas? | When to use                                                  |
| --------------------------------------------------------------------------- | -------- | --------------- | ------------------------------------------------------------ |
| [ERC-3009 `transferWithAuthorization`](#erc-3009-transferwithauthorization) | 0        | No              | **Recommended**: single-tx gasless (USDC-native)             |
| [EIP-2612 Permit](#eip-2612-permit)                                         | 0        | No              | Fallback for tokens that support `permit()` but not ERC-3009 |
| [Direct transfer](#direct-transfer)                                         | 1        | Yes             | Sender already has source-chain gas                          |

Under the gasless flows the user pays nothing onchain: the **deposit address service** pays gas for the source-chain transaction, and the **Eco solver service** pays gas for final fulfillment on the destination chain.

For both gasless methods, Eco's operator wallet submits the onchain transactions. Requests are validated, then routed through a serialized transaction queue to prevent operator-wallet nonce collisions under concurrent load.

Prerequisite for all three: call `POST /circle-gateway/v2/depositAddresses` to create a quoted vault. The response includes the `vaultAddress` to fund, the quoted `amount`, and the quote `deadline`. The vault must receive at least `amount` USDC before `deadline`.

## ERC-3009 transferWithAuthorization

**Recommended.** USDC's native gasless path. User signs a `TransferWithAuthorization` offchain; the operator wallet submits a single `transferWithAuthorization()` call that moves tokens directly from the signer to the vault.

USDC's EIP-712 domain `name` varies per chain, `"USDC"` on Base Sepolia, `"USD Coin"` elsewhere. Use the correct value for the chain you're signing on.

```ts theme={null}
import { toHex } from 'viem'

const value = BigInt(amount) // the quoted amount from POST /circle-gateway/v2/depositAddresses
const nonce = toHex(crypto.getRandomValues(new Uint8Array(32)))
const validAfter = '0'
const validBefore = String(Math.floor(Date.now() / 1000) + 3600)

const signature = await signTypedDataAsync({
  domain: { name: 'USD Coin', version: '2', chainId, verifyingContract: USDC_ADDRESS },
  types: {
    TransferWithAuthorization: [
      { name: 'from', type: 'address' },
      { name: 'to', type: 'address' },
      { name: 'value', type: 'uint256' },
      { name: 'validAfter', type: 'uint256' },
      { name: 'validBefore', type: 'uint256' },
      { name: 'nonce', type: 'bytes32' },
    ],
  },
  primaryType: 'TransferWithAuthorization',
  message: { from: owner, to: vaultAddress, value, validAfter: BigInt(validAfter), validBefore: BigInt(validBefore), nonce },
})

const { data } = await fetch(`${BASE_URL}/circle-gateway/v1/gasless/transferWithAuthorization`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    chainId,
    from: owner,
    to: vaultAddress,
    value: value.toString(),
    validAfter,
    validBefore,
    nonce,
    signature,
  }),
}).then((r) => r.json())

// data.id → poll GET /circle-gateway/v1/gasless/jobs/{id}
```

The authorization `value` must be at least the quoted `amount`, the vault's quote `deadline` must not have passed, and `validBefore` must be at least a minute in the future. Status transitions `PENDING → COMPLETED` (or `FAILED`).

## EIP-2612 Permit

Fallback when the token supports `permit()` but not ERC-3009. User signs a Permit offchain with the vault as spender; the operator wallet submits the transactions that pull the tokens into the vault on their behalf via `POST /circle-gateway/v1/gasless/permit`.

```ts theme={null}
const value = BigInt(amount) // the quoted amount from POST /circle-gateway/v2/depositAddresses
const deadline = BigInt(Math.floor(Date.now() / 1000) + 3600)

const signature = await signTypedDataAsync({
  domain: { name: 'USD Coin', version: '2', chainId, verifyingContract: USDC_ADDRESS },
  types: {
    Permit: [
      { name: 'owner', type: 'address' },
      { name: 'spender', type: 'address' },
      { name: 'value', type: 'uint256' },
      { name: 'nonce', type: 'uint256' },
      { name: 'deadline', type: 'uint256' },
    ],
  },
  primaryType: 'Permit',
  message: { owner, spender: vaultAddress, value, nonce, deadline },
})

const { data } = await fetch(`${BASE_URL}/circle-gateway/v1/gasless/permit`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    chainId,
    owner,
    depositAddress: vaultAddress,
    value: value.toString(),
    deadline: deadline.toString(),
    signature,
  }),
}).then((r) => r.json())

// data.id → poll GET /circle-gateway/v1/gasless/jobs/{id}
```

Response is `202 Accepted` with `{ id, status: "PENDING" }`. Status transitions `PENDING → PERMIT_SENT → COMPLETED` (or `FAILED`).

## Direct transfer

The sender submits a vanilla ERC-20 `transfer()` to the vault address. Balance monitoring detects when the vault balance reaches the quoted `amount` and publishes the deposit intent.

```ts theme={null}
import { createWalletClient, http, erc20Abi } from 'viem'
import { base } from 'viem/chains'

const walletClient = createWalletClient({ chain: base, transport: http() })

await walletClient.writeContract({
  address: USDC_ADDRESS,
  abi: erc20Abi,
  functionName: 'transfer',
  args: [vaultAddress, BigInt(amount)], // the quoted amount
})
```

No signature, no API call beyond the initial `POST` to obtain the vault. The sender pays gas.

## Polling

For gasless transfers, poll the relayer job:

```ts theme={null}
const { data } = await fetch(`${BASE_URL}/circle-gateway/v1/gasless/jobs/${jobId}`).then((r) => r.json())
// data: { id, status, permitTxHash?, transferTxHash?, amount?, error? }
```

For any method, poll the vault status to track the deposit itself:

```ts theme={null}
const { data } = await fetch(
  `${BASE_URL}/circle-gateway/v2/depositAddresses/${vaultAddress}?sourceChainId=${chainId}`,
).then((r) => r.json())
// data: { vaultAddress, amount, deadline, state, intentHash, sourceChainId }
```

Typical client: poll every few seconds until the job is `COMPLETED`/`FAILED` and the vault `state` is `PUBLISHED`, with a 1–2 minute overall timeout. See the [state table](/addresses/gateway-fast-deposits#step-3-poll-for-completion) for all vault states.

## Validation

The service rejects gasless requests before queueing if any of:

* `to` isn't a known quoted vault (or legacy deposit address) on `chainId`
* The quoted vault isn't in a fundable state, or its quote `deadline` has passed
* USDC isn't configured for `chainId`
* Permit `deadline` is in the past, ERC-3009 `validBefore` is less than a minute in the future, or `validAfter` is in the future
* `value` is below the vault's quoted `amount`, or the signer's USDC balance is less than `value`
* Signature, nonce, or address fields don't match the expected shapes (see [Validation rules](/api-reference/programmable-addresses/gateway-overview#validation-rules))

On failure after queueing, the job record captures the error and moves to `FAILED`; the transaction is never retried automatically.
