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 aschains/src/avalanche/andchains/src/zksync/, not as top-levellibs/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 | refundedConfirmationStatus=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 synthetic — sha256-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:
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 andsrc/oracle-aggregator.ts/src/cold-spend-queue.ts/src/receipt-signer.tsas flat files — it is the reverse.trust-tier-disclosure.tsis a flat file;oracle-aggregator/,cold-spend-queue/, andreceipt-signer/are directories. There is nowebhook-router.ts, nocrisis-suppression.ts, and notelegram-handoff.tsin 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: nosent/viewed/overdue; addspending/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 }. ThetxIdis a separate top-levelReceiptPayloadfield, 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
PaymentRailunion actually lists 14 (crypto-cardanois included) — off by one. (2) There is an internal event-vocabulary split the code itself does not reconcile:state-mapper.tsemitssettled | underpaid | expired | refund.broadcast, whileentitlement-bus/topics.tsdefinesconfirmed | settled | refund.broadcast. The two modules use different event sets;payment.invoice.confirmedis never produced by the state mapper, andunderpaid/expiredare 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 generation —
qr-matrix.tsandqr-svg.tsimplement a QR matrix/SVG encoder (withqr-format-bits.test.tsverifying the format bits). This is a genuine V1 artifact, not a passthrough to@aje/payments. paywall-spec.ts+disclosure-gate.ts+asset-chain-filter.tsdrive what the customer sees and which chains are offered.reminder-cadence.tsimplements the renewal-invoice reminder cadence (7-day / 24-hour / 1-hour) described in the product docs — real code behind that claim.telegram-bot-router.tsis the actual home of crisis suppression.routeUpgradeCryptoCommand(input)returns{ action: 'suppress', reason: 'crisis-active' }wheninput.crisisState === 'active'. This gates the Telegram/upgradecommand — not "every invoice-creation path." (A separate, unrelated crisis-suppression policy lives in the BFF underapps/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:
-
Collapses six billing classes onto three product tiers.
TIER_BY_CLASSmapsEntitlementClass(free | starter | plus | pro | scholar | institutional) ontoOshunEntitlementTier(free | pro | premium):free → free,starter/plus → pro,pro/scholar/institutional → premium. So the product reads one tier vocabulary, not two.textfree -> free starter, plus -> pro pro, scholar, institutional -> premium -
Advances the subscription state machine from a settlement.
applyPaymentSettlementToSubscriptiontakes anAjePaymentSettlement { status: 'confirmed' | 'failed' | 'refunded', reference }(normalized from the confirmation ladder) and drives the subscription state. Only theENTITLING_STATESset —trial,active,grace,restored— actually entitles the customer; a subscription inpast-due/paused/canceled/lapsedfalls back tofree, so a lapsed payment immediately de-entitles instead of stranding a stale tier. Aconfirmedsettlement movestrial/past-due/grace/paused/restored→activeandcanceled/lapsed→restored.
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.tsimportslistSupportedAssetsfrom@oshun/payments-bridgeand serves the real asset catalog atGET /v1/payments/methods(not a fixture).payments/payments-composition.tsandpayments/quote-builder.tsimportReceiptSignerandV1PaymentAsset.resolvePaymentsReceiptSigner(env)constructs aReceiptSigneronly whenOSHUN_PAYMENTS_RECEIPT_ED25519_PRIVATE_KEYis set, else returnsnull.payments/also holdsinvoice-store.ts,settlement-route.ts, andquote-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 Monerotx_extrapayment 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
gateInvoiceCreationgate 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.
Related#
- Support, Entitlements, Billing, and the Aje Entitlement Bridge — product-facing companion
- Lilith — Contemplative Policy Substrate — reviews disclosure copy and owns crisis-state policy
- Isis — Generation Control Substrate — sibling substrate
- Trust, Safety, and Privacy — crisis suppression and safety posture
- Security, Privacy, and Compliance — custody and key-handling posture
- Communication Patterns — the event-bus topics consumers subscribe to
- Subsystem Glossary — substrate names and roles
- ../ARCHITECTURE.md — architecture hub