dexsession

package
v1.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 29, 2026 License: BSD-3-Clause Imports: 9 Imported by: 0

Documentation

Overview

Package dexsession is the ZAP/Cap'n-Proto orchestration layer for the Lux DEX.

It makes the native C<->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/chains/dexvm/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.

Index

Constants

View Source
const (
	MsgQuote        uint16 = 0xA0 // QuoteRequest  -> QuoteResult
	MsgGetState     uint16 = 0xA1 // StateRequest  -> StateResult
	MsgPrepare      uint16 = 0xA2 // SwapIntentRequest -> PreparedIntent
	MsgNotify       uint16 = 0xA3 // PreparedIntent -> IntentWatchRef
	MsgWatchPoll    uint16 = 0xA4 // IntentWatchRef -> IntentStatus
	MsgImport       uint16 = 0xA5 // DExportRef -> SettlementSubmitResult
	MsgPipeline     uint16 = 0xA6 // PipelineBatch -> PipelineResult
	MsgAdminHalt    uint16 = 0xA7 // AdminRequest -> AdminStatus
	MsgWatchPush    uint16 = 0xA8 // server-initiated watch resolution (Push)
	MsgRoutePrepare uint16 = 0xA9 // RouteRequest -> PreparedIntent (route-intent calldata)
	MsgRouteStatus  uint16 = 0xAA // RouteWatchRef -> RouteStatus (hop progress + final ref)
)

--- Message types -----------------------------------------------------------

The ZAP dispatcher routes on msg.Flags()>>8 (FinishWithFlags(t<<8)), so a type must fit the upper byte (uint8). The 0xA0+ range avoids collision with forward's 0x80/0x81 and base/plugins' lower-byte IDs.

View Source
const AuthorityPublic = AuthQuote | AuthIntent | AuthWatch | AuthSettlement

AuthorityPublic is the grant a public API surface bootstraps with: everything a user needs to trade end-to-end, WITHOUT admin. Note this is the maximal user-facing grant and it STILL cannot move money — see the package invariant.

View Source
const AuthorityReadOnly = AuthQuote

AuthorityReadOnly is a quote/state-only grant (e.g. a price widget).

View Source
const SelectorModifyLiquidity uint32 = 0x5A6BCFDA

SelectorModifyLiquidity is the V4 modifyLiquidity selector — IDENTICAL to precompile/dex/module.go SelectorModifyLiquidity (0x5A6BCFDA):

modifyLiquidity((address,address,uint24,int24,address),(int24,int24,int256,bytes32),bytes)

The ModifyLiquidityParams tuple is (tickLower int24, tickUpper int24, liquidityDelta int256, salt bytes32). A POSITIVE liquidityDelta adds (commit C->D funds); a NEGATIVE delta removes (collect/decrease/cancel -> D->C export -> DS01 credit).

View Source
const SelectorSwap uint32 = 0xF3CD914C

SelectorSwap is the V4 swap selector — IDENTICAL to precompile/dex/module.go SelectorSwap (0xF3CD914C):

swap((address,address,uint24,int24,address),(bool,int256,uint160),bytes)

Both Phase A (intent) and Phase B (settlement) ride this one selector; the hookData CONTENTS select the phase (the V4 ABI is unchanged).

Variables

View Source
var (
	// ErrNoAuthority is returned when a session/capability is asked for an
	// operation outside its granted authority. Fail-closed: deny, never widen.
	ErrNoAuthority = errors.New("dexsession: capability lacks the required authority")
	// ErrRevoked is returned when a capability has been revoked at the session.
	ErrRevoked = errors.New("dexsession: capability revoked")
)
View Source
var (
	// ErrScopeMismatch is returned when a session capability is used for an action
	// (intent / pool / params / kind) it was not scoped to. Fail-closed.
	ErrScopeMismatch = errors.New("dexsession: capability is scoped to a different V4 action")
	// ErrScopeUnset is returned when a scoped operation is attempted on a session
	// whose scope was never bound (a zero scope never authorizes).
	ErrScopeUnset = errors.New("dexsession: V4 session scope is unset")
)
View Source
var ZeroID = ID{}

ZeroID is the all-zero ID — native asset / empty sentinel.

Functions

func DecodeAtomicObject

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.

func EncodeAtomicObject

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).

func EncodeIntentHookData

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]

func EncodeModifyLiquidityCalldata

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).

func EncodeSettlementHookData

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.)

func EncodeSwapCalldata

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.

func NewSession

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

type Account

type Account [20]byte

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.

type AdminCap

type AdminCap struct {
	// contains filtered or unexported fields
}

AdminCap permits halt/status only, and is granted SEPARATELY (AuthAdmin is not in AuthorityPublic). It cannot withdraw, credit, or override a match.

func (AdminCap) Authority

func (c AdminCap) Authority() Authority

type Authority

type Authority uint32

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.

const (
	AuthQuote      Authority = 1 << 0 // read book/state (Quote, GetState)
	AuthIntent     Authority = 1 << 1 // build a C->D intent + notify (PrepareSwapIntent, NotifyIntent)
	AuthWatch      Authority = 1 << 2 // subscribe to a D result (Poll, OnCommitted)
	AuthSettlement Authority = 1 << 3 // point C at an existing D->C object (ImportSettlement)
	AuthAdmin      Authority = 1 << 4 // halt / status only (separately gated)
)

type CollectRequest

type CollectRequest struct {
	NetworkID      uint32
	CChainID       ID
	DChainID       ID
	Account        Account
	MarketID       ID
	TickLower      int32
	TickUpper      int32
	LiquidityDelta int64 // <0 to remove/collect (a cancel removes the resting amount)
	Salt           ID
	AssetOut       ID // the asset the collect/cancel returns (for correlation)
	CallIndex      uint32
	IsCancel       bool // true => cancel a resting order; false => collect/decrease
}

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.

type DExportRef

type DExportRef struct {
	SourceChainID ID
	SourceTxID    ID
	OutputIndex   uint32
	IntentID      ID
}

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).

func (DExportRef) ObjectKey

func (r DExportRef) ObjectKey() ID

ObjectKey is the shared-memory UTXO key the on-chain ImportSettlement reads. Derived deterministically from (SourceTxID, OutputIndex) — the chain derives the same key and binds the object to it, so a forged key just names a missing object and the import reverts.

type DexSession

type DexSession interface {
	// Capability derivation (Cap'n-Proto: you get authority by ASKING the session;
	// it only hands out caps within its bootstrap grant).
	QuoteCap() (QuoteCap, error)
	IntentCap() (IntentCap, error)
	WatchCap() (WatchCap, error)
	SettlementCap() (SettlementCap, error)
	AdminCap() (AdminCap, error)

	// Operations. Each returns a Promise for pipelining; Await for the value.
	Quote(ctx context.Context, req QuoteRequest) *Promise[QuoteResult]
	GetState(ctx context.Context, req StateRequest) *Promise[StateResult]
	PrepareSwapIntent(ctx context.Context, req SwapIntentRequest) *Promise[PreparedIntent]
	NotifyIntent(ctx context.Context, intent *Promise[PreparedIntent]) *Promise[IntentWatch]
	ImportSettlement(ctx context.Context, ref *Promise[DExportRef]) *Promise[SettlementSubmitResult]

	// Grant reports the authority this session was bootstrapped with.
	Grant() Authority
}

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).

type FlowStages

type FlowStages struct {
	Quote  *Promise[QuoteResult]
	Intent *Promise[PreparedIntent]
	Watch  *Promise[IntentWatch]
	// Committed resolves to the D->C export POINTER when D commits.
	Committed *Promise[DExportRef]
	// Settle resolves to the on-chain settlement calldata/tx (bytes/pointer).
	Settle *Promise[SettlementSubmitResult]
}

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.

type FullServiceConfig

type FullServiceConfig struct {
	Node  *zaplib.Node
	Venue Venue
	// Markets maps a marketID to its V4 PoolKey args, shared with bootstrapped
	// client sessions so prepareSwapIntent can build calldata. Required for swap
	// preparation; nil => prepare fail-closes for every market.
	Markets func(marketID ID) (PoolKeyArgs, bool)
}

FullServiceConfig configures the server node.

type FullServiceNode

type FullServiceNode struct {
	// contains filtered or unexported fields
}

FullServiceNode wraps a zap.Node and a Venue, exposing Bootstrap. It is the server-side object the spec's `FullServiceNode.bootstrap() -> DexSession` describes.

func NewFullServiceNode

func NewFullServiceNode(cfg FullServiceConfig) *FullServiceNode

NewFullServiceNode constructs the server and registers its ZAP handlers on the node. Call node.Start() separately (the caller owns the node lifecycle).

func (*FullServiceNode) Bootstrap

func (fsn *FullServiceNode) Bootstrap(peerID string, grant Authority) DexSession

Bootstrap returns a RESTRICTED client DexSession for a peer, granted `grant` authority. This is the capability bootstrap: the caller receives a session that can derive only capabilities within `grant`, and NONE of those can move money (see capability.go). A public surface passes AuthorityPublic; a price widget passes AuthorityReadOnly; an operator console adds AuthAdmin.

peerID is the ZAP node id of THIS server (the session Calls it). In-process and remote callers both use the node's correlated Call.

type ID

type ID [32]byte

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).

func DeriveIntentID

func DeriveIntentID(
	networkID uint32,
	cChainID, dChainID ID,
	account Account,
	assetIn ID,
	amountIn uint64,
	marketID ID,
	nonce uint64,
) ID

DeriveIntentID computes the deterministic id of a C->D atomic intent object, byte-identical to precompile/dex/native_wire.go DeriveIntentID:

SHA-256( domain | networkID | cChainID | dChainID |
         account | assetIn | amountIn | marketID | nonce )

Every component is fixed width and length-stable. There is NO txID: the id is CHAIN-OBSERVABLE — dexsession derives the SAME id the on-chain SubmitSwapIntent mints, from values known BEFORE the user signs (the nonce carried in the swap's DI01 hookData), so the watch can PRE-REGISTER against the live chain. (Previously dexsession substituted a zero txID while the chain used the real one, so the ids never matched.) It is a NAME, never an authority.

FEE-ON-TRANSFER CAVEAT (the amountIn axis). On-chain, SubmitSwapIntent derives the id over the OBSERVED-DELTA `locked` amount (what the 0x9999 vault actually received), NOT the requested amount — because for an ERC-20 the object must be backed by the value really locked. For native LUX and standard (non-fee-on-transfer) ERC-20s, locked == amountIn, so this off-chain pre-derivation EQUALS the on-chain id exactly and the pre-registered watch fires. For a FEE-ON-TRANSFER token, locked < amountIn, so this pre-derived id DIVERGES from the on-chain id. That is a missed pre-registration optimization, NOT a broken correlation or a safety issue: the on-chain IntentSubmitted event carries the REAL id as its INDEXED topic (and the real `locked` amount), so a keeper correlates by watching that event topic — the AUTHORITATIVE binding — and builds the D order from the event, never from a guessed id. The pre-derived id here is therefore valid for pre-registration exactly when locked == amountIn; for fee-on-transfer inputs, correlate via the IntentSubmitted indexed topic.

func DeriveLiquidityParamsHash

func DeriveLiquidityParamsHash(tickLower, tickUpper int32, liquidityDelta int64, salt ID) ID

DeriveLiquidityParamsHash hashes the modifyLiquidity-defining params (tick range, signed liquidity delta, salt) into the scope's ParamsHash.

func DerivePoolKeyHash

func DerivePoolKeyHash(pk PoolKeyArgs) ID

DerivePoolKeyHash hashes a V4 PoolKey tuple into the scope's PoolKeyHash. It binds the exact market a session may act on; a capability for pool P1 cannot drive pool P2 because the derived session ids differ.

func DeriveRoutePathHash

func DeriveRoutePathHash(path []ID) ID

DeriveRoutePathHash hashes a route path (the ordered marketID list) into a scope PoolKeyHash. Two routes with different paths derive different session ids, so a route capability is confined to its exact path.

func DeriveSessionID

func DeriveSessionID(s V4ActionScope) ID

DeriveSessionID is the stable, deterministic id of a V4 action scope. It binds EVERY distinguishing field, so the id is a faithful fingerprint of the action: any change to network/chain/account/pool/params/kind/intent yields a different id, and a capability presented for one session id cannot satisfy another.

func DeriveSwapParamsHash

func DeriveSwapParamsHash(zeroForOne bool, amountIn uint64) ID

DeriveSwapParamsHash hashes the swap-defining params (direction + exact-input amount) into the scope's ParamsHash. Two swaps that differ in direction or amount derive different session ids — a capability for one cannot drive the other.

func DeriveUTXOID

func DeriveUTXOID(sourceTxID ID, outputIndex uint32) ID

DeriveUTXOID computes the deterministic D->C export object key from (sourceTxID, outputIndex), byte-identical to chains/dexvm/atomic.go deriveUTXOID (SHA-256 over txID||index). importSettlement uses it to name the object the on-chain ImportSettlement will read; the chain re-derives and binds it, so a wrong index just names a non-existent (or someone else's) object and the on-chain bind rejects.

type IntentCap

type IntentCap struct {
	// contains filtered or unexported fields
}

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.

func (IntentCap) Authority

func (c IntentCap) Authority() Authority

type IntentPhase

type IntentPhase uint8

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.

const (
	PhasePending   IntentPhase = 0 // notified; D has not produced a block yet
	PhaseMatching  IntentPhase = 1 // D imported the C->D object, matching in progress
	PhaseCommitted IntentPhase = 2 // D exported a D->C object (a D block committed)
	PhaseRejected  IntentPhase = 3 // D could not import/match (e.g. unbacked, expired)
	PhaseUnknown   IntentPhase = 4 // watcher has no information
)

type IntentStatus

type IntentStatus struct {
	IntentID   ID
	Phase      IntentPhase
	Ref        DExportRef // valid only when Phase==PhaseCommitted
	Reason     string     // human-readable, for Rejected/Unknown
	MatchedOut uint64     // ESTIMATE (matched output) — orchestration only, never a credit
}

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).

type IntentWatch

type IntentWatch struct {
	// contains filtered or unexported fields
}

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.

func (IntentWatch) IntentID

func (w IntentWatch) IntentID() ID

IntentID is the intent this watch tracks.

func (IntentWatch) OnCommitted

func (w IntentWatch) OnCommitted(ctx context.Context) *Promise[DExportRef]

OnCommitted returns a Promise that resolves to the committed DExportRef. It pipelines: the returned promise feeds straight into ImportSettlement. Internally it streams the watch — polling the server until PhaseCommitted (a committed D block produced the export) or a terminal PhaseRejected, with a bounded backoff.

This is the streaming watch the spec asks for: the caller writes watch.OnCommitted().then(ImportSettlement) and the resolution arrives when D has actually committed — not a synchronous fiction. (A server that supports push resolution sends MsgWatchPush; the client treats a push and a poll identically — both deliver an IntentStatus, and only a Committed status with a ref resolves the promise.)

CANNOT move value: the promise resolves to a POINTER. The credit happens later, on-chain, against the real object.

func (IntentWatch) Poll

func (w IntentWatch) Poll(ctx context.Context) (IntentStatus, error)

Poll observes the current status once. Gated by AuthWatch. Read-only.

type IntentWatchRef

type IntentWatchRef struct {
	IntentID ID
}

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.

type LiquidityRequest

type LiquidityRequest struct {
	NetworkID      uint32
	CChainID       ID
	DChainID       ID
	Account        Account
	MarketID       ID
	TickLower      int32
	TickUpper      int32
	LiquidityDelta int64 // >0 to commit funds
	Salt           ID
	AssetIn        ID
	AmountIn       uint64 // the C-side funding amount locked for the position
	CallIndex      uint32
}

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.

type ModifyLiquidityArgs

type ModifyLiquidityArgs struct {
	TickLower      int32 // int24
	TickUpper      int32 // int24
	LiquidityDelta int64 // int256 (we carry the int64 magnitude+sign; ABI sign-extends)
	Salt           ID    // bytes32 position salt
}

ModifyLiquidityArgs are the V4 ModifyLiquidityParams tuple fields. LiquidityDelta is signed: >0 add, <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.

type PoolKeyArgs

type PoolKeyArgs struct {
	Currency0   Account
	Currency1   Account
	Fee         uint32 // uint24
	TickSpacing int32  // int24
	Hooks       Account
}

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.

type PreparedIntent

type PreparedIntent struct {
	To        Account // 0x9999
	Calldata  []byte  // selector + args (the user signs a tx with this data)
	HookData  []byte  // V4 hookData consumed by the 0x9999 handler
	IntentID  ID      // deterministic id the signed tx will create on C
	QuotedOut uint64  // estimate only
	// Echoed request identity so the watch / import can reconstruct the binding
	// without holding the whole request.
	DChainID  ID
	CChainID  ID
	Account   Account
	Recipient Account
	AssetIn   ID
	AmountIn  uint64
	MarketID  ID
}

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.

type Promise

type Promise[T any] struct {
	// contains filtered or unexported fields
}

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).

func (*Promise[T]) Await

func (p *Promise[T]) Await(ctx context.Context) (T, error)

Await blocks until the promise resolves or ctx is cancelled. On cancellation it returns the context error WITHOUT resolving the promise (a later resolution still delivers to other awaiters).

type QuoteCap

type QuoteCap struct {
	// contains filtered or unexported fields
}

QuoteCap permits read-only book/state queries. It can quote and observe; it cannot build an intent, notify, settle, or administer.

func (QuoteCap) Authority

func (c QuoteCap) Authority() Authority

Authority reports the bits each capability was granted (introspection / tests).

type QuoteRequest

type QuoteRequest struct {
	MarketID   ID
	AmountIn   uint64
	ZeroForOne bool // sell currency0 for currency1
}

QuoteRequest asks the D book for an estimated output. Read-only.

type QuoteResult

type QuoteResult struct {
	MarketID    ID
	AmountIn    uint64
	AmountOut   uint64 // ESTIMATE ONLY — not enforceable, not a credit
	BestPricePx uint64 // top-of-book price, IEEE-754 bits (informational)
	Liquid      bool   // false = no resting book / market unknown
}

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).

type RoutePhase

type RoutePhase uint8

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.

const (
	RoutePending   RoutePhase = 0 // notified; D has not started the route
	RouteHopping   RoutePhase = 1 // D is walking the path (a hop is in progress)
	RouteCommitted RoutePhase = 2 // D produced the ONE final D->C export
	RouteRefunded  RoutePhase = 3 // route failed; D produced the ONE refund export
	RouteRejected  RoutePhase = 4 // D could not import/route (e.g. unbacked, expired)
	RouteUnknown   RoutePhase = 5
)

type RouteRequest

type RouteRequest struct {
	NetworkID    uint32
	CChainID     ID
	DChainID     ID
	Account      Account // taker — bound as the single C->D input object owner
	AssetIn      ID      // the input asset locked on C (hop 0 input)
	AssetInAddr  Account // ERC-20 token (zero for native) for the C lock
	AmountIn     uint64  // the SINGLE input amount
	Path         []ID    // ordered marketIDs the route walks: A->B->C
	MinAmountOut uint64  // floor on the FINAL output asset (D-enforced)
	Recipient    Account // where the FINAL D->C export must credit
	Deadline     uint64
	CallIndex    uint32
}

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).

type RouteStatus

type RouteStatus struct {
	IntentID     ID
	Phase        RoutePhase
	HopIndex     uint32     // the current/last hop the router reached (0-based)
	HopCount     uint32     // total hops in the path
	HopAmountOut uint64     // ESTIMATE: output of the current hop (orchestration only)
	FinalOut     uint64     // ESTIMATE: final output (orchestration only)
	Ref          DExportRef // the ONE final (or refund) export; valid at Committed/Refunded
	Reason       string
}

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.

type RouteVenue

type RouteVenue interface {
	// NotifyRoute tells the venue to scan the SINGLE C->D input object the user's
	// signed tx created and begin walking the path carried in the request. It returns
	// the deterministic route intent id to watch; it does NOT build calldata (the
	// SESSION builds calldata client-side from the user's own path, so a malicious
	// server cannot inject a forged path the user would sign), does not block on a D
	// block, and credits nothing.
	NotifyRoute(req RouteRequest) (IntentWatchRef, error)

	// RouteStatus reports the current route phase + hop progress, and (when D has
	// committed) the POINTER to the ONE final (or refund) export. It reads D/shared-
	// memory state; it never fabricates a credit.
	RouteStatus(intentID ID) (RouteStatus, error)
}

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.

type RouteWatch

type RouteWatch struct {
	// contains filtered or unexported fields
}

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.

func (RouteWatch) IntentID

func (w RouteWatch) IntentID() ID

IntentID is the route intent this watch tracks.

func (RouteWatch) Poll

func (w RouteWatch) Poll(ctx context.Context) (RouteStatus, error)

Poll observes the current route status once (read-only). Gated by AuthWatch.

type SessionConfig

type SessionConfig struct {
	Conn    caller
	PeerID  string
	Grant   Authority
	Markets func(marketID ID) (PoolKeyArgs, bool)
}

SessionConfig configures a client session (normally produced by Bootstrap, but exported so a node can construct one directly against a known peer).

type SettlementCap

type SettlementCap struct {
	// contains filtered or unexported fields
}

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.

func (SettlementCap) Authority

func (c SettlementCap) Authority() Authority

type SettlementMode

type SettlementMode uint8

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.

const (
	// SettleCalldata: the caller (a wallet / keeper) signs a C tx to 0x9999 with
	// the returned Calldata. The chain's ImportSettlement reads the real D->C
	// object and credits the recipient.
	SettleCalldata SettlementMode = 0
	// SettleSubmitted: a keeper with a (non-custodial) signing key already
	// submitted the C tx; CTxHash is the submitted tx. The keeper cannot alter
	// recipient/asset/amount — the chain binds them to the object.
	SettleSubmitted SettlementMode = 1
)

type SettlementSubmitResult

type SettlementSubmitResult struct {
	Mode      SettlementMode
	To        Account // 0x9999
	Calldata  []byte  // selector + SettlementClaim args, points at the object
	ObjectKey ID      // the shared-memory key the chain will read
	CTxHash   ID      // set only in SettleSubmitted mode
}

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.

type StateKind

type StateKind uint8

StateKind selects which read-only view getState returns.

const (
	StateMarket  StateKind = 0 // market existence / base+quote asset ids
	StateBalance StateKind = 1 // an account's D available balance for an asset
	StateIntent  StateKind = 2 // a C->D intent's known routing status (off-chain view)
)

type StateRequest

type StateRequest struct {
	Kind     StateKind
	MarketID ID
	Account  Account
	Asset    ID
	IntentID ID
}

StateRequest is a read-only DEX state query.

type StateResult

type StateResult struct {
	Kind      StateKind
	Exists    bool
	BaseID    ID
	QuoteID   ID
	Available uint64 // D available balance (observation only)
	Known     bool   // intent known to the venue's router index
}

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.

type SwapIntentRequest

type SwapIntentRequest struct {
	NetworkID    uint32
	CChainID     ID
	DChainID     ID
	Account      Account // taker — bound as the C->D object owner on-chain
	AssetIn      ID      // full injective asset id locked on C
	AssetInAddr  Account // ERC-20 token (zero for native) for the C lock
	AmountIn     uint64
	MarketID     ID
	MinAmountOut uint64  // taker slippage floor — encoded into the signed tx
	Recipient    Account // where the D->C settlement must credit
	Deadline     uint64
	// Nonce is the taker's intent disambiguator, carried in the swap's DI01 hookData and
	// folded into DeriveIntentID. It makes the intent id CHAIN-OBSERVABLE — the off-chain
	// prepare derives the SAME id the on-chain SubmitSwapIntent mints (the watch then
	// correlates against the live chain), and it distinguishes two otherwise-identical
	// swaps. The user's wallet sets it (a per-account order nonce); 0 for a single swap.
	// It REPLACES the former CallIndex (which depended on the unknown-pre-signing txID
	// ordering and so could not be reproduced off-chain).
	Nonce uint64
}

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.

type V4Action

type V4Action uint8

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.

const (
	ActionSwap V4Action = iota
	ActionRoute
	ActionModifyLiquidity
	ActionCollect
	ActionCancel
	ActionState
)

func (V4Action) String

func (a V4Action) String() string

type V4ActionScope

type V4ActionScope struct {
	NetworkID   uint32
	CChainID    ID
	DChainID    ID
	Addr9999    Account
	Account     Account
	PoolKeyHash ID
	ParamsHash  ID
	Kind        V4Action
	IntentID    ID
}

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).

type V4CollectSession

type V4CollectSession struct {
	// contains filtered or unexported fields
}

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.

func (*V4CollectSession) Close

func (s *V4CollectSession) Close()

Close retires the collect session's capability.

func (*V4CollectSession) IntentID

func (s *V4CollectSession) IntentID() ID

func (*V4CollectSession) OnExportReady

func (s *V4CollectSession) OnExportReady(ctx context.Context, watch IntentWatch) *Promise[DExportRef]

OnExportReady [D->C, V4_COLLECT_D_EXPORT_READY / V4_CANCEL_D_EXPORT_READY] resolves to the D->C export POINTER of the collected/cancelled value.

func (*V4CollectSession) SessionID

func (s *V4CollectSession) SessionID() ID

func (*V4CollectSession) WriteNotify

func (s *V4CollectSession) WriteNotify(ctx context.Context, intent *Promise[PreparedIntent]) *Promise[IntentWatch]

WriteNotify [C->D] tells D to scan the removal request and produce the D->C export.

func (*V4CollectSession) WritePrepareCSettlement

func (s *V4CollectSession) WritePrepareCSettlement(ctx context.Context, ref *Promise[DExportRef]) *Promise[SettlementSubmitResult]

WritePrepareCSettlement [C->D] settles the export via the ONE DS01 credit path — byte-identical to a swap settlement. The collected/cancelled value credits on-chain when the real object is consumed.

func (*V4CollectSession) WriteRequest

func (s *V4CollectSession) WriteRequest(ctx context.Context) *Promise[PreparedIntent]

WriteRequest [C->D, V4_COLLECT_REQUEST / V4_CANCEL_REQUEST] builds the 0x9999 modifyLiquidity (-delta) calldata the user signs to ask D to remove. The removal's output becomes a D->C export the session then settles. It reserves/credits nothing.

type V4Config

type V4Config struct {
	Session   DexSession
	NetworkID uint32
	CChainID  ID
	DChainID  ID
	// Addr9999 is the on-chain settlement authority. Defaults to the canonical
	// 0x...9999 if zero.
	Addr9999 Account
}

V4Config configures the V4 precompile session. The chain identifiers and the 0x9999 address are fixed for the deployment; they bind every action's scope.

type V4Dir

type V4Dir uint8

V4Dir classifies a message by who originates it on the bidirectional plane.

const (
	// DirCToD is a C-side -> D-side WRITE (request side of a Call, or the user's
	// signed C tx the write refers to). It may trigger D work; it never moves value.
	DirCToD V4Dir = iota
	// DirDToC is a D-side -> C-side READ (response side of a Call, or a watch
	// push/poll). It carries estimates / status / a POINTER; never a credit.
	DirDToC
	// DirLocal is a client-side lifecycle transition (open/close): it mints/retires a
	// scoped capability and session state. It touches no peer and no value.
	DirLocal
)

type V4Event

type V4Event struct {
	Type      V4MsgType
	SessionID ID
	IntentID  ID
	EstAmount uint64
	HopIndex  uint32
	Ref       DExportRef
	Reason    string
}

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.

func (V4Event) HasRef

func (e V4Event) HasRef() bool

HasRef reports whether the event carries a settleable export pointer. Even when true, the pointer only NAMES an object the chain re-verifies on import — it is not a credit.

type V4LiquiditySession

type V4LiquiditySession struct {
	// contains filtered or unexported fields
}

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).

func (*V4LiquiditySession) Close

func (s *V4LiquiditySession) Close()

Close retires the liquidity session's capability.

func (*V4LiquiditySession) IntentID

func (s *V4LiquiditySession) IntentID() ID

func (*V4LiquiditySession) SessionID

func (s *V4LiquiditySession) SessionID() ID

func (*V4LiquiditySession) WriteNotifyCToDExport

func (s *V4LiquiditySession) WriteNotifyCToDExport(ctx context.Context, intent *Promise[PreparedIntent]) *Promise[IntentWatch]

WriteNotifyCToDExport [C->D, V4_LP_NOTIFY_C_EXPORT] tells D to scan the funded C->D position object and record the position. Returns a watch (position-open is the committed phase).

func (*V4LiquiditySession) WritePrepareCommit

func (s *V4LiquiditySession) WritePrepareCommit(ctx context.Context) *Promise[PreparedIntent]

WritePrepareCommit [C->D, V4_LP_PREPARE_COMMIT] builds the 0x9999 modifyLiquidity (+delta) calldata the user signs to fund the position from C. It reserves nothing off-chain; the C lock happens when the user's signed tx executes. Scoped to this position's exact params.

type V4MsgType

type V4MsgType uint16

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.

const (
	V4_SWAP_OPEN V4MsgType = 0x1000 + iota
	V4_SWAP_PREPARE_INTENT
	V4_SWAP_NOTIFY_C_EXPORT
	V4_SWAP_D_IMPORTED
	V4_SWAP_MATCHED
	V4_SWAP_D_EXPORT_READY
	V4_SWAP_PREPARE_C_SETTLEMENT
	V4_SWAP_SUBMIT_C_SETTLEMENT
	V4_SWAP_C_SETTLED
)

---------------------------------------------------------------------------- Swap action (V4SwapSession): the canonical single-swap lifecycle. ----------------------------------------------------------------------------

Lifecycle (promise-pipelined), with direction in brackets:

OPEN [local]
  -> PREPARE_INTENT     [C->D] build 0x9999 swap calldata (DI01) the user signs
  -> NOTIFY_C_EXPORT    [C->D] tell D to scan the C->D object the signed tx made
  <- D_IMPORTED         [D->C] D imported the C->D object (Phase Matching)
  <- MATCHED            [D->C] D matched (MatchResult.amountOut = ESTIMATE only)
  <- D_EXPORT_READY     [D->C] D committed a D->C export (DExportRef POINTER)
  -> PREPARE_C_SETTLEMENT [C->D] build 0x9999 settlement calldata (DS01) at the ref
  -> SUBMIT_C_SETTLEMENT  [C->D] (optional) a keeper submits the signed C tx
  <- C_SETTLED          [D->C] C consumed the real object and credited (observed)
const (
	V4_ROUTE_OPEN V4MsgType = 0x1100 + iota
	V4_ROUTE_PREPARE_INTENT
	V4_ROUTE_NOTIFY_C_EXPORT
	V4_ROUTE_HOP_STARTED
	V4_ROUTE_HOP_FILLED
	V4_ROUTE_D_EXPORT_READY
	V4_ROUTE_REFUND_READY
	V4_ROUTE_PREPARE_C_SETTLEMENT
	V4_ROUTE_C_SETTLED
)

---------------------------------------------------------------------------- Route action (V4RouteSession): MULTI-HOP. C input -> D route A->B->C -> ONE final D->C export. The route STAYS ON D between hops; ZAP streams hop progress. ----------------------------------------------------------------------------

OPEN [local]
  -> PREPARE_INTENT     [C->D] ONE route-intent calldata (DI01 + path body)
  -> NOTIFY_C_EXPORT    [C->D] tell D to scan the single C->D input object
  <- HOP_STARTED        [D->C] D began hop i (orchestration — no C object)
  <- HOP_FILLED         [D->C] D filled hop i (intermediate amount — orchestration)
  <- D_EXPORT_READY     [D->C] D committed the ONE final D->C export (POINTER)
  <- REFUND_READY       [D->C] route failed: D exported a refund of the INPUT asset
  -> PREPARE_C_SETTLEMENT [C->D] settle the ONE final (or refund) export (DS01)
  <- C_SETTLED          [D->C] C credited from the single real object (observed)
const (
	V4_LP_OPEN V4MsgType = 0x1200 + iota
	V4_LP_PREPARE_COMMIT
	V4_LP_NOTIFY_C_EXPORT
	V4_LP_D_COMMITTED
	V4_LP_POSITION_OPEN
)

---------------------------------------------------------------------------- Liquidity action (V4LiquiditySession): modifyLiquidity COMMIT — a funded C->D position. A deposit: it creates a C->D object only. No D->C export, no C credit. ----------------------------------------------------------------------------

OPEN [local]
  -> PREPARE_COMMIT     [C->D] build 0x9999 modifyLiquidity calldata (+delta)
  -> NOTIFY_C_EXPORT    [C->D] tell D to scan the funded C->D position object
  <- D_COMMITTED        [D->C] D recorded the position (orchestration)
  <- POSITION_OPEN      [D->C] the position id is live (orchestration)
const (
	V4_COLLECT_OPEN V4MsgType = 0x1300 + iota
	V4_COLLECT_REQUEST
	V4_COLLECT_D_EXPORT_READY
	V4_COLLECT_PREPARE_C_SETTLEMENT
	V4_COLLECT_C_SETTLED
)

---------------------------------------------------------------------------- Collect action (V4CollectSession): collect/decrease — D->C export -> C credit. The withdrawn fees / removed liquidity become a D->C object settled via DS01. ----------------------------------------------------------------------------

OPEN [local]
  -> REQUEST            [C->D] ask D to collect/decrease (build -delta calldata)
  <- D_EXPORT_READY     [D->C] D committed the D->C export of the collected value
  -> PREPARE_C_SETTLEMENT [C->D] settle the export (DS01) -> credit
  <- C_SETTLED          [D->C] C credited from the real object (observed)
const (
	V4_CANCEL_OPEN V4MsgType = 0x1400 + iota
	V4_CANCEL_REQUEST
	V4_CANCEL_D_EXPORT_READY
	V4_CANCEL_PREPARE_C_SETTLEMENT
	V4_CANCEL_C_SETTLED
)

---------------------------------------------------------------------------- Cancel action (V4 openCancel): cancel a resting order -> D->C refund export -> C credit (of the originally-locked asset). Same credit path as collect (DS01). ----------------------------------------------------------------------------

const (
	V4_STATE_OPEN V4MsgType = 0x1500 + iota
	V4_STATE_QUOTE_UPDATE
	V4_STATE_BOOK_UPDATE
	V4_STATE_CLOSE
)

---------------------------------------------------------------------------- State action (V4StateSession): read-only quote/book/state streaming. ----------------------------------------------------------------------------

const (
	// V4_ERROR is a D->C error read (the venue reports a non-fatal failure for this
	// action). It carries a reason; it never moves value.
	V4_ERROR V4MsgType = 0x1F00 + iota
	// V4_HALTED is a D->C read that the action's market/asset/global swap path is
	// halted. The session refuses to advance (fail-secure). It moves no value (a halt
	// blocks NEW swaps; funds still exit via the un-halted settlement path on-chain).
	V4_HALTED
)

Common terminal reads, valid on any session.

func (V4MsgType) CreditsValue

func (t V4MsgType) CreditsValue() bool

CreditsValue reports whether a message-type, by ITSELF, credits a C balance. IT IS ALWAYS FALSE. This is the machine-checkable statement of the money-plane invariant over the entire bidirectional message set: no control-plane message — in either direction — moves value. The credit happens on-chain, off this plane, when a real D->C atomic object is consumed (DS01 Phase-B settlement). The RED suite proves this dynamically; this method states it for the type system and for any caller that wants to assert it (e.g. a gateway audit).

func (V4MsgType) Dir

func (t V4MsgType) Dir() V4Dir

Dir reports the control-plane direction of a message-type. C->D writes carry a request to D (or refer to the user's signed C tx); D->C reads carry status / estimates / a pointer; local transitions are client-side capability lifecycle.

func (V4MsgType) String

func (t V4MsgType) String() string

String renders a message-type for logs/tests. (No fmt dependency in the hot path; a small switch keeps it allocation-free for the common types.)

type V4PrecompileSession

type V4PrecompileSession struct {
	// contains filtered or unexported fields
}

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 {networkID, cChainID, dChainID, 0x9999, account, poolKeyHash, paramsHash, intentID} for exactly that action, so a session cannot drive any other intent/pool/params/kind.

func NewV4PrecompileSession

func NewV4PrecompileSession(cfg V4Config) (*V4PrecompileSession, error)

NewV4PrecompileSession builds the top-level session over a bootstrapped DexSession. The DexSession MUST be a *clientSession (the only implementation); a foreign implementation is rejected, because the V4 layer composes the concrete client ops.

func (*V4PrecompileSession) OpenCancel

OpenCancel opens a cancel session (cancel a resting order; refund the locked asset). Same removal shape as collect; the credit is the identical DS01 settlement.

func (*V4PrecompileSession) OpenCollect

func (v *V4PrecompileSession) OpenCollect(req CollectRequest) (*V4CollectSession, error)

OpenCollect opens a collect/decrease session (negative-delta removal). It produces a D->C export -> C credit via DS01, so it needs the watch + settlement authorities.

func (*V4PrecompileSession) OpenModifyLiquidity

func (v *V4PrecompileSession) OpenModifyLiquidity(req LiquidityRequest) (*V4LiquiditySession, error)

OpenModifyLiquidity opens a position-commit session scoped to {req}. A commit is a DEPOSIT: it creates a C->D funded-position object and never credits C, so the session needs only the intent authority (build + notify), not settlement.

func (*V4PrecompileSession) OpenRoute

func (v *V4PrecompileSession) OpenRoute(req RouteRequest) (*V4RouteSession, error)

OpenRoute opens a MULTI-HOP route session scoped to {req.Path}. The route intent id is derived from the FIRST hop's market (the input lock binds there) and the path is hashed into the scope, so this capability can drive ONLY this exact path.

func (*V4PrecompileSession) OpenState

func (v *V4PrecompileSession) OpenState(account Account, marketID ID) (*V4StateSession, error)

OpenState opens a read-only state session scoped to a market. It needs only the quote authority; it has no write or settlement surface.

func (*V4PrecompileSession) OpenSwap

openSwap opens a single-swap session scoped to {req}. The intent id is derived deterministically (identically to the on-chain SubmitSwapIntent) and frozen into the scope, so this session's capability can drive ONLY this swap. Requires the intent + watch + settlement authorities (a swap walks the full lifecycle).

type V4RouteSession

type V4RouteSession struct {
	// contains filtered or unexported fields
}

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.

func (*V4RouteSession) Close

func (s *V4RouteSession) Close()

Close retires the route session's capability.

func (*V4RouteSession) IntentID

func (s *V4RouteSession) IntentID() ID

func (*V4RouteSession) OnFinalExport

func (s *V4RouteSession) OnFinalExport(ctx context.Context, watch RouteWatch) *Promise[DExportRef]

OnFinalExport [D->C] resolves to the ONE final (or refund) export POINTER when the route terminates. This is the SINGLE settleable pointer of the whole route.

func (*V4RouteSession) Path

func (s *V4RouteSession) Path() []ID

func (*V4RouteSession) ReadStream

func (s *V4RouteSession) ReadStream(ctx context.Context, watch RouteWatch) <-chan V4Event

ReadStream [D->C] streams the route's D->C reads (HOP_STARTED / HOP_FILLED -> D_EXPORT_READY or REFUND_READY) as V4Events. Hop events carry intermediate amounts as ESTIMATES; only the terminal export/refund carries the ONE settleable POINTER.

func (*V4RouteSession) SessionID

func (s *V4RouteSession) SessionID() ID

func (*V4RouteSession) WriteNotifyCToDExport

func (s *V4RouteSession) WriteNotifyCToDExport(ctx context.Context, intent *Promise[PreparedIntent]) *Promise[RouteWatch]

WriteNotifyCToDExport [C->D, V4_ROUTE_NOTIFY_C_EXPORT] tells D to scan the SINGLE C->D input object and walk the path. Returns a route watch.

func (*V4RouteSession) WritePrepareCSettlement

func (s *V4RouteSession) WritePrepareCSettlement(ctx context.Context, ref *Promise[DExportRef]) *Promise[SettlementSubmitResult]

WritePrepareCSettlement [C->D, V4_ROUTE_PREPARE_C_SETTLEMENT] settles the ONE final (or refund) export. Identical to the swap settlement — the route's single output is just a D->C object consumed by the one DS01 credit path.

func (*V4RouteSession) WritePrepareIntent

func (s *V4RouteSession) WritePrepareIntent(ctx context.Context) *Promise[PreparedIntent]

WritePrepareIntent [C->D, V4_ROUTE_PREPARE_INTENT] builds the ONE route-intent calldata: a 0x9999 swap (DI01 intent) on the ENTRY market. This is the single C->D input the user signs; there is no per-hop calldata. The route's atomicity starts here: one calldata, one intent id, one input. The PATH is NOT carried in the signed on-chain hookData — it travels to the D router over the ZAP control plane (buildRouteRequest / NotifyRoute, MsgRoutePrepare) — because the on-chain precompile classifies the intent purely by its entry market + nonce (DeriveIntentID does NOT hash the path) and only supports the DI01 deadline/nonce body; an RT01 path body in the signed calldata would REVERT on-chain (the precompile has no route-marker awareness). Keeping the path on the control plane is also the right seam: the path is D-matcher orchestration that moves no C-side value (one input object, one final output object regardless of hop count).

type V4StateSession

type V4StateSession struct {
	// contains filtered or unexported fields
}

V4StateSession is the read-only streaming session: quotes and book/state. It cannot build calldata, notify, or settle — it observes.

func (*V4StateSession) Close

func (s *V4StateSession) Close()

Close retires the state session's capability.

func (*V4StateSession) ReadQuote

func (s *V4StateSession) ReadQuote(ctx context.Context, amountIn uint64, zeroForOne bool) *Promise[QuoteResult]

ReadQuote [D->C, V4_STATE_QUOTE_UPDATE] reads one quote estimate. Read-only.

func (*V4StateSession) ReadState

func (s *V4StateSession) ReadState(ctx context.Context, kind StateKind, account Account, asset ID) *Promise[StateResult]

ReadState [D->C, V4_STATE_BOOK_UPDATE] reads one state observation. Read-only.

func (*V4StateSession) SessionID

func (s *V4StateSession) SessionID() ID

func (*V4StateSession) StreamQuotes

func (s *V4StateSession) StreamQuotes(ctx context.Context, amountIn uint64, zeroForOne bool) <-chan V4Event

StreamQuotes [D->C] streams quote updates on a cadence until ctx ends, surfacing each as a V4_STATE_QUOTE_UPDATE event. Pure reads; moves nothing.

type V4SwapSession

type V4SwapSession struct {
	// contains filtered or unexported fields
}

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.

func (*V4SwapSession) Close

func (s *V4SwapSession) Close()

Close retires the session's capability (no further operation may use it). Idempotent.

func (*V4SwapSession) IntentID

func (s *V4SwapSession) IntentID() ID

IntentID is the deterministic intent id this swap targets.

func (*V4SwapSession) OnExportReady

func (s *V4SwapSession) OnExportReady(ctx context.Context, watch IntentWatch) *Promise[DExportRef]

OnExportReady [D->C, V4_SWAP_D_EXPORT_READY] resolves to the ONE D->C export POINTER when D commits — the pipelining hook the spec's lifecycle wants (watch.OnCommitted -> settlement). It is the streaming watch; the credit still happens on-chain against the real object.

func (*V4SwapSession) ReadStream

func (s *V4SwapSession) ReadStream(ctx context.Context, watch IntentWatch) <-chan V4Event

ReadStream [D->C] streams the D->C reads of the swap lifecycle (D_IMPORTED -> MATCHED -> D_EXPORT_READY) as V4Events on the returned channel, until the export is ready, the intent is rejected, or ctx ends. Each event is orchestration; only the final D_EXPORT_READY carries the settleable POINTER. This is the bidirectional read side made explicit: the caller observes D writing to the control plane.

func (*V4SwapSession) Run

Run drives the FULL bidirectional swap lifecycle, promise-pipelined, returning the stages. It is the V4-native analogue of SwapFlow: prepare -> notify -> watch -> export-ready -> settlement calldata. The user signs+sends the prepared intent (this layer has no key); pass a sender to RunWithSender for a managed flow. Every stage is scoped to this swap.

func (*V4SwapSession) SessionID

func (s *V4SwapSession) SessionID() ID

SessionID is the action's stable id (binds events to this session).

func (*V4SwapSession) WriteNotifyCToDExport

func (s *V4SwapSession) WriteNotifyCToDExport(ctx context.Context, intent *Promise[PreparedIntent]) *Promise[IntentWatch]

WriteNotifyCToDExport [C->D, V4_SWAP_NOTIFY_C_EXPORT] tells D to scan the C->D object the user's signed tx created and returns a watch. It cannot validate a match without a D block. Pipelines on the prepared-intent promise.

func (*V4SwapSession) WritePrepareCSettlement

func (s *V4SwapSession) WritePrepareCSettlement(ctx context.Context, ref *Promise[DExportRef]) *Promise[SettlementSubmitResult]

WritePrepareCSettlement [C->D, V4_SWAP_PREPARE_C_SETTLEMENT] builds the 0x9999 DS01 settlement calldata pointing at the export ref. It binds the ref's intent id to THIS session (a ref for another intent is refused). The chain credits on consuming the real object; this returns bytes. Pipelines on the ref promise.

func (*V4SwapSession) WritePrepareIntent

func (s *V4SwapSession) WritePrepareIntent(ctx context.Context) *Promise[PreparedIntent]

WritePrepareIntent [C->D, V4_SWAP_PREPARE_INTENT] builds the 0x9999 swap calldata (DI01 intent) the user signs. It reserves nothing; QuotedOut is an estimate. Scoped: only this swap's params. Pipelined: returns a Promise.

type Venue

type Venue interface {
	// Quote returns an ESTIMATE from the resting book. Read-only.
	Quote(ctx context.Context, req QuoteRequest) (QuoteResult, error)

	// State returns a read-only C/D DEX observation.
	State(ctx context.Context, req StateRequest) (StateResult, error)

	// NotifyIntent asks the venue to begin scanning shared memory for the C->D
	// object named by the intent and submit a D tx that imports+matches it under
	// dexvm consensus. It returns immediately with the intent id to watch; it does
	// NOT block on a D block and does NOT credit anything. A no-op-but-record
	// implementation is valid (the keeper picks up the on-chain IntentSubmitted
	// event); the watch then observes the export when D commits.
	NotifyIntent(ctx context.Context, intent PreparedIntent) (IntentWatchRef, error)

	// WatchStatus reports the current phase of a notified intent and, when D has
	// committed an export, the POINTER to it. It reads D/shared-memory state; it
	// never fabricates a credit. Returning PhaseCommitted with a ref is a CLAIM the
	// chain re-verifies on import.
	WatchStatus(ctx context.Context, intentID ID) (IntentStatus, error)

	// ResolveExport maps a DExportRef to the on-chain settlement calldata that
	// points at the real D->C object. It does NOT submit and does NOT credit; it
	// returns the bytes a wallet/keeper signs. The asset+recipient are NOT taken
	// from the ref (the chain derives the asset from swap direction and binds the
	// recipient to the caller); the calldata carries only outputID + amount, and
	// even those are bound on-chain. amount is the venue's best knowledge of the
	// export amount, used to populate the claim; a wrong amount makes the on-chain
	// bind revert (it must equal the recorded object's amount).
	ResolveExport(ctx context.Context, ref DExportRef) (SettlementSubmitResult, error)
}

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.)

type WatchCap

type WatchCap struct {
	// contains filtered or unexported fields
}

WatchCap permits subscribing to a notified intent's D result. It cannot move value; OnCommitted yields a POINTER (DExportRef), not a credit.

func (WatchCap) Authority

func (c WatchCap) Authority() Authority

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL