npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@figaro-protocol/sdk

v0.1.1

Published

Figaro Protocol SDK — event parsing, state reconstruction, EIP-712 commitment building, and bond math for agents and integrations.

Readme

@figaro-protocol/sdk

TypeScript SDK for the Figaro Protocol — self-enforcing agreements between strangers.

Event parsing, state reconstruction, EIP-712 commitment building, bond math, agent coordination, the template→orders projection, and the runtime handoff key-agreement. Three runtime dependencies: viem, plus @noble/curves + @noble/hashes for the handoff ECDH/AES-GCM (audited, zero-dependency crypto).

Install

npm install @figaro-protocol/sdk viem

viem is a peer dependency, not a bundled one (sdk/package.json declares "peerDependencies": { "viem": "^2.55.1" }) — install it explicitly alongside the SDK, or the first chain call throws a missing-module error rather than a Figaro-shaped one.

Provenance: @figaro-protocol/sdk is live on the npm registry with a Sigstore provenance attestation binding the tarball to this repository and the workflow that built it — npm audit signatures verifies it downstream. Working from a repo checkout instead? Wire "@figaro-protocol/sdk": "file:../sdk" (build it first: npm run build --workspace sdk from the repo root).

Trap — a consumer npm install can DELETE the SDK's dist/. Read this before you wire the file: dependency; it is the one way to make the SDK unimportable by following its own install instructions.

Installed from the registry (npm install @figaro-protocol/sdk)? It cannot reach you — npm never runs a registry dependency's prepare, so nothing re-runs the rm -rf dist below: measured, the published tarball's dist/ arrives intact with no tsc anywhere on PATH, and if you ever delete it, a plain npm install @figaro-protocol/sdk re-extracts it. What follows is repo-checkout-only.

sdk/package.json declares "prepare": "npm run build", and npm runs a file: dependency's prepare on every consumer install. build is rm -rf dist && tsc. The rm always succeeds. So if tsc cannot be resolved from the SDK's own directory, the rebuild dies with sh: tsc: command not found, the install exits 127, and dist/ is gone — the package the README just told you to install no longer has an entry point (Cannot find package '@figaro-protocol/sdk'). --ignore-scripts does not save you: measured, npm runs the linked package's prepare regardless and dist/ still goes.

tsc is a devDependency of the sdk workspace, hoisted to the checkout root's node_modules. That is the whole fix — do this once, in this order, before the consumer install:

git clone https://github.com/figaro-protocol/Figaro && cd Figaro
npm install                       # root: hoists typescript for the sdk workspace
npm run build --workspace sdk     # produces sdk/dist

…and point file: at the sdk directory inside that checkout, never at a copy you moved somewhere else. Then the consumer's install finds tsc up the directory chain and its prepare rebuilds dist/ instead of destroying it. Already hit it? Nothing is lost: rerun those two commands from the repo root. (Do not "fix" this by editing sdk/package.json — that prepare hook is what keeps the workspace build honest.)

Your first commit

The shortest path from nothing to a bonded order on chain, on a devnet you own. Every step is a command in a checkout of the public repo — which is also how you install the SDK today (see Install above). Nothing here is hosted by anyone: your Anvil, your IPFS node, contracts you deployed, the standard public Anvil test keys. The SDK calls are the same ones you make against a public chain; only the addresses, the RPC URL and the signer change.

1. Bring up the devnet. From the repo root:

./scripts/devup.sh

One shot, idempotent, safe to re-run: clean-rebuilds sdk/dist, ensures Anvil on :8545 and a Kubo daemon (API :5001, gateway :8080), deploys the protocol stack, and pins every clause spec to IPFS + anchors it on ClauseRegistry. It writes the deployed addresses to frontend/.env.local (and .deployments/local.json) — that file is where every step below reads addresses from. It installs nothing: run npm install once at the repo root first (that is what puts tsc where the SDK build below finds it — see the Install trap above), and Foundry (anvil, cast) plus a running Kubo must already be there. Full prerequisites, env vars and the native-Kubo recipe: docs/LOCAL_DEV.md.

2. Put something on the network to discover. A fresh chain is an EMPTY network — no assemblies, no members, nothing to buy, and discovery correctly returns nothing. Fill it either way:

  • the real path, identical to what you would do on a public chain: publish a profile + catalogue (MembersRegistry — "Member Profile + Catalogue Documents" below) and register an assembly (AssemblyRegistry.registerAssembly), each against its registration deposit;
  • the shortcut, to reach a commit today — the repo's test seeder, which registers a few seed assemblies and sellers through those same contracts:
cd frontend && node scripts/populate-test-data.mjs   # idempotent

3. Build the SDK.

npm run build --workspace sdk    # from the repo root; devup already ran it

4. Originate. sdk/scripts/verify-origination.devnet.mjs is the runnable form of the whole handshake — two agents holding nothing but private keys, no browser and no human:

cd sdk && node scripts/verify-origination.devnet.mjs

What it does, in order:

  1. Discovers the network. new FigaroContext(publicClient, addresses) + await ctx.sync() folds the registry event streams into a live catalogue. It picks its assembly by HYDRATING each contentURI from the IPFS gateway and taking the first single-order template — no hardcoded id — and locates the commerce clause by the field it DECLARES (lineItems), never by clause name.
  2. Registers the seller loop. makeSellerOfferHandler(…) on an InProcessChannel, with both refusal floors filled in explicitly (an accept business rule plus an economic policy bounding currency and magnitude) and the registry-built SpecSource (specs), which arms the merkle-leaf sign gate. A handler missing either floor declines every offer.
  3. Runs the buyer loop. originateProcess(…) instantiates the discovered template's root agreement with the buyer's overrides, runs the merkle-leaf sign gate (assertAgreementSignable — every section conforms to its spec, and the currency/payment TERMS equal the commitment struct's mirrors; a missing required term or a leaf/struct contradiction refuses to sign), signs the EIP-712 commitment against a CHAIN-time deadline (readChainTimestamp + computeDeadline — never the machine clock), and hands the offer to the channel. The seller re-hashes the agreement against the committed agreementHash, runs the same gate through its own specs, applies its floors, approves its 2× bond and counter-signs. The buyer approves its own 2× bond and submits FigaroCore.commit.
  4. Asserts what landed. The commit receipt must be success: one OrderCommitted on the kernel, both bonds pulled into it.
  5. Reads it back out of band. A second ctx.sync(), then ctx.getProcessesAsBuyer(buyer) — the process is found from chain events, not from the return value of the call that created it, carrying the expected seller and payment.

A green run prints:

✓ discovered a single-order seed assembly
✓ located the commerce clause by its declared field
✓ origination returned a tx (seller counter-signed, commit submitted)
✓ initiate-process commit landed on chain (status success)
✓ the originated process is discoverable on chain with the right seller + payment

AUTONOMOUS ORIGINATION PROVEN — no human in the loop

Three siblings run the same recipe with exactly one thing changed: verify-origination-chain.devnet.mjs (a three-order value-added chain, one seller taking two of the nodes), verify-origination-http.devnet.mjs (the offer envelope crosses a real HTTP socket instead of the in-process channel), and verify-origination-a2a.devnet.mjs (the same envelope rides the A2A JSON-RPC interop wire via A2aChannel).

5. Close it — the buyer resolves. The script stops at a live bonded process, which is the state the mechanism is about. Ending it is a single call, and only the buyer can make it:

import { proposeActions, executeAction } from "@figaro-protocol/sdk/agent";

// The proposer rebuilds the commitment structs resolveProcess needs from the
// events themselves — nothing had to be stored client-side.
const [resolve] = proposeActions(ctx.getProcess(processId)!, buyer)
  .filter((a) => a.type === "resolve-process");
await executeAction(walletClient, publicClient, addresses, resolve);

// AND RECORD THE USAGE — at settlement, not later. The RPGF path pays clause
// authors and assembly designers from records the BUYER's side writes when the
// process resolves; a deferred record is permanently deniable (a seller can
// unstake, a period can close — docs/DESIGN_DECISIONS.md §21). One call, the
// headless twin of what the frontend does at the same moment. The mandatory
// clauses EARN — commerce and topology are scored for their author of record
// like any other — so the only routine revert inside it is the excluded
// figaro-assembly-provenance leg (attribution plumbing; its designer accrues
// through recordAssemblyUsage instead). Read the report, not the absence of an
// exception, and read excludedClauseOrAssembly(key) off the deployment you are
// calling rather than assuming any list:
import { instantiateRootAgreement, recordProcessUsage } from "@figaro-protocol/sdk/agent";
// The agreement is REBUILT, never stored: step 4's template + the same overrides and
// specs re-instantiate it identically, and its merkle root IS the committed
// `agreementHash` the counter opens each section's proof against.
const agreement = instantiateRootAgreement(template, { buyer, seller: resolve.commitments[0].seller, overrides, specs });
const report = await recordProcessUsage(walletClient, publicClient, addresses.usageCounter!, [  // optional on the record type; present on every shipped record
  { commitment: resolve.commitments[0], agreement }, // the agreement each order signed
]);

Every order in the process settles atomically, ProcessResolved lands, and the process reads resolved on the next ctx.sync(). No timeout, no arbitrator, no third party who can do this instead — and resolution is terminal. A buyer agent that resolves without recording credits no author and no designer — the reward mechanism's uniformity across actors is exactly this call.

Two things bite here. Both are silent.

The two processIds. resolveProcess(bytes32 processId, Commitment[] commitments) takes two different ids that share a name, and they are not interchangeable: the ARGUMENT is the kernel's DERIVED process id (the storage key it looks the process up by), while every struct INSIDE commitments must be the one the parties SIGNED — and a root order signed processId = 0. The kernel recomputes keccak256(processId ‖ hashStruct(c)) from both (src/kernel/FigaroCore.sol:280-285), so putting the derived id inside the root struct — the natural move, since that is what OrderCommitted carries and what event reconstruction hands you — yields a hash that matches no committed order and reverts OrderNotCommitted. The bridge is restoreSignedProcessId(c, chainId, core) (root export): it re-derives the id from the struct-as-root and, if that reproduces the id the event carried, hands the struct back with processId = 0; a genuine sub-order is returned untouched. executeAction (above) applies it to every element for you, which is the reason to prefer it — the lower-level resolveProcess(walletClient, core, processId, commitments) does NOT, and neither does hand-rolled cast.

AccrualClosed(). recordClauseUsage and recordAssemblyUsage both open by calling UsageCounter.currentPeriod(), which reverts AccrualClosed() once the last accrual period has ended (src/protocol/usage/UsageCounter.sol:389-395) — the nine annual periods are the RPGF mechanism's whole life, and after the ninth boundary usage is permanently unrecordable. Two consequences before that day: a record is attributed to the period open when you call, not the one the process resolved in, so crossing a boundary between resolve and record moves the credit into the next period's budget and denominator; and recordProcessUsage tolerates per-leg reverts by design, so a closed accrual does not throw — it returns a report where every leg sits in failures and recorded is 0. Check before you trust a run, and never after: currentPeriod() (it reverts, so wrap it — the revert IS the answer), periodClosed(uint8), periodCount(), and periodEnd(uint256) are all in USAGE_COUNTER_ABI, and AccrualClosed() is in it too so the revert decodes by name instead of arriving as opaque bytes. This is the mechanical form of the at-settlement rule: record in the same transaction batch as the resolve and none of it can happen.

6. Know the traps before you extend this. The site's /pitfalls page is the canonical list; the first one a chain integration hits is sub-order approval — every commit, root or sub-order, pulls the FULL per-order bond and nets nothing against bonds the kernel already holds, so approving the increment reverts inside the settlement token with ERC20InsufficientAllowance while the earlier bonds stay locked until the buyer resolves. Size it with calculateSubOrderApproval and check it with assertApprovalCoversBond (both below).

Six Entry Points

The generated API reference — every export, every signature. TypeDoc over this same source is served at figaroprotocol.com/sdk-api, one page per entry point — all six: the root package plus /agent, /derive, /clauses, /handoff, and /signer. npm run docs from sdk/ regenerates the whole reference from a checkout. This README stays the manual — recipes, traps, and the order to do things in; the reference is where you look a signature up.

Where each entry point can run

Nothing here is browser-only, and only one thing is Node-only. The column that matters is the last one: what the entry point actually needs from its host.

| Entry point | Browser | Node | What it needs from the host | |---|---|---|---| | root | yes | yes | fetch + AbortSignal (chain reads through viem, IPFS reads); no filesystem, no sockets. Bundles into a UI as-is. | | /agent | yes | yes | Same as root, plus an outbound HTTP request per coordination hop (HttpChannel, A2aChannel, SequencerClient, did:web resolution). In a browser those are cross-origin — the counterparty's endpoint must send CORS headers, which is why most agent loops run server-side. | | /derive | yes | yes | Nothing. Pure functions over values you already hold — no chain client, no network. | | /clauses | yes | yes | Nothing but viem's encoders. Parse, validate and encode clause content with no chain and no network at all. | | /handoff | yes | yes | The WebCrypto global (crypto.subtle) for AES-GCM: present in Node, and in a browser only in a secure context (https, or localhost) — over plain http the wrap/unwrap calls throw. | | /signer | no | yes | node:net, node:fs, node:crypto, node:path — a UNIX socket and a keystore file. It is a daemon plus a client: run the daemon with the figaro-signer bin, then socketSignerAccount connects to its socket from your Node process. Never bundle this into a browser build. |

Verified by importing each built entry point under bare Node (sdk/dist/*), and by the module graph: /signer is the only one that reaches a node: builtin, and /handoff the only one that reaches crypto.subtle.

Synopsis — which entry point is each export from?

Nothing is re-exported: every name below lives in exactly one entry point, so this table answers the question you hit while reading a recipe ("was attestAsSeller root or /agent?") without scrolling back to an import line. It is a SYNOPSIS, not a reference — one line per export, no signatures. Scope: every export a recipe on this page calls. For the full surface — every export of all six entry points, with signatures, parameters and types — figaroprotocol.com/sdk-api.

Constants follow a rule instead of a row: every *_ABI, EV_* (event definition) and RPGF_* constant is a root export.

| Export | Entry point | What it does | |---|---|---| | A2aChannel | /agent | Coordination channel over the A2A JSON-RPC wire; a declining seller comes back as null. | | a2aMessageFromOffer | /agent | Wrap a commitment payload as an A2A message for the wire. | | ActionQueue | /agent | Typed queue holding proposed actions for human approval before execution. | | addressesFromDeploymentRecord | root | Map a published deployment record's keys onto FigaroAddresses — never spread the record. | | assertAgreementSignable | root | The ONE pre-signature thrower: every section conforms to its spec, and the terms equal the struct. | | assertApprovalCoversBond | root | Throws when an approval is short of the full per-order bond the kernel will pull. | | attestAsSeller | /agent | Submit a seller attestation for one clause section of a committed order. | | buildChainOffers | /agent | Buyer-sign a whole chain's offers, in commit order, through the one template walk. | | buildCommitment | root | Build the Commitment struct and the EIP-712 typed data to sign. | | buildDomain | root | The EIP-712 domain for a chain id + FigaroCore address. | | buildOrderAgreement | root | Build one order's agreement document and its merkle tree from its clause map. | | buildQuoteRequest | /agent | Build an UNSIGNED RFQ draft, priced at the buyer's ceiling. | | buildSectionInclusionProof | root | Merkle proof that one clause section sits under a signed agreementHash. | | buildSwapWitnessTypedData | root | Permit2 witness typed data for the swap-and-commit funding leg. | | buildUsageClaims | root | Turn a settled BATCH order plus its agreement into the RPGF claims a sequencer proves. | | calculateBonds | root | sellerBond = 2 × cumulativeValue, buyerBond = 2 × payment. | | calculateRootApproval | root | The ERC-20 approval each party needs before a ROOT commit. | | calculateSettlement | root | What each party receives after resolveProcess: its bond back, and exactly payment crossing. | | calculateSubOrderApproval | root | The approval before a SUB-order commit — the FULL bond, never the increment. | | canonicalContentHash | root | keccak256 over the canonical serialization — the digest the registries anchor. | | canonicalize | root | THE canonical-JSON convention: sorted keys at every depth, array order kept, no whitespace. | | commit | /agent | Submit FigaroCore.commit with both signatures; any holder of the payload may broadcast. | | computeAgreementHash | root | The agreement's merkle root over its sorted section leaves. | | computeClauseKey | root | keccak256(abi.encode(clauseId, version)) — the registry key, and the attest calls' clauseId. | | computeDeadline | root | A deadline from CHAIN time; pair with readChainTimestamp — there is no wall-clock fallback. | | computeRpgfAllocations | root | Off-chain mirror of the 600M pro-rata split for a closed accrual period. | | computeSectionLeaf | root | One merkle leaf — double-hashed, so a leaf preimage can never be replayed as an internal node. | | counterSignDraft | /agent | Candidate side: validate an inbound race draft and countersign, or decline. | | decodeContentFromSpec | /clauses | Canonical ABI bytes back to JSON content — the exact inverse of encodeContentFromSpec. | | deriveAssemblyWithdrawGate | /derive | Whether an assembly's registration deposit is withdrawable, and what still blocks it. | | deriveClauseWithdrawGate | /derive | Whether a clause's registration deposit is withdrawable, and what still blocks it. | | deriveInFlightOrders | /derive | Every committed order whose process has not resolved. | | deriveSharedSecretAsReceiver | /handoff | ECDH shared secret from the sender's public key and your private key. | | deriveSharedSecretAsSender | /handoff | ECDH shared secret from your private key and the receiver's public key. | | deserializeCommitmentPayload | /agent | Parse a wire envelope back into a CommitmentPayload. | | didDocumentMatchesAddress | /agent | Does this DID document name this wallet? A consistency check, never proof of control. | | didWebEndpointResolver | /agent | Resolve a seller's coordination endpoint through did:web, address-checked. | | DISABLED_SWAP_FUNDING_LEG | root | The inert swap-funding leg — pass it for the party that is not swapping. | | encodeContentFromSpec | /clauses | JSON clause content to canonical ABI bytes — one generic encoder, no per-clause path. | | executeAction | /agent | The single dispatch point for any ProposedAction; restores each root's signed processId for you. | | extractServiceEndpoints | /agent | Read a DID document's service entries — WHERE to reach the agent behind it. | | fetchBatchUsageRecords | root | BatchUsageRecorded events — the batch half of the RPGF mirror. | | fetchCoreEvents | root | Every FigaroCore event in a block range, grouped and typed; chunks getLogs internally. | | fetchDiscoveryEvents | root | Registry events (clauses, assemblies, members); an unconfigured registry contributes nothing. | | fetchUsageRecords | root | UsageRecorded events — the direct-path half of the RPGF mirror. | | FigaroContext | /agent | The stateful agent context; sync() folds chain events into a live catalogue and process set. | | fillCargoSection | root | Fold the order's summed mass and volume onto its cargo leaf, found by declared field. | | fillClassSections | root | Fold catalogue-authored class values (freight class, hazmat, cold chain, …) onto their leaves. | | fillCommerceSection | root | Write payment, currency and (root only) the cart's line items into the commerce leaf. | | fillDerivedSections | root | Run every logistics fill the order composes — cargo, class, profile, then dimweight. | | fillDimweightSection | root | Billed weight = max(gross mass, volumetric) onto the dimweight leaf. DERIVED, never authored. | | fillProfileSections | root | Fold the seller's profile-authored clause values onto their leaves. | | fillProvenanceSection | root | Write the template's own compositionHash into the provenance leaf. | | filterByClause | /derive | Narrow attestation events to one clause. | | generateOrderKeypair | /handoff | A fresh ephemeral secp256k1 keypair for a single order's handoff. | | geohashesMatch | /derive | Do two geohashes agree at a given precision? Default 6 characters. | | getRateQuantityResolver | root | Look up a registered rate-quantity resolver by its source name. | | haversineDistance | /derive | Great-circle distance between two lat/lng points, in kilometres. | | HttpChannel | /agent | Coordination channel over plain HTTP; 204 is the seller declining, not an error. | | InProcessChannel | /agent | In-process channel — both parties run real sign/validate logic; only the wire is elided. | | instantiateRootAgreement | /agent | Instantiate a template's ROOT order into the signable agreement; same inputs rebuild it identically. | | makeA2aOfferResponder | /agent | Turn a seller's OfferHandler into a framework-agnostic A2A responder. | | makeSellerOfferHandler | /agent | SELLER LOOP: validate, apply both refusal floors, approve the bond, counter-sign. | | makeSellerQuoteHandler | /agent | Mountable seller responder for the RFQ quote leg. | | makeSellerRaceHandler | /agent | Mountable candidate responder for the dispatch-race leg. | | maxOrdersResolvablePerProcess | root | The largest N whose resolveProcess fits the active chain's block gas budget. | | offerFromA2aMessage | /agent | Read a commitment payload back out of an A2A message; null when the message is not an offer. | | originateProcess | /agent | BUYER LOOP: instantiate, sign, offer, await the counter-signature, approve, commit. | | parseAttestationLogs | root | Decode Attestation logs — filter by contract ADDRESS; the topic hash is shared with the batch path. | | parseClauseSpec | /clauses | Parse and validate an unknown value as a ClauseSpec (the spec's own structure, not its content). | | parseFieldSpec | /clauses | Parse ONE field spec — for fields declared outside a clause's content fields. | | parseMemberCatalogueDocument | root | Strict parse of a pinned catalogue document; throws on malformed input. | | parseMemberProfileDocument | root | Strict parse of a pinned profile document; throws on malformed input. | | parseProjectionHints | root | Read a spec's block projection hints — design fills, checkout fills, article. | | planSubOrderSellers | root | Topologically order an assembly's sub-orders and resolve each one's bound seller. | | planTemplateOrders | root | A template's agreements in commit order, each with its clause bag and complete version map. | | profileValuesFor | root | The profile-authored clause values a given seller publishes, read from its catalogue. | | projectAgentServices | root | Read the agent service endpoints out of a profile document, tolerating partial ones. | | proposeActions | /agent | Every action a wallet may take on a process it is already in. | | proposeInitiations | /agent | Every process a wallet could START — one per live-staked assembly. | | readChainTimestamp | root | The chain's block.timestamp: the only clock a protocol deadline may be computed from. | | readUtilityTokenPin | root | The designer's pinned settlement token, read from a template's composed clauses. | | reconstruct | root | Rebuild the full process topology from parsed core events. | | reconstructDiscovery | root | Rebuild the live registry view; a member's current profile URI is EVENT-derived, not a getter. | | reconstructOrdersFromTemplate | root | THE template→orders walk: root signs processId = 0, children carry real parent order hashes. | | recordProcessUsage | /agent | Record direct-path RPGF usage AT settlement; per-leg reverts land in failures, never thrown. | | registerRateQuantitySource | root | Register a resolver for a catalogue's rate-quantity source (a composition tenant, no core edit). | | requestCounterSignatures | /agent | Fan out race drafts, verify each reply by exact struct match, rank cheapest first. | | requestQuotes | /agent | Fan out RFQ requests, verify each reply by reconstruction, rank cheapest first. | | resolveDidWeb | /agent | Resolve a did:web identifier — https-only, no redirects, size-capped (SSRF-hardened). | | resolveProcess | /agent | The low-level buyer-only resolve. Does NOT restore signed root ids — prefer executeAction. | | resolveSubOrderPricing | root | Price a sub-order live from its own contributor's catalogue. | | restoreSignedProcessId | root | Turn an event-derived ROOT commitment back into the struct that was signed (processId = 0). | | sectionByField | root | Find the agreement section whose spec DECLARES a field — never look one up by clause name. | | sectionDataHash | root | A section's canonical-JSON fingerprint; a content-withheld section carries it directly. | | selectRaceWinner | /agent | Cheapest verified countersigner wins; ties break by arrival order. | | SequencerClient | /agent | HTTP client for a sequencer relay — submission (the batch path's entry point) and the publication reads. | | socketSignerAccount | /signer | A viem account backed by the policy-signer daemon's socket. | | strippingReviver | root | A JSON.parse reviver that drops __proto__/constructor/prototype keys. | | templateCompositionHash | root | The compositionHash AssemblyRegistry binds — an assembly's identity IS its composition. | | topologicalOrder | root | Order ids so every node follows its parents; throw or degrade on a cycle. | | Topology | root | The mutable shadow state an agent keeps, updated incrementally as events arrive. | | tryParseMemberProfileDocument | root | Lenient profile parse — returns null instead of throwing, for discovery lists. | | unwrapWithSharedSecret | /handoff | Decrypt what wrapWithSharedSecret produced. | | validateCommitmentAgreement | root | The non-throwing form of assertAgreementSignable — returns the findings instead. | | validateContent | /clauses | Validate clause content against its spec; on a closed clause, unknown fields are rejected. | | validateDraft | /agent | The structural check a candidate MUST run before countersigning a race draft. | | verifyCommitmentSignature | root | Does this signature over this commitment recover to this signer? Refuse early, off chain. | | verifyRaceReply | /agent | Buyer side: the reply's struct must EXACTLY equal the draft, and recover to the drafted candidate. | | warnProcessLogFillsTrap | root | Warn when a spec pins design/checkout fills on a process-log clause — content that commits unchecked. | | wrapWithSharedSecret | /handoff | Encrypt a string payload under the ECDH shared secret (12-byte IV ‖ AES-256-GCM, base64url). | | writeTopologySection | root | Write the REAL parent order hashes into the topology leaf; the template carries only local ids. |

@figaro-protocol/sdk — Protocol Primitives

Lost track of where a name below lives? → Synopsis.

Event parsing, state reconstruction, EIP-712 commitments, bond calculations, chain gas ceilings. Also home to the distribution mirror — computeRpgfAllocations (src/rpgf/formula.json): a deterministic integer pipeline that reproduces, off chain, what UsageCounter + RpgfMinter compute on chain for the 600M retroactive distribution. Usage is counted as the facts happen — recorded against a resolved order — so there is nothing to post, nothing to bond and nothing to dispute. Trade settled through FigaroBatchVerifier never acquires kernel status, so it reaches the counter by a second route: buildUsageClaims turns a settled batch order plus its agreement into the claims a sequencer proves, and the mirror folds BOTH event streams (fetchUsageRecords + fetchBatchUsageRecords). Reading only the first under-reports every clause or assembly whose trade moved to batches, and the two merge as SCORES, never as components. The reward is UNIFORM (no tag, category or weight — every clause or assembly's score is icbrt(c·d²·10^18), its real usage alone) and UNCAPPED; the only eligibility gate is a two-sided live ETH stake (usage counts only for a live-staked seller-of-record, and an author earns only while the clause or assembly's registration deposit stays un-withdrawn). The mirror exists to display a distribution, predict a claim, and verify a recorded accrual; formula.json is the normative prose statement of the mechanism and the source of every constant the mirror uses.

import {
  addressesFromDeploymentRecord,
  fetchCoreEvents,
  reconstruct,
  calculateBonds,
  calculateSettlement,
  buildCommitment,
  buildDomain,
  Topology,
  maxOrdersResolvablePerProcess,
} from "@figaro-protocol/sdk";

// `addresses` everywhere below is a `FigaroAddresses` ({ core, token, … }).
// A PUBLISHED DEPLOYMENT RECORD uses different key names (`figaroCore`,
// `tokenAddress`, …) — do not spread it verbatim; map it once:
const addresses = addressesFromDeploymentRecord(deploymentRecord);
// The mapping reads the keys it knows and IGNORES every other one, silently
// and by design: a record carrying extras the SDK has never heard of (a local
// development record's own keys, a future deployment's additions) maps cleanly.
// Only a missing `figaroCore` throws. Keys that carry no SDK field at all —
// `florinToken`, `swapQuoter`, `chainId`, `deploymentBlock` — you read off the
// record yourself; `deploymentBlock` is the `fromBlock` every scan below wants.
// WHICH TOKEN CAN YOU SPEND? On a devnet record the settlement balances sit in the
// MOCK tokens — `tokenAddress` (MOCK) and `permitTokenAddress` (MPMT), 100,000 of
// each pre-funded to the standard Anvil test keys. `florinToken` is not a settlement
// currency on such a record: its deployer mint is renounced and those wallets hold
// zero, so an order denominated in it reverts `ERC20InsufficientBalance` the moment
// the kernel pulls a bond. Read balances off the record's tokens, never assume one.

// Fetch all FigaroCore events from a block range. The return is a GROUPED
// object — { orderCommitted, orderResolved, processResolved }, each a typed
// array — NOT one flat log list. (Attestations are NOT in here: they live on
// the AttestationCoordinator, a separate contract — read those with
// EV_ATTESTATION + parseAttestationLogs; see @figaro-protocol/sdk/derive.)
// `fetchCoreEvents` (and `fetchDiscoveryEvents`, `fetchUsageRecords`,
// `fetchBatchUsageRecords`) chunk `getLogs` internally in sub-ranges of
// `DEFAULT_LOG_CHUNK_SIZE` (9,500 blocks) so a wide range doesn't exceed a
// public RPC provider's block-range cap; pass a trailing `chunkSize` to tune
// it for a stricter (or more permissive) provider.
const events = await fetchCoreEvents(client, addresses, 0n);

// Reconstruct full process/order state from events
const topology = new Topology();
topology.applyEvents(events);

const process = topology.getProcess(processId);
const active = topology.getActiveProcesses();

// Calculate bond requirements
const bonds = calculateBonds(cumulativeValue, payment);
// → { sellerBond, buyerBond, totalLocked }

// And what those locked funds become once the buyer resolves. This is the
// arithmetic to assert your balance deltas against — read the balances out of
// band after the resolve, never off the screen that claims to have moved them.
const settlement = calculateSettlement(payment, bonds.sellerBond, bonds.buyerBond);
// → { sellerPayout: payment + sellerBond,   // bond back, plus the payment
//     buyerPayout:  buyerBond − payment,    // bond back, minus the payment
//     netTransfer:  payment }               // exactly `payment` crosses, and nothing else
// At payment = cumulativeValue = 100: bonds 200/200, payouts 300/100, net 100.

// Per-process resolve ceiling on the active chain (a process grown past
// this can NEVER settle — check before every commit; the kernel cannot)
const cap = await maxOrdersResolvablePerProcess(client);

// Build EIP-712 typed data for signing.
//
// THE FIELD ORDER BELOW IS CANONICAL, NOT STYLISTIC. There is exactly one
// authoritative ordering: `CommitmentTypes.COMMITMENT_TYPEHASH`
// (`src/kernel/CommitmentTypes.sol:31-33`), the type string the kernel hashes
// and recovers both signatures against. The SDK derives its own typehash from
// the same field list and exports it — `COMMITMENT_TYPEHASH` (a root
// `@figaro-protocol/sdk` export) is
// 0xea70b4a1b704921c6919c3e8358981256c050e862e155886edf8828ee897f75c.
// Anything that transcribes the struct (the `cast` tuple below, a non-JS
// client, a Rust signer) must reproduce that order: permute two fields and the
// struct hash changes, so the kernel recovers a different address and rejects
// the bond.
const domain = buildDomain(chainId, coreAddress);
const { commitment, typedData } = buildCommitment(
  {
    processId: "0x0000000000000000000000000000000000000000000000000000000000000000",
    buyer,
    seller,
    currency,
    payment,
    expectedCumulativeValue: payment,
    agreementHash,
  },
  domain,
);

Calling the kernel without the SDK. A cast-only participant talks to FigaroCore directly with two functions:

commit((bytes32 processId, address buyer, address seller, address currency,
        uint256 payment, uint256 expectedCumulativeValue, bytes32 agreementHash,
        uint256 salt, uint256 deadline) c, bytes buyerSig, bytes sellerSig)
resolveProcess(bytes32 processId, <that same tuple>[] commitments)   // buyer only

That tuple's field order is the same canonical one — COMMITMENT_TYPEHASH in src/kernel/CommitmentTypes.sol:31-33. The sketch above and the buildCommitment literal earlier are two transcriptions of that one source; check either against the SDK's re-export (COMMITMENT_TYPEHASH === keccak256(toBytes(yourTypeString))) before signing anything you hand-rolled.

A ROOT commitment signs processId = 0 (the kernel derives the real id and returns it); a sub-order carries the root's derived processId. buyerSig / sellerSig are EIP-712 signatures over the Commitment struct under domain { name: "FigaroCore", version: "3", chainId, verifyingContract: <core> }. The SDK wrappers (buildDomain + buildCommitment) encode exactly these EIP-712 type/domain details — a raw caller must reproduce them byte-for-byte or the kernel's on-chain recovery rejects the bond. Reach for the wrappers unless you have a reason not to; this sketch is only enough to orient a raw caller.

Token approvals before commit — the whole per-order bond, every time. The kernel pulls the FULL per-order bonds on EVERY commit, root or sub-order, and nets nothing against bonds it already holds from earlier orders in the process.

Two different things state that, and it is worth keeping them apart. The KERNEL only pulls exactly: src/kernel/FigaroCore.sol:208-209 is two _pullExact transfer calls, c.payment * 2 from c.buyer and c.expectedCumulativeValue * 2 from c.seller, with no approval commentary and no netting logic anywhere in the file — if the allowance falls short the transferFrom reverts inside the settlement token and the kernel never sees the reason. WHAT TO APPROVE is therefore an off-chain calculation, and the SDK's calculateRootApproval / calculateSubOrderApproval (sdk/src/bonds.ts) are the authority for it. Approve the settlement ERC-20 for both legs before each commit:

import { calculateRootApproval, calculateSubOrderApproval } from "@figaro-protocol/sdk";

// Root order:
const { buyerApproval, sellerApproval } = calculateRootApproval(payment);
// → buyerApproval = 2 × payment,  sellerApproval = 2 × payment

// Sub-order (extends an existing process):
const approvals = calculateSubOrderApproval(payment, newCumulativeValue);
// → buyerApproval  = 2 × payment
//   sellerApproval = 2 × newCumulativeValue  — the WHOLE cumulative bond for
//   this order, NOT the increment over the previous order's bond.

Worked: one root plus a two-link chain. Whole settlement-token units (scale by your token's decimals). Each row is one commit; the seller column is THAT order's seller, bonding twice the cumulative value at their own link.

| Order | payment | expectedCumulativeValue | Buyer approves | Seller approves | The common mistake | |---|---|---|---|---|---| | 1 — root | 100 | 100 | 200 | 200 | — (root: the two legs coincide, which is why the trap only bites later) | | 2 — sub-order | 40 | 140 | 80 | 280 | approving 80 — 2 × the 40 increment — instead of 2 × 140 | | 3 — sub-order | 25 | 165 | 50 | 330 | approving 50 — 2 × the 25 increment — instead of 2 × 165 |

Read the rows as calculateRootApproval(100n) and calculateSubOrderApproval(40n, 140n) / calculateSubOrderApproval(25n, 165n) — they are that output. Two things the table makes visible that the prose doesn't: the BUYER is charged again on every order (200 + 80 + 50 = 330 pulled across the three commits, not 330 total value bonded once), and the seller's number GROWS with the chain even though their own link only added 40 or 25. Every one of those 330 + 810 units stays locked in the kernel until the buyer calls resolveProcess; nothing is released order by order.

Approving the increment instead of the full 2 × newCumulativeValue is the reverting mistake: commit reverts inside the settlement token with ERC20InsufficientAllowance, and the bonds already pulled for the earlier orders stay locked in the kernel until the buyer resolves the process. (The helper was calculateSubOrderSellerApproval before; it is now calculateSubOrderApproval and returns both legs.)

Catch the mistake before it reverts on-chain: pass the approval you're about to submit and the calculator's own output to assertApprovalCoversBond — it throws with the specific "never approve only the increment" message instead of leaving you to decode ERC20InsufficientAllowance.

import { assertApprovalCoversBond } from "@figaro-protocol/sdk";

const required = calculateSubOrderApproval(payment, newCumulativeValue);
assertApprovalCoversBond({ buyerApproval, sellerApproval }, required); // throws if either falls short

Bonding in a token you do not hold — a DIRECT-path composition

A party who does not hold the process settlement currency can still bond in one transaction, through WitnessSwapAndCommitCoordinator.swapAndCommit: it pulls their input token via a Permit2 WITNESS signature, swaps it at the coordinator's immutable venue, forwards the proceeds to the party's own address, then calls FigaroCore.commit. The kernel still pulls the bond from the named party, so the commitment stays bilaterally signed and the coordinator never becomes a counterparty. The SDK ships the off-chain half — the typed data whose hash IS the digest Permit2 verifies:

import { buildSwapWitnessTypedData, DISABLED_SWAP_FUNDING_LEG,
         SWAP_ROUTER_02_ABI,
         WITNESS_SWAP_AND_COMMIT_COORDINATOR_ABI } from "@figaro-protocol/sdk";
import { encodeFunctionData } from "viem";

// `swapData` IS THE VENUE'S OWN CALLDATA, forwarded verbatim after the
// coordinator approves the router for your input token — so the venue must PULL
// by ERC-20 allowance. The immutable venue is Uniswap's SwapRouter02 (the deploy
// probes factory() + WETH9() before wiring one), and its exactOutputSingle
// pulls exactly that way: deliver an EXACT output — the bond — for at most
// `amountInMaximum` of input. Build the bytes with this package's
// SWAP_ROUTER_02_ABI, which carries that shape:
//
//   recipient       — the COORDINATOR: it measures the output-balance delta,
//                     then forwards everything to the party, so the kernel's
//                     pull finds the bond and any residual stays the party's.
//   amountOut       — the leg's bond, mirroring the kernel pull exactly:
//                     2 × payment (buyer leg) or
//                     2 × expectedCumulativeValue (seller leg).
//   amountInMaximum — maxInput, the SAME cap the witness signs below.
const swapData = encodeFunctionData({
  abi: SWAP_ROUTER_02_ABI,
  functionName: "exactOutputSingle",
  args: [{
    tokenIn: inputToken,
    tokenOut: settlementCurrency,
    fee: 500,                  // the pool's fee tier — quote the tiers, take the cheapest
    recipient: coordinator,
    amountOut: bondAmount,
    amountInMaximum: maxInput,
    sqrtPriceLimitX96: 0n,
  }],
});

// The witness binds { router, inputToken, maxInput, keccak256(swapData) } into
// the digest, so a relayer cannot substitute the swap route and skim the
// slippage. `coordinator` is Permit2's spender (it performs the pull).
const typedData = buildSwapWitnessTypedData({
  chainId, permit2, coordinator, router, inputToken,
  maxInput, nonce, deadline, swapData,
});
const permitSignature = await walletClient.signTypedData({ account, ...typedData });

// swapAndCommit(c, buyerSig, sellerSig, buyerFunding, sellerFunding) — one leg
// per party; pass DISABLED_SWAP_FUNDING_LEG for a party that self-funds.
// Per-party prerequisites: approve(FigaroCore) for the bond currency (as
// always) plus a one-time approve(Permit2) for the input token.

There is no batch-path equivalent, and none can exist in-batch. A sequencer accepts a Commit operation that is the commitment plus both signatures and nothing else — no funding leg in the wire format, none in the proof — and FigaroBatchVerifier.settleBatch pulls each party's NET deposit with transferFrom when the batch lands. So on the batch path: swap in your own wallet first, then sign the commitment in the process currency, hold that balance, and approve FigaroBatchVerifier (not FigaroCore) until the batch settles. POST-settlement composition is identical on both paths — both deliver ERC-20 to the party's own address, so wallet-side routing of what you received is path-blind.

Verifying what you are about to sign

Settlement is UI-independent; presentation at the signing moment is not. The kernel verifies both EIP-712 signatures itself over a struct whose agreementHash is the merkle ROOT of the agreement's sections — so what was agreed is fixed by arithmetic once committed, and no origin can restate it. But the wallet prompt shows 32 bytes, and the readable document sits beside it on some page: a compromised origin can display document D while the struct binds hash(D′). Nothing in the signing flow catches that. Recompute the root yourself, off-origin, before signing:

import { computeAgreementHash, computeSectionLeaf,
         sectionDataHash, verifyCommitmentSignature } from "@figaro-protocol/sdk";

// `shown` — the agreement JSON you were displayed.
// `typedData` — the EIP-712 payload the WALLET displayed (domain + message).
for (const section of shown.sections) {
  console.log(section.clause, sectionDataHash(section),   // what this hash covers
                              computeSectionLeaf(section));
}
if (computeAgreementHash(shown).toLowerCase() !==
    typedData.message.agreementHash.toLowerCase()) {
  throw new Error("MISMATCH — the page showed one document and asked the " +
                  "wallet to bind another. Do not sign.");
}

// After the fact: did an address really sign this struct? (uint256 fields
// arrive from a wallet as strings — pass bigints.)
await verifyCommitmentSignature(commitment, sig, commitment.buyer,
                                { chainId, core });

scripts/verify-signed-agreement.mjs in the repo is a ready-made runner for the above (agreement file + typed-data file, optional --buyer-sig/--seller-sig, exit 0 only if every check passed). Struct-level legibility inside the wallet is a KERNEL question and is deliberately out of scope: Commitment binds the agreement by root, the kernel is frozen, and that root-binding is exactly what makes this off-origin check possible.

Recovering an in-flight process

A half-committed process — the root landed but a sub-order's counter-signature never arrived, or a client crashed mid-checkout — is recoverable from chain state alone, because OrderCommitted carries the FULL commitment payload (processId, buyer, seller, currency, payment, cumulativeValue, agreementHash, salt, deadline — everything except the two signatures).

  1. const events = await fetchCoreEvents(client, addresses, fromBlock) and read events.orderCommitted for the process (or reconstruct(events) for the live ProcessState).
  2. Re-derive the Commitment struct from an event's fields — the payload IS the struct. reconstruct also gives you the running cumulativeValue and activeOrderCount, so a resuming sub-order signs the correct expectedCumulativeValue (previous cumulative + this order's payment).
  3. Continue: re-request the missing counter-signature for that struct and re-broadcast the commit, or — as the buyer — resolveProcess the orders that DID commit. Nothing the chain can't re-derive is stranded; the bonds the kernel already pulled stay against their orders until the buyer resolves.

@figaro-protocol/sdk/agent — Agent Coordination

Lost track of where a name below lives? → Synopsis.

Context sync, network discovery, action proposer, human-in-the-loop queue, autonomous execution, did:web identity, and the coordination transports that carry an offer between two agents — InProcessChannel, HttpChannel, and A2aChannel (the Agent2Agent wire), all one interface.

import { FigaroContext, proposeActions, proposeInitiations, ActionQueue } from "@figaro-protocol/sdk/agent";
import { commit, executeAction } from "@figaro-protocol/sdk/agent";

// Sync on-chain state into a live context — the agent's own processes AND the
// live-staked network catalogue (clauses, sellers, assemblies).
const ctx = new FigaroContext(client, addresses);
await ctx.sync();

// Discover what exists (cold start): getAssemblies() / getMembers() / getClauses()
const assemblies = ctx.getAssemblies();

// FigaroContext wraps the low-level discovery primitives, which are ROOT
// `@figaro-protocol/sdk` exports — NOT `@figaro-protocol/sdk/agent`. Use them directly for a
// one-shot catalogue read without a context:
import { fetchDiscoveryEvents, reconstructDiscovery } from "@figaro-protocol/sdk";
const discovery = reconstructDiscovery(await fetchDiscoveryEvents(client, addresses, 0n));

// Propose actions on a process the agent is in, and originations from discovery
const actions = proposeActions(ctx.getProcess(processId)!, myAddress);
const initiations = proposeInitiations(assemblies, myAddress);

// Human-in-the-loop: queue actions for approval with optional review context
type ApprovalContext = {
  bindingId?: string;
  party?: string;          // "buyer" | "seller"
  runtimeSummary?: string; // free-form context for the approver
};

const queue = new ActionQueue<ApprovalContext>();
queue.enqueueAll(actions.map((action) => ({
  action,
  approvalContext: {
    bindingId: "binding:my-seller:local-anvil",
    party: "seller",
    runtimeSummary: "Seller of record · process 0x9c2b…",
  },
})));
// ... user reviews and approves ...
const approved = queue.approve(1);
console.log(approved.approvalContext?.runtimeSummary);

// Autonomous: submit transactions directly after collecting both EIP-712 signatures
const result = await commit(walletClient, publicClient, coreAddress, commitment, buyerSig, sellerSig);
// Or dispatch from a proposed action. resolve-process is self-contained; commit/
// attest/initiate take signed `inputs` — the SDK never fabricates a signature.
const result = await executeAction(walletClient, publicClient, addresses, approvedAction);

// Attest one clause end-to-end from a hydrated Agreement. Pick the clause from
// the agreement's OWN sections — never a bundled list.
import { buildSectionInclusionProof, sectionDataHash, computeClauseKey } from "@figaro-protocol/sdk";
import { attestAsSeller } from "@figaro-protocol/sdk/agent";
import { parseClauseSpec, encodeContentFromSpec } from "@figaro-protocol/sdk/clauses";
import { keccak256 } from "viem";

const section = agreement.sections[0]; // e.g. { clause: "figaro-assembly-provenance", version, data }

// 1. Inclusion proof — buildSectionInclusionProof takes the RAW section name.
const { proof } = buildSectionInclusionProof(agreement, section.clause);
// 2. Section FINGERPRINT — keccak256 of the committed canonical bytes. The
//    coordinator takes only the hash, never the preimage, so a `private`-
//    disposition section's plaintext never touches public calldata.
const sectionHash = sectionDataHash(section);
// 3. Content FINGERPRINT — hash the ABI-encoded content (which lives OFF-chain).
//    Omit content to RE-ASSERT the committed section: contentRef = sectionHash.
const parsed = parseClauseSpec(specJson);
if (!parsed.ok) throw new Error(parsed.errors[0].message);
const content = encodeContentFromSpec(parsed.spec, section.data);
const contentRef = keccak256(content);
// 4. Attest. `clauseId` is the bytes32 HASH — NOT the raw name from step 1.
// `stage` vocabulary: 0 = the clause's COMMITTED content (encode with no
// stage option); N ≥ 1 = a runtime witness whose field shape is the spec's
// own `stages[N]` declaration (encode with `{ stage: N }`); a process-log
// ladder attests its enum's index as the stage. The vocabulary is the
// clause spec's data — the SDK and the chain assign it no meaning.
const clauseId = computeClauseKey(section.clause, section.version);
// `roleCommitment`/`targetCommitment` are the SIGNED commitment structs (a root
// order carries processId = 0), NOT the reconstruction-derived form. For
// SAME-ORDER attestation pass the SAME commitment as both role and target
// (one struct in both slots) — two distinct commitments are only the
// cross-order case (seller attesting from a different order in the process).
await attestAsSeller(
  walletClient, addresses.attestationCoordinator!,
  roleCommitment, targetCommitment, clauseId, /* stage */ 0, sectionHash, proof, contentRef,
);
// The coordinator has THREE attest entry points, all merkle-binding identically
// to the signed agreement — they differ only in how caller authority is proven:
//   • attestAsSeller     — the order's seller attests (role + target commitments;
//                          pass the same struct twice for same-order attestation).
//   • attestAsBuyer      — the root buyer attests (target commitment only; the
//                          commit invariant makes msg.sender == c.buyer the check).
//   • attestViaResolver  — the order's seller is a MECHANISM CONTRACT implementing
//                          IRoleResolver, which authorizes msg.sender via
//                          isAuthorized(orderHash, caller): delegated attestation
//                          for contract-seller mechanisms.
// The SDK ships wrappers for the first two (attestAsSeller / attestAsBuyer, both
// from @figaro-protocol/sdk/agent); attestViaResolver is in ATTESTATION_COORDINATOR_ABI —
// call it directly (writeContract) when the seller is a resolver contract.

// Autonomous origination — the two-party handshake over a coordination channel:
// buyer instantiates a discovered assembly + signs; seller validates + counter-signs.
import { originateProcess, makeSellerOfferHandler, InProcessChannel } from "@figaro-protocol/sdk/agent";
// REFUSE-ALL FLOOR, BOTH HALVES: with no `accept` business rule OR no economic
// `policy` the handler declines EVERY offer. Autonomy is opt-in — these are
// where you bound currency/magnitude before the seller bonds against them.
// (A `() => true` accept-all is possible but unsafe.)
channel.register(sellerAddr, makeSellerOfferHandler(sellerWallet, publicClient, addresses, {
    accept: (offer) => offer.commitment.currency === myAcceptedToken
        && offer.commitment.expectedCumulativeValue <= myMaxBond,
    policy: { requireRootShape: true, currencyAllowlist: [myAcceptedToken], maxValue: myMaxBond },
    specs, // the merkle-leaf sign gate — a leaf/struct contradiction or missing
           // required term REFUSES before counter-signing; omit it and no
           // content check runs on this side
}));
const tx = await originateProcess(buyerWallet, publicClient, addresses, {
    channel, template, buyer, seller, currency, payment, chainId, core, overrides,
    deadline, // CHAIN time: computeDeadline(await readChainTimestamp(publicClient))
    specs,    // same gate before the buyer signs + the mechanical provenance fill
});

// TRANSPORTS — `CoordinationChannel` is ONE method (`sendOffer`), and the SDK
// ships three implementations of it: `InProcessChannel` (both agents in one
// process — tests), `HttpChannel` (a bare POST to the endpoint the seller
// publishes), and `A2aChannel` (the Agent2Agent wire). Only the channel
// changes: the handshake, the anti-tamper gate, and the refuse-all floor are
// the same object underneath, and the SDK never fabricates the counterparty
// signature on any of them.

// A2A — reach for it when the counterparty already speaks Agent2Agent (use
// HttpChannel when it just exposes an offer URL). The offer envelope rides as
// the `data` part of an A2A message, and the JSON-RPC `message/send`
// request/response IS the handshake's request/response — so a third-party A2A
// agent interoperates WITHOUT importing this SDK: it sees an ordinary message
// whose data part carries the envelope, counter-signs, and replies in kind.
import { A2aChannel, makeA2aOfferResponder, didWebEndpointResolver } from "@figaro-protocol/sdk/agent";
// BUYER: resolve the seller's A2A endpoint — a did:web service entry of type
// "A2AEndpoint", a static map, or a read of the seller's published profile
// (`projectAgentServices(profileJson).services.a2a`, below) — then originate
// over it exactly as above.
const a2a = new A2aChannel({
    resolveEndpoint: didWebEndpointResolver(sellerToDid, { serviceType: "A2AEndpoint", chainId }),
});
const a2aTx = await originateProcess(buyerWallet, publicClient, addresses,
    { channel: a2a, template, buyer, seller, currency, payment, chainId, core, overrides });
// SELLER: the SDK ships no server. `makeA2aOfferResponder` is a pure
// request→response function any server drives (node:http, express, a
// serverless function), wrapping the SAME `makeSellerOfferHandler` — so the
// refuse-all floor is unchanged: no `accept` OR no `policy` declines everything.
const respond = makeA2aOfferResponder(
    makeSellerOfferHandler(sellerWallet, publicClient, addresses, { accept, policy, specs }), // the two floors + the sign gate
);
const { status, body } = await respond(rawRequestBody); // status is always 200 — JSON-RPC carries the outcome
// The three handshake outcomes on the wire, mirroring HttpChannel's 200/204/422:
//   • result message WITH a data part  → counter-signed (sendOffer returns the envelope);
//   • result message with NO data part → policy DECLINE (sendOffer returns null);
//   • JSON-RPC error → sendOffer THROWS — `-32002` when the seller's anti-tamper
//     gate rejected the offer, `-32602`/`-32700` for a malformed request. A
//     rejection is never a silent decline.
// An unresolvable endpoint is ABSENCE, not a decline: sendOffer returns null.
// Hand-rolling either side (a non-SDK A2A agent, a custom server)? The codec is
// exported: `a2aMessageFromOffer(offer, "user" | "agent", messageId)` and
// `offerFromA2aMessage(message)` — null when the message carries no data part,
// THROWS on a malformed one (malformed is not absence). `messageId` is
// correlation metadata only; the envelope's own signatures authenticate.

// The dispatch race — market formation with zero contracts, the seller-signs-
// first INVERSE of the handshake above: a buyer relays the SAME unsigned draft
// shape to k candidates (each draft naming that candidate at their own posted
// price), candidates counter-sign to answer "available", and the buyer signs
// EXACTLY ONE winner — the single buyer signature is both the selection event
// and the seller-address answer. A draft binds nobody and cannot be broadcast
// (the kernel needs both signatures); a losing countersignature expires inert
// at the struct deadline. Same two candidate-side floors as counterSignOffer,
// and the same optional `specs` merkle-leaf gate: with a SpecSource, a draft
// whose commerce leaf contradicts the struct is refused before any signature.
import { validateDraft, counterSignDraft, verifyRaceReply, selectRaceWinner } from "@figaro-protocol/sdk/agent";
const reply = await counterSignDraft(courierWallet, draft, { chainId, core }, accept, policy, specs);
// Buyer side: exact struct-hash equality against the SENT draft, then recovery —
// a doctored reply cannot ride a valid signature.
const check = await verifyRaceReply(reply!, draft, { chainId, core });
const winner = selectRaceWinner(replies); // cheapest countersigner; ties by arrival
// Packaged fan-out + mountable responder (the RFQ leg below has the same pair):
import { requestCounterSignatures, makeSellerRaceHandler } from "@figaro-protocol/sdk/agent";
channel.register(courierAddr, makeSellerRaceHandler(courierWallet, { chainId, core }, { accept, policy, specs }));
const race = await requestCounterSignatures(channel, drafts, { chainId, core }); // { replies, winner }

// The RFQ leg — same choreography, the CANDIDATE authors the price (bespoke
// jobs, thin markets — no posted figure fits). The request goes out at the
// buyer's CEILING (their reservation price, inside the signed struct so the
// cap is enforceable); the candidate's pricing function quotes below it; the
// counter-draft re-prices ONLY the fields the buyer named (`pricedFields` —
// the buyer names their own clause, the SDK names none). The buyer verifies
// by RECONSTRUCTION: the same substitution applied to their OWN draft must
// reproduce the reply hash-for-hash — a quote can change the price and
// nothing else. Cheapest verified quote wins; the buyer signs exactly one.
import { buildQuoteRequest, requestQuotes, makeSellerQuoteHandler } from "@figaro-protocol/sdk/agent";
channel.register(courierAddr, makeSellerQuoteHandler(courierWallet, { chainId, core }, {
    quote: (draft) => myPriceFor(draft),           // null declines; > ceiling declines
    policy: { requireRootShape: true, currencyAllowlist: [myToken], maxValue: myMaxBond },
}));
const drafts = candidates.map((seller) => buildQuoteRequest({
    template, buyer, seller, currency, ceiling, chainId, core,
    pricedFields: [{ clause: "figaro-commerce", path: "payment" },
                   { clause: "figaro-commerce", path: "lineItems.0.unitPrice" }],
    overrides,
}));
const { winner: quoted } = await requestQuotes(channel, drafts, { chainId, core });

// A relayed offer envelope is untrusted input. `deserializeCommitmentPayload`
// parses through the root-exported `strippingReviver`, dropping any
// `__proto__` / `constructor` / `prototype` keys at parse time — a malicious
// envelope cannot pollute the prototype chain of the receiving agent. Reuse
// `strippingReviver` for any other untrusted JSON you parse (IPFS bodies,
// channel payloads): `JSON.parse(body, strippingReviver)`.
import { strippingReviver } from "@figaro-protocol/sdk";
import { deserializeCommitmentPayload } from "@figaro-protocol/sdk/agent";

// Submitting to the BATCH path — SequencerClient. `FigaroBatchVerifier.
// settleBatch` is PERMISSIONLESS (no caller gate, no owner, no fee), but it
// takes an SP1 proof over a whole batch, so the ordinary route is to hand the
// signed operation to a sequencer: an HTTP relay that pools operations, proves
// the batch, and settles it. This client emits EXACTLY the wire format the
// endpoint accepts — never hand-roll the JSON.
//
// A RELAY, NOT AN AUTHORITY: it holds no key of yours, its admission checks
// call the same kernel functions the proof runs (so it rejects earlier than
// the proof, never accepts more), and its honest powers are censor and delay —
// never forge. Fall back to direct FigaroCore submission with the SAME
// signed operations. There is no hosted public endpoint today; the URL is deployment
// config, like an RPC URL. Surface + run-your-own recipe: prover/sequencer.
//
// A batch operation is the SIGNED PAYLOAD AND NOTHING ELSE — there is no
// funding leg, so swap-and-commit does not exist here. Bonding in a token you
// do not hold means swapping in your own WALLET first, then submitting; and
// settleBatch pulls your net deposit, so approve FigaroBatchVerifier, not the
// kernel. (See "Bonding in a token you do not hold" above.)
import { SequencerClient } from "@figaro-protocol/sdk/agent";
const seq = new SequencerClient({ url: SEQUENCER_URL });
if (!(await seq.isAvailable())) { /* direct path instead */ }
const { id } = await seq.submitCommit(commitment, buyerSig, sellerSig);
// Admission is IDEMPOTENT on ON-CHAIN IDENTITY (order hash / process id /
// attestation identity) — a retry, even a RE-SIGNED one, returns the original
// id and enqueues nothing. `{ id }` is a queue receipt, NOT settlement:
// confirm from chain (BatchSettled, the ERC-20 transfers, scoreOf).
// FigaroCore.orderStatus(orderHash) stays 0 for this order FOREVER — 0 means
// "not on this path", never "not settled". Gating any read on orderStatus is
// blind to everything that settles here; see docs/SCALING_STRATEGY.md §
// "Two settlement paths, two DISJOINT state universes".
await seq.submitResolve(processId, commitments, buyerSig);
await seq.submitAttestAsSeller({ role, target, clauseId, stage, contentRef, sellerSig, proof });
await seq.submitUsageClaim(claim);  // the RPGF leg — build with buildUsageClaims
await seq.status();  // { state_root, pending_ops, pending_usage_claims, batches_settled, archive }

// READING BATCHED TRADE BACK. A batch-settled order has no kernel event and no
// per-order flag on chain, so do NOT chase stateRoot() and BatchSettled by
// hand: the relay PUBLISHES the batch universe's mirror of the kernel's
// events, and the client encodes the 404 rule you must not get wrong.
const view = await seq.process(processId);   // the orders + the resolution facts
const one  = await seq.order(orderHash);     // one published order
const page = await seq.batches({ from: 0 }); // ≤50 a page; follow next_cursor
// `null` means "not in THIS relay's archive" — set