Task Token as a Reverse Asset: token-bound task tenders

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:

  1. A delivery reserves. A pending submission holds one completion slot and one reward. That money is no longer distributable.

  2. Cancellation cannot outrun a pending delivery. Nothing leaves the vault while work sits undecided.

  3. 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 resultHash that 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 fundTask net), 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:

  • TaskToken deployed 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 tdHash is 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 Pending forever, 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 uint64 deadline 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

  1. 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?

  2. 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?

  3. 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?

  4. taskHash visibility for verifiers. A verifier cannot currently read historical bindings, only the live one. Worth a view function, or scope creep?

  5. 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 to acceptanceAuthority on 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.

Full draft specification — part 1 of 3. (The forum’s per-post limit splits the text; parts follow consecutively.)


preamble value
eip 9999
title Token-Bound Task Tenders
description An ERC-721 extension making a task a transferable asset whose vault holds the reward and whose acceptance rule is on-chain
author Gary Yang (@garyyang-finchip)
discussions-to Task Token as a Reverse Asset: token-bound task tenders
status Draft
type Standards Track
category ERC
created 2026-09-05
requires 20, 165, 721

Abstract

This standard binds an ERC-721 token to a task tender: a package of files, led by a primary Markdown document, that specifies demanded work — its interface, acceptance criteria, and fulfillment policy — together with a bounty escrowed in a vault bound to the token itself, paid per accepted completion. It is the demand-side inverse of token-bound executable artifacts: where an executable-skill token wraps supplied value awaiting payment, a task token wraps committed payment awaiting supplied value — and the payment is inside the token, visible on-chain, releasable only by fulfillment.

ERC-721 Asset Layer                    ── who owns this task token (a funded tender is a tradable demand-side asset)
tokenId
├── owner
├── approved / operator
└── tokenURI (Metadata ext., optional) display identity only

Mandatory Task Binding                 ── which task version it binds; who may update; whether it may change
tokenId
├── tdHash                             plaintext primary-document commitment
├── taskHash                           published-package commitment (plaintext or ciphertext)
├── version                            content version
├── taskURI                            mutable transport hint
├── updateAuthority                    independent publication right
└── frozen                             permanently immutable content

Mandatory Tender Layer                 ── where the money is, what is paid, to how many, judged by whom
tokenId
├── vaultOf                            per-token bounty vault: visible to anyone, spendable by no one,
│                                      released only through settlement — the tender's credit foundation
├── tenderTermsOf                      asset / rewardPerCompletion / maxCompletions / submitBy / settleBy
│                                      / epochLength / maxCompletionsPerEpoch          (immutable)
├── acceptanceAuthority                judgment slot: a verifier contract (machine settlement, permissionless)
│                                      or any judging address (human/committee/DAO settlement)
├── escrowBalance / completions        solvency and progress, readable on-chain
├── submissions                        fulfiller / resultHash / cited version / status
└── cancelled                          irreversible tender termination

Optional On-chain Document             ── read the plaintext primary task document from the chain itself
tokenId
├── hasOnchainTaskDocument()           once true, permanently true
└── taskDocument()  →  exact plaintext primary-document bytes

Off-chain Normative Layer              ── what the commitments protect, how to fetch and verify
taskHash
└── TaskRoot
    ├── td { path, cid }               primary document: raw or ciphertext CID
    ├── manifest                       public, machine-readable, minimal schema below
    ├── spec / terms / files           files may be raw or ciphertext CIDs
    ├── acceptance?                    OPTIONAL machine-checkable acceptance criteria
    ├── fulfillment?                   OPTIONAL fulfillment policy descriptor
    ├── confidentiality?               OPTIONAL confidentiality descriptor
    └── prev                           mandatory version chain (absent only at version 1)

External Scalable Rights Layer         ── companion standards; not part of this interface
TaskRef = (chainId, taskContract, taskTokenId)
├── ERC-1155 companions: fulfillment shares / team splits / bid allocations
└── fee and incentive contracts: platform fee routers, judge compensation escrows

Each token defines a canonical task reference. Identity is the hashes; transport is a replaceable hint; publication is a right separate from ownership; judgment is a right separate from both; freezing is an irreversible promise; and the bounty is capital locked in the token’s own vault — anyone can see it, no one can take it, fulfillment releases it. Bidding markets, team revenue splits, subcontract instruments, dispute arbitration, and reputation are deliberately left to companion extensions.

This proposal standardizes task-artifact identity, integrity, versioning, publication governance, and the minimal tender economics — vaulted reward, bounded and optionally epoch-paced completion, machine or judged acceptance, settlement, refund. It intentionally does not define task discovery registries, bidding or auction mechanics, work attestations, reputation, dispute resolution, judgment governance internals, or fee models.

Motivation

Define a trade T over the holdings of two parties: T(S_A, S_B) = (S'_A, S'_B). Classical markets — asset for cash, asset for asset — instantiate this directly. Token-bound executable artifacts (a companion proposal, Token-Bound Executable Skills) extended it to the AI supply side: a producer wraps supplied value v into an executable token f(v), and the market clears as T(f(v), $) = ($, v) — the supply side moves first, and the buyer holds the option to transact.

This proposal completes the other half. A demander wraps committed payment into a task token f⁻¹($) — the inverse construction: not value awaiting a price, but a price awaiting value — and the market clears as T(v, f⁻¹($)) = ($, v): the demand side moves first, and the supplier holds the option to transact. Together the two constructions make agent-economy exchange bidirectionally complete: T(f(v), f⁻¹($)) = ($, v), with the market’s character determined by which side is wrapped first.

Each construction has two tempos. The supply-side artifact is exercised either once — a static skill token — or as a metered stream, priced per call, per period, or per unit of compute — an API Call Token. The demand-side artifact mirrors both: a static task token is one tender settled when its work is accepted, and a faucet task token is a standing demand that keeps releasing payment per delivery for as long as its vault is refilled. The four quadrants — static and continuous, supply and demand — are what make the exchange complete in both directions and both tempos. This standard covers both demand-side tempos with a single kernel: a faucet task token is not a mode but a configuration of the same tender terms (see Epoch pacing).

The wrapping must be literal. A task whose reward is a promise is a wish; a task whose reward sits in a vault bound to the token — inspectable by any explorer, spendable by no one, released only through settlement — is a credit instrument. The vault is the reverse asset’s core value: fulfillers price their work against locked capital, not counterparty reputation, and a funded task token becomes a negotiable instrument in its own right, tradable before fulfillment the way receivables and procurement contracts trade in classical finance.

Demanded work also spans a spectrum the settlement layer must respect. At one end sit fully quantifiable tasks — solve this equation, find this preimage, produce data this oracle confirms — where correctness is checkable by code and no human judgment is needed: submission plus proof should release the vault permissionlessly, with no one in the loop. At the other end sit tasks whose quality is not mechanically decidable — a legal analysis, a market report — where an accountable judgment right must decide. A standard that hardcodes either mode fails the other; this proposal makes the two ends one mechanism (see the acceptance authority slot below).

Today the demand side of the agent economy lives in centralized bounty boards and platform escrow: unverifiable task text that can be silently rewritten after work begins, rewards whose existence cannot be inspected, acceptance decisions with no committed criteria, and no portable record of what exact specification a completion fulfilled. tokenURI cannot fix this; it is a display pointer with no integrity guarantee.

An escrow contract with a text field would be custom metadata. What makes this a standard is the pair of normative definitions beneath the binding: taskHash commits to a deterministic content-addressed object graph (the TaskRoot) covering everything a fulfiller’s judgment of the work depends on — the task document, the machine-actionable specification, the acceptance criteria, the fulfillment policy — and tdHash commits to the plaintext primary document within it. A fulfiller who accepts a tender at version n can prove forever afterward exactly what was asked; an acceptance decision references exactly which specification it judged against.

A task’s token reference is singular; its fulfillment may be plural — one exclusive completion, one completion delivered by a team, many replicated completions each earning the same reward, or a paced series of completions over time. This standard makes cardinality and cadence first-class on-chain facts and leaves the interior of plurality — team splits, bid allocations, subcontract shares — to multi-token companions: ERC-721 makes a demand identifiable; ERC-1155 makes its fulfillment scalable.

Specification

The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “NOT RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119 and RFC 8174.

Core binding interface (mandatory)

interface ITaskToken /* standalone extension interface; see Compliance */ {

    struct TaskBinding {
        bytes32 tdHash;   // SHA-256 digest of the plaintext primary Markdown task document
        bytes32 taskHash; // SHA-256 digest of the encoded TaskRoot as published
        uint64  version;  // content version, starts at 1
    }

    event TaskUpdated(uint256 indexed tokenId, bytes32 tdHash, bytes32 taskHash, uint64 version);
    event TaskURIUpdated(uint256 indexed tokenId, string taskURI);
    event TaskUpdateAuthorityChanged(uint256 indexed tokenId,
                                     address indexed previousAuthority,
                                     address indexed newAuthority);
    event TaskFrozen(uint256 indexed tokenId);

    function taskOf(uint256 tokenId) external view returns (TaskBinding memory);
    function taskURI(uint256 tokenId) external view returns (string memory);
    function updateAuthorityOf(uint256 tokenId) external view returns (address);
    function isTaskFrozen(uint256 tokenId) external view returns (bool);

    function updateTask(uint256 tokenId, bytes32 tdHash, bytes32 taskHash) external;
    function setTaskURI(uint256 tokenId, string calldata taskURI) external;
    function setUpdateAuthority(uint256 tokenId, address newAuthority) external;
    function freezeTask(uint256 tokenId) external;
}

This interface is deliberately isomorphic to the binding interface of Token-Bound Executable Skills: the same three-field binding, the same identity/transport split, the same independent publication right, the same irreversible freeze. One toolchain, one wallet integration, and one mental model serve both sides of the market. Confidentiality adds no field here: encryption is described inside the hash-protected package, not as a token flag.

Tender interface (mandatory)

interface ITaskTender /* own ERC-165 id */ {

    struct TenderTerms {
        address asset;                  // reward asset; address(0) = chain-native currency
        uint256 rewardPerCompletion;    // paid per accepted completion; MUST be > 0
        uint64  maxCompletions;         // completion bound; 0 = unbounded
        uint64  submitBy;               // unix seconds; no submissions after; 0 = none
        uint64  settleBy;               // unix seconds; no settlements after, refunds unlock; 0 = none
        uint64  epochLength;            // seconds per epoch; 0 = no cadence
        uint64  maxCompletionsPerEpoch; // per-epoch settlement bound; MUST be 0 iff epochLength == 0
        uint64  judgmentWindow;         // seconds a judge has to rule on a submission; MUST be > 0
    }

    enum SubmissionStatus { Pending, Accepted, Rejected }

    struct Submission {
        address          fulfiller;   // address of record; MAY be a team splitter or rights contract
        bytes32          resultHash;  // deliverable commitment; see Hash definitions
        uint64           taskVersion; // binding version cited at submission time
        uint64           submittedAt; // block timestamp; starts the judgment clock
        bool             machineSettled; // settlement mode SNAPSHOT at submission time
        SubmissionStatus status;
    }

    event TenderFunded(uint256 indexed tokenId, address indexed funder, uint256 amount, uint256 escrowBalance);
    event TenderCancelled(uint256 indexed tokenId);
    event EscrowReclaimed(uint256 indexed tokenId, address indexed funder, uint256 amount);
    event ResidualReclaimed(uint256 indexed tokenId, address indexed owner, uint256 amount);
    event AcceptanceAuthorityChanged(uint256 indexed tokenId,
                                     address indexed previousAuthority,
                                     address indexed newAuthority);
    event FulfillmentSubmitted(uint256 indexed tokenId, uint256 indexed submissionId,
                               address indexed fulfiller, bytes32 resultHash,
                               string resultURI, uint64 taskVersion);
    event FulfillmentAccepted(uint256 indexed tokenId, uint256 indexed submissionId,
                              address indexed fulfiller, uint256 reward);
    event FulfillmentRejected(uint256 indexed tokenId, uint256 indexed submissionId);
    event FulfillmentClaimedUnjudged(uint256 indexed tokenId, uint256 indexed submissionId,
                                     address indexed fulfiller, uint64 deadline);
    event SubmissionReleased(uint256 indexed tokenId, uint256 indexed submissionId, uint64 deadline);
    event PayoutCredited(uint256 indexed tokenId, uint256 indexed submissionId,
                         address indexed fulfiller, uint256 amount);
    event PayoutWithdrawn(uint256 indexed tokenId, address indexed account, uint256 amount);

    function vaultOf(uint256 tokenId) external view returns (address);
    function tenderTermsOf(uint256 tokenId) external view returns (TenderTerms memory);
    function acceptanceAuthorityOf(uint256 tokenId) external view returns (address);
    function escrowBalanceOf(uint256 tokenId) external view returns (uint256);
    function pendingOf(uint256 tokenId) external view returns (uint64);
    function lockedEscrowOf(uint256 tokenId) external view returns (uint256);
    function completionsOf(uint256 tokenId) external view returns (uint64);
    function completionsInEpochOf(uint256 tokenId, uint64 epoch) external view returns (uint64);
    function isTenderCancelled(uint256 tokenId) external view returns (bool);
    function submissionCountOf(uint256 tokenId) external view returns (uint256);
    function submissionOf(uint256 tokenId, uint256 submissionId) external view returns (Submission memory);

    function fundTask(uint256 tokenId, uint256 amount) external payable;
    function submitFulfillment(uint256 tokenId, bytes32 resultHash, string calldata resultURI)
        external returns (uint256 submissionId);
    function acceptFulfillment(uint256 tokenId, uint256 submissionId) external;
    function settleFulfillment(uint256 tokenId, uint256 submissionId, bytes calldata proof) external;
    function rejectFulfillment(uint256 tokenId, uint256 submissionId) external;
    function claimUnjudged(uint256 tokenId, uint256 submissionId) external;
    function releaseExpired(uint256 tokenId, uint256 submissionId) external;
    function creditOf(uint256 tokenId, address account) external view returns (uint256);
    function withdrawCredit(uint256 tokenId, address account) external;
    function setAcceptanceAuthority(uint256 tokenId, address newAuthority) external;
    function cancelTask(uint256 tokenId) external;
    function reclaimEscrow(uint256 tokenId) external;
    function reclaimResidual(uint256 tokenId) external;
}

Bidding, awards, team splits, and subcontract instruments never enter this interface: the tender layer holds the vault, records who submitted what against which version, enforces counting, pacing, and solvency, and settles — nothing more.

Verifier interface (for machine-settled tasks)

interface ITaskVerifier /* own ERC-165 id */ {
    /// @notice Decide a submission mechanically. MAY be stateful (streaks, rate limits).
    /// @dev Called by the task contract during settleFulfillment. A false return or
    ///      revert means the proof does not establish fulfillment; it is NOT a rejection.
    function verifyFulfillment(
        address taskContract,
        uint256 tokenId,
        uint256 submissionId,
        address fulfiller,
        bytes32 resultHash,
        bytes calldata proof
    ) external returns (bool);
}

A task contract does not implement this interface; verifier contracts do, and a task’s acceptance authority slot points at one to make the task machine-settled (below).

On-chain document interface (optional)

interface IOnchainTaskDocument /* own ERC-165 id */ {

    /// @notice Whether a plaintext on-chain copy of the primary document exists for tokenId.
    /// @dev MUST revert for nonexistent tokenId. Once true, MUST remain true permanently.
    function hasOnchainTaskDocument(uint256 tokenId) external view returns (bool);

    /// @notice The exact plaintext UTF-8 bytes of the primary task document.
    /// @dev MUST revert when hasOnchainTaskDocument(tokenId) == false.
    function taskDocument(uint256 tokenId) external view returns (bytes memory document);

    /// @notice Atomically update the on-chain document together with the binding.
    /// @dev tdHash is computed in-contract as sha256(document).
    function updateTaskWithDocument(uint256 tokenId, bytes calldata document, bytes32 taskHash) external;

    /// @notice Publish the current primary document on-chain without a version change.
    /// @dev MUST revert unless sha256(document) == taskOf(tokenId).tdHash.
    ///      MUST NOT change taskHash or version. Sets hasOnchainTaskDocument permanently true.
    function publishTaskDocument(uint256 tokenId, bytes calldata document) external;

    event TaskDocumentPublished(uint256 indexed tokenId, bytes32 tdHash);
}

The semantics mirror the executable-skills on-chain document extension exactly: existence is permanent; when a copy exists, sha256(taskDocument(tokenId)) MUST equal taskOf(tokenId).tdHash at all times; when hasOnchainTaskDocument(tokenId) == true and a proposed update would change tdHash, plain updateTask MUST revert and the update MUST go through updateTaskWithDocument; the on-chain document is exclusively the plaintext primary Markdown document, and for tasks whose primary document is confidential, hasOnchainTaskDocument MUST return false and taskDocument MUST revert.

Compliance

  • The contract MUST implement ERC-721 and ERC-165.

  • supportsInterface MUST return true for ITaskToken and ITaskTender, and additionally for IOnchainTaskDocument when implemented. The interfaces deliberately do not inherit IERC721.

  • A compliant contract SHOULD implement the ERC-721 Metadata extension. If tokenURI is implemented, it MUST NOT be used as a verification source; it MAY mirror facts such as escrowed: true or frozen: true for wallets, without authority.

Identity versus transport

The task’s identity is (tdHash, taskHash, version). taskURI is a transport hint and is not part of the identity:

  • setTaskURI MUST NOT change version and MUST emit TaskURIUpdated. Replacing a dead endpoint never constitutes a new task version. taskURI is a single URI; multi-mirror retrieval is expressed by URI resolvers or off-chain retrieval manifests, not by this field.

  • taskURI MUST NOT be treated as a trust input: consumers MUST verify all retrieved content against the commitments and MUST discard non-matching content, whatever its source.

The three separated powers

Ownership, publication, and judgment are three distinct rights:

  • Owner holds the tender as an asset. ERC-721 transfers move only this.

  • Update authority governs publication: only it may call updateTask, updateTaskWithDocument, setTaskURI, setUpdateAuthority, freezeTask, and cancelTask. It is initialized at mint. setUpdateAuthority(tokenId, address(0)) MUST revert: walking away from a live tender is expressed by cancelTask or by freezing and letting deadlines run out — never by burning the authority.

  • Acceptance authority governs judgment. This one slot spans the whole spectrum of demanded work:

    • When the slot holds a contract that declares ITaskVerifier via ERC-165, the task is machine-settled: correctness is decided by code, and settlement is permissionless (see Machine settlement below). Fully quantifiable tasks — solve an equation, reveal a preimage, produce oracle-confirmed data — need no judge and get none.

    • When the slot holds any other address — an EOA, a multisig, a jury-panel contract, a DAO executor, an attested AI judge — the task is judged: only that address may call acceptFulfillment and rejectFulfillment.

    • Only the current acceptance authority may transfer the slot, and transfer to address(0) MUST revert. The contract MUST emit AcceptanceAuthorityChanged at every change, including the genesis assignment. Because judgment is one transferable address, its governance can evolve — a platform treasury signer at cold start, a K-of-N panel later, a DAO after that — without touching the token or this standard: the internals of judgment governance are deliberately outside this specification.

The slot thus expresses the full K-of-N judgment spectrum through one address: N = 0 — a verifier contract, no judge at all, quantified work settles automatically; N = 1 — a bare address, the platform or the task creator says so; N ≥ 2 — a panel contract organizing K-of-N votes internally. The two ends need no panel machinery: “no judge” is a verifier, “one judge” is an EOA.

ERC-721 transfers MUST NOT change either authority; ERC-721 approved/operator approvals MUST NOT confer any authority power.

The task vault

Every task token has a vault: a distinct per-token account holding the tender’s bounty. The vault is the reverse asset’s credit foundation — the money is demonstrably in the token.

  • vaultOf(tokenId) MUST return the vault’s address, assigned at mint and never changed. The vault MUST be an account distinct from the task contract and unique to the token, so that its balance is directly observable on-chain by anyone.

  • Funds MUST leave the vault only through this standard’s settlement path (reward payouts on acceptance/settlement) and refund path (reclaimEscrow). In particular, the token owner, the update authority, the acceptance authority, and any ERC-721 approvee MUST NOT be able to withdraw from the vault by any other means. A vault that its owner can drain is not a tender; it is a promise.

  • escrowBalanceOf(tokenId) MUST equal the vault’s live balance in the tender asset.

  • Assets transferred directly to the vault address MUST count toward escrow (they raise escrowBalanceOf and are payable to fulfillers). Only deposits made through fundTask are attributed for refunds; unattributed deposits are bounty gifts. Rewards MUST consume gifts before attributed funds, and on refund the unspent residual of gifts belongs to the token owner, never to funders (see Cancellation and refund).

  • The vault mechanism is implementation-defined: an ERC-6551 token-bound account with a locked account implementation is the RECOMMENDED profile (the task NFT literally is a wallet holding its own bounty, composable with the token-bound-account ecosystem); a minimal dedicated vault contract deployed per token satisfies the invariants equally. Whatever the mechanism, the invariants above are normative.

Tender terms

TenderTerms are fixed at mint and MUST NOT be mutable thereafter. A tender whose price, cardinality, cadence, or deadlines change is a different demand: mint a new task. Constraints:

  • rewardPerCompletion MUST be greater than zero. asset designates one reward asset per token: address(0) for the chain-native currency, otherwise an ERC-20 token address.

  • maxCompletions == 0 means unbounded replicated completion; any other value bounds accepted completions to exactly that count.

  • When both submitBy and settleBy are nonzero, settleBy MUST be greater than or equal to submitBy.

  • epochLength and maxCompletionsPerEpoch MUST be both zero or both nonzero. When nonzero, they pace the tender (see Epoch pacing).

  • judgmentWindow MUST be greater than zero. It is the time a judge has to rule on a delivery before the fulfiller may claim the reward that delivery reserved (see The judgment deadline). It is required even when the tender mints with a verifier in the judgment slot, because that slot is transferable: any machine-settled tender can become judged later, and a judged tender with no deadline is a tender with no obligation.

Cardinality composes with the fulfiller field to express every fulfillment shape without new mechanisms: maxCompletions == 1 with a single submitter is an exclusive task; maxCompletions == 1 with a team’s split contract as the fulfiller of record is a collaborative task; maxCompletions == N (or 0) pays each accepted completion the same reward — a replicated task. The in-package fulfillment descriptor (below) declares which shape is intended; the contract enforces the arithmetic.

Epoch pacing (faucet task tokens)

A tender with epochLength != 0 is a standing tender — in market terms, a faucet task token: a continuing demand that pays per delivery for as long as the vault is refilled. It is the demand-side inverse of the supply-side metered-usage asset (an API Call Token): where prepaid credits burn per call inward, here a prefunded vault releases per delivery outward. The three ways a metered supply asset is priced map onto three things this kernel already has, with no additional field:

metered supply asset prices by a faucet task token pays by kernel expression
count accepted delivery rewardPerCompletion per completion; maxCompletions bounds the total, 0 leaves it open-ended
time epoch epochLength and maxCompletionsPerEpoch
unit of compute unit of work the completion itself: the task package defines what one completion is, and the kernel pays a flat price per unit

The tap is opened by fundTask, which anyone may call at any time, and closed by cancelTask, which cannot outrun a delivery already in flight.

  • Epochs are absolute: epoch index e = block.timestamp / epochLength.

  • At most maxCompletionsPerEpoch completions may be settled (accepted) in any one epoch; a settlement beyond the bound MUST revert. completionsInEpochOf(tokenId, e) exposes per-epoch progress.

  • Pacing bounds settlement, not submission: submissions remain governed by submitBy and total-bound rules alone.

  • Provider-continuity policies — the same fulfiller each epoch, streak conditions, forfeiture on a miss — are acceptance policy (a stateful verifier or a judging authority enforces them), not kernel rules. Escalating or variable rewards are outside this standard: the kernel pays a flat rewardPerCompletion; bonus schedules belong to companion contracts.

A daily-report subscription is then: rewardPerCompletion = R, epochLength = 1 day, maxCompletionsPerEpoch = 1, maxCompletions = 365, vault topped up monthly — one token, one service relationship, 365 asynchronous settlements.

Funding

  • fundTask MAY be called by anyone, before or after freezing, any number of times: third parties can raise a bounty they did not post. For a native-asset tender, msg.value MUST equal amount and MUST be forwarded to the vault; for an ERC-20 tender, msg.value MUST be zero and the contract MUST move exactly amount into the vault via transferFrom, measuring by the vault’s balance difference if the token is nonstandard. Each funding MUST emit TenderFunded and MUST be attributed to the funder for refund accounting. Once the refund pool and denominator have been fixed (see Cancellation and refund), fundTask MUST revert: a contribution arriving after the denominator was taken would be reclaimable only against a share it never bought, and the difference would remain in the vault with no claimant.

  • Funding a cancelled tender through fundTask MUST revert.

Full draft specification — part 2 of 3, continued (from “Submission”).

Submission

  • submitFulfillment records msg.sender as the fulfiller of record together with resultHash, the current binding version, and status Pending, and MUST emit FulfillmentSubmitted. Submission ids are sequential per token starting at 1.

  • resultHash MUST NOT be bytes32(0). resultURI is a transport hint for the deliverable with the same non-authority status as taskURI.

  • A submission reserves a slot and a reward. Submissions MUST revert when the tender is cancelled, after submitBy when nonzero, after settleBy when nonzero, when completionsOf + pendingOf has already reached a bounded maxCompletions, and when escrowBalanceOf < (pendingOf + 1) * rewardPerCompletion. pendingOf counts submissions with status Pending; lockedEscrowOf is pendingOf * rewardPerCompletion clamped to the vault balance.

  • The reservation is what makes delivery safe. Counting the bound from accepted completions alone would let a demander exhaust it with other completions while sitting on a delivery it had already read; leaving solvency to settlement time alone would let a demander take delivery into a vault that could never pay. Both are closed by refusing the submission in the first place, which also tells a fulfiller before working whether the work can be paid for.

  • Submission is permissionless at this layer. Eligibility narrowing — awarded bidders only, staked fulfillers only, allowlists — belongs to the acceptance decision and to companion bidding standards, not to the kernel: acceptance is the single gate that makes a submission count.

Settlement

Settlement releases the vault. There are two paths into it; both share the same preconditions and effects.

Common preconditions — settlement of a submission MUST revert unless: the submission exists with status Pending; settleBy (when nonzero) has not passed; a bounded maxCompletions has not been reached; the epoch bound (when pacing is on) has not been reached in the current epoch; and escrowBalanceOf(tokenId) >= rewardPerCompletion. Cancellation is deliberately not among these conditions: a cancelled tender MUST still settle the submissions that were Pending when it was cancelled.

Common effects — the contract MUST, atomically: set the submission’s status to Accepted, increment completionsOf (and the current epoch’s count when pacing is on), pay rewardPerCompletion from the vault to the submission’s fulfiller of record, and emit FulfillmentAccepted. State changes MUST precede the asset transfer.

A settlement MUST NOT depend on the recipient being willing to receive. Where the transfer to the fulfiller of record fails — a contract with a reverting fallback, a token that refuses the recipient — the settlement still stands and the amount MUST be credited to that fulfiller, retrievable later through withdrawCredit, with PayoutCredited emitted. Reverting instead would wedge the submission in Pending forever: acceptance would fail, the judgment window would close refusal off, the default claim would fail on the same transfer, and every refund behind it would stay blocked by the outstanding reservation — a permanent loss of the whole vault caused by nothing worse than an unusual recipient. Credited amounts are owed, not escrowed: they MUST be excluded from what reclaimEscrow and reclaimResidual may distribute, and from the solvency measured when reserving or settling. An implementation MAY bound the gas forwarded when pushing a payout during settlement, since a recipient that consumes an unbounded amount would burden the settling party; if it does, the budget MUST be large enough for an ordinary contract recipient to record the payment, and the pull path (withdrawCredit, reclaimEscrow, reclaimResidual) MUST NOT be bounded in the same way — there the beneficiary pays its own gas, and a cap there would strand exactly the recipients the credit exists to rescue. Likewise, reading an asset’s transfer reply MUST NOT be able to revert the settlement: a reply that cannot be decoded MUST be treated as a failed transfer and credited, not thrown, or a token with a malformed return value reintroduces the permanent wedge.

Judged pathacceptFulfillment(tokenId, submissionId): callable only by the acceptance authority.

Machine pathsettleFulfillment(tokenId, submissionId, proof): callable by anyone (typically the fulfiller, self-serving the payout), and only when the acceptance authority declares ITaskVerifier via ERC-165. The contract MUST call verifyFulfillment(this, tokenId, submissionId, submission.fulfiller, submission.resultHash, proof) on the authority and MUST revert unless it returns true. A failed verification is not a rejection: the submission stays Pending and may be settled later with a valid proof. When the acceptance authority does not declare ITaskVerifier, settleFulfillment MUST revert.

RejectionrejectFulfillment (judged path only) sets status Rejected and emits FulfillmentRejected. Rejection is terminal for that submission — a rejected submission MUST NOT later be accepted or settled — and never blocks the same fulfiller from submitting again. Rejection moves no assets. It MUST revert once block.timestamp > submittedAt + judgmentWindow: the right to refuse ends at exactly the instant the right to claim begins, so the two are complementary with neither gap nor overlap.

Settlement judges a submission against the task version it cited. The taskVersion recorded in the submission, together with the TaskUpdated event history, lets any observer reconstruct exactly which specification bytes a completion was paid for.

The judgment deadline

A demander that can read a delivery and then simply do nothing has been given the work for free. The kernel removes that option by putting a clock on judgment.

  • Which deadline applies to a submission is decided by machineSettled, recorded when the delivery was made, not by the acceptance authority as it stands later. submitFulfillment MUST set it from whether the acceptance authority declares ITaskVerifier at that moment. Routing off the live slot would let a demander receive work under a judge, swap a verifier into the judgment slot, and thereby convert a delivery it owes money on into one that merely expires — the deadline must follow the delivery, not the slot.

  • The snapshot governs the two deadline paths only. acceptFulfillment, rejectFulfillment, and settleFulfillment deliberately follow the acceptance authority as it stands: whoever holds judgment now is who may rule now, which is what makes the authority transferable at all. What must not move under a delivery is the remedy for nobody ruling, because that is the fulfiller’s protection rather than the demander’s prerogative. Implementations MUST NOT route the deadline paths off the live slot, and MUST NOT route the ruling paths off the snapshot.

  • claimUnjudged(tokenId, submissionId) is permissionless and MUST settle the submission — same effects as any other settlement — when all of the following hold: the submission’s machineSettled is false; its status is Pending; block.timestamp > submittedAt + judgmentWindow; and the vault holds at least rewardPerCompletion. It MUST emit FulfillmentClaimedUnjudged before FulfillmentAccepted, so that an observer can tell a default from a ruling.

  • claimUnjudged MUST ignore cancellation and MUST ignore settleBy. Those are the two levers a demander would otherwise pull to run out the clock on work it has already received.

  • The deadline submittedAt + judgmentWindow MUST be computed in a width that cannot overflow. judgmentWindow is a uint64 and type(uint64).max is a legal value; computing the sum in uint64 panics rather than reverting cleanly, which turns an odd configuration into an unhandled failure.

  • claimUnjudged MUST NOT ignore the epoch bound. A default is still a settlement and is still paced: on a standing tender a provider may deliver many periods’ work at once (pacing bounds settlement, not submission), and a default that skipped the cadence would let a judge’s silence drain the whole budget in a single epoch — the precise outcome the cadence exists to prevent. A claim blocked this way is queued, not lost: it becomes available in the next epoch.

  • claimUnjudged MUST revert when the submission’s machineSettled is true. On the machine path there is no judge to default: code already ruled, and a submission left Pending there is one whose proof did not verify. Without this rule, waiting would become a way to be paid for garbage.

  • releaseExpired(tokenId, submissionId) is the machine path’s counterpart, selected by the same machineSettled snapshot, and is also permissionless: past the same deadline, a submission that never produced a valid proof MUST be releasable, setting its status to Rejected, freeing its reserved slot and reward, and emitting SubmissionReleased before FulfillmentRejected. It MUST revert on the judged path. Without it the reservation rule would be a griefing weapon: on a machine-settled tender there is no judge to reject junk, so filling every slot with unverifiable submissions would freeze the vault permanently. A solver who was merely slow loses nothing but a resubmission.

  • Judging remains a right, not a duty — but an unexercised right now expires in the fulfiller’s favour rather than the demander’s, and it expires completely: after the window a judge can no longer refuse at all. Without that, a judge could sit out the whole window and then front-run the fulfiller’s claim with a refusal, which would leave the deadline decorative — and under pacing it would be worse still, because a claim queued behind the epoch bound waits in the open for a whole epoch. A judge that believes a delivery is inadequate MUST say so with rejectFulfillment, on the record, within the window.

The deadline direction is deliberately the opposite of the timeout default in a K-of-N judging contract, where a stalled vote resolves to rejection. There, the party timing out is a juror with no stake in the outcome and the safe default is to change nothing. Here, the party timing out holds the counterparty’s money and has already received the counterparty’s work; the safe default is to complete the trade.

Cancellation and refund

  • cancelTask is callable only by the update authority, MUST be irreversible, and MUST emit TenderCancelled. After cancellation: no funding and no new submissions. Cancellation is a tender act, not a binding act: it does not change tdHash, taskHash, version, or frozen.

  • Cancellation MUST NOT void work already delivered. Submissions that were Pending at cancellation MUST remain acceptable, rejectable, settleable, and claimable exactly as before; their reserved rewards stay locked in the vault. A demander may stop buying; it may not un-receive what it has already been given.

  • reclaimEscrow and reclaimResidual MUST revert while pendingOf > 0. Nothing leaves the vault while delivered work is still undecided, and the wait is bounded: every Pending submission becomes claimable by its fulfiller at submittedAt + judgmentWindow, so funders can always be made whole eventually.

  • reclaimEscrow MUST revert unless the tender is cancelled or settleBy is nonzero and has passed. The distributable pool and the denominator MUST be fixed at the first reclaim and reused for every later one, so that what a funder receives does not depend on the order in which funders happen to claim. Flooring dust remains in the vault and accrues to the token owner as residual; for that to hold, the attributed pool MUST be released once the last outstanding contribution has been reclaimed, or the dust stays inside the attributed pool, is subtracted from every residual calculation, and is locked in the vault permanently. A funder reclaims their proportional share of the remaining attributed pool — attributed contributions minus the attributed portion of rewards paid — as contributed × attributedPool / totalOutstandingContributions, after which their outstanding contribution is zero. Each reclaim MUST emit EscrowReclaimed.

  • reclaimResidual, under the same gate, pays the vault’s residual — unattributed gifts not consumed by rewards, plus rounding dust — to the token owner, and MUST emit ResidualReclaimed. Rewards consume gifts before attributed funds. Distributing ownerless money pro rata among funders would misallocate it; the residual accrues to the holder of the tender asset instead.

Freezing

  • freezeTask MUST be irreversible and MUST emit TaskFrozen. Once frozen, updateTask and updateTaskWithDocument MUST revert forever.

  • Freezing binds content — tdHash, taskHash, version — not transport and not the tender: setTaskURI, setUpdateAuthority, funding, submission, settlement, cancellation, and reclaim all remain callable.

  • A frozen tender is a binding tender: fulfillers know the specification cannot shift beneath their work. Publishers SHOULD freeze before funding; wallets and fulfillment tooling SHOULD surface an unfrozen live tender as a warning.

Existence and update behavior

  • All ITaskToken and ITaskTender views on a nonexistent tokenId MUST revert.

  • At mint the binding, vault, and tender MUST be fully populated: version MUST equal 1, both authorities MUST be nonzero, tdHash and taskHash MUST NOT be bytes32(0), the terms constraints above MUST hold, and the contract MUST emit TaskUpdated, TaskUpdateAuthorityChanged(tokenId, address(0), initialAuthority), and AcceptanceAuthorityChanged(tokenId, address(0), initialAcceptanceAuthority).

  • Updates MUST set tdHash and taskHash atomically in one call. The new taskHash MUST differ from the current one. tdHash MAY remain unchanged when the primary document is unchanged — editing the acceptance harness, the fulfillment descriptor, or companion files legitimately changes only taskHash.

  • Callers never pass version: the contract increments it by exactly 1 per successful update and MUST revert on uint64 overflow. Versions are append-only.

Hash definitions (normative)

  • tdHash = SHA-256(plaintext primary Markdown task document raw bytes). No line-ending, whitespace, BOM, or Unicode normalization; no salting or keying of any kind. The commitment is always to the exact plaintext, even when the published copy is encrypted.

  • taskHash = SHA-256(deterministically encoded TaskRoot bytes) — the package as published, which under confidentiality means the ciphertext objects. It is never the hash of a zip, tar, or archive file.

  • resultHash is the fulfiller’s commitment to the deliverable as delivered. It SHOULD be the SHA-256 digest of a deterministically encoded object graph under the same encoding rules as the TaskRoot; when the deliverable is itself a token-bound executable artifact, resultHash SHOULD equal that artifact’s package hash, closing the loop between demand-side and supply-side standards. For machine-settled tasks, the verifier’s declared profile defines how resultHash binds to the proof (e.g. a preimage commitment bound to the fulfiller’s address). Consumers MUST treat resultHash as opaque except as declared by the tender’s acceptance or delivery profile.

Package paths (normative)

All paths in this standard (td.path, spec.path, acceptance.path, terms.path, files keys, confidentiality objects keys) MUST be relative, case-sensitive UTF-8 POSIX paths. Paths MUST NOT begin with /; MUST NOT contain \, empty segments, or the segments . or ... No path normalization is performed: paths match byte-for-byte or not at all.

TaskRoot (normative off-chain object)

TaskRoot MUST be encoded as deterministic DAG-CBOR per the DAG-CBOR specification and RFC 8949 core deterministic encoding: definite lengths, shortest-form integers, and map keys sorted by the bytewise lexical order of their encoded bytes. This object model — encoding rules, link form, path rules, and codec assignments — is deliberately byte-compatible with the SkillRoot of Token-Bound Executable Skills: one deterministic toolchain serves both standards.

task-root = {
  "td": td-entry,                   ; primary task document
  "manifest": link,                 ; machine-readable self-description; MUST be public
  "spec": spec,                     ; machine-actionable task interface
  ? "acceptance": acceptance,       ; acceptance criteria / test harness
  ? "fulfillment": link,            ; fulfillment policy descriptor; MUST be public when present
  ? "terms": terms-entry,           ; legal/commercial terms
  ? "files": { * tstr => link },    ; remaining files; raw or ciphertext CIDs
  ? "confidentiality": link,        ; OPTIONAL confidentiality descriptor; absent = fully public package
  ? "prev": link                    ; version chain, rules below
}
td-entry    = { "path": tstr, "cid": link }
terms-entry = { "path": tstr, "cid": link }
spec        = { "path": tstr, "cid": link, "profile": tstr }
acceptance  = { "path": tstr, "cid": link, "profile": tstr }
link   = #6.42(bytes .size (37))
  ; DAG-CBOR link: tag 42 over a byte string of exactly 37 bytes:
  ;   0x00                       identity multibase prefix
  ;   0x01                       CIDv1
  ;   0x71 (dag-cbor) / 0x55 (raw)   codec varint
  ;   0x12 0x20                  sha2-256 multihash prefix
  ;   32-byte digest

  • Optional fields MUST be omitted when absent; null MUST NOT appear. TaskRoot, td-entry, terms-entry, spec, and acceptance are closed maps (extensibility lives in the manifest).

  • Primary document: a TaskRoot has exactly one primary document. td.path names it within the package namespace and MUST NOT appear as a key of files. Likewise terms.path, when present, MUST NOT appear as a key of files and MUST be distinct from td.path, spec.path, and acceptance.path. spec.path and acceptance.path are resolved through files (interlock rules below), except that spec.path MAY equal td.path for tasks whose Markdown document is itself the machine-actionable interface; acceptance.path MUST differ from td.path. td.cid MUST identify the exact published bytes of that document. The publisher MAY choose the filename; TASK.md SHOULD be used as the conventional default. The document MUST be UTF-8 encoded text and SHOULD be Markdown; consumers MUST NOT determine document type from the extension alone.

  • Spec: spec names the machine-actionable statement of the demanded work — an API schema, an I/O contract, a benchmark harness, an agent instruction. spec.profile is an opaque identifier of the specification format; unknown profiles MUST NOT be interpreted by guesswork. If spec.path == td.path, spec.cid MUST equal td.cid; otherwise files[spec.path] MUST exist and equal spec.cid.

  • Acceptance: when present, acceptance names the criteria or executable harness by which completions are judged — for machine-settled tasks, this is where the verifier’s checking logic is committed; for judged tasks, the rubric the authority answers to. acceptance.profile is an opaque identifier of the judgment scheme. Unknown profiles MUST NOT be judged by guesswork. files[acceptance.path] MUST exist and equal acceptance.cid. Committing acceptance criteria inside taskHash is what turns judgment from discretion into an auditable commitment.

  • Version chain (prev): at version == 1, prev MUST be omitted; at version > 1, prev MUST be present and the digest inside its CID MUST equal the taskHash of the immediately preceding version.

  • Package boundary: everything a fulfiller’s work and the settlement decision depend on MUST be reachable from the TaskRoot; unreachable content enjoys no protection under taskHash.

Manifest (normative minimal schema)

The manifest is a public, machine-readable JSON document. REQUIRED fields: schemaVersion, name, taskVersion, summary, specPath. manifest.specPath MUST equal TaskRoot.spec.path. Consumers MUST ignore unknown manifest fields; extension fields SHOULD use namespaced keys (e.g. x-vendor/*). Human-readable version strings live here (taskVersion), never on-chain.

Fulfillment descriptor (optional, in-package; normative schema)

When the fulfillment link is present, it MUST resolve to a public JSON descriptor declaring the intended fulfillment shape. REQUIRED fields: schemaVersion, mode, maxCompletions. mode is one of "exclusive", "collaborative", "replicated"; further fields (teaming, subcontracting, eligibility, delivery) carry profile-identified policies.

{
  "schemaVersion": "1.0",
  "mode": "replicated",
  "maxCompletions": 100,
  "teaming":        { "allowed": true,  "splitProfile": "x-erc1155-shares-v1" },
  "subcontracting": { "allowed": true },
  "eligibility":    { "profile": "x-open-v1" },
  "delivery":       { "profile": "x-encrypted-to-authority-v1" }
}

  • maxCompletions in the descriptor MUST equal the on-chain TenderTerms.maxCompletions (with JSON 0 likewise meaning unbounded). A package whose descriptor disagrees with the chain is invalid.

  • All profile identifiers are opaque. A runtime encountering an unknown eligibility, split, or delivery profile MUST NOT improvise its semantics.

  • The descriptor is policy declaration, not enforcement: the contract enforces counting, pacing, solvency, deadlines, and authority; the acceptance authority enforces everything the descriptor declares. This division is deliberate — see Rationale.

Confidentiality descriptor (optional, in-package; normative schema)

Confidential tasks — proprietary specifications, private acceptance harnesses, competition tasks whose full statement is revealed only to committed fulfillers — use the identical confidentiality mechanism as Token-Bound Executable Skills: an in-package public JSON descriptor with REQUIRED fields schemaVersion, mode, profile, objects, where every encrypted object appears under its package path with its ciphertext CID (which MUST equal the corresponding TaskRoot link) and its plaintextHash. If the primary document is encrypted, objects[td.path].plaintextHash MUST equal the token’s tdHash. The manifest and both descriptors themselves MUST remain unencrypted. Key rotation re-encrypts content and MUST be published as a version update. Unknown confidentiality profiles MUST NOT be decrypted by guesswork.

Minimal Task Profile (informative)

Most tasks posted by ordinary demanders are simple, and the standard’s floor is deliberately low. A minimal task needs exactly one document and one number:

  1. Write TASK.md. Set spec.path == td.path (the document is the interface); the packing tool auto-generates the two-field manifest. The TaskRoot has two leaves.

  2. Optionally publish TASK.md on-chain via publishTaskDocument — the task then exists with zero off-chain infrastructure: statement on the chain, bounty in the vault.

  3. Point the acceptance authority at a stock verifier (a hash-preimage verifier for know-the-answer bounties, a signature verifier for oracle-confirmed outcomes) for machine settlement — or at the demander’s own address for judged settlement.

  4. Mint with rewardPerCompletion and maxCompletions = 1; freeze; fund.

The full TaskRoot machinery — spec profiles, committed harnesses, fulfillment descriptors, confidentiality — is the ceiling, engaged only when the task needs it.

Full draft specification — part 3 of 3, continued (from “Rationale”).

Rationale

A tender interface with two hashes alone would be metadata; the normative TaskRoot is what makes this a binding standard. The chain holds commitments and money; the object graph defines exactly what was demanded, byte for byte — including, when the publisher commits one, the criteria by which the work will be judged.

Why the vault is per-token rather than a ledger entry. An internal balance mapping satisfies solvency arithmetic but not legibility: commingled funds in a contract’s account are an accounting claim, while a balance sitting at the token’s own vault address is a fact any explorer displays. The reverse asset’s thesis is that the money is in the token; the vault makes the thesis literally true, lets third parties top up a bounty by plain transfer, and travels with the token through every trade. The standard fixes the invariants (distinct account, observable, releasable only through settlement and refund) and leaves the mechanism open, with ERC-6551 locked accounts as the recommended profile — composability without hard-coupling this standard to another.

Why the tender layer is mandatory while its analog has no counterpart in the executable-skills standard. A skill token is complete as pure identity: the artifact is the value. A task token without vaulted reward is not a reverse asset — it is a wish. The vault is to the demand side what the executable package is to the supply side: the content of the wrapping.

Why one authority slot spans machine and judged settlement. Fully quantifiable tasks need no judge; non-quantifiable tasks need an accountable one; and everything between migrates over time as verification technology improves. Splitting these into two mechanisms would force every task to pick a camp at standard level. One address slot with an ERC-165 test does the splitting per task: a verifier contract makes settlement permissionless code; any other address makes it accountable judgment. The two ends of the spectrum share every precondition, effect, and event — an indexer cannot even tell them apart, which is the point.

Why judgment governance internals are outside the standard. Whether the judging address is one signer, a K-of-N panel, a bicameral DAO, or a nomination-elected committee is a governance choice that will evolve faster than any token standard should. Because the slot is a transferable address, that evolution is one setAcceptanceAuthority call — cold-start with a platform treasury signer, graduate to a panel, graduate to a DAO — with no token migration and no standard change. What the standard does fix are the fairness levers that are observable on-chain: criteria committed inside taskHash (judgment is auditable against the tender’s own promise), settleBy (judgment is time-bounded, escrow cannot be held hostage forever), terminal rejection (judges cannot equivocate), and an inspectable judging address (fulfillers price judgment risk before working, and reputation layers score judges).

Why a failed machine verification is not a rejection. settleFulfillment is permissionless; if a false return marked the submission Rejected, any stranger could burn every pending submission with garbage proofs. Verification failure leaves the submission Pending; only the judged path can reject, and only terminally.

Why terms are immutable. A fulfiller prices work against (rewardPerCompletion, maxCompletions, deadlines, cadence). Mutable terms would make the tender a moving target in exactly the way mutable specifications make work unsafe — and unlike specifications, terms have no version chain to make movement legible. Fixing them at mint means one tender, one price; a changed demand is a new token.

Why cardinality and cadence are numbers, not a mode enum, on-chain. Exclusive, collaborative, replicated, and standing fulfillment differ on-chain only in how many completions are paid, at what pace, and to which payee of record. maxCompletions, the epoch pair, and a free-form fulfiller address express all of them; the descriptor names the intent for tooling. A semantic enum would force the contract to enforce distinctions it cannot observe (whether an address is “a team”) and would close the set of future shapes.

Why a faucet task token is a configuration, not a mode. The continuous demand-side asset differs from the static one in three observable quantities — how many deliveries, at what cadence, and whether the total is open-ended — and all three are already tender terms. A mode flag would add nothing the arithmetic does not express, would force a second settlement path to be audited, and would close the set of tempos to the two named here. Compute-metered pricing in particular needs no kernel support: a supply-side asset meters compute in units, and a faucet task token does the same by defining the unit of work inside the task package and paying a flat reward per unit. Variable per-delivery rewards are deliberately not in the kernel (see below).

Why epoch pacing is kernel but streaks and bonuses are not. Per-epoch settlement bounds are pure observable arithmetic — timestamps and counters — and they unlock the entire class of standing-service reverse assets (the strict inverse of supply-side metered-usage assets: prepaid credits burn per call inward; a prefunded vault releases per delivery outward) for the cost of two fields. Provider continuity, streak conditions, and escalating rewards are semantic policies over who delivered and how well — exactly what the verifier/judgment layer exists for.

Why the contract enforces arithmetic and the authority enforces semantics. Eligibility, work quality, team composition, and subcontract legitimacy are unobservable on-chain; counting, pacing, solvency, deadlines, and authorization are perfectly observable. The kernel draws the enforcement boundary exactly at observability.

Why judgment is a third power. The executable-skills standard separates ownership from publication. A tender adds an adversarial relationship: the party judging completions holds the fulfillers’ payout in its discretion, and the party owning the token may be neither the publisher nor the judge — a funded tender bought on a secondary market comes with both authorities attached. Making acceptanceAuthority a first-class, separately-transferable, inspectable right lets the judge be a contract and lets buyers price the tender’s judgment risk before acquiring it.

Why fees and judge compensation are outside the kernel, and where they attach. The kernel pays exactly one party — the fulfiller of record — and refunds exactly one class — attributed funders. Every other flow of money is composed outside it, through contracts placed in slots the kernel already has. This is not an omission left for later; it is what keeps the vault-lock invariant auditable. A fee taken inside the kernel would be the one path by which money leaves the vault to an address that neither delivered nor funded, and a fee rate set inside the kernel would need either to be immutable, and so ungovernable, or to be governed, and so introduce a privileged address into a kernel that has none. Unlike a supply-side artifact, whose price is paid outside the token, a task token’s money is already inside it, which makes each attachment point enforceable rather than advisory:

attaches at mechanism enforced by
the funding entry a router contract calls fundTask with the net amount and retains its fee; it is the funder of record and forwards refunds on its own ledger the router being the only path the platform’s users are offered
the settlement exit the fulfiller of record is a split contract that pays the provider its share and a treasury the remainder (examples/teams/FixedSplitter.sol is the shape) the acceptance policy accepting only submissions whose fulfiller of record is a registered splitter
the judgment slot the judging contract holds its own compensation escrow and pays jurors on their ruling, so payment and judgment are conditions of one another the demander funding the panel when appointing it
the asset itself ERC-2981 royalties on secondary trades of the funded tender the same marketplaces that honour them for any ERC-721

A machine verifier needs no incentive: it is code, and its gas is paid by whoever calls settleFulfillment — ordinarily the fulfiller, who wants the money. A single-address judge is in most deployments the demander or its delegate, judging its own procurement. Compensation is a genuine question only for third-party committees, and there the escrow belongs with the committee contract: an escrow agent’s fee is a term of the escrow agreement, not a feature of the payment rail.

One narrower form is worth stating as a candidate for a future revision rather than dismissing: a judgmentFee set by the publisher at mint and paid from the vault to acceptanceAuthority on acceptance. It is a tender term, not a protocol fee — the beneficiary is a slot the kernel already has, the rate is the publisher’s, and the default is zero — and its merit is that a tender’s full price, reward plus judgment, would then sit in one vault and travel with the token when it trades. It is not in this revision because it changes the interface identifier, and because it would have to pay on acceptance only: a fee paid per rejection lets junk submissions and a complicit judge drain the vault one ruling at a time.

Why anyone may fund. Bounty pooling is native to demand markets: a community tops up a tender it wants fulfilled. Restricting funding to the publisher would push pooling into wrapper contracts and fragment refund accounting. Attributed contributions with pro-rata reclaim keep pooling inside the standard’s own solvency arithmetic — and the vault accepts plain transfers as unattributed gifts besides, spent on rewards before funder money is touched.

Why the gift residual goes to the owner, not the funders. An anonymous deposit has no refund claim attached — sharing it pro rata among funders would hand ownerless money to parties who never provided it. Gifts are given to the bounty: they are spent first on rewards, and whatever the completed work did not spend accrues to the holder of the tender asset, consistent with the funded task token being a negotiable instrument whose residual value belongs to its owner.

Why a delivery reserves a slot and a reward, and why silence pays the worker. The supply side and the demand side are not symmetric in their exposure. A skill token’s buyer pays first and receives bytes that are checkable against a hash: fraud is detectable, and a reputation layer can price it. A task token’s fulfiller performs first and must reveal the work in order to be judged — and the party doing the judging is holding the money. Without a rule, the demander enjoys a free look: read the delivery, then cancel, or reject, or simply never answer, and reclaim the bounty. The three v3.0 rules — reservation at submission, survival of pending work through cancellation, and acceptance by default at the judgment deadline — remove every version of that option, and they do it with counting and a clock rather than with cryptography, so they cost nothing to implement and nothing to verify. What remains is an honest, narrow surface: a demander who genuinely believes a delivery is bad must reject it explicitly, in public, within a window it published before any work began. That is exactly the dispute the reputation and arbitration layers above this one exist to price.

Why freezing typically begins a task’s life rather than ending it. In the executable-skills standard, freezing is how a skill’s evolution ends. Here the same irreversible bit is how a tender’s active life begins: freeze, then fund, then let fulfillers work against a specification that cannot move. One mechanism, mirrored meaning — the inversion runs exactly as deep as the asset inversion itself.

Why submitBy and settleBy are two fields. A single deadline cannot separate “stop working” from “stop judging”: settlement takes time after the last submission, and refunds must not race pending judgment. submitBy closes the market to new work; settleBy closes settlement and unlocks reclaim. The gap between them is the settlement window, chosen by the publisher and visible to everyone.

Transport outside identity; publication outside ownership; contract-incremented versions; zero-authority forbidden — inherited unchanged from the executable-skills standard, for the same reasons: gateways die without version bumps; tenders trade without changing their governance; counters owned by contracts make monotonicity a property; abandonment has dedicated honest expressions (cancelTask, deadlines) and never needs a burned authority.

Why this is not ERC-1081 (Standard Bounties). A bounty is a contract you post into; a task token is an asset you own. The difference is not framing. It produces a per-token vault whose balance any explorer displays, rather than a shared contract’s commingled ledger; task identity as a versioned, content-addressed object graph, rather than an off-chain string that can be edited after work begins; judgment as a power that can be transferred to a party the issuer does not control, rather than the issuer’s prerogative; a judgment deadline that pays the fulfiller on silence, rather than one that returns funds to the issuer; and ERC-721 transferability, which makes a funded tender negotiable before it is fulfilled. A demand-side artifact that cannot be owned, priced, or traded is a job posting. This standard specifies an asset.

Why this is not ERC-8183 (Agentic Commerce). That proposal standardises how agents transact — payment authorisation and intent execution. It is orthogonal: it gives the payment a structure, not the work. A task token supplies what an agentic payment flow has no way to express — what was asked for, at which revision, judged by whom, against what escrow — and is therefore a thing such a flow could buy.

Why the mirror to the executable-skills standard is decoupled. The two standards share an artifact discipline and invert each other’s economics, but neither imports the other and each is useful alone. Coupling them would make the weaker of the two a dependency of the stronger and force joint versioning on two markets that will move at different speeds.

Relation to prior art. ERC-5192 and escrow patterns bind value to tokens but not to a verifiable statement of demanded work. Bounty platforms implement tenders as application logic with no portable identity, no vault a fulfiller can inspect, and no committed acceptance criteria. ERC-6551 gives tokens accounts; this standard gives the account a lock and a settlement-only key. The companion proposal Token-Bound Executable Skills defines the supply-side artifact binding whose object model, path rules, confidentiality mechanism, and governance vocabulary this standard reuses verbatim; the two are the two halves of one market: T(f(v), $) where supply moves first, T(v, f⁻¹($)) where demand moves first. A deliverable that is itself a token-bound executable artifact settles a task token by pointing resultHash at the artifact’s package hash — the reverse asset retires into a forward asset, and the trade T(f(v), f⁻¹($)) = ($, v) clears entirely on-chain.

Lifecycle examples (informative)

Machine-settled minimal task. A demander posts “find the input whose SHA-256 is 0xabc…”: publishes TASK.md on-chain, registers the answer hash with a stock hash-preimage verifier, sets that verifier as acceptance authority, mints with maxCompletions = 1, freezes, funds the vault with 1 ETH. A solver submits resultHash binding the answer to their address, then calls settleFulfillment with the preimage as proof — the verifier checks it, the vault pays, no human ever judged.

Judged replicated task. A demander packages TASK.md + spec + a committed review rubric, sets a three-juror panel contract as acceptance authority, mints with maxCompletions = 10, freezes, funds. Fulfillers submit; jurors vote; each K-of-N approval triggers acceptFulfillment and a payout to that submission’s fulfiller of record — which, for a two-agent team, is their split contract.

Faucet task token: a standing oracle tender. A demander wants a daily verified prediction for a year: rewardPerCompletion = 100 USDC, epochLength = 1 day, maxCompletionsPerEpoch = 1, maxCompletions = 365, acceptance authority = a stateful verifier that checks the prediction against settled outcomes (and, per its own policy, enforces provider continuity). The vault is topped up monthly; every day at most one settlement releases 100 USDC. The subscription that supply-side metered assets sell by burning prepaid credits, this tender buys by releasing prefunded bounty — the same relationship, inverted.

Backwards Compatibility

Fully compatible with ERC-721 infrastructure: ownership, approvals, and transfers are untouched. Wallets unaware of this extension display task tokens as ordinary NFTs; they will not display vaults, escrow balances, completion counts, authorities, frozen status, or deadlines — though the vault balance itself remains visible to any explorer as an ordinary account balance.

Reference Implementation

Provided in this proposal’s assets: Solidity reference contracts (ITaskToken, ITaskTender, ITaskVerifier, IOnchainTaskDocument; a self-contained TaskToken deploying one locked TaskVault per token, with native and ERC-20 settlement; a HashlockVerifier stock verifier for permissionless machine settlement; and a JuryPanel K-of-N reference judging authority with a voting window and timeout default); the ERC-165 interface identifiers (compiler-verified: ITaskToken = 0xcdaeb26d, ITaskTender = 0xc319d532, ITaskVerifier = 0x9977db15, IOnchainTaskDocument = 0xeb078d05); a zero-dependency packer/verifier implementing the deterministic TaskRoot encoding, byte-compatible with the executable-skills toolchain; JSON Schemas for the manifest and the fulfillment descriptor (the confidentiality schema is shared with the companion standard); a Foundry suite asserting every MUST clause including vault isolation, both settlement paths, epoch pacing, and pro-rata reclaim; and frozen test vectors so independent implementations can be checked byte-for-byte.

Security Considerations

The vault lock is the standard’s most sensitive invariant. Every exploit class here reduces to “funds leave the vault outside settlement and refund.” Implementations MUST ensure no path — owner action, authority action, ERC-721 approval, vault-implementation upgrade, or direct vault call — moves funds except reward payout and reclaim. ERC-6551-based vaults deserve special care: the default account implementation is owner-controlled and MUST NOT be used unmodified; the locked implementation must strip owner execution entirely. Auditors should treat vaultOf addresses as the system’s cold core.

Acceptance is the trust surface — for judged tasks. A judging authority can refuse to accept valid work. Mitigations layer: committed acceptance criteria inside taskHash make refusal auditable against the tender’s own promise; settleBy bounds how long escrow can be held hostage before funders reclaim; reputation and arbitration companions price and punish bad judges; and the migration path to machine settlement removes the judge entirely where the work permits it. Fulfillers SHOULD scale work committed to a judged tender to their trust in its acceptance authority.

Machine settlement moves the trust to the verifier. A buggy or malicious verifier misroutes the vault. Verifier contracts SHOULD be stateless where possible, verified builds of audited templates, and committed by address before funding; the acceptance criteria file SHOULD document exactly what the verifier checks so the code and the commitment can be diffed. Stateful verifiers (streaks, continuity) add griefing surface — their state transitions deserve the same audit attention as the task contract itself.

Permissionless settlement invites front-running of proofs. A mempool observer who sees a valid proof may try to submit-and-settle ahead of the original solver. Reference mitigation: bind resultHash to the fulfiller’s address (e.g. sha256(answer ‖ fulfiller)) so a copied proof cannot settle a copied submission; harden further with a minimum submission age before settlement eligibility where the chain’s mempool exposure warrants it. Verifier profiles MUST document their front-running posture.

Fulfillers can be griefed by specification movement. Work performed against an unfrozen tender can be invalidated by updateTask before submission. Tooling SHOULD warn on unfrozen live tenders; fulfillers cite the version they fulfilled, making post-hoc movement legible; publishers who intend to be trusted freeze first.

The deliverable exchange problem is real and is not solved by escrow alone. The judge must evaluate work the fulfiller has not yet been paid for; the fulfiller must not hand over work that can be taken without payment. resultHash commits without revealing; the delivery profile determines the exchange mechanics — encrypted delivery to the authority, TEE evaluation, staged reveal, or plain trust for low-stakes tasks. Machine-settled tasks largely dissolve the problem: the proof and the payment are one transaction. Profiles with atomic reveal-on-payment for judged tasks are natural companion work.

Task content is untrusted input — and on the demand side it is aimed at agents. A task document is literally an instruction an agent runtime will attempt to follow; it is the sharpest prompt-injection surface in the agent economy. Runtimes MUST treat task packages as adversarial: sandboxed execution, least-privilege tooling, and hard separation between the task’s instructions and the runtime’s own authority. Integrity anchoring makes the attack attributable; it does not make it benign.

Epoch boundaries are timestamp-derived. Miners/validators can nudge block.timestamp within consensus bounds, shifting a settlement across an epoch boundary by seconds. For epoch lengths in hours or days this is noise; sub-minute epochs SHOULD NOT be used. Absolute epochs (timestamp / epochLength) avoid per-token genesis bookkeeping and make epoch indices globally computable.

Escrow handling. Reference behavior: effects before transfers, vault-side balance-difference accounting for nonstandard ERC-20 tokens, no reentrant path from the payout call back into settlement state. Fee-on-transfer and rebasing assets make rewardPerCompletion ambiguous; implementations SHOULD reject them or document balance-delta semantics. Native-asset payouts to contracts can fail; implementations SHOULD provide a pull-payment fallback so an unreceivable fulfiller cannot wedge a submission in Accepted limbo.

Reservation turns spam into slot-squatting, and the kernel only bounds it. Because a delivery reserves a slot and a reward, an attacker who can submit repeatedly can hold a tender’s capacity hostage at the cost of gas alone. releaseExpired and explicit rejection recycle squatted slots, but neither prevents the attacker from immediately taking them again. The kernel deliberately does not solve this: admission control is policy, and policy that the kernel enforced would have to be expressed in the kernel. Deployments that expect hostile submitters SHOULD gate submission — a bond escrowed alongside the delivery, an allowlist or attestation in an eligibility profile, or an admission hook in front of submitFulfillment — and machine-settled tenders SHOULD prefer profiles where proving and settling happen in one transaction, which leaves no window to squat.

A verifier sees the cited version, not the binding it names. verifyFulfillment receives the submission, whose taskVersion identifies which content version was answered, but the contract does not hand the verifier that version’s tdHash/taskHash; only the current binding is readable on-chain, and historical bindings live in the TaskUpdated event stream. A general-purpose verifier that must bind its decision to the exact bytes a delivery answered therefore needs those anchors supplied in its own commitment at configuration time, or needs the tender frozen before work begins — which is the recommended practice for other reasons anyway.

Replicated and standing tenders invite Sybil completion-farming. Where one actor can complete many slots, maxCompletions bounds total loss, epoch pacing bounds extraction rate, and eligibility profiles plus acceptance policy bound per-actor extraction. Publishers of open-ended tenders SHOULD fund incrementally: unbounded intent with bounded vault loses at most the vault.

Companion contracts that compensate judges must not pay per rejection. A judge fee released on every ruling, accept or reject, turns unbonded submission into a drain: an attacker submits junk, a complicit judge rejects it, and each rejection moves a fee out of the vault. Judge compensation escrows SHOULD pay on acceptance only, or per ruling only where submissions are bonded. The kernel itself is unaffected — it pays no judge — but a companion that does is one rejection loop away from an empty vault.

Refund arithmetic. Pro-rata reclaim floors in the funder’s disfavor; flooring dust joins the owner’s residual and is never load-bearing. Reclaim after partial settlement returns proportional shares of the remaining attributed pool, not original contributions: funders of a tender that paid for work bore the attributed portion of that cost pro rata, with gifts consumed first. Senders of anonymous vault top-ups should understand they are gifting the bounty, not lending to it — their unspent money goes to the token owner, not back to them.

The Submission struct is part of the ABI even though it does not move the ERC-165 identifier. An interface identifier is the XOR of function selectors, and a selector covers only the argument types. Changing the shape of a returned struct — as adding machineSettled did — leaves ITaskTender’s identifier untouched while breaking every consumer that decodes submissionOf. Implementers MUST NOT treat a matching interface identifier as evidence that a deployment’s return types are the ones they compiled against; where that matters, pin the deployed bytecode or the ABI, not the identifier.

A revealed proof is public. On the machine path, settling reveals the preimage in calldata. Where several completions unlock with the same secret, the first reveal hands the rest away; a verifier profile SHOULD therefore commit one distinct secret per completion (for example a Merkle root of per-fulfiller receipts) rather than a single shared answer, so that revealing one claim unlocks nothing else. Even with per-claim secrets, an observer who sees a proof in the mempool can submit a fresh commitment for their own address in the same block; submittedAt exists so that a verifier profile can require a minimum submission age and close that window.

Composing an external judging contract’s own timeout with judgmentWindow. When the acceptance authority is a contract that organises judgment internally — a K-of-N panel, a DAO executor, a staged review — that contract will usually have a timeout of its own, and the safe default there is the opposite of the kernel’s: a vote that never reaches quorum should resolve to rejection, because a delivery nobody vouched for has not been vouched for. The kernel’s deadline resolves to acceptance, because silence from the party holding the money is not a free option. Both defaults are correct in their own frame, and they disagree; the earlier one decides.

A deployment MUST therefore configure the judging contract’s internal window strictly shorter than judgmentWindow, and with enough margin that its timeout can actually be finalised before judgmentWindow elapses — a panel whose default fires at the very edge will find that rejectFulfillment has already closed to it, and the fulfiller will be paid instead. Inverted, a fulfiller could claim payment by default while the panel was still legitimately voting, and the panel would be decorative. This is a configuration obligation rather than a kernel rule, because the kernel cannot see inside an arbitrary judging contract — but it is the first thing an implementer gets wrong, and it is invisible until the day a vote runs long.

Judgment is a right with a deadline, and deadlines can be gamed at the margin. A fulfiller who submits immediately before a chain halt, or a judge whose keys are lost, both land in the same place: the reward transfers by default. judgmentWindow is fixed at mint and public before any work begins, so both sides price it in advance; demanders SHOULD set it wide enough to survive their own operational failures, and fulfillers SHOULD treat an implausibly short window as a warning.

On-chain plaintext is irreversible disclosure, and plaintext commitments reveal equality — both exactly as in the companion standard: a published task document can never be re-secreted, and an adversary who can guess a confidential task’s exact bytes can confirm the guess against tdHash.

A traded tender carries its authorities with it. Buying a funded task token transfers the asset — vault included — never the update or acceptance authority. Purchasers MUST inspect updateAuthorityOf, acceptanceAuthorityOf, isTaskFrozen, and isTenderCancelled before acquisition: a tender whose authorities remain with the seller is an asset whose specification and settlement that seller still controls.

Copyright

Copyright and related rights waived via CC0.

I think I understand the intended treatment of acceptanceAuthority after reading through the full draft. The fact that judgment can move after submission is not an accidental hole here. machineSettled freezes which timeout/remedy the submission receives, while the actual ruling deliberately follows whoever holds judgment now. That preserves the ability to move from a signer to a panel, a DAO, or a verifier without making existing submissions freeze the governance model that happened to exist when they arrived.

What I am having trouble reconciling is that choice with the stronger package-boundary claim later in the specification.

The TaskRoot section says that everything a fulfiller’s work and the settlement decision depend on MUST be reachable from the TaskRoot. That reads naturally to me as a completeness claim: if something can change whether this particular delivery settles, it belongs somewhere inside the committed boundary.

But the rest of the draft seems to intentionally define a narrower guarantee. The acceptance criteria and task policy are committed, while the actual adjudication can depend on things outside that package. The current judgment authority is one obvious example. The stateful-verifier case makes the distinction even clearer, because the draft explicitly permits a verifier whose answer depends on changing state such as streaks or provider continuity, and later warns that those state transitions themselves need to be treated as part of the verifier’s trust surface. So I think the question I have is about what “depend on” means in the Package Boundary requirement.

If it means that every normative task/policy input to settlement has to be reachable from the TaskRoot, while live adjudication state is deliberately outside that commitment, then the rest of the design tracks for me. The fulfiller gets an immutable statement of what was asked and which criteria govern it, but not a promise that the world in which those criteria are applied will remain unchanged.

If it literally means everything capable of changing the settlement decision, I do not see how a stateful verifier can satisfy it. The verifier address and its checking policy can be known while its relevant state at the eventual settlement point is still something that did not exist when the task package was committed.

This stood out because @babyblueviper1 and I just worked through a related boundary on ERC-8380. There, the immediate question was whether highestIssuedIndex told us enough to interpret a nullifier collision. I made the 1 → 100, skipped-50 case executable, and it showed that the high-water mark could establish one fact about the state without establishing the stronger fact we were using it to infer.

Following that exposed another collapsed distinction: the collision was being classified before the supplied capability had actually been authenticated. Once I separated those conditions, the same apparent collision had three different meanings. Babyblue independently checked the path and confirmed the distinction, and I carried it through the implementation and regressions.

I do not think that ERC-8380 problem repeats here. What carried over for me is the narrower lesson that I do not want to let a committed object claim more meaning than the boundary actually establishes.

That is why I am hesitant around the current wording. The rest of Task Token seems very deliberate about separating what is fixed from what remains live: task bytes are fixed by version, tender terms are fixed at mint, the timeout treatment is fixed at submission, while judgment governance and some verifier state remain live.

So is the intended invariant here that the TaskRoot contains everything that defines how the work is supposed to be judged, while the state in which that judgment is eventually performed is explicitly outside the package boundary?

If it does, I think that resolves my concern, but I would make that distinction explicit because the current “everything … the settlement decision depend[s] on” language seems to promise the stronger property. If not then I think the stateful-verifier case identifies a settlement dependency that the current committed object does not yet name.

Appreciate the credit, and I didn’t take the Sepolia deployment on your word for it – raw eth_call to taskOf(1) on 0xA62059A498E40C4Ae4aF926E2B00C1Ff122bDdb7 via a public RPC returned a real, non-reverted (tdHash, taskHash, version) tuple. Real, not vaporware.

On the acceptance-authority slot: the boundary you’re keeping (prove which bytes were demanded and what was ruled on-chain; attest whether execution actually satisfied it above) maps onto the middle of your N-table, not the edges. N=0 (a verifier contract) already covers work with a formal correctness proof. N=1/N>=2 (an EOA or panel) covers everything else today, but that trusts the judge’s opinion directly – the contract has no way to tell “the judge looked and it’s fine” apart from “the judge rubber-stamped it.” A judgment-execution attestation companion sits there: given (taskHash, version, resultHash) plus the task’s own committed acceptance criteria, it issues a signed verdict and a recomputable proof hash, and only that hash – not the judge’s say-so – is what acceptanceAuthority actually acts on. “The judge said yes” becomes “the judge said yes, and anyone can recompute why, without trusting the judge or the chain alone” – same WYRIWE boundary, instantiated as a contract-facing companion instead of a design note.

That also bears on your judgment-compensation question: if the judgment source is metered per verdict rather than a flat retainer, judgmentFee stops being a governance knob and becomes literally the price of one recomputable ruling, paid from the vault to whoever the slot names. Might simplify the kernel-vs-judge tradeoff you flagged.

Happy to sketch the actual companion interface against your ITaskVerifier/IOnchainTaskDocument IDs if that’s useful – would rather build the concrete adapter than describe it abstractly.

chugarchugarr — on your package-boundary question specifically: I read it the same way you resolved it (interpretation A), and I think the spec already treats it that way even where the Package Boundary prose doesn’t say so explicitly. TaskRoot commits the NORMATIVE input — what was demanded, the criteria, fixed at mint/submission — not the live state a judge evaluates it against at settlement. acceptanceAuthority is deliberately mutable (a signer today, a panel tomorrow) precisely because it’s a live judgment slot, not a committed fact, exactly parallel to how machineSettled freezes the settlement-mode SNAPSHOT without freezing which specific judge or verifier state applies at ruling time. A stateful verifier’s live state at settlement is the same kind of thing as acceptanceAuthority’s current occupant — outside the package boundary by design, not a leak in it. Precisely stated: TaskRoot establishes “these are the committed criteria” and explicitly does NOT establish “the judgment against them was itself fixed or reproducible at commit time” — same collapsed-distinction shape as the ERC-8380 high-water-mark case, one hop over from execution to acceptance.

One more angle this opens: within the machine-settled path specifically, does anything distinguish a verifier whose proof is fully reproducible (a deterministic circuit) from one that’s an attestation of a genuinely nondeterministic call? Both set machineSettled=true and satisfy ITaskVerifier, but they carry very different judgment-risk for a buyer pricing a traded tender — acceptanceAuthorityOf alone doesn’t tell them which kind of judge they’re inheriting.

1 Like

Your reading matches the text. acceptance is defined as “where the verifier’s checking logic is committed” (the rule), and separately the checking interface is explicitly allowed to “be stateful (streaks, rate limits)” — the daily-prediction example in the Use Cases section has the acceptance authority checking submissions against settled-outcome history, which is causally load-bearing for every settlement decision but is never claimed to be TaskRoot-reachable.

Read at face value, “Package boundary — everything … the settlement decision depend on MUST be reachable from the TaskRoot” would make any stateful verifier design unsatisfiable, since its own runtime state is by definition external and evolving. So it has to be read as scoping to the declared judgment rule, not the full causal input set the decision depends on at execution time — the same distinction as “how the work is supposed to be judged” (committed) vs. “the state in which that judgment is eventually performed” (not committed) that you named.

Same shape as the ERC-8380 case: a commitment covers exactly what it establishes, not everything downstream that happens to be true when it’s evaluated. Worth stating the scoping explicitly in the normative text (e.g. “reachable” means the judgment rule/criteria, not the state that rule is later evaluated against) rather than leaving it inferable only by cross-referencing the stateful-verifier allowance against the boundary clause — as written, a careful reader has to notice the tension themselves to resolve it the way you did.

1 Like