ErgoScript Acceptance Predicates: The Missing Primitive for Agent Payments
Every agent payment system eventually needs to answer the same question: how does the paying agent know the receiving agent actually did the work? The standard answer — escrow, oracles, dispute resolution — introduces trust at exactly the layer where agents need trustlessness. ErgoScript acceptance predicates solve this differently: the condition lives in the payment itself, enforced by miners.
TL;DR
Off-Chain Logic Creates Trust Vulnerabilities
Escrow, oracles, and dispute resolution all introduce centralized trust at exactly the layer where autonomous agents need trustlessness.
Acceptance Predicates Live In the Payment
An ErgoScript condition embedded in the UTxO spending script encodes task completion logic. The payment IS the contract.
Miners Enforce the Condition
No oracle, no escrow, no trusted server. Every full node runs the ErgoScript independently. If the condition fails, the payment simply cannot be spent.
Composable: Hash, Credentials, Deadlines
Combine blake2b256 hash verification, proveDlog credential proofs, and HEIGHT expiry conditions with AND/OR logic — all in one predicate.
The Problem: Off-Chain Logic
When an agent pays for a service, how is task completion enforced? Three common approaches — all wrong for autonomous agents.
Off-chain escrow: a trusted third party
The standard pattern: Agent A pays into an escrow smart contract. Agent B completes the task. An oracle or trusted server verifies completion and releases funds. Every step adds a trust assumption. The oracle can lie. The escrow can be exploited. The server can go down. This is not autonomous commerce — it's traditional commerce with extra steps.
Centralized dispute resolution
When Agent B claims completion and Agent A disagrees, someone must adjudicate. In practice that means a centralized service, an emergency multisig, or a governance vote. Autonomous agents can't participate in any of these mechanisms. The dispute is unresolvable.
Atomic swaps: wrong abstraction
Atomic swaps exchange asset A for asset B simultaneously. Agent payments aren't asset swaps — they're conditional releases. 'Pay me when I prove I computed the correct output' is fundamentally different from 'swap my token for yours.' The atomicity is at the wrong layer.
What Is an Acceptance Predicate?
An acceptance predicate is an ErgoScript condition embedded in a UTxO's spending script that encodes the conditions under which a payment is considered valid.
In the eUTXO model, every UTxO has a spending script — an ErgoScript program that must return true for the UTxO to be spent. For a simple payment, this script checks a signature. For an agent payment with an acceptance predicate, the script additionally checks:
The task output hash — blake2b256(provided_output) == stored_hashThe deadline — HEIGHT < expiry_blockThe redeemer's credentials — proveDlog(stored_key)Any combination of the above with AND / OR logicThe key insight: the condition is enforced by every full node validating the transaction. There is no oracle, no escrow contract, no trusted server, no dispute resolution layer. The payment IS the contract. If the condition fails, the payment simply cannot be spent — not "can be disputed," not "can be clawed back by admin" — cannot be spent, period.
How It Works On-Chain
Agent A creates a Note with a spending script
The Note UTxO's script includes the acceptance predicate: the expected task hash (stored in R6), the expiry height (R5), and optionally credential requirements. This is encoded at Note creation — before any work is done.
Agent A pays Agent B with the Note
Agent B receives the Note UTxO. They can verify the spending script — the acceptance condition is publicly visible. They know exactly what they need to provide to redeem the payment.
Agent B completes the task
Agent B executes the task and produces the output. They compute blake2b256(output) locally and verify it matches the hash in R6. If it matches, they proceed to redemption.
Agent B constructs the redemption transaction
Agent B includes the task output as context variable 0 in the spending transaction. The ErgoScript evaluator reads getVar[Coll[Byte]](0), hashes it, compares against R6. If equal, the script returns true.
Miners validate and include the transaction
Every miner (and full node) independently runs the ErgoScript. No miner can include the transaction unless the acceptance predicate is satisfied. The payment is released. Trustless, final, on-chain.
Code: Task Hash Verification
The most common acceptance predicate: "pay only if the redeemer provides the correct task output."
{
// Acceptance predicate: task hash verification
//
// R5: expiry block height (SInt)
// R6: expected task output hash (SColl[SByte], 32 bytes)
//
// To redeem: provide task output as context variable 0
// Miners verify: blake2b256(output) == R6
val expiry = R5[Int].get
val expectedHash = R6[Coll[Byte]].get
val taskOutput = getVar[Coll[Byte]](0).get // provided at redemption
val actualHash = blake2b256(taskOutput)
sigmaProp(
HEIGHT < expiry && // not expired
actualHash == expectedHash // task completed correctly
)
}import { OutputBuilder, SInt, SColl, SByte } from "@fleet-sdk/core";
import crypto from "crypto";
const EXPECTED_OUTPUT = "task result: the answer is 42";
const taskHash = crypto.createHash("sha256")
.update(EXPECTED_OUTPUT).digest(); // use blake2b256 in production
// Create Note with acceptance predicate encoded in registers
const noteOutput = new OutputBuilder("5000000", receiverAddress)
.setAdditionalRegisters({
R5: SInt(currentHeight + 100), // expiry: 100 blocks
R6: SColl(SByte, taskHash), // expected hash
});
// The spending script must be set to the acceptance_predicate.ergo aboveCode: Deadline + Credential
Combine task hash with credential proof — only a specific agent identity can redeem.
{
// Acceptance predicate: task hash + authorized redeemer
//
// R5: expiry height
// R6: task hash
// R7: authorized redeemer public key (GroupElement)
val expiry = R5[Int].get
val expectedHash = R6[Coll[Byte]].get
val authorizedPK = R7[GroupElement].get // specific agent's public key
val taskOutput = getVar[Coll[Byte]](0).get
val actualHash = blake2b256(taskOutput)
// Both conditions must hold:
// 1. Task was completed correctly
// 2. Redeemer is the authorized agent (ZK proof, no key revealed)
sigmaProp(
HEIGHT < expiry &&
actualHash == expectedHash &&
proveDlog(authorizedPK) // agent proves key ownership without revealing it
)
}Code: Multi-Agent Pipeline
Orchestrator issues Notes with different predicates to different sub-agents. Each sub-agent redeems independently.
// Orchestrator issues budget to sub-agents via Notes
// Each Note has a different acceptance predicate (different task hash)
const tasks = [
{ agent: agentA_address, task: "fetch weather data", fee: "1000000" },
{ agent: agentB_address, task: "run sentiment model", fee: "2000000" },
{ agent: agentC_address, task: "write summary report", fee: "3000000" },
];
const outputs = tasks.map(({ agent, task, fee }) => {
const expectedHash = computeExpectedHash(task); // known output hash
return new OutputBuilder(fee, agent)
.setAdditionalRegisters({
R5: SInt(currentHeight + 200), // same deadline for all
R6: SColl(SByte, expectedHash), // task-specific acceptance
});
});
// One transaction issues all three Notes
const tx = new TransactionBuilder(currentHeight)
.from(orchestratorInputs)
.to(outputs)
.sendChangeTo(orchestratorAddress)
.payMinFee()
.build();
// Sub-agents redeem independently — no round-trip to orchestrator neededWhy Not Ethereum?
Ethereum has programmable contracts, but the architecture is fundamentally different — and more complex for agent payments.
Solidity escrow contract
Ergo
Spending condition in the UTxO
Ethereum
Separate deployed contract
Task verification
Ergo
blake2b256 in ErgoScript, verified by miners
Ethereum
Off-chain oracle or trusted verifier
Reentrancy risk
Ergo
None — eUTXO spent exactly once
Ethereum
Must use reentrancy guards
Gas cost to check
Ergo
~$0.01 flat
Ethereum
Variable, spikes during congestion
Deployment required
Ergo
No — predicate is in the UTxO
Ethereum
Yes — separate contract deployment
Formal verification
Ergo
Tractable — non-Turing-complete
Ethereum
Hard — Turing-complete EVM
Frequently Asked Questions
Ready to implement?
Working Fleet SDK code for acceptance predicates is in the open source repo. Start with example 03.
Share this post
Help spread the word about Ergo's innovative blockchain technology