Skip to content

About

Pay a person or an AI agent for finished work on NEAR. Each job's money sits in its own account whose only key can deliver. Live on mainnet.

Topics

Resources

Stars

1 star

Watchers

1 watching

Forks

Repository files navigation

Clausehold

Pay a person or an agent for finished work, on NEAR.

The money is held under a clause, and the clause is the only key that moves it. Each job gets its own NEAR account with one key, the worker's, which can do exactly one thing: deliver. Delivery pays the worker in the same transaction. If the deadline passes first, the money goes back to whoever paid.

Open the app · Watch the demo · Judges: five-minute path · Threat model · Contract ABI

Clausehold demo video, 3 minutes 20

The demo: one job from invite to payout on the live site, an agent hiring an agent over MCP, 15 refused attacks, and the first clause on mainnet.

Built solo for NEAR HACKATHON, November 2026.

At a glance

What it is Escrow with no escrow agent: a per-job NEAR account whose only key can deliver, and whose code can only pay the worker or refund the buyer
Live on mainnet Factory clausehold.near. Clause r-0.clausehold.near paid 2 real USDC to angelraph.near, then closed itself
Live on testnet clausehold.vercel.app with free test tokens, factory clausehold.testnet owned by a Sputnik DAO
For workers No wallet, no gas. A passkey in the browser is the only key the clause accepts; the desk relays the signed delivery
For agents An MCP server with 7 tools. One agent hires another, funds the job, and pays only for delivered work
Earned standing Every paid job is a line in the worker's public record. New workers are capped at 10; one paid job lifts that to 25, enforced by the contract
Proof npm run judge: 25 checks on testnet, 15 of them attempts to cheat, each refused with its reason code
Tracks agents · realworld · social · security

Contents

The idea in one minute

A studio, a DAO or an AI agent wants to pay someone for a piece of work: a 30 second cut, a translation, a bug fix. Today it either pays first and hopes, or the worker delivers first and hopes. An escrow service fixes that by holding the money, which means trusting the escrow service, and paying its cut. Agents hiring agents have nobody to trust at all.

who holds the money what can go wrong
Pay first the worker the work never comes
Work first the buyer the money never comes
Escrow service a company it freezes, fails, or takes a cut
Clausehold a NEAR account made for this one job nothing outside its code: pay on delivery, refund at the deadline

That account has no owner key. Its code allows exactly three exits: to the payee on delivery, back to the buyer on expiry, or back to the buyer after a dispute is settled. Nobody, including us, can take it anywhere else.

Live on mainnet

The factory is deployed at clausehold.near with a real mandate: native USDC only, at most 5 USDC per clause, new payees at most 3, and only clausehold.near may write clauses. One real job ran end to end with real USDC:

step transaction
deploy the factory 57Ujmi4P…
upload the clause code (same code as testnet, sha256 a765271079ca…) 5ZXubHJ1…
set the mandate hU8Tq2Ej…
write r-0.clausehold.near for 2 USDC to angelraph.near JDosYTk7…
fund it with 2 USDC H7frpW7B…
the worker key signs the delivery, clausehold.near relays it; released, angelraph.near +2 USDC FCJwGQWG…
close: the account deletes itself, 1.986 NEAR back to the buyer 8bKWE8is…

Funding from another chain goes through NEAR Intents. A dry 1Click quote for USDC on Base delivering exactly 2 USDC on NEAR asked for 2.0206 USDC and estimated 35 seconds, with refunds set to land on NEAR. npx tsx scripts/mainnet-proof.ts --payee <account> --dry prints the costs of a run without sending anything.

Product tour

Write a clause. The buyer names the payee, the amount, the deadline and an optional acceptance check. The desk checks it against the treasury's rules before anyone signs.

Writing a clause

Accept as the worker. The invite page makes a passkey that becomes the only key the clause will accept. The worker can run the acceptance check before starting.

The worker's invite page

Funded, then paid. The clause page follows the job on chain, from written to funded to released.

A funded clause

Earned standing. Every account's record of paid jobs, read from the factory, with the limit it has earned.

Standing

How it works

sequenceDiagram
    participant B as Buyer (person, DAO or agent)
    participant D as Desk
    participant F as Factory clausehold.near
    participant C as Clause r-n.clausehold.near
    participant W as Worker
    B->>D: propose a job (payee, amount, deadline, check)
    D-->>B: PASS against the mandate, invite link
    W->>D: accept: browser makes the job key (public half only)
    B->>F: create_clause (checked again on chain)
    F->>C: create account, deploy code, add the one worker key
    B->>C: ft_transfer_call (exact amount)
    W->>D: signed submit_delivery (meta-transaction)
    D->>C: relay it, desk pays the gas
    C->>W: pay the worker in the same transaction
    C->>F: report released (worker's standing +1)
    Note over C: past the deadline instead: anyone calls expire, money back to the buyer
    B->>C: close: account deletes itself, storage NEAR back to the buyer
Loading
flowchart LR
    O[Open] -->|ft_transfer_call| Fu[Funded]
    Fu -->|worker key delivers| R[Released: worker paid]
    Fu -->|deadline passes, anyone calls expire| X[Returned: buyer refunded]
    Fu -->|buyer freezes in review window| Z[Frozen: buyer and DAO co-signer decide]
    R --> Cl[Closed: account deleted, deposit back]
    X --> Cl
Loading

Try it in five minutes

Everything below is on testnet, so it is free.

1. Get a wallet and test tokens. Open https://clausehold.vercel.app/faucet.

  • Connect any NEAR wallet (HOT, Meteor, MyNearWallet, Intear and others are listed).
  • Get test NEAR from the faucet link on that page.
  • Click Get 100 mUSD. You should see your mUSD balance go to 100. mUSD is our test dollar token.

2. Write a clause. Go to Write a clause.

  • Payee: any testnet account you control, or clausehold.testnet.
  • Amount: 2. Expires after: 30. Review window: 0.
  • Click Check and open an invite. You should see Cleared, every mandate rule listed with a tick, and an invite link.

3. Accept as the payee. Open the invite link in a new tab of the same browser and click Accept with a passkey. Your browser makes a key. It is the only key the clause will ever accept, and it can do one thing: submit a delivery. The page shows your new key and says it is waiting for the buyer to write the clause.

4. Write it on chain. Back on the clause page, click Write the clause and approve it in your wallet. You should see a new account r-<n>.clausehold.testnet. Open it on the explorer: it has exactly one access key, a function-call key limited to submit_delivery.

5. Fund it. Click Fund 2.00 mUSD and approve. The clause now holds 2 mUSD and shows Funded.

6. Deliver. On the invite tab, attach any file or type a note and click Submit delivery. There is no wallet prompt and no gas to pay: the passkey signs, and the desk relays it. You should see Paid, and the payee's mUSD goes up by 2.

7. Check the record. Open the Desk. The clause shows Released. Its receipt page and the payee's standing page read straight from the contract.

To see a refusal, go back to step 2 and ask for 12. The clause is refused before anyone signs, and the reason names the rule: a payee who has never been paid may be offered at most 10. Ask for 40 and the per-clause cap of 25 refuses it too. The same rules run inside the factory contract, so calling it directly fails the same way.

What it looks like on chain

A real run from the website with a browser wallet, clause r-2.clausehold.testnet:

step transaction
buyer's wallet calls create_clause; the factory creates r-2, deploys its code and adds the one worker key 93YxwwVK…
buyer's wallet sends 2 mUSD with ft_transfer_call; the clause keeps it and turns Funded 14EjWX8W…
the payee's key signs submit_delivery as a meta-transaction, the desk relays it, the clause pays the payee and reports to the factory, all in one transaction HxM4eEim…

Clause r-3 went the same way and was then closed: the account deleted itself and its 1.99 NEAR of storage went back to the buyer in 2smpAzwS…. Its receipt now comes from the factory's record.

And one that ran out of time, clause r-0: after its deadline anyone may call expire, and the 2 mUSD went back to the buyer in Eu2Kpjyg….

Why NEAR

Each of these does real work in the design. Take one away and the product stops working.

  • Sub-accounts. Every job gets its own account, r-<n>.clausehold.testnet. Its balance is the job's money and nothing else, so it is easy to audit and impossible to mix up with other jobs.
  • Function-call access keys. The only key on a clause account is the payee's key, limited to calling submit_delivery on that one account with no deposit. It cannot transfer, cannot call anything else, and there is no full-access key at all.
  • Meta-transactions (NEP-366). The payee never needs a wallet or NEAR for gas. Their key signs a delegate action and the desk relays it. The contract checks only that the call came from the clause account itself, which is true only when the worker's key signed it.
  • Fungible tokens (NEP-141). Funding is ft_transfer_call; ft_on_transfer keeps the tokens while the clause is open and refunds anything extra. Payouts register the receiver's storage first, then transfer, and only change state in the final callback, so a failed payout can simply be retried.
  • Events (NEP-297). Every step logs a clausehold event. The desk mirrors them into a hash-chained log, but the chain is the record.
  • NEAR Intents (mainnet). A buyer can fund a clause from another chain. The 1Click quote is exact-output, so the clause receives exactly its amount on NEAR, and any refund lands on NEAR too.
  • Sputnik DAO. The factory belongs to a DAO. A treasury votes once on the rule, not on every invoice, and the same DAO settles disputes as co-signer.
  • NEAR AI Cloud. Turns a plain-English spending rule into a mandate and writes a short note about each delivery. It never decides whether money moves.

The mandate

A treasury sets its rules once, and every clause is checked against them twice: by the desk before anyone signs, so the refusal can name the rule, and by the factory contract on chain, which is the one that counts. The testnet mandate says:

Stablecoins only. No clause above $25. Anything above $15 needs a second person. At most $50 to any one payee a day and $200 in total a day. A payee who has never delivered gets at most $10. A clause may stay open for at most a week.

The factory reserves each clause's amount against the daily caps when it is written. A returned clause gives the reservation back; a released one keeps it counted.

Acceptance checks

The buyer can attach a test that every delivery must pass before the desk relays it:

  • a public https page must contain some text (the site is live, the post is up)
  • a GitHub pull request must be merged (the fix landed)
  • the delivery must be an exact file (its SHA-256 is agreed in advance)

The payee sees the check on the invite page and can run it before delivering. A delivery that fails is refused before anything is sent, with the reason (The acceptance check failed: ...), so through the desk it is never paid. The contract itself still only checks who delivered, so a buyer who wants a hard stop also sets a review window and can freeze the clause for the DAO co-signer to decide. Agents attach the same checks through the MCP clausehold_open_job tool.

Limits are earned

A payee nobody has paid yet can be offered at most the new-payee ceiling (10 on testnet). Once one clause is released to them, the factory lifts that to the full per-clause cap (25). The factory reads this from its own record of released clauses when a clause is written, so it cannot be skipped, and the desk reads the same record before any wallet prompt. Ask for 12 for a brand-new account and the desk refuses it, naming newPayeeCeiling. Ask for 12 for clausehold.testnet, which has been paid through many released clauses, and it passes.

Every account's record is public: the Standing page ranks payees by released clauses, and each account page shows the limit it has earned and a badge it can embed anywhere, drawn from the chain:

[![Clausehold standing](https://clausehold-api-testnet.vercel.app/standing/clausehold.testnet/badge.svg)](https://clausehold.vercel.app/s/clausehold.testnet)

Owned by a DAO

On testnet the factory belongs to a Sputnik DAO, clausehold-council.sputnikv2.testnet. The rule changes only when the DAO votes for a proposal that calls set_mandate. Disputes on new clauses are settled with the DAO as co-signer.

step transaction
create the DAO: one council member, a 1-of-1 vote Faow7mFx…
the factory's owner hands it to the DAO with set_owner GmxPAgH6…
proposal 0: make the DAO the co-signer, by calling the factory's set_mandate 5zRuj9Nb…
the council approves it, and the DAO calls the factory FwFoGeE3…

The Mandate page reads the owner and the rule straight from the factory. npm run dao:testnet repeats these steps for another DAO; each step checks the chain first and skips what is already done.

Run it yourself

You need Node 22 or later.

npm install
npm run dev -- init
npm run dev -- clause editor.testnet 2 --buyer studio-agent.testnet --terms "Cut intro.mp4 to 30 seconds"

You should see Verdict: PASS, ten rules ticked, and CLEARED. Change 2 to 12 and you get VETO from newPayeeCeiling. Change it to 40 and maxPerClause refuses it as well.

npm test

To run the desk and the website locally, start the API, then the site in a second terminal, and open http://localhost:5173:

npm run dev -- serve
cd web && npm install && npm run dev

The end-to-end script runs one full clause against the hosted testnet desk: invite, key, writing, funding, a gasless delivery and the release, then checks the payee's balance. It needs a funded testnet account whose key is in ~/.near-credentials/testnet (set CLAUSEHOLD_E2E_BUYER). Each run stores one clause account on chain, which costs about 2 test NEAR.

npm run e2e:testnet

The contracts build and test separately; see contracts/README.md and the full interface in contracts/ABI.md.

For agents

Hire another agent from Claude Code, Cursor or any MCP client

Clausehold ships an MCP server, so an AI agent can hire another agent or a person and pay only for finished work. The same server runs on both sides of a job, each with its own NEAR account:

claude mcp add clausehold -e CLAUSEHOLD_AGENT_ACCOUNT=you.testnet -- npx tsx src/mcp/server.ts

Run that from the repository after npm install. The account's key is read from ~/.near-credentials/testnet/you.testnet.json (or set CLAUSEHOLD_AGENT_PRIVATE_KEY).

tool side what it does
clausehold_check_payee buyer the payee's record from the factory, and the largest job it has earned
clausehold_open_job buyer checks the job against the mandate before anything is signed; returns an invite
clausehold_accept_job worker makes the only key the clause will accept, and binds it; the secret stays on that machine
clausehold_write_and_fund buyer the buyer's own key writes the clause and sends exactly the job amount
clausehold_deliver worker signs the delivery as a meta-transaction; the desk pays the gas, and the clause pays the worker at once
clausehold_status either where the job stands, with an explorer link
clausehold_close either deletes the settled clause; its storage NEAR goes back to the buyer

See it work without an AI client: npm run mcp:demo starts two copies of the server, a buyer agent and a worker agent, and runs one job end to end on testnet. A real run, clause r-4.clausehold.testnet:

step transaction
the buyer agent writes the clause HJJtDqaj…
the buyer agent funds it with 2 mUSD 4LaVt8Gk…
the worker agent delivers with no gas and is paid BSGXQQyc…
the buyer agent closes it and gets the deposit back vR9FWZBU…

The worker's standing went from 3 to 4 released clauses in the same run.

Over HTTP

An agent can also propose work over HTTP. The desk answers PASS, VETO or ESCALATE before anything is signed.

route what it does
POST /invites propose a clause; if it clears, get an invite link for the payee
GET /invites/:id the invite and its status
POST /invites/:id/key the payee's browser posts its public key
GET /invites/:id/create the exact create_clause call for the buyer's wallet to sign
POST /invites/:id/written report the new clause; the desk checks it on chain before linking it
POST /relay submit the payee's signed delivery; the desk pays the gas
GET /desk/clauses every clause and where it stands
GET /mandate the active mandate
GET /standing/:account an account's record from the factory, and the largest clause it has earned
GET /standing/:account/badge.svg an embeddable badge drawn from that record
GET /leaderboard payees ranked by released clauses, from every factory receipt

The hosted testnet desk is https://clausehold-api-testnet.vercel.app. The routes above are open, because the website calls them without a key. /relay only ever submits a delivery that the payee's own key signed. Routes that decide something (approve, halt) or make the desk sign any other call need an API key. More examples are on the For agents page.

What can go wrong, and what happens

  • The payee loses their key. Nothing can be delivered, so after the deadline anyone may call expire and the money goes back to the buyer.
  • The desk goes down or turns hostile. It holds no money and no key to any clause. The worst it can do is refuse to relay a delivery, and then the deadline returns the money. A payee can also send their signed delivery through any other relayer.
  • Someone calls submit_delivery directly. It fails. Only a call from the clause account itself passes, and only the worker's function-call key can make one.
  • The work is wrong. With a review window above zero, the buyer may freeze the clause after delivery. Then it moves only when the buyer and the mandate's co-signer agree.
  • Tokens arrive late or twice. After a clause settles, sync_funding sends any leftover balance to the buyer.
  • A clause asks for too much. The factory panics with the rule's name, and nothing is created.

The full threat model, with the test behind each control and the known weaknesses, is in docs/THREAT_MODEL.md.

Honest limits

  • Writing a clause stores a small contract on chain, which needs about 1.97 NEAR from the buyer while the clause runs. It is a deposit, not a fee: once the clause settles, anyone can close it, and the account deletes itself and sends that NEAR back to the buyer. The buyer still needs the 2 NEAR up front.
  • Funding through NEAR Intents is mainnet only, because 1Click has no testnet. On testnet, clauses are funded directly with mUSD. On mainnet the one clause so far was funded directly with USDC; the Intents path was checked with a dry quote, not a paid cross-chain transfer.
  • The mainnet factory is owned by clausehold.near itself for now, not yet by a DAO.
  • The testnet DAO has a single council member, so one vote passes a proposal. That proves the wiring; a real treasury would add its members and raise the threshold.

What is in this repository

path what it is
contracts/ Rust: the factory, the per-job clause contract, a test token, and sandbox tests
src/ the desk: mandate checks, invites, the relayer, the NEAR and Intents clients, the audit log, the CLI
web/ the website: desk, clause pages, the payee's invite page, wallet connect, faucet
scripts/e2e-testnet.ts one full clause against the hosted testnet desk
tests/ unit and API tests for the desk

Prior work, disclosed

This repository previously held CHARTER, a policy layer for AI trading agents on Binance, built in September 2026 for a different hackathon. Everything up to the tag pre-hackathon is that baseline; everything after it is Clausehold, built from November 1 for NEAR HACKATHON. The audit log, the rule-engine shape, the approval flow and the agent keys were kept and rewritten around clauses. All Binance and trading code was removed. The full diff: https://github.com/angelraph/clausehold/compare/pre-hackathon...master

Code in this repository was written with help from Claude, an AI assistant, and reviewed and run by the author.


License

MIT. Built by angelraph for NEAR HACKATHON, November 2026.

About

Pay a person or an AI agent for finished work on NEAR. Each job's money sits in its own account whose only key can deliver. Live on mainnet.

Topics

Resources

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages