@zeroxyz/cli 0.0.6 → 0.0.8
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/index.js +163 -72
- package/package.json +6 -2
package/dist/index.js
CHANGED
|
@@ -6,7 +6,7 @@ import { Command as Command8 } from "commander";
|
|
|
6
6
|
// package.json
|
|
7
7
|
var package_default = {
|
|
8
8
|
name: "@zeroxyz/cli",
|
|
9
|
-
version: "0.0.
|
|
9
|
+
version: "0.0.8",
|
|
10
10
|
type: "module",
|
|
11
11
|
bin: {
|
|
12
12
|
zero: "dist/index.js",
|
|
@@ -26,8 +26,9 @@ var package_default = {
|
|
|
26
26
|
dev: "tsx src/index.ts",
|
|
27
27
|
cli: "ZERO_API_URL=http://localhost:1111 tsx src/index.ts",
|
|
28
28
|
"test:integration": "vitest run --project integration",
|
|
29
|
+
"test:online": "vitest run --project online",
|
|
29
30
|
"test:unit": "vitest run --project unit",
|
|
30
|
-
test: "pnpm run test:integration",
|
|
31
|
+
test: "pnpm run test:unit && pnpm run test:integration",
|
|
31
32
|
typecheck: "tsc"
|
|
32
33
|
},
|
|
33
34
|
dependencies: {
|
|
@@ -43,7 +44,10 @@ var package_default = {
|
|
|
43
44
|
zod: "^4.3.5"
|
|
44
45
|
},
|
|
45
46
|
devDependencies: {
|
|
47
|
+
"@hono/node-server": "^1.19.13",
|
|
46
48
|
"@types/node": "^25.0.7",
|
|
49
|
+
"@x402/hono": "^2.9.0",
|
|
50
|
+
hono: "^4.12.12",
|
|
47
51
|
tsup: "^8.5.1",
|
|
48
52
|
tsx: "^4.21.0",
|
|
49
53
|
typescript: "^5.9.3",
|
|
@@ -105,7 +109,7 @@ var configCommand = (_appContext) => new Command("config").description("View or
|
|
|
105
109
|
import { Command as Command2 } from "commander";
|
|
106
110
|
var detectPaymentRequirement = (headers, status) => {
|
|
107
111
|
if (status !== 402) return null;
|
|
108
|
-
const x402Header = headers.get("x-payment-required");
|
|
112
|
+
const x402Header = headers.get("payment-required") ?? headers.get("x-payment-required");
|
|
109
113
|
if (x402Header) {
|
|
110
114
|
try {
|
|
111
115
|
const decoded = JSON.parse(
|
|
@@ -235,39 +239,48 @@ var fetchCommand = (appContext2) => new Command2("fetch").description("Fetch a c
|
|
|
235
239
|
|
|
236
240
|
// src/commands/get-command.ts
|
|
237
241
|
import { Command as Command3 } from "commander";
|
|
238
|
-
var getCommand = (appContext2) => new Command3("get").description(
|
|
242
|
+
var getCommand = (appContext2) => new Command3("get").description(
|
|
243
|
+
"Get details for a capability by position from last search, or by slug"
|
|
244
|
+
).argument(
|
|
245
|
+
"<identifier>",
|
|
246
|
+
"Position number from search results, or a capability slug"
|
|
247
|
+
).action(async (identifier) => {
|
|
239
248
|
try {
|
|
240
249
|
const { analyticsService, apiService, stateService } = appContext2.services;
|
|
241
|
-
const position = Number.parseInt(
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
(c) => c.position === position
|
|
255
|
-
);
|
|
256
|
-
if (!entry) {
|
|
257
|
-
console.error(
|
|
258
|
-
`No capability at position ${position}. Positions: ${lastSearch.capabilities.map((c) => c.position).join(", ")}`
|
|
250
|
+
const position = Number.parseInt(identifier, 10);
|
|
251
|
+
const isPosition = !Number.isNaN(position) && position >= 1;
|
|
252
|
+
let capabilityId;
|
|
253
|
+
let searchId;
|
|
254
|
+
if (isPosition) {
|
|
255
|
+
const lastSearch = stateService.loadLastSearch();
|
|
256
|
+
if (!lastSearch) {
|
|
257
|
+
console.error("No recent search found. Run `zero search` first.");
|
|
258
|
+
process.exitCode = 1;
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
const entry = lastSearch.capabilities.find(
|
|
262
|
+
(c) => c.position === position
|
|
259
263
|
);
|
|
260
|
-
|
|
261
|
-
|
|
264
|
+
if (!entry) {
|
|
265
|
+
console.error(
|
|
266
|
+
`No capability at position ${position}. Positions: ${lastSearch.capabilities.map((c) => c.position).join(", ")}`
|
|
267
|
+
);
|
|
268
|
+
process.exitCode = 1;
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
capabilityId = entry.id;
|
|
272
|
+
searchId = lastSearch.searchId;
|
|
273
|
+
} else {
|
|
274
|
+
capabilityId = identifier;
|
|
262
275
|
}
|
|
263
276
|
const capability = await apiService.getCapability(
|
|
264
|
-
|
|
265
|
-
|
|
277
|
+
capabilityId,
|
|
278
|
+
searchId
|
|
266
279
|
);
|
|
267
280
|
console.log(JSON.stringify(capability, null, 2));
|
|
268
281
|
analyticsService.capture("capability_viewed", {
|
|
269
|
-
capabilityId
|
|
270
|
-
position
|
|
282
|
+
capabilityId,
|
|
283
|
+
...isPosition ? { position } : {}
|
|
271
284
|
});
|
|
272
285
|
} catch (err) {
|
|
273
286
|
console.error(err instanceof Error ? err.message : "Get failed");
|
|
@@ -920,7 +933,7 @@ import {
|
|
|
920
933
|
encodePaymentSignatureHeader
|
|
921
934
|
} from "@x402/core/http";
|
|
922
935
|
import { ExactEvmScheme } from "@x402/evm/exact/client";
|
|
923
|
-
import { Challenge } from "mppx";
|
|
936
|
+
import { Challenge, Receipt } from "mppx";
|
|
924
937
|
import { Mppx, tempo } from "mppx/client";
|
|
925
938
|
import {
|
|
926
939
|
createPublicClient,
|
|
@@ -928,11 +941,16 @@ import {
|
|
|
928
941
|
formatUnits,
|
|
929
942
|
http
|
|
930
943
|
} from "viem";
|
|
931
|
-
import { base } from "viem/chains";
|
|
944
|
+
import { base, baseSepolia } from "viem/chains";
|
|
932
945
|
var USDC_BASE = "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913";
|
|
946
|
+
var USDC_BASE_SEPOLIA = "0x036CbD53842c5426634e7929541eC2318f3dCF7e";
|
|
933
947
|
var USDC_TEMPO = "0x20c000000000000000000000b9537d11c60e8b50";
|
|
948
|
+
var PATHUSD_TEMPO = "0x20c0000000000000000000000000000000000000";
|
|
934
949
|
var BASE_CHAIN_ID = 8453;
|
|
935
950
|
var TEMPO_CHAIN_ID = 4217;
|
|
951
|
+
var TEMPO_TESTNET_CHAIN_ID = 42431;
|
|
952
|
+
var X402_PAYMENT_MAX_ATTEMPTS = 6;
|
|
953
|
+
var X402_PAYMENT_RETRY_DELAY_MS = 300;
|
|
936
954
|
var calculateBuffer = (baseBalance) => {
|
|
937
955
|
const twentyFivePercent = baseBalance / 4n;
|
|
938
956
|
const twoDollars = 2000000n;
|
|
@@ -946,6 +964,14 @@ var tempoChain = {
|
|
|
946
964
|
default: { http: ["https://rpc.tempo.xyz"] }
|
|
947
965
|
}
|
|
948
966
|
};
|
|
967
|
+
var tempoTestnetChain = {
|
|
968
|
+
id: TEMPO_TESTNET_CHAIN_ID,
|
|
969
|
+
name: "Tempo Testnet",
|
|
970
|
+
nativeCurrency: { name: "USD", symbol: "USD", decimals: 18 },
|
|
971
|
+
rpcUrls: {
|
|
972
|
+
default: { http: ["https://rpc.moderato.tempo.xyz"] }
|
|
973
|
+
}
|
|
974
|
+
};
|
|
949
975
|
var ERC20_BALANCE_ABI = [
|
|
950
976
|
{
|
|
951
977
|
inputs: [{ name: "account", type: "address" }],
|
|
@@ -1044,47 +1070,84 @@ var PaymentService = class {
|
|
|
1044
1070
|
}
|
|
1045
1071
|
throw new Error("Unrecognized 402 payment protocol");
|
|
1046
1072
|
};
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
throw new Error("No accepted payment methods in x402 challenge");
|
|
1073
|
+
toX402RawChallenge = (headerValue) => {
|
|
1074
|
+
try {
|
|
1075
|
+
return JSON.parse(Buffer.from(headerValue, "base64").toString("utf8"));
|
|
1076
|
+
} catch {
|
|
1077
|
+
return { encoded: headerValue };
|
|
1053
1078
|
}
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1079
|
+
};
|
|
1080
|
+
toX402HeaderValue = (raw) => {
|
|
1081
|
+
const encoded = raw?.encoded;
|
|
1082
|
+
if (typeof encoded === "string" && encoded.length > 0) {
|
|
1083
|
+
return encoded;
|
|
1059
1084
|
}
|
|
1085
|
+
return Buffer.from(JSON.stringify(raw)).toString("base64");
|
|
1086
|
+
};
|
|
1087
|
+
payX402 = async (url, request, raw, maxPay) => {
|
|
1060
1088
|
const client = this.getX402Client();
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
const settlement = decodePaymentResponseHeader(paymentResponseHeader);
|
|
1076
|
-
txHash = settlement.transaction ?? null;
|
|
1077
|
-
} catch {
|
|
1089
|
+
let currentRaw = raw;
|
|
1090
|
+
let amountUsdc = "0";
|
|
1091
|
+
for (let attempt = 1; attempt <= X402_PAYMENT_MAX_ATTEMPTS; attempt++) {
|
|
1092
|
+
const headerValue = this.toX402HeaderValue(currentRaw);
|
|
1093
|
+
const paymentRequired = decodePaymentRequiredHeader(headerValue);
|
|
1094
|
+
const requirement = paymentRequired.accepts[0];
|
|
1095
|
+
if (!requirement) {
|
|
1096
|
+
throw new Error("No accepted payment methods in x402 challenge");
|
|
1097
|
+
}
|
|
1098
|
+
amountUsdc = formatUnits(BigInt(requirement.amount), 6);
|
|
1099
|
+
if (maxPay && Number.parseFloat(amountUsdc) > Number.parseFloat(maxPay)) {
|
|
1100
|
+
throw new Error(
|
|
1101
|
+
`Payment of ${amountUsdc} USDC exceeds --max-pay ${maxPay}`
|
|
1102
|
+
);
|
|
1078
1103
|
}
|
|
1104
|
+
const paymentPayload = await client.createPaymentPayload(paymentRequired);
|
|
1105
|
+
const signatureHeader = encodePaymentSignatureHeader(paymentPayload);
|
|
1106
|
+
const retryResponse = await fetch(url, {
|
|
1107
|
+
method: request.method,
|
|
1108
|
+
headers: {
|
|
1109
|
+
...request.headers,
|
|
1110
|
+
"PAYMENT-SIGNATURE": signatureHeader
|
|
1111
|
+
},
|
|
1112
|
+
body: request.body
|
|
1113
|
+
});
|
|
1114
|
+
if (retryResponse.status === 402) {
|
|
1115
|
+
if (attempt >= X402_PAYMENT_MAX_ATTEMPTS) {
|
|
1116
|
+
throw new Error(
|
|
1117
|
+
`x402 payment failed after ${X402_PAYMENT_MAX_ATTEMPTS} attempts`
|
|
1118
|
+
);
|
|
1119
|
+
}
|
|
1120
|
+
const nextHeader = retryResponse.headers.get("payment-required") ?? retryResponse.headers.get("x-payment-required");
|
|
1121
|
+
if (!nextHeader) {
|
|
1122
|
+
throw new Error(
|
|
1123
|
+
"x402 payment retry returned 402 without payment-required header"
|
|
1124
|
+
);
|
|
1125
|
+
}
|
|
1126
|
+
currentRaw = this.toX402RawChallenge(nextHeader);
|
|
1127
|
+
await new Promise(
|
|
1128
|
+
(resolve) => setTimeout(resolve, attempt * X402_PAYMENT_RETRY_DELAY_MS)
|
|
1129
|
+
);
|
|
1130
|
+
continue;
|
|
1131
|
+
}
|
|
1132
|
+
let txHash = null;
|
|
1133
|
+
const paymentResponseHeader = retryResponse.headers.get("payment-response") ?? retryResponse.headers.get("x-payment-response");
|
|
1134
|
+
if (paymentResponseHeader) {
|
|
1135
|
+
try {
|
|
1136
|
+
const settlement = decodePaymentResponseHeader(paymentResponseHeader);
|
|
1137
|
+
txHash = settlement.transaction ?? null;
|
|
1138
|
+
} catch {
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
return {
|
|
1142
|
+
response: retryResponse,
|
|
1143
|
+
protocol: "x402",
|
|
1144
|
+
chain: "base",
|
|
1145
|
+
txHash,
|
|
1146
|
+
amount: amountUsdc,
|
|
1147
|
+
asset: "USDC"
|
|
1148
|
+
};
|
|
1079
1149
|
}
|
|
1080
|
-
|
|
1081
|
-
response: retryResponse,
|
|
1082
|
-
protocol: "x402",
|
|
1083
|
-
chain: "base",
|
|
1084
|
-
txHash,
|
|
1085
|
-
amount: amountUsdc,
|
|
1086
|
-
asset: "USDC"
|
|
1087
|
-
};
|
|
1150
|
+
throw new Error("x402 payment failed");
|
|
1088
1151
|
};
|
|
1089
1152
|
payMpp = async (url, request, raw, maxPay) => {
|
|
1090
1153
|
const wwwAuth = raw?.["www-authenticate"] ?? "";
|
|
@@ -1093,15 +1156,25 @@ var PaymentService = class {
|
|
|
1093
1156
|
headers: { "www-authenticate": wwwAuth }
|
|
1094
1157
|
});
|
|
1095
1158
|
const challenge = Challenge.fromResponse(challengeResponse);
|
|
1096
|
-
const
|
|
1159
|
+
const challengeRequest = challenge.request;
|
|
1160
|
+
const amountRaw = challengeRequest.amount;
|
|
1097
1161
|
const amountUsdc = formatUnits(BigInt(amountRaw), 6);
|
|
1098
1162
|
if (maxPay && Number.parseFloat(amountUsdc) > Number.parseFloat(maxPay)) {
|
|
1099
1163
|
throw new Error(
|
|
1100
1164
|
`Payment of ${amountUsdc} USDC exceeds --max-pay ${maxPay}`
|
|
1101
1165
|
);
|
|
1102
1166
|
}
|
|
1103
|
-
const
|
|
1167
|
+
const methodDetails = challengeRequest.methodDetails;
|
|
1168
|
+
const challengeChainId = challengeRequest.chainId ?? methodDetails?.chainId;
|
|
1169
|
+
const isTestnet = challengeChainId === TEMPO_TESTNET_CHAIN_ID;
|
|
1170
|
+
const balanceChain = isTestnet ? "tempo-testnet" : "tempo";
|
|
1171
|
+
const tempoBalance = await this.getBalanceRaw(balanceChain);
|
|
1104
1172
|
if (tempoBalance < BigInt(amountRaw)) {
|
|
1173
|
+
if (isTestnet) {
|
|
1174
|
+
throw new Error(
|
|
1175
|
+
`Insufficient pathUSD on Tempo testnet: have ${formatUnits(tempoBalance, 6)}, need ${amountUsdc}. Fund your wallet with the Tempo testnet faucet: https://docs.tempo.xyz/quickstart/faucet`
|
|
1176
|
+
);
|
|
1177
|
+
}
|
|
1105
1178
|
await this.bridgeToTempo(BigInt(amountRaw));
|
|
1106
1179
|
}
|
|
1107
1180
|
const mppx = this.getMppxClient();
|
|
@@ -1115,24 +1188,42 @@ var PaymentService = class {
|
|
|
1115
1188
|
},
|
|
1116
1189
|
body: request.body
|
|
1117
1190
|
});
|
|
1191
|
+
let txHash = null;
|
|
1192
|
+
try {
|
|
1193
|
+
const receipt = Receipt.fromResponse(retryResponse);
|
|
1194
|
+
txHash = receipt.reference ?? null;
|
|
1195
|
+
} catch {
|
|
1196
|
+
}
|
|
1118
1197
|
return {
|
|
1119
1198
|
response: retryResponse,
|
|
1120
1199
|
protocol: "mpp",
|
|
1121
1200
|
chain: "tempo",
|
|
1122
|
-
txHash
|
|
1123
|
-
// MPP pull mode — server broadcasts, tx hash not returned in response
|
|
1201
|
+
txHash,
|
|
1124
1202
|
amount: amountUsdc,
|
|
1125
1203
|
asset: "USDC"
|
|
1126
1204
|
};
|
|
1127
1205
|
};
|
|
1206
|
+
resolveChainConfig = (chain) => {
|
|
1207
|
+
switch (chain) {
|
|
1208
|
+
case "base":
|
|
1209
|
+
return { viemChain: base, token: USDC_BASE };
|
|
1210
|
+
case "base-sepolia":
|
|
1211
|
+
return { viemChain: baseSepolia, token: USDC_BASE_SEPOLIA };
|
|
1212
|
+
case "tempo":
|
|
1213
|
+
return { viemChain: tempoChain, token: USDC_TEMPO };
|
|
1214
|
+
case "tempo-testnet":
|
|
1215
|
+
return { viemChain: tempoTestnetChain, token: PATHUSD_TEMPO };
|
|
1216
|
+
}
|
|
1217
|
+
};
|
|
1128
1218
|
getBalanceRaw = async (chain) => {
|
|
1129
1219
|
if (!this.account) return 0n;
|
|
1220
|
+
const { viemChain, token } = this.resolveChainConfig(chain);
|
|
1130
1221
|
const client = createPublicClient({
|
|
1131
|
-
chain:
|
|
1222
|
+
chain: viemChain,
|
|
1132
1223
|
transport: http()
|
|
1133
1224
|
});
|
|
1134
1225
|
const balance = await client.readContract({
|
|
1135
|
-
address:
|
|
1226
|
+
address: token,
|
|
1136
1227
|
abi: ERC20_BALANCE_ABI,
|
|
1137
1228
|
functionName: "balanceOf",
|
|
1138
1229
|
args: [this.account.address]
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zeroxyz/cli",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.8",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"zero": "dist/index.js",
|
|
@@ -20,8 +20,9 @@
|
|
|
20
20
|
"dev": "tsx src/index.ts",
|
|
21
21
|
"cli": "ZERO_API_URL=http://localhost:1111 tsx src/index.ts",
|
|
22
22
|
"test:integration": "vitest run --project integration",
|
|
23
|
+
"test:online": "vitest run --project online",
|
|
23
24
|
"test:unit": "vitest run --project unit",
|
|
24
|
-
"test": "pnpm run test:integration",
|
|
25
|
+
"test": "pnpm run test:unit && pnpm run test:integration",
|
|
25
26
|
"typecheck": "tsc"
|
|
26
27
|
},
|
|
27
28
|
"dependencies": {
|
|
@@ -37,7 +38,10 @@
|
|
|
37
38
|
"zod": "^4.3.5"
|
|
38
39
|
},
|
|
39
40
|
"devDependencies": {
|
|
41
|
+
"@hono/node-server": "^1.19.13",
|
|
40
42
|
"@types/node": "^25.0.7",
|
|
43
|
+
"@x402/hono": "^2.9.0",
|
|
44
|
+
"hono": "^4.12.12",
|
|
41
45
|
"tsup": "^8.5.1",
|
|
42
46
|
"tsx": "^4.21.0",
|
|
43
47
|
"typescript": "^5.9.3",
|