Lux SDKGoPackages

aichain

dial.go wires the LIVE EVM transport (luxfi/evm/ethclient) into the SDK.

import "github.com/luxfi/sdk/aichain"

dial.go wires the LIVE EVM transport (luxfi/evm/ethclient) into the SDK.

It is behind the livedial build tag because luxfi/evm is a heavy node-side module: with GOWORK=off (the SDK's CI lane) its published version drags in a luxfi/upgrade pin that does not resolve standalone — the same reason the lux workspace force-replaces luxfi/upgrade in go.work. The CORE SDK (encoders, receipt/proof codecs, the Client over the EVMBackend interface, all tests) therefore compiles and tests with NO luxfi/evm dependency, so default GOWORK=off go test ./... stays green. Build/import this file only inside the lux workspace (where evm resolves) or with -tags livedial:

c, err := aichain.Dial("https://api.lux.network/v1/bc/C/rpc", privKeyHex)

Everywhere else, construct the Client directly from any EVMBackend (aichain.NewClient) — luxfi/evm/ethclient.Client satisfies that interface, so callers already holding a node client pass it straight through with no tag.

Package aichain is the developer-facing client SDK for the Lux A-Chain (Beluga) "Thinking Chains" architecture: on-chain LLM inference and embeddings settled by quorum.

It wraps the three on-chain entry points:

  • the AI bridge precompile (LP-5301) at 0x0300…0004 — Tier-2 large-model inference, submitted as a committed C-Chain intent and later settled by an A-Chain quorum receipt (Pattern A submit + Pattern B verify);
  • the deterministic in-consensus inference precompile (LP-0303) at 0x0300…0003 — Tier-1 small models run identically by every validator, answered inside one EVM call;
  • the model registry precompile at 0x0300…0002 — governance adoption of the model the chain treats as canonical.

wire.go pins the SHARED CROSS-CHAIN WIRE SPEC byte-for-byte. The encoders here MUST produce exactly the preimages the on-chain side hashes/decodes (chains/aivm/quorum_wire.go ComputeIntentID, chains/aivm/receipts.go AInferenceReceipt.Encode, and the LP-5301 aivmbridge calldata layout) or an intent submitted via this SDK will not re-derive to the same intent_id on A-Chain, and a receipt minted there will not verify against the SDK's recomputed hash. wire_test.go freezes golden vectors so any drift fails the build; the optional crossmodule build tag cross-checks against the live chains/aivm encoders.

keccak256 is luxfi/crypto.Keccak256 — the canonical keccak in the Lux stack, the same primitive the geth-side precompile and the A-Chain settlement engine use — so a digest computed here equals one computed on either chain bit-for-bit.

Functions

ComputeIntentID

func ComputeIntentID(
	cChainID, aChainID, cTxHash common.Hash,
	callIndex uint32,
	caller common.Address,
	modelSpecHash, promptHash common.Hash,
	n, threshold uint16,
	fee *big.Int,
) common.Hash

ComputeIntentID derives the cross-chain intent id from the COMMITTED C-Chain intent fields, exactly the preimage chains/aivm/quorum_wire.go ComputeIntentID hashes:

keccak256( DomainIntent || c_chain_id(32) || a_chain_id(32) || c_tx_hash(32) || u32be(call_index) || caller(20) || model_spec_hash(32) || prompt_hash(32) || u16be(N) || u16be(threshold) || u256be(fee,32) )

Every field is fixed-width in this precise order. The A-Chain importer recomputes this from the delivered fields and rejects the intent unless it equals the id the C side committed, so any altered field yields a different id.

Note: the submitInferenceIntent CALLER does not pick the intent id — the precompile derives it from the landed tx hash and call index. The SDK exposes this so a client that already knows (cTxHash, callIndex) — e.g. after the tx lands — can reproduce the id locally and correlate the eventual receipt.

ComputeReceiptId

func ComputeReceiptId(modelID, promptHash, outputHash, paymentHash common.Hash, payer, operator common.Address) common.Hash

ComputeReceiptId reproduces the on-chain ProofOfThoughtRegistry.computeReceiptId for a paid thought:

keccak256( abi.encode(modelId, promptHash, outputHash, paymentHash, payer, operator) )

abi.encode of (bytes32,bytes32,bytes32,bytes32,address,address) is six 32-byte words (the two addresses left-padded to 32 bytes). This is the deterministic id under which the receipt is registered on-chain; the SDK computes it so a paid inference can be located/verified on-chain without a round-trip.

DecodeGenerateResult

func DecodeGenerateResult(ret []byte) ([]uint32, error)

DecodeGenerateResult decodes the inference precompile return: a tight big-endian uint32 array (prompt+generated tokens). Errors on a non-multiple-of-4 length.

DecodeSubmitInferenceIntentResult

func DecodeSubmitInferenceIntentResult(ret []byte) (common.Hash, error)

DecodeSubmitInferenceIntentResult decodes the aivmbridge Pattern-A return: a single bytes32 intentID.

EncodeGenerate

func EncodeGenerate(nNew uint32, promptTokens []uint32) []byte

EncodeGenerate builds calldata for the deterministic in-consensus inference precompile (LP-0303). The frame is tight (NOT Solidity-ABI): the 4-byte selector, then u32be(nNew), then each prompt token as u32be. This matches precompile/inference/module.go Run exactly (input[4:8] = nNew, input[8:] = big-endian uint32 tokens).

EncodeGetApproved

func EncodeGetApproved(name common.Hash) []byte

EncodeGetApproved builds calldata for getApproved(bytes32 name) -> (uint256 version, bytes32 weightHash).

EncodeRegisterModel

func EncodeRegisterModel(spec ModelSpec) []byte

EncodeRegisterModel builds calldata for the registry's adopt(bytes32 name, uint256 version, bytes32 weightHash) op. The name is the spec's canonical id (ModelSpec.RegistryName == Hash), version is the spec version, and weightHash is the spec's weight commitment. Layout after the selector: name(32) | version(32, uint256) | weightHash(32).

EncodeSubmitInferenceIntent

func EncodeSubmitInferenceIntent(modelSpecHash, promptHash common.Hash, n, threshold uint16, fee *big.Int, routing common.Hash) []byte

EncodeSubmitInferenceIntent builds calldata for the aivmbridge Pattern-A op (LP-5301). After the 4-byte selector, a fixed 6-word frame:

[0:32] modelSpecHash (bytes32) [32:64] promptHash (bytes32) [64:96] n (uint16, right-aligned; high 30 bytes zero) [96:128] threshold (uint16, right-aligned; high 30 bytes zero) [128:160] fee (uint256) [160:192] routing (bytes32, opaque transport hint)

The precompile rejects a short OR oversized frame and dirty high bytes; this encoder always emits the exact 196-byte (4+192) well-formed frame.

EncodeVerifyInferenceReceipt

func EncodeVerifyInferenceReceipt(receipt, proof []byte) ([]byte, error)

EncodeVerifyInferenceReceipt builds calldata for the aivmbridge Pattern-B op (LP-5301). After the 4-byte selector, a tight length-prefixed frame:

[0:2] u16be receiptLen [2:4] u16be proofLen [4:4+rl] receipt bytes (canonical 355-byte AInferenceReceipt encoding) [..] proof bytes (ReceiptRoot|Index|pathLen|path*32)

receipt must be a canonical 355-byte encoding (InferenceReceipt.Encode) and proof the LP-5301 proof frame (MerkleProof.EncodeProof). Both are emitted verbatim; the precompile re-decodes them with the same exact-length discipline.

PromptHash

func PromptHash(prompt []byte) common.Hash

PromptHash is the canonical commitment to a prompt/input: keccak256 of the raw prompt bytes. The chain treats promptHash as opaque (equality only), so any stable hash the requester and provider agree on works; this SDK standardises on keccak of the UTF-8/binary prompt so the same prompt always yields the same commitment.

VerifyReceiptInclusion

func VerifyReceiptInclusion(receiptHash common.Hash, proof MerkleProof, root common.Hash) bool

VerifyReceiptInclusion checks that receiptHash is included under root at the proof's Index, re-applying the leaf hash and folding with the siblings, choosing left/right by the index bit at each level — exactly inverting the chains/aivm merkleProof/merkleRoot construction. Returns true iff the recomputed root equals root.

Types

ApprovedModel

ApprovedModel is the decoded getApproved return: the adopted version and weight commitment for a model name (weight == zero means none adopted).

func DecodeGetApprovedResult(ret []byte) (ApprovedModel, error)

BudgetGuard

BudgetGuard enforces a BudgetPolicy across many GenerateWithBudget calls on one Client, holding the rolling hourly spend. Construct with NewBudgetGuard; it is safe for concurrent use. The guard is SEPARATE from the Client so the same client can serve different budgets (one and only one spend ledger per policy).

func NewBudgetGuard(policy BudgetPolicy, now func() time.Time) *BudgetGuard

BudgetPolicy

BudgetPolicy is a CLIENT-SIDE spend guard checked BEFORE any payment is made. It caps the per-request amount and the rolling per-hour spend, optionally restricts which models may be paid for, and can require that a PoT receipt be produced. It is advisory (the client's own discipline), independent of and complementary to the on-chain escrow.

Client

Client is the high-level A-Chain (Beluga) inference client over an EVM JSON-RPC endpoint. It signs and sends txs to the AI precompiles and reads results.

  • Tier-1 (GenerateDeterministic): the deterministic in-consensus inference precompile (0x0300…0003) answers INSIDE one EVM call — synchronous, no quorum, small models. Use it via an eth_call (no tx, no gas spent) or a tx if you want the call recorded.
  • Tier-2 (SubmitInferenceIntent / WaitReceipt / Infer): a large model on the A-Chain settled by an M-of-N quorum — ASYNCHRONOUS. Submit returns an intent id; the result arrives later as a committed A-Chain receipt the bridge can verify.
func Dial(rpcURL, privKeyHex string, opts ...Option) (*Client, error)
func NewClient(backend EVMBackend, privKeyHex string, opts ...Option) (*Client, error)
func (c *Client) From() common.Address
func (c *Client) GenerateDeterministic(ctx context.Context, nNew uint32, promptTokens []uint32) ([]uint32, error)
func (c *Client) GenerateWithBudget(
	ctx context.Context,
	model ModelSpec,
	prompt []byte,
	n, threshold uint16,
	reqs PaymentRequirements,
	guard *BudgetGuard,
	facilitator Facilitator,
	signer *ecdsa.PrivateKey,
) (PaidResult, error)
func (c *Client) GetModel(ctx context.Context, name common.Hash) (ApprovedModel, error)
func (c *Client) GetReceipt(ctx context.Context, intentID common.Hash) (receipt InferenceReceipt, proof MerkleProof, found bool, err error)
func (c *Client) Infer(ctx context.Context, model ModelSpec, prompt []byte, n, threshold uint16, fee *big.Int) (InferResult, error)
func (c *Client) InferPaid(
	ctx context.Context,
	model ModelSpec,
	prompt []byte,
	n, threshold uint16,
	reqs PaymentRequirements,
	facilitator Facilitator,
	signer *ecdsa.PrivateKey,
) (PaidResult, error)
func (c *Client) RegisterModel(ctx context.Context, spec ModelSpec) (common.Hash, error)
func (c *Client) SubmitInferenceIntent(ctx context.Context, opts SubmitOptions) (intentID common.Hash, txHash common.Hash, err error)
func (c *Client) VerifyPaidReceipt(_ context.Context, pot PoTReceipt, settlement SettlementReceipt) (bool, error)
func (c *Client) WaitReceipt(ctx context.Context, intentID common.Hash) (InferenceReceipt, error)

EVMBackend

EVMBackend is the SUBSET of luxfi/evm/ethclient.Client this SDK needs: enough to read chain id / nonce / fees, send a tx, poll a receipt, and make an eth_call. luxfi/evm/ethclient.Client satisfies it directly (see DialClient), and a test can supply a mock without a live chain. Keeping the surface minimal is the decoupling: the SDK depends on these eight methods, not on the whole node client.

Facilitator

Facilitator is the x402 verify+settle service. A real implementation calls the facilitator HTTP endpoint (or settles on-chain itself); MockFacilitator settles deterministically in-process for tests. Verify is the cheap pre-check (valid signature, well-formed, binds the requirements, not expired); Settle performs the transfer and returns the SettlementReceipt carrying the paymentHash.

InferResult

InferResult is the outcome of a one-call Tier-2 Infer.

InferenceReceipt

InferenceReceipt is the cross-chain settlement receipt produced by the A-Chain when a Tier-2 inference task settles. Its canonical encoding is PINNED byte-for-byte (Encode / ReceiptEncodedLen = 355) and identical to chains/aivm/receipts.go AInferenceReceipt; its hash is keccak(DomainReceipt || Encode()), the leaf the receipt_root commits to.

FeePaid is a non-negative *big.Int (nil == zero on the wire). Field order here is presentation; the WIRE order is fixed by Encode and MUST NOT be reordered.

func DecodeReceipt(b []byte) (InferenceReceipt, error)
func (r InferenceReceipt) Completed() bool
func (r InferenceReceipt) Encode() []byte
func (r InferenceReceipt) Hash() common.Hash

MerkleProof

MerkleProof is an inclusion proof that a receipt_hash leaf is committed under a ReceiptRoot: the sibling hashes from leaf to root plus the leaf Index (which fixes left/right at each level). It mirrors chains/aivm MerkleProof and the LP-5301 proofBytes frame exactly so a proof exported by the A-Chain (Engine.ExportReceipt) verifies here and vice-versa.

func DecodeProof(b []byte) (MerkleProof, error)
func (p MerkleProof) EncodeProof() ([]byte, error)
func (p MerkleProof) Verify(r InferenceReceipt) bool

MockFacilitator

MockFacilitator is an in-process Facilitator for tests + local dev. It runs the real verification (signature recovery, binding, expiry) so tests exercise the genuine path, then "settles" by returning a deterministic receipt (no chain). Now is injectable for deterministic expiry tests (zero == time.Now).

func (m *MockFacilitator) Settle(payload PaymentPayload, reqs PaymentRequirements) (SettlementReceipt, error)
func (m *MockFacilitator) Verify(payload PaymentPayload, reqs PaymentRequirements) error

ModelSpec

ModelSpec is the SDK's canonical description of a model the chain can serve. On-chain (chains/aivm, modelregistry) a model is referred to ONLY by its 32-byte weight-commitment hash (an opaque equality key); ModelSpec is the human-facing identity that hashes DOWN to that key, so a requester naming a model and a provider advertising one derive the IDENTICAL modelSpecHash from the IDENTICAL fields.

Fields:

  • Name: model identity, e.g. "zenlm/zen-omni" (free-form, UTF-8).
  • Version: monotonically increasing version the registry adopts.
  • WeightCommit: the 32-byte commitment to the exact weights (a content hash of the weight file / a Merkle root over shards). This is the load-bearing field — it pins WHICH weights are canonical. If you already have the on-chain commitment, set it directly; Name/Version then only decorate it.
  • Quantization: the inference format that makes the result deterministic, e.g. "int8", "bf16-kat". Different quantizations of the same weights are different specs (they produce different canonical outputs).
func (m ModelSpec) Hash() common.Hash
func (m ModelSpec) RegistryName() common.Hash

Option

Option configures a Client.

func WithChainID(id *big.Int) Option
func WithPolling(interval, timeout time.Duration) Option
func WithReceiptStore(s ReceiptStore) Option

PaidResult

PaidResult is the outcome of an InferPaid call: the inference result joined to its x402 settlement and the PoT record that binds them on-chain.

PaymentPayload

PaymentPayload is the signed authorization the client sends in the X-PAYMENT header. It is an EIP-3009-shaped transferWithAuthorization payload: a signature over (from, to, value, validAfter, validBefore, nonce) bound to (token, chain id) via the EIP-712 domain. The facilitator recovers from from the signature and settles the transfer.

func BuildPaymentPayload(signer *ecdsa.PrivateKey, reqs PaymentRequirements) (PaymentPayload, error)
func (p PaymentPayload) PaymentHash() common.Hash
func (p PaymentPayload) RecoverPayer() (common.Address, error)

PaymentRequirements

PaymentRequirements is what a resource server returns in its HTTP 402 response: what it costs, in what token, on what network, to whom, and until when. The client turns this into a signed PaymentPayload.

PoTReceipt

PoTReceipt is the SDK's view of a Proof-of-Thought record: the join of a paid x402 settlement (PaymentHash), a model output (OutputHash), and the producing proof (QuorumProof), keyed by the on-chain ReceiptId. It is what a caller registers via ProofOfThoughtRegistry.register and what VerifyPaidReceipt checks against a SettlementReceipt.

func (r PoTReceipt) ReceiptID() common.Hash

ReceiptStore

ReceiptStore reads committed A-Chain receipts for the high-level WaitReceipt / GetReceipt convenience. It is intentionally SEPARATE from EVMBackend (the settlement read path is not the EVM tx path): an implementation may poll the A-Chain RPC directly, or watch the aivmbridge receipt-root checkpoint and the A->C export. ReceiptByIntent returns the committed receipt + its inclusion proof for an intent id, or (false) if not yet settled.

The SDK ships no live ReceiptStore (it depends on the node-local A-Chain RPC shape, out of scope here); callers wire their own, and tests use a fake. This keeps the SDK decoupled from any single A-Chain transport while still offering the one-call Infer convenience.

SettlementReceipt

SettlementReceipt is what the facilitator returns after Verify+Settle — the X-PAYMENT-RESPONSE the resource server echoes. PaymentHash is the EIP-712 digest of the authorization (the x402<->PoT join key); TxHash is the on-chain settlement tx (zero for an off-chain/escrowed settlement).

SubmitOptions

SubmitOptions parametrise a Tier-2 inference intent.

VerifyResult

VerifyResult is the decoded aivmbridge Pattern-B return: (bytes32 intentID, bytes32 canonicalOutputHash, uint8 status) packed as 3 words (96 bytes).

func DecodeVerifyInferenceReceiptResult(ret []byte) (VerifyResult, error)

X402Scheme

X402Scheme is the x402 payment scheme. exact is a fixed per-request charge; upto caps a charge that the facilitator may settle for less (e.g. metered by actual tokens). The signed authorization is identical; the scheme tells the facilitator how to interpret MaxAmount vs the settled amount.