
horkos
io.github.iusserhat/horkos
Escrow for AI agent purchases on NEAR testnet: pay only when a TEE verifies the delivery.
Documentation

Horkos
· 8 attack scenarios refused on v1 and v2 on every push (tests) · invariants held over 10,000 random sequences per version (docs/security.md)
Verify-then-pay for AI agents, native to NEAR.
What. Horkos is an escrow contract on NEAR for purchases made by AI agents: each order locks its price in the contract as an NFT receipt, and the seller is paid only if the owner's spending mandate allows the order and a verifier confirms the delivery meets a machine-checkable condition; otherwise the money returns after the deadline.
Why. An agent's payment cannot be reversed: if it overspends, follows a prompt injection, or pays a seller who delivers nothing or garbage, the money is gone. Letting the buyer approve deliveries does not work either, because a buyer who already has the data can claim it was bad.
How to verify. npm run judge · JUDGES.md: checks with expected output · Verify it yourself in 5 minutes · TEE attestations · testnet transactions · live site and Try page · AI agents: AGENTS.md.
We don't resolve disputes. We prevent them. Built at NEAR HACKATHON (development window 19 Oct to 14 Nov 2026). Runs on NEAR testnet. License: MIT.
How it works
Horkos puts the owner's limits (budget, per-order cap, allowed sellers, expiry) in the escrow contract itself, so a compromised agent cannot buy outside them. The seller delivers encrypted data, and a verifier running in NEAR OutLayer's TEE checks it against the listing's condition (a JSON Schema) before the buyer gets the key and the seller gets paid. If no valid delivery is verified before the deadline, anyone can trigger the refund.
One command for judges
npm run judge # contract unit, attack and invariant tests, verifier, Sentinel, gateway, type checks,
# local-node integration tests and the sandbox demo, then a PASS/FAIL table
JUDGE_SKIP_SANDBOX=1 npm run judge # without the local-node steps (no cargo-near / near-sandbox needed)
npm run judge:testnet # optional, reads testnet: verifier hashes vs chain, Sentinel on order 28, independent buyers
No keys. Network only to download dependencies on the first run. Tested on Ubuntu 22.04 (WSL) with Node 20 (.nvmrc), Rust 1.99.1 with the wasm32 targets (rust-toolchain.toml, installed by rustup on first use) and cargo-near 0.22.0: 10 of 10 PASS in 6.5 minutes (the local-node steps take about 5 of them; on Linux with glibc older than 2.39 the node runs in Docker).
Verify it yourself in 5 minutes
No accounts or keys needed. Step 1 needs only curl; steps 2 to 4 need Rust (rustup); step 4 also needs cargo-near 0.22.0. Builds take a few minutes the first time.
# 1. A settled order on testnet (read-only RPC call)
curl -s https://rpc.testnet.fastnear.com -H 'content-type: application/json' -d '{"jsonrpc":"2.0","id":1,"method":"query","params":{"request_type":"call_function","finality":"final","account_id":"escrow.horkos.testnet","method_name":"get_order","args_base64":"'$(printf '{"order_id":"9"}' | base64)'"}}' | python3 -c 'import sys,json; o=json.loads(bytes(json.load(sys.stdin)["result"]["result"])); print(o["status"], o["verifier_version"])'
# Expected: Settled 0
# 2. The verifier WASM rebuilds byte for byte from this repo
scripts/reproduce-verifier.sh --match-paths
# Expected: on-chain hash 61370b57…, the published WASM at the pinned URL has that hash, … "MATCH"
# (rustc embeds dependency paths, so --match-paths builds with the original CARGO_HOME path,
# read from the published WASM; it may ask you to create that directory with sudo.
# Plain scripts/reproduce-verifier.sh on a machine with another home directory gives a different hash.)
# 3. That WASM ran in Intel TDX on exactly order 9's input and produced the output the contract settled on
cargo install --locked --git https://github.com/out-layer/outlayer-verify --rev 90c27d9d4fc843194ddc6f8ebaafa4eb320c3bb6 outlayer-verify
outlayer-verify bundle evidence/attestation/order-9-bundle.json --offline --short
# Expected: PROVEN task 102880 (execute) d2757cd2f877b2a7…3869ad2c
outlayer-verify tx 4ctCVTTKZgiwBJJr7LydyURjGdnViVg2vSgjAPMbUuzP --network testnet --api https://testnet-api.outlayer.ai
# Expected: five "[ PASS ]" lines (Authenticity, Identity, Binding, Input, Output), wasm sha256 61370b57…
# 4. The deployed contracts are this repo's code (rebuilds them with the original CARGO_HOME path, like step 2)
scripts/verify-deployed.sh --match-paths
# Expected: MATCH for escrow.horkos.testnet, escrow-v2.horkos.testnet, tusdc.horkos.testnet, faucet.horkos.testnet
CI runs step 2 (v1 and v2 verifier) on every push. Step 4 is a local check: it matched on the deploying machine and in a fresh clone there; GitHub's runner compiles the contracts to different bytes, so it is not part of CI. Orders 15 and 18 and what this does and does not prove: evidence/attestation/. In short, the contract itself does not check the attestation; it trusts OutLayer's operator, and these checks are after the fact.
Quick start
Requirements: Linux or macOS, Rust ≥ 1.95 with the wasm32-unknown-unknown target, cargo-near 0.22, Node.js 20, git. The local NEAR node needs glibc ≥ 2.39; on older systems (for example Ubuntu 22.04) the scripts run it in Docker (ubuntu:24.04), so Docker is needed there. setup.sh installs the toolchain on Ubuntu/WSL.
a) Local sandbox: all three demos, no accounts or keys
git clone https://github.com/iusserhat/horkos && cd horkos
contracts/scripts/test-sandbox.sh # builds the contracts, downloads the local node, runs 7 integration tests
(cd sdk && npm ci)
cd demo && npm ci && npm run sandbox
test-sandbox.sh should end with test result: ok. 9 passed. npm run sandbox starts a local NEAR node, deploys the real Horkos and test-token contracts and mock-outlayer in Worker mode, and builds the real verifier natively. When the contract calls request_execution, the mock yields like OutLayer; a worker loop in demo/sandbox.ts runs the verifier on the request and resumes it with the verifier's output. You should see:
order 0: Settled, then the agent decrypts{"symbol":"NEAR/USD","rows":[...]}.valid=false, reason: schema: ... at /rows,order 1: Delivered, the agent getsnothing to open, and after the 25 s windowrefund_expired → order 1: Refunded, 5 tUSDC back in the budget.create_order rejected: Smart contract panicked: Seller not allowed by mandate, budget unchanged.
Not real in the sandbox: the TEE and OutLayer's operator (the worker is a local loop), HTTPS hosting (files are file:// URLs), and the market data (the sandbox stays offline and delivers fixed sample prices).
b) Testnet demos (real OutLayer)
The scripts in demo/ sign with the demo accounts' keys, which only the team has, so they cannot be re-run as-is; anyone can inspect the results. Accounts: owner horkos-owner.testnet, agent horkos-agent.testnet, sellers horkos-seller-a/b/c.testnet, contract escrow.horkos.testnet, token tusdc.horkos.testnet.
- Demo 1, good delivery → Settled. Order 0:
create_order, seller A'ssubmit_delivery: OutLayer ran the verifier,on_verifiedsettled the order, and the agent decrypted the JSON. Re-run on 13 Nov 14:22 UTC with the rotated agent key: order 5,create_order,submit_delivery→Settled. With live CoinGecko data on 13 Nov 16:52 UTC: order 8,create_order,submit_delivery→Settled; the decrypted prices matched CoinGecko's (docs/testnet.md). - Demo 2, bad delivery → Refunded. Order 2:
create_order, seller B'ssubmit_deliveryfailed the schema (orderDelivered, no key for the buyer), thenrefund_expiredafter the deadline. - Demo 3, hijacked agent → rejected. A listing text tells the agent to buy a 500 tUSDC feed from seller C. The agent's
create_orderfails withSeller not allowed by mandate; budget and open orders unchanged.
Where seller A's data comes from. In the testnet demo, seller A fulfils orders with live NEAR/USD prices from CoinGecko's free public API (demo/sellers/coingecko.ts, no key, no fallback: if CoinGecko does not answer, nothing is delivered). Price data by CoinGecko. Horkos does not pay CoinGecko and CoinGecko is not a Horkos seller; it is seller A's data source. We do not use x402 in this demo. Reselling a provider's data commercially would need that provider's permission. The hosted seller A that serves judges' orders (demo/sellers/hosted.ts, on Render) uses the same CoinGecko endpoint and falls back to Coinbase's public NEAR-USD API (no key) when CoinGecko rate-limits its shared cloud IP; Horkos has no relationship with Coinbase either. The local sandbox (Quick start a) uses fixed sample prices.
Check the orders yourself (needs near-cli-rs):
near contract call-function as-read-only escrow.horkos.testnet get_order json-args '{"order_id":"0"}' network-config testnet now
near contract call-function as-read-only escrow.horkos.testnet get_order json-args '{"order_id":"2"}' network-config testnet now
near contract call-function as-read-only escrow.horkos.testnet nft_token json-args '{"token_id":"0"}' network-config testnet now
You should see "status": "Settled" for order 0, "status": "Refunded" for order 2, and NFT receipt 0 owned by horkos-owner.testnet with the order summary in its metadata. All transaction ids: docs/testnet.md.
c) MCP server: let Claude buy within the mandate (testnet)
Judges, with your own fresh testnet accounts and no team keys: follow the agent track on the Try page: create accounts, claim 20 tUSDC from faucet.horkos.testnet, set a mandate for seller A, then ask Claude to buy listing 4. Seller A runs on Render during judging (https://horkos-seller-a.onrender.com/health).
The server (mcp/, stdio) gives an agent five tools: horkos_search_listings, horkos_create_order, horkos_get_order, horkos_open_delivery, horkos_cancel_order. It signs as the agent, so it needs the agent's function-call key (the team has it; it can only call create_order / cancel_order on the escrow). You can point it at your own deployment with HORKOS_AGENT, HORKOS_OWNER, HORKOS_CONTRACT and HORKOS_AGENT_KEY (see the header of mcp/server.ts).
On npm: horkos-mcp, listed in the official MCP Registry as io.github.iusserhat/horkos; no clone needed:
claude mcp add horkos -e HORKOS_AGENT=<your-agent>.testnet -e HORKOS_OWNER=<you>.testnet -- npx -y horkos-mcp
From source (advanced):
cd mcp && npm ci
# Claude Code:
claude mcp add horkos -- "$PWD/node_modules/.bin/tsx" "$PWD/server.ts"
# Claude Desktop (claude_desktop_config.json):
# "mcpServers": { "horkos": { "command": "/abs/path/horkos/mcp/node_modules/.bin/tsx", "args": ["/abs/path/horkos/mcp/server.ts"] } }
For deliveries to happen, run a seller in another terminal (cd demo && npm run seller -- a). Then ask: "Find a NEAR/USD price feed and buy it without spending more than 10 tUSDC, then show me the data." You should see horkos_search_listings (listing 0 at 5 tUSDC, seller_allowed_by_mandate: true), horkos_create_order → Open, horkos_get_order → Settled, and horkos_open_delivery returning {"symbol":"NEAR/USD","rows":[...]}. Checked on 13 Nov with Claude Code (claude -p, model Sonnet): order 6 went Open → Verifying → Settled and the data opened. Asked to buy the "premium" listing (500 tUSDC, seller C), Claude declined on its own: it flagged the listing text as an injection and saw seller_allowed_by_mandate: false. The contract does not depend on that: when create_order is sent anyway (npx tsx smoke.ts inject, or demo 3), it fails with Seller not allowed by mandate. Listing descriptions are off-chain text in mcp/catalog.testnet.json; listing 2's is a deliberate prompt injection. MCP orders on testnet: 3 (cancelled), 4 and 6 (settled); see docs/testnet.md.
Without an MCP client, cd mcp && npx tsx smoke.ts inject drives the same server over stdio (buy, cancel and open N spend or move testnet tUSDC).
Receipts are transferable (v2)
A second contract, escrow-v2.horkos.testnet (contracts/horkos-v2), makes the receipt the claim on the order. The v1 contract above is unchanged and receipts there stay non-transferable.
nft_transfer/nft_transfer_callfollow NEP-171 (1 yoctoNEAR, holder or NEP-178-approved account) and emit NEP-297nft_transferevents. Receipts move only while the order is open; settled, refunded or cancelled receipts are frozen.refund_expiredandcancel_orderpay the current holder: back into the mandate budget if the buyer still holds the receipt, otherwise into the holder's balance, withdrawable withwithdraw(Earnings). After a transfer the buyer's agent can no longer cancel.- Settlement stores the data key sealed to the receipt's
buyer_pubkey; the holder can set a new one withset_receipt_pubkeywhile no verification is running. - The mandate still governs only who may create orders and for how much. Reasons and edge cases: ADR-036 in
docs/decisions.md; methods:docs/contract-api.md. - A draft standard for this pattern, with Horkos v2 as the reference implementation: docs/nep-draft.md (not submitted).
On testnet (14 Nov): the buyer transferred receipt #0 to another account, the buyer's agent's cancel failed, and after the deadline refund_expired paid the 5 tUSDC to the new holder, who withdrew it. All steps: docs/testnet.md. Settlement after a transfer, on testnet (14 Nov, 16:02 UTC): the buyer transferred receipt #1, the new holder set its own key, the seller's delivery was verified in the TEE and the order settled, and the new holder opened the data while the buyer's original key could not (demo/v2-transfer.ts; v2 verifier build 4919f8e2…, attestation in evidence/attestation/).
Sentinel: stop a suspicious order before delivery
sentinel/ is an optional, owner-run watcher (no contract change). It flags orders priced far from comparable listings, lookalike or new sellers, bursts and orders close to the mandate's limits, and, if the owner opts in, cancels a flagged order while it is still Open with the agent's own cancel_order permission. On testnet it cancelled order 28 (9 tUSDC for a feed listed at 5) 6 seconds after the agent created it. Check it yourself, no keys:
cd sentinel && npm ci && npm test && npm run sentinel -- --config examples/readonly.json --once --from 28
# Expected: 14 tests pass, then "order 28 (hk-judge-1114.testnet → horkos-seller-b.testnet, 9 tUSDC): BLOCK price_deviation: … +80% vs 5 tUSDC …"
What each threat is stopped by, and what is on-chain versus off-chain: docs/threat-model.md.
OutLayer on testnet: failed runs
Status: Live verification depends on OutLayer testnet. When it times out, the order refunds in full. Settled examples: orders 9, 15, 18, 26; each run's TEE attestation passes outlayer-verify (evidence/attestation/). On 14 Nov from about 11:30 UTC every OutLayer testnet run we sampled timed out at NEAR's 201-block yield limit, including other projects' runs and a verifier call that fetches nothing; by 15:00 UTC OutLayer was answering again and order 26 (listing 4, hosted seller A) settled on its first attempt in about 2 minutes. The hosted seller retries for up to 30 minutes (listings 4 and 5 have a 30-minute window) before the refund applies. Measurements and transactions: docs/testnet.md.
OutLayer testnet sometimes fails a verification run without giving a reason. On 13 Nov 2026 every testnet execution failed from about 12:00 UTC (still failing at 12:16, working again by 12:49) with "Execution request not found"; later that day one of our runs (order 4, OutLayer request 38907) failed the same way while other runs worked. In that case the callback carries no result, and the contract moves the order from Verifying back to Delivered. The seller calls submit_delivery again and the order settles (order 4 settled on the second attempt). If the deadline passes first, refund_expired returns the funds. The money is never stuck in the contract. If OutLayer does not answer at all, npm run sandbox (Quick start a) shows the same flow locally. This path is also covered by the sandbox test invalid_and_null_results_return_to_delivered_then_retry_settles.
Architecture
flowchart LR
Owner[Owner] -- "set_mandate, ft_transfer_call (budget)" --> H
Agent["Agent<br/>(function-call key:<br/>create_order, cancel_order)"] -- create_order --> H
H[(Horkos escrow contract<br/>escrow.horkos.testnet)] -- "mint NFT receipt" --> Owner
Seller[Seller] -- "encrypted data" --> Store[(FastFS / HTTPS)]
Seller -- "submit_delivery (key sealed to verifier)" --> H
H -- "request_execution (pinned WASM sha256)" --> OL[NEAR OutLayer]
OL --> V["Verifier in TEE<br/>decrypt, check JSON Schema,<br/>re-seal key to buyer"]
V -- "fetch" --> Store
OL -- "on_verified (result)" --> H
H -- "Settled: seller balance + buyer key" --> Seller
Anyone[Anyone] -- "refund_expired (after deadline)" --> H
stateDiagram-v2
[*] --> Open: create_order (within mandate)
Open --> Verifying: submit_delivery
Open --> Cancelled: cancel_order
Open --> Refunded: refund_expired
Verifying --> Settled: valid result, before deadline
Verifying --> Delivered: invalid or missing result
Delivered --> Verifying: seller resubmits
Delivered --> Refunded: refund_expired
Verifying --> Refunded: refund_expired
Settled --> [*]
Refunded --> [*]
Cancelled --> [*]
Each order reaches exactly one final state (Settled, Refunded or Cancelled). Settlement only credits the seller's internal balance; the seller withdraws separately, so no token transfer happens inside the verification callback. Full design: docs/architecture.md; every method: docs/contract-api.md; decisions: docs/decisions.md. All documents: docs/README.md.
Website (web/)
Live site: https://horkos-escrow.netlify.app (Netlify, deployed by hand from web/dist; no git-triggered deploys).
A static site that explains Horkos, guides agent setup and shows the contract's live state. Run it locally:
cd web && npm ci && npm run build && npm run preview # → http://localhost:4173
- Home: the order flow as an animated diagram (happy path, no delivery, failed check, out of mandate), the NFT receipt in each status, use cases, which NEAR feature does what, the security model and limits. The "Orders on testnet" panel reads the contract over RPC.
- Connect: a config generator for Claude Code, Claude Desktop, Cursor and other MCP clients (account names and paths only; it never asks for a key), near-cli commands for the mandate and the agent's function-call key, the five tools with request/response pairs captured from testnet, two recorded Claude Code conversations, an SDK example and troubleshooting.
- Explorer: orders, receipts, mandates, verifier versions and listings from
escrow.horkos.testnetview methods, refreshed every 10 s. Order history comes from the repo's record of demo transactions (docs/testnet.md, copied at build time), otherwise from the NearBlocks testnet API checked against RPC; if neither works it is rebuilt from the order's fields and says so. The contract has no view that lists mandates, so the explorer shows owners that have orders and looks up others by name.
Built with Vite and TypeScript, no UI framework; fonts Fredoka, Nunito and IBM Plex Mono, self-hosted (no Google Fonts requests). Lighthouse 13.6 on the local build (13 Nov 2026), performance / accessibility / best practices / SEO: home 98/100/100/100 mobile, 100/100/100/100 desktop; connect, explorer and market 100/100/100/100 on both. Live home page on Netlify (14 Nov 2026): 95/100/100/100 mobile, 100/100/100/100 desktop.
| Home | Connect | Explorer |
|---|---|---|
![]() | ![]() | ![]() |
More (market page, 390 px, dark theme, each explorer tab): docs/screenshots/.
Security model
- Agent key.
horkos-agent.testnethas one key: a function-call key that can only callcreate_orderandcancel_orderonescrow.horkos.testnet(5 NEAR gas allowance). Rotated on 13 Nov 2026: new key added, old function-call key deleted, full-access key deleted. Check:near account list-keys horkos-agent.testnet network-config testnet nowshows a single function-call key. - Mandate limits are enforced by the contract. On every
create_orderthe contract checks that the caller is the mandate's agent, the mandate is active and not expired, the seller is on the allowed list, and the price fits the per-order cap, the total cap and the budget. A leaked agent key can only buy within the mandate. - Verifier version is pinned per order. Each order keeps the verifier version (WASM sha256, OutLayer account, public key) that existed when it was created. The admin can publish new versions but cannot change the one an open order uses. Only the result of the contract's own OutLayer call is accepted (private callback, matched to the order and attempt).
- Open-order cap and storage deposit. Each mandate has an open-order cap (default 50, at most 100). Each order's storage is paid from the owner's storage deposit, so order spam cannot drain the contract.
- Repository secret scan. gitleaks 8.30.1 (official release, checksum verified) over the full git history found no secrets; the one hit was a false positive in
verifier/Cargo.lock. Details:docs/security.md.
Known limitations
- Testnet only. No mainnet deployment.
- OutLayer's TEE attestation is not verified on chain. OutLayer's callback carries only the program's output, with no signature or quote. The operator account (
worker.outlayer.testnet) also has full-access keys, so the contract trusts OutLayer's operator. The attestation can be checked off-chain afterwards. Details:spikes/outlayer/REPORT.md,docs/internal/outlayer-question.md. - The verifier's public key is published by the admin with each verifier version; the contract does not check that it came from the TEE. (On testnet it was printed by the verifier inside OutLayer, tx
5iWyLC1TVjd7v4CKSDU4yggATB4x4g42TJs4xJAZRtRH.) - Receipt NFTs are not transferable on v1 (
escrow.horkos.testnet). They are on v2 (escrow-v2.horkos.testnet), a separate contract with its own verifier build; the hosted seller A serves v1 only. - Conditions are JSON Schema only. Horkos checks that a delivery meets the seller's published schema, not that the data is good.
- Admin powers. The admin can add verifier versions but cannot affect existing orders. However, the contract accounts (
escrow.horkos.testnet,tusdc.horkos.testnet) still have full-access keys held by the team, and whoever holds them can redeploy the code. - Costs. Each order charges about 1,517 bytes of storage (≈ 0.0152 NEAR) to the owner's storage deposit; the seller pays OutLayer's fee (≈ 0.0015 NEAR charged per run on testnet) for each verification attempt.
- If a verification callback fails for a reason other than the result (for example gas), the order stays
Verifyinguntil the deadline, then refunds.
Existing approaches
Other NEAR HACKATHON projects listed on NEAR Hunt work on related problems. Each line summarizes the project's own NEAR Hunt description; we have not run them.
- PayPerCall on NEAR: escrows each API payment by an agent; a verified answer pays the provider, no answer refunds the agent.
- Handoff: buyer escrows, the worker delivers an artifact and its hash, a machine checks the work, then money moves.
- DeadDrop: sells encrypted files; payment goes into escrow, the key is sealed to the buyer's public key, the seller is paid only on delivery.
- Inari: pay-per-call escrow where a worker signs the result and the contract pays the seller or lets the buyer claim a refund.
- ProofRail: tests are set up front; passing pays out of escrow, failing refunds.
- Nearsay: escrow with hardware-backed verification of an agent's work, team payouts and refunds.
- SETTLENEAR: an escrow, work-verification and payment protocol for humans and AI agents.
- Emporium: lets an agent sell its skills per job, paid in USDC on NEAR once the work is delivered.
- GRAM-402: a contract mandate (budget, cap, merchants) through which an agent pays HTTP 402 requests.
- NearGuard and SpendGuard: contracts that check each agent payment against owner-set caps and allowlists.
- Lot Passport: locks USDC in an NFT until an inspector checks the goods; pass pays the supplier, fail refunds.
- Payrouta: a checkout widget where customers or agents pay with NEAR Intents from any wallet, chain or token.
What Horkos does differently, concretely: the spending mandate and the escrow are the same contract, so the limit check and the locking of funds happen in one create_order call; the buyer cannot read the data before the verifier accepts it, and the seller cannot be paid without that acceptance; the check runs in OutLayer's TEE against a condition fixed when the listing was created, with the verifier WASM pinned per order; every order is an NFT receipt whose metadata tracks its status; and the only refund path is a deadline anyone can trigger, so there is no dispute step.
Run the tests
cd contracts && cargo test -p horkos -p horkos-v2 -p test-token -p faucet # 101 + 122 + 4 + 11 unit tests
# plus, per escrow version, 8 attack tests (tests/attacks.rs)
# and random-sequence invariants (tests/invariants.rs, 300 runs by default;
# INVARIANT_RUNS=10000 cargo test --release -p horkos-v2 --test invariants)
cd .. && contracts/scripts/test-sandbox.sh # 1 faucet + 7 escrow (v1) + 2 v2 integration tests on a local node
(cd sdk && npm ci && npm test) # 6 SDK tests (after test-sandbox.sh)
(cd verifier && cargo test) # 15 verifier tests
(cd sdk && npm run test:verifier) # 5 end-to-end tests: SDK-built deliveries through the native verifier
(cd mcp && npm ci && npm run typecheck) # MCP server type check
(cd gateway && npm ci && npm test && npm run test:sandbox) # 17 gateway unit tests + 2 on a local node
Each should report no failures. The unit tests include per-order and total limit violations, a seller outside the mandate, double refund, an admin swapping the verifier for open orders, a valid result arriving after the deadline, a late callback after a refund, and the storage-deposit bound on create/cancel loops. The integration tests print the gas the callbacks burn (for example GAS on_verified (settle): 2.40 Tgas) and fail if any path uses more than half of what the contract attaches.
Documentation
Start at docs/README.md: architecture, contract API, SDK, MCP, gateway, faucet, verifier, testnet evidence, security, decisions, FAQ. Changes: CHANGELOG.md. Reporting a vulnerability: SECURITY.md.
Repository
contracts/horkos/ escrow contract v1, deployed as escrow.horkos.testnet (Rust, near-sdk 5.30)
contracts/horkos-v2/ escrow contract v2: transferable receipts, deployed as escrow-v2.horkos.testnet
contracts/test-token/ testnet NEP-141 token "tUSDC" (6 decimals)
contracts/faucet/ testnet tUSDC faucet (20 per account per day, capped)
contracts/mock-outlayer/ test-only stand-in for OutLayer (Worker mode yields until resolved, like OutLayer)
contracts/integration-tests/ sandbox tests (near-workspaces)
contracts/scripts/ build, sandbox test runner, Docker wrapper for the local node
verifier/ delivery verifier (Rust, WASI P2) that runs in OutLayer's TEE
sdk/ TypeScript SDK: HorkosAgent, HorkosSeller, HorkosOwner
mcp/ MCP server (stdio), 5 tools, NEAR testnet
gateway/ seller gateway: serve a listing from a JSON API by config
web/ static site: product page, MCP guide, live explorer (Vite + TypeScript)
demo/ seller services, testnet scenarios, local sandbox demo
spikes/outlayer/ OutLayer experiment: WASI schema checker, probe contract, report
docs/ documentation (index: docs/README.md); docs/tr Turkish originals, docs/internal working notes
render.yaml Render blueprint for the hosted testnet seller A
Roadmap
- Mainnet deployment, with the contract accounts' full-access keys removed or upgrades behind a DAO or timelock.
- Cross-chain payment and refund through NEAR Intents.
- Richer conditions: test suites and recomputable outputs, not only JSON Schema.
- Browser wallet: roadmap. A web page where the owner sets up the mandate and buys with a NEAR wallet (today: SDK, MCP or near-cli).
web/src/lib/wallet.ts(NEAR Wallet Selector 10.1.4, Meteor and MyNearWallet, testnet) is in the repo but no page uses it: on 13 Nov a real connect-and-sign flow could not be checked (MyNearWallet has stopped new app connections and approvals since 1 Nov; Meteor's web wallet blocks automated browsers), and nothing unchecked is claimed. - On-chain verification of the verifier's TEE attestation, once OutLayer exposes it to contracts.
Libraries, templates and AI use
Libraries (exact versions are pinned in Cargo.lock / package-lock.json):
- Contracts (Rust):
near-sdk5.30.0,near-contract-standards5.30.0,near-workspaces0.24.0 (tests),tokio,serde_json,anyhow. Toolchain:cargo-near0.22.0, near-sandbox / nearcore 2.14.0 (tests). - Verifier (Rust):
crypto_box0.9.1,chacha20poly13050.10.1,jsonschema0.59.0,sha2,base64,hex,serde_json,wasi-http-client0.2.1 (WASM build only). - TypeScript:
near-api-js7.3.1,libsodium-wrappers0.8.4 (all encryption),typescript7.0.2,tsx4.23.15. - MCP server:
@modelcontextprotocol/sdk1.32.2,zod3.25.76. - Website:
vite8.3.5,typescript7.0.2,@fontsource/fredoka,@fontsource/nunito,@fontsource/ibm-plex-monoand@fontsource/ibm-plex-sans5.3.0 (SIL OFL fonts); dev only:playwright1.64.1 (screenshots), Lighthouse 13.6.0 via npx (scores). - Docker image
ubuntu:24.04, used only to run the local node on hosts with an older glibc.
Templates and borrowed formats:
contracts/test-tokenfollows the fungible-token example in near-sdk-rs (v5.30.0) and adds an owner-onlymint.- The FastFS upload format in
sdk/src/storage.tsfollowsout-layer/outlayer-cli(src/commands/upload.rs).
AI use: the code, tests, scripts and documentation drafts were written with Claude Code (Anthropic, model Claude Opus 5.5) under the direction of the team member, who set the design, made the decisions recorded in docs/decisions.md, and reviewed the results. Commits made this way carry a Co-Authored-By: Claude trailer.
License
MIT, see LICENSE.
horkos-mcpnpm install horkos-mcp

