LayerX Daemon, Auth, Chain, Accumulator, Store, Ledger, and Types
The LayerX settlement daemon (layerxd): DID-scoped accounts, USDX balances, Merkle-provable receipts, tiered settlement, and the full-transparency public explorer/RPC surface.
Overview
LayerX is the always-on settlement fabric for Matrix agents. The layerxd daemon gives each agent a DID-scoped account, mints and tracks a USD-denominated balance called USDX (fully reserved 1:1 by USDL), produces a Merkle-provable signed receipt for every accepted transfer, and batches settlement to Paxeer mainnet (chain 125).
Version: 0.1.0
Runtime Architecture
cmd/layerxd # the sequencer daemon
internal/config # env-first + layerx.config.kvx overlay
internal/auth # ed25519 agent-DID challenge/verify + HMAC principal token
internal/store # Postgres ledger: accounts, transfers, batches, deposits, withdrawals, holds
internal/accumulator # domain-separated Merkle leaves + root + inclusion proofs
internal/sig # the sequencer's ed25519 receipt-signing key
internal/ledger # atomic Pay + signed receipts + proof reconstruction
internal/settle # tiered settlement worker (window net + force-settle + anchor + hold sweep)
internal/chain # Settler interface + Paxeer chain bindings (vault, anchor, ERC-20, router)
internal/ratelimit # per-client token-bucket rate limiter
internal/events # SSE event broker for live streaming
internal/server # HTTP surface (public read + principal write)
pkg/types # {ok,data,error} envelope + wire contracts
migrations # forward-only SQL
HTTP Surface (Phase 4: Full-Transparency Model)
The surface is split into two groups:
- Public, unauthenticated READ surface (explorer/RPC): info, supply (reserve proof), batches, anchors, receipts, transfers, accounts, live SSE stream. Anyone can read so receipts/roots are independently verifiable and the reserve is publicly auditable.
- Write/principal surface authorized by DID signature alone: pay/withdraw/hold accept a directly DID-signed intent, or an
X-LayerX-Agentprincipal token as a convenience. The shared transport bearer (LAYERX_TOKEN) is an optional fleet gate, never the public gate.
Public read routes (no auth)
| Method | Path | Description |
|---|---|---|
| GET | /healthz | Liveness (DB ping) |
| GET | / | Service info (version, health link) |
| GET | /v1/info | Sequencer metadata (chain ID, vault, anchor, USDL, DEX router, pubkey, window, threshold) |
| GET | /v1/supply | Reserve proof: circulating USDX, on-chain USDL reserve, drift, accounts, transfers |
| GET | /v1/batches | Paginated settlement batches |
| GET | /v1/batch/{id} | Single batch detail |
| GET | /v1/anchor/{root} | Paxeer anchor tx for a root |
| GET | /v1/receipt/{seq} | Signed inclusion receipt (public, no caller scoping) |
| GET | /v1/transfers | KEYSET-paginated transfer feed (optional ?did= filter) |
| GET | /v1/account/{did} | Public account view (balance, escrow, EVM address, history) |
| GET | /v1/stream | Live SSE event stream (transfer + anchor events; reconnect via Last-Event-ID) |
| GET | /v1/hold/{id} | Public hold view |
Auth lane (public)
| Method | Path | Description |
|---|---|---|
| POST | /v1/agent/auth/challenge | Open ed25519 DID auth challenge |
| POST | /v1/agent/auth/verify | Verify signature, mint principal token |
Write/principal surface (DID-signed intent or principal token)
| Method | Path | Description |
|---|---|---|
| GET | /v1/balance | USDX balance + escrow |
| GET | /v1/deposit | Vault address + DID-claim payload |
| POST | /v1/account/evm | Bind/update EVM payout address |
| POST | /v1/pay | Pay another agent by DID |
| POST | /v1/hold | Authorize a hold (card-network auth model) |
| POST | /v1/hold/{id}/capture | Capture a hold (captor-only) |
| POST | /v1/hold/{id}/release | Release a hold (captor or payer) |
| POST | /v1/withdraw | Burn USDX, release USDL on-chain |
| POST | /v1/settle | Force-settle the open window now |
Authentication
DID challenge/verify
- Agent sends
POST /v1/agent/auth/challengewith{did: "did:matrix:label:fingerprint"} - Server creates a single-use nonce (24-byte random, base64url, TTL-bound), returns
{nonce, message} - Agent ed25519-signs the message, sends
POST /v1/agent/auth/verifywith{did, public_key, nonce, signature} - Server verifies: consumes nonce (single-use), verifies ed25519 signature, checks public key matches DID fingerprint
- Server mints an HMAC-bound principal token (DID + expiry, base64url payload + HMAC signature)
Dual authorization on writes
Every write endpoint accepts either:
X-LayerX-Agentprincipal token (convenience), OR- A directly DID-signed intent (
from_did,public_key,nonce,signature) where the signature IS the authorization (invariant i6)
Rate Limiting
Per-client token-bucket rate limiting with three classes:
| Class | Endpoints | Description |
|---|---|---|
| Auth | /v1/agent/auth/* | Tightest limit |
| Write | POST endpoints | Value-moving operations |
| Read | GET endpoints | Public explorer reads |
/healthz and / are exempt. Client IP is extracted from X-Real-IP / X-Forwarded-For when TrustProxy is enabled, or from the transport peer address.
Ledger
Source: internal/ledger/ledger.go
The Ledger ties the store, sequencer signer, and tier policy together.
Pay
Pay(ctx, fromDID, toDID, amountMicro, ref) moves USDX atomically:
- Classifies the tier (micropayment or material)
- Inside a single DB transaction: debit sender, credit recipient, insert transfer, compute Merkle leaf + sequencer signature
- Returns a signed
Receipt
CaptureHold
CaptureHold(ctx, holdID, captorDID, amountMicro) consumes an open hold through the same transfer commitment path as Pay, with any remainder returned to the payer.
Receipt reconstruction
Receipt(ctx, seq, callerDID) returns the signed receipt. If the transfer's batch is sealed, it populates the Merkle root + inclusion path (and anchor tx once anchored) so the receipt is independently verifiable.
ReceiptPublic(ctx, seq) is the same but without caller-DID scoping (public explorer read).
Settlement Worker
Source: internal/settle/settle.go
The Worker runs on a configurable window (default 12 hours):
Window settlement (SettleNow)
- List all unsettled transfers
- Build Merkle root from leaf hashes
- Seal the batch in Postgres
- Anchor the root on Paxeer via
chain.Settler.AnchorBatch - Mark anchored with the on-chain tx hash
Withdrawal settlement (SettleWithdrawals)
- List queued withdrawals with mapped EVM addresses
- Build a deterministic payout root (domain-separated SHA-256 over id + recipient + amount)
- Seal withdrawals, anchor on Paxeer, mark settled
Hold expiry sweep
Runs every 30 seconds. Returns past-expiry open holds to their payers (fail-open refund).
Crash recovery
RecoverPending and RecoverPendingWithdrawals re-submit sealed-but-unanchored batches at startup. AnchorBatch is idempotent on the root.
Accumulator
Source: internal/accumulator/accumulator.go
Domain-separated Merkle tree for receipt proofs:
| Function | Description |
|---|---|
CanonicalLeaf(seq, from, to, amount, ts) | Deterministic byte-stable preimage |
LeafHash(canonical) | `sha256("layerx.settlement.receipt.v1" |
Root(leaves) | Merkle root with duplicate-last promotion |
Proof(leaves, idx) | Inclusion path for a leaf index |
Verify(root, leaf, proof) | Recompute root from leaf + proof |
EncodePath/DecodePath | l: / r: hex string encoding |
Store
Source: internal/store/store.go, internal/store/accounts.go
Postgres-backed via pgxpool.Pool. Forward-only migrations tracked in layerx_schema_migrations.
Key operations
| Method | Description |
|---|---|
GetAccount(did) | Load account or ErrNotFound |
SetEVMAddress(did, evm) | Bind payout address |
CreditDeposit(did, amount, depositTx) | Idempotent deposit credit |
Pay(from, to, amount, tier, ref, finalize) | Atomic debit/credit/insert with leaf+sig callback |
QueueWithdrawal(did, amount, tier, swapOut) | Debit balance, queue withdrawal |
GetTransfer(seq, callerDID) | Caller-scoped transfer read |
GetTransferPublic(seq) | Public transfer read |
SealBatch(rootHex, seqs, window) | Seal a settlement batch |
MarkAnchored(batchID, txHash) | Record on-chain anchor |
Types
Source: pkg/types/types.go
USDX
Balances are stored as int64 micro-USDX (1 USDX = 1,000,000 micro-USDX). FormatUSDX(micro) renders decimal strings. ParseUSDX(s) is strict: rejects trailing garbage, excess precision, overflow.
Error codes
| Code | Meaning |
|---|---|
invalid_request | Malformed input |
unauthorized | Auth failed |
not_found | Unknown account/receipt |
insufficient_funds | Escrow-bounded spend exceeded |
conflict | Idempotency collision |
internal | Server error |
rate_limited | Per-client rate exceeded |
Settlement tiers
| Tier | Condition | Settlement |
|---|---|---|
micropayment | Below micro threshold | Net-batched on the window |
material | At or above threshold | Force-settled |
