Skip to content

Partner interface

A spoke is whatever pays local currency. Most of them are Stellar anchors, reached over the SEPs; one of them, for now, is a licensed partner that settles on Solana. Both implement this interface, so the legs, the webhooks and reconciliation never depend on which.

type PartnerChain = "STELLAR" | "SOLANA" | "BASE";
interface RampPartner {
readonly id: string;
readonly payoutCurrencies: readonly Fiat[];
readonly collectionCurrencies: readonly Fiat[];
readonly chains: readonly PartnerChain[];
rates(currency: Fiat): Promise<Rates>;
openPayout(req: PayoutRequest): Promise<PartnerOrder>; // offramp
openCollection(req: CollectionRequest): Promise<PartnerOrder>; // onramp
getOrder(id: string): Promise<PartnerOrder>;
verifyWebhook(rawBody: string, headers: Record<string, string | undefined>, now?: Date): boolean;
parseWebhook(rawBody: string): PartnerOrder;
}

SepAnchor implements it over SEP-1/10/12/38/31 and declares chains: ["STELLAR"]. PajPartner implements it over a REST API and declares ["SOLANA"]. MockPartner implements it for tests and testnet runs, on whichever chain it is given.

Every provider is normalised to one lifecycle: open → processing → completed | failed.

PartnerOrderobject

fields

partnerstring

Which spoke this order belongs to.

idstring

The provider’s own order id.

kind'payout' | 'collection'

status'open' | 'processing' | 'completed' | 'failed'

partnerStatusstring

The provider’s own status string, kept for audit.

chain'STELLAR' | 'SOLANA' | 'BASE'

currencyFiat

usdcAmountbigint

USDC in base units.

fiatAmountbigint

Local currency in minor units.

ratenumber

Local currency per USD at pricing time.

fundingAddressstring

Payout: where to send the funding payment. One-off, valid only for this order.

fundingMemostring

Payout: the memo the receiving anchor requires. When set, the funding payment carries this rather than the Corridor Reference.

fundingMemoType'hash' | 'text' | 'id'

virtualAccountobject

Collection: the bank account the payer sends local currency to.

recipientstring

Collection: the wallet the provider delivers to.

txSignaturestring

On-chain reference of the provider’s leg, when known.

referenceHex

The Corridor Reference, echoed back where the provider carries it.

Settle in a stablecoin on a chain Corridor reaches

Stellar for an anchor. A provider that settles elsewhere needs a cross-chain hop, and today only the naira corridor has one.

One-off addresses or memos

A fresh funding address or memo per payout, and a fresh virtual account per collection, so every movement maps to exactly one order.

Echo the reference

Carry the 32-byte Corridor Reference in a field you return, and into the bank narration where you can. A statement line should trace back to the payout that caused it.

A status Corridor can poll or receive

Either a polled endpoint (what SEP-31 gives) or HMAC-signed webhooks over a timestamp and the raw body, verified in constant time within a five-minute window.

  • A PartnerRegistry picks the provider per (currency, direction, chain).
  • Exactly-once funding: the payout leg records the order and the funding transaction durably, so retries reuse them rather than paying twice.
  • Failure after funding stops at manual_review; Corridor never unwinds its own books while the money is at the provider.
  • Anchor rounding is booked as dust held for the customer, not written off.
  • Each completed order becomes an Observation (partner_fiat, ${partner}:${orderId}) for reconciliation.

Anchors first, in the order corridors open. None is signed.

Region Candidates
East Africa KES and TZS anchors; mobile-money rails
West Africa XOF anchors; a licensed naira anchor, which would move NGN onto the hub
Southern Africa ZAR and GHS anchors
Asia and the Middle East Licensed Hong Kong payment institutions and regional anchors for supplier payments
Americas USD, BRL and MXN anchors; Pix and SPEI rails