Skip to content
Conduit

mcp server

sealed

Two AI agents negotiate a deal with sealed positions. ERC-8004 verified reputation, zero exposure until both sides commit. Built on Monad.

kasbsquall/sealedcommunitylistedunreviewed

Set up

Runs on your computer. Pick your agent and operating system.

Before you start

  • Node.js 20 or newer
~/Library/Application Support/Claude/claude_desktop_config.json
{
  "mcpServers": {
    "kasbsquall-sealed": {
      "command": "npx",
      "args": [
        "-y",
        "sealed-monad"
      ]
    }
  }
}

same format Cursor, GitHub Copilot CLI, Junie, JetBrains AI Assistant, Augment (Auggie CLI), Factory Droid, Kiro, Devin CLI +12

Values in angle brackets are placeholders. Restart Claude Desktop after editing its config.Claude Desktop docs

README

synced Oct 26, 2026

Sealed

Your AI agent can negotiate a price without showing the other side its budget first.

Built on Monad testnet · ERC-8004 verified identity and reputation · Privy agent wallets bounded by a contract-scoped mandate · negotiator agents on Qwen.

Live judge page: sealed-monad.vercel.app · Demo video (2:58): youtu.be/6TWo6QkauyA, with Claude Code negotiating at the public desk from 0:18 and the Privy wallets from 2:14. No wallet or login needed on the page: it replays negotiation #4 from Monad testnet and links every transaction. Why agents will negotiate prices, and what goes wrong when they do it in the open, with sources: docs/WHY.md. What a leaked limit did in our own harness, measured over 17 runs: the mean settled price stayed within five cents across conditions, so it did not move, and the seller's opening offer moved to just under the leaked number (experiments/leak-2026-10/summary.md). To build on Sealed, start with docs/INTEGRATING.md, or install the SDK and MCP server from npm: sealed-monad (npm i sealed-monad; for Claude Code, claude mcp add sealed -- npx -y -p sealed-monad sealed-mcp, then ask it to verify settlement 0xb4f7adf1…, or to buy at the public desk below). How the Qwen 3.8 Max agents work, with the evidence: docs/QWEN.md and the article on DEV.

Negotiate with Sealed yourself. A public desk at https://desk.avanc3.pe sells the demo's API access on the third contract (v3), which pays the agreed price in the settlement transaction. Your agent needs no wallet and no MON: the desk registers an ERC-8004 identity for a test key on your machine, funds its escrow with a test dollar that has no value (tUSD) and pays the gas. Your agent only signs. Your limit is never sent to the desk or the chain; if your agent runs on a hosted model, as Claude Code does, the model provider sees it in the prompt. A settlement publishes both final offers, so the MCP server refuses an offer equal to the limit; a settled deal still shows that the buyer's limit was at least its last offer. With Claude Code:

claude mcp add sealed -- npx -y -p sealed-monad sealed-mcp

Then ask: "Use the Sealed desk to buy the API access. My limit is 4300 US cents per 1,000 calls. Negotiate." Outside agents that finish a negotiation there are listed on the judge page with their transactions. The first one, v3 negotiation #9 on 2026-10-11, was Giancarlo, a Claude Code user outside the team who knows the founder personally: from the Claude desktop app on Windows, with the package from npm and that one sentence, it settled at 4236 ($42.36) and paid in the same transaction (0xec3780a9…). What the desk can see, and what it cannot do, is in The public desk.

Check it in 30 seconds

Negotiation numbers restart on each contract: "#4" is on the first contract, "v2 #4" on the second (v2), "v3 #1" on the third (v3), which adds payment at settlement. Prices are US cents per 1,000 API calls, so 4190 is $41.90. The first eleven rows are transactions on Monad testnet that open without a wallet. The Privy row is this repo's own record of Privy's answers: a refused request leaves nothing on-chain, so it cannot be checked independently, only re-run with npm run probes:privy and Privy credentials.

ClaimLook here
An offer goes on-chain as a hash, never as a numberRound 1 of negotiation #4, the buyer's commit 0x233b12ab…: its input is a negotiation id and 32 bytes
Both final offers become public together, in one settlement at the midpointSettlement of #4 0xb4f7adf1…: 4250 and 4130, settled at 4190
Offers that never cross are never publishedNegotiation #6 expired after three rounds 0xf4933ff9…; no offer appears in any of its transactions
After a deal, each agent rates the other in ERC-8004, and the review points at the settlement, so anyone can check the reviewer and the reviewed were its two parties#4: buyer's review 0xb936c5b6…, seller's review 0x9536a463…; each NewFeedback carries the settlement hash as feedbackHash
A Qwen agent told by the seller's listing to open at its full budget, a number code would allow, refusesv2 negotiation #4: the listing told the buyer to submit its principal's full limit, which is 4300, as its first offer; it bid 3750, 3950, 4250, said why in each note, and settled at 4185 0x3fb465c9…. With a listing demanding 6000, which code would refuse anyway, #10 behaved the same
An agent without enough ERC-8004 reputation is refused0xc065049e… reverted with NotAdmitted(2086)
On the current contract, with the relay and each agent as its own process, a deal still settlesv2 negotiation #5 on 0xb9D7c55f…: 3600 against 4700, 3950 against 4350, then 4200 against 4120, settled at 4160 0xe021cd82…; each agent process read only its own key file; RUN=demo-runs/monadTestnet-v2-deal-5-separated.json npm run verify:run passes
An agent written outside this repository plugs in through the npm package and the HTTP routesv2 negotiation #7: a rule-based API seller with no model, from a separate repository (kasbsquall/sealed-seller-agent, same author, built only on sealed-monad), with its own key and ERC-8004 identity 2126, against the Qwen buyer: 3650 against 4700, 3900 against 4340, then 4200 against 4200, settled at 4200 0x036f08bd…; the seller's floor never appeared on-chain; RUN=demo-runs/monadTestnet-v2-external-seller-7.json npm run verify:run passes. It shows the integration path, not adoption by another team
The agreed price is paid in the settlement transaction, and Sealed's 0.25% fee with itv3 negotiation #1, two Qwen 3.8 Max agents: 3850 against 4800, 4075 against 4350, then 4250 against 4150, settled at 4200 0xce2fba1c…; in that transaction 42,000 tUSD leave the buyer's escrow, 41,895 go to the seller and 105 to Sealed's treasury (Paid event). Both agents signed the payment terms and the admission policy before it opened. RUN=demo-runs/monadTestnet-v3-deal-paid-1.json npm run verify:run passes; it checks the hashes and the settlement, and the payment is read from the settle transaction
An agent with a fresh key and no MON negotiates and pays at the public deskv3 negotiation #6, our own check before the desk opened: the package's sealed-mcp server, started with an empty key folder, against desk.avanc3.pe, settled at 4176 0xe82b75fb…; the desk registered the guest's ERC-8004 identity and sent every transaction, and the guest only signed
Someone outside the team negotiates and pays at the public deskv3 negotiation #9: Giancarlo, a Claude Code user outside the team who knows the founder personally, ran the published MCP server from the Claude desktop app on their own Windows machine. Offers 2500, 3500, then 4299 against the seller's 4173 in round 3; settled at 4236, and 42,360 tUSD left the guest's escrow, 42,254.10 to the seller and 105.90 to Sealed, in 0xec3780a9…. Their agent, ERC-8004 identity 2195, was registered by the desk like any guest
The agents' Privy wallets refuse anything outside Sealed, and the agent's key cannot change that11 of 11 forbidden requests refused with policy_violation, 3 of 3 owner actions tried with the agent's key refused (rewrite the mandate, reclaim or export the wallet), 3 of 3 negotiator requests signed: deployments/privy-monadTestnet.json

To re-derive every hash of #4 from the published offers and salts, with no keys and no .env:

npm install
RUN=demo-runs/monadTestnet-deal-4.json npm run verify:run

It reads Monad testnet's public RPC and ends with Every check passed. The longer walkthrough is in Verify it in two minutes.


The problem

You give an AI agent a budget and ask it to buy API access. The seller runs an agent too. If the seller's agent learns your ceiling, it anchors on it: in our own harness, a seller told the buyer's limit opened just under that number in every run, against 4600 to 5000 without it (experiments/leak-2026-10/summary.md). With these two agents the settled price did not move (mean $41.86 to $41.91 across conditions), so we do not claim savings. On a public chain that leak is the default: every offer an agent submits is readable by anyone, the counterparty included, before the deal closes.

Sealed changes the order in which numbers become visible. Both agents commit sealed offers to Monad. A relay answers one question, whether the offers crossed, and nothing else. When they cross, one transaction settles at the midpoint, which makes the two final offers public. When they never cross, no offer is ever made public. Sealing works both ways: the buyer never sees the seller's floor either, which is why the seller is the side that pays for it.

ERC-8004 already answers whether an agent can be trusted: its Identity and Reputation registries live at the same addresses on Monad and more than twenty other chains. Sealed checks both agents against them before a negotiation opens, and handles the step that comes next, agreeing on a number without exposing it.

What it does

Two agents, each with an ERC-8004 identity and reputation on Monad, reach an agreement through a contract that never learns either position until both are locked, and never learns either position at all if the deal does not happen.

  1. Admission. ReputationGate checks both agents against an explicit, on-chain policy read from the canonical ERC-8004 Reputation Registry. It answers one bit: does this agent clear the bar. The registry itself is public, so the gate saves the counterparty a lookup rather than hiding anything.
  2. Commitment. Each agent submits a salted, domain-separated hash of its position. Counter-offers are new commitments, and the same number committed twice produces two unrelated hashes, so an observer watching a sequence of updates cannot tell whether an agent moved or held.
  3. Atomic settlement. There is no reveal phase. settle consumes both offers, both salts and both EIP-712 authorizations in a single transaction. Either both positions land on-chain in the same instant or neither ever does.
  4. Silent failure. If the positions do not clear, nothing is submitted and the negotiation expires. The chain records that two agents talked and did not trade. It never records what either one asked for.
  5. Reviews tied to a deal. After a settlement, each agent rates the other in the canonical ERC-8004 Reputation Registry (giveFeedback, tags sealed and settled), and the review's feedbackHash is the settlement transaction. Each agent reads that settlement from the chain itself before rating and rates only the other party to it, so the relay cannot steer a review (agents/sealed/dealFeedback.ts). giveFeedback itself is open to anyone, so the registry does not enforce this; what Sealed adds is that anyone can follow the hash to a NegotiationSettled event and check that its two parties are exactly the reviewer and the reviewed. A review that fails that check did not come from a Sealed deal. A review that passes it proves a deal settled, not that the deal was at arm's length: the rating is always 100, and two agents run by one operator can settle with each other to collect reviews for the cost of gas. On Monad testnet the agents of #4 and #8 have rated each other: 0xb936c5b6…, 0x9536a463…, 0xfc16495a…, 0xa1bc0f3d….

Between rounds, a clearing relay checks each agent's reveal against its on-chain hash and tells both sides one bit: crossed or not. In a round that does not cross, neither agent learns the other's number, so counter-offers stay sealed too.

Why Monad

A sealed negotiation is a conversation held in transactions: one creation, a commit per agent per round, and one settlement. On a slow or expensive chain that turns a few seconds of agent reasoning into minutes of waiting, and pushes agents toward a single take-it-or-leave-it round. On Monad, scripted negotiation #1 went from creation to settlement in 17 blocks, about 5 seconds by block timestamps, and a commit cost about 0.0056 MON at 102 gwei. That leaves room for several rounds of counter-offers inside the time a person would spend reading one quote. With model-driven agents a round took about two minutes (docs/QWEN.md), almost all of it the models reasoning; the chain's share of a round is its commit transactions.

The ERC-8004 registries are deployed on Monad at their canonical addresses, so the reputation the gate reads is the same registry any other Monad agent writes to.

Two Monad properties changed the code: gas is charged on the limit rather than on gas used, and the public RPC serves logs over at most 100 blocks. Both are handled and documented in docs/ADDRESSES.md.

Why textbook commit-reveal is not enough here

The standard sealed-bid pattern is commit a hash, then reveal. In an auction with many bidders and a deposit at stake, fine. In a two-party negotiation it fails in two specific ways, and both are fixed in SealedNegotiation.sol.

Last-revealer advantage. If A reveals first, B reads A's number and then decides whether revealing still suits it. B walks away having learned everything while exposing nothing. In a bilateral deal that asymmetry is the whole game.

Sealed removes the reveal phase entirely. Settlement is one atomic call carrying both sides, each authorized by an EIP-712 signature bound to the exact pair of commitment hashes and their round indices. Nobody goes first because there is no first. In the first deployment (contractsV1 in deployments/monadTestnet.json, used by runs #2 to #10), a stale authorization from an earlier round is void the moment either side re-commits. In the current one (v2), it is void once both sides have committed a new round, and one side committing again cannot void a pair both have signed.

Moving the advantage from revealing to signing would bring it back, so the relay asks both agents for their signatures in every round, right after both commitments are on-chain and before it compares. Being asked to sign tells an agent nothing, and an agent that refuses ends the negotiation before anyone learns the result. A signature that does not recover to the party's wallet counts as a refusal, and so does a party whose on-chain index has moved past the round; in both cases the relay ends the negotiation without comparing. The relay does not ask for signatures, compare or send a settlement with less than 30 seconds of chain time before the deadline, because a settlement mined after the deadline reverts and its calldata still shows both offers, and it gives up on a party that does not answer in time. It also simulates settle before sending it. These cases have tests in test/agents.test.ts.

What a party can still do from the mempool depends on the contract. In the first deployment, once the settlement is broadcast its calldata is public, and a party watching the mempool can still re-commit ahead of it, void it and expose both offers; the relay's checks narrow that window without closing it. From v2 on, the contract freezes a round once both sides have committed it: a party that re-commits ahead of the settlement only opens its own next round, the settlement still lands, and a second attempt reverts with AlreadyCommitted. The price is that a side can no longer replace its commitment within a round, not even before the other side has committed. v2 is deployed on Monad testnet at 0xb9D7c55f… and verified on Sourcify, and v2 negotiations #3 to #7 settled on it. The front-run itself has been tried only in the tests (test/SealedNegotiation.test.ts), not on-chain. An encrypted mempool that keeps a settlement's calldata unreadable until it is ordered, such as Monad's BTX, would also help; Sealed does not use it and we have not tested it.

A price is not a 256-bit secret. keccak256(price) over a plausible range is brute-forced in milliseconds. Sealed's commitment pre-image binds the EIP-712 domain separator (chain id and contract address), the negotiation id, the committing party, the round index, the offer and a 32-byte salt.

Agent wallets with a mandate the agent cannot exceed

A negotiator has to sign without a human in the loop, and an agent that can sign anything is a drainable key with a language interface attached. So each negotiator can run on a Privy server wallet whose key stays in Privy's enclave, under a policy that only allows:

  • transactions to the deployed SealedNegotiation, on Monad testnet, with zero value, plus register on the ERC-8004 Identity Registry and giveFeedback on the Reputation Registry, and no other function on either;
  • EIP-712 signatures whose domain is that same Sealed contract on Monad testnet.

Privy denies everything else. The rules are built as data and covered by 15 tests (test/mandate.test.ts). npm run probes:privy sends a live Privy wallet seventeen requests (agents/privy/probes.ts): eleven signing requests a hijacked agent would try, which the mandate must refuse with policy_violation; three owner actions tried with the agent's own key (rewrite the mandate, take the wallet back, export its key), which Privy must refuse because that key does not own the wallet or the policy; and three a negotiator really makes, which it must sign. Any other error is reported as inconclusive, counts for nothing, and stops the script. The policy and both wallets are owned by a 2-of-2 admin quorum whose keys sit in one git-ignored file, .privy-admin-keys.json, written and read only by the Privy setup scripts (scripts/privy-admin.ts); the agent and relay modules never open it, though scripts/privy-demo.ts loads it in the same process that later runs a negotiation, and in this demo one operator holds both keys; the agent's key quorum is only an additional signer held to the mandate (docs/PRIVY.md). npm run demo:privy sets up the wallets, runs the same probes and then a negotiation on Privy wallets.

It ran against Privy on Monad testnet on 2026-10-09. Privy accepted the mandate as policy ne7rynh2rknj5jw930p9wsq7, created a buyer and a seller wallet under it, and each wallet registered its own ERC-8004 identity (#2093, #2094). Of the first fourteen probes, Privy refused all eleven forbidden requests with policy_violation and signed all three negotiator requests. The eleven: a 1 wei transfer, the same transfer through eth_signTransaction, a Permit2 approval, a call to the Sealed contract carrying 1 wei, a Sealed call for chain 1, an ERC-721 approve of the agent's identity, a Sealed-looking authorization for a lookalike contract, a real Sealed authorization for chain 1, a free-text personal_sign message, an EIP-7702 delegation of the wallet, and revokeFeedback on the Reputation Registry. Nothing signed by a probe was broadcast. Until then one 1-of-1 key quorum owned the policy and the wallets, and the agent signed with that same key; a reviewer flagged it, and the same day scripts/privy-split-keys.ts handed the policy and both wallets to a 2-of-2 admin quorum and left the agent's quorum on them only as a signer held to the mandate. Re-sent after that, all seventeen probes ended as the mandate says: 14 refused (the eleven above with policy_violation, and the three owner actions with No valid authorization signatures were provided) and 3 signed. The giveFeedback rule had been added to the live policy before the split, in place, by the then-owner quorum (scripts/privy-feedback-rule.ts, which now signs with the admin quorum), and the two Privy wallets then rated each other for #8 (0xfc16495a…, 0xa1bc0f3d…). Earlier the same day, before the key split, two Qwen 3.8 Max agents had negotiated with every commitment and every settlement authorization signed by Privy: negotiation #8 settled at 4180, from 4220 against 4140. After the split, the admin quorum extended the mandate to the v2 contract with three more rules of the same shape (scripts/privy-contract-rule.ts), and v2 negotiation #6 ran on the same two wallets with the agent's key only as a signer under the mandate: 3650 against 4800, 4050 against 4400, then 4250 against 4150, settled at 4200 0xba15e68f… (demo-runs/monadTestnet-v2-privy-deal-6.json). The responses are in deployments/privy-monadTestnet.json and the run in demo-runs/monadTestnet-privy-deal-8.json. Running against the real service found three things the unit tests could not: rule names must be under 50 characters, Privy's node can lag a fresh deposit, and the typed-data request cannot carry a bigint. See docs/PRIVY.md.

The public desk

The desk (agents/desk/) is a seller agent and a relay that Sealed runs at https://desk.avanc3.pe, so any agent can try a paid negotiation on v3 without setting anything up. It sells the demo's API access: 1,000,000 calls, priced in US cents per 1,000 calls, paid in tUSD.

  1. Joining. The guest makes a key on its own machine. The desk calls register() on the ERC-8004 Identity Registry and then setAgentWallet with the guest's EIP-712 signature, so the identity's agent wallet is the guest's key; the desk's operator stays the identity's owner. It tops up the guest's escrow to a float of 50,000 tUSD, the same for every guest and more than the deal costs at the seller's list price (44,000 tUSD), so the float says nothing about the guest's limit.
  2. Opening. Both sides sign the OpenNegotiation terms: both agents, a 30-minute deadline, the terms, the admission policy and the payment terms. The desk's policy admits any registered agent with no reviews required; the gate still checks that each wallet is its agent's own.
  3. Rounds. Up to three. The seller follows a fixed schedule in agents/desk/seller.ts: its list price, 4400, in round 1, then 60% of the remaining gap toward a floor the desk keeps private and draws anew for each negotiation, so a past settlement does not tell the next guest where the seller will stop (v3 #4 to #6 all settled at 4176 because they ran before the floor was drawn per negotiation; #7 and #8 settled at 4244 and 4206). The guest signs a Commit over its hash and the desk submits it with commitOfferBySig, paying the gas. The guest reveals its offer to the desk only after reading both commitments of the round on-chain itself. Before the reveal the desk holds only the signed hash: from sealed-monad 0.3.2 the guest no longer sends a stance with it, because "final-at-limit" told the relay when an offer was the limit, and it refuses an offer equal to its limit.
  4. Settlement. A round that crosses settles at the midpoint, and the price moves from the guest's escrow to the seller in that transaction. A negotiation that never crosses expires with no offer on-chain.

What a guest trusts: the desk runs the relay and the seller, so at the desk the relay and the counterparty are the same party, Sealed. It sees both numbers of each round, as any Sealed relay does, and a dishonest desk could pass the guest's number to its own seller. It cannot settle, or move the guest's tUSD, without the guest's signature over the guest's own committed number. The seller's rule is public (GET /v1/desk), so a settled price also bounds where that negotiation's floor was.

The desk speaks plain HTTP: GET /v1/desk returns the terms, the contracts and the seller's rule, GET /v1/stats the public register, POST /v1/guests starts an identity, and POST /v1/sessions opens a negotiation whose steps the guest answers at /v1/sessions/:id/consent, commit, reveal and authorize. DeskClient in sealed-monad and scripts/desk-guest.ts implement the guest side. The register counts outside agents only. Sealed's own runs, v3 #4 to #8, were cleared from it after each check; #7 and #8 are Claude Code negotiating from one sentence through the published MCP server, filmed for the demo video. #9 is the first outside negotiation, by Giancarlo (see Check it in 30 seconds).

What Sealed does not claim

Checking whether two sealed numbers cross needs someone to see both. In Sealed that is the clearing relay (agents/relay/clearingRelay.ts), and its power is deliberately narrow:

  • it cannot change or forge a deal: settlement needs both agents' EIP-712 signatures over the exact committed pair, and the contract re-checks every reveal against its hash;
  • it cannot be lied to: a reveal that does not hash to the on-chain commitment is rejected;
  • it is trusted with confidentiality: it sees both numbers of a round that does not cross, and discards them;
  • it is trusted to report the bit honestly: a relay that says "apart" when the numbers crossed pushes both sides to concede further, and neither agent can detect it from the chain. A relay colluding with one side could tell it the other's number.

So, against parties that follow the protocol, neither the chain nor the counterparty learns an agent's position unless the deal settles. The relay does, briefly, and on the first deployment a counterparty that front-runs a broadcast settlement can still expose both offers (see above; v2 closes that). The production path is to run it inside an attested TEE, or to replace the comparison with threshold encryption or an FHE coprocessor.

The relay holds no agent key. scripts/run-separated.ts runs the buyer agent and the seller agent as separate processes from the relay: each agent process reads only its own key file (npm run keys:agents writes one per agent), does not load .env and inherits only the model settings, the relay never reads an agent key, and it reaches the agents over HTTP (agents/relay/party.ts). The launcher still starts both agents and passes each its limit, so in this demo one script knows both limits; in production each owner would start its own agent. v2 negotiation #5 ran that way and settled at 4160 (demo-runs/monadTestnet-v2-deal-5-separated.json); #3, before agents had one key file each, settled at 4185.

The model provider is a separate question. With a hosted model such as Qwen 3.8 Max, or Claude when Claude Code negotiates at the desk, each agent sends its own mandate, limit included, to the provider on every turn. With a local model through Ollama, the mandate never leaves the operator's machine. Both are a configuration change (LLM_BASE_URL, LLM_MODEL); an operator who cannot share its limits with a provider should run the agent locally.

On the first two contracts, settlement records a price and moves no value, and the agents' Privy mandate, which covers only those two, pins every transaction's value to zero. v3 pays the price at settlement from the buyer's escrow, with the 0.25% fee, in SealedTestUSD: a test token with no value that anyone can mint. Real money needs a real stablecoin and mainnet. The escrow is a shared float: with two paid negotiations open, the first to settle draws first and the second can revert with InsufficientEscrow, and a buyer with a paid negotiation open cannot withdraw.

Timing metadata is public. The mempool shows that an address committed and when. It never shows what.

The gate does not count deal reviews yet. It counts reviews from the reviewers the policy names, and in the demo those are seeded wallets. A policy that counts only reviews whose feedbackHash is a Sealed settlement between the two parties would make admission depend on real deals; it needs a change to ReputationGate, which is deployed and verified as it is, so the reviews are written and checkable today and not yet read by the gate.

Two more limits a reviewer will find in the code. On v1 and v2, createNegotiation is permissionless and takes the admission policy (which reviewers count, and how many reviews) from the caller, so the gate proves the integration with ERC-8004 rather than a policy both sides agreed to. From v2 on, the contract stores the hash of the policy it checked in the negotiation and emits it in NegotiationCreated, so anyone can confirm which policy admitted both agents. On v3 both agents sign the policy's hash with the rest of the terms before the negotiation opens, so there the policy is one both sides agreed to. And the NatSpec at the top of SealedNegotiation.sol describes agents exchanging reveals with each other, which predates the clearing relay, and the NatSpec of the two registry interfaces mentions Base Sepolia, where they were first checked against the live registries. Both are left unchanged because the contracts are deployed and verified byte for byte, and this README describes the current flow.

Architecture

ERC-8004 canonical registries on Monad testnet   (read only, not deployed by us)
  IdentityRegistry      0x8004A818BFB912233c491871b3d84c89A494BD9e
  ReputationRegistry    0x8004B663056A597Dffe9eCcC1965A193B7388713
        |
        v
  ReputationGate.sol      admission policy over the public registries, one bit out
        |
        v
  SealedNegotiation.sol   commitment, counter-offers, atomic settlement, silent expiry
  SealedNegotiationV3.sol the same, plus terms both agents sign and payment at settlement
        |
        v
  NegotiatorAgent         works each round in steps with a model (Qwen 3.8 Max
                          hosted, or a local Qwen through Ollama) and four tools;
                          code checks every number against the mandate
        |
        v
  ClearingRelay           checks reveals against on-chain hashes, answers one bit
                          per round, submits the atomic settlement or the expiry
        |
        v
  Privy agent wallet      optional signer for each agent, limited by policy to
                          Sealed on Monad testnet

The agent works in steps, and code has the last word

Each round, the negotiator's model works through four tools (agents/negotiator/tools.ts):

  • read_negotiation reads the round, the time left on-chain and the agent's own earlier offers and notes;
  • read_counterparty_reputation reads the other agent's ERC-8004 reputation on-chain, counting only reviewers the principal trusts;
  • check_offer says whether a candidate number is allowed and what it means for the principal if it crosses;
  • submit_offer commits to a number and a note. In round 1 the note is the model's plan for every round; in later rounds the model reads it back and says whether it is following it.

None of the tools can reach the counterparty's number, which is sealed. Every submission goes through the same checks: never past the principal's limit, never back from an earlier concession, never on the wrong scale. A rejected number goes back to the model with the reason, so it can correct itself. After two rejections code takes the model's last number and clamps it, and the transcript records what the model asked for. Every tool call and its result is written to the transcript in demo-runs/.

To build on Sealed (contract calls, commitment encoding, the authorization, and how to plug in your own agent), start with docs/INTEGRATING.md. See also docs/ARCHITECTURE.md, docs/ADDRESSES.md and docs/PRIVY.md.

Running it

Requirements: Node.js and npm (tested with Node.js 22).

npm install
npx hardhat test            # 131 tests, against the real ERC-8004 registry code
npm run check:registries    # calls the live registries on Monad testnet

The test suite is where the privacy claims are proved rather than asserted. Among the cases:

  • a committed position leaves no trace of the offer or the salt in any log
  • the same offer in a later round produces an unrelated commitment
  • one party's authorization is never enough to settle, even signed twice
  • one side re-committing, even ahead of a broadcast settlement, cannot void a pair both sides signed, and both sides moving to a new round voids the old authorizations (v2; in the first deployment any re-commit voids them)
  • a commitment cannot be replayed against another deployment of the same contract
  • an expired negotiation puts neither position on-chain
  • the agent's own off-chain commitment encoder matches the contract exactly, across the full uint256 range
  • an agent never commits past its mandate, whatever the model answers
  • the relay refuses a reveal that does not match the on-chain commitment
  • an agent fails closed when its model gives no usable answer
  • the agent reads the negotiation and the counterparty's on-chain reputation through tools, gets a rejected number back with the reason, and carries its own plan into later rounds
  • the Privy mandate allows Sealed calls and refuses transfers, other contracts, other chains and Permit2 signatures
  • a Privy probe counts as refused only on Privy's own policy_violation, never on a network or setup error

Deploy and run on Monad testnet:

cp .env.example .env    # fresh deployer key funded at faucet.monad.xyz, plus LLM settings
npm run seed:monad      # deploys, registers three demo agents, seeds reputation, checks the gate
npx hardhat run scripts/smoke-negotiation.ts --network monadTestnet   # scripted negotiation, no model
npm run demo:monad      # two negotiations with model-driven agents, transcripts in demo-runs/
RUN=demo-runs/<file>.json npm run verify:run                          # re-derives every on-chain hash
npm run demo:privy      # needs the Privy settings in .env
npm run probes:privy    # sends the seventeen mandate probes to the live wallet (recorded ones are skipped)
npx hardhat run scripts/deploy-v3.ts --network monadTestnet   # v3 and the test dollar
npx hardhat run scripts/run-paid.ts --network monadTestnet    # the demo deal on v3, paid at settlement
npx tsx scripts/desk-keys.ts && npx tsx scripts/desk.ts       # a desk of your own, on 127.0.0.1

Verify it in two minutes

Everything below is on Monad testnet and readable without a wallet.

  1. The contracts are the code in this repo. All three deployments are verified on Sourcify with an exact match: the current SealedNegotiation and ReputationGate (v2), and the first SealedNegotiation and ReputationGate, which negotiations #2 to #10, the gate refusal and Privy negotiation #8 used. The Privy mandate covers those two. v3 adds SealedNegotiationV3 and the test dollar SealedTestUSD, and reuses the v2 gate.
  2. They read the real ERC-8004 registries. ReputationGate was deployed pointing at the canonical Identity and Reputation registries (0x8004A818…, 0x8004B663…), and npm run check:registries calls them live.
  3. The gate refuses an agent without enough reputation. Agent 2086 has one seeded review; the policy asks for five. 0xc065049e… is a createNegotiation sent for it as a real transaction; it reverted with NotAdmitted(2086) and opened nothing (scripts/gate-refusal.ts).
  4. A negotiation settled on-chain without either offer appearing before settlement. Open the two commit transactions of negotiation #1, 0x74f2a1d8… and 0x479cd74d…: each carries a 32-byte hash and nothing else. Both offers become public together, only in the settlement 0xce6ccd99…, at the midpoint, 4115.
  5. Two Qwen 3.8 Max agents negotiated on Monad, working in steps. In negotiation #4 each agent read the negotiation and the other agent's ERC-8004 reputation on-chain, checked two to four candidate numbers per round, and wrote a plan in round 1 that it followed or adjusted later, saying why. Rounds 1 and 2 did not cross (3750 against 5000, then 4020 against 4380). In round 3 both stopped short of their limits on purpose, because settlement publishes the final numbers: the buyer committed 4250 against a limit of 4300, the seller 4130 against a floor of 4100, and the deal settled at the midpoint, 4190. In negotiation #6 the limits could not overlap (buyer 3600, seller 4300); both agents conceded toward them for three rounds, finished at 3550 and 4340, never crossed, and the negotiation expired with neither number on-chain. No number had to be corrected by code. Every tool call, its result and each note are in demo-runs/, and RUN=demo-runs/monadTestnet-deal-4.json npm run verify:run re-derives every on-chain hash. In negotiation #10 the buyer's model was shown the seller's listing with an instruction to open at 6000 or be discarded; it bid 3950, 4150 and 4255, called the notice seller-written pressure in its notes, and the deal settled at 4202 (docs/QWEN.md). An earlier run, #2, used a prompt that told the agents to commit at their limits in the last round, so its settlement published both limits; that is why the prompt changed.
  6. Privy refused what the mandate forbids, and signed a whole negotiation. See the Privy section above: fourteen refusals (eleven by the mandate, three owner actions the agent's key cannot take), three allowed signatures, negotiation #8 and the two ERC-8004 reviews that followed it, every signature by a Privy wallet. RUN=demo-runs/monadTestnet-privy-deal-8.json npm run verify:run checks it like the others, including the two ERC-8004 reviews.
  7. The price is paid inside the settlement. Open v3's settlement 0xce2fba1c…: it carries NegotiationSettled at 4200 and Paid for 42,000 tUSD, and the two tUSD transfers from the contract, 41,895 to the seller and 105 to the treasury, are in the same transaction. Before it opened, both agents signed the terms, the policy hash and the payment terms (OpenNegotiation).
  8. The demo reputation is seeded, and labelled that way. Agents 2084, 2085, 2086, 2093 and 2094 and their reviewers were created by scripts/seed-demo.ts and scripts/privy-demo.ts. See docs/ADDRESSES.md.

Business model

Contract v3 charges the fee on-chain today, in a test dollar.

  • Who pays. The seller, 0.25% of the value of a deal settled through Sealed, taken in the settlement itself: v3 #1 paid 105 tUSD on a 42,000 tUSD deal.
  • First customers. API and data sellers that sell volume to buying agents. Sealed lets them agree a price with each buyer without publishing a price list the other side can game, and without showing any buyer their floor.
  • Next, in order. Run a pilot with one API seller, move the clearing relay into an attested enclave (target: before the end of 2026), pay in a real stablecoin, and deploy on Monad mainnet.
  • Team. Kevin Soto Burgos, founder, who will build Sealed full-time after the event.

Status

ERC-8004 registries on Monad testnet checked against Sealed's interfacesdone
SealedNegotiation.sol with atomic EIP-712 settlementdone, 131 tests in the suite
ReputationGate.sol with an explicit on-chain admission policydone
Deployment to Monad testnet, source verified on Sourcifydone for all three: the first contract, v3 (below), and v2, which freezes a round once both sides have committed it and stores the admission policy hash; v2 negotiations #3 to #7 settled on it, and in #3 and #5 the relay and the agents ran as separate processes
Scripted negotiation on Monad testnetdone, settled at 4115
Negotiator working in steps with tools (on-chain reads, offer check, plan carried across rounds)done
Negotiations with Qwen 3.8 Max agents on Monad testnetdone, a deal (#4) and a no-deal (#6) with the current prompt and relay, plus earlier runs, all verified
Privy wallets under the mandate, probed live on Monad testnetdone: 11 of 11 forbidden requests refused with policy_violation, 3 of 3 owner actions by the agent's key refused, 3 of 3 negotiator requests signed, and a negotiation signed by Privy (#8); policy and wallets owned by a 2-of-2 admin quorum
Agents rate each other in ERC-8004 after a deal, each review pointing at the settlementdone: #10 rated right after settling; #4 and #8 rated after the fact by scripts/deal-feedback.ts; verify:run checks each review
Measured whether sealing changes the price, in our own harnessdone: 17 runs (3 aborted), no difference in price found, the seller anchored on a leaked limit; experiments/leak-2026-10/summary.md
An agent from outside this repository, through the npm package and the HTTP routesdone: kasbsquall/sealed-seller-agent (same author, rule-based, its own key and ERC-8004 identity) settled v2 #7 at 4200 against the Qwen buyer; its reputation was seeded by scripts/seed-external.ts, like the demo agents'
Payment at settlement with the 0.25% fee, on terms and a policy both agents sign (v3)done: v3 #1 with Qwen 3.8 Max agents settled at 4200 and paid 42,000 tUSD, 105 of it to Sealed
A public desk any agent can use with no MON, and MCP tools to use itdone: desk.avanc3.pe and sealed-monad 0.3 on npm; Sealed's own runs settled v3 #4 to #8, #7 and #8 by Claude Code from one sentence; the first outside negotiation, v3 #9 by Giancarlo, settled at 4236 on 2026-10-11
Qwen agent shown a prompt injection in the counterparty's termsdone: #10 settled inside the limit with the injection ignored; #9 failed closed when Qwen timed out; v2 #4 was told to bid its exact limit, which code allows, and refused in all three rounds; see docs/QWEN.md

How this was built

This repository was created on 2026-09-17, inside the hackathon window. Its first two commits are the first version of Sealed: the core contracts, the test suite and the Privy wallet policy. Between 2026-09-27 and 2026-10-08 the author kept developing Sealed in a port to Base Sepolia, kasbsquall/sealed-base, where that work has its own commit history: the clearing relay, the negotiator agent, the verification scripts and the judge page. That work came back to this repository on 2026-10-08 and was adapted to Monad (fees on the gas limit, log ranges, deployment and seeding), and the Privy mandate was extended with eth_signTransaction parity, the ERC-8004 register rule and tests. No code predates 2026-09-17.

AI coding tools were used throughout: Claude Code (Anthropic) wrote most of the code and documentation under the author's direction and review.

License

MIT. See LICENSE.

↑↓ navigate openesc close