moltspay 2.0.0 → 2.4.0

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 (50) hide show
  1. package/.env.example +23 -0
  2. package/CHANGELOG.md +551 -0
  3. package/README.md +466 -8
  4. package/dist/cdp/index.js.map +1 -1
  5. package/dist/cdp/index.mjs.map +1 -1
  6. package/dist/chains/index.d.mts +69 -4
  7. package/dist/chains/index.d.ts +69 -4
  8. package/dist/chains/index.js +45 -1
  9. package/dist/chains/index.js.map +1 -1
  10. package/dist/chains/index.mjs +39 -1
  11. package/dist/chains/index.mjs.map +1 -1
  12. package/dist/cli/index.js +5444 -2126
  13. package/dist/cli/index.js.map +1 -1
  14. package/dist/cli/index.mjs +5457 -2133
  15. package/dist/cli/index.mjs.map +1 -1
  16. package/dist/client/index.d.mts +307 -1
  17. package/dist/client/index.d.ts +307 -1
  18. package/dist/client/index.js +639 -34
  19. package/dist/client/index.js.map +1 -1
  20. package/dist/client/index.mjs +656 -52
  21. package/dist/client/index.mjs.map +1 -1
  22. package/dist/client/web/index.mjs.map +1 -1
  23. package/dist/facilitators/index.d.mts +512 -10
  24. package/dist/facilitators/index.d.ts +512 -10
  25. package/dist/facilitators/index.js +925 -13
  26. package/dist/facilitators/index.js.map +1 -1
  27. package/dist/facilitators/index.mjs +906 -12
  28. package/dist/facilitators/index.mjs.map +1 -1
  29. package/dist/index.d.mts +2 -2
  30. package/dist/index.d.ts +2 -2
  31. package/dist/index.js +2843 -551
  32. package/dist/index.js.map +1 -1
  33. package/dist/index.mjs +2849 -558
  34. package/dist/index.mjs.map +1 -1
  35. package/dist/mcp/index.js +635 -32
  36. package/dist/mcp/index.js.map +1 -1
  37. package/dist/mcp/index.mjs +660 -57
  38. package/dist/mcp/index.mjs.map +1 -1
  39. package/dist/server/index.d.mts +252 -11
  40. package/dist/server/index.d.ts +252 -11
  41. package/dist/server/index.js +2049 -261
  42. package/dist/server/index.js.map +1 -1
  43. package/dist/server/index.mjs +2049 -261
  44. package/dist/server/index.mjs.map +1 -1
  45. package/dist/verify/index.js.map +1 -1
  46. package/dist/verify/index.mjs.map +1 -1
  47. package/dist/wallet/index.js.map +1 -1
  48. package/dist/wallet/index.mjs.map +1 -1
  49. package/package.json +14 -2
  50. package/schemas/moltspay.services.schema.json +127 -16
@@ -12,16 +12,17 @@ import { webcrypto } from "crypto";
12
12
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
13
13
 
14
14
  // src/mcp/server.ts
15
- import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
16
- import { join as join4 } from "path";
15
+ import { existsSync as existsSync3, readFileSync as readFileSync4 } from "fs";
16
+ import { join as join5 } from "path";
17
17
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
18
18
  import { z } from "zod";
19
19
 
20
20
  // src/client/node/index.ts
21
- import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, statSync, chmodSync } from "fs";
22
- import { homedir as homedir3 } from "os";
23
- import { join as join3 } from "path";
24
- import { Wallet as Wallet2, ethers as ethers2 } from "ethers";
21
+ import { existsSync as existsSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, statSync, chmodSync, readdirSync as readdirSync2 } from "fs";
22
+ import { homedir as homedir4 } from "os";
23
+ import { join as join4 } from "path";
24
+ import { randomUUID as randomUUID3 } from "crypto";
25
+ import { Wallet as Wallet2, ethers as ethers3 } from "ethers";
25
26
 
26
27
  // src/chains/index.ts
27
28
  var CHAINS = {
@@ -1092,7 +1093,7 @@ var AlipayClient = class {
1092
1093
  if (m) media.push(m);
1093
1094
  else opts.onLine?.(line);
1094
1095
  };
1095
- const intentSummary = opts.intentSummary?.trim() || `\u652F\u4ED8 ${requirement.amount ?? ""} ${requirement.asset ?? "CNY"}`.trim();
1096
+ const intentSummary = opts.intentSummary?.trim() || `Pay ${requirement.amount ?? ""} ${requirement.asset ?? "CNY"}`.trim();
1096
1097
  await timeStep("ensure-cli", flow, () => ensureCli(this.getVersion));
1097
1098
  const intentTtlMs = resolveIntentTtlMs();
1098
1099
  const intentKey = `${this.configDir}::${this.framework}`;
@@ -1177,10 +1178,205 @@ var AlipayClient = class {
1177
1178
  }
1178
1179
  };
1179
1180
 
1181
+ // src/client/wechat/index.ts
1182
+ import { randomUUID as randomUUID2 } from "crypto";
1183
+ import { mkdirSync as mkdirSync2, readdirSync, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
1184
+ import { homedir as homedir3 } from "os";
1185
+ import { join as join3 } from "path";
1186
+ var WECHAT_SCHEME = "wechatpay-native";
1187
+ var WECHAT_NETWORK = "wechat";
1188
+ var POLL_INTERVAL_MS2 = 3e3;
1189
+ var TIMEOUT_MS = 5 * 60 * 1e3;
1190
+ var WechatClient = class {
1191
+ configDir;
1192
+ sessionDir;
1193
+ now;
1194
+ constructor(options = {}) {
1195
+ this.configDir = options.configDir || join3(homedir3(), ".moltspay");
1196
+ this.sessionDir = join3(this.configDir, "wechat-sessions");
1197
+ this.now = options.now ?? Date.now;
1198
+ }
1199
+ async pay402(opts) {
1200
+ const session = this.start402({ ...opts });
1201
+ const result = await this.pollSession(session.paymentSessionId, {
1202
+ pollIntervalMs: opts.pollIntervalMs,
1203
+ timeoutMs: opts.timeoutMs,
1204
+ signal: opts.signal
1205
+ });
1206
+ if (result.status === "paid" || result.status === "completed") {
1207
+ return {
1208
+ body: result.resultBody ?? "",
1209
+ status: result.lastHttpStatus ?? 200
1210
+ };
1211
+ }
1212
+ throw new Error(result.lastError || `WeChat payment ended with status ${result.status}`);
1213
+ }
1214
+ /**
1215
+ * Start a recoverable WeChat payment session. This returns immediately after
1216
+ * persisting `out_trade_no`, QR payload, original request body, and context.
1217
+ */
1218
+ start402(opts) {
1219
+ const extra = opts.requirement.extra ?? {};
1220
+ const codeUrl = typeof extra.code_url === "string" ? extra.code_url : "";
1221
+ const outTradeNo = typeof extra.out_trade_no === "string" ? extra.out_trade_no : "";
1222
+ if (!codeUrl || !outTradeNo) {
1223
+ throw new Error(
1224
+ "WechatClient.pay402: wechatpay-native requirement is missing extra.code_url / extra.out_trade_no"
1225
+ );
1226
+ }
1227
+ const now = new Date(this.now());
1228
+ const timeoutMs = opts.timeoutMs ?? (opts.requirement.maxTimeoutSeconds ?? TIMEOUT_MS / 1e3) * 1e3;
1229
+ const session = {
1230
+ paymentSessionId: opts.paymentSessionId ?? `mpay_sess_${randomUUID2()}`,
1231
+ status: "pending",
1232
+ resourceUrl: opts.resourceUrl,
1233
+ method: opts.method ?? "POST",
1234
+ data: opts.data,
1235
+ requirement: opts.requirement,
1236
+ codeUrl,
1237
+ outTradeNo,
1238
+ createdAt: now.toISOString(),
1239
+ updatedAt: now.toISOString(),
1240
+ expiresAt: new Date(this.now() + timeoutMs).toISOString(),
1241
+ context: opts.context
1242
+ };
1243
+ this.saveSession(session);
1244
+ opts.onPaymentPending?.({ codeUrl, outTradeNo });
1245
+ return session;
1246
+ }
1247
+ /**
1248
+ * Query a persisted session once by replaying the original request with the
1249
+ * stored `out_trade_no` proof. 200 means paid + fulfilled; 402 means pending.
1250
+ */
1251
+ async status(identifier) {
1252
+ const session = this.loadSession(identifier);
1253
+ if (session.status === "cancelled" || session.status === "completed") return session;
1254
+ if (this.now() >= new Date(session.expiresAt).getTime()) {
1255
+ return this.updateSession(session, {
1256
+ status: "expired",
1257
+ lastError: `WeChat payment expired at ${session.expiresAt}`
1258
+ });
1259
+ }
1260
+ const xPayment = this.buildPaymentHeader(session);
1261
+ const res = await fetch(session.resourceUrl, {
1262
+ method: session.method,
1263
+ headers: { "Content-Type": "application/json", "X-Payment": xPayment },
1264
+ body: session.method === "POST" ? session.data : void 0
1265
+ });
1266
+ const text = await res.text().catch(() => "");
1267
+ if (res.status === 200) {
1268
+ return this.updateSession(session, {
1269
+ status: "completed",
1270
+ lastHttpStatus: res.status,
1271
+ lastError: void 0,
1272
+ resultBody: text
1273
+ });
1274
+ }
1275
+ if (res.status === 402) {
1276
+ return this.updateSession(session, {
1277
+ status: "pending",
1278
+ lastHttpStatus: res.status,
1279
+ lastError: text.slice(0, 500) || "wechat payment pending"
1280
+ });
1281
+ }
1282
+ return this.updateSession(session, {
1283
+ status: "failed",
1284
+ lastHttpStatus: res.status,
1285
+ lastError: `WeChat /execute returned HTTP ${res.status}: ${text.slice(0, 500)}`
1286
+ });
1287
+ }
1288
+ /** Poll a session until it completes, expires, fails, or is aborted. */
1289
+ async pollSession(identifier, opts = {}) {
1290
+ const first = this.loadSession(identifier);
1291
+ const deadline = Math.min(
1292
+ new Date(first.expiresAt).getTime(),
1293
+ this.now() + (opts.timeoutMs ?? TIMEOUT_MS)
1294
+ );
1295
+ const interval = opts.pollIntervalMs ?? POLL_INTERVAL_MS2;
1296
+ let session = first;
1297
+ while (this.now() < deadline) {
1298
+ if (opts.signal?.aborted) throw new Error("WeChat payment aborted");
1299
+ session = await this.status(session.paymentSessionId);
1300
+ if (session.status !== "pending") return session;
1301
+ await new Promise((r) => setTimeout(r, interval));
1302
+ }
1303
+ return this.updateSession(session, {
1304
+ status: "expired",
1305
+ lastError: `WeChat payment timed out after ${Math.round((opts.timeoutMs ?? TIMEOUT_MS) / 1e3)}s (out_trade_no=${session.outTradeNo}, last status ${session.lastHttpStatus ?? 0})`
1306
+ });
1307
+ }
1308
+ /** `fulfill` is an idempotent status check: paid sessions return stored body. */
1309
+ async fulfill(identifier) {
1310
+ const session = this.loadSession(identifier);
1311
+ if (session.status === "completed") return session;
1312
+ return this.status(identifier);
1313
+ }
1314
+ /** Mark a local session cancelled. Closing the merchant order is server-side. */
1315
+ cancel(identifier) {
1316
+ const session = this.loadSession(identifier);
1317
+ return this.updateSession(session, {
1318
+ status: "cancelled",
1319
+ lastError: "cancelled locally"
1320
+ });
1321
+ }
1322
+ listSessions() {
1323
+ this.ensureSessionDir();
1324
+ return readdirSync(this.sessionDir).filter((name) => name.endsWith(".json")).map((name) => JSON.parse(readFileSync2(join3(this.sessionDir, name), "utf-8"))).sort((a, b) => b.createdAt.localeCompare(a.createdAt));
1325
+ }
1326
+ buildPaymentHeader(session) {
1327
+ const proof = {
1328
+ x402Version: 2,
1329
+ scheme: WECHAT_SCHEME,
1330
+ network: WECHAT_NETWORK,
1331
+ accepted: {
1332
+ scheme: WECHAT_SCHEME,
1333
+ network: WECHAT_NETWORK,
1334
+ extra: { out_trade_no: session.outTradeNo }
1335
+ },
1336
+ payload: { out_trade_no: session.outTradeNo }
1337
+ };
1338
+ return Buffer.from(JSON.stringify(proof)).toString("base64");
1339
+ }
1340
+ loadSession(identifier) {
1341
+ this.ensureSessionDir();
1342
+ const direct = join3(this.sessionDir, `${identifier}.json`);
1343
+ try {
1344
+ return JSON.parse(readFileSync2(direct, "utf-8"));
1345
+ } catch {
1346
+ const byTradeNo = this.listSessions().find((s) => s.outTradeNo === identifier);
1347
+ if (byTradeNo) return byTradeNo;
1348
+ throw new Error(`WeChat payment session not found: ${identifier}`);
1349
+ }
1350
+ }
1351
+ saveSession(session) {
1352
+ this.ensureSessionDir();
1353
+ writeFileSync2(
1354
+ join3(this.sessionDir, `${session.paymentSessionId}.json`),
1355
+ JSON.stringify(session, null, 2)
1356
+ );
1357
+ }
1358
+ updateSession(session, updates) {
1359
+ const next = {
1360
+ ...session,
1361
+ ...updates,
1362
+ updatedAt: new Date(this.now()).toISOString()
1363
+ };
1364
+ this.saveSession(next);
1365
+ return next;
1366
+ }
1367
+ ensureSessionDir() {
1368
+ mkdirSync2(this.sessionDir, { recursive: true });
1369
+ }
1370
+ };
1371
+
1180
1372
  // src/client/alipay/router.ts
1181
1373
  var ALIPAY_RAIL = "alipay";
1374
+ var WECHAT_RAIL = "wechat";
1375
+ var BALANCE_RAIL = "balance";
1182
1376
  function railOf(req) {
1183
1377
  if (req.scheme === "alipay-aipay" || req.network === ALIPAY_RAIL) return ALIPAY_RAIL;
1378
+ if (req.scheme === "wechatpay-native" || req.network === WECHAT_RAIL) return WECHAT_RAIL;
1379
+ if (req.scheme === BALANCE_RAIL || req.network === BALANCE_RAIL) return BALANCE_RAIL;
1184
1380
  return networkToChainName(req.network) ?? req.network;
1185
1381
  }
1186
1382
  function findRail(accepts, rail) {
@@ -1220,6 +1416,14 @@ function selectRail(input) {
1220
1416
  return { rail: railOf(serverAccepts[0]), requirement: serverAccepts[0] };
1221
1417
  }
1222
1418
 
1419
+ // src/facilitators/balance/auth.ts
1420
+ import { ethers as ethers2 } from "ethers";
1421
+ var BALANCE_AUTH_DOMAIN = "moltspay-balance-auth:v1";
1422
+ var BALANCE_AUTH_MAX_SKEW_MS = 5 * 60 * 1e3;
1423
+ function buildDeductMessage(f) {
1424
+ return [BALANCE_AUTH_DOMAIN, "balance-deduct", f.buyerId, f.requestId, f.service, String(f.timestamp)].join("\n");
1425
+ }
1426
+
1223
1427
  // src/client/node/index.ts
1224
1428
  var DEFAULT_CONFIG = {
1225
1429
  chain: "base",
@@ -1239,7 +1443,7 @@ var MoltsPayClient = class {
1239
1443
  railPreference;
1240
1444
  alipaySessionId;
1241
1445
  constructor(options = {}) {
1242
- this.configDir = options.configDir || join3(homedir3(), ".moltspay");
1446
+ this.configDir = options.configDir || join4(homedir4(), ".moltspay");
1243
1447
  this.config = this.loadConfig();
1244
1448
  this.railPreference = options.railPreference ?? this.config.railPreference;
1245
1449
  this.alipaySessionId = options.alipaySessionId;
@@ -1323,6 +1527,12 @@ var MoltsPayClient = class {
1323
1527
  if (options.rail === ALIPAY_RAIL) {
1324
1528
  return this.payViaAlipay(serverUrl, service, params, options);
1325
1529
  }
1530
+ if (options.rail === WECHAT_RAIL) {
1531
+ return this.payViaWechat(serverUrl, service, params, options);
1532
+ }
1533
+ if (options.rail === BALANCE_RAIL) {
1534
+ return this.payViaBalance(serverUrl, service, params, options);
1535
+ }
1326
1536
  if (!this.wallet || !this.walletData) {
1327
1537
  throw new Error("Client not initialized. Run: npx moltspay init");
1328
1538
  }
@@ -1584,6 +1794,399 @@ Please specify: --chain <chain_name>`
1584
1794
  return { body: result.body, payment: result.payment, media: result.media };
1585
1795
  }
1586
1796
  }
1797
+ /**
1798
+ * Pay for a service over the WeChat Pay Native fiat rail (2.1.0).
1799
+ *
1800
+ * Mirrors {@link payViaAlipay} and needs no EVM wallet. The server placed the
1801
+ * Native order when it built the 402, so its `wechatpay-native` accepts[]
1802
+ * entry already carries `extra.code_url` + `extra.out_trade_no`. Flow: hit the
1803
+ * resource to get the 402, confirm the server offers the wechat rail, surface
1804
+ * the code_url (caller renders a QR), then poll the resource with the
1805
+ * out_trade_no proof until the server verifies the order paid and delivers.
1806
+ */
1807
+ async payViaWechat(serverUrl, service, params, options) {
1808
+ const session = await this.startWechatPayment(serverUrl, service, params, options);
1809
+ const wechat = new WechatClient({ configDir: this.configDir });
1810
+ const result = await wechat.pollSession(session.paymentSessionId, {
1811
+ timeoutMs: options.timeoutMs,
1812
+ signal: options.signal
1813
+ });
1814
+ if (result.status !== "completed") {
1815
+ throw new Error(result.lastError || `WeChat payment ended with status ${result.status}`);
1816
+ }
1817
+ try {
1818
+ const json = JSON.parse(result.resultBody ?? "");
1819
+ return json.result ?? json;
1820
+ } catch {
1821
+ return { body: result.resultBody ?? "" };
1822
+ }
1823
+ }
1824
+ /**
1825
+ * The ethers wallet used to sign balance-rail deductions: the client's EVM
1826
+ * wallet if it has one, else a per-configDir identity key persisted at
1827
+ * `<configDir>/balance-identity.key` (0600) so a balance-only client works
1828
+ * without a full crypto wallet. Under `agent 统一代付`, one key spends every
1829
+ * account the agent tops up.
1830
+ */
1831
+ balanceSigner() {
1832
+ if (this.wallet) return this.wallet;
1833
+ const p = join4(this.configDir, "balance-identity.key");
1834
+ let pk;
1835
+ if (existsSync2(p)) {
1836
+ pk = readFileSync3(p, "utf-8").trim();
1837
+ } else {
1838
+ mkdirSync3(this.configDir, { recursive: true });
1839
+ pk = Wallet2.createRandom().privateKey;
1840
+ writeFileSync3(p, pk, { mode: 384 });
1841
+ }
1842
+ return new Wallet2(pk);
1843
+ }
1844
+ /** The balance-rail spending signer address (lowercase 0x…). Stable per
1845
+ * configDir; this is the identity the server TOFU-binds and later verifies. */
1846
+ getBalanceSignerAddress() {
1847
+ return this.balanceSigner().address.toLowerCase();
1848
+ }
1849
+ /** The buyer id for the balance rail: explicit option > persisted config. */
1850
+ resolveBuyerId(explicit) {
1851
+ const buyerId = explicit ?? this.config.buyerId;
1852
+ if (!buyerId) {
1853
+ throw new Error(
1854
+ "Balance rail needs a buyer id. Pass { buyerId } or persist one with setBuyerId()."
1855
+ );
1856
+ }
1857
+ return buyerId;
1858
+ }
1859
+ /**
1860
+ * Pay via the custodial balance rail (2.2.0, password-free).
1861
+ *
1862
+ * No wallet, no QR: the request carries `{buyer_id, request_id}` in the
1863
+ * X-Payment payload and the server deducts the prepaid balance atomically
1864
+ * before running the skill. The client-generated `request_id` makes the
1865
+ * charge idempotent — a network retry can never double-deduct.
1866
+ */
1867
+ async payViaBalance(serverUrl, service, params, options) {
1868
+ const buyerId = this.resolveBuyerId(options.buyerId);
1869
+ let attempt = await this.balanceDeduct(serverUrl, service, params, options, buyerId);
1870
+ if (attempt.ok) return attempt.result;
1871
+ const fundable = attempt.status === 402 && (attempt.code === "buyer_not_found" || attempt.code === "insufficient_balance" || /insufficient balance|unknown buyer|top up first/i.test(attempt.error || ""));
1872
+ if (!fundable || options.autoTopup === false) {
1873
+ throw new Error(attempt.error || `Balance payment failed with HTTP ${attempt.status}`);
1874
+ }
1875
+ if (options.topupMode === "manual") {
1876
+ const order = await this.createBalanceTopupOrder(serverUrl, {
1877
+ pack: options.topupPack,
1878
+ buyerId,
1879
+ context: { service }
1880
+ });
1881
+ options.onTopupRequired?.(order.pack, order.codeUrl);
1882
+ return {
1883
+ status: "topup_required",
1884
+ out_trade_no: order.outTradeNo,
1885
+ code_url: order.codeUrl,
1886
+ pack: order.pack,
1887
+ server_url: serverUrl
1888
+ };
1889
+ }
1890
+ const credited = await this.topupBalancePack(serverUrl, {
1891
+ pack: options.topupPack,
1892
+ buyerId,
1893
+ pollIntervalMs: options.topupPollIntervalMs,
1894
+ signal: options.signal,
1895
+ onCodeUrl: (pack, codeUrl) => options.onTopupRequired?.(pack, codeUrl)
1896
+ });
1897
+ options.onTopupCredited?.(credited.balance);
1898
+ attempt = await this.balanceDeduct(serverUrl, service, params, options, buyerId);
1899
+ if (attempt.ok) return attempt.result;
1900
+ throw new Error(attempt.error || "Balance payment failed after top-up");
1901
+ }
1902
+ /** One password-free deduct attempt. Never throws on an HTTP error; the
1903
+ * caller inspects `{ ok, status, error }` to decide whether to auto-fund. */
1904
+ async balanceDeduct(serverUrl, service, params, options, buyerId) {
1905
+ let executeUrl = `${serverUrl}/execute`;
1906
+ try {
1907
+ const services = await this.getServices(serverUrl);
1908
+ const svc = services.services?.find((s) => s.id === service);
1909
+ if (svc?.endpoint) executeUrl = `${serverUrl}${svc.endpoint}`;
1910
+ } catch {
1911
+ }
1912
+ const requestBody = options.rawData ? { service, ...params } : { service, params };
1913
+ const requestId = randomUUID3();
1914
+ const timestamp = Math.floor(Date.now() / 1e3);
1915
+ const signature = await this.balanceSigner().signMessage(
1916
+ buildDeductMessage({ buyerId, requestId, service, timestamp })
1917
+ );
1918
+ const xPayment = Buffer.from(JSON.stringify({
1919
+ x402Version: 2,
1920
+ scheme: BALANCE_RAIL,
1921
+ network: BALANCE_RAIL,
1922
+ payload: { buyer_id: buyerId, request_id: requestId, auth: { timestamp, signature } }
1923
+ })).toString("base64");
1924
+ const res = await fetch(executeUrl, {
1925
+ method: "POST",
1926
+ headers: { "Content-Type": "application/json", "X-Payment": xPayment },
1927
+ body: JSON.stringify(requestBody),
1928
+ signal: options.signal
1929
+ });
1930
+ const data = await res.json().catch(() => ({}));
1931
+ if (!res.ok) return { ok: false, status: res.status, error: data.error, code: data.code };
1932
+ return { ok: true, status: res.status, result: data.result ?? data };
1933
+ }
1934
+ /**
1935
+ * Non-blocking: POST /balance/topup/order, persist a recoverable session, and
1936
+ * return at once (no polling). Use with {@link confirmBalanceTopup} for
1937
+ * turn-based agents; the blocking {@link topupBalancePack} is built on this.
1938
+ */
1939
+ async createBalanceTopupOrder(serverUrl, opts = {}) {
1940
+ const id = this.resolveBuyerId(opts.buyerId);
1941
+ const orderRes = await fetch(`${serverUrl}/balance/topup/order`, {
1942
+ method: "POST",
1943
+ headers: { "Content-Type": "application/json" },
1944
+ // Carry the spending signer so the server binds it to the account on
1945
+ // confirm (topup-time binding). The same key signs deductions later.
1946
+ body: JSON.stringify({ buyer_id: id, pack: opts.pack, signer_address: this.getBalanceSignerAddress() })
1947
+ });
1948
+ const order = await orderRes.json().catch(() => ({}));
1949
+ if (!orderRes.ok) throw new Error(order.error || `Top-up order failed with HTTP ${orderRes.status}`);
1950
+ const maxTimeoutSeconds = order.max_timeout_seconds ?? 300;
1951
+ const now = Date.now();
1952
+ this.saveBalanceTopupSession({
1953
+ out_trade_no: order.out_trade_no,
1954
+ buyer_id: id,
1955
+ pack: order.pack,
1956
+ server_url: serverUrl,
1957
+ code_url: order.code_url,
1958
+ status: "pending",
1959
+ created_at: new Date(now).toISOString(),
1960
+ expires_at: new Date(now + maxTimeoutSeconds * 1e3).toISOString(),
1961
+ context: opts.context
1962
+ });
1963
+ return { outTradeNo: order.out_trade_no, codeUrl: order.code_url, pack: order.pack, maxTimeoutSeconds };
1964
+ }
1965
+ /**
1966
+ * One-shot: POST /balance/topup/confirm for a single order. No polling.
1967
+ * Updates the persisted session on credit. `serverUrl` defaults to the one
1968
+ * recorded in the session (recover by out_trade_no alone).
1969
+ */
1970
+ async confirmBalanceTopup(outTradeNo, opts = {}) {
1971
+ const session = this.getBalanceTopupSession(outTradeNo);
1972
+ const serverUrl = opts.serverUrl || session?.server_url;
1973
+ if (!serverUrl) {
1974
+ return { credited: false, reason: `No server URL for ${outTradeNo}: pass serverUrl or run topup-order first` };
1975
+ }
1976
+ const confRes = await fetch(`${serverUrl}/balance/topup/confirm`, {
1977
+ method: "POST",
1978
+ headers: { "Content-Type": "application/json" },
1979
+ body: JSON.stringify({ out_trade_no: outTradeNo })
1980
+ });
1981
+ const conf = await confRes.json().catch(() => ({}));
1982
+ if (!confRes.ok) return { credited: false, reason: conf.error || `Confirm failed with HTTP ${confRes.status}` };
1983
+ if (conf.credited) {
1984
+ if (session) {
1985
+ session.status = "credited";
1986
+ session.tx_id = conf.tx_id;
1987
+ session.balance = conf.balance;
1988
+ this.saveBalanceTopupSession(session);
1989
+ }
1990
+ return { credited: true, balance: conf.balance, txId: conf.tx_id };
1991
+ }
1992
+ return { credited: false, pending: !!conf.pending, reason: conf.reason };
1993
+ }
1994
+ /**
1995
+ * Blocking terminal wrapper: create the order then poll confirm until the
1996
+ * scan is credited (or the order expires). Built on
1997
+ * {@link createBalanceTopupOrder} + {@link confirmBalanceTopup}.
1998
+ */
1999
+ async topupBalancePack(serverUrl, opts = {}) {
2000
+ const order = await this.createBalanceTopupOrder(serverUrl, { pack: opts.pack, buyerId: opts.buyerId });
2001
+ opts.onCodeUrl?.(order.pack, order.codeUrl);
2002
+ const interval = opts.pollIntervalMs ?? 2e3;
2003
+ const deadline = Date.now() + order.maxTimeoutSeconds * 1e3;
2004
+ while (Date.now() < deadline) {
2005
+ if (opts.signal?.aborted) throw new Error("Top-up aborted");
2006
+ const conf = await this.confirmBalanceTopup(order.outTradeNo, { serverUrl });
2007
+ if (conf.credited) return { balance: conf.balance, outTradeNo: order.outTradeNo, txId: conf.txId };
2008
+ await this.sleep(interval, opts.signal);
2009
+ }
2010
+ throw new Error("Top-up timed out before the payment was confirmed");
2011
+ }
2012
+ // --- Recoverable balance top-up sessions (<configDir>/balance-topup-sessions) ---
2013
+ balanceTopupSessionDir() {
2014
+ return join4(this.configDir, "balance-topup-sessions");
2015
+ }
2016
+ saveBalanceTopupSession(session) {
2017
+ const dir = this.balanceTopupSessionDir();
2018
+ mkdirSync3(dir, { recursive: true });
2019
+ writeFileSync3(join4(dir, `${session.out_trade_no}.json`), JSON.stringify(session, null, 2));
2020
+ }
2021
+ /** Read a persisted top-up session by out_trade_no, or null. */
2022
+ getBalanceTopupSession(outTradeNo) {
2023
+ const p = join4(this.balanceTopupSessionDir(), `${outTradeNo}.json`);
2024
+ if (!existsSync2(p)) return null;
2025
+ try {
2026
+ return JSON.parse(readFileSync3(p, "utf-8"));
2027
+ } catch {
2028
+ return null;
2029
+ }
2030
+ }
2031
+ /** List persisted top-up sessions, newest first. */
2032
+ listBalanceTopupSessions() {
2033
+ const dir = this.balanceTopupSessionDir();
2034
+ if (!existsSync2(dir)) return [];
2035
+ return readdirSync2(dir).filter((f) => f.endsWith(".json")).map((f) => {
2036
+ try {
2037
+ return JSON.parse(readFileSync3(join4(dir, f), "utf-8"));
2038
+ } catch {
2039
+ return null;
2040
+ }
2041
+ }).filter((s) => s !== null).sort((a, b) => (b.created_at || "").localeCompare(a.created_at || ""));
2042
+ }
2043
+ /** Abortable sleep used by the top-up poll loop. */
2044
+ sleep(ms, signal) {
2045
+ return new Promise((resolve, reject) => {
2046
+ if (signal?.aborted) return reject(new Error("aborted"));
2047
+ const t = setTimeout(resolve, ms);
2048
+ signal?.addEventListener("abort", () => {
2049
+ clearTimeout(t);
2050
+ reject(new Error("aborted"));
2051
+ }, { once: true });
2052
+ });
2053
+ }
2054
+ /** Persist the buyer id used by the balance rail (bearer semantics). */
2055
+ setBuyerId(buyerId) {
2056
+ this.config.buyerId = buyerId;
2057
+ this.saveConfig();
2058
+ }
2059
+ /** GET /balance — custodial balance, limits, and today's spend for a buyer. */
2060
+ async getBuyerBalance(serverUrl, buyerId) {
2061
+ const id = this.resolveBuyerId(buyerId);
2062
+ const res = await fetch(`${serverUrl}/balance?buyer_id=${encodeURIComponent(id)}`);
2063
+ const data = await res.json().catch(() => ({}));
2064
+ if (!res.ok) throw new Error(data.error || `Balance query failed with HTTP ${res.status}`);
2065
+ return data;
2066
+ }
2067
+ /**
2068
+ * POST /balance/topup — report an externally settled payment (on-chain
2069
+ * tx hash / Alipay trade_no / WeChat out_trade_no) so the server verifies
2070
+ * and credits the ledger. Idempotent per reference.
2071
+ */
2072
+ async topupBalance(serverUrl, opts) {
2073
+ const id = this.resolveBuyerId(opts.buyerId);
2074
+ const res = await fetch(`${serverUrl}/balance/topup`, {
2075
+ method: "POST",
2076
+ headers: { "Content-Type": "application/json" },
2077
+ body: JSON.stringify({
2078
+ buyer_id: id,
2079
+ rail: opts.rail,
2080
+ amount: opts.amount,
2081
+ tx_hash: opts.txHash,
2082
+ chain: opts.chain,
2083
+ trade_no: opts.tradeNo,
2084
+ out_trade_no: opts.outTradeNo
2085
+ })
2086
+ });
2087
+ const data = await res.json().catch(() => ({}));
2088
+ if (!res.ok) throw new Error(data.error || `Top-up failed with HTTP ${res.status}`);
2089
+ return data;
2090
+ }
2091
+ /** GET /balance/transactions — ledger history for a buyer, newest first. */
2092
+ async listBalanceTransactions(serverUrl, opts = {}) {
2093
+ const id = this.resolveBuyerId(opts.buyerId);
2094
+ const qs = new URLSearchParams({ buyer_id: id });
2095
+ if (opts.limit) qs.set("limit", String(opts.limit));
2096
+ if (opts.offset) qs.set("offset", String(opts.offset));
2097
+ const res = await fetch(`${serverUrl}/balance/transactions?${qs}`);
2098
+ const data = await res.json().catch(() => ({}));
2099
+ if (!res.ok) throw new Error(data.error || `Transaction query failed with HTTP ${res.status}`);
2100
+ return data;
2101
+ }
2102
+ /**
2103
+ * Start a recoverable WeChat Pay Native session and return immediately with
2104
+ * QR metadata. The SDK client persists enough context to poll/fulfill later.
2105
+ */
2106
+ async startWechatPayment(serverUrl, service, params, options = {}) {
2107
+ let executeUrl = `${serverUrl}/execute`;
2108
+ try {
2109
+ const services = await this.getServices(serverUrl);
2110
+ const svc = services.services?.find((s) => s.id === service);
2111
+ if (svc?.endpoint) executeUrl = `${serverUrl}${svc.endpoint}`;
2112
+ } catch {
2113
+ }
2114
+ const requestBody = options.rawData ? { service, ...params } : { service, params };
2115
+ const bodyJson = JSON.stringify(requestBody);
2116
+ const res = await fetch(executeUrl, {
2117
+ method: "POST",
2118
+ headers: { "Content-Type": "application/json" },
2119
+ body: bodyJson
2120
+ });
2121
+ if (res.status !== 402) {
2122
+ const data = await res.json().catch(() => ({}));
2123
+ if (res.ok && data.result) return data.result;
2124
+ throw new Error(data.error || `Expected 402, got ${res.status}`);
2125
+ }
2126
+ const header = res.headers.get(PAYMENT_REQUIRED_HEADER);
2127
+ if (!header) throw new Error("Missing x-payment-required header on 402");
2128
+ const parsed = JSON.parse(Buffer.from(header, "base64").toString("utf-8"));
2129
+ const accepts = Array.isArray(parsed) ? parsed : parsed.accepts ?? [parsed];
2130
+ const { requirement } = selectRail({
2131
+ serverAccepts: accepts,
2132
+ explicitRail: WECHAT_RAIL,
2133
+ preference: this.railPreference,
2134
+ availability: { evmReady: this.isInitialized }
2135
+ });
2136
+ const wechat = new WechatClient({ configDir: this.configDir });
2137
+ const session = wechat.start402({
2138
+ resourceUrl: executeUrl,
2139
+ requirement,
2140
+ method: "POST",
2141
+ data: bodyJson,
2142
+ onPaymentPending: options.onPaymentPending ? (info) => options.onPaymentPending({ paymentUrl: info.codeUrl, tradeNo: info.outTradeNo }) : void 0,
2143
+ timeoutMs: options.timeoutMs,
2144
+ signal: options.signal,
2145
+ context: {
2146
+ serverUrl,
2147
+ service,
2148
+ params,
2149
+ rawData: options.rawData ?? false,
2150
+ rail: WECHAT_RAIL
2151
+ }
2152
+ });
2153
+ if (options.autoPoll || options.onWechatPaymentCompleted || options.onWechatPaymentFailed) {
2154
+ void wechat.pollSession(session.paymentSessionId, {
2155
+ timeoutMs: options.timeoutMs,
2156
+ signal: options.signal
2157
+ }).then(async (finalSession) => {
2158
+ if (finalSession.status === "completed") {
2159
+ await options.onWechatPaymentCompleted?.(finalSession);
2160
+ } else {
2161
+ await options.onWechatPaymentFailed?.(finalSession);
2162
+ }
2163
+ }).catch(async (error) => {
2164
+ const failed = await wechat.fulfill(session.paymentSessionId).catch(() => ({
2165
+ ...session,
2166
+ status: "failed",
2167
+ lastError: error instanceof Error ? error.message : String(error)
2168
+ }));
2169
+ await options.onWechatPaymentFailed?.(failed);
2170
+ });
2171
+ }
2172
+ return session;
2173
+ }
2174
+ /** Query a persisted WeChat session once. */
2175
+ async getWechatPaymentStatus(identifier) {
2176
+ return new WechatClient({ configDir: this.configDir }).status(identifier);
2177
+ }
2178
+ /** Idempotently fulfill a paid WeChat session, returning stored or fetched result. */
2179
+ async fulfillWechatPayment(identifier) {
2180
+ return new WechatClient({ configDir: this.configDir }).fulfill(identifier);
2181
+ }
2182
+ /** Mark a local WeChat session as cancelled. */
2183
+ cancelWechatPayment(identifier) {
2184
+ return new WechatClient({ configDir: this.configDir }).cancel(identifier);
2185
+ }
2186
+ /** List persisted WeChat sessions, newest first. */
2187
+ listWechatPaymentSessions() {
2188
+ return new WechatClient({ configDir: this.configDir }).listSessions();
2189
+ }
1587
2190
  /**
1588
2191
  * Handle MPP (Machine Payments Protocol) payment flow
1589
2192
  * Called when pay() detects WWW-Authenticate header in 402 response
@@ -1679,14 +2282,14 @@ Please specify: --chain <chain_name>`
1679
2282
  async handleBNBPayment(executeUrl, service, params, paymentDetails, options = {}) {
1680
2283
  const { to, amount, token, chainName, chain, spender } = paymentDetails;
1681
2284
  const tokenConfig = chain.tokens[token];
1682
- const provider = new ethers2.JsonRpcProvider(chain.rpc);
2285
+ const provider = new ethers3.JsonRpcProvider(chain.rpc);
1683
2286
  const allowance = await this.checkAllowance(tokenConfig.address, spender, provider);
1684
2287
  const amountWeiCheck = BigInt(Math.floor(amount * 10 ** tokenConfig.decimals));
1685
2288
  if (allowance < amountWeiCheck) {
1686
2289
  const nativeBalance = await provider.getBalance(this.wallet.address);
1687
- const minGasBalance = ethers2.parseEther("0.0005");
2290
+ const minGasBalance = ethers3.parseEther("0.0005");
1688
2291
  if (nativeBalance < minGasBalance) {
1689
- const nativeBNB = parseFloat(ethers2.formatEther(nativeBalance)).toFixed(4);
2292
+ const nativeBNB = parseFloat(ethers3.formatEther(nativeBalance)).toFixed(4);
1690
2293
  const isTestnet = chainName === "bnb_testnet";
1691
2294
  if (isTestnet) {
1692
2295
  throw new Error(
@@ -1859,7 +2462,7 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
1859
2462
  * Check ERC20 allowance for a spender
1860
2463
  */
1861
2464
  async checkAllowance(tokenAddress, spender, provider) {
1862
- const contract = new ethers2.Contract(
2465
+ const contract = new ethers3.Contract(
1863
2466
  tokenAddress,
1864
2467
  ["function allowance(address owner, address spender) view returns (uint256)"],
1865
2468
  provider
@@ -1878,7 +2481,7 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
1878
2481
  async signEIP3009(to, amount, chain, token = "USDC", domainOverride) {
1879
2482
  const tokenConfig = chain.tokens[token];
1880
2483
  const value = BigInt(Math.floor(amount * 10 ** tokenConfig.decimals)).toString();
1881
- const nonce = ethers2.hexlify(ethers2.randomBytes(32));
2484
+ const nonce = ethers3.hexlify(ethers3.randomBytes(32));
1882
2485
  const tokenName = domainOverride?.name || tokenConfig.eip712Name || (token === "USDC" ? "USD Coin" : "Tether USD");
1883
2486
  const tokenVersion = domainOverride?.version || "2";
1884
2487
  const envelope = buildEIP3009TypedData({
@@ -1924,26 +2527,26 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
1924
2527
  }
1925
2528
  // --- Config & Wallet Management ---
1926
2529
  loadConfig() {
1927
- const configPath = join3(this.configDir, "config.json");
2530
+ const configPath = join4(this.configDir, "config.json");
1928
2531
  if (existsSync2(configPath)) {
1929
- const content = readFileSync2(configPath, "utf-8");
2532
+ const content = readFileSync3(configPath, "utf-8");
1930
2533
  return { ...DEFAULT_CONFIG, ...JSON.parse(content) };
1931
2534
  }
1932
2535
  return { ...DEFAULT_CONFIG };
1933
2536
  }
1934
2537
  saveConfig() {
1935
- mkdirSync2(this.configDir, { recursive: true });
1936
- const configPath = join3(this.configDir, "config.json");
1937
- writeFileSync2(configPath, JSON.stringify(this.config, null, 2));
2538
+ mkdirSync3(this.configDir, { recursive: true });
2539
+ const configPath = join4(this.configDir, "config.json");
2540
+ writeFileSync3(configPath, JSON.stringify(this.config, null, 2));
1938
2541
  }
1939
2542
  /**
1940
2543
  * Load spending data from disk
1941
2544
  */
1942
2545
  loadSpending() {
1943
- const spendingPath = join3(this.configDir, "spending.json");
2546
+ const spendingPath = join4(this.configDir, "spending.json");
1944
2547
  if (existsSync2(spendingPath)) {
1945
2548
  try {
1946
- const data = JSON.parse(readFileSync2(spendingPath, "utf-8"));
2549
+ const data = JSON.parse(readFileSync3(spendingPath, "utf-8"));
1947
2550
  const today = (/* @__PURE__ */ new Date()).setHours(0, 0, 0, 0);
1948
2551
  if (data.date && data.date === today) {
1949
2552
  this.todaySpending = data.amount || 0;
@@ -1962,17 +2565,17 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
1962
2565
  * Save spending data to disk
1963
2566
  */
1964
2567
  saveSpending() {
1965
- mkdirSync2(this.configDir, { recursive: true });
1966
- const spendingPath = join3(this.configDir, "spending.json");
2568
+ mkdirSync3(this.configDir, { recursive: true });
2569
+ const spendingPath = join4(this.configDir, "spending.json");
1967
2570
  const data = {
1968
2571
  date: this.lastSpendingReset || (/* @__PURE__ */ new Date()).setHours(0, 0, 0, 0),
1969
2572
  amount: this.todaySpending,
1970
2573
  updatedAt: Date.now()
1971
2574
  };
1972
- writeFileSync2(spendingPath, JSON.stringify(data, null, 2));
2575
+ writeFileSync3(spendingPath, JSON.stringify(data, null, 2));
1973
2576
  }
1974
2577
  loadWallet() {
1975
- const walletPath = join3(this.configDir, "wallet.json");
2578
+ const walletPath = join4(this.configDir, "wallet.json");
1976
2579
  if (existsSync2(walletPath)) {
1977
2580
  if (process.platform !== "win32") {
1978
2581
  try {
@@ -1986,7 +2589,7 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
1986
2589
  } catch {
1987
2590
  }
1988
2591
  }
1989
- const content = readFileSync2(walletPath, "utf-8");
2592
+ const content = readFileSync3(walletPath, "utf-8");
1990
2593
  return JSON.parse(content);
1991
2594
  }
1992
2595
  return null;
@@ -1995,15 +2598,15 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
1995
2598
  * Initialize a new wallet (called by CLI)
1996
2599
  */
1997
2600
  static init(configDir, options) {
1998
- mkdirSync2(configDir, { recursive: true });
2601
+ mkdirSync3(configDir, { recursive: true });
1999
2602
  const wallet = Wallet2.createRandom();
2000
2603
  const walletData = {
2001
2604
  address: wallet.address,
2002
2605
  privateKey: wallet.privateKey,
2003
2606
  createdAt: Date.now()
2004
2607
  };
2005
- const walletPath = join3(configDir, "wallet.json");
2006
- writeFileSync2(walletPath, JSON.stringify(walletData, null, 2), { mode: 384 });
2608
+ const walletPath = join4(configDir, "wallet.json");
2609
+ writeFileSync3(walletPath, JSON.stringify(walletData, null, 2), { mode: 384 });
2007
2610
  const config = {
2008
2611
  chain: options.chain,
2009
2612
  limits: {
@@ -2011,8 +2614,8 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
2011
2614
  maxPerDay: options.maxPerDay
2012
2615
  }
2013
2616
  };
2014
- const configPath = join3(configDir, "config.json");
2015
- writeFileSync2(configPath, JSON.stringify(config, null, 2));
2617
+ const configPath = join4(configDir, "config.json");
2618
+ writeFileSync3(configPath, JSON.stringify(config, null, 2));
2016
2619
  return { address: wallet.address, configDir };
2017
2620
  }
2018
2621
  /**
@@ -2028,17 +2631,17 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
2028
2631
  } catch {
2029
2632
  throw new Error(`Unknown chain: ${this.config.chain}`);
2030
2633
  }
2031
- const provider = new ethers2.JsonRpcProvider(chain.rpc);
2634
+ const provider = new ethers3.JsonRpcProvider(chain.rpc);
2032
2635
  const tokenAbi = ["function balanceOf(address) view returns (uint256)"];
2033
2636
  const [nativeBalance, usdcBalance, usdtBalance] = await Promise.all([
2034
2637
  provider.getBalance(this.wallet.address),
2035
- new ethers2.Contract(chain.tokens.USDC.address, tokenAbi, provider).balanceOf(this.wallet.address),
2036
- new ethers2.Contract(chain.tokens.USDT.address, tokenAbi, provider).balanceOf(this.wallet.address)
2638
+ new ethers3.Contract(chain.tokens.USDC.address, tokenAbi, provider).balanceOf(this.wallet.address),
2639
+ new ethers3.Contract(chain.tokens.USDT.address, tokenAbi, provider).balanceOf(this.wallet.address)
2037
2640
  ]);
2038
2641
  return {
2039
- usdc: parseFloat(ethers2.formatUnits(usdcBalance, chain.tokens.USDC.decimals)),
2040
- usdt: parseFloat(ethers2.formatUnits(usdtBalance, chain.tokens.USDT.decimals)),
2041
- native: parseFloat(ethers2.formatEther(nativeBalance))
2642
+ usdc: parseFloat(ethers3.formatUnits(usdcBalance, chain.tokens.USDC.decimals)),
2643
+ usdt: parseFloat(ethers3.formatUnits(usdtBalance, chain.tokens.USDT.decimals)),
2644
+ native: parseFloat(ethers3.formatEther(nativeBalance))
2042
2645
  };
2043
2646
  }
2044
2647
  /**
@@ -2061,38 +2664,38 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
2061
2664
  supportedChains.map(async (chainName) => {
2062
2665
  try {
2063
2666
  const chain = getChain(chainName);
2064
- const provider = new ethers2.JsonRpcProvider(chain.rpc);
2667
+ const provider = new ethers3.JsonRpcProvider(chain.rpc);
2065
2668
  if (chainName === "tempo_moderato") {
2066
2669
  const [nativeBalance, pathUSD, alphaUSD, betaUSD, thetaUSD] = await Promise.all([
2067
2670
  provider.getBalance(this.wallet.address),
2068
- new ethers2.Contract(tempoTokens.pathUSD, tokenAbi, provider).balanceOf(this.wallet.address),
2069
- new ethers2.Contract(tempoTokens.alphaUSD, tokenAbi, provider).balanceOf(this.wallet.address),
2070
- new ethers2.Contract(tempoTokens.betaUSD, tokenAbi, provider).balanceOf(this.wallet.address),
2071
- new ethers2.Contract(tempoTokens.thetaUSD, tokenAbi, provider).balanceOf(this.wallet.address)
2671
+ new ethers3.Contract(tempoTokens.pathUSD, tokenAbi, provider).balanceOf(this.wallet.address),
2672
+ new ethers3.Contract(tempoTokens.alphaUSD, tokenAbi, provider).balanceOf(this.wallet.address),
2673
+ new ethers3.Contract(tempoTokens.betaUSD, tokenAbi, provider).balanceOf(this.wallet.address),
2674
+ new ethers3.Contract(tempoTokens.thetaUSD, tokenAbi, provider).balanceOf(this.wallet.address)
2072
2675
  ]);
2073
2676
  results[chainName] = {
2074
- usdc: parseFloat(ethers2.formatUnits(pathUSD, 6)),
2677
+ usdc: parseFloat(ethers3.formatUnits(pathUSD, 6)),
2075
2678
  // pathUSD as default USDC
2076
- usdt: parseFloat(ethers2.formatUnits(alphaUSD, 6)),
2679
+ usdt: parseFloat(ethers3.formatUnits(alphaUSD, 6)),
2077
2680
  // alphaUSD as default USDT
2078
- native: parseFloat(ethers2.formatEther(nativeBalance)),
2681
+ native: parseFloat(ethers3.formatEther(nativeBalance)),
2079
2682
  tempo: {
2080
- pathUSD: parseFloat(ethers2.formatUnits(pathUSD, 6)),
2081
- alphaUSD: parseFloat(ethers2.formatUnits(alphaUSD, 6)),
2082
- betaUSD: parseFloat(ethers2.formatUnits(betaUSD, 6)),
2083
- thetaUSD: parseFloat(ethers2.formatUnits(thetaUSD, 6))
2683
+ pathUSD: parseFloat(ethers3.formatUnits(pathUSD, 6)),
2684
+ alphaUSD: parseFloat(ethers3.formatUnits(alphaUSD, 6)),
2685
+ betaUSD: parseFloat(ethers3.formatUnits(betaUSD, 6)),
2686
+ thetaUSD: parseFloat(ethers3.formatUnits(thetaUSD, 6))
2084
2687
  }
2085
2688
  };
2086
2689
  } else {
2087
2690
  const [nativeBalance, usdcBalance, usdtBalance] = await Promise.all([
2088
2691
  provider.getBalance(this.wallet.address),
2089
- new ethers2.Contract(chain.tokens.USDC.address, tokenAbi, provider).balanceOf(this.wallet.address),
2090
- new ethers2.Contract(chain.tokens.USDT.address, tokenAbi, provider).balanceOf(this.wallet.address)
2692
+ new ethers3.Contract(chain.tokens.USDC.address, tokenAbi, provider).balanceOf(this.wallet.address),
2693
+ new ethers3.Contract(chain.tokens.USDT.address, tokenAbi, provider).balanceOf(this.wallet.address)
2091
2694
  ]);
2092
2695
  results[chainName] = {
2093
- usdc: parseFloat(ethers2.formatUnits(usdcBalance, chain.tokens.USDC.decimals)),
2094
- usdt: parseFloat(ethers2.formatUnits(usdtBalance, chain.tokens.USDT.decimals)),
2095
- native: parseFloat(ethers2.formatEther(nativeBalance))
2696
+ usdc: parseFloat(ethers3.formatUnits(usdcBalance, chain.tokens.USDC.decimals)),
2697
+ usdt: parseFloat(ethers3.formatUnits(usdtBalance, chain.tokens.USDT.decimals)),
2698
+ native: parseFloat(ethers3.formatEther(nativeBalance))
2096
2699
  };
2097
2700
  }
2098
2701
  } catch (err2) {
@@ -2250,14 +2853,14 @@ function wrap(fn) {
2250
2853
  }
2251
2854
  function getPackageVersion() {
2252
2855
  const candidates = [
2253
- join4(__dirname, "../../package.json"),
2254
- join4(__dirname, "../package.json"),
2255
- join4(process.cwd(), "node_modules/moltspay/package.json")
2856
+ join5(__dirname, "../../package.json"),
2857
+ join5(__dirname, "../package.json"),
2858
+ join5(process.cwd(), "node_modules/moltspay/package.json")
2256
2859
  ];
2257
2860
  for (const loc of candidates) {
2258
2861
  try {
2259
2862
  if (existsSync3(loc)) {
2260
- const pkg = JSON.parse(readFileSync3(loc, "utf-8"));
2863
+ const pkg = JSON.parse(readFileSync4(loc, "utf-8"));
2261
2864
  if (pkg.name === "moltspay") return pkg.version;
2262
2865
  }
2263
2866
  } catch {