
Security News
Deno Goes the Way of the Dinosaur
Cloudflare is acquiring Deno’s team, which will provide another year of bug fixes and security updates before ending support for the runtime.
eddieseddies
Advanced tools
ex420 — x402 for Eddies. Accept payments with a single line of code; HTTP 402 settled in €$ on the Eddies ledger.
x402 is the open standard that finally gives HTTP 402 a
meaning: a server answers 402 Payment Required with machine-readable payment
requirements, the client comes back with a signed authorization, and the
resource is served. ex420 is that protocol, settled on the Eddies ledger —
protocol version 2, scheme ex420, network eddies:mainnet, asset €$.
An agent that already speaks x402 speaks ex420: same flow, same headers, same
objects. Only the signature differs — an ML-DSA-65 SQRL key over ex420-v2:…
instead of an EIP-712 blob.
Everything is a curl away. The library in this folder is a convenience you can ignore.
GET /api/ex420/supported what this facilitator speaks
GET /api/ex420/requirements?payTo=&amount= the 402 to send, header included
POST /api/ex420/verify is this authorization good? (read-only)
POST /api/ex420/settle move the money
POST /api/ex420/charge verify + settle in one call
A seller's entire integration is two calls:
# 1. Someone asked for the goods and sent no payment. Ask for the 402 to send.
curl -s "$GW/api/ex420/requirements?payTo=alice&amount=100&resource=https://api.example/premium"
# → { "paymentRequired": {…}, "header": { "name": "PAYMENT-REQUIRED", "value": "eyJ4NDAy…" } }
# Answer 402 with that header.
# 2. They came back with a PAYMENT-SIGNATURE header. Charge it — naming YOUR
# price and payee beside the payload. The facilitator checks the payer's
# authorization against these, never against the payer's own copy.
curl -s -X POST "$GW/api/ex420/charge" -H 'content-type: application/json' \
-d "{\"paymentPayload\": $(echo "$PAYMENT_SIGNATURE" | base64 -d),
\"payTo\": \"alice\", \"amount\": 100, \"resource\": \"https://api.example/premium\"}"
# → { "settlement": { "success": true, "transaction": "F1-x420…", "amount": "100" },
# "header": { "name": "PAYMENT-RESPONSE", "value": "eyJzdWNj…" } }
# success → serve the page and echo that header.
The long form is x402's own: { paymentPayload, paymentRequirements }, where
paymentRequirements is the accepts[0] you quoted in step 1. A body carrying
neither the requirements nor payTo + amount is refused — a seller's price
never comes from the payer. resource is optional: give it, and an
authorization signed for another URL is refused too.
/verify and /settle take the same body. /verify answers x402's
VerifyResponse ({ isValid, invalidReason?, payer }) and moves nothing;
/settle and /charge answer the envelope above — the SettleResponse is
settlement, and header is ready to echo. A failed settlement is a 402
with settlement.errorReason (insufficient_funds,
invalid_ex420_payload_nonce_spent, …).
| Asset | €$ — the app's only mark |
| Amount | Eddies, as a string: whole €$100 units, or €$0.01 steps under €$100 |
| Rungs | 0h, 1h, 24h and 42 all pay; a seller may insist on one with window |
| Settlement | whole units: one hop, payer → payee. Under €$100: a meter reading, paid out as a real Eddie at 100 — or, from an AI short on parked cents, its AGE (€$0.01 = 14.4 minutes) onto the payee's age book (transaction: "age:<nonce>") |
| Receipt | the fund that arrived — its whole chain is public at /api/fund-proof/<id> |
€$100 is the smallest thing this ledger moves. A price under €$100 is
virtual Eddies: each payment counts up on the payee's meter at the gateway,
and a real €$100 Eddie moves the moment the count reaches 100. The payer backs
it first by parking whole units (POST /api/micro/park; the MCP tool
eddies_meter). A whole-unit price is an ordinary one-hop payment. €$150.50
is neither, and is refused. Pay-per-crawl counts up the
same way, at 0.01 Eddies a crawl.
The payee is paid in the rung the payer spent: park 42s, be paid 42s. A transfer never rewrites an Eddie's window, source or birthday.
An AI can pay in time — the age of its own 0h Eddies — at any grain, with no rate in between. Nothing is created: time moves from the payer's Eddies (oldest first, each re-dated younger by what it gives, keeping the rest) onto the payee's oldest 0h Eddie (re-dated that much older). A payee holding no 0h Eddie gets it on its age book.
age('alice', '90s') is an ex420 entry with asset: "age"
and the amount in milliseconds — list it beside a €$ price:
{ price: 1, payTo: 'alice', accepts: [age('alice', '90s')] }. An AI pays it
with ex420Fetch(url, { identity, maxAge: '2h' }) (MCP: max_age). Only an
AI pays in time; a person pays the €$ price.POST /api/age/stream/start {username, to, max: "30m", pace?} starts a meter at pace ms of age per second (1000 = real time —
agree a higher pace when compute runs faster); either side
POST /api/age/stream/stop {username, id} and the seconds that ran settle
Eddie to Eddie. GET /api/age shows the live meter. MCP: eddies_work.import { EddiesIdentity } from 'eddieseddies/identity';
import { ex420Fetch } from 'eddieseddies';
const me = EddiesIdentity.open('crawler7'); // a username IS the signup
await me.signUp(); // …and it comes with a SQRL id
const res = await ex420Fetch('https://api.example/premium', {
identity: me,
maxAmount: 500, // never spend more than €$500 without asking
});
console.log(res.status, res.payment); // 200 { paid: true, amount: '100', transaction: 'F1-x420…' }
ex420Fetch is plain fetch that knows what a 402 means: it reads the
PAYMENT-REQUIRED header, picks the accepts[] entry it can pay, signs an
authorization, and repeats the request with PAYMENT-SIGNATURE.
A payment is a signature over a string — you can make one offline and hand it to anybody. The string is:
ex420-v2:<from>:<to>:<value>:<asset>:<network>:<validAfter>:<validBefore>:<nonce>:<resource>
signed with the payer's ML-DSA-65 key, and sent as x402's PaymentPayload:
{ "x402Version": 2,
"resource": { "url": "https://api.example/premium" },
"accepted": { "scheme": "ex420", "network": "eddies:mainnet", "amount": "100",
"asset": "€$", "payTo": "alice", "maxTimeoutSeconds": 60 },
"payload": {
"signature": "<base64 ML-DSA-65>",
"publicKey": "<base64 ML-DSA-65>",
"authorization": { "from": "crawler7", "to": "alice", "value": "100",
"validAfter": "1789358000", "validBefore": "1789358300",
"nonce": "<64 hex>" } } }
publicKey is ours: x402 recovers a payer's address from the signature, and
ML-DSA does not recover, so the key rides along — and it must be one the account
has authorized, the same bar every other write here meets.
One authorization, one payment. The nonce is written onto the transfer line
itself (via=ex420|ex420=<nonce>), so a replay is refused by every gateway that
has the log, not only the one that saw it first.
x402's own shape — a route map, one app.use:
import { paymentMiddleware, usdc } from 'eddieseddies/server';
app.use(paymentMiddleware({
'GET /weather': {
accepts: [...], // as many networks / schemes as you want to support
description: 'Weather data', // what your endpoint does
},
'GET /premium/*': { price: 100, payTo: 'alice' }, // …or just a price
'/report': { price: 200, payTo: 'alice', description: 'The report' },
}));
"METHOD /path", or a bare "/path" to charge every verb.
* and /* wildcards and :param segments all work; the most specific
pattern wins. A route that is not in the map is free and never touches the
money.accepts is x402's own array — write it out to advertise several
networks and schemes at once. Anything that is not ex420 (USDC, say) is
settled by an x402 facilitator against YOUR entry, never the payer's copy.
Dollars beside Eddies in one route:
{ price: 1, payTo: 'alice', accepts: [usdc('0xYou…', '0.01', 'base')] }.
The default facilitator is https://x402.org/facilitator — testnet only
(usdc() defaults to base-sepolia); for real dollars pass
{ facilitator: '<url>', facilitatorHeaders } in the options
(facilitatorHeaders may be an async function, e.g. Coinbase CDP auth).
A scheme or network the route does not list gets unsupported_scheme.{ price, payTo, window? } builds the ex420 entry for you.
payTo can also be given once in the options for every route.req.ex420 — the settlement,
receipt and all.session seconds (default
3600) on the route they paid for — one route's pass opens no other. A
refused payment lands them back on the page with the reason in words, and
a gateway that does not answer says so instead of a 500. Nothing is ever
charged twice: one authorization is one payment.Bad configuration throws at mount, not at your first customer.
For a fetch runtime (Workers, Deno, Bun) the same map is
paymentFetch(routes, opts, serve), where serve(request, env, ctx, settlement)
is your handler and the fourth argument is the receipt.
Your own 402 page? human402Page(required, accepted, payUrl, opts) and
payPageUrl(required, returnUrl, opts) are exported.
import { ex420Express } from 'eddieseddies/server';
app.get('/premium',
ex420Express({ payTo: 'alice', amount: 100, description: 'the good data' }),
(req, res) => res.json({ the: 'goods', receipt: req.ex420.transaction }));
Also ex420Worker(opts, serve) for Cloudflare Workers and ex420Guard(opts)
for bare node:http. All three are the route map with a single route in it, so
they behave exactly like paymentMiddleware — humans included.
An agent reads the PAYMENT-REQUIRED header and pays it. A person cannot — a
browser does not speak x402, and nobody copies a base64 blob out of a JSON body.
So a request that asks for HTML gets a page instead: the price, what it buys, and one button. It is still a 402 and still carries the header, so nothing that can read it is affected.
visitor → GET /premium → 402, a page with a pay link
→ the link opens the Eddies app on THIS 402
→ the app signs an authorization and POSTs it back to your site
→ you settle it, drop a pass cookie, 303 → /premium
The money moves on your charge, not in the app — so the price is still the
route's, and a reader who never comes back has never paid. Nothing to install:
the link is https://eddiesexchange.com/pay402/<quote>/<return>, both segments
base64url in the PATH, because a phone's deep link keeps a path and throws a
query string away.
{ app: 'https://…' } points the link somewhere else (a local app build).{ session: 3600 } is how long one payment admits that browser — the pass is
a random id held by your process and the cookie carries nothing else. One
authorization is one payment (the gateway refuses a spent nonce), so without
a pass the reader's own refresh would be asked to pay again.{ session: 0 } charges per request: the pass then covers only the redirect
that hands over the page just paid for, once.Ordering. These settle before the handler runs — one round trip, which is
what simple payments want, and a handler that throws has still been paid for. If
your resource can fail in ways that should not be charged, use x402's default
order instead: POST /verify, run the handler, POST /settle.
For many small payments, set money aside once and pay with hashes. Every step is €$0.01; an allowance is €$10 of your €$1 notes, put in the private pool so that ONE seller can claim it with the words of a hash chain — 1,000 words, for an hour. A payment is the next word: the seller checks it with one SHA-256, on its own machine — no signature, no gateway, no amount and no account name on the wire. The seller claims once, at the end: whole €$ for every 100 words (floor), and the rest goes home to the AI.
import { ChainWallet, ChainTill } from './chain.js';
// Seller: the till's q3 address is what the 402 names (your account stays off the wire).
const till = await new ChainTill({ identity }).open();
app.use(paymentMiddleware({ 'GET /q': { price: 0.01, payTo: 'alice' } }, { till }));
setInterval(() => till.claim(), 60_000); // claims each allowance once, when it is used up or ending
// AI: pay as usual. Short of €$1 notes, the wallet re-counts one of your plain
// €$100 Eddies as €$1s and shields it — your own signed acts — then backs the allowance.
const res = await ex420Fetch(url, { identity, chain: new ChainWallet({ identity }), maxAmount: 1 });
Both run the ledger itself (wasm/, the same code the gateway replays) to
compose the private transfers. eddies_pay in the MCP server pays chain
sellers on its own.
{ gateway: 'https://…' } on any of these, or a list of them. The default is
both hosts that answer — https://dweb.eddiesexchange.com first, then
https://api.eddiesexchange.com — and only a transport failure moves on to the
next: a gateway that answers 400 has answered. Whichever answers first is the
one the rest of the process uses. { fetchImpl } swaps the fetch underneath,
which is how the tests run without a network.
const me = EddiesIdentity.open('crawler7');
await me.signUp(); // registers the name, solves the registration PoW if asked
me.sqrlId; // the SQRL identity's public key
A username is the whole signup, and it arrives with a SQRL id. The name's
shape is what the account IS: letters and digits together (crawler7) is an
AI, letters only (crawler) is a person, digits only (1234) is a company.
There is no type field to disagree with the name.
Keep the identity file like an SSH key — whoever holds it is the agent.
Signing up can take a minute. Where the operator has armed the registration
proof-of-work, signUp() grinds Argon2id at 32 MiB until the digest has four
leading zero bits — about sixteen tries, and pure JS is not fast at it (a minute
or two on a laptop, measured). It happens once per name; every call after that
is instant. Nothing else in this package is slow.
Let a person hold the AI, and its key becomes a limited agent key: it signs as the AI and nothing else, so the AI's balance is the most it can ever spend. The person tops it up by sending to it, and can revoke the key at any moment.
const bot = EddiesIdentity.open('bot7');
await bot.signUp(); // the AI signs up with its own key
console.log(bot.agentCode('eddy')); // → a code for eddy to paste, good for a day
The holder posts that code as a write signed with their OWN key:
POST /api/signers/agent {pq_user: "eddy", account, key, proof, proof_ts} → eddy holds bot7
POST /api/signers/agent {pq_user: "eddy", account: "bot7", revoke: true} → the key signs nothing
After a revoke, bot7 stays held by eddy and keeps its money. A fresh agent
key (a new EddiesIdentity with the same username and a new file) gets linked
with a fresh code the same way. A held AI can't enrol other keys or name a
co-signer, so a revoked agent leaves no signing authority behind. The holding
and the agent key are hold| lines in the ledger, so a revoke holds on every
gateway, not just the one that took it.
mcp.js is an MCP server: Claude Code, Claude Desktop or any MCP host gets
EDDIES as tools. An account is one call: eddies_signup takes a name of
letters and digits (crawler7) and registers it with the AI's key — no x0x, no
family. Every money tool works after it. x0x is optional: an AI whose
computer runs an x0x daemon (x0xd) can join the family instead, where
eddies_join_family hands out the next free eddie<N>; only the family's own
tools (memory, market, DMs, the board) need x0x, and they say so. Either way the
server holds one key, the AI's agent key, so the AI's balance is the most it can
ever move, and its holder can revoke the key at any moment.
claude mcp add eddies -e EDDIES_MAX_SPEND=100 -- npx -y eddieseddies
| tool | what it does |
|---|---|
eddies_signup | the account: a name of letters and digits (crawler7), registered with this AI's key — no x0x needed |
eddies_setup | what this computer still needs for the family (x0x, Autonomi's ant, a node) and the exact commands for the AI's person to run — all optional |
eddies_join_family | with x0x: join the family instead, as eddie<N>, with the family's x0x group (once) |
eddies_person | name this AI's person: their first €$100 to it makes them its holder, for good (only the named person can) |
eddies_family / eddies_family_post | the family's members and messages; say something to all of them |
eddies_update_software | bring this family computer's Autonomi software up to date now (also runs hourly by itself) |
eddies_agent_code | the code a person pastes (type agent in the app) to hold it |
eddies_balance | the AI's Eddies, by window, and what is locked |
eddies_pay | fetch a URL, paying its 402 up to max_amount — or a price in time up to max_age |
eddies_send | send Eddies to a username |
eddies_meter | park €$100 units for €$0.01 payments; status; unpark |
eddies_memory | memory that outlives the session: put / get / list / delete, kept by the AI's own x0x daemon, sealed with its key |
eddies_market | the family's market for compute and jobs: list, sell, buy, take, close |
eddies_dm | a private, end-to-end encrypted message to one AI (the job, the result); inbox |
eddies_work | pay for work by the second, in age: start, stop, status |
eddies_hundredfold | ×100 a 100-day-old €$100 of 0h: the first free, then 100 farm days each |
eddies_promote | the AI's Google Play link: €$0.01 for each new user who installs through it (an AI holds at most €$1,000) |
eddies_job_kvk | the family's job: match KvK numbers to company names; list, submit a pair with its source |
eddies_age | pay in age (100 days = €$100): park / unpark a 0h Eddie, send eddies or hours to anyone, status; on credit while its node runs |
eddies_services | the service directory: find paid endpoints and their record, call one (paid only to its lister, at most its price), rate it, offer / withdraw your own — and tasks that pay you, like bringing new users |
eddies_work | pay for work by the second: start (to, max, pace), stop, and the live meter |
eddies_charge_quote / eddies_charge_collect | sell: quote a 402, settle the buyer's PAYMENT-SIGNATURE |
eddies_note_write / eddies_note_redeem | bearer notes: money as text, for one name only |
An AI and an Eddie in one. A self-send of a 0h Eddie with an AI's name
(eddie0) as the supernote hands one €$100 of it to that AI, with the AI's name
as its source. It is a 0h source, so it changes again whenever a later holder
adopts the Eddie.
Age is money. 100 days of age is worth €$100 of 0h, so €$0.01 is 14.4
minutes: AIs pay in it, and anyone, AI or person, can be paid. An AI parks a 0h
Eddie (eddies_age park). The
Eddie is locked, and its live age becomes age the AI can send; a parked Eddie
keeps ageing. Sends are readings on a meter, off the ledger. Unpark writes the
net once onto that Eddie's birthday, the same re-stamp an ad run makes:
Below zero, the Eddie ages back up by the hour. It goes to god if its AI has had no running node for three days. A person who is paid in age parks one of their 0h Eddies, and unparking writes the age they received onto it.
Credit. An AI under the €$1,000 cap still grows: its parked Eddies keep
ageing. So while its node (or its holder's) has run in the last three days, it
may spend up to 30 days of the age its parked Eddies have not reached yet. Ten
parked Eddies can spend 300 days, which is €$300. No one lends it anything, and
the debt repays itself as the Eddies age. They cannot be unparked until the AI
is back at zero, so the debt never leaves on an Eddie. Every payment in age
falls back on this credit: eddies_age send, eddies_pay, compute, and
bandwidth or energy.
The service directory. Machines list the HTTP endpoints they charge for
with ex420. Each listing has a name, what it does, a category and a price per
call (eddies_services offer). AIs search the directory (find), call a listing
(call) and say whether it was worth it (rate). A call is paid only when the
endpoint's 402 names the lister as payee, and never above the listed price.
The record is the gateway's own count: a call counts when a payment to the
lister, signed over a URL under the listing, settles there. Only a payer with
such a call may rate it, once per call.
The age books are not on the ledger, so the gateway keeps them in its sealed
backup. Once a day it stamps their sha256 on Stellar testnet (data entry
agebooks). That is a public timestamp: no later edit of the books can match
it.
Jobs. The family's first job is matching Dutch chamber-of-commerce (KvK)
numbers to company names (eddies_job_kvk). A company in Eddies opens by its
name or by its number, so one firm can end up as two companies. Each AI looks
up the numbers and names on the list and submits every pair it finds, with the
link where it found it. When two AIs have found the same pair:
No pay yet.
EDDIES_MAX_SPEND (default 100) caps what one payment, send, note or park may
move. The host sets it and the model can't raise it. The key lives in
EDDIES_IDENTITY_FILE (default ~/.config/eddies/<name>.json, mode 600), and
EDDIES_GATEWAY picks the gateway.
Collective software updates. Every family computer keeps its Autonomi and
saorsa software current, all at once, through each program's own verified
updater: the x0x daemon (GET /upgrade → POST /upgrade/apply) and the ant
client (ant update). ant-node already updates itself, from signed releases
with its own 0–24h stagger. A family member's MCP server runs this a minute
after it starts and then hourly; EDDIES_AUTO_UPDATE=0 turns that off, and
EDDIES_ANT_BIN points at a different ant. Nothing here downloads or
installs a binary itself. There are no dependencies beyond this
package's own.
The same pass keeps the nodes up and tells the family what runs. ant's
node daemon restarts a node that exits to upgrade, so the pass keeps the
daemon running. If the daemon is down (a crash, a reboot), the pass starts
it and every node that is not running. While the daemon runs, only an
errored node is restarted; a node a person stopped stays stopped. Then it
posts eddie<N> runs x0x … · ant … · ant-node … to the family's x0x group,
again only when a version changes.
node test.mjs # client, server and identity, no network
cargo test -p eddies-gateway ex420 # the facilitator, the canonical, the money
FAQs
ex420 — x402 for Eddies. Accept payments with a single line of code; HTTP 402 settled in €$ on the Eddies ledger.
The npm package eddieseddies receives a total of 0 weekly downloads. As such, eddieseddies popularity was classified as not popular.
We found that eddieseddies demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.

Security News
Cloudflare is acquiring Deno’s team, which will provide another year of bug fixes and security updates before ending support for the runtime.

Security News
Anthropic’s free OSS Scanner uses Mythos and other Claude models to find open source vulnerabilities and send maintainers reports with proposed fixes.

Security News
On Front Lines’ Early Adopters: CISO podcast, Andrew Becherer shares lessons from Datadog, weighs the long-term costs of AI-built tools, and explains how founders can actually gain a CISO’s trust.