Commands
agent-commerce reconcile
Resolve uncertain payment attempts from read-only chain evidence, without broadcasting, refunding or retrying delivery.
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>--env-file <path>--dry-rundefault: false--jsondefault: false--actor <name>default: the OS user--min-age <duration>default: 15mreserved 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 limitOutcomes
| Outcome | Requires | Recorded as |
|---|---|---|
settled | a final transfer matching the authorization | settled, with its transaction ID |
not-settled | final 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 nonpayment | rejected, with rejectionReason not_settled |
unknown | anything inconclusive; the report gives a reason and, when known, a time to try again | nothing: 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
| Family | How an attempt is found |
|---|---|
| EVM | the 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) |
| Solana | the 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 |
| NEAR | the 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
$ 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.
unknown results, retry after retryAfterwhen provided, or investigate on chain and record a verdict with agent-commerce resolve.