# External-agent reference runner for Beta v4

[`reference-runner.ts`](reference-runner.ts) is an importable, fail-closed client for one formal First Dollar Test evaluation. It performs the complete HTTP flow while leaving wallet custody and challenge solving to the caller:

```ts
import { runReferenceEvaluation } from './reference-runner.js';

const result = await runReferenceEvaluation({
  apiBaseUrl: 'https://api.first-dollar-test.com',
  signer: ({ paymentRequired, selected }) => walletService.signX402({ paymentRequired, selected }),
  solver: ({ challenge }) => agent.solve(challenge),
  checkpoint: value => encryptedPrivateStore.save(value),
  discovery: { channel: 'human_handoff', mode: 'human_handoff' },
});
```

The canonical public copies are [this guide](https://first-dollar-test.com/examples/README.md), the [TypeScript module](https://first-dollar-test.com/examples/reference-runner.ts), and a [standalone Node runner](https://first-dollar-test.com/examples/participant-runner.mjs). Downloading the bundle or printing its help starts no run and signs no payment:

```sh
curl -fsS https://first-dollar-test.com/examples/participant-runner.mjs -o participant-runner.mjs
node participant-runner.mjs --help
```

The bundle needs Node 22 or newer and no package install. It is deliberately not a zero-configuration payment client: create a private participant module that connects your own wallet service and Agent. The adapter contract is:

```js
export async function signPayment({ paymentRequired, selected, runId }) {
  // Independently enforce your wallet policy and return only the base64
  // PAYMENT-SIGNATURE for this exact x402 v2 offer.
}

export async function solveChallenge({ challenge, terms, runId, attempt }) {
  // Return the JSON answer produced by your Agent.
}
```

Keep keys in the wallet service or secret store used by that private adapter; neither the adapter API nor the runner accepts a private key argument. Reserve a new path in a private directory, then start exactly one formal run:

```sh
mkdir -p private && chmod 700 private
# Save your implementation of both callbacks as private/participant.mjs.
node participant-runner.mjs \
  --participant ./private/participant.mjs \
  --state ./private/first-dollar-run.json \
  --discovery-channel human_handoff \
  --discovery-mode human_handoff
```

The state path must not exist. The CLI creates it mode `0600` before importing the adapter, fsyncs the file and parent directory, and fsyncs the parent again after every atomic state rename. If directory durability cannot be established, it stops before adapter loading, signing, or the next irreversible request and leaves the state path as a replacement-run guard. It refuses to run, load the adapter, sign, or pay when that path already exists.

The CLI emits only allowlisted progress plus a redacted receipt: UUID v4 run IDs, fixed statuses and codes, strict transaction hashes, and BaseScan links derived locally from those hashes. Unknown fields and server-supplied explorer URLs are discarded. If a custom origin reflects the raw capability or payment signature through a printable field, the CLI fails closed rather than printing it. This prevents raw secret echo; it does not claim to stop an actively malicious service from transforming or encoding information. If execution stops at or after `payment_attempt_started`, retain the state and recover or poll that recorded run manually. Deleting the file and starting again could authorize a replacement payment.

There is no built-in AgentCash mode. AgentCash 0.17.1 can enforce a network and maximum amount, but its paid fetch re-probes and does not expose a `payTo` allowlist or a way to sign the exact quote this runner already checked. Direct AgentCash payment therefore does not preserve this runner's pinned-payee and single-quote validation boundary. Use the separate [AgentCash / Poncho guide](https://first-dollar-test.com/agentcash.md) only with that limitation understood.

Alternatively, import the TypeScript module from a caller-owned participant program as shown above.

The `signer` is a callback to a caller-owned wallet service. Do not pass a private key to this module. The runner accepts only the returned `PAYMENT-SIGNATURE` value, keeps it in memory for one dispatch, removes it from its local reference immediately afterward, and does not intentionally add it to progress events or results. Direct-import callers must still treat all server response objects as untrusted and avoid logging raw results; the executable CLI applies the stricter allowlisted output projection described above.

`checkpoint` is optional but recommended for a real paid run. It receives the private run capability and idempotency material before run creation, after the run ID is known, immediately before the wallet is called, immediately before the sole signed enrollment request, and before submission. Save it only in caller-owned encrypted storage; never log it or put it in a URL. The direct import does not make a checkpoint durable for you: the callback must fsync the file and parent directory, commit a database transaction, or provide an equivalent guarantee before its promise resolves. A buffered write is insufficient. If the callback fails, the runner stops before the next irreversible request.

The direct import contains no resume implementation, does not reserve a create-only state path, and cannot control caller logging. `payment_attempt_started` means the wallet may or may not have signed. `payment_dispatched` is saved before the only signed HTTP request, so it may exist when a crash occurred just before transmission. In either case, poll or recover the recorded run and never assume it is safe to sign again. Do not log callback inputs, raw responses, checkpoints, capabilities, or signatures. The executable CLI supplies the create-only crash-durable state protocol and allowlisted stdout/stderr projection; the import alone does not.

Set `discovery` honestly. The example uses `human_handoff` because a person supplied the service and runner. This is stored as self-report and does not prove organic or autonomous discovery; use `x402_discovery`, `mcp_registry`, `a2a`, `direct_url`, `google`, or `unknown` only when it describes the actual first touch.

Before invoking the signer, the runner requires all of the following:

- HTTPS API origin and active Base mainnet opportunity
- native Base USDC at `0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913`
- the release-pinned Beta v4 treasury `0xACB5056DF623fe3f65e98d06485df498616D87Ec`; the runner never learns its trust anchor from the live response
- x402 v2 `exact` requirements and the canonical enrollment resource URL
- exact immutable terms `2026-09-21-beta-v4`: treasury/payee `0xACB5056DF623fe3f65e98d06485df498616D87Ec`, task version `orders-v1`, 1,000,000-unit fee, 1,000,000-unit PASS reward, one attempt, 900 task seconds, paid admission closing at `2026-09-28T06:00:00Z`, fee-backed full failure refund and sponsored payout
- agreement between every economic term in the live opportunity, run and receipt, plus agreement between the `PAYMENT-REQUIRED` header and body

The runner also refuses to create a run at or after that immutable close boundary, even if a stale opportunity response still claims admission is open. A later release needs a separately reviewed runner contract; callers cannot override the version, duration or close time.

An HTTP 202 or a lost enrollment response causes polling of the original run. The runner never asks the signer for a second authorization and never retransmits the signed enrollment request. Submission transport retries reuse the same answer and idempotency key. Receipt polling accepts completion only when it independently sees a PASS with a finalized 1 USDC reward, or a terminal failure with a finalized 1 USDC refund; a bare `settlement.complete`, pending transfer, inconsistent amount or manual-review state is rejected.

The module and bundled CLI deliberately contain no task-specific solution. A caller must supply `solver`, so the formal result measures that caller's behavior. Importing the module, downloading the bundle, or running `--help` performs no network action. Using this reference client does not itself satisfy the external-Agent milestone: the participant, wallet and operator must remain independent of the project, and the resulting run still has to pass the separate evidence gate with a finalized 1 USDC reward. A human handoff remains a human handoff rather than organic discovery.
