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.HashComputeIntentID 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.HashComputeReceiptId 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) []byteEncodeGenerate 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) []byteEncodeGetApproved builds calldata for getApproved(bytes32 name) -> (uint256 version, bytes32 weightHash).
EncodeRegisterModel
func EncodeRegisterModel(spec ModelSpec) []byteEncodeRegisterModel 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) []byteEncodeSubmitInferenceIntent 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.HashPromptHash 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) boolVerifyReceiptInclusion 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) *BudgetGuardBudgetPolicy
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.Addressfunc (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() boolfunc (r InferenceReceipt) Encode() []bytefunc (r InferenceReceipt) Hash() common.HashMerkleProof
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) boolMockFacilitator
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) errorModelSpec
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.Hashfunc (m ModelSpec) RegistryName() common.HashOption
Option configures a Client.
func WithChainID(id *big.Int) Optionfunc WithPolling(interval, timeout time.Duration) Optionfunc WithReceiptStore(s ReceiptStore) OptionPaidResult
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.Hashfunc (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.HashReceiptStore
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.