@bankofai/x402-cli 1.0.1-beta.5 → 1.0.1-beta.7

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/README.md CHANGED
@@ -3,9 +3,9 @@
3
3
  TypeScript command-line client for BankofAI x402 payments. This version uses
4
4
  the npm TypeScript SDK packages only:
5
5
 
6
- - `@bankofai/x402-core@1.0.1-beta.4`
7
- - `@bankofai/x402-evm@1.0.1-beta.4`
8
- - `@bankofai/x402-tron@1.0.1-beta.4`
6
+ - `@bankofai/x402-core@1.0.1`
7
+ - `@bankofai/x402-evm@1.0.1`
8
+ - `@bankofai/x402-tron@1.0.1`
9
9
 
10
10
  Stablecoin payments support `scheme=exact` and TRON `scheme=exact_gasfree`.
11
11
  The GasFree flow lets the relayer pay network energy while deducting its fee
@@ -16,7 +16,7 @@ from the payment token, so the payer does not need TRX.
16
16
  Install the CLI package:
17
17
 
18
18
  ```bash
19
- npm install -g @bankofai/x402-cli
19
+ npm install -g @bankofai/x402-cli@beta
20
20
  x402-cli --version
21
21
  ```
22
22
 
@@ -58,7 +58,7 @@ envelope with `ok`, `command`, `result`, or structured `error` fields.
58
58
  Start a local x402 paywall endpoint:
59
59
 
60
60
  ```bash
61
- node dist/cli.js serve \
61
+ x402-cli serve \
62
62
  --pay-to <recipient> \
63
63
  --amount 0.0001 \
64
64
  --network tron:0xcd8690dc \
@@ -79,7 +79,7 @@ Pay an x402-protected URL:
79
79
 
80
80
  ```bash
81
81
  TRON_PRIVATE_KEY=<hex> \
82
- node dist/cli.js pay http://127.0.0.1:4020/pay \
82
+ x402-cli pay http://127.0.0.1:4020/pay \
83
83
  --network tron:0xcd8690dc \
84
84
  --token USDT
85
85
  ```
@@ -92,7 +92,7 @@ the server challenge):
92
92
 
93
93
  ```bash
94
94
  TRON_PRIVATE_KEY=<hex> \
95
- node dist/cli.js pay https://api.example.com/pay \
95
+ x402-cli pay https://api.example.com/pay \
96
96
  --network tron:0xcd8690dc \
97
97
  --token USDT \
98
98
  --scheme exact_gasfree
@@ -101,6 +101,24 @@ node dist/cli.js pay https://api.example.com/pay \
101
101
  Use `--gasfree-api-url <url>` or `X402_GASFREE_API_URL` to override the SDK's
102
102
  default relayer endpoint.
103
103
 
104
+ GasFree fees are separate from the advertised payment amount. Set a fee limit
105
+ so the CLI estimates the relayer fee and rejects the payment before signing if
106
+ the estimate is too high:
107
+
108
+ ```bash
109
+ x402-cli pay https://api.example.com/pay \
110
+ --scheme exact_gasfree \
111
+ --max-amount 0.01 \
112
+ --max-gasfree-fee 0.5 \
113
+ --json
114
+ ```
115
+
116
+ Use `--max-gasfree-fee-raw` to express the fee limit in the token's smallest
117
+ unit. Successful and failed paid responses distinguish `settled` (payment
118
+ completed) from `delivered` (HTTP business response succeeded). A settled
119
+ upstream failure has `paid=true`, `settled=true`, and `delivered=false` and
120
+ includes its transaction information.
121
+
104
122
  For EVM networks use `EVM_PRIVATE_KEY` or `PRIVATE_KEY`.
105
123
  Prefer environment variables over `--private-key` in shared environments,
106
124
  because command-line arguments may be visible to other local processes.
@@ -115,7 +133,7 @@ Start a temporary local server and immediately pay it:
115
133
 
116
134
  ```bash
117
135
  TRON_PRIVATE_KEY=<hex> \
118
- node dist/cli.js roundtrip \
136
+ x402-cli roundtrip \
119
137
  --pay-to <recipient> \
120
138
  --amount 0.0001 \
121
139
  --network tron:0xcd8690dc \
package/dist/cli.js CHANGED
@@ -321,6 +321,8 @@ Options:
321
321
  --token <symbol> Require a specific token
322
322
  --scheme <scheme> Require a specific x402 scheme
323
323
  --gasfree-api-url <url> Override the TRON GasFree relayer API URL
324
+ --max-gasfree-fee <amt> Maximum GasFree relayer fee in token units
325
+ --max-gasfree-fee-raw <n> Maximum GasFree relayer fee in smallest units
324
326
  --max-amount <amount> Maximum human-readable payment amount
325
327
  --max-raw-amount <amount> Maximum smallest-unit payment amount
326
328
  --dry-run Read requirements but do not sign or pay
@@ -1084,6 +1086,31 @@ function validateAmountLimits(selected, options) {
1084
1086
  }
1085
1087
  }
1086
1088
  }
1089
+ function gasfreeFeeLimitRaw(selected, options) {
1090
+ const maxRaw = opt(options, "max-gasfree-fee-raw");
1091
+ const maxHuman = opt(options, "max-gasfree-fee");
1092
+ if (maxRaw && maxHuman) {
1093
+ throw new CliError("INVALID_ARGUMENT", "--max-gasfree-fee and --max-gasfree-fee-raw are mutually exclusive", "Pass one GasFree fee limit.", 2);
1094
+ }
1095
+ if (selected.scheme !== "exact_gasfree") {
1096
+ if (maxRaw || maxHuman)
1097
+ throw new Error("GasFree fee limits require an exact_gasfree payment requirement");
1098
+ return undefined;
1099
+ }
1100
+ if (maxRaw)
1101
+ return assertRawAmount(maxRaw, "--max-gasfree-fee-raw");
1102
+ if (!maxHuman)
1103
+ return undefined;
1104
+ const token = findTokenByAddress(selected.network, selected.asset);
1105
+ const decimalsOption = opt(options, "decimals");
1106
+ if (!token && decimalsOption === undefined) {
1107
+ throw new Error("cannot evaluate --max-gasfree-fee for an unknown asset; pass --max-gasfree-fee-raw or --decimals");
1108
+ }
1109
+ const decimals = decimalsOption !== undefined ? Number(decimalsOption) : token.decimals;
1110
+ if (!Number.isInteger(decimals) || decimals < 0)
1111
+ throw new Error("--decimals must be a non-negative integer");
1112
+ return toSmallestUnit(maxHuman, decimals);
1113
+ }
1087
1114
  async function serve(options) {
1088
1115
  const host = opt(options, "host", "127.0.0.1");
1089
1116
  const port = Number(opt(options, "port", "4020"));
@@ -1236,6 +1263,7 @@ async function pay(url, options) {
1236
1263
  const required = decodeRequired(header);
1237
1264
  const selected = selectRequirement(required.accepts ?? [], options);
1238
1265
  validateAmountLimits(selected, options);
1266
+ const maxGasfreeFeeRaw = gasfreeFeeLimitRaw(selected, options);
1239
1267
  if (options["dry-run"]) {
1240
1268
  emit({
1241
1269
  command: "client",
@@ -1251,16 +1279,17 @@ async function pay(url, options) {
1251
1279
  });
1252
1280
  return;
1253
1281
  }
1254
- const payload = await withSdkStdoutRedirect(outputMode(options) === "json", () => createPaymentPayload({
1282
+ const creation = await withSdkStdoutRedirect(outputMode(options) === "json", () => createPaymentPayload({
1255
1283
  selected,
1256
1284
  resource: required.resource?.url ?? url,
1257
1285
  extensions: required.extensions,
1258
1286
  rpcUrl: opt(options, "rpc-url"),
1259
1287
  privateKey: opt(options, "private-key"),
1260
1288
  gasfreeApiUrl: opt(options, "gasfree-api-url"),
1289
+ maxGasfreeFeeRaw,
1261
1290
  }));
1262
1291
  const retryHeaders = new Headers(baseHeaders);
1263
- retryHeaders.set(headers.signature, encodeSignature(payload));
1292
+ retryHeaders.set(headers.signature, encodeSignature(creation.payload));
1264
1293
  const paid = await fetchWithTimeout(url, {
1265
1294
  method,
1266
1295
  headers: retryHeaders,
@@ -1268,12 +1297,20 @@ async function pay(url, options) {
1268
1297
  }, timeoutMs(options), `fetch ${url}`);
1269
1298
  const body = await responsePayload(paid);
1270
1299
  const paymentResponse = paid.headers.get(headers.response);
1300
+ const settlement = paymentResponse ? decodeResponse(paymentResponse) : undefined;
1301
+ const settled = settlement !== undefined;
1271
1302
  const result = {
1272
1303
  url,
1273
1304
  status: paid.status,
1274
- paid: paid.ok,
1305
+ paid: settled,
1306
+ settled,
1307
+ delivered: paid.ok,
1275
1308
  response: body,
1276
- ...(paymentResponse ? { paymentResponse: decodeResponse(paymentResponse) } : {}),
1309
+ ...(settlement !== undefined ? {
1310
+ paymentResponse: settlement,
1311
+ transaction: settlement?.transaction ?? settlement?.txHash ?? null,
1312
+ } : {}),
1313
+ ...(creation.gasfreeEstimate ? { gasfreeEstimate: creation.gasfreeEstimate } : {}),
1277
1314
  };
1278
1315
  if (!paid.ok) {
1279
1316
  const retryAfter = paid.headers.get("retry-after");
@@ -173,6 +173,8 @@ function isVerifySuccess(verify) {
173
173
  return verify?.valid === true || verify?.isValid === true;
174
174
  }
175
175
  function isSettleSuccess(settle) {
176
+ if (settle?.success === false || settle?.settled === false)
177
+ return false;
176
178
  return (settle?.success === true ||
177
179
  settle?.settled === true ||
178
180
  (typeof settle?.transaction === "string" && settle.transaction.length > 0) ||
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";
@@ -142,9 +142,10 @@ export async function createPaymentPayload(args) {
142
142
  const publicClient = rpcUrl ? createPublicClient({ transport: http(rpcUrl) }) : undefined;
143
143
  const signer = toClientEvmSigner(account, publicClient);
144
144
  const scheme = new ExactEvmScheme(signer, rpcUrl ? { rpcUrl } : undefined);
145
- return new x402Client()
145
+ const payload = await new x402Client()
146
146
  .register(selected.network, scheme)
147
147
  .createPaymentPayload(required);
148
+ return { payload };
148
149
  }
149
150
  if (selected.network.startsWith("tron:")) {
150
151
  const privateKey = privateKeyFrom(["TRON_PRIVATE_KEY", "AGENT_WALLET_PRIVATE_KEY", "PRIVATE_KEY"], args.privateKey, ["tron_client", "payer", "default"]);
@@ -156,14 +157,20 @@ export async function createPaymentPayload(args) {
156
157
  allowanceMode: args.allowanceMode || process.env.X402_TRON_ALLOWANCE_MODE || "auto",
157
158
  });
158
159
  const client = new x402Client();
160
+ let gasfreeEstimate;
159
161
  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
- : {}),
162
+ const apiUrl = args.gasfreeApiUrl || process.env.X402_GASFREE_API_URL || getGasFreeApiBaseUrl(selected.network);
163
+ const scheme = new ExactGasFreeTronScheme(signer, {
164
+ apiClients: createGasFreeApiClients({ [selected.network]: apiUrl }),
166
165
  });
166
+ const total = await scheme.estimateCost(selected);
167
+ const amount = BigInt(selected.amount);
168
+ const fee = total - amount;
169
+ if (args.maxGasfreeFeeRaw !== undefined && fee > BigInt(args.maxGasfreeFeeRaw)) {
170
+ throw new Error(`estimated GasFree fee ${fee} exceeds --max-gasfree-fee limit ${args.maxGasfreeFeeRaw}`);
171
+ }
172
+ gasfreeEstimate = { fee: fee.toString(), total: total.toString() };
173
+ client.register(selected.network, scheme);
167
174
  }
168
175
  else if (selected.scheme === "exact") {
169
176
  client.register(selected.network, new ExactTronScheme(signer));
@@ -171,7 +178,8 @@ export async function createPaymentPayload(args) {
171
178
  else {
172
179
  throw new Error(`unsupported scheme ${selected.scheme} on ${selected.network}`);
173
180
  }
174
- return client.createPaymentPayload(required);
181
+ const payload = await client.createPaymentPayload(required);
182
+ return { payload, ...(gasfreeEstimate ? { gasfreeEstimate } : {}) };
175
183
  }
176
184
  throw new Error(`unsupported network ${selected.network}`);
177
185
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bankofai/x402-cli",
3
- "version": "1.0.1-beta.5",
3
+ "version": "1.0.1-beta.7",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist"
@@ -20,10 +20,10 @@
20
20
  "node": ">=20"
21
21
  },
22
22
  "dependencies": {
23
- "@bankofai/x402-core": "1.0.1-beta.4",
24
- "@bankofai/x402-evm": "1.0.1-beta.4",
25
- "@bankofai/x402-gateway": "1.0.1-beta.5",
26
- "@bankofai/x402-tron": "1.0.1-beta.4",
23
+ "@bankofai/x402-core": "1.0.1",
24
+ "@bankofai/x402-evm": "1.0.1",
25
+ "@bankofai/x402-gateway": "1.0.1-beta.6",
26
+ "@bankofai/x402-tron": "1.0.1",
27
27
  "tronweb": "6.4.0",
28
28
  "viem": "^2.55.0",
29
29
  "yaml": "^2.8.2"