@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.
Files changed (2) hide show
  1. package/dist/index.js +129 -47
  2. 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.7",
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
- payX402 = async (url, request, raw, maxPay) => {
1057
- const headerValue = Buffer.from(JSON.stringify(raw)).toString("base64");
1058
- const paymentRequired = decodePaymentRequiredHeader(headerValue);
1059
- const requirement = paymentRequired.accepts[0];
1060
- if (!requirement) {
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
- const amountUsdc = formatUnits(BigInt(requirement.amount), 6);
1064
- if (maxPay && Number.parseFloat(amountUsdc) > Number.parseFloat(maxPay)) {
1065
- throw new Error(
1066
- `Payment of ${amountUsdc} USDC exceeds --max-pay ${maxPay}`
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
- const paymentPayload = await client.createPaymentPayload(paymentRequired);
1071
- const signatureHeader = encodePaymentSignatureHeader(paymentPayload);
1072
- const retryResponse = await fetch(url, {
1073
- method: request.method,
1074
- headers: {
1075
- ...request.headers,
1076
- "PAYMENT-SIGNATURE": signatureHeader
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
- return {
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 amountRaw = challenge.request.amount;
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 tempoBalance = await this.getBalanceRaw("tempo");
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: null,
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: chain === "base" ? base : tempoChain,
1222
+ chain: viemChain,
1141
1223
  transport: http()
1142
1224
  });
1143
1225
  const balance = await client.readContract({
1144
- address: chain === "base" ? USDC_BASE : USDC_TEMPO,
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.7",
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",