@bankofai/x402-cli 1.0.1-beta.5 → 1.0.1-beta.6
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 +18 -0
- package/dist/cli.js +41 -4
- package/dist/x402.js +17 -9
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -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.
|
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
|
|
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:
|
|
1305
|
+
paid: settled,
|
|
1306
|
+
settled,
|
|
1307
|
+
delivered: paid.ok,
|
|
1275
1308
|
response: body,
|
|
1276
|
-
...(
|
|
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");
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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
|
-
|
|
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
|
}
|