EIP.tools

EIP.tools

โš ๏ธ DraftStandards Track: ERC

ERC-8414: Token-Bound Task Tenders

An ERC-721 extension making a task a transferable asset whose vault holds the reward and whose acceptance rule is on-chain

Authors
Created2026-09-05
Discussion Linkhttps://ethereum-magicians.org/t/erc-8414-token-bound-task-tenders/29597
Requires

Markdown

https://raw.githubusercontent.com/garyyang-finchip...

EIP-GPT summary

Contents
AbstractMotivationSpecificationCore binding interface (mandatory)Tender interface (mandatory)Verifier interface (for machine-settled tasks)On-chain document interface (optional)ComplianceIdentity versus transportThe three separated powersThe task vaultTender termsEpoch pacing (faucet task tokens)FundingSubmissionSettlementThe judgment deadlineCancellation and refundFreezingExistence and update behaviorHash definitions (normative)Package paths (normative)TaskRoot (normative off-chain object)Manifest (normative minimal schema)Fulfillment descriptor (optional, in-package; normative schema)Confidentiality descriptor (optional, in-package; normative schema)Minimal Task Profile (informative)RationaleLifecycle examples (informative)Backwards CompatibilityReference ImplementationSecurity ConsiderationsCopyright

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 bya faucet task token pays bykernel expression
countaccepted deliveryrewardPerCompletion per completion; maxCompletions bounds the total, 0 leaves it open-ended
timeepochepochLength and maxCompletionsPerEpoch
unit of computeunit of workthe 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.

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 path โ€” acceptFulfillment(tokenId, submissionId): callable only by the acceptance authority.

Machine path โ€” settleFulfillment(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.

Rejection โ€” rejectFulfillment (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 that defines the demanded work and how completions are to be judged โ€” the task statement, the machine-actionable specification, the acceptance criteria and their profiles when committed, the fulfillment policy, the confidentiality descriptor โ€” MUST be reachable from the TaskRoot; unreachable content enjoys no protection under taskHash. The commitment is to definitions, not to the state in which they are eventually applied. Settlement inputs that are deliberately outside the boundary and remain live include: the identity of the current acceptance authority (transferable by design), the internal state of a stateful verifier or judging contract (streaks, continuity, votes โ€” a trust surface named in Security Considerations), and chain state at settlement time (epoch position, vault balance). The kernel fixes exactly one adjudication-environment fact per submission โ€” the machineSettled snapshot selecting the deadline remedy โ€” because that fact, left live, converts a debt into an expiry. A fulfiller therefore receives an immutable statement of what was asked and by which criteria it is to be judged, not a promise that the world applying those criteria will remain unchanged.

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.

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 atmechanismenforced by
the funding entrya router contract calls fundTask with the net amount and retains its fee; it is the funder of record and forwards refunds on its own ledgerthe router being the only path the platform's users are offered
the settlement exitthe 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 slotthe judging contract holds its own compensation escrow and pays jurors on their ruling, so payment and judgment are conditions of one anotherthe demander funding the panel when appointing it
the asset itselfERC-2981 royalties on secondary trades of the funded tenderthe 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 agentic-commerce payment standards. Those proposals standardise how agents transact โ€” payment authorisation and intent execution. They are orthogonal: they give 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.

A machine-settled tender does not tell a buyer what kind of machine. Two verifiers can both declare ITaskVerifier and both set machineSettled, while carrying very different judgment risk: one recomputes a deterministic check that anyone can reproduce from the committed criteria; the other attests to the outcome of a nondeterministic or off-chain process. acceptanceAuthorityOf alone does not distinguish them. The committed acceptance profile SHOULD state which kind the verifier is and how, or whether, its rulings can be independently recomputed, so that a purchaser of a traded tender can price the judgment it is inheriting; on-chain verifier-kind discovery interfaces are natural companion work.

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.

On the judged path, the only unpaid exit from a reservation is a rejection in time. releaseExpired exists only where an on-chain fact already distinguishes junk from genuine work โ€” a verifier that refused the proof. A judged tender has no such fact, so a permissionless release there would race claimUnjudged past the deadline and let anyone convert a delivery the judge merely ignored into a rejection โ€” the free look, weaponized. A judged slot therefore has three exits โ€” rejection (in the window, whose clock the submitter starts), acceptance, and the default claim โ€” and only the first is unpaid. A judge's lapse pays the submitter, junk included: that is the priced consequence of the deadline default, not an oversight. Publishers of judged tenders MUST size judgmentWindow to their judge's real availability, and open judged tenders SHOULD narrow eligibility, require policy-layer bonds, or fund incrementally. Epoch pacing lengthens the exposure โ€” inside a saturated epoch a Pending submission whose window has closed can be simultaneously unrejectable, unacceptable, and unclaimable until the next epoch begins โ€” and also caps it, bounding what squatted slots can extract to maxCompletionsPerEpoch ร— rewardPerCompletion per epoch.

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 and related rights waived via CC0.

EIP.tools

EIP.tools

Search, read, and map Ethereum improvement proposals, ERCs, RIPs, and CAIPs from one focused interface.

Farcaster
by @apoorveth