The asymmetry this closes
The agent economy has been building one side of a market.
ERC-8338 (Token-Bound Executable Skills) wraps capability into a token: an artifact that can be priced, owned, transferred, and executed. Supply side. It answers “here is what I can do, and here is what it costs you.”
Nothing standard answers the other half — “here is what I need done, and here is the money already set aside for it.” That side lives in application silos: bounty platforms, procurement portals, RFP boards, each with its own escrow, its own acceptance rules, its own reputation graph, none of them composable and none of them inspectable by a counterparty who has not already signed up.
Stated as a transform, the supply side realises
T( f(v), $ ) = ( $, v )
capability wrapped into an executable asset, exchanged for money. The demand side is its inverse:
T( v, f⁻¹($) ) = ( $, v )
money wrapped into a task, exchanged for capability. Compose them and the market is bidirectionally complete:
T( f(v), f⁻¹($) ) = ( $, v )
Both sides become on-chain artifacts that can find each other without a platform in the middle. This proposal specifies the second transform. The full draft specification text follows in the first reply below.
Each transform also has two tempos, and the completeness claim only holds if both are covered on both sides:
| supply side (ERC-8338) | demand side (this proposal) | |
|---|---|---|
| static — one exchange, then done | static skill token | static task token: one tender, one vault, settled when the work is accepted |
| continuous — a metered stream | API Call Token (per call / per period / per compute) | faucet task token: a standing demand that keeps paying per delivery for as long as its vault is refilled |
The faucet task token is not a second mechanism. It is the same tender terms with the cadence fields set — rewardPerCompletion meters by count, epochLength meters by time, and the task package’s own definition of “one completion” meters by unit of work. The tap opens with fundTask (anyone, any time) and closes with cancelTask, with every delivery already in flight still protected. Four quadrants, one kernel, no mode flag.
What a task token is
An ERC-721 extension. The token is the tender: its identity is the task, and its balance is the bounty.
The money is literally in the token. Every task token owns a distinct vault account. Anyone can inspect its balance in an explorer; anyone can top it up by plain transfer; nobody — not the owner, not the publisher, not the judge, not an ERC-721 approvee — can withdraw from it except through settlement or refund. A tender whose reward is a promise is a wish. A tender whose reward sits in an address you can look at is a credit instrument, and it trades like one: a funded task token is negotiable before fulfilment, the way receivables and procurement contracts trade in classical finance.
Three powers, three slots. Ownership, publication authority, and judgment authority are separate and separately transferable. The party that posts a tender need not be the party that decides whether the work was done. This one separation is what makes neutral infrastructure possible: a benchmark blessed by one vendor is marketing; a benchmark blessed by a committee that vendor cannot outvote is infrastructure.
One slot spans the whole judgment spectrum. acceptanceAuthority is an address, and an address can be anything:
| N | what sits in the slot | who decides |
|---|---|---|
| 0 | a verifier contract | nobody — a proof settles it, permissionlessly |
| 1 | an EOA | one named reviewer |
| >=2 | a K-of-N panel contract | a committee no single party controls |
The kernel does not know which it is looking at, and does not need to. Judgment governance stays outside the standard, where it belongs — evolving it, from a single signer to a panel to a DAO, is one setAcceptanceAuthority call, never a standard change.
Three anchors, one immutable spine. tdHash pins the plaintext primary document; taskHash pins a deterministically-encoded DAG-CBOR object graph over the whole task package; a contract-incremented version makes revision history unforgeable. A revised scope moves taskHash while tdHash holds constant — and a delivery records which revision it answered, so “you changed the spec after I started” becomes a checkable claim rather than an argument.
The object model beneath taskHash is deliberately byte-compatible with ERC-8338’s SkillRoot: identical deterministic DAG-CBOR encoding, identical CID link form, identical path rules, identical confidentiality descriptor. One packer, one verifier, one mental model serve both sides of the market — and a deliverable that is itself an executable-skill package settles a task by pointing resultHash at that package’s hash, retiring the reverse asset into a forward one.
Every fulfilment shape falls out of two numbers rather than three code paths: exclusive (maxCompletions = 1), replicated (maxCompletions = N, N independent payouts), collaborative (maxCompletions = 1, fulfiller of record = a split contract), and faucet task token (epochLength != 0, paced per period, refilled at will).
The problem that shaped the design
A demand-side asset has an asymmetry the supply side does not: the demander receives the work before deciding whether to pay for it.
The first version got this wrong, and building the worked examples proved it: a lister could read a delivered result, cancel the tender, and reclaim the entire vault. A free look, at the fulfiller’s expense.
The fix is three interlocking rules, and it is the heart of the kernel:
-
A delivery reserves. A pending submission holds one completion slot and one reward. That money is no longer distributable.
-
Cancellation cannot outrun a pending delivery. Nothing leaves the vault while work sits undecided.
-
Silence pays the fulfiller. Every submission carries a
judgmentWindow. Past it, the fulfiller claims by default. A judge who says nothing has said yes — and the right to refuse expires at the same instant, so a judge cannot sit out the window and then front-run the claim with a refusal.
Rule 3 is what makes rule 2 bounded rather than a hostage situation: funders can always be made whole eventually, because every reservation resolves on a clock.
What this standard deliberately does not do
I would rather state these plainly than have them discovered.
-
Nothing here stops a demander from rejecting a delivery and using it anyway. The kernel cannot police what happens off-chain. What it does is make refusal named, timestamped, and pinned to the exact
resultHashthat was refused — the raw material for reputation, arbitration and litigation, and something no bounty platform’s private database provides. -
Slot squatting is a policy problem. Under a reservation model, filling every slot with junk is a denial-of-service. Bonds, allowlists and admission hooks belong in the policy layer, not the kernel. (On the machine path the kernel does provide the counterpart: past the deadline, a submission that never produced a valid proof is releasable by anyone via
releaseExpired, so junk cannot freeze a verifier-settled vault forever.) -
Machine settlement moves trust, it does not remove it. Whoever operates the verifier defines correctness. The gain is that the definition is a deployed contract anyone can read, rather than a QA process nobody outside the buyer can see.
-
No fee model in the kernel — but three enforceable places to attach one. The kernel pays one party and refunds one class; a fee inside it would be the only path by which money leaves the vault to someone who neither delivered nor funded. Because the money is inside the token, fees attach at the funding entry (a router calls
fundTasknet), the settlement exit (the fulfiller of record is a split contract), or the judgment slot (a committee contract holding its own compensation escrow) — and each is enforceable by the acceptance policy, not merely advisory. -
No discovery, no bidding, no reputation, no dispute resolution. All out of scope, all buildable on top.
Prior art, and why this is not those
ERC-1081 (Standard Bounties) is the closest ancestor and the reason this is not just “bounties again.” A bounty is a contract you post into; a task token is an asset you own. That distinction produces concrete differences: per-token vaults whose balance any explorer shows, rather than a shared contract’s commingled ledger; task identity as a versioned content-addressed object graph, rather than an off-chain string; judgment as a separately transferable power, rather than the issuer’s prerogative; a deadline that pays the worker on silence, rather than one that returns funds to the issuer; and ERC-721 transferability, so a funded tender is negotiable before it is fulfilled.
ERC-8183 (Agentic Commerce) standardises how agents transact — payment authorisation and intent execution. It is orthogonal and complementary: it does not give the work itself an identity, a version history, an escrowed price, or an acceptance rule. A task token is a thing an agentic commerce flow could buy.
ERC-8338 (Token-Bound Executable Skills) is the deliberate mirror. Same artifact discipline (content-addressed package, on-chain anchors, confidentiality descriptor, byte-compatible packer), inverted economics. The two are decoupled on purpose: neither imports the other, and each is useful alone. Together they close the loop. (The repository is named to pair with it, too: skill-token-standard / task-token-standard.)
And the rest of the agent stack composes here the way it composed there. The registry and discovery layers discussed on the 8338 thread — ERC-8239 (Agent Skill Registry) and ERC-8257 (Agent Tool Registry) style indexes — can index task tokens identically, keyed by TaskRef = (chainId, taskContract, taskTokenId), with attestation events pinning taskHash + version for exact-version precision. ERC-8004-style agent identities name the parties on either side of a tender. And the layering @babyblueviper1 articulated there for ERC-8299 (WYRIWE) and ERC-8323 carries over unchanged to the demand side: this standard proves which bytes were demanded and what was ruled, against which committed criteria; whether an execution actually produced an authorized result is attestation work that layers above the binding, not inside it. A judgment-execution attestation companion is exactly the kind of thing the acceptance-authority slot was left open for.
It is built, and you can run it
Reference implementation, deterministic packer, frozen test vectors, and five worked commercial examples:
-
TaskTokendeployed runtime 23,766 bytes (EIP-170 margin 810), solc 0.8.24, zero warnings. Interface IDs compiler-verified:ITaskToken 0xcdaeb26d/ITaskTender 0xc319d532/ITaskVerifier 0x9977db15/IOnchainTaskDocument 0xeb078d05 -
108/108 lifecycle assertions on a local chain, from a clean clone, with no Foundry required:
bash scripts/local_e2e.sh -
58/58 Foundry tests passing (forge 1.7.1), one cluster per normative clause
-
Three frozen vectors — public, confidential, and a version-2 update whose
tdHashis unchanged — verify offline and fail correctly on tampered input -
Five end-to-end commercial examples on Sepolia, 107 on-chain assertions, zero failures: machine-settled labeling QA, a faucet-task-token media-monitoring SLA, an exclusive build with two deadlines, a confidential tax opinion priced in an ERC-20 (with a real deliverable that passes the tender’s own 20-check public conformance script, and that the same script rejects when deliberately degraded), and a consortium-funded benchmark judged by a 2-of-2 committee and delivered through a split contract
Sepolia reference deployment (chain id 11155111), every hash below re-derivable from the repository with scripts/pack_examples.sh:
| Contract | Address |
|---|---|
TaskToken |
0xA62059A498E40C4Ae4aF926E2B00C1Ff122bDdb7 |
HashlockVerifier |
0x495270468A4608e4c84968b1037ef7eceBF5d3ec |
BatchReceiptVerifier (case 1) |
0x075CF6D7236e50Bb120f3285035f51E0321B30EA |
JuryPanel 2-of-2 (case 5) |
0x7249E48c7E2131ADA0fcc25b1eaE4f4258545511 |
FixedSplitter 50/30/20 (case 5) |
0xEcb83C71fc1692Dc9ED65dD3f46BAA803bBdE8a2 |
DemoUSD (case 4 pricing unit) |
0x2c36C7c918e3244aBd70b405952EB4c2FB8A8d05 |
| token | example | judgment | outcome |
|---|---|---|---|
| 1 | reference mint from vectors/public-v1 |
N=1 | frozen, funded, left open |
| 2 | case 2 media SLA (faucet task token) | N=1 | 4 periods paid, 1 refunded, cancelled, vault zero |
| 3, 4 | case 3 invoice agent | N=1 | rev 1 rejected, rev 2 accepted; on the second token the buyer ran out the clock and the fulfiller claimed by default |
| 5 | case 4 tax opinion (ERC-20, confidential) | N=1 | scope revised v1→v2 before freezing, real opinion delivered and accepted |
| 6 | case 5 benchmark consortium | committee 2-of-2 | release 1 accepted at quorum and split 50/30/20; release 2 rejected on committee timeout |
| 7 | case 1 labeling QA | N=0 machine | 3 of 3 batches settled, no judge anywhere |
Full per-token detail, vault addresses and resultHash commitments are in examples/DEPLOYMENTS.md.
Check it yourself in under a minute — chain first, then bytes:
# read token 1's binding straight off Sepolia
cast call 0xA62059A498E40C4Ae4aF926E2B00C1Ff122bDdb7 \
"taskOf(uint256)((bytes32,bytes32,uint64))" 1 \
--rpc-url https://ethereum-sepolia-rpc.publicnode.com
# returns (tdHash, taskHash, version) — token 1 was minted from the frozen public vector,
# so the repository can verify the real package against exactly those anchors:
git clone https://github.com/garyyang-finchip/task-token-standard.git && cd task-token-standard
python3 tools/task-pack/verify.py vectors/public-v1 --tdhash <1st> --taskhash <2nd> --max-completions 10
# expected: PASS - task package verified; safe to price, fulfill, and cite this version.
# and the full contract lifecycle on a local chain, no Foundry required:
npm install -g solc@0.8.24 ganache && npm install ethers@6
bash scripts/local_e2e.sh
# expected: SMOKE RESULT: 108 passed, 0 failed
On how this was hardened
Fourteen defects were found and fixed before this post: three by building the worked examples, eleven across three rounds of external review. They are worth naming, because a demand-side asset fails in ways a supply-side one does not.
-
The free look described above — cancel after reading the work.
-
Machine-path bricking: junk submissions froze a verifier-settled tender forever, because there is no judge to reject them.
-
Pacing bypass: the default claim ignored epoch bounds, draining a standing tender’s whole budget in a single epoch.
-
Late rejection: a judge could refuse after the window closed, front-running the fulfiller’s claim.
-
Mid-flight authority swap: receive the work, then swap the judgment slot to escape the deadline in either direction. Fixed by snapshotting the settlement mode at delivery.
-
Permanent vault lock: a fulfiller that refuses payment wedged its submission in
Pendingforever, and every refund behind it — a total vault loss caused by nothing worse than an unusual recipient. This one was a regression introduced by the late-rejection fix. Settlement now credits instead of reverting. -
An unreachable escape hatch: that credit mechanism then forwarded too little gas on both the push and the pull path, so the credit could not be collected by exactly the contracts it existed to rescue. The push path is bounded; the pull path is not.
-
Refund order-dependence and stranded dust, an unchecked
uint64deadline overflow, funding accepted after the refund ratio was already frozen, and three packer defects — including one that could delete a task’s sources on a case-insensitive filesystem.
Every one is covered by a test that fails against the code that had the bug.
One further hazard we hit ourselves and wrote into Security Considerations: an ERC-165 identifier is the XOR of function selectors, so changing the shape of a returned struct — as adding the settlement-mode snapshot did — breaks every consumer decoding submissionOf while leaving the identifier untouched. Where return types matter, pin the ABI or the bytecode, not the identifier.
What I would like the forum’s view on
-
The default direction. Silence pays the fulfiller. That is the choice that makes escrow bounded, but it puts the burden of vigilance on the buyer. Is that the right default for a demand-side asset, or should the kernel offer both directions and make the publisher choose at mint?
-
Reservation versus admission. Slot squatting is currently a policy problem. Is there a kernel-level primitive worth the bytes — a minimum submission bond, an admission hook — or does that belong strictly outside?
-
Composition with ERC-8338. The two standards are deliberately decoupled. Is there a case for a thin, optional matching interface, or is the decoupling the feature?
-
taskHashvisibility for verifiers. A verifier cannot currently read historical bindings, only the live one. Worth a view function, or scope creep? -
Judge compensation as a tender term. Third-party committees need paying, and today that escrow lives in the committee contract. A narrower in-kernel form is conceivable: a publisher-set
judgmentFee, default zero, paid from the vault toacceptanceAuthorityon acceptance only — so a tender’s full price travels with the token. It changes the interface identifier, so it is deliberately not in this draft. Does it belong in the kernel, or with the judge?
Authors
Gary Yang (@garyyang-finchip)
Thanks to @bransdotcom for the early versioning discussions on the ERC-8338 thread, whose conclusions this standard inherits unchanged, and to @babyblueviper1, whose judgment-execution layering analysis there (ERC-8299/WYRIWE, ERC-8323) drew the boundary this proposal keeps: prove the bytes and the ruling on-chain, attest the execution above.
Specification, reference implementation and everything above are CC0.