@zeroxyz/cli 0.0.7 → 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 +129 -47
- 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(
|
|
@@ -929,7 +933,7 @@ import {
|
|
|
929
933
|
encodePaymentSignatureHeader
|
|
930
934
|
} from "@x402/core/http";
|
|
931
935
|
import { ExactEvmScheme } from "@x402/evm/exact/client";
|
|
932
|
-
import { Challenge } from "mppx";
|
|
936
|
+
import { Challenge, Receipt } from "mppx";
|
|
933
937
|
import { Mppx, tempo } from "mppx/client";
|
|
934
938
|
import {
|
|
935
939
|
createPublicClient,
|
|
@@ -937,11 +941,16 @@ import {
|
|
|
937
941
|
formatUnits,
|
|
938
942
|
http
|
|
939
943
|
} from "viem";
|
|
940
|
-
import { base } from "viem/chains";
|
|
944
|
+
import { base, baseSepolia } from "viem/chains";
|
|
941
945
|
var USDC_BASE = "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913";
|
|
946
|
+
var USDC_BASE_SEPOLIA = "0x036CbD53842c5426634e7929541eC2318f3dCF7e";
|
|
942
947
|
var USDC_TEMPO = "0x20c000000000000000000000b9537d11c60e8b50";
|
|
948
|
+
var PATHUSD_TEMPO = "0x20c0000000000000000000000000000000000000";
|
|
943
949
|
var BASE_CHAIN_ID = 8453;
|
|
944
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;
|
|
945
954
|
var calculateBuffer = (baseBalance) => {
|
|
946
955
|
const twentyFivePercent = baseBalance / 4n;
|
|
947
956
|
const twoDollars = 2000000n;
|
|
@@ -955,6 +964,14 @@ var tempoChain = {
|
|
|
955
964
|
default: { http: ["https://rpc.tempo.xyz"] }
|
|
956
965
|
}
|
|
957
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
|
+
};
|
|
958
975
|
var ERC20_BALANCE_ABI = [
|
|
959
976
|
{
|
|
960
977
|
inputs: [{ name: "account", type: "address" }],
|
|
@@ -1053,47 +1070,84 @@ var PaymentService = class {
|
|
|
1053
1070
|
}
|
|
1054
1071
|
throw new Error("Unrecognized 402 payment protocol");
|
|
1055
1072
|
};
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
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 };
|
|
1062
1078
|
}
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1079
|
+
};
|
|
1080
|
+
toX402HeaderValue = (raw) => {
|
|
1081
|
+
const encoded = raw?.encoded;
|
|
1082
|
+
if (typeof encoded === "string" && encoded.length > 0) {
|
|
1083
|
+
return encoded;
|
|
1068
1084
|
}
|
|
1085
|
+
return Buffer.from(JSON.stringify(raw)).toString("base64");
|
|
1086
|
+
};
|
|
1087
|
+
payX402 = async (url, request, raw, maxPay) => {
|
|
1069
1088
|
const client = this.getX402Client();
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
body: request.body
|
|
1079
|
-
});
|
|
1080
|
-
let txHash = null;
|
|
1081
|
-
const paymentResponseHeader = retryResponse.headers.get("payment-response");
|
|
1082
|
-
if (paymentResponseHeader) {
|
|
1083
|
-
try {
|
|
1084
|
-
const settlement = decodePaymentResponseHeader(paymentResponseHeader);
|
|
1085
|
-
txHash = settlement.transaction ?? null;
|
|
1086
|
-
} 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");
|
|
1087
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
|
+
);
|
|
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
|
+
};
|
|
1088
1149
|
}
|
|
1089
|
-
|
|
1090
|
-
response: retryResponse,
|
|
1091
|
-
protocol: "x402",
|
|
1092
|
-
chain: "base",
|
|
1093
|
-
txHash,
|
|
1094
|
-
amount: amountUsdc,
|
|
1095
|
-
asset: "USDC"
|
|
1096
|
-
};
|
|
1150
|
+
throw new Error("x402 payment failed");
|
|
1097
1151
|
};
|
|
1098
1152
|
payMpp = async (url, request, raw, maxPay) => {
|
|
1099
1153
|
const wwwAuth = raw?.["www-authenticate"] ?? "";
|
|
@@ -1102,15 +1156,25 @@ var PaymentService = class {
|
|
|
1102
1156
|
headers: { "www-authenticate": wwwAuth }
|
|
1103
1157
|
});
|
|
1104
1158
|
const challenge = Challenge.fromResponse(challengeResponse);
|
|
1105
|
-
const
|
|
1159
|
+
const challengeRequest = challenge.request;
|
|
1160
|
+
const amountRaw = challengeRequest.amount;
|
|
1106
1161
|
const amountUsdc = formatUnits(BigInt(amountRaw), 6);
|
|
1107
1162
|
if (maxPay && Number.parseFloat(amountUsdc) > Number.parseFloat(maxPay)) {
|
|
1108
1163
|
throw new Error(
|
|
1109
1164
|
`Payment of ${amountUsdc} USDC exceeds --max-pay ${maxPay}`
|
|
1110
1165
|
);
|
|
1111
1166
|
}
|
|
1112
|
-
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);
|
|
1113
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
|
+
}
|
|
1114
1178
|
await this.bridgeToTempo(BigInt(amountRaw));
|
|
1115
1179
|
}
|
|
1116
1180
|
const mppx = this.getMppxClient();
|
|
@@ -1124,24 +1188,42 @@ var PaymentService = class {
|
|
|
1124
1188
|
},
|
|
1125
1189
|
body: request.body
|
|
1126
1190
|
});
|
|
1191
|
+
let txHash = null;
|
|
1192
|
+
try {
|
|
1193
|
+
const receipt = Receipt.fromResponse(retryResponse);
|
|
1194
|
+
txHash = receipt.reference ?? null;
|
|
1195
|
+
} catch {
|
|
1196
|
+
}
|
|
1127
1197
|
return {
|
|
1128
1198
|
response: retryResponse,
|
|
1129
1199
|
protocol: "mpp",
|
|
1130
1200
|
chain: "tempo",
|
|
1131
|
-
txHash
|
|
1132
|
-
// MPP pull mode — server broadcasts, tx hash not returned in response
|
|
1201
|
+
txHash,
|
|
1133
1202
|
amount: amountUsdc,
|
|
1134
1203
|
asset: "USDC"
|
|
1135
1204
|
};
|
|
1136
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
|
+
};
|
|
1137
1218
|
getBalanceRaw = async (chain) => {
|
|
1138
1219
|
if (!this.account) return 0n;
|
|
1220
|
+
const { viemChain, token } = this.resolveChainConfig(chain);
|
|
1139
1221
|
const client = createPublicClient({
|
|
1140
|
-
chain:
|
|
1222
|
+
chain: viemChain,
|
|
1141
1223
|
transport: http()
|
|
1142
1224
|
});
|
|
1143
1225
|
const balance = await client.readContract({
|
|
1144
|
-
address:
|
|
1226
|
+
address: token,
|
|
1145
1227
|
abi: ERC20_BALANCE_ABI,
|
|
1146
1228
|
functionName: "balanceOf",
|
|
1147
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",
|