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
@@ -1,8 +1,9 @@
1
1
  // src/client/node/index.ts
2
- import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, statSync, chmodSync } from "fs";
3
- import { homedir as homedir3 } from "os";
4
- import { join as join3 } from "path";
5
- import { Wallet as Wallet2, ethers as ethers2 } from "ethers";
2
+ import { existsSync as existsSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, statSync, chmodSync, readdirSync as readdirSync2 } from "fs";
3
+ import { homedir as homedir4 } from "os";
4
+ import { join as join4 } from "path";
5
+ import { randomUUID as randomUUID3 } from "crypto";
6
+ import { Wallet as Wallet2, ethers as ethers3 } from "ethers";
6
7
 
7
8
  // src/chains/index.ts
8
9
  var CHAINS = {
@@ -1073,7 +1074,7 @@ var AlipayClient = class {
1073
1074
  if (m) media.push(m);
1074
1075
  else opts.onLine?.(line);
1075
1076
  };
1076
- const intentSummary = opts.intentSummary?.trim() || `\u652F\u4ED8 ${requirement.amount ?? ""} ${requirement.asset ?? "CNY"}`.trim();
1077
+ const intentSummary = opts.intentSummary?.trim() || `Pay ${requirement.amount ?? ""} ${requirement.asset ?? "CNY"}`.trim();
1077
1078
  await timeStep("ensure-cli", flow, () => ensureCli(this.getVersion));
1078
1079
  const intentTtlMs = resolveIntentTtlMs();
1079
1080
  const intentKey = `${this.configDir}::${this.framework}`;
@@ -1158,10 +1159,205 @@ var AlipayClient = class {
1158
1159
  }
1159
1160
  };
1160
1161
 
1162
+ // src/client/wechat/index.ts
1163
+ import { randomUUID as randomUUID2 } from "crypto";
1164
+ import { mkdirSync as mkdirSync2, readdirSync, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
1165
+ import { homedir as homedir3 } from "os";
1166
+ import { join as join3 } from "path";
1167
+ var WECHAT_SCHEME = "wechatpay-native";
1168
+ var WECHAT_NETWORK = "wechat";
1169
+ var POLL_INTERVAL_MS2 = 3e3;
1170
+ var TIMEOUT_MS = 5 * 60 * 1e3;
1171
+ var WechatClient = class {
1172
+ configDir;
1173
+ sessionDir;
1174
+ now;
1175
+ constructor(options = {}) {
1176
+ this.configDir = options.configDir || join3(homedir3(), ".moltspay");
1177
+ this.sessionDir = join3(this.configDir, "wechat-sessions");
1178
+ this.now = options.now ?? Date.now;
1179
+ }
1180
+ async pay402(opts) {
1181
+ const session = this.start402({ ...opts });
1182
+ const result = await this.pollSession(session.paymentSessionId, {
1183
+ pollIntervalMs: opts.pollIntervalMs,
1184
+ timeoutMs: opts.timeoutMs,
1185
+ signal: opts.signal
1186
+ });
1187
+ if (result.status === "paid" || result.status === "completed") {
1188
+ return {
1189
+ body: result.resultBody ?? "",
1190
+ status: result.lastHttpStatus ?? 200
1191
+ };
1192
+ }
1193
+ throw new Error(result.lastError || `WeChat payment ended with status ${result.status}`);
1194
+ }
1195
+ /**
1196
+ * Start a recoverable WeChat payment session. This returns immediately after
1197
+ * persisting `out_trade_no`, QR payload, original request body, and context.
1198
+ */
1199
+ start402(opts) {
1200
+ const extra = opts.requirement.extra ?? {};
1201
+ const codeUrl = typeof extra.code_url === "string" ? extra.code_url : "";
1202
+ const outTradeNo = typeof extra.out_trade_no === "string" ? extra.out_trade_no : "";
1203
+ if (!codeUrl || !outTradeNo) {
1204
+ throw new Error(
1205
+ "WechatClient.pay402: wechatpay-native requirement is missing extra.code_url / extra.out_trade_no"
1206
+ );
1207
+ }
1208
+ const now = new Date(this.now());
1209
+ const timeoutMs = opts.timeoutMs ?? (opts.requirement.maxTimeoutSeconds ?? TIMEOUT_MS / 1e3) * 1e3;
1210
+ const session = {
1211
+ paymentSessionId: opts.paymentSessionId ?? `mpay_sess_${randomUUID2()}`,
1212
+ status: "pending",
1213
+ resourceUrl: opts.resourceUrl,
1214
+ method: opts.method ?? "POST",
1215
+ data: opts.data,
1216
+ requirement: opts.requirement,
1217
+ codeUrl,
1218
+ outTradeNo,
1219
+ createdAt: now.toISOString(),
1220
+ updatedAt: now.toISOString(),
1221
+ expiresAt: new Date(this.now() + timeoutMs).toISOString(),
1222
+ context: opts.context
1223
+ };
1224
+ this.saveSession(session);
1225
+ opts.onPaymentPending?.({ codeUrl, outTradeNo });
1226
+ return session;
1227
+ }
1228
+ /**
1229
+ * Query a persisted session once by replaying the original request with the
1230
+ * stored `out_trade_no` proof. 200 means paid + fulfilled; 402 means pending.
1231
+ */
1232
+ async status(identifier) {
1233
+ const session = this.loadSession(identifier);
1234
+ if (session.status === "cancelled" || session.status === "completed") return session;
1235
+ if (this.now() >= new Date(session.expiresAt).getTime()) {
1236
+ return this.updateSession(session, {
1237
+ status: "expired",
1238
+ lastError: `WeChat payment expired at ${session.expiresAt}`
1239
+ });
1240
+ }
1241
+ const xPayment = this.buildPaymentHeader(session);
1242
+ const res = await fetch(session.resourceUrl, {
1243
+ method: session.method,
1244
+ headers: { "Content-Type": "application/json", "X-Payment": xPayment },
1245
+ body: session.method === "POST" ? session.data : void 0
1246
+ });
1247
+ const text = await res.text().catch(() => "");
1248
+ if (res.status === 200) {
1249
+ return this.updateSession(session, {
1250
+ status: "completed",
1251
+ lastHttpStatus: res.status,
1252
+ lastError: void 0,
1253
+ resultBody: text
1254
+ });
1255
+ }
1256
+ if (res.status === 402) {
1257
+ return this.updateSession(session, {
1258
+ status: "pending",
1259
+ lastHttpStatus: res.status,
1260
+ lastError: text.slice(0, 500) || "wechat payment pending"
1261
+ });
1262
+ }
1263
+ return this.updateSession(session, {
1264
+ status: "failed",
1265
+ lastHttpStatus: res.status,
1266
+ lastError: `WeChat /execute returned HTTP ${res.status}: ${text.slice(0, 500)}`
1267
+ });
1268
+ }
1269
+ /** Poll a session until it completes, expires, fails, or is aborted. */
1270
+ async pollSession(identifier, opts = {}) {
1271
+ const first = this.loadSession(identifier);
1272
+ const deadline = Math.min(
1273
+ new Date(first.expiresAt).getTime(),
1274
+ this.now() + (opts.timeoutMs ?? TIMEOUT_MS)
1275
+ );
1276
+ const interval = opts.pollIntervalMs ?? POLL_INTERVAL_MS2;
1277
+ let session = first;
1278
+ while (this.now() < deadline) {
1279
+ if (opts.signal?.aborted) throw new Error("WeChat payment aborted");
1280
+ session = await this.status(session.paymentSessionId);
1281
+ if (session.status !== "pending") return session;
1282
+ await new Promise((r) => setTimeout(r, interval));
1283
+ }
1284
+ return this.updateSession(session, {
1285
+ status: "expired",
1286
+ lastError: `WeChat payment timed out after ${Math.round((opts.timeoutMs ?? TIMEOUT_MS) / 1e3)}s (out_trade_no=${session.outTradeNo}, last status ${session.lastHttpStatus ?? 0})`
1287
+ });
1288
+ }
1289
+ /** `fulfill` is an idempotent status check: paid sessions return stored body. */
1290
+ async fulfill(identifier) {
1291
+ const session = this.loadSession(identifier);
1292
+ if (session.status === "completed") return session;
1293
+ return this.status(identifier);
1294
+ }
1295
+ /** Mark a local session cancelled. Closing the merchant order is server-side. */
1296
+ cancel(identifier) {
1297
+ const session = this.loadSession(identifier);
1298
+ return this.updateSession(session, {
1299
+ status: "cancelled",
1300
+ lastError: "cancelled locally"
1301
+ });
1302
+ }
1303
+ listSessions() {
1304
+ this.ensureSessionDir();
1305
+ 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));
1306
+ }
1307
+ buildPaymentHeader(session) {
1308
+ const proof = {
1309
+ x402Version: 2,
1310
+ scheme: WECHAT_SCHEME,
1311
+ network: WECHAT_NETWORK,
1312
+ accepted: {
1313
+ scheme: WECHAT_SCHEME,
1314
+ network: WECHAT_NETWORK,
1315
+ extra: { out_trade_no: session.outTradeNo }
1316
+ },
1317
+ payload: { out_trade_no: session.outTradeNo }
1318
+ };
1319
+ return Buffer.from(JSON.stringify(proof)).toString("base64");
1320
+ }
1321
+ loadSession(identifier) {
1322
+ this.ensureSessionDir();
1323
+ const direct = join3(this.sessionDir, `${identifier}.json`);
1324
+ try {
1325
+ return JSON.parse(readFileSync2(direct, "utf-8"));
1326
+ } catch {
1327
+ const byTradeNo = this.listSessions().find((s) => s.outTradeNo === identifier);
1328
+ if (byTradeNo) return byTradeNo;
1329
+ throw new Error(`WeChat payment session not found: ${identifier}`);
1330
+ }
1331
+ }
1332
+ saveSession(session) {
1333
+ this.ensureSessionDir();
1334
+ writeFileSync2(
1335
+ join3(this.sessionDir, `${session.paymentSessionId}.json`),
1336
+ JSON.stringify(session, null, 2)
1337
+ );
1338
+ }
1339
+ updateSession(session, updates) {
1340
+ const next = {
1341
+ ...session,
1342
+ ...updates,
1343
+ updatedAt: new Date(this.now()).toISOString()
1344
+ };
1345
+ this.saveSession(next);
1346
+ return next;
1347
+ }
1348
+ ensureSessionDir() {
1349
+ mkdirSync2(this.sessionDir, { recursive: true });
1350
+ }
1351
+ };
1352
+
1161
1353
  // src/client/alipay/router.ts
1162
1354
  var ALIPAY_RAIL = "alipay";
1355
+ var WECHAT_RAIL = "wechat";
1356
+ var BALANCE_RAIL = "balance";
1163
1357
  function railOf(req) {
1164
1358
  if (req.scheme === "alipay-aipay" || req.network === ALIPAY_RAIL) return ALIPAY_RAIL;
1359
+ if (req.scheme === "wechatpay-native" || req.network === WECHAT_RAIL) return WECHAT_RAIL;
1360
+ if (req.scheme === BALANCE_RAIL || req.network === BALANCE_RAIL) return BALANCE_RAIL;
1165
1361
  return networkToChainName(req.network) ?? req.network;
1166
1362
  }
1167
1363
  function findRail(accepts, rail) {
@@ -1201,6 +1397,14 @@ function selectRail(input) {
1201
1397
  return { rail: railOf(serverAccepts[0]), requirement: serverAccepts[0] };
1202
1398
  }
1203
1399
 
1400
+ // src/facilitators/balance/auth.ts
1401
+ import { ethers as ethers2 } from "ethers";
1402
+ var BALANCE_AUTH_DOMAIN = "moltspay-balance-auth:v1";
1403
+ var BALANCE_AUTH_MAX_SKEW_MS = 5 * 60 * 1e3;
1404
+ function buildDeductMessage(f) {
1405
+ return [BALANCE_AUTH_DOMAIN, "balance-deduct", f.buyerId, f.requestId, f.service, String(f.timestamp)].join("\n");
1406
+ }
1407
+
1204
1408
  // src/client/node/index.ts
1205
1409
  var DEFAULT_CONFIG = {
1206
1410
  chain: "base",
@@ -1220,7 +1424,7 @@ var MoltsPayClient = class {
1220
1424
  railPreference;
1221
1425
  alipaySessionId;
1222
1426
  constructor(options = {}) {
1223
- this.configDir = options.configDir || join3(homedir3(), ".moltspay");
1427
+ this.configDir = options.configDir || join4(homedir4(), ".moltspay");
1224
1428
  this.config = this.loadConfig();
1225
1429
  this.railPreference = options.railPreference ?? this.config.railPreference;
1226
1430
  this.alipaySessionId = options.alipaySessionId;
@@ -1304,6 +1508,12 @@ var MoltsPayClient = class {
1304
1508
  if (options.rail === ALIPAY_RAIL) {
1305
1509
  return this.payViaAlipay(serverUrl, service, params, options);
1306
1510
  }
1511
+ if (options.rail === WECHAT_RAIL) {
1512
+ return this.payViaWechat(serverUrl, service, params, options);
1513
+ }
1514
+ if (options.rail === BALANCE_RAIL) {
1515
+ return this.payViaBalance(serverUrl, service, params, options);
1516
+ }
1307
1517
  if (!this.wallet || !this.walletData) {
1308
1518
  throw new Error("Client not initialized. Run: npx moltspay init");
1309
1519
  }
@@ -1565,6 +1775,399 @@ Please specify: --chain <chain_name>`
1565
1775
  return { body: result.body, payment: result.payment, media: result.media };
1566
1776
  }
1567
1777
  }
1778
+ /**
1779
+ * Pay for a service over the WeChat Pay Native fiat rail (2.1.0).
1780
+ *
1781
+ * Mirrors {@link payViaAlipay} and needs no EVM wallet. The server placed the
1782
+ * Native order when it built the 402, so its `wechatpay-native` accepts[]
1783
+ * entry already carries `extra.code_url` + `extra.out_trade_no`. Flow: hit the
1784
+ * resource to get the 402, confirm the server offers the wechat rail, surface
1785
+ * the code_url (caller renders a QR), then poll the resource with the
1786
+ * out_trade_no proof until the server verifies the order paid and delivers.
1787
+ */
1788
+ async payViaWechat(serverUrl, service, params, options) {
1789
+ const session = await this.startWechatPayment(serverUrl, service, params, options);
1790
+ const wechat = new WechatClient({ configDir: this.configDir });
1791
+ const result = await wechat.pollSession(session.paymentSessionId, {
1792
+ timeoutMs: options.timeoutMs,
1793
+ signal: options.signal
1794
+ });
1795
+ if (result.status !== "completed") {
1796
+ throw new Error(result.lastError || `WeChat payment ended with status ${result.status}`);
1797
+ }
1798
+ try {
1799
+ const json = JSON.parse(result.resultBody ?? "");
1800
+ return json.result ?? json;
1801
+ } catch {
1802
+ return { body: result.resultBody ?? "" };
1803
+ }
1804
+ }
1805
+ /**
1806
+ * The ethers wallet used to sign balance-rail deductions: the client's EVM
1807
+ * wallet if it has one, else a per-configDir identity key persisted at
1808
+ * `<configDir>/balance-identity.key` (0600) so a balance-only client works
1809
+ * without a full crypto wallet. Under `agent 统一代付`, one key spends every
1810
+ * account the agent tops up.
1811
+ */
1812
+ balanceSigner() {
1813
+ if (this.wallet) return this.wallet;
1814
+ const p = join4(this.configDir, "balance-identity.key");
1815
+ let pk;
1816
+ if (existsSync2(p)) {
1817
+ pk = readFileSync3(p, "utf-8").trim();
1818
+ } else {
1819
+ mkdirSync3(this.configDir, { recursive: true });
1820
+ pk = Wallet2.createRandom().privateKey;
1821
+ writeFileSync3(p, pk, { mode: 384 });
1822
+ }
1823
+ return new Wallet2(pk);
1824
+ }
1825
+ /** The balance-rail spending signer address (lowercase 0x…). Stable per
1826
+ * configDir; this is the identity the server TOFU-binds and later verifies. */
1827
+ getBalanceSignerAddress() {
1828
+ return this.balanceSigner().address.toLowerCase();
1829
+ }
1830
+ /** The buyer id for the balance rail: explicit option > persisted config. */
1831
+ resolveBuyerId(explicit) {
1832
+ const buyerId = explicit ?? this.config.buyerId;
1833
+ if (!buyerId) {
1834
+ throw new Error(
1835
+ "Balance rail needs a buyer id. Pass { buyerId } or persist one with setBuyerId()."
1836
+ );
1837
+ }
1838
+ return buyerId;
1839
+ }
1840
+ /**
1841
+ * Pay via the custodial balance rail (2.2.0, password-free).
1842
+ *
1843
+ * No wallet, no QR: the request carries `{buyer_id, request_id}` in the
1844
+ * X-Payment payload and the server deducts the prepaid balance atomically
1845
+ * before running the skill. The client-generated `request_id` makes the
1846
+ * charge idempotent — a network retry can never double-deduct.
1847
+ */
1848
+ async payViaBalance(serverUrl, service, params, options) {
1849
+ const buyerId = this.resolveBuyerId(options.buyerId);
1850
+ let attempt = await this.balanceDeduct(serverUrl, service, params, options, buyerId);
1851
+ if (attempt.ok) return attempt.result;
1852
+ 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 || ""));
1853
+ if (!fundable || options.autoTopup === false) {
1854
+ throw new Error(attempt.error || `Balance payment failed with HTTP ${attempt.status}`);
1855
+ }
1856
+ if (options.topupMode === "manual") {
1857
+ const order = await this.createBalanceTopupOrder(serverUrl, {
1858
+ pack: options.topupPack,
1859
+ buyerId,
1860
+ context: { service }
1861
+ });
1862
+ options.onTopupRequired?.(order.pack, order.codeUrl);
1863
+ return {
1864
+ status: "topup_required",
1865
+ out_trade_no: order.outTradeNo,
1866
+ code_url: order.codeUrl,
1867
+ pack: order.pack,
1868
+ server_url: serverUrl
1869
+ };
1870
+ }
1871
+ const credited = await this.topupBalancePack(serverUrl, {
1872
+ pack: options.topupPack,
1873
+ buyerId,
1874
+ pollIntervalMs: options.topupPollIntervalMs,
1875
+ signal: options.signal,
1876
+ onCodeUrl: (pack, codeUrl) => options.onTopupRequired?.(pack, codeUrl)
1877
+ });
1878
+ options.onTopupCredited?.(credited.balance);
1879
+ attempt = await this.balanceDeduct(serverUrl, service, params, options, buyerId);
1880
+ if (attempt.ok) return attempt.result;
1881
+ throw new Error(attempt.error || "Balance payment failed after top-up");
1882
+ }
1883
+ /** One password-free deduct attempt. Never throws on an HTTP error; the
1884
+ * caller inspects `{ ok, status, error }` to decide whether to auto-fund. */
1885
+ async balanceDeduct(serverUrl, service, params, options, buyerId) {
1886
+ let executeUrl = `${serverUrl}/execute`;
1887
+ try {
1888
+ const services = await this.getServices(serverUrl);
1889
+ const svc = services.services?.find((s) => s.id === service);
1890
+ if (svc?.endpoint) executeUrl = `${serverUrl}${svc.endpoint}`;
1891
+ } catch {
1892
+ }
1893
+ const requestBody = options.rawData ? { service, ...params } : { service, params };
1894
+ const requestId = randomUUID3();
1895
+ const timestamp = Math.floor(Date.now() / 1e3);
1896
+ const signature = await this.balanceSigner().signMessage(
1897
+ buildDeductMessage({ buyerId, requestId, service, timestamp })
1898
+ );
1899
+ const xPayment = Buffer.from(JSON.stringify({
1900
+ x402Version: 2,
1901
+ scheme: BALANCE_RAIL,
1902
+ network: BALANCE_RAIL,
1903
+ payload: { buyer_id: buyerId, request_id: requestId, auth: { timestamp, signature } }
1904
+ })).toString("base64");
1905
+ const res = await fetch(executeUrl, {
1906
+ method: "POST",
1907
+ headers: { "Content-Type": "application/json", "X-Payment": xPayment },
1908
+ body: JSON.stringify(requestBody),
1909
+ signal: options.signal
1910
+ });
1911
+ const data = await res.json().catch(() => ({}));
1912
+ if (!res.ok) return { ok: false, status: res.status, error: data.error, code: data.code };
1913
+ return { ok: true, status: res.status, result: data.result ?? data };
1914
+ }
1915
+ /**
1916
+ * Non-blocking: POST /balance/topup/order, persist a recoverable session, and
1917
+ * return at once (no polling). Use with {@link confirmBalanceTopup} for
1918
+ * turn-based agents; the blocking {@link topupBalancePack} is built on this.
1919
+ */
1920
+ async createBalanceTopupOrder(serverUrl, opts = {}) {
1921
+ const id = this.resolveBuyerId(opts.buyerId);
1922
+ const orderRes = await fetch(`${serverUrl}/balance/topup/order`, {
1923
+ method: "POST",
1924
+ headers: { "Content-Type": "application/json" },
1925
+ // Carry the spending signer so the server binds it to the account on
1926
+ // confirm (topup-time binding). The same key signs deductions later.
1927
+ body: JSON.stringify({ buyer_id: id, pack: opts.pack, signer_address: this.getBalanceSignerAddress() })
1928
+ });
1929
+ const order = await orderRes.json().catch(() => ({}));
1930
+ if (!orderRes.ok) throw new Error(order.error || `Top-up order failed with HTTP ${orderRes.status}`);
1931
+ const maxTimeoutSeconds = order.max_timeout_seconds ?? 300;
1932
+ const now = Date.now();
1933
+ this.saveBalanceTopupSession({
1934
+ out_trade_no: order.out_trade_no,
1935
+ buyer_id: id,
1936
+ pack: order.pack,
1937
+ server_url: serverUrl,
1938
+ code_url: order.code_url,
1939
+ status: "pending",
1940
+ created_at: new Date(now).toISOString(),
1941
+ expires_at: new Date(now + maxTimeoutSeconds * 1e3).toISOString(),
1942
+ context: opts.context
1943
+ });
1944
+ return { outTradeNo: order.out_trade_no, codeUrl: order.code_url, pack: order.pack, maxTimeoutSeconds };
1945
+ }
1946
+ /**
1947
+ * One-shot: POST /balance/topup/confirm for a single order. No polling.
1948
+ * Updates the persisted session on credit. `serverUrl` defaults to the one
1949
+ * recorded in the session (recover by out_trade_no alone).
1950
+ */
1951
+ async confirmBalanceTopup(outTradeNo, opts = {}) {
1952
+ const session = this.getBalanceTopupSession(outTradeNo);
1953
+ const serverUrl = opts.serverUrl || session?.server_url;
1954
+ if (!serverUrl) {
1955
+ return { credited: false, reason: `No server URL for ${outTradeNo}: pass serverUrl or run topup-order first` };
1956
+ }
1957
+ const confRes = await fetch(`${serverUrl}/balance/topup/confirm`, {
1958
+ method: "POST",
1959
+ headers: { "Content-Type": "application/json" },
1960
+ body: JSON.stringify({ out_trade_no: outTradeNo })
1961
+ });
1962
+ const conf = await confRes.json().catch(() => ({}));
1963
+ if (!confRes.ok) return { credited: false, reason: conf.error || `Confirm failed with HTTP ${confRes.status}` };
1964
+ if (conf.credited) {
1965
+ if (session) {
1966
+ session.status = "credited";
1967
+ session.tx_id = conf.tx_id;
1968
+ session.balance = conf.balance;
1969
+ this.saveBalanceTopupSession(session);
1970
+ }
1971
+ return { credited: true, balance: conf.balance, txId: conf.tx_id };
1972
+ }
1973
+ return { credited: false, pending: !!conf.pending, reason: conf.reason };
1974
+ }
1975
+ /**
1976
+ * Blocking terminal wrapper: create the order then poll confirm until the
1977
+ * scan is credited (or the order expires). Built on
1978
+ * {@link createBalanceTopupOrder} + {@link confirmBalanceTopup}.
1979
+ */
1980
+ async topupBalancePack(serverUrl, opts = {}) {
1981
+ const order = await this.createBalanceTopupOrder(serverUrl, { pack: opts.pack, buyerId: opts.buyerId });
1982
+ opts.onCodeUrl?.(order.pack, order.codeUrl);
1983
+ const interval = opts.pollIntervalMs ?? 2e3;
1984
+ const deadline = Date.now() + order.maxTimeoutSeconds * 1e3;
1985
+ while (Date.now() < deadline) {
1986
+ if (opts.signal?.aborted) throw new Error("Top-up aborted");
1987
+ const conf = await this.confirmBalanceTopup(order.outTradeNo, { serverUrl });
1988
+ if (conf.credited) return { balance: conf.balance, outTradeNo: order.outTradeNo, txId: conf.txId };
1989
+ await this.sleep(interval, opts.signal);
1990
+ }
1991
+ throw new Error("Top-up timed out before the payment was confirmed");
1992
+ }
1993
+ // --- Recoverable balance top-up sessions (<configDir>/balance-topup-sessions) ---
1994
+ balanceTopupSessionDir() {
1995
+ return join4(this.configDir, "balance-topup-sessions");
1996
+ }
1997
+ saveBalanceTopupSession(session) {
1998
+ const dir = this.balanceTopupSessionDir();
1999
+ mkdirSync3(dir, { recursive: true });
2000
+ writeFileSync3(join4(dir, `${session.out_trade_no}.json`), JSON.stringify(session, null, 2));
2001
+ }
2002
+ /** Read a persisted top-up session by out_trade_no, or null. */
2003
+ getBalanceTopupSession(outTradeNo) {
2004
+ const p = join4(this.balanceTopupSessionDir(), `${outTradeNo}.json`);
2005
+ if (!existsSync2(p)) return null;
2006
+ try {
2007
+ return JSON.parse(readFileSync3(p, "utf-8"));
2008
+ } catch {
2009
+ return null;
2010
+ }
2011
+ }
2012
+ /** List persisted top-up sessions, newest first. */
2013
+ listBalanceTopupSessions() {
2014
+ const dir = this.balanceTopupSessionDir();
2015
+ if (!existsSync2(dir)) return [];
2016
+ return readdirSync2(dir).filter((f) => f.endsWith(".json")).map((f) => {
2017
+ try {
2018
+ return JSON.parse(readFileSync3(join4(dir, f), "utf-8"));
2019
+ } catch {
2020
+ return null;
2021
+ }
2022
+ }).filter((s) => s !== null).sort((a, b) => (b.created_at || "").localeCompare(a.created_at || ""));
2023
+ }
2024
+ /** Abortable sleep used by the top-up poll loop. */
2025
+ sleep(ms, signal) {
2026
+ return new Promise((resolve, reject) => {
2027
+ if (signal?.aborted) return reject(new Error("aborted"));
2028
+ const t = setTimeout(resolve, ms);
2029
+ signal?.addEventListener("abort", () => {
2030
+ clearTimeout(t);
2031
+ reject(new Error("aborted"));
2032
+ }, { once: true });
2033
+ });
2034
+ }
2035
+ /** Persist the buyer id used by the balance rail (bearer semantics). */
2036
+ setBuyerId(buyerId) {
2037
+ this.config.buyerId = buyerId;
2038
+ this.saveConfig();
2039
+ }
2040
+ /** GET /balance — custodial balance, limits, and today's spend for a buyer. */
2041
+ async getBuyerBalance(serverUrl, buyerId) {
2042
+ const id = this.resolveBuyerId(buyerId);
2043
+ const res = await fetch(`${serverUrl}/balance?buyer_id=${encodeURIComponent(id)}`);
2044
+ const data = await res.json().catch(() => ({}));
2045
+ if (!res.ok) throw new Error(data.error || `Balance query failed with HTTP ${res.status}`);
2046
+ return data;
2047
+ }
2048
+ /**
2049
+ * POST /balance/topup — report an externally settled payment (on-chain
2050
+ * tx hash / Alipay trade_no / WeChat out_trade_no) so the server verifies
2051
+ * and credits the ledger. Idempotent per reference.
2052
+ */
2053
+ async topupBalance(serverUrl, opts) {
2054
+ const id = this.resolveBuyerId(opts.buyerId);
2055
+ const res = await fetch(`${serverUrl}/balance/topup`, {
2056
+ method: "POST",
2057
+ headers: { "Content-Type": "application/json" },
2058
+ body: JSON.stringify({
2059
+ buyer_id: id,
2060
+ rail: opts.rail,
2061
+ amount: opts.amount,
2062
+ tx_hash: opts.txHash,
2063
+ chain: opts.chain,
2064
+ trade_no: opts.tradeNo,
2065
+ out_trade_no: opts.outTradeNo
2066
+ })
2067
+ });
2068
+ const data = await res.json().catch(() => ({}));
2069
+ if (!res.ok) throw new Error(data.error || `Top-up failed with HTTP ${res.status}`);
2070
+ return data;
2071
+ }
2072
+ /** GET /balance/transactions — ledger history for a buyer, newest first. */
2073
+ async listBalanceTransactions(serverUrl, opts = {}) {
2074
+ const id = this.resolveBuyerId(opts.buyerId);
2075
+ const qs = new URLSearchParams({ buyer_id: id });
2076
+ if (opts.limit) qs.set("limit", String(opts.limit));
2077
+ if (opts.offset) qs.set("offset", String(opts.offset));
2078
+ const res = await fetch(`${serverUrl}/balance/transactions?${qs}`);
2079
+ const data = await res.json().catch(() => ({}));
2080
+ if (!res.ok) throw new Error(data.error || `Transaction query failed with HTTP ${res.status}`);
2081
+ return data;
2082
+ }
2083
+ /**
2084
+ * Start a recoverable WeChat Pay Native session and return immediately with
2085
+ * QR metadata. The SDK client persists enough context to poll/fulfill later.
2086
+ */
2087
+ async startWechatPayment(serverUrl, service, params, options = {}) {
2088
+ let executeUrl = `${serverUrl}/execute`;
2089
+ try {
2090
+ const services = await this.getServices(serverUrl);
2091
+ const svc = services.services?.find((s) => s.id === service);
2092
+ if (svc?.endpoint) executeUrl = `${serverUrl}${svc.endpoint}`;
2093
+ } catch {
2094
+ }
2095
+ const requestBody = options.rawData ? { service, ...params } : { service, params };
2096
+ const bodyJson = JSON.stringify(requestBody);
2097
+ const res = await fetch(executeUrl, {
2098
+ method: "POST",
2099
+ headers: { "Content-Type": "application/json" },
2100
+ body: bodyJson
2101
+ });
2102
+ if (res.status !== 402) {
2103
+ const data = await res.json().catch(() => ({}));
2104
+ if (res.ok && data.result) return data.result;
2105
+ throw new Error(data.error || `Expected 402, got ${res.status}`);
2106
+ }
2107
+ const header = res.headers.get(PAYMENT_REQUIRED_HEADER);
2108
+ if (!header) throw new Error("Missing x-payment-required header on 402");
2109
+ const parsed = JSON.parse(Buffer.from(header, "base64").toString("utf-8"));
2110
+ const accepts = Array.isArray(parsed) ? parsed : parsed.accepts ?? [parsed];
2111
+ const { requirement } = selectRail({
2112
+ serverAccepts: accepts,
2113
+ explicitRail: WECHAT_RAIL,
2114
+ preference: this.railPreference,
2115
+ availability: { evmReady: this.isInitialized }
2116
+ });
2117
+ const wechat = new WechatClient({ configDir: this.configDir });
2118
+ const session = wechat.start402({
2119
+ resourceUrl: executeUrl,
2120
+ requirement,
2121
+ method: "POST",
2122
+ data: bodyJson,
2123
+ onPaymentPending: options.onPaymentPending ? (info) => options.onPaymentPending({ paymentUrl: info.codeUrl, tradeNo: info.outTradeNo }) : void 0,
2124
+ timeoutMs: options.timeoutMs,
2125
+ signal: options.signal,
2126
+ context: {
2127
+ serverUrl,
2128
+ service,
2129
+ params,
2130
+ rawData: options.rawData ?? false,
2131
+ rail: WECHAT_RAIL
2132
+ }
2133
+ });
2134
+ if (options.autoPoll || options.onWechatPaymentCompleted || options.onWechatPaymentFailed) {
2135
+ void wechat.pollSession(session.paymentSessionId, {
2136
+ timeoutMs: options.timeoutMs,
2137
+ signal: options.signal
2138
+ }).then(async (finalSession) => {
2139
+ if (finalSession.status === "completed") {
2140
+ await options.onWechatPaymentCompleted?.(finalSession);
2141
+ } else {
2142
+ await options.onWechatPaymentFailed?.(finalSession);
2143
+ }
2144
+ }).catch(async (error) => {
2145
+ const failed = await wechat.fulfill(session.paymentSessionId).catch(() => ({
2146
+ ...session,
2147
+ status: "failed",
2148
+ lastError: error instanceof Error ? error.message : String(error)
2149
+ }));
2150
+ await options.onWechatPaymentFailed?.(failed);
2151
+ });
2152
+ }
2153
+ return session;
2154
+ }
2155
+ /** Query a persisted WeChat session once. */
2156
+ async getWechatPaymentStatus(identifier) {
2157
+ return new WechatClient({ configDir: this.configDir }).status(identifier);
2158
+ }
2159
+ /** Idempotently fulfill a paid WeChat session, returning stored or fetched result. */
2160
+ async fulfillWechatPayment(identifier) {
2161
+ return new WechatClient({ configDir: this.configDir }).fulfill(identifier);
2162
+ }
2163
+ /** Mark a local WeChat session as cancelled. */
2164
+ cancelWechatPayment(identifier) {
2165
+ return new WechatClient({ configDir: this.configDir }).cancel(identifier);
2166
+ }
2167
+ /** List persisted WeChat sessions, newest first. */
2168
+ listWechatPaymentSessions() {
2169
+ return new WechatClient({ configDir: this.configDir }).listSessions();
2170
+ }
1568
2171
  /**
1569
2172
  * Handle MPP (Machine Payments Protocol) payment flow
1570
2173
  * Called when pay() detects WWW-Authenticate header in 402 response
@@ -1660,14 +2263,14 @@ Please specify: --chain <chain_name>`
1660
2263
  async handleBNBPayment(executeUrl, service, params, paymentDetails, options = {}) {
1661
2264
  const { to, amount, token, chainName, chain, spender } = paymentDetails;
1662
2265
  const tokenConfig = chain.tokens[token];
1663
- const provider = new ethers2.JsonRpcProvider(chain.rpc);
2266
+ const provider = new ethers3.JsonRpcProvider(chain.rpc);
1664
2267
  const allowance = await this.checkAllowance(tokenConfig.address, spender, provider);
1665
2268
  const amountWeiCheck = BigInt(Math.floor(amount * 10 ** tokenConfig.decimals));
1666
2269
  if (allowance < amountWeiCheck) {
1667
2270
  const nativeBalance = await provider.getBalance(this.wallet.address);
1668
- const minGasBalance = ethers2.parseEther("0.0005");
2271
+ const minGasBalance = ethers3.parseEther("0.0005");
1669
2272
  if (nativeBalance < minGasBalance) {
1670
- const nativeBNB = parseFloat(ethers2.formatEther(nativeBalance)).toFixed(4);
2273
+ const nativeBNB = parseFloat(ethers3.formatEther(nativeBalance)).toFixed(4);
1671
2274
  const isTestnet = chainName === "bnb_testnet";
1672
2275
  if (isTestnet) {
1673
2276
  throw new Error(
@@ -1840,7 +2443,7 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
1840
2443
  * Check ERC20 allowance for a spender
1841
2444
  */
1842
2445
  async checkAllowance(tokenAddress, spender, provider) {
1843
- const contract = new ethers2.Contract(
2446
+ const contract = new ethers3.Contract(
1844
2447
  tokenAddress,
1845
2448
  ["function allowance(address owner, address spender) view returns (uint256)"],
1846
2449
  provider
@@ -1859,7 +2462,7 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
1859
2462
  async signEIP3009(to, amount, chain, token = "USDC", domainOverride) {
1860
2463
  const tokenConfig = chain.tokens[token];
1861
2464
  const value = BigInt(Math.floor(amount * 10 ** tokenConfig.decimals)).toString();
1862
- const nonce = ethers2.hexlify(ethers2.randomBytes(32));
2465
+ const nonce = ethers3.hexlify(ethers3.randomBytes(32));
1863
2466
  const tokenName = domainOverride?.name || tokenConfig.eip712Name || (token === "USDC" ? "USD Coin" : "Tether USD");
1864
2467
  const tokenVersion = domainOverride?.version || "2";
1865
2468
  const envelope = buildEIP3009TypedData({
@@ -1905,26 +2508,26 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
1905
2508
  }
1906
2509
  // --- Config & Wallet Management ---
1907
2510
  loadConfig() {
1908
- const configPath = join3(this.configDir, "config.json");
2511
+ const configPath = join4(this.configDir, "config.json");
1909
2512
  if (existsSync2(configPath)) {
1910
- const content = readFileSync2(configPath, "utf-8");
2513
+ const content = readFileSync3(configPath, "utf-8");
1911
2514
  return { ...DEFAULT_CONFIG, ...JSON.parse(content) };
1912
2515
  }
1913
2516
  return { ...DEFAULT_CONFIG };
1914
2517
  }
1915
2518
  saveConfig() {
1916
- mkdirSync2(this.configDir, { recursive: true });
1917
- const configPath = join3(this.configDir, "config.json");
1918
- writeFileSync2(configPath, JSON.stringify(this.config, null, 2));
2519
+ mkdirSync3(this.configDir, { recursive: true });
2520
+ const configPath = join4(this.configDir, "config.json");
2521
+ writeFileSync3(configPath, JSON.stringify(this.config, null, 2));
1919
2522
  }
1920
2523
  /**
1921
2524
  * Load spending data from disk
1922
2525
  */
1923
2526
  loadSpending() {
1924
- const spendingPath = join3(this.configDir, "spending.json");
2527
+ const spendingPath = join4(this.configDir, "spending.json");
1925
2528
  if (existsSync2(spendingPath)) {
1926
2529
  try {
1927
- const data = JSON.parse(readFileSync2(spendingPath, "utf-8"));
2530
+ const data = JSON.parse(readFileSync3(spendingPath, "utf-8"));
1928
2531
  const today = (/* @__PURE__ */ new Date()).setHours(0, 0, 0, 0);
1929
2532
  if (data.date && data.date === today) {
1930
2533
  this.todaySpending = data.amount || 0;
@@ -1943,17 +2546,17 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
1943
2546
  * Save spending data to disk
1944
2547
  */
1945
2548
  saveSpending() {
1946
- mkdirSync2(this.configDir, { recursive: true });
1947
- const spendingPath = join3(this.configDir, "spending.json");
2549
+ mkdirSync3(this.configDir, { recursive: true });
2550
+ const spendingPath = join4(this.configDir, "spending.json");
1948
2551
  const data = {
1949
2552
  date: this.lastSpendingReset || (/* @__PURE__ */ new Date()).setHours(0, 0, 0, 0),
1950
2553
  amount: this.todaySpending,
1951
2554
  updatedAt: Date.now()
1952
2555
  };
1953
- writeFileSync2(spendingPath, JSON.stringify(data, null, 2));
2556
+ writeFileSync3(spendingPath, JSON.stringify(data, null, 2));
1954
2557
  }
1955
2558
  loadWallet() {
1956
- const walletPath = join3(this.configDir, "wallet.json");
2559
+ const walletPath = join4(this.configDir, "wallet.json");
1957
2560
  if (existsSync2(walletPath)) {
1958
2561
  if (process.platform !== "win32") {
1959
2562
  try {
@@ -1967,7 +2570,7 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
1967
2570
  } catch {
1968
2571
  }
1969
2572
  }
1970
- const content = readFileSync2(walletPath, "utf-8");
2573
+ const content = readFileSync3(walletPath, "utf-8");
1971
2574
  return JSON.parse(content);
1972
2575
  }
1973
2576
  return null;
@@ -1976,15 +2579,15 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
1976
2579
  * Initialize a new wallet (called by CLI)
1977
2580
  */
1978
2581
  static init(configDir, options) {
1979
- mkdirSync2(configDir, { recursive: true });
2582
+ mkdirSync3(configDir, { recursive: true });
1980
2583
  const wallet = Wallet2.createRandom();
1981
2584
  const walletData = {
1982
2585
  address: wallet.address,
1983
2586
  privateKey: wallet.privateKey,
1984
2587
  createdAt: Date.now()
1985
2588
  };
1986
- const walletPath = join3(configDir, "wallet.json");
1987
- writeFileSync2(walletPath, JSON.stringify(walletData, null, 2), { mode: 384 });
2589
+ const walletPath = join4(configDir, "wallet.json");
2590
+ writeFileSync3(walletPath, JSON.stringify(walletData, null, 2), { mode: 384 });
1988
2591
  const config = {
1989
2592
  chain: options.chain,
1990
2593
  limits: {
@@ -1992,8 +2595,8 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
1992
2595
  maxPerDay: options.maxPerDay
1993
2596
  }
1994
2597
  };
1995
- const configPath = join3(configDir, "config.json");
1996
- writeFileSync2(configPath, JSON.stringify(config, null, 2));
2598
+ const configPath = join4(configDir, "config.json");
2599
+ writeFileSync3(configPath, JSON.stringify(config, null, 2));
1997
2600
  return { address: wallet.address, configDir };
1998
2601
  }
1999
2602
  /**
@@ -2009,17 +2612,17 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
2009
2612
  } catch {
2010
2613
  throw new Error(`Unknown chain: ${this.config.chain}`);
2011
2614
  }
2012
- const provider = new ethers2.JsonRpcProvider(chain.rpc);
2615
+ const provider = new ethers3.JsonRpcProvider(chain.rpc);
2013
2616
  const tokenAbi = ["function balanceOf(address) view returns (uint256)"];
2014
2617
  const [nativeBalance, usdcBalance, usdtBalance] = await Promise.all([
2015
2618
  provider.getBalance(this.wallet.address),
2016
- new ethers2.Contract(chain.tokens.USDC.address, tokenAbi, provider).balanceOf(this.wallet.address),
2017
- new ethers2.Contract(chain.tokens.USDT.address, tokenAbi, provider).balanceOf(this.wallet.address)
2619
+ new ethers3.Contract(chain.tokens.USDC.address, tokenAbi, provider).balanceOf(this.wallet.address),
2620
+ new ethers3.Contract(chain.tokens.USDT.address, tokenAbi, provider).balanceOf(this.wallet.address)
2018
2621
  ]);
2019
2622
  return {
2020
- usdc: parseFloat(ethers2.formatUnits(usdcBalance, chain.tokens.USDC.decimals)),
2021
- usdt: parseFloat(ethers2.formatUnits(usdtBalance, chain.tokens.USDT.decimals)),
2022
- native: parseFloat(ethers2.formatEther(nativeBalance))
2623
+ usdc: parseFloat(ethers3.formatUnits(usdcBalance, chain.tokens.USDC.decimals)),
2624
+ usdt: parseFloat(ethers3.formatUnits(usdtBalance, chain.tokens.USDT.decimals)),
2625
+ native: parseFloat(ethers3.formatEther(nativeBalance))
2023
2626
  };
2024
2627
  }
2025
2628
  /**
@@ -2042,38 +2645,38 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
2042
2645
  supportedChains.map(async (chainName) => {
2043
2646
  try {
2044
2647
  const chain = getChain(chainName);
2045
- const provider = new ethers2.JsonRpcProvider(chain.rpc);
2648
+ const provider = new ethers3.JsonRpcProvider(chain.rpc);
2046
2649
  if (chainName === "tempo_moderato") {
2047
2650
  const [nativeBalance, pathUSD, alphaUSD, betaUSD, thetaUSD] = await Promise.all([
2048
2651
  provider.getBalance(this.wallet.address),
2049
- new ethers2.Contract(tempoTokens.pathUSD, tokenAbi, provider).balanceOf(this.wallet.address),
2050
- new ethers2.Contract(tempoTokens.alphaUSD, tokenAbi, provider).balanceOf(this.wallet.address),
2051
- new ethers2.Contract(tempoTokens.betaUSD, tokenAbi, provider).balanceOf(this.wallet.address),
2052
- new ethers2.Contract(tempoTokens.thetaUSD, tokenAbi, provider).balanceOf(this.wallet.address)
2652
+ new ethers3.Contract(tempoTokens.pathUSD, tokenAbi, provider).balanceOf(this.wallet.address),
2653
+ new ethers3.Contract(tempoTokens.alphaUSD, tokenAbi, provider).balanceOf(this.wallet.address),
2654
+ new ethers3.Contract(tempoTokens.betaUSD, tokenAbi, provider).balanceOf(this.wallet.address),
2655
+ new ethers3.Contract(tempoTokens.thetaUSD, tokenAbi, provider).balanceOf(this.wallet.address)
2053
2656
  ]);
2054
2657
  results[chainName] = {
2055
- usdc: parseFloat(ethers2.formatUnits(pathUSD, 6)),
2658
+ usdc: parseFloat(ethers3.formatUnits(pathUSD, 6)),
2056
2659
  // pathUSD as default USDC
2057
- usdt: parseFloat(ethers2.formatUnits(alphaUSD, 6)),
2660
+ usdt: parseFloat(ethers3.formatUnits(alphaUSD, 6)),
2058
2661
  // alphaUSD as default USDT
2059
- native: parseFloat(ethers2.formatEther(nativeBalance)),
2662
+ native: parseFloat(ethers3.formatEther(nativeBalance)),
2060
2663
  tempo: {
2061
- pathUSD: parseFloat(ethers2.formatUnits(pathUSD, 6)),
2062
- alphaUSD: parseFloat(ethers2.formatUnits(alphaUSD, 6)),
2063
- betaUSD: parseFloat(ethers2.formatUnits(betaUSD, 6)),
2064
- thetaUSD: parseFloat(ethers2.formatUnits(thetaUSD, 6))
2664
+ pathUSD: parseFloat(ethers3.formatUnits(pathUSD, 6)),
2665
+ alphaUSD: parseFloat(ethers3.formatUnits(alphaUSD, 6)),
2666
+ betaUSD: parseFloat(ethers3.formatUnits(betaUSD, 6)),
2667
+ thetaUSD: parseFloat(ethers3.formatUnits(thetaUSD, 6))
2065
2668
  }
2066
2669
  };
2067
2670
  } else {
2068
2671
  const [nativeBalance, usdcBalance, usdtBalance] = await Promise.all([
2069
2672
  provider.getBalance(this.wallet.address),
2070
- new ethers2.Contract(chain.tokens.USDC.address, tokenAbi, provider).balanceOf(this.wallet.address),
2071
- new ethers2.Contract(chain.tokens.USDT.address, tokenAbi, provider).balanceOf(this.wallet.address)
2673
+ new ethers3.Contract(chain.tokens.USDC.address, tokenAbi, provider).balanceOf(this.wallet.address),
2674
+ new ethers3.Contract(chain.tokens.USDT.address, tokenAbi, provider).balanceOf(this.wallet.address)
2072
2675
  ]);
2073
2676
  results[chainName] = {
2074
- usdc: parseFloat(ethers2.formatUnits(usdcBalance, chain.tokens.USDC.decimals)),
2075
- usdt: parseFloat(ethers2.formatUnits(usdtBalance, chain.tokens.USDT.decimals)),
2076
- native: parseFloat(ethers2.formatEther(nativeBalance))
2677
+ usdc: parseFloat(ethers3.formatUnits(usdcBalance, chain.tokens.USDC.decimals)),
2678
+ usdt: parseFloat(ethers3.formatUnits(usdtBalance, chain.tokens.USDT.decimals)),
2679
+ native: parseFloat(ethers3.formatEther(nativeBalance))
2077
2680
  };
2078
2681
  }
2079
2682
  } catch (err) {
@@ -2200,6 +2803,7 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
2200
2803
  }
2201
2804
  };
2202
2805
  export {
2203
- MoltsPayClient
2806
+ MoltsPayClient,
2807
+ WechatClient
2204
2808
  };
2205
2809
  //# sourceMappingURL=index.mjs.map