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.
- package/.env.example +23 -0
- package/CHANGELOG.md +551 -0
- package/README.md +466 -8
- package/dist/cdp/index.js.map +1 -1
- package/dist/cdp/index.mjs.map +1 -1
- package/dist/chains/index.d.mts +69 -4
- package/dist/chains/index.d.ts +69 -4
- package/dist/chains/index.js +45 -1
- package/dist/chains/index.js.map +1 -1
- package/dist/chains/index.mjs +39 -1
- package/dist/chains/index.mjs.map +1 -1
- package/dist/cli/index.js +5444 -2126
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/index.mjs +5457 -2133
- package/dist/cli/index.mjs.map +1 -1
- package/dist/client/index.d.mts +307 -1
- package/dist/client/index.d.ts +307 -1
- package/dist/client/index.js +639 -34
- package/dist/client/index.js.map +1 -1
- package/dist/client/index.mjs +656 -52
- package/dist/client/index.mjs.map +1 -1
- package/dist/client/web/index.mjs.map +1 -1
- package/dist/facilitators/index.d.mts +512 -10
- package/dist/facilitators/index.d.ts +512 -10
- package/dist/facilitators/index.js +925 -13
- package/dist/facilitators/index.js.map +1 -1
- package/dist/facilitators/index.mjs +906 -12
- package/dist/facilitators/index.mjs.map +1 -1
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2843 -551
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2849 -558
- package/dist/index.mjs.map +1 -1
- package/dist/mcp/index.js +635 -32
- package/dist/mcp/index.js.map +1 -1
- package/dist/mcp/index.mjs +660 -57
- package/dist/mcp/index.mjs.map +1 -1
- package/dist/server/index.d.mts +252 -11
- package/dist/server/index.d.ts +252 -11
- package/dist/server/index.js +2049 -261
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +2049 -261
- package/dist/server/index.mjs.map +1 -1
- package/dist/verify/index.js.map +1 -1
- package/dist/verify/index.mjs.map +1 -1
- package/dist/wallet/index.js.map +1 -1
- package/dist/wallet/index.mjs.map +1 -1
- package/package.json +14 -2
- package/schemas/moltspay.services.schema.json +127 -16
package/dist/mcp/index.js
CHANGED
|
@@ -37,7 +37,8 @@ var import_zod = require("zod");
|
|
|
37
37
|
var import_fs2 = require("fs");
|
|
38
38
|
var import_os3 = require("os");
|
|
39
39
|
var import_path3 = require("path");
|
|
40
|
-
var
|
|
40
|
+
var import_node_crypto3 = require("crypto");
|
|
41
|
+
var import_ethers3 = require("ethers");
|
|
41
42
|
|
|
42
43
|
// src/chains/index.ts
|
|
43
44
|
var CHAINS = {
|
|
@@ -1098,7 +1099,7 @@ var AlipayClient = class {
|
|
|
1098
1099
|
if (m) media.push(m);
|
|
1099
1100
|
else opts.onLine?.(line);
|
|
1100
1101
|
};
|
|
1101
|
-
const intentSummary = opts.intentSummary?.trim() ||
|
|
1102
|
+
const intentSummary = opts.intentSummary?.trim() || `Pay ${requirement.amount ?? ""} ${requirement.asset ?? "CNY"}`.trim();
|
|
1102
1103
|
await timeStep("ensure-cli", flow, () => ensureCli(this.getVersion));
|
|
1103
1104
|
const intentTtlMs = resolveIntentTtlMs();
|
|
1104
1105
|
const intentKey = `${this.configDir}::${this.framework}`;
|
|
@@ -1183,10 +1184,205 @@ var AlipayClient = class {
|
|
|
1183
1184
|
}
|
|
1184
1185
|
};
|
|
1185
1186
|
|
|
1187
|
+
// src/client/wechat/index.ts
|
|
1188
|
+
var import_node_crypto2 = require("crypto");
|
|
1189
|
+
var import_node_fs = require("fs");
|
|
1190
|
+
var import_node_os = require("os");
|
|
1191
|
+
var import_node_path = require("path");
|
|
1192
|
+
var WECHAT_SCHEME = "wechatpay-native";
|
|
1193
|
+
var WECHAT_NETWORK = "wechat";
|
|
1194
|
+
var POLL_INTERVAL_MS2 = 3e3;
|
|
1195
|
+
var TIMEOUT_MS = 5 * 60 * 1e3;
|
|
1196
|
+
var WechatClient = class {
|
|
1197
|
+
configDir;
|
|
1198
|
+
sessionDir;
|
|
1199
|
+
now;
|
|
1200
|
+
constructor(options = {}) {
|
|
1201
|
+
this.configDir = options.configDir || (0, import_node_path.join)((0, import_node_os.homedir)(), ".moltspay");
|
|
1202
|
+
this.sessionDir = (0, import_node_path.join)(this.configDir, "wechat-sessions");
|
|
1203
|
+
this.now = options.now ?? Date.now;
|
|
1204
|
+
}
|
|
1205
|
+
async pay402(opts) {
|
|
1206
|
+
const session = this.start402({ ...opts });
|
|
1207
|
+
const result = await this.pollSession(session.paymentSessionId, {
|
|
1208
|
+
pollIntervalMs: opts.pollIntervalMs,
|
|
1209
|
+
timeoutMs: opts.timeoutMs,
|
|
1210
|
+
signal: opts.signal
|
|
1211
|
+
});
|
|
1212
|
+
if (result.status === "paid" || result.status === "completed") {
|
|
1213
|
+
return {
|
|
1214
|
+
body: result.resultBody ?? "",
|
|
1215
|
+
status: result.lastHttpStatus ?? 200
|
|
1216
|
+
};
|
|
1217
|
+
}
|
|
1218
|
+
throw new Error(result.lastError || `WeChat payment ended with status ${result.status}`);
|
|
1219
|
+
}
|
|
1220
|
+
/**
|
|
1221
|
+
* Start a recoverable WeChat payment session. This returns immediately after
|
|
1222
|
+
* persisting `out_trade_no`, QR payload, original request body, and context.
|
|
1223
|
+
*/
|
|
1224
|
+
start402(opts) {
|
|
1225
|
+
const extra = opts.requirement.extra ?? {};
|
|
1226
|
+
const codeUrl = typeof extra.code_url === "string" ? extra.code_url : "";
|
|
1227
|
+
const outTradeNo = typeof extra.out_trade_no === "string" ? extra.out_trade_no : "";
|
|
1228
|
+
if (!codeUrl || !outTradeNo) {
|
|
1229
|
+
throw new Error(
|
|
1230
|
+
"WechatClient.pay402: wechatpay-native requirement is missing extra.code_url / extra.out_trade_no"
|
|
1231
|
+
);
|
|
1232
|
+
}
|
|
1233
|
+
const now = new Date(this.now());
|
|
1234
|
+
const timeoutMs = opts.timeoutMs ?? (opts.requirement.maxTimeoutSeconds ?? TIMEOUT_MS / 1e3) * 1e3;
|
|
1235
|
+
const session = {
|
|
1236
|
+
paymentSessionId: opts.paymentSessionId ?? `mpay_sess_${(0, import_node_crypto2.randomUUID)()}`,
|
|
1237
|
+
status: "pending",
|
|
1238
|
+
resourceUrl: opts.resourceUrl,
|
|
1239
|
+
method: opts.method ?? "POST",
|
|
1240
|
+
data: opts.data,
|
|
1241
|
+
requirement: opts.requirement,
|
|
1242
|
+
codeUrl,
|
|
1243
|
+
outTradeNo,
|
|
1244
|
+
createdAt: now.toISOString(),
|
|
1245
|
+
updatedAt: now.toISOString(),
|
|
1246
|
+
expiresAt: new Date(this.now() + timeoutMs).toISOString(),
|
|
1247
|
+
context: opts.context
|
|
1248
|
+
};
|
|
1249
|
+
this.saveSession(session);
|
|
1250
|
+
opts.onPaymentPending?.({ codeUrl, outTradeNo });
|
|
1251
|
+
return session;
|
|
1252
|
+
}
|
|
1253
|
+
/**
|
|
1254
|
+
* Query a persisted session once by replaying the original request with the
|
|
1255
|
+
* stored `out_trade_no` proof. 200 means paid + fulfilled; 402 means pending.
|
|
1256
|
+
*/
|
|
1257
|
+
async status(identifier) {
|
|
1258
|
+
const session = this.loadSession(identifier);
|
|
1259
|
+
if (session.status === "cancelled" || session.status === "completed") return session;
|
|
1260
|
+
if (this.now() >= new Date(session.expiresAt).getTime()) {
|
|
1261
|
+
return this.updateSession(session, {
|
|
1262
|
+
status: "expired",
|
|
1263
|
+
lastError: `WeChat payment expired at ${session.expiresAt}`
|
|
1264
|
+
});
|
|
1265
|
+
}
|
|
1266
|
+
const xPayment = this.buildPaymentHeader(session);
|
|
1267
|
+
const res = await fetch(session.resourceUrl, {
|
|
1268
|
+
method: session.method,
|
|
1269
|
+
headers: { "Content-Type": "application/json", "X-Payment": xPayment },
|
|
1270
|
+
body: session.method === "POST" ? session.data : void 0
|
|
1271
|
+
});
|
|
1272
|
+
const text = await res.text().catch(() => "");
|
|
1273
|
+
if (res.status === 200) {
|
|
1274
|
+
return this.updateSession(session, {
|
|
1275
|
+
status: "completed",
|
|
1276
|
+
lastHttpStatus: res.status,
|
|
1277
|
+
lastError: void 0,
|
|
1278
|
+
resultBody: text
|
|
1279
|
+
});
|
|
1280
|
+
}
|
|
1281
|
+
if (res.status === 402) {
|
|
1282
|
+
return this.updateSession(session, {
|
|
1283
|
+
status: "pending",
|
|
1284
|
+
lastHttpStatus: res.status,
|
|
1285
|
+
lastError: text.slice(0, 500) || "wechat payment pending"
|
|
1286
|
+
});
|
|
1287
|
+
}
|
|
1288
|
+
return this.updateSession(session, {
|
|
1289
|
+
status: "failed",
|
|
1290
|
+
lastHttpStatus: res.status,
|
|
1291
|
+
lastError: `WeChat /execute returned HTTP ${res.status}: ${text.slice(0, 500)}`
|
|
1292
|
+
});
|
|
1293
|
+
}
|
|
1294
|
+
/** Poll a session until it completes, expires, fails, or is aborted. */
|
|
1295
|
+
async pollSession(identifier, opts = {}) {
|
|
1296
|
+
const first = this.loadSession(identifier);
|
|
1297
|
+
const deadline = Math.min(
|
|
1298
|
+
new Date(first.expiresAt).getTime(),
|
|
1299
|
+
this.now() + (opts.timeoutMs ?? TIMEOUT_MS)
|
|
1300
|
+
);
|
|
1301
|
+
const interval = opts.pollIntervalMs ?? POLL_INTERVAL_MS2;
|
|
1302
|
+
let session = first;
|
|
1303
|
+
while (this.now() < deadline) {
|
|
1304
|
+
if (opts.signal?.aborted) throw new Error("WeChat payment aborted");
|
|
1305
|
+
session = await this.status(session.paymentSessionId);
|
|
1306
|
+
if (session.status !== "pending") return session;
|
|
1307
|
+
await new Promise((r) => setTimeout(r, interval));
|
|
1308
|
+
}
|
|
1309
|
+
return this.updateSession(session, {
|
|
1310
|
+
status: "expired",
|
|
1311
|
+
lastError: `WeChat payment timed out after ${Math.round((opts.timeoutMs ?? TIMEOUT_MS) / 1e3)}s (out_trade_no=${session.outTradeNo}, last status ${session.lastHttpStatus ?? 0})`
|
|
1312
|
+
});
|
|
1313
|
+
}
|
|
1314
|
+
/** `fulfill` is an idempotent status check: paid sessions return stored body. */
|
|
1315
|
+
async fulfill(identifier) {
|
|
1316
|
+
const session = this.loadSession(identifier);
|
|
1317
|
+
if (session.status === "completed") return session;
|
|
1318
|
+
return this.status(identifier);
|
|
1319
|
+
}
|
|
1320
|
+
/** Mark a local session cancelled. Closing the merchant order is server-side. */
|
|
1321
|
+
cancel(identifier) {
|
|
1322
|
+
const session = this.loadSession(identifier);
|
|
1323
|
+
return this.updateSession(session, {
|
|
1324
|
+
status: "cancelled",
|
|
1325
|
+
lastError: "cancelled locally"
|
|
1326
|
+
});
|
|
1327
|
+
}
|
|
1328
|
+
listSessions() {
|
|
1329
|
+
this.ensureSessionDir();
|
|
1330
|
+
return (0, import_node_fs.readdirSync)(this.sessionDir).filter((name) => name.endsWith(".json")).map((name) => JSON.parse((0, import_node_fs.readFileSync)((0, import_node_path.join)(this.sessionDir, name), "utf-8"))).sort((a, b) => b.createdAt.localeCompare(a.createdAt));
|
|
1331
|
+
}
|
|
1332
|
+
buildPaymentHeader(session) {
|
|
1333
|
+
const proof = {
|
|
1334
|
+
x402Version: 2,
|
|
1335
|
+
scheme: WECHAT_SCHEME,
|
|
1336
|
+
network: WECHAT_NETWORK,
|
|
1337
|
+
accepted: {
|
|
1338
|
+
scheme: WECHAT_SCHEME,
|
|
1339
|
+
network: WECHAT_NETWORK,
|
|
1340
|
+
extra: { out_trade_no: session.outTradeNo }
|
|
1341
|
+
},
|
|
1342
|
+
payload: { out_trade_no: session.outTradeNo }
|
|
1343
|
+
};
|
|
1344
|
+
return Buffer.from(JSON.stringify(proof)).toString("base64");
|
|
1345
|
+
}
|
|
1346
|
+
loadSession(identifier) {
|
|
1347
|
+
this.ensureSessionDir();
|
|
1348
|
+
const direct = (0, import_node_path.join)(this.sessionDir, `${identifier}.json`);
|
|
1349
|
+
try {
|
|
1350
|
+
return JSON.parse((0, import_node_fs.readFileSync)(direct, "utf-8"));
|
|
1351
|
+
} catch {
|
|
1352
|
+
const byTradeNo = this.listSessions().find((s) => s.outTradeNo === identifier);
|
|
1353
|
+
if (byTradeNo) return byTradeNo;
|
|
1354
|
+
throw new Error(`WeChat payment session not found: ${identifier}`);
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
saveSession(session) {
|
|
1358
|
+
this.ensureSessionDir();
|
|
1359
|
+
(0, import_node_fs.writeFileSync)(
|
|
1360
|
+
(0, import_node_path.join)(this.sessionDir, `${session.paymentSessionId}.json`),
|
|
1361
|
+
JSON.stringify(session, null, 2)
|
|
1362
|
+
);
|
|
1363
|
+
}
|
|
1364
|
+
updateSession(session, updates) {
|
|
1365
|
+
const next = {
|
|
1366
|
+
...session,
|
|
1367
|
+
...updates,
|
|
1368
|
+
updatedAt: new Date(this.now()).toISOString()
|
|
1369
|
+
};
|
|
1370
|
+
this.saveSession(next);
|
|
1371
|
+
return next;
|
|
1372
|
+
}
|
|
1373
|
+
ensureSessionDir() {
|
|
1374
|
+
(0, import_node_fs.mkdirSync)(this.sessionDir, { recursive: true });
|
|
1375
|
+
}
|
|
1376
|
+
};
|
|
1377
|
+
|
|
1186
1378
|
// src/client/alipay/router.ts
|
|
1187
1379
|
var ALIPAY_RAIL = "alipay";
|
|
1380
|
+
var WECHAT_RAIL = "wechat";
|
|
1381
|
+
var BALANCE_RAIL = "balance";
|
|
1188
1382
|
function railOf(req) {
|
|
1189
1383
|
if (req.scheme === "alipay-aipay" || req.network === ALIPAY_RAIL) return ALIPAY_RAIL;
|
|
1384
|
+
if (req.scheme === "wechatpay-native" || req.network === WECHAT_RAIL) return WECHAT_RAIL;
|
|
1385
|
+
if (req.scheme === BALANCE_RAIL || req.network === BALANCE_RAIL) return BALANCE_RAIL;
|
|
1190
1386
|
return networkToChainName(req.network) ?? req.network;
|
|
1191
1387
|
}
|
|
1192
1388
|
function findRail(accepts, rail) {
|
|
@@ -1226,6 +1422,14 @@ function selectRail(input) {
|
|
|
1226
1422
|
return { rail: railOf(serverAccepts[0]), requirement: serverAccepts[0] };
|
|
1227
1423
|
}
|
|
1228
1424
|
|
|
1425
|
+
// src/facilitators/balance/auth.ts
|
|
1426
|
+
var import_ethers2 = require("ethers");
|
|
1427
|
+
var BALANCE_AUTH_DOMAIN = "moltspay-balance-auth:v1";
|
|
1428
|
+
var BALANCE_AUTH_MAX_SKEW_MS = 5 * 60 * 1e3;
|
|
1429
|
+
function buildDeductMessage(f) {
|
|
1430
|
+
return [BALANCE_AUTH_DOMAIN, "balance-deduct", f.buyerId, f.requestId, f.service, String(f.timestamp)].join("\n");
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1229
1433
|
// src/client/node/index.ts
|
|
1230
1434
|
var DEFAULT_CONFIG = {
|
|
1231
1435
|
chain: "base",
|
|
@@ -1252,7 +1456,7 @@ var MoltsPayClient = class {
|
|
|
1252
1456
|
this.walletData = this.loadWallet();
|
|
1253
1457
|
this.loadSpending();
|
|
1254
1458
|
if (this.walletData) {
|
|
1255
|
-
this.wallet = new
|
|
1459
|
+
this.wallet = new import_ethers3.Wallet(this.walletData.privateKey);
|
|
1256
1460
|
const configDir = this.configDir;
|
|
1257
1461
|
this.signer = new NodeSigner(this.wallet, {
|
|
1258
1462
|
getSolanaKeypair: () => loadSolanaWallet(configDir)
|
|
@@ -1329,6 +1533,12 @@ var MoltsPayClient = class {
|
|
|
1329
1533
|
if (options.rail === ALIPAY_RAIL) {
|
|
1330
1534
|
return this.payViaAlipay(serverUrl, service, params, options);
|
|
1331
1535
|
}
|
|
1536
|
+
if (options.rail === WECHAT_RAIL) {
|
|
1537
|
+
return this.payViaWechat(serverUrl, service, params, options);
|
|
1538
|
+
}
|
|
1539
|
+
if (options.rail === BALANCE_RAIL) {
|
|
1540
|
+
return this.payViaBalance(serverUrl, service, params, options);
|
|
1541
|
+
}
|
|
1332
1542
|
if (!this.wallet || !this.walletData) {
|
|
1333
1543
|
throw new Error("Client not initialized. Run: npx moltspay init");
|
|
1334
1544
|
}
|
|
@@ -1590,6 +1800,399 @@ Please specify: --chain <chain_name>`
|
|
|
1590
1800
|
return { body: result.body, payment: result.payment, media: result.media };
|
|
1591
1801
|
}
|
|
1592
1802
|
}
|
|
1803
|
+
/**
|
|
1804
|
+
* Pay for a service over the WeChat Pay Native fiat rail (2.1.0).
|
|
1805
|
+
*
|
|
1806
|
+
* Mirrors {@link payViaAlipay} and needs no EVM wallet. The server placed the
|
|
1807
|
+
* Native order when it built the 402, so its `wechatpay-native` accepts[]
|
|
1808
|
+
* entry already carries `extra.code_url` + `extra.out_trade_no`. Flow: hit the
|
|
1809
|
+
* resource to get the 402, confirm the server offers the wechat rail, surface
|
|
1810
|
+
* the code_url (caller renders a QR), then poll the resource with the
|
|
1811
|
+
* out_trade_no proof until the server verifies the order paid and delivers.
|
|
1812
|
+
*/
|
|
1813
|
+
async payViaWechat(serverUrl, service, params, options) {
|
|
1814
|
+
const session = await this.startWechatPayment(serverUrl, service, params, options);
|
|
1815
|
+
const wechat = new WechatClient({ configDir: this.configDir });
|
|
1816
|
+
const result = await wechat.pollSession(session.paymentSessionId, {
|
|
1817
|
+
timeoutMs: options.timeoutMs,
|
|
1818
|
+
signal: options.signal
|
|
1819
|
+
});
|
|
1820
|
+
if (result.status !== "completed") {
|
|
1821
|
+
throw new Error(result.lastError || `WeChat payment ended with status ${result.status}`);
|
|
1822
|
+
}
|
|
1823
|
+
try {
|
|
1824
|
+
const json = JSON.parse(result.resultBody ?? "");
|
|
1825
|
+
return json.result ?? json;
|
|
1826
|
+
} catch {
|
|
1827
|
+
return { body: result.resultBody ?? "" };
|
|
1828
|
+
}
|
|
1829
|
+
}
|
|
1830
|
+
/**
|
|
1831
|
+
* The ethers wallet used to sign balance-rail deductions: the client's EVM
|
|
1832
|
+
* wallet if it has one, else a per-configDir identity key persisted at
|
|
1833
|
+
* `<configDir>/balance-identity.key` (0600) so a balance-only client works
|
|
1834
|
+
* without a full crypto wallet. Under `agent 统一代付`, one key spends every
|
|
1835
|
+
* account the agent tops up.
|
|
1836
|
+
*/
|
|
1837
|
+
balanceSigner() {
|
|
1838
|
+
if (this.wallet) return this.wallet;
|
|
1839
|
+
const p = (0, import_path3.join)(this.configDir, "balance-identity.key");
|
|
1840
|
+
let pk;
|
|
1841
|
+
if ((0, import_fs2.existsSync)(p)) {
|
|
1842
|
+
pk = (0, import_fs2.readFileSync)(p, "utf-8").trim();
|
|
1843
|
+
} else {
|
|
1844
|
+
(0, import_fs2.mkdirSync)(this.configDir, { recursive: true });
|
|
1845
|
+
pk = import_ethers3.Wallet.createRandom().privateKey;
|
|
1846
|
+
(0, import_fs2.writeFileSync)(p, pk, { mode: 384 });
|
|
1847
|
+
}
|
|
1848
|
+
return new import_ethers3.Wallet(pk);
|
|
1849
|
+
}
|
|
1850
|
+
/** The balance-rail spending signer address (lowercase 0x…). Stable per
|
|
1851
|
+
* configDir; this is the identity the server TOFU-binds and later verifies. */
|
|
1852
|
+
getBalanceSignerAddress() {
|
|
1853
|
+
return this.balanceSigner().address.toLowerCase();
|
|
1854
|
+
}
|
|
1855
|
+
/** The buyer id for the balance rail: explicit option > persisted config. */
|
|
1856
|
+
resolveBuyerId(explicit) {
|
|
1857
|
+
const buyerId = explicit ?? this.config.buyerId;
|
|
1858
|
+
if (!buyerId) {
|
|
1859
|
+
throw new Error(
|
|
1860
|
+
"Balance rail needs a buyer id. Pass { buyerId } or persist one with setBuyerId()."
|
|
1861
|
+
);
|
|
1862
|
+
}
|
|
1863
|
+
return buyerId;
|
|
1864
|
+
}
|
|
1865
|
+
/**
|
|
1866
|
+
* Pay via the custodial balance rail (2.2.0, password-free).
|
|
1867
|
+
*
|
|
1868
|
+
* No wallet, no QR: the request carries `{buyer_id, request_id}` in the
|
|
1869
|
+
* X-Payment payload and the server deducts the prepaid balance atomically
|
|
1870
|
+
* before running the skill. The client-generated `request_id` makes the
|
|
1871
|
+
* charge idempotent — a network retry can never double-deduct.
|
|
1872
|
+
*/
|
|
1873
|
+
async payViaBalance(serverUrl, service, params, options) {
|
|
1874
|
+
const buyerId = this.resolveBuyerId(options.buyerId);
|
|
1875
|
+
let attempt = await this.balanceDeduct(serverUrl, service, params, options, buyerId);
|
|
1876
|
+
if (attempt.ok) return attempt.result;
|
|
1877
|
+
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 || ""));
|
|
1878
|
+
if (!fundable || options.autoTopup === false) {
|
|
1879
|
+
throw new Error(attempt.error || `Balance payment failed with HTTP ${attempt.status}`);
|
|
1880
|
+
}
|
|
1881
|
+
if (options.topupMode === "manual") {
|
|
1882
|
+
const order = await this.createBalanceTopupOrder(serverUrl, {
|
|
1883
|
+
pack: options.topupPack,
|
|
1884
|
+
buyerId,
|
|
1885
|
+
context: { service }
|
|
1886
|
+
});
|
|
1887
|
+
options.onTopupRequired?.(order.pack, order.codeUrl);
|
|
1888
|
+
return {
|
|
1889
|
+
status: "topup_required",
|
|
1890
|
+
out_trade_no: order.outTradeNo,
|
|
1891
|
+
code_url: order.codeUrl,
|
|
1892
|
+
pack: order.pack,
|
|
1893
|
+
server_url: serverUrl
|
|
1894
|
+
};
|
|
1895
|
+
}
|
|
1896
|
+
const credited = await this.topupBalancePack(serverUrl, {
|
|
1897
|
+
pack: options.topupPack,
|
|
1898
|
+
buyerId,
|
|
1899
|
+
pollIntervalMs: options.topupPollIntervalMs,
|
|
1900
|
+
signal: options.signal,
|
|
1901
|
+
onCodeUrl: (pack, codeUrl) => options.onTopupRequired?.(pack, codeUrl)
|
|
1902
|
+
});
|
|
1903
|
+
options.onTopupCredited?.(credited.balance);
|
|
1904
|
+
attempt = await this.balanceDeduct(serverUrl, service, params, options, buyerId);
|
|
1905
|
+
if (attempt.ok) return attempt.result;
|
|
1906
|
+
throw new Error(attempt.error || "Balance payment failed after top-up");
|
|
1907
|
+
}
|
|
1908
|
+
/** One password-free deduct attempt. Never throws on an HTTP error; the
|
|
1909
|
+
* caller inspects `{ ok, status, error }` to decide whether to auto-fund. */
|
|
1910
|
+
async balanceDeduct(serverUrl, service, params, options, buyerId) {
|
|
1911
|
+
let executeUrl = `${serverUrl}/execute`;
|
|
1912
|
+
try {
|
|
1913
|
+
const services = await this.getServices(serverUrl);
|
|
1914
|
+
const svc = services.services?.find((s) => s.id === service);
|
|
1915
|
+
if (svc?.endpoint) executeUrl = `${serverUrl}${svc.endpoint}`;
|
|
1916
|
+
} catch {
|
|
1917
|
+
}
|
|
1918
|
+
const requestBody = options.rawData ? { service, ...params } : { service, params };
|
|
1919
|
+
const requestId = (0, import_node_crypto3.randomUUID)();
|
|
1920
|
+
const timestamp = Math.floor(Date.now() / 1e3);
|
|
1921
|
+
const signature = await this.balanceSigner().signMessage(
|
|
1922
|
+
buildDeductMessage({ buyerId, requestId, service, timestamp })
|
|
1923
|
+
);
|
|
1924
|
+
const xPayment = Buffer.from(JSON.stringify({
|
|
1925
|
+
x402Version: 2,
|
|
1926
|
+
scheme: BALANCE_RAIL,
|
|
1927
|
+
network: BALANCE_RAIL,
|
|
1928
|
+
payload: { buyer_id: buyerId, request_id: requestId, auth: { timestamp, signature } }
|
|
1929
|
+
})).toString("base64");
|
|
1930
|
+
const res = await fetch(executeUrl, {
|
|
1931
|
+
method: "POST",
|
|
1932
|
+
headers: { "Content-Type": "application/json", "X-Payment": xPayment },
|
|
1933
|
+
body: JSON.stringify(requestBody),
|
|
1934
|
+
signal: options.signal
|
|
1935
|
+
});
|
|
1936
|
+
const data = await res.json().catch(() => ({}));
|
|
1937
|
+
if (!res.ok) return { ok: false, status: res.status, error: data.error, code: data.code };
|
|
1938
|
+
return { ok: true, status: res.status, result: data.result ?? data };
|
|
1939
|
+
}
|
|
1940
|
+
/**
|
|
1941
|
+
* Non-blocking: POST /balance/topup/order, persist a recoverable session, and
|
|
1942
|
+
* return at once (no polling). Use with {@link confirmBalanceTopup} for
|
|
1943
|
+
* turn-based agents; the blocking {@link topupBalancePack} is built on this.
|
|
1944
|
+
*/
|
|
1945
|
+
async createBalanceTopupOrder(serverUrl, opts = {}) {
|
|
1946
|
+
const id = this.resolveBuyerId(opts.buyerId);
|
|
1947
|
+
const orderRes = await fetch(`${serverUrl}/balance/topup/order`, {
|
|
1948
|
+
method: "POST",
|
|
1949
|
+
headers: { "Content-Type": "application/json" },
|
|
1950
|
+
// Carry the spending signer so the server binds it to the account on
|
|
1951
|
+
// confirm (topup-time binding). The same key signs deductions later.
|
|
1952
|
+
body: JSON.stringify({ buyer_id: id, pack: opts.pack, signer_address: this.getBalanceSignerAddress() })
|
|
1953
|
+
});
|
|
1954
|
+
const order = await orderRes.json().catch(() => ({}));
|
|
1955
|
+
if (!orderRes.ok) throw new Error(order.error || `Top-up order failed with HTTP ${orderRes.status}`);
|
|
1956
|
+
const maxTimeoutSeconds = order.max_timeout_seconds ?? 300;
|
|
1957
|
+
const now = Date.now();
|
|
1958
|
+
this.saveBalanceTopupSession({
|
|
1959
|
+
out_trade_no: order.out_trade_no,
|
|
1960
|
+
buyer_id: id,
|
|
1961
|
+
pack: order.pack,
|
|
1962
|
+
server_url: serverUrl,
|
|
1963
|
+
code_url: order.code_url,
|
|
1964
|
+
status: "pending",
|
|
1965
|
+
created_at: new Date(now).toISOString(),
|
|
1966
|
+
expires_at: new Date(now + maxTimeoutSeconds * 1e3).toISOString(),
|
|
1967
|
+
context: opts.context
|
|
1968
|
+
});
|
|
1969
|
+
return { outTradeNo: order.out_trade_no, codeUrl: order.code_url, pack: order.pack, maxTimeoutSeconds };
|
|
1970
|
+
}
|
|
1971
|
+
/**
|
|
1972
|
+
* One-shot: POST /balance/topup/confirm for a single order. No polling.
|
|
1973
|
+
* Updates the persisted session on credit. `serverUrl` defaults to the one
|
|
1974
|
+
* recorded in the session (recover by out_trade_no alone).
|
|
1975
|
+
*/
|
|
1976
|
+
async confirmBalanceTopup(outTradeNo, opts = {}) {
|
|
1977
|
+
const session = this.getBalanceTopupSession(outTradeNo);
|
|
1978
|
+
const serverUrl = opts.serverUrl || session?.server_url;
|
|
1979
|
+
if (!serverUrl) {
|
|
1980
|
+
return { credited: false, reason: `No server URL for ${outTradeNo}: pass serverUrl or run topup-order first` };
|
|
1981
|
+
}
|
|
1982
|
+
const confRes = await fetch(`${serverUrl}/balance/topup/confirm`, {
|
|
1983
|
+
method: "POST",
|
|
1984
|
+
headers: { "Content-Type": "application/json" },
|
|
1985
|
+
body: JSON.stringify({ out_trade_no: outTradeNo })
|
|
1986
|
+
});
|
|
1987
|
+
const conf = await confRes.json().catch(() => ({}));
|
|
1988
|
+
if (!confRes.ok) return { credited: false, reason: conf.error || `Confirm failed with HTTP ${confRes.status}` };
|
|
1989
|
+
if (conf.credited) {
|
|
1990
|
+
if (session) {
|
|
1991
|
+
session.status = "credited";
|
|
1992
|
+
session.tx_id = conf.tx_id;
|
|
1993
|
+
session.balance = conf.balance;
|
|
1994
|
+
this.saveBalanceTopupSession(session);
|
|
1995
|
+
}
|
|
1996
|
+
return { credited: true, balance: conf.balance, txId: conf.tx_id };
|
|
1997
|
+
}
|
|
1998
|
+
return { credited: false, pending: !!conf.pending, reason: conf.reason };
|
|
1999
|
+
}
|
|
2000
|
+
/**
|
|
2001
|
+
* Blocking terminal wrapper: create the order then poll confirm until the
|
|
2002
|
+
* scan is credited (or the order expires). Built on
|
|
2003
|
+
* {@link createBalanceTopupOrder} + {@link confirmBalanceTopup}.
|
|
2004
|
+
*/
|
|
2005
|
+
async topupBalancePack(serverUrl, opts = {}) {
|
|
2006
|
+
const order = await this.createBalanceTopupOrder(serverUrl, { pack: opts.pack, buyerId: opts.buyerId });
|
|
2007
|
+
opts.onCodeUrl?.(order.pack, order.codeUrl);
|
|
2008
|
+
const interval = opts.pollIntervalMs ?? 2e3;
|
|
2009
|
+
const deadline = Date.now() + order.maxTimeoutSeconds * 1e3;
|
|
2010
|
+
while (Date.now() < deadline) {
|
|
2011
|
+
if (opts.signal?.aborted) throw new Error("Top-up aborted");
|
|
2012
|
+
const conf = await this.confirmBalanceTopup(order.outTradeNo, { serverUrl });
|
|
2013
|
+
if (conf.credited) return { balance: conf.balance, outTradeNo: order.outTradeNo, txId: conf.txId };
|
|
2014
|
+
await this.sleep(interval, opts.signal);
|
|
2015
|
+
}
|
|
2016
|
+
throw new Error("Top-up timed out before the payment was confirmed");
|
|
2017
|
+
}
|
|
2018
|
+
// --- Recoverable balance top-up sessions (<configDir>/balance-topup-sessions) ---
|
|
2019
|
+
balanceTopupSessionDir() {
|
|
2020
|
+
return (0, import_path3.join)(this.configDir, "balance-topup-sessions");
|
|
2021
|
+
}
|
|
2022
|
+
saveBalanceTopupSession(session) {
|
|
2023
|
+
const dir = this.balanceTopupSessionDir();
|
|
2024
|
+
(0, import_fs2.mkdirSync)(dir, { recursive: true });
|
|
2025
|
+
(0, import_fs2.writeFileSync)((0, import_path3.join)(dir, `${session.out_trade_no}.json`), JSON.stringify(session, null, 2));
|
|
2026
|
+
}
|
|
2027
|
+
/** Read a persisted top-up session by out_trade_no, or null. */
|
|
2028
|
+
getBalanceTopupSession(outTradeNo) {
|
|
2029
|
+
const p = (0, import_path3.join)(this.balanceTopupSessionDir(), `${outTradeNo}.json`);
|
|
2030
|
+
if (!(0, import_fs2.existsSync)(p)) return null;
|
|
2031
|
+
try {
|
|
2032
|
+
return JSON.parse((0, import_fs2.readFileSync)(p, "utf-8"));
|
|
2033
|
+
} catch {
|
|
2034
|
+
return null;
|
|
2035
|
+
}
|
|
2036
|
+
}
|
|
2037
|
+
/** List persisted top-up sessions, newest first. */
|
|
2038
|
+
listBalanceTopupSessions() {
|
|
2039
|
+
const dir = this.balanceTopupSessionDir();
|
|
2040
|
+
if (!(0, import_fs2.existsSync)(dir)) return [];
|
|
2041
|
+
return (0, import_fs2.readdirSync)(dir).filter((f) => f.endsWith(".json")).map((f) => {
|
|
2042
|
+
try {
|
|
2043
|
+
return JSON.parse((0, import_fs2.readFileSync)((0, import_path3.join)(dir, f), "utf-8"));
|
|
2044
|
+
} catch {
|
|
2045
|
+
return null;
|
|
2046
|
+
}
|
|
2047
|
+
}).filter((s) => s !== null).sort((a, b) => (b.created_at || "").localeCompare(a.created_at || ""));
|
|
2048
|
+
}
|
|
2049
|
+
/** Abortable sleep used by the top-up poll loop. */
|
|
2050
|
+
sleep(ms, signal) {
|
|
2051
|
+
return new Promise((resolve, reject) => {
|
|
2052
|
+
if (signal?.aborted) return reject(new Error("aborted"));
|
|
2053
|
+
const t = setTimeout(resolve, ms);
|
|
2054
|
+
signal?.addEventListener("abort", () => {
|
|
2055
|
+
clearTimeout(t);
|
|
2056
|
+
reject(new Error("aborted"));
|
|
2057
|
+
}, { once: true });
|
|
2058
|
+
});
|
|
2059
|
+
}
|
|
2060
|
+
/** Persist the buyer id used by the balance rail (bearer semantics). */
|
|
2061
|
+
setBuyerId(buyerId) {
|
|
2062
|
+
this.config.buyerId = buyerId;
|
|
2063
|
+
this.saveConfig();
|
|
2064
|
+
}
|
|
2065
|
+
/** GET /balance — custodial balance, limits, and today's spend for a buyer. */
|
|
2066
|
+
async getBuyerBalance(serverUrl, buyerId) {
|
|
2067
|
+
const id = this.resolveBuyerId(buyerId);
|
|
2068
|
+
const res = await fetch(`${serverUrl}/balance?buyer_id=${encodeURIComponent(id)}`);
|
|
2069
|
+
const data = await res.json().catch(() => ({}));
|
|
2070
|
+
if (!res.ok) throw new Error(data.error || `Balance query failed with HTTP ${res.status}`);
|
|
2071
|
+
return data;
|
|
2072
|
+
}
|
|
2073
|
+
/**
|
|
2074
|
+
* POST /balance/topup — report an externally settled payment (on-chain
|
|
2075
|
+
* tx hash / Alipay trade_no / WeChat out_trade_no) so the server verifies
|
|
2076
|
+
* and credits the ledger. Idempotent per reference.
|
|
2077
|
+
*/
|
|
2078
|
+
async topupBalance(serverUrl, opts) {
|
|
2079
|
+
const id = this.resolveBuyerId(opts.buyerId);
|
|
2080
|
+
const res = await fetch(`${serverUrl}/balance/topup`, {
|
|
2081
|
+
method: "POST",
|
|
2082
|
+
headers: { "Content-Type": "application/json" },
|
|
2083
|
+
body: JSON.stringify({
|
|
2084
|
+
buyer_id: id,
|
|
2085
|
+
rail: opts.rail,
|
|
2086
|
+
amount: opts.amount,
|
|
2087
|
+
tx_hash: opts.txHash,
|
|
2088
|
+
chain: opts.chain,
|
|
2089
|
+
trade_no: opts.tradeNo,
|
|
2090
|
+
out_trade_no: opts.outTradeNo
|
|
2091
|
+
})
|
|
2092
|
+
});
|
|
2093
|
+
const data = await res.json().catch(() => ({}));
|
|
2094
|
+
if (!res.ok) throw new Error(data.error || `Top-up failed with HTTP ${res.status}`);
|
|
2095
|
+
return data;
|
|
2096
|
+
}
|
|
2097
|
+
/** GET /balance/transactions — ledger history for a buyer, newest first. */
|
|
2098
|
+
async listBalanceTransactions(serverUrl, opts = {}) {
|
|
2099
|
+
const id = this.resolveBuyerId(opts.buyerId);
|
|
2100
|
+
const qs = new URLSearchParams({ buyer_id: id });
|
|
2101
|
+
if (opts.limit) qs.set("limit", String(opts.limit));
|
|
2102
|
+
if (opts.offset) qs.set("offset", String(opts.offset));
|
|
2103
|
+
const res = await fetch(`${serverUrl}/balance/transactions?${qs}`);
|
|
2104
|
+
const data = await res.json().catch(() => ({}));
|
|
2105
|
+
if (!res.ok) throw new Error(data.error || `Transaction query failed with HTTP ${res.status}`);
|
|
2106
|
+
return data;
|
|
2107
|
+
}
|
|
2108
|
+
/**
|
|
2109
|
+
* Start a recoverable WeChat Pay Native session and return immediately with
|
|
2110
|
+
* QR metadata. The SDK client persists enough context to poll/fulfill later.
|
|
2111
|
+
*/
|
|
2112
|
+
async startWechatPayment(serverUrl, service, params, options = {}) {
|
|
2113
|
+
let executeUrl = `${serverUrl}/execute`;
|
|
2114
|
+
try {
|
|
2115
|
+
const services = await this.getServices(serverUrl);
|
|
2116
|
+
const svc = services.services?.find((s) => s.id === service);
|
|
2117
|
+
if (svc?.endpoint) executeUrl = `${serverUrl}${svc.endpoint}`;
|
|
2118
|
+
} catch {
|
|
2119
|
+
}
|
|
2120
|
+
const requestBody = options.rawData ? { service, ...params } : { service, params };
|
|
2121
|
+
const bodyJson = JSON.stringify(requestBody);
|
|
2122
|
+
const res = await fetch(executeUrl, {
|
|
2123
|
+
method: "POST",
|
|
2124
|
+
headers: { "Content-Type": "application/json" },
|
|
2125
|
+
body: bodyJson
|
|
2126
|
+
});
|
|
2127
|
+
if (res.status !== 402) {
|
|
2128
|
+
const data = await res.json().catch(() => ({}));
|
|
2129
|
+
if (res.ok && data.result) return data.result;
|
|
2130
|
+
throw new Error(data.error || `Expected 402, got ${res.status}`);
|
|
2131
|
+
}
|
|
2132
|
+
const header = res.headers.get(PAYMENT_REQUIRED_HEADER);
|
|
2133
|
+
if (!header) throw new Error("Missing x-payment-required header on 402");
|
|
2134
|
+
const parsed = JSON.parse(Buffer.from(header, "base64").toString("utf-8"));
|
|
2135
|
+
const accepts = Array.isArray(parsed) ? parsed : parsed.accepts ?? [parsed];
|
|
2136
|
+
const { requirement } = selectRail({
|
|
2137
|
+
serverAccepts: accepts,
|
|
2138
|
+
explicitRail: WECHAT_RAIL,
|
|
2139
|
+
preference: this.railPreference,
|
|
2140
|
+
availability: { evmReady: this.isInitialized }
|
|
2141
|
+
});
|
|
2142
|
+
const wechat = new WechatClient({ configDir: this.configDir });
|
|
2143
|
+
const session = wechat.start402({
|
|
2144
|
+
resourceUrl: executeUrl,
|
|
2145
|
+
requirement,
|
|
2146
|
+
method: "POST",
|
|
2147
|
+
data: bodyJson,
|
|
2148
|
+
onPaymentPending: options.onPaymentPending ? (info) => options.onPaymentPending({ paymentUrl: info.codeUrl, tradeNo: info.outTradeNo }) : void 0,
|
|
2149
|
+
timeoutMs: options.timeoutMs,
|
|
2150
|
+
signal: options.signal,
|
|
2151
|
+
context: {
|
|
2152
|
+
serverUrl,
|
|
2153
|
+
service,
|
|
2154
|
+
params,
|
|
2155
|
+
rawData: options.rawData ?? false,
|
|
2156
|
+
rail: WECHAT_RAIL
|
|
2157
|
+
}
|
|
2158
|
+
});
|
|
2159
|
+
if (options.autoPoll || options.onWechatPaymentCompleted || options.onWechatPaymentFailed) {
|
|
2160
|
+
void wechat.pollSession(session.paymentSessionId, {
|
|
2161
|
+
timeoutMs: options.timeoutMs,
|
|
2162
|
+
signal: options.signal
|
|
2163
|
+
}).then(async (finalSession) => {
|
|
2164
|
+
if (finalSession.status === "completed") {
|
|
2165
|
+
await options.onWechatPaymentCompleted?.(finalSession);
|
|
2166
|
+
} else {
|
|
2167
|
+
await options.onWechatPaymentFailed?.(finalSession);
|
|
2168
|
+
}
|
|
2169
|
+
}).catch(async (error) => {
|
|
2170
|
+
const failed = await wechat.fulfill(session.paymentSessionId).catch(() => ({
|
|
2171
|
+
...session,
|
|
2172
|
+
status: "failed",
|
|
2173
|
+
lastError: error instanceof Error ? error.message : String(error)
|
|
2174
|
+
}));
|
|
2175
|
+
await options.onWechatPaymentFailed?.(failed);
|
|
2176
|
+
});
|
|
2177
|
+
}
|
|
2178
|
+
return session;
|
|
2179
|
+
}
|
|
2180
|
+
/** Query a persisted WeChat session once. */
|
|
2181
|
+
async getWechatPaymentStatus(identifier) {
|
|
2182
|
+
return new WechatClient({ configDir: this.configDir }).status(identifier);
|
|
2183
|
+
}
|
|
2184
|
+
/** Idempotently fulfill a paid WeChat session, returning stored or fetched result. */
|
|
2185
|
+
async fulfillWechatPayment(identifier) {
|
|
2186
|
+
return new WechatClient({ configDir: this.configDir }).fulfill(identifier);
|
|
2187
|
+
}
|
|
2188
|
+
/** Mark a local WeChat session as cancelled. */
|
|
2189
|
+
cancelWechatPayment(identifier) {
|
|
2190
|
+
return new WechatClient({ configDir: this.configDir }).cancel(identifier);
|
|
2191
|
+
}
|
|
2192
|
+
/** List persisted WeChat sessions, newest first. */
|
|
2193
|
+
listWechatPaymentSessions() {
|
|
2194
|
+
return new WechatClient({ configDir: this.configDir }).listSessions();
|
|
2195
|
+
}
|
|
1593
2196
|
/**
|
|
1594
2197
|
* Handle MPP (Machine Payments Protocol) payment flow
|
|
1595
2198
|
* Called when pay() detects WWW-Authenticate header in 402 response
|
|
@@ -1685,14 +2288,14 @@ Please specify: --chain <chain_name>`
|
|
|
1685
2288
|
async handleBNBPayment(executeUrl, service, params, paymentDetails, options = {}) {
|
|
1686
2289
|
const { to, amount, token, chainName, chain, spender } = paymentDetails;
|
|
1687
2290
|
const tokenConfig = chain.tokens[token];
|
|
1688
|
-
const provider = new
|
|
2291
|
+
const provider = new import_ethers3.ethers.JsonRpcProvider(chain.rpc);
|
|
1689
2292
|
const allowance = await this.checkAllowance(tokenConfig.address, spender, provider);
|
|
1690
2293
|
const amountWeiCheck = BigInt(Math.floor(amount * 10 ** tokenConfig.decimals));
|
|
1691
2294
|
if (allowance < amountWeiCheck) {
|
|
1692
2295
|
const nativeBalance = await provider.getBalance(this.wallet.address);
|
|
1693
|
-
const minGasBalance =
|
|
2296
|
+
const minGasBalance = import_ethers3.ethers.parseEther("0.0005");
|
|
1694
2297
|
if (nativeBalance < minGasBalance) {
|
|
1695
|
-
const nativeBNB = parseFloat(
|
|
2298
|
+
const nativeBNB = parseFloat(import_ethers3.ethers.formatEther(nativeBalance)).toFixed(4);
|
|
1696
2299
|
const isTestnet = chainName === "bnb_testnet";
|
|
1697
2300
|
if (isTestnet) {
|
|
1698
2301
|
throw new Error(
|
|
@@ -1865,7 +2468,7 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
|
|
|
1865
2468
|
* Check ERC20 allowance for a spender
|
|
1866
2469
|
*/
|
|
1867
2470
|
async checkAllowance(tokenAddress, spender, provider) {
|
|
1868
|
-
const contract = new
|
|
2471
|
+
const contract = new import_ethers3.ethers.Contract(
|
|
1869
2472
|
tokenAddress,
|
|
1870
2473
|
["function allowance(address owner, address spender) view returns (uint256)"],
|
|
1871
2474
|
provider
|
|
@@ -1884,7 +2487,7 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
|
|
|
1884
2487
|
async signEIP3009(to, amount, chain, token = "USDC", domainOverride) {
|
|
1885
2488
|
const tokenConfig = chain.tokens[token];
|
|
1886
2489
|
const value = BigInt(Math.floor(amount * 10 ** tokenConfig.decimals)).toString();
|
|
1887
|
-
const nonce =
|
|
2490
|
+
const nonce = import_ethers3.ethers.hexlify(import_ethers3.ethers.randomBytes(32));
|
|
1888
2491
|
const tokenName = domainOverride?.name || tokenConfig.eip712Name || (token === "USDC" ? "USD Coin" : "Tether USD");
|
|
1889
2492
|
const tokenVersion = domainOverride?.version || "2";
|
|
1890
2493
|
const envelope = buildEIP3009TypedData({
|
|
@@ -2002,7 +2605,7 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
|
|
|
2002
2605
|
*/
|
|
2003
2606
|
static init(configDir, options) {
|
|
2004
2607
|
(0, import_fs2.mkdirSync)(configDir, { recursive: true });
|
|
2005
|
-
const wallet =
|
|
2608
|
+
const wallet = import_ethers3.Wallet.createRandom();
|
|
2006
2609
|
const walletData = {
|
|
2007
2610
|
address: wallet.address,
|
|
2008
2611
|
privateKey: wallet.privateKey,
|
|
@@ -2034,17 +2637,17 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
|
|
|
2034
2637
|
} catch {
|
|
2035
2638
|
throw new Error(`Unknown chain: ${this.config.chain}`);
|
|
2036
2639
|
}
|
|
2037
|
-
const provider = new
|
|
2640
|
+
const provider = new import_ethers3.ethers.JsonRpcProvider(chain.rpc);
|
|
2038
2641
|
const tokenAbi = ["function balanceOf(address) view returns (uint256)"];
|
|
2039
2642
|
const [nativeBalance, usdcBalance, usdtBalance] = await Promise.all([
|
|
2040
2643
|
provider.getBalance(this.wallet.address),
|
|
2041
|
-
new
|
|
2042
|
-
new
|
|
2644
|
+
new import_ethers3.ethers.Contract(chain.tokens.USDC.address, tokenAbi, provider).balanceOf(this.wallet.address),
|
|
2645
|
+
new import_ethers3.ethers.Contract(chain.tokens.USDT.address, tokenAbi, provider).balanceOf(this.wallet.address)
|
|
2043
2646
|
]);
|
|
2044
2647
|
return {
|
|
2045
|
-
usdc: parseFloat(
|
|
2046
|
-
usdt: parseFloat(
|
|
2047
|
-
native: parseFloat(
|
|
2648
|
+
usdc: parseFloat(import_ethers3.ethers.formatUnits(usdcBalance, chain.tokens.USDC.decimals)),
|
|
2649
|
+
usdt: parseFloat(import_ethers3.ethers.formatUnits(usdtBalance, chain.tokens.USDT.decimals)),
|
|
2650
|
+
native: parseFloat(import_ethers3.ethers.formatEther(nativeBalance))
|
|
2048
2651
|
};
|
|
2049
2652
|
}
|
|
2050
2653
|
/**
|
|
@@ -2067,38 +2670,38 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
|
|
|
2067
2670
|
supportedChains.map(async (chainName) => {
|
|
2068
2671
|
try {
|
|
2069
2672
|
const chain = getChain(chainName);
|
|
2070
|
-
const provider = new
|
|
2673
|
+
const provider = new import_ethers3.ethers.JsonRpcProvider(chain.rpc);
|
|
2071
2674
|
if (chainName === "tempo_moderato") {
|
|
2072
2675
|
const [nativeBalance, pathUSD, alphaUSD, betaUSD, thetaUSD] = await Promise.all([
|
|
2073
2676
|
provider.getBalance(this.wallet.address),
|
|
2074
|
-
new
|
|
2075
|
-
new
|
|
2076
|
-
new
|
|
2077
|
-
new
|
|
2677
|
+
new import_ethers3.ethers.Contract(tempoTokens.pathUSD, tokenAbi, provider).balanceOf(this.wallet.address),
|
|
2678
|
+
new import_ethers3.ethers.Contract(tempoTokens.alphaUSD, tokenAbi, provider).balanceOf(this.wallet.address),
|
|
2679
|
+
new import_ethers3.ethers.Contract(tempoTokens.betaUSD, tokenAbi, provider).balanceOf(this.wallet.address),
|
|
2680
|
+
new import_ethers3.ethers.Contract(tempoTokens.thetaUSD, tokenAbi, provider).balanceOf(this.wallet.address)
|
|
2078
2681
|
]);
|
|
2079
2682
|
results[chainName] = {
|
|
2080
|
-
usdc: parseFloat(
|
|
2683
|
+
usdc: parseFloat(import_ethers3.ethers.formatUnits(pathUSD, 6)),
|
|
2081
2684
|
// pathUSD as default USDC
|
|
2082
|
-
usdt: parseFloat(
|
|
2685
|
+
usdt: parseFloat(import_ethers3.ethers.formatUnits(alphaUSD, 6)),
|
|
2083
2686
|
// alphaUSD as default USDT
|
|
2084
|
-
native: parseFloat(
|
|
2687
|
+
native: parseFloat(import_ethers3.ethers.formatEther(nativeBalance)),
|
|
2085
2688
|
tempo: {
|
|
2086
|
-
pathUSD: parseFloat(
|
|
2087
|
-
alphaUSD: parseFloat(
|
|
2088
|
-
betaUSD: parseFloat(
|
|
2089
|
-
thetaUSD: parseFloat(
|
|
2689
|
+
pathUSD: parseFloat(import_ethers3.ethers.formatUnits(pathUSD, 6)),
|
|
2690
|
+
alphaUSD: parseFloat(import_ethers3.ethers.formatUnits(alphaUSD, 6)),
|
|
2691
|
+
betaUSD: parseFloat(import_ethers3.ethers.formatUnits(betaUSD, 6)),
|
|
2692
|
+
thetaUSD: parseFloat(import_ethers3.ethers.formatUnits(thetaUSD, 6))
|
|
2090
2693
|
}
|
|
2091
2694
|
};
|
|
2092
2695
|
} else {
|
|
2093
2696
|
const [nativeBalance, usdcBalance, usdtBalance] = await Promise.all([
|
|
2094
2697
|
provider.getBalance(this.wallet.address),
|
|
2095
|
-
new
|
|
2096
|
-
new
|
|
2698
|
+
new import_ethers3.ethers.Contract(chain.tokens.USDC.address, tokenAbi, provider).balanceOf(this.wallet.address),
|
|
2699
|
+
new import_ethers3.ethers.Contract(chain.tokens.USDT.address, tokenAbi, provider).balanceOf(this.wallet.address)
|
|
2097
2700
|
]);
|
|
2098
2701
|
results[chainName] = {
|
|
2099
|
-
usdc: parseFloat(
|
|
2100
|
-
usdt: parseFloat(
|
|
2101
|
-
native: parseFloat(
|
|
2702
|
+
usdc: parseFloat(import_ethers3.ethers.formatUnits(usdcBalance, chain.tokens.USDC.decimals)),
|
|
2703
|
+
usdt: parseFloat(import_ethers3.ethers.formatUnits(usdtBalance, chain.tokens.USDT.decimals)),
|
|
2704
|
+
native: parseFloat(import_ethers3.ethers.formatEther(nativeBalance))
|
|
2102
2705
|
};
|
|
2103
2706
|
}
|
|
2104
2707
|
} catch (err2) {
|