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

# Deposit into Stellar

> Bring USDC or USDT from an EVM chain into the user's own Stellar wallet.

A deposit always lands in the authenticated owner's Stellar wallet as native USDC. You choose the route (`cctp-deposit` for USDC, `usdt0-deposit` for USDT) and the funding mode.

| Funding mode | Use it when | The user |
| - | - | - |
| `direct` | The user connects their own EVM wallet (MetaMask, Rabby, ...) | Signs the calls and pays gas |
| `deposit_address` | The user has a Privy smart wallet in your app, or will send from an exchange | Sends funds to an address; on sponsored chains Pesa pays the gas |

## Before you start

The owner's Stellar account needs a trustline to Circle's USDC (and USDT0, for USDT). If it is missing, the first action is `stellar_trustline`. Handle it with Pesa's sponsored onboarding, which pays the reserve.

## Direct mode

<Steps>
  <Step title="Quote and create">
    ```ts theme={null}
    const quote = await pesa.quote({
      route: "cctp-deposit",
      source_chain: "eip155:8453",          // Base
      source_address: evmAddress,
      dest_address: stellarAddress,         // must be the owner
      amount_in: "20000000",                // 20 USDC
      funding_mode: "direct",
    });
    const transfer = await pesa.createTransfer(quote.id, crypto.randomUUID());
    ```

    Show the quote's `buy_amount` and `fee.details` before the user confirms. Keep the idempotency key: if the request times out, retry with the same key.
  </Step>

  <Step title="Verify and sign the calls">
    ```ts theme={null}
    const action = await pesa.getActions(transfer.id);
    if (action?.kind !== "evm_calls") throw new Error(`unexpected ${action?.kind}`);

    const calls = action.calls.map((c) => ({ ...c, value: BigInt(c.value) }));
    verifyCctpCalls(calls, {
      chain: findEvmChain("mainnet", "eip155:8453"),
      stellar: STELLAR_CCTP.mainnet,
      recipient: stellarAddress,
      amount6: 20_000_000n,
    });

    let hash;
    for (const call of calls) {
      hash = await wallet.sendTransaction({ account: evmAddress, ...call });
      await publicClient.waitForTransactionReceipt({ hash });
    }
    ```
  </Step>

  <Step title="Submit the burn">
    ```ts theme={null}
    await pesa.submit(transfer.id, { kind: "evm_tx", hash });
    ```

    Submit only after the burn is mined. The server checks the `DepositForBurn` log in the receipt.
  </Step>

  <Step title="Wait for credit">
    Poll `getTransfer` every few seconds until `status` is `completed`. Show `stage` as progress.
  </Step>
</Steps>

## Deposit address mode

With a deposit address, the transfer starts at `AWAITING_FUNDS` and the first action is `send_to_address`.

```ts theme={null}
const action = await pesa.getActions(transfer.id);
if (action?.kind === "send_to_address") {
  verifySendToAddress(action, { chain: "eip155:8453", address: smartWallet.address, amount: "20000000" });
  showAddressAndQr(action.address, action.amount);
}
```

When the address holds the amount, the transfer moves to `FUNDED` and the next action is `evm_calls`. A Privy smart wallet sends them as one batched transaction. See [React Native](/guides/react-native).

At most one open deposit-address transfer can exist per chain and address.

## USDT through USDT0

Use `route: "usdt0-deposit"`. The actions come in this order:

1. `stellar_trustline`, if the USDT0 or USDC trustline is missing.
2. `evm_calls`: an `approve` (Ethereum only) and the OFT `send`. Check them with `verifyUsdt0Calls`. In `deposit_address` mode, Pesa first tops up the LayerZero fee in native coin.
3. `stellar_tx`: the USDT0 to USDC swap on the Stellar DEX, after LayerZero delivers. Check it with `verifyStellarSwap`, sign, and submit `{ kind: "stellar_tx", signed_xdr }`.

If the price moves, the stage is `SWAP_REQUOTE` and the swap action carries a lower output. Show the new amount; signing it is the user's approval.

## Errors to handle

| Response | Meaning | What to do |
| - | - | - |
| `400` on quote | Amount outside $1 to $20, bad address, or no deposit address on that chain | Fix the input |
| `409` on create | The quote was used or expired | Get a new quote |
| `422` on create | Over the daily or outstanding limit | Show the limit |
| `422` on submit | The transaction is not mined yet, or does not match the action | Wait for the receipt; on a mismatch, do not retry |
| `status: "expired"` | Nothing irreversible happened | The funds are still in the user's wallet |


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