x402-negotiator
Agent-to-agent price negotiation as signed instruments. Open an offer, counter it, accept it — each move returns a signed artifact that links to the one before it, so an entire negotiation can be verified offline, in order, without trusting the server that produced it.
USDC on BaseUSDC on SolanaHTTP 402Apache-2.0hash-linked chainconcession analysisself-contained
Pay on either rail — your client picks. Every 402 challenge lists USDC on Base and USDC on Solana at the same price.
git clone https://github.com/nirholas/x402-negotiator && cd x402-negotiator
npm install && cp .env.example .env # pre-filled — runs with no edits
npm run dev # http://localhost:4045
Pricing
| Route | Price | Returns |
|---|---|---|
POST /offers | $0.001 | Signed offer instrument + thread + counter/accept links |
POST /counter/:offerId | $0.001 | Signed counter with concession analysis |
POST /accept/:offerId | $0.001 | Signed agreement + settlement terms + full chain |
GET /threads/:id | free | The thread and a server-side chain check |
POST /verify-chain | free | Per-instrument signature and link verification |
POST /verify | free | Signature check for one instrument |
Payment rails
USDC on Base
EIP-3009 transferWithAuthorization, signed in the wallet. Nothing on-chain
until the facilitator settles.
- network
base-sepolia(default) ·base- payTo
0x40252CFDF8B20Ed757D61ff157719F33Ec332402- facilitator
https://x402.org/facilitator
USDC on Solana
Fee-sponsored SPL transferChecked. The facilitator's feePayer
covers the SOL fee — you need only USDC.
- network
solana(default) ·solana-devnet- payTo
WwwuGbqHrwF5RG89KhUbmRWEvjnRH9k5kVM5p7T3WwW- facilitator
https://facilitator.payai.network
A rail with no configured address is dropped from the challenge instead of failing the request, so a single-rail deployment is a one-line change.
How a paid call works
The chain is the product
Every instrument carries the previous one’s signature and a SHA-256 of its payload.
Editing any price breaks two links at once — its own signature and the next instrument’s
prevHash — so the tamper is visible without asking anyone:
POST /verify-chain ← free, no server state consulted
{ "valid": true, "length": 4, "problems": [] }
// after editing seq 3’s price:
{ "valid": false,
"problems": [ "seq 3: invalid signature",
"seq 4: prevHash does not match the previous instrument’s payload" ] }
// after reordering two instruments:
{ "valid": false, "problems": [ "seq 3: seq 3 does not follow 1" ] }
Both counterparties hold the same chain. Neither needs this server to prove what was agreed.
Counters tell you whether to keep going
Every counter carries a concession analysis — how far you moved, how far apart the sides still are, who has been doing the conceding, and where the two land if the current rate holds:
"concession": {
"movedUsd": 90, "movedPct": 30,
"gapUsd": 90, "gapPct": 20.69,
"totalMoved": { "buyer": 90, "seller": 0 },
"projectedSettleUsd": 435, "projectedRounds": 1,
"note": "At the current rate of concession the sides meet near $435 in about 1 more round(s)."
}
totalMoved is the honest field. A thread where one side has moved $90 and the
other $0 is not a negotiation, and the projection refuses to guess rather than flattering you.
Refusals are artifacts too
Counter out of turn, act on a stale instrument, or accept your own position and you get a signed rejection instrument naming the reason — chain-linked like any other, and presentable as proof the move was refused. You paid for the call; you get an artifact for it.
It does not move money
An agreement is the instrument of record: what was agreed, by whom, at what price, by when, and on which rails settlement should happen. The transfer itself happens on a named rail — often an x402 call to the seller’s own endpoint. Keeping the two separate is what lets the same agreement travel between parties, escrow agents and auditors unchanged.