How payments work on SDP

Wallets, tokens, server-side signing, fees, and the data model that backs SDP payments.

SDP payments move value between Solana accounts: a custody wallet sends tokens to a destination address and SDP records the resulting transfer. This page introduces the building blocks — wallets, tokens, execution, fees, and the data model — that the rest of the section assumes.

Custody wallets

Every SDP payment uses a custody wallet in your organization, optionally scoped to a project. A custody provider holds its key; SDP exposes its Solana address and routes signing to that provider. The wallet signs outbound transfers and receives inbound funds. Limit what an API key may do with it through grants and wallet scope.

You provision custody wallets up front (see Set Up Wallets). Transfer requests use the exact SDP Wallet id (cwlt_…) as sourceCustodyWalletId; responses expose it as custodyWalletId and retain the provider's identifier separately as providerWalletId evidence. The destination remains the recipient's on-chain Solana address.

Tokens and token accounts

For SPL tokens, POST /v1/payments/transfers takes the on-chain mint address, not a symbol such as USDC. Use the literal SOL for native SOL.

The token filter on GET /v1/payments/transfers matches the stored Transfer.token exactly. API-created transfers store the mint you supplied (or SOL); indexed transfers may store a resolved symbol. Filter with the label returned in the response, or filter client-side. Balance responses attempt symbol resolution and return the symbol when known.

Amounts on the wire are UI-unit decimal strings — "100.00" means 100 tokens, regardless of the mint's decimal count. This applies to every amount SDP accepts. SDP handles the smallest-unit conversion when it builds the on-chain transaction. The one exception is wallet-balance responses, which return both a raw amount (smallest unit) and a uiAmount (decimal string) so balance consumers can pick whichever fits.

Transfer execution

Outbound wallet transfers are server-executed through POST /v1/payments/transfers. SDP builds the transaction, signs with the source wallet's custody key, submits it on the configured Solana cluster, and records the resulting transfer lifecycle.

The rest of this section assumes transfers are submitted by SDP from a custody wallet you control.

Fees and sponsorship

SDP pays the network fees and recipient token-account rent on the transfers it signs from Privy wallets, so those wallets need the tokens they send, with no SOL for fees. Sponsorship limits apply; an unavailable sponsor refuses the transfer rather than charging the source wallet. Fireblocks submits its own transfers, whose fees SDP does not sponsor.

The transfer data model

Every successful or attempted transfer creates a Transfer record. The fields that matter for most flows:

FieldMeaning
idSDP-internal transfer identifier (use this in API URLs).
statusWallet transfers: processing, confirmed, finalized, failed. See Verifying a payment.
directioninbound (someone paid you) or outbound (you paid someone).
source, destinationOn-chain addresses.
counterpartyIdSet when the transfer is tied to a counterparty, for example a transfer batch recipient or a transfer sent with counterpartyAccountId.
token, amountWhat moved and how much. amount is a UI-unit decimal string (e.g. "100.00").
memoOptional UTF-8 string, up to 256 chars. See Payment with memo.
signatureSolana transaction signature once confirmed. Unique across SDP — the natural dedup key.
slot, blockTime, feeOn-chain settlement details, populated as the transaction lands.
errorSet if the transfer failed; the rest of the record explains what reached the network.
riskOptional risk-score metadata, when a risk provider is configured.

Outbound wallet transfers start at processing and move to confirmed → finalized. Inbound transfers discovered on-chain surface directly at confirmed. See Verifying a payment for the full state table.

What SDP does and does not give you

SDP exposes everything you need to send, track, and reconcile payments at the transfer level: status polling, filtered lists, wallet-level balances, exact-wallet Idempotency-Key replay, and a unique on-chain signature once populated (see Basic payment → Deduplication). It does not ship higher-level commerce primitives such as a checkout session or invoice object. The Accept payments section shows how to build those abstractions on top of the transfer model.

Is this page helpful?