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
- Valid SEP-10 JWT token (Authentication)
- KYC status is
ACCEPTED(KYC) - 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
| Parameter | Required | Description |
|---|---|---|
asset_code | Yes | Asset to withdraw — USDC |
funding_method | Yes | Payout rail — mobile for Mobile Money (type is accepted as an alias) |
amount | No | Amount of USDC to send |
account | No | Your Stellar public key. Defaults to the SEP-10 JWT's account |
on_change_callback | No | URL to receive transaction status updates |
refund_memo | No | Memo to use if the withdrawal is refunded |
refund_memo_type | No | Type of refund_memo: text, id, or hash |
quote_id | No | A 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:
- Validates the payment amount and memo
- Looks up the associated withdrawal transaction
- Calculates the GHS equivalent at the current rate
- 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
| Status | Error | Meaning |
|---|---|---|
| 400 | invalid_amount | Amount outside min/max range |
| 400 | invalid_asset_code | Asset not supported |
| 403 | kyc_required | KYC not yet approved |
| 404 | transaction_not_found | Transaction ID does not exist |
| 500 | internal_error | Anchor-side error — retry later |