Your agent’s access renews itself on-chain at 3am, with nobody awake.
An x402-gated feed on Hedera. One payment, signed once; the Hedera Schedule Service renews the window.
How it works
402 → pay → 200 → still 200.
The ordinary x402 pattern charges on every request: 402, sign, retry, pay, repeat forever. That works for one-off calls and breaks for anything an agent needs continuously — somebody has to keep paying, so somebody has to stay awake. Here the gate is an on-chain subscription that the network renews.
- 01
GET /api/retainer/access → 402A cold agent is charged
The route reads
hasAccess(agent)on-chain, getsfalse, and answers with an x402 challenge:scheme: exact,network: hedera:testnet, native HBAR. - 02
PAYMENT-SIGNATURE → settled by Blocky402It signs one payment
Verified and settled through the hosted Blocky402 facilitator. This is the only thing the agent ever signs.
- 03
subscribeFor(agent) · scheduleCall(0x16b)The payment becomes on-chain state
The server forwards what it received into
RetainerAccess, which charges period one and arms the first renewal with the Hedera Schedule Service. - 04
GET … → 200 · paidThisRequest:falseStill 200 after the window expired
Between the two requests the network executed
renew()itself —CONTRACTCALL,scheduled=true. Nothing was paid, nobody was awake, no cron ran.
The window, live
Every number here is a chain read.
/api/retainer/status asks the contract and nothing else, so this can be polled without ever touching the payment path. Paste an address in the instrument above, or watch the demo agent.
No agent selected. The meter, the balance, the pending schedule and the event log appear here as chain state.
Proof
Check it on Hedera yourself, not on our word.
scheduled=true on a SUCCESS CONTRACTCALL is the ledger saying the transaction had no submitter. The run below is the one the instrument replays: the contract paid for its own executions, re-armed itself twice, and stopped loudly when the money ran out.
| Consensus | UTC | Type | scheduled | Charged | What the contract said |
|---|---|---|---|---|---|
| 1788780167.771453657 | 11:22:47 | ETHEREUMTRANSACTION | false | 1,582,554 gas | SubscriptionStarted · period 60s · 4 ℏ funded · RenewalScheduled 0.0.10406098 |
| 1788780226.016366208 | 11:23:46 | CONTRACTCALL | true | 1.54896 ℏ | Renewed · paid 1 ℏ · balance left 2 ℏ · RenewalScheduled 0.0.10406108 |
| 1788780286.019735208 | 11:24:46 | CONTRACTCALL | true | 1.54896 ℏ | Renewed · paid 1 ℏ · balance left 1 ℏ · RenewalScheduled 0.0.10406116 |
| 1788780346.345418842 | 11:25:46 | CONTRACTCALL | true | 0.0507 ℏ | Renewed · paid 1 ℏ · balance left 0 · Lapsed("balance will not cover the next period") · no schedule |
| Consensus | UTC | Type | scheduled | Charged | What the contract said |
|---|---|---|---|---|---|
| 1788840325.135282208 | 04:05:25 | CONTRACTCALL | true | 1.54327368 ℏ | Renewed · after a 3 ℏ x402 payment opened the window · re-armed 0.0.10416101 |
| 1788840415.078121802 | 04:06:55 | CONTRACTCALL | true | 0.0654 ℏ | CONTRACT_REVERT_EXECUTED Insolvent() — the contract’s own solvency guard rejected the network’s call. Limitation 03. |
| 1788844245.406393732 | 05:10:45 | ETHEREUMTRANSACTION | false | 1,481,020 gas | renew() sent by the seller after creditFor(+8 ℏ) — the restart · RenewalScheduled 0.0.10416711 |
| 1788844334.069565823 | 05:12:14 | CONTRACTCALL | true | 1.54036168 ℏ | Renewed · re-armed 0.0.10416728 |
| 1788844424.147499693 | 05:13:44 | CONTRACTCALL | true | 1.54036168 ℏ | Renewed · re-armed 0.0.10416743 |
| 1788844514.031171208 | 05:15:14 | CONTRACTCALL | true | 1.54036168 ℏ | Renewed · re-armed 0.0.10416751 |
| 1788844604.062103189 | 05:16:44 | CONTRACTCALL | true | 1.54036168 ℏ | Renewed · re-armed 0.0.10416761 |
| 1788844694.152962208 | 05:18:14 | CONTRACTCALL | true | 1.54036168 ℏ | Renewed · re-armed 0.0.10416782 |
| 1788844784.021991773 | 05:19:44 | CONTRACTCALL | true | 1.54036168 ℏ | Renewed · re-armed 0.0.10416798 |
| 1788844874.087845672 | 05:21:14 | CONTRACTCALL | true | 1.53536968 ℏ | Renewed · re-armed 0.0.10416816 |
| 1788844964.085627208 | 05:22:44 | CONTRACTCALL | true | 0.0522288 ℏ | Renewed · balance 0 · Lapsed("balance will not cover the next period") · no schedule |
The recorded run, whole chain, one request
curl -s "https://testnet.mirrornode.hedera.com/api/v1/transactions/0.0.7314364-1788780163-271854529" \
| jq -r '.transactions[] | [.consensus_timestamp, .name, "scheduled=\(.scheduled)", .result, "fee=\(.charged_tx_fee)"] | @tsv'The current deployment's eight renewals, one request
curl -s "https://testnet.mirrornode.hedera.com/api/v1/transactions/0.0.7314364-1788844238-651641588" \
| jq -r '.transactions[] | [.consensus_timestamp, .name, "scheduled=\(.scheduled)", .result, "fee=\(.charged_tx_fee)"] | @tsv'The scheduled calls inherit the transaction id of the EVM call that armed the first schedule, so one response holds that call, every renewal it led to and every SCHEDULECREATE between them.
Three deployments exist — the recorded run’s 0.0.10406083, an intermediate 0.0.10414167, and the current one — and they are not the same code. docs/proof.md keeps them apart and says exactly where they differ.
Contract on HashScan: 0.0.10415845
Consumable by an agent
The same gate, as MCP tools.
The API publishes an OpenAPI 3.1 document at /openapi.json. Registering it with Bazantic produces an HTTP gateway and, from the same document, an MCP server — the tool descriptions an agent reads are that document’s own description strings, verbatim. The spec is the interface; nothing is written twice.
A second gateway wraps one endpoint of Hedera’s Mirror Node as the tool findScheduledExecutions. It is deliberately unpublished: that API is Hedera’s, not ours.
Four tools, from the spec
getAccess · getStatus · info · externalDocs, priced per method: the paid call at 1000 millicents, the chain read at 0. The same asymmetry the contract has.
The gateway is published to Bazantic’s marketplace at retainer-x402.bazgateway.com, currently pending verification.
A recipe that checks the claim
It reads the seller’s own getStatus, then asks Hedera whether the renewal that claim rests on was executed. The vendor asserts; the network confirms.
verification_result verified · access_status true · scheduled_renewals_found 8 in a ten-transaction window · 1788941916.005290514
The endpoint that cannot see a self-renewal
Building it surfaced a finding worth carrying: /api/v1/contracts/{id}/results does not return scheduled executions — it lists calls that arrived as an EthereumTransaction, and a renewal the Schedule Service executes never was one. The scheduled renewal at 1788940215.030907876 was absent from that endpoint’s twenty newest results when measured on 2026-09-09, and is plainly present under /api/v1/transactions?account.id=0.0.10415845 as CONTRACTCALL with scheduled: true. The first version of the recipe queried it, reported none found, and looked entirely correct while doing it. Read the account’s transactions, not the contract’s results.
Questions a judge asks
Answered from the docs, not from the pitch.
Is anything on this page simulated?
/api/retainer/status, a chain read. The recorded run is four transactions from the public mirror node; each row and each chip links to HashScan. There is no offline, mock or demo mode for the product.What does the agent sign?
TransferTransaction under the exact scheme, settled by the Blocky402 facilitator. Renewals are CONTRACTCALLs the network executes from a schedule; scheduled=true is the ledger saying there was no submitter.Who pays for a renewal?
gasReserve. One re-arming renewal cost 1.54896 ℏ on testnet; at 1 ℏ per period the seller loses money on every renewal. That is stated in the README and on /judge, not hidden.What happens when the money runs out?
Lapsed("balance will not cover the next period") and does not re-arm. The last row of each ledger above is that event — 0.0507 ℏ on the recorded run, 0.0522 ℏ on the current deployment — with no SCHEDULECREATE after it. Loud, not silent. It does not restart itself either: from there renew() reverts NotSubscribed(), and opening a subscription again is the way back.Which Hedera Schedule Service calls are load-bearing?
scheduleCall arms the renewal, hasScheduleCapacity is asked before arming so a full second becomes a clean Lapsed rather than a silent stop, and deleteSchedule returns the held gas on cancel(). Remove any one and the product breaks rather than degrades.What this does not claim
Four real limitations. None is fixed here.
01
It loses money at the default price.
A renewal burns ~1.55 ℏ of the seller’s gas reserve to collect 1 ℏ. Pricing a period above the renewal cost is a product decision this build did not make.
02
The metering write is fire-and-forget.
The allowance is enforced by simulating
meter(), but the recording transaction is not awaited. A burst can overshoot by roughly the number in flight.03
One scheduled renewal reverted on the deployed source.
At 04:06:55 the network fired
renew()and the contract’s ownInsolvent()guard rejected it; the run had to be restarted by hand. The numbers point at the gas Hedera reserves on the payer during a scheduled call. Not fixed here.04
A lapse cannot restart itself.
Once
activeis false the contract will not re-arm:renew()revertsNotSubscribed()andfund()only adds money. Opening a subscription again is the way back, so the unattended part runs exactly as far as the funding does.
For whoever is judging this
The 30-second read: the claim, four commands, the measured costs, and the limitations.
No account, no key, no clone. Everything runs against the live deployment and the real Hedera testnet.
contract 0.0.10415845 · retainer.edycu.dev · v1.5.3