docs(zap): generate the ZAP-native package reference from godoc

24 package pages under zap/reference, generated by luxfi/zap tools/docgen
from source comments. Build verified (781+ static pages).

Co-authored-by: Hanzo Dev <[email protected]>
This commit is contained in:
2026-08-22 23:52:44 -07:00
co-authored by Hanzo Dev
parent 8640733c4b
commit b2b217bdcb
34 changed files with 5423 additions and 2 deletions
+4 -2
View File
@@ -1,6 +1,8 @@
{
"pages": [
"index",
"api"
"api",
"---Package reference---",
"reference"
]
}
}
@@ -0,0 +1,14 @@
---
title: "main"
description: "Real-world benchmark: ZAP vs gRPC/Protobuf"
---
{/* Generated from godoc by tools/docgen — edit the Go source, not this file. */}
```go
import "github.com/luxfi/zap/bench"
```
Real-world benchmark: ZAP vs gRPC/Protobuf
@@ -0,0 +1,881 @@
---
title: "dexsession"
description: "Package dexsession is the ZAP/Cap'n-Proto orchestration layer for the Lux DEX."
---
{/* Generated from godoc by tools/docgen — edit the Go source, not this file. */}
```go
import "github.com/luxfi/zap/dexsession"
```
Package dexsession is the ZAP/Cap'n-Proto orchestration layer for the Lux DEX.
It makes the native C&lt;->D atomic settlement flow FEEL synchronous (capability
transport + promise pipelining) while keeping the value boundary 100% native.
# The hard invariant (the whole point)
A ZAP response is NEVER sufficient to move money. Money moves ONLY when:
C consumes a real D->C atomic shared-memory export object, OR
D consumes a real C->D atomic export object.
Even a fully malicious or stale ZAP layer cannot mint, credit, or substitute
value: it can only POINT the precompile/keeper at an atomic object that the
chain independently verifies (asset / owner / amount / one-time bound). This
package therefore traffics in VALUES — quotes (estimates), calldata (bytes to
sign), and POINTERS (DExportRef) — never in authority over a balance.
# Why the invariant holds structurally, not just by policy
The C-side value boundary (luxfi/precompile/dex/native_dchain_client.go) and
the D-side value boundary (luxfi/dex/pkg/dchain/atomic.go) both operate on EVM
host capabilities (a StateDB and an AtomicState / shared memory). This package
holds NONE of those: it has no StateDB, no AtomicState, no shared memory, and
— by deliberate module discipline — no compile-time edge to the precompile or
the EVM at all. It cannot import the C Verify path, and the C Verify path does
not import it. The only things crossing the boundary are bytes:
- prepareSwapIntent returns CALLDATA (hookData for 0x9999) the USER signs;
it reserves no funds and returns no amountOut the chain trusts.
- notifyIntent tells D to SCAN/import a C->D object the user's signed C tx
already created; it cannot make a D match valid without a committed D
block.
- importSettlement returns finalization CALLDATA / submits a C tx that
merely POINTS at a DExportRef; the chain's ImportSettlement re-reads the
real D->C object and binds recipient/asset/amount to it.
So "no capability moves money" is provable by EXHAUSTION over the API surface:
there is no creditBalance / settleFill / overrideMatch / adminWithdraw method
to call (see capability.go). The capability gate is defence in depth on top of
a surface that is already value-free by construction.
# Capabilities (Cap'n-Proto style)
FullServiceNode.Bootstrap returns a RESTRICTED DexSession. From the session a
caller derives narrowly-scoped, unforgeable capabilities — QuoteCap (read),
IntentCap (request a C->D intent + notify), WatchCap (subscribe to a D
result), SettlementCap (ask C to import an EXISTING D->C object), AdminCap
(halt/status only, separately gated). A capability cannot grant authority the
session was not bootstrapped with, and none of them can mint or credit a C
balance.
# Promise pipelining (the latency win)
Dependent calls issue before each prior resolves:
quote := dex.Quote(req)
intent := dex.PrepareSwapIntent(req, quote) // takes the quote promise
watch := dex.NotifyIntent(intent) // takes the intent promise
settle := watch.OnCommitted().ImportSettlement()
The client overlaps network latency (each call dispatches the instant its
inputs resolve), and a Pipeline batch collapses a whole dependency chain into
a single round trip. The consensus path stays native atomic throughout.
## Functions
### DecodeAtomicObject
```go
func DecodeAtomicObject(v []byte) (owner Account, asset ID, amount uint64, ok bool)
```
DecodeAtomicObject is the inverse. ok=false for any value that is not exactly
the canonical width, so a corrupt record is never reinterpreted — the same
defence the precompile and dexvm decoders apply. A consumer binds the credited
owner/asset/amount to THIS recorded value, never to a declared claim.
### EncodeAtomicObject
```go
func EncodeAtomicObject(owner Account, asset ID, amount uint64) []byte
```
EncodeAtomicObject serializes a cross-chain value object as the shared-memory
value: owner(20) | asset(32) | amount(8). Byte-identical with the precompile
and dexvm encoders. This is the ONLY value-bearing identity the atomic
conservation binds; dexsession reproduces the encoding so a watcher can derive
the object key it points at — it NEVER writes one into shared memory (it has
no shared memory).
### EncodeIntentHookData
```go
func EncodeIntentHookData(deadline, nonce uint64) []byte
```
EncodeIntentHookData builds an explicit Phase-A hookData carrying the deadline and the
intent NONCE. BYTE-IDENTICAL to precompile/dex EncodeIntentHookData — both sides MUST
produce the same calldata for the same (deadline, nonce) or the off-chain-derived
intent id would diverge from the on-chain one (the watch-correlation contract). The
encoding is MINIMAL-WIDTH (the precompile decodes all three widths):
- deadline==0 && nonce==0 -> tag only (4 bytes)
- nonce==0 -> tag | deadline[32]
- else -> tag | deadline[32] | nonce[32]
### EncodeModifyLiquidityCalldata
```go
func EncodeModifyLiquidityCalldata(pk PoolKeyArgs, args ModifyLiquidityArgs, hookData []byte) []byte
```
EncodeModifyLiquidityCalldata builds the full 0x9999 modifyLiquidity calldata:
selector(4) || PoolKey(5 words) || ModifyParams(4 words: tickLower, tickUpper,
liquidityDelta, salt) || offset(1 word) || hookData(length word + padded bytes)
Standard Solidity ABI encoding of the modifyLiquidity signature. The dynamic
`bytes hookData` is tail-encoded with a head offset word. hookData selects the
phase exactly as for swap: empty / DI01 for the commit intent, DS01 for settling a
removal's D->C export (though a removal's settlement is built via the swap-shaped
EncodeSwapCalldata + DS01, since the credit kernel is the swap path — see
v4session.go collect/cancel).
### EncodeSettlementHookData
```go
func EncodeSettlementHookData(outputID ID, amount uint64, intentID ID) []byte
```
EncodeSettlementHookData builds a Phase-B hookData: tag + outputID + amount + intentID.
Byte-identical to precompile/dex EncodeSettlementHookData (the INVERSE of its
decodeSettlementBody). amount is right-aligned in a uint256 word (the low 8 bytes),
matching the precompile's binary.BigEndian.PutUint64(amt[24:32], amount). intentID names
the originating C->D intent the settlement draws against — the precompile binds the credit
to that taker's intent record (the per-taker cap + the deadline gate), so it is REQUIRED;
a body without it is the wrong width and the on-chain decode reverts.
CRITICAL: the body carries outputID + amount + intentID, but NOT the output ASSET or the
RECIPIENT — on-chain the asset is derived from the swap direction and the recipient is the
CALLER. So this calldata cannot name a victim's recipient or a re-denominated asset;
ImportSettlement binds outputID/amount/intentID against the RECORDED object + the recorded
owner's intent. (This is why a tampered DExportRef cannot substitute recipient/asset/amount
— see the package invariant.)
### EncodeSwapCalldata
```go
func EncodeSwapCalldata(pk PoolKeyArgs, zeroForOne bool, amountIn uint64, hookData []byte) []byte
```
EncodeSwapCalldata builds the full 0x9999 swap calldata:
selector(4) || PoolKey(5 words) || SwapParams(3 words) || offset(1 word) ||
hookData(length word + padded bytes)
This is the standard Solidity ABI encoding of
swap((address,address,uint24,int24,address),(bool,int256,uint160),bytes). The
dynamic `bytes hookData` is tail-encoded with a head offset word.
zeroForOne + amountSpecified come from the request; amountSpecified is encoded
as -AmountIn (exact input). The hookData selects the phase: pass
EncodeIntentHookData()/empty for Phase A, EncodeSettlementHookData(...) for
Phase B.
### NewSession
```go
func NewSession(cfg SessionConfig) *clientSession
```
NewSession builds a client session. Bootstrap (server.go) is the canonical
entry; this is the explicit constructor for a node that already has a peer.
## Types
### Account
Account is a 20-byte EVM account (taker / recipient / owner). Byte-identical
to the low 20 bytes of an EVM address; the precompile binds it as the atomic
object owner.
### AdminCap
AdminCap permits halt/status only, and is granted SEPARATELY (AuthAdmin is not
in AuthorityPublic). It cannot withdraw, credit, or override a match.
```go
func (c AdminCap) Authority() Authority
```
### Authority
Authority is a bitmask of the operation classes a capability permits. Each bit
maps to exactly one capability type; a bit grants ONLY the read/build/point/
subscribe operations of that class — never a credit.
### CollectRequest
CollectRequest is the off-chain request to COLLECT/DECREASE (negative-delta) or
CANCEL a position/order. Both produce a D->C export (collected fees / withdrawn
liquidity / cancelled-order refund) consumed by the ONE DS01 credit path. Cancel is
the same shape with the cancel marker; the credit is identical.
### DExportRef
DExportRef points at a D->C atomic export object. It is deliberately NOT a
DFillReceipt: it carries no amount the chain trusts and no signature C honours.
The chain re-reads the actual object (asset/owner/amount/one-time) on import.
SourceChainID — the D chain whose export the object lives under.
SourceTxID — the D tx that produced the export.
OutputIndex — which exported output (deriveUTXOID(SourceTxID, OutputIndex)).
IntentID — the originating C->D intent (correlation only).
```go
func (r DExportRef) ObjectKey() ID
```
### DexSession
DexSession is the RESTRICTED capability a FullServiceNode.Bootstrap returns. It
exposes ONLY the orchestration surface — quote/getState/prepareSwapIntent/
notifyIntent/importSettlement — and derives narrowly-scoped capabilities. There
is no creditBalance / settleFill / overrideMatch / adminWithdraw: the surface is
value-free by construction (see capability.go).
### FlowStages
FlowStages is the set of promises a SwapFlow produces, exposed so a caller can
observe intermediate artifacts (e.g. show the user the estimate, the intent id,
the watch) without re-issuing calls. Each is a value/pointer, never authority.
### FullServiceConfig
FullServiceConfig configures the server node.
### FullServiceNode
FullServiceNode wraps a zap.Node and a Venue, exposing Bootstrap. It is the
server-side object the spec's `FullServiceNode.bootstrap() -> DexSession`
describes.
```go
func NewFullServiceNode(cfg FullServiceConfig) *FullServiceNode
```
```go
func (fsn *FullServiceNode) Bootstrap(peerID string, grant Authority) DexSession
```
### ID
ID is a 32-byte identifier (chain id, tx id, market id, intent id, asset id).
Byte-identical to luxfi/ids ids.ID and to an EVM bytes32. Native asset == the
all-zero ID (mirrors ids.Empty in the dexvm ledger).
```go
func DeriveIntentID(
networkID uint32,
cChainID, dChainID ID,
account Account,
assetIn ID,
amountIn uint64,
marketID ID,
nonce uint64,
) ID
```
```go
func DeriveLiquidityParamsHash(tickLower, tickUpper int32, liquidityDelta int64, salt ID) ID
```
```go
func DerivePoolKeyHash(pk PoolKeyArgs) ID
```
```go
func DeriveRoutePathHash(path []ID) ID
```
```go
func DeriveSessionID(s V4ActionScope) ID
```
```go
func DeriveSwapParamsHash(zeroForOne bool, amountIn uint64) ID
```
```go
func DeriveUTXOID(sourceTxID ID, outputIndex uint32) ID
```
### IntentCap
IntentCap permits requesting a C->D intent (build calldata) and notifying D to
scan/import the resulting object. It cannot reserve funds, claim a fill, credit,
or settle.
```go
func (c IntentCap) Authority() Authority
```
### IntentPhase
IntentPhase is the lifecycle of a notified intent as the off-chain watcher
observes it. NONE of these phases moves value — Committed merely means the
watcher SAW a D export object; the user still imports it on C.
### IntentStatus
IntentStatus is one poll/push of a watch. When Phase==PhaseCommitted the Ref
points at the produced D->C object. A malicious server can set Phase=Committed
with a bogus Ref, but importSettlement of that Ref reverts on-chain (the object
is missing or binds to a different owner/asset/amount).
MatchedOut is an ESTIMATE the venue reports when D has matched (PhaseMatching/
Committed): the orchestration "you'll receive ~N" figure the bidirectional
MatchResult read surfaces. It is INFORMATIONAL ONLY — exactly the QuotedOut
discipline extended to the match phase. The chain NEVER trusts it: the credit is
the recorded D->C object's amount, bound on-chain at settlement. A lying venue can
set MatchedOut to anything; it changes no balance (proven by the RED suite).
### IntentWatch
IntentWatch is the subscription handle a NotifyIntent returns. Poll observes the
current phase; OnCommitted yields a Promise that resolves to the DExportRef when
(and only when) the watch observes a committed D export.
```go
func (w IntentWatch) IntentID() ID
```
```go
func (w IntentWatch) OnCommitted(ctx context.Context) *Promise[DExportRef]
```
```go
func (w IntentWatch) Poll(ctx context.Context) (IntentStatus, error)
```
### IntentWatchRef
IntentWatchRef is the server-side handle a notifyIntent returns: the intent id
the watch tracks. The client polls it (MsgWatchPoll) or receives a Push
(MsgWatchPush). It is a subscription token, not authority.
### LiquidityRequest
LiquidityRequest is the off-chain request to COMMIT (open/increase) a funded
position. LiquidityDelta MUST be positive for a commit (it funds the position from
C); the negative-delta (collect/decrease/cancel) path is V4CollectSession /
openCancel, whose credit rides the DS01 settlement.
### ModifyLiquidityArgs
ModifyLiquidityArgs are the V4 ModifyLiquidityParams tuple fields. LiquidityDelta
is signed: >0 add, &lt;0 remove. The session sets the sign per action (commit vs
collect/cancel); the SIGN is what distinguishes a funding C->D commit from a
value-returning D->C removal — both ride this one selector, exactly as the
position facade does.
### PoolKeyArgs
PoolKeyArgs are the V4 PoolKey tuple fields the swap selector takes:
(currency0, currency1, fee uint24, tickSpacing int24, hooks address). For the
native seam the pool is identified by these; the keeper/market binds them to a
D market. dexsession fills them from the request's market mapping.
### PreparedIntent
PreparedIntent is the OUTPUT of prepareSwapIntent: everything the user needs to
sign a normal C tx to 0x9999 that creates the funded C->D intent. It is bytes,
not authority:
- To — the 0x9999 settlement address (the precompile).
- Calldata — selector + ABI-encoded args for the on-chain swap entry.
- HookData — the V4 hookData the 0x9999 handler consumes (routing payload).
- IntentID — the deterministic id the on-chain SubmitSwapIntent will mint
(derived identically here; lets the watch locate the D result).
- QuotedOut — the ESTIMATE used to build it (informational, not enforceable).
MUST NOT: reserve funds off-chain, claim a fill final, return an amountOut the
chain trusts. The QuotedOut is explicitly informational; the enforceable floor
is MinAmountOut baked into Calldata, which the chain/D check.
### Promise
Promise is a future for an async DexSession call result of type T. It is safe
for one producer (the dispatching goroutine) and many consumers (Await is
idempotent and concurrent-safe).
```go
func (p *Promise[T]) Await(ctx context.Context) (T, error)
```
### QuoteCap
QuoteCap permits read-only book/state queries. It can quote and observe; it
cannot build an intent, notify, settle, or administer.
```go
func (c QuoteCap) Authority() Authority
```
### QuoteRequest
QuoteRequest asks the D book for an estimated output. Read-only.
### QuoteResult
QuoteResult is an ESTIMATE. AmountOut is informational only: the chain never
trusts it. The user still encodes their own MinAmountOut in the signed C tx,
and D enforces slippage at match. A stale or malicious quote can only mislead
a UI — it cannot move value, and a bad quote that misses MinAmountOut makes
the on-chain swap revert (TestZAP_StaleResponse_BadQuoteMissesMinOutOrReverts).
### RoutePhase
RoutePhase is the lifecycle of a route as the off-chain watcher observes it. Like
IntentPhase, NONE of these moves value; Committed means the watcher saw the ONE
final D->C export, Refunded means it saw the ONE refund export.
### RouteRequest
RouteRequest is the off-chain request to PREPARE a multi-hop route intent. Like a
SwapIntentRequest it reserves no funds and returns calldata; the difference is
Path — the ordered list of marketIDs D walks (A->B->C). AmountIn is the SINGLE
input the user locks on C; MinAmountOut is the floor on the FINAL output, enforced
by D at the end of the route (a route that cannot deliver MinAmountOut of the final
asset refunds the input — it never partially settles an intermediate asset).
### RouteStatus
RouteStatus is one poll/push of a route watch. HopIndex/HopAmountOut describe the
CURRENT hop (orchestration ESTIMATES). Ref is the FINAL export POINTER, valid only
when Phase==RouteCommitted (the final output) or RouteRefunded (the refund). There
is no per-hop Ref field — by construction there is no per-hop settleable object.
### RouteVenue
RouteVenue is the route backend a Venue MAY ALSO implement. It is a SEPARATE
interface (composition, not interface-expansion) so the base Venue — and every
existing implementation — stays unchanged: a server type-asserts for RouteVenue
and serves routes only when the backend provides it. Like Venue, it has NO
credit/settle method: it reports route progress and the final export POINTER; it
never moves value. Exactly TWO methods — one trigger, one status — keeps it DRY.
### RouteWatch
RouteWatch is the subscription a route NotifyCToDExport returns. Poll observes the
current route phase + hop progress; streamUntilFinal resolves the ONE final (or
refund) export pointer. It NEVER moves value — the terminal pointer feeds the one
DS01 settlement, which the chain judges.
```go
func (w RouteWatch) IntentID() ID
```
```go
func (w RouteWatch) Poll(ctx context.Context) (RouteStatus, error)
```
### SessionConfig
SessionConfig configures a client session (normally produced by Bootstrap, but
exported so a node can construct one directly against a known peer).
### SettlementCap
SettlementCap permits asking C to import an EXISTING D->C object (build the
finalization calldata / submit a pointing tx). It cannot credit C through RPC,
nor substitute recipient/asset/amount — the chain binds those to the object.
```go
func (c SettlementCap) Authority() Authority
```
### SettlementMode
SettlementMode tells the caller HOW the settlement is realised. In both modes
the credit happens ON-CHAIN by consuming the real object — never via this RPC.
### SettlementSubmitResult
SettlementSubmitResult is the OUTPUT of importSettlement. It NEVER credits C; it
returns the calldata to consume the object (or the hash of a tx that points at
it). The on-chain ImportSettlement is the sole credit path.
### StateKind
StateKind selects which read-only view getState returns.
### StateRequest
StateRequest is a read-only DEX state query.
### StateResult
StateResult is the read-only answer. Like a quote it is informational: a
balance reported here is NOT a spendable claim the chain honours — only a
consumed D->C object credits C.
### SwapIntentRequest
SwapIntentRequest is the off-chain request to PREPARE a C->D swap intent. The
session validates params, estimates a quote, derives the intent id, and
returns a PreparedIntent (calldata). It reserves NO funds and returns NO
enforceable amountOut.
### V4Action
V4Action is the kind of V4 action a session capability is scoped to. A scoped
capability is confined to exactly one kind — a swap cap cannot drive a liquidity
action and vice versa.
```go
func (a V4Action) String() string
```
### V4ActionScope
V4ActionScope binds a session capability to ONE V4 action. Every field that
distinguishes one action from another is here; DeriveSessionID hashes them into a
stable id. Two sessions for different intents/pools/params/kinds derive different
ids and their capabilities cannot be cross-used.
NetworkID — the Lux network the action runs on.
CChainID — the C-Chain (EVM balance authority).
DChainID — the D-Chain (matching authority).
Addr9999 — the on-chain settlement authority (the precompile address).
Account — the caller / account the action is for (bound as the object owner).
PoolKeyHash — hash of the V4 PoolKey (which market). For a route, hash of the
whole path (see DeriveRoutePathHash).
ParamsHash — hash of the action params (direction+amount for a swap; tick range
+delta for liquidity; the collected/cancelled selector for those).
Kind — the V4Action this scope permits.
IntentID — the deterministic intent id (for swap/route) the action targets.
Zero for actions whose object is not an intent (a position commit
binds by PoolKeyHash+ParamsHash+Account instead).
### V4CollectSession
V4CollectSession is the removal lifecycle (collect/decrease/cancel). It requests the
removal, watches for the ONE D->C export, and settles it via the DS01 credit path.
```go
func (s *V4CollectSession) Close()
```
```go
func (s *V4CollectSession) IntentID() ID
```
```go
func (s *V4CollectSession) OnExportReady(ctx context.Context, watch IntentWatch) *Promise[DExportRef]
```
```go
func (s *V4CollectSession) SessionID() ID
```
```go
func (s *V4CollectSession) WriteNotify(ctx context.Context, intent *Promise[PreparedIntent]) *Promise[IntentWatch]
```
```go
func (s *V4CollectSession) WritePrepareCSettlement(ctx context.Context, ref *Promise[DExportRef]) *Promise[SettlementSubmitResult]
```
```go
func (s *V4CollectSession) WriteRequest(ctx context.Context) *Promise[PreparedIntent]
```
### V4Config
V4Config configures the V4 precompile session. The chain identifiers and the 0x9999
address are fixed for the deployment; they bind every action's scope.
### V4Dir
V4Dir classifies a message by who originates it on the bidirectional plane.
### V4Event
V4Event is the typed D->C read a session surfaces to the caller (the orchestration
stream). It is a VALUE: a label + the orchestration payload + an OPTIONAL pointer.
It NEVER carries authority over a balance:
- Type — the V4MsgType (always a DirDToC read).
- SessionID — the action the event belongs to (binds it to its session).
- IntentID — the originating intent (correlation).
- EstAmount — an ESTIMATE (quote / matched-out / hop-out). NOT a credit; the
chain never trusts it. Exactly the QuotedOut discipline, extended to matches.
- HopIndex — the route hop this event describes (route events only).
- Ref — the D->C export POINTER, set ONLY on *_EXPORT_READY / REFUND_READY.
The credit still happens on-chain when the real object behind Ref is consumed.
- Reason — human text for ERROR / HALTED / REFUND.
```go
func (e V4Event) HasRef() bool
```
### V4LiquiditySession
V4LiquiditySession is the position-commit lifecycle. It builds modifyLiquidity
(+delta) calldata the user signs to fund the position, notifies D, and observes the
position opening. It has NO settlement method (a commit credits nothing).
```go
func (s *V4LiquiditySession) Close()
```
```go
func (s *V4LiquiditySession) IntentID() ID
```
```go
func (s *V4LiquiditySession) SessionID() ID
```
```go
func (s *V4LiquiditySession) WriteNotifyCToDExport(ctx context.Context, intent *Promise[PreparedIntent]) *Promise[IntentWatch]
```
```go
func (s *V4LiquiditySession) WritePrepareCommit(ctx context.Context) *Promise[PreparedIntent]
```
### V4MsgType
V4MsgType is a control-plane message-type. The constants below are the V4-native
SEMANTIC names the spec mandates. Each maps to an underlying wire frame (Msg* in
wire.go) or to a LOCAL lifecycle transition (no wire frame — e.g. *_OPEN mints a
scoped capability client-side). A session is a state machine that emits/consumes
these; the mapping (v4Wire) keeps the wire the single source of transport truth.
```go
func (t V4MsgType) CreditsValue() bool
```
```go
func (t V4MsgType) Dir() V4Dir
```
```go
func (t V4MsgType) String() string
```
### V4PrecompileSession
V4PrecompileSession is the per-action session factory the spec mandates. It wraps a
restricted DexSession (the bootstrap grant) and opens a NARROWLY-SCOPED session per
V4 action. Each open* mints a scopedCap confined to &#123;networkID, cChainID, dChainID,
0x9999, account, poolKeyHash, paramsHash, intentID} for exactly that action, so a
session cannot drive any other intent/pool/params/kind.
```go
func NewV4PrecompileSession(cfg V4Config) (*V4PrecompileSession, error)
```
```go
func (v *V4PrecompileSession) OpenCancel(req CollectRequest) (*V4CollectSession, error)
```
```go
func (v *V4PrecompileSession) OpenCollect(req CollectRequest) (*V4CollectSession, error)
```
```go
func (v *V4PrecompileSession) OpenModifyLiquidity(req LiquidityRequest) (*V4LiquiditySession, error)
```
```go
func (v *V4PrecompileSession) OpenRoute(req RouteRequest) (*V4RouteSession, error)
```
```go
func (v *V4PrecompileSession) OpenState(account Account, marketID ID) (*V4StateSession, error)
```
```go
func (v *V4PrecompileSession) OpenSwap(req SwapIntentRequest) (*V4SwapSession, error)
```
### V4RouteSession
V4RouteSession is the multi-hop route lifecycle state machine. It prepares EXACTLY
ONE C->D input intent (carrying the path), streams hop progress as orchestration,
and settles the ONE final D->C export. It NEVER produces an intermediate-asset
settlement.
```go
func (s *V4RouteSession) Close()
```
```go
func (s *V4RouteSession) IntentID() ID
```
```go
func (s *V4RouteSession) OnFinalExport(ctx context.Context, watch RouteWatch) *Promise[DExportRef]
```
```go
func (s *V4RouteSession) Path() []ID
```
```go
func (s *V4RouteSession) ReadStream(ctx context.Context, watch RouteWatch) <-chan V4Event
```
```go
func (s *V4RouteSession) SessionID() ID
```
```go
func (s *V4RouteSession) WriteNotifyCToDExport(ctx context.Context, intent *Promise[PreparedIntent]) *Promise[RouteWatch]
```
```go
func (s *V4RouteSession) WritePrepareCSettlement(ctx context.Context, ref *Promise[DExportRef]) *Promise[SettlementSubmitResult]
```
```go
func (s *V4RouteSession) WritePrepareIntent(ctx context.Context) *Promise[PreparedIntent]
```
### V4StateSession
V4StateSession is the read-only streaming session: quotes and book/state. It cannot
build calldata, notify, or settle — it observes.
```go
func (s *V4StateSession) Close()
```
```go
func (s *V4StateSession) ReadQuote(ctx context.Context, amountIn uint64, zeroForOne bool) *Promise[QuoteResult]
```
```go
func (s *V4StateSession) ReadState(ctx context.Context, kind StateKind, account Account, asset ID) *Promise[StateResult]
```
```go
func (s *V4StateSession) SessionID() ID
```
```go
func (s *V4StateSession) StreamQuotes(ctx context.Context, amountIn uint64, zeroForOne bool) <-chan V4Event
```
### V4SwapSession
V4SwapSession is the single-swap lifecycle state machine. It exposes the
bidirectional message set as typed methods that compose the clientSession ops and
pipeline via Promise. It holds a scopedCap confined to ONE swap.
```go
func (s *V4SwapSession) Close()
```
```go
func (s *V4SwapSession) IntentID() ID
```
```go
func (s *V4SwapSession) OnExportReady(ctx context.Context, watch IntentWatch) *Promise[DExportRef]
```
```go
func (s *V4SwapSession) ReadStream(ctx context.Context, watch IntentWatch) <-chan V4Event
```
```go
func (s *V4SwapSession) Run(ctx context.Context) FlowStages
```
```go
func (s *V4SwapSession) SessionID() ID
```
```go
func (s *V4SwapSession) WriteNotifyCToDExport(ctx context.Context, intent *Promise[PreparedIntent]) *Promise[IntentWatch]
```
```go
func (s *V4SwapSession) WritePrepareCSettlement(ctx context.Context, ref *Promise[DExportRef]) *Promise[SettlementSubmitResult]
```
```go
func (s *V4SwapSession) WritePrepareIntent(ctx context.Context) *Promise[PreparedIntent]
```
### Venue
Venue is the read + trigger backend a DexSession server delegates to. It is the
D-Chain CLOB venue and a C/D state reader. CRITICALLY, it has no credit/settle
method: the orchestration server cannot move value because its backend cannot
either. (A real deployment backs Venue with the lux/dex ZAP CLOB client over the
existing clob_* transport for quotes, plus a chain-state reader for exports.)
### WatchCap
WatchCap permits subscribing to a notified intent's D result. It cannot move
value; OnCommitted yields a POINTER (DExportRef), not a credit.
```go
func (c WatchCap) Authority() Authority
```
@@ -0,0 +1,227 @@
---
title: "forward"
description: "Package forward is the canonical HIP-0110 'HTTP-over-ZAP' contract."
---
{/* Generated from godoc by tools/docgen — edit the Go source, not this file. */}
```go
import "github.com/luxfi/zap/forward"
```
Package forward is the canonical HIP-0110 "HTTP-over-ZAP" contract.
It is the single source of truth for the gateway↔backend boundary,
replacing the two inconsistent ad-hoc encodings that previously lived
in the gateway repo (zap_wire.go and zap_backend.go). Three envelopes
cover the entire boundary:
Forward — gateway → backend, one per inbound HTTP request, carrying
the edge-validated identity (so the backend trusts the edge).
Response — backend → gateway, the buffered HTTP response for a Forward.
Push — backend → gateway, one per server-initiated stream frame
(SSE / WebSocket), correlated to a Forward by ConnID.
On-wire bytes are github.com/luxfi/zap Objects. Every field is packed
at an explicit byte offset within the object's fixed payload.
# Offset discipline
luxfi/zap's ObjectBuilder.SetBytes / SetText write an 8-byte slot at
the field offset: &#123;relOffset uint32, length uint32}. The variable-
length data tail is appended after the fixed section in Finish(), and
the relOffset is patched to point at it. Consequently EVERY text or
bytes field consumes 8 bytes of fixed payload — adjacent text fields
MUST be spaced 8 bytes apart or their slots overlap and corrupt each
other (proven empirically: a 4-byte-spaced layout drops the body).
Fixed scalars use their natural width (bool=1, uint32=4, int64=8) and
are placed on a natural boundary. These offsets are the contract;
changing one is a wire break.
## Functions
### BuildForward
```go
func BuildForward(f Forward) (*zaplib.Message, error)
```
BuildForward serializes f into a ZAP message tagged MsgTypeForward.
### BuildPush
```go
func BuildPush(p Push) (*zaplib.Message, error)
```
BuildPush serializes p into a ZAP message tagged MsgTypePush.
### BuildResponse
```go
func BuildResponse(r Response) (*zaplib.Message, error)
```
BuildResponse serializes r into a ZAP message. Status rides as a
uint32; body and headers-JSON follow, matching the Response read order.
### HandleReversePush
```go
func HandleReversePush(ctx context.Context, from string, msg *zaplib.Message) (*zaplib.Message, error)
```
HandleReversePush is the zaplib.Handler for MsgTypePush. It decodes the
Push, looks up the sink for its ConnID, and delivers. An unknown ConnID
(client already gone) is a silent no-op. A delivery error drops the sink.
### RegisterReversePush
```go
func RegisterReversePush(connID string, sink PushSink)
```
RegisterReversePush records the sink for an active SSE / WS subscription.
### RegisterReversePushHandler
```go
func RegisterReversePushHandler(node *zaplib.Node)
```
RegisterReversePushHandler installs HandleReversePush on node so a
Forwarder's node receives backend-initiated Push frames.
### Relay
```go
func Relay(node *zaplib.Node, gate Gate, pick PeerPicker)
```
Relay registers the gateway relay handler on node for MsgTypeForward.
On each inbound Forward the relay:
1. ReadForward(msg) — decodes the envelope (Body stays a sub-slice;
it is not decoded).
2. gate(ctx, &f) — runs auth + the balance gate on the envelope. A
non-nil deny Response short-circuits: the relay returns it
immediately via BuildResponse and NEVER touches a backend. A
non-nil error is returned to the ZAP layer. Otherwise the gate has
mutated f's identity fields in place and the request is allowed.
3. BuildForward(f) — re-emits the Forward carrying the injected
identity (the single per-request envelope re-encode; see the
budget note above), then node.Call(ctx, pick(f.Path), augmented)
forwards it to the chosen backend.
4. returns the backend's reply *zaplib.Message VERBATIM — no
ReadResponse/BuildResponse round-trip, the response bytes pass
through unchanged.
### Serve
```go
func Serve(node *zaplib.Node, h http.Handler)
```
Serve registers the canonical MsgTypeForward handler on node, dispatching
each Forward envelope to h. The handler reconstructs the *http.Request,
injects the edge-validated identity as X-* headers (so the backend trusts
the edge), serves it through h, and returns a Response envelope carrying
the captured status, headers, and body.
Streaming (SSE / chunked) is not yet emitted as Push frames: the full
response is buffered and returned as one Response. The extension point is
marked below.
### UnregisterReversePush
```go
func UnregisterReversePush(connID string)
```
UnregisterReversePush drops the sink when the client connection closes.
## Types
### Forward
Forward is the typed view of the gateway→backend envelope. Path
includes the raw query string (there is no separate query field).
```go
func ReadForward(msg *zaplib.Message) Forward
```
### Forwarder
Forwarder is the gateway-side client: it tunnels an *http.Request to a
backend Peer over ZAP and returns the *http.Response. It is an
http.RoundTripper, so it drops into any http.Client/transport chain.
```go
func (f *Forwarder) RoundTrip(req *http.Request) (*http.Response, error)
```
### Gate
Gate is the gateway-supplied policy run on each inbound Forward
ENVELOPE. It reads f.Headers (to find and validate the JWT) and
f.Path, runs the prepaid balance gate, and EITHER:
- mutates f's identity fields in place — f.TenantID, f.UserID,
f.IsAdmin, f.Permissions — and returns (nil, nil) to ALLOW, or
- returns a non-nil *Response to short-circuit (deny), e.g.
&#123;Status: 401} for a bad token or &#123;Status: 402, Body:
insufficient_balance JSON} when the balance gate trips.
A Gate MUST NOT read or copy f.Body. It operates on the envelope only.
If it returns a non-nil error the relay surfaces it to the ZAP layer
(the caller's Call fails); use a deny *Response for ordinary policy
rejections so the client sees a clean HTTP status.
### PeerPicker
PeerPicker chooses the backend node for a request path. It mirrors
pickBackend from the gateway: /v1/base/* routes to the base peer,
everything else routes to the cloud peer (which fans out to the
per-subsystem services). It is given the Forward's Path (raw query
included) and returns the backend NodeID to Call.
### Push
Push is the typed view of the backend→gateway reverse-push envelope.
```go
func ReadPush(msg *zaplib.Message) Push
```
### PushSink
PushSink delivers a Push frame to a live client connection.
### Response
Response is the typed view of the backend→gateway response envelope.
```go
func ReadResponse(msg *zaplib.Message) Response
```
@@ -0,0 +1,540 @@
---
title: "handshake"
description: "Package handshake implements SPEC-ZAP-PQ-v1: the native post-quantum handshake and AEAD framing for ZAP."
---
{/* Generated from godoc by tools/docgen — edit the Go source, not this file. */}
```go
import "github.com/luxfi/zap/handshake"
```
Package handshake implements SPEC-ZAP-PQ-v1: the native post-quantum
handshake and AEAD framing for ZAP. See docs/SPEC-ZAP-PQ-v1.md for the
authoritative wire specification.
The package decomposes the protocol along value/behaviour lines:
- Identity, Profile, SuiteID : values (data, no behaviour)
- Transcript, SessionKeys : derived values
- Frame&#123;Hello, KEMInit, ...} : wire codecs
- Initiator, Responder : state machines
- Session : the post-handshake AEAD stream
- ReplayCache, PSKStore : independent storage policies
Nothing in this package imports net.Conn — it works against any
io.ReadWriter so it can be exercised over in-memory pipes in tests.
## Functions
### PSKID
```go
func PSKID(psk [PSKKeyLen]byte) [PSKIDLen]byte
```
PSKID derives the §12.1 psk_id (16-byte truncation of SHA3-256 of
the resumption_psk). Pinned in code so issuance and lookup agree.
### Ratchet
```go
func Ratchet(kPrev [AEADKeyLen]byte, epoch uint8) (kNext [AEADKeyLen]byte, saltNext [NonceSaltLen]byte)
```
Ratchet implements §13 — derive the next per-direction key and
nonce salt from the current key.
info_key = LBL_REKEY ∥ 0x00 ∥ epoch_n ∥ 0x00
info_salt = LBL_REKEY ∥ 0x00 ∥ epoch_n ∥ 0x01
k_&#123;n+1} = HKDF-Expand(k_n, info_key, 32)
salt_&#123;n+1} = HKDF-Expand(k_n, info_salt, 4)
Two distinct Expand calls (not a single 36-byte read) — the
info bytes differ so the output streams are independent.
The caller is responsible for zeroising the old k_n / salt_n.
## Types
### AlertCode
AlertCode is the §14 error code carried in ALERT frame bodies.
Each code maps 1:1 to a sentinel error so callers can branch with
errors.Is on the named sentinel rather than parsing the byte.
```go
func (c AlertCode) String() string
```
### AlertFrame
```go
func DecodeAlert(body []byte) (*AlertFrame, error)
```
```go
func (a *AlertFrame) Encode() []byte
```
### AuthFrame
```go
func DecodeAuth(body []byte) (*AuthFrame, error)
```
```go
func (a *AuthFrame) Encode() ([]byte, error)
```
### AuthRole
AuthRole is the §6.4 role byte signed by each side.
```go
func (r AuthRole) Label() []byte
```
### ClientPSK
ClientPSK is the value the initiator caches after a successful full
handshake. It is the §12.1 record minus the server-side state.
PeerID is the verified responder identity from the ORIGINAL full
handshake. The resumed handshake re-derives session keys but does
NOT re-verify the responder's static_pk (possession of the PSK is
the authentication, §12.2), so the trust anchor must be carried
forward from when the responder's signature was last checked.
Callers reading [Session.PeerID] after a resumed handshake see this
value, ensuring authorization decisions remain anchored to the
identity that the initiator originally pinned.
Fields are exported ONLY to support persistence / serialization
(KMS round-trip, sticky-session cache). Do NOT construct ClientPSK
literals by hand — populating PeerID with a value that wasn't
verified during the original handshake silently corrupts the
resumed Session.PeerID() return. Always go through MakeClientPSK
or copy a struct returned by Session.ResumptionPSK().
```go
func MakeClientPSK(psk [PSKKeyLen]byte, peerID [IDLen]byte, now time.Time) ClientPSK
```
### DataFrame
```go
func DecodeData(body []byte) (*DataFrame, error)
```
```go
func (d *DataFrame) Encode() []byte
```
### FrameType
FrameType encodes the outer envelope type byte (§5, §6).
### HelloFrame
```go
func DecodeHello(body []byte) (*HelloFrame, error)
```
```go
func (h *HelloFrame) Encode() ([]byte, error)
```
### HelloPSKFrame
```go
func DecodeHelloPSK(body []byte) (*HelloPSKFrame, error)
```
```go
func (h *HelloPSKFrame) Encode() []byte
```
### Identity
Identity holds a node's or VM's static ML-DSA-65 keypair.
The PrivateKey field is non-nil for our own identity; for a pinned
peer identity (Initiator.Expected, Responder.Local), PrivateKey is
nil and only PublicKey is consulted.
```go
func GenerateIdentity() (*Identity, error)
```
```go
func GenerateIdentityFrom(r io.Reader) (*Identity, error)
```
```go
func IdentityFromPrivate(priv *mldsa.PrivateKey) (*Identity, error)
```
```go
func IdentityFromPublicBytes(pub []byte) (*Identity, error)
```
```go
func (id *Identity) ID() [IDLen]byte
```
```go
func (id *Identity) PublicBytes() []byte
```
```go
func (id *Identity) Sign(rand io.Reader, h2 [TranscriptLen]byte, role AuthRole, suite SuiteID) ([]byte, error)
```
```go
func (id *Identity) SignDeterministic(h2 [TranscriptLen]byte, role AuthRole, suite SuiteID) ([]byte, error)
```
```go
func (id *Identity) VerifyAuth(
h2 [TranscriptLen]byte,
role AuthRole,
suite SuiteID,
sig []byte,
) error
```
### Initiator
Initiator runs the §4 client side of the handshake.
Required fields:
- Local: this side's static ML-DSA-65 identity (must have a
private key).
Optional fields:
- Expected: pin the responder's identity. If non-nil, the
handshake aborts with ErrVMIdentityMismatch when
SHA3-256(responder_static_pk) ≠ Expected.ID().
- Profile: chain-security stance. Affects only what the wrapper
does on magic-prefix mismatch; once the handshake is engaged
the profile is enforced through PQMode + OfferedSchemes.
- PQMode: HELLO.pq_mode byte. Defaults to PQModePQOnly under
StrictPQ / FIPS, PQModeClassicalPermitted otherwise.
- Suite: ciphersuite byte. Defaults to SuiteX25519MLKEM (0x01).
- OfferedSchemes: HELLO.offered_schemes. Defaults to [Suite].
- Resume: cached PSK to attempt resumption with. If nil or
expired, Initiator runs a full handshake.
- Rand: entropy source for ephemerals + signing nonces. Defaults
to crypto/rand.Reader. KAT tests inject a deterministic reader.
- Now: clock for HELLO timestamps. Defaults to time.Now.
```go
func (i *Initiator) Run(conn io.ReadWriter) (*Session, error)
```
### KEMInitFrame
```go
func DecodeKEMInit(body []byte) (*KEMInitFrame, error)
```
```go
func (k *KEMInitFrame) Encode() []byte
```
### KEMReplyFrame
```go
func DecodeKEMReply(body []byte) (*KEMReplyFrame, error)
```
```go
func (k *KEMReplyFrame) Encode() ([]byte, error)
```
### PQMode
PQMode encodes HELLO.pq_mode (§6.1).
### PSKStore
PSKStore implements §12 PSK issuance and lookup on the responder.
- Issue records a fresh (psk_id, resumption_psk, client_id) at the
end of a full handshake.
- Redeem looks up a psk_id presented in HELLO_PSK; on hit it
atomically marks the entry consumed (single-use, §12.2).
PSKs expire after PSKLifetimeSec. Expired or unknown lookups
return (nil, false) and the caller MUST send ALERT 0x08.
An ABSENT store (PSKStore == nil) disables resumption. The
responder treats every HELLO_PSK as ErrPSKUnknown.
```go
func NewPSKStore() *PSKStore
```
```go
func (s *PSKStore) Issue(psk [PSKKeyLen]byte, clientID [IDLen]byte) [PSKIDLen]byte
```
```go
func (s *PSKStore) Len() int
```
```go
func (s *PSKStore) Redeem(id [PSKIDLen]byte) (psk [PSKKeyLen]byte, clientID [IDLen]byte, ok bool)
```
```go
func (s *PSKStore) Sweep()
```
### Profile
Profile is the chain-security stance applied at the wire boundary
(§6.0). It is intentionally local — callers map their richer
notions (lux/pq.Mode, ChainConfig) onto this small enum.
### RekeyFrame
```go
func DecodeRekey(body []byte) (*RekeyFrame, error)
```
```go
func (r *RekeyFrame) Encode() []byte
```
### ReplayCache
ReplayCache holds the §11 nonce-cache state for a Responder.
Two independent gates protect against replay:
1. Timestamp window: |now - timestamp_ns| ≤ 30s
2. Nonce cache: dedup on (client_id, client_random)
Implementation: a two-generation map rotated every TTL window.
New entries land in `active`; on insert we also probe `frozen`
(the previous generation, still inside its TTL). When `now -
frozenAt >= ttl`, `frozen` is dropped wholesale and the current
`active` becomes the new `frozen`. This is O(1) per insert and
O(1) per generation-flip (just rebind the map pointers). The
worst-case admission window is between ttl and 2×ttl — any
(id, rand) tuple is remembered for AT LEAST ttl seconds after
its first appearance, which is what §11 requires.
Memory bound: 2× maxLen entries (one ttl-window of each
generation). At the §3 design budget of 2^20 entries per
generation, that's ~6 MiB of `(clientID, clientRandom)` tuples in
memory — within the 4 MiB Cuckoo target order-of-magnitude, with
the operational advantage of O(1) flush instead of O(N) sweep.
The previous implementation (single map + inline sweep at
maxLen) was correct but degraded to O(N) per insert once the cap
was reached, which a fuzzer or attacker can drive into the slow
path. The two-generation rotation eliminates that.
```go
func NewReplayCache() *ReplayCache
```
```go
func (c *ReplayCache) CheckTimestamp(timestampNS uint64) error
```
```go
func (c *ReplayCache) Len() int
```
```go
func (c *ReplayCache) SeenOrAdd(clientID [IDLen]byte, clientRandom [ClientRandLen]byte) bool
```
```go
func (c *ReplayCache) Sweep()
```
### Responder
Responder runs the §4 server side of the handshake.
Required fields:
- Local: server's static ML-DSA-65 identity (must have a
private key).
Optional fields:
- Profile: chain-security stance. Under StrictPQ / FIPS the
responder refuses HELLOs that advertise PQModeClassicalPermitted
or offered_schemes lists containing non-PQ suites.
- AcceptedSuites: server-side ciphersuite allowlist. Empty means
&#123;SuiteX25519MLKEM}.
- ReplayCache: §11 replay state. nil disables cache lookups
(timestamp-only protection — production must supply a cache).
- PSKStore: §12 PSK issuer + redeemer. nil disables resumption.
Rand / Now: deterministic overrides for KAT testing.
```go
func (rs *Responder) Run(conn io.ReadWriter) (*Session, error)
```
### Session
Session is the post-handshake AEAD-keyed stream specified by §9, §13.
Send → produces one DATA frame on the wire.
Recv → consumes one DATA (or REKEY) frame and returns the
plaintext payload of a DATA frame.
Send and Recv are independently safe to call concurrently against
the same Session, each under their own mutex.
A Session is NOT net.Conn directly — the package-level conn_pq.go
adapter wraps it with Read/Write semantics for legacy callers.
```go
func (s *Session) Close() error
```
```go
func (s *Session) Epoch() uint8
```
```go
func (s *Session) PeerID() [IDLen]byte
```
```go
func (s *Session) Recv() ([]byte, error)
```
```go
func (s *Session) Rekey() error
```
```go
func (s *Session) ResumptionPSK() *ClientPSK
```
```go
func (s *Session) Role() AuthRole
```
```go
func (s *Session) Send(payload []byte) error
```
### SessionKeys
SessionKeys is §8.3's five-expand output — the post-handshake
secrets every Session is keyed from.
```go
func DeriveResumed(
h2psk [TranscriptLen]byte,
x25519Shared [X25519SharedLen]byte,
resumptionPSK [PSKKeyLen]byte,
) SessionKeys
```
```go
func DeriveSession(
h2 [TranscriptLen]byte,
x25519Shared [X25519SharedLen]byte,
mlkemShared [MLKEM768SharedLen]byte,
) SessionKeys
```
```go
func (k *SessionKeys) Zeroize()
```
### SuiteID
§3 / §3.2 ciphersuite registry. Only 0x01 is wire-callable today.
```go
func (s SuiteID) IsValid() bool
```
### Transcript
Transcript chains SHA3-256 over every handshake byte (§7).
The state machine:
NewTranscript(suite)
AbsorbHello(helloBody) -> commits H_0
AbsorbKEM(initBody, replyBody) -> commits H_1
FinishFull(pkI, pkR, schemes) -> returns H_2 (full handshake)
-- OR --
FinishPSK(serverEphX25519Pub) -> returns H_2_psk (resumed handshake)
Each step replaces the internal SHA3-256 state with the digest of
the previous chain ∥ new material. This matches the spec's
definition of H_n as `SHA3-256(H_&#123;n-1} ∥ &lt;new bytes>)`.
The encoded body of each frame is whatever is between the outer
type/length fields — i.e. the slice the codec returns / consumes.
Callers MUST feed exactly those bytes (not the outer envelope) so
both sides agree on the transcript without re-running the codec.
```go
func NewTranscript(suite SuiteID) *Transcript
```
```go
func (t *Transcript) AbsorbHello(hello []byte)
```
```go
func (t *Transcript) AbsorbKEM(init, reply []byte)
```
```go
func (t *Transcript) FinishFull(pkI, pkR []byte, schemes []SuiteID) [TranscriptLen]byte
```
```go
func (t *Transcript) FinishPSK(serverEphX25519Pub []byte) [TranscriptLen]byte
```
```go
func (t *Transcript) H0() [TranscriptLen]byte
```
```go
func (t *Transcript) H1() [TranscriptLen]byte
```
```go
func (t *Transcript) H2() [TranscriptLen]byte
```
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,84 @@
---
title: "mcp"
description: "Package mcp provides a bridge between MCP (Model Context Protocol) servers and ZAP for high-performance tool calling."
---
{/* Generated from godoc by tools/docgen — edit the Go source, not this file. */}
```go
import "github.com/luxfi/zap/mcp"
```
Package mcp provides a bridge between MCP (Model Context Protocol) servers
and ZAP for high-performance tool calling.
This bridge auto-discovers MCP servers and exposes their capabilities via ZAP,
providing 10-30x performance improvement over native MCP JSON-RPC.
## Types
### Bridge
Bridge manages multiple MCP servers and exposes them via ZAP
```go
func NewBridge(node *zap.Node) *Bridge
```
```go
func (b *Bridge) AddServer(name, command string, args ...string) error
```
```go
func (b *Bridge) AddServerConfig(cfg ServerConfig) error
```
```go
func (b *Bridge) CallTool(ctx context.Context, toolID uint32, args map[string]interface{}) (interface{}, error)
```
```go
func (b *Bridge) CallToolByName(ctx context.Context, name string, args map[string]interface{}) (interface{}, error)
```
```go
func (b *Bridge) GetTools() []Tool
```
```go
func (b *Bridge) Stop()
```
### MCPServer
MCPServer represents a connection to an MCP server
```go
func (s *MCPServer) CallTool(name string, args map[string]interface{}) (interface{}, error)
```
```go
func (s *MCPServer) ListTools() ([]Tool, error)
```
```go
func (s *MCPServer) Start() error
```
```go
func (s *MCPServer) Stop()
```
### ServerConfig
ServerConfig configures an MCP server
### Tool
Tool represents an MCP tool capability
@@ -0,0 +1,50 @@
---
title: "zapmdns"
description: "Package zapmdns publishes and browses _hanzo._tcp.local."
---
{/* Generated from godoc by tools/docgen — edit the Go source, not this file. */}
```go
import "github.com/luxfi/zap/mdns/go"
```
Package zapmdns publishes and browses _hanzo._tcp.local. services per
HIP-0069. Single source of truth for the service-type, TXT key list and
role enum across every Go service in the Hanzo stack (kms, mpc, base,
ingress, gateway, static).
## Types
### PublishOptions
PublishOptions captures the TXT keys defined by HIP-0069.
### Publisher
Publisher retracts the announcement on Close.
```go
func Publish(opts PublishOptions) (*Publisher, error)
```
```go
func (p *Publisher) Close() error
```
### Service
Service is one peer found on the LAN.
```go
func Browse(ctx context.Context, role string, timeout time.Duration) ([]Service, error)
```
```go
func (s Service) URL() string
```
@@ -0,0 +1 @@
{"pages":["go"]}
@@ -0,0 +1 @@
{"pages":["bench","dexsession","forward","handshake","mcp","mdns","quic","tools","transport","v1","zapclient","zapweb"]}
@@ -0,0 +1,279 @@
---
title: "quic"
description: "Package quic implements the QUIC transport for the ZAP messaging substrate."
---
{/* Generated from godoc by tools/docgen — edit the Go source, not this file. */}
```go
import "github.com/luxfi/zap/quic"
```
Package quic implements the QUIC transport for the ZAP messaging substrate.
QUIC offers, compared to ZAP's existing TCP+TLS transport:
- Stream multiplexing: N concurrent RPCs over one connection without
head-of-line blocking.
- Connection migration: the connection survives client-IP changes
(Wi-Fi-to-LTE, NAT rebinding, etc.) because the QUIC connection ID
is independent of the 5-tuple.
- 0-RTT resumption: cached session tickets let the second handshake to
a peer complete without a round trip before app data flows.
- TLS 1.3 with the X25519MLKEM768 hybrid post-quantum key exchange
(IANA NamedGroup 0x11ec) baked in.
# Wire format
One ZAP message is one length-prefixed Cap'n Proto frame on a QUIC stream.
The frame format is identical to the TCP transport:
[4-byte little-endian length][ZAP message bytes]
Two stream patterns are used:
- Bidirectional streams carry one request/response exchange. After the
response is written the server side closes its half of the stream and
the client closes its half on Recv, freeing the stream ID. This maps
onto Node.Call.
- Unidirectional streams from the server carry one-way notifications
and subscription deliveries. The receiver routes each frame through
the same handler dispatch as the TCP path.
The control stream (the first bidirectional stream opened by the dialer)
carries the 64-byte ZAP node-identity handshake exchanged before any RPC.
# Cryptography
The default TLS 1.3 configuration prefers X25519MLKEM768. That is the
IANA-registered hybrid: the shared secret is HKDF-Extract of X25519_ss
concatenated with ML-KEM-768 ciphertext-derived ss. The Go runtime
performs the combination internally — this package does not roll its
own KEM combiner. See ../papers/pq-hybrid-kem/main.pdf.
The server certificate signature is still classical (ECDSA or RSA);
post-quantum signature schemes are not yet wired into Go's stdlib TLS.
The threat model that motivates X25519MLKEM768 is harvest-now /
decrypt-later against the confidentiality of the key-exchange, not
forgery against a trusted-today certificate authority.
# 0-RTT replay
QUIC permits 0-RTT application data, which can be replayed by a network
adversary. The server defaults to accepting 0-RTT data, but application
handlers MUST treat 0-RTT-carried RPCs as either idempotent or rejected.
Use ServerConfig.RejectEarlyData to force a full 1-RTT handshake for
every connection.
## Functions
### GenerateSelfSignedCert
```go
func GenerateSelfSignedCert(hosts ...string) (tls.Certificate, error)
```
GenerateSelfSignedCert generates a short-lived self-signed ECDSA-P256
certificate suitable for testing. NOT for production use: production
deployments must supply real certificates via tls.Config.Certificates
or GetCertificate, signed by a trusted CA.
Exposed as a public helper because tests in other packages may want
to spin up a real ZAP-QUIC server.
## Types
### Client
Client is a QUIC dialer with optional connection migration and
0-RTT support.
One Client may be shared across many goroutines and used to Dial
multiple peers concurrently. The Client owns one *quic.Transport
which is bound to a single UDP socket — every Dial uses the same
local socket, so a connection migration of the local side will
affect all connections owned by this Client (this matches QUIC
semantics).
```go
func NewClient(cfg ClientConfig) (*Client, error)
```
```go
func (c *Client) Close() error
```
```go
func (c *Client) Dial(ctx context.Context, addr string) (*Conn, error)
```
```go
func (c *Client) DialEarly(ctx context.Context, addr string) (*Conn, error)
```
```go
func (c *Client) LocalAddr() net.Addr
```
```go
func (c *Client) Transport() *quicgo.Transport
```
### ClientConfig
ClientConfig configures a QUIC dialer.
### Config
Config is the user-facing knob that the zap parent package's
NodeConfig.QUICConfig accepts. It's a thin wrapper so callers
don't have to import quic-go just to flip RejectEarlyData; they
can supply nil for QUIC if defaults are fine.
Typical use:
cfg := zap.NodeConfig&#123;
NodeID: "node-a",
Port: 9999,
TLS: tlsCfg,
Transport: zap.TransportQUIC,
QUICConfig: &quic.Config&#123;RejectEarlyData: true},
}
### Conn
Conn is a multiplexed ZAP connection over QUIC.
Conn is the transport-level connection abstraction shared between the
dialer (client.go) and the listener (server.go). It exposes the same
shape as ZAP's TCP *Conn (Send, Recv, Close, peer identity) and adds
stream-level primitives that the underlying TCP transport cannot
efficiently express.
Conn is safe for concurrent use. Send serializes onto the control
stream; concurrent callers may instead OpenStream to get independent
per-RPC streams.
```go
func (c *Conn) AcceptStream(ctx context.Context) (*Stream, error)
```
```go
func (c *Conn) AcceptUniStream(ctx context.Context) (*UniReceiveStream, error)
```
```go
func (c *Conn) Close() error
```
```go
func (c *Conn) ConnectionState() tls.ConnectionState
```
```go
func (c *Conn) Context() context.Context
```
```go
func (c *Conn) IsZeroRTT() bool
```
```go
func (c *Conn) OpenStream(ctx context.Context) (*Stream, error)
```
```go
func (c *Conn) OpenUniStream(ctx context.Context) (*UniStream, error)
```
```go
func (c *Conn) QUIC() *quicgo.Conn
```
```go
func (c *Conn) Recv() ([]byte, error)
```
```go
func (c *Conn) Send(frame []byte) error
```
### Server
Server is a QUIC listener that yields *Conn instances ready for
ZAP-frame exchange.
Server is the QUIC analogue of net.Listener. The outer ZAP Node
drives the accept loop; this type does not spawn its own goroutines.
```go
func Listen(cfg ServerConfig) (*Server, error)
```
```go
func (s *Server) Accept(ctx context.Context) (*Conn, error)
```
```go
func (s *Server) Addr() net.Addr
```
```go
func (s *Server) Close() error
```
### ServerConfig
ServerConfig configures a QUIC listener.
### Stream
Stream is a bidirectional QUIC stream carrying ZAP frames.
```go
func (s *Stream) Close() error
```
```go
func (s *Stream) CloseWrite() error
```
```go
func (s *Stream) ReadFrame() ([]byte, error)
```
```go
func (s *Stream) WriteFrame(frame []byte) error
```
### UniReceiveStream
UniReceiveStream is a unidirectional receive-only QUIC stream.
```go
func (u *UniReceiveStream) ReadFrame() ([]byte, error)
```
### UniStream
UniStream is a unidirectional send-only QUIC stream.
```go
func (u *UniStream) Close() error
```
```go
func (u *UniStream) WriteFrame(frame []byte) error
```
@@ -0,0 +1,19 @@
---
title: "main"
description: "docgen renders the node's package reference from godoc — every package under the module, one MDX page mirroring the import path — for docs.lux.network."
---
{/* Generated from godoc by tools/docgen — edit the Go source, not this file. */}
```go
import "github.com/luxfi/zap/tools/docgen"
```
docgen renders the node's package reference from godoc — every package under
the module, one MDX page mirroring the import path — for docs.lux.network.
It PARSES (go/parser + go/doc), never builds, so it is immune to build-graph
skew. Same model as hanzo/cloud generating its docs from source.
go run ./tools/docgen &lt;out-dir> [root] # default root: .
@@ -0,0 +1 @@
{"pages":["docgen"]}
@@ -0,0 +1,86 @@
---
title: "transport"
description: "Package transport is the pluggable GPU-aware ZAP transport."
---
{/* Generated from godoc by tools/docgen — edit the Go source, not this file. */}
```go
import "github.com/luxfi/zap/transport"
```
Package transport is the pluggable GPU-aware ZAP transport.
Three transports are implemented:
default — standard Go net (CPU parsing). Always available.
uma — Apple Silicon / NVIDIA Grace unified-memory. cgo + darwin.
Network packets land in shared RAM; GPU kernels read them
at GPU memory speed without a copy.
gpudirect — NVIDIA + Mellanox CX-6/7 (GPUDirect RDMA). cgo + linux.
NIC DMA into VRAM, ZAP parsed by GPU kernel, no CPU touch.
dpdk — Linux DPDK + GPU-mapped hugepages. cgo + linux.
Kernel-bypass packet ingestion, GPU reads from mapped huge-
pages. Best for non-Mellanox NICs.
All four implement the same Transport interface. Pick() chooses the
best available implementation at runtime, honoring a caller-supplied
preference and falling through gracefully when a preferred path is
unavailable. There is no `gpu` build tag — cgo is the only gate, and
the OS gate selects which native transports can compile in.
## Functions
### Registered
```go
func Registered() []string
```
Registered returns the list of transports that this binary can attempt
to instantiate (i.e. the platform-and-build-tag matrix). Used by tools
like `zap-info` and the boot-time diagnostic in lqd.
## Types
### Buffer
Buffer is one slab of managed memory handed out by a BufferAllocator.
For the UMA transport on Linux+CUDA, the underlying pointer is
cudaMallocManaged memory — the same bytes the GPU sees. Calling Bytes()
gives a Go slice that shares the storage; calling DevicePtr() gives the
raw device pointer for CUDA kernel launches.
Release returns the buffer to its allocator. Double-Release is a no-op
(allocators detect and ignore it).
### BufferAllocator
BufferAllocator hands out managed buffers. Transports that can deliver
GPU-resident bytes (UMA Linux+CUDA, UMA Darwin+Metal) implement this;
transports that can't (default TCP) return ErrNotAvailable from
AllocBuffer so the caller knows to fall back to heap allocation.
Capacity in bytes is the operator-configurable pool size (default 4
GiB); allocations from a full pool block until a buffer is Released.
### Capabilities
Capabilities describes what a Transport implementation supports.
### Transport
Transport is the wire-level surface multichain gossip uses to ship
sealed MultiChainBlocks between validators. The interface is sized for
one ZAP message per Send/Recv — batching happens above this layer.
```go
func Pick(preferred string) (Transport, error)
```
@@ -0,0 +1,18 @@
---
title: "main"
description: "Command gennest generates the nestwire test package: a singular nested object (Outer carrying an Inner that itself has a string tail)."
---
{/* Generated from godoc by tools/docgen — edit the Go source, not this file. */}
```go
import "github.com/luxfi/zap/v1/codegen/cmd/gennest"
```
Command gennest generates the nestwire test package: a singular nested
object (Outer carrying an Inner that itself has a string tail). It is the
nested-field peer of the one-off that produced testpkg/listwire, and
proves the codegen's WriteNested/NestedAt emission round-trips on the
wire (see testpkg/nestwire/roundtrip_test.go).
@@ -0,0 +1 @@
{"pages":["gennest","zapgen","zapgen-all"]}
@@ -0,0 +1,52 @@
---
title: "main"
description: "Command zapgen-all bulk-emits per-schema ZAP v2 accessor files from a directory of JSON schema declarations."
---
{/* Generated from godoc by tools/docgen — edit the Go source, not this file. */}
```go
import "github.com/luxfi/zap/v1/codegen/cmd/zapgen-all"
```
Command zapgen-all bulk-emits per-schema ZAP v2 accessor files
from a directory of JSON schema declarations.
Each *.json file in the schemas directory declares one or more
schemas in a single LP-aligned bundle. The bundle's top-level
"schemas" array contains entries with the same fields as a
codegen.Schema plus an "out" field naming the relative output path:
&#123;
"lp": "lp-201",
"description": "ZAP P2P transport (LP-201 §Wire schemas)",
"schemas": [
&#123;
"wire_name": "ConsensusVote",
"go_name": "ConsensusVoteSchema",
"kind": "0xD0",
"size": 62,
"package": "p2pwire",
"out": "lp-201/consensus_vote_zap.go",
"fields": [
&#123; "name": "Round", "type": "uint64", "offset": 1 },
&#123; "name": "BlockID", "type": "bytes32", "offset": 9 },
&#123; "name": "SignerID", "type": "bytes20", "offset": 41 },
&#123; "name": "SigOff", "type": "uint32", "offset": 61 }
]
}
]
}
Usage:
zapgen-all -schemas ./schemas -out ./generated
The tool walks schemas/, parses every *.json file, validates every
schema, and emits one *.go file per schema under -out at the path
declared by the schema's "out" field. Existing files are overwritten.
Exit codes: 0 = all schemas emitted; 1 = at least one schema failed
to parse/validate/emit.
@@ -0,0 +1,25 @@
---
title: "main"
description: "Command zapgen emits per-schema ZAP v2 accessors from a declarative schema description."
---
{/* Generated from godoc by tools/docgen — edit the Go source, not this file. */}
```go
import "github.com/luxfi/zap/v1/codegen/cmd/zapgen"
```
Command zapgen emits per-schema ZAP v2 accessors from a declarative
schema description.
Usage:
zapgen -name AdvanceTimeTx -go-name AdvanceTimeSchema -kind 1 \
-size 9 -package examples -field Time:uint64:1 \
-out v2/examples/advance_time_zap.go
The emitted file's Wrap/New functions use v1 primitives directly so
the Go compiler inlines them into the caller — matching v1
hand-rolled performance to within compiler noise.
@@ -0,0 +1,195 @@
---
title: "codegen"
description: "Package codegen emits per-schema ZAP v2 accessors that match v1's hand-rolled inline-everything performance."
---
{/* Generated from godoc by tools/docgen — edit the Go source, not this file. */}
```go
import "github.com/luxfi/zap/v1/codegen"
```
Package codegen emits per-schema ZAP v2 accessors that match v1's
hand-rolled inline-everything performance.
# Why codegen
Go's generic dispatch and inlining cost budget combine to put a
hard ceiling on how fast a generic API can be: any function whose
body is larger than the inliner's 80-cost budget cannot inline
into its caller, so the call site pays a function-call cost (~5-7
ns on modern hardware). The work performed by a "Wrap" function
(parse the ZAP wire frame, validate the kind discriminator, build
a typed view) exceeds that budget. The generic [zapv1.Wrap[S]]
function therefore costs one function call per Wrap, even when
every other primitive in its body would inline.
Hand-written per-schema [WrapX] shims (e.g. [examples.WrapAdvanceTime])
have the same problem — even though they pin S&#123;}.Kind() and
S&#123;}.Size() as constants, the body is still too large to inline.
Codegen solves this by emitting Wrap/Build/Read/Write functions
that DON'T live inside a function at all — they expand inline at
the call site through Go templates instantiated at build time.
The user writes a single schema declaration; the codegen tool
produces a *_zap.go file whose functions match v1's hand-rolled
pattern byte-for-byte.
# Output shape
For a schema declared as:
type AdvanceTimeSchema struct&#123;}
func (AdvanceTimeSchema) Kind() zapv1.KindByte &#123; return 1 }
func (AdvanceTimeSchema) Size() int &#123; return 9 }
func (AdvanceTimeSchema) Name() string &#123; return "AdvanceTimeTx" }
//zap:field Time uint64 @1
the codegen tool emits a sibling file (advance_time_zap.go) with:
const sizeAdvanceTimeTx = 9
const kindAdvanceTimeTx uint8 = 1
const offsetAdvanceTimeTx_Time = 1
func WrapAdvanceTime(b []byte) (zapv1.View[AdvanceTimeSchema], error) &#123;
msg, err := zap.Parse(b)
...
}
The emitted code uses v1 primitives directly (zap.Parse, msg.Root,
root.Uint8) so that the inliner folds them into the caller's frame,
matching v1 hand-rolled performance.
# When to use codegen
- Hot-path schemas where the function-call overhead matters
(per-tx assembly, per-block validation).
- When you have a schema description in a declarative form (a YAML
file, a Cap'n-Proto-style schema file, struct tags) and want
ZAP v2 accessors emitted automatically.
For cold paths and ad-hoc schemas, the generic [zapv1.Wrap[S]] /
[zapv1.Build[S]] are equally correct and one extra function call
slower — which is rarely the bottleneck.
# Status
This package is the entry point. The current implementation emits
the canonical AdvanceTimeTx fast-path file (used as the canary in
the bench suite); adding new schemas means feeding the codegen
tool a schema declaration in one of the supported forms.
## Functions
### Emit
```go
func Emit(w io.Writer, s Schema) error
```
Emit writes the per-schema *.go file to w. Returns an error if the
schema is invalid (unknown field type, offset overlap, fields past
declared Size, etc.).
The emitted code follows the v1 hand-rolled pattern: every step
(Parse, Root, Uint reads, kind check, View compose) uses v1
primitives that inline into the caller's frame. The result matches
v1 hand-rolled performance to within compiler noise — verified by
the bench suite.
Scalar fields go through the [zapv1.Field][S, T] generic handle
(declared in the per-schema &lt;GoName>Fields var). Fixed-width
byte-array fields ("bytes&lt;N>") are emitted as standalone typed
accessor functions that call v1's SetBytesFixed / BytesFixedSlice
— they do NOT appear in the Fields struct because [N]byte is not a
[zapv1.FieldKind] member. Both forms produce the same wire layout.
## Types
### Field
Field describes one fixed-size field in a schema.
Two field kinds are supported:
1. Scalar fields. Type is one of the [zapv1.FieldKind] members
(bool, int8/16/32/64, uint8/16/32/64, float32/64). The emitted
code uses a [zapv1.Field][S, T] handle and the standard
zapv1.Read/Write generic functions.
2. Fixed-width byte-array fields. Type is "bytes&lt;N>" where N is a
positive integer (e.g., "bytes20" for NodeID, "bytes32" for
hashes, "bytes16" for session IDs). The emitted code uses the
v1 ObjectBuilder.SetBytesFixed / Object.BytesFixedSlice
accessors and returns the value as a [N]byte. Byte-array fields
do NOT use the zapv1.Field generic handle because [N]byte is
not a [zapv1.FieldKind] member; instead they get a typed
accessor function emitted alongside the schema.
3. Variable-length tail fields. Type is "string" or "bytes" (no
&lt;N> suffix). These occupy an 8-byte tail pointer &#123;relOffset
uint32, length uint32} in the fixed payload; the data lives in
the object tail after the fixed section. The constructor uses v1
ObjectBuilder.SetText / SetBytes; reads go through a standalone
accessor over v1's Object.Text / Object.Bytes (zero-copy
sub-slice of the buffer). Like byte-array fields, they do NOT use
the zapv1.Field generic handle (string/[]byte are not FieldKind
members).
List and nested-object tail fields are still hand-written (the
generic [zapv1.ListAt] / out-of-line pointer machinery).
```go
func (f Field) IsBytes() (int, bool)
```
```go
func (f Field) IsList() (*ListElem, bool)
```
```go
func (f Field) IsNested() (*NestedMsg, bool)
```
```go
func (f Field) IsVarBytes() bool
```
```go
func (f Field) IsVarString() bool
```
```go
func (f Field) IsVariable() bool
```
### ListElem
ListElem describes the element type of a list field. The element must
be a FIXED-SIZE schema (scalars + fixed byte arrays only — no
variable-length tails of its own), so every element is a flat
Stride-byte slot the list machinery can index in O(1).
### NestedMsg
NestedMsg describes a singular nested-object field — the proto3 message
field. The nested object must be a FIXED-SIZE (flat, no-kind) Element
schema, reached through a 4-byte object pointer; its flat payload lives
in the parent's object tail. The singular peer of [ListElem].
### Schema
Schema is the declarative description of a ZAP v1 schema. The
codegen tool consumes a [Schema] and emits a per-schema *.go file
with the Wrap/Build/Read/Write functions hand-rolled to inline.
One schema description, one emitted file, one and only one way to
access the wire format from Go code — Hickey-style.
@@ -0,0 +1 @@
{"pages":["cmd","testpkg"]}
@@ -0,0 +1,78 @@
---
title: "lcwire"
description: "Package lcwire"
---
{/* Generated from godoc by tools/docgen — edit the Go source, not this file. */}
```go
import "github.com/luxfi/zap/v1/codegen/testpkg"
```
## Functions
### LightClientBlockHeaderRequestBlockContentHash
```go
func LightClientBlockHeaderRequestBlockContentHash(v zapv1.View[LCBlockHeaderReqSchema]) [32]byte
```
LightClientBlockHeaderRequestBlockContentHash reads the 32-byte inline BlockContentHash field of LightClientBlockHeaderRequest.
Zero-copy on the read side (v1's Object.BytesFixedSlice returns
a sub-slice of the underlying buffer); the returned [32]byte is a
value copy so the caller's slot owns the bytes after the View's
buffer is freed.
### NewLightClientBlockHeaderRequest
```go
func NewLightClientBlockHeaderRequest(version uint8, chainID uint32, blockHeight uint64, certMinTier uint8, requestID uint32, blockContentHash [32]byte) (zapv1.View[LCBlockHeaderReqSchema], []byte)
```
NewLightClientBlockHeaderRequest builds a fresh LightClientBlockHeaderRequest in a ZAP buffer.
Hand-rolled fast path: inlines v1 primitives ([zap.NewBuilder],
[*Builder.StartObject], [*ObjectBuilder.Set*], [*Builder.Finish])
so the entire build expands at the call site. Result: 1 heap
alloc (the buffer), zero overhead vs v1.
### WrapLightClientBlockHeaderRequest
```go
func WrapLightClientBlockHeaderRequest(b []byte) (zapv1.View[LCBlockHeaderReqSchema], error)
```
WrapLightClientBlockHeaderRequest parses a ZAP buffer into a typed
[zapv1.View[LightClientBlockHeaderRequest]]. Validates the wire frame and the kind
discriminator at offset 0; returns [*zapv1.SchemaError] on kind
mismatch.
Hand-rolled fast path: inlines [zap.Parse] (which is itself
inlineable). Zero heap allocs on the happy path; matches v1
hand-rolled performance.
## Types
### LCBlockHeaderReqSchema
LCBlockHeaderReqSchema is the v2 schema marker for LightClientBlockHeaderRequest. Methods are constant-
returning so concrete-typed [zapv1.Wrap[LCBlockHeaderReqSchema]] calls fold to
literals at the call site.
```go
func (LCBlockHeaderReqSchema) Kind() zapv1.KindByte
```
```go
func (LCBlockHeaderReqSchema) Name() string
```
```go
func (LCBlockHeaderReqSchema) Size() int
```
@@ -0,0 +1,135 @@
---
title: "listwire"
description: "Package listwire"
---
{/* Generated from godoc by tools/docgen — edit the Go source, not this file. */}
```go
import "github.com/luxfi/zap/v1/codegen/testpkg/listwire"
```
## Functions
### BatchTxItems
```go
func BatchTxItems(v zapv1.View[BatchSchema]) zapv1.List[ItemSchema]
```
BatchTxItems returns the variable-length Items list of BatchTx as a
range-over-func-ready [zapv1.List][ItemSchema]. Zero-copy: each element view
indexes straight into the underlying buffer.
for it := range BatchTxItems(view).All() &#123;
// zapv1.Read(it, ItemSchemaFields.X)
}
### NewBatchItem
```go
func NewBatchItem(iD uint64, value uint64) (zapv1.View[ItemSchema], []byte)
```
NewBatchItem builds a fresh BatchItem in a ZAP buffer.
Hand-rolled fast path: inlines v1 primitives ([zap.NewBuilder],
[*Builder.StartObject], [*ObjectBuilder.Set*], [*Builder.Finish])
so the entire build expands at the call site. Scalars and fixed byte
arrays live in the fixed payload; variable-length string/bytes go in
the object tail (the fixed payload holds their 8-byte pointers).
### NewBatchTx
```go
func NewBatchTx(iD uint64, items []Item) (zapv1.View[BatchSchema], []byte)
```
NewBatchTx builds a fresh BatchTx in a ZAP buffer.
Hand-rolled fast path: inlines v1 primitives ([zap.NewBuilder],
[*Builder.StartObject], [*ObjectBuilder.Set*], [*Builder.Finish])
so the entire build expands at the call site. Scalars and fixed byte
arrays live in the fixed payload; variable-length string/bytes go in
the object tail (the fixed payload holds their 8-byte pointers).
### WrapBatchItem
```go
func WrapBatchItem(b []byte) (zapv1.View[ItemSchema], error)
```
WrapBatchItem parses a ZAP buffer into a typed
[zapv1.View[BatchItem]]. Validates the wire frame and the kind
discriminator at offset 0; returns [*zapv1.SchemaError] on kind
mismatch.
Hand-rolled fast path: inlines [zap.Parse] (which is itself
inlineable). Zero heap allocs on the happy path; matches v1
hand-rolled performance.
### WrapBatchTx
```go
func WrapBatchTx(b []byte) (zapv1.View[BatchSchema], error)
```
WrapBatchTx parses a ZAP buffer into a typed
[zapv1.View[BatchTx]]. Validates the wire frame and the kind
discriminator at offset 0; returns [*zapv1.SchemaError] on kind
mismatch.
Hand-rolled fast path: inlines [zap.Parse] (which is itself
inlineable). Zero heap allocs on the happy path; matches v1
hand-rolled performance.
## Types
### BatchSchema
BatchSchema is the v2 schema marker for BatchTx. Methods are constant-
returning so concrete-typed [zapv1.Wrap[BatchSchema]] calls fold to
literals at the call site.
```go
func (BatchSchema) Kind() zapv1.KindByte
```
```go
func (BatchSchema) Name() string
```
```go
func (BatchSchema) Size() int
```
### Item
Item is the per-element input for the BatchTx.Items list.
### ItemSchema
ItemSchema is the v2 schema marker for BatchItem. Methods are constant-
returning so concrete-typed [zapv1.Wrap[ItemSchema]] calls fold to
literals at the call site.
```go
func (ItemSchema) Kind() zapv1.KindByte
```
```go
func (ItemSchema) Name() string
```
```go
func (ItemSchema) Size() int
```
@@ -0,0 +1,64 @@
---
title: "consensuswire"
description: "Package consensuswire"
---
{/* Generated from godoc by tools/docgen — edit the Go source, not this file. */}
```go
import "github.com/luxfi/zap/v1/codegen/testpkg/lp182_consensuswire"
```
## Functions
### NewQuasarCert
```go
func NewQuasarCert(epoch uint64, finalityUnixNano int64, validators uint32, bLSOff uint32, bLSLen uint32, coronaOff uint32, coronaLen uint32, pulsarOff uint32, pulsarLen uint32, magnetarOff uint32, magnetarLen uint32, mLDSARollupOff uint32, mLDSARollupLen uint32) (zapv1.View[QuasarCertSchema], []byte)
```
NewQuasarCert builds a fresh QuasarCert in a ZAP buffer.
Hand-rolled fast path: inlines v1 primitives ([zap.NewBuilder],
[*Builder.StartObject], [*ObjectBuilder.Set*], [*Builder.Finish])
so the entire build expands at the call site. Result: 1 heap
alloc (the buffer), zero overhead vs v1.
### WrapQuasarCert
```go
func WrapQuasarCert(b []byte) (zapv1.View[QuasarCertSchema], error)
```
WrapQuasarCert parses a ZAP buffer into a typed
[zapv1.View[QuasarCert]]. Validates the wire frame and the kind
discriminator at offset 0; returns [*zapv1.SchemaError] on kind
mismatch.
Hand-rolled fast path: inlines [zap.Parse] (which is itself
inlineable). Zero heap allocs on the happy path; matches v1
hand-rolled performance.
## Types
### QuasarCertSchema
QuasarCertSchema is the v2 schema marker for QuasarCert. Methods are constant-
returning so concrete-typed [zapv1.Wrap[QuasarCertSchema]] calls fold to
literals at the call site.
```go
func (QuasarCertSchema) Kind() zapv1.KindByte
```
```go
func (QuasarCertSchema) Name() string
```
```go
func (QuasarCertSchema) Size() int
```
@@ -0,0 +1,78 @@
---
title: "zkvmwire"
description: "Package zkvmwire"
---
{/* Generated from godoc by tools/docgen — edit the Go source, not this file. */}
```go
import "github.com/luxfi/zap/v1/codegen/testpkg/lp186_zkvmwire"
```
## Functions
### NewZKVMUTXO
```go
func NewZKVMUTXO(outputIndex uint32, height uint64, commitmentOff uint32, commitmentLen uint32, ciphertextOff uint32, ciphertextLen uint32, ephemeralPKOff uint32, ephemeralPKLen uint32, txID [32]byte) (zapv1.View[ZKVMUTXOSchema], []byte)
```
NewZKVMUTXO builds a fresh ZKVMUTXO in a ZAP buffer.
Hand-rolled fast path: inlines v1 primitives ([zap.NewBuilder],
[*Builder.StartObject], [*ObjectBuilder.Set*], [*Builder.Finish])
so the entire build expands at the call site. Result: 1 heap
alloc (the buffer), zero overhead vs v1.
### WrapZKVMUTXO
```go
func WrapZKVMUTXO(b []byte) (zapv1.View[ZKVMUTXOSchema], error)
```
WrapZKVMUTXO parses a ZAP buffer into a typed
[zapv1.View[ZKVMUTXO]]. Validates the wire frame and the kind
discriminator at offset 0; returns [*zapv1.SchemaError] on kind
mismatch.
Hand-rolled fast path: inlines [zap.Parse] (which is itself
inlineable). Zero heap allocs on the happy path; matches v1
hand-rolled performance.
### ZKVMUTXOTxID
```go
func ZKVMUTXOTxID(v zapv1.View[ZKVMUTXOSchema]) [32]byte
```
ZKVMUTXOTxID reads the 32-byte inline TxID field of ZKVMUTXO.
Zero-copy on the read side (v1's Object.BytesFixedSlice returns
a sub-slice of the underlying buffer); the returned [32]byte is a
value copy so the caller's slot owns the bytes after the View's
buffer is freed.
## Types
### ZKVMUTXOSchema
ZKVMUTXOSchema is the v2 schema marker for ZKVMUTXO. Methods are constant-
returning so concrete-typed [zapv1.Wrap[ZKVMUTXOSchema]] calls fold to
literals at the call site.
```go
func (ZKVMUTXOSchema) Kind() zapv1.KindByte
```
```go
func (ZKVMUTXOSchema) Name() string
```
```go
func (ZKVMUTXOSchema) Size() int
```
@@ -0,0 +1 @@
{"pages":["listwire","lp182_consensuswire","lp186_zkvmwire","nestwire","repeatwire"]}
@@ -0,0 +1,143 @@
---
title: "nestwire"
description: "Package nestwire"
---
{/* Generated from godoc by tools/docgen — edit the Go source, not this file. */}
```go
import "github.com/luxfi/zap/v1/codegen/testpkg/nestwire"
```
## Functions
### InnerLabel
```go
func InnerLabel(v zapv1.View[InnerSchema]) string
```
InnerLabel reads the variable-length Label string field of Inner.
Zero-copy: v1's Object.Text returns a string over a sub-slice of the
underlying buffer. Valid only while the View's buffer is alive.
### NewInner
```go
func NewInner(iD uint64, label string) (zapv1.View[InnerSchema], []byte)
```
NewInner builds a fresh Inner in a ZAP buffer.
Hand-rolled fast path: inlines v1 primitives ([zap.NewBuilder],
[*Builder.StartObject], [*ObjectBuilder.Set*], [*Builder.Finish])
so the entire build expands at the call site. Scalars and fixed byte
arrays live in the fixed payload; variable-length string/bytes go in
the object tail (the fixed payload holds their 8-byte pointers).
### NewOuter
```go
func NewOuter(seq uint64, inner *Inner) (zapv1.View[OuterSchema], []byte)
```
NewOuter builds a fresh Outer in a ZAP buffer.
Hand-rolled fast path: inlines v1 primitives ([zap.NewBuilder],
[*Builder.StartObject], [*ObjectBuilder.Set*], [*Builder.Finish])
so the entire build expands at the call site. Scalars and fixed byte
arrays live in the fixed payload; variable-length string/bytes go in
the object tail (the fixed payload holds their 8-byte pointers).
### OuterInner
```go
func OuterInner(v zapv1.View[OuterSchema]) zapv1.View[InnerSchema]
```
OuterInner returns the singular Inner nested object of Outer as a
zero-copy [zapv1.View][InnerSchema]. An unset (null) nested field yields the zero
View — test it with [zapv1.View.IsZero].
### WrapInner
```go
func WrapInner(b []byte) (zapv1.View[InnerSchema], error)
```
WrapInner parses a ZAP buffer into a typed
[zapv1.View[Inner]]. Validates the wire frame and the kind
discriminator at offset 0; returns [*zapv1.SchemaError] on kind
mismatch.
Hand-rolled fast path: inlines [zap.Parse] (which is itself
inlineable). Zero heap allocs on the happy path; matches v1
hand-rolled performance.
### WrapOuter
```go
func WrapOuter(b []byte) (zapv1.View[OuterSchema], error)
```
WrapOuter parses a ZAP buffer into a typed
[zapv1.View[Outer]]. Validates the wire frame and the kind
discriminator at offset 0; returns [*zapv1.SchemaError] on kind
mismatch.
Hand-rolled fast path: inlines [zap.Parse] (which is itself
inlineable). Zero heap allocs on the happy path; matches v1
hand-rolled performance.
## Types
### Inner
Inner is the value input for the singular Outer.Inner nested object.
### InnerSchema
InnerSchema is the v1 schema marker for Inner. Methods are constant-
returning so concrete-typed [zapv1.Wrap[InnerSchema]] calls fold to
literals at the call site.
```go
func (InnerSchema) Kind() zapv1.KindByte
```
```go
func (InnerSchema) Name() string
```
```go
func (InnerSchema) Size() int
```
### OuterSchema
OuterSchema is the v1 schema marker for Outer. Methods are constant-
returning so concrete-typed [zapv1.Wrap[OuterSchema]] calls fold to
literals at the call site.
```go
func (OuterSchema) Kind() zapv1.KindByte
```
```go
func (OuterSchema) Name() string
```
```go
func (OuterSchema) Size() int
```
@@ -0,0 +1,147 @@
---
title: "repeatwire"
description: "Package repeatwire"
---
{/* Generated from godoc by tools/docgen — edit the Go source, not this file. */}
```go
import "github.com/luxfi/zap/v1/codegen/testpkg/repeatwire"
```
## Functions
### ChildName
```go
func ChildName(v zapv1.View[ChildSchema]) string
```
ChildName reads the variable-length Name string field of Child.
Zero-copy: v1's Object.Text returns a string over a sub-slice of the
underlying buffer. Valid only while the View's buffer is alive.
### NewChild
```go
func NewChild(iD uint64, name string) (zapv1.View[ChildSchema], []byte)
```
NewChild builds a fresh Child in a ZAP buffer.
Hand-rolled fast path: inlines v1 primitives ([zap.NewBuilder],
[*Builder.StartObject], [*ObjectBuilder.Set*], [*Builder.Finish])
so the entire build expands at the call site. Scalars and fixed byte
arrays live in the fixed payload; variable-length string/bytes go in
the object tail (the fixed payload holds their 8-byte pointers).
### NewParent
```go
func NewParent(count uint64, children []Child) (zapv1.View[ParentSchema], []byte)
```
NewParent builds a fresh Parent in a ZAP buffer.
Hand-rolled fast path: inlines v1 primitives ([zap.NewBuilder],
[*Builder.StartObject], [*ObjectBuilder.Set*], [*Builder.Finish])
so the entire build expands at the call site. Scalars and fixed byte
arrays live in the fixed payload; variable-length string/bytes go in
the object tail (the fixed payload holds their 8-byte pointers).
### ParentChildren
```go
func ParentChildren(v zapv1.View[ParentSchema]) zapv1.ListNested[ChildSchema]
```
ParentChildren returns the variable-length Children list of Parent as a
range-over-func-ready [zapv1.ListNested][ChildSchema]. Zero-copy: each element view
indexes straight into the underlying buffer.
for it := range ParentChildren(view).All() &#123;
// zapv1.Read(it, ChildSchemaFields.X)
}
### WrapChild
```go
func WrapChild(b []byte) (zapv1.View[ChildSchema], error)
```
WrapChild parses a ZAP buffer into a typed
[zapv1.View[Child]]. Validates the wire frame and the kind
discriminator at offset 0; returns [*zapv1.SchemaError] on kind
mismatch.
Hand-rolled fast path: inlines [zap.Parse] (which is itself
inlineable). Zero heap allocs on the happy path; matches v1
hand-rolled performance.
### WrapParent
```go
func WrapParent(b []byte) (zapv1.View[ParentSchema], error)
```
WrapParent parses a ZAP buffer into a typed
[zapv1.View[Parent]]. Validates the wire frame and the kind
discriminator at offset 0; returns [*zapv1.SchemaError] on kind
mismatch.
Hand-rolled fast path: inlines [zap.Parse] (which is itself
inlineable). Zero heap allocs on the happy path; matches v1
hand-rolled performance.
## Types
### Child
Child is the per-element input for the Parent.Children list.
### ChildSchema
ChildSchema is the v1 schema marker for Child. Methods are constant-
returning so concrete-typed [zapv1.Wrap[ChildSchema]] calls fold to
literals at the call site.
```go
func (ChildSchema) Kind() zapv1.KindByte
```
```go
func (ChildSchema) Name() string
```
```go
func (ChildSchema) Size() int
```
### ParentSchema
ParentSchema is the v1 schema marker for Parent. Methods are constant-
returning so concrete-typed [zapv1.Wrap[ParentSchema]] calls fold to
literals at the call site.
```go
func (ParentSchema) Kind() zapv1.KindByte
```
```go
func (ParentSchema) Name() string
```
```go
func (ParentSchema) Size() int
```
@@ -0,0 +1,749 @@
---
title: "zapv1"
description: "Package zapv1 is the elegant, generic, idiomatic Go reference implementation of the ZAP wire format."
---
{/* Generated from godoc by tools/docgen — edit the Go source, not this file. */}
```go
import "github.com/luxfi/zap/v1"
```
Package zapv1 is the elegant, generic, idiomatic Go reference
implementation of the ZAP wire format.
Where the v1 [github.com/luxfi/zap] package exposes a C-shaped API
(one hand-rolled Wrap/Build/accessor block per schema, runtime offset
constants, runtime kind-byte switch), v2 expresses the same wire
format through Go 1.23+ generics so that every schema gets:
- One typed [View] over the buffer.
- One typed [Field] per offset, with the field's value type
enforced at compile time. A [Field][SchemaA, uint64] cannot be
read from a [View][SchemaB]; the compiler rejects it.
- One generic [List] with [iter.Seq] range-over-func iteration.
- One generic [Pool] for reuse.
- One generic [Registry] for kind-byte dispatch (reflection at
boot, none in the hot path).
# Wire compatibility
v2 reads and writes the exact same bytes as v1. Both APIs ship
side-by-side and may share buffers freely; nothing about the on-wire
encoding has changed. v2 is purely an ergonomic upgrade.
# Threat model
All wire-level safety properties of v1 are inherited verbatim by v2
(Magic check, version check, bounds-checked offsets, signed-but-
non-header-aliasing Object/List pointers, RED-HIGH-1 length clamp,
RED-HIGH-2 header-alias rejection). v2 adds compile-time type safety
on top — it does not loosen any runtime check.
# Hickey-style decomposition
- A schema is a value (something implementing [Schema]), not a
place. It carries a kind byte, a fixed size, and a name. Schemas
are qualified by package, not by prefix.
- A view is a value ([View]) — a typed reference into a buffer.
View[S] and View[T] are distinct at compile time.
- A field is a value ([Field]) — a phantom-typed offset, qualified
by the schema it belongs to and the wire type it reads/writes.
- A list is a value ([List]) — its element type is the schema for
that element; iteration is generic via [iter.Seq].
One and only one way to express any of these. No braiding of policy
with primitive, no inheritance, no duplication.
## Functions
### Collect
```go
func Collect[T any](src iter.Seq[T]) []T
```
Collect drains src into a freshly allocated slice. Useful in tests
where the caller wants to assert on the materialized list; not
recommended in the hot path because it defeats the zero-copy
property of [List].
### Count
```go
func Count[T any](src iter.Seq[T]) int
```
Count returns the number of elements yielded by src. Drains the
sequence — do not call on infinite sequences.
### Filter
```go
func Filter[T any](src iter.Seq[T], pred func(T) bool) iter.Seq[T]
```
Filter returns an iter.Seq yielding only those elements of src for
which pred returns true. Constant memory; one allocation per call
(the closure), zero per element.
### Map
```go
func Map[T, U any](src iter.Seq[T], f func(T) U) iter.Seq[U]
```
Map returns an iter.Seq[U] obtained by applying f to each element of
src. Constant memory per element.
### Read
```go
func Read[S Schema, T FieldKind](v View[S], f Field[S, T]) T
```
Read reads the field f's value from view v.
Compile-time safety: the type parameters S and T are inferred from
the (view, field) pair. Passing a Field for a different schema to a
View[S] fails to type-check because Field[OtherSchema, T] is not
assignable to Field[S, T].
Zero copy, zero allocation. Out-of-range offsets return the zero
value of T (matches the audited v1 [zap.Object] bounds-check
behaviour: degrade gracefully to zero rather than panic).
Performance: this function reads bytes directly from the View's
pre-sliced payload via [unsafe.Pointer], folding to a single load
instruction per concrete T after generic instantiation. Verified by
[bench_test.go]: matches hand-rolled v1 [zap.Object.Uint64] to
within compiler-noise.
Wire format: ZAP integers are little-endian. On little-endian hosts
(every Lux target — amd64, arm64, aarch64) the load is a direct
cast. The pkg builds only on little-endian platforms; a future big-
endian port would need a per-type byteswap (see init()).
### ReadPayload
```go
func ReadPayload[S Schema, T FieldKind](payload []byte, f Field[S, T]) T
```
ReadPayload reads a field directly from a payload byte slice (as
yielded by [List.Payloads]). This is the hot-path counterpart to
[Read] — equivalent wire semantics, but the input is a 24-byte
slice header (registers-friendly) rather than a 56-byte [View[S]]
(stack-spilling).
Use ReadPayload inside a for-range over [List.Payloads] when
iterating large lists with fixed-field reads only. For full View
operations (Bytes, sub-objects, lists) use [Read] with [List.All].
Compile-time safety: same as [Read] — Field[S, T] is phantom-typed
against schema S, so the compiler rejects cross-schema misuse even
though the payload slice itself is plain []byte. The S type
parameter is what ties the field to its declared schema.
### Register
```go
func Register[S Schema](r *Registry)
```
Register registers schema S in r. It is safe to call from multiple
goroutines, but the typical pattern is init():
func init() &#123;
zapv1.Register[AdvanceTimeSchema](DefaultRegistry)
}
Register panics if two schemas declare the same kind byte — that
would be a wire-protocol bug, not a runtime condition.
### RootOff
```go
func RootOff[S Schema](v View[S]) int32
```
RootOff returns the absolute byte offset of the root object within
the underlying buffer. Exposed for codegen'd byte-array accessors
that need to re-anchor a [zap.Object] at the root to call v1's
out-of-line byte-slice readers ([zap.Object.BytesFixedSlice]).
Application code typically does not need this — use the typed
accessors emitted alongside each schema.
### Take
```go
func Take[T any](src iter.Seq[T], n int) iter.Seq[T]
```
Take returns an iter.Seq yielding at most n elements from src. If
src yields fewer than n, all of them are passed through; if more,
the iteration stops after n.
Constant memory: Take does not buffer. It counts and short-circuits.
### Write
```go
func Write[S Schema, T FieldKind](s Setter[S], f Field[S, T], val T)
```
Write writes value val into field f of setter s.
Compile-time safety: as with [Read], passing a Field declared for a
different schema fails to type-check.
Zero allocation. The write goes through the underlying v1
[zap.ObjectBuilder] for the fixed payload — its bounds-check
(ensureField) is inherited verbatim. After generic instantiation +
inlining, this collapses to a direct little-endian store at the
computed offset, matching v1 hand-rolled SetUint64 performance —
verified by [bench_test.go].
Implementation note: writing through the ObjectBuilder (rather than
a direct unsafe store into the underlying buffer) preserves v1's
audited "ensureField" semantics — fields past the currently-written
region get the buffer growth + zero-fill that the builder
guarantees. A bare unsafe write would bypass that and corrupt the
builder's invariant.
### WriteB
```go
func WriteB[S Schema, T FieldKind](bb Builder[S], f Field[S, T], val T)
```
WriteB is the [Builder]-receiver counterpart to [Write]. Used in
the imperative-style build path:
bb := zapv1.NewBuilderFor[Schema]()
zapv1.WriteB(bb, Fields.Time, ts)
view, buf := bb.Finish()
Identical wire semantics to Write — the difference is only that
passing a Builder[S] (by value) instead of a Setter[S] (through a
closure) keeps the wrapper on the caller's stack. The underlying
v1 *zap.Builder still escapes (its buffer is what goes on the
wire), so the imperative form does not eliminate all v2 overhead —
it eliminates only the closure-captured pointer allocation.
### WriteBytes
```go
func WriteBytes[S Schema](s Setter[S], byteOffset uint32, v []byte)
```
WriteBytes writes a variable-length byte slice to the object tail and
stores its &#123;relOffset, length} pointer at byteOffset. The typed peer
of [zap.ObjectBuilder.SetBytes]. Zero-copy on read via Object.Bytes.
### WriteList
```go
func WriteList[S, E Schema](s Setter[S], byteOffset uint32, each func(*ElemSetter[E]))
```
WriteList writes a homogeneous list of element-schema E into the
list-pointer field at byteOffset within schema S. The `each` closure
is called once with an [*ElemSetter[E]] handle that writes one
element per call to [ElemSetter.Append]; the list pointer in S's
fixed payload is patched with the final (relOffset, length) when
each returns.
Example (BatchTx.Items, see examples/batch_tx.go):
zapv1.Build[BatchSchema](func(s zapv1.Setter[BatchSchema]) &#123;
zapv1.Write(s, BatchFields.ID, batchID)
zapv1.WriteList[BatchSchema, ItemSchema](s, OffsetBatchItems,
func(items *zapv1.ElemSetter[ItemSchema]) &#123;
for _, it := range source &#123;
items.Append(func(e zapv1.Setter[ItemSchema]) &#123;
zapv1.Write(e, ItemFields.ID, it.id)
zapv1.Write(e, ItemFields.Value, it.val)
})
}
})
})
Stride is taken from E&#123;}.Size(). The list lives in the variable
section of the parent message, after the fixed payload — the exact
wire layout that v1 [zap.Builder.StartList]/SetList produces.
Wire encoding: the list-pointer field at byteOffset holds (relOffset,
length); length is the number of elements (NOT bytes), matching v1's
[zap.List.Object] indexing convention.
### WriteListNested
```go
func WriteListNested[S, N Schema](s Setter[S], byteOffset uint32, each func(*NestedElemSetter[N]))
```
WriteListNested writes a repeated nested-object field: each element is
built as a flat out-of-line object in the parent tail (with its own
scalars/strings/lists), then a contiguous stride-4 pointer array is laid
down and the list field patched to it. The pointer-array peer of
[WriteList] (which inlines fixed-stride elements). An empty list writes a
null pointer.
### WriteNested
```go
func WriteNested[S, N Schema](s Setter[S], byteOffset uint32, init func(Setter[N]))
```
WriteNested builds a singular nested object of schema N into the parent
object's tail and stores its 4-byte object pointer at byteOffset in the
parent's fixed payload. The init closure populates the nested object's
fields (via [Write]/[WriteString]/[WriteBytes]/[WriteList]/[WriteNested]
— it composes recursively). The singular peer of [WriteList].
Omitting the call entirely leaves the pointer field zero — a null
pointer — which [NestedAt] reads back as the zero View[N]. This is how
codegen encodes an unset proto3 message field: emit the WriteNested only
inside an `if msg != nil` guard.
Wire layout matches the singular case of [WriteList]: the parent's
fixed payload is reserved first (so the nested object cannot overwrite
the still-unwritten pointer field), then the nested object is appended
to the tail and the pointer patched with its relative offset.
### WriteString
```go
func WriteString[S Schema](s Setter[S], byteOffset uint32, v string)
```
WriteString writes a variable-length UTF-8 string field to the object
tail and stores its &#123;relOffset, length} pointer at byteOffset in the
fixed payload. The typed peer of [zap.ObjectBuilder.SetText] for the
Setter-based build path. Read it back with the generated string
accessor (which calls v1's zero-copy Object.Text).
## Types
### Builder
Builder is the imperative-style construction API for schema S.
Where [Build] takes a closure (more readable, more allocations
because the closure escapes), Builder gives the caller direct
control over the build sequence — at the cost of three lines
instead of one. Use it on hot paths that match the v1 hand-rolled
allocation profile (1 alloc per Finish: the buffer itself).
Usage:
bb := zapv1.NewBuilderFor[AdvanceTimeSchema]()
zapv1.WriteB(bb, examples.AdvanceTimeFields.Time, ts)
view, buf := bb.Finish()
WriteB is the explicit-receiver counterpart to [Write] — both take
a (Field, value) pair, but WriteB writes through a [Builder][S]
instead of a [Setter][S], allowing escape analysis to keep the
builder on the stack.
Builder is NOT safe for concurrent use; each goroutine should
allocate its own.
Layout: Builder holds two pointers (b, ob) into the v1 builder
state. Both pointers' targets are heap-allocated by v1, but the
Builder[S] struct itself stays on the caller's stack because it's
passed by value.
```go
func NewBuilderFor[S Schema]() Builder[S]
```
```go
func (bb Builder[S]) AsSetter() Setter[S]
```
```go
func (bb Builder[S]) Finish() (View[S], []byte)
```
### ElemSetter
ElemSetter writes one element at a time into a list of schema E.
Always passed by pointer so [WriteList] observes the final count
after [each] returns.
```go
func (es *ElemSetter[E]) Append(init func(Setter[E]))
```
### Entry
Entry describes how to handle one schema. The Wrap closure parses
a buffer and returns the validated data slice + root offset,
reflection-free.
Name and Kind are duplicated from the Schema for convenient
inspection without instantiating the Schema receiver.
### Field
Field is a phantom-typed accessor for a fixed-size field within
schema S. The type parameter T is the field's wire type (one of the
[FieldKind] union). At compile time:
- Read[S, T](View[S], Field[S, T]) returns a T.
- Read[S, T](View[S], Field[OtherSchema, T]) is a compile error.
- Write[S, T](Setter[S], Field[S, T], v) writes v at the field's
offset.
- Write[S, T](Setter[OtherSchema], ...) is a compile error.
The runtime representation of Field is just an offset; the compile-
time tag is free. This is what gives v2 zero abstraction cost
versus the v1 hand-rolled offset constants.
Construct Field values once at package init (see the canary
AdvanceTime schema in examples/advance_time_tx.go for the pattern).
Reusing Field values across goroutines is safe — they are immutable
after declaration.
Implementation note: Go 1.23+ does not allow type parameters on
methods of generic types ([View[S].Read[T] would be illegal]). v2
therefore exposes Read and Write as top-level generic functions
rather than methods on [View] / [Setter]. The call site is at least
as terse as a method call: `zapv1.Read(view, F.Time)`.
```go
func At[S Schema, T FieldKind](offset uint32) Field[S, T]
```
### FieldKind
FieldKind is the type-level constraint a [Field]'s value type must
satisfy: any Go value type that can be stored in a fixed-size slot.
The exhaustive set is the integer + float + Address + Hash + bool
family; variable-length values (bytes, text, lists, sub-objects)
have their own accessors because their wire representation is a
pointer pair, not a primitive.
We use a comparable union so the generic [Field] type is constrained
at compile time. Users who add new fixed-size element types extend
this constraint; no other API surface changes.
### KindByte
KindByte is the one-byte discriminator at offset 0 of every v2
object payload. It identifies the schema concretely.
In v1 this lived as `TxKind uint8` per-package; v2 lifts it into
the ZAP layer so the generic [View], [Wrap] and [Registry] code can
reason about kind without importing every consumer.
```go
func PeekKind(b []byte) (KindByte, error)
```
### List
List is a typed, zero-copy view into a homogeneous list of objects
of schema E. List[E] and List[F] are distinct types at compile time;
you cannot accidentally iterate a list of Outputs as if it were a
list of Inputs.
The element type E identifies the schema for each entry. Each call
to [List.At] returns a View[E] over the corresponding fixed-size
slot, and [List.All] returns an [iter.Seq] for range-over-func.
Layout: like [View], List is value-typed and contains no pointers
other than the slice header for `data`. It carries the absolute byte
offset of the first element and the wire-encoded length. Stride
derives from E&#123;}.Size() at call time.
```go
func ListAt[S, E Schema](v View[S], offset uint32) List[E]
```
```go
func (l List[E]) All() iter.Seq[View[E]]
```
```go
func (l List[E]) At(i int) View[E]
```
```go
func (l List[E]) Indexed() iter.Seq2[int, View[E]]
```
```go
func (l List[E]) IsZero() bool
```
```go
func (l List[E]) Len() int
```
```go
func (l List[E]) Payloads() iter.Seq[[]byte]
```
### ListNested
ListNested is a typed, zero-copy view into a repeated nested-object
field. Each element is reached by following a 4-byte pointer slot to an
out-of-line [View[N]]; elements may carry their own variable-length
tails. Distinct compile-time type from [List[E]] so an inline-element
list and a pointer-element list can never be confused.
```go
func ListNestedAt[S, N Schema](v View[S], offset uint32) ListNested[N]
```
```go
func (l ListNested[N]) All() iter.Seq[View[N]]
```
```go
func (l ListNested[N]) At(i int) View[N]
```
```go
func (l ListNested[N]) IsZero() bool
```
```go
func (l ListNested[N]) Len() int
```
### NestedElemSetter
NestedElemSetter builds the elements of a repeated nested-object field,
one out-of-line object per [Append]. It records each object's position
so [WriteListNested] can emit the pointer array after the last element.
```go
func (es *NestedElemSetter[N]) Append(init func(Setter[N]))
```
### Pool
Pool is a generic, type-safe sync.Pool wrapper. Instead of writing a
dedicated sync.Pool + Get + Put per type as v1 did, register a
constructor once:
var ViewPool = zapv1.NewPool(func() *View[AdvanceTimeSchema] &#123;
return &View[AdvanceTimeSchema]&#123;}
})
v := ViewPool.Get()
defer ViewPool.Put(v)
Get returns a *T; Put returns it to the pool. The generic guarantees
that a value pulled from Pool[A] cannot be returned to Pool[B]
(would not type-check).
Pool itself is safe for concurrent use (delegated to [sync.Pool]).
```go
func NewPool[T any](new func() *T) *Pool[T]
```
```go
func (p *Pool[T]) Get() *T
```
```go
func (p *Pool[T]) Put(t *T)
```
### Raw
Raw is the non-generic shape of [View]: same three fields, no
schema type parameter. It exists as the value-shape that the per-
schema Wrap shims construct in their hot path. Construction is via
[WrapRaw] / [WrapRawUnchecked]; the public typed View[S] wraps a
Raw with one struct cast.
Treat Raw as an implementation detail of the per-schema shim
constructors — application code uses [View[S]] / [Wrap].
```go
func RawFromSlices(data []byte, rootOff, end int) Raw
```
```go
func WrapRaw(b []byte, wantKind uint8, size int, name string) (Raw, error)
```
```go
func WrapRawUnchecked(b []byte, size int) (Raw, error)
```
### Registry
Registry is a kind-byte → schema-typed wrapper dispatcher. Each
registered schema produces an [Entry] that knows, at runtime, how
to wrap a buffer as the right typed View. The Entry itself was
generated at compile time when [Register] was called, so dispatch
requires no reflection and no per-call allocation — only a map
lookup.
Registry is safe for concurrent reads; Register is intended for use
at package init and is guarded by a mutex against concurrent
init-time races.
Hickey decomposition: Registry is a value (map of values). The
"what kind is this?" question is answered by looking at a byte;
the "how do I parse it?" question is answered by an Entry value;
there is no inheritance, no interface tower, no "TxFactory".
```go
func NewRegistry() *Registry
```
```go
func (r *Registry) Entries() []Entry
```
```go
func (r *Registry) Lookup(kind KindByte) (Entry, bool)
```
### Schema
Schema describes a v2 message shape. A type implements Schema by
declaring its kind byte, its fixed object size (in bytes within the
data segment), and a human-readable name.
A schema is a value, not a place. Implementations are typically
zero-sized marker structs whose name documents the wire shape:
type AdvanceTimeSchema struct&#123;}
func (AdvanceTimeSchema) Kind() KindByte &#123; return 0x14 }
func (AdvanceTimeSchema) Size() int &#123; return 9 }
func (AdvanceTimeSchema) Name() string &#123; return "AdvanceTimeTx" }
Implementations MUST be safe to use as the zero value of their type
— the methods consult only the receiver type, never any state.
This invariant is what lets Registry.Register and reflect-free
generic dispatch work.
### SchemaError
SchemaError is returned when a buffer does not match the expected
schema (wrong kind byte, undersized object payload, etc).
```go
func NewSchemaError(want, got KindByte, name string) *SchemaError
```
```go
func (e *SchemaError) Error() string
```
### Setter
Setter is the write-side counterpart to [View]. A Setter[S] writes
fields into the buffer for schema S. Like View, Setter is phantom-
typed: a Field[S, uint64] cannot be written through Setter[T].
Setter holds two pointers (ob, b) — both into the active builder.
These pointers' targets always outlive the Setter (the builder owns
them and is itself reachable from the caller's stack). Setter values
are NOT safe to retain after the builder's Finish returns.
```go
func SetterFrom[S Schema](ob zap.ObjectBuilder, b *zap.Builder) Setter[S]
```
### View
View is a typed, zero-copy reference into a ZAP buffer. The type
parameter S identifies the schema; a View[A] and a View[B] are
distinct types and the compiler refuses to mix them. This is what
makes [Field] accessors safe: the compiler will not let you pass a
Field[A, uint64] to a View[B].Read.
View carries no per-instance schema state — the schema's identity
lives in the type parameter, and S&#123;}'s methods (Kind/Size/Name) are
queried only at boundaries (Wrap, Build, Registry). Inside the hot
path the type parameter is purely a compile-time tag; no boxing, no
interface dispatch.
View is safe to copy. The underlying buffer must outlive every View
that references it.
Layout: a View holds two slice headers — `data` aliases the full ZAP
buffer (used by variable-length tail accessors: list, bytes), and
`payload` aliases the fixed section of the root object (used by
Read/Write for direct unsafe-pointer indexing). Neither field is a
pointer to a heap-allocated `*zap.Message` — that was the v1.0 design
and forced a heap allocation per Wrap because the *Message escaped
from the View's lifetime. The v1.1 redesign keeps everything in slice
headers; View[S] is 56 bytes (3 slice headers + an int32 offset);
stack-allocatable; escape-analysis-friendly.
```go
func AsView[S Schema](r Raw) View[S]
```
```go
func Build[S Schema](init func(Setter[S])) (View[S], []byte)
```
```go
func NestedAt[S, N Schema](v View[S], offset uint32) View[N]
```
```go
func Wrap[S Schema](b []byte) (View[S], error)
```
```go
func WrapAs[S Schema](b []byte) (View[S], error)
```
```go
func WrapUnchecked[S Schema](b []byte) (View[S], error)
```
```go
func (v View[S]) Bytes() []byte
```
```go
func (v View[S]) IsZero() bool
```
@@ -0,0 +1 @@
{"pages":["codegen"]}
@@ -0,0 +1,281 @@
---
title: "zapclient"
description: "Package zapclient"
---
{/* Generated from godoc by tools/docgen — edit the Go source, not this file. */}
```go
import "github.com/luxfi/zap/zapclient"
```
## Functions
### MustOpcode
```go
func MustOpcode(name string) uint16
```
MustOpcode is ProcedureOpcode that panics on error. Use in
package-level var initialisers where a bad procedure name is a
build-time bug.
### ProcedureOpcode
```go
func ProcedureOpcode(name string) (uint16, error)
```
ProcedureOpcode returns the uint16 opcode for a procedure name.
The lower 8 bits are zero (matching the existing MsgType&lt;&lt;8 shape
in lux/zap); the upper 8 bits are FNV-1a(name) modulo 254 + 1.
Returns ErrReservedOpcode if the hash lands on 0x00 or 0xFF.
## Types
### AllowListVerifier
AllowListVerifier accepts only peers whose NodeID appears in
Allowed[procedure]. Empty Allowed[procedure] means the procedure is
not exposed.
```go
func (a AllowListVerifier) Verify(ctx context.Context, peer PeerInfo, procedure string) error
```
### Client
Client is a native ZAP RPC client. Created by Connect; closed by
Close (or returned stop function from MustConnect).
```go
func Connect(ctx context.Context, serviceType string, opts ...ClientOption) (*Client, error)
```
```go
func MustConnect(ctx context.Context, serviceType string, opts ...ClientOption) *Client
```
```go
func (c *Client) Broadcast(ctx context.Context, procedure string, req *zap.Message) map[string]error
```
```go
func (c *Client) Call(ctx context.Context, procedure string, req *zap.Message) (*zap.Message, error)
```
```go
func (c *Client) Close() error
```
```go
func (c *Client) Peers() []Peer
```
```go
func (c *Client) Send(ctx context.Context, procedure string, req *zap.Message) error
```
### ClientOption
ClientOption is the functional-option constructor knob.
```go
func WithBrowseInterval(d time.Duration) ClientOption
```
```go
func WithCallTimeout(d time.Duration) ClientOption
```
```go
func WithDiscoverTimeout(d time.Duration) ClientOption
```
```go
func WithDiscovery(d Discovery) ClientOption
```
```go
func WithLogger(l *slog.Logger) ClientOption
```
```go
func WithMinPeers(n int) ClientOption
```
```go
func WithNodeID(id string) ClientOption
```
```go
func WithPicker(p Picker) ClientOption
```
```go
func WithStaticPeers(addrs ...string) ClientOption
```
```go
func WithTLS(cfg *tls.Config) ClientOption
```
### ClientOptions
ClientOptions configure Connect. Construct via WithX options.
### Discovery
Discovery is the peer-enumeration contract.
Implementations MUST be goroutine-safe; the client may call Peers()
from every in-flight Call and Send.
### LocalTrustVerifier
LocalTrustVerifier accepts any peer presenting a valid mTLS cert
from the cluster CA. The underlying TLS handshake already verified
the chain; this verifier returns nil for every authenticated peer.
Use when:
- the peer is on the cluster-private network
- the listener was constructed with the cluster CA in ClientCAs
- all in-cluster services should be allowed to call all
procedures (apply per-procedure RBAC in the handler if needed)
```go
func (LocalTrustVerifier) Verify(ctx context.Context, peer PeerInfo, procedure string) error
```
### Peer
Peer is the discovery view of one reachable instance of a service.
NodeID uniquely identifies the peer within ServiceType across
discovery cycles. Address is the dial target (host:port). For
in-cluster peers under mTLS, the authenticated peer identity comes
from the TLS session — NodeID is a hint, not a trust anchor.
### PeerInfo
PeerInfo is the authenticated peer identity surfaced to server-side
handlers and PeerVerifier.
### PeerVerifier
PeerVerifier is the server-side authorisation hook. Runs on every
inbound message before procedure dispatch — return non-nil to
reject the call.
The PeerInfo carries the authenticated peer identity (NodeID +
mTLS cert chain if present). Procedure is the procedure name the
caller requested. PeerVerifier should compare the (peer, procedure)
pair against the service's authorisation policy.
### Picker
Picker selects one peer from a Discovery snapshot for a single
Call or Send. Implementations MUST be cheap (sub-microsecond) and
goroutine-safe; the client invokes Pick from every concurrent op.
### ProcedureHandler
ProcedureHandler runs server-side for one inbound procedure call.
Return nil + nil-err to ack a fire-and-forget Send. Return a
response + nil-err to reply to a Call. Return nil + non-nil err to
signal failure; the client side observes the err on its Call.
### RoundRobinPicker
RoundRobinPicker rotates through peers in registration order.
Zero-value is ready to use; goroutine-safe.
```go
func (p *RoundRobinPicker) Pick(peers []Peer) (Peer, error)
```
### Server
Server is the native ZAP server side: procedure-name dispatch on
top of *zap.Node. Construct with NewServer, Register procedures,
Start to begin accepting, Stop to release.
```go
func NewServer(serviceType string, opts ...ServerOption) (*Server, error)
```
```go
func (s *Server) Dispatch(ctx context.Context, from string, msg *zap.Message) (*zap.Message, error)
```
```go
func (s *Server) NodeID() string
```
```go
func (s *Server) Register(procedure string, h ProcedureHandler) error
```
```go
func (s *Server) Start() error
```
```go
func (s *Server) Stop()
```
### ServerOption
ServerOption is a functional-option knob.
```go
func WithNoDiscovery() ServerOption
```
```go
func WithServerLogger(l *slog.Logger) ServerOption
```
```go
func WithServerMetadata(m map[string]string) ServerOption
```
```go
func WithServerNodeID(id string) ServerOption
```
```go
func WithServerPort(p int) ServerOption
```
```go
func WithServerTLS(cfg *tls.Config) ServerOption
```
```go
func WithVerifier(v PeerVerifier) ServerOption
```
### ServerOptions
ServerOptions configure NewServer.
@@ -0,0 +1,20 @@
---
title: "main"
description: "Command echoserver runs a zapweb (ZAP-over-WebSocket) echo service for cross-language integration testing."
---
{/* Generated from godoc by tools/docgen — edit the Go source, not this file. */}
```go
import "github.com/luxfi/zap/zapweb/cmd/echoserver"
```
Command echoserver runs a zapweb (ZAP-over-WebSocket) echo service for
cross-language integration testing. It registers test.echo (prefixes
its text@0 argument with "echo:") and serves it at /zap, so the
TypeScript client in zapweb/ts can prove the browser path is wire-
compatible end to end.
go run ./zapweb/cmd/echoserver # serves :8089/zap
@@ -0,0 +1 @@
{"pages":["echoserver"]}
@@ -0,0 +1,78 @@
---
title: "zapweb"
description: "Package zapweb serves ZAP procedures over WebSocket so browser clients — which cannot open a raw TCP/QUIC socket — reach the EXACT same zapclient.Server procedure dispatch as native in-cluster peers."
---
{/* Generated from godoc by tools/docgen — edit the Go source, not this file. */}
```go
import "github.com/luxfi/zap/zapweb"
```
Package zapweb serves ZAP procedures over WebSocket so browser clients
— which cannot open a raw TCP/QUIC socket — reach the EXACT same
zapclient.Server procedure dispatch as native in-cluster peers.
One dispatch path, two transports:
- native ZAP (TCP/QUIC), via zap.Node, for service-to-service calls
- zapweb (WebSocket), via this package, for browser ↔ service calls
There is no REST here. The WebSocket handshake is an HTTP/1.1 Upgrade;
everything after it is binary ZAP frames, dispatched through
zapclient.Server.Dispatch (the same opcode routing + PeerVerifier the
native node uses).
# Wire format (one WebSocket BINARY message per call)
[ reqID uint32 LE ] correlation id — echoed on the reply
[ flag uint32 LE ] zap.ReqFlagReq on request, zap.ReqFlagResp on
reply, FlagErr on a dispatch-level failure
[ zap.Message bytes ] the procedure message; its Flags() header
carries the procedure opcode, identical to the
native transport. On a FlagErr reply the tail
is a UTF-8 error string instead.
The JS/TS client builds byte-identical frames (see zapweb/ts), so the
browser and a Go caller speak the same protocol to the same handlers.
## Functions
### Handler
```go
func Handler(srv *zapclient.Server, opts Options) http.Handler
```
Handler returns an http.Handler that upgrades to WebSocket and bridges
each binary frame to srv.Dispatch. Mount it on the daemon's listener
(e.g. at "/zap"); the same zapclient.Server already serves native ZAP
peers, so browsers and services share one procedure registry.
## Types
### Client
Client is a zapweb (ZAP-over-WebSocket) client. Construct with Dial,
always Close.
```go
func Dial(ctx context.Context, url string) (*Client, error)
```
```go
func (c *Client) Call(ctx context.Context, procedure string, req *zap.Message) (*zap.Message, error)
```
```go
func (c *Client) Close() error
```
### Options
Options configure Handler.
@@ -0,0 +1 @@
{"pages":["cmd"]}