NIP-XX
Escrow Services
draft optional
This NIP defines a protocol for decentralized escrow services on Nostr, enabling trust-minimized commerce between buyers and sellers. Escrow operators advertise their services, buyers and sellers declare their trusted escrow providers, accepted contract bytecode hashes, and accepted payment forms, and the protocol coordinates on-chain settlement with arbitration capabilities.
Terms
- Buyer — Nostr user making a payment for a good or service.
- Seller — Nostr user providing a good or service.
- Escrow — Nostr user operating an escrow service that holds funds and can arbitrate disputes.
- Trade — A single escrow-backed transaction between a buyer and seller, mediated by an escrow.
Event Kinds
Escrow Service (kind:30303)
Published by escrow operators to advertise their service. The d tag is a stable service identifier chosen by the escrow operator and MUST remain stable across updates to the same advertised service.
Content
JSON object:
{
"pubkey": "<escrow-operator-nostr-pubkey>",
"evmAddress": "<eip55-checksummed-address>",
"contractAddress": "<deployed-escrow-contract-address>",
"contractBytecodeHash": "<sha256-of-runtime-bytecode>",
"chainId": 30,
"maxDuration": 31536000,
"type": "EVM",
"feePercent": 1.0,
"tokenFeeHints": {
"native": { "baseFee": 0, "maxFee": 0, "minFee": 0 },
"0xdAC17F958D2ee523a2206206994597C13D831ec7": {
"baseFee": 50000,
"maxFee": 1000000,
"minFee": 10000,
},
},
}
Token Fee Hints
Each entry in tokenFeeHints contains:
Fee calculation: fee = clamp(floor(amount × feePercent / 100) + baseFee, minFee, maxFee)
Tags
Escrow Method (kind:17388)
Usually published by sellers to declare which escrow services they trust and which payment forms they accept. When placing an order, buyers normally defer to the seller's escrow method event and choose from the seller's advertised escrow and payment options.
Buyers MAY publish their own escrow method event to advertise preferred escrow services or accepted payment forms. When a buyer pays through an escrow service used with a seller, the buyer SHOULD adopt that escrow pubkey in their own escrow method event so future counterparties can discover that trust relationship.
This event is replaceable per pubkey. Implementations SHOULD publish at most one current escrow method event per user. The event content is empty.
Tags
Payment Form Tags
The o tag maps a denomination code (e.g. "BTC", "USD") to an on-chain token identifier:
The token tag ID format is <chainId>:<tokenAddress>. The zero address (0x000...) denotes the chain's native asset. Non-EVM payment rails MAY use their own token tag IDs, such as "BTC" for Lightning-denominated BTC.
When present, <app-id> scopes the payment form to an application.
Example
{
"kind": 17388,
"pubkey": "<seller-pubkey>",
"tags": [
["p", "abc123..."],
["p", "def456..."],
["c", "a1b2c3d4e5..."],
["o", "BTC", "30:0x0000000000000000000000000000000000000000", "example"],
["o", "USD", "30:0xdAC17F958D2ee523a2206206994597C13D831ec7", "example"],
],
"content": "",
// ...
}
Escrow Service Selected (kind:30302)
Created by the buyer to record the chosen escrow service and the seller's escrow method for a trade.
This event is OPTIONAL. A buyer MAY use it to keep trade state synchronized across clients or with other participants, but it is not required before funding escrow or before publishing a reservation payment proof. The buyer is responsible for tracking where funds were deposited and for publishing a valid payment-proof reservation when committing the trade.
In private negotiation flows this event is usually sent as a signed child event inside a private kind:1327 structured-message rumor tagged ["conversation", "<trade-id>"] and delivered with NIP-59 gift wraps, rather than being broadcast as a standalone public event. Implementations MAY publish it as a standalone event when the trade context is intended to be public.
Tags
The d tag is the only trade identifier carried by this child event. Private message grouping is performed by the enclosing kind:1327 rumor's conversation tag.
Content
JSON object containing the full serialized Nostr events:
{
"service": "<JSON string of the EscrowService kind:30303 event>",
"sellerMethods": "<JSON string of the seller's EscrowMethod kind:17388 event>"
}
Mutual Escrow Resolution
When a buyer and seller wish to transact, their clients SHOULD automatically resolve a mutually trusted escrow:
- Query both parties'
kind:17388(Escrow Method) events. - Find the intersection of trusted escrow pubkeys (
ptags). - Find the intersection of supported contract bytecode hashes (
ctags). - Query
kind:30303(Escrow Service) events from mutually trusted pubkeys whosecontractBytecodeHashmatches a mutually supported bytecode. - If no mutual match exists, fall back to the seller's trusted escrows.
On-Chain Escrow Contract
The on-chain escrow contract manages funds with the following lifecycle:
Trade Lifecycle
┌──── releaseToCounterparty ────┐
│ ▼
(empty) ── createTrade ──► funded ──► released / arbitrated / claimed
│ ▲
│ ┌── arbitrate ───┘
│ │
└───────┼── claim (after unlockAt) ──► claimed
Trade Structure
Each trade is identified by a tradeId (bytes32) and stores:
Operations
All operations use EIP-712 typed-data signatures, enabling gas-sponsored relay (anyone can broadcast the transaction).
Settlement
All settlements credit a balances[recipient][token] mapping (pull pattern) rather than performing direct transfers. This prevents reentrancy and enables batched withdrawals.
Arbitration
When a dispute arises:
- Either party messages the escrow operator via Nostr DMs.
- The escrow operator reviews the trade context (reservation, listing, payment proof, on-chain state).
- The operator submits an
arbitratetransaction with afactorvalue:0= full refund to buyer1000= all funds to seller500= 50/50 split
amountAfterFee = amount - escrowFeeforwardAmount = (amountAfterFee × factor) / 1000→ credited to selleramountAfterFee - forwardAmount→ credited to buyerescrowFee→ credited to arbiter
Escrow Proof
When a reservation is backed by escrow, the commitment includes an EscrowProof in the reservation's PaymentProof:
{
"txHash": "<evm-transaction-hash>",
"escrowService": "<JSON string of the EscrowService kind:30303 event>",
"hostsEscrowMethods": "<JSON string of the host's EscrowMethod kind:17388 event>"
}
Verification
Clients and escrow operators MUST verify escrow proofs:
- Validate the
EscrowServiceevent signature and pubkey. - Verify the host's
EscrowMethodevent lists the escrow pubkey in aptag. - Verify the host's
EscrowMethodevent lists the contract bytecode hash in actag. - Query on-chain for the
TradeCreatedevent matchingtxHash. - Verify the on-chain funded amount covers the reservation cost (accounting for token denomination and decimals).
- Verify the host's
EscrowMethodaccepts the on-chain token for the reservation's denomination.
Payment Integration
Funding (Lightning → On-Chain)
Buyers MAY fund escrow via Lightning using submarine swaps (e.g. Boltz):
- Build a swap-in request with post-claim calls:
ERC20.approve(if token) +createTrade. - Buyer pays the Lightning invoice.
- Swap provider claims the HTLC and atomically executes the on-chain escrow funding.
Withdrawal (On-Chain → Lightning)
Settled funds MAY be withdrawn to Lightning via reverse submarine swaps:
- Build a
withdrawcall as a pre-lock call in a swap-out operation. - Atomically execute:
withdraw()from escrow →lock()into swap contract. - Swap provider pays a Lightning invoice to the beneficiary.
Related NIPs
- NIP-01 — Event structure and parameterized replaceable events.
- NIP-17 — Private message rumor kind
14. - NIP-44 — Encryption scheme.
- NIP-59 — Gift wrap.