SDK
@0xgks/mandate-sdk is a TypeScript library for generating and submitting MANDATE-compliant agent orders: Poseidon2 commitments, sparse-Merkle portfolio membership, Noir/Barretenberg proving, and sequencer/contract submission. Every snippet on this page is written directly against the package's real source.
Install:
npm install @0xgks/mandate-sdkimport { MandateClient } from "@0xgks/mandate-sdk";
import type { MandateClientConfig, PolicyParams, MandateOrder } from "@0xgks/mandate-sdk";Configuration
MandateClient is constructed with a single MandateClientConfig object. Every field below comes directly from packages/mandate-sdk/src/types.ts.
# .env.example — never commit real values for the lines marked SECRET
MANDATE_RPC_URL=http://127.0.0.1:8545
MANDATE_SEQUENCER_URL=http://127.0.0.1:8787
MANDATE_AUCTION_ADDRESS=0x...
MANDATE_REGISTRY_ADDRESS=0x...
MANDATE_AGENT_ID=0x...
MANDATE_SESSION_KEY=0x... # SECRET
MANDATE_POLICY_SALT=... # SECRET
MANDATE_CIRCUIT_PATH=./circuits/policy_check// Construct the client's config from environment variables.
// agentId is Poseidon2(session pk, salt) — the agent's on-chain identity.
const policy: PolicyParams = {
whitelistRoot: BigInt(process.env.MANDATE_WHITELIST_ROOT!),
maxOrderNotional: BigInt(process.env.MANDATE_MAX_ORDER_NOTIONAL!), // e.g. 1_000_000n
maxPosition: BigInt(process.env.MANDATE_MAX_POSITION!),
maxDailyLoss: BigInt(process.env.MANDATE_MAX_DAILY_LOSS!),
policySalt: BigInt(process.env.MANDATE_POLICY_SALT!),
};
const config: MandateClientConfig = {
agentId: process.env.MANDATE_AGENT_ID as `0x${string}`,
sequencerUrl: process.env.MANDATE_SEQUENCER_URL ?? "http://127.0.0.1:8787",
rpcUrl: process.env.MANDATE_RPC_URL ?? "http://127.0.0.1:8545",
auctionAddress: process.env.MANDATE_AUCTION_ADDRESS as `0x${string}`,
registryAddress: process.env.MANDATE_REGISTRY_ADDRESS as `0x${string}`,
sessionPrivateKey: process.env.MANDATE_SESSION_KEY as `0x${string}`,
policy,
circuitDir: process.env.MANDATE_CIRCUIT_PATH ?? "./circuits/policy_check",
};
const mandate = new MandateClient(config);Optional config overrides not shown above: chain (defaults to the local Anvil/foundry chain), nargoBin / bbBin (default to $NARGO_BIN/$BB_BIN or the binaries on PATH), orderSaltBase, and test-only injection points (publicClient, walletClient, fetch).
Agent registration
MandateClient does not register agents itself — registration is an on-chain, one-time setup step (see demo/register.ts and scripts/setup-local-mandate.tsin the repository), independent of the SDK's per-order proving path. What the SDK does check, on every call, is that your local plaintext policy still opens the on-chain registered commitment:
// Agent registration and policy commitment happen on-chain, ahead of time
// (see demo/register.ts and scripts/setup-local-mandate.ts), not through a
// MandateClient method — MandateClient assumes an already-registered
// agentId whose on-chain policyCommitmentOf(agentId) matches the plaintext
// PolicyParams you configure it with. proveAndSubmit() checks this on
// every call and throws PolicyMismatchError if the two disagree:
import { policyCommitment } from "@0xgks/mandate-sdk";
const localCommitment = policyCommitment(policy);
// must equal MandateRegistry.policyCommitmentOf(agentId) on-chainPolicy setup
A PolicyParams object is the plaintext opening of the on-chain policy commitment. All fields are bigint and must mirror circuits/policy_check/src/main.nr exactly:
| Field | Meaning |
|---|---|
| whitelistRoot | Merkle root of the approved-markets whitelist |
| maxOrderNotional | Maximum notional (quantity × limit price) per order |
| maxPosition | Maximum post-fill absolute position |
| maxDailyLoss | Maximum realized daily loss |
| policySalt | Random salt binding the policy commitment |
Reading portfolio state
// MandateClient does not expose a standalone "read portfolio" method — // proveAndSubmit() fetches and verifies the anchored portfolio witness // internally, from the sequencer's GET /portfolio/:agentId endpoint, and // throws PortfolioMismatchError if the reported state root disagrees with // the auction contract's stateRootOf(agentId). This is what the MCP // server's mandate_get_portfolio tool reads directly for read-only display // (see /docs/mcp#available-tools).
Submitting an order
The one method on MandateClient: proveAndSubmit(order: MandateOrder). It reads the current epoch and on-chain state root, verifies the local policy against the registered commitment, fetches and verifies the sequencer's portfolio witness, generates the Noir proof, and — only if every step succeeds — submits the proof-gated commitment on-chain.
// The order an agent/strategy hands to the SDK — human-friendly,
// string-encoded fields (see MandateOrder in types.ts).
const order: MandateOrder = {
market: "1", // circuit market id, as a decimal string
side: "buy",
amount: "250", // whole circuit units, as a decimal string
limitPrice: "3500", // whole circuit units, as a decimal string
};
const result = await mandate.proveAndSubmit(order);
// result: { epoch, orderCommitment, proof, publicInputs, txHash }
console.log(`submitted in epoch ${result.epoch}: ${result.txHash}`);On success, it resolves to a ProveAndSubmitResult:
{
epoch: 396n,
orderCommitment: "0x12a254be...5e74d89",
proof: "0x...",
publicInputs: [
"0x..." /* policy_commitment */,
"0x..." /* state_root */,
"0x..." /* order_commitment */,
"0x..." /* epoch */,
"0x..." /* breaker */,
],
txHash: "0xdee28569...9a60bc01",
}Handling proof failures
A mandate-violating order throws before anything touches the chain — nargo execute fails on the violated circuit assertion, and proveAndSubmit surfaces it as a typed error:
import {
MandateViolationError,
PolicyMismatchError,
PortfolioMismatchError,
EpochClosedError,
} from "@0xgks/mandate-sdk";
try {
await mandate.proveAndSubmit({
market: "1",
side: "buy",
amount: "10000", // 10,000 * 3,500 = 35,000,000 notional
limitPrice: "3500",
});
} catch (err) {
if (err instanceof MandateViolationError) {
// The order cannot be proven: nargo execute failed on a circuit
// assertion (e.g. "order notional exceeds mandate maximum").
// Nothing was submitted on-chain. err.reason is the bare circuit
// assertion text; err.order is the rejected order.
console.error(err.reason);
} else if (err instanceof PolicyMismatchError) {
// Local plaintext policy doesn't open the on-chain registered
// commitment — refusing to prove with a stale or wrong mandate.
} else if (err instanceof PortfolioMismatchError) {
// Sequencer's reported state root disagrees with the on-chain
// anchored root — refusing to prove against untrusted state.
} else if (err instanceof EpochClosedError) {
// The commit window closed while proving; not submitted.
}
}Types and errors
| Export | Kind | Summary |
|---|---|---|
| MandateClient | class | The SDK's single public entry point. Constructed with a MandateClientConfig; exposes proveAndSubmit(order). |
| MandateClientConfig | interface | agentId, sequencerUrl, rpcUrl, auctionAddress, registryAddress, sessionPrivateKey, policy, circuitDir, plus optional chain/nargoBin/bbBin/orderSaltBase/publicClient/walletClient/fetch overrides. |
| MandateOrder | interface | The human-friendly order shape: { market: string; side: "buy" | "sell"; amount: string; limitPrice: string }. |
| PolicyParams | interface | The plaintext opening of the on-chain policy commitment: whitelistRoot, maxOrderNotional, maxPosition, maxDailyLoss, policySalt (all bigint). |
| ProveAndSubmitResult | interface | { epoch, orderCommitment, proof, publicInputs, txHash } returned on a successful proveAndSubmit() call. |
Error classes
| Error | Thrown when |
|---|---|
| MandateViolationError | The order fails a Noir circuit assertion (whitelist, notional cap, position cap, daily-loss cap, or circuit breaker). Carries `reason` (the bare circuit assertion string) and `order`. Nothing is submitted on-chain. |
| PolicyMismatchError | The locally-held plaintext PolicyParams does not open the on-chain registered policy commitment for this agentId. Carries `localCommitment` and `registeredCommitment`. |
| PortfolioMismatchError | The sequencer's reported portfolio state root disagrees with the auction contract's on-chain root for this agent. Carries `sequencerRoot` and `onchainRoot`. |
| EpochClosedError | The epoch's commit window closed (or the epoch advanced) while a proof was being generated. Thrown instead of submitting a transaction guaranteed to revert. Carries `epoch`. |
No preview or dry-run method
previewOrder() or simulation method — the only way to check compliance is to actually attempt proveAndSubmit(). A rejection is free of on-chain side effects (no transaction is ever sent for a non-compliant order), but proving itself still runs locally.