Overview
Portal integrates with Circle’s Cross-Chain Transfer Protocol (CCTP) so you can move USDC between EVM chains and Solana through Portal APIs. CCTP burns USDC on the source chain and Circle mints the same amount of native USDC on the destination chain. There are no wrapped tokens or liquidity pools involved. Portal uses Circle’s Forwarding Service, so the user signs only on the source chain. Circle mints on the destination chain and pays the gas there, so the wallet needs no gas on the destination. Circle’s protocol and forwarding fees come out of the amount you send. You can also charge your own fee on top, paid in USDC on the source chain (see Fees).How it works
- Quote:
POST /quotereturns the fees, the exact amount the recipient receives and a set of preflight checks. Nothing is stored. - Create:
POST /transfersquotes again with fresh fees, runs every check and returns what to sign: EVM transactions, a user operation for Account Abstraction clients, or one Solana transaction for a Solana source. - Sign: the wallet signs and broadcasts on the source chain with the Enclave MPC API.
- Submit:
POST /transfers/{id}/submittells Portal the hash (or Solana signature) of the burn. - Track: Portal follows the burn through Circle’s attestation and the mint on the destination chain. Poll
GET /transfers/{id}for the status.
https://api.portalhq.io/api/v3/clients/me/integrations/cctp.
Enable the CCTP integration in Portal
CCTP is keyless: there is no Circle account or API key to set up.- Open the Portal Dashboard.
- Navigate to the
Integrationspage. - Locate the
CCTPintegration and clickAdd. - Optionally, set your own fee under Your fee (see Custodian fee).
- Click
Enable.
Configure on the CCTP card and save.
Until it’s enabled, the routes, quote and create endpoints return 400 with the id CCTP_NOT_ENABLED. Transfers that were already created can still be submitted and tracked.
Integrations are environment‑scoped. Repeat these steps per Portal environment.
Supported chains
Any supported chain can be the source or the destination: transfers run between two EVM chains, from any EVM chain to Solana and from Solana to any EVM chain. Both chains must be mainnets or both must be testnets.GET /routes returns the live list.
The client needs a wallet on the source chain, and on the destination chain too unless you pass a recipient. A Solana source needs a Solana wallet, and sending to the client’s own address on Solana needs one as well. Otherwise the ROUTE_SUPPORTED check fails.
Mainnets
Testnets
Times are estimates and depend on the source chain. Circle only offers Fast from chains where Standard is slow; the others already settle in seconds.
Speed
Passspeed on the quote and create requests:
auto(default): Fast when the source chain offers it, otherwise Standard.fast: same asauto. A source chain without Fast still uses Standard.standard: always Standard. From Ethereum and the L2s above this takes minutes, and from Linea hours. The response carries aLONG_FINALITYwarning when Standard is slow.
speed field in the response is the speed Portal actually used. Portal runs transfers from Solana at Standard, so a transfer from Solana always uses Standard (about 25 seconds), even when you ask for fast. There’s no Fast fee from Solana.
Fees
Every quote shows the fees and the exact amount the recipient receives. Circle’s fees come out of the amount:- Protocol fee (
fees.protocol): Circle’s fee, in basis points of the amount, rounded up to the nearest USDC unit (0.000001). Circle sets the rate per route and speed. - Forwarding fee (
fees.forwarding): what Circle charges to mint on the destination chain and pay the gas there. - Max fee (
fees.maxFee): protocol fee plus forwarding fee.
- Custodian fee (
fees.custodian): your fee, set on the CCTP card in the Portal Dashboard (see Custodian fee).0when you haven’t set one. - Total debit (
totalDebit):amount + fees.custodian, the USDC that leaves the wallet.
amount - maxFee (amountOut). Circle can charge less than maxFee, in which case the recipient gets slightly more, but in practice it charges the full maxFee. For example, sending 0.1 USDC from Base Sepolia to Avalanche Fuji with a 0.5% custodian fee:
Fees change with gas prices on the destination chain.
POST /transfers always quotes again, so show your user the fees, totalDebit and amountOut from that response, not from an earlier quote.
The amount must be larger than Circle’s fees. minAmount in the quote is the smallest amount that delivers anything on that route.
Forwarding into Solana costs more. Circle mints into the recipient wallet’s USDC token account, and when that account doesn’t exist yet, Circle creates it during the mint and charges for it. On mainnet the forwarding fee is about 0.32 USDC into a new token account and about 0.14 USDC into an existing one, compared with about 0.05 USDC on EVM routes. The quote carries the RECIPIENT_ACCOUNT_SETUP warning when Circle will create the account, and minAmount reflects the higher fee.
Custodian fee
You can charge your own fee on each transfer. Set it on the CCTP card in the Portal Dashboard, under Your fee:- Fee: a percentage of
amount, in steps of 0.01%. Portal rounds it down to the nearest USDC unit (0.000001). - Flat fee (USDC): a fixed USDC amount per transfer.
- EVM fee address and Solana fee address: where the fee goes.
amount, so it doesn’t change amountOut. The BALANCE check makes sure the wallet holds totalDebit. On an EVM source, each fee is a separate USDC transfer in transactions (kind: "fee"), after the burn. For Account Abstraction clients the fee transfers are inside the user operation, and on a Solana source they’re inside the burn transaction.
Exact-out transfers
To have the recipient receive an exact amount, sendamountOut instead of amount. Portal works out how much to burn so that the recipient gets at least amountOut after Circle’s fees (exactly amountOut when Circle charges the full maxFee, as it does in practice). Your fee is still charged on top, with any percentage applied to the amount Portal burns. Send exactly one of amount or amountOut.
Both are decimal USDC strings with up to 6 decimals, for example "0.1", not base units.
Recipients
By default the USDC goes to the client’s own Portal address on the destination chain. You can send it elsewhere withrecipient: an EVM address for an EVM destination, or a Solana wallet address for a Solana destination. Portal checks the address against the destination chain. When recipient isn’t one of the client’s own addresses, it must be on your environment’s destination allowlist, which you manage on the Destination Allowlist page of the Portal Dashboard. Otherwise the RECIPIENT_ALLOWED check fails and the transfer isn’t built. A recipient other than the client’s own destination address also adds the EXTERNAL_RECIPIENT warning.
On Solana, Circle mints into the recipient wallet’s USDC token account, so pass the wallet address, not a token account address. When that token account doesn’t exist yet, Circle creates it during the mint, the forwarding fee is higher and the quote carries the RECIPIENT_ACCOUNT_SETUP warning (see Fees).
These recipient checks apply to transfers built through the Portal CCTP API.
Preflight checks
The quote and create endpoints return achecks array. Each check has an id, an ok value and, when it fails, a message and a fix.
ok: true: the check passed.ok: false: the check failed and blocks the transfer.ok: null: the check can’t be known yet, or a check it depends on failed. It doesn’t block.
ready is true when no check is false. POST /quote returns 200 either way. POST /transfers returns 400 with the id CCTP_PREFLIGHT_FAILED and every failed check, and stores nothing.
For Account Abstraction clients on an EVM source,
SOURCE_GAS and SPONSORSHIP are null on POST /quote, because Portal only builds the user operation on POST /transfers. They are also null on POST /transfers when an earlier check fails. For a Solana source, both checks are known on the quote.
Solana gas
When your environment has a Solana gas sponsor that’s enabled, included in your plan and funded, the sponsor pays the transaction fee for a Solana source, plus the rent (about 0.003 SOL) for the event account that Circle’s program creates with each burn. The wallet then needs no SOL. Otherwise the wallet pays both, and must keep Solana’s minimum account balance (about 0.0009 SOL) on top.sourceGas.payer in the quote says who pays, and sourceGas.estimatedLamports how much.
Warnings
warnings never block a transfer:
Transfer statuses
COMPLETED, FAILED and STALLED are final.
A FAILED transfer has a failureReason:
Portal tracks transfers through Circle’s attestation API. While it’s unavailable, transfers wait in their current status until it’s back; Circle still forwards the mint, so the USDC still arrives.
STALLED still applies 24 hours after submission (48 hours from Linea). If a transfer is STALLED and the funds haven’t arrived, the burn’s message and attestation can be fetched from Circle’s attestation API with sourceTxHash and minted on the destination chain manually; contact Portal support with the transfer id. On Solana, destinationTxHash is a base58 signature and destinationExplorerUrl links to Solscan.
Next steps
Implement the flow in your app:- Move USDC with CCTP using the Enclave MPC API
- CCTP endpoints in the Client API reference