@bankofai/x402-cli 1.0.1-beta.0 → 1.0.1-beta.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/help.js ADDED
@@ -0,0 +1,169 @@
1
+ import fs from "node:fs";
2
+ export function getVersion() {
3
+ try {
4
+ const pkg = JSON.parse(fs.readFileSync(new URL("../package.json", import.meta.url), "utf8"));
5
+ return String(pkg.version ?? "0.0.0");
6
+ }
7
+ catch {
8
+ return "0.0.0";
9
+ }
10
+ }
11
+ export function helpText(topic = "root") {
12
+ const sections = {
13
+ root: `x402-cli ${getVersion()}
14
+
15
+ Usage:
16
+ x402-cli <command> [options]
17
+
18
+ Commands:
19
+ pay <url> Pay an x402-protected URL
20
+ serve Run a local x402 paywall endpoint
21
+ roundtrip Start serve, pay it, then exit
22
+ gateway <command> Manage local gateway provider files
23
+ catalog <command> Search, cache, and export provider catalog assets
24
+
25
+ Global options:
26
+ -h, --help Show help
27
+ -V, --version Show version
28
+ --json Print machine-readable JSON envelope
29
+ --human Print human-readable output (default)
30
+ `,
31
+ pay: `Usage:
32
+ x402-cli pay <url> [options]
33
+
34
+ Options:
35
+ --method <method> HTTP method (default: GET)
36
+ --header "Name: Value" Request header, repeatable
37
+ --body <body> Request body for non-GET/HEAD methods
38
+ --network <caip2> Require a specific network
39
+ --token <symbol> Require a specific token
40
+ --scheme <scheme> Require a specific x402 scheme
41
+ --gasfree-api-url <url> Override the TRON GasFree relayer API URL
42
+ --max-gasfree-fee <amt> Maximum GasFree relayer fee in token units
43
+ --max-gasfree-fee-raw <n> Maximum GasFree relayer fee in smallest units
44
+ --max-amount <amount> Maximum human-readable payment amount
45
+ --max-raw-amount <amount> Maximum smallest-unit payment amount
46
+ --dry-run Read requirements but do not sign or pay
47
+ --private-key <hex> Explicit payer private key (or PRIVATE_KEY/TRON_PRIVATE_KEY/EVM_PRIVATE_KEY)
48
+ --rpc-url <url> Explicit network RPC URL
49
+ --timeout-ms <ms> Network timeout in milliseconds (default: 30000)
50
+ --json Print JSON envelope
51
+
52
+ Examples:
53
+ x402-cli pay https://api.example.com/paid --dry-run --json
54
+ x402-cli pay https://api.example.com/paid --max-amount 0.01
55
+ `,
56
+ serve: `Usage:
57
+ x402-cli serve --pay-to <address> [options]
58
+
59
+ Options:
60
+ --pay-to <address> Recipient wallet address
61
+ --amount <amount> Human-readable token amount (default: 0.0001)
62
+ --raw-amount <amount> Smallest-unit amount
63
+ --network <caip2> Payment network (default: tron:0xcd8690dc)
64
+ --scheme <scheme> Payment scheme: exact or exact_gasfree (default: exact)
65
+ --token <symbol> Token symbol (default: USDT)
66
+ --asset <address> Explicit token address
67
+ --decimals <count> Token decimals for unregistered --asset
68
+ --host <host> Bind host (default: 127.0.0.1)
69
+ --port <port> Bind port (default: 4020)
70
+ --resource-url <url> URL advertised in payment requirements
71
+ --facilitator-url <url> Facilitator base URL
72
+ --timeout-ms <ms> Facilitator timeout in milliseconds (default: 30000)
73
+ --daemon Run in background and print the child pid
74
+ --json Print JSON envelope
75
+
76
+ Examples:
77
+ x402-cli serve --pay-to T... --network tron:0xcd8690dc --token USDT
78
+ x402-cli serve --pay-to 0x... --network eip155:97 --token USDT --amount 0.0001
79
+ `,
80
+ roundtrip: `Usage:
81
+ x402-cli roundtrip --pay-to <address> [serve/pay options]
82
+ `,
83
+ gateway: `Usage:
84
+ x402-cli gateway <search|start|check|scaffold|catalog> [options]
85
+
86
+ Commands:
87
+ search <query> Search a gateway/catalog artifact
88
+ start Start a local x402 gateway process
89
+ check <providers> Validate provider.yml files
90
+ scaffold <name> Write a starter provider.yml
91
+ catalog <command> Build/check/search gateway catalog assets
92
+ `,
93
+ "gateway-catalog": `Usage:
94
+ x402-cli gateway catalog <build|check|pay-assets|search> [options]
95
+
96
+ Commands:
97
+ build <providers> Build a local catalog from provider.yml files
98
+ check <providers> Validate local provider.yml files
99
+ pay-assets <providers> List payable endpoint assets
100
+ search <query> Search a catalog artifact
101
+ `,
102
+ catalog: `Usage:
103
+ x402-cli catalog <update|search|show|endpoints|pay-json|export-gateway|build> [options]
104
+
105
+ Commands:
106
+ update Cache hosted/local catalog assets under ~/.cache
107
+ search <query> Search providers
108
+ show <provider> Show provider detail JSON
109
+ endpoints <provider> List provider endpoints
110
+ pay-json <provider> Print provider pay JSON
111
+ export-gateway <url> Export catalog.json and pay.md from a gateway
112
+ build <providers> Build catalog from provider.yml files
113
+
114
+ Options:
115
+ --catalog <source> catalog.json path or URL
116
+ --provider <fqn> Provider FQN for export-gateway
117
+ --output-dir <dir> Output directory for generated files
118
+ -n, --limit <count> Search result limit
119
+ --timeout-ms <ms> Network timeout in milliseconds (default: 30000)
120
+ --include-blocked Include blocked providers in search
121
+ --json Print JSON envelope
122
+ `,
123
+ "catalog-search": `Usage:
124
+ x402-cli catalog search <query> [--catalog <source>] [options]
125
+
126
+ Options:
127
+ --catalog <source> catalog.json path or URL
128
+ -n, --limit <count> Search result limit
129
+ --timeout-ms <ms> Network timeout in milliseconds (default: 30000)
130
+ --include-blocked Include blocked providers in search
131
+ --json Print JSON envelope
132
+ `,
133
+ "catalog-show": `Usage:
134
+ x402-cli catalog show <provider> [--catalog <source>] [options]
135
+
136
+ Options:
137
+ --catalog <source> catalog.json path or URL
138
+ --timeout-ms <ms> Network timeout in milliseconds (default: 30000)
139
+ --json Print JSON envelope
140
+ `,
141
+ "catalog-pay-json": `Usage:
142
+ x402-cli catalog pay-json <provider> [--catalog <source>] [options]
143
+
144
+ Options:
145
+ --catalog <source> catalog.json path or URL
146
+ --timeout-ms <ms> Network timeout in milliseconds (default: 30000)
147
+ --raw Print raw pay payload instead of JSON envelope
148
+ --json Print JSON envelope
149
+ `,
150
+ "catalog-endpoints": `Usage:
151
+ x402-cli catalog endpoints <provider> [--catalog <source>] [options]
152
+
153
+ Options:
154
+ --catalog <source> catalog.json path or URL
155
+ --timeout-ms <ms> Network timeout in milliseconds (default: 30000)
156
+ --json Print JSON envelope
157
+ `,
158
+ "catalog-export-gateway": `Usage:
159
+ x402-cli catalog export-gateway <gateway-url> --provider <fqn> [options]
160
+
161
+ Options:
162
+ --provider <fqn> Provider FQN to export
163
+ --output-dir <dir> Output directory for generated files
164
+ --force Overwrite existing files
165
+ --json Print JSON envelope
166
+ `,
167
+ };
168
+ return sections[topic] ?? sections.root;
169
+ }
@@ -0,0 +1,82 @@
1
+ import fs from "node:fs";
2
+ import { CliError, opt } from "./args.js";
3
+ const MAX_HTTP_BODY_BYTES = 10 * 1024 * 1024;
4
+ const DEFAULT_TIMEOUT_MS = 30_000;
5
+ export function positiveIntegerOption(options, key, fallback) {
6
+ const value = opt(options, key, String(fallback));
7
+ if (!/^\d+$/.test(value))
8
+ throw new CliError("INVALID_ARGUMENT", `--${key} must be a positive integer`, `Pass --${key} with a value greater than zero.`, 2);
9
+ const parsed = Number(value);
10
+ if (!Number.isSafeInteger(parsed) || parsed <= 0)
11
+ throw new CliError("INVALID_ARGUMENT", `--${key} must be a positive integer`, `Pass --${key} with a value greater than zero.`, 2);
12
+ return parsed;
13
+ }
14
+ export function timeoutMs(options) {
15
+ return options ? positiveIntegerOption(options, "timeout-ms", DEFAULT_TIMEOUT_MS) : DEFAULT_TIMEOUT_MS;
16
+ }
17
+ export async function fetchWithTimeout(input, init = {}, timeout = DEFAULT_TIMEOUT_MS, label = "request") {
18
+ const controller = new AbortController();
19
+ const timer = setTimeout(() => controller.abort(), timeout);
20
+ try {
21
+ return await fetch(input, { ...init, signal: controller.signal });
22
+ }
23
+ catch (error) {
24
+ if (error instanceof Error && error.name === "AbortError")
25
+ throw new Error(`${label} timed out after ${timeout}ms`);
26
+ throw error;
27
+ }
28
+ finally {
29
+ clearTimeout(timer);
30
+ }
31
+ }
32
+ export async function readBoundedText(response, label) {
33
+ const declared = Number(response.headers.get("content-length"));
34
+ if (Number.isFinite(declared) && declared > MAX_HTTP_BODY_BYTES)
35
+ throw new Error(`${label} exceeds ${MAX_HTTP_BODY_BYTES} byte limit`);
36
+ if (!response.body)
37
+ return "";
38
+ const reader = response.body.getReader();
39
+ const chunks = [];
40
+ let size = 0;
41
+ for (;;) {
42
+ const { done, value } = await reader.read();
43
+ if (done)
44
+ break;
45
+ size += value.byteLength;
46
+ if (size > MAX_HTTP_BODY_BYTES) {
47
+ await reader.cancel();
48
+ throw new Error(`${label} exceeds ${MAX_HTTP_BODY_BYTES} byte limit`);
49
+ }
50
+ chunks.push(value);
51
+ }
52
+ const bytes = new Uint8Array(size);
53
+ let offset = 0;
54
+ for (const chunk of chunks) {
55
+ bytes.set(chunk, offset);
56
+ offset += chunk.byteLength;
57
+ }
58
+ return new TextDecoder().decode(bytes);
59
+ }
60
+ export async function readText(source, options) {
61
+ if (!source.startsWith("http://") && !source.startsWith("https://"))
62
+ return fs.readFileSync(source, "utf8");
63
+ const response = await fetchWithTimeout(source, {}, timeoutMs(options), `fetch ${source}`);
64
+ if (!response.ok)
65
+ throw new Error(`failed to fetch ${source}: ${response.status}`);
66
+ return readBoundedText(response, `response from ${source}`);
67
+ }
68
+ export async function readJson(source, options) {
69
+ return JSON.parse(await readText(source, options));
70
+ }
71
+ export async function responsePayload(response) {
72
+ const text = await readBoundedText(response, "HTTP response");
73
+ if ((response.headers.get("content-type") ?? "").toLowerCase().includes("json")) {
74
+ try {
75
+ return JSON.parse(text);
76
+ }
77
+ catch {
78
+ return text;
79
+ }
80
+ }
81
+ return text;
82
+ }
package/dist/output.js ADDED
@@ -0,0 +1,91 @@
1
+ import { CliError } from "./args.js";
2
+ export function printJson(value) {
3
+ process.stdout.write(`${JSON.stringify(value, null, 2)}\n`);
4
+ }
5
+ export function emit(args) {
6
+ const mode = args.mode ?? "human";
7
+ if (mode === "json") {
8
+ const envelope = { ok: !args.error, command: args.command };
9
+ if (args.network)
10
+ envelope.network = args.network;
11
+ if (args.scheme)
12
+ envelope.scheme = args.scheme;
13
+ if (args.error)
14
+ envelope.error = args.error;
15
+ else
16
+ envelope.result = args.result ?? null;
17
+ printJson(envelope);
18
+ return;
19
+ }
20
+ if (args.error) {
21
+ process.stderr.write(`ERROR ${args.command}: ${args.error.code}\n`);
22
+ process.stderr.write(` ${args.error.message}\n`);
23
+ if (args.error.hint)
24
+ process.stderr.write(` hint: ${args.error.hint}\n`);
25
+ if (args.error.details !== undefined)
26
+ process.stderr.write(` details: ${JSON.stringify(args.error.details)}\n`);
27
+ return;
28
+ }
29
+ const suffix = [args.network, args.scheme].filter(Boolean).join(" ");
30
+ process.stdout.write(`OK ${args.command}${suffix ? ` (${suffix})` : ""}\n`);
31
+ if (args.result && typeof args.result === "object" && !Array.isArray(args.result)) {
32
+ for (const [key, value] of Object.entries(args.result)) {
33
+ if (value === undefined)
34
+ continue;
35
+ process.stdout.write(value && typeof value === "object" ? ` ${key}: ${JSON.stringify(value)}\n` : ` ${key}: ${value}\n`);
36
+ }
37
+ }
38
+ else if (args.result !== undefined)
39
+ process.stdout.write(` ${args.result}\n`);
40
+ }
41
+ export function classify(error) {
42
+ const message = error instanceof Error ? error.message : String(error);
43
+ if (error instanceof CliError)
44
+ return { code: error.code, message, hint: error.hint, details: error.details };
45
+ const lower = message.toLowerCase();
46
+ if (lower.includes("missing private key") || lower.includes("could not find a wallet"))
47
+ return { code: "WALLET_NOT_CONFIGURED", message, hint: "Set PRIVATE_KEY, TRON_PRIVATE_KEY, EVM_PRIVATE_KEY, or configure agent-wallet with a payer wallet." };
48
+ if (lower.includes("wallets_config") || lower.includes("wallet config"))
49
+ return { code: "WALLET_CONFIG_CORRUPT", message, hint: "Check ~/.agent-wallet/wallets_config.json or recreate the local agent-wallet configuration." };
50
+ if (lower.includes("does not exist") && lower.includes("account [t"))
51
+ return { code: "TRON_ACCOUNT_NOT_ACTIVATED", message, hint: "Activate the TRON address by sending it a small amount of TRX before signing contract calls." };
52
+ if (lower.includes("permit2_insufficient_balance") || lower.includes("insufficient") && lower.includes("balance"))
53
+ return { code: "INSUFFICIENT_TOKEN_BALANCE", message, hint: "Fund the payer address with the exact token and network advertised by the provider, then retry." };
54
+ if (lower.includes("transfer_from_failed") || lower.includes("transferfrom failed"))
55
+ return { code: "TOKEN_TRANSFER_FAILED", message, hint: "Check token balance, token contract, payer address, and that the selected x402 route matches the provider requirement." };
56
+ if (lower.includes("insufficient funds for gas") || lower.includes("insufficient gas") || lower.includes("energy"))
57
+ return { code: "INSUFFICIENT_GAS", message, hint: "Fund the payer address with the native gas token for this network." };
58
+ if (lower.includes("deadline") || lower.includes("expired"))
59
+ return { code: "DEADLINE_OR_CLOCK_SKEW", message, hint: "Check local clock sync and retry with a fresh payment requirement." };
60
+ if (lower.includes("permittransferfrom") || lower.includes("invalid signature") || lower.includes("permit reverted"))
61
+ return { code: "PERMIT_REVERTED", message, hint: "The token or Permit2 contract rejected the signature; retry with a fresh requirement and verify token/network support." };
62
+ if (lower.includes("tokenregistry") && lower.includes("import"))
63
+ return { code: "SDK_API_DRIFT", message, hint: "Installed x402 SDK packages do not match this CLI; reinstall @bankofai/x402-cli and SDK dependencies." };
64
+ if (lower.includes("429") || lower.includes("too many requests") || lower.includes("rate limit"))
65
+ return { code: "RATE_LIMITED", message, hint: "Wait briefly and retry; the upstream service or RPC is rate limiting requests." };
66
+ if (lower.includes("402 response missing"))
67
+ return { code: "INVALID_X402_RESPONSE", message, hint: "The endpoint returned HTTP 402 without a PAYMENT-REQUIRED header." };
68
+ if (lower.includes("no matching payment requirement"))
69
+ return { code: "NO_MATCHING_PAYMENT_REQUIREMENT", message, hint: "Relax --network, --token, or --scheme, or use values offered by the provider." };
70
+ if (lower.includes("exceeds --max"))
71
+ return { code: "PAYMENT_AMOUNT_TOO_HIGH", message, hint: "Increase the max amount flag only if this provider price is expected." };
72
+ if (lower.includes("failed to fetch") || lower.includes("fetch failed") || lower.includes("econnrefused") || lower.includes("timed out"))
73
+ return { code: "NETWORK_ERROR", message, hint: "Check the URL, local server, proxy, and network connectivity." };
74
+ if (lower.includes(" is required") || lower.includes("must be") || lower.includes("invalid --") || lower.includes("mutually exclusive"))
75
+ return { code: lower.includes("required") ? "MISSING_ARGUMENT" : "INVALID_ARGUMENT", message, hint: "Run the command with --help to see valid usage and options." };
76
+ return { code: "IO_ERROR", message, hint: "Run with --json for structured output, and check the provider/gateway logs for details." };
77
+ }
78
+ export async function withSdkStdoutRedirect(enabled, fn) {
79
+ if (!enabled)
80
+ return fn();
81
+ const originalLog = console.log;
82
+ console.log = (...args) => {
83
+ process.stderr.write(`${args.map(arg => typeof arg === "string" ? arg : JSON.stringify(arg, null, 2)).join(" ")}\n`);
84
+ };
85
+ try {
86
+ return await fn();
87
+ }
88
+ finally {
89
+ console.log = originalLog;
90
+ }
91
+ }
package/dist/tokens.js CHANGED
@@ -1,5 +1,5 @@
1
1
  export const TOKENS = {
2
- "tron:mainnet": {
2
+ "tron:0x2b6653dc": {
3
3
  USDT: {
4
4
  address: "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t",
5
5
  decimals: 6,
@@ -17,7 +17,7 @@ export const TOKENS = {
17
17
  assetTransferMethod: "permit2",
18
18
  },
19
19
  },
20
- "tron:nile": {
20
+ "tron:0xcd8690dc": {
21
21
  USDT: {
22
22
  address: "TXYZopYRdj2D9XRtbG411XZZ3kM5VkAeBf",
23
23
  decimals: 6,
@@ -35,7 +35,7 @@ export const TOKENS = {
35
35
  assetTransferMethod: "permit2",
36
36
  },
37
37
  },
38
- "tron:shasta": {
38
+ "tron:0x94a9059e": {
39
39
  USDT: {
40
40
  address: "TG3XXyExBkPp9nzdajDZsozEu4BkaSJozs",
41
41
  decimals: 6,
@@ -74,13 +74,25 @@ export const TOKENS = {
74
74
  },
75
75
  };
76
76
  export function normalizeNetwork(network) {
77
- return ({
78
- "tron-mainnet": "tron:mainnet",
79
- "tron-shasta": "tron:shasta",
80
- "tron-nile": "tron:nile",
77
+ const legacyTronIds = {
78
+ "tron-mainnet": "tron:0x2b6653dc",
79
+ "tron:mainnet": "tron:0x2b6653dc",
80
+ mainnet: "tron:0x2b6653dc",
81
+ "tron-shasta": "tron:0x94a9059e",
82
+ "tron:shasta": "tron:0x94a9059e",
83
+ shasta: "tron:0x94a9059e",
84
+ "tron-nile": "tron:0xcd8690dc",
85
+ "tron:nile": "tron:0xcd8690dc",
86
+ nile: "tron:0xcd8690dc",
87
+ };
88
+ const canonical = legacyTronIds[network];
89
+ if (canonical) {
90
+ throw new Error(`legacy TRON network identifier ${network} is not supported; use ${canonical}`);
91
+ }
92
+ return {
81
93
  "bsc-mainnet": "eip155:56",
82
94
  "bsc-testnet": "eip155:97",
83
- }[network] ?? network);
95
+ }[network] ?? network;
84
96
  }
85
97
  export function getToken(network, symbol) {
86
98
  const token = TOKENS[normalizeNetwork(network)]?.[symbol.toUpperCase()];
@@ -98,8 +110,8 @@ export function assertRawAmount(value, name = "raw amount") {
98
110
  return value.replace(/^0+(?=\d)/, "");
99
111
  }
100
112
  export function toSmallestUnit(amount, decimals) {
101
- if (!Number.isInteger(decimals) || decimals < 0)
102
- throw new Error("token decimals must be a non-negative integer");
113
+ if (!Number.isInteger(decimals) || decimals < 0 || decimals > 255)
114
+ throw new Error("token decimals must be an integer between 0 and 255");
103
115
  if (!/^\d+(\.\d+)?$/.test(amount))
104
116
  throw new Error("amount must be a non-negative decimal string");
105
117
  const [whole, fraction = ""] = amount.split(".");
package/dist/x402.js CHANGED
@@ -2,7 +2,7 @@ import { decodePaymentRequiredHeader, decodePaymentResponseHeader, decodePayment
2
2
  import { x402Client } from "@bankofai/x402-core/client";
3
3
  import { ExactEvmScheme, toClientEvmSigner } from "@bankofai/x402-evm";
4
4
  import { ExactTronScheme, createClientTronSigner } from "@bankofai/x402-tron";
5
- import { registerExactGasFreeTronScheme } from "@bankofai/x402-tron/gasfree/client";
5
+ import { ExactGasFreeTronScheme, createGasFreeApiClients, getGasFreeApiBaseUrl } from "@bankofai/x402-tron/gasfree";
6
6
  import { createPublicClient, http } from "viem";
7
7
  import { privateKeyToAccount } from "viem/accounts";
8
8
  import { TronWeb } from "tronweb";
@@ -106,7 +106,7 @@ function evmRpcUrl(network, explicit) {
106
106
  (chainId === "56" ? "https://bsc-dataseed.binance.org" : undefined) ||
107
107
  (chainId === "97" ? "https://data-seed-prebsc-1-s1.binance.org:8545" : undefined));
108
108
  }
109
- async function createTronWallet(privateKey) {
109
+ async function createTronWallet(privateKey, maxGasfreeFeeRaw) {
110
110
  const rawKey = privateKey.replace(/^0x/, "");
111
111
  const tronWeb = new TronWeb({ fullHost: "https://api.trongrid.io" });
112
112
  const address = TronWeb.address.fromPrivateKey(rawKey);
@@ -115,6 +115,12 @@ async function createTronWallet(privateKey) {
115
115
  return {
116
116
  getAddress: () => address,
117
117
  async signTypedData(args) {
118
+ if (maxGasfreeFeeRaw !== undefined && args?.primaryType === "PermitTransfer") {
119
+ const maxFee = BigInt(args?.message?.maxFee ?? -1);
120
+ if (maxFee < 0n || maxFee > BigInt(maxGasfreeFeeRaw)) {
121
+ throw new Error(`final GasFree maxFee ${maxFee} exceeds --max-gasfree-fee limit ${maxGasfreeFeeRaw}`);
122
+ }
123
+ }
118
124
  const signature = await signTronTypedData(tronWeb, args, rawKey);
119
125
  return signature.startsWith("0x") ? signature : `0x${signature}`;
120
126
  },
@@ -142,13 +148,14 @@ export async function createPaymentPayload(args) {
142
148
  const publicClient = rpcUrl ? createPublicClient({ transport: http(rpcUrl) }) : undefined;
143
149
  const signer = toClientEvmSigner(account, publicClient);
144
150
  const scheme = new ExactEvmScheme(signer, rpcUrl ? { rpcUrl } : undefined);
145
- return new x402Client()
151
+ const payload = await new x402Client()
146
152
  .register(selected.network, scheme)
147
153
  .createPaymentPayload(required);
154
+ return { payload };
148
155
  }
149
156
  if (selected.network.startsWith("tron:")) {
150
157
  const privateKey = privateKeyFrom(["TRON_PRIVATE_KEY", "AGENT_WALLET_PRIVATE_KEY", "PRIVATE_KEY"], args.privateKey, ["tron_client", "payer", "default"]);
151
- const wallet = await createTronWallet(privateKey);
158
+ const wallet = await createTronWallet(privateKey, selected.scheme === "exact_gasfree" ? args.maxGasfreeFeeRaw : undefined);
152
159
  const signer = await createClientTronSigner(wallet, {
153
160
  network: selected.network,
154
161
  rpcUrl: args.rpcUrl || process.env.TRON_RPC_URL,
@@ -156,14 +163,20 @@ export async function createPaymentPayload(args) {
156
163
  allowanceMode: args.allowanceMode || process.env.X402_TRON_ALLOWANCE_MODE || "auto",
157
164
  });
158
165
  const client = new x402Client();
166
+ let gasfreeEstimate;
159
167
  if (selected.scheme === "exact_gasfree") {
160
- registerExactGasFreeTronScheme(client, {
161
- signer,
162
- networks: [selected.network],
163
- ...(args.gasfreeApiUrl || process.env.X402_GASFREE_API_URL
164
- ? { schemeOptions: { apiBaseUrls: { [selected.network]: args.gasfreeApiUrl || process.env.X402_GASFREE_API_URL } } }
165
- : {}),
168
+ const apiUrl = args.gasfreeApiUrl || process.env.X402_GASFREE_API_URL || getGasFreeApiBaseUrl(selected.network);
169
+ const scheme = new ExactGasFreeTronScheme(signer, {
170
+ apiClients: createGasFreeApiClients({ [selected.network]: apiUrl }),
166
171
  });
172
+ const total = await scheme.estimateCost(selected);
173
+ const amount = BigInt(selected.amount);
174
+ const fee = total - amount;
175
+ if (args.maxGasfreeFeeRaw !== undefined && fee > BigInt(args.maxGasfreeFeeRaw)) {
176
+ throw new Error(`estimated GasFree fee ${fee} exceeds --max-gasfree-fee limit ${args.maxGasfreeFeeRaw}`);
177
+ }
178
+ gasfreeEstimate = { fee: fee.toString(), total: total.toString() };
179
+ client.register(selected.network, scheme);
167
180
  }
168
181
  else if (selected.scheme === "exact") {
169
182
  client.register(selected.network, new ExactTronScheme(signer));
@@ -171,7 +184,8 @@ export async function createPaymentPayload(args) {
171
184
  else {
172
185
  throw new Error(`unsupported scheme ${selected.scheme} on ${selected.network}`);
173
186
  }
174
- return client.createPaymentPayload(required);
187
+ const payload = await client.createPaymentPayload(required);
188
+ return { payload, ...(gasfreeEstimate ? { gasfreeEstimate } : {}) };
175
189
  }
176
190
  throw new Error(`unsupported network ${selected.network}`);
177
191
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bankofai/x402-cli",
3
- "version": "1.0.1-beta.0",
3
+ "version": "1.0.1-beta.10",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist"
@@ -9,10 +9,12 @@
9
9
  "x402-cli": "dist/cli.js"
10
10
  },
11
11
  "scripts": {
12
- "build": "tsc -p tsconfig.json",
12
+ "clean": "node scripts/clean.mjs",
13
+ "build": "npm run clean && tsc -p tsconfig.json",
13
14
  "bundle:gateway": "node scripts/bundle-gateway.mjs",
14
15
  "prepack": "npm run build && npm run bundle:gateway",
15
16
  "test": "npm run build && node --test tests/*.test.mjs",
17
+ "test:package": "node scripts/test-package.mjs",
16
18
  "start": "node dist/cli.js",
17
19
  "dev": "tsx src/cli.ts"
18
20
  },
@@ -20,17 +22,19 @@
20
22
  "node": ">=20"
21
23
  },
22
24
  "dependencies": {
23
- "@bankofai/x402-core": "1.0.1-beta.2",
24
- "@bankofai/x402-evm": "1.0.1-beta.2",
25
- "@bankofai/x402-gateway": "^1.0.0",
26
- "@bankofai/x402-tron": "1.0.1-beta.2",
25
+ "@bankofai/x402-core": "1.0.1",
26
+ "@bankofai/x402-evm": "1.0.1",
27
+ "@bankofai/x402-gateway": "1.0.1-beta.10",
28
+ "@bankofai/x402-tron": "1.0.1",
27
29
  "tronweb": "6.4.0",
28
- "viem": "^2.55.0",
29
- "yaml": "^2.8.2"
30
+ "viem": "^2.55.0"
30
31
  },
31
32
  "devDependencies": {
32
33
  "@types/node": "^24.10.1",
33
34
  "tsx": "^4.20.6",
34
35
  "typescript": "^5.9.3"
36
+ },
37
+ "overrides": {
38
+ "ws": "8.21.0"
35
39
  }
36
40
  }