Skip to main content

Money Movement

What this covers / who it's for. The money journeys a client experiences: accounts and balances, deposits (including travel-rule holds), payouts under dual control, conversion orders, exports and statements, and the master balance. Behavior and guarantees here; execution mechanics in Providers & rails, specifics in the module READMEs.

One principle shapes everything on this page: the finance rail executes; Stables orchestrates and mirrors. Funds live at the regulated provider (Conduit in V1). Every account, balance, transaction, order, and payout you see in Stables is our local mirror of the provider's truth, kept in sync by webhooks plus a scheduled reconciliation backstop — so what you see can briefly lag reality, but never diverges from it for long.

Accounts, wallets, balances

A tenant's customer gets virtual accounts (fiat, with deposit instructions) and wallets (crypto, chain-bound), both mirrored from the rail (accounts module).

A balance is a mirror, not a ledger we compute. Balances (available / pending / frozen per asset) are projected wholesale from each provider snapshot — overwritten in full, never incremented locally. If Stables ever disagreed with the rail about a balance, the rail would win by construction.

Wallet signers and signing quorum are managed through MFA-protected ceremonies: every signer or quorum change demands step-up MFA in Stables and then completes out of band at a provider-hosted URL, where the signer proves possession of their own credentials. Stables never optimistically updates the roster — it waits for the provider to confirm.

Deposits — and the travel-rule hold

Deposits arrive as a mirrored transaction stream; statuses flow from the provider (funding module). A deposit from a registered source address simply progresses to completed.

A deposit from an unregistered address parks in a travel-rule window:

  • The affected users get a durable RFI notification (inbox + email; it is a compliance notice, so it cannot be opted out of) with the deadline.
  • Submitting the sender information is asynchronous: the API accepts it (HTTP 202) and the deposit's status updates when the provider confirms — the user watches the transaction, not a spinner.
  • If the window lapses, the deposit becomes a failed transaction carrying the failure code SENDER_INFO_TIMEOUT plus when and why it was held — "held" describes that failed deposit, not a status of its own. Compliance then takes over automatically as a side effect: a compliance case opens and a travel-rule record is created (Compliance & trust). Failed and cancelled deposits always carry the provider's failure/cancellation code and user-facing reason.

Payouts — dual control, then co-sign

A payout never moves on one person's say-so. Three independent controls stand between "I want to pay" and moved funds (payments module):

  1. Make. The maker creates the payout with every money-material field mandatory. It sits at pending_approvalthe provider has not been called; nothing can move yet.
  2. Check. A different user approves — self-approval is rejected (SELF_APPROVAL_FORBIDDEN), and the approval demands fresh step-up MFA plus a written rationale. The submission body is rebuilt only from the fields the approver reviewed; if the stored request carries anything that differs from the reviewed values, approval fails with PAYOUT_BODY_TAMPERED — and that rejected attempt still lands in the audit trail. A payout the rail rejects reverts to pending_approval rather than getting stuck.
  3. Co-sign. The rail then requires an out-of-band signature at its own hosted verification_url — outside Stables entirely, so compromising Stables alone cannot move funds.

Internal approval statuses: pending_approvalsubmittingapproved (or rejected / cancelled). Provider execution ends at completed, failed, or cancelled. The full status vocabulary is in the glossary.

Orders, registered addresses, whitelist recipients

  • Orders are on/off-ramp conversions (fiat ↔ stablecoin), executed by the rail and mirrored back; terminal statuses are succeeded / failed / cancelled.
  • Registered addresses whitelist deposit sources — the flow that keeps future deposits from those addresses out of the travel-rule hold.
  • Whitelist recipients are pre-vetted fiat payout destinations. Their bank details (account/IBAN/routing numbers) are encrypted at rest and never returned by the API.

Exports & statements — deliberately not instant

Transaction exports and statements follow the request-then-poll pattern (Async by design):

SurfaceHow it behaves
On-demand exportPOST accepts immediately (202) with a pollable request id; when it succeeds, the result is a short-lived, permission-gated download link — never a permanent URL.
Monthly statementsGenerated automatically for every eligible tenant on the 1st of each month at 02:00, as a tracked background run operators can inspect; clients discover them via the statements endpoint, which mints fresh signed links on each read.

The master balance

The treasury module answers "how much do I have, in total?" — it sums every asset's balance and converts each into one base currency with exact decimal math. Honest limits, stated by the API itself:

  • V1 supports a USD base only; any other requested base is cleanly refused.
  • The response carries rates_as_of (how fresh the conversion rates are — kept within ~20 seconds) and rates_provisional: true today — V1's rates come from a placeholder configuration, not a live market feed, and the flag says so explicitly so a UI can render the total as an estimate. An asset that cannot be priced is reported unconverted rather than silently guessed.

← Product wiki index