Skill: vraspar/x402r-dispute (version 0.1.3)

--- name: x402r-dispute description: Pay merchants and file payment disputes on the x402r refundable payments protocol version: 0.2.0 author: x402r tags: [x402r, payments, disputes, web3, arbitration] --- # x402r CLI You help users make escrow payments and file payment disputes on the x402r protocol. The x402r protocol adds refundable payments to HTTP 402 — buyers can request refunds through on-chain arbitration. ## Setup ```bash npx --yes @x402r/cli config --key <private-key> --arbiter-url https://www.moltarbiter.com/arbiter ``` Operator, network, and RPC are auto-discovered from the arbiter. The wallet needs Base Sepolia ETH (gas) and USDC (payments). Test merchant: `https://fantastic-optimism-production-602a.up.railway.app/weather` ## Commands ### pay ```bash npx --yes @x402r/cli pay <url> npx --yes @x402r/cli pay <url> --output response.json ``` Makes an escrow payment. Saves payment info to `~/.x402r/last-payment.json` for later dispute. ### dispute ```bash npx --yes @x402r/cli dispute "reason" --evidence "details" ``` Creates on-chain refund request + submits evidence in one step. Uses saved payment info from `pay`. Prints a dashboard link. Options: `-e/--evidence <text>`, `-f/--file <path>`, `-p/--payment-json <json>`, `-n/--nonce <n>`, `-a/--amount <n>` ### status ```bash npx --yes @x402r/cli status ``` Check dispute status. Tries arbiter API first, falls back to on-chain. Options: `--id <compositeKey>`, `-p/--payment-json`, `-n/--nonce` ### show ```bash npx --yes @x402r/cli show ``` Show all evidence (payer, merchant, arbiter) for a dispute. Options: `-p/--payment-json`, `-n/--nonce` ### verify ```bash npx --yes @x402r/cli verify ``` Replay the arbiter's AI evaluation to verify the commitment hash is deterministic. Options: `-p/--payment-json`, `-n/--nonce` ### list ```bash npx --yes @x402r/cli list ``` List disputes from the arbiter. Options: `-r/--receiver <addr>`, `--offset <n>`, `--count <n>` ## Typical Workflow 1. `npx --yes @x402r/cli pay <merchant-url>` — escrow payment, saves state 2. `npx --yes @x402r/cli dispute "reason" --evidence "details"` — files dispute 3. `npx --yes @x402r/cli status` — check arbiter ruling 4. `npx --yes @x402r/cli show` — view evidence from all parties 5. `npx --yes @x402r/cli verify` — verify ruling was deterministic ## Notes - State chains between commands: `pay` saves for `dispute`, `dispute` saves for `status`/`show`/`verify`. - Without saved state, pass `--payment-json` and `--nonce` explicitly. - `verify` requires the arbiter server — no on-chain fallback.

path: bin/x402r.ts
content: #!/usr/bin/env tsx

/**
 * x402r CLI — Agent-friendly dispute resolution tool
 *
 * Usage:
 *   x402r config --key 0x... --operator 0x...
 *   x402r pay <url>
 *   x402r dispute "reason" --evidence "details"
 *   x402r status
 *   x402r verify
 *   x402r list
 *   x402r show
 */

import { Command } from "commander";
import { config as dotenvConfig } from "dotenv";
import { fileURLToPath } from "url";
import { dirname, join } from "path";
import { registerConfigCommand } from "../src/commands/config.js";
import { registerDisputeCommand } from "../src/commands/dispute.js";
import { registerStatusCommand } from "../src/commands/status.js";
import { registerVerifyCommand } from "../src/commands/verify.js";
import { registerListCommand } from "../src/commands/list.js";
import { registerShowCommand } from "../src/commands/show.js";
import { registerPayCommand } from "../src/commands/pay.js";

// Load .env from cli/ directory
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
dotenvConfig({ path: join(__dirname, "..", ".env") });

const program = new Command();

program
  .name("x402r")
  .description("Agent-friendly CLI for x402r dispute resolution")
  .version("0.1.0");

registerConfigCommand(program);
registerPayCommand(program);
registerDisputeCommand(program);
registerStatusCommand(program);
registerVerifyCommand(program);
registerListCommand(program);
registerShowCommand(program);

program.parse();


path: clawhub.json
content: {
  "name": "x402r-dispute",
  "tagline": "File and track payment disputes on the x402r refundable payments protocol",
  "description": "CLI tool for the x402r protocol that lets AI agents file payment disputes, check status, view evidence, and verify arbiter rulings. Designed for autonomous agent workflows with simple command patterns and JSON-parseable output.",
  "category": "development",
  "tags": ["x402r", "payments", "disputes", "web3", "arbitration"],
  "version": "0.1.2",
  "license": "MIT",
  "pricing": "free",
  "support_url": "https://github.com/x402r/x402r-arbiter-eigencloud/issues",
  "homepage": "https://x402r.org"
}


path: dist/bin/x402r.d.ts
content: #!/usr/bin/env tsx
/**
 * x402r CLI — Agent-friendly dispute resolution tool
 *
 * Usage:
 *   x402r config --key 0x... --operator 0x...
 *   x402r pay <url>
 *   x402r dispute "reason" --evidence "details"
 *   x402r status
 *   x402r verify
 *   x402r list
 *   x402r show
 */
export {};


path: dist/bin/x402r.js
content: #!/usr/bin/env node
/**
 * x402r CLI — Agent-friendly dispute resolution tool
 *
 * Usage:
 *   x402r config --key 0x... --operator 0x...
 *   x402r pay <url>
 *   x402r dispute "reason" --evidence "details"
 *   x402r status
 *   x402r verify
 *   x402r list
 *   x402r show
 */
import { Command } from "commander";
import { config as dotenvConfig } from "dotenv";
import { fileURLToPath } from "url";
import { dirname, join } from "path";
import { registerConfigCommand } from "../src/commands/config.js";
import { registerDisputeCommand } from "../src/commands/dispute.js";
import { registerStatusCommand } from "../src/commands/status.js";
import { registerVerifyCommand } from "../src/commands/verify.js";
import { registerListCommand } from "../src/commands/list.js";
import { registerShowCommand } from "../src/commands/show.js";
import { registerPayCommand } from "../src/commands/pay.js";
// Load .env from cli/ directory
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
dotenvConfig({ path: join(__dirname, "..", ".env") });
const program = new Command();
program
    .name("x402r")
    .description("Agent-friendly CLI for x402r dispute resolution")
    .version("0.1.0");
registerConfigCommand(program);
registerPayCommand(program);
registerDisputeCommand(program);
registerStatusCommand(program);
registerVerifyCommand(program);
registerListCommand(program);
registerShowCommand(program);
program.parse();
//# sourceMappingURL=x402r.js.map

path: dist/src/commands/config.d.ts
content: /**
 * config command — Save/load CLI configuration
 */
import type { Command } from "commander";
export declare function registerConfigCommand(program: Command): void;


path: dist/src/commands/config.js
content: /**
 * config command — Save/load CLI configuration
 */
import { saveConfigFile, printConfig } from "../config.js";
export function registerConfigCommand(program) {
    program
        .command("config")
        .description("Save or view CLI configuration")
        .option("-k, --key <privateKey>", "Set private key")
        .option("-o, --operator <address>", "Set operator address")
        .option("-a, --arbiter-url <url>", "Set arbiter server URL")
        .option("-c, --court-url <url>", "Set court UI URL (for independent verification)")
        .option("-n, --network <networkId>", "Set network ID (e.g., eip155:84532)")
        .option("-r, --rpc <url>", "Set RPC URL")
        .option("--pinata-jwt <jwt>", "Set Pinata JWT token")
        .action(async (options) => {
        const updates = {};
        let hasUpdates = false;
        if (options.key) {
            updates.privateKey = options.key;
            hasUpdates = true;
        }
        if (options.operator) {
            updates.operatorAddress = options.operator;
            hasUpdates = true;
        }
        if (options.arbiterUrl) {
            updates.arbiterUrl = options.arbiterUrl;
            hasUpdates = true;
        }
        if (options.courtUrl) {
            updates.courtUrl = options.courtUrl;
            hasUpdates = true;
        }
        if (options.network) {
            updates.networkId = options.network;
            hasUpdates = true;
        }
        if (options.rpc) {
            updates.rpcUrl = options.rpc;
            hasUpdates = true;
        }
        if (options.pinataJwt) {
            updates.pinataJwt = options.pinataJwt;
            hasUpdates = true;
        }
        if (hasUpdates) {
            saveConfigFile(updates);
            console.log("Config updated.");
        }
        await printConfig();
    });
}
//# sourceMappingURL=config.js.map

path: dist/src/commands/dispute.d.ts
content: /**
 * dispute command — All-in-one: requestRefund + pin evidence + submitEvidence
 */
import type { Command } from "commander";
export declare function registerDisputeCommand(program: Command): void;


path: dist/src/commands/dispute.js
content: /**
 * dispute command — All-in-one: requestRefund + pin evidence + submitEvidence
 */
import { keccak256, encodePacked } from "viem";
import { X402rClient } from "@x402r/client";
import { computePaymentInfoHash } from "@x402r/core";
import { initCli } from "../setup.js";
import { getPaymentInfo, saveDisputeState } from "../state.js";
import { pinToIpfs } from "../ipfs.js";
export function registerDisputeCommand(program) {
    program
        .command("dispute")
        .description("Create a dispute for the last payment (requestRefund + submit evidence)")
        .argument("<reason>", "Reason for the dispute")
        .option("-e, --evidence <text>", "Additional evidence text")
        .option("-f, --file <path>", "Path to evidence file (JSON)")
        .option("-p, --payment-json <json>", "Payment info JSON (uses saved state if omitted)")
        .option("-n, --nonce <nonce>", "Nonce (default: 0)", "0")
        .option("-a, --amount <amount>", "Refund amount (default: full payment amount)")
        .action(async (reason, options) => {
        const { publicClient, walletClient, addresses, operatorAddress } = await initCli();
        const paymentInfo = getPaymentInfo(options);
        const nonce = BigInt(options.nonce);
        const amount = options.amount ? BigInt(options.amount) : paymentInfo.maxAmount;
        console.log("\n=== Creating Dispute ===");
        console.log("  Reason:", reason);
        console.log("  Amount:", amount.toString());
        console.log("  Nonce:", nonce.toString());
        const client = new X402rClient({
            publicClient: publicClient,
            walletClient: walletClient,
            operatorAddress,
            refundRequestAddress: addresses.refundRequestAddress,
            refundRequestEvidenceAddress: addresses.evidenceAddress,
            chainId: addresses.chainId,
        });
        // Step 1: Request refund on-chain
        console.log("\n[1/3] Requesting refund on-chain...");
        let refundTxHash;
        try {
            const hasRequest = await client.hasRefundRequest(paymentInfo, nonce);
            if (hasRequest) {
                const status = await client.getRefundStatus(paymentInfo, nonce);
                const statusNames = ["Pending", "Approved", "Denied", "Cancelled"];
                console.log(`  Refund request already exists (status: ${statusNames[status] || status})`);
            }
            else {
                const { txHash } = await client.requestRefund(paymentInfo, amount, nonce);
                refundTxHash = txHash;
                console.log("  Refund requested:", txHash);
                console.log("  Waiting for confirmation...");
                await publicClient.waitForTransactionReceipt({ hash: txHash });
                // Brief delay to ensure state propagation on testnet RPCs
                await new Promise(resolve => setTimeout(resolve, 3000));
                console.log("  Confirmed.");
            }
        }
        catch (error) {
            console.error("  Failed to request refund:", error instanceof Error ? error.message : error);
            process.exit(1);
        }
        // Step 2: Build and pin evidence
        console.log("\n[2/3] Building and pinning evidence...");
        let fileContent;
        if (options.file) {
            try {
                const { readFileSync } = await import("fs");
                fileContent = JSON.parse(readFileSync(options.file, "utf-8"));
            }
            catch (error) {
                console.error("  Failed to read evidence file:", error instanceof Error ? error.message : error);
                process.exit(1);
            }
        }
        const evidenceData = {
            reason,
            timestamp: new Date().toISOString(),
            payer: paymentInfo.payer,
            receiver: paymentInfo.receiver,
        };
        if (options.evidence) {
            evidenceData.evidence = options.evidence;
        }
        if (fileContent) {
            evidenceData.attachments = fileContent;
        }
        let cid;
        try {
            cid = await pinToIpfs(evidenceData);
        }
        catch (error) {
            console.error("  Failed to pin evidence:", error instanceof Error ? error.message : error);
            process.exit(1);
        }
        // Step 3: Submit evidence on-chain (skip if payer already submitted)
        console.log("\n[3/3] Submitting evidence on-chain...");
        let evidenceTxHash;
        try {
            const existing = await client.getAllEvidence(paymentInfo, nonce);
            const alreadySubmitted = existing.some((e) => e.submitter.toLowerCase() === paymentInfo.payer.toLowerCase());
            if (alreadySubmitted) {
                console.log("  Evidence already submitted for this dispute — skipping");
            }
            else {
                const { txHash } = await client.submitEvidence(paymentInfo, nonce, cid);
                evidenceTxHash = txHash;
                console.log("  Evidence submitted:", txHash);
                console.log("  Waiting for confirmation...");
                await publicClient.waitForTransactionReceipt({ hash: txHash });
                console.log("  Confirmed.");
            }
        }
        catch (error) {
            console.error("  Failed to submit evidence:", error instanceof Error ? error.message : error);
            process.exit(1);
        }
        // Compute compositeKey for dashboard link
        const paymentHash = computePaymentInfoHash(addresses.chainId, addresses.escrowAddress, paymentInfo);
        const compositeKey = keccak256(encodePacked(["bytes32", "uint256"], [paymentHash, nonce]));
        // Save dispute state
        saveDisputeState({
            nonce: nonce.toString(),
            compositeKey,
            refundTxHash,
            evidenceTxHash,
            evidenceCid: cid,
            timestamp: new Date().toISOString(),
        });
        console.log("\n=== Dispute Created ===");
        if (refundTxHash)
            console.log("  Refund Tx:", refundTxHash);
        if (evidenceTxHash)
            console.log("  Evidence Tx:", evidenceTxHash);
        console.log("  Evidence CID:", cid);
        console.log("  Composite Key:", compositeKey);
        console.log("\n  State saved to ~/.x402r/last-dispute.json");
        console.log("  Run 'x402r status' to check dispute status");
        // Print dashboard link if arbiter URL is configured
        const { getConfig } = await import("../config.js");
        const config = getConfig();
        if (config.arbiterUrl) {
            const dashboardBase = config.arbiterUrl.replace(/\/arbiter\/?$/, "");
            console.log(`  Dashboard: ${dashboardBase}/dispute/${compositeKey}`);
        }
    });
}
//# sourceMappingURL=dispute.js.map

path: dist/src/commands/list.d.ts
content: /**
 * list command — List disputes via arbiter server or SDK
 */
import type { Command } from "commander";
export declare function registerListCommand(program: Command): void;


path: dist/src/commands/list.js
content: /**
 * list command — List disputes via arbiter server or SDK
 */
import { getConfig } from "../config.js";
export function registerListCommand(program) {
    program
        .command("list")
        .description("List pending disputes")
        .option("-r, --receiver <address>", "Filter by receiver address")
        .option("--offset <n>", "Offset for pagination", "0")
        .option("--count <n>", "Number of results", "20")
        .action(async (options) => {
        const config = getConfig();
        const url = config.arbiterUrl;
        console.log(`\nQuerying disputes from ${url}...`);
        const params = new URLSearchParams();
        if (options.receiver)
            params.set("receiver", options.receiver);
        params.set("offset", options.offset);
        params.set("count", options.count);
        try {
            const response = await fetch(`${url}/api/disputes?${params.toString()}`);
            if (!response.ok) {
                const error = await response.text();
                console.error(`\nArbiter returned ${response.status}:`, error);
                process.exit(1);
            }
            const data = await response.json();
            console.log(`\n=== Disputes (${data.keys.length} of ${data.total}) ===`);
            if (data.keys.length === 0) {
                console.log("  No pending disputes found.");
                return;
            }
            for (let i = 0; i < data.keys.length; i++) {
                console.log(`  [${parseInt(data.offset) + i}] ${data.keys[i]}`);
            }
            console.log(`\n  Showing ${data.offset}-${parseInt(data.offset) + data.keys.length} of ${data.total}`);
        }
        catch (error) {
            console.error("\nFailed to list disputes:", error instanceof Error ? error.message : error);
            console.error("Is the arbiter server running at", url, "?");
            process.exit(1);
        }
    });
}
//# sourceMappingURL=list.js.map

path: dist/src/commands/pay.d.ts
content: /**
 * pay command — Make an x402r escrow payment to a merchant
 *
 * 1. GET <url> → 402 with PAYMENT-REQUIRED header
 * 2. Sign escrow authorization locally using @x402r/evm scheme
 * 3. Retry with PAYMENT-SIGNATURE header → merchant's facilitator verifies + settles → 200
 * 4. Extract PaymentInfo from escrow payload via toPaymentInfo()
 * 5. Save to ~/.x402r/last-payment.json for later dispute
 * 6. Print response body
 */
import type { Command } from "commander";
export declare function registerPayCommand(program: Command): void;


path: dist/src/commands/pay.js
content: /**
 * pay command — Make an x402r escrow payment to a merchant
 *
 * 1. GET <url> → 402 with PAYMENT-REQUIRED header
 * 2. Sign escrow authorization locally using @x402r/evm scheme
 * 3. Retry with PAYMENT-SIGNATURE header → merchant's facilitator verifies + settles → 200
 * 4. Extract PaymentInfo from escrow payload via toPaymentInfo()
 * 5. Save to ~/.x402r/last-payment.json for later dispute
 * 6. Print response body
 */
import { x402Client, x402HTTPClient } from "@x402/core/client";
import { registerEscrowScheme } from "@x402r/evm/escrow/client";
import { toPaymentInfo, computePaymentInfoHash } from "@x402r/core";
import { initCli } from "../setup.js";
import { savePaymentState } from "../state.js";
import { getConfig } from "../config.js";
import * as fs from "fs";
export function registerPayCommand(program) {
    program
        .command("pay")
        .description("Pay a merchant via x402r escrow payment")
        .argument("<url>", "Merchant URL to pay")
        .option("-o, --output <path>", "Save response body to file")
        .action(async (url, options) => {
        const { account, networkId, operatorAddress, addresses } = await initCli();
        console.log("\n=== x402r Pay ===");
        console.log("  URL:", url);
        console.log("  Payer:", account.address);
        console.log("  Network:", networkId);
        // 1. Create x402 payment client with escrow scheme
        const paymentClient = new x402Client();
        registerEscrowScheme(paymentClient, { signer: account, networks: networkId });
        const httpClient = new x402HTTPClient(paymentClient);
        // 2. Initial request
        console.log("\n[1/4] Fetching resource...");
        const res402 = await fetch(url);
        if (res402.status !== 402) {
            console.log(`  Server returned ${res402.status} (no payment required)`);
            const body = await res402.text();
            console.log("\n--- Response ---");
            console.log(body);
            if (options.output) {
                fs.writeFileSync(options.output, body);
                console.log(`\n  Response saved to ${options.output}`);
            }
            return;
        }
        // 3. Parse payment requirements from 402 response
        console.log("  Got 402 Payment Required");
        console.log("\n[2/4] Parsing payment requirements...");
        let body;
        try {
            body = await res402.json();
        }
        catch {
            body = undefined;
        }
        const paymentRequired = httpClient.getPaymentRequiredResponse((name) => res402.headers.get(name), body);
        console.log("  Requirements parsed");
        // 4. Create signed payment payload
        console.log("\n[3/4] Signing escrow payment...");
        const paymentPayload = await httpClient.createPaymentPayload(paymentRequired);
        const paymentHeaders = httpClient.encodePaymentSignatureHeader(paymentPayload);
        console.log("  Payment signed");
        // 5. Retry with payment header
        console.log("\n[4/4] Sending payment...");
        const res200 = await fetch(url, { headers: paymentHeaders });
        if (!res200.ok) {
            console.error(`  Payment failed: ${res200.status} ${res200.statusText}`);
            try {
                const errorBody = await res200.text();
                console.error("  ", errorBody);
            }
            catch { /* ignore */ }
            process.exit(1);
        }
        console.log(`  Payment accepted (${res200.status})`);
        // 6. Extract settlement info from response headers
        let settleTxHash;
        try {
            const settleResponse = httpClient.getPaymentSettleResponse((name) => res200.headers.get(name));
            settleTxHash = settleResponse?.transaction;
        }
        catch {
            // Settlement headers may not be present
        }
        // 7. Extract PaymentInfo from escrow payload
        const escrowPayload = paymentPayload.payload;
        const paymentInfo = toPaymentInfo(escrowPayload);
        const paymentHash = computePaymentInfoHash(addresses.chainId, addresses.escrowAddress, paymentInfo);
        // 8. Save payment state for later dispute
        savePaymentState({
            paymentInfo,
            operatorAddress,
            paymentHash,
            timestamp: new Date().toISOString(),
            networkId,
        });
        // 9. Cache payment info on arbiter for dashboard lookups
        const config = getConfig();
        try {
            const piSerialized = {
                operator: paymentInfo.operator,
                payer: paymentInfo.payer,
                receiver: paymentInfo.receiver,
                [REDACTED_SECRET],
                maxAmount: paymentInfo.maxAmount.toString(),
                preApprovalExpiry: paymentInfo.preApprovalExpiry.toString(),
                authorizationExpiry: paymentInfo.authorizationExpiry.toString(),
                refundExpiry: paymentInfo.refundExpiry.toString(),
                minFeeBps: paymentInfo.minFeeBps,
                maxFeeBps: paymentInfo.maxFeeBps,
                feeReceiver: paymentInfo.feeReceiver,
                salt: paymentInfo.salt.toString(),
            };
            await fetch(`${config.arbiterUrl}/api/payment-info`, {
                method: "POST",
                headers: { "Content-Type": "application/json" },
                body: JSON.stringify(piSerialized),
            });
        }
        catch {
            // Non-critical — dashboard lookup may not work but payment is saved locally
        }
        // 10. Print response
        const responseBody = await res200.text();
        console.log("\n--- Response ---");
        console.log(responseBody);
        if (options.output) {
            fs.writeFileSync(options.output, responseBody);
            console.log(`\n  Response saved to ${options.output}`);
        }
        console.log("\n=== Payment Complete ===");
        if (settleTxHash) {
            console.log("  Settle Tx:", settleTxHash);
        }
        console.log("  Payment Hash:", paymentHash);
        console.log("  State saved to ~/.x402r/last-payment.json");
        console.log("  Run 'x402r dispute \"reason\"' to dispute this payment");
    });
}
//# sourceMappingURL=pay.js.map

path: dist/src/commands/show.d.ts
content: /**
 * show command — Show evidence for a dispute
 */
import type { Command } from "commander";
export declare function registerShowCommand(program: Command): void;


path: dist/src/commands/show.js
content: /**
 * show command — Show evidence for a dispute
 */
import { X402rClient } from "@x402r/client";
import { SubmitterRole } from "@x402r/core";
import { initReadOnly } from "../setup.js";
import { getPaymentInfo, getNonce } from "../state.js";
function shortAddress(address) {
    return `${address.slice(0, 10)}...${address.slice(-8)}`;
}
function roleName(role) {
    switch (role) {
        case SubmitterRole.Payer:
            return "Payer";
        case SubmitterRole.Receiver:
            return "Receiver";
        case SubmitterRole.Arbiter:
            return "Arbiter";
        default:
            return `Unknown(${role})`;
    }
}
function formatEvidence(evidence, index) {
    const ts = new Date(Number(evidence.timestamp) * 1000).toISOString();
    return `  [${index}] ${roleName(evidence.role)} ${shortAddress(evidence.submitter)} | ${ts} | CID: ${evidence.cid}`;
}
export function registerShowCommand(program) {
    program
        .command("show")
        .description("Show all evidence for a dispute")
        .option("-p, --payment-json <json>", "Payment info JSON (uses saved state if omitted)")
        .option("-n, --nonce <nonce>", "Nonce")
        .action(async (options) => {
        const { publicClient, addresses, operatorAddress } = await initReadOnly();
        const paymentInfo = getPaymentInfo(options);
        const nonce = getNonce(options);
        const client = new X402rClient({
            publicClient: publicClient,
            operatorAddress,
            refundRequestEvidenceAddress: addresses.evidenceAddress,
            chainId: addresses.chainId,
        });
        console.log("\nFetching evidence...");
        const count = await client.getEvidenceCount(paymentInfo, nonce);
        console.log(`\n=== Evidence (${count} entries) ===`);
        if (count === 0n) {
            console.log("  No evidence submitted.");
            return;
        }
        const entries = await client.getAllEvidence(paymentInfo, nonce);
        for (let i = 0; i < entries.length; i++) {
            console.log(formatEvidence(entries[i], i));
        }
    });
}
//# sourceMappingURL=show.js.map

path: dist/src/commands/status.d.ts
content: /**
 * status command — Check dispute status via arbiter server or SDK
 */
import type { Command } from "commander";
export declare function registerStatusCommand(program: Command): void;


path: dist/src/commands/status.js
content: /**
 * status command — Check dispute status via arbiter server or SDK
 */
import { X402rClient } from "@x402r/client";
import { initReadOnly } from "../setup.js";
import { getPaymentInfo, getNonce, getCompositeKey } from "../state.js";
import { getConfig } from "../config.js";
export function registerStatusCommand(program) {
    program
        .command("status")
        .description("Check the status of a dispute")
        .option("--id <compositeKey>", "Composite key of the dispute")
        .option("-p, --payment-json <json>", "Payment info JSON (uses saved state if omitted)")
        .option("-n, --nonce <nonce>", "Nonce")
        .action(async (options) => {
        const config = getConfig();
        const compositeKey = getCompositeKey(options);
        // Try arbiter server first if composite key available
        if (compositeKey && config.arbiterUrl) {
            try {
                console.log(`\nQuerying arbiter at ${config.arbiterUrl}...`);
                const response = await fetch(`${config.arbiterUrl}/api/dispute/${compositeKey}`);
                if (response.ok) {
                    const data = await response.json();
                    const statusNames = { 0: "Pending", 1: "Approved", 2: "Denied", 3: "Cancelled" };
                    console.log("\n=== Dispute Status ===");
                    console.log("  Composite Key:", compositeKey);
                    console.log("  Status:", statusNames[data.status] || data.status);
                    console.log("  Amount:", data.amount);
                    console.log("  Nonce:", data.nonce);
                    console.log("  Payment Info Hash:", data.paymentInfoHash);
                    return;
                }
                console.log("  Arbiter returned", response.status, "— falling back to on-chain");
            }
            catch {
                console.log("  Arbiter unavailable — falling back to on-chain");
            }
        }
        // Fall back to on-chain query
        const { publicClient, addresses, operatorAddress } = await initReadOnly();
        const paymentInfo = getPaymentInfo(options);
        const nonce = getNonce(options);
        const client = new X402rClient({
            publicClient: publicClient,
            operatorAddress,
            refundRequestAddress: addresses.refundRequestAddress,
            chainId: addresses.chainId,
        });
        const hasRequest = await client.hasRefundRequest(paymentInfo, nonce);
        if (!hasRequest) {
            console.log("\nNo refund request found for this payment (nonce:", nonce.toString(), ")");
            return;
        }
        const status = await client.getRefundStatus(paymentInfo, nonce);
        const statusNames = ["Pending", "Approved", "Denied", "Cancelled"];
        console.log("\n=== Dispute Status (on-chain) ===");
        console.log("  Status:", statusNames[status] || status);
        console.log("  Nonce:", nonce.toString());
    });
}
//# sourceMappingURL=status.js.map

path: dist/src/commands/verify.d.ts
content: /**
 * verify command — Replay arbiter evaluation via court-ui's independent verifier
 */
import type { Command } from "commander";
export declare function registerVerifyCommand(program: Command): void;


path: dist/src/commands/verify.js
content: /**
 * verify command — Replay arbiter evaluation via court-ui's independent verifier
 */
import { initReadOnly } from "../setup.js";
import { getPaymentInfo, getNonce } from "../state.js";
import { getConfig } from "../config.js";
export function registerVerifyCommand(program) {
    program
        .command("verify")
        .description("Verify arbiter ruling by replaying AI evaluation (via court-ui)")
        .option("-p, --payment-json <json>", "Payment info JSON (uses saved state if omitted)")
        .option("-n, --nonce <nonce>", "Nonce")
        .option("-c, --court-url <url>", "Court UI URL (overrides config)")
        .action(async (options) => {
        const config = getConfig();
        await initReadOnly();
        const paymentInfo = getPaymentInfo(options);
        const nonce = getNonce(options);
        const courtUrl = options.courtUrl || config.courtUrl;
        if (!courtUrl) {
            console.error("\nError: Court UI URL not configured.");
            console.error("Set it with: x402r config --court-url https://your-court-ui.vercel.app");
            console.error("Or pass it directly: x402r verify --court-url https://...");
            process.exit(1);
        }
        console.log(`\nVerifying dispute via ${courtUrl}...`);
        try {
            const response = await fetch(`${courtUrl}/api/verify`, {
                method: "POST",
                headers: { "Content-Type": "application/json" },
                body: JSON.stringify({
                    paymentInfo: JSON.parse(JSON.stringify(paymentInfo, (_, v) => (typeof v === "bigint" ? v.toString() : v))),
                    nonce: nonce.toString(),
                }),
            });
            if (!response.ok) {
                const error = await response.text();
                console.error(`\nCourt UI returned ${response.status}:`, error);
                process.exit(1);
            }
            const data = await response.json();
            console.log("\n=== Verification Result ===");
            console.log("\n  Replay Commitment:", data.replayCommitment.commitmentHash);
            console.log("  Prompt Hash:", data.replayCommitment.promptHash);
            console.log("  Response Hash:", data.replayCommitment.responseHash);
            console.log("  Seed:", data.replayCommitment.seed);
            if (data.originalCommitment) {
                const match = data.replayCommitment.commitmentHash === data.originalCommitment.commitmentHash;
                console.log("\n  Original Commitment:", data.originalCommitment.commitmentHash);
                console.log(`  Match: ${match ? "MATCH" : "MISMATCH"}`);
            }
            // Try to parse the AI response
            try {
                const decision = JSON.parse(data.displayContent);
                console.log("\n=== AI Decision (Replay) ===");
                console.log("  Decision:", decision.decision);
                console.log("  Confidence:", decision.confidence);
                console.log("  Reasoning:", decision.reasoning);
            }
            catch {
                console.log("\n  Raw Response:", data.displayContent);
            }
            console.log("\n  Note:", data.note);
        }
        catch (error) {
            console.error("\nFailed to verify:", error instanceof Error ? error.message : error);
            console.error("Is the court UI running at", courtUrl, "?");
            process.exit(1);
        }
    });
}
//# sourceMappingURL=verify.js.map

path: dist/src/config.d.ts
content: /**
 * CLI configuration — loads from ~/.x402r/config.json, .env, and env vars.
 * Priority: env vars > .env > config file > arbiter auto-discovery > defaults
 */
export interface CliConfigFile {
    privateKey?: string;
    operatorAddress?: string;
    arbiterUrl?: string;
    courtUrl?: string;
    networkId?: string;
    rpcUrl?: string;
    pinataJwt?: string;
}
interface ArbiterContracts {
    chainId: number;
    rpcUrl: string;
    operatorAddress: string | null;
}
/**
 * Fetch operator address, network, and RPC from the arbiter's /api/contracts endpoint.
 */
export declare function fetchArbiterContracts(arbiterUrl: string): Promise<ArbiterContracts | null>;
/**
 * Load config from ~/.x402r/config.json
 */
export declare function loadConfigFile(): CliConfigFile;
/**
 * Save config to ~/.x402r/config.json
 */
export declare function saveConfigFile(config: CliConfigFile): void;
/**
 * Get resolved config: env vars > .env > config file > defaults
 */
export declare function getConfig(): Required<Pick<CliConfigFile, "networkId" | "arbiterUrl">> & CliConfigFile;
/**
 * Get resolved config with auto-discovery from arbiter.
 * Falls back to local config if arbiter is unreachable.
 */
export declare function getConfigWithDiscovery(): Promise<Required<Pick<CliConfigFile, "networkId" | "arbiterUrl">> & CliConfigFile>;
/**
 * Print current config (masked key)
 */
export declare function printConfig(): Promise<void>;
export {};


path: dist/src/config.js
content: /**
 * CLI configuration — loads from ~/.x402r/config.json, .env, and env vars.
 * Priority: env vars > .env > config file > arbiter auto-discovery > defaults
 */
import * as fs from "fs";
import * as path from "path";
import * as os from "os";
const CONFIG_DIR = path.join(os.homedir(), ".x402r");
const CONFIG_FILE = path.join(CONFIG_DIR, "config.json");
/** Cache so we only fetch once per CLI invocation */
let arbiterContractsCache = null;
/**
 * Fetch operator address, network, and RPC from the arbiter's /api/contracts endpoint.
 */
export async function fetchArbiterContracts(arbiterUrl) {
    if (arbiterContractsCache)
        return arbiterContractsCache;
    try {
        const res = await fetch(`${arbiterUrl}/api/contracts`, { signal: AbortSignal.timeout(5000) });
        if (!res.ok)
            return null;
        const data = (await res.json());
        arbiterContractsCache = data;
        return data;
    }
    catch {
        return null;
    }
}
/**
 * Load config from ~/.x402r/config.json
 */
export function loadConfigFile() {
    if (!fs.existsSync(CONFIG_FILE)) {
        return {};
    }
    try {
        return JSON.parse(fs.readFileSync(CONFIG_FILE, "utf-8"));
    }
    catch {
        return {};
    }
}
/**
 * Save config to ~/.x402r/config.json
 */
export function saveConfigFile(config) {
    if (!fs.existsSync(CONFIG_DIR)) {
        fs.mkdirSync(CONFIG_DIR, { recursive: true });
    }
    // Merge with existing config
    const existing = loadConfigFile();
    const merged = { ...existing, ...config };
    // Remove undefined/null values
    for (const key of Object.keys(merged)) {
        if (merged[key] === undefined || merged[key] === null) {
            delete merged[key];
        }
    }
    fs.writeFileSync(CONFIG_FILE, JSON.stringify(merged, null, 2));
}
/**
 * Get resolved config: env vars > .env > config file > defaults
 */
export function getConfig() {
    const file = loadConfigFile();
    return {
        privateKey: process.env.PRIVATE_KEY || file.privateKey,
        operatorAddress: process.env.OPERATOR_ADDRESS || file.operatorAddress,
        arbiterUrl: process.env.ARBITER_URL || file.arbiterUrl || "http://localhost:3000",
        courtUrl: process.env.COURT_URL || file.courtUrl,
        networkId: process.env.NETWORK_ID || file.networkId || "eip155:11155111",
        rpcUrl: process.env.RPC_URL || file.rpcUrl,
        pinataJwt: process.env.PINATA_JWT || file.pinataJwt,
    };
}
/**
 * Get resolved config with auto-discovery from arbiter.
 * Falls back to local config if arbiter is unreachable.
 */
export async function getConfigWithDiscovery() {
    const config = getConfig();
    // If operator or network are already set locally, skip discovery
    if (config.operatorAddress && config.networkId !== "eip155:11155111") {
        return config;
    }
    const contracts = await fetchArbiterContracts(config.arbiterUrl);
    if (contracts) {
        if (!config.operatorAddress && contracts.operatorAddress) {
            config.operatorAddress = contracts.operatorAddress;
        }
        if (contracts.chainId) {
            config.networkId = `eip155:${contracts.chainId}`;
        }
        if (!config.rpcUrl && contracts.rpcUrl) {
            config.rpcUrl = contracts.rpcUrl;
        }
    }
    return config;
}
/**
 * Print current config (masked key)
 */
export async function printConfig() {
    const config = await getConfigWithDiscovery();
    console.log("\n=== x402r CLI Config ===");
    console.log("  Private Key:", config.privateKey ? `${config.privateKey.slice(0, 6)}...${config.privateKey.slice(-4)}` : "(not set)");
    console.log("  Operator:", config.operatorAddress || "(not set)");
    console.log("  Arbiter URL:", config.arbiterUrl);
    console.log("  Court URL:", config.courtUrl || "(not set)");
    console.log("  Network:", config.networkId);
    console.log("  RPC URL:", config.rpcUrl || "(chain default)");
    console.log("  Pinata JWT:", config.pinataJwt ? `${config.pinataJwt.slice(0, 12)}...` : "(not set)");
    console.log(`\n  Config file: ${CONFIG_FILE}`);
}
//# sourceMappingURL=config.js.map

path: dist/src/ipfs.d.ts
content: /**
 * IPFS pinning — uses Pinata JWT, falls back to placeholder CID.
 */
/**
 * Pin JSON to IPFS.
 * 1. Pinata JWT (required for production use)
 * 2. Placeholder CID fallback (dev/testing only)
 */
export declare function pinToIpfs(data: Record<string, unknown>): Promise<string>;


path: dist/src/ipfs.js
content: /**
 * IPFS pinning — uses Pinata JWT, falls back to placeholder CID.
 */
import { getConfig } from "./config.js";
/**
 * Pin JSON to IPFS.
 * 1. Pinata JWT (required for production use)
 * 2. Placeholder CID fallback (dev/testing only)
 */
export async function pinToIpfs(data) {
    const config = getConfig();
    if (config.pinataJwt) {
        console.log("  Pinning to IPFS via Pinata...");
        try {
            const response = await fetch("https://api.pinata.cloud/pinning/pinJSONToIPFS", {
                method: "POST",
                headers: {
                    "Content-Type": "application/json",
                    Authorization: `Bearer ${config.pinataJwt}`,
                },
                body: JSON.stringify({
                    pinataContent: data,
                    pinataMetadata: { name: `x402r-evidence-${Date.now()}` },
                }),
            });
            if (response.ok) {
                const result = (await response.json());
                console.log(`  Pinned: ${result.IpfsHash}`);
                return result.IpfsHash;
            }
            console.warn(`  Pinata failed (${response.status})`);
        }
        catch (err) {
            console.warn(`  Pinata error:`, err instanceof Error ? err.message : err);
        }
    }
    // Placeholder fallback
    console.log("  (Using placeholder CID — set pinataJwt in config for production)");
    return [REDACTED_SECRET];
}
//# sourceMappingURL=ipfs.js.map

path: dist/src/setup.d.ts
content: /**
 * CLI setup — creates viem clients from resolved config.
 * Ported from x402r-sdk/examples/dev-tools/shared/cli-setup.ts with config file support.
 */
import { type PublicClient, type WalletClient } from "viem";
import { type PrivateKeyAccount } from "viem/accounts";
import { type ResolvedAddresses } from "@x402r/core";
export interface CliSetup {
    account: PrivateKeyAccount;
    publicClient: PublicClient;
    walletClient: WalletClient;
    networkId: string;
    addresses: ResolvedAddresses;
    operatorAddress: `0x${string}`;
    arbiterUrl: string;
}
/**
 * Initialize CLI: validates config, creates viem clients.
 * Auto-discovers operator and network from arbiter if not set locally.
 */
export declare function initCli(): Promise<CliSetup>;
/**
 * Read-only setup — no private key required.
 * Auto-discovers operator and network from arbiter if not set locally.
 */
export declare function initReadOnly(): Promise<Pick<CliSetup, "publicClient" | "networkId" | "addresses" | "arbiterUrl" | "operatorAddress">>;


path: dist/src/setup.js
content: /**
 * CLI setup — creates viem clients from resolved config.
 * Ported from x402r-sdk/examples/dev-tools/shared/cli-setup.ts with config file support.
 */
import { createPublicClient, createWalletClient, http, } from "viem";
import { baseSepolia, base, sepolia } from "viem/chains";
import { privateKeyToAccount } from "viem/accounts";
import { resolveAddresses } from "@x402r/core";
import { getConfigWithDiscovery } from "./config.js";
const CHAINS = {
    84532: baseSepolia,
    8453: base,
    11155111: sepolia,
};
/**
 * Initialize CLI: validates config, creates viem clients.
 * Auto-discovers operator and network from arbiter if not set locally.
 */
export async function initCli() {
    const config = await getConfigWithDiscovery();
    if (!config.privateKey) {
        console.error("Error: Private key not configured.");
        console.error("Run: x402r config --key 0x...");
        process.exit(1);
    }
    if (!config.operatorAddress) {
        console.error("Error: Operator address not configured.");
        console.error("Run: x402r config --operator 0x...");
        process.exit(1);
    }
    const account = privateKeyToAccount(config.privateKey);
    const addresses = resolveAddresses(config.networkId);
    const chainId = addresses.chainId;
    const chain = CHAINS[chainId];
    if (!chain) {
        console.error(`Unsupported chain ID: ${chainId} (network: ${config.networkId})`);
        process.exit(1);
    }
    const rpcUrl = config.rpcUrl?.startsWith("http") ? config.rpcUrl : undefined;
    const transport = http(rpcUrl);
    const publicClient = createPublicClient({ chain, transport });
    const walletClient = createWalletClient({ account, chain, transport });
    return {
        account,
        publicClient,
        walletClient,
        networkId: config.networkId,
        addresses,
        operatorAddress: config.operatorAddress,
        arbiterUrl: config.arbiterUrl,
    };
}
/**
 * Read-only setup — no private key required.
 * Auto-discovers operator and network from arbiter if not set locally.
 */
export async function initReadOnly() {
    const config = await getConfigWithDiscovery();
    const addresses = resolveAddresses(config.networkId);
    const chainId = addresses.chainId;
    const chain = CHAINS[chainId];
    if (!chain) {
        console.error(`Unsupported chain ID: ${chainId}`);
        process.exit(1);
    }
    const rpcUrl = config.rpcUrl?.startsWith("http") ? config.rpcUrl : undefined;
    const publicClient = createPublicClient({ chain, transport: http(rpcUrl) });
    return {
        publicClient,
        networkId: config.networkId,
        addresses,
        arbiterUrl: config.arbiterUrl,
        operatorAddress: (config.operatorAddress || "0x0000000000000000000000000000000000000000"),
    };
}
//# sourceMappingURL=setup.js.map

path: dist/src/state.d.ts
content: /**
 * State persistence — saves payment and dispute state to ~/.x402r/
 * Ported from x402r-sdk/examples/dev-tools/shared/state.ts with dispute state added.
 */
import type { PaymentInfo } from "@x402r/core";
export interface PaymentState {
    paymentInfo: PaymentInfo;
    operatorAddress: string;
    paymentHash: string;
    timestamp: string;
    networkId: string;
    merchantPrivateKey?: string;
}
export declare function savePaymentState(state: PaymentState): void;
export declare function loadPaymentState(): PaymentState | null;
/**
 * Get PaymentInfo from CLI options or state file.
 */
export declare function getPaymentInfo(options: {
    paymentJson?: string;
}): PaymentInfo;
export interface DisputeState {
    nonce: string;
    compositeKey?: string;
    refundTxHash?: string;
    evidenceTxHash?: string;
    evidenceCid?: string;
    arbiterResponse?: Record<string, unknown>;
    timestamp: string;
}
export declare function saveDisputeState(state: DisputeState): void;
export declare function loadDisputeState(): DisputeState | null;
/**
 * Get nonce from CLI options or dispute state.
 */
export declare function getNonce(options: {
    nonce?: string;
    id?: string;
}): bigint;
/**
 * Get composite key from CLI options or dispute state.
 */
export declare function getCompositeKey(options: {
    id?: string;
}): string | undefined;


path: dist/src/state.js
content: /**
 * State persistence — saves payment and dispute state to ~/.x402r/
 * Ported from x402r-sdk/examples/dev-tools/shared/state.ts with dispute state added.
 */
import * as fs from "fs";
import * as path from "path";
import * as os from "os";
import { parsePaymentInfo as coreParsePaymentInfo } from "@x402r/core";
const STATE_DIR = path.join(os.homedir(), ".x402r");
const PAYMENT_STATE_FILE = path.join(STATE_DIR, "last-payment.json");
const DISPUTE_STATE_FILE = path.join(STATE_DIR, "last-dispute.json");
function ensureDir() {
    if (!fs.existsSync(STATE_DIR)) {
        fs.mkdirSync(STATE_DIR, { recursive: true });
    }
}
export function savePaymentState(state) {
    ensureDir();
    const serializable = {
        ...state,
        paymentInfo: {
            ...state.paymentInfo,
            maxAmount: state.paymentInfo.maxAmount.toString(),
            preApprovalExpiry: state.paymentInfo.preApprovalExpiry.toString(),
            authorizationExpiry: state.paymentInfo.authorizationExpiry.toString(),
            refundExpiry: state.paymentInfo.refundExpiry.toString(),
            salt: state.paymentInfo.salt.toString(),
        },
    };
    fs.writeFileSync(PAYMENT_STATE_FILE, JSON.stringify(serializable, null, 2));
}
export function loadPaymentState() {
    if (!fs.existsSync(PAYMENT_STATE_FILE)) {
        return null;
    }
    try {
        const raw = JSON.parse(fs.readFileSync(PAYMENT_STATE_FILE, "utf-8"));
        return {
            ...raw,
            paymentInfo: {
                ...raw.paymentInfo,
                maxAmount: BigInt(raw.paymentInfo.maxAmount),
                preApprovalExpiry: BigInt(raw.paymentInfo.preApprovalExpiry),
                authorizationExpiry: BigInt(raw.paymentInfo.authorizationExpiry),
                refundExpiry: BigInt(raw.paymentInfo.refundExpiry),
                salt: BigInt(raw.paymentInfo.salt),
            },
        };
    }
    catch {
        return null;
    }
}
/**
 * Get PaymentInfo from CLI options or state file.
 */
export function getPaymentInfo(options) {
    if (options.paymentJson) {
        try {
            return coreParsePaymentInfo(options.paymentJson);
        }
        catch {
            console.error("Error: Invalid payment JSON");
            process.exit(1);
        }
    }
    const state = loadPaymentState();
    if (state) {
        console.log(`  (Using saved payment from ${state.timestamp})`);
        return state.paymentInfo;
    }
    console.error("Error: No payment state found. Make a payment first or provide --payment-json.");
    process.exit(1);
}
export function saveDisputeState(state) {
    ensureDir();
    fs.writeFileSync(DISPUTE_STATE_FILE, JSON.stringify(state, null, 2));
}
export function loadDisputeState() {
    if (!fs.existsSync(DISPUTE_STATE_FILE)) {
        return null;
    }
    try {
        return JSON.parse(fs.readFileSync(DISPUTE_STATE_FILE, "utf-8"));
    }
    catch {
        return null;
    }
}
/**
 * Get nonce from CLI options or dispute state.
 */
export function getNonce(options) {
    if (options.nonce !== undefined) {
        return BigInt(options.nonce);
    }
    const state = loadDisputeState();
    if (state) {
        console.log(`  (Using saved dispute nonce: ${state.nonce})`);
        return BigInt(state.nonce);
    }
    return 0n;
}
/**
 * Get composite key from CLI options or dispute state.
 */
export function getCompositeKey(options) {
    if (options.id)
        return options.id;
    const state = loadDisputeState();
    return state?.compositeKey;
}
//# sourceMappingURL=state.js.map

path: e2e-test.ts
content: /**
 * E2E Integration Test: CLI Dispute Flow
 *
 * Tests the x402r CLI commands end-to-end against real contracts.
 * Reuses the SDK's shared e2e infrastructure for account setup, operator
 * deployment, and HTTP 402 payment, then runs CLI commands via execSync.
 * Chain is determined by NETWORK_ID env var (default: shared infra default).
 *
 * Modes:
 *   - Default: deploys fresh operator, runs full 12-step lifecycle
 *   - OPERATOR_ADDRESS=0x...: uses existing operator, runs payment + dispute
 *     only (steps 1-7), skips SDK arbiter steps so the live arbiter server
 *     can evaluate the dispute and show it on the dashboard
 *
 * Flow (full):
 *   Setup → HTTP 402 Payment → CLI config → CLI dispute → CLI status →
 *   CLI show → Arbiter approve → CLI status → Execute refund → CLI show
 *
 * Flow (existing operator):
 *   Setup → HTTP 402 Payment → CLI config → CLI dispute → CLI status →
 *   CLI show → Done (arbiter server handles the rest)
 *
 * Prerequisites:
 *   - ETH on target chain (~0.01 for gas)
 *   - USDC on target chain (0.01 USDC = 10000 units)
 *   - SDK packages built (cd x402r-sdk && pnpm build)
 *
 * Usage:
 *   PRIVATE_KEY=0x... NETWORK_ID=eip155:11155111 pnpm cli:e2e
 *   PRIVATE_KEY=0x... NETWORK_ID=eip155:11155111 OPERATOR_ADDRESS=0x... pnpm cli:e2e
 */

import dotenv from "dotenv";
import { execSync } from "child_process";
import * as path from "path";
import { fileURLToPath } from "url";
import { toHex } from "viem";

dotenv.config();
import {
  StepRunner,
  PAYMENT_AMOUNT,
  USDC_ADDRESS,
  NETWORK_ID as SHARED_NETWORK_ID,
  RPC_URL as SHARED_RPC_URL,
  setupE2EAccounts,
  checkAndLogBalances,
  fundDerivedAccounts,
  deployTestOperator,
  setupHTTP402,
  performHTTP402Payment,
  createSDKInstances,
  shortAddr,
  waitForTx,
  erc20Abi,
  formatUnits,
  type Address,
} from "../../x402r-sdk/examples/e2e-test/shared.js";
import { savePaymentState } from "./src/state.js";

// ============ Config ============

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const PROJECT_ROOT = path.resolve(__dirname, "..");
const CLI_BIN = path.join(__dirname, "bin", "x402r.ts");

// If OPERATOR_ADDRESS is set, use existing operator (skip deploy + arbiter steps)
const EXISTING_OPERATOR = process.env.OPERATOR_ADDRESS as Address | undefined;

// ============ CLI Helper ============

// Override env so CLI commands use the same network as the shared e2e infra
const cliEnv = {
  ...process.env,
  NETWORK_ID: SHARED_NETWORK_ID,
  RPC_URL: SHARED_RPC_URL,
};

function runCli(args: string): string {
  const cmd = `tsx "${CLI_BIN}" ${args}`;
  try {
    return execSync(cmd, {
      cwd: PROJECT_ROOT,
      env: cliEnv,
      encoding: "utf-8",
      timeout: 120000,
    });
  } catch (error: unknown) {
    const execError = error as {
      stdout?: string;
      stderr?: string;
    };
    if (execError.stdout) return execError.stdout;
    throw new Error(
      `CLI failed: ${cmd}\nstdout: ${execError.stdout ?? ""}\nstderr: ${execError.stderr ?? ""}`,
    );
  }
}

// ============ Main ============

async function main() {
  const mode = EXISTING_OPERATOR ? "live arbiter" : "full lifecycle";
  console.log(
    "╔══════════════════════════════════════════════════════════╗",
  );
  console.log(
    `║     x402r CLI E2E Test — ${SHARED_NETWORK_ID.padEnd(24)}║`,
  );
  console.log(
    `║     Mode: ${mode.padEnd(43)}║`,
  );
  console.log(
    "╚══════════════════════════════════════════════════════════╝",
  );

  const runner = new StepRunner();

  // ---- Step 1: Setup Accounts ----
  runner.step("1. Setup Accounts & Clients");

  const privateKey = process.env.PRIVATE_KEY as `0x${string}`;
  if (!privateKey) {
    console.error("Error: PRIVATE_KEY environment variable is required");
    console.error("Usage: PRIVATE_KEY=0x... npm run cli:e2e");
    process.exit(1);
  }

  const derivedCount = EXISTING_OPERATOR ? 1 : 2;
  const accounts = await setupE2EAccounts(privateKey, { derivedCount });

  runner.log(`Payer:    ${accounts.payerAccount.address}`);
  runner.log(`Merchant: ${accounts.merchantAccount.address}`);
  if (accounts.arbiterAccount) {
    runner.log(`Arbiter:  ${accounts.arbiterAccount.address}`);
  }

  await checkAndLogBalances(accounts, runner);
  await fundDerivedAccounts(accounts, runner);
  runner.pass("Setup accounts and fund derived wallets");

  // ---- Step 2: Deploy or use existing Operator ----
  let operatorAddress: Address;

  if (EXISTING_OPERATOR) {
    runner.step("2. Using Existing Operator");
    operatorAddress = EXISTING_OPERATOR;
    runner.log(`Operator: ${operatorAddress} (from OPERATOR_ADDRESS env)`);
    runner.pass("Using existing operator");
  } else {
    runner.step("2. Deploy Marketplace Operator");
    const deployResult = await deployTestOperator(accounts, runner);
    operatorAddress = deployResult.operatorAddress as Address;
    runner.log(`Operator: ${operatorAddress}`);
  }

  // ---- Step 3: HTTP 402 Payment ----
  runner.step("3. HTTP 402 Payment (402 → Pay → Verify → Settle)");

  const infra = await setupHTTP402(accounts, operatorAddress);
  runner.pass("Setup HTTP 402 infrastructure");

  const { paymentInfo } = await performHTTP402Payment(
    infra,
    accounts,
    runner,
  );
  runner.log(`PaymentInfo operator: ${shortAddr(paymentInfo.operator)}`);
  runner.log(`PaymentInfo payer: ${shortAddr(paymentInfo.payer)}`);
  runner.log(`PaymentInfo receiver: ${shortAddr(paymentInfo.receiver)}`);

  // Save payment state so CLI + merchant bot pick it up
  const merchantHdKey = accounts.merchantAccount.getHdKey();
  const merchantPrivateKey = merchantHdKey.privateKey
    ? toHex(merchantHdKey.privateKey)
    : undefined;

  savePaymentState({
    paymentInfo,
    operatorAddress,
    paymentHash: "e2e-test",
    timestamp: new Date().toISOString(),
    networkId: SHARED_NETWORK_ID,
    merchantPrivateKey,
  });
  runner.pass("Payment state saved to ~/.x402r/last-payment.json");

  // ---- Step 4: CLI config ----
  runner.step("4. CLI config command");

  try {
    const configOutput = runCli(
      `config --key ${privateKey} --operator ${operatorAddress} --network ${SHARED_NETWORK_ID} --rpc ${SHARED_RPC_URL}`,
    );
    runner.log(configOutput.trim());
    runner.assert(
      configOutput.includes("Config updated") &&
        configOutput.includes(operatorAddress),
      "CLI config sets key and operator",
    );
  } catch (error) {
    runner.fail(
      "CLI config",
      error instanceof Error ? error.message : String(error),
    );
  }

  // ---- Step 5: CLI dispute ----
  runner.step(
    "5. CLI dispute command (requestRefund + submitEvidence)",
  );

  let disputeOutput = "";
  try {
    disputeOutput = runCli(
      'dispute "E2E test: service returned wrong data" --evidence "Expected sunny, got rainy"',
    );
    runner.log(disputeOutput.trim());
    runner.assert(
      disputeOutput.includes("Dispute Created") ||
        disputeOutput.includes("Evidence submitted"),
      "CLI dispute creates refund request and submits evidence",
    );
  } catch (error) {
    runner.fail(
      "CLI dispute",
      error instanceof Error ? error.message : String(error),
    );
  }

  // ---- Step 5b: Verify compositeKey in dispute output ----
  const compositeKeyMatch = disputeOutput.match(/Composite Key:\s+(0x[a-fA-F0-9]+)/);
  if (compositeKeyMatch) {
    runner.log(`Composite Key: ${compositeKeyMatch[1]}`);
    runner.assert(true, "Dispute output includes composite key");
  } else {
    runner.fail("Dispute composite key", "No composite key found in dispute output");
  }

  // ---- Step 6: CLI status ----
  runner.step("6. CLI status command (Pending)");

  try {
    const statusOutput = runCli("status");
    runner.log(statusOutput.trim());
    runner.assert(
      statusOutput.includes("Pending") ||
        statusOutput.includes("Dispute Status"),
      "CLI status shows Pending dispute",
    );
  } catch (error) {
    runner.fail(
      "CLI status",
      error instanceof Error ? error.message : String(error),
    );
  }

  // ---- Step 7: CLI show ----
  runner.step("7. CLI show command (evidence)");

  // Brief delay for RPC state propagation after evidence confirmation
  await new Promise(resolve => setTimeout(resolve, 3000));

  try {
    const showOutput = runCli("show");
    runner.log(showOutput.trim());
    runner.assert(
      showOutput.includes("Evidence") && showOutput.includes("Payer"),
      "CLI show displays payer evidence",
    );
  } catch (error) {
    runner.fail(
      "CLI show",
      error instanceof Error ? error.message : String(error),
    );
  }

  // ---- Steps 8-12: Arbiter lifecycle (skip when using existing operator) ----
  if (EXISTING_OPERATOR) {
    runner.step("8-12. Skipped (live arbiter server handles approval)");
    runner.log("Dispute created on existing operator — check the dashboard!");
    runner.log(`  Dashboard: http://localhost:3001`);
    runner.log(`  Operator:  ${operatorAddress}`);
    runner.log(`  Payer:     ${accounts.payerAccount.address}`);
    runner.pass("Live arbiter mode — dispute submitted for server evaluation");
  } else {
    // ---- Step 8: Arbiter approves (SDK) ----
    runner.step(
      "8. Arbiter approves refund (SDK — no CLI approve command)",
    );

    const { arbiter } = createSDKInstances(accounts, operatorAddress);

    try {
      const { txHash: approveTx } = await arbiter!.approveRefundRequest(
        paymentInfo,
        0n,
      );
      await waitForTx(accounts.publicClient, approveTx);
      runner.pass("Arbiter approves refund", approveTx);
    } catch (error) {
      runner.fail(
        "Arbiter approve",
        error instanceof Error ? error.message : String(error),
      );
    }

    // ---- Step 9: CLI status after approval ----
    runner.step("9. CLI status command (Approved)");

    // Delay for RPC state propagation after arbiter approval
    await new Promise(resolve => setTimeout(resolve, 3000));

    try {
      const statusOutput = runCli("status");
      runner.log(statusOutput.trim());
      runner.assert(
        statusOutput.includes("Approved"),
        "CLI status shows Approved after arbiter ruling",
      );
    } catch (error) {
      runner.fail(
        "CLI status after approval",
        error instanceof Error ? error.message : String(error),
      );
    }

    // ---- Step 10: Arbiter executes refund ----
    runner.step("10. Arbiter executes refund (SDK)");

    const payerUsdcBefore = await accounts.publicClient.readContract({
      address: USDC_ADDRESS,
      abi: erc20Abi,
      functionName: "balanceOf",
      args: [accounts.payerAccount.address],
    });

    try {
      const { txHash: executeTx } = await arbiter!.executeRefundInEscrow(
        paymentInfo,
        PAYMENT_AMOUNT,
      );
      await waitForTx(accounts.publicClient, executeTx);
      runner.pass("Execute refund in escrow", executeTx);

      const payerUsdcAfter = await accounts.publicClient.readContract({
        address: USDC_ADDRESS,
        abi: erc20Abi,
        functionName: "balanceOf",
        args: [accounts.payerAccount.address],
      });

      const recovered = payerUsdcAfter - payerUsdcBefore;
      runner.log(`Payer recovered: ${formatUnits(recovered, 6)} USDC`);
      runner.assert(
        recovered > 0n,
        "USDC returned to payer",
        `recovered=${recovered}`,
      );
    } catch (error) {
      runner.fail(
        "Execute refund",
        error instanceof Error ? error.message : String(error),
      );
    }

    // ---- Step 11: CLI status final ----
    runner.step("11. CLI status command (final)");

    try {
      const statusOutput = runCli("status");
      runner.log(statusOutput.trim());
      runner.assert(
        statusOutput.includes("Approved"),
        "CLI status still shows Approved after execution",
      );
    } catch (error) {
      runner.fail(
        "CLI status final",
        error instanceof Error ? error.message : String(error),
      );
    }

    // ---- Step 12: CLI show final ----
    runner.step("12. CLI show command (evidence persists)");

    try {
      const showOutput = runCli("show");
      runner.log(showOutput.trim());
      runner.assert(
        showOutput.includes("Evidence") && showOutput.includes("Payer"),
        "CLI show displays evidence after full lifecycle",
      );
    } catch (error) {
      runner.fail(
        "CLI show final",
        error instanceof Error ? error.message : String(error),
      );
    }
  }

  // ---- Summary ----
  console.log(
    "\n╔══════════════════════════════════════════════════════════╗",
  );
  console.log(
    "║                    TEST SUMMARY                        ║",
  );
  console.log(
    "╚══════════════════════════════════════════════════════════╝",
  );

  runner.printSummary("CLI E2E TEST RESULTS");
  runner.exitWithResults(
    EXISTING_OPERATOR
      ? "CLI E2E TEST PASSED — Dispute submitted, check dashboard!"
      : "CLI E2E TEST PASSED — Full dispute lifecycle verified",
    "CLI E2E TEST FAILED",
  );
}

main().catch((error) => {
  console.error("\nCLI E2E test failed with error:", error);
  process.exit(1);
});


path: package-lock.json
content: {
  "name": "@x402r/cli",
  "version": "0.1.0",
  "lockfileVersion": 3,
  "requires": true,
  "packages": {
    "": {
      "name": "@x402r/cli",
      "version": "0.1.0",
      "license": "MIT",
      "dependencies": {
        "@x402r/client": "^0.0.2",
        "@x402r/core": "^0.0.2",
        "commander": "^12.1.0",
        "dotenv": "^16.4.0",
        "viem": "^2.21.0"
      },
      "bin": {
        "x402r": "dist/bin/x402r.js"
      },
      "devDependencies": {
        "@types/node": "^20.11.0",
        "tsx": "^4.7.0",
        "typescript": "^5.7.0"
      }
    },
    "node_modules/@adraffy/ens-normalize": {
      "version": "1.11.1",
      "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.11.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT"
    },
    "node_modules/@esbuild/aix-ppc64": {
      "version": "0.27.3",
      "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "ppc64"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "aix"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@esbuild/android-arm": {
      "version": "0.27.3",
      "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "arm"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "android"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@esbuild/android-arm64": {
      "version": "0.27.3",
      "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "arm64"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "android"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@esbuild/android-x64": {
      "version": "0.27.3",
      "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "x64"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "android"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@esbuild/darwin-arm64": {
      "version": "0.27.3",
      "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "arm64"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "darwin"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@esbuild/darwin-x64": {
      "version": "0.27.3",
      "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "x64"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "darwin"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@esbuild/freebsd-arm64": {
      "version": "0.27.3",
      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "arm64"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "freebsd"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@esbuild/freebsd-x64": {
      "version": "0.27.3",
      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "x64"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "freebsd"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@esbuild/linux-arm": {
      "version": "0.27.3",
      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "arm"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "linux"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@esbuild/linux-arm64": {
      "version": "0.27.3",
      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "arm64"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "linux"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@esbuild/linux-ia32": {
      "version": "0.27.3",
      "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "ia32"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "linux"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@esbuild/linux-loong64": {
      "version": "0.27.3",
      "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "loong64"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "linux"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@esbuild/linux-mips64el": {
      "version": "0.27.3",
      "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "mips64el"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "linux"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@esbuild/linux-ppc64": {
      "version": "0.27.3",
      "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "ppc64"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "linux"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@esbuild/linux-riscv64": {
      "version": "0.27.3",
      "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "riscv64"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "linux"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@esbuild/linux-s390x": {
      "version": "0.27.3",
      "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "s390x"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "linux"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@esbuild/linux-x64": {
      "version": "0.27.3",
      "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "x64"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "linux"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@esbuild/netbsd-arm64": {
      "version": "0.27.3",
      "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "arm64"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "netbsd"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@esbuild/netbsd-x64": {
      "version": "0.27.3",
      "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "x64"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "netbsd"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@esbuild/openbsd-arm64": {
      "version": "0.27.3",
      "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "arm64"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "openbsd"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@esbuild/openbsd-x64": {
      "version": "0.27.3",
      "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "x64"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "openbsd"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@esbuild/openharmony-arm64": {
      "version": "0.27.3",
      "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "arm64"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "openharmony"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@esbuild/sunos-x64": {
      "version": "0.27.3",
      "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "x64"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "sunos"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@esbuild/win32-arm64": {
      "version": "0.27.3",
      "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "arm64"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "win32"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@esbuild/win32-ia32": {
      "version": "0.27.3",
      "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "ia32"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "win32"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@esbuild/win32-x64": {
      "version": "0.27.3",
      "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "x64"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "win32"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@noble/ciphers": {
      "version": "1.3.0",
      "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.3.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": "^14.21.3 || >=16"
      },
      "funding": {
        "url": "https://paulmillr.com/funding/"
      }
    },
    "node_modules/@noble/curves": {
      "version": "1.9.1",
      "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "@noble/hashes": "1.8.0"
      },
      "engines": {
        "node": "^14.21.3 || >=16"
      },
      "funding": {
        "url": "https://paulmillr.com/funding/"
      }
    },
    "node_modules/@noble/hashes": {
      "version": "1.8.0",
      "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": "^14.21.3 || >=16"
      },
      "funding": {
        "url": "https://paulmillr.com/funding/"
      }
    },
    "node_modules/@scure/base": {
      "version": "1.2.6",
      "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.2.6.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "funding": {
        "url": "https://paulmillr.com/funding/"
      }
    },
    "node_modules/@scure/bip32": {
      "version": "1.7.0",
      "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.7.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "@noble/curves": "~1.9.0",
        "@noble/hashes": "~1.8.0",
        "@scure/base": "~1.2.5"
      },
      "funding": {
        "url": "https://paulmillr.com/funding/"
      }
    },
    "node_modules/@scure/bip39": {
      "version": "1.6.0",
      "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.6.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "@noble/hashes": "~1.8.0",
        "@scure/base": "~1.2.5"
      },
      "funding": {
        "url": "https://paulmillr.com/funding/"
      }
    },
    "node_modules/@types/node": {
      "version": "20.19.33",
      "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.33.tgz",
      "integrity": [REDACTED_SECRET],
      "dev": true,
      "license": "MIT",
      "dependencies": {
        "undici-types": "~6.21.0"
      }
    },
    "node_modules/@x402r/client": {
      "version": "0.0.2",
      "resolved": "https://registry.npmjs.org/@x402r/client/-/client-0.0.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "Apache-2.0",
      "dependencies": {
        "@x402r/core": "0.0.2",
        "viem": "^2.21.0"
      },
      "peerDependencies": {
        "viem": "^2.0.0"
      }
    },
    "node_modules/@x402r/core": {
      "version": "0.0.2",
      "resolved": "https://registry.npmjs.org/@x402r/core/-/core-0.0.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "Apache-2.0",
      "dependencies": {
        "viem": "^2.21.0"
      },
      "peerDependencies": {
        "viem": "^2.0.0"
      }
    },
    "node_modules/abitype": {
      "version": "1.2.3",
      "resolved": "https://registry.npmjs.org/abitype/-/abitype-1.2.3.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "funding": {
        "url": "https://github.com/sponsors/wevm"
      },
      "peerDependencies": {
        "typescript": ">=5.0.4",
        "zod": "^3.22.0 || ^4.0.0"
      },
      "peerDependenciesMeta": {
        "typescript": {
          "optional": true
        },
        "zod": {
          "optional": true
        }
      }
    },
    "node_modules/commander": {
      "version": "12.1.0",
      "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/dotenv": {
      "version": "16.6.1",
      "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "BSD-2-Clause",
      "engines": {
        "node": ">=12"
      },
      "funding": {
        "url": "https://dotenvx.com"
      }
    },
    "node_modules/esbuild": {
      "version": "0.27.3",
      "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz",
      "integrity": [REDACTED_SECRET],
      "dev": true,
      "hasInstallScript": true,
      "license": "MIT",
      "bin": {
        "esbuild": "bin/esbuild"
      },
      "engines": {
        "node": ">=18"
      },
      "optionalDependencies": {
        "@esbuild/aix-ppc64": "0.27.3",
        "@esbuild/android-arm": "0.27.3",
        "@esbuild/android-arm64": "0.27.3",
        "@esbuild/android-x64": "0.27.3",
        "@esbuild/darwin-arm64": "0.27.3",
        "@esbuild/darwin-x64": "0.27.3",
        "@esbuild/freebsd-arm64": "0.27.3",
        "@esbuild/freebsd-x64": "0.27.3",
        "@esbuild/linux-arm": "0.27.3",
        "@esbuild/linux-arm64": "0.27.3",
        "@esbuild/linux-ia32": "0.27.3",
        "@esbuild/linux-loong64": "0.27.3",
        "@esbuild/linux-mips64el": "0.27.3",
        "@esbuild/linux-ppc64": "0.27.3",
        "@esbuild/linux-riscv64": "0.27.3",
        "@esbuild/linux-s390x": "0.27.3",
        "@esbuild/linux-x64": "0.27.3",
        "@esbuild/netbsd-arm64": "0.27.3",
        "@esbuild/netbsd-x64": "0.27.3",
        "@esbuild/openbsd-arm64": "0.27.3",
        "@esbuild/openbsd-x64": "0.27.3",
        "@esbuild/openharmony-arm64": "0.27.3",
        "@esbuild/sunos-x64": "0.27.3",
        "@esbuild/win32-arm64": "0.27.3",
        "@esbuild/win32-ia32": "0.27.3",
        "@esbuild/win32-x64": "0.27.3"
      }
    },
    "node_modules/eventemitter3": {
      "version": "5.0.1",
      "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT"
    },
    "node_modules/fsevents": {
      "version": "2.3.3",
      "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
      "integrity": [REDACTED_SECRET],
      "dev": true,
      "hasInstallScript": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "darwin"
      ],
      "engines": {
        "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
      }
    },
    "node_modules/get-tsconfig": {
      "version": "4.13.6",
      "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.6.tgz",
      "integrity": [REDACTED_SECRET],
      "dev": true,
      "license": "MIT",
      "dependencies": {
        "resolve-pkg-maps": "^1.0.0"
      },
      "funding": {
        "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
      }
    },
    "node_modules/isows": {
      "version": "1.0.7",
      "resolved": "https://registry.npmjs.org/isows/-/isows-1.0.7.tgz",
      "integrity": [REDACTED_SECRET],
      "funding": [
        {
          "type": "github",
          "url": "https://github.com/sponsors/wevm"
        }
      ],
      "license": "MIT",
      "peerDependencies": {
        "ws": "*"
      }
    },
    "node_modules/ox": {
      "version": "0.12.4",
      "resolved": "https://registry.npmjs.org/ox/-/ox-0.12.4.tgz",
      "integrity": [REDACTED_SECRET],
      "funding": [
        {
          "type": "github",
          "url": "https://github.com/sponsors/wevm"
        }
      ],
      "license": "MIT",
      "dependencies": {
        "@adraffy/ens-normalize": "^1.11.0",
        "@noble/ciphers": "^1.3.0",
        "@noble/curves": "1.9.1",
        "@noble/hashes": "^1.8.0",
        "@scure/bip32": "^1.7.0",
        "@scure/bip39": "^1.6.0",
        "abitype": "^1.2.3",
        "eventemitter3": "5.0.1"
      },
      "peerDependencies": {
        "typescript": ">=5.4.0"
      },
      "peerDependenciesMeta": {
        "typescript": {
          "optional": true
        }
      }
    },
    "node_modules/resolve-pkg-maps": {
      "version": "1.0.0",
      "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
      "integrity": [REDACTED_SECRET],
      "dev": true,
      "license": "MIT",
      "funding": {
        "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
      }
    },
    "node_modules/tsx": {
      "version": "4.21.0",
      "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz",
      "integrity": [REDACTED_SECRET],
      "dev": true,
      "license": "MIT",
      "dependencies": {
        "esbuild": "~0.27.0",
        "get-tsconfig": "^4.7.5"
      },
      "bin": {
        "tsx": "dist/cli.mjs"
      },
      "engines": {
        "node": ">=18.0.0"
      },
      "optionalDependencies": {
        "fsevents": "~2.3.3"
      }
    },
    "node_modules/typescript": {
      "version": "5.9.3",
      "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
      "integrity": [REDACTED_SECRET],
      "devOptional": true,
      "license": "Apache-2.0",
      "bin": {
        "tsc": "bin/tsc",
        "tsserver": "bin/tsserver"
      },
      "engines": {
        "node": ">=14.17"
      }
    },
    "node_modules/undici-types": {
      "version": "6.21.0",
      "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
      "integrity": [REDACTED_SECRET],
      "dev": true,
      "license": "MIT"
    },
    "node_modules/viem": {
      "version": "2.46.2",
      "resolved": "https://registry.npmjs.org/viem/-/viem-2.46.2.tgz",
      "integrity": [REDACTED_SECRET],
      "funding": [
        {
          "type": "github",
          "url": "https://github.com/sponsors/wevm"
        }
      ],
      "license": "MIT",
      "dependencies": {
        "@noble/curves": "1.9.1",
        "@noble/hashes": "1.8.0",
        "@scure/bip32": "1.7.0",
        "@scure/bip39": "1.6.0",
        "abitype": "1.2.3",
        "isows": "1.0.7",
        "ox": "0.12.4",
        "ws": "8.18.3"
      },
      "peerDependencies": {
        "typescript": ">=5.0.4"
      },
      "peerDependenciesMeta": {
        "typescript": {
          "optional": true
        }
      }
    },
    "node_modules/ws": {
      "version": "8.18.3",
      "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">=10.0.0"
      },
      "peerDependencies": {
        "bufferutil": "^4.0.1",
        "utf-8-validate": ">=5.0.2"
      },
      "peerDependenciesMeta": {
        "bufferutil": {
          "optional": true
        },
        "utf-8-validate": {
          "optional": true
        }
      }
    }
  }
}


path: package.json
content: {
  "name": "@x402r/cli",
  "version": "0.1.2",
  "description": "Agent-friendly CLI for x402r dispute resolution",
  "type": "module",
  "bin": {
    "x402r": "dist/bin/x402r.js"
  },
  "scripts": {
    "start": "tsx bin/x402r.ts",
    "dev": "tsx bin/x402r.ts",
    "build": "tsc && sed -i '' '1s|#!/usr/bin/env tsx|#!/usr/bin/env node|' dist/bin/x402r.js && chmod +x dist/bin/x402r.js",
    "prepublishOnly": "npm run build"
  },
  "dependencies": {
    "@x402/core": "^2.3.0",
    "@x402r/client": "^0.0.2",
    "@x402r/core": "^0.0.2",
    "@x402r/evm": "^0.0.2",
    "commander": "^12.1.0",
    "dotenv": "^16.4.0",
    "viem": "^2.21.0"
  },
  "devDependencies": {
    "@types/node": "^20.11.0",
    "tsx": "^4.7.0",
    "typescript": "^5.7.0"
  },
  "files": [
    "dist/",
    "SKILL.md"
  ],
  "keywords": [
    "x402r",
    "dispute",
    "arbiter",
    "payments",
    "web3",
    "openclaw"
  ],
  "license": "MIT"
}


path: pnpm-lock.yaml
content: lockfileVersion: '9.0'

settings:
  autoInstallPeers: true
  excludeLinksFromLockfile: false

importers:

  .:
    dependencies:
      '@x402r/client':
        specifier: ^0.0.2
        version: 0.0.2(viem@2.46.2(typescript@5.9.3))
      '@x402r/core':
        specifier: ^0.0.2
        version: 0.0.2(viem@2.46.2(typescript@5.9.3))
      commander:
        specifier: ^12.1.0
        version: 12.1.0
      dotenv:
        specifier: ^16.4.0
        version: 16.6.1
      tsx:
        specifier: ^4.7.0
        version: 4.21.0
      viem:
        specifier: ^2.21.0
        version: 2.46.2(typescript@5.9.3)
    devDependencies:
      '@types/node':
        specifier: ^20.11.0
        version: 20.19.33
      typescript:
        specifier: ^5.7.0
        version: 5.9.3

packages:

  '@adraffy/ens-normalize@1.11.1':
    resolution: {integrity: sha512-nhCBV3quEgesuf7c7KYfperqSS14T8bYuvJ8PcLJp6znkZpFc0AuW4qBtr8eKVyPPe/8RSr7sglCWPU5eaxwKQ==}

  '@esbuild/aix-ppc64@0.27.3':
    resolution: {integrity: sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==}
    engines: {node: '>=18'}
    cpu: [ppc64]
    os: [aix]

  '@esbuild/android-arm64@0.27.3':
    resolution: {integrity: sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==}
    engines: {node: '>=18'}
    cpu: [arm64]
    os: [android]

  '@esbuild/android-arm@0.27.3':
    resolution: {integrity: sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==}
    engines: {node: '>=18'}
    cpu: [arm]
    os: [android]

  '@esbuild/android-x64@0.27.3':
    resolution: {integrity: sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==}
    engines: {node: '>=18'}
    cpu: [x64]
    os: [android]

  '@esbuild/darwin-arm64@0.27.3':
    resolution: {integrity: sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==}
    engines: {node: '>=18'}
    cpu: [arm64]
    os: [darwin]

  '@esbuild/darwin-x64@0.27.3':
    resolution: {integrity: sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==}
    engines: {node: '>=18'}
    cpu: [x64]
    os: [darwin]

  '@esbuild/freebsd-arm64@0.27.3':
    resolution: {integrity: sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==}
    engines: {node: '>=18'}
    cpu: [arm64]
    os: [freebsd]

  '@esbuild/freebsd-x64@0.27.3':
    resolution: {integrity: sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==}
    engines: {node: '>=18'}
    cpu: [x64]
    os: [freebsd]

  '@esbuild/linux-arm64@0.27.3':
    resolution: {integrity: sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==}
    engines: {node: '>=18'}
    cpu: [arm64]
    os: [linux]

  '@esbuild/linux-arm@0.27.3':
    resolution: {integrity: sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==}
    engines: {node: '>=18'}
    cpu: [arm]
    os: [linux]

  '@esbuild/linux-ia32@0.27.3':
    resolution: {integrity: sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==}
    engines: {node: '>=18'}
    cpu: [ia32]
    os: [linux]

  '@esbuild/linux-loong64@0.27.3':
    resolution: {integrity: sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==}
    engines: {node: '>=18'}
    cpu: [loong64]
    os: [linux]

  '@esbuild/linux-mips64el@0.27.3':
    resolution: {integrity: sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==}
    engines: {node: '>=18'}
    cpu: [mips64el]
    os: [linux]

  '@esbuild/linux-ppc64@0.27.3':
    resolution: {integrity: sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==}
    engines: {node: '>=18'}
    cpu: [ppc64]
    os: [linux]

  '@esbuild/linux-riscv64@0.27.3':
    resolution: {integrity: sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==}
    engines: {node: '>=18'}
    cpu: [riscv64]
    os: [linux]

  '@esbuild/linux-s390x@0.27.3':
    resolution: {integrity: sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==}
    engines: {node: '>=18'}
    cpu: [s390x]
    os: [linux]

  '@esbuild/linux-x64@0.27.3':
    resolution: {integrity: sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==}
    engines: {node: '>=18'}
    cpu: [x64]
    os: [linux]

  '@esbuild/netbsd-arm64@0.27.3':
    resolution: {integrity: sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==}
    engines: {node: '>=18'}
    cpu: [arm64]
    os: [netbsd]

  '@esbuild/netbsd-x64@0.27.3':
    resolution: {integrity: sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==}
    engines: {node: '>=18'}
    cpu: [x64]
    os: [netbsd]

  '@esbuild/openbsd-arm64@0.27.3':
    resolution: {integrity: sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==}
    engines: {node: '>=18'}
    cpu: [arm64]
    os: [openbsd]

  '@esbuild/openbsd-x64@0.27.3':
    resolution: {integrity: sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==}
    engines: {node: '>=18'}
    cpu: [x64]
    os: [openbsd]

  '@esbuild/openharmony-arm64@0.27.3':
    resolution: {integrity: sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==}
    engines: {node: '>=18'}
    cpu: [arm64]
    os: [openharmony]

  '@esbuild/sunos-x64@0.27.3':
    resolution: {integrity: sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==}
    engines: {node: '>=18'}
    cpu: [x64]
    os: [sunos]

  '@esbuild/win32-arm64@0.27.3':
    resolution: {integrity: sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==}
    engines: {node: '>=18'}
    cpu: [arm64]
    os: [win32]

  '@esbuild/win32-ia32@0.27.3':
    resolution: {integrity: sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==}
    engines: {node: '>=18'}
    cpu: [ia32]
    os: [win32]

  '@esbuild/win32-x64@0.27.3':
    resolution: {integrity: sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==}
    engines: {node: '>=18'}
    cpu: [x64]
    os: [win32]

  '@noble/ciphers@1.3.0':
    resolution: {integrity: sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==}
    engines: {node: ^14.21.3 || >=16}

  '@noble/curves@1.9.1':
    resolution: {integrity: sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA==}
    engines: {node: ^14.21.3 || >=16}

  '@noble/hashes@1.8.0':
    resolution: {integrity: sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==}
    engines: {node: ^14.21.3 || >=16}

  '@scure/base@1.2.6':
    resolution: {integrity: sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg==}

  '@scure/bip32@1.7.0':
    resolution: {integrity: sha512-E4FFX/N3f4B80AKWp5dP6ow+flD1LQZo/w8UnLGYZO674jS6YnYeepycOOksv+vLPSpgN35wgKgy+ybfTb2SMw==}

  '@scure/bip39@1.6.0':
    resolution: {integrity: sha512-+lF0BbLiJNwVlev4eKelw1WWLaiKXw7sSl8T6FvBlWkdX+94aGJ4o8XjUdlyhTCjd8c+B3KT3JfS8P0bLRNU6A==}

  '@types/node@20.19.33':
    resolution: {integrity: sha512-Rs1bVAIdBs5gbTIKza/tgpMuG1k3U/UMJLWecIMxNdJFDMzcM5LOiLVRYh3PilWEYDIeUDv7bpiHPLPsbydGcw==}

  '@x402r/client@0.0.2':
    resolution: {integrity: sha512-pqLQ+5cRgIX3bNh0sPzAcD7JFJBxAqL9Ru9cte79QqSjDRpLcGVRh9WNutI/9D1Cv59HNWORPvqMterAhXxIoA==}
    peerDependencies:
      viem: ^2.0.0

  '@x402r/core@0.0.2':
    resolution: {integrity: sha512-PVo5xhicTs6UuYgNAXvMJPvHvxevCSyqMnXiYVRGU6vaN+E9+XwQlBPCKvnvktPbuj7BtGmPnu0B9W9u1HqUCg==}
    peerDependencies:
      viem: ^2.0.0

  abitype@1.2.3:
    resolution: {integrity: sha512-Ofer5QUnuUdTFsBRwARMoWKOH1ND5ehwYhJ3OJ/BQO+StkwQjHw0XyVh4vDttzHB7QOFhPHa/o413PJ82gU/Tg==}
    peerDependencies:
      typescript: '>=5.0.4'
      zod: ^3.22.0 || ^4.0.0
    peerDependenciesMeta:
      typescript:
        optional: true
      zod:
        optional: true

  commander@12.1.0:
    resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==}
    engines: {node: '>=18'}

  dotenv@16.6.1:
    resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==}
    engines: {node: '>=12'}

  esbuild@0.27.3:
    resolution: {integrity: sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==}
    engines: {node: '>=18'}
    hasBin: true

  eventemitter3@5.0.1:
    resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==}

  fsevents@2.3.3:
    resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
    engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
    os: [darwin]

  get-tsconfig@4.13.6:
    resolution: {integrity: sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw==}

  isows@1.0.7:
    resolution: {integrity: sha512-I1fSfDCZL5P0v33sVqeTDSpcstAg/N+wF5HS033mogOVIp4B+oHC7oOCsA3axAbBSGTJ8QubbNmnIRN/h8U7hg==}
    peerDependencies:
      ws: '*'

  ox@0.12.4:
    resolution: {integrity: sha512-+P+C7QzuwPV8lu79dOwjBKfB2CbnbEXe/hfyyrff1drrO1nOOj3Hc87svHfcW1yneRr3WXaKr6nz11nq+/DF9Q==}
    peerDependencies:
      typescript: '>=5.4.0'
    peerDependenciesMeta:
      typescript:
        optional: true

  resolve-pkg-maps@1.0.0:
    resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==}

  tsx@4.21.0:
    resolution: {integrity: sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==}
    engines: {node: '>=18.0.0'}
    hasBin: true

  typescript@5.9.3:
    resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
    engines: {node: '>=14.17'}
    hasBin: true

  undici-types@6.21.0:
    resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}

  viem@2.46.2:
    resolution: {integrity: sha512-w8Qv5Vyo7TfXcH3vgmxRa1NRvzJCDy2aSGSRsJn3503nC/qVbgEQ+n3aj/CkqWXbloudZh97h5o5aQrQSVGy0w==}
    peerDependencies:
      typescript: '>=5.0.4'
    peerDependenciesMeta:
      typescript:
        optional: true

  ws@8.18.3:
    resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==}
    engines: {node: '>=10.0.0'}
    peerDependencies:
      bufferutil: ^4.0.1
      utf-8-validate: '>=5.0.2'
    peerDependenciesMeta:
      bufferutil:
        optional: true
      utf-8-validate:
        optional: true

snapshots:

  '@adraffy/ens-normalize@1.11.1': {}

  '@esbuild/aix-ppc64@0.27.3':
    optional: true

  '@esbuild/android-arm64@0.27.3':
    optional: true

  '@esbuild/android-arm@0.27.3':
    optional: true

  '@esbuild/android-x64@0.27.3':
    optional: true

  '@esbuild/darwin-arm64@0.27.3':
    optional: true

  '@esbuild/darwin-x64@0.27.3':
    optional: true

  '@esbuild/freebsd-arm64@0.27.3':
    optional: true

  '@esbuild/freebsd-x64@0.27.3':
    optional: true

  '@esbuild/linux-arm64@0.27.3':
    optional: true

  '@esbuild/linux-arm@0.27.3':
    optional: true

  '@esbuild/linux-ia32@0.27.3':
    optional: true

  '@esbuild/linux-loong64@0.27.3':
    optional: true

  '@esbuild/linux-mips64el@0.27.3':
    optional: true

  '@esbuild/linux-ppc64@0.27.3':
    optional: true

  '@esbuild/linux-riscv64@0.27.3':
    optional: true

  '@esbuild/linux-s390x@0.27.3':
    optional: true

  '@esbuild/linux-x64@0.27.3':
    optional: true

  '@esbuild/netbsd-arm64@0.27.3':
    optional: true

  '@esbuild/netbsd-x64@0.27.3':
    optional: true

  '@esbuild/openbsd-arm64@0.27.3':
    optional: true

  '@esbuild/openbsd-x64@0.27.3':
    optional: true

  '@esbuild/openharmony-arm64@0.27.3':
    optional: true

  '@esbuild/sunos-x64@0.27.3':
    optional: true

  '@esbuild/win32-arm64@0.27.3':
    optional: true

  '@esbuild/win32-ia32@0.27.3':
    optional: true

  '@esbuild/win32-x64@0.27.3':
    optional: true

  '@noble/ciphers@1.3.0': {}

  '@noble/curves@1.9.1':
    dependencies:
      '@noble/hashes': 1.8.0

  '@noble/hashes@1.8.0': {}

  '@scure/base@1.2.6': {}

  '@scure/bip32@1.7.0':
    dependencies:
      '@noble/curves': 1.9.1
      '@noble/hashes': 1.8.0
      '@scure/base': 1.2.6

  '@scure/bip39@1.6.0':
    dependencies:
      '@noble/hashes': 1.8.0
      '@scure/base': 1.2.6

  '@types/node@20.19.33':
    dependencies:
      undici-types: 6.21.0

  '@x402r/client@0.0.2(viem@2.46.2(typescript@5.9.3))':
    dependencies:
      '@x402r/core': 0.0.2(viem@2.46.2(typescript@5.9.3))
      viem: 2.46.2(typescript@5.9.3)

  '@x402r/core@0.0.2(viem@2.46.2(typescript@5.9.3))':
    dependencies:
      viem: 2.46.2(typescript@5.9.3)

  abitype@1.2.3(typescript@5.9.3):
    optionalDependencies:
      typescript: 5.9.3

  commander@12.1.0: {}

  dotenv@16.6.1: {}

  esbuild@0.27.3:
    optionalDependencies:
      '@esbuild/aix-ppc64': 0.27.3
      '@esbuild/android-arm': 0.27.3
      '@esbuild/android-arm64': 0.27.3
      '@esbuild/android-x64': 0.27.3
      '@esbuild/darwin-arm64': 0.27.3
      '@esbuild/darwin-x64': 0.27.3
      '@esbuild/freebsd-arm64': 0.27.3
      '@esbuild/freebsd-x64': 0.27.3
      '@esbuild/linux-arm': 0.27.3
      '@esbuild/linux-arm64': 0.27.3
      '@esbuild/linux-ia32': 0.27.3
      '@esbuild/linux-loong64': 0.27.3
      '@esbuild/linux-mips64el': 0.27.3
      '@esbuild/linux-ppc64': 0.27.3
      '@esbuild/linux-riscv64': 0.27.3
      '@esbuild/linux-s390x': 0.27.3
      '@esbuild/linux-x64': 0.27.3
      '@esbuild/netbsd-arm64': 0.27.3
      '@esbuild/netbsd-x64': 0.27.3
      '@esbuild/openbsd-arm64': 0.27.3
      '@esbuild/openbsd-x64': 0.27.3
      '@esbuild/openharmony-arm64': 0.27.3
      '@esbuild/sunos-x64': 0.27.3
      '@esbuild/win32-arm64': 0.27.3
      '@esbuild/win32-ia32': 0.27.3
      '@esbuild/win32-x64': 0.27.3

  eventemitter3@5.0.1: {}

  fsevents@2.3.3:
    optional: true

  get-tsconfig@4.13.6:
    dependencies:
      resolve-pkg-maps: 1.0.0

  isows@1.0.7(ws@8.18.3):
    dependencies:
      ws: 8.18.3

  ox@0.12.4(typescript@5.9.3):
    dependencies:
      '@adraffy/ens-normalize': 1.11.1
      '@noble/ciphers': 1.3.0
      '@noble/curves': 1.9.1
      '@noble/hashes': 1.8.0
      '@scure/bip32': 1.7.0
      '@scure/bip39': 1.6.0
      abitype: 1.2.3(typescript@5.9.3)
      eventemitter3: 5.0.1
    optionalDependencies:
      typescript: 5.9.3
    transitivePeerDependencies:
      - zod

  resolve-pkg-maps@1.0.0: {}

  tsx@4.21.0:
    dependencies:
      esbuild: 0.27.3
      get-tsconfig: 4.13.6
    optionalDependencies:
      fsevents: 2.3.3

  typescript@5.9.3: {}

  undici-types@6.21.0: {}

  viem@2.46.2(typescript@5.9.3):
    dependencies:
      '@noble/curves': 1.9.1
      '@noble/hashes': 1.8.0
      '@scure/bip32': 1.7.0
      '@scure/bip39': 1.6.0
      abitype: 1.2.3(typescript@5.9.3)
      isows: 1.0.7(ws@8.18.3)
      ox: 0.12.4(typescript@5.9.3)
      ws: 8.18.3
    optionalDependencies:
      typescript: 5.9.3
    transitivePeerDependencies:
      - bufferutil
      - utf-8-validate
      - zod

  ws@8.18.3: {}


path: src/commands/config.ts
content: /**
 * config command — Save/load CLI configuration
 */

import type { Command } from "commander";
import { saveConfigFile, printConfig, type CliConfigFile } from "../config.js";

export function registerConfigCommand(program: Command): void {
  program
    .command("config")
    .description("Save or view CLI configuration")
    .option("-k, --key <privateKey>", "Set private key")
    .option("-o, --operator <address>", "Set operator address")
    .option("-a, --arbiter-url <url>", "Set arbiter server URL")
    .option("-c, --court-url <url>", "Set court UI URL (for independent verification)")
    .option("-n, --network <networkId>", "Set network ID (e.g., eip155:84532)")
    .option("-r, --rpc <url>", "Set RPC URL")
    .option("--pinata-jwt <jwt>", "Set Pinata JWT token")
    .action(async (options) => {
      const updates: CliConfigFile = {};
      let hasUpdates = false;

      if (options.key) {
        updates.privateKey = options.key;
        hasUpdates = true;
      }
      if (options.operator) {
        updates.operatorAddress = options.operator;
        hasUpdates = true;
      }
      if (options.arbiterUrl) {
        updates.arbiterUrl = options.arbiterUrl;
        hasUpdates = true;
      }
      if (options.courtUrl) {
        updates.courtUrl = options.courtUrl;
        hasUpdates = true;
      }
      if (options.network) {
        updates.networkId = options.network;
        hasUpdates = true;
      }
      if (options.rpc) {
        updates.rpcUrl = options.rpc;
        hasUpdates = true;
      }
      if (options.pinataJwt) {
        updates.pinataJwt = options.pinataJwt;
        hasUpdates = true;
      }

      if (hasUpdates) {
        saveConfigFile(updates);
        console.log("Config updated.");
      }

      await printConfig();
    });
}


path: src/commands/dispute.ts
content: /**
 * dispute command — All-in-one: requestRefund + pin evidence + submitEvidence
 */

import type { Command } from "commander";
import { keccak256, encodePacked } from "viem";
import { X402rClient } from "@x402r/client";
import { computePaymentInfoHash } from "@x402r/core";
import { initCli } from "../setup.js";
import { getPaymentInfo, saveDisputeState } from "../state.js";
import { pinToIpfs } from "../ipfs.js";

export function registerDisputeCommand(program: Command): void {
  program
    .command("dispute")
    .description("Create a dispute for the last payment (requestRefund + submit evidence)")
    .argument("<reason>", "Reason for the dispute")
    .option("-e, --evidence <text>", "Additional evidence text")
    .option("-f, --file <path>", "Path to evidence file (JSON)")
    .option("-p, --payment-json <json>", "Payment info JSON (uses saved state if omitted)")
    .option("-n, --nonce <nonce>", "Nonce (default: 0)", "0")
    .option("-a, --amount <amount>", "Refund amount (default: full payment amount)")
    .action(async (reason: string, options) => {
      const { publicClient, walletClient, addresses, operatorAddress } = await initCli();
      const paymentInfo = getPaymentInfo(options);
      const nonce = BigInt(options.nonce);
      const amount = options.amount ? BigInt(options.amount) : paymentInfo.maxAmount;

      console.log("\n=== Creating Dispute ===");
      console.log("  Reason:", reason);
      console.log("  Amount:", amount.toString());
      console.log("  Nonce:", nonce.toString());

      const client = new X402rClient({
        publicClient: publicClient as any,
        walletClient: walletClient as any,
        operatorAddress,
        refundRequestAddress: addresses.refundRequestAddress,
        refundRequestEvidenceAddress: addresses.evidenceAddress,
        chainId: addresses.chainId,
      });

      // Step 1: Request refund on-chain
      console.log("\n[1/3] Requesting refund on-chain...");
      let refundTxHash: string | undefined;
      try {
        const hasRequest = await client.hasRefundRequest(paymentInfo, nonce);
        if (hasRequest) {
          const status = await client.getRefundStatus(paymentInfo, nonce);
          const statusNames = ["Pending", "Approved", "Denied", "Cancelled"];
          console.log(`  Refund request already exists (status: ${statusNames[status] || status})`);
        } else {
          const { txHash } = await client.requestRefund(paymentInfo, amount, nonce);
          refundTxHash = txHash;
          console.log("  Refund requested:", txHash);
          console.log("  Waiting for confirmation...");
          await (publicClient as any).waitForTransactionReceipt({ hash: txHash });
          // Brief delay to ensure state propagation on testnet RPCs
          await new Promise(resolve => setTimeout(resolve, 3000));
          console.log("  Confirmed.");
        }
      } catch (error) {
        console.error("  Failed to request refund:", error instanceof Error ? error.message : error);
        process.exit(1);
      }

      // Step 2: Build and pin evidence
      console.log("\n[2/3] Building and pinning evidence...");
      let fileContent: unknown;
      if (options.file) {
        try {
          const { readFileSync } = await import("fs");
          fileContent = JSON.parse(readFileSync(options.file, "utf-8"));
        } catch (error) {
          console.error("  Failed to read evidence file:", error instanceof Error ? error.message : error);
          process.exit(1);
        }
      }

      const evidenceData: Record<string, unknown> = {
        reason,
        timestamp: new Date().toISOString(),
        payer: paymentInfo.payer,
        receiver: paymentInfo.receiver,
      };
      if (options.evidence) {
        evidenceData.evidence = options.evidence;
      }
      if (fileContent) {
        evidenceData.attachments = fileContent;
      }

      let cid: string;
      try {
        cid = await pinToIpfs(evidenceData);
      } catch (error) {
        console.error("  Failed to pin evidence:", error instanceof Error ? error.message : error);
        process.exit(1);
      }

      // Step 3: Submit evidence on-chain (skip if payer already submitted)
      console.log("\n[3/3] Submitting evidence on-chain...");
      let evidenceTxHash: string | undefined;
      try {
        const existing = await client.getAllEvidence(paymentInfo, nonce);
        const alreadySubmitted = existing.some(
          (e) => e.submitter.toLowerCase() === paymentInfo.payer.toLowerCase()
        );
        if (alreadySubmitted) {
          console.log("  Evidence already submitted for this dispute — skipping");
        } else {
          const { txHash } = await client.submitEvidence(paymentInfo, nonce, cid);
          evidenceTxHash = txHash;
          console.log("  Evidence submitted:", txHash);
          console.log("  Waiting for confirmation...");
          await (publicClient as any).waitForTransactionReceipt({ hash: txHash });
          console.log("  Confirmed.");
        }
      } catch (error) {
        console.error("  Failed to submit evidence:", error instanceof Error ? error.message : error);
        process.exit(1);
      }

      // Compute compositeKey for dashboard link
      const paymentHash = computePaymentInfoHash(
        addresses.chainId,
        addresses.escrowAddress,
        paymentInfo,
      );
      const compositeKey = keccak256(
        encodePacked(["bytes32", "uint256"], [paymentHash, nonce]),
      );

      // Save dispute state
      saveDisputeState({
        nonce: nonce.toString(),
        compositeKey,
        refundTxHash,
        evidenceTxHash,
        evidenceCid: cid,
        timestamp: new Date().toISOString(),
      });

      console.log("\n=== Dispute Created ===");
      if (refundTxHash) console.log("  Refund Tx:", refundTxHash);
      if (evidenceTxHash) console.log("  Evidence Tx:", evidenceTxHash);
      console.log("  Evidence CID:", cid);
      console.log("  Composite Key:", compositeKey);
      console.log("\n  State saved to ~/.x402r/last-dispute.json");
      console.log("  Run 'x402r status' to check dispute status");

      // Print dashboard link if arbiter URL is configured
      const { getConfig } = await import("../config.js");
      const config = getConfig();
      if (config.arbiterUrl) {
        const dashboardBase = config.arbiterUrl.replace(/\/arbiter\/?$/, "");
        console.log(`  Dashboard: ${dashboardBase}/dispute/${compositeKey}`);
      }
    });
}


path: src/commands/list.ts
content: /**
 * list command — List disputes via arbiter server or SDK
 */

import type { Command } from "commander";
import { getConfig } from "../config.js";

export function registerListCommand(program: Command): void {
  program
    .command("list")
    .description("List pending disputes")
    .option("-r, --receiver <address>", "Filter by receiver address")
    .option("--offset <n>", "Offset for pagination", "0")
    .option("--count <n>", "Number of results", "20")
    .action(async (options) => {
      const config = getConfig();
      const url = config.arbiterUrl;

      console.log(`\nQuerying disputes from ${url}...`);

      const params = new URLSearchParams();
      if (options.receiver) params.set("receiver", options.receiver);
      params.set("offset", options.offset);
      params.set("count", options.count);

      try {
        const response = await fetch(`${url}/api/disputes?${params.toString()}`);

        if (!response.ok) {
          const error = await response.text();
          console.error(`\nArbiter returned ${response.status}:`, error);
          process.exit(1);
        }

        const data = await response.json() as {
          keys: string[];
          total: string;
          offset: string;
          count: string;
        };

        console.log(`\n=== Disputes (${data.keys.length} of ${data.total}) ===`);

        if (data.keys.length === 0) {
          console.log("  No pending disputes found.");
          return;
        }

        for (let i = 0; i < data.keys.length; i++) {
          console.log(`  [${parseInt(data.offset) + i}] ${data.keys[i]}`);
        }

        console.log(`\n  Showing ${data.offset}-${parseInt(data.offset) + data.keys.length} of ${data.total}`);
      } catch (error) {
        console.error("\nFailed to list disputes:", error instanceof Error ? error.message : error);
        console.error("Is the arbiter server running at", url, "?");
        process.exit(1);
      }
    });
}


path: src/commands/show.ts
content: /**
 * show command — Show evidence for a dispute
 */

import type { Command } from "commander";
import { X402rClient } from "@x402r/client";
import { type Evidence, SubmitterRole } from "@x402r/core";
import { initReadOnly } from "../setup.js";
import { getPaymentInfo, getNonce } from "../state.js";

function shortAddress(address: string): string {
  return `${address.slice(0, 10)}...${address.slice(-8)}`;
}

function roleName(role: SubmitterRole): string {
  switch (role) {
    case SubmitterRole.Payer:
      return "Payer";
    case SubmitterRole.Receiver:
      return "Receiver";
    case SubmitterRole.Arbiter:
      return "Arbiter";
    default:
      return `Unknown(${role})`;
  }
}

function formatEvidence(evidence: Evidence, index: number): string {
  const ts = new Date(Number(evidence.timestamp) * 1000).toISOString();
  return `  [${index}] ${roleName(evidence.role)} ${shortAddress(evidence.submitter)} | ${ts} | CID: ${evidence.cid}`;
}

export function registerShowCommand(program: Command): void {
  program
    .command("show")
    .description("Show all evidence for a dispute")
    .option("-p, --payment-json <json>", "Payment info JSON (uses saved state if omitted)")
    .option("-n, --nonce <nonce>", "Nonce")
    .action(async (options) => {
      const { publicClient, addresses, operatorAddress } = await initReadOnly();
      const paymentInfo = getPaymentInfo(options);
      const nonce = getNonce(options);

      const client = new X402rClient({
        publicClient: publicClient as any,
        operatorAddress,
        refundRequestEvidenceAddress: addresses.evidenceAddress,
        chainId: addresses.chainId,
      });

      console.log("\nFetching evidence...");

      const count = await client.getEvidenceCount(paymentInfo, nonce);
      console.log(`\n=== Evidence (${count} entries) ===`);

      if (count === 0n) {
        console.log("  No evidence submitted.");
        return;
      }

      const entries = await client.getAllEvidence(paymentInfo, nonce);
      for (let i = 0; i < entries.length; i++) {
        console.log(formatEvidence(entries[i], i));
      }
    });
}


path: tsconfig.json
content: {
  "compilerOptions": {
    "target": "ES2022",
    "module": "Node16",
    "moduleResolution": "Node16",
    "lib": ["ES2022"],
    "outDir": "./dist",
    "rootDir": ".",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true,
    "declaration": true,
    "sourceMap": true
  },
  "include": ["bin/**/*", "src/**/*"],
  "exclude": ["node_modules", "dist"]
}


Security scan verdicts — ClawScan: suspicious | static analysis: suspicious | VirusTotal: suspicious | SkillSpector: suspicious (CRITICAL)