Oshun Platform · Architecture

Aje — Non-Custodial Payment Substrate

A focused page within the Oshun Platform Architecture documentation. The full map and every sibling page live in the Architecture hub.

9sections18 minread7tables

On this page

Aje is V1's blockchain and Web3 payment substrate — the library-only domain (libs/aje/) that lets Oshun accept money without ever custodying customer funds and without a centralized processor in the middle. Named for the Yoruba orisha of wealth, commerce, and the marketplace, it serves the billing and entitlement surfaces (a learner upgrades to a paid tier by paying in crypto) through one Oshun-specific glue library, libs/oshun/payments-bridge/. This page specifies what Aje ships, the five chains V1 contributes, and the bridge that turns an on-chain confirmation into an Oshun entitlement — and it is candid about where the wiring is real versus where it is spec-only or provider-gated.

This page sits alongside the other substrate pages (Sophia — Grounding Substrate, Isis — Generation Control Substrate, Lilith — Contemplative Policy Substrate) and is the architectural counterpart to the product-facing Support, Entitlements, Billing, and the Aje Entitlement Bridge. The backlog lives at §23.1; cross-cutting dependencies at deps§13. The hub is ../ARCHITECTURE.md.

Why a non-custodial substrate#

The product promise (see V1 Product Promise) is that a learner can pay for an upgrade without Oshun touching their keys, holding their balance, or routing them through a card processor whose chargeback and KYC posture would distort the product. The whole design follows from that:

  • No custody. The application server holds view keys and watch-only xpubs only. It can watch a deposit address and detect a payment, but it cannot move funds. Spend authority lives behind hardware co-signers on an air-gapped signing station. That separation is described in code comments and types (see Cold-spend refund queue), but it is an operational concern, not provable from the repo alone.
  • No centralized processor. Confirmation is read directly from chain nodes and RPC providers; the bridge decides when "enough confirmations" have accrued for a given asset and amount tier, then emits an entitlement event.
  • Audited cryptography only. Every address-derivation and signing path uses @noble/curves, @noble/hashes, and @scure/* — no hand-rolled crypto.
  • TypeScript end to end. Cryptographically-sensitive node work (monerod, monero-wallet-rpc, ergo-node, java-tron) runs in upstream processes; the Aje TypeScript modules own only the JSON-RPC client and the deterministic key-derivation math.

Real-vs-aspirational at a glance#

Several pieces of this design are genuinely implemented and tested; several are operational or external-integration concerns that the repo describes but cannot exercise headless. Stating it plainly up front:

Area Status
Five new chain modules (chain-valid address derivation, RPC clients, confirmation policies, regtest/testnet e2e) Implemented, real, tested
state-mapper.ts confirmation policy + V1 event mapping Implemented, real, tested
trust-tier-disclosure.ts rail registry + acknowledgement gate Implemented, real, tested
Ed25519 ReceiptSigner + verification snippet Implemented, real, tested
Oracle aggregator, cold-spend queue, entitlement bus, QR generation, admin/security surfaces Implemented, real, tested
BFF consumes the bridge (catalog, quotes, receipt signing, invoice store) Implemented, real
The bridge imports Aje (@aje/chains, @aje/payments, …) Not wired — declared as deps, imported by zero source files
Live wallet → merchant settlement Provider-gated — needs an external merchant integration (BTCPay/OpenNode); fail-closed in-repo
Self-hosted nodes, air-gapped signing, hardware co-signers, multisig vaults, Tor egress Spec / policy only — described in types and comments, not provable in-repo

Aje library surface (reused without change)#

V1 reuses Aje's existing libraries as a chain/wallet/payment toolkit. The following table reflects what Aje ships; the important caveat is that the V1 bridge does not call any of it (see The bridge is functionally standalone).

Aje library What it provides
@aje/core Address types, hex utilities, audited crypto primitives (@noble/@scure).
@aje/chains EVM L1 + L2s and non-EVM chains. The chains/src/ tree contains ethereum/, arbitrum/, optimism/, polygon/, avalanche/, zksync/, cardano/, solana/, plus the abstraction/ submodule (UnifiedProvider, ChainRegistry, NonceTracker). Base is covered via the OP Stack.
@aje/bitcoin Lightning (LND/CLN/Phoenixd), LSP, BitVM, sBTC, Stacks, Ordinals, Runes, RGB.
@aje/wallets HD (BIP32/39/44), hardware (Trezor/Ledger/Coldcard), MPC, account abstraction, paymaster, session keys, WalletConnect.
@aje/payments/merchant PaymentRequest, Invoice, InvoiceLineItem, PaymentConfirmation, Refund, RecurringPayment, Subscription, payment links, QR codes, escrow, milestones, notifications, receipts.
@aje/payments/stablecoins USDC, USDT, DAI, FRAX, GHO with risk + swap + aggregation.
@aje/payments/circle Circle Mint + Cross-Chain Transfer Protocol (CCTP) for USDC.
@aje/oracles Pyth, Chainlink, RedStone, API3 adapters.
@aje/nodes / @aje/privacy / @aje/settlement-escrow Node runners, privacy pools, escrow primitives.

Staleness fix. Earlier prose claimed @aje/chains "already provides Avalanche and zkSync" as if they were missing. They are present — but as chains/src/avalanche/ and chains/src/zksync/, not as top-level libs/aje/chains/<chain>/ modules like the five V1 additions below.

Aje's actual merchant vocabulary#

The real Aje merchant types (libs/aje/payments/src/merchant/types.ts) define:

  • InvoiceStatus = draft | sent | viewed | partial | paid | overdue | cancelled | refunded
  • ConfirmationStatus = pending | confirming | confirmed | failed | finalized

These are accurate to Aje. The catch — detailed below — is that the V1 bridge reimplements its own status vocabulary rather than importing these.

The five V1 chain modules#

V1's contribution to Aje is five new top-level chain modules at libs/aje/chains/{monero,litecoin,ton,ergo,tron}/. Each is its own package with a provider.ts, a node/RPC client, types.ts, and an end-to-end test against a local regtest / testnet / stagenet / sandbox. Every one derives chain-valid addresses with audited crypto. The audit that prompted these modules found that the earlier per-invoice addresses were syntheticsha256-derived strings that no node would validate and no operator could sweep.

Module Derivation + policy (real) e2e harness
libs/aje/chains/monero/ Per-invoice subaddress allocator (account 0, fresh subaddress_index); view-only monero-wallet-rpc; 10-block unlock; get_tx_proof payment proofs. stagenet e2e
libs/aje/chains/litecoin/ BIP84 native-SegWit (P2WPKH) addresses at m/84'/2'/0'/0/* (LTC_BIP84_DERIVATION_PREFIX = m/84'/2'/0', coin type 2 per SLIP-0044); watch-only xpub enforced at depth 3; rbf-detector.ts for fee-bump detection; 1/3/6 confirmation tiers. regtest e2e
libs/aje/chains/ton/ subwallet.ts derives genuine v4r2 contract addresses (workchain:sha256(StateInit), StateInit data = seqno(u32) ‖ subwallet_id(u32) ‖ public_key(256) ‖ plugins); per-invoice subwallet_id from a monotonic counter; canonical v4r2 code BoC loaded via @ton/core Cell.fromBoc. Trust-tier C. sandbox e2e
libs/aje/chains/ergo/ p2pk-allocator.ts derives real P2PK addresses from a watch-only xpub (@scure/bip32 HDKey); ERG_BIP44_PURPOSE=44, ERG_SLIP44_COIN_TYPE=429, ERG_BIP44_DERIVATION_PREFIX = m/44'/429'/0'/0; UTXO-set polling; 5/10/30 tiers. testnet e2e
libs/aje/chains/tron/ tron-address.ts derives genuine base58check addresses; USDT-TRC20 only (native TRX not accepted). Trust-tier C. nile testnet e2e

TRON address derivation — worked example#

tron-address.ts is a good illustration of "chain-valid, not synthetic." TRON reuses Ethereum's secp256k1 address scheme: take keccak_256 of the uncompressed public key's X‖Y (dropping the 0x04 tag), keep the last 20 bytes, prefix the network byte 0x41 (TRON_ADDRESS_PREFIX), and base58check-encode with a double-SHA-256 checksum. tronAddressFromPublicKey normalizes the key onto the curve (secp256k1.Point.fromHex(...).toBytes(false), which throws if the point isn't on-curve), and isValidTronAddress round-trips the decode so a caller can cross-check an address before accepting payment.

The TRON USDT constants are pinned (usdt-trc20.ts):

Constant Value
USDT_TRC20_CONTRACT TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t
USDT_TRC20_NILE_CONTRACT TXLAQ63Xg1NAzckPwKHvzw7CSEmLMEqcdj
TRON_USDT_DECIMALS 6
TRON_CONFIRMATION_DEPTH 19

The indexer scopes TRC-20 Transfer events to those contract addresses only — a transfer of the wrong token contract to the deposit address is not counted.

The V1 entitlement bridge — libs/oshun/payments-bridge/#

@oshun/payments-bridge (v0.1.0, private) is the single Oshun-specific library on top of Aje. Its job is to turn an observed on-chain confirmation into an Oshun entitlement event, with V1's trust-tier disclosures, receipt signing, refund queue, and customer/admin surfaces layered on. Its src/index.ts re-exports nine modules:

text
state-mapper.ts            trust-tier-disclosure.ts   oracle-aggregator/
cold-spend-queue/          receipt-signer/            entitlement-bus/
customer-surface/          admin-surface/             security-gates/

Layout correction. Earlier docs listed src/trust-tier-disclosure/ as a directory and src/oracle-aggregator.ts/src/cold-spend-queue.ts/ src/receipt-signer.ts as flat files — it is the reverse. trust-tier-disclosure.ts is a flat file; oracle-aggregator/, cold-spend-queue/, and receipt-signer/ are directories. There is no webhook-router.ts, no crisis-suppression.ts, and no telegram-handoff.ts in this package — those module names in older prose never existed here.

The bridge is functionally standalone#

This is the single most important correction to the architecture's earlier framing. The narrative was "Aje stays library-only, consumed through this bridge" — implying the bridge adapts Aje's Invoice / PaymentConfirmation / Refund state machine. It does not. package.json declares @aje/chains, @aje/oracles, @aje/payments, @aje/wallets, @oshun/audit-platform, @oshun/event-bus, and @oshun/identity as dependencies, but a grep -rn "from '@aje" src/ returns nothing, and @oshun/event-bus is likewise never imported. The bridge reimplements the invoice/confirmation model and the event-emitter seam rather than adapting Aje. This is honest to record because every downstream claim about "the bridge maps Aje's types onto the bus" is, today, aspirational: the consumption path exists in package.json but not in code.

state-mapper.ts — the confirmation engine#

This is the real heart of the bridge. It defines its own vocabularies (not Aje's):

  • AJE_INVOICE_STATUSES = draft | pending | paid | partial | refunded | expired | cancelled (note: no sent/viewed/overdue; adds pending/expired).
  • AJE_CONFIRMATION_STATUSES = unconfirmed | confirmed | finalized (not Aje's five-value ladder).
  • V1_PAYMENT_EVENT_TYPES = payment.invoice.settled | payment.invoice.underpaid | payment.invoice.expired | payment.refund.broadcast.

mapAjeStateToV1(input) is a pure function from an AjeInvoiceStateInput to a V1PaymentEvent | null. The mapping:

Source status Emitted event Notes
paid (and confirmation meets requirement) payment.invoice.settled grantsEntitlement is true when an entitlementId is present
paid (below depth) null not yet settled
partial payment.invoice.underpaid underpaidByAtomic = expected − observed (clamped ≥ 0)
refunded payment.refund.broadcast
expired / cancelled payment.invoice.expired
draft / pending null nothing to emit

mapAjeStateToV1WithAudit wraps that with an injected V1PaymentStateAuditAppender and writes an immutable V1PaymentStateAuditRecord ({ immutable: true, … }) via toAuditRecord before returning. Amounts are bigint atomic units throughout, so there is no float drift.

CONFIRMATION_POLICY — 32 assets, per-tier depth#

V1_PAYMENT_ASSETS is exactly 32 entries: btc-onchain, btc-lightning, ltc, xmr, eth-mainnet, eth-base, eth-arbitrum, eth-optimism, matic-polygon, sol, ton, ada, erg, then usdc-{mainnet,base,arbitrum, optimism,polygon,solana}, usdt-{mainnet,base,arbitrum,optimism,polygon,tron, solana,ton}, and dai-{mainnet,base,arbitrum,optimism,polygon}. Each maps to a ConfirmationPolicy { micro, standard, highValue }requiredConfirmations selects the depth from the amount tier. Representative real values:

Asset micro standard high-value Why
btc-onchain 0 1 3 small payments accept 0-conf; large wait for depth
ltc 1 3 6 faster blocks, deeper for high value
xmr 10 10 20 Monero's 10-block unlock baseline
eth-mainnet 12 12 30 reorg safety scales with value
ada 15 15 30
erg 5 10 30
usdt-tron 19 19 19 flat 19-block (TRON_CONFIRMATION_DEPTH)
matic-polygon 256 256 256 one Heimdall checkpoint window
sol / usdc-solana / usdt-solana −1 −1 −2 commitment sentinels

Solana has no block-depth notion that maps cleanly, so its "depth" is encoded as a commitment sentinel: SOLANA_COMMITMENT_CONFIRMED = -1 and SOLANA_COMMITMENT_FINALIZED = -2. The private computeConfirmationMeetsRequirement branches on these: -1 accepts a confirmed-or-finalized confirmation status; -2 requires finalized; required === 0 accepts any non-negative observed count; and the default branch demands observedConfirmations >= required and a confirmed/finalized status. This is real domain logic — it would not pass against a random or hardcoded return.

trust-tier-disclosure.ts — the acknowledgement gate#

V1 will not render a deposit address for a riskier rail until the customer has acknowledged what they are paying with. This file is the source of truth for that policy. It exports RAIL_TIERS = ['A','B','C'], ISSUER_TRUST_CLASSES = ['native','decentralized-issuer','central-issuer-with-freeze'], a per-asset RAIL_REGISTRY of RailDescriptor, and the helpers getRailDescriptor, railRequiresDisclosure, listSupportedAssets, and gateInvoiceCreation.

A RailDescriptor carries tier, issuerTrustClass, an English disclosureCopyEn, and requiresDisclosureAcknowledgement. The copy is real, specific, and reviewed by Lilith policy each release — e.g. Base reads "Base is an OP-Stack L2; the sequencer is currently operated by Coinbase and may pause transactions. Withdrawals to L1 require a 7-day challenge period," and usdt-tron reads "USDT on Tron is issued by Tether and can be frozen at the issuer's discretion. The Tron network is operated by a small set of Super Representatives."

Tier / class Assets Ack required?
A, native btc-onchain, btc-lightning, ltc, xmr, eth-mainnet, ada, erg No
B, native sol, and the EVM L2 rails eth-base, eth-arbitrum, eth-optimism, matic-polygon Yes
B/C, central-issuer-with-freeze all USDC / USDT / DAI assets Yes
C, native ton Yes
C, central-issuer-with-freeze usdt-tron, usdt-ton Yes

gateInvoiceCreation(request) returns { verdict: 'allow' } when no acknowledgement is required (Tier-A native rails) or when one is present; otherwise it returns { verdict: 'block', reason: 'disclosure-acknowledgement-required', disclosureCopy }, carrying the exact copy the surface must show. This is a fail-closed gate: a USDC or TON invoice cannot mint an address until the customer's acknowledgement timestamp is recorded.

receipt-signer/ — independently-verifiable receipts#

A crypto receipt is signed with Ed25519 (@noble/curves/ed25519) so that a customer holding only the receipt and the chain can verify Oshun's claim without trusting Oshun's API. ReceiptSigner.sign() canonicalizes the payload — JSON.stringify with deterministically sorted keys and bigints encoded as decimal strings to survive JSON — and signs those bytes; verifyReceipt re-derives the same canonical bytes and checks the signature.

ReceiptPayload (receipt-signer/types.ts) carries invoiceId, merchantId, customerLocale, txId, blockHash, blockHeight, asset (one of the 17 RECEIPT_ASSETS), amount (bigint, chain smallest unit), fiatCurrency, fiatAmountMinor (bigint cents), confirmedAtUnixSeconds, a tax (TaxBreakdown, reused from the fiat billing receipt formatter via locale-tax.ts), and a verificationSnippet — a real shell/curl invocation the customer can run to reproduce the on-chain check.

Monero proof correction. The Monero payment proof is a 2-tuple, not a 3-tuple. MoneroPaymentProof = { txKey, address }. The txId is a separate top-level ReceiptPayload field, not part of the proof tuple. Older prose describing (txid, tx_key, address) as the proof was wrong.

The SignedReceipt wraps the payload with signatureHex (hex Ed25519 signature over the canonical JSON) and the auditKeyId that signed it. The BFF constructs a ReceiptSigner only when a private key is configured (see below).

entitlement-bus/ — the emitter seam#

entitlement-bus/topics.ts defines three topics — PAYMENT_INVOICE_CONFIRMED_TOPIC = 'payment.invoice.confirmed', PAYMENT_INVOICE_SETTLED_TOPIC = 'payment.invoice.settled', PAYMENT_REFUND_BROADCAST_TOPIC = 'payment.refund.broadcast' — and a PaymentRail union with 14 crypto-* values plus 4 fiat (fiat-stripe | fiat-adyen | fiat-paypal | fiat-generic). Every event carries schemaVersion: 1 and a structurally-fiat-compatible shape (fiatEquivalent, chainAsset, chainAmount, …AtUnixSeconds), so a consumer need not know whether a payment came from Stripe or from a crypto rail — the rail field distinguishes them. The emitter (emitter.ts) takes an injected publish function and never imports @oshun/event-bus; this is the standalone seam noted above.

Two corrections worth pinning. (1) The file's doc-comment says "thirteen crypto rails," but the PaymentRail union actually lists 14 (crypto-cardano is included) — off by one. (2) There is an internal event-vocabulary split the code itself does not reconcile: state-mapper.ts emits settled | underpaid | expired | refund.broadcast, while entitlement-bus/topics.ts defines confirmed | settled | refund.broadcast. The two modules use different event sets; payment.invoice.confirmed is never produced by the state mapper, and underpaid/expired are not topics on the bus. Earlier docs conflated the two as one coherent vocabulary.

oracle-aggregator/ — fiat-rate median + rate-lock#

oracle-aggregator/ (price-feed.ts, tor-egress.ts, rate-lock.ts) composes multiple price sources into a V1 fiat-rate median, routes the off-Aje sources through a Tor egress, and holds a per-invoice rate-lock cache so the quoted fiat amount is stable for the life of an invoice rather than drifting with spot price between quote and settlement.

cold-spend-queue/ — refund signing path#

cold-spend-queue/ (queue.ts, sweep-policy.ts, hw-signing-fixture.ts, audit-attestation.ts, plus integration.test.ts) models the refund/sweep flow: queue an unsigned transaction artifact, apply the sweep policy, route it through a hardware-signing fixture, and attach an audit attestation per signing event. The unsigned-tx artifacts span chain families (PSBT, EIP-1559, Monero unsigned transfer, Cardano CBOR, Solana versioned tx, TON external message, Ergo UnsignedTransaction, Tron raw tx). The queue logic and attestation are real; the air-gapped station, hardware co-signers, and multisig vaults themselves are operational, not provable in-repo.

customer-surface/ — paywall, QR, reminders, Telegram#

customer-surface/ is a substantial real surface, underdescribed by older docs:

  • From-scratch QR generationqr-matrix.ts and qr-svg.ts implement a QR matrix/SVG encoder (with qr-format-bits.test.ts verifying the format bits). This is a genuine V1 artifact, not a passthrough to @aje/payments.
  • paywall-spec.ts + disclosure-gate.ts + asset-chain-filter.ts drive what the customer sees and which chains are offered.
  • reminder-cadence.ts implements the renewal-invoice reminder cadence (7-day / 24-hour / 1-hour) described in the product docs — real code behind that claim.
  • telegram-bot-router.ts is the actual home of crisis suppression. routeUpgradeCryptoCommand(input) returns { action: 'suppress', reason: 'crisis-active' } when input.crisisState === 'active'. This gates the Telegram /upgrade command — not "every invoice-creation path." (A separate, unrelated crisis-suppression policy lives in the BFF under apps/oshun/bff/src/safety/.)

admin-surface/ and security-gates/#

Two real modules the bridge table never mentioned:

  • admin-surface/ (explorer-urls.ts, invoice-timeline.ts, node-health-panel.ts, refund-initiation.ts, invariant-guards.ts) is the operator-facing crypto billing surface — block-explorer links, an invoice timeline, a node-health panel, refund initiation, and invariant guards.
  • security-gates/ (build-time-invariants.ts, chaos-tester.ts, disclosure-audit.ts, node-health-probes.ts, tabletop.ts) implements build-time invariant scanning, chaos and tabletop testing, disclosure auditing, and node-health probing. These back the "tests cover" claims the prose only alluded to.

How an entitlement is actually granted#

The mechanism that turns a settled payment into a tier change lives in @oshun/billing-support (billing-aje-bridge.ts), which the audit found had previously been an island with zero importers. It does two concrete things:

  1. Collapses six billing classes onto three product tiers. TIER_BY_CLASS maps EntitlementClass (free | starter | plus | pro | scholar | institutional) onto OshunEntitlementTier (free | pro | premium): free → free, starter/plus → pro, pro/scholar/institutional → premium. So the product reads one tier vocabulary, not two.

    text
    free          -> free
    starter, plus -> pro
    pro, scholar, institutional -> premium
    
  2. Advances the subscription state machine from a settlement. applyPaymentSettlementToSubscription takes an AjePaymentSettlement { status: 'confirmed' | 'failed' | 'refunded', reference } (normalized from the confirmation ladder) and drives the subscription state. Only the ENTITLING_STATES set — trial, active, grace, restored — actually entitles the customer; a subscription in past-due / paused / canceled / lapsed falls back to free, so a lapsed payment immediately de-entitles instead of stranding a stale tier. A confirmed settlement moves trial/past-due/grace/paused/restoredactive and canceled/lapsedrestored.

This is the concrete entitlement linkage the architecture previously described only abstractly.

The BFF is the real consumer#

Older prose implied apps consume Aje "through the bridge" without naming the layer. The real consumer is the BFF (apps/oshun/bff/src/payments/):

  • routes/domain-stubs.ts imports listSupportedAssets from @oshun/payments-bridge and serves the real asset catalog at GET /v1/payments/methods (not a fixture).
  • payments/payments-composition.ts and payments/quote-builder.ts import ReceiptSigner and V1PaymentAsset. resolvePaymentsReceiptSigner(env) constructs a ReceiptSigner only when OSHUN_PAYMENTS_RECEIPT_ED25519_PRIVATE_KEY is set, else returns null.
  • payments/ also holds invoice-store.ts, settlement-route.ts, and quote-builder.ts, which back /v1/payments/crypto/quote, /v1/payments/invoices, and the entitlement route /v1/entitlements/aaa.

@aje itself appears only in a BFF comment: server.ts (~line 930) notes that in-repo no settlement provisioner is injected, so the crypto-quote route is fail-closed (503, no address issued). It also notes that a real deploy "constructs the provisioner (BTCPay/OpenNode/@aje) + binds a real runtime here." That is an honest fail-loud seam, not a stub.

Live settlement is provider-gated#

Consequently the sign-up-and-pay-crypto walkthrough verdict is "pass (surfaces) / partial (settlement)": /billing/crypto and /aaa-upgrade render, the /v1/payments/* and /v1/entitlements/aaa BFF endpoints respond, the asset catalog is real, and receipt signing works. But live wallet → merchant settlement needs a real external merchant integration (BTCPay/OpenNode), which is a documented external dependency. The v1-completeness-audit-2026-06-22 lists this walkthrough as partial for exactly this reason.

Operational invariants (spec / policy)#

These are stated in code comments and types and enforced where buildable, but several are operational concerns the repo cannot exercise headless:

  • View-only on the app server. Spend keys never reach the application layer; they live on an air-gapped signing station behind hardware co-signers.
  • Per-invoice fresh derivation. Fresh derivation paths / subaddresses on every chain; address reuse is rejected at the invoice contract layer.
  • Sweep to a 2-of-3 multisig vault per chain family (Bitcoin SegWit multisig, EVM Safe, Monero MMS, Solana SPL multisig, TON multisig, Cardano Plutus-script multisig, Ergo P2S multisig, Tron multisig).
  • Telemetry stores only the hash of any per-invoice identifier (Monero subaddress, Solana reference pubkey, TON subwallet address).
  • No on-chain customer identifiers — no OP_RETURN, no Monero tx_extra payment IDs, no Solana/TON memo customer IDs; enforced at the build layer by the chain modules' invariant scanning.
  • Mandatory trust-tier disclosures on every Tier-B/Tier-C rail and every centrally-issued asset, with the acknowledgement timestamp recorded before address rendering (the gateInvoiceCreation gate above).

The multisig vaults, air-gapped station, hardware co-signers, Tor egress in production, and any running node or live settlement could not be verified in-repo and should be treated as spec until an operational deployment exists.

Backlog and scope#

The crypto settlement work is tracked under §23.1 (asset enum §23.1.1, state mapping, per-tier confirmation policy, trust-tier registry, receipt signer, entitlement bus). Payment- and dependency-level concerns are at deps§13. Out-of-V1 Aje capabilities (DePIN, on-chain gaming, NFT, RWA, prediction markets, intents, restaking, ZKP, governance, Sui/Move) ship as part of the library but are not used by any V1 surface.