> ## 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.

# Withdraw from Stellar

> Send USDC from the user's Stellar wallet to USDC or USDT on an EVM chain.

A withdrawal spends USDC from the owner's Stellar wallet and delivers it to an EVM address the user types. The user signs Stellar transactions only; Pesa pays the Stellar fees and the destination-chain gas.

## CCTP: USDC to an EVM chain

<Steps>
  <Step title="Quote with the destination">
    ```ts theme={null}
    const quote = await pesa.quote({
      route: "cctp-withdraw",
      source_chain: "stellar:pubnet",
      source_address: stellarAddress,   // must be the owner
      dest_chain: "eip155:42161",       // Arbitrum
      dest_address: typedEvmAddress,
      amount_in: "200000000",           // 20 USDC, 7 decimals
    });
    const transfer = await pesa.createTransfer(quote.id, crypto.randomUUID());
    ```

    An invalid address for the chain is refused here, before any signature.
  </Step>

  <Step title="Prompt 1: approve">
    ```ts theme={null}
    const action = await pesa.getActions(transfer.id);   // stellar_tx
    const tx = verifyStellarApprove(action.xdr, { owner: stellarAddress, stellar: STELLAR_CCTP.mainnet, amount7: 200_000_000n });
    const signed = await signWithStellarWallet(tx);
    await pesa.submit(transfer.id, { kind: "stellar_tx", signed_xdr: signed });
    ```

    Circle's Stellar minter moves USDC with `transfer_from`, so it needs an allowance. If an allowance already covers the amount, this prompt is skipped.
  </Step>

  <Step title="Prompt 2: burn">
    ```ts theme={null}
    const action = await pesa.getActions(transfer.id);   // stellar_tx
    const tx = verifyStellarBurn(action.xdr, {
      owner: stellarAddress,
      stellar: STELLAR_CCTP.mainnet,
      chain: findEvmChain("mainnet", "eip155:42161"),
      recipient: typedEvmAddress,     // what the user typed, not the server's copy
      amount7: 200_000_000n,
    });
    await pesa.submit(transfer.id, { kind: "stellar_tx", signed_xdr: await signWithStellarWallet(tx) });
    ```
  </Step>

  <Step title="Delivery">
    Iris attests the burn, and Pesa's relayer calls `receiveMessage` on Arbitrum. The status becomes `completed` when the mint is observed there.
  </Step>
</Steps>

The amount is floored to 6 decimals for CCTP. The 7th decimal stays in the wallet.

If the allowance expires between the two prompts (it lasts about a day), the transfer returns to `AWAITING_SIGNATURE` and asks for prompt 1 again.

## USDT0: USDC to USDT on an EVM chain

Use `route: "usdt0-withdraw"`. Pesa first tops up the XLM the LayerZero fee needs (stage `FUNDED`, status `pending_anchor`). Then:

1. Prompt 1: the USDC to USDT0 swap. Check it with `verifyStellarSwap`.
2. Prompt 2: the OFT `send` to the destination chain. Check it with `verifyStellarOftSend`.

If the user stops after the swap, the transfer expires after 24 hours and the USDT0 stays in their wallet. Withdrawals to Ethereum through USDT0 are refused because the LayerZero fee is above the cap.

## Always pass what the user typed

The withdrawal address in `verifyStellarBurn` and `verifyStellarOftSend` must come from your UI. These two verifiers are the last check that the funds go where the user asked.


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