Commands

agent-commerce reconcile

Resolve uncertain payment attempts from read-only chain evidence, without broadcasting, refunding or retrying delivery.

Usagebash
agent-commerce reconcile [--config <path>] [--env-file <path>] [--dry-run] [--json]
                         [--actor <name>] [--min-age <duration>] [--limit <n>]

Resolves payment attempts whose settlement outcome the gateway did not confirm: settlement-uncertain and legacy failed attempts, plus reserved attempts in the journal at least --min-age old. Payment providers read chain evidence over RPC. The command builds providers as start does, without starting a listener. It makes no facilitator calls, broadcasts, refunds or delivery retries, and works with the gateway running or stopped.

Databases below schema v5 are refused without migration, with FAIL reconcile could not run: … is at schema v4 …. Run backup, start the 2.6.0 gateway once, then retry. With AP2 enabled, reconcile also refuses an existing, unimported authorization.ap2.replay.path file; start imports it.

Options

--config <path>
Path to the config.
--env-file <path>
Load environment variables before reading the config. Variables already set in the environment take precedence.
--dry-rundefault: false
Check evidence and report without recording resolutions or hold updates. It still opens and initializes the receipt database.
--jsondefault: false
Machine-readable JSON instead of the formatted report. Warnings go to stderr, so stdout stays parseable.
--actor <name>default: the OS user
A label for who ran it, stored with each resolution. The caller supplies it; it does not authenticate their identity.
--min-age <duration>default: 15m
Skip reserved attempts younger than this, because the gateway may still be settling them. Whole s, m, h or d, such as 30s or 2h, or 0.
--limit <n>default: no limit
Apply this limit separately to attempts, hold updates and orphan hold scans.

Outcomes

OutcomeRequiresRecorded as
settleda final transfer matching the authorizationsettled, with its transaction ID
not-settledfinal chain state showing the authorization unused after its last valid second on EVM, or its maxBlockHeight on NEAR. Never returned on Solana: blockhash expiry alone does not prove nonpaymentrejected, with rejectionReason not_settled
unknownanything inconclusive; the report gives a reason and, when known, a time to try againnothing: the attempt stays unresolved

Missing logs, receipts or history entries alone do not prove nonpayment. Each resolution records its method, --actor, the provider's reason (such as transfer_final or expired_unused), time and any public chain evidence. The write requires the attempt's expected status and no earlier resolution, so competing runs cannot overwrite each other.

Evidence by chain

FamilyHow an attempt is found
EVMthe recorded transaction hash, or a log search for the used nonce near the attempt's creation time and within its validity bounds. The search covers at most 200,000 blocks; a miss with range left unsearched is unknown (search_window_too_wide)
Solanathe recorded signature, or a search of a known payer's history; only successful finalized transactions count. A search that reaches its page cap is history_too_long; otherwise a miss is not_expired while the blockhash is valid, then blockhash_expired
NEARthe recorded transaction hash and the relayer IDs from the facilitator's /supported, both needed to prove settled. Without them, final access-key state can still prove not-settled after expiry

Other unknown reasons include not_final for a referenced EVM or NEAR transaction awaiting finality, chain_mismatch when the RPC serves another chain, used_without_record for a used nonce with no matching transfer, no_locator for missing coordinates (for example, on attempts written before 2.5.0), and rpc_error. Reconciliation reads the RPC of the offer the attempt paid, so run it with the configuration that took the payment: a disabled rail gives provider_not_enabled, and an offer no longer configured gives network_not_configured. EVM reconciliation covers x402 EIP-3009, Permit2 and MPP. Tests use Anvil for EVM, solana-test-validator for Solana and a local NEAR sandbox. Public-network validation is pending.

Order and AP2 holds

Journal entries are checked in this order: payment-uncertain, then payment-pending, then delivery-started. Each group is newest first. A capped batch of uncertain attempts recorded before the journal follows. With authorization.ap2 enabled, the run also tries to align pending mandate holds: settled consumes a hold and rejected releases it. It updates holds in the receipt database without verifying mandates. Later runs can retry pending updates, including those left by a resolve run with AP2 disabled.

A request can leave a reserved hold without a current or archived payment attempt. Attempts are reserved before settlement. With AP2 enabled, reconcile releases orphan holds whose last update predates --min-age, listing them as ORPHAN. A dry run lists one page at most and releases nothing. Age reduces races with active requests but cannot prove they have finished.

Output

console
$ agent-commerce reconcile --env-file .env --dry-run
reconcile (dry run, no resolutions written): 2 candidates, 1 settled, 0 not settled, 1 unknown, 0 resolved
SETTLED     attempt_7c1e… request=req_51d0… provider=x402 status=settlement-uncertain reason=transfer_final reference=0x4f2c…9ab1
UNKNOWN     attempt_93ab… request=mcp_0e2f… provider=x402 status=settlement-uncertain reason=not_expired retryAfter=2026-10-30T12:05:00.000Z

--json returns dryRun, counts, entries, holds, orphanHolds and failedScans. A completed run exits 0, even with unresolved attempts. Invalid options/config or incomplete runs exit 1. If a hold scan fails, saved resolutions remain and failedScans names the scan; rerun the command.

For unknown results, retry after retryAfterwhen provided, or investigate on chain and record a verdict with agent-commerce resolve.