Documentation
¶
Overview ¶
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.
Index ¶
- Constants
- Variables
- func ComputeIntentID(cChainID, aChainID, cTxHash common.Hash, callIndex uint32, ...) common.Hash
- func ComputeReceiptId(modelID, promptHash, outputHash, paymentHash common.Hash, ...) common.Hash
- func DecodeGenerateResult(ret []byte) ([]uint32, error)
- func DecodeSubmitInferenceIntentResult(ret []byte) (common.Hash, error)
- func EncodeGenerate(nNew uint32, promptTokens []uint32) []byte
- func EncodeGetApproved(name common.Hash) []byte
- func EncodeRegisterModel(spec ModelSpec) []byte
- func EncodeSubmitInferenceIntent(modelSpecHash, promptHash common.Hash, n, threshold uint16, fee *big.Int, ...) []byte
- func EncodeVerifyInferenceReceipt(receipt, proof []byte) ([]byte, error)
- func PromptHash(prompt []byte) common.Hash
- func VerifyReceiptInclusion(receiptHash common.Hash, proof MerkleProof, root common.Hash) bool
- type ApprovedModel
- type BudgetGuard
- type BudgetPolicy
- type Client
- 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, ...) (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, ...) (InferResult, error)
- func (c *Client) InferPaid(ctx context.Context, model ModelSpec, prompt []byte, n, threshold uint16, ...) (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)
- type EVMBackend
- type Facilitator
- type InferResult
- type InferenceReceipt
- type MerkleProof
- type MockFacilitator
- type ModelSpec
- type Option
- type PaidResult
- type PaymentPayload
- type PaymentRequirements
- type PoTReceipt
- type ReceiptStore
- type SettlementReceipt
- type SubmitOptions
- type VerifyResult
- type X402Scheme
Constants ¶
const ( // aivmbridge (0x0300…0004), LP-5301. SelectorSubmitInferenceIntent uint32 = 0x10000000 SelectorVerifyInferenceReceipt uint32 = 0x11000000 // inference precompile (0x0300…0003), LP-0303 — generate(uint32,uint32[]). SelectorGenerate uint32 = 0x01000000 // model registry (0x0300…0002). SelectorAdopt uint32 = 0x01000000 SelectorGetApproved uint32 = 0x02000000 SelectorIsAdmin uint32 = 0x03000000 SelectorSetAdmin uint32 = 0x04000000 )
--------------------------------------------------------------------------- Selectors (first 4 bytes of calldata), pinned to the on-chain modules. ---------------------------------------------------------------------------
const ( DomainIntent = "lux/aivmbridge/intent/v1" DomainReceipt = "lux/aivmbridge/receipt/v1" DomainModelSpec = "lux/aivmbridge/modelspec/v1" )
Domain separators (raw UTF-8 bytes, no length prefix — concatenated verbatim). They keep the intent-id, receipt-hash, and model-spec keyspaces disjoint and version the wire so a future v2 cannot collide with v1. The first two are the cross-chain contract pinned in chains/aivm/quorum_wire.go and MUST match byte-for-byte. DomainModelSpec is the SDK's canonical model-identity hash; it is the value carried as modelSpecHash in the intent (opaque to the chain, which only ever compares it for equality), so the SDK is its sole author.
const ( StatusUnknown uint8 = 0 StatusPending uint8 = 1 StatusCompleted uint8 = 2 StatusFailed uint8 = 3 StatusChallenged uint8 = 4 )
Receipt status codes (the AInferenceReceipt.Status byte). Pinned values shared with the bridge + settlement engine. Only StatusCompleted with a non-zero CanonicalOutputHash is actionable C-side.
const MaxFanout uint16 = 256
MaxFanout is the upper bound on the intent fan-out N (LP-5301). It also caps Threshold.
const MaxProofDepth = 64
MaxProofDepth caps the Merkle path length the verifier (and the precompile) will walk — a 2^64-leaf tree is never reached in practice, but an unbounded pathLen is a calldata-DoS, so it is hard-capped (LP-5301 MaxProofDepth).
const ReceiptEncodedLen = 2 + 32 + 32 + 32 + 32 + 20 + 32 + 32 + 32 + 1 + 2 + 2 + 32 + 32 + 32 + 8 // = 355
ReceiptEncodedLen is the exact byte length of the canonical AInferenceReceipt encoding (see AInferenceReceipt.Encode). Pinned so a drift in field widths or order is a compile/test failure, not a silent cross-chain mismatch.
u16(Version)2 + IntentID32 + TaskID32 + CChainID32 + AChainID32 + Requester20 + ModelSpecHash32 + PromptHash32 + CanonicalOutputHash32 + u8(Status)1 + u16(N)2 + u16(Threshold)2 + WinnersRoot32 + OperatorsRoot32 + u256(FeePaid)32 + u64(SettledAtHeight)8
const ReceiptVersion uint16 = 1
ReceiptVersion is the wire version stamped into every receipt (the u16be Version field). Bumping it is a wire change shared with the settlement engine.
Variables ¶
var ( AIBridgeAddress = common.HexToAddress("0x0300000000000000000000000000000000000004") InferenceAddress = common.HexToAddress("0x0300000000000000000000000000000000000003") ModelRegistryAddress = common.HexToAddress("0x0300000000000000000000000000000000000002") )
Precompile addresses in the AI reserved range 0x0300…0000 .. 0x0300…00FF.
Functions ¶
func 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.
func 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.
func DecodeGenerateResult ¶
DecodeGenerateResult decodes the inference precompile return: a tight big-endian uint32 array (prompt+generated tokens). Errors on a non-multiple-of-4 length.
func DecodeSubmitInferenceIntentResult ¶
DecodeSubmitInferenceIntentResult decodes the aivmbridge Pattern-A return: a single bytes32 intentID.
func EncodeGenerate ¶
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).
func EncodeGetApproved ¶
EncodeGetApproved builds calldata for getApproved(bytes32 name) -> (uint256 version, bytes32 weightHash).
func EncodeRegisterModel ¶
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).
func 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.
func EncodeVerifyInferenceReceipt ¶
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.
func PromptHash ¶
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.
func VerifyReceiptInclusion ¶
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 ¶
type ApprovedModel ¶
ApprovedModel is the decoded getApproved return: the adopted version and weight commitment for a model name (weight == zero means none adopted).
func DecodeGetApprovedResult ¶
func DecodeGetApprovedResult(ret []byte) (ApprovedModel, error)
DecodeGetApprovedResult decodes the 64-byte getApproved return: (uint256 version, bytes32 weightHash). Version is read from the low 8 bytes of the first word (matching modelregistry GetApproved, which reads v[24:32]).
type BudgetGuard ¶
type BudgetGuard struct {
// contains filtered or unexported fields
}
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 ¶
func NewBudgetGuard(policy BudgetPolicy, now func() time.Time) *BudgetGuard
NewBudgetGuard builds a guard for a policy. `now` is injectable for tests (nil == time.Now). The hourly window is fixed at one hour (the policy's unit).
type BudgetPolicy ¶
type BudgetPolicy struct {
// MaxPerRequest caps a single request's amount (nil/zero == no per-request cap).
MaxPerRequest *big.Int
// MaxPerHour caps the rolling 1-hour spend (nil/zero == no hourly cap).
MaxPerHour *big.Int
// AllowedModels, if non-empty, whitelists model spec hashes that may be paid
// for (a request for any other model is rejected before paying).
AllowedModels []common.Hash
// RequirePoT, when true, requires the paid result to carry a non-zero PoT
// paymentHash<->settlement join (InferPaid always produces one; this asserts it).
RequirePoT bool
}
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.
type Client ¶
type Client struct {
// PollInterval / PollTimeout bound the tx-receipt and settlement polling.
PollInterval time.Duration
PollTimeout time.Duration
// contains filtered or unexported fields
}
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 NewClient ¶
func NewClient(backend EVMBackend, privKeyHex string, opts ...Option) (*Client, error)
NewClient builds a Client over the given backend, signing with privKeyHex (a 0x-optional hex secp256k1 key). For a read-only client (eth_call Tier-1, or reads), pass an empty key.
func (*Client) GenerateDeterministic ¶
func (c *Client) GenerateDeterministic(ctx context.Context, nNew uint32, promptTokens []uint32) ([]uint32, error)
GenerateDeterministic runs the Tier-1 in-consensus inference precompile (0x0300…0003) via eth_call and returns the full token sequence (prompt + generated). This is SYNCHRONOUS and answered inside the single call — no quorum, no async settlement, no gas spent (it is an eth_call). Use it for the small deterministic model; use Infer for large Tier-2 models.
func (*Client) GenerateWithBudget ¶
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)
GenerateWithBudget is the budget-gated InferPaid: it enforces `guard`'s policy (per-request cap, hourly cap, model allow-list) BEFORE building or settling any payment, then runs the full paid inference. An over-budget or disallowed request is rejected with NO payment made. If the policy RequirePoT, the result is checked to carry a valid PoT join before returning.
This is the Tier-2 entrypoint agents use to pay-per-cognition safely: the guard is the agent's spending discipline, the facilitator settles, and the PoT join makes every paid thought accountable on-chain.
func (*Client) GetModel ¶
GetModel reads the registry's currently-adopted (version, weight commitment) for a model name via eth_call (no tx). A zero weight commitment means the model is not adopted.
func (*Client) GetReceipt ¶
func (c *Client) GetReceipt(ctx context.Context, intentID common.Hash) (receipt InferenceReceipt, proof MerkleProof, found bool, err error)
GetReceipt does a single (non-blocking) read of the committed receipt for an intent id. found==false means not yet settled.
func (*Client) Infer ¶
func (c *Client) Infer(ctx context.Context, model ModelSpec, prompt []byte, n, threshold uint16, fee *big.Int) (InferResult, error)
Infer is the one-call Tier-2 convenience: submit an intent for `model` over `prompt`, wait for the quorum to settle, and return the canonical result.
SETTLEMENT IS ASYNC. Unlike GenerateDeterministic (Tier-1, answered in one EVM call), Infer submits a C-Chain intent and BLOCKS polling the A-Chain until an M-of-N quorum settles a receipt — seconds to minutes, bounded by PollTimeout. The returned Receipt.CanonicalOutputHash is the agreed output digest; fetch the full output bytes from your provider/DA layer keyed by that hash (the chain commits the digest, not the bytes).
func (*Client) InferPaid ¶
func (c *Client) InferPaid( ctx context.Context, model ModelSpec, prompt []byte, n, threshold uint16, reqs PaymentRequirements, facilitator Facilitator, signer *ecdsa.PrivateKey, ) (PaidResult, error)
InferPaid runs the full x402 pay-per-cognition flow for a Tier-2 inference:
- build + sign the x402 PaymentPayload from `reqs` with `signer`;
- have the facilitator Settle it (verify signature, charge) -> SettlementReceipt;
- run the quorum inference (Infer) for (model, prompt);
- assemble the PoT join (paymentHash from the settlement, outputHash from the receipt) so the result can be registered on-chain.
Payment happens BEFORE inference (x402 is pay-first). The returned Settlement.PaymentHash is the on/off-chain join key; PoT.ReceiptID() is the id the result registers under on ProofOfThoughtRegistry. Errors from settle abort before any inference is requested.
func (*Client) RegisterModel ¶
RegisterModel adopts a model version in the registry precompile (0x0300…0002). The caller must be a registry admin (governance); a non-admin tx reverts on chain. Returns the tx hash.
func (*Client) SubmitInferenceIntent ¶
func (c *Client) SubmitInferenceIntent(ctx context.Context, opts SubmitOptions) (intentID common.Hash, txHash common.Hash, err error)
SubmitInferenceIntent sends a Tier-2 intent tx to the aivmbridge precompile (Pattern A) and returns the deterministic intent id (re-derived from the LANDED tx hash + call index, exactly as the precompile derives it) and the tx hash.
The intent is the START of an async quorum settlement; use WaitReceipt (or the one-call Infer) to obtain the result. The single-call submit places the intent at call index 0 of its tx — the SDK derives the id on that assumption (a contract that batches multiple submits in one tx must compute ids itself).
func (*Client) VerifyPaidReceipt ¶
func (c *Client) VerifyPaidReceipt(_ context.Context, pot PoTReceipt, settlement SettlementReceipt) (bool, error)
VerifyPaidReceipt proves the payment<->thought join: it checks that the PoT receipt's PaymentHash equals the settlement's PaymentHash (the value the signature was over and that the on-chain registry stores). A true result means "this recorded thought was paid for by exactly this settlement". It also guards against a zero paymentHash (which the on-chain registry rejects).
func (*Client) WaitReceipt ¶
WaitReceipt polls the A-Chain receipt store for the committed receipt of intentID until it is Completed (or the deadline / ctx fires). It returns the receipt only when actionable (Status==Completed, non-zero output); a Failed or Challenged terminal receipt returns an error carrying the receipt.
type EVMBackend ¶
type EVMBackend interface {
ChainID(ctx context.Context) (*big.Int, error)
AcceptedNonceAt(ctx context.Context, account common.Address) (uint64, error)
SuggestGasTipCap(ctx context.Context) (*big.Int, error)
EstimateBaseFee(ctx context.Context) (*big.Int, error)
EstimateGas(ctx context.Context, msg ethereum.CallMsg) (uint64, error)
SendTransaction(ctx context.Context, tx *gethtypes.Transaction) error
TransactionReceipt(ctx context.Context, txHash common.Hash) (*gethtypes.Receipt, error)
CallContract(ctx context.Context, msg ethereum.CallMsg, blockNumber *big.Int) ([]byte, error)
}
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.
type Facilitator ¶
type Facilitator interface {
Verify(payload PaymentPayload, reqs PaymentRequirements) error
Settle(payload PaymentPayload, reqs PaymentRequirements) (SettlementReceipt, error)
}
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.
type InferResult ¶
type InferResult struct {
IntentID common.Hash
TxHash common.Hash
Receipt InferenceReceipt
}
InferResult is the outcome of a one-call Tier-2 Infer.
type InferenceReceipt ¶
type InferenceReceipt struct {
Version uint16 `json:"version"`
IntentID common.Hash `json:"intentId"` // source C-chain intent id
TaskID common.Hash `json:"taskId"` // A-chain task id
CChainID common.Hash `json:"cChainId"`
AChainID common.Hash `json:"aChainId"`
Requester common.Address `json:"requester"`
ModelSpecHash common.Hash `json:"modelSpecHash"`
PromptHash common.Hash `json:"promptHash"`
CanonicalOutputHash common.Hash `json:"canonicalOutputHash"` // zero if Failed
Status uint8 `json:"status"`
N uint16 `json:"n"`
Threshold uint16 `json:"threshold"`
WinnersRoot common.Hash `json:"winnersRoot"`
OperatorsRoot common.Hash `json:"operatorsRoot"`
FeePaid *big.Int `json:"feePaid"`
SettledAtHeight uint64 `json:"settledAtHeight"`
}
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 ¶
func DecodeReceipt(b []byte) (InferenceReceipt, error)
DecodeReceipt parses a canonical 355-byte receipt encoding back into an InferenceReceipt. It is the inverse of Encode and rejects any input that is not EXACTLY ReceiptEncodedLen bytes (a corrupt/short/over-long frame is never reinterpreted into a credit — the same fail-secure discipline the precompile's DecodeReceipt applies).
func (InferenceReceipt) Completed ¶
func (r InferenceReceipt) Completed() bool
Completed reports whether the receipt is the only actionable shape C-side: Status == StatusCompleted with a non-zero canonical output. A Pending, Failed, or Challenged receipt — or a zero output — is never credited.
func (InferenceReceipt) Encode ¶
func (r InferenceReceipt) Encode() []byte
Encode produces the canonical fixed-width encoding (exactly ReceiptEncodedLen = 355 bytes) in the PINNED order, byte-identical to chains/aivm:
u16be(Version) || IntentID(32) || TaskID(32) || CChainID(32) || AChainID(32) || Requester(20) || ModelSpecHash(32) || PromptHash(32) || CanonicalOutputHash(32) || u8(Status) || u16be(N) || u16be(Threshold) || WinnersRoot(32) || OperatorsRoot(32) || u256be(FeePaid,32) || u64be(SettledAtHeight)
func (InferenceReceipt) Hash ¶
func (r InferenceReceipt) Hash() common.Hash
Hash is the receipt commitment: keccak256(DomainReceipt || Encode()). This is the leaf value (pre-leaf-hash) that goes into the receipt_root and that an exported proof proves membership of. Identical to chains/aivm AInferenceReceipt.Hash.
type MerkleProof ¶
type MerkleProof struct {
ReceiptRoot common.Hash `json:"receiptRoot"` // the committed root this proof is against
Index uint64 `json:"index"` // leaf index in the tree
Siblings []common.Hash `json:"siblings"` // sibling at each level, leaf->root
}
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 ¶
func DecodeProof(b []byte) (MerkleProof, error)
DecodeProof parses an LP-5301 proofBytes frame back into a MerkleProof. It is the inverse of EncodeProof and is exact-length: it rejects a short frame, a frame whose declared pathLen exceeds MaxProofDepth, and a frame with trailing junk — the same calldata-hardening the precompile applies.
func (MerkleProof) EncodeProof ¶
func (p MerkleProof) EncodeProof() ([]byte, error)
EncodeProof serializes a proof into the LP-5301 proofBytes wire frame:
ReceiptRoot(32) | u64be Index(8) | u16be pathLen(2) | pathLen*32
This is the exact bytes the aivmbridge verifyInferenceReceipt op decodes.
func (MerkleProof) Verify ¶
func (p MerkleProof) Verify(r InferenceReceipt) bool
Verify is the receipt-aware check: it confirms the proof is against the root the proof itself carries (proof.ReceiptRoot) and that the receipt's Hash() is included under it. The CALLER must still confirm proof.ReceiptRoot is a root the C-Chain holds committed (the precompile does this against its receipt-root checkpoint; off-chain you compare against a known-good root).
type 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 (*MockFacilitator) Settle ¶
func (m *MockFacilitator) Settle(payload PaymentPayload, reqs PaymentRequirements) (SettlementReceipt, error)
Settle verifies then returns a deterministic settlement receipt. The settled amount equals payload.Value (the mock charges the full authorized amount even for `upto`). PaymentHash is the EIP-712 digest (the PoT join key); TxHash is derived deterministically from the paymentHash so a settlement is reproducible in tests (a real facilitator returns the actual on-chain tx hash).
func (*MockFacilitator) Verify ¶
func (m *MockFacilitator) Verify(payload PaymentPayload, reqs PaymentRequirements) error
Verify runs the genuine x402 verification: the payload binds the requirements, the signature recovers to payload.From, and the authorization is not expired.
type ModelSpec ¶
type ModelSpec struct {
Name string `json:"name"`
Version uint64 `json:"version"`
WeightCommit common.Hash `json:"weightCommit"`
Quantization string `json:"quantization"`
}
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 (ModelSpec) Hash ¶
Hash is the canonical model-spec hash — the 32-byte value carried as modelSpecHash in an intent and adopted (as weightHash) in the registry:
keccak256( DomainModelSpec ||
u32be(len(Name)) || Name ||
u64be(Version) ||
WeightCommit(32) ||
u32be(len(Quantization)) || Quantization )
Variable-length fields are length-prefixed so no two distinct specs can concatenate to the same preimage (injective). The domain separator keeps this keyspace disjoint from intent ids and receipt hashes.
func (ModelSpec) RegistryName ¶
RegistryName is the bytes32 model name the registry's adopt(name, version, weightHash) ABI keys on. We use the spec Hash itself as the registry name so a model is addressed by one stable id everywhere (the registry maps that id -> the adopted weight commitment + version).
type Option ¶
type Option func(*Client)
Option configures a Client.
func WithChainID ¶
WithChainID pre-sets the EVM chain id, skipping the eth_chainId round-trip (useful in tests / offline signing).
func WithPolling ¶
WithPolling overrides the receipt/settlement polling cadence and deadline.
func WithReceiptStore ¶
func WithReceiptStore(s ReceiptStore) Option
WithReceiptStore wires the A-Chain receipt read path (enables WaitReceipt / Infer / GetReceipt).
type PaidResult ¶
type PaidResult struct {
IntentID common.Hash
TxHash common.Hash
Inference InferenceReceipt
Settlement SettlementReceipt
PoT PoTReceipt // ready to register on ProofOfThoughtRegistry (PoT.ReceiptID())
}
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.
type PaymentPayload ¶
type PaymentPayload struct {
Scheme X402Scheme `json:"scheme"`
Network *big.Int `json:"network"`
From common.Address `json:"from"` // payer (recovered-equals the signer)
To common.Address `json:"to"` // payee (== requirements.PayTo)
Value *big.Int `json:"value"` // amount authorized (== requirements.MaxAmount)
Token common.Address `json:"token"` // payment token
ValidAfter uint64 `json:"validAfter"` // unix seconds the auth becomes valid (0 == immediately)
ValidBefore uint64 `json:"validBefore"` // unix seconds the auth expires (== requirements.ValidUntil)
Nonce common.Hash `json:"nonce"` // == requirements.Nonce
Signature []byte `json:"signature"` // 65-byte secp256k1 [R||S||V]
}
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 ¶
func BuildPaymentPayload(signer *ecdsa.PrivateKey, reqs PaymentRequirements) (PaymentPayload, error)
BuildPaymentPayload constructs the x402 authorization from a server's PaymentRequirements and EIP-712-signs it with `signer`. The payload binds from=signer-address, to=PayTo, value=MaxAmount, token, validBefore=ValidUntil, nonce, on the requirements' network. The returned payload's PaymentHash() is the EIP-712 digest the signature is over (and the PoT join key). Deterministic: the same (signer, requirements) always yields the same payload + paymentHash.
func (PaymentPayload) PaymentHash ¶
func (p PaymentPayload) PaymentHash() common.Hash
PaymentHash is the EIP-712 signing digest of the authorization: keccak256(0x1901 || domainSeparator(network) || hashStruct(msg)). This is the value the signature is over AND the value stored on-chain as ThoughtReceipt.paymentHash, so it is the canonical x402<->PoT join key.
func (PaymentPayload) RecoverPayer ¶
func (p PaymentPayload) RecoverPayer() (common.Address, error)
RecoverPayer recovers the address that signed the authorization from the signature over PaymentHash(). It is the facilitator's check that `from` really authorized the payment. Errors on a malformed signature or a recovered address that does not equal payload.From.
type PaymentRequirements ¶
type PaymentRequirements struct {
Scheme X402Scheme `json:"scheme"`
Network *big.Int `json:"network"` // EVM chain id the token/settlement lives on
MaxAmount *big.Int `json:"maxAmount"` // amount (exact) or cap (upto), in token base units
Token common.Address `json:"token"` // ERC-20 payment token (zero == native)
PayTo common.Address `json:"payTo"` // payee (resource server's receiving address)
Resource string `json:"resource"` // the resource being paid for (e.g. model id / URL)
Description string `json:"description"` // human-readable description
Nonce common.Hash `json:"nonce"` // server-chosen unique nonce (replay protection)
ValidUntil uint64 `json:"validUntil"` // unix seconds the authorization is valid until (validBefore)
}
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.
type PoTReceipt ¶
type PoTReceipt struct {
ModelID common.Hash `json:"modelId"`
PromptHash common.Hash `json:"promptHash"`
OutputHash common.Hash `json:"outputHash"`
PaymentHash common.Hash `json:"paymentHash"`
QuorumProof common.Hash `json:"quorumProof"`
Payer common.Address `json:"payer"`
Operator common.Address `json:"operator"`
Cost *big.Int `json:"cost"`
}
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 (PoTReceipt) ReceiptID ¶
func (r PoTReceipt) ReceiptID() common.Hash
ReceiptID is the deterministic on-chain id for this PoT record (== ComputeReceiptId over its fields).
type ReceiptStore ¶
type ReceiptStore interface {
ReceiptByIntent(ctx context.Context, intentID common.Hash) (receipt InferenceReceipt, proof MerkleProof, found bool, err error)
}
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.
type SettlementReceipt ¶
type SettlementReceipt struct {
PaymentHash common.Hash `json:"paymentHash"` // EIP-712 digest of the payload (== PoT paymentHash)
TxHash common.Hash `json:"txHash"` // settlement tx (zero if off-chain)
Settled bool `json:"settled"`
Payer common.Address `json:"payer"`
Payee common.Address `json:"payee"`
Amount *big.Int `json:"amount"` // amount actually settled (<= payload.Value for `upto`)
}
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).
type SubmitOptions ¶
type SubmitOptions struct {
// ModelSpecHash is the canonical model id (ModelSpec.Hash). Required, non-zero.
ModelSpecHash common.Hash
// PromptHash commits to the prompt (PromptHash(prompt)). Required, non-zero.
PromptHash common.Hash
// N is the fan-out (independent provider executions), in [1, 256].
N uint16
// Threshold is the M-of-N agreement, in [1, N] (the A-Chain also requires
// floor(N/2)+1 <= Threshold).
Threshold uint16
// Fee funds the A escrow + burn (256-bit, non-negative).
Fee *big.Int
// Routing is an opaque transport hint (NOT consensus state, NOT in the id).
Routing common.Hash
// GasLimit overrides gas estimation when non-zero.
GasLimit uint64
}
SubmitOptions parametrise a Tier-2 inference intent.
type VerifyResult ¶
VerifyResult is the decoded aivmbridge Pattern-B return: (bytes32 intentID, bytes32 canonicalOutputHash, uint8 status) packed as 3 words (96 bytes).
func DecodeVerifyInferenceReceiptResult ¶
func DecodeVerifyInferenceReceiptResult(ret []byte) (VerifyResult, error)
DecodeVerifyInferenceReceiptResult decodes the 96-byte Pattern-B return.
type X402Scheme ¶
type X402Scheme string
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.
const ( // SchemeExact: settle exactly MaxAmount. SchemeExact X402Scheme = "exact" // SchemeUpto: settle at most MaxAmount (the facilitator may charge less). SchemeUpto X402Scheme = "upto" )