# How the Deposit Contract Works Source: https://docs.eco.com/addresses/architecture/deposit-contract Programmable address contract that receives tokens and executes cross-chain actions The Deposit Contract is the programmable address smart contract that receives tokens and executes the programmed action. Each contract is bound to a specific destination address and handles the conversion of deposits into Routes intents. ## Purpose Each programmable address contract serves a single destination: * **Token reception**: Receives tokens from users * **Action execution**: Converts deposits into cross-chain intents via Portal * **Refund handling**: Returns funds for expired/unfulfilled intents ## Functions ### initialize Called by the factory immediately after deployment. ```solidity theme={null} function initialize( bytes32 _destinationAddress, address _depositor ) external ``` **Parameters:** * `_destinationAddress`: Destination wallet that will receive funds * `_depositor`: Address authorized to trigger refunds ### createIntent Creates a cross-chain intent for the specified amount. ```solidity theme={null} function createIntent(uint256 amount) external returns (bytes32 intentHash) ``` **Parameters:** * `amount`: Amount of tokens to include in the intent **Returns:** * `intentHash`: Unique identifier for the created intent ### refund Triggers a refund for an expired intent. ```solidity theme={null} function refund( bytes32 routeHash, Reward calldata reward ) external ``` **Parameters:** * `routeHash`: Hash of the route parameters * `reward`: Reward structure for the intent ### View Functions ```solidity theme={null} function destinationAddress() external view returns (bytes32) function depositor() external view returns (address) ``` ## Reward Structure The refund function requires a Reward struct: ```solidity theme={null} struct Reward { uint64 deadline; address creator; address prover; uint256 nativeAmount; TokenAmount[] tokens; } struct TokenAmount { address token; uint256 amount; } ``` | Field | Type | Description | | -------------- | -------------- | ------------------------------ | | `deadline` | uint64 | Intent expiry timestamp | | `creator` | address | Intent creator (this contract) | | `prover` | address | Prover contract address | | `nativeAmount` | uint256 | Native token amount | | `tokens` | TokenAmount\[] | Token amounts in reward | ## Events ### IntentCreated ```solidity theme={null} event IntentCreated( bytes32 indexed intentHash, uint256 amount, address indexed caller ) ``` Emitted when a new intent is created from a deposit. | Field | Type | Indexed | Description | | ------------ | ------- | ------- | -------------------------------------- | | `intentHash` | bytes32 | Yes | Unique intent identifier | | `amount` | uint256 | No | Token amount in the intent | | `caller` | address | Yes | Address that triggered intent creation | ### IntentRefunded ```solidity theme={null} event IntentRefunded( bytes32 indexed routeHash, address indexed refundee ) ``` Emitted when funds are refunded for an expired intent. | Field | Type | Indexed | Description | | ----------- | ------- | ------- | --------------------------------- | | `routeHash` | bytes32 | Yes | Route hash of the refunded intent | | `refundee` | address | Yes | Address receiving the refund | ## Errors ### AlreadyInitialized ```solidity theme={null} error AlreadyInitialized() ``` Thrown when `initialize()` is called more than once. ### NotInitialized ```solidity theme={null} error NotInitialized() ``` Thrown when calling functions before initialization. ### OnlyFactory ```solidity theme={null} error OnlyFactory() ``` Thrown when non-factory address calls `initialize()`. ### InvalidDepositor ```solidity theme={null} error InvalidDepositor() ``` Thrown when non-depositor attempts to trigger refund. ### NoDepositorSet ```solidity theme={null} error NoDepositorSet() ``` Thrown when depositor address is zero during initialization. ### InsufficientBalance ```solidity theme={null} error InsufficientBalance(uint256 requested, uint256 available) ``` Thrown when intent amount exceeds contract's token balance. ### ZeroAmount ```solidity theme={null} error ZeroAmount() ``` Thrown when attempting to create intent with zero amount. ### ReentrancyGuardReentrantCall ```solidity theme={null} error ReentrancyGuardReentrantCall() ``` Thrown on reentrancy attempt (security protection). # How the Address Factory Works Source: https://docs.eco.com/addresses/architecture/factory-contract Factory contract that generates and deploys deterministic programmable addresses The Address Factory is a smart contract that generates deterministic programmable addresses and deploys their contracts on demand. Each factory is configured for a specific source-destination token pair. ## Purpose The factory serves as the entry point for the programmable address system: * **Address generation**: Computes programmable addresses without onchain transactions * **Contract deployment**: Deploys programmable address contracts when funds are received * **Configuration storage**: Holds immutable settings for all programmable address contracts it creates ## Constructor ```solidity theme={null} constructor( uint64 _destinationChain, address _sourceToken, bytes32 _targetToken, address _portalAddress, address _proverAddress, bytes32 _destinationPortal, uint64 _intentDeadlineDuration ) ``` | Parameter | Type | Description | | ------------------------- | ------- | ------------------------------------------- | | `_destinationChain` | uint64 | Target chain ID | | `_sourceToken` | address | Token on source chain | | `_targetToken` | bytes32 | Token on destination (bytes32 for cross-VM) | | `_portalAddress` | address | Routes Portal contract | | `_proverAddress` | address | Cross-chain prover | | `_destinationPortal` | bytes32 | Portal on destination chain | | `_intentDeadlineDuration` | uint64 | Intent validity period | ## Functions ### getDepositAddress Computes the deterministic programmable address for a given destination. ```solidity theme={null} function getDepositAddress(bytes32 destinationAddress) external view returns (address) ``` **Parameters:** * `destinationAddress`: destination wallet address as bytes32 (cross-VM compatible) **Returns:** * The EVM address where the user should send tokens ### isDeployed Checks if a programmable address contract has been deployed for a destination. ```solidity theme={null} function isDeployed(bytes32 destinationAddress) external view returns (bool) ``` ### deploy Deploys a new programmable address contract for the specified destination. ```solidity theme={null} function deploy( bytes32 destinationAddress, address depositor ) external returns (address deployed) ``` **Parameters:** * `destinationAddress`: destination wallet address as bytes32 (cross-VM compatible) * `depositor`: Address authorized to trigger refunds **Returns:** * Address of the deployed programmable address contract ### getConfiguration Returns all factory configuration values. ```solidity theme={null} function getConfiguration() external view returns ( uint64 destinationChain, address sourceToken, bytes32 targetToken, address portalAddress, address proverAddress, bytes32 destinationPortal, uint64 intentDeadlineDuration ) ``` ### Configuration Getters Individual getters for each configuration value: * `DESTINATION_CHAIN()` - Target chain ID * `SOURCE_TOKEN()` - Source token address * `TARGET_TOKEN()` - Target token (bytes32) * `PORTAL_ADDRESS()` - Portal contract address * `PROVER_ADDRESS()` - Prover contract address * `DESTINATION_PORTAL()` - Destination portal (bytes32) * `INTENT_DEADLINE_DURATION()` - Deadline duration * `DEPOSIT_IMPLEMENTATION()` - Implementation contract address ## Events ### DepositContractDeployed ```solidity theme={null} event DepositContractDeployed( bytes32 indexed destinationAddress, address indexed depositAddress ) ``` Emitted when a new programmable address contract is deployed. | Field | Type | Indexed | Description | | -------------------- | ------- | ------- | ------------------------------------- | | `destinationAddress` | bytes32 | Yes | Destination wallet address (cross-VM) | | `depositAddress` | address | Yes | Deployed contract address | ## Errors ### ContractAlreadyDeployed ```solidity theme={null} error ContractAlreadyDeployed(address depositAddress) ``` Thrown when attempting to deploy a contract that already exists. ### InvalidDeadlineDuration ```solidity theme={null} error InvalidDeadlineDuration() ``` Thrown during construction if deadline duration is zero. ### InvalidPortalAddress ```solidity theme={null} error InvalidPortalAddress() ``` Thrown during construction if Portal address is zero. ### InvalidProverAddress ```solidity theme={null} error InvalidProverAddress() ``` Thrown during construction if Prover address is zero. ### InvalidSourceToken ```solidity theme={null} error InvalidSourceToken() ``` Thrown during construction if source token address is invalid. ### InvalidTargetToken ```solidity theme={null} error InvalidTargetToken() ``` Thrown during construction if target token is invalid. ### InvalidDestinationPortal ```solidity theme={null} error InvalidDestinationPortal() ``` Thrown during construction if destination Portal is zero bytes. # Funding methods Source: https://docs.eco.com/addresses/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. # Fast Deposits for Circle Gateway Source: https://docs.eco.com/addresses/gateway-fast-deposits Deposit USDC into Circle Gateway from any supported EVM chain Fast Deposits for Circle Gateway lets users fund a [Circle Gateway](https://developers.circle.com/gateway) balance in seconds from any Eco-supported chain, regardless of the source chain's finality. Eco fills each deposit and settles the source-chain side in the background, so you ship no custom contracts and never take custody of user funds. The user can deposit as a regular transfer to an address or gaslessly. For the full REST endpoint reference, see [Gateway API](/api-reference/programmable-addresses/gateway-overview) under the API Reference tab. Three properties define the system: * **Non-custodial**: Eco never holds user funds. USDC moves from the user's wallet into a dedicated source-chain vault that can only fund the quoted deposit intent. If the intent is never fulfilled, funds return to the `refundRecipient` (defaults to the depositor). * **Permissionless**: Any wallet or application can request a deposit vault via the API. No whitelisting, no KYC gate. * **Per-intent vaults**: Each vault is created for every intent and carries a quote `deadline`. The vault address is derived deterministically from the quoted intent. ## Supported chains | Source chains | Destination | | ------------------------ | ------------------ | | Base, Optimism, Arbitrum | Gateway on Polygon | Any Gateway-enabled chain can be added. Reach out if you need a chain that isn't listed. ## Environment Base URL: `https://api.eco.com` ## How it works How deposit addresses power gasless settlement into Circle Gateway: the app requests a deposit address from the Eco API, the user funds it, the first deposit triggers deploy and createIntent, and a solver fulfills to credit the Gateway balance. Each `POST` creates a quoted Routes intent and returns its vault address. When the vault's USDC balance reaches the quoted `amount`, Eco publishes the intent and a solver fulfills it on Polygon by calling Gateway's `depositFor` for the recipient. The solver is repaid from the vault once fulfillment is proven. ## Quickstart `POST /circle-gateway/v2/depositAddresses` with the source chain, amount, recipient, and depositor. Collect an ERC-3009 signature and call `POST /circle-gateway/v1/gasless/transferWithAuthorization`, or just do a vanilla ERC-20 transfer. Send at least the quoted `amount` before the `deadline`. Hit `GET /circle-gateway/v2/depositAddresses/{vaultAddress}` until `state` is `PUBLISHED`. For gasless transfers you can also poll `GET /circle-gateway/v1/gasless/jobs/{id}`. ### Step 1: Create a quoted deposit vault ```bash theme={null} curl -X POST https://api.eco.com/circle-gateway/v2/depositAddresses \ -H "Content-Type: application/json" \ -d '{ "sourceChainId": 8453, "amount": "1000000", "recipient": "0xRecipientOnGateway", "depositor": "0xYourWalletAddress" }' ``` | Field | Type | Required | Description | | ----------------- | ------ | -------- | ------------------------------------------------------------- | | `sourceChainId` | number | Yes | Source chain (one of the supported chains above) | | `amount` | string | Yes | Source-chain USDC amount in base units (6 decimals) | | `recipient` | string | Yes | Recipient credited on Gateway (Polygon) | | `depositor` | string | Yes | Address that will fund the vault | | `refundRecipient` | string | No | Address that receives expiry refunds. Defaults to `depositor` | Response (`201 Created`): ```json theme={null} { "data": { "vaultAddress": "0x...", "amount": "1000000", "deadline": 1798915200 } } ``` `deadline` is the quote expiry in Unix seconds. Fund the vault before it passes. Repeating the same request while the quote is pending and unexpired returns the same `vaultAddress`; a different `amount` (or an expired quote) produces a new vault. ### Step 2: Fund the vault Three ways, pick one: * **ERC-3009 `transferWithAuthorization`** (recommended for gasless UX). Single transaction, no user gas. The authorization `value` must cover the quoted `amount`. * **EIP-2612 Permit.** Two transactions (`permit()` and the pull into the vault), no user gas. * **Direct ERC-20 transfer.** Sender pays gas. #### Recommended: ERC-3009 gasless transfer Have the user sign an EIP-712 `TransferWithAuthorization` over the USDC contract with `to` set to the vault address, then post it: ```bash theme={null} curl -X POST https://api.eco.com/circle-gateway/v1/gasless/transferWithAuthorization \ -H "Content-Type: application/json" \ -d '{ "chainId": 8453, "from": "0xSignerAddress", "to": "0xVaultAddress", "value": "1000000", "validAfter": "0", "validBefore": "1776098178", "nonce": "0x", "signature": "0x<65-byte-eip712-signature>" }' ``` Response (`202 Accepted`): ```json theme={null} { "data": { "id": "", "status": "PENDING" } } ``` See [Funding methods](/addresses/funding-methods) for signing code and field-by-field details. ### Step 3: Poll for completion Poll the vault status (note the required `sourceChainId` query parameter): ```bash theme={null} curl "https://api.eco.com/circle-gateway/v2/depositAddresses/0xVaultAddress?sourceChainId=8453" ``` Response (`200 OK`): ```json theme={null} { "data": { "vaultAddress": "0x...", "amount": "1000000", "deadline": 1798915200, "state": "PUBLISHED", "intentHash": "0x...", "sourceChainId": 8453 } } ``` | State | Meaning | | -------------------- | --------------------------------------------------------------- | | `PENDING` | Vault created, waiting for funds | | `FUNDING_DETECTED` | Vault balance reached the quoted `amount` | | `PUBLISHED` | Deposit intent published; solver fulfillment follows in seconds | | `EXPIRED_UNFUNDED` | Quote `deadline` passed before the vault was funded | | `FAILED` | Publishing failed; funds follow the refund path | | `REFUNDED_BY_USER` | Depositor reclaimed the funds | | `RECOVERY_PUBLISHED` | Recovery service published a recovery intent for the funds | For gasless transfers you can additionally poll `GET /circle-gateway/v1/gasless/jobs/{id}`, which transitions `PENDING` → (`PERMIT_SENT` for Permit only) → `COMPLETED` or `FAILED` and includes `transferTxHash` when complete. The `intentHash` comes from the vault status endpoint above. To confirm the deposit reached Gateway, query Circle Gateway's balance API for the recipient address. ## FAQ Poll `GET /circle-gateway/v2/depositAddresses/{vaultAddress}?sourceChainId={id}` until `state` is `PUBLISHED`, then confirm the credited balance by querying Circle Gateway's balance API for the recipient address. Nothing. `FAILED` means the service couldn't initiate the transfer (e.g. signature invalid, balance dropped, deadline expired). USDC never left the user's wallet. The signature just expires. Submit a new one to retry. The deposit isn't triggered until the vault balance reaches the quoted `amount`, so the vault stays `PENDING`. Top it up before the `deadline`. If the deadline passes first, the quote moves to `EXPIRED_UNFUNDED` and any funds in the vault are recoverable to the `refundRecipient`. The vault status tells you which path the funds took: `FAILED` means publishing didn't complete, `REFUNDED_BY_USER` means the depositor reclaimed the funds, and `RECOVERY_PUBLISHED` means an independent recovery service stepped in. In every non-`PUBLISHED` outcome, funds return to the `refundRecipient`. Refund and recovery are permissionless, so you (or anyone) can also drive them yourself. On Base Sepolia → Polygon Amoy we typically see **20–40 seconds** from submission to Gateway balance update. Time scales with source-chain finality. **ERC-3009 `transferWithAuthorization`**, single transaction, cleaner UX, USDC-native. Use Permit if the token you're working with supports `permit()` but not ERC-3009. Any Gateway-enabled chain. Today: Base, Optimism, Arbitrum. Ask if you need a chain added. While a quote is pending and unexpired, repeating the same request returns the same `vaultAddress`. A different `amount`, a different recipient, or an expired quote produces a new vault. Always send users the address from the latest response. ## Security * Each quote maps to a dedicated vault derived deterministically from the intent it funds. No shared contract state. * USDC in a vault can only fund the quoted deposit intent or be refunded to the `refundRecipient`. It cannot be redirected. * Refunds and recovery are driven by an independent, permissionless service. Funds cannot be stuck. * Full audit reports (Cantina) are available on request. * Eco never holds or touches user funds. # Programmable Addresses Source: https://docs.eco.com/addresses/overview Deterministic addresses that execute pre-programmed actions on funding, no bridge UI, no signature, no contract call. Just an ERC-20 transfer. A **Programmable Address** is a deterministic CREATE2 contract address that executes custom logic on inbound or outbound stablecoin transfers. When funds arrive, its actions run automatically, the sender just makes a normal ERC-20 transfer, and the address handles whatever it's programmed to do: settlement into a preferred token or chain, payment splits, or deposits into a yield vault. Developers program the rules once to streamline their product experience. The model is compatible with both Solana and EVM chains supported by Eco. REST endpoint reference: [Solana Deposit Addresses API](/api-reference/programmable-addresses/solana-overview) · [Gateway Fast Deposits API](/api-reference/programmable-addresses/gateway-overview) ## What ships today | Capability | Source chains | Destination | Use case | | --------------------------------------------------------------- | ------------------------ | ------------------------- | ------------------------------------------------- | | [**Solana Deposit Addresses**](/addresses/solana) | Base | Any Solana wallet | Bridge USDC from EVM to Solana with no bridge UI | | [**Circle Gateway Deposits**](/addresses/gateway-fast-deposits) | Base, Optimism, Arbitrum | Circle Gateway on Polygon | Fast, gasless USDC deposits into Gateway (20–40s) | Both capabilities share the same architecture (a [factory](/addresses/architecture/factory-contract) plus per-address [deposit contracts](/addresses/architecture/deposit-contract)) and use [Routes](/routes/overview) for fulfillment. They differ in source chains, destination, and funding model. ## How it works 1. **Address generation.** Your app calls the API for a deposit address derived from `(depositor, destination, chainId)` via CREATE2. The address is deterministic, same inputs always return the same address. 2. **Token transfer.** The user sends USDC to the address. First deposit triggers contract deployment automatically (no gas until funds actually arrive). 3. **Action execution.** The contract publishes a [Routes intent](/routes/overview) for the deposited amount. 4. **Fulfillment.** Solvers compete to deliver the destination outcome. ## Properties **Permissionless.** Any wallet or app can generate an address via the API. No whitelisting, no KYC at the protocol level. **Non-custodial.** The deposit contract's only operation is `createIntent()`. It cannot be drained, redirected, or upgraded. If the intent expires unfulfilled, an independent permissionless refund service returns the USDC to the depositor. **Immutable.** The address is CREATE2-derived from the inputs. Calling the endpoint again with identical inputs always returns the same address, and the address can be shared before the contract is deployed onchain. ## Funding methods Funds can arrive at a Programmable Address by any of: | Method | User pays gas? | When to use | | ------------------------------------ | -------------- | ------------------------------------------------------------ | | Direct ERC-20 transfer | Yes | Sender already has source-chain gas | | ERC-3009 `transferWithAuthorization` | No | **Recommended** for gasless UX (USDC-native) | | EIP-2612 Permit | No | Fallback for tokens that support `permit()` but not ERC-3009 | See [Funding methods](/addresses/funding-methods) for signing code and field-by-field details. ## Architecture | Contract | Role | | ------------------------------------------------------------ | ---------------------------------------------------------------------------- | | [Factory](/addresses/architecture/factory-contract) | Computes deterministic addresses, deploys deposit contracts on first deposit | | [Deposit contract](/addresses/architecture/deposit-contract) | Per-destination contract, receives tokens, calls `createIntent()` | The factory is configured per source-token / destination-chain pair, with immutable parameters for Portal address, prover address, and intent-deadline duration. # Deposit Addresses for Solana Source: https://docs.eco.com/addresses/solana Create a deposit address that automatically routes USDC from Base to a Solana wallet Deposit addresses for Solana let users receive USDC on a Solana wallet by sending to a deterministic EVM address on Base. The sender makes a normal ERC-20 transfer. No bridge UI, no approval step, no contract interaction required. Looking for gasless deposits into Circle Gateway instead? See [Circle Gateway Deposits](/addresses/gateway-fast-deposits). That's a different product with its own endpoints and supported chains. For the full REST endpoint reference, see [Solana API](/api-reference/programmable-addresses/solana-overview) under the API Reference tab. ## Environment Base URL: `https://deposit-addresses.eco.com` In this quickstart, you will: Request a deposit address for your Solana wallet via the API. Transfer USDC to your deposit address on Base. The deposit address routes funds automatically. Solvers fulfill the intent on Solana. ## Step 1: Generate a Deposit Address Call the API to generate a deposit address for your Solana wallet. ```bash theme={null} curl -X POST https://deposit-addresses.eco.com/api/v1/depositAddresses/solana \ -H "Content-Type: application/json" \ -d '{ "chainId": 8453, "solanaAddress": "YOUR_SOLANA_ADDRESS", "depositor": "YOUR_EVM_ADDRESS" }' ``` **Request parameters:** | Field | Type | Description | | --------------- | ------ | ------------------------------------------------------------------------- | | `chainId` | number | Source chain ID. Currently only Base (`8453`) is supported | | `solanaAddress` | string | Your Solana wallet address (base58, 32-44 characters) | | `depositor` | string | EVM address authorized to claim refunds if the intent expires unfulfilled | **Response (201 Created):** ```json theme={null} { "data": { "addressID": "573c3006-0e7d-4874-add7-57afe6a98ee6", "depositAddressType": "SOLANA", "chainId": 8453, "evmDepositAddress": "0x...", "solanaAddress": "0x...", "depositor": "0x...", "factoryAddress": "0x...", "isDeployed": false, "lastCheckedBalance": "0", "lastBalanceCheckAt": "1970-01-01T00:00:00.000Z", "lastBlockNumber": "0", "createdAt": "2026-01-29T12:00:00.000Z", "updatedAt": "2026-01-29T12:00:00.000Z" } } ``` The `evmDepositAddress` is your deposit address. This is where you'll send tokens. The address is deterministic: repeating the same request always returns the same address, and you can share it before the contract is deployed onchain. The `solanaAddress` in the response is the hex encoding of the base58 address you submitted (a 32-byte public key). ## Step 2: Send Tokens Transfer USDC to your deposit address on Base. The sender makes a normal ERC-20 transfer. No special app or bridge UI required. ```javascript theme={null} import { createWalletClient, http, parseUnits } from 'viem'; import { base } from 'viem/chains'; const walletClient = createWalletClient({ chain: base, transport: http() }); // Transfer USDC to the deposit address const hash = await walletClient.writeContract({ address: USDC_ADDRESS, abi: erc20Abi, functionName: 'transfer', args: [ depositAddress, // evmDepositAddress from API response parseUnits('100', 6) // Amount in token decimals ] }); ``` ## Step 3: Automatic Processing Once tokens arrive at the deposit address, the routing logic executes automatically: 1. **Detects the deposit**: Balance monitoring detects the incoming tokens 2. **Deploys the contract**: If not already deployed, the contract is deployed via the factory 3. **Executes the action**: For cross-chain deposits, `createIntent()` is called 4. **Publishes to Portal**: The intent is published to the Routes Portal 5. **Solver fulfillment**: Solvers compete to fulfill the intent on Solana ## Step 4: Check Status Query the deposit address to check its status. ```bash theme={null} curl https://deposit-addresses.eco.com/api/v1/depositAddresses/evmAddress/0xYOUR_DEPOSIT_ADDRESS ``` **Response:** ```json theme={null} { "data": { "addressID": "573c3006-0e7d-4874-add7-57afe6a98ee6", "depositAddressType": "SOLANA", "chainId": 8453, "evmDepositAddress": "0x...", "solanaAddress": "0x...", "depositor": "0x...", "factoryAddress": "0x...", "isDeployed": true, "lastCheckedBalance": "100000000", "deploymentTxHash": "0x...", "deploymentBlockNumber": "12345678" } } ``` | Field | Description | | -------------------- | ------------------------------------------- | | `isDeployed` | `true` once the contract is deployed | | `lastCheckedBalance` | Current token balance at the address | | `deploymentTxHash` | Transaction hash of the contract deployment | ## Validation rules The API validates input parameters: * **solanaAddress**: Must be a valid base58 Solana address (32-44 characters) * **depositor**: Must be a valid Ethereum address * **chainId**: Must be a number >= 1 ## Error handling Validation failures return `400` with a `validationErrors` map: ```json theme={null} { "statusCode": 400, "createdBy": "ValidationFilter", "validationErrors": { "solanaAddress": "solanaAddress must be a valid base58 Solana address" } } ``` Business errors also return `400`, with an error envelope. If the factory is not configured for the specified chain: ```json theme={null} { "statusCode": 400, "createdBy": "HttpExceptionFilter", "details": { "errorCode": 1016, "errorDesc": "FactoryAddressNotConfiguredForChain", "cause": "FactoryAddressNotConfiguredForChain" } } ``` If the address is not found: ```json theme={null} { "statusCode": 400, "createdBy": "HttpExceptionFilter", "details": { "errorCode": 1014, "errorDesc": "NoSuchDepositAddress", "cause": "NoSuchDepositAddress" } } ``` # Create a quoted Circle Gateway deposit vault Source: https://docs.eco.com/api-reference/circle-gateway/create-a-quoted-circle-gateway-deposit-vault /gateway_fast_deposits_openapi.json post /circle-gateway/v2/depositAddresses Returns a source-chain vault address plus the requested amount and quote deadline. Fund the vault with at least `amount` USDC before `deadline` and Eco publishes the deposit intent. Repeating the same request while the quote is still pending and unexpired returns the same vault. # Get Circle Gateway deposit vault status Source: https://docs.eco.com/api-reference/circle-gateway/get-circle-gateway-deposit-vault-status /gateway_fast_deposits_openapi.json get /circle-gateway/v2/depositAddresses/{vaultAddress} Returns the persisted quoted gateway intent state and metadata for a vault address. # Generate Solana deposit address Source: https://docs.eco.com/api-reference/deposit-addresses/generate-solana-deposit-address /solana_deposit_addresses_openapi.json post /api/v1/depositAddresses/solana # Get deposit address record Source: https://docs.eco.com/api-reference/deposit-addresses/get-deposit-address-record /solana_deposit_addresses_openapi.json get /api/v1/depositAddresses/evmAddress/{evmAddress} # Poll gasless job status Source: https://docs.eco.com/api-reference/gasless-funding/poll-gasless-job-status /gateway_fast_deposits_openapi.json get /circle-gateway/v1/gasless/jobs/{id} # Queue EIP-2612 Permit transfer Source: https://docs.eco.com/api-reference/gasless-funding/queue-eip-2612-permit-transfer /gateway_fast_deposits_openapi.json post /circle-gateway/v1/gasless/permit Fallback for tokens that support `permit()` but not ERC-3009. The user signs a Permit with the vault as spender; the operator wallet submits the transactions that pull the tokens into the vault. # Queue ERC-3009 gasless transfer Source: https://docs.eco.com/api-reference/gasless-funding/queue-erc-3009-gasless-transfer /gateway_fast_deposits_openapi.json post /circle-gateway/v1/gasless/transferWithAuthorization Submit a signed `TransferWithAuthorization` for the operator wallet to broadcast. Single onchain tx, USDC-native. For a quoted vault, `value` must cover the quoted `amount` and the quote must not be expired. # API Reference Source: https://docs.eco.com/api-reference/introduction Eco's REST API. Five endpoint groups. No authentication required. Pass a `dAppID` in the request body for attribution. Each API group is documented from its OpenAPI spec, request/response shapes, validation rules, and error handling are authoritative. ## API groups | API | Purpose | Base URL | | --------------------------------------------------------------------------------- | ------------------------------------------------------------------------ | ----------------------------------- | | [Quotes & Intents](#quotes) | Get pricing for a candidate intent, query intent status | `https://quotes.eco.com` | | [Solana Deposit Addresses](/api-reference/programmable-addresses/solana-overview) | Generate Solana deposit addresses, query records | `https://deposit-addresses.eco.com` | | [Gateway Fast Deposits](/api-reference/programmable-addresses/gateway-overview) | Create quoted Gateway deposit vaults, queue gasless funding, poll status | `https://api.eco.com` | | [Solver Registry](#solver-registry) | Register and update solver endpoints (signed requests) | `https://quotes.eco.com` | | [Solver Interface](#solver-interface) | Solver-implemented endpoints Eco calls for quoting | Solver-hosted | ## Quotes and intents Used by integrators to get quotes from solvers and track fulfillment. | Endpoint | Purpose | | ----------------------------------- | ---------------------------------------- | | `POST /api/v3/quotes/single` | Best single quote for a candidate intent | | `POST /api/v3/quotes/exactIn` | Exact-input quotes | | `POST /api/v3/quotes/exactOut` | Exact-output quotes | | `POST /api/v3/intents/intentStatus` | Status of one or more intents | | `POST /api/v3/intents/status` | Alternate status endpoint | No authentication required. Pass an identifier as `dAppID` in the request body for attribution. ## Programmable addresses Two products with separate REST surfaces: * [**Solana Deposit Addresses API**](/api-reference/programmable-addresses/solana-overview), generate Solana deposit addresses and query their records * [**Gateway Fast Deposits API**](/api-reference/programmable-addresses/gateway-overview), create quoted Gateway deposit vaults and gasless funding endpoints ## Solver registry For solvers registering with Eco. Requests are signed. | Endpoint | Purpose | | -------------------------------------------- | --------------------------- | | `POST /api/v1/solverRegistry/registerSolver` | Register a new solver | | `PATCH /api/v1/solverRegistry/updateSolver` | Update solver configuration | → Full guide: [Becoming an Eco solver](/recipes/become-a-solver) ## Solver interface Endpoints **solvers implement** that Eco's quote aggregator calls. | Endpoint | Required for | | ---------------------------- | ------------ | | `POST /api/v2/quote` | All solvers | | `POST /api/v2/quote/reverse` | All solvers | → Full guide: [Becoming an Eco solver](/recipes/become-a-solver) ## Environments | Environment | Quotes / Intents | Gateway Fast Deposits | Solana Deposit Addresses | | ----------- | ------------------------ | --------------------- | ----------------------------------- | | Mainnet | `https://quotes.eco.com` | `https://api.eco.com` | `https://deposit-addresses.eco.com` | # Gateway Fast Deposits API Source: https://docs.eco.com/api-reference/programmable-addresses/gateway-overview REST surface for creating quoted Circle Gateway deposit vaults, queueing gasless funding via ERC-3009, and polling vault and job status. REST surface for [Gateway Fast Deposits](/addresses/gateway-fast-deposits). Create a quoted vault for a specific `(sourceChainId, amount, recipient, depositor)`; fund it gaslessly via ERC-3009 or EIP-2612 Permit, or with a direct transfer; USDC settles into the user's Circle Gateway balance on Polygon. ## Base URL `https://api.eco.com` All responses are wrapped as `{ "data": ... }` unless otherwise noted. ## Validation rules * EVM addresses: must pass `isEthereumAddress` (EIP-55 / lowercase hex) * `sourceChainId` / `chainId`: integer ≥ 1 * `amount` / `value` / `validAfter` / `validBefore`: non-negative integer strings * `nonce`: 0x-prefixed 32-byte hex (64 hex chars) * `signature`: 0x-prefixed 65-byte hex (130 hex chars) ## Endpoints Use the sidebar to navigate to each endpoint. Two categories: **Circle Gateway**, create a quoted deposit vault, poll its state. Vault state transitions: `PENDING` → `FUNDING_DETECTED` → `PUBLISHED`, with `EXPIRED_UNFUNDED`, `FAILED`, `REFUNDED_BY_USER`, and `RECOVERY_PUBLISHED` as the non-happy-path outcomes. **Gasless funding**, submit a signed ERC-3009 authorization or EIP-2612 Permit; poll the resulting relayer job via `GET /circle-gateway/v1/gasless/jobs/{id}`. Job status transitions: `PENDING` → (`PERMIT_SENT` for Permit only) → `COMPLETED` | `FAILED`. # Solana Deposit Addresses API Source: https://docs.eco.com/api-reference/programmable-addresses/solana-overview REST surface for generating Solana deposit addresses (a deterministic EVM address on Base that auto-bridges USDC to a Solana wallet) and querying their records. REST surface for [Solana deposit addresses](/addresses/solana). One deterministic EVM address per `(depositor, solanaAddress)` pair on Base; USDC sent to it auto-routes to the Solana wallet. ## Base URL `https://deposit-addresses.eco.com` All responses are wrapped as `{ "data": ... }` unless otherwise noted. ## Authentication No authentication required. ## Validation rules * `solanaAddress`: base58, 32-44 chars, matching `^[1-9A-HJ-NP-Za-km-z]{32,44}$` * EVM addresses: must pass `isEthereumAddress` (EIP-55 / lowercase hex) * `chainId`: integer >= 1 Validation failures return `400` with a `validationErrors` map. Business errors (unknown address, unconfigured chain) also return `400`, with a `details` envelope carrying `errorCode` and `errorDesc`. ## Endpoints Use the sidebar to navigate to each endpoint: **Deposit Addresses**: generate a Solana deposit address, look up its record. # Get Exact In Quotes Source: https://docs.eco.com/api-reference/quotes-v3/get-exact-in-quotes /openapi.json post /api/v3/quotes/exactIn Retrieve exact in quotes from solvers for a given intent. Exact in quotes allow you to specify what you want to offer and get quotes for what will be received and can only have transfer calls. # Get Exact Out Quotes Source: https://docs.eco.com/api-reference/quotes-v3/get-exact-out-quotes /openapi.json post /api/v3/quotes/exactOut Retrieve exact out quotes from solvers for a cross-chain token swap. Returns detailed pricing, fees, contract addresses, and estimated fulfillment time for the requested swap. # Get Intent Status Source: https://docs.eco.com/api-reference/quotes-v3/get-intent-status /openapi.json post /api/v3/intents/intentStatus Query the current status of a cross-chain intent transaction. Provide one of: intentHash, intentCreatedHash, or fulfillmentHash to track the intent's progress through the execution lifecycle. # Get Intent Status Array Source: https://docs.eco.com/api-reference/quotes-v3/get-intent-status-array /openapi.json post /api/v3/intents/status Query the status of multiple intents created in the same transaction. Returns an array of intent statuses ordered by creation event log index. Provide one of: intentHash, intentCreatedHash, or fulfillmentHash. Limited to 100 results. # Get Single Quote Source: https://docs.eco.com/api-reference/quotes-v3/get-single-quote /openapi.json post /api/v3/quotes/single Retrieve the best quote for a cross-chain single token swap. Returns detailed pricing, fees, contract addresses, and estimated fulfillment time for the requested swap. This endpoint should only be used for intents intended to be published onchain. # Post apiv2quote Source: https://docs.eco.com/api-reference/quotev2/post-apiv2quote /solver_openapi.json post /api/v2/quote # Post apiv2quotereverse Source: https://docs.eco.com/api-reference/quotev2/post-apiv2quotereverse /solver_openapi.json post /api/v2/quote/reverse # Register Solver Source: https://docs.eco.com/api-reference/solver-registration/register-solver /openapi_1.json post /api/v1/solverRegistry/registerSolver Register a new solver. Solvers provide quotes and execute intents on behalf of users. This endpoint requires authentication via request signature headers. # Update Solver Source: https://docs.eco.com/api-reference/solver-registration/update-solver /openapi_1.json patch /api/v1/solverRegistry/updateSolver Update an existing registered solver configuration. Allows updating endpoints, supported routes, and execution types. This endpoint requires authentication via request signature headers from the solver owner. # Glossary Source: https://docs.eco.com/concepts/glossary Definitions of every term used across Eco's docs, in one place. | Term | Definition | | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Account abstraction (AA)** | Smart-contract accounts that allow flexible transaction rules: batching, social recovery, gas abstraction, multi-sig. | | **Crowd Liquidity** | Permissionless pool of stablecoin liquidity that solvers can flash-borrow from to fulfill larger intents than their reserves. | | **CCTP** | Circle's Cross-Chain Transfer Protocol. Native USDC movement via burn-and-mint with attestation. Used by Eco's [CCTP Prover](/routes/architecture/provers/cctp). | | **Destination call** | An onchain action included in an intent's route, executed atomically with the transfer on the destination chain. | | **ERC-3009** | `transferWithAuthorization`, USDC's native gasless transfer standard. Single-tx, signature-based. | | **ERC-7683** | Standard for cross-chain order protocols. Eco's Portal implements both origin and destination settler interfaces. | | **Executor** | Stateless contract that executes user-specified calls on the destination chain in isolation from the Portal's storage. | | **Flash Intents** | A configurable Routes mode that settles same-chain stablecoin orders atomically using the user's own funds. Powered by `flashFulfill` via the [Local Prover](/routes/architecture/provers/local). | | **flashFulfill** | Same-chain atomic operation that fulfills an intent using the vault's own funds, no solver required. Enabled by the Local Prover. The mechanism behind **Flash Intents**. | | **Gateway** | Circle Gateway, Circle's USDC orchestration product. Eco routes deposits into Gateway via [Gateway Fast Deposits](/addresses/gateway-fast-deposits). | | **Intent** | A signed declaration of a desired outcome on a destination chain, fulfilled by a solver. | | **Orchestration mode** | Intent fulfillment without a solver: the user's vaulted funds route through underlying infrastructure at gas cost. | | **Permit3** | Multi-chain token-permission protocol. One signature authorizes spending across multiple chains via Unbalanced Merkle Trees. | | **Portal** | Eco's main contract on each chain. Non-upgradable factory that handles intent publishing, fulfillment, and reward release. | | **Programmable Address** | A deterministic CREATE2 address with pre-programmed actions. When funds arrive, the actions execute automatically. | | **Programmable Transaction** | A single transaction with embedded decision logic, reads state, picks paths, executes optimally. Built on [Sauce](/transactions/overview). | | **Prover** | Source-chain contract that verifies an intent was fulfilled on the destination chain. User-selectable per intent. | | **Resource lock** | A per-operation reservation of funds that releases on cryptographic proof. Eco's vaults are resource locks. | | **Routes** | Eco's intent-based product for moving and swapping stablecoins across chains. | | **Sauce** | The protocol that powers [Programmable Transactions](/transactions/overview). | | **Settlement mode** | Intent fulfillment by a solver fronting capital, the default execution path. | | **Solver** | An independent operator that fulfills intents in exchange for the source-chain reward. | | **Storage proof** | A cryptographic proof that specific data exists in a blockchain's state, used by some provers for trust-minimized verification. | | **Supertransaction** | A unit of execution in [Programmable Transactions](/transactions/overview). One transaction, multiple contracts, dynamic decisions, atomic outcome. | | **Vault** | Per-intent escrow contract. Deterministic, non-custodial, releases on proof. | # Intents Source: https://docs.eco.com/concepts/intents An intent is a signed declaration of a desired outcome on a destination chain. Solvers compete to fulfill it; provers verify execution. An **intent** is a signed declaration of a desired outcome on a destination chain. Instead of specifying a route ("call this contract, then this one"), the user specifies the result ("I want 1000 USDT on Base in exchange for 1000 USDC on Optimism"). [Solvers](/concepts/solvers) compete to deliver the result; [provers](/concepts/provers) verify it happened. ## Anatomy of an intent An intent has two parts: | Part | Holds | Lives where | | ---------- | ---------------------------------------------------------------------------------- | ------------------------------------------------------------- | | **Route** | What to execute on the destination chain (token transfer, contract call, deadline) | Read by the solver on the destination | | **Reward** | What the solver gets paid on the source chain when execution is proven | Locked in a [Vault](/routes/architecture/vault) on the source | The intent hash is `keccak256(destination, routeHash, rewardHash)`, a deterministic identifier that both chains agree on without ever transmitting the full intent. ## The intent lifecycle Intent lifecycle from publication to settlement: user publishes and funds the vault, solver fulfills on the destination chain, prover generates and verifies the settlement proof, vault releases and transfers the reward. 1. **Publish and fund.** The user (or a relayer) publishes the intent and transfers the reward tokens to a deterministic vault address. Funding can happen before publishing. The vault address is CREATE2-derived from the intent hash. 2. **Fulfill.** A solver executes the route on the destination chain, recording the fulfillment in the destination Portal. 3. **Prove.** A prover (chosen by the user) carries proof of fulfillment back to the source chain. 4. **Settle.** The source-chain Portal verifies the proof and releases the reward from the vault to the solver. 5. **Refund** (if no fulfillment by deadline). A permissionless refund service returns the reward to the user. ## Why intents (vs. transactions) Transactions specify a path. Intents specify an outcome. The difference matters when the optimal path is unknown at signing time, or when the path crosses chains that don't share execution. | Property | Transaction | Intent | | ---------------- | ------------------------------ | ----------------------------------------- | | Specifies | Exact contract calls | Desired outcome | | Best path chosen | At signing time | At fulfillment time, by competing solvers | | Cross-chain | Manual coordination required | Atomic with cryptographic proof | | Failure mode | Partial state, manual recovery | Atomic refund, no partial states | # Provers Source: https://docs.eco.com/concepts/provers A prover carries proof of fulfillment from the destination chain back to the source. The security model of an intent is the security model of its prover. A **prover** is a contract on the source chain that verifies an intent was fulfilled on the destination chain. The user picks the prover at intent-creation time, which means the user picks their own settlement security model. The Portal accepts any contract that implements the `IProver` interface. Eco ships six proving paths today. ## Available provers | Prover | Settlement method | Security model | Best for | | --------------------------------------------------- | ---------------------------------------- | ----------------------------------------- | -------------------------------------- | | [CCTP](/routes/architecture/provers/cctp) | Issuer attestations via `receiveMessage` | Circle's attestation service | USDC routes, regulated flows | | [Hyperlane](/routes/architecture/provers/hyperlane) | Hyperlane ISM validators | Configurable security module | General cross-chain, fast | | [LayerZero](/routes/architecture/provers/layerzero) | DVN consensus | Decentralized verifier network | Wide chain coverage | | Chainlink CCIP | Cross-Chain Interoperability Protocol | Chainlink DON and Risk Management Network | General cross-chain, wide coverage | | [Polymer](/routes/architecture/provers/polymer) | IBC light clients | Light-client proofs | Trust-minimized cross-chain | | [Local](/routes/architecture/provers/local) | Same-chain `flashFulfill` | Transaction atomicity | Same-chain intents, orchestration mode | ## How proving works Sequence of how the prover confirms fulfillment and unlocks the solver reward: fulfill, emit fulfillment, dispatch proof, withdraw, verify proof, release reward. Step 4 is what differentiates provers. Each prover uses a different cross-chain mechanism: CCTP attestations, Hyperlane ISM verification, LayerZero DVN consensus, Polymer IBC, and so on. The source Portal interface is identical. ## Why modularity matters Different intents have different security needs. A retail USDC transfer can use CCTP. A regulated institutional rebalance might prefer Polymer's light-client proofs. A same-chain RFQ uses the Local prover for instant atomicity. The user picks per intent. This also future-proofs the system: a new messaging protocol can plug in without changes to the Portal, the vault model, or solver tooling. # Settlement vs Orchestration Source: https://docs.eco.com/concepts/settlement-vs-orchestration Eco fulfills intents in two modes, solver settlement (capital fronted) or orchestration (user funds routed through underlying infrastructure at gas cost). Same Portal, same vaults, same API. Eco fulfills intents in two fundamentally different ways using the same contracts: | Mode | What happens | Capital required | Speed | | ----------------- | ------------------------------------------------------------------------------------------- | ---------------- | ---------------------------------------- | | **Settlement** | A solver fronts inventory and delivers the user's outcome | Solver capital | Fastest, depends on solver | | **Orchestration** | The user's vaulted funds move through underlying infrastructure within a single transaction | Gas only | Bounded by underlying messaging finality | Most intent systems only do settlement. Eco does both, and the choice is automatic. ## Settlement (the default) A solver acts as counterparty. They observe the intent, decide it's profitable, and front their own capital on the destination chain to deliver the user's requested asset. Once they deliver, a [prover](/concepts/provers) carries proof back to the source, and the solver withdraws the reward. This is how most intent systems work, and it's what produces the best execution under normal conditions because solvers compete on price. ## Orchestration When no solver is willing to settle (e.g. no pricing advantage, or no solver online for that pair), Eco can fulfill the intent **without a solver** by routing the user's own vaulted funds through the underlying infrastructure inside a single atomic transaction. This is enabled by the [Local Prover](/routes/architecture/provers/local) and its `flashFulfill` operation, a flash-loan-style operation that uses the vault's contents as the source of liquidity for fulfillment, with no cross-chain message required. **The vault model is what makes this possible.** Because funds sit in self-custodied, deterministic vaults, the system can route them through underlying infrastructure without ever taking custody. ## The fallback chain ``` ┌─────────────────────────────────────────────────────────────┐ │ Tier 1: Solver available, better pricing → Settlement │ │ Tier 2: Solver available, no pricing edge → Orchestration │ │ Tier 3: No solver available → Self-solve │ └─────────────────────────────────────────────────────────────┘ ``` Transitions between tiers are transparent to the caller. The integration code is identical. ## Why this matters **System availability equals chain availability**, not solver availability. If solvers go offline, intents still execute. If a solver outbids you, you still get the better price. The dual-mode design eliminates a common failure case in single-mode intent systems: stuck intents with no solver to fulfill them. It also gives institutions a predictable bound: the worst-case fulfillment time is bounded by underlying chain finality, not by an opaque offchain market. # Solvers Source: https://docs.eco.com/concepts/solvers Solvers are independent operators that fulfill intents on destination chains in exchange for the reward locked in the source-chain vault. A **solver** is an independent operator that fulfills intents on destination chains in exchange for the reward locked in the source-chain [vault](/routes/architecture/vault). Solvers compete on price and speed; the user gets the best execution; failed solvers lose nothing but gas. ## What a solver does For each intent, a solver: 1. Reads the intent from the source chain (or receives it offchain) 2. Decides whether the reward covers the destination-chain cost plus margin 3. Executes the route on the destination chain 4. Calls a [prover](/concepts/provers) to carry proof back to the source 5. Withdraws the reward from the vault Most production solvers also run a **quoting service**, an HTTP endpoint Eco's quote aggregator calls to get pricing for a candidate intent. See the [solver integration guide](/recipes/become-a-solver). ## Competition model Solvers compete in two ways: * **Price**: best quote on the input/output pair wins the user's intent * **Speed**: faster fulfillment captures volume from time-sensitive flows Eco's quote aggregator queries all registered solvers in parallel and surfaces the best to the user. ## Capital efficiency Settling intents requires the solver to front capital on the destination chain. Two mechanisms reduce that constraint: * **Orchestration mode**: when no solver is willing to front capital, the user's vaulted funds move through underlying infrastructure at gas cost. See [Settlement vs Orchestration](/concepts/settlement-vs-orchestration). * **Crowd Liquidity**: solvers can flash-borrow stablecoin liquidity from a permissionless pool to fulfill larger intents than their reserves would otherwise allow. See [Crowd Liquidity](/routes/primitives/crowd-liquidity). ## Becoming a solver Solvers are permissionless. To register: 1. Implement the V2 quote endpoints (`/api/v2/quote`, `/api/v2/quote/reverse`) 2. Register your endpoint URLs via `POST /api/v1/solverRegistry/registerSolver` 3. Monitor the source-chain Portal for `IntentPublished` events 4. Fulfill, prove, withdraw → Full guide: [Become a solver](/recipes/become-a-solver) · [Solver Registry API](/api-reference/introduction#solver-registry) # Vaults & resource locks Source: https://docs.eco.com/concepts/vaults Every intent has its own deterministic vault. Funds are non-custodial, released only by cryptographic proof of fulfillment, or refunded automatically on expiry. A **Vault** is a per-intent escrow contract that holds reward tokens until execution is proven. Every intent gets its own vault, deployed deterministically via CREATE2. There is no shared protocol account. There are no admin keys. ## Why per-intent vaults Two consequences make the design useful: **Funding is just a transfer.** The vault address can be computed offchain from the intent hash. Cold wallets, hardware devices, multisigs, exchanges, and PSPs can fund an intent with a standard ERC-20 `transfer` to the vault, no EIP-712 signature, no permit, no contract interaction. **Funds release only on proof.** No private key controls withdrawal. The source-chain Portal will only release funds after a registered prover confirms fulfillment. If the deadline passes without fulfillment, a permissionless refund service returns the funds to the depositor. ## Resource locks vs escrow The vault model is sometimes called a **resource lock**: instead of escrowing funds in a shared protocol account, each operation reserves its own resource container. This eliminates protocol-level risk concentration and lets the funding flow look exactly like a payment. | Pooled escrow | Per-intent vault | | ---------------------------------------------- | ------------------------------------ | | One contract holds all reward tokens | One vault per intent | | Trust the protocol's accounting | Trust the proof | | Funding requires interaction with the protocol | Funding is a vanilla ERC-20 transfer | | Bug in escrow contract → all users affected | Bug in vault → bounded to one intent | ## Determinism in practice ``` vaultAddress = CREATE2( portalAddress, intentHash, // salt keccak256(initCode) ) ``` Because the vault address is a deterministic function of the intent hash, an application can: * Display "Send USDC to `0xVaultAddr`" before the intent is even published * Pre-fund a vault, then publish the intent later when ready * Verify funding by checking onchain balance, with no protocol coupling # Choose your product Source: https://docs.eco.com/get-started/choose-your-product Match your use case to one of Eco's four products in 30 seconds. Eco currently features four products. Pick the one that matches what you're building. ## The 30-second decision | If you want to… | Use | Live today | | ----------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------- | ---------- | | Move stablecoins between chains in one signed action | [Routes](/routes/overview) | ✅ | | Give a user a deterministic address with pre-programmed actions. Arriving funds auto-route to the right chain and token | [Programmable Addresses](/addresses/overview) | ✅ | | Embed decision logic into a single transaction (best-price selection, conditional execution) | [Programmable Transactions](/transactions/overview) | Beta | | Compose routing, liquidity, and compliance behind one API | [Orchestration](/transactions/orchestration) | Beta | Beta products are available to **Eco partners for early access**. [Contact us](mailto:contact@eco.com) to integrate. ## Detailed comparison ### Routes: Real-time transfers and swaps The default product. An intent-based execution layer for stablecoin movement. Best execution comes from solver competition; failure modes are atomic refunds. **Use Routes when** you need: cross-chain transfers, cross-stable swaps (RFQ), gasless deposits via Permit3, or destination calls that bridge-and-deposit in a single intent. **Don't use Routes when** the user shouldn't have to interact with your app at the moment of funding. That's [Programmable Addresses](/addresses/overview). → [Routes overview](/routes/overview) ### Programmable Addresses: Custom deposit & withdrawal logic Deterministic CREATE2 addresses with pre-programmed actions: when funds arrive, the actions execute automatically. Live today: * [**Solana Deposit Addresses**](/addresses/solana), a deterministic EVM address on Base that auto-bridges USDC to a Solana wallet * [**Circle Gateway Deposits**](/addresses/gateway-fast-deposits), fast, gasless USDC deposits into Circle Gateway from Base/Optimism/Arbitrum, settled in 20–40 seconds **Use Programmable Addresses when** the funding event needs to be a plain ERC-20 transfer, from any wallet, exchange, or partner, with no bridge UI and no signature. → [Programmable Addresses overview](/addresses/overview) ### Programmable Transactions: Advanced routing & execution (beta) Contact for access Make a single transaction smart enough to read state from multiple contracts, pick the optimal path, and execute atomically. No contract deployment required. **Use Programmable Transactions when** the optimal path depends on live onchain state at execution time (best-price aggregation, MEV-resistant routing, agentic strategies). → [Programmable Transactions overview](/transactions/overview) ### Orchestration (beta) Contact for access Combines Routes, Programmable Transactions, Permit3, and compliance hooks behind a single API for teams who want one integration point. **Use Orchestration when** you're building a regulated payment flow, an agentic system that needs all of the above, or a partner integration where you don't want to glue products together yourself. → [Orchestration overview](/transactions/orchestration) ## When to combine Most non-trivial integrations use more than one product: * A **payment platform** uses Programmable Addresses for funding and Routes for settlement. * A **DeFi protocol** uses Routes for cross-chain deposits and Programmable Transactions for best-price internal routing. * An AI agent uses Orchestration to chain a Programmable Address deposit, a Programmable Transactions swap, and a Routes destination call into one composable flow. Move your first USDC across chains in 5 minutes. Persona-shaped guides with the right product picked for you. # Quickstart Source: https://docs.eco.com/get-started/quickstart Pick a product, then a path. Each card jumps straight to its own quickstart. Pick the product you're integrating, then the path that fits your stack. Each link is a self-contained quickstart, install or curl, get to a working call, then keep going. ## Routes: Real-time transfers and swaps Move and swap stablecoins across chains. Two integration paths: Ship your first stablecoin transfer. Server-to-server integration via REST API. ## Programmable Addresses: Custom deposit logic A deterministic address with pre-programmed actions. When funds arrive, they execute automatically. A deterministic EVM address on Base that auto-bridges USDC to a Solana wallet. 10 minutes to set up. Gasless USDC deposits from Base, Optimism, or Arbitrum to Polygon. 15 minutes to set up, settles in 20–40 seconds. # What is Eco? Source: https://docs.eco.com/get-started/what-is-eco Eco is the stablecoin network that makes money programmable across every major blockchain. Building cross-chain stablecoin flows today means trusting bridges that wrap tokens, custody funds, and fail mid-transaction. Eco gives developers a single API for moving stablecoins across chains: non-custodial, composable, and guaranteed to execute exactly as specified or refund automatically. Stablecoins are natively issued on 16+ chains, creating 240+ directional pairs. Eco handles the routing, solver competition, and destination logic so you don't have to. ## What you can build today | Product | Use it to | Best for | | ------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------- | ------------------------------------------------------------ | | [Routes](/routes/overview) | Move and swap stablecoins across chains in real time | Wallets, DEXs, payment apps, cross-chain DeFi | | [Programmable Addresses](/addresses/overview) | Give users a deterministic address that auto-routes deposits to the right chain and token | Exchanges, neobanks, protocol treasuries | | [Programmable Transactions](/transactions/overview) (beta) Contact for access | Embed decision logic into a single transaction, no contract deployment | Aggregators, AI agents, latency-sensitive apps | | [Orchestration](/transactions/orchestration) (beta) Contact for access | Compose routing, liquidity sourcing, and compliance behind a unified API | Enterprise teams, regulated fintechs, agentic workflows | | [Verified Liquidity](/verified/overview) (closed beta) Contact for access | Move stablecoins through KYB'd solvers with KYC and OFAC user screening | PSPs, regulated treasury, stablecoin issuers, B2B settlement | ## How Eco is Built Eco delivers programmable stablecoin outcomes, guaranteed. Four design choices make this possible. **Non-custodial by construction.** Every intent on Eco is a [Vault](/routes/architecture/vault), which keeps the whole protocol non-custodial and user and solver funds safe. Each vault is a deterministic CREATE2 contract that holds funds until proof of fulfillment, and there is no protocol-owned account. Refunds are permissionless and run independently of Eco. For institutional signers, funding looks like a single ERC-20 `transfer` to a precomputed address. **Permissionless and immutable.** Anyone can build on Eco, and the contract you integrate against today behaves the same way years from now. The [Portal](/routes/architecture/portal) is a non-upgradable factory: no proxy pattern, no admin keys, no upgrade path. Any wallet, app, or solver can join. Compliance is enforced at the solver-selection layer rather than in the contract, and provers are modular, so users **choose** the security model of their own intent. **Dual-mode execution.** Your transfer goes through as long as the chains are live, even when no solver is willing to take it. Most intent systems only do one thing: a solver fronts capital and delivers your outcome. Eco does that, and can also fulfill an intent **without a solver** by routing the user's funds through underlying infrastructure inside a single transaction (see [Local Prover](/routes/architecture/provers/local)). | Tier | Condition | Mode | Capital required | | ---- | --------------------------------- | --------------------------- | ---------------- | | 1 | Solver available, better pricing | Settlement | Solver capital | | 2 | Solver available, no pricing edge | Orchestration | Gas only | | 3 | No solver available | Self-solve via local intent | Gas only | Transitions between tiers are transparent. **System availability equals chain availability**, not solver availability. **Runtime intelligence.** You get the best execution available the moment your transaction lands, not a path locked in when you signed. Most onchain operations bake the route into the transaction at signing time, so by the time it lands the chosen path may no longer be optimal. Eco's [Programmable Transactions](/transactions/overview) layer lets a single transaction collect data from multiple contracts, choose between paths, and execute the optimal one, all atomically and without deploying a contract. This is what makes "best execution" a runtime claim, not a marketing one. ## What building on Eco ensures * **Predictable settlement.** No bridge limbo, no partial states, no support tickets about stuck transactions. * **Stablecoin-grade speed.** Solver competition and dual-mode mean typical fulfillment in **20–40 seconds** even when no solver is willing to front capital. * **Compliance-ready.** Add address screening, AML, and region rules without touching your integration. # Eco: Program stablecoin flows across chains Source: https://docs.eco.com/home Atomic, composable stablecoin operations with cryptographic execution guarantees. One API across 16+ chains and 240+ directional pairs.
Eco developer docs

Build stablecoin products without building stablecoin infrastructure.

Cross-chain stablecoin flows break in the same four places: bridge UX, gas requirements, stuck intermediate funds, and partial settlement. Eco handles all four. One API, production-ready, audited.

From
1,000.00
Base · USDC
To
999.85
Arbitrum · USDC
step 1User publishes and funds the intent
step 2Solver fulfills the intent
step 3Prover carries proof back to source
step 4Portal contract verifies and releases the funds
User sends
5,000 USDC
Any wallet · Base
Auto
Lands at
5,000 USDC
Circle Gateway · Polygon
apiApp requests a deterministic CREATE2 address
wireUser sends a plain ERC-20 transfer — no signature, no bridge UI
autoContract deploys on deposit and triggers an intent
landFunds arrive at the destination, gaslessly for the sender
read price("DEX\_A") a
read price("DEX\_B") b
read price("DEX\_C") c
if (max(a, b, c) >= 0.999)
  execute swap(winner)
else
  revert (atomic refund)
singleOne transaction reads multiple contracts and picks optimal path
atomicAtomic transactions eliminate front-running between steps
deployNo contract deployment required — logic lives in calldata
Choose your path

Where do you
want to start?

Four entry points, each shaped around how teams actually use Eco. Pick the one that matches what you're building.

Four products

Composed however
you need.

Each product works standalone. Compose them behind a single integration when you need the full stack.

Routes

Move and swap stablecoins across chains in real time via a non-custodial intent network. Sign once, settle atomically.

  • Typical fulfillment in 20–40 seconds across more than 240 directional pairs
  • Per-intent vault: reward released only on cryptographic proof of fulfillment
  • Stable 1:1 transfers, cross-stable RFQ, and destination calls in one signed action
Read the docs
quote.sh cURL
curl -X POST [https://quotes.eco.com/circle-gateway/v3/quotes/single](https://quotes.eco.com/circle-gateway/v3/quotes/single)
  -H "Content-Type: application/json"
  -d '
    "dAppID": "your-app",
    "quoteRequest":
      "sourceChainID": 8453,
      "destinationChainID": 42161,
      "sourceToken": "0x833...02913",
      "destinationToken": "0xaf8...30831",
      "sourceAmount": "1000000000"
    
  
 

Programmable Addresses

A deterministic CREATE2 address with pre-programmed actions. Funds arriving auto-route to the right chain, balance, or vault.

  • Same inputs always return the same address: share it before deployment
  • No bridge UI, no signature: the sender makes a plain ERC-20 transfer
  • Live: Circle Gateway fast deposits from Base, Optimism, and Arbitrum to Polygon
Read the docs
address.sh cURL
curl -X POST [https://.../circle-gateway/v2/depositAddresses](https://.../circle-gateway/v2/depositAddresses)
  -H "Content-Type: application/json"
  -d '
    "depositor": "0xUser…",
    "recipient": "0xUser…",
    "amount": "1000000",
    "sourceChainId": 8453
  
 

Programmable Transactions Beta

Embed decision logic into a single transaction. Read state from multiple contracts, pick the optimal path, execute atomically. No contract deployment.

  • Sauce: more than 50 opcodes for data extraction, control flow, and crypto operations
  • Best-price routing, conditional execution, and MEV-resistant flows in one atomic tx
  • External contracts see normal calldata: no integration changes required
Read the docs
supertx.calldata Sauce
header 0x5A00CE
 
a = read quote("DEX\_A", in)
b = read quote("DEX\_B", in)
c = read quote("DEX\_C", in)
best = argmax(a, b, c)
 
if best.out >= minOut:
  call best.dex.swap(in, minOut)
else: revert

Orchestration Beta

The composition layer. Wraps Routes, Programmable Transactions, Permit3, and compliance hooks behind a single API.

  • One API for routing, liquidity, and compliance, including address screening at the solver layer
  • Compose a Programmable Address deposit, a Sauce swap, and a Routes destination call into one flow
  • Designed for regulated payment platforms, treasury automation, and agentic systems
Read the docs
orchestration.flow Composed
 
flow payInvoice(user, invoiceID):
  addr = programmable\_address(
    destination = "USDC\@Polygon"
  )
  on deposit(addr):
    screen(sender)
    route(amount, recipient)
    webhook(invoiceID, "paid")

Start building with Eco.

Send your first stablecoin across chains in five minutes — from the CLI, or wire the V3 quote API directly into your backend.

Docs map

Everything
in one place.

Jump straight to the page you need. Every product, every recipe, every reference, cross-linked so you can browse by what you're building, not where it lives.

# Auto-routing deposits to any chain Source: https://docs.eco.com/recipes/auto-route-deposits Give a user a single deterministic EVM address that auto-routes any USDC sent to it to a Solana wallet (or any supported destination). This recipe gives a user one permanent address that auto-routes any USDC sent to it to a destination on a different chain. The sender makes a normal ERC-20 transfer. No bridge UI, no signature, no contract call. ## When to use * Exchanges adding Solana withdrawal support * Wallets giving users a "deposit anywhere, land on Solana" address * Any flow where the funder is not the user (sweeping, payroll, partner deposits) ## 1. Generate a deposit address ```bash theme={null} curl -X POST https://deposit-addresses.eco.com/api/v1/depositAddresses/solana \ -H "Content-Type: application/json" \ -d '{ "chainId": 8453, "solanaAddress": "YOUR_SOLANA_ADDRESS", "depositor": "YOUR_EVM_ADDRESS" }' ``` Response: ```json theme={null} { "data": { "evmDepositAddress": "0x...", "isDeployed": false } } ``` The `evmDepositAddress` is deterministic: same inputs always return the same address. You can show it to the user before any contract is deployed. ## 2. Send USDC to the address The user (or any third party) makes a vanilla ERC-20 `transfer()` to `evmDepositAddress` on Base: ```typescript theme={null} import { createWalletClient, http, parseUnits, erc20Abi } from 'viem'; import { base } from 'viem/chains'; const walletClient = createWalletClient({ chain: base, transport: http() }); await walletClient.writeContract({ address: BASE_USDC, abi: erc20Abi, functionName: 'transfer', args: [evmDepositAddress, parseUnits('100', 6)], }); ``` ## 3. The address auto-routes On first deposit, the contract deploys (the deposit address pays gas, not the sender). The contract calls `createIntent()`, which publishes a Routes intent. Solvers compete to fulfill on Solana. ## 4. Confirm delivery Poll the deposit address record to check `isDeployed` and `lastCheckedBalance`: ```bash theme={null} curl https://deposit-addresses.eco.com/api/v1/depositAddresses/evmAddress/0x... ``` See the [Solana Deposit Addresses API](/api-reference/programmable-addresses/solana-overview) for the full deposit-address endpoint reference. You've successfully created an auto-routing deposit address. # Becoming an Eco solver Source: https://docs.eco.com/recipes/become-a-solver End-to-end guide to integrating as a solver, implement the V2 quote endpoints, register, monitor Portal events, fulfill, withdraw. A solver fulfills intents on destination chains in exchange for the source-chain reward. This recipe walks through registration and the steady-state operating loop. ## What you need to provide | Endpoint | Required for | Purpose | | ---------------------------- | ------------ | --------------------------------------------------- | | `POST /api/v2/quote` | All solvers | Return pricing for a candidate intent (exact-input) | | `POST /api/v2/quote/reverse` | All solvers | Return pricing for an exact-output intent | See the [API Reference](/api-reference/introduction#solver-interface) for full request/response schemas. ## 1. Implement the V2 endpoints Your `/api/v2/quote` handler: 1. Parses the candidate intent (source chain, destination chain, route tokens, requested output) 2. Calculates your cost (destination gas, capital cost, slippage) 3. Returns reward tokens you'd accept on the source chain (covering cost and margin), or rejects if unprofitable `/api/v2/quote/reverse` is the same idea for exact-output intents. ## 2. Register your solver ```bash theme={null} curl -X POST https://quotes.eco.com/api/v1/solverRegistry/registerSolver \ -H "Content-Type: application/json" \ -d '{ "solverName": "your-solver", "intentExecutionTypes": ["SELF_PUBLISH"], "crossChainRoutes": [ ... ], "quotesV2Url": "https://your-solver.com/api/v2/quote", "reverseQuotesV2Url": "https://your-solver.com/api/v2/quote/reverse", "useSolverDataFormat": true }' ``` Routes V2 only, do not include `quotesUrl`, `reverseQuotesUrl`, or `receiveSignedIntentUrl`. Those are V1 legacy fields. ## 3. Monitor Portal events for SELF\_PUBLISH intents Watch the source-chain Portal for `IntentPublished` events. Match on intents that used your quote (your reward tokens will be present in the event). ```solidity theme={null} event IntentPublished( bytes32 indexed hash, uint64 destination, bytes route, address indexed creator, address indexed prover, uint256 deadline, uint256 nativeAmount, TokenAmount[] tokens ); ``` ## 4. Fulfill on the destination ```solidity theme={null} portal.fulfillAndProve{value: route.nativeAmount + bridgeFee}( intentHash, route, rewardHash, claimant, proverAddress, sourceChainDomainID, proverData ); ``` The Portal transfers your reward tokens to the [Executor](/routes/architecture/executor), runs `route.calls` atomically, and emits `IntentFulfilled`. ## 5. Wait for proof, then withdraw The chosen prover dispatches the fulfillment proof to the source chain. Once the source-chain Portal verifies it: ```solidity theme={null} portal.withdraw(destination, routeHash, reward); ``` You receive the reward tokens. ## Capital efficiency Two patterns reduce your inventory needs: * **Crowd Liquidity:** flash-borrow stablecoin liquidity for fulfillment, repay from the destination outcome. See [Crowd Liquidity](/routes/primitives/crowd-liquidity). * **Issuer-direct integration:** if you have native mint/burn for a stablecoin, use it instead of pre-positioned inventory. ## Best practices * Pre-simulate `route.calls` before fulfilling to avoid wasted gas on calls that would revert * Use `batchWithdraw` to amortize tx costs across multiple proven intents * Set `deadline` margins that allow for proof verification time on your chosen prover * Treat `Fulfillment` event detection as the success signal, not the tx receipt You're now operating as a solver. # Gasless USDC deposits into Circle Gateway Source: https://docs.eco.com/recipes/gasless-gateway-deposit Have a user deposit USDC into Circle Gateway on Polygon without holding source-chain gas, they sign an ERC-3009 authorization; Eco broadcasts. Settled in 20–40 seconds. This recipe walks through the cleanest gasless funding path Eco supports today: a user signs an ERC-3009 `transferWithAuthorization`; Eco's relayer broadcasts; USDC lands in the user's Circle Gateway balance on Polygon. ## When to use * Onboarding flows where users don't yet hold the source chain's gas token * Nanopayment / micro-deposit flows where gas would dominate the value transferred * Any flow where the UX win of "no gas" beats the marginal latency ## 1. Create the quoted deposit vault ```bash theme={null} curl -X POST https://api.eco.com/circle-gateway/v2/depositAddresses \ -H "Content-Type: application/json" \ -d '{ "sourceChainId": 8453, "amount": "100000000", "recipient": "0xRecipientOnGateway", "depositor": "0xUserWallet" }' ``` Returns `{ vaultAddress, amount, deadline }`. The vault is quoted for this specific `amount`; fund it before `deadline` (Unix seconds). Repeating the same request while the quote is pending returns the same vault. ## 2. Have the user sign an ERC-3009 authorization Sign for the quoted `amount`, with `to` set to the `vaultAddress`. USDC's EIP-712 domain `name` varies per chain: `"USDC"` on Base Sepolia, `"USD Coin"` elsewhere. Use the right one. ```typescript theme={null} import { toHex } from 'viem'; const value = BigInt(amount); // quoted amount from step 1 const nonce = toHex(crypto.getRandomValues(new Uint8Array(32))); 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: 0n, validBefore: BigInt(validBefore), nonce }, }); ``` ## 3. Submit the signed authorization ```bash theme={null} curl -X POST https://api.eco.com/circle-gateway/v1/gasless/transferWithAuthorization \ -H "Content-Type: application/json" \ -d '{ "chainId": 8453, "from": "0xSigner", "to": "0xVaultAddress", "value": "100000000", "validAfter": "0", "validBefore": "1717200000", "nonce": "0x...", "signature": "0x..." }' ``` Returns `202 Accepted` with `{ id, status: "PENDING" }`. The `value` must be at least the quoted `amount` and the vault's quote must not be expired. ## 4. Poll for completion Poll the relayer job: ```bash theme={null} curl https://api.eco.com/circle-gateway/v1/gasless/jobs/ ``` Status transitions: `PENDING` → `COMPLETED` (or `FAILED`). Response includes `transferTxHash` once complete. Then poll the vault status until the deposit intent is published: ```bash theme={null} curl "https://api.eco.com/circle-gateway/v2/depositAddresses/0xVaultAddress?sourceChainId=8453" ``` `state` transitions `PENDING` → `FUNDING_DETECTED` → `PUBLISHED`, and the response carries the `intentHash`. See the [state table](/addresses/gateway-fast-deposits#step-3-poll-for-completion) for the full list. End-to-end (Base Sepolia → Polygon Amoy): typically **20–40 seconds**. ## What the user paid Nothing onchain. Eco's deposit-address service paid source-chain gas; Eco's solver service paid Polygon-side gas. You've successfully completed a gasless deposit into Gateway. # Recipes Source: https://docs.eco.com/recipes/overview End-to-end task guides, opinionated, code-heavy, and built around the most common Eco integration patterns. Recipes are end-to-end task guides. Each one starts with a concrete outcome ("send USDC across chains", "auto-route deposits", "become a solver") and walks through every step: API calls, code, validation, what to do if it fails. ## Popular recipes | Recipe | Time | Uses | | -------------------------------------------------------------------- | ------ | ---------------------------------- | | [Sending USDC across chains](/recipes/send-usdc-cross-chain) | 15 min | Routes | | [Auto-routing deposits to any chain](/recipes/auto-route-deposits) | 10 min | Programmable Addresses (Solana) | | [Gasless USDC into Circle Gateway](/recipes/gasless-gateway-deposit) | 15 min | Programmable Addresses (Gateway) | | [Cross-chain treasury rebalancing](/recipes/treasury-rebalancing) | 30 min | Routes API, programmatic | | [Becoming an Eco solver](/recipes/become-a-solver) | 1 day | Solver V2 endpoints, Portal events | ## Picking the right recipe | If you're… | Start with | | ---------------------- | ----------------------------------------------------------------- | | New to Eco | [Sending USDC across chains](/recipes/send-usdc-cross-chain) | | Building a wallet | [Auto-routing deposits](/recipes/auto-route-deposits) | | Building a payment app | [Gasless USDC into Gateway](/recipes/gasless-gateway-deposit) | | Operating a treasury | [Cross-chain treasury rebalancing](/recipes/treasury-rebalancing) | | Fulfilling intents | [Becoming an Eco solver](/recipes/become-a-solver) | More recipes ship as new patterns emerge. Have one you'd like documented? [Tell us](mailto:contact@eco.com). # Sending USDC across chains Source: https://docs.eco.com/recipes/send-usdc-cross-chain End-to-end pattern for moving USDC from one chain to another via the V3 quote API, quote, publish, track. Sending USDC across chains is the most common Routes flow. This recipe walks through a server-side integration: get a quote, publish and fund the intent onchain using the `encodedRoute` from the quote, poll for fulfillment. For an interactive CLI walkthrough instead, see the [Routes CLI guide](/routes/integrate/cli). ## 1. Get a quote ```bash theme={null} curl -X POST https://quotes.eco.com/api/v3/quotes/single \ -H "Content-Type: application/json" \ -d '{ "dAppID": "your-app", "quoteRequest": { "sourceChainID": 10, "destinationChainID": 8453, "sourceToken": "0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85", "destinationToken": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", "sourceAmount": "1000000", "funder": "0xYourWalletAddress", "recipient": "0xRecipientAddress" } }' ``` The response includes `quoteResponse.encodedRoute`, `quoteResponse.deadline`, `contracts.sourcePortal`, and `contracts.prover`. That's everything you need to publish onchain. ## 2. Approve the reward token to the source Portal ```typescript theme={null} import { erc20Abi } from 'viem'; const portal = quote.data.contracts.sourcePortal; const rewardToken = quote.data.quoteResponse.sourceToken; const rewardAmount = BigInt(quote.data.quoteResponse.sourceAmount); const allowance = await publicClient.readContract({ address: rewardToken, abi: erc20Abi, functionName: 'allowance', args: [user, portal], }); if (allowance < rewardAmount) { await walletClient.writeContract({ address: rewardToken, abi: erc20Abi, functionName: 'approve', args: [portal, rewardAmount], }); } ``` ## 3. Publish and fund Build the reward struct from the quote and call `publishAndFund` on the source Portal. ```typescript theme={null} const reward = { deadline: BigInt(quote.data.quoteResponse.deadline), creator: user, prover: quote.data.contracts.prover, nativeAmount: 0n, tokens: [{ token: rewardToken, amount: rewardAmount }], }; const tx = await walletClient.writeContract({ address: portal, abi: portalAbi, functionName: 'publishAndFund', args: [ BigInt(quote.data.quoteResponse.destinationChainID), quote.data.quoteResponse.encodedRoute, reward, false, // allowPartial ], }); const receipt = await publicClient.waitForTransactionReceipt({ hash: tx }); // Read the IntentPublished event from the receipt logs to get intentHash ``` ## 4. Track fulfillment ```bash theme={null} curl -X POST https://quotes.eco.com/api/v3/intents/intentStatus \ -H "Content-Type: application/json" \ -d '{ "intentHash": "0x..." }' ``` Status returns `{ status: "Pending" | "Completed", subStatus: "WaitingToFulfill" | "WaitingForRefund" | "Fulfilled" | "Refunded", subStatusMessage }`. Typical fulfillment: **20–40 seconds**. You've successfully sent USDC across chains. # Cross-chain treasury rebalancing Source: https://docs.eco.com/recipes/treasury-rebalancing Build a service that watches per-chain utilization and publishes Routes intents to rebalance USDC across chains, with cryptographic execution guarantees. This recipe builds a server-side rebalancer that programmatically moves treasury USDC between chains based on a threshold (e.g. utilization, yield differential, opportunity). ## When to use * Multi-chain protocols managing distributed treasury * Yield managers chasing the highest opportunity across chains * Operational treasuries that want to remove manual rebalancing ## 1. Define the trigger ```typescript theme={null} interface RebalancePolicy { sourceChain: number; destChain: number; token: string; // USDC on dest threshold: bigint; // Trigger amount minTransfer: bigint; } async function shouldRebalance(p: RebalancePolicy): Promise { const sourceBal = await getBalance(p.sourceChain); const destBal = await getBalance(p.destChain); const delta = sourceBal - destBal; if (delta < p.threshold) return null; return delta / 2n; // Move half the gap } ``` ## 2. Get a quote for the rebalance A treasury rebalance is a simple transfer between two of your wallets. Use the V3 quote API with the source treasury as `funder` and the destination treasury as `recipient`. ```typescript theme={null} const res = await fetch('https://quotes.eco.com/api/v3/quotes/single', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ dAppID: 'treasury-rebalancer', quoteRequest: { sourceChainID: policy.sourceChain, destinationChainID: policy.destChain, sourceToken: policy.sourceToken, destinationToken: policy.destToken, sourceAmount: amount.toString(), funder: SOURCE_TREASURY, recipient: DEST_TREASURY, }, }), }); const quote = await res.json(); ``` ## 3. Approve, publish, fund Approve the reward token to `quote.data.contracts.sourcePortal`, then call `publishAndFund(destinationChainID, encodedRoute, reward, false)`, same exact pattern as [Sending USDC across chains](/recipes/send-usdc-cross-chain). ## 4. Run on a schedule ```typescript theme={null} async function tick() { for (const policy of POLICIES) { const amount = await shouldRebalance(policy); if (amount && amount > policy.minTransfer) { await publishRebalance(policy, amount); } } } setInterval(tick, 60_000); ``` You've successfully built an atomic, auditable cross-chain rebalancer. # Contract Addresses Source: https://docs.eco.com/resources/contract-addresses Deployed contract addresses for Eco Routes on all supported chains ### Arbitrum Contract Addresses | Contract | Address | | ----------- | -------------------------------------------------------------------------------------------------------------------- | | HyperProver | [0x0f124aA8F92F47302fCba08b7349AEFEe853Ed8d](https://arbiscan.io/address/0x0f124aA8F92F47302fCba08b7349AEFEe853Ed8d) | ### Base Contract Addresses | Contract | Address | | ----------- | --------------------------------------------------------------------------------------------------------------------- | | HyperProver | [0x0f124aA8F92F47302fCba08b7349AEFEe853Ed8d](https://basescan.org/address/0x0f124aA8F92F47302fCba08b7349AEFEe853Ed8d) | ### Celo Contract Addresses | Contract | Address | | ----------- | -------------------------------------------------------------------------------------------------------------------- | | HyperProver | [0x0f124aA8F92F47302fCba08b7349AEFEe853Ed8d](https://celoscan.io/address/0x0f124aA8F92F47302fCba08b7349AEFEe853Ed8d) | ### Ethereum Mainnet Contract Addresses | Contract | Address | | ----------- | --------------------------------------------------------------------------------------------------------------------- | | HyperProver | [0x0f124aA8F92F47302fCba08b7349AEFEe853Ed8d](https://etherscan.io/address/0x0f124aA8F92F47302fCba08b7349AEFEe853Ed8d) | ### Ink Contract Addresses | Contract | Address | | ----------- | -------------------------------------------------------------------------------------------------------------------------------- | | HyperProver | [0x0f124aA8F92F47302fCba08b7349AEFEe853Ed8d](https://explorer.inkonchain.com/address/0x0f124aA8F92F47302fCba08b7349AEFEe853Ed8d) | ### Optimism Contract Addresses | Contract | Address | | ----------- | -------------------------------------------------------------------------------------------------------------------------------- | | HyperProver | [0x0f124aA8F92F47302fCba08b7349AEFEe853Ed8d](https://optimistic.etherscan.io/address/0xb4B22BaFafc0Fe12Bc9Be00D6611Dd2d8A42a7a8) | ### Polygon Contract Addresses | Contract | Address | | ----------- | ------------------------------------------------------------------------------------------------------------------------ | | HyperProver | [0xb4B22BaFafc0Fe12Bc9Be00D6611Dd2d8A42a7a8](https://polygonscan.com/address/0xb4B22BaFafc0Fe12Bc9Be00D6611Dd2d8A42a7a8) | ### Unichain Contract Addresses | Contract | Address | | ----------- | -------------------------------------------------------------------------------------------------------------------- | | HyperProver | [0xb4B22BaFafc0Fe12Bc9Be00D6611Dd2d8A42a7a8](https://uniscan.xyz/address/0xb4B22BaFafc0Fe12Bc9Be00D6611Dd2d8A42a7a8) | ### World Chain Contract Addresses | Contract | Address | | | ----------- | ---------------------------------------------------------------------------------------------------------------------- | - | | HyperProver | [0x1871c138cEaB2bB36c5dBcAED0AB4BC88271feE7](https://worldscan.org/address/0x1871c138cEaB2bB36c5dBcAED0AB4BC88271feE7) | | ### Sonic Contract Addresses | Contract | Address | | | ----------- | ---------------------------------------------------------------------------------------------------------------------- | - | | HyperProver | [0x0f124aA8F92F47302fCba08b7349AEFEe853Ed8d](https://sonicscan.org/address/0x0f124aA8F92F47302fCba08b7349AEFEe853Ed8d) | | # Supported chains and tokens Source: https://docs.eco.com/resources/supported-chains-tokens Supported chains and tokens for Eco Routes These are the tokens currently supported by the SDK. There may be more tokens supported by other solvers in the network. ## Stablecoins | Chain ID | Chain Name | Token | Address | | ---------- | --------------- | ------ | -------------------------------------------------------------------------------------------------------------------------------- | | 1 | Ethereum | USDC | [0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48](https://etherscan.io/address/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48) | | | | USDT | [0xdAC17F958D2ee523a2206206994597C13D831ec7](https://etherscan.io/address/0xdAC17F958D2ee523a2206206994597C13D831ec7) | | | | USDG | [0xe343167631d89B6Ffc58B88d6b7fB0228795491D](https://etherscan.io/address/0xe343167631d89B6Ffc58B88d6b7fB0228795491D) | | 10 | OP Mainnet | USDC | [0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85](https://optimistic.etherscan.io/address/0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85) | | | | USDT | [0x94b008aA00579c1307B0EF2c499aD98a8ce58e58](https://optimistic.etherscan.io/address/0x94b008aA00579c1307B0EF2c499aD98a8ce58e58) | | | | USDC.e | [0x7F5c764cBc14f9669B88837ca1490cCa17c31607](https://optimistic.etherscan.io/address/0x7F5c764cBc14f9669B88837ca1490cCa17c31607) | | | | oUSDT | [0x1217BfE6c773EEC6cc4A38b5Dc45B92292B6E189](https://optimistic.etherscan.io/address/0x1217BfE6c773EEC6cc4A38b5Dc45B92292B6E189) | | 56 | BNB Smart Chain | USDC | [0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d](https://bscscan.com/address/0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d) | | | | USDT | [0x55d398326f99059fF775485246999027B3197955](https://bscscan.com/address/0x55d398326f99059fF775485246999027B3197955) | | 130 | Unichain | USDC | [0x078D782b760474a361dDA0AF3839290b0EF57AD6](https://uniscan.xyz/address/0x078D782b760474a361dDA0AF3839290b0EF57AD6) | | | | USDT0 | [0x9151434b16b9763660705744891fA906F660EcC5](https://uniscan.xyz/address/0x9151434b16b9763660705744891fA906F660EcC5) | | 137 | Polygon | USDC | [0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359](https://polygonscan.com/address/0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359) | | | | USDT0 | [0xc2132D05D31c914a87C6611C10748AEb04B58e8F](https://polygonscan.com/address/0xc2132D05D31c914a87C6611C10748AEb04B58e8F) | | | | USDC.e | [0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174](https://polygonscan.com/address/0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174) | | 146 | Sonic | USDC | [0x29219dd400f2Bf60E5a23d13Be72B486D4038894](https://sonicscan.org/address/0x29219dd400f2Bf60E5a23d13Be72B486D4038894) | | 999 | HyperEVM | USDC | [0xb88339CB7199b77E23DB6E890353E22632Ba630f](https://hyperevmscan.io/address/0xb88339CB7199b77E23DB6E890353E22632Ba630f) | | | | USDT0 | [0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb](https://hyperevmscan.io/address/0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb) | | 2020 | Ronin | USDC | [0x0B7007c13325C48911F73A2daD5FA5dCBf808aDc](https://app.roninchain.com/address/0x0B7007c13325C48911F73A2daD5FA5dCBf808aDc) | | 8453 | Base | USDC | [0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913](https://basescan.org/address/0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913) | | | | USDbC | [0xd9aAEc86B65D86f6A7B5B1b0c42FFA531710b6CA](https://basescan.org/address/0xd9aAEc86B65D86f6A7B5B1b0c42FFA531710b6CA) | | | | oUSDT | [0x1217BfE6c773EEC6cc4A38b5Dc45B92292B6E189](https://basescan.org/address/0x1217BfE6c773EEC6cc4A38b5Dc45B92292B6E189) | | 9745 | Plasma | USDT0 | [0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb](https://plasmascan.to/address/0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb) | | 42161 | Arbitrum One | USDC | [0xaf88d065e77c8cC2239327C5EDb3A432268e5831](https://arbiscan.io/address/0xaf88d065e77c8cC2239327C5EDb3A432268e5831) | | | | USDT0 | [0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9](https://arbiscan.io/address/0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9) | | | | USDC.e | [0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8](https://arbiscan.io/address/0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8) | | 42220 | Celo | USDC | [0xcebA9300f2b948710d2653dD7B07f33A8B32118C](https://celoscan.io/address/0xcebA9300f2b948710d2653dD7B07f33A8B32118C) | | | | USDT | [0x48065fbBE25f71C9282ddf5e1cD6D6A887483D5e](https://celoscan.io/address/0x48065fbBE25f71C9282ddf5e1cD6D6A887483D5e) | | 57073 | Ink | USDG | [0xe343167631d89B6Ffc58B88d6b7fB0228795491D](https://explorer.inkonchain.com/address/0xe343167631d89B6Ffc58B88d6b7fB0228795491D) | | | | USDT0 | [0x0200C29006150606B650577BBE7B6248F58470c1](https://explorer.inkonchain.com/address/0x0200C29006150606B650577BBE7B6248F58470c1) | | 728126428 | Tron | USDT | [TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t](https://tronscan.org/#/address/TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t) | | 1399811149 | Solana | USDC | [EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v](https://solscan.io/token/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v) | | | | USDG | [2u1tszSeqZ3qBWF3uNGPFc8TzMk2tdiwknnRMWGWjGWH](https://solscan.io/token/2u1tszSeqZ3qBWF3uNGPFc8TzMk2tdiwknnRMWGWjGWH) | | | | USDT | [Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB](https://solscan.io/token/Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB) | # How ERC-7683 works in Eco Routes Source: https://docs.eco.com/routes/architecture/erc-7683 Learn how Eco Routes implements ERC-7683 for standardized order execution. ERC-7683 is the standard for cross-chain order protocols. Eco Routes implements this standard interface through two core contracts that handle the intent lifecycle across chains. ## Architecture **Origin Chain**: `OriginSettler` handles intent creation, signature verification, and fund escrow\ **Destination Chain**: `DestinationSettler` manages intent fulfillment and proof generation ## OriginSettler Entry point for creating cross-chain intents on source chains. Implements EIP-712 signature verification with domain name "EcoPortal" and version "1". ### open() Creates an intent directly onchain with atomic funding. ```solidity theme={null} function open(OnchainCrossChainOrder calldata order) external payable ``` **Process:** 1. Validates `orderDataType` matches `ORDER_DATA_TYPEHASH` 2. Decodes `OrderData` from the order 3. Calls `_publishAndFund()` to create intent and transfer funds 4. Emits `Open` event with resolved order details ### openFor() Creates a gasless intent on behalf of a user using EIP-712 signatures. ```solidity theme={null} function openFor( GaslessCrossChainOrder calldata order, bytes calldata signature, bytes calldata /* originFillerData */ ) external payable ``` **Security Validations:** * `block.timestamp <= order.openDeadline` * `order.originSettler == address(this)` * `order.originChainId == block.chainid` * `orderDataType` matches `ORDER_DATA_TYPEHASH` * Signature verification via `_validateOrderSig()` **Replay Protection:** Built into `_publishAndFund()` through vault state checking (Initial, Funded, Withdrawn, Refunded). ### Resolution Functions Convert Eco-specific order data into ERC-7683 compliant format: ```solidity theme={null} function resolve(OnchainCrossChainOrder calldata order) public view returns (ResolvedCrossChainOrder memory) function resolveFor(GaslessCrossChainOrder calldata order, bytes calldata) public view returns (ResolvedCrossChainOrder memory) ``` **Resolution Process:** 1. Converts `Reward` structure into `Output[]` array (rewards and native ETH if present) 2. Calculates `routeHash`, `rewardHash`, and `intentHash` 3. Creates `FillInstruction` with encoded `(route, rewardHash)` as `originData` **Key Mappings:** * `user`: `orderData.reward.creator` * `originChainId`: `block.chainid` * `fillDeadline`: `orderData.routeDeadline` * `orderId`: `intentHash` * `minReceived`: Reward outputs with origin chain IDs * `fillInstructions[0].destinationSettler`: `orderData.routePortal` ### EIP-712 Signature **GaslessCrossChainOrder TypeHash:** ```solidity theme={null} keccak256( "GaslessCrossChainOrder(address originSettler,address user,uint256 nonce,uint256 originChainId,uint32 openDeadline,uint32 fillDeadline,bytes32 orderDataType,bytes32 orderDataHash)" ) ``` Verification recovers signer using ECDSA and compares with `order.user`. ### Abstract Method ```solidity theme={null} function _publishAndFund( uint64 destination, bytes memory route, Reward memory reward, bool allowPartial, address funder ) internal virtual returns (bytes32 intentHash, address vault); ``` Implementations handle vault creation, fund transfers, state checking, and excess ETH returns. ## DestinationSettler Handles intent fulfillment on destination chains. ### fill() Executes a cross-chain order on the destination. ```solidity theme={null} function fill( bytes32 orderId, bytes calldata originData, bytes calldata fillerData ) external payable ``` **Parameters:** * `orderId`: Intent hash from origin chain * `originData`: Encoded `(bytes route, bytes32 rewardHash)` * `fillerData`: Encoded `(address prover, uint64 source, bytes32 claimant, bytes proverData)` **Execution:** 1. Decodes `originData` to extract route and rewardHash 2. Emits `OrderFilled(orderId, msg.sender)` 3. Decodes `fillerData` for prover details 4. Calls `fulfillAndProve()` with decoded parameters ### Abstract Method ```solidity theme={null} function fulfillAndProve( bytes32 intentHash, Route memory route, bytes32 rewardHash, bytes32 claimant, address prover, uint64 source, bytes memory data ) public payable virtual returns (bytes[] memory); ``` Implementations execute route instructions and initiate proof generation atomically. ## Data Types ### OnchainCrossChainOrder ```solidity theme={null} struct OnchainCrossChainOrder { uint32 fillDeadline; bytes32 orderDataType; bytes orderData; // ABI-encoded OrderData } ``` ### GaslessCrossChainOrder ```solidity theme={null} struct GaslessCrossChainOrder { address originSettler; address user; uint256 nonce; uint256 originChainId; uint32 openDeadline; uint32 fillDeadline; bytes32 orderDataType; bytes orderData; // ABI-encoded OrderData } ``` ### OrderData ```solidity theme={null} struct OrderData { uint64 destination; bytes route; Reward reward; uint256 maxSpent; address routePortal; uint256 routeDeadline; } ``` ### ResolvedCrossChainOrder ```solidity theme={null} struct ResolvedCrossChainOrder { address user; uint256 originChainId; uint32 openDeadline; uint32 fillDeadline; bytes32 orderId; uint256 maxSpent; Output[] minReceived; FillInstruction[] fillInstructions; } ``` ### Output ```solidity theme={null} struct Output { bytes32 token; // Address as bytes32, zero for native uint256 amount; bytes32 recipient; // Zero address in Eco's model uint256 chainId; } ``` ### FillInstruction ```solidity theme={null} struct FillInstruction { uint64 destinationChainId; address destinationSettler; bytes originData; // Encoded (route, rewardHash) } ``` ## Integration Examples ### Creating Direct Intent ```solidity theme={null} OnchainCrossChainOrder memory order = OnchainCrossChainOrder({ fillDeadline: uint32(block.timestamp + 1 hours), orderDataType: ORDER_DATA_TYPEHASH, orderData: abi.encode(OrderData({ destination: 42161, route: encodedRoute, reward: rewardStruct, maxSpent: 1000000, routePortal: portalAddress, routeDeadline: block.timestamp + 1 hours })) }); originSettler.open{value: fundingAmount}(order); ``` ### Creating Gasless Intent ```solidity theme={null} // Off-chain: User signs EIP-712 message bytes32 structHash = keccak256(abi.encode( GASLESS_CROSSCHAIN_ORDER_TYPEHASH, order.originSettler, order.user, order.nonce, order.originChainId, order.openDeadline, order.fillDeadline, order.orderDataType, keccak256(order.orderData) )); bytes32 digest = keccak256(abi.encodePacked( "\x19\x01", domainSeparator, structHash )); // On-chain: Solver submits originSettler.openFor(order, signature, ""); ``` ### Fulfilling Intent ```solidity theme={null} bytes memory originData = abi.encode(route, rewardHash); bytes memory fillerData = abi.encode( proverAddress, sourceChainId, claimantBytes32, proverSpecificData ); destinationSettler.fill{value: executionValue}( orderId, originData, fillerData ); ``` ## Error Handling * `TypeSignatureMismatch()`: OrderDataType doesn't match ORDER\_DATA\_TYPEHASH * `OpenDeadlinePassed()`: Current time exceeds opening deadline * `InvalidOriginSettler()`: Order specifies wrong settler address * `InvalidOriginChainId()`: Order targets different chain * `InvalidSignature()`: Signature verification failed ## Eco-Specific Implementation Notes 1. **Recipient Field**: `Output.recipient` always zero address; actual claimant specified in `fillerData` at fulfillment 2. **ChainId Semantics**: `minReceived` outputs use origin chainId (rewards claimed on source chain) 3. **FillInstruction.originData**: Contains `(route, rewardHash)` tuple instead of complete intent 4. **Single Fill**: Each order has exactly one fill instruction # How the executor contract works Source: https://docs.eco.com/routes/architecture/executor Isolated contract that executes intent calls on the destination chain The Executor is an isolated contract that executes intent calls on the destination chain. It provides security isolation by separating arbitrary call execution from the Portal's storage and balance. ## Purpose The Executor serves as a security boundary for intent execution: **Without Executor:** ``` Portal (with storage) → executes arbitrary calls ❌ Risk: Malicious calls can manipulate Portal state ``` **With Executor:** ``` Portal (with storage) → Executor (stateless) → executes arbitrary calls ✅ Portal state protected from execution context ``` ## Architecture ``` Portal ↓ calls Executor (single instance) ↓ executes User-specified calls (swaps, transfers, etc.) ``` **Key Properties:** * One executor per Portal deployment * Stateless (no storage between transactions) * Only Portal can trigger execution * Receives tokens from solver before execution * Returns unused tokens/ETH to solver after execution ## Access Control ```solidity theme={null} modifier onlyPortal() ``` The executor can only be called by the Portal that deployed it. This prevents: * Direct execution of arbitrary calls * Bypassing intent validation * Unauthorized token access ## Execution Flow ### Call Execution ```solidity theme={null} function execute(Call[] calldata calls) external payable onlyPortal returns (bytes[] memory) ``` **Process:** 1. Portal validates intent and transfers tokens to executor 2. Portal calls executor with intent's call array 3. Executor validates and executes each call sequentially 4. Executor returns results to Portal 5. Portal refunds unused ETH to solver ### Single Call ```solidity theme={null} function execute(Call calldata call) internal returns (bytes memory) ``` Each call is executed with: * **Target**: Contract address to call * **Value**: Native token amount to send * **Data**: Encoded function call data **Validation:** * Checks if call targets EOA with calldata (security check) * Uses low-level `call()` for execution * Reverts entire transaction if any call fails ## Security Features ### EOA Protection ```solidity theme={null} function _isCallToEoa(Call calldata call) internal view returns (bool) ``` Prevents calls to externally owned accounts (EOAs) when calldata is present: ```solidity theme={null} if (target.code.length == 0 && calldata.length > 0) { revert CallToEOA(target); } ``` **Why this matters:** * EOAs cannot execute code, so calldata is meaningless * Calldata to EOA might indicate misconfiguration * Prevents potential phishing where calldata appears to be a function call **Allowed:** * Native token transfers to EOAs (zero calldata) * Calls to contracts (non-zero code length) **Blocked:** * Calls to EOAs with calldata (suspicious pattern) ### Storage Isolation The Executor has no state variables except `immutable portal`: * Cannot be manipulated through reentrancy * No persistent state to corrupt * Fresh execution context for each intent ### Atomic Execution All calls execute atomically: * If any call fails, entire transaction reverts * No partial execution of intent * Solver either succeeds completely or loses only gas ## Token Handling ### Token Flow ``` Solver (approves tokens) ↓ Portal (validates and transfers) ↓ Executor (receives tokens) ↓ Executor (executes calls using tokens) ↓ Destination contracts (swaps, transfers, etc.) ``` ### Native Tokens The Executor accepts ETH via: ```solidity theme={null} receive() external payable {} ``` Portal transfers `route.nativeAmount` to Executor before execution: ```solidity theme={null} executor.execute{value: route.nativeAmount}(route.calls); ``` ### ERC20 Tokens Portal transfers ERC20s to Executor before execution: ```solidity theme={null} for (TokenAmount memory token : route.tokens) { IERC20(token.token).safeTransferFrom( solver, address(executor), token.amount ); } ``` Executor then uses these tokens during call execution. ## Call Structure ```solidity theme={null} struct Call { address target; // Contract to call uint256 value; // Native tokens to send bytes data; // Encoded function call } ``` ### Example Calls **Token Swap:** ```solidity theme={null} Call({ target: uniswapRouter, value: 0, data: abi.encodeWithSelector( IUniswapRouter.swapExactTokensForTokens.selector, amountIn, amountOutMin, path, recipient, deadline ) }) ``` **Native Token Transfer:** ```solidity theme={null} Call({ target: recipientAddress, value: 1 ether, data: "" // Empty for simple transfer }) ``` **Contract Interaction:** ```solidity theme={null} Call({ target: dappContract, value: 0.1 ether, data: abi.encodeWithSelector( IDapp.deposit.selector, amount, params ) }) ``` ## Error Handling ### CallToEOA ```solidity theme={null} error CallToEOA(address target) ``` Thrown when attempting to call an EOA with calldata. **Resolution:** * Remove calldata for simple transfers * Use correct contract address if calling contract * Verify target address is not an EOA ### CallFailed ```solidity theme={null} error CallFailed(Call call, bytes result) ``` Thrown when any call execution fails. **Contains:** * Full call data (target, value, data) * Revert reason from failed call **Common Causes:** * Insufficient token balance in executor * Reverted function on target contract * Out of gas * Invalid function selector ### NonPortalCaller ```solidity theme={null} error NonPortalCaller(address caller) ``` Thrown when non-Portal address attempts execution. **Resolution:** * Only Portal can call executor * Use Portal's fulfill methods instead of direct executor calls ## Execution Patterns ### Simple Transfer Intent that sends tokens to recipient: ```solidity theme={null} Call[] memory calls = new Call[](1); calls[0] = Call({ target: recipientAddress, value: 0, data: "" }); ``` Executor transfers tokens that were sent to it by Portal. ### Swap and Transfer Intent that swaps tokens and sends result: ```solidity theme={null} Call[] memory calls = new Call[](2); // 1. Swap on DEX calls[0] = Call({ target: dexRouter, value: 0, data: abi.encodeWithSelector( IRouter.swap.selector, tokenIn, tokenOut, amountIn, address(executor) // Swap result to executor ) }); // 2. Transfer swapped tokens calls[1] = Call({ target: tokenOut, value: 0, data: abi.encodeWithSelector( IERC20.transfer.selector, finalRecipient, amountOut ) }); ``` ### Multi-Step DeFi Intent executing complex DeFi operations: ```solidity theme={null} Call[] memory calls = new Call[](4); // 1. Approve DEX calls[0] = Call({ target: token, value: 0, data: abi.encodeWithSelector( IERC20.approve.selector, dexRouter, amount ) }); // 2. Swap calls[1] = Call({ target: dexRouter, value: 0, data: /* swap calldata */ }); // 3. Add liquidity calls[2] = Call({ target: liquidityPool, value: 0, data: /* addLiquidity calldata */ }); // 4. Transfer LP tokens calls[3] = Call({ target: lpToken, value: 0, data: /* transfer calldata */ }); ``` ## Gas Considerations ### Per-Call Overhead Each call incurs: * **Call validation**: \~2,000 gas (EOA check) * **Low-level call**: \~2,600 gas base, plus execution * **Result handling**: \~1,000 gas ### Batch Optimization Multiple calls in one intent are more efficient than separate intents: * Shared validation costs * Single token transfer from solver * Atomic execution reduces failure risk ### Failed Call Costs When a call fails: * Gas consumed up to failure point * Full transaction reverts (no state changes) * Solver pays gas but receives no reward ## Integration Patterns ### For Intent Creators **Encoding Calls:** ```solidity theme={null} // Create calls for route Call[] memory calls = new Call[](2); calls[0] = Call({ target: tokenAddress, value: 0, data: abi.encodeWithSelector( IERC20.transfer.selector, recipient, amount ) }); // Include in intent Intent memory intent = Intent({ // ... route: Route({ // ... calls: calls }) }); ``` **Testing Calls:** ```solidity theme={null} // Test call execution offchain (bool success, bytes memory result) = target.call{value: value}(data); require(success, "Call would fail"); ``` ### For Solvers **Pre-Execution Validation:** ```solidity theme={null} // Before fulfilling, validate calls won't fail for (Call memory call : route.calls) { // Simulate each call (bool success, ) = call.target.call{value: call.value}(call.data); require(success, "Call simulation failed"); } // Now fulfill intent portal.fulfill(intentHash, route, rewardHash, claimant); ``` **Token Preparation:** ```solidity theme={null} // Approve tokens for Portal before fulfillment for (TokenAmount memory token : route.tokens) { IERC20(token.token).approve( address(portal), token.amount ); } ``` ## Security Best Practices ### For Intent Creators 1. **Test Thoroughly**: Simulate all calls before publishing intent 2. **Minimal Privileges**: Only include necessary calls in route 3. **Verify Targets**: Ensure all target addresses are correct contracts 4. **Amount Validation**: Verify all token amounts and values are correct 5. **Deadline Safety**: Set appropriate route deadline for execution window ### For Solvers 1. **Validate Before Fulfilling**: Simulate execution to avoid wasted gas 2. **Check Token Balances**: Ensure sufficient tokens to provide route amounts 3. **Gas Estimation**: Estimate execution costs accurately 4. **Slippage Protection**: Account for price changes in DEX calls 5. **Revert Analysis**: Understand why calls might fail before executing ### For Protocol Integrators 1. **Immutable Executor**: Executor address never changes per Portal 2. **No Direct Calls**: Never call executor directly, always through Portal 3. **Result Handling**: Process execution results appropriately 4. **Error Recovery**: Handle call failures gracefully in UI/SDK ## Limitations ### No Persistent State Executor cannot: * Store data between transactions * Accumulate tokens across intents * Maintain allowances or permissions Each execution starts fresh. ### No Token Retention After execution: * All tokens should be transferred to final destinations * Executor should have zero balance * Any remaining tokens are stuck until next execution **Best Practice:** Include cleanup call to sweep any dust. ### Sequential Execution Calls execute in order: * Cannot parallelize * Later calls depend on earlier ones * Any failure reverts all **Design Consideration:** Order calls carefully for dependencies. ## Comparison to Alternatives ### Direct Portal Execution ``` ❌ Portal executes calls directly Risk: Malicious calls access Portal storage ``` ### Executor Pattern (Current) ``` ✅ Executor executes calls in isolation Benefit: Portal storage protected ``` ### External Executor ``` ❌ Separate executor deployed per intent Cost: Higher gas for deployment Complexity: Address management ``` The single stateless executor provides optimal balance of security, gas efficiency, and simplicity. # Routes architecture Source: https://docs.eco.com/routes/architecture/overview How the Routes contracts fit together, Portal, Vault, Executor, ERC-7683, and the modular Prover stack. The Routes protocol is a small set of contracts deployed on every supported chain, plus a modular settlement layer the user picks at intent-creation time. ## The contracts | Contract | Role | Per-intent? | | ----------------------------------------- | -------------------------------------------------------------------------- | ------------------------ | | [Portal](/routes/architecture/portal) | Single entry point: publish, fund, fulfill, prove, withdraw, refund | One per chain | | [Vault](/routes/architecture/vault) | Per-intent escrow: holds reward tokens until proven fulfillment | One per intent (CREATE2) | | [Executor](/routes/architecture/executor) | Stateless contract that runs user-specified destination calls | One per Portal | | [ERC-7683](/routes/architecture/erc-7683) | Standardized cross-chain order interface (origin and destination settlers) | Built into Portal | ## The modular settlement layer The Portal accepts any contract that implements the `IProver` interface. Six provers ship today; the user picks one per intent. | Prover | Settlement method | Best for | | --------------------------------------------------- | ------------------------------------- | ---------------------------------- | | [CCTP](/routes/architecture/provers/cctp) | Issuer attestations | USDC routes, regulated flows | | [Hyperlane](/routes/architecture/provers/hyperlane) | Hyperlane ISM | General-purpose, fast | | [LayerZero](/routes/architecture/provers/layerzero) | DVN consensus | Wide chain coverage | | Chainlink CCIP | Cross-Chain Interoperability Protocol | General cross-chain, wide coverage | | [Polymer](/routes/architecture/provers/polymer) | IBC light clients | Trust-minimized | | [Local](/routes/architecture/provers/local) | Same-chain `flashFulfill` | Same-chain, orchestration mode | ## Information flow Sequence of every contract call from intent publication to reward release: publishAndFund, lock reward, fulfill, execute route calls, emit and dispatch proof, withdraw, verify proof, release and transfer reward. ## Design principles **Non-upgradable.** The Portal has no proxy, no admin keys, no upgrade path. Audited once, valid forever. **Stateless executor.** The Executor has no persistent storage. Each intent's calls run in isolation. A malicious call cannot corrupt Portal state. **Per-intent vaults.** Funding is a vanilla ERC-20 transfer to a CREATE2 address. Cold wallets, hardware devices, multisigs, exchanges, PSPs all participate without changes. **Modular settlement.** New messaging protocols plug in via `IProver` without changes to Portal, vaults, or solver tooling. **Cross-VM.** EVM, SVM, and TVM are first-class. Address fields use `bytes32` to hold any chain's address format. # How the portal contract works Source: https://docs.eco.com/routes/architecture/portal Main entry point for intent publishing, fulfillment, and reward claiming The Portal is the single contract you interact with on every chain. It handles intent publishing, fulfillment, reward claiming, and [ERC-7683](/routes/architecture/erc-7683) compatibility. It holds no funds between transactions, all rewards sit in deterministically-deployed per-intent vaults. ## Architecture ``` Portal ├── Source-chain operations (intent publishing and management) │ └── OriginSettler (ERC-7683 origin functionality) └── Destinationchain operations (fulfillment and proving) └── DestinationSettler (ERC-7683 destination functionality) ``` The Portal does not hold funds between transactions. All rewards are escrowed in deterministically-deployed vault contracts. ## Intent Lifecycle ### 1. Publishing (Source Chain) Users create intents specifying execution instructions for the destination chain and rewards for solvers. **Direct Publishing:** ```solidity theme={null} function publish(Intent calldata intent) public returns (bytes32 intentHash, address vault) ``` Creates an intent without funding. Emits `IntentPublished` event. **Atomic Publishing with Funding:** ```solidity theme={null} function publishAndFund(Intent calldata intent, bool allowPartial) public payable returns (bytes32 intentHash, address vault) ``` Creates and funds an intent in a single transaction. **Universal Format Publishing:** ```solidity theme={null} function publish( uint64 destination, bytes memory route, Reward memory reward ) public returns (bytes32 intentHash, address vault) ``` Cross-VM compatible publishing using bytes format for route data. ### 2. Funding (Source Chain) Intents can be funded separately after creation: ```solidity theme={null} function fund( uint64 destination, bytes32 routeHash, Reward calldata reward, bool allowPartial ) external payable returns (bytes32 intentHash) ``` **Funding for Others:** ```solidity theme={null} function fundFor( uint64 destination, bytes32 routeHash, Reward calldata reward, bool allowPartial, address funder, address permitContract ) external payable returns (bytes32 intentHash) ``` Uses permit contracts for gasless token approvals. Cannot be used for intents with native token rewards. ### 3. Fulfillment (Destination Chain) Solvers execute intent instructions on the destination chain: ```solidity theme={null} function fulfill( bytes32 intentHash, Route memory route, bytes32 rewardHash, bytes32 claimant ) external payable returns (bytes[] memory) ``` **Parameters:** * `intentHash`: Unique identifier of the intent * `route`: Execution instructions and token requirements * `rewardHash`: Hash of reward structure for verification * `claimant`: Cross-VM compatible identifier for reward recipient (bytes32) **Atomic Fulfillment with Proving:** ```solidity theme={null} function fulfillAndProve( bytes32 intentHash, Route memory route, bytes32 rewardHash, bytes32 claimant, address prover, uint64 sourceChainDomainID, bytes memory data ) public payable returns (bytes[] memory) ``` Executes intent and initiates cross-chain proof in one transaction. **⚠️ Important:** `sourceChainDomainID` is NOT the chain ID. Each bridge uses its own domain ID system: * **Hyperlane**: Custom domain IDs * **LayerZero**: Endpoint IDs Consult the bridge provider's documentation for correct domain IDs. ### 4. Proving (Destination Chain) Generate proofs for fulfilled intents: ```solidity theme={null} function prove( address prover, uint64 sourceChainDomainID, bytes32[] memory intentHashes, bytes memory data ) public payable ``` Sends cross-chain message to source chain verifying intent execution. Can batch multiple intents for gas efficiency. ### 5. Claiming Rewards (Source Chain) After proof verification, solvers claim rewards: ```solidity theme={null} function withdraw( uint64 destination, bytes32 routeHash, Reward calldata reward ) public ``` Transfers rewards from vault to the claimant address specified during fulfillment. **Batch Withdrawal:** ```solidity theme={null} function batchWithdraw( uint64[] calldata destinations, bytes32[] calldata routeHashes, Reward[] calldata rewards ) external ``` Efficiently claim multiple rewards in one transaction. ### 6. Refunds (Source Chain) Creators can reclaim rewards after expiry: ```solidity theme={null} function refund( uint64 destination, bytes32 routeHash, Reward calldata reward ) external ``` Only succeeds if: * Intent has expired (`block.timestamp >= reward.deadline`) * Intent was not fulfilled on the correct destination chain ## ERC-7683 Compatibility The Portal implements both ERC-7683 origin and destination settler interfaces. ### Origin Functions **Direct Order Opening:** ```solidity theme={null} function open(OnchainCrossChainOrder calldata order) external payable ``` Creates and funds an intent from an onchain order structure. **Gasless Order Opening:** ```solidity theme={null} function openFor( GaslessCrossChainOrder calldata order, bytes calldata signature, bytes calldata /* originFillerData */ ) external payable ``` Creates an intent using EIP-712 signature. Validates: * Signature matches `order.user` * `openDeadline` not passed * `originSettler` matches contract address * `originChainId` matches current chain **Order Resolution:** ```solidity theme={null} function resolve(OnchainCrossChainOrder calldata order) public view returns (ResolvedCrossChainOrder memory) function resolveFor(GaslessCrossChainOrder calldata order, bytes calldata) public view returns (ResolvedCrossChainOrder memory) ``` Converts Eco orders into ERC-7683 standard format. ### Destination Functions **ERC-7683 Fulfillment:** ```solidity theme={null} function fill( bytes32 orderId, bytes calldata originData, bytes calldata fillerData ) external payable ``` **Parameters:** * `orderId`: Intent hash from origin chain * `originData`: Encoded `(bytes route, bytes32 rewardHash)` * `fillerData`: Encoded `(address prover, uint64 source, bytes32 claimant, bytes proverData)` Decodes data and calls `fulfillAndProve()` internally. ## Vault System The Portal uses deterministic CREATE2 deployment for intent vaults. ### Computing Vault Address ```solidity theme={null} function intentVaultAddress(Intent calldata intent) public view returns (address) function intentVaultAddress( uint64 destination, bytes memory route, Reward calldata reward ) public view returns (address) ``` Returns the deterministic vault address without deployment. ### Vault States Vaults track intent lifecycle through status enum: * `Initial`: Intent created but not funded * `Funded`: Fully funded and ready for fulfillment * `Withdrawn`: Rewards claimed by solver * `Refunded`: Rewards returned to creator ```solidity theme={null} function getRewardStatus(bytes32 intentHash) public view returns (Status status) ``` ### Funding Validation ```solidity theme={null} function isIntentFunded(Intent calldata intent) public view returns (bool) function isIntentFunded( uint64 destination, bytes memory route, Reward calldata reward ) public view returns (bool) ``` Checks if vault contains sufficient tokens and native currency. ## Token Recovery Recover tokens mistakenly sent to vaults: ```solidity theme={null} function recoverToken( uint64 destination, bytes32 routeHash, Reward calldata reward, address token ) external ``` **Restrictions:** * Cannot recover zero address * Cannot recover any token in `reward.tokens` * Intent must have zero native rewards OR already be claimed/refunded ## Intent Hashing The Portal uses a deterministic hashing scheme: ```solidity theme={null} function getIntentHash(Intent memory intent) public pure returns ( bytes32 intentHash, bytes32 routeHash, bytes32 rewardHash ) ``` **Formula:** ``` routeHash = keccak256(abi.encode(route)) rewardHash = keccak256(abi.encode(reward)) intentHash = keccak256(abi.encodePacked(destination, routeHash, rewardHash)) ``` This allows verification on destination chains without transmitting full intent data. ## Execution Model ### Source Chain Execution The Portal transfers tokens from users to vaults: 1. Native tokens via `payable` function calls 2. ERC20 tokens via `SafeERC20.safeTransferFrom` 3. Excess ETH refunded automatically ### Destination Chain Execution The Portal delegates call execution to an `Executor` contract: 1. Transfers tokens from solver to Executor 2. Executor performs calls with delegated tokens 3. Executor has no persistent state between transactions This isolation ensures: * Portal storage is protected during arbitrary calls * Failed calls don't corrupt Portal state * Executor can be upgraded independently ## Security Features ### Replay Protection * Intent hashes are unique (include salt in route) * Fulfilled intents cannot be fulfilled again * Withdrawn/refunded intents cannot be withdrawn/refunded again * ERC-7683 gasless orders use nonces ### Validation **Publishing:** * Cannot republish withdrawn/refunded intents * Validates reward structure consistency **Fulfillment:** * Verifies intent hash matches route and reward * Checks portal address in route matches contract * Validates deadline hasn't passed * Prevents zero claimant address * Requires minimum native token amount **Withdrawal:** * Validates intent is proven on correct destination * Challenges proofs for incorrect destinations * Prevents withdrawal before proof **Refund:** * Only after deadline expiry * Only if not proven on correct destination * Prevents refund of claimed intents ### Reentrancy Protection All external calls are made through: * `SafeERC20` for token transfers * Isolated `Executor` for user-specified calls * State updates before external interactions ## Cross-VM Compatibility The Portal supports both EVM and non-EVM chains: ### Address Conversion ```solidity theme={null} import {AddressConverter} from "./libs/AddressConverter.sol"; // EVM address to bytes32 bytes32 universalId = address.toBytes32(); // bytes32 to EVM address address evmAddress = bytes32Id.toAddress(); ``` ### Claimant Identifiers On destination chains, claimants are stored as `bytes32`: ```solidity theme={null} mapping(bytes32 => bytes32) public claimants; ``` This supports: * EVM addresses (20 bytes, left-padded) * Solana addresses (32 bytes) * Other blockchain address formats ## Events ### IntentPublished ```solidity theme={null} event IntentPublished( bytes32 indexed hash, uint64 destination, bytes route, address indexed creator, address indexed prover, uint256 deadline, uint256 nativeAmount, TokenAmount[] tokens ); ``` ### IntentFunded ```solidity theme={null} event IntentFunded( bytes32 indexed intentHash, address indexed funder, bool complete ); ``` ### IntentFulfilled ```solidity theme={null} event IntentFulfilled( bytes32 indexed intentHash, bytes32 claimant ); ``` ### IntentProven ```solidity theme={null} event IntentProven( bytes32 indexed intentHash, bytes32 claimant ); ``` ### IntentWithdrawn ```solidity theme={null} event IntentWithdrawn( bytes32 indexed hash, address indexed recipient ); ``` ### IntentRefunded ```solidity theme={null} event IntentRefunded( bytes32 indexed hash, address indexed recipient ); ``` ### IntentTokenRecovered ```solidity theme={null} event IntentTokenRecovered( bytes32 indexed intentHash, address indexed recipient, address indexed token ); ``` ### Open (ERC-7683) ```solidity theme={null} event Open( bytes32 indexed orderId, ResolvedCrossChainOrder resolvedOrder ); ``` ### OrderFilled (ERC-7683) ```solidity theme={null} event OrderFilled( bytes32 indexed orderId, address indexed filler ); ``` ## Error Handling * `ArrayLengthMismatch()`: Array parameters have mismatched lengths * `ChainIdTooLarge(uint256)`: Chain ID exceeds uint64 maximum * `InsufficientFunds(bytes32)`: Incomplete funding when partial not allowed * `InsufficientNativeAmount(uint256, uint256)`: Insufficient native tokens for execution * `IntentAlreadyExists(bytes32)`: Cannot republish existing intent * `IntentAlreadyFulfilled(bytes32)`: Intent already fulfilled * `IntentExpired()`: Past route deadline * `IntentNotClaimed(bytes32)`: Cannot refund claimed intent * `IntentNotFulfilled(bytes32)`: Intent not in fulfilled state * `InvalidClaimant()`: Zero address claimant * `InvalidHash(bytes32)`: Computed hash doesn't match provided hash * `InvalidOriginChainId(uint256, uint256)`: Chain ID mismatch in ERC-7683 order * `InvalidOriginSettler(address, address)`: Settler address mismatch in ERC-7683 order * `InvalidPortal(address)`: Route portal doesn't match contract * `InvalidRecoverToken(address)`: Cannot recover specified token * `InvalidSignature()`: EIP-712 signature verification failed * `InvalidStatusForFunding(Status)`: Cannot fund withdrawn/refunded intent * `InvalidStatusForRefund(Status, uint256, uint256)`: Invalid refund conditions * `InvalidStatusForWithdrawal(Status)`: Cannot withdraw non-funded intent * `OpenDeadlinePassed()`: ERC-7683 order opening deadline exceeded * `TypeSignatureMismatch()`: ERC-7683 orderDataType mismatch * `ZeroClaimant()`: Claimant cannot be zero ## Integration Examples ### Creating and Funding an Intent ```solidity theme={null} Intent memory intent = Intent({ destination: 42161, // Arbitrum route: Route({ salt: keccak256(abi.encode(user, nonce)), deadline: block.timestamp + 1 hours, portal: destinationPortalAddress, nativeAmount: 0.1 ether, tokens: tokenAmounts, calls: calls }), reward: Reward({ creator: msg.sender, prover: hyperProverAddress, deadline: block.timestamp + 1 hours, nativeAmount: 0.05 ether, tokens: rewardTokens }) }); portal.publishAndFund{value: totalNativeAmount}(intent, false); ``` ### Fulfilling on Destination ```solidity theme={null} bytes32 intentHash = /* from IntentPublished event */; Route memory route = /* from event data */; bytes32 rewardHash = keccak256(abi.encode(reward)); bytes32 claimant = bytes32(uint256(uint160(solverAddress))); // Approve tokens for Portal for (uint i = 0; i < route.tokens.length; i++) { IERC20(route.tokens[i].token).approve( address(portal), route.tokens[i].amount ); } // Fulfill and prove portal.fulfillAndProve{value: route.nativeAmount + bridgeFee}( intentHash, route, rewardHash, claimant, proverAddress, sourceChainDomainID, proverData ); ``` ### Claiming Rewards ```solidity theme={null} // After proof verification on source chain portal.withdraw(destination, routeHash, reward); ``` ### Batch Operations ```solidity theme={null} uint64[] memory destinations = new uint64[](3); bytes32[] memory routeHashes = new bytes32[](3); Reward[] memory rewards = new Reward[](3); // Populate arrays... portal.batchWithdraw(destinations, routeHashes, rewards); ``` # How the CCTP Prover Works Source: https://docs.eco.com/routes/architecture/provers/cctp Attestation-based settlement using Circle's CCTP infrastructure The CCTP Prover settles intents using Circle's own attestation infrastructure, the `receiveMessage` function that powers native CCTP transfers. Settlement security is identical to CCTP because settlement IS a CCTP transfer. ## How It Works 1. **Fulfillment**: Solver fulfills the intent on the destination chain Portal 2. **Attestation request**: The CCTP Prover dispatches a message through Circle's `MessageTransmitter` 3. **Circle verification**: Circle's attestation service verifies and signs the message, the same process used for all native CCTP transfers 4. **Settlement**: The source chain receives the attested message via `receiveMessage` and marks the intent as proven, allowing the solver to withdraw rewards ## Security Model The CCTP Prover inherits its security directly from Circle's attestation service. There is no additional trust assumption beyond what a native CCTP transfer already requires, the prover is a thin wrapper that routes intent settlement through Circle's existing infrastructure. This makes the CCTP Prover particularly suitable for regulated environments and institutional use cases where the trust model of the settlement layer must be well-understood and auditable. ## Message Format CCTP Prover uses the same standardized message format as other Eco bridge provers: * **8 bytes**: Destination chain ID * **64 bytes per intent**: `[intentHash (32 bytes)][claimant (32 bytes)]` ## When to Use CCTP Prover **Ideal for:** * Native USDC transfers where Circle's attestation security is preferred * Institutional and regulated use cases requiring a well-established trust model * Flows that already involve CCTP (e.g., burn-and-mint USDC transfers) **For other cross-chain options, see:** * [Hyperlane Prover](/routes/architecture/provers/hyperlane) * [LayerZero Prover](/routes/architecture/provers/layerzero) * [Polymer Prover](/routes/architecture/provers/polymer) * [Local Prover](/routes/architecture/provers/local) for same-chain intents # How the Hyperlane prover works Source: https://docs.eco.com/routes/architecture/provers/hyperlane Cross-chain verification using Hyperlane's modular interoperability stack The Hyperlane Prover enables cross-chain intent verification using Hyperlane's modular interoperability stack. It receives messages via `IMessageRecipient` and extends `MessageBridgeProver` for common proving functionality. ## Architecture ``` Destination Chain Source Chain ━━━━━━━━━━━━━━━━ ━━━━━━━━━━━━━━ Portal Portal ↓ ↑ HyperProver HyperProver ↓ ↑ Hyperlane Mailbox ──────────→ Hyperlane Mailbox ``` ## Configuration ```solidity theme={null} constructor( address mailbox, // Hyperlane mailbox on this chain address portal, // Portal contract address bytes32[] memory provers // Trusted prover addresses on other chains ) ``` **Key Points:** * `mailbox`: Chain-specific Hyperlane mailbox address * `portal`: Portal contract that initiates proving * `provers`: Whitelist of trusted provers on source chains (bytes32 format for cross-VM compatibility) * `minGasLimit`: Set to 0 in constructor (no minimum enforced, unlike LayerZero) ## Proving Flow ### Sending Proofs (Destination → Source) Called by Portal after intent fulfillment: ```solidity theme={null} portal.prove{value: fee}( address(prover), sourceChainDomainID, intentHashes, data ); ``` **Data Parameter:** ```solidity theme={null} abi.encode( HyperProver.UnpackedData({ sourceChainProver: bytes32(uint256(uint160(sourceProverAddress))), metadata: bytes(""), // Hyperlane metadata or empty for default hookAddr: address(0) // Post-dispatch hook or zero for default }) ) ``` ### Receiving Proofs (Source Chain) Hyperlane mailbox calls `handle()` with message containing: * 8 bytes: destination chain ID * 64 bytes per intent: `[intentHash (32 bytes)][claimant (32 bytes)]` ```solidity theme={null} function handle( uint32 origin, bytes32 sender, bytes calldata messageBody ) public payable ``` Only whitelisted senders accepted via `isWhitelisted(sender)`. ## Fee Calculation ```solidity theme={null} function fetchFee( uint64 domainID, bytes calldata encodedProofs, bytes calldata data ) public view returns (uint256) ``` Queries Hyperlane mailbox using `quoteDispatch()` for accurate fee. Always call before proving to avoid reverts. ## Domain IDs **Critical:** Hyperlane uses custom domain IDs, not chain IDs. ```solidity theme={null} params.destinationDomain = uint32(domainID); ``` The `sourceChainDomainID` parameter must be Hyperlane's domain ID. Consult [Hyperlane Documentation](https://docs.hyperlane.xyz/docs/reference/domains) for mappings. **Example:** * Ethereum: Chain ID 1 → Domain ID 1 * Arbitrum: Chain ID 42161 → Domain ID 42161 * Optimism: Chain ID 10 → Domain ID 10 Note: Many Hyperlane domains match chain IDs, but not all (e.g., testnets). ## Metadata and Hooks ### Default Configuration ```solidity theme={null} metadata: bytes("") hookAddr: address(0) ``` Uses mailbox's default hook: ```solidity theme={null} params.hook = IMailbox(MAILBOX).defaultHook(); ``` ### Custom Hook Specify custom post-dispatch hook for advanced configurations: ```solidity theme={null} HyperProver.UnpackedData({ sourceChainProver: sourceProver, metadata: customMetadata, hookAddr: customHookAddress }) ``` **Hook Use Cases:** * Custom gas payment strategies * Message aggregation * Alternative security models ### Metadata Pass custom metadata for hook configuration: ```solidity theme={null} metadata: encodedMetadata ``` Metadata format depends on the hook being used. See Hyperlane documentation for hook-specific encoding. ## Security ### Whitelisting Only provers in the whitelist can send messages: ```solidity theme={null} function isWhitelisted(bytes32 sender) internal view returns (bool) ``` ### Access Control * Only Hyperlane mailbox can call `handle()` * Only Portal can call `prove()` * Zero domain ID rejected: `if (origin == 0) revert ZeroDomainID()` ### Validation Sender validation in `handle()`: ```solidity theme={null} if (sender == bytes32(0)) revert SenderCannotBeZeroAddress(); ``` ## Integration Example ```solidity theme={null} // Deploy prover bytes32[] memory trustedProvers = new bytes32[](1); trustedProvers[0] = bytes32(uint256(uint160(ethereumProverAddress))); HyperProver prover = new HyperProver( hyperlaneMailbox, // Chain-specific mailbox portalAddress, trustedProvers ); // Fulfill and prove bytes memory proverData = abi.encode( HyperProver.UnpackedData({ sourceChainProver: bytes32(uint256(uint160(ethereumProver))), metadata: "", hookAddr: address(0) // Use default hook }) ); uint256 fee = prover.fetchFee(1, encodedProofs, proverData); portal.fulfillAndProve{value: route.nativeAmount + fee}( intentHash, route, rewardHash, claimant, address(prover), 1, // Ethereum domain ID proverData ); ``` ## Advantages * **Lower Costs**: Generally cheaper than LayerZero * **Modular Security**: Flexible hook system for custom security models * **Simpler Configuration**: No delegate setup, domain IDs often match chain IDs * **Transparent Security**: Open-source validators and relayers ## Limitations * **Smaller Chain Coverage**: \~30 chains vs LayerZero's 50+ * **Less Mature Tooling**: Fewer third-party integrations and explorers * **Hook Complexity**: Advanced features require understanding hook system * **Bridge Dependency**: Relies on Hyperlane infrastructure availability ## Errors * `MailboxCannotBeZeroAddress()`: Invalid mailbox in constructor * `ZeroDomainID()`: Origin domain is zero in handle() * `SenderCannotBeZeroAddress()`: Zero sender in handle() * `DomainIdTooLarge(uint64)`: Domain ID exceeds uint32.max * `NonPortalCaller(address)`: Unauthorized prove() caller * `NotWhitelisted(bytes32)`: Sender not in whitelist ## Constant ```solidity theme={null} string public constant PROOF_TYPE = "Hyperlane"; ``` Identifies this prover's mechanism type. # How the LayerZero prover works Source: https://docs.eco.com/routes/architecture/provers/layerzero Cross-chain verification using LayerZero's omnichain messaging The LayerZero Prover enables cross-chain intent verification using LayerZero's omnichain messaging infrastructure. It receives messages via `ILayerZeroReceiver` and extends `MessageBridgeProver` for common proving functionality. ## Architecture ``` Destination Chain Source Chain ━━━━━━━━━━━━━━━━ ━━━━━━━━━━━━━━ Portal Portal ↓ ↑ LayerZeroProver LayerZeroProver ↓ ↑ LayerZero Endpoint ──────────→ LayerZero Endpoint ``` ## Configuration ```solidity theme={null} constructor( address endpoint, // LayerZero V2 endpoint on this chain address delegate, // Address authorized to configure LayerZero address portal, // Portal contract address bytes32[] memory provers, // Trusted prover addresses on other chains uint256 minGasLimit // Minimum gas limit (defaults to 200k if zero) ) ``` **Key Points:** * `endpoint`: Chain-specific LayerZero V2 endpoint address * `delegate`: Set via `ILayerZeroEndpointV2(endpoint).setDelegate(delegate)` - can configure paths and settings * `provers`: Whitelist of trusted provers on source chains (bytes32 format for cross-VM compatibility) * `minGasLimit`: Enforced minimum is 200,000 gas if zero or lower value provided ## Proving Flow ### Sending Proofs (Destination → Source) Called by Portal after intent fulfillment: ```solidity theme={null} portal.prove{value: fee}( address(prover), sourceChainDomainID, intentHashes, data ); ``` **Data Parameter:** ```solidity theme={null} abi.encode( LayerZeroProver.UnpackedData({ sourceChainProver: bytes32(uint256(uint160(sourceProverAddress))), options: bytes(""), // Empty for default, or custom LayerZero options gasLimit: 250000 // Minimum MIN_GAS_LIMIT enforced }) ) ``` ### Receiving Proofs (Source Chain) LayerZero endpoint calls `lzReceive()` with message containing: * 8 bytes: destination chain ID * 64 bytes per intent: `[intentHash (32 bytes)][claimant (32 bytes)]` Only whitelisted senders accepted via `isWhitelisted(origin.sender)`. ## Fee Calculation ```solidity theme={null} function fetchFee( uint64 domainID, bytes calldata encodedProofs, bytes calldata data ) public view returns (uint256) ``` Returns native token amount required for LayerZero message dispatch. Always call before proving to avoid reverts. ## Domain IDs **Critical:** LayerZero uses Endpoint IDs (eids), not chain IDs. ```solidity theme={null} params.dstEid = uint32(domainID); ``` The `sourceChainDomainID` parameter must be LayerZero's endpoint ID. Consult [LayerZero V2 Documentation](https://docs.layerzero.network/v2/developers/evm/technical-reference/deployed-contracts) for mappings. **Example:** * Ethereum: Chain ID 1 → Endpoint ID 30101 * Arbitrum: Chain ID 42161 → Endpoint ID 30110 ## Message Options ### Default (Empty Options) ```solidity theme={null} options: "" ``` Creates basic options with gas limit: ```solidity theme={null} abi.encodePacked(uint16(3), gasLimit) ``` ### Custom Options Pass encoded LayerZero options for advanced configurations: ```solidity theme={null} options: customEncodedOptions ``` See LayerZero documentation for option encoding. ## Security ### Whitelisting Only provers in the whitelist can send messages: ```solidity theme={null} function isWhitelisted(bytes32 sender) internal view returns (bool) ``` ### Access Control * Only LayerZero endpoint can call `lzReceive()` * Only Portal can call `prove()` * Path initialization allowed only for whitelisted senders ### Gas Limits Minimum gas enforced during unpacking: ```solidity theme={null} if (unpacked.gasLimit < MIN_GAS_LIMIT) { unpacked.gasLimit = MIN_GAS_LIMIT; } ``` ## Integration Example ```solidity theme={null} // Deploy prover bytes32[] memory trustedProvers = new bytes32[](1); trustedProvers[0] = bytes32(uint256(uint160(ethereumProverAddress))); LayerZeroProver prover = new LayerZeroProver( layerZeroEndpoint, // Chain-specific endpoint delegateAddress, // Governance/admin address portalAddress, trustedProvers, 250_000 // 250k gas minimum ); // Fulfill and prove bytes memory proverData = abi.encode( LayerZeroProver.UnpackedData({ sourceChainProver: bytes32(uint256(uint160(ethereumProver))), options: "", gasLimit: 250_000 }) ); uint256 fee = prover.fetchFee(30101, encodedProofs, proverData); portal.fulfillAndProve{value: route.nativeAmount + fee}( intentHash, route, rewardHash, claimant, address(prover), 30101, // Ethereum endpoint ID proverData ); ``` ## Advantages * **Wide Chain Support**: 50+ chains including EVM and non-EVM * **Mature Infrastructure**: Battle-tested with significant TVL * **Flexible Configuration**: Delegate model allows operational control * **Good Tooling**: LayerZero Scan for message tracking ## Limitations * **Higher Costs**: More expensive than native bridges (\$5-50 per message) * **Domain ID Complexity**: Endpoint IDs differ from chain IDs, requires mapping * **Gas Estimation**: Must specify gas limit upfront, failures if underestimated * **Bridge Dependency**: Relies on LayerZero infrastructure availability ## Errors * `EndpointCannotBeZeroAddress()`: Invalid endpoint in constructor * `DelegateCannotBeZeroAddress()`: Invalid delegate in constructor * `SenderCannotBeZeroAddress()`: Zero sender in lzReceive * `DomainIdTooLarge(uint64)`: Domain ID exceeds uint32.max * `InvalidExecutor(address)`: Unexpected executor address * `NonPortalCaller(address)`: Unauthorized prove() caller * `NotWhitelisted(bytes32)`: Sender not in whitelist ## Constant ```solidity theme={null} string public constant PROOF_TYPE = "LayerZero"; ``` Identifies this prover's mechanism type. # How the local prover works Source: https://docs.eco.com/routes/architecture/provers/local Same-chain intent fulfillment without cross-chain messaging The Local Prover handles same-chain intent fulfillment where both intent creation and execution occur on the same blockchain. Unlike cross-chain provers, it requires no message passing infrastructure. ## Architecture Same-chain swap with Eco Routes standard intents: the user publishes and funds on the source chain, then fulfill, prove, and settle execute atomically between the Portal, Vault, Prover, and Solver. ## Purpose Local Prover enables intents to be fulfilled on their origin chain: * No cross-chain messaging required * Zero additional fees beyond gas * Instant proof availability * Useful for same-chain swaps, transfers, or operations ## Orchestration Mode Beyond same-chain convenience, the Local Prover enables a fundamentally different execution model: **orchestration**. In orchestration mode, the user's own funds move through underlying infrastructure, no solver capital required. This is powered by `flashFulfill`, a flash-loan-style operation that fulfills an intent using the vault's own funds within a single atomic transaction. The vault's contents are used directly to execute the desired action on the destination, with no counterparty and no cross-chain message. In Eco Routes, this same-chain self-solve capability is offered as **Flash Intents**, a configurable intents mode that settles same-chain stablecoin orders atomically using the user's own funds. **Settlement vs. Orchestration:** * **Settlement**: A solver provides its own inventory to deliver the user's requested outcome. Zero slippage. * **Orchestration**: User funds move through infrastructure at the cost of gas alone. Zero solver capital required. The Local Prover and `flashFulfill` make the system available even when no solver is online, creating a high-availability fallback: | **Tier** | **Condition** | **Mode** | **Capital Required** | | -------- | -------------------------------------- | --------------------------- | -------------------- | | 1 | Solver available, better pricing | Settlement | Solver capital | | 2 | Solver available, no pricing advantage | Orchestration | Gas only | | 3 | No solver available | Self-solve via local intent | Gas only | All three tiers use the same Portal, vaults, and API. System availability equals chain availability, not solver availability. ## Configuration ```solidity theme={null} constructor(address inbox) ``` **Parameters:** * `inbox`: Portal contract address **Initialization:** * Stores Portal reference as immutable * Records chain ID for proof data validation * Validates chain ID fits in uint64 ## How It Works ### Immediate Proof Creation Unlike cross-chain provers, the Local Prover doesn't create proofs. It reads them: 1. Intent fulfilled on Portal → claimant stored in `Portal.claimants` mapping 2. `provenIntents()` queries Portal's claimant mapping directly 3. Returns proof data with current chain ID and claimant address ### Proof Lookup ```solidity theme={null} function provenIntents(bytes32 intentHash) public view returns (ProofData memory) ``` **Returns:** * `claimant`: Address that fulfilled the intent (converted from bytes32) * `destination`: Current chain ID **If not fulfilled:** * Returns `ProofData(address(0), 0)` ### No-Op Functions #### prove() ```solidity theme={null} function prove( address sender, uint64 sourceChainId, bytes calldata encodedProofs, bytes calldata data ) external payable ``` **Intentionally empty** - no action needed because: * Claimant already stored by Portal during fulfillment * No cross-chain message to send * Proof is immediately available **Can be called** without reverting to support `fulfillAndProve()` pattern. #### challengeIntentProof() ```solidity theme={null} function challengeIntentProof( uint64 destination, bytes32 routeHash, bytes32 rewardHash ) external pure ``` **Intentionally empty** - challenges not applicable because: * Same-chain intents cannot be proven on wrong chain * No cross-chain discrepancy possible ## Usage Patterns ### Creating Same-Chain Intent ```solidity theme={null} Intent memory intent = Intent({ destination: block.chainid, // Same as current chain route: Route({ portal: address(portal), // ... other route params }), reward: Reward({ prover: address(localProver), // Use LocalProver // ... other reward params }) }); portal.publishAndFund{value: amount}(intent, false); ``` ### Fulfilling Same-Chain Intent ```solidity theme={null} // Fulfill without proving portal.fulfill{value: route.nativeAmount}( intentHash, route, rewardHash, claimant ); // OR fulfill and prove (prove is no-op but doesn't revert) portal.fulfillAndProve{value: route.nativeAmount}( intentHash, route, rewardHash, claimant, address(localProver), uint64(block.chainid), "" // Empty data ); ``` Both patterns work identically. `prove()` is a no-op. ### Claiming Rewards ```solidity theme={null} // Check if intent is proven (fulfilled) IProver.ProofData memory proof = localProver.provenIntents(intentHash); require(proof.claimant != address(0), "Not fulfilled"); // Withdraw rewards portal.withdraw( uint64(block.chainid), routeHash, reward ); ``` ## Fee Structure **Zero additional fees:** * No cross-chain messaging costs * No bridge fees * Only standard gas costs for fulfillment and withdrawal ## When to Use Local Prover ### Ideal Use Cases **Same-Chain Operations:** * Token swaps on same chain * Transfers between accounts * DeFi operations on single chain * Testing and development **Advantages:** * Instant settlement (no message delays) * Zero bridging costs * Simpler integration * No external dependencies ### Not Suitable For * Cross-chain transfers * Multi-chain operations * Bridging assets between chains For cross-chain intents, use: * [HyperProver](/routes/architecture/provers/hyperlane) (Hyperlane) * [LayerZeroProver](/routes/architecture/provers/layerzero) (LayerZero) * [PolymerProver](/routes/architecture/provers/polymer) (Polymer IBC) * [CCTP Prover](/routes/architecture/provers/cctp) (Circle attestation) ## Integration Example ```solidity theme={null} // Deploy Local Prover LocalProver localProver = new LocalProver(address(portal)); // Create same-chain intent Intent memory intent = Intent({ destination: uint64(block.chainid), route: Route({ salt: keccak256(abi.encode(user, nonce)), deadline: block.timestamp + 1 hours, portal: address(portal), nativeAmount: 0, tokens: tokenAmounts, calls: calls }), reward: Reward({ creator: msg.sender, prover: address(localProver), deadline: block.timestamp + 1 hours, nativeAmount: 0.01 ether, tokens: rewardTokens }) }); // Publish and fund portal.publishAndFund{value: 0.01 ether}(intent, false); // Solver fulfills portal.fulfill{value: route.nativeAmount}( intentHash, route, rewardHash, bytes32(uint256(uint160(solverAddress))) ); // Solver claims rewards immediately portal.withdraw( uint64(block.chainid), routeHash, reward ); ``` ## Comparison with Cross-Chain Provers | Feature | LocalProver | Cross-Chain Provers | | ---------------------- | --------------- | ----------------------- | | **Proof Availability** | Immediate | After message delivery | | **Additional Fees** | None | \$5-50 per message | | **Message Delays** | None | Seconds to minutes | | **Infrastructure** | None required | Bridge dependencies | | **Complexity** | Minimal | Domain IDs, hooks, etc. | | **Use Case** | Same chain only | Cross-chain operations | ## Best Practices ### Intent Creation **Set destination correctly:** ```solidity theme={null} destination: uint64(block.chainid) // Must match current chain ``` **Use Local Prover:** ```solidity theme={null} prover: address(localProver) // Not a cross-chain prover ``` ### Fulfillment **Either pattern works:** ```solidity theme={null} // Simple: fulfill only portal.fulfill(intentHash, route, rewardHash, claimant); // With prove (no-op): works with fulfillAndProve portal.fulfillAndProve( intentHash, route, rewardHash, claimant, address(localProver), uint64(block.chainid), "" ); ``` ### Validation **Check destination matches:** ```solidity theme={null} require( intent.destination == block.chainid, "Not same-chain intent" ); ``` **Verify prover is LocalProver:** ```solidity theme={null} require( intent.reward.prover == address(localProver), "Wrong prover" ); ``` ## Error Handling * `ChainIdTooLarge(uint256)`: Chain ID exceeds uint64.max during construction **Note:** LocalProver functions (`prove`, `challengeIntentProof`) never revert. They are intentionally no-ops. ## Technical Notes ### Why prove() is Empty The `prove()` function is empty because: 1. Portal stores claimant in `claimants` mapping during fulfillment 2. `provenIntents()` queries this mapping directly 3. No cross-chain message needed 4. Proof is instantly available ### Why No Fees No fees because: * No cross-chain message to send * No bridge infrastructure to pay * No external services required * Only standard EVM gas costs ### Claimant Storage ```solidity theme={null} // Portal stores during fulfill() claimants[intentHash] = claimant; // LocalProver reads it bytes32 claimant = _PORTAL.claimants(intentHash); ``` Direct storage access eliminates need for proving. ## Constant ```solidity theme={null} function getProofType() external pure returns (string memory) { return "Same chain"; } ``` Identifies this prover handles same-chain intents. # Provers Source: https://docs.eco.com/routes/architecture/provers/overview Provers are the modular settlement layer. They verify on the source chain that an intent was fulfilled on the destination. Six provers ship today; users pick per intent. A **prover** is a contract on the source chain that verifies fulfillment proofs from the destination chain. The Portal accepts any contract that implements the `IProver` interface. The user picks a prover per intent, meaning the user picks the security model. ## Available provers | Prover | Settlement method | Security model | Best for | | --------------------------------------------------- | ---------------------------------------- | ----------------------------------------- | ---------------------------------- | | [CCTP](/routes/architecture/provers/cctp) | Issuer attestations via `receiveMessage` | Circle's attestation service | USDC routes, regulated flows | | [Hyperlane](/routes/architecture/provers/hyperlane) | Hyperlane ISM validators | Configurable security module | General cross-chain, fast | | [LayerZero](/routes/architecture/provers/layerzero) | DVN consensus | Decentralized verifier network | Wide chain coverage | | Chainlink CCIP | CCIP messaging | Chainlink DON and Risk Management Network | General cross-chain, wide coverage | | [Polymer](/routes/architecture/provers/polymer) | IBC light clients | Light-client proofs | Trust-minimized | | [Local](/routes/architecture/provers/local) | Same-chain atomicity | Transaction atomicity | Same-chain, orchestration mode | ## How proving works Sequence showing proof of fulfillment travelling from the destination chain back to the source chain: fulfill, emit fulfillment, dispatch proof, withdraw, verify proof, release reward. The dispatch step (proof crossing chains) is what differentiates each prover. The Portal interface is identical regardless of which prover the user chose. ## Picking a prover A few rules of thumb: * **USDC routes.** Use CCTP, same security as a native CCTP transfer, no AMB. * **Same-chain intents.** Use Local, instant, atomic, no cross-chain message. * **Wide coverage.** Use Hyperlane or LayerZero, many chains, many integrations. * **Trust-minimized.** Use Polymer, IBC light clients, no validator set to trust. * **Chainlink-secured.** Use Chainlink CCIP, settlement carried by Chainlink's decentralized oracle and Risk Management networks. Provers can also be added by anyone implementing `IProver`, no protocol-level approval required. # How the Polymer Prover Works Source: https://docs.eco.com/routes/architecture/provers/polymer Cross-chain verification using Polymer's IBC-based messaging The Polymer Prover enables cross-chain intent verification using Polymer's IBC-based messaging infrastructure. It uses light client proofs for settlement verification, extending `MessageBridgeProver` for common proving functionality. ## How It Works 1. **Fulfillment**: Solver fulfills the intent on the destination chain Portal 2. **Proof dispatch**: The Polymer Prover on the destination chain sends a message via Polymer's IBC transport 3. **Light client verification**: Polymer uses IBC light client proofs to verify the message on the source chain, no external validator set required 4. **Settlement**: The source chain Polymer Prover receives the verified message and marks the intent as proven, allowing the solver to withdraw rewards ## Security Model Polymer's security derives from IBC light client proofs, the same cryptographic verification mechanism used across the Cosmos ecosystem. Rather than relying on a set of external validators or an attestation service, light clients verify state transitions directly, providing trust-minimized cross-chain communication. ## Message Format Polymer Prover uses the same standardized message format as other Eco bridge provers: * **8 bytes**: Destination chain ID * **64 bytes per intent**: `[intentHash (32 bytes)][claimant (32 bytes)]` Multiple intents can be batched into a single IBC message. ## When to Use Polymer Prover **Ideal for:** * Cross-chain intents where IBC-based verification is preferred * Chains with Polymer IBC support * Use cases that benefit from light-client-based trust minimization **For other cross-chain options, see:** * [Hyperlane Prover](/routes/architecture/provers/hyperlane) * [LayerZero Prover](/routes/architecture/provers/layerzero) * [Local Prover](/routes/architecture/provers/local) for same-chain intents # Settlement vs Orchestration Source: https://docs.eco.com/routes/architecture/settlement-vs-orchestration Routes fulfills intents in two modes, solver settlement (capital fronted) or orchestration (user funds routed through underlying rails at gas cost). Same Portal, same vaults, same API. Routes can fulfill an intent in two modes using the same contracts and the same API. Most intent systems only do settlement; Routes does both. | Mode | What happens | Capital required | Speed | | ----------------- | ------------------------------------------------------------------------------ | ---------------- | ---------------------------------------- | | **Settlement** | A solver fronts inventory and delivers the requested outcome | Solver capital | Fastest, depends on solver | | **Orchestration** | The user's vaulted funds route through underlying rails inside one transaction | Gas only | Bounded by underlying messaging finality | For the conceptual frame, see [Settlement vs Orchestration in Concepts](/concepts/settlement-vs-orchestration). ## What this means for your integration You don't choose the mode. The system picks automatically: ``` Tier 1: Solver available, better pricing → Settlement Tier 2: Solver available, no pricing edge → Orchestration Tier 3: No solver available → Self-solve via Local prover ``` Your code is identical in all three cases. Same intent struct, same publishing call, same status polling. The user sees one outcome. ## Why this matters for SLAs If you're building on Routes for a regulated counterparty, the dual-mode design gives you a **bounded worst-case fulfillment time** that doesn't depend on solver liveness. The fallback is the chain itself. This is the design choice that makes "best execution guarantee" a runtime property rather than a marketing claim. # How the vault contract works Source: https://docs.eco.com/routes/architecture/vault Escrow contract that holds intent rewards until claimed or refunded The Vault is an escrow contract that holds intent rewards until they can be claimed by solvers or refunded to creators. Each intent has its own dedicated vault, deployed deterministically using CREATE2 for predictable addressing. ## Architecture Vaults use a minimal proxy pattern (ERC-1167) for gas-efficient deployment: ``` Vault Implementation (deployed once) ↓ Proxy (deployed per intent via CREATE2) ↓ delegates to Vault Implementation ``` **Key Properties:** * One vault per intent, deployed on-demand * Deterministic addresses computed from intent hash * Only the Portal can manage vault operations * No persistent state between intents (stateless implementation) ## Deployment ### CREATE2 Determinism Vault addresses are computed using CREATE2 with the intent hash as salt: ```solidity theme={null} address vaultAddress = keccak256( abi.encodePacked( CREATE2_PREFIX, // 0xff (standard) or 0x41 (TRON) portalAddress, // Deployer intentHash, // Salt keccak256( // Init code hash abi.encodePacked( type(Proxy).creationCode, abi.encode(vaultImplementation) ) ) ) ) ``` ### On-Demand Deployment Vaults are deployed lazily when first needed: * **Intent Publishing**: Address is computed but not deployed * **First Funding**: Vault deployed if not already exists * **Withdrawal/Refund**: Vault deployed if not already exists This saves gas when intents are published but never funded. ### Chain Compatibility The CREATE2 prefix adapts to different chains: * **Standard EVM Chains**: `0xff` prefix * **TRON Mainnet** (728126428): `0x41` prefix * **TRON Testnet** (2494104990): `0x41` prefix ## Access Control ```solidity theme={null} modifier onlyPortal() ``` All vault operations are restricted to the Portal contract that deployed the implementation. This ensures: * Only validated intents can withdraw funds * Refunds require proper deadline checks * Token recovery follows protocol rules ## Funding ### Standard Funding Called during `Portal.publishAndFund()` or `Portal.fund()`: ```solidity theme={null} function fundFor( Reward calldata reward, address funder, IPermit permit ) external payable onlyPortal returns (bool fullyFunded) ``` **Funding Process:** 1. Check native token balance against `reward.nativeAmount` 2. For each ERC20 token: * Attempt permit-based transfer first (if permit contract provided) * Fall back to standard `transferFrom` with allowance 3. Return true only if all tokens fully funded **Partial Funding:** The function transfers as much as available based on: * Funder's token balance * Funder's allowance/permit to vault * Existing vault balance ### Permit-Based Funding Vaults support gasless approvals via permit contracts (e.g., Permit2): **Permit Transfer:** ```solidity theme={null} function _fundFromPermit( address funder, IERC20 token, uint256 rewardAmount, IPermit permit ) internal returns (uint256 remaining) ``` Queries permit contract for allowance and transfers tokens directly without requiring prior `approve()` call. **Standard Transfer (Fallback):** ```solidity theme={null} function _fundFrom( address funder, IERC20 token, uint256 remainingAmount ) internal returns (uint256 remaining) ``` Uses standard ERC20 `transferFrom` based on existing allowance. **Funding Strategy:** 1. Check current vault balance 2. Try permit transfer if permit contract provided 3. Fall back to standard transfer for any remaining amount 4. Return unfunded amount ## Withdrawal Solvers claim rewards after intent fulfillment and proof verification: ```solidity theme={null} function withdraw( Reward calldata reward, address claimant ) external onlyPortal ``` **Withdrawal Process:** 1. Transfer all reward ERC20 tokens to claimant (up to reward amounts) 2. Transfer native tokens to claimant (up to reward amount) 3. Use actual balance (may be less than reward if partially funded) **Safety Features:** * Uses `SafeERC20.safeTransfer` for token transfers * Minimum of reward amount and actual balance prevents over-withdrawal * Native transfers use low-level call with success check * Reverts on failed native transfer with `NativeTransferFailed` error ## Refund Creators reclaim rewards after intent expiry: ```solidity theme={null} function refund(Reward calldata reward) external onlyPortal ``` **Refund Process:** 1. Transfer all ERC20 token balances to `reward.creator` 2. Transfer all native token balance to `reward.creator` 3. No amount restrictions (returns full vault contents) **Key Differences from Withdrawal:** * Returns actual balance, not limited to reward amounts * Always transfers to `reward.creator` * Portal validates deadline before calling ## Token Recovery Recover tokens mistakenly sent to vault: ```solidity theme={null} function recover(address refundee, address token) external onlyPortal ``` **Use Cases:** * User accidentally sent tokens directly to vault address * Tokens sent to wrong vault * Extra tokens beyond reward amounts **Restrictions (enforced by Portal):** * Cannot recover zero address (native tokens) * Cannot recover any token in `reward.tokens` array * Intent must have zero native rewards OR already be claimed/refunded ## Fund Flow ### Publishing Flow ``` User → Portal.publishAndFund() ↓ Portal → Vault.fundFor() ↓ Vault ← User (tokens via transferFrom/permit) ``` ### Withdrawal Flow ``` Solver → Portal.withdraw() ↓ Portal → Prover (verify proof) ↓ Portal → Vault.withdraw() ↓ Vault → Solver (claimant address) ``` ### Refund Flow ``` Creator → Portal.refund() ↓ Portal (validates deadline/proof) ↓ Portal → Vault.refund() ↓ Vault → Creator ``` ## Security Model ### Immutable Portal The portal address is set at construction and cannot be changed: ```solidity theme={null} address private immutable portal; ``` This creates a strong binding between vault and portal, preventing: * Unauthorized withdrawals * Fake refunds before expiry * Bypassing protocol validation ### Balance-Based Logic All transfers use actual balance rather than storing state: * No accounting variables to manipulate * Always transfers real tokens held by vault * Cannot withdraw more than vault contains ### SafeERC20 Integration Uses OpenZeppelin's SafeERC20 for all token transfers: * Handles non-standard ERC20 returns * Prevents silent transfer failures * Reverts on insufficient balance ### Minimal Proxy Pattern Using ERC-1167 minimal proxies provides: * **Gas Efficiency**: \~2,500 gas to deploy vs \~200,000 for full contract * **Security**: Implementation cannot be changed after proxy deployment * **Predictability**: Deterministic addresses enable pre-funding ## Integration Examples ### Computing Vault Address ```solidity theme={null} // From Portal Intent memory intent = /* ... */; address vault = portal.intentVaultAddress(intent); // Or with components bytes32 intentHash = portal.getIntentHash( destination, route, reward ); address vault = portal.intentVaultAddress( destination, abi.encode(route), reward ); ``` ### Pre-Funding Vault Users can send tokens directly to the computed vault address before publishing: ```solidity theme={null} // Compute vault address address vault = portal.intentVaultAddress(intent); // Send tokens to vault IERC20(tokenAddress).transfer(vault, amount); // Publish without funding (vault already has tokens) portal.publish(intent); ``` **Note:** This skips the `Funded` status check, so verify funding manually with `portal.isIntentFunded()`. ### Partial Funding ```solidity theme={null} // User has limited balance uint256 userBalance = token.balanceOf(user); uint256 rewardAmount = 1000e18; // Approve partial amount token.approve(address(portal), userBalance); // Publish with partial funding allowed portal.publishAndFund{value: nativeAmount}(intent, true); // allowPartial = true // Check funding status bool funded = portal.isIntentFunded(intent); // Returns false if partially funded ``` ### Using Permit for Gasless Funding ```solidity theme={null} // User signs permit offchain IPermit permit = IPermit(permitContract); // Relayer calls publishAndFundFor portal.publishAndFundFor( intent, true, // allowPartial user, // funder address(permit) // permit contract ); // Vault uses permit.transferFrom instead of token.transferFrom ``` ## Error Handling * `NotPortalCaller(address)`: Caller is not the authorized Portal contract * `NativeTransferFailed(address, uint256)`: Failed to send native tokens to recipient * `ZeroRecoverTokenBalance(address)`: Attempted to recover token with zero balance ## Gas Considerations ### Deployment Costs * **Full Vault Contract**: \~200,000 gas * **Minimal Proxy**: \~2,500 gas (98.75% savings) * **Implementation**: One-time cost, shared across all vaults ### Operation Costs **Funding:** * Native transfer: \~21,000 gas * ERC20 transfer: \~50,000 gas per token * Permit transfer: \~70,000 gas per token (includes permit verification) **Withdrawal:** * Per token: \~50,000 gas * Native transfer: \~21,000 gas **Refund:** * Similar to withdrawal costs * No amount validation overhead ### Optimization Strategies 1. **Batch Operations**: Use `Portal.batchWithdraw()` to amortize vault deployment costs 2. **Pre-Funding**: Send tokens to vault address before publishing to skip funding transaction 3. **Single Token Rewards**: Minimize token array length in rewards 4. **Standard Transfers**: Use regular approvals instead of permits when possible (lower gas) ## Cross-Chain Considerations ### Address Consistency Vault addresses are deterministic across chains with same: * Portal deployment address * Intent parameters (destination, route, reward) * CREATE2 prefix (chain-specific) **Example:** ``` Ethereum: 0x1234...5678 (0xff prefix) TRON: 0x4321...8765 (0x41 prefix, different address) ``` ### Native Token Handling Each chain's native token is handled uniformly: * Ethereum: ETH * Polygon: MATIC * Arbitrum: ETH * TRON: TRX Vaults treat all as "native amount" in `reward.nativeAmount`. ## Best Practices ### For Intent Creators 1. **Verify Funding**: Call `portal.isIntentFunded()` before expecting fulfillment 2. **Adequate Approvals**: Ensure sufficient allowance for all reward tokens 3. **Gas Budgeting**: Include native amount for destination chain execution 4. **Deadline Buffer**: Set `reward.deadline` with buffer for proof verification time ### For Solvers 1. **Check Vault Balance**: Verify vault is fully funded before fulfilling 2. **Gas Estimation**: Account for withdrawal transaction costs in profitability calculations 3. **Claimant Address**: Ensure claimant address is correct and can receive tokens 4. **Token Compatibility**: Verify all reward tokens are standard ERC20 ### For Integrators 1. **Address Prediction**: Compute vault addresses offchain to display to users 2. **Event Monitoring**: Watch `IntentFunded` events to track funding progress 3. **Error Handling**: Handle partial funding scenarios gracefully 4. **Permit Integration**: Support gasless approvals for better UX # Destination calls Source: https://docs.eco.com/routes/capabilities/destination-calls Embed arbitrary onchain actions on the destination chain into an intent, bridge AND deposit, bridge AND swap, bridge AND stake, all atomically. A **destination call** is an arbitrary contract interaction on the destination chain, included in the intent's route and executed atomically by the [Executor contract](/routes/architecture/executor) after the solver delivers the requested asset. This turns "bridge then do something" into a single signed action with all-or-nothing execution. ## When to use * One-click cross-chain protocol deposits ("deposit USDC into our Arbitrum vault from any chain") * Bridge-and-swap into a non-stable destination asset * Pay an invoice that requires a specific contract call beyond a transfer * Cross-chain auto-stake or auto-compound flows ## How it works 1. The intent's `route.calls` array contains one or more `Call` structs, `{ target, value, data }`, to execute on the destination chain. 2. The solver fronts the destination-chain tokens to the Executor. 3. The Executor runs each call sequentially. If any call reverts, the entire intent reverts. The solver pays only gas; the user gets refunded. ```solidity theme={null} struct Call { address target; uint256 value; bytes data; } ``` The Executor is a stateless contract that isolates the user's calls from the Portal's storage. See [Executor](/routes/architecture/executor) for the security model. ## Examples ### Bridge and deposit ```solidity theme={null} calls[0] = Call({ target: tokenAddress, value: 0, data: abi.encodeCall(IERC20.approve, (vaultAddress, amount)) }); calls[1] = Call({ target: vaultAddress, value: 0, data: abi.encodeCall(IVault.deposit, (amount, beneficiary)) }); ``` ### Bridge, DEX swap, transfer ```solidity theme={null} calls[0] = Call({ target: tokenIn, value: 0, data: approveCalldata }); calls[1] = Call({ target: dexRouter, value: 0, data: swapCalldata }); calls[2] = Call({ target: tokenOut, value: 0, data: transferCalldata }); ``` ## Constraints | Constraint | Reason | | ----------------------------------------- | ----------------------------- | | Calls execute sequentially in array order | Deterministic execution | | Any failure reverts the whole intent | Atomicity, no partial states | | Calls to EOAs with calldata are blocked | Phishing-pattern protection | | The Executor has no persistent state | Isolation from Portal storage | # Stable 1:1 transfers Source: https://docs.eco.com/routes/capabilities/stable-1-to-1 Move the same stablecoin between chains at predictable, near-1:1 pricing, the simplest Routes flow. A **stable 1:1 transfer** moves the same stablecoin (USDC, USDT, USDG, USDC.e) between chains. Pricing is predictable and near-1:1 because no swap occurs, the solver delivers the same asset on the destination as the user offered on the source. ## When to use * Bridging USDC between EVM chains * Funding a Solana wallet from Base USDC * Rebalancing protocol-owned liquidity across chains * Any flow where the user holds and wants the same stablecoin on the destination ## Pricing properties * **Predictable.** Solver fees are tight because there's no swap risk. * **Quote-stable.** Stablecoin-optimized quotes remain valid longer than general-purpose bridge quotes, reducing failed transactions. * **Atomic.** Either the full amount lands, or the user is refunded. No partial states. ## Example: Optimism USDC → Base USDC Via CLI: ```bash theme={null} eco-routes-cli publish --source optimism --destination base # wizard prompts for token (USDC) and amount ``` Via REST: ```json theme={null} { "intent": { "route": { "source": 10, "destination": 8453, "tokens": [{ "token": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", "amount": "1000000" }] }, "reward": { "creator": "0xYou", "deadline": 1717200000, "tokens": [] } }, "originChain": 10 } ``` Get the quote, fund the vault, publish. See the [REST API quickstart](/get-started/quickstart#rest-api). See [Supported chains & tokens](/resources/supported-chains-tokens) for the full matrix. USDC is available on every supported chain. USDT, USDG, oUSDT, USDT0, USDC.e are available on subsets. # Cross-stable RFQ Source: https://docs.eco.com/routes/capabilities/stable-rfq Swap one stablecoin for another (USDC ↔ USDT, USDC ↔ USDG) across chains, with competitive solver pricing and atomic settlement. A **cross-stable RFQ** is an intent where the input and output stablecoins differ, for example, paying USDC on Optimism and receiving USDT on Base. Solvers compete on pricing in real time; the user gets the best quote without manual route comparison. ## When to use * Accepting payment in any major stablecoin and settling to one preferred currency * Moving from a chain-native stable (e.g. USDbC) to the canonical version (e.g. USDC) on the destination * Cross-chain stable-to-stable swaps that would otherwise require routing through a DEX ## How quoting works 1. Your app submits a candidate intent (with `source`, `destination`, input token, and desired output token) to the [Quotes API](/api-reference/introduction#quotes). 2. Eco fans the request out to all registered solvers in parallel. 3. The aggregator returns ranked quotes, best price first. 4. Your app picks a quote, populates `intent.reward.tokens` from it, and publishes. Quotes specify the reward tokens the user must lock in the source vault. Settlement is conditional on solver-provided proof. ## Properties * **Best execution by competition.** Multiple solvers, ranked offers, no monopoly LP. * **Atomic.** The user signs once. Either the swap lands or the reward is refunded. * **No DEX exposure.** Solvers source the destination stable however they like; the user holds no DEX risk. ## Example pairs | Source (chain → token) | Destination (chain → token) | | ---------------------- | --------------------------- | | Optimism USDC | Base USDT | | Arbitrum USDT0 | Polygon USDC | | Base USDC | Solana USDG | | Ethereum USDC | Optimism oUSDT | See [Supported chains & tokens](/resources/supported-chains-tokens) for the full matrix. # Routes API Source: https://docs.eco.com/routes/integrate/api Integrate Routes via the V3 quote API plus a single Portal contract call. Production-ready, language-agnostic, server-to-server. The Routes V3 API is a unified interface to Eco's stablecoin settlement network, a production-grade, server-to-server integration path that gives you full control to program custom money-movement logic and configure the speed, cost, and security parameters that match your use case. Three steps: get a quote, publish and fund the intent onchain using the `encodedRoute` from the quote, poll for fulfillment. For the full OpenAPI surface, see the [API Reference](/api-reference/introduction). ## 0. Prerequisites * Source-chain RPC and a funded wallet * HTTP client (curl, fetch, axios) * Web3 library (viem, ethers) No authentication is required. Pass an identifier as `dAppID` in the request body for attribution. ## 1. Get a quote The V3 quote API returns the best quote ready for onchain execution. It returns the `encodedRoute` you'll pass straight to the Portal, you don't construct the intent struct yourself. ```bash theme={null} curl -X POST https://quotes.eco.com/api/v3/quotes/single \ -H "Content-Type: application/json" \ -d '{ "dAppID": "your-app", "quoteRequest": { "sourceChainID": 10, "destinationChainID": 8453, "sourceToken": "0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85", "destinationToken": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", "sourceAmount": "1000000", "funder": "0xYourWalletAddress", "recipient": "0xRecipientAddress" } }' ``` Available quote endpoints: | Endpoint | Use for | | ------------------------------ | ---------------------------------------------------- | | `POST /api/v3/quotes/single` | Best single quote, ready for execution | | `POST /api/v3/quotes/exactIn` | Specify input amount, get multiple competing quotes | | `POST /api/v3/quotes/exactOut` | Specify output amount, get quotes for required input | Response shape: ```typescript theme={null} { data: { quoteResponse: { intentExecutionType: "SELF_PUBLISH", sourceChainID: number, destinationChainID: number, sourceToken: string, destinationToken: string, sourceAmount: string, // bigint as string (reward you'll lock) destinationAmount: string, // bigint as string (recipient receives) funder: string, recipient: string, fees: [{ name, description, token, amount }], deadline: number, // unix seconds estimatedFulfillTimeSec: number, encodedRoute: string, // ABI-encoded, pass straight to Portal }, contracts: { sourcePortal: string, // Portal address on source chain destinationPortal: string, prover: string, } } } ``` ## 2. Approve the reward token Standard ERC-20 approve to `contracts.sourcePortal` for the reward amount (`quoteResponse.sourceAmount`). ```typescript theme={null} import { erc20Abi } from 'viem'; const portal = quote.data.contracts.sourcePortal; const rewardToken = quote.data.quoteResponse.sourceToken; const rewardAmount = BigInt(quote.data.quoteResponse.sourceAmount); const allowance = await publicClient.readContract({ address: rewardToken, abi: erc20Abi, functionName: 'allowance', args: [user, portal], }); if (allowance < rewardAmount) { await walletClient.writeContract({ address: rewardToken, abi: erc20Abi, functionName: 'approve', args: [portal, rewardAmount], }); } ``` ## 3. Publish and fund Build the reward struct from the quote and call `publishAndFund` on the source Portal. ```typescript theme={null} const reward = { deadline: BigInt(quote.data.quoteResponse.deadline), creator: user, prover: quote.data.contracts.prover, nativeAmount: 0n, tokens: [{ token: rewardToken, amount: rewardAmount }], }; const tx = await walletClient.writeContract({ address: portal, abi: portalAbi, functionName: 'publishAndFund', args: [ BigInt(quote.data.quoteResponse.destinationChainID), quote.data.quoteResponse.encodedRoute, reward, false, // allowPartial ], }); const receipt = await publicClient.waitForTransactionReceipt({ hash: tx }); // Read the IntentPublished event from receipt.logs to extract intentHash ``` ## 4. Track fulfillment ```bash theme={null} curl -X POST https://quotes.eco.com/api/v3/intents/intentStatus \ -H "Content-Type: application/json" \ -d '{ "intentHash": "0x..." }' ``` Status returns: ```typescript theme={null} { data: { intentHash: string, status: { status: "Pending" | "Completed", subStatus: "WaitingToFulfill" | "WaitingForRefund" | "Fulfilled" | "Refunded", subStatusMessage: string }, intentCreated: { chainId, transactionHash, blockExplorerUrl }, fulfillment?: { chainId, transactionHash, blockExplorerUrl }, refund?: { chainId, transactionHash, blockExplorerUrl } } } ``` Typical fulfillment: **20–40 seconds**, depending on source-chain finality. ## 5. Refunds (if expired) If the intent expires unfulfilled, call `refund(routeHash, reward)` on the source Portal to reclaim the reward tokens. Refunds are also driven by an independent permissionless service. ## Custom intents with destination calls If you need arbitrary contract calls on the destination chain (bridge AND deposit, for example), build the intent's `route.calls` array yourself and call `publishAndFund` with a hand-built route, see [Destination calls](/routes/capabilities/destination-calls). # Routes CLI Source: https://docs.eco.com/routes/integrate/cli Use the Eco Routes CLI to publish, fund, and track intents from the command line. Best for first-time users, manual operators, and scripting. The [Eco Routes CLI](https://www.npmjs.com/package/eco-routes-cli) is the fastest path to move money with Eco, direct access to the stablecoin settlement layer with no integration code to write. Query routes, simulate transfers, and execute cross-chain transactions from the command line. It launches an interactive wizard that handles quote fetching, vault derivation, funding, and publishing. Use the CLI for: * First-time exploration * Manual operations and scripting * Multi-VM source/destination pairs (EVM, SVM) For programmatic, server-side integration, use the [REST API](/routes/integrate/api). ## 0. Prerequisites * Node 18+ * Funded wallet on the source chain * Source-chain RPC endpoint (optional, sane defaults are used otherwise) ## 1. Install globally ```bash theme={null} npm i -g eco-routes-cli ``` ## 2. Publish your first EVM intent ```bash theme={null} eco-routes-cli publish --private-key 0xYOUR_PRIVATE_KEY ``` Follow the prompts to select source chain (e.g. Base), destination chain (e.g. Optimism), token, and amounts. Done. Pass `--source` and `--destination` to skip the chain selection prompts. Prefer not to pass keys inline? Copy `.env.example` to `.env` and set `EVM_PRIVATE_KEY` (or `SVM_PRIVATE_KEY` for Solana), the CLI will pick it up automatically. Never commit `.env`. Always add it to `.gitignore`. Never hardcode private keys in source. ## 3. Track fulfillment The intent hash returned in step 2 can be used to monitor the destination Portal's `IntentFulfilled` event, or polled via the [intent-status API endpoint](/api-reference/introduction#quotes). # Routes Source: https://docs.eco.com/routes/overview Routes is Eco's intent-based product for real-time stablecoin transfers and swaps across chains, with cryptographic execution guarantees and dual-mode fulfillment. Routes is Eco's intent-based product for **real-time stablecoin sends and swaps across chains**, built for institutional scale and reliability. Users sign a desired outcome; solvers compete to fulfill it; the source-chain vault releases the reward only on cryptographic proof. The intent-based model delivers lightning-fast execution with predictable pricing, and Routes is live across all major stablecoin assets and chains. ## What Routes does | Capability | What it does | | ----------------------------------------------------------- | -------------------------------------------------------------------------------------- | | [Stable 1:1 transfers](/routes/capabilities/stable-1-to-1) | Move the same stablecoin (e.g. USDC) between chains at predictable pricing | | [Cross-stable RFQ](/routes/capabilities/stable-rfq) | Swap one stablecoin for another (USDC↔USDT, USDC↔USDG) with competitive solver pricing | | [Destination calls](/routes/capabilities/destination-calls) | Bridge AND interact with a destination contract atomically, one signature, one outcome | Routes can also fulfill intents in two execution modes, solver settlement or orchestration, covered under [Settlement vs Orchestration](/routes/architecture/settlement-vs-orchestration) in the architecture section. ## How Routes works Eco Routes architecture: source-chain Portal, Vault, and Prover connected to destination-chain Portal, Executor, and Prover, with a Solver bridging the two via reward claim and proof message. 1. **Publish and fund.** The user creates an intent and funds the source-chain vault. 2. **Fulfill.** A solver delivers the requested outcome on the destination chain. 3. **Prove.** A user-selected prover carries proof of fulfillment back to the source. 4. **Settle.** The Portal verifies the proof and releases the reward. If no solver fulfills before the deadline, a permissionless refund service returns funds to the user. ## Integration paths | Path | Best for | Time to first transfer | | --------------------------------- | ---------------------------------------- | ---------------------- | | [CLI](/routes/integrate/cli) | Manual operators, scripting, exploration | 5 min | | [REST API](/routes/integrate/api) | Production server-to-server integration | 15 min | For the underlying smart-contract surface, see [Architecture](/routes/architecture/overview). For the REST endpoints, see the [API Reference](/api-reference/introduction). ## Architecture at a glance | Component | Purpose | | ------------------------------------------------ | -------------------------------------------------------------------------------------------------- | | [Portal](/routes/architecture/portal) | Non-upgradable factory contract, entry point for publish, fulfill, prove, withdraw | | [Vault](/routes/architecture/vault) | Per-intent CREATE2 escrow, non-custodial, deterministic, refund-on-expiry | | [Executor](/routes/architecture/executor) | Stateless call-execution contract, isolates user code from Portal storage | | [Provers](/routes/architecture/provers/overview) | Modular settlement layer, six options (Chainlink CCIP, CCTP, Hyperlane, LayerZero, Polymer, Local) | | [ERC-7683](/routes/architecture/erc-7683) | Standardized cross-chain order interface | ## Primitives Routes uses | Primitive | Where | | ----------------------------------------------------- | -------------------------------------------------------------------------- | | [Permit3](/routes/primitives/permit3/overview) | Multi-chain token approvals via single signature | | [Crowd Liquidity](/routes/primitives/crowd-liquidity) | Permissionless liquidity backstop, solvers flash-borrow for larger intents | Routes also has a regulated counterpart, [Verified Liquidity](/verified/overview) (closed access): a permissioned fulfillment lane with a KYB'd solver network, OFAC screening built in, and a per-transfer audit record. # What is Eco Crowd Liquidity? Source: https://docs.eco.com/routes/primitives/crowd-liquidity Permissionless liquidity aggregation for cross-chain transactions ## Introduction Crowd Liquidity is a protocol for aggregating stablecoin liquidity across bridges and DeFi products, making it available just-in-time to support Eco's solvers and vaults. Rather than relying solely on pre-positioned private capital, Crowd Liquidity gives Eco's liquidity orchestration layer a dynamic, on-demand backstop, one that scales with network demand. Crowd Liquidity is designed as a complement to solvers and market makers, not a replacement for them. It activates when network volume surges beyond what private liquidity can absorb, allowing Eco solvers and vaults to draw on aggregated stablecoin liquidity from across the ecosystem without requiring additional collateral of their own. ## How it works Crowd Liquidity functions as a **permissionless liquidity aggregation layer** that incentivizes market makers, vault curators, and stablecoin LPs to make their liquidity available to Eco's solvers and vaults on a flash-borrow basis. * **Liquidity is aggregated** from participating market makers, vault curators, and stablecoin LPs across multiple assets and chains. * **Solvers and vaults can flash-borrow** from this aggregated pool to fulfill transactions or rebalance the network, without posting collateral, by verifying proof of profitability and the necessary transaction execution guarantees. * **Crowd Liquidity activates just-in-time**, stepping in when private liquidity availability is exceeded during volume surges so the network maintains execution quality. * **Participants are incentivized** to permit access to their liquidity, earning fees in exchange for making their capital available to the protocol. By aggregating liquidity in a decentralized and permissionless manner, Crowd Liquidity improves execution speed, lowers costs, and enhances network resilience. ## Benefits of Crowd Liquidity ### For vault managers & liquidity providers * **Earn fees** by permitting Eco solvers and vaults to access liquidity on a flash-borrow basis. * **Maintain control**: participation is permissioned per provider, with execution guarantees enforced by the protocol. * **Participate across the stack**: open to market makers, vault curators, and stablecoin LPs operating across bridges and DeFi. ### For the Eco Network * **Ensures liquidity availability** during demand spikes, without requiring solvers to pre-position excess capital. * **Reduces single points of failure** by distributing liquidity provision across a broad set of participants. * **Optimizes capital efficiency** by sourcing liquidity just-in-time, where and when it is most needed. ## Use cases Crowd Liquidity strengthens Eco's liquidity orchestration across a range of scenarios, including: * **Volume surge absorption** – When transaction demand exceeds private liquidity, Crowd Liquidity provides an immediate, protocol-coordinated backstop. * **Cross-chain rebalancing** – Solvers and vaults can flash-borrow to rebalance liquidity positions across chains without tying up additional capital. * **Bridging and stablecoin swaps** – Aggregated liquidity deepens available capacity for large or time-sensitive cross-chain transactions. ## Getting started Crowd Liquidity is available to market makers, vault curators, and stablecoin LPs building on or integrating with the Eco Network. More details on participation structures, fee mechanics, and integration paths will be provided as access expands. For updates, follow Eco and check back here for further details on how to contribute liquidity to the network. # What is Permit3? Source: https://docs.eco.com/routes/primitives/permit3/overview Multi-chain token permissions protocol extending Permit2 [Permit3](https://github.com/eco/permit3) is an open-source token approval protocol that enables multichain token permission with a single signature and full gas abstraction. It eliminates the need for redundant approvals and native gas tokens, letting developers simplify cross-chain token operations with a single user signature. Permit3 extends the concepts introduced by Permit2 to support multi-chain operations through Unbalanced Merkle Trees, while maintaining full backward compatibility with it. ## The Problem When protocols need to coordinate token operations across multiple chains, users face compounding issues: * Multiple approval transactions per chain * Separate signatures for each blockchain * Complex coordination between different chain states * Race conditions when executing cross-chain operations ## Standard Cross-Chain Flow The traditional multi-chain approach requires: * User approves Protocol A on Chain 1 * User approves Protocol A on Chain 2 * User signs/submits transaction on Chain 1 * User signs/submits transaction on Chain 2 * Protocol coordinates between chains (often through bridges or relayers) This results in 4+ transactions and multiple signatures for a single logical operation. ## Permit3 Model Permi3 Architecture Permit3 introduces a unified approach: * User calls approve() on each chain's ERC20 tokens to grant infinite allowance to the canonical Permit3 contract (one-time setup per token per chain) * User signs a single offchain permit message containing a merkle root of all intended operations across chains * The signed message and merkle proofs are distributed to relevant chains by app * Each chain's protocol contract calls Permit3 contract with the appropriate merkle proof * Permit3 verifies the proof against the signed root and executes the permitted operations ## Core Components The Permit3 system consists of three main components: ### Permit3 Contract The main contract that inherits from PermitBase and NonceManager, implementing the core functionality: * Processes permit signatures for approvals and transfers * Verifies EIP-712 signatures with standard and witness data * Handles cross-chain operations through hash chaining * Implements witness functionality for custom data verification ### PermitBase Contract Manages token approvals and transfers: * Tracks allowances with amounts and expiration times * Handles token transfers through approvals * Implements allowance modes (increase, decrease, lock, unlock) * Provides emergency account locking functionality ### NonceManager Contract Handles nonce management for replay protection: * Uses non-sequential nonces for gas efficiency * Supports cross-chain nonce invalidation * Implements salt-based signature replay protection * Provides domain separation for EIP-712 signatures ## Contract Inheritance Structure ``` ┌───────────────┐ │ EIP-712 │ └───────┬───────┘ │ ┌───────┴───────┐ │ NonceManager │ └───────┬───────┘ │ ┌───────┴───────┐ │ PermitBase │ └───────┬───────┘ │ ┌───────┴───────┐ │ Permit3 │ └───────────────┘ ``` ## Key Data Structures ### Allowance Structure ```solidity theme={null} struct Allowance { uint160 amount; // Approved amount uint48 expiration; // Timestamp when approval expires uint48 timestamp; // Timestamp when approval was set } ``` ### AllowanceOrTransfer Structure ```solidity theme={null} struct AllowanceOrTransfer { uint48 modeOrExpiration; // Operation mode or expiration time address token; // Token address address account; // Spender or recipient uint160 amountDelta; // Amount change } ``` ### ChainPermits Structure ```solidity theme={null} struct ChainPermits { uint64 chainId; // Target chain ID (uint64 for gas optimization) AllowanceOrTransfer[] permits; // Operations for this chain } ``` ### Cross-Chain Permit Parameters In the implementation, cross-chain operations use separate parameters: ```solidity theme={null} function permit( address owner, bytes32 salt, uint48 deadline, uint48 timestamp, ChainPermits calldata permits, // Permit operations for the current chain bytes32[] calldata proof, // Standard merkle proof using OpenZeppelin's MerkleProof bytes calldata signature ) external; // Uses OpenZeppelin's MerkleProof.processProof() with bytes32[] arrays // Each element represents a sibling hash needed for proof verification ``` ## Core Operations ### 1. Permit Processing Permit3 supports two main permit functions: 1. **Single Chain Permits**: * Process token approvals for a specific chain * Verify signature against chain permits * Update allowances or execute transfers 2. **Cross-Chain Permits**: * Process token approvals across multiple chains * Chain permit hashes together for verification * Execute operations on current chain only Both functions can be extended with witness functionality to include custom data in signature verification. ### 2. Allowance Management Permit3 supports five operation modes controlled by the `modeOrExpiration` parameter: 1. **Transfer Mode** (`modeOrExpiration = 0`): * Executes immediate token transfer * `account` field represents recipient * `amountDelta` represents transfer amount 2. **Decrease Mode** (`modeOrExpiration = 1`): * Reduces existing allowance * `amountDelta` represents decrease amount * Special case: `type(uint160).max` resets to 0 3. **Lock Mode** (`modeOrExpiration = 2`): * Locks allowance for security * Blocks any increases or transfers * Requires newer timestamp to unlock 4. **Unlock Mode** (`modeOrExpiration = 3`): * Cancels locked state * Sets new allowance amount * Requires newer timestamp than lock 5. **Increase Mode** (`modeOrExpiration > 3`): * Value represents expiration timestamp * Increases allowance by `amountDelta` * Updates expiration if timestamp is newer ### 3. Nonce Management Permit3 uses non-sequential nonces with salt parameters: ```solidity theme={null} function _useNonce(address owner, bytes32 salt) internal { if (usedNonces[owner][salt] != NONCE_NOT_USED) { revert NonceAlreadyUsed(owner, salt); } usedNonces[owner][salt] = NONCE_USED; } ``` This approach: * Enables concurrent operations with different salts * Optimizes gas usage compared to sequential nonces * Supports cross-chain nonce management * Prevents signature replay attacks ### 4. Witness Functionality Witness functionality extends the standard permit flow: 1. **Type String Construction**: ```solidity theme={null} bytes32 typeHash = keccak256( abi.encodePacked(PERMIT_WITNESS_TYPEHASH_STUB, witnessTypeString) ); ``` 2. **Signature Verification**: ```solidity theme={null} bytes32 signedHash = keccak256( abi.encode( typeHash, permitDataHash, owner, salt, deadline, timestamp, witness ) ); ``` 3. **Processing**: After verification, the permit is processed using the standard flow while the application can verify the witness data. ## Cross-Chain Mechanism Permit3 enables cross-chain operations through Unbalanced Merkle Trees: 1. Hash permits for each chain individually 2. Build an Unbalanced Merkle Tree from all chain hashes (two-part structure) 3. Sign the Unbalanced root 4. Generate Unbalanced proofs for each chain 5. Process the portion relevant to the current chain This approach leverages the two-part design: * **Bottom Part**: Efficient membership proofs within individual chains * **Top Part**: Unbalanced upper structure that minimizes calldata for chains with high gas costs * **Verification**: Merkle tree verification for security and compatibility * **Benefits**: One signature for multiple chains with gas optimization potential * **Security**: Chain ID validation prevents cross-chain replay attacks * **Extensibility**: Supports witness data across chains with future optimization opportunities ### Unbalanced Merkle tree Example ```solidity theme={null} // Calculate the leaf hash for this chain's permits bytes32 leaf = permit3.hashChainPermits(proof.permits); // Verify the Unbalanced Merkle Tree proof using OpenZeppelin's MerkleProof // (traverses both balanced subtrees and unbalanced upper structure) bool valid = MerkleProof.processProof( proof.proof, leaf ) == merkleRoot; // Verify signature against Unbalanced root bytes32 signedHash = keccak256(abi.encode( SIGNED_PERMIT3_TYPEHASH, owner, salt, deadline, timestamp, merkleRoot // The hybrid structure root )); ``` ## Security Features Permit3 implements several security features: 1. **Signature Validation**: * EIP-712 typed signatures for structured data * Signature replay protection through nonces * Deadline validation to prevent expired signatures 2. **Access Control**: * Allowance-based access for token operations * Time-bound permissions with expiration timestamps * Owner validation for all operations 3. **Account Locking**: * Special locked state for emergency security * Operations blocked until explicit unlock * Timestamp validation for unlock operations 4. **Chain ID Validation**: * Explicit chain ID verification * Prevents cross-chain replay attacks * Works with witness functionality 5. **Timestamp Ordering**: * Operations ordered by timestamp across chains * Prevents race conditions in cross-chain operations * Critical for asynchronous allowance updates ## Gas Optimization Permit3 implements several gas optimization strategies: 1. **Non-Sequential Nonces**: * Bitmap-based nonce tracking * Constant gas cost regardless of nonce value * Efficient for concurrent operations 2. **Batched Operations**: * Process multiple permits in one transaction * Amortize fixed costs across operations * Reduce total transaction count 3. **Efficient Storage**: * Packed storage slots for allowances * Minimal state updates * Optimized data types 4. **Cross-Chain Efficiency**: * Execute only relevant operations on each chain * Single signature for multiple chains leveraging two-part structure * Optimized Unbalanced tree structure with gas optimization potential ## Integration with External Systems Permit3 is designed for seamless integration: 1. **Permit2 Compatibility**: * Implements IPermit interface for compatibility with contracts that are already using Permit2 for transfers * Existing contracts integrated with Permit2 can work with Permit3 without any changes * Maintains backward compatibility while offering enhanced functionality 2. **ERC20 Token Interaction**: * Works with any ERC20 token * No special token requirements * Compatible with standard token interfaces 3. **Cross-Contract Integration**: * Clear interfaces for external contracts * Witness functionality for custom verification * Support for complex integration patterns ## Deployments Permit3 contracts are deployed at `0xEc00030C0000245E27d1521Cc2EE88F071c2Ae34` on all supported chains. # How to integrate Permit3 Source: https://docs.eco.com/routes/primitives/permit3/quickstart Quick integration guide for Permit3 cross-chain token permissions This guide will help you quickly integrate Permit3 into your application, enabling cross-chain token approvals and transfers with witness functionality. ## Prerequisites * An Ethereum development environment (Hardhat, Foundry, etc.) * Basic understanding of EIP-712 signatures * Familiarity with ERC20 tokens * Access to the Permit3 contract address on your target chain(s) ## Installation ### Using npm ```bash theme={null} npm install @permit3/contracts ``` ### Using Foundry ```bash theme={null} forge install username/permit3 ``` ## Basic Integration ### Initialize Permit3 Interface ```solidity theme={null} // Existing contracts integrated with Permit2 can work with Permit3 without any changes IPermit permit = IPermit(PERMIT3_ADDRESS); permit.transferFrom(from, to, amount, token); // For Permit3 features IPermit3 permit3 = IPermit3(PERMIT3_ADDRESS); ``` ### User Setup Users need to approve Permit3 to spend their tokens (once per token): ```solidity theme={null} // User approves Permit3 contract ERC20(token).approve(PERMIT3_ADDRESS, type(uint256).max); ``` ### Creating and Signing Permits #### Simple Token Transfer The chainId in the domain object must ALWAYS be set to 1, regardless of which chain the permit is for. This is because Permit3 uses a universal signature scheme where permits are signed once with chainId: 1 and can then be used across multiple chains. The actual chain where the permit will be executed is specified separately in the permit data itself (e.g., in chainPermits.chainId), NOT in the EIP-712 domain. Changing the domain chainId from 1 will cause signature verification to fail. ```javascript theme={null} // JavaScript (ethers.js) const domain = { name: 'Permit3', version: '1', chainId: 1, // ALWAYS 1 (CROSS_CHAIN_ID) for cross-chain compatibility verifyingContract: permit3Address }; const permit = { modeOrExpiration: 0, // Transfer mode token: tokenAddress, account: recipientAddress, amountDelta: ethers.utils.parseUnits('10', 18) // 10 tokens }; const chainPermits = { chainId: 1, // chainId for network where this permit will be executed (e.g., 1 for Ethereum, 137 for Polygon) permits: [permit] }; const permitData = { owner: userAddress, salt: ethers.utils.randomBytes(32), deadline: Math.floor(Date.now() / 1000) + 3600, // 1 hour timestamp: Math.floor(Date.now() / 1000), chain: chainPermits }; const types = { Permit3: [ { name: 'owner', type: 'address' }, { name: 'salt', type: 'bytes32' }, { name: 'deadline', type: 'uint48' }, { name: 'timestamp', type: 'uint48' }, { name: 'merkleRoot', type: 'bytes32' } ], ChainPermits: [ { name: 'chainId', type: 'uint64' }, { name: 'permits', type: 'AllowanceOrTransfer[]' } ], AllowanceOrTransfer: [ { name: 'modeOrExpiration', type: 'uint48' }, { name: 'token', type: 'address' }, { name: 'account', type: 'address' }, { name: 'amountDelta', type: 'uint160' } ] }; // Calculate the permits hash const permitsHash = ethers.utils.keccak256(/* hash calculation logic */); const value = { owner: permitData.owner, salt: permitData.salt, deadline: permitData.deadline, timestamp: permitData.timestamp, merkleRoot: permitsHash }; const signature = await signer._signTypedData(domain, types, value); ``` ### 4. Executing Permits ```solidity theme={null} // In your contract function executePermit( address owner, bytes32 salt, uint48 deadline, uint48 timestamp, IPermit3.ChainPermits calldata chainPermits, bytes calldata signature ) external { permit3.permit( owner, salt, deadline, timestamp, chainPermits, signature ); // Now you can use the transferred tokens or the allowance } ``` ## Using Witness Functionality Witness functionality allows you to include arbitrary data in your permits for enhanced verification. ### 1. Define Your Witness Data ```javascript theme={null} // Example: Order data as witness const orderData = { orderId: 12345, price: ethers.utils.parseUnits('2000', 18), expiration: Math.floor(Date.now() / 1000) + 3600 }; // Hash the order data to create witness const witness = ethers.utils.keccak256( ethers.utils.defaultAbiCoder.encode( ['uint256', 'uint256', 'uint256'], [orderData.orderId, orderData.price, orderData.expiration] ) ); // Define witness type string const witnessTypeString = "OrderData data)OrderData(uint256 orderId,uint256 price,uint256 expiration)"; ``` ### 2. Sign Witness Permit ```javascript theme={null} // Add witness types const types = { // ... previous types PermitWitness: [ { name: 'permitted', type: 'ChainPermits' }, { name: 'spender', type: 'address' }, { name: 'salt', type: 'bytes32' }, { name: 'deadline', type: 'uint48' }, { name: 'timestamp', type: 'uint48' }, { name: 'data', type: 'OrderData' } ], OrderData: [ { name: 'orderId', type: 'uint256' }, { name: 'price', type: 'uint256' }, { name: 'expiration', type: 'uint256' } ] }; // Create and sign witness permit const witnessValue = { permitted: chainPermits, spender: userAddress, salt: salt, deadline: deadline, timestamp: timestamp, data: orderData }; const witnessSignature = await signer._signTypedData(domain, types, witnessValue); ``` ### 3. Execute Witness Permit ```solidity theme={null} function executeWitnessPermit( address owner, bytes32 salt, uint48 deadline, uint48 timestamp, IPermit3.ChainPermits calldata chainPermits, bytes32 witness, string calldata witnessTypeString, bytes calldata signature, OrderData calldata orderData // Your application's data structure ) external { // Verify witness matches the order data bytes32 expectedWitness = keccak256(abi.encode( orderData.orderId, orderData.price, orderData.expiration )); require(witness == expectedWitness, "Invalid witness data"); // Execute permit with witness permit3.permitWitness( owner, salt, deadline, timestamp, chainPermits, witness, witnessTypeString, signature ); // Continue with application logic, using the transferred tokens // and the validated order data } ``` ## Cross-Chain Operations Permit3 supports cross-chain operations with a single signature. ### 1. Create Permits for Multiple Chains ```javascript theme={null} // Ethereum permits const ethPermits = { chainId: 1, // Ethereum permits: [/* permits for Ethereum */] }; // Arbitrum permits const arbPermits = { chainId: 42161, // Arbitrum permits: [/* permits for Arbitrum */] }; // Optimism permits const optPermits = { chainId: 10, // Optimism permits: [/* permits for Optimism */] }; ``` ### 2. Generate Merkle Tree ```javascript theme={null} // Generate leaf hash for each chain's permits const ethLeaf = permit3.hashChainPermits(ethPermits); const arbLeaf = permit3.hashChainPermits(arbPermits); const optLeaf = permit3.hashChainPermits(optPermits); // Build merkle tree from all leaves const leaves = [ethLeaf, arbLeaf, optLeaf]; // Simple merkle root calculation (use a library in production) function buildMerkleRoot(leaves) { if (leaves.length === 1) return leaves[0]; const pairs = []; for (let i = 0; i < leaves.length; i += 2) { const left = leaves[i]; const right = leaves[i + 1] || leaves[i]; const [first, second] = left < right ? [left, right] : [right, left]; pairs.push(keccak256(encode(['bytes32', 'bytes32'], [first, second]))); } return buildMerkleRoot(pairs); } const merkleRoot = buildMerkleRoot(leaves); ``` ### 3. Sign and Execute on Each Chain ```javascript theme={null} // Sign the merkle root const signature = signPermit3(owner, salt, deadline, timestamp, merkleRoot); // Generate merkle proofs (use a library in production) function generateMerkleProof(leaves, targetIndex) { // Returns array of sibling hashes // In this example with 3 leaves, proofs would be: // ethProof: [arbLeaf, hash(optLeaf, optLeaf)] // arbProof: [ethLeaf, hash(optLeaf, optLeaf)] // optProof: [optLeaf, hash(ethLeaf, arbLeaf)] } // On Ethereum const ethProof = { permits: ethPermits, proof: generateMerkleProof(leaves, 0) // Direct array }; permit3.permit(owner, salt, deadline, timestamp, ethProof, signature); // On Arbitrum const arbProof = { permits: arbPermits, proof: generateMerkleProof(leaves, 1) // Direct array }; permit3.permit(owner, salt, deadline, timestamp, arbProof, signature); // On Optimism const optProof = { permits: optPermits, proof: generateMerkleProof(leaves, 2) // Direct array }; permit3.permit(owner, salt, deadline, timestamp, optProof, signature); ``` ## Common Operations ### Setting an Allowance ```javascript theme={null} const permitData = { modeOrExpiration: Math.floor(Date.now() / 1000) + 86400, // 24 hours expiration token: tokenAddress, account: spenderAddress, amountDelta: ethers.utils.parseUnits('100', 18) // 100 tokens }; ``` ### Decreasing an Allowance ```javascript theme={null} const permitData = { modeOrExpiration: 1, // Decrease mode token: tokenAddress, account: spenderAddress, amountDelta: ethers.utils.parseUnits('50', 18) // Decrease by 50 tokens }; ``` ### Locking an Account ```javascript theme={null} const permitData = { modeOrExpiration: 2, // Lock mode token: tokenAddress, account: address(0), // Not used for locking amountDelta: 0 // Not used for locking }; ``` ### Unlocking an Account ```javascript theme={null} const permitData = { modeOrExpiration: 3, // Unlock mode token: tokenAddress, account: address(0), // Not used for unlocking amountDelta: 0 // Not used for unlocking }; ``` ## Best Practices 1. **Use Unique Salts**: Generate cryptographically secure random values for salts 2. **Set Reasonable Deadlines**: Keep signature validity periods as short as practical 3. **Validate Chain IDs**: Always verify chain IDs match when processing cross-chain permits 4. **Handle Expiration**: Check for expired signatures before attempting to process them 5. **Validate Witness Data**: Verify witness data matches expected values before taking action 6. **Monitor Allowances**: Track allowance changes to prevent unexpected behavior 7. **Test Thoroughly**: Test all permit scenarios, including error cases 8. **Gas Optimization**: Batch related operations when possible ## Troubleshooting ### Signature Verification Fails * Ensure domain parameters (name, version, chainId, verifyingContract) are correct * Verify the signer is the token owner * Check salt hasn't been used before * Ensure deadline is in the future * Verify chainId matches the current chain ### Cross-Chain Issues * Ensure hash chaining is correct (order matters) * Verify each chain's proof contains the correct hashes * Check chainId matches for each chain * Ensure the same salt and deadline are used across chains ### Witness Verification Problems * Verify witness type string is properly formatted (must end with ')') * Ensure witness data matches expected values * Check EIP-712 type definitions are consistent across frontend and contracts # Solutions for AI agents & autonomous systems Source: https://docs.eco.com/solutions/agents Give an autonomous agent one API to move stablecoins, choose between paths, and execute multi-step strategies, without deploying contracts or managing per-chain wallets. Agents need three things humans can fake: predictable transactional outcomes, runtime decision-making, and a single API for cross-chain action. Eco's atomicity guarantees, Programmable Transactions, and intent abstraction were built for exactly this. ## The pain you're solving * Agents writing contracts is risky and slow * Multi-step cross-chain strategies break when chain state shifts mid-flow * Per-chain wallet management explodes operational complexity * "What just happened" is hard to reconstruct when an agent emits 12 transactions ## What Eco gives you | Capability | Product | | -------------------------------------------------- | --------------------------------------------------- | | Single-call multi-contract orchestration | [Programmable Transactions](/transactions/overview) | | Cross-chain execution with atomic guarantees | [Routes](/routes/overview) | | One signature for multi-chain authorization | [Permit3](/routes/primitives/permit3/overview) | | Deterministic deposit address per agent / per task | [Programmable Addresses](/addresses/overview) | | Conditional and best-execution logic in one tx | [Programmable Transactions](/transactions/overview) | ## Recommended product mix | Use case | Use | | -------------------------------------------- | --------------------------------------------------- | | Agent moves USDC across chains | [Routes API](/routes/integrate/api) | | Agent picks best DEX route at execution time | [Programmable Transactions](/transactions/overview) | | Agent has authority to spend across chains | [Permit3](/routes/primitives/permit3/overview) | | Agent receives payments from anywhere | [Programmable Addresses](/addresses/overview) | ## Patterns ### Single-tx best-execution swap Agent decides to swap. Instead of querying three DEXes and submitting four transactions, it emits one Programmable Transaction that quotes Uniswap, Aerodrome, and PancakeSwap atomically and executes on the winner. MEV-resistant, atomic, no contract deployment. → [Programmable Transactions overview](/transactions/overview) ### Cross-chain execution with bounded authority User signs one Permit3 authorization granting an agent spending power across chains, with amount limit, expiration, scope. Agent operates within those bounds; revocation is one tx. → [Permit3](/routes/primitives/permit3/overview) ### Auditable multi-step strategy Agent emits one composite intent, bridge, swap, deposit, that succeeds or refunds atomically. Postmortem is one transaction trace, not 12. ## Get started → [Programmable Transactions overview](/transactions/overview), the primary surface for agent-driven execution # Solutions for exchanges & onramps Source: https://docs.eco.com/solutions/exchanges-onramps Let users withdraw to any chain with one address. Route onramped fiat to any destination chain or wallet. Eliminate withdrawal-stuck support tickets. Exchanges and onramps face a structural problem: users want destinations on every chain, but you can't run withdrawal infrastructure for each one. Programmable Addresses turn that into one EVM withdrawal that auto-routes anywhere. ## The pain you're solving * Users want to withdraw to Solana, Polygon, Sonic, Ronin. You'd need a separate integration per chain * Withdrawal failures generate the highest-volume support tickets * Onramp users want their fiat to land on the chain where they actually use it * Your treasury sits on chains you don't actively operate on ## What Eco gives you | Capability | Product | | --------------------------------------------------------- | ----------------------------------------------------------- | | Deterministic EVM address that auto-bridges to Solana | [Solana Deposit Addresses](/addresses/solana) | | Fast gasless deposits into Circle Gateway | [Circle Gateway Deposits](/addresses/gateway-fast-deposits) | | Cross-chain withdrawal infra without per-chain deployment | [Routes API](/routes/integrate/api) | | Atomic refunds if a withdrawal fails | [Vault model](/concepts/vaults) | ## Recommended product mix | Use case | Use | | ------------------------------------- | ----------------------------------------------------------- | | Solana withdrawals | [Solana Deposit Addresses](/addresses/solana) | | Gateway-funded withdrawals | [Circle Gateway Deposits](/addresses/gateway-fast-deposits) | | Withdrawal to any supported EVM chain | [Routes API](/routes/integrate/api) | | Onramp → end chain in one step | [Programmable Addresses](/addresses/overview) | ## Patterns ### "Withdraw USDC to Solana" with one EVM transfer Per user, generate a Solana deposit address derived from their Solana wallet. Store the EVM address. When the user withdraws to Solana, your hot wallet sends a normal ERC-20 transfer to that EVM address. The address auto-bridges to the user's Solana wallet. → [Recipe: Auto-route deposits to any chain](/recipes/auto-route-deposits) ### Fast deposits into Circle Gateway Onramp partner sweeps customer USDC into Circle Gateway on Polygon for unified treasury management. Use Circle Gateway Deposits for 20–40 second settlement, gasless from the source chain. → [Recipe: Gasless USDC into Gateway](/recipes/gasless-gateway-deposit) ### Withdrawal infra without N integrations Instead of running per-chain withdrawal infrastructure, route every withdrawal through Routes. One integration, every supported chain. ## Get started → [Solana Deposit Addresses](/addresses/solana), the most common starting point # Solutions for stablecoin issuers Source: https://docs.eco.com/solutions/issuers Deepen liquidity for your stablecoin across every chain it's deployed on, capture transfer-fee revenue, and let solvers route directly to native mint/burn. A stablecoin's utility scales with its cross-chain liquidity. For issuers, deeper liquidity means more daily volume, more integrations, and a stronger network effect. Eco's solver market and Crowd Liquidity layer are tuned to maximize that. ## The pain you're solving * Your stablecoin is deployed on 10+ chains but liquidity is concentrated on 2 * Cross-chain transfers route through intermediaries (USDC) instead of staying native * You can't capture fee revenue from cross-chain transfer flow * New chain launches require bootstrapping liquidity manually ## What Eco gives you | Capability | Product | | ----------------------------------------------- | --------------------------------------------------------------------- | | Native cross-chain transfers of your stablecoin | [Routes](/routes/overview) (add your token) | | Provide liquidity, earn transfer fees | [Crowd Liquidity](/routes/primitives/crowd-liquidity) | | Issuer-direct mint/burn integration | Solver implementation pattern | | Verified routing path | [Verified Liquidity](/verified/overview) (closed access) | ## Recommended product mix | Use case | Use | | -------------------------------------- | --------------------------------------------------------------------------------- | | Get listed across Eco's solver network | Add your token to [Supported chains & tokens](/resources/supported-chains-tokens) | | Provide liquidity on your own token | [Crowd Liquidity](/routes/primitives/crowd-liquidity) | | Run an issuer-operated solver | [Recipe: Become a solver](/recipes/become-a-solver) | ## Patterns ### Issuer as preferred solver Operate your own solver that fronts native mint/burn for your stablecoin. You capture the routing fee, users get the cleanest possible cross-chain experience, and you set the spread. → [Recipe: Become a solver](/recipes/become-a-solver) ### LP your own treasury Deploy idle treasury into Crowd Liquidity. Earn fees on every flash-borrow that uses your tokens. Maintain control, flash-borrow is collateralized and instant; no lockup. → [Crowd Liquidity](/routes/primitives/crowd-liquidity) ### Bootstrap a new chain When you launch on a new chain, register the chain and token with Eco. Solvers begin quoting against it immediately, surfacing your stablecoin in every Routes integration. ## Get started → [Contact us](mailto:contact@eco.com), to add your token, become a solver, or LP into Crowd Liquidity # Solutions for payment platforms & PSPs Source: https://docs.eco.com/solutions/payments Accept any stablecoin from any chain, settle to one preferred currency. Deterministic deposit addresses with pre-programmed actions for invoice flows. Compliance enforced at the solver-selection layer. Payment platforms need three things crypto rails rarely deliver: predictable settlement, multi-asset acceptance, and a compliance story. Eco's vault model and dual-mode execution were designed around these constraints. ## The pain you're solving * Customers want to pay with the stablecoin they already hold; you only want to receive USDC on Polygon * Bridge failures in the middle of a payment flow create unrecoverable customer-service incidents * Invoice flows need a deterministic destination address with pre-programmed actions * Compliance team needs address screening and AML before funds settle ## What Eco gives you | Capability | Product | | ------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------- | | Accept any stablecoin, settle to one | [Routes RFQ](/routes/capabilities/stable-rfq) | | Deterministic address per invoice / per customer | [Programmable Addresses](/addresses/overview) | | Gasless funding (no gas-token UX) | [Funding methods](/addresses/funding-methods), ERC-3009, Permit | | Atomic refunds on failure | [Vault model](/concepts/vaults) | | End-to-end compliance across solvers and users | [Verified Liquidity](/verified/overview) (closed access): KYB'd solver network, KYC/AML/OFAC users, audit record | | Predictable settlement under solver outage | [Settlement vs Orchestration](/concepts/settlement-vs-orchestration) | ## Recommended product mix | Use case | Use | | --------------------------- | --------------------------------------------------------------------------------------- | | Per-invoice deposit address | [Programmable Addresses](/addresses/overview) | | Cross-chain settlement | [Routes API](/routes/integrate/api) | | Gasless customer payment | ERC-3009 → Routes via [Gateway Fast Deposits](/addresses/gateway-fast-deposits) pattern | | Multi-currency acceptance | [Stable RFQ](/routes/capabilities/stable-rfq) | ## Patterns ### Customer pays an invoice with any stable Generate a Programmable Address per invoice, derived from `(merchantAddress, invoiceId)`. Show the address to the customer. Customer transfers (or signs ERC-3009 for gasless). The address publishes an intent that delivers the merchant's preferred stablecoin on the merchant's preferred chain. → [Recipe: Gasless USDC into Gateway](/recipes/gasless-gateway-deposit) ### Refunds without operational risk Eco's refund path is permissionless and runs independently. If a payment fails to settle within the deadline, the refund service returns funds to the customer automatically. You don't operate a refund service; you don't hold customer funds. ### End-to-end compliance Route through [Verified Liquidity](/verified/overview) (closed access), the permissioned fulfillment lane built on a KYB'd solver network with OFAC screening built in. Every user requesting a quote is KYC/AML/OFAC-screened, and every transfer produces a structured audit record. The flow is identical to Routes, with the participant set constrained to verified parties on both sides. ## Get started → [Programmable Addresses overview](/addresses/overview), the primary product for invoice and checkout flows # Solutions for DeFi protocols Source: https://docs.eco.com/solutions/protocols Accept deposits from any chain in one click, automate cross-chain treasury rebalancing, and deepen your protocol's effective liquidity, without forcing users to bridge. DeFi protocols are limited by the chain they're on. A vault on Arbitrum doesn't see capital sitting on Optimism. Users abandon at the bridging step. Eco gives you cross-chain reach without deploying anywhere new. ## The pain you're solving * Users on chain A want to deposit into your vault on chain B but get lost in the bridge UI * Treasury sits idle on one chain while opportunities exist on another * Cross-chain rewards distribution forces users to claim on a chain they don't use * Adding a new chain means deploying contracts, bridging liquidity, maintaining infrastructure ## What Eco gives you | Capability | Product | | ---------------------------------------------- | ---------------------------------------------------------------------------------- | | One-tx cross-chain deposit into your contracts | [Routes destination calls](/routes/capabilities/destination-calls) | | Conditional treasury rebalancing | [Routes](/routes/overview) and [Programmable Transactions](/transactions/overview) | | Universal deposit address per user | [Programmable Addresses](/addresses/overview) | | Cross-chain reward distribution | [Routes API](/routes/integrate/api) | | Best-execution internal routing | [Programmable Transactions](/transactions/overview) | ## Recommended product mix | Use case | Use | | ------------------------------------------ | ---------------------------------------------------------- | | One-click cross-chain deposits | [Routes](/routes/overview) and destination calls | | Treasury rebalancing | [Routes API](/routes/integrate/api) (programmatic intents) | | Pre-positioned deposit addresses for users | [Programmable Addresses](/addresses/overview) | | Best-price internal swaps | [Programmable Transactions](/transactions/overview) | ## Patterns ### "Deposit from any chain" button User clicks Deposit on your Arbitrum vault. Your frontend builds a Routes intent with `route.tokens` = \[USDC on Arbitrum] and `route.calls` = \[`approve(vault)`, `vault.deposit(amount, user)`]. User signs once on their source chain. The whole sequence executes atomically, or refunds. → [Destination calls](/routes/capabilities/destination-calls) ### Auto-rebalance treasury Run a service that watches utilization on each deployed chain. When a threshold trips, publish a Routes intent moving USDC from over-supplied chain to under-supplied. Settlement guarantees mean no partial-state reconciliation. → [Recipe: Treasury rebalancing](/recipes/treasury-rebalancing) ### Withdraw to any chain User specifies a withdrawal address on any supported chain. Your contract publishes a Routes intent funded from your treasury, with the user's address as the recipient. User receives funds on their chain of choice. ## Get started → [Destination calls](/routes/capabilities/destination-calls), the primary capability you'll use # Solutions for solvers & market makers Source: https://docs.eco.com/solutions/solvers Compete for cross-chain stablecoin order flow with capital efficiency. Flash-borrow from Crowd Liquidity to fulfill larger intents than your reserves allow. Solving Eco intents is a market-making business with two structural advantages over traditional bridge LP: per-intent rewards (no inventory drift), and Crowd Liquidity backstop (no need to pre-position capital on every chain). ## The pain you're solving * Capital efficiency, pre-positioning USDC on every chain is expensive * Inventory drift, pool-based bridges accumulate the wrong assets over time * Limited deal size, your reserves cap the intents you can fulfill * No way to differentiate on speed/price beyond raw capital ## What Eco gives you | Capability | Product | | --------------------------------------------------------- | ------------------------------------------------------------------ | | Per-intent reward, no inventory accumulation | [Routes architecture](/routes/architecture/overview) | | Flash-borrow stablecoin liquidity for larger fulfillments | [Crowd Liquidity](/routes/primitives/crowd-liquidity) | | Permissionless registration | [Solver Registry API](/api-reference/introduction#solver-registry) | ## Recommended product mix | Use case | Use | | ---------------------------------- | ----------------------------------------------------------------------- | | Quote and fulfill standard intents | [Routes API V2 endpoints](/api-reference/introduction#solver-interface) | | Capital backstop for large intents | [Crowd Liquidity](/routes/primitives/crowd-liquidity) | ## Patterns ### Standard solver 1. Implement `/api/v2/quote` and `/api/v2/quote/reverse` 2. Register your endpoints 3. Monitor source-chain Portal for `IntentCreated` events with your quote 4. Fulfill on the destination chain 5. Wait for proof, then withdraw the reward → [Recipe: Become a solver](/recipes/become-a-solver) ### Issuer-direct solver If you're an issuer or have native mint/burn access for a stablecoin, run a solver that uses your native channel for fulfillment instead of bridging. Lower cost = better quotes = more flow. ## Get started → [Recipe: Become a solver](/recipes/become-a-solver), the full integration walkthrough # Solutions for treasury & yield managers Source: https://docs.eco.com/solutions/treasury-yield Automate cross-chain stablecoin rebalancing with cryptographic execution guarantees. Earn yield on idle stablecoins by providing JIT liquidity to the solver network. Treasury and yield managers care about three things: predictable execution at size, capital efficiency, and risk transparency. Eco's vault-based settlement and Crowd Liquidity layer give you all three. ## The pain you're solving * Manually monitoring and rebalancing across chains is operationally expensive * AMM-based bridges have high slippage at size * Idle treasury earning \~0% * Risk of bridge failure leaving capital in wrapped form mid-flow ## What Eco gives you | Capability | Product | | -------------------------------------------------------------- | ----------------------------------------------------- | | Programmatic cross-chain rebalancing with guaranteed execution | [Routes API](/routes/integrate/api) | | Earn yield on idle treasury | [Crowd Liquidity](/routes/primitives/crowd-liquidity) | | Conditional execution ("rebalance when X") | [Programmable Transactions](/transactions/overview) | | Atomic, all-or-nothing rebalance flows | [Vault model](/concepts/vaults) | | Compliance / multi-sig integration | [Orchestration](/transactions/orchestration) | ## Recommended product mix | Use case | Use | | ------------------------------- | ----------------------------------------------------- | | Treasury rebalancing service | [Routes API](/routes/integrate/api) | | Idle-stable yield | [Crowd Liquidity](/routes/primitives/crowd-liquidity) | | Conditional/threshold execution | [Programmable Transactions](/transactions/overview) | ## Patterns ### Threshold-triggered rebalance Run a service that watches per-chain utilization. When chain A \< 20% AND chain B > 80%, publish a Routes intent moving USDC. Atomic execution means no half-state to reconcile. → [Recipe: Treasury rebalancing](/recipes/treasury-rebalancing) ### Yield on idle USDC Deposit treasury USDC into Crowd Liquidity. Solvers flash-borrow against it for fulfillment; you earn fees on actual usage. No lockup, withdraw anytime. → [Crowd Liquidity](/routes/primitives/crowd-liquidity) ### Multi-sig rebalance with audit trail Use [Permit3](/routes/primitives/permit3/overview) to authorize the rebalance flow with a single multi-sig signature, then trigger Routes intents over the lifetime of the authorization. Every leg is provable onchain. ## Get started → [Routes API](/routes/integrate/api), programmatic intent publishing # Solutions for wallets & consumer apps Source: https://docs.eco.com/solutions/wallets Add cross-chain stablecoin movement, gasless deposits, and one-click swaps to a consumer wallet, without users ever switching networks. Wallets and consumer apps live or die by friction. Network switching, gas-token acquisition, and bridge UX are the top three reasons new users abandon. Eco removes all three. ## The pain you're solving * Users hold USDC on chain A, your app needs them to use it on chain B * Users don't have native gas on the destination chain * Bridge UI is a separate app, breaking your flow * "Stuck bridges" support tickets eat your team's time * No way to accept any stablecoin and settle to one preferred currency ## What Eco gives you | Capability | Product | | ---------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | | One-signature cross-chain transfer | [Routes](/routes/overview) | | Gasless funding via ERC-3009 / Permit | [Routes](/routes/capabilities/stable-1-to-1) and [Permit3](/routes/primitives/permit3/overview) | | Deterministic deposit address per user with pre-programmed actions, auto-routes anywhere | [Programmable Addresses](/addresses/overview) | | Bridge AND swap AND deposit, atomically | [Destination calls](/routes/capabilities/destination-calls) | | Predictable settlement time, even under solver outage | [Settlement vs Orchestration](/concepts/settlement-vs-orchestration) | ## Recommended product mix | Use case | Use | | -------------------------------------- | ---------------------------------------------------------------------- | | In-app cross-chain swap | [Routes API](/routes/integrate/api), direct REST integration | | Gasless USDC sends | [Routes API](/routes/integrate/api) and ERC-3009 funding | | User wants a permanent deposit address | [Programmable Addresses](/addresses/overview) | | Pay-from-any-chain checkout | Routes and [Destination calls](/routes/capabilities/destination-calls) | ## Patterns ### Universal stable balance Surface the user's USDC balance across all chains as one number. When they spend, fetch a Routes quote, present the route, fund and publish from the chain with the cheapest path. No network switching in the UI. ### One-click deposit into any protocol User taps "Deposit into protocol X on Arbitrum." App builds a Routes intent with `route.calls` containing the destination protocol's `deposit()` call. User signs once. The result either lands fully or refunds. → [Destination calls](/routes/capabilities/destination-calls) ### Deterministic withdrawal address with pre-programmed actions When a user adds a withdrawal destination on a different chain, generate a Programmable Address once and store it. Future withdrawals just transfer to that address, no bridge, no signature, no support tickets. → [Recipe: Auto-routing deposits to any chain](/recipes/auto-route-deposits) ## Get started → [Routes quickstart](/get-started/quickstart), integrate cross-chain transfers in 15 minutes # Orchestration Source: https://docs.eco.com/transactions/orchestration Compose Routes, Programmable Addresses, Programmable Transactions, Permit3, compliance behind a single API. Beta. Orchestration is in **beta** and available to **Eco partners for early access**. It exists today as a composition pattern across Eco's existing products; a unified Orchestration API is on the roadmap. [Contact us](mailto:contact@eco.com) to integrate. The **Orchestration** layer combines four primitives behind a single integration surface: Routes, Programmable Addresses, Programmable Transactions, and Permit3. ## Why this exists Most non-trivial integrations span multiple Eco products: * A **payment platform** uses Programmable Addresses for funding and Routes for settlement * A **DeFi protocol** uses Routes for cross-chain deposits and Programmable Transactions for internal best-price routing * An **AI agent** chains a Programmable Address deposit, a Sauce-based swap, and a Routes destination call into one composable flow * A **regulated payments app** layers compliance (address screening, AML, region rules) over Routes via solver selection Today, a partner glues these together themselves. The Orchestration API is the future single-call API for these composite flows. ## What it composes | Primitive | What it contributes | | --------------------------------------------------- | ---------------------------------------------------------------------------- | | [Routes](/routes/overview) | Cross-chain stable transfers, RFQ, destination calls | | [Programmable Addresses](/addresses/overview) | Deterministic-address funding | | [Programmable Transactions](/transactions/overview) | Runtime decision logic, best-execution selection | | [Permit3](/routes/primitives/permit3/overview) | Single-signature multi-chain authorization | | [Verified Liquidity](/verified/overview) | KYB'd solver network, KYC/AML/OFAC-screened users, per-transfer audit record | ## Roadmap | Item | Status | | --------------------------------------------------------------------------- | ------------------------------------------------------------- | | Composition pattern docs (recipes) | Available, see [Recipes](/recipes/send-usdc-cross-chain) | | Unified `/orchestration/*` API | In design | | [Verified Liquidity](/verified/overview), the permissioned fulfillment lane | Closed access, [contact to integrate](mailto:contact@eco.com) | | Single-API "compose" surface for partners | Future | # Programmable Transactions Source: https://docs.eco.com/transactions/overview A single transaction with embedded decision logic, read state from multiple contracts, pick the optimal path, execute atomically. No contract deployment. Powered by the Sauce protocol. Beta. Programmable Transactions is in **beta**, available for **early access to select Eco partners and customers**. The protocol (Sauce) is functional and audited; the developer-facing integration surfaces, SDK, MCP, and template APIs for common transaction and trading flows, are still landing. [Contact us](mailto:contact@eco.com) to integrate. A **Programmable Transaction** is a single onchain transaction with embedded decision logic. It can read state from multiple contracts, pick between paths based on the result, and execute the optimal one, atomically, without deploying a contract. This turns "best execution" from a marketing claim into a runtime property: the transaction itself picks the best path at execution time, not at signing time. Sauce is Eco's protocol for automating stablecoin flows and liquidity strategies. It gives developers full transaction control, encoding intelligent transactions that adapt to market conditions at runtime and execute atomically, for superior price execution and faster time to market. ## What it enables | Pattern | Today (without programmable tx) | With programmable tx | | -------------------------- | ---------------------------------------------------------- | ------------------------------------------------------ | | Multi-DEX best price | Three quote calls and one swap, prices drift between calls | Quote three DEXes, decide, swap on the winner, one tx | | Conditional execution | Off-chain logic and manual triggering | "Only swap if price \< X" inline in the tx | | Cross-protocol composition | Custom contract per integration | Compose any two contracts at tx-time | | MEV-resistant routing | Difficult; multi-tx leaks intent | Single atomic tx, no inter-tx window for front-running | | AI agent execution | Agent must deploy contracts or send many txs | Agent emits one programmable tx | ## How Sauce works ```text theme={null} Standard transaction: user → contract A → contract B → done (no decision-making) Programmable tx: user → orchestration layer (Sauce) ├── reads contract A ├── reads contract B ├── reads contract C ├── compares results └── executes optimal path (A, B, or C) ``` The "orchestration layer" lives in the calldata itself. A magic header (`0x5A00CE`) signals that the transaction contains embedded **operation bytecode**, a Turing-complete sequence of opcodes that handles decision-making between calls. External contracts see normal calldata; they don't know they're part of an orchestrated transaction. Sauce ships **50+ opcodes** covering data extraction, cross-call memory, control flow (if/else, loops), boolean logic, arithmetic, and crypto operations. ## Use cases | Who | What they build | | ------------------------ | ---------------------------------------------------------- | | **DEX aggregators** | Real best-price routing without separate quoter calls | | **Liquidation services** | Atomic check-and-repay across positions | | **Yield optimizers** | Read N vault APYs, switch position into the winner | | **AI agents** | Single-tx multi-step strategies, no contracts to deploy | | **MEV-resistant apps** | One atomic tx eliminates the inter-tx front-running window | ## Status | Capability | Status | | ---------------------------- | ----------------------------- | | EVM support | Fully featured, audit planned | | Dev tools and recipe library | In development | | SVM support | In development | | MCP server and AI skills | Future | # Verified Liquidity Source: https://docs.eco.com/verified/overview A permissioned fulfillment lane for stablecoin transfers, built on a KYB'd solver network with OFAC screening built in and global cross-chain coverage. Closed access. Verified Liquidity is **closed access**. [Contact us](mailto:contact@eco.com) to integrate. **Verified Liquidity** is a permissioned fulfillment lane for stablecoin transfers on Eco. It works the same way as [Routes](/routes/overview): you sign an intent, a solver fulfills it, and a prover settles. The difference is that every participant is verified. Every solver in this lane is a KYB'd legal entity, and every user requesting a quote is screened against KYC/AML/OFAC checks before an intent is published. ## What you get ### KYB'd solver network Verified legal entities only. Routing through Verified Liquidity means every solver that can fulfill your intent has completed business-entity verification (KYB), beneficial-owner checks, and AML review before being admitted to the lane. ### KYC and OFAC screening Every user requesting a quote is identity-verified, AML-screened, and OFAC-checked before an intent is published. A screening hit blocks the request before any onchain action, so no flow ever reaches a solver. ### Global cross-chain coverage Stablecoin flows across every major chain Eco supports, including non-EVM chains like Solana. The lane reuses the same settlement stack as Routes, so its coverage tracks the broader network. ## How it works The flow is identical to [Routes](/routes/overview). The only thing that changes is who is allowed to participate. 1. **Request a quote.** You submit a quote request to the Verified Liquidity lane. The requesting user is screened against KYC/AML/OFAC before anything proceeds. 2. **KYB'd solvers compete.** Every solver that can quote your intent in this lane has completed KYB and AML review. 3. **Sign and publish.** You sign an intent and publish it, exactly as you would in Routes. 4. **Fulfill and settle.** The chosen solver fulfills on the destination chain, and a prover settles the reward. 5. **Audit record.** Every transfer produces a structured, exportable compliance record that captures counterparty attestations, screening results with list version and timestamp, and onchain execution references. The lane reuses the existing [Portal](/routes/architecture/portal), [Vault](/routes/architecture/vault), and [Prover](/routes/architecture/provers/overview) stack. There is no new contract surface and no fork of the protocol. ## Why this exists Stablecoin rails have historically either been fast or met the requirements of regulated parties, but not both. Permissionless solver networks make no claim about who is fulfilling a transfer or who is requesting it, which makes them a poor fit for institutional flows where regulators, counterparties, and internal compliance functions all require verified participants on both sides of the trade. Verified Liquidity is the lane partners route through when every party, both the solver and the end user, has to be a verified and regulated entity, and when the partner needs a demonstrable compliance trail. ## Who it's for * **Payment platforms and PSPs** settling stablecoin flows where the merchant or regulator requires verified counterparties * **Stablecoin issuers** that want a verified routing path for their token * **B2B payments, payroll, and FX settlement** that cannot run over an anonymous network * **Treasury operations** at regulated institutions ## How it composes with Routes Verified Liquidity constrains **who** participates on both sides. It is not a separate protocol, and it composes with everything Routes already supports: * [Stable 1:1 transfers](/routes/capabilities/stable-1-to-1) for same-stable cross-chain over the verified lane * [Cross-stable RFQ](/routes/capabilities/stable-rfq) for stable-to-stable swaps with verified fulfillment * [Destination calls](/routes/capabilities/destination-calls) for bridge-and-act flows with verified fulfillment ## What's included * A KYB'd solver network * KYC/AML/OFAC screening for every user requesting a quote * A per-transfer compliance audit record * Global cross-chain coverage, including non-EVM chains like Solana * Partner onboarding through Eco ## Access Verified Liquidity is **closed access**. → [Contact us](mailto:contact@eco.com) to integrate as a routing partner, or to be reviewed as a solver for the verified lane.