SSeev PlusDocs
Stellar Anchor

SEP-6: Withdraw (USDC → GHS)

Off-ramp USDC to Ghana Cedis via Mobile Money using the SEP-6 withdrawal API.

A withdrawal converts USDC on the Stellar network to GHS (Ghana Cedis) paid out to a Mobile Money wallet. The customer sends USDC to the anchor's Stellar address and receives GHS in their mobile wallet.

Flow overview

Client                    Anchor                    Stellar          MoMo Gateway
  │                         │                         │                │
  │  GET /sep6/withdraw     │                         │                │
  │────────────────────────►│                         │                │
  │  { account_id, memo }   │                         │                │
  │◄────────────────────────│                         │                │
  │                         │                         │                │
  │  Send USDC + memo       │                         │                │
  │────────────────────────────────────────────────►►│                │
  │                         │                         │                │
  │                         │  PaymentMonitor detects │                │
  │                         │◄────────────────────────│                │
  │                         │                         │                │
  │                         │  Disburse GHS           │                │
  │                         │────────────────────────────────────────►►│
  │                         │                         │                │
  │  on_change_callback     │                         │                │
  │◄────────────────────────│                         │                │

Prerequisites

  1. Valid SEP-10 JWT token (Authentication)
  2. KYC status is ACCEPTED (KYC)
  3. USDC balance in your Stellar account

Step 1: Initiate a withdrawal

curl -X GET "https://<your-anchor-domain>/sep6/withdraw?\
asset_code=USDC&\
amount=6.5&\
account=GCEXAMPLE4KEYPAIR7HERE2REPLACE5WITH5YOUR5ACTUAL5STELLAR5KEY&\
on_change_callback=https://yourapp.com/webhooks/anchor" \
  -H "Authorization: Bearer <sep10_jwt_token>"
import { Wallet, Keypair } from '@stellar/typescript-wallet-sdk';

const wallet = Wallet.TestNet();
const ANCHOR_HOME_DOMAIN = '<your-anchor-domain>';
const anchor = wallet.anchor({ homeDomain: ANCHOR_HOME_DOMAIN });
const accountKp = Keypair.fromSecret('SCZANGBA5YHTNYVVV3C7CAZMCLXPJLNS2YFGXDNASLFTGBPFMRKCY6ML');

// Authenticate
const sep10 = await anchor.sep10();
const authToken = await sep10.authenticate({ accountKp });

// Initiate withdrawal
const sep6 = anchor.sep6();
const withdrawal = await sep6.withdraw({
  authToken,
  params: {
    asset_code: 'USDC',
    account: accountKp.publicKey,
    amount: '6.5',
    funding_method: 'mobile',
  },
});

console.log("Withdrawal ID:", withdrawal.id);
console.log("Send USDC to:", withdrawal.account_id);
console.log("Memo:", withdrawal.memo);

Parameters

ParameterRequiredDescription
asset_codeYesAsset to withdraw — USDC
amountNoAmount of USDC to send
accountYesYour Stellar public key
on_change_callbackNoURL to receive transaction status updates

Response:

{
  "id": "txn_9a4b5c6d-7e8f-9a0b-1c2d-3e4f5a6b7c8d",
  "account_id": "GANCHOR5RECEIVING5ADDRESS5HERE2REPLACE5WITH5ACTUAL5ANCHOR5KEY",
  "memo_type": "text",
  "memo": "SEEV-WDR-9A4B5C",
  "eta": 300,
  "min_amount": "1",
  "max_amount": "10000"
}

The response includes account_id (the anchor's Stellar address) and a memo. You must include this exact memo when sending USDC — it's how the anchor identifies your transaction.

Step 2: Send USDC to the anchor

Send the USDC payment to the anchor's Stellar address with the provided memo.

import { Wallet, Keypair } from '@stellar/typescript-wallet-sdk';
import {
  Asset,
  TransactionBuilder,
  Networks,
  Horizon,
  Operation,
  Memo,
} from '@stellar/stellar-sdk';

const USDC_ISSUER = 'GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5';
const server = new Horizon.Server('https://horizon-testnet.stellar.org');

async function sendUsdcToAnchor(
  accountKp: Keypair,
  anchorAddress: string,
  memo: string,
  amount: string
) {
  const account = await server.loadAccount(accountKp.publicKey);
  const usdc = new Asset('USDC', USDC_ISSUER);

  const tx = new TransactionBuilder(account, {
    fee: '100',
    networkPassphrase: Networks.TESTNET,
  })
    .addOperation(
      Operation.payment({
        destination: anchorAddress,
        asset: usdc,
        amount, // e.g. "6.5"
      })
    )
    .addMemo(Memo.text(memo))
    .setTimeout(30)
    .build();

  tx.sign(accountKp);
  const result = await server.submitTransaction(tx);
  console.log("Payment submitted:", result.hash);
  return result;
}

Step 3: PaymentMonitor detects payment

Once the anchor's PaymentMonitor service detects the incoming USDC payment with the matching memo, it:

  1. Validates the payment amount and memo
  2. Looks up the associated withdrawal transaction
  3. Calculates the GHS equivalent at the current rate
  4. Initiates the Mobile Money disbursement

This happens automatically — no additional API calls are needed from your side.

Step 4: Auto payout to Mobile Money

The anchor disburses GHS to the mobile number registered in the customer's KYC data. The payout is automatic once the USDC payment is confirmed.

Payout timing:

  • MTN Mobile Money: 1–3 minutes
  • Vodafone Cash: 1–3 minutes
  • AirtelTigo Money: 1–5 minutes

Step 5: Confirm withdrawal (optional)

In some cases, the anchor may require explicit confirmation before disbursing. Check the transaction status — if it's pending_user_transfer_complete, confirm the withdrawal:

curl -X POST "https://<your-anchor-domain>/sep6/confirm-withdrawal" \
  -H "Authorization: Bearer <sep10_jwt_token>" \
  -H "Content-Type: application/json" \
  -d '{
    "id": "txn_9a4b5c6d-7e8f-9a0b-1c2d-3e4f5a6b7c8d"
  }'

Response (200):

{
  "transaction": {
    "id": "txn_9a4b5c6d-7e8f-9a0b-1c2d-3e4f5a6b7c8d",
    "status": "pending_anchor"
  }
}

Poll transaction status

curl -X GET "https://<your-anchor-domain>/sep6/transaction?id=txn_9a4b5c6d-7e8f-9a0b-1c2d-3e4f5a6b7c8d" \
  -H "Authorization: Bearer <sep10_jwt_token>"
import { Wallet, Keypair } from '@stellar/typescript-wallet-sdk';

const wallet = Wallet.TestNet();
const anchor = wallet.anchor({ homeDomain: '<your-anchor-domain>' });
const accountKp = Keypair.fromSecret('SCZANGBA5YHTNYVVV3C7CAZMCLXPJLNS2YFGXDNASLFTGBPFMRKCY6ML');

const sep10 = await anchor.sep10();
const authToken = await sep10.authenticate({ accountKp });

// Use the watcher for real-time withdrawal tracking
const sep6 = anchor.sep6();
const watcher = sep6.watcher();

const { stop, refresh } = watcher.watchOneTransaction({
  authToken,
  assetCode: 'USDC',
  id: 'txn_9a4b5c6d-7e8f-9a0b-1c2d-3e4f5a6b7c8d',
  onMessage: (txn) => console.log(`Status: ${txn.status}`),
  onSuccess: (txn) => {
    console.log(`Withdrawal complete! ${txn.amount_out} GHS sent to MoMo`);
    stop();
  },
  onError: (err) => {
    console.error('Withdrawal error:', err);
    stop();
  },
});

// Or get a single transaction directly
const { transaction } = await sep6.getTransactionBy({
  authToken,
  id: 'txn_9a4b5c6d-7e8f-9a0b-1c2d-3e4f5a6b7c8d',
});
console.log(`Status: ${transaction.status}`);

Response (completed):

{
  "transaction": {
    "id": "txn_9a4b5c6d-7e8f-9a0b-1c2d-3e4f5a6b7c8d",
    "kind": "withdrawal",
    "status": "completed",
    "amount_in": "6.50",
    "amount_in_asset": "stellar:USDC:GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5",
    "amount_out": "100.00",
    "amount_out_asset": "iso4217:GHS",
    "amount_fee": "0.10",
    "amount_fee_asset": "stellar:USDC:GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5",
    "started_at": "2026-08-05T01:15:00Z",
    "completed_at": "2026-08-05T01:18:45Z",
    "stellar_transaction_id": "def789abc012...",
    "more_info_url": "https://<your-anchor-domain>/more-info/txn_9a4b5c6d"
  }
}

Withdrawal status flow

pending_user_transfer_start  →  Waiting for you to send USDC to anchor
pending_anchor               →  Anchor received USDC, processing GHS payout
pending_external             →  GHS payout submitted to MoMo network
completed                    →  GHS delivered to customer's mobile wallet
error                        →  Something went wrong (check message)

Full TypeScript example

import { Wallet, Keypair } from '@stellar/typescript-wallet-sdk';
import {
  Asset,
  TransactionBuilder,
  Networks,
  Horizon,
  Operation,
  Memo,
} from '@stellar/stellar-sdk';

const wallet = Wallet.TestNet();
const ANCHOR_HOME_DOMAIN = '<your-anchor-domain>';
const anchor = wallet.anchor({ homeDomain: ANCHOR_HOME_DOMAIN });
const accountKp = Keypair.fromSecret('SCZANGBA5YHTNYVVV3C7CAZMCLXPJLNS2YFGXDNASLFTGBPFMRKCY6ML');

const USDC_ISSUER = 'GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5';
const server = new Horizon.Server('https://horizon-testnet.stellar.org');

// Full withdrawal flow
async function withdraw() {
  // Step 1: Authenticate
  const sep10 = await anchor.sep10();
  const authToken = await sep10.authenticate({ accountKp });

  // Step 2: Initiate withdrawal
  const sep6 = anchor.sep6();
  const withdrawal = await sep6.withdraw({
    authToken,
    params: {
      asset_code: 'USDC',
      account: accountKp.publicKey,
      amount: '6.5',
      funding_method: 'mobile',
    },
  });

  console.log("Withdrawal initiated:", withdrawal.id);
  console.log("Send USDC to:", withdrawal.account_id);
  console.log("With memo:", withdrawal.memo);

  // Step 3: Send USDC to anchor
  const account = await server.loadAccount(accountKp.publicKey);
  const usdc = new Asset('USDC', USDC_ISSUER);

  const tx = new TransactionBuilder(account, {
    fee: '100',
    networkPassphrase: Networks.TESTNET,
  })
    .addOperation(
      Operation.payment({
        destination: withdrawal.account_id,
        asset: usdc,
        amount: '6.5',
      })
    )
    .addMemo(Memo.text(withdrawal.memo))
    .setTimeout(30)
    .build();

  tx.sign(accountKp);
  await server.submitTransaction(tx);

  // Step 4: Watch for completion (GHS payout)
  const watcher = sep6.watcher();
  const { stop } = watcher.watchOneTransaction({
    authToken,
    assetCode: 'USDC',
    id: withdrawal.id,
    onMessage: (txn) => console.log(`Status: ${txn.status}`),
    onSuccess: (txn) => {
      console.log(`Withdrawal complete! ${txn.amount_out} GHS sent to MoMo`);
      stop();
    },
    onError: (err) => {
      console.error('Withdrawal error:', err);
      stop();
    },
  });
}

await withdraw();

Always include the exact memo returned by the withdraw endpoint. Payments without the correct memo cannot be automatically matched and will require manual intervention.

Error responses

StatusErrorMeaning
400invalid_amountAmount outside min/max range
400invalid_asset_codeAsset not supported
403kyc_requiredKYC not yet approved
404transaction_not_foundTransaction ID does not exist
500internal_errorAnchor-side error — retry later

On this page