noswap.io

Move USDC between chains without swapping it.

Get a deposit address on one chain. Send native USDC to it. The same USDC arrives in your wallet on the chain you picked. There is no swap, no pool, no price and no slippage: Circle burns your dollars on one side and mints the same number on the other.

The deposit address is a contract with no private key. It can do exactly one thing: forward everything it receives to the destination written into it when it was created. We never hold your funds, and there is no key to hold.

Get a deposit address

This page asks the contract directly. Nothing you type is sent to us.

How it works

  1. You get an address

    It is derived from the destination you gave, so it exists before anyone deploys anything, it never changes, and anyone can ask the chain for the same answer without going through us.

  2. Someone sends USDC to it

    An exchange withdrawal, an on-ramp, another wallet. Native Circle USDC on that chain, like to any other address.

  3. The contract forwards it

    The contract burns the USDC through Circle's Cross-Chain Transfer Protocol, naming your wallet as the only possible recipient. Circle attests the burn, and the same amount is minted to you on the destination chain. About half an hour, measured end to end.

What we never hold

Not your keys

The deposit address is a contract, not a wallet. There is no private key for it, on our servers or anywhere else. Nothing to steal, nothing to lose, nothing to hand over.

Not your funds

USDC sitting in the contract can only leave one way: to the destination written into it when it was created. Our servers cannot redirect it, hold it back, or take more than the fee cap written in the code. There is no function for that.

What our server actually does: it pays the network fees. It calls the contract's single public function when USDC arrives, and it presents Circle's attested message on the destination chain. That is the whole job. If it disappears, anyone, including you, can call the same function and the money still goes to you.

There is no server between you and your money

This page is a static file. It talks to the blockchain directly, with several public endpoints in reserve, and it works the same served from anywhere, including IPFS.

your browser
     |
     +-- asks the factory contract for your deposit address   (eth_call)
     +-- asks the departure chain whether the route exists    (eth_call)
     +-- derives your Solana token account, then checks it    (getAccountInfo)
     |
     +-- optionally tells our keeper there is gas to pay      (can fail, nothing breaks)

How you can check it

Supported chains

Where a deposit address can live, and where it can deliver. Every chain below was probed twice: the chain names its own CCTP domain, and another chain recognises it.

ChainChain idCCTP domainCan send fromCan receive on
loading

Sonic and Sei can receive but cannot send: their native USDC contract has not been measured, and a guessed token address is an address where money dies.

The API, for anything that is not a browser

The page above does not need it. It exists so that a program can get the same answer in one call.

GET /api/address?from=base&to=optimism&wallet=0xYourWallet

from and to accept a name (base, optimism, solana), an alias (op, arb, sol), an EVM chain id (8453), or a CCTP domain written as a domain (domain:6).

A bare number is always a chain id, never a domain. The two numberings collide: 1 is Ethereum as a chain id and Avalanche as a domain, 10 is Optimism as a chain id and Unichain as a domain. A service that tried one then the other would one day deliver on the wrong chain without a single error on screen. So bare numbers are chain ids, domains must be written domain:n, and anything ambiguous is refused rather than guessed.

{
  "depositAddress": "0x...",
  "depositChain": { "name": "Base", "chainId": 8453, "domain": 6 },
  "send": { "token": "USDC", "contract": "0x8335...2913" },
  "destination": {
    "chain": { "name": "Optimism", "chainId": 10, "domain": 2 },
    "wallet": "0xYourWallet",
    "mintRecipient": "0x000...YourWallet"
  },
  "fee": { "maxMicroUsdc": 13000, "maxUsdc": 0.013 },
  "delivery": "about half an hour",
  "verifyYourself": "call predict(2, 0x000...) on 0xFactory over any RPC"
}

Other endpoints: GET /api/chains lists the chains and every accepted spelling, GET /api/status reports what the service is doing. Both are free of the rate limit.

Rate limit

One request per IP per minute on /api/address. Over it you get 429 with a Retry-After header and the number of seconds to wait. The limit protects our work, not your money: the address is deterministic, so you can compute it yourself, or read predict() on the factory, and never call us at all.

When it refuses, and why

unknown_chainthe name or code matches no chain. No fallback guess is made.
same_chaindeparture and arrival are the same chain. No bridge is needed, and such a contract would have no way out.
invalid_destinationthe wallet is not a valid address for the receiving chain.
destination_account_missingSolana only: your USDC token account does not exist yet. See below.
route_not_supportedthe departure chain does not register that CCTP destination. Read live from the chain, not from a table.
route_not_operatedno gas is held on that arrival chain yet, so the delivery could not be finished. Refused rather than left half done.
no_factoryno factory is deployed on that departure chain yet.

Receiving on Solana

Solana works differently, and the difference costs money if you get it wrong, so here it is in the open.

Sending from Solana is not available yet: it needs its own on chain program, specified in the repository, not written.

Limits, stated plainly

Status

PhaseWhatState
1The contracts, compiled and testedwritten, not yet deployed
2This page and the APIwritten, not yet running
3The keeper that pays the gaswritten, not yet running
4First real transfer, with measured costsnot started
5Sending from Solanaspecified, not written

Nothing here handles real money yet. This page will say so until it does.