Skip to content
MANDATE owl logoMANDATE

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-sdk
import { 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
# .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
mandate-client.ts
// 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-chain

Policy 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:

FieldMeaning
whitelistRootMerkle root of the approved-markets whitelist
maxOrderNotionalMaximum notional (quantity × limit price) per order
maxPositionMaximum post-fill absolute position
maxDailyLossMaximum realized daily loss
policySaltRandom 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

ExportKindSummary
MandateClientclassThe SDK's single public entry point. Constructed with a MandateClientConfig; exposes proveAndSubmit(order).
MandateClientConfiginterfaceagentId, sequencerUrl, rpcUrl, auctionAddress, registryAddress, sessionPrivateKey, policy, circuitDir, plus optional chain/nargoBin/bbBin/orderSaltBase/publicClient/walletClient/fetch overrides.
MandateOrderinterfaceThe human-friendly order shape: { market: string; side: "buy" | "sell"; amount: string; limitPrice: string }.
PolicyParamsinterfaceThe plaintext opening of the on-chain policy commitment: whitelistRoot, maxOrderNotional, maxPosition, maxDailyLoss, policySalt (all bigint).
ProveAndSubmitResultinterface{ epoch, orderCommitment, proof, publicInputs, txHash } returned on a successful proveAndSubmit() call.

Error classes

ErrorThrown when
MandateViolationErrorThe 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.
PolicyMismatchErrorThe locally-held plaintext PolicyParams does not open the on-chain registered policy commitment for this agentId. Carries `localCommitment` and `registeredCommitment`.
PortfolioMismatchErrorThe sequencer's reported portfolio state root disagrees with the auction contract's on-chain root for this agent. Carries `sequencerRoot` and `onchainRoot`.
EpochClosedErrorThe 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

The SDK exposes no 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.