Seev PlusDocs
Docs
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://anchor.seevcash.com/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 = 'anchor.seevcash.com';
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
funding_methodYesPayout rail — mobile for Mobile Money (type is accepted as an alias)
amountNoAmount of USDC to send
accountNoYour Stellar public key. Defaults to the SEP-10 JWT's account
on_change_callbackNoURL to receive transaction status updates
refund_memoNoMemo to use if the withdrawal is refunded
refund_memo_typeNoType of refund_memo: text, id, or hash
quote_idNoA SEP-38 quote_id to lock the rate

funding_method (or its alias type) is required — the anchor returns 400 funding_method is required without it. Unlike deposit, account is optional on withdraw and falls back to the authenticated account.

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 = 'GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN';
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: Payout confirmation (server-side, not for wallets)

For the mobile-money corridor the anchor completes the payout automatically — a wallet integration does not call anything here. Completion is driven internally: once the off-chain GHS payout is delivered, the transaction moves pending_external → completed and your on_change_callback fires.

There is a POST /sep6/confirm-withdrawal endpoint, but it is a server-to-server callback for the off-chain payment provider — not a wallet-facing call. It is not authenticated with the SEP-10 JWT; it requires a shared-secret x-server-secret header and must never be exposed to end users. Wallets should rely on polling / on_change_callback for completion instead.

The server-only contract, for reference (the payment provider calls this, not your wallet):

curl -X POST "https://anchor.seevcash.com/sep6/confirm-withdrawal" \
  -H "x-server-secret: <shared_secret>" \
  -H "Content-Type: application/json" \
  -d '{
    "transaction_id": "txn_9a4b5c6d-7e8f-9a0b-1c2d-3e4f5a6b7c8d",
    "external_transaction_id": "MOMO-REF-12345",
    "amount_out": "100.00"
  }'

The call only succeeds when the transaction is in status pending_external; it then marks the transaction completed.

Response (200):

{
  "transaction_id": "txn_9a4b5c6d-7e8f-9a0b-1c2d-3e4f5a6b7c8d",
  "status": "completed",
  "external_transaction_id": "MOMO-REF-12345",
  "completed_at": "2026-08-05T01:18:45Z"
}

Poll transaction status

curl -X GET "https://anchor.seevcash.com/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: 'anchor.seevcash.com' });
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:GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN",
    "amount_out": "100.00",
    "amount_out_asset": "iso4217:GHS",
    "amount_fee": "0.10",
    "amount_fee_asset": "stellar:USDC:GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN",
    "started_at": "2026-08-05T01:15:00Z",
    "completed_at": "2026-08-05T01:18:45Z",
    "stellar_transaction_id": "def789abc012...",
    "more_info_url": "https://anchor.seevcash.com/sep6/transaction/more_info?id=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 = 'anchor.seevcash.com';
const anchor = wallet.anchor({ homeDomain: ANCHOR_HOME_DOMAIN });
const accountKp = Keypair.fromSecret('SCZANGBA5YHTNYVVV3C7CAZMCLXPJLNS2YFGXDNASLFTGBPFMRKCY6ML');

const USDC_ISSUER = 'GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN';
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