|
1 | 1 | import { Command } from "commander" |
2 | 2 | import pc from "picocolors" |
3 | | -import { isAddress } from "viem" |
4 | 3 | import { |
5 | 4 | parseX402Challenge, |
6 | 5 | resolveNetwork, |
@@ -34,45 +33,56 @@ interface PayOptions { |
34 | 33 | toolRef?: string |
35 | 34 | } |
36 | 35 |
|
37 | | -/** ERC-8257 composite key identifying a tool onchain. */ |
| 36 | +/** Composite key identifying a tool (ERC-8257 onchain or x402 registry). */ |
38 | 37 | export interface ToolRef { |
39 | 38 | toolChainId: number |
40 | | - toolRegistryAddress: `0x${string}` |
41 | | - toolOnchainId: number |
| 39 | + toolRegistryAddress: string |
| 40 | + toolOnchainId: string |
42 | 41 | } |
43 | 42 |
|
44 | 43 | /** |
45 | | - * Parse a `--tool-ref` value of the form `chainId:registryAddress:onchainId` |
46 | | - * (e.g. `8453:0x265bb2dbfc0a8165c9a1941eb1372f349bad2cf1:65`) into an ERC-8257 |
47 | | - * composite key. The registry address contains no colons and both IDs are |
48 | | - * numeric, so a 3-way split is unambiguous. Throws on a malformed ref. |
| 44 | + * Parse a `--tool-ref` value of the form `chainId,registryAddress,onchainId` |
| 45 | + * (comma-separated, exactly three fields). |
| 46 | + * |
| 47 | + * A comma delimiter keeps the registry identifier unambiguous even when it |
| 48 | + * contains a colon, as the x402 registries do (`x402:bazaar`, `x402:bankr`). |
| 49 | + * |
| 50 | + * Examples: |
| 51 | + * `8453,0x265bb2dbfc0a8165c9a1941eb1372f349bad2cf1,65` → onchain ERC-8257 |
| 52 | + * `8453,x402:bazaar,8679018179619845322` → x402 bazaar tool |
| 53 | + * `8453,x402:bankr,5311379622895099236` → x402 bankr tool |
| 54 | + * |
| 55 | + * The onchainId is kept as a string to preserve precision for IDs that |
| 56 | + * exceed `Number.MAX_SAFE_INTEGER`. |
49 | 57 | */ |
50 | 58 | export function parseToolRef(ref: string): ToolRef { |
51 | | - const parts = ref.split(":") |
| 59 | + const parts = ref.split(",") |
52 | 60 | if (parts.length !== 3) { |
53 | 61 | throw new Error( |
54 | | - `Invalid --tool-ref "${ref}": expected chainId:registryAddress:onchainId`, |
| 62 | + `Invalid --tool-ref "${ref}": expected chainId,registryAddress,onchainId`, |
55 | 63 | ) |
56 | 64 | } |
57 | | - const [chainStr, registry, onchainStr] = parts |
| 65 | + const [chainStr = "", registry = "", onchainStr = ""] = parts |
| 66 | + |
58 | 67 | const toolChainId = Number(chainStr) |
59 | 68 | if (!Number.isInteger(toolChainId) || toolChainId <= 0) { |
60 | 69 | throw new Error( |
61 | 70 | `Invalid --tool-ref chain id "${chainStr}": expected a positive integer`, |
62 | 71 | ) |
63 | 72 | } |
64 | | - if (!isAddress(registry)) { |
65 | | - throw new Error( |
66 | | - `Invalid --tool-ref registry address "${registry}": expected a 0x address`, |
67 | | - ) |
| 73 | + if (!registry) { |
| 74 | + throw new Error(`Invalid --tool-ref registry: must not be empty`) |
68 | 75 | } |
69 | | - const toolOnchainId = Number(onchainStr) |
70 | | - if (!Number.isInteger(toolOnchainId) || toolOnchainId < 0) { |
| 76 | + if (!/^\d+$/.test(onchainStr)) { |
71 | 77 | throw new Error( |
72 | 78 | `Invalid --tool-ref onchain id "${onchainStr}": expected a non-negative integer`, |
73 | 79 | ) |
74 | 80 | } |
75 | | - return { toolChainId, toolRegistryAddress: registry, toolOnchainId } |
| 81 | + return { |
| 82 | + toolChainId, |
| 83 | + toolRegistryAddress: registry, |
| 84 | + toolOnchainId: onchainStr, |
| 85 | + } |
76 | 86 | } |
77 | 87 |
|
78 | 88 | /** HTTP verbs that carry no request body — inputs go in the query string. */ |
@@ -111,7 +121,7 @@ export const payCommand = new Command("pay") |
111 | 121 | ) |
112 | 122 | .option( |
113 | 123 | "--tool-ref <ref>", |
114 | | - "ERC-8257 tool coordinates as chainId:registryAddress:onchainId (e.g. 8453:0x265b...2cf1:65). Disambiguates usage reporting when multiple tools share one endpoint.", |
| 124 | + "Tool coordinates as chainId,registryAddress,onchainId (e.g. 8453,0x265b...2cf1,65 or 8453,x402:bazaar,123). Disambiguates usage reporting when multiple tools share one endpoint.", |
115 | 125 | ) |
116 | 126 | .action(async (url: string, options: PayOptions) => { |
117 | 127 | // The x402 X-Payment signature is a gasless EIP-3009 authorization — it is |
|
0 commit comments