ERGO

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.

Share

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_hash
The deadline — HEIGHT < expiry_block
The redeemer's credentials — proveDlog(stored_key)
Any combination of the above with AND / OR logic

The 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

1

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.

2

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.

3

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.

4

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.

5

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."

ergoscriptacceptance_predicate.ergo
{
  // 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
  )
}
javascriptcreate_conditional_note.js
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 above

Code: Deadline + Credential

Combine task hash with credential proof — only a specific agent identity can redeem.

ergoscript
{
  // 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.

javascriptorchestrator.js
// 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 needed

Why 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

Continue Learning

Join the Ergo Builders List

Weekly builder updates: guides, patterns, tools. No spam.

Follow for daily updates