When to use this path
Your users hold USDC in Crossmint smart wallets on Base (or another EVM chain that Circle CCTP supports) and you want that USDC earning yield in a DeFindex vault. DeFindex vaults run on Stellar, so the USDC has to cross first. Circle’s Cross-Chain Transfer Protocol (CCTP) burns USDC on the source chain and mints the same USDC on Stellar. There is no wrapped token and no liquidity pool in the middle: what lands in the wallet is the same USDC that DeFindex USDC vaults accept.
The Crossmint setup is the same in both: one server key registered as
adminSigner on an EVM smart wallet and on a Stellar smart wallet, and every transaction sent through Crossmint’s REST API. If you have not set that up yet, start with the reference repository and the Crossmint Smart Wallets guide.
The flow
- Burn on Base. The Crossmint EVM smart wallet approves USDC to Circle’s
TokenMessengerV2and callsdepositForBurnWithHookwith destination domain27(Stellar).mintRecipientanddestinationCallerare the StellarCctpForwardercontract, and the hook data carries the address of the user’s Stellar smart wallet. - Attestation. Circle’s attestation service (Iris) signs the burn. You poll it until the message is
complete. - Mint and forward on Stellar. Anyone can call
mint_and_forward(message, attestation)onCctpForwarder. In one Soroban invocation it mints the USDC to itself and transfers it to the address in the hook data. - Deposit. The Stellar smart wallet deposits the USDC into a DeFindex vault, the same way as any other deposit from a Crossmint wallet.
On the burn,
mintRecipient and destinationCaller must both be the CctpForwarder contract. Circle states that if either is wrong the funds are permanently stuck and cannot be recovered. Never put the user’s wallet address in mintRecipient: it goes only in the hook data. See CCTP on Stellar.Addresses and parameters
Every value below comes from Circle’s documentation, linked next to it. Read them from Circle’s pages again before going to mainnet: Circle’s own instructions are to look addresses up rather than hardcode them.Mainnet
Testnet
Crossmint
staging runs on base-sepolia and Stellar testnet. The reference repository’s Sodax example uses a Crossmint test token on Base Sepolia; CCTP burns only Circle’s testnet USDC listed above.Step by step
The snippets reuse theCrossmintRestClient and helpers from defindex-io/crossmint-defindex-guide: getOrCreateEvmWallet, getStellarWalletAddress, sendTransactionAndGetHash, getStellarDepositBalance and depositToDefindexVault. They use ethers v6 for EVM encoding and @stellar/stellar-sdk for Stellar.
1. Configuration
2. Get both wallets
The EVM wallet holds the USDC to move. The Stellar wallet is a Soroban smart contract wallet, so its address is aC... contract address. That address is the final recipient of the USDC.
3. Build the hook data
These two helpers are Circle’s, from CCTP on Stellar. The forwarder is encoded as the raw 32 bytes of its contract ID. The recipient goes in the hook data as its full strkey text.4. Approve and burn on Base through Crossmint
Both calls are ordinary EVM transactions from the Crossmint smart wallet:POST /transactions, sign the approval message with the adminSigner key, POST /approvals, poll for onChain.txId. sendTransactionAndGetHash in the reference repository does all of that.
The parameters of depositForBurnWithHook are from Circle’s contract interfaces. The amount is in Base USDC units, 6 decimals.
5. Wait for the attestation and mint on Stellar
Poll Circle’sGET /v2/messages/{sourceDomainId} with the burn transaction hash until status is complete. The source domain is Base (6), not Stellar.
mint_and_forward itself. CctpForwarder is publicly callable, so any funded Stellar account can pay the fee: the minted USDC goes to the address in the hook data, not to the caller. This is Circle’s submitSorobanTx pattern from the Stellar quickstart, signed with a relayer account your server controls.
To keep a single signing path, you could also send
mint_and_forward as a Crossmint contract-call from the Stellar smart wallet. Crossmint’s custom Stellar transactions guide passes args as a JSON object and does not document how to pass Soroban Bytes, so this page uses a plain relayer account. Confirm the encoding with Crossmint before taking that route.6. Deposit into the vault
The wallet now holds USDC with 7 decimals. Read what actually arrived and deposit it.depositToDefindexVault sends a Crossmint contract-call to the vault’s deposit method with amounts_desired, amounts_min (0.5% slippage), from set to the smart wallet, and invest: true. The parameters mean the same as in Deposit and in Vault Methods. The DeFindex API’s deposit endpoint returns an unsigned transaction for a classic account to sign, while a Crossmint smart wallet signs through Crossmint, which is why the reference repository calls the vault method directly. Use the API for everything that only reads, such as the position: GET /vault/{address}/balance (Get balance).
Stellar specifics
Precision. USDC has 6 decimals on Base and every other CCTP chain, and 7 on Stellar. The CCTP messageamount is always in 6-decimal units, and the mint on Stellar multiplies it by 10: 100000000 (100 USDC) burned on Base becomes 1000000000 on Stellar. Use 7 decimals for every amount you pass to the vault. In the other direction, a burn from Stellar ignores the seventh decimal and leaves it in the wallet. Source: USDC precision for CCTP and Stellar.
Address encoding. CCTP messages carry 32 bytes per address with no type prefix, so the protocol cannot tell an account (G...) from a contract (C...) and assumes mintRecipient is a contract. That is why the mint goes to CctpForwarder and the real recipient travels as a strkey in the hook data. Source: Stellar address types.
Contract wallets need no trustline. A Crossmint Stellar wallet is a contract (C...) and can receive USDC directly. A classic G... or M... recipient needs a USDC trustline first, or the forward fails. Source: Transfer USDC to and from Stellar.
Atomic and non-custodial. mint_and_forward validates the message, mints to the forwarder and pays the recipient in one invocation. Any failure reverts the whole call, and Circle does not hold the funds in between. Source: How it works.
Fast Transfer and forwarding. Fast Transfer is not applicable when Stellar is the source, because Stellar’s standard attestation already takes seconds. With Base as the source, Fast Transfer is available for a fee. Circle’s Forwarding Service does not cover Stellar as a destination, so you always submit mint_and_forward yourself. Source: Supported chains and domains.
Timing. Most of the wait is on the source side. A Standard Transfer from Base waits for Ethereum finality of the Base batch, which Circle lists at roughly 15 to 19 minutes; a Fast Transfer from Base attests in seconds. The Stellar side is one Soroban transaction. Source: Finality and block confirmations.
Null fields in Iris. For messages that involve Stellar, Iris returns the address fields of decodedMessage as null, because it cannot tell an account from a contract. Read the raw message hex if you need them. amount and feeExecuted are still decoded. Source: Null address fields.
Balances are not on Horizon. A contract wallet’s USDC balance lives in the token contract. Read it by simulating balance(C...) on the USDC contract over Soroban RPC, as getStellarDepositBalance does.
Troubleshooting
Resources
- CCTP on Stellar, Circle
- CCTP Stellar contracts and interfaces, Circle
- Transfer USDC to and from Stellar, Circle quickstart
- Custom EVM transactions and custom Stellar transactions, Crossmint
defindex-io/crossmint-defindex-guide, the reference code for the Crossmint wallets and the vault calls