Documentation
¶
Overview ¶
Package stellaranchor provides the reusable Stellar-anchor primitives that any SEP-24-compliant anchor implementation can compose on top of.
Protocol surface:
- sep1.go — TOML fetch + validation of WEB_AUTH / TRANSFER_SERVER / SIGNING_KEY / NETWORK_PASSPHRASE / USDC issuer
- sep9.go — KYC Customer payload + SplitFullName helper
- sep10.go — challenge / cosign / token submit flow
- sep24.go — interactive withdraw initiation, transaction lookup, and the Status enum that callers poll on
Supporting machinery:
- client.go — top-level Client that wires Auth + Anchor + JWTCache from a single Config
- jwt_cache.go — per-memo JWT cache so SEP-10 tokens are reused until near expiry
- memo.go — ChildAccountMemo derivation (per-user 64-bit positive integer memo) for custodial-wallet anchors
- iso.go — ISO-3166 alpha-2 to alpha-3 mapping for SEP-9 country fields
- util.go — shared low-level helpers
- errors.go — protocol-level sentinels and parsed error types
Anchor-specific packages (e.g. moneygram) embed *stellaranchor.Client and layer their own REST / OAuth / FX surface on top. Nothing here knows about any specific anchor, channel, database, or notification system.
Index ¶
- Constants
- Variables
- func ChildAccountMemo(treasuryPubkey string, accountIndex uint32) int64
- func CountryISO3(iso2 string) string
- func DefaultTOMLClient() *http.Client
- func SplitFullName(fullName string) (first, last string)
- type AnchorClient
- type AnchorConfig
- type AuthClient
- type AuthConfig
- type AuthResult
- type Client
- func (c *Client) GetTransaction(ctx context.Context, childMemo int64, txID string) (*Transaction, error)
- func (c *Client) InitiateWithdrawal(ctx context.Context, childMemo int64, req WithdrawRequest) (*WithdrawResponse, error)
- func (c *Client) Token(ctx context.Context, childMemo int64) (string, error)
- func (c *Client) TreasuryAddress() string
- func (c *Client) USDCIssuer() string
- type Config
- type Currency
- type Customer
- type JWTCache
- type RefundPayment
- type Refunds
- type Status
- type TOML
- type Transaction
- type ValidateOptions
- type WithdrawRequest
- type WithdrawResponse
Constants ¶
const RefundIDTypeStellar = "stellar"
RefundIDTypeStellar marks a refund payment whose ID is a Stellar transaction hash and can therefore be verified on-ledger.
Variables ¶
var ( // ErrInvalidConfig is returned by constructors when required configuration // is missing or malformed. ErrInvalidConfig = errors.New("invalid config") // ErrTOMLFetch is returned when the SEP-1 stellar.toml cannot be retrieved. ErrTOMLFetch = errors.New("toml fetch failed") // ErrTOMLValidation is returned when a fetched TOML is missing required // fields or fails sanity checks (network passphrase mismatch, signing key // rotation, etc.). ErrTOMLValidation = errors.New("toml validation failed") // anchor endpoint. Callers should evict cached tokens and retry once. ErrUnauthorized = errors.New("unauthorized") // ErrInvalidAmount is returned when an anchor sends a decimal amount // string that cannot be parsed. ErrInvalidAmount = errors.New("invalid amount") )
Sentinel errors. Wrap with fmt.Errorf("stellaranchor: ...: %w", err) at call sites. Anchor-specific consumers (e.g. moneygram, future SDKs) should re- wrap with their own prefix so log readers can tell which anchor failed.
Functions ¶
func ChildAccountMemo ¶
ChildAccountMemo derives a deterministic positive int64 memo from the custodial treasury's public key and a user's BIP-44 Stellar derivation index (`accounts.account_index` — assigned by the existing user-account allocator; not minted here). The result is used as the SEP-10 memo so that a single treasury Stellar account can host many users without their JWTs or in-flight SEP-24 transactions cross-contaminating.
This is NOT the SEP-24 withdraw memo. The withdraw memo is supplied by the anchor on the SEP-24 transaction response (Transaction.WithdrawMemo) — that value is what we pass as the Stellar payment memo when sending USDC to the anchor, and what MG sends back on refund. The refund-ingest worker matches inbound payments against `loans.ramp_withdraw_memo`, not against this hash. See §13 of docs/moneygram-integration.md.
Properties:
- Deterministic across runs and processes.
- Treasury-scoped: testnet/preview/mainnet treasuries produce disjoint memo spaces.
- Fits Stellar MEMO_ID and MoneyGram's "≤64-bit positive integer" constraint.
Collision probability at 10^9 users on a 63-bit space (the high bit is masked off to keep the value positive) is ≈ 5×10^-11. A collision causes JWT scope mis-routing — log loudly and resolve manually if observed.
func CountryISO3 ¶
CountryISO3 returns the ISO-3166-1 alpha-3 code for the given alpha-2 code, or "" if the alpha-2 is not in the map. Callers should omit the SEP-9 address_country_code field entirely when this returns "" — sending a wrong or guessed code is worse than sending none.
func DefaultTOMLClient ¶
DefaultTOMLClient returns an http.Client suitable for fetching SEP-1 TOMLs: short timeout, no redirects to other hosts. Use this if you don't already have an http.Client to inject.
func SplitFullName ¶
SplitFullName splits a full-name string on the first whitespace into (first, last). One-token names become (token, ""); empty input yields ("", ""). Used to map a single stored full_name field onto SEP-9 first_name / last_name keys.
Types ¶
type AnchorClient ¶
type AnchorClient struct {
// contains filtered or unexported fields
}
AnchorClient implements the subset of SEP-24 needed for cash-pickup off-ramp: /transactions/withdraw/interactive and /transaction.
func NewAnchorClient ¶
func NewAnchorClient(cfg AnchorConfig, httpClient *http.Client, logger *slog.Logger) (*AnchorClient, error)
NewAnchorClient validates configuration. httpClient may be nil; logger may be nil.
func (*AnchorClient) GetTransaction ¶
func (c *AnchorClient) GetTransaction(ctx context.Context, jwt, txID string) (*Transaction, error)
GetTransaction calls GET /transaction?id={txID} with the given JWT.
func (*AnchorClient) InitiateWithdrawal ¶
func (c *AnchorClient) InitiateWithdrawal(ctx context.Context, jwt string, req WithdrawRequest) (*WithdrawResponse, error)
InitiateWithdrawal calls POST /transactions/withdraw/interactive with the given JWT (from AuthClient/JWTCache), the USDC amount, and any prefilled SEP-9 customer fields. Returns the interactive URL and MG transaction ID.
type AnchorConfig ¶
type AnchorConfig struct {
TransferServerURL string // from TOML's TRANSFER_SERVER_SEP0024
}
AnchorConfig configures the SEP-24 client.
type AuthClient ¶
type AuthClient struct {
// contains filtered or unexported fields
}
AuthClient implements the SEP-10 challenge/co-sign/token flow against a MoneyGram anchor in custodial mode (single treasury Stellar account, per- user int64 memo).
func NewAuthClient ¶
func NewAuthClient(cfg AuthConfig, httpClient *http.Client, logger *slog.Logger) (*AuthClient, error)
NewAuthClient validates the config and constructs a client. httpClient may be nil; logger may be nil.
func (*AuthClient) Authenticate ¶
func (c *AuthClient) Authenticate(ctx context.Context, childMemo int64) (AuthResult, error)
Authenticate runs the SEP-10 challenge/co-sign/submit flow for a given child-memo and returns the JWT plus its expiry. Callers should cache the result via JWTCache rather than calling this on every SEP-24 request.
func (*AuthClient) TreasuryAddress ¶
func (c *AuthClient) TreasuryAddress() string
TreasuryAddress returns the custodial G... account ID derived from the configured treasury secret. Useful for diagnostics and Stellar payments.
type AuthConfig ¶
type AuthConfig struct {
WebAuthEndpoint string // from TOML
ServerSigningKey string // from TOML — used to verify the challenge signature
NetworkPassphrase string // from TOML, e.g. "Public Global Stellar Network ; September 2015"
HomeDomain string // e.g. "stellar.moneygram.com"
WebAuthDomain string // typically equal to HomeDomain unless MG runs the auth server on a different host
// TreasurySecret is the S... seed of the custodial treasury account.
// Held in memory only — never logged. The corresponding G... address
// becomes the SEP-10 `account` parameter.
TreasurySecret string
// FallbackTokenTTL is used when the issued JWT has no `exp` claim.
// Defaults to 23h to stay safely under MG's typical 24h validity.
FallbackTokenTTL time.Duration
// HTTPTimeout caps the duration of any single SEP-10 HTTP exchange.
// Defaults to 15s.
HTTPTimeout time.Duration
}
AuthConfig configures the SEP-10 client.
type AuthResult ¶
AuthResult is the output of a successful SEP-10 round trip.
type Client ¶
type Client struct {
Auth *AuthClient
Anchor *AnchorClient
JWTCache *JWTCache
// contains filtered or unexported fields
}
Client is the top-level Stellar anchor handle: SEP-10 auth + SEP-24 anchor + JWT cache, glued together. Anchor-specific SDKs (e.g. moneygram) compose this client with their own REST/OAuth/FX extras.
func New ¶
New wires the sub-clients. Returns an error if AuthClient or AnchorClient validation fails.
func (*Client) GetTransaction ¶
func (c *Client) GetTransaction(ctx context.Context, childMemo int64, txID string) (*Transaction, error)
GetTransaction wraps Anchor.GetTransaction with the same JWT cache and 401-retry semantics as InitiateWithdrawal.
func (*Client) InitiateWithdrawal ¶
func (c *Client) InitiateWithdrawal(ctx context.Context, childMemo int64, req WithdrawRequest) (*WithdrawResponse, error)
InitiateWithdrawal fetches a JWT for childMemo and calls Anchor.InitiateWithdrawal. On a 401 it evicts the cached token and retries once.
func (*Client) Token ¶
Token returns a cached SEP-10 JWT for the given child memo, refreshing when stale.
func (*Client) TreasuryAddress ¶
TreasuryAddress returns the custodial G... account ID.
func (*Client) USDCIssuer ¶
USDCIssuer returns the USDC issuer pubkey from Config.
type Config ¶
type Config struct {
HomeDomain string // e.g. "stellar.moneygram.com"
WebAuthEndpoint string // SEP-1 WEB_AUTH_ENDPOINT
TransferServerURL string // SEP-1 TRANSFER_SERVER_SEP0024
ServerSigningKey string // SEP-1 SIGNING_KEY
NetworkPassphrase string // SEP-1 NETWORK_PASSPHRASE
USDCIssuer string // expected USDC issuer (validated against TOML upstream)
TreasurySecret string // S... seed of the custodial treasury account
HTTPClient *http.Client
Logger *slog.Logger
// JWTSafetyMargin tunes the JWT cache. Defaults to 60s.
JWTSafetyMargin time.Duration
}
Config bundles the SEP-1 derived values plus the custodial treasury secret needed to talk to a Stellar anchor. Fetch the TOML at startup via FetchTOML, validate it via TOML.Validate, then pass the relevant fields here — surfacing rotation as an explicit re-Validate at boot rather than silent runtime drift.
type Currency ¶
type Currency struct {
Code string `toml:"code"`
Issuer string `toml:"issuer"`
IsAssetAnchored bool `toml:"is_asset_anchored"`
AnchorAssetType string `toml:"anchor_asset_type"`
AnchorAsset string `toml:"anchor_asset"`
Name string `toml:"name"`
Desc string `toml:"desc"`
}
Currency is one entry from the SEP-1 [[CURRENCIES]] array.
type Customer ¶
type Customer struct {
FirstName string `json:"first_name,omitempty"`
LastName string `json:"last_name,omitempty"`
MobileNumber string `json:"mobile_number,omitempty"` // E.164, e.g. "+254712345678"
BirthDate string `json:"birth_date,omitempty"` // YYYY-MM-DD
Address string `json:"address,omitempty"` // line1[, line2]
City string `json:"city,omitempty"`
PostalCode string `json:"postal_code,omitempty"`
AddressCountryCode string `json:"address_country_code,omitempty"` // ISO-3, e.g. "KEN"
}
Customer carries the subset of SEP-9 fields MoneyGram honours when passed at SEP-24 withdrawal initiation. MoneyGram silently ignores SEP-9 keys it does not support (email_address, id_*, occupation, etc.).
All fields are optional from MoneyGram's perspective; populate as much as you can to reduce friction in MoneyGram's interactive webview.
type JWTCache ¶
type JWTCache struct {
// contains filtered or unexported fields
}
JWTCache is an in-memory store of SEP-10 JWTs keyed by child-memo. Each entry is held until its parsed `exp` minus SafetyMargin. Safe for concurrent use.
func NewJWTCache ¶
func NewJWTCache(auth *AuthClient, safetyMargin time.Duration) *JWTCache
NewJWTCache wraps an AuthClient with caching. SafetyMargin is how long before nominal expiry the cache treats a token as stale and refreshes; defaults to 60s when zero.
func (*JWTCache) Get ¶
Get returns a valid JWT for the given child-memo, calling AuthClient only when the cache is empty or stale.
func (*JWTCache) Invalidate ¶
Invalidate evicts the cached token for childMemo. Use after a 401 from a SEP-24 endpoint — the next Get call will fetch a fresh token.
func (*JWTCache) InvalidateAll ¶
func (c *JWTCache) InvalidateAll()
InvalidateAll clears the entire cache. Useful when the SEP-1 SIGNING_KEY has rotated and every cached token is now invalid.
type RefundPayment ¶
type RefundPayment struct {
// ID is the Stellar transaction hash when IDType is "stellar", or an
// anchor-internal identifier when it is "external".
ID string `json:"id"`
IDType string `json:"id_type"`
Amount string `json:"amount"`
Fee string `json:"fee"`
}
RefundPayment is one individual refund payment within a Refunds object.
type Refunds ¶
type Refunds struct {
AmountRefunded string `json:"amount_refunded"`
AmountFee string `json:"amount_fee"`
Payments []RefundPayment `json:"payments"`
}
Refunds describes money returned to the user for a transaction. For a withdrawal this is the anchor sending our USDC back on Stellar.
Amounts are denominated in amount_in_asset (USDC for our withdrawals) and are the anchor's own account of what it did. They are not reliable enough to settle against — read NetRefundedStroops before using them.
func (*Refunds) NetRefundedStroops ¶
NetRefundedStroops is the anchor's stated refund total in stroops, taken as AmountRefunded less AmountFee.
Treat this as a cross-check, never as the amount to settle against. It is only as good as the anchor's bookkeeping, and MoneyGram's does not hold: it reports AmountRefunded already net of the withdrawal fee and then repeats that fee in AmountFee, so this subtracts it twice and understates what MG actually sent. Take the figure that moves money from the ledger.
Parsing is exact — SEP-24 amounts are decimal strings and float conversion would lose stroops on values like "49.9999999".
func (*Refunds) StellarPayments ¶
func (r *Refunds) StellarPayments() []RefundPayment
StellarPayments returns the refund payments settled on Stellar, whose IDs are transaction hashes.
An empty IDType counts as Stellar: the field is required by SEP-24 but anchors omit it in practice, and for a withdrawal refund the funds can only come back over Stellar.
type Status ¶
type Status string
Status is the SEP-24 transaction status.
const ( StatusIncomplete Status = "incomplete" StatusPendingUserTransferStart Status = "pending_user_transfer_start" StatusPendingUserTransferComplete Status = "pending_user_transfer_complete" StatusPendingExternal Status = "pending_external" StatusPendingAnchor Status = "pending_anchor" StatusOnHold Status = "on_hold" StatusPendingStellar Status = "pending_stellar" StatusPendingTrust Status = "pending_trust" StatusPendingUser Status = "pending_user" StatusCompleted Status = "completed" StatusRefunded Status = "refunded" StatusExpired Status = "expired" StatusNoMarket Status = "no_market" StatusTooSmall Status = "too_small" StatusTooLarge Status = "too_large" StatusError Status = "error" )
SEP-24 transaction states. The full set documented by [SEP-0024 protocol](https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0024.md) in the "Shared fields for both deposits and withdrawals" section.
type TOML ¶
type TOML struct {
Version string `toml:"VERSION"`
NetworkPassphrase string `toml:"NETWORK_PASSPHRASE"`
SigningKey string `toml:"SIGNING_KEY"`
WebAuthEndpoint string `toml:"WEB_AUTH_ENDPOINT"`
TransferServerSEP24 string `toml:"TRANSFER_SERVER_SEP0024"`
Accounts []string `toml:"ACCOUNTS"`
Currencies []Currency `toml:"CURRENCIES"`
}
TOML is the parsed subset of a Stellar SEP-1 stellar.toml that matters for the MoneyGram Ramps integration. Fields not used by this SDK are not captured — extend if your wallet needs more.
func FetchTOML ¶
FetchTOML retrieves and parses the SEP-1 stellar.toml served at https://{homeDomain}/.well-known/stellar.toml. It does NOT validate semantically — call TOML.Validate to enforce environment-specific expectations (passphrase, signing key, USDC issuer).
httpClient may be nil; http.DefaultClient is used in that case. The response is capped at 64 KiB — well above the size of any well-formed stellar.toml — to avoid pathological responses exhausting memory.
func (*TOML) AssetIssuer ¶
AssetIssuer returns the issuer pubkey for the named currency code, or "" if the code is not in the [[CURRENCIES]] list.
func (*TOML) Validate ¶
func (t *TOML) Validate(opts ValidateOptions) error
Validate returns nil if the TOML carries every field needed to drive a SEP-10 + SEP-24 withdrawal flow against MoneyGram, and matches any expectations supplied in opts.
type Transaction ¶
type Transaction struct {
ID string `json:"id"`
Kind string `json:"kind"` // "deposit" | "withdrawal"
Status Status `json:"status"`
StatusEta int64 `json:"status_eta,omitempty"`
AmountIn string `json:"amount_in,omitempty"`
AmountInAsset string `json:"amount_in_asset,omitempty"`
AmountOut string `json:"amount_out,omitempty"`
AmountOutAsset string `json:"amount_out_asset,omitempty"`
AmountFee string `json:"amount_fee,omitempty"`
AmountFeeAsset string `json:"amount_fee_asset,omitempty"`
StartedAt string `json:"started_at,omitempty"`
CompletedAt string `json:"completed_at,omitempty"`
StellarTransactionID string `json:"stellar_transaction_id,omitempty"`
ExternalTransactionID string `json:"external_transaction_id,omitempty"` // cash-pickup reference
WithdrawAnchorAccount string `json:"withdraw_anchor_account,omitempty"`
WithdrawMemo string `json:"withdraw_memo,omitempty"`
WithdrawMemoType string `json:"withdraw_memo_type,omitempty"` // typically "id"
MoreInfoURL string `json:"more_info_url,omitempty"`
Message string `json:"message,omitempty"`
// Refunded is the deprecated SEP-24 boolean, kept because some anchors
// still emit it. Refunds is the authoritative field.
Refunded bool `json:"refunded,omitempty"`
Refunds *Refunds `json:"refunds,omitempty"`
}
Transaction is the SEP-24 transaction object returned from /transaction. Field set is intentionally a superset of what we need to drive the off- ramp state machine — some fields are populated only at certain statuses.
type ValidateOptions ¶
type ValidateOptions struct {
ExpectedNetworkPassphrase string
ExpectedSigningKey string // optional; "" disables the check
ExpectedUSDCIssuer string // optional; "" disables the check
}
ValidateOptions configures TOML.Validate. Set ExpectedSigningKey when you have committed to a specific MG signing key for the environment — a mismatch should be a hard fail rather than silent drift.
type WithdrawRequest ¶
type WithdrawRequest struct {
AssetCode string // "USDC"
Amount string // decimal USD, e.g. "50.00" — required for custodial wallets
Lang string // ISO-639-1, defaults to "en"
Account string // funds wallet G... address — the withdrawal payment source
Customer Customer // optional SEP-9 prefill
}
WithdrawRequest is the body of a SEP-24 /transactions/withdraw/interactive call. Customer fields are SEP-9 — see Customer.
type WithdrawResponse ¶
type WithdrawResponse struct {
Type string `json:"type"` // typically "interactive_customer_info_needed"
URL string `json:"url"` // interactive webview URL — SMS this to the user
ID string `json:"id"` // MoneyGram's transaction ID — persist and poll
}
WithdrawResponse is what MoneyGram returns from /transactions/withdraw/interactive.