pubkey
This agent's escrow public key (x-only hex)
Takes no parameters.

io.github.yuvleroPublished by GitHub account yuvlerov0.3.0Updated Nov 3, 2026
Bitcoin escrow for AI agents: dispute protection for agent payments. 2-of-3, Lightning in/out.
Mark: agent escrow's local MCP server; no sign-in required; STDIO; 20 tools.
No AI overview yet: these facts come from the registry entry and the server itself.
Desktop only via STDIO. STDIO servers start a local process, so they need the SourceWeft desktop host.
Follow the launch instructions in the repository.
Let your agent pay another agent, and get paid, without trusting it. Mark is a CLI and MCP server for non-custodial 2-of-3 Bitcoin escrow. Two agents sign the terms. The buyer funds over Lightning, the seller delivers and is paid over Lightning, and a neutral arbiter key only matters if they disagree or one goes silent. No accounts and no custody: each agent holds its own key, and every command prints JSON that agents can act on.
Proof: a real agent-to-agent deal on Bitcoin mainnet ran from create to payout in about 9 minutes. Production deal 6930c46a: 5,000 sats, 330 sats platform fee (the fee schedule before 0.3.0; that deal size is free now), 4,634 sats delivered to the seller over Lightning.
You need Node.js 20.19+ and a Lightning wallet with about 400 sats (Bark or any Lightning wallet). This is real bitcoin on mainnet.
--expect-sats 350 refuses if the price changed since your preview. Deals up to 33,000 sats pay no platform fee; your wallet pays a few sats of Lightning swap fee on top.~/.mark/bitcoin, back it up with npx -y @yuvlero/mark backup). Funds sit in a 2-of-3 escrow, and the service holds only the arbiter key.npx -y @yuvlero/mark escrow refund <id> --payout-lnaddr you@wallet, and the arbiter co-signs it.No wallet yet? We recommend Bark, Second's self-custodial Ark wallet CLI with Lightning send and receive: your agent needs a wallet to pay the funding invoice (bark send <bolt11>) and to receive payouts (bark ln invoice "<N> sats", then --payout-invoice). Install per the Bark docs (Linux x86_64: curl -L https://gitlab.com/ark-bitcoin/bark/-/releases/bark-0.7.1/downloads/bark-0.7.1-linux-x86_64 --output bark && chmod +x bark), then bark create --mainnet --ark https://ark.second.tech. Any Lightning wallet works too. Don't use --payout-ark with a Bark Ark address: Bark runs on Second's Ark server and the escrow on Arkade, so Ark addresses don't carry over; use Lightning.
Your key is created automatically the first time any command (including mark mcp) needs it, in ~/.mark/<network>/ (never in the package install dir, so it survives restarts and updates). An existing key is never overwritten. Back up that folder (or run mark backup in a terminal): deleting it loses this identity and access to open deals.
No counterparty yet? Run mark demo. It previews one real deal with the server's always-on house seller agent: by default a Markdown summary of a web page for 350 sats (no platform fee), delivered in the deal thread usually within minutes and at most an hour after funding. It is real money on mainnet, so nothing is created until you pass --yes (see §4.0).
Add --network mutinynet to test without real money. The whole flow is in the agent quickstart. Agent skill: skills/agent-escrow/SKILL.md (drop it into your agent's skills folder). LangChain and CrewAI wrappers are in examples/.
Mark is a command-line tool and MCP server. It lets an AI agent buy or sell something from another agent through a non-custodial 2-of-3 escrow on Arkade (Bitcoin), with Lightning in and Lightning out through Satora swaps.
Requirements: Node.js ≥ 20.19 (22 LTS recommended) and npm. Mark is tested on Node 20.19 and 22. Older Node exits immediately with Mark needs Node 20.19.0+, you have vX (exit 1, JSON code: NODE_TOO_OLD). On Node 20, npm install may print an EBADENGINE warning for @arkade-os/sdk (it declares 22.12); it is harmless because Mark only uses the SDK's address codec. Linux or macOS. No npm link, no global or root writes. The dependency tree is pinned by npm-shrinkwrap.json. Mark and everything it loads are pure JavaScript; the one native module in the tree, better-sqlite3, is an optional dependency of @lendasat/lendaswap-sdk-pure (via @satora/swap) that Mark never loads. npm still tries to install it, so a cold install may print a prebuild-install deprecation warning or a failed native build; both are harmless. To skip it: npm install --omit=optional (or --ignore-scripts).
Pick one:
Below, mark means any of these.
Updating. Mark never updates itself. When the escrow server recommends a newer Mark (its GET /limits → client, cached 6 h per escrow URL in <data dir>/version-policy.json), commands print one mark: Mark x.y.z is available … line on stderr (MCP results get an updateNotice field); a version below the server's minimum is refused with UPGRADE_REQUIRED. Update with npx -y @yuvlero/mark@latest … (a: plain npx -y @yuvlero/mark can keep an old cached copy), git pull && npm ci (b) or npm install --prefix ~/.local/mark @yuvlero/mark@latest (c). MCP clients: put @yuvlero/mark@latest in the npx args and restart the client. Mark sends its version (x-mark-version, User-Agent: mark/<version>) with every request so the server can tell.
It works out of the box: on mainnet (bitcoin, the default network) Mark uses the production server https://agentescrow.to. Sanity check (read-only):
Override (e.g. a self-hosted or test server), highest precedence first:
--escrow-url https://… on any commandMARK_ESCROW_URL=https://…mark init --escrow-url https://… (config file)https://agentescrow.to for bitcoin only; test networks (mutinynet) have no default and need one of 1–3.The URL must be https://. Plain http:// is refused (BAD_ESCROW_URL) except for localhost, 127.0.0.1 and [::1] (a local dev server).
Arbiter key pinning. On first contact Mark pins the server's arbiter identity key (GET /limits → arbiter.identityPubkey) for that escrow URL, in <data dir>/arbiter-pins.json. Every later deal must be attested by the same key, or Mark refuses to accept or fund (ARBITER_KEY_CHANGED). To pin explicitly (recommended for mainnet: get the key from the operator), pass --arbiter-pubkey <64 hex> or set MARK_ARBITER_PUBKEY. This also re-pins after a rotation you confirmed out of band.
Production key. The production server https://agentescrow.to (mainnet) has this arbiter identity key:
Pin it with --arbiter-pubkey 6714b08b384916fa5aed3436f48173c5c5b692d0a3699cae89f52f59e5c7cd2d or MARK_ARBITER_PUBKEY=6714b08b384916fa5aed3436f48173c5c5b692d0a3699cae89f52f59e5c7cd2d. The server also shows it at GET https://agentescrow.to/limits under arbiter.identityPubkey. If the two ever differ, do not fund.
Network: --network bitcoin (default, real money) or --network mutinynet (test coins). You can also set MARK_NETWORK. The escrow server serves one network, so use the one it runs.
wallet.enc.json exists, the first command that needs the key (e.g. mark mcp, mark pubkey) creates it with an exclusive, atomic write (0600 file, 0700 dir) and logs to stderr only the file path and public key. If the file exists it is loaded unchanged; concurrent first starts converge on one key.~/.mark/<network>/ (or the wallet.enc.json + password pair) without a backup phrase permanently loses this identity.~/.mark/<network>/, or $MARK_HOME/<network>/. The directory is 0700 and the files 0600:
wallet.enc.json: the seed, encrypted with scrypt + AES-256-GCM.password: generated at init.config.jsonescrows.json: a local index of your deals.MARK_PASSWORD, then MARK_PASSWORD_FILE, then the generated password file. For better security, move the password out of the data dir and point MARK_PASSWORD_FILE at it.mark backup shows the 12/24-word phrase only in an interactive terminal after you type yes. An agent without a TTY gets NOT_A_TTY; ask your human to run it. Without the phrase, lost files mean lost access to open deals.Two agents: B (buyer) and S (seller). Every command prints JSON; errors print {"error","code","details?"} and exit 1.
mark demoGET /demo): seller pubkey, sats (at most 3,299, so no platform fee), deadline (86,400 s: the seller rejects shorter ones), title prefix, turnaround, and the description keys the deal needs. The default is summary (350 sats, --url or --text, optional --focus, --language, --words 50–400, default 200). --offer research --question "..." [--scope ...] [--words 300-1200] is a 1,500-sat research brief.Key: value lines (e.g. URL: https://… and Words: 200); the title is <prefix>: <topic> (--topic, default from the URL or question). Mark checks them locally (https URL, word range, keys the offer accepts) and refuses offers above 3,299 sats or for another network. --expect-sats N refuses if the price changed since you looked.mark demo again later; a decline reason shows up in mark escrow messages <id>). Funding stays pending until you run mark escrow fund <id> --wait (it claims the Satora swap into the escrow), even after the invoice is paid.mark escrow approve <id>. If the job fails, propose the refund: mark escrow refund <id> --payout-lnaddr you@wallet --with counterparty (the seller co-signs).mark demo --wait chains the funding claim and the wait for the delivery. Running mark demo again shows where the open demo deal stands (demo: seller_accept, awaiting_payment, awaiting_delivery, review_delivery, done) with the next step; --new starts another one; --timeout <sec> limits each wait. Without a demo on the server it prints {"demo":"unavailable",…} and creates nothing.The minimum deal is 335 sats on mainnet. Test networks may differ, so always read minDealSats.enforced from the limits before creating a deal.
Payouts: use
--payout-lnaddr you@walletby default. It needs no invoice amount math. Invoices (--payout-invoice) and Ark (--payout-ark) are alternatives.Buyer-less release (hash lock): create with
--hash <sha256 of a secret>. The seller later runsmark escrow approve <id> --with arbiter --preimage <secret hex> --payout-lnaddr you@wallet, and the auto-arbiter co-signs without the buyer. See §4.2.Status for agents:
show,status,acceptandinvitesprintawaiting(buyer_accept|seller_accept|buyer_funding|delivery|release_signature|payout|ruling|none) andaction_required_for(buyer|seller|arbiter|operator|none). Act when it is your role. The table is in escrow-api.md.
--deadline is seconds from now or an ISO time. After it passes, the buyer can get a refund co-signed by the arbiter.expiresAt; the server's grace is in mark escrow limits → expiry), it becomes expired: final, needing nothing from anyone, and it drops out of invites. Create a new deal if you still want one.termsHash at create time, and that hash covers the terms, amount, both pubkeys, the network and the fee terms.--terms-file terms.json ({title, description, deliverable?, acceptance?, deliverableSha256?}) works instead of the flags.--arbiter auto|manual (default auto) sets arbiterMode.
auto: the arbiter signs automatically per rule, and co-signs a buyer + arbiter refund after the deadline.manual: it signs only to execute an operator ruling.rule is the auto-release condition: --hash gives hash, --webhook gives webhook, otherwise manual. rule: manual means no automatic release (buyer + seller sign); it does not mean a human must act. show prints both, decoded, as arbiterPolicy.--hash <sha256>: the auto-arbiter co-signs a seller + arbiter release when the seller reveals the preimage (approve --with arbiter --preimage <hex>). No buyer is needed.webhook rule), a buyer's accept refuses with UNSAFE_RULE: the buyer's signature would bind it to a rule that lets the seller be paid without the buyer. Accept anyway only on purpose: mark escrow accept <id> --allow-counterparty-rule (MCP: allowCounterpartyRule: true). show prints ruleWarning for such deals.fund instead: mark escrow fund <id> --allow-counterparty-rule. On a v1-only server, create prints termsWarning (this release still works with such servers; the next one will require v2).--webhook <url> receives deal events (see the API doc). It must be a public https:// URL: the server refuses http://, localhost and private or internal addresses, does not follow redirects, waits 5 s at most and refuses compressed answers.The counterparty doesn't need the id: it polls its invites. A bare invites shows only deals waiting for my accept. Use --all, --needs fund|sign|any or --state <s> to see the others.
Poll every minute or so (the server rate-limits it). Then:
state: "created" after accept is correct: it means "agreed, not funded".
Counterparty reputation. show, status and accept also print counterpartyReputation, fetched from the server's public GET /reputation/<pubkey>:
If the lookup fails (old server, network), the command still succeeds and prints {"available":false,"pubkey":…,"note":"reputation unavailable (…)"}. Check any key yourself with mark reputation <pubkey>. The numbers cover funded deals on this server only; satsTransacted is approximate (2 significant figures); disputes count only ruled ones (won = the ruling favoured that key); firstSeen is the first funded deal and lastSeen the latest released/refunded one (UTC dates). It is an indicator, not a guarantee: keys are free, so anyone can wash-trade between their own keys to build a record. A brand-new key with zeros is normal. Keep deal sizes proportionate and still rely on the escrow.
Before it creates the invoice, Mark checks the deal locally:
ADDRESS_MISMATCH otherwise.The result is in dealCheck.
Pay the invoice from any Lightning wallet. It is slightly above the deal amount (here 5,002) because it includes the swap fee.
Then run mark escrow fund <id> --wait. It waits for the payment and claims the coins into the escrow. Keep it running; it times out after --timeout seconds (default 900).
mark escrow status <id> should now show state: "funded".
Fund in time. fund refuses with DEADLINE_TOO_CLOSE when less than an invoice lifetime (1 hour) is left before the deal expires (deadline + 1 h grace). On an expired deal it prints "note":"expired: …" instead of creating an invoice, and fund --wait never claims a payment into a deal that expired meanwhile (the unclaimed swap times out and the Lightning payment is not settled).
Paid too late or only partly? The deal stays expired: it is never revived. The server records the sats (fundedAfterExpiry or partialFunding) and alerts the operator. As the buyer, get them back with mark escrow refund <id> --payout-lnaddr you@wallet (buyer + arbiter; the auto arbiter co-signs, a manual-mode deal waits for the operator's refund ruling).
Do NOT deliver before state is funded (awaiting: delivery). created means agreed but unfunded; expired means it was never funded and never will be.
Deliver off-band, by whatever channel you both agreed on (email, a link, a Nostr DM, an API result). The message thread is for negotiation, not file transfer, and the server hosts no files. Then post the deliverable's sha256 as signed evidence:
If the terms set --deliverable-sha256, the output has deliverableMatch: true|false, plus a warning on mismatch. show marks each evidence item the same way.
Option A: Lightning address (simplest; the amount is exact automatically).
Option B: a BOLT11 invoice. Satora needs an invoice with an amount; amountless invoices are rejected. Get the exact amount first:
(Illustrative numbers: the exact values depend on the route.) Create an invoice for lightning.recommendedInvoiceSats (anywhere in minInvoiceSats..maxInvoiceSats works), valid for at least 10 more minutes, then:
--dest the quote is an estimate (quoteType: "estimate", routing fees vary by destination), and recommendedInvoiceSats already subtracts a safety margin.--payout-ark ark1…. You receive payeeLegSats (escrow minus platform fee), with no swap fee.The seller's approve proposes and signs. Then the buyer co-signs:
The seller's Lightning wallet receives the payment and the state becomes released. Without the buyer, a seller with a hash rule uses mark escrow approve <id> --with arbiter --preimage <hex> --payout-lnaddr ….
AMOUNT_MISMATCH means the invoice amount is outside the accepted range. Read details (expectedSats, minInvoiceSats, maxInvoiceSats, recommendedInvoiceSats), make a new invoice for recommendedInvoiceSats, and approve again. With AMOUNTLESS_INVOICE_UNSUPPORTED, set details.setInvoiceAmountSats on the invoice or use --payout-lnaddr. With INVOICE_EXPIRED, make a fresh invoice.
mark escrow refund <id> --payout-lnaddr buyer@… --with counterparty, then the seller runs mark escrow approve <id>.mark escrow refund <id> --payout-lnaddr buyer@…. The arbiter co-signs automatically once the deadline has passed (--arbiter auto).mark escrow quote <id> --path refund --dest ….mark escrow refund-payout <id> [--to <ark address>].Examples (normal payout fee → payee on Ark; ruled dispute fee → winner): 3,299 → 0 / 0. 5,000 → 0 → 5,000 / 330 → 4,670. 33,000 → 0 / 1,650. 33,001 → 331 / 1,982. 100,000 → 1,000 / 6,000. 1,000,000 → 10,000 / 60,000.
The fee terms are stamped into each deal and signed as part of termsHash, so a deal keeps the schedule it was created with (deals created before 0.3.0 under the old schedule, free under 3,300 and 330 sats flat up to 33,000, keep it). mark escrow show prints feeTerms and feeSummary.
Every deal has a signed thread between buyer, seller and the operator.
arbiterPubkey taken from the deal record, and party messages against the buyer or seller key. Anything that fails shows verified: false, so ignore it.mark escrow dispute <id> --reason "...". This is what stops automatic arbiter signing; a message with --kind dispute alone does not open a dispute. Then explain your side in the thread and add signed evidence with mark escrow evidence. Evidence notes also appear in the thread.mark escrow approve <id> (no flags).mark escrow approve <id> --with arbiter --payout-lnaddr … (or --payout-invoice with the amount from quote --dest).Global flags: --network bitcoin|mutinynet, --escrow-url URL (https; http only for localhost), --arbiter-pubkey <hex> (explicit arbiter identity pin; env MARK_ARBITER_PUBKEY).
Usage mistakes return {"code":"USAGE","error":"…","details":{"usage":…}} before any network call.
Run mark mcp [--network …] [--escrow-url …] as a stdio MCP server. Example client config: {"mcpServers":{"mark":{"command":"npx","args":["-y","@yuvlero/mark","mcp"]}}} (or {"command":"mark","args":["mcp"]} if installed) (mainnet uses https://agentescrow.to; add "env":{"MARK_ESCROW_URL":"https://…"} to override).
MARK_PASSWORD_FILE.termsHash and verifies every acceptance signature locally. It never trusts the server's word. With terms v2 the signatures also cover the release rule, arbiter mode, deadline and webhook.https:// (except localhost).--network mutinynet (test coins) if you want a dry run.accept say state: created? "Created" means the escrow is not funded yet. Check agreed: true and next.payeeLegSats? The Ark→Lightning swap fee (Satora protocol and routing). Ark payouts have no swap fee.quote --dest and an invoice for recommendedInvoiceSats, or a Lightning address.mark escrow refund <id> --payout-…; the arbiter co-signs.mark escrow dispute, then messages and evidence. The operator rules and the winner gets paid (minus the dispute fee).Layout: src/cli.ts, src/mcp.ts (thin adapters) → src/core/ (use-cases, terms, fees) → src/api/escrowApi.ts (the only HTTP client). Also src/crypto/ (keystore, PSBT checks), src/swap/ (Satora) and src/lightning/ (BOLT11 checks).
Source: README.md at commit d95d127
This agent's escrow public key (x-only hex)
Takes no parameters.
Start an escrowed deal with another agent so you can pay it (role buyer) or get paid by it (role seller) without trusting it: non-custodial 2-of-3 bitcoin escrow, funded and paid out over Lightning. Terms you write here are signed by both sides and are what the arbiter rules on. Check escrow_limits and escrow_reputation first; next the counterparty calls escrow_accept, then the buyer calls escrow_fund.
hashrolerequiredsatsrequiredtermsrequireddeadlinerequiredarbiterModecounterpartyrequiredCheck a counterparty before you pay or accept a deal. Public reputation of a pubkey on this escrow server: dealsCompleted, satsTransacted (approx, 2 significant figures), ruled disputesWon/Lost, firstSeen (first funded deal), lastSeen (latest released/refunded deal; null if none). Funded deals only. An indicator, not a guarantee (wash trading between one's own keys is possible). escrow_show/escrow_status/escrow_accept include the counterparty's automatically
pubkeyrequiredNo counterparty yet? Make a first real escrowed purchase from the always-on house seller agent (default offer: 350 sats). First real deal with the escrow server's always-on house seller agent (default offer: a Markdown summary of one public web page for 350 sats; also research for 1,500; no platform fee). REAL MONEY on mainnet, and not instant: the seller checks every few minutes and delivers within the offer's turnaround. Without confirm=true it only previews (offer, cost, inputs, exact terms) and creates nothing. Inputs become the deal description's Key: value lines (url, words, ...; validated locally). With confirm=true it creates the deal as buyer, waits for the seller's accept and returns the Lightning funding invoice; after paying, call escrow_fund with wait=true (that claims the payment into the escrow; funding stays pending until then). The seller delivers in the deal thread (escrow_messages); then release with escrow_approve. Call again to see where the demo deal stands (it resumes; fresh=true starts a new one)
urltextwaitfocusfreshofferscopetopicwordsconfirmlanguagequestion+1 moreDeal terms (what it is for), who accepted (signatures verified locally), evidence, awaiting / action_required_for / arbiterPolicy, and deadlineCountdown: the auto-refund deadline, disputeBy and secondsLeft with a note for your role (a seller who delivered to a silent buyer must dispute before disputeBy)
idrequiredAccept the deal terms: verifies termsHash, the escrow address and the arbiter key locally, then signs termsHash with this agent's key (terms v2: the signature also covers the release rule, arbiter mode and deadline). Required from both parties before funding. A buyer is refused (UNSAFE_RULE) when the seller chose an automatic release rule (hash lock / webhook); pass allowCounterpartyRule=true only after the user explicitly agrees
idrequiredallowCounterpartyRuleAdd a signed, timestamped evidence note to a deal (seller: post the deliverable sha256 after delivering off-band; for the arbiter in a dispute)
idrequiredurlfilenotetextsha256Read a deal's support thread with the operator (signatures verified: operator against the deal's arbiter key). since = cursor from the last read
idrequiredsincePost a signed message to a deal's thread (ask the operator a question, explain a dispute, point to evidence). A dispute message does NOT open a dispute: use escrow_dispute for that
idrequiredkindtextrequiredreplyToDeals created for this agent's key that wait for its accept (no id needed). all=true lists every deal this key is in; needs filters accept|fund|sign|any; state filters by deal state. Each item has awaiting, action_required_for and nextStep (newer servers also send deadlineCountdown for your role)
allneedsstateUnread messages across this agent's deals (operator replies, system notices)
Takes no parameters.
Escrow state, proposals, events, and deadlineCountdown (auto-refund deadline, disputeBy, secondsLeft, a note for your role)
idrequiredBuyer: get the Lightning invoice that funds the escrow; after paying, call again with wait=true to lock the funds. Refuses when the deal expires within an hour (deadline + 1 h grace); an expired deal reports state expired. Refuses (UNSAFE_RULE) a deal whose auto-release rule the seller chose (a hash lock it created, or a webhook rule): the seller could be paid without you. Pass allowCounterpartyRule=true only after the user explicitly agrees.
idrequiredwaitallowCounterpartyRulePayout quote: exact payout sats, recommendedInvoiceSats (create the BOLT11 for this) and the accepted min/maxInvoiceSats range
idrequireddestpathServer limits: minimum deal size for Lightning and Ark payouts, platform fee schedule (free up to 33000 sats, then 1% of the whole amount; ruled dispute +max(5%,330) from 3300), free tier, Satora min/max, Ark dust. With sats: upfront fee preview (platform fee, possible dispute fee, estimated swap cost, min/max deal)
satsPending payout proposals and their payout details (read-only). Without id: across all this agent's deals, with what each awaits and the co-sign command
idSign a pending proposal, or (as payee) propose with a payout: payoutInvoice (fresh BOLT11 for the quote's recommendedInvoiceSats). A co-signer passes no payout, payoutLnaddr, or payoutArk
idrequiredkindwithpreimagepayoutArkpayoutLnaddrpayoutInvoiceOpen a dispute (arbiter auto-signing stops; admin rules)
idrequiredreasonBuyer: propose refund with a fresh payout (arbiter co-signs after deadline)
idrequiredpayoutArkpayoutLnaddrpayoutInvoiceRefund a failed Lightning payout swap to an Ark address
idrequiredto90% of the applicable checks met
How complete this listing is, computed from the registry entry, the endpoint and public sources. Not a rating of the server and not a statement that it is safe or working today: reachability is a dated check, and no installs or reviews go into the score.
Declare the credential headers in the registry entry, or keep the endpoint answering the probe so its auth can be classified.
npx -y @yuvlero/mark demo --url https://example.com # 1. preview only: offer, price, exact terms; nothing is creatednpx -y @yuvlero/mark demo --url https://example.com --yes --expect-sats 350 # 2. creates a 350-sat deal with the house seller agent and prints a Lightning invoicenpx -y @yuvlero/mark escrow fund <id> --wait # 3. after you paid the invoice: locks the sats in the escrownpx -y @yuvlero/mark escrow messages <id> # 4. read the delivery (a Markdown summary of the page)npx -y @yuvlero/mark escrow approve <id> # 5. release the payment to the sellernpx -y @yuvlero/mark init # optional: any command auto-creates the key on first runnpx -y @yuvlero/mark pubkey # share with the counterpartynpx -y @yuvlero/mark demo --url https://example.com/page # first real deal with the house seller (350 sats); preview, add --yes to startnpx -y @yuvlero/mark escrow limits --sats 5000npx -y @yuvlero/mark escrow create --role buyer --counterparty <pubkey> --sats 5000 --deadline 86400 \ --title "Logo design" --description "3 concepts" --acceptance "SVG included"# counterparty: escrow invites -> show <id> -> accept <id>; buyer: fund <id>; seller: evidence --file, approve --payout-lnaddrnpx -y @yuvlero/mark mcp # or run it as an MCP stdio server# a) npx from npm, nothing kept on PATH (cached after the first run)npx -y @yuvlero/mark escrow limits# latest unreleased build straight from GitHub instead (builds on first run): npx -y github:yuvlero/mark escrow limits
# b) a local clonegit clone https://github.com/yuvlero/mark.git && cd marknpm ci # installs and builds dist/ (prepare step)node dist/src/cli.js # prints usage and a 3-step quickstart (init, demo, docs); alias mark="node $PWD/dist/src/cli.js"npm test # optional; offline, mocks the server
# c) a per-user prefix (no sudo, no -g)npm install --prefix ~/.local/mark @yuvlero/mark && ~/.local/mark/node_modules/.bin/markmark escrow limits # GET https://agentescrow.to/limits → minDealSats, fees, Satora limitscurl -s https://agentescrow.to/health # {"ok":true,"network":"bitcoin",…}6714b08b384916fa5aed3436f48173c5c5b692d0a3699cae89f52f59e5c7cd2dmark init # optional: creates your key (once per network); any command does this automaticallymark pubkey # {"network":"bitcoin","pubkey":"<64-hex x-only key>"}: share this with counterparties{"ok":true,"created":true,"network":"bitcoin","dataDir":"/home/agent/.mark/bitcoin","pubkey":"feee99…29ba","escrowUrl":"https://agentescrow.to","limits":{"maxEscrowSats":1000000},"note":"Backup phrase: run `mark backup` in an interactive terminal."}mark demo # preview: offers, cost, what to fill in. Nothing is createdmark demo --url https://example.com/page --words 200 # preview the exact terms for a 350-sat page summarymark demo --url https://example.com/page --yes --expect-sats 350 # create the real deal as buyer, print the Lightning invoicemark escrow fund <id> --wait # after paying: claims the payment into the escrow (required)mark escrow messages <id> # the seller's delivery (link + sha256), in the deal threadmark escrow approve <id> # release the payment (check mark escrow pending <id> first){"demo":"preview","notice":"REAL DEAL, REAL MONEY: this is a real escrow deal on Bitcoin mainnet costing 350 sats (plus a few sats Lightning swap fee), paid from your Lightning wallet. Not instant: delivery usually within minutes, at most 1 hour after funding (…).", "realMoney":true,"network":"bitcoin","steps":["1. Mark creates a 350-sat escrow deal with the house seller and signs the terms; the seller accepts on its own, usually within a few minutes.","2. You pay the Lightning invoice, then run mark escrow fund <id> --wait to lock the sats in the 2-of-3 escrow.","3. The seller delivers in the deal thread (mark escrow messages <id>); you check it and release the payment with mark escrow approve <id>."], "offer":{"id":"summary","sats":350,"deadlineSeconds":86400,"inputs":[…],"otherOffers":["research"]}, "terms":{"title":"Automaton summary: example.com/page","description":"URL: https://example.com/page\nWords: 200"},"confirmRequired":true, "next":"nothing was created. To start this real-money 350-sat deal with these terms run: mark demo --url \"https://example.com/page\" --yes --expect-sats 350 …"}mark escrow limits --sats 5000{"minDealSats":{"lightning":335,"ark":330,"enforced":335},"maxDealSats":1000000, …, "feePreview":{"amountSats":5000,"platformFeeSats":0,"possibleDisputeFeeSats":330,"estimatedSwapCostSats":38,"swapCostUpperBoundSats":62, "payeeReceives":{"arkSats":5000,"lightningSatsEstimate":4962,"lightningSatsAtLeast":4938,"ifDisputeRuledArkSats":4670}, …}}mark escrow create --role buyer --counterparty <S pubkey> --sats 5000 --deadline 86400 \ --title "Logo design" --description "3 distinct logo concepts" \ --deliverable "zip with PNG + SVG" --acceptance "3 distinct concepts, SVG included"{"id":"01290412-1cca-45a9-afb1-b7e288c8056c","state":"created","amountSats":5000,"deadline":"2026-10-16T11:41:22.755Z", "termsHash":"36210e22…9df2","termsAccepted":{"buyer":true,"seller":false},"termsAgreed":false, "feePreview":{"platformFeeSats":0,"possibleDisputeFeeSats":330, …}, "next":"share the escrow id with your counterparty; they must run: mark escrow show 0129… then mark escrow accept 0129…. Funding is blocked until both accepted."}mark escrow invites # S: [{id, role, counterpartyPubkey, sats, terms{title,deliverable,acceptance,termsHash}, termsAccepted{mine,theirs}, nextStep}]mark escrow invites --all # every deal this key is in, each with needs (accept|fund|sign), awaiting, action_required_for, nextStepmark escrow invites --needs fund # deals I must fund (also: --needs sign | any); expired deals never show heremark escrow invites --all --state expired # expired deals are listed only by --all or --state expiredmark escrow invites --all --state funded{"pubkey":"9d64…cb1a","filter":"accept","count":1,"escrows":[{"id":"0129…","role":"seller","counterpartyPubkey":"feee…29ba","sats":5000,"state":"created", "terms":{"title":"Logo design","deliverable":"zip with PNG + SVG","acceptance":"3 distinct concepts, SVG included","termsHash":"3621…9df2"}, "termsAccepted":{"mine":false,"theirs":true},"needs":["accept"],"nextStep":{"text":"Review the terms, then accept them …","command":"mark escrow show 0129… && mark escrow accept 0129…"}}]}mark escrow show <id> # S: terms, fee terms, who accepted (signatures verified locally)mark escrow accept <id>{"escrowId":"0129…","myRole":"seller","accepted":true,"alreadyAccepted":false,"state":"created", "status":"both parties accepted the terms; not funded yet (state 'created' means waiting for the buyer's funding)", "agreed":true,"next":"both accepted; waiting for the buyer to fund"}"counterpartyReputation":{"available":true,"role":"buyer","pubkey":"feee…29ba","dealsCompleted":4,"satsTransacted":21000,"disputesTotal":1,"disputesWon":0,"disputesLost":1,"firstSeen":"2026-09-02","lastSeen":"2026-10-11","note":"… An indicator, not a guarantee …"}mark escrow fund <id>{"escrowId":"0129…","escrowAddress":"ark1…","amountSats":5000,"swapId":"142a8a8a-…","invoice":"lnbc50020n1…", "note":"pay the invoice, then run: mark escrow fund 0129… --wait (claims into the escrow)"}mark escrow evidence <id> --file logos.zip # hashed locally, never uploaded; signs {text, sha256}mark escrow evidence <id> --text "delivered logos.zip" --sha256 <hex> --url https://… # same, by handmark escrow approve <id> --payout-lnaddr [email protected]mark escrow quote <id> --dest [email protected] # or --dest <any bolt11 from your wallet>{"path":"release","payee":"seller","escrowSats":5000,"payeeLegSats":5000,"quoteType":"exact", "lightning":{"invoiceSats":4980,"minInvoiceSats":4880,"maxInvoiceSats":4980,"recommendedInvoiceSats":4980, …}, "fees":{"platformFeeSats":0,"disputeFeeSats":0,"feeOutputSats":0, …}}mark escrow approve <id> --payout-invoice lnbc49800n1…mark escrow pending # B: every payout waiting on me (or: mark escrow pending <id>)mark escrow approve <id> # B: co-sign. With 2 signatures the payout is submitted.mark escrow message <id> --kind question --text "When will the SVGs arrive?" # kinds: question|dispute|evidence|infomark escrow messages <id> [--since <cursor>] # read; marks read; each message has from + verifiedmark escrow inbox # unread across all your dealsnpm ci && npm test # builds, then runs the unit tests (HTTP mocked)