Stellar address types
On Stellar, addresses arestrkey strings composed of a type identifier and a
32-byte payload. The type identifier determines the account type: user accounts
(G) carry an Ed25519 public key, contracts (C) carry a contract ID hash.
Muxed accounts (M) are a type of G account that additionally embeds a
numeric identifier alongside the Ed25519 public key (see Stellar’s
muxed accounts documentation).
CCTP messages store only the raw 32-byte payload without the type identifier, so
the protocol cannot distinguish between address types and assumes the
mintRecipient is always a contract. Use CctpForwarder when transferring to
Stellar to ensure funds are forwarded to the intended recipient.
Use CctpForwarder for Stellar recipients
CctpForwarder is a publicly callable onchain contract that receives minted
USDC on Stellar and atomically forwards it to forwardRecipient. Encode
forwardRecipient in hook data as a Stellar strkey. The prefix G, M, or
C identifies the recipient address type.
How it works
Callmint_and_forward on CctpForwarder through the Stellar Soroban client
for your language. Pass the raw CCTP message and attestation bytes.
The following shows the onchain contract interface. It is not a TypeScript or
JavaScript function you call directly. Your Soroban client builds an
invokeHostFunction operation from these arguments.
@stellar/stellar-sdk documents
how to encode arguments and how to simulate, sign, and submit transactions
against Stellar RPC.
Inside mint_and_forward, CctpForwarder does the following:
- Validates the message.
- Extracts
forwardRecipientfrom hook data. - Calls
receive_messageonMessageTransmitter, which mints USDC toCctpForwarder. - Transfers the minted USDC to
forwardRecipient. - Runs atomically. Any failure reverts the invocation.
The
CctpForwarder flow is non-custodial. The mint and the payout to
forwardRecipient both run onchain in that single Soroban invocation. Circle
does not take custody of the minted balance in between.Hook format
The hook data begins with the reserved magic bytescctp-forward, followed by
versioning and payload fields. On Stellar, bytes 28 onward carry the length of
forwardRecipient, the forwardRecipient strkey, and any optional trailing
bytes for integrator use.
| Bytes | Type | Data |
|---|---|---|
| 0-23 | bytes24 | Magic. cctp-forward for Circle-relayed; all zero bytes for self-relay |
| 24-27 | uint32 | Version, set to 0 |
| 28-31 | uint32 | L: length of forwardRecipient in bytes |
32..(32+L-1) | bytes | forwardRecipient as a strkey |
(32+L).. | bytes | Optional integrator-defined payload; omit if unused |
Building forwarder hook data (example)
The following helper functions validate Stellar contractstrkey inputs and
build the hookData payload for an EVM depositForBurnWithHook call:
TypeScript
Stellar addresses in CCTP messages and API responses
Stellar addresses arestrkey strings. CCTP message
fields store only 32-byte address payloads. They omit the strkey encoding,
including the G, M, or C type marker, so the raw bytes in the message do
not say whether the address is an account or a contract. mintRecipient is
always assumed to be a contract address. You must
use CctpForwarder to make
transfers to Stellar.
CCTP message fields
The following tables describe each address field in the CCTP message, explain how Stellar uses it during a mint (inbound) or burn (outbound), and indicate whether you need to design around the address type. For the full message layout, see the CCTP Technical Guide.Inbound transfers to Stellar destination
| Field | Operation | Must design around address type? |
|---|---|---|
sender | Validate against the source domain TokenMessenger mapping. | No |
recipient | Select the Stellar contract that handles the destination receive_message. | No, always a contract (C) |
destinationCaller | Restrict who may call receive_message (require_auth compares bytes). | No, compare raw bytes |
burnToken | Map the burned token identifier to Stellar USDC. | No, known asset contract |
messageSender | Not used operationally on Stellar. | No |
mintRecipient | Mint USDC to this 32-byte destination on Stellar. | Yes, always assumed to be a contract; use CctpForwarder for Stellar recipients |
Outbound transfers from Stellar source
| Field | Operation | Must design around address type? |
|---|---|---|
sender | 32 byte address payload of the Stellar TokenMessengerMinterV2 used to perform the burn. | No |
burnToken | Identify the Stellar USDC contract that is burned. | No |
mintRecipient | Encode the recipient on the destination blockchain. | No |
messageSender | Record caller context (not used operationally on Stellar). | No |
destinationCaller | Encode which address may call receive on the destination blockchain. | No |
recipient | Encode the handler contract on the destination blockchain. | No |
Null address fields in API responses
When a CCTP message involves Stellar, the Get messages endpoint returns all address fields indecodedMessage and decodedMessageBody as null because
the API cannot distinguish a 32-byte Stellar account from a contract. To read
those addresses, parse the raw hex in the message field directly.
The following example shows an Ethereum-to-Stellar transfer response with
typical null address fields:
JSON
USDC precision for CCTP and Stellar
Stellar represents USDC in seven-decimal subunits while other CCTP-supported blockchains use six. How CCTP handles that difference depends on whether Stellar is the source or destination blockchain. Regardless of direction, theamount
field in a CCTP message is always in six-decimal subunits.
Stellar wallets and SDKs often display seven fractional digits. Use six-decimal
subunits in
amount when handling CCTP messages offchain.Stellar as the source
When Stellar is the source blockchain, the burn debits only through the sixth decimal digit of the user’s balance. Anything in the seventh decimal place stays in the user’s account.- A user bridges 0.1234567 USDC from Stellar to the destination blockchain.
- Stellar burns 0.1234560 USDC.
- 0.0000007 USDC stays in the user’s Stellar account.
- The CCTP message
amountis 123456 (six-decimal subunits). - The destination blockchain mints 0.123456 USDC to the recipient.
Stellar as the destination
When Stellar is the destination blockchain, the mint converts the six-decimal messageamount into seven by scaling the integer by 10 (for example, 123456
becomes 1234560 seven-decimal subunits).
- A user bridges 0.123456 USDC from the source blockchain to Stellar.
- The CCTP message
amountis 123456 (six-decimal subunits). - Stellar mints 0.1234560 USDC to the recipient.