Core ConceptsLedger (double-entry)

Ledger (double-entry)

Every movement of funds is a set of balanced entries, and balances are computed from the ledger. Available and reserved, historical balances, idempotency, and immutability.

In V3 Custody, money isn't a number that gets overwritten; it's a ledger. Every movement of funds is recorded with double-entry accounting, and balances are computed from those records. So answering "how much and why" takes a query, not an investigation.

Entries that always balance

Each ledger record (entry) consists of postings, and their sum is zero. One side touches your address's account, and the other touches the counterpart of the movement (the on-chain side or an adjustment). A zero sum guarantees that funds never appear or disappear; they only move.

Schematically, an outgoing transfer of 100 USDT goes through two entries. First, a reserve within your address:

AccountPosting
your address · available−100
your address · reserved+100
Total0

Then, execution after signing:

AccountPosting
your address · reserved−100
counterpart (on-chain)+100
Total0

The ledger is read-only and immutable:

  • GET /api/v1/vaults/{code}/ledger/entries returns immutable entries with their postings (postings sum to 0). Sorted by postedAt (newest first), with keyset pagination and the from / to / token filters. A regular member sees only entries that touch their own addresses; an administrator sees the entire Vault.

Balances are computed, not stored

A balance is a rollup of the ledger, not a separately stored number. It's updated atomically with the postings, so it can never drift from them. Every balance is split into two states:

StateWhat it isWhen it changes
availablefree to spend↓ when a transfer is created; ↑ when a reserve is returned or funds arrive
reservedlocked by pending transfers, but still yours↑ when a transfer is created; ↓ on execution or return
"owned"available + reservedthe sum of the two states
  • GET /api/v1/vaults/{code}/balances returns balances for a Vault, a user, or an address, with the summary, addressId, and token parameters. The response contains per-token totals (totals).

Reserved: funds "in transit," but still yours

When a transfer is created, the amount is first reserved, even before the transaction is sent to the network. The reserve protects against double spending: the same funds can't be sent twice.

What happens to the reserve depends on the Policy Engine. If the transfer is allowed and confirmed, the reserve is debited. If it's queued for approval, the amount waits in reserved. If it's cancelled or rejected, the amount returns to available. The funds remain yours the entire time.

How to read a balance

  • Current: GET /api/v1/vaults/{code}/balances (available and reserved right now).
  • As of a date: GET /api/v1/vaults/{code}/balances/as-of?at=... (a past position, for statements and reconciliation).
  • Summary or per address: summary=true (per-token totals only) or addressId (a single address).
  • What you can spend: go by available; available + reserved is the total that's yours.

Balances at any point in the past

Because the ledger is immutable, a position can be reconstructed for any date from the postings recorded before that point:

  • GET /api/v1/vaults/{code}/balances/as-of?at=... returns the balance as of at, calculated from postings with postedAt <= at. The past never changes retroactively. The "owned" balance = available + reserved; a regular member sees only their own addresses.

This is exactly what you need for end-of-period statements and reconciliation: the answer to "how much was there on March 31" is always the same.

Idempotency: one request, one payment

Transfers accept an idempotencyKey. Retrying a request with the same key returns the operation that was already created instead of creating a new one. That makes retries after network failures safe, with no risk of a double payment. For details, see Idempotency.

Nothing is rewritten, only appended

Ledger entries are never edited or deleted. A mistake is corrected with a new operation linked to the original, so the full history is preserved:

Relation typeMeaning
refunda return of funds (partial refunds use relatedAmountRaw)
reversala reversal or cancellation of the original operation
correctiona fix for an error in the original operation
replacementreplacement of the operation with a new one
  • POST /api/v1/vaults/{code}/operation-relations creates an explicit link between operations. An opposite payment is not automatically treated as a refund; the link must be set explicitly. The exact linking rules are in the API Reference.

Each operation also has a status history:

  • GET /api/v1/vaults/{code}/operations/{type}/{id}/status-history returns the timeline of status transitions.

The operations feed and the ledger: two different views

The ledger (postings) is the accounting truth. For humans, though, the operations feed of business events is more convenient:

  • GET /api/v1/vaults/{code}/history returns your activity in the Vault as a single feed (your transfers and deposits to your addresses), sorted by (createdAt, id) descending, with keyset pagination. Filters: direction=in|out, from / to, and status (for example, stuck transfers).

Postings answer "how is this reflected in the books," while the feed answers "what happened."

Amounts in the ledger and balances are stored in the token's smallest units (raw) and displayed in decimal form using the network's decimals. For details, see Amounts and precision.

Terms

Next steps