> ## Documentation Index
> Fetch the complete documentation index at: https://docs.pesapay.xyz/llms.txt
> Use this file to discover all available pages before exploring further.

# USDC through CCTP

> How a USDC burn on an EVM chain becomes native USDC in a Stellar wallet, and back.

Circle's Cross-Chain Transfer Protocol (CCTP) burns USDC on one chain and mints the same amount on another, after Circle's attestation service (Iris) signs the burn. No wrapped token and no liquidity pool sit in between. Stellar is CCTP domain 27.

## Deposit: EVM to Stellar

The user's wallet sends two calls in one batch on the source chain:

1. `approve` the chain's USDC for TokenMessengerV2, for the exact amount.
2. `depositForBurnWithHook(amount, 27, forwarder, usdc, forwarder, maxFee, minFinality, hookData)`.

Both `mintRecipient` and `destinationCaller` are Circle's Stellar `CctpForwarder` contract. The hook data names the real recipient:

```
hookData = 24 zero bytes ‖ u32 version (0) ‖ u32 length ‖ recipient strkey (UTF-8)
```

Then:

<Steps>
  <Step title="Iris attests">
    Pesa's worker polls `GET /v2/messages/{sourceDomain}?transactionHash=...` until Circle returns the message and its attestation.
  </Step>

  <Step title="Pesa's relay mints">
    The relay checks that the nonce is unused, then submits `mint_and_forward(message, attestation)` on Stellar. The forwarder mints and pays the recipient in one atomic call. The function is public, so anyone may submit it; the nonce check makes a second submit a no-op.
  </Step>

  <Step title="The arrival is credited">
    The transfer becomes `CREDITED` only after the USDC transfer to the user's wallet is observed on Stellar.
  </Step>
</Steps>

<Warning>
  If `mintRecipient` or `destinationCaller` is not the forwarder, or the hook data names the wrong account, the USDC is lost for good. The SDK's `verifyCctpCalls` refuses any such call before the wallet prompt.
</Warning>

## Withdraw: Stellar to EVM

The user signs two Stellar transactions:

1. A USDC `approve` of TokenMessengerMinterV2 for the exact amount. The burn moves USDC with `transfer_from`, so it needs this allowance. The allowance lives 17,280 ledgers (about a day). If it already covers the amount, this prompt is skipped.
2. `deposit_for_burn` to the destination chain's domain, with `mint_recipient` set to the user's EVM address padded to 32 bytes, zero `destination_caller`, `max_fee` 0 and finality 2000.

Iris attests the burn (source domain 27). Pesa's relayer on the destination chain calls `receiveMessage(message, attestation)`. It mints to the address in the message, so the relayer cannot redirect funds. If a third party relays first, the nonce check skips Pesa's submit and the delivery is still observed.

## Units

CCTP amounts have 6 decimals. Stellar USDC has 7.

| Direction | Conversion |
| - | - |
| Deposit | `amount7 = amount6 × 10` |
| Withdraw | `amount6 = floor(amount7 / 10)`; the last digit stays in the wallet |

## Speed

Pesa uses CCTP Standard Transfer (`maxFee = 0`, `minFinality = 2000`). The wait is the source chain's finality: seconds on Avalanche, about 15 to 19 minutes on Base, Arbitrum and Ethereum. Fast Transfer is turned on per source chain once a canary shows Stellar accepts it.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.