Docs / Bundler
Inside Ordeo
The bundler carries your spend so your address stays off it
A bundler is a small service that submits your proof from its own wallet and takes a flat fee out of your notes. It can submit what you built or refuse; it cannot change anything.
On this page
What it is
When you spend notes from your own wallet, your address is the sender and pays gas in ETH. A bundler (keeper/bundler.mjs) sends the transaction instead, from its hot wallet, so its address appears and yours does not. You pay it a flat fee in ORDEO (or ETH for ETH notes), deducted from the notes you spend.
The proof names the bundler and its fee. The pool only lets the named bundler land a spend that pays it, so nobody can copy your proof and race it. Anyone can run a bundler; the site lists the ones it knows.
HTTP interface
| Request | Returns |
|---|---|
GET /info | Bundler address, chain id, pool, router (or null), the flat fee for ETH notes in wei, whether it accepts token fees yet, the flat token fee in base units, and whether it is paused |
POST /relay | A JSON body { "kind": "transact" | "unshield" | "sell", "data": "0x…" }. Answers with the transaction hash, or an error that says why it refused. |
GET /health | Liveness and the number of its transactions still pending |
The fee rule
cost = gas × gasPrice × (160% margin + 10% revert allowance) + recent revert surcharge fee = max(25 ORDEO, cost converted at the REFERENCE price) rounded up ETH notes: max(0.0002 ETH, cost)
/info quotes the fee for a typical spend of 4,700,000 gas; the check at /relay uses a fresh gas estimate for your transaction. The surcharge spreads the gas lost to reverted transactions in the past hour across the successful ones.
The reference price is never the quote of the moment. Every 5 minutes the bundler asks what a 0.01 ETH buy would return, keeps 6 hours of these samples, ignores the newest minute, and takes the median. Samples closer than 4 minutes replace each other. Until it has 12 usable samples (about an hour after a fresh start) it refuses token-fee work with a “still building its price reference” message. Samples are saved to disk, so a restart keeps them.
When it refuses
A bundler protects its own gas. It refuses a request when:
- the calldata does not decode as a pool spend or a router sale, or does not match the kind requested;
- the proof names a different bundler (for a sale: the terms name a different submitter);
- the asset is unknown, or a sale is not of ORDEO notes, or is not a router sale;
- the sale terms are malformed, or the sale expires within a minute;
- the fee is below the computed cost, or the price reference is still warming up;
- a note in the proof is already spent, or already being carried by another request;
- the proof’s root is no longer known;
- a dry run says the transaction would fail, or its gas estimate is above 6,000,000;
- its hot wallet is below 0.002 ETH, or 3 of its transactions are still pending.
Right before signing it runs one more eth_call. Request bodies, proofs and keys are never logged.
Rate limits, back-off and pause
- At most 6 relay requests a minute per IPv4 address or IPv6 /64, 96 per IPv4 /24 or IPv6 /48, and 30 a minute in total. Bodies above 64 KiB are refused.
- A network whose requests end in reverted transactions is backed off for an hour: after 2 reverts for one address, after 6 for the wider network.
- When reverted gas in the last hour reaches 0.01 ETH, the bundler stops taking work for 5 minutes, doubling on each repeat up to 1 hour.
- Requests are processed one at a time, and each transaction gets a gas limit of the estimate plus 20%.
Where its fees and gas come from
By default (BUNDLER_RECYCLE=donate) the bundler donates the ORDEO it earns to the vault. Every 30 minutes, when it holds at least 5,000 ORDEO and has nothing pending, it approves the pool and calls donate. Private payment fees therefore end up with shielded holders too.
Its ETH for gas is topped up by the harvester, paid from the holders’ share of creator fees: up to 0.02 ETH whenever the bundler falls below 0.005 ETH.
Running your own
- Create a hot wallet key with
keeper/make-key.mjs. It writes the key file with mode 400 and prints only the address to fund. - Set
POOL_ADDRESSandTOKEN_ADDRESS(without them it idles), andROUTER_ADDRESSif it should carry sales. The other settings have defaults;keeper/bundler.conf.examplelists them. - Set
BUNDLER_ALLOWED_ORIGINSto the sites whose browsers may call it. - Run it with Docker Compose from
keeper/, or with Node directly, behind HTTPS.
A site offers bundlers through NEXT_PUBLIC_ORDEO_BUNDLER_URL, a comma-separated list tried in order: the first that answers is used, and the next takes over when one refuses.
Without a bundler
If no bundler answers, or each one refuses, you can submit the spend from your own wallet. You pay gas in ETH and your address appears as the sender, but nothing else changes. Your funds never depend on a bundler.