Matrix logo

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-Agent principal token as a convenience. The shared transport bearer (LAYERX_TOKEN) is an optional fleet gate, never the public gate.

Public read routes (no auth)

MethodPathDescription
GET/healthzLiveness (DB ping)
GET/Service info (version, health link)
GET/v1/infoSequencer metadata (chain ID, vault, anchor, USDL, DEX router, pubkey, window, threshold)
GET/v1/supplyReserve proof: circulating USDX, on-chain USDL reserve, drift, accounts, transfers
GET/v1/batchesPaginated 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/transfersKEYSET-paginated transfer feed (optional ?did= filter)
GET/v1/account/{did}Public account view (balance, escrow, EVM address, history)
GET/v1/streamLive SSE event stream (transfer + anchor events; reconnect via Last-Event-ID)
GET/v1/hold/{id}Public hold view

Auth lane (public)

MethodPathDescription
POST/v1/agent/auth/challengeOpen ed25519 DID auth challenge
POST/v1/agent/auth/verifyVerify signature, mint principal token

Write/principal surface (DID-signed intent or principal token)

MethodPathDescription
GET/v1/balanceUSDX balance + escrow
GET/v1/depositVault address + DID-claim payload
POST/v1/account/evmBind/update EVM payout address
POST/v1/payPay another agent by DID
POST/v1/holdAuthorize a hold (card-network auth model)
POST/v1/hold/{id}/captureCapture a hold (captor-only)
POST/v1/hold/{id}/releaseRelease a hold (captor or payer)
POST/v1/withdrawBurn USDX, release USDL on-chain
POST/v1/settleForce-settle the open window now

Authentication

DID challenge/verify

  1. Agent sends POST /v1/agent/auth/challenge with {did: "did:matrix:label:fingerprint"}
  2. Server creates a single-use nonce (24-byte random, base64url, TTL-bound), returns {nonce, message}
  3. Agent ed25519-signs the message, sends POST /v1/agent/auth/verify with {did, public_key, nonce, signature}
  4. Server verifies: consumes nonce (single-use), verifies ed25519 signature, checks public key matches DID fingerprint
  5. Server mints an HMAC-bound principal token (DID + expiry, base64url payload + HMAC signature)

Dual authorization on writes

Every write endpoint accepts either:

  • X-LayerX-Agent principal 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:

ClassEndpointsDescription
Auth/v1/agent/auth/*Tightest limit
WritePOST endpointsValue-moving operations
ReadGET endpointsPublic 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:

  1. Classifies the tier (micropayment or material)
  2. Inside a single DB transaction: debit sender, credit recipient, insert transfer, compute Merkle leaf + sequencer signature
  3. 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)

  1. List all unsettled transfers
  2. Build Merkle root from leaf hashes
  3. Seal the batch in Postgres
  4. Anchor the root on Paxeer via chain.Settler.AnchorBatch
  5. Mark anchored with the on-chain tx hash

Withdrawal settlement (SettleWithdrawals)

  1. List queued withdrawals with mapped EVM addresses
  2. Build a deterministic payout root (domain-separated SHA-256 over id + recipient + amount)
  3. 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:

FunctionDescription
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/DecodePathl: / 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

MethodDescription
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

CodeMeaning
invalid_requestMalformed input
unauthorizedAuth failed
not_foundUnknown account/receipt
insufficient_fundsEscrow-bounded spend exceeded
conflictIdempotency collision
internalServer error
rate_limitedPer-client rate exceeded

Settlement tiers

TierConditionSettlement
micropaymentBelow micro thresholdNet-batched on the window
materialAt or above thresholdForce-settled