@aeon-ai-pay/aigateway 0.1.3 → 0.1.5
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/CHANGELOG.md +46 -8
- package/bin/cli.mjs +3 -2
- package/package.json +1 -1
- package/scripts/postinstall.mjs +6 -6
- package/skills/aigateway/SKILL.md +65 -27
- package/src/balance.mjs +7 -7
- package/src/commands/clean.mjs +5 -5
- package/src/commands/create-card.mjs +26 -7
- package/src/commands/create-image.mjs +5 -5
- package/src/commands/wallet-gas.mjs +2 -1
- package/src/commands/wallet-init.mjs +20 -19
- package/src/commands/wallet-topup.mjs +15 -15
- package/src/commands/wallet-withdraw.mjs +7 -7
- package/src/config.mjs +6 -5
- package/src/error-codes.mjs +11 -11
- package/src/output.mjs +19 -16
- package/src/sanitize.mjs +11 -11
- package/src/update-check.mjs +11 -11
- package/src/walletconnect.mjs +65 -63
- package/src/x402.mjs +13 -10
package/src/walletconnect.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* WalletConnect v2
|
|
3
|
-
*
|
|
2
|
+
* WalletConnect v2 wrapper.
|
|
3
|
+
* Connects to the user's wallet over WalletConnect and submits transactions.
|
|
4
4
|
*/
|
|
5
5
|
import { SignClient } from "@walletconnect/sign-client";
|
|
6
6
|
import { encodeFunctionData, parseUnits } from "viem";
|
|
@@ -13,8 +13,9 @@ import { WC_CONNECT_TIMEOUT_MS, ERC20_TRANSFER_ABI, DEFAULT_WC_PROJECT_ID } from
|
|
|
13
13
|
import { loadConfig, saveConfig } from "./config.mjs";
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
|
-
* WalletConnect
|
|
17
|
-
*
|
|
16
|
+
* Error type thrown by the WalletConnect flow, carrying a stable error code
|
|
17
|
+
* (PAYMENT_TIMEOUT / PAYMENT_REJECTED / WALLET_ERROR). The Commands layer
|
|
18
|
+
* catches it and forwards it via emitErr; no caller needs to look at the underlying details.
|
|
18
19
|
*/
|
|
19
20
|
export class WalletConnectError extends Error {
|
|
20
21
|
constructor(code, message) {
|
|
@@ -24,7 +25,7 @@ export class WalletConnectError extends Error {
|
|
|
24
25
|
}
|
|
25
26
|
}
|
|
26
27
|
|
|
27
|
-
// ==============
|
|
28
|
+
// ============== Status server (polled by the browser-side QR page) ==============
|
|
28
29
|
|
|
29
30
|
let _status = { state: "waiting_scan" };
|
|
30
31
|
let _server = null;
|
|
@@ -65,14 +66,14 @@ export function stopStatusServer() {
|
|
|
65
66
|
|
|
66
67
|
const BSC_CHAIN_ID = "eip155:56";
|
|
67
68
|
|
|
68
|
-
// QR
|
|
69
|
+
// QR-page countdown (matches the WalletConnect connection timeout)
|
|
69
70
|
const QR_EXPIRE_MS = 5 * 60 * 1000;
|
|
70
71
|
|
|
71
72
|
/**
|
|
72
|
-
*
|
|
73
|
+
* Generate the QR-code HTML page and open it in a browser (follows the Figma "AI card v1.2" design).
|
|
73
74
|
* @param {string} uri - WalletConnect URI
|
|
74
|
-
* @param {number} statusPort -
|
|
75
|
-
* @param {string|null} amount -
|
|
75
|
+
* @param {number} statusPort - port of the local status server
|
|
76
|
+
* @param {string|null} amount - USDT amount the user has to pay (e.g. "0.66")
|
|
76
77
|
*/
|
|
77
78
|
function openQRInBrowser(uri, statusPort, amount, token = "USDT", network = "BNB Chain(BEP20) only", gasAmount = null) {
|
|
78
79
|
const html = `<!DOCTYPE html>
|
|
@@ -165,16 +166,16 @@ function openQRInBrowser(uri, statusPort, amount, token = "USDT", network = "BNB
|
|
|
165
166
|
const EXPIRE_MS = ${QR_EXPIRE_MS};
|
|
166
167
|
const startTime = Date.now();
|
|
167
168
|
|
|
168
|
-
//
|
|
169
|
+
// Clock countdown icon (exported from Figma 1:63, 16x16) — uses currentColor to track `.timer` color
|
|
169
170
|
const CLOCK_SVG = '<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M14.667 8C14.667 11.68 11.68 14.667 8 14.667C4.32 14.667 1.334 11.68 1.334 8C1.334 4.32 4.32 1.333 8 1.333C11.68 1.333 14.667 4.32 14.667 8Z" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/><path d="M10.476 10.12L8.409 8.887C8.049 8.674 7.756 8.16 7.756 7.74V5.007" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg>';
|
|
170
|
-
//
|
|
171
|
+
// Info / hint icon (exported from Figma 1:43, 20x20)
|
|
171
172
|
const INFO_SVG = '<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg"><path opacity="0.2" d="M10 18.333C14.6 18.333 18.331 14.602 18.331 10C18.331 5.397 14.6 1.666 10 1.666C5.395 1.666 1.664 5.397 1.664 10C1.664 14.602 5.395 18.333 10 18.333Z" fill="#737A86"/><path d="M10 13.333V10" stroke="#737A86" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/><path d="M10 6.666H10.008" stroke="#737A86" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>';
|
|
172
|
-
// Loading
|
|
173
|
+
// Loading spinner icon (exported from Figma 1:50 loading-02, 24x24)
|
|
173
174
|
const LOADING_SVG = '<svg class="loading-icon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path opacity=".7" d="M4.922 5l2.828 2.828" stroke="#191B1F" stroke-width="2.5" stroke-linecap="round"/><path opacity=".6" d="M6 12H2" stroke="#191B1F" stroke-width="2.5" stroke-linecap="round"/><path opacity=".5" d="M4.922 19.078l2.828-2.828" stroke="#191B1F" stroke-width="2.5" stroke-linecap="round"/><path opacity=".4" d="M12 18v4" stroke="#191B1F" stroke-width="2.5" stroke-linecap="round"/><path opacity=".3" d="M19.078 19.078L16.25 16.25" stroke="#191B1F" stroke-width="2.5" stroke-linecap="round"/><path opacity=".2" d="M22 12h-4" stroke="#191B1F" stroke-width="2.5" stroke-linecap="round"/><path opacity=".1" d="M19.078 5L16.25 7.828" stroke="#191B1F" stroke-width="2.5" stroke-linecap="round"/><path opacity=".8" d="M12 2v4" stroke="#191B1F" stroke-width="2.5" stroke-linecap="round"/></svg>';
|
|
174
|
-
//
|
|
175
|
+
// Success checkmark
|
|
175
176
|
const CHECK_SVG = '<div class="check-icon"><svg viewBox="0 0 14 14" fill="none"><path d="M3 7l3 3 5-5" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg></div>';
|
|
176
177
|
|
|
177
|
-
//
|
|
178
|
+
// "Expired" illustration SVG (exported from Figma)
|
|
178
179
|
const EXPIRED_SVG = \`<svg width="144" height="129" viewBox="0 0 144 129" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
179
180
|
<path opacity="0.1" d="M140.369 64.988C140.369 82.769 133.106 98.797 121.461 110.317C110.066 121.711 94.289 128.598 76.884 128.598C59.604 128.598 43.826 121.586 32.306 110.317C20.661 98.797 13.398 82.769 13.398 64.988C13.398 29.802 41.823 1.377 76.884 1.377C111.944 1.377 140.369 29.927 140.369 64.988Z" fill="#1A72F7"/>
|
|
180
181
|
<path d="M134.859 23.291C137.695 23.291 139.993 20.992 139.993 18.157C139.993 15.321 137.695 13.023 134.859 13.023C132.024 13.023 129.725 15.321 129.725 18.157C129.725 20.992 132.024 23.291 134.859 23.291Z" fill="#E8F1FE"/>
|
|
@@ -194,7 +195,7 @@ function openQRInBrowser(uri, statusPort, amount, token = "USDT", network = "BNB
|
|
|
194
195
|
<linearGradient id="pb2" x1="53.891" y1="104.557" x2="103.297" y2="105.207" gradientUnits="userSpaceOnUse"><stop stop-color="#DBDEE3" stop-opacity=".7"/><stop offset="1" stop-color="#DBDEE3"/></linearGradient>
|
|
195
196
|
</defs></svg>\`;
|
|
196
197
|
|
|
197
|
-
//
|
|
198
|
+
// "Rejected" illustration SVG (variant of the expired illustration: clock swapped for an X, orange swapped for red)
|
|
198
199
|
const REJECTED_SVG = \`<svg width="144" height="129" viewBox="0 0 144 129" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
199
200
|
<path opacity="0.1" d="M140.369 64.988C140.369 82.769 133.106 98.797 121.461 110.317C110.066 121.711 94.289 128.598 76.884 128.598C59.604 128.598 43.826 121.586 32.306 110.317C20.661 98.797 13.398 82.769 13.398 64.988C13.398 29.802 41.823 1.377 76.884 1.377C111.944 1.377 140.369 29.927 140.369 64.988Z" fill="#1A72F7"/>
|
|
200
201
|
<path d="M134.859 23.291C137.695 23.291 139.993 20.992 139.993 18.157C139.993 15.321 137.695 13.023 134.859 13.023C132.024 13.023 129.725 15.321 129.725 18.157C129.725 20.992 132.024 23.291 134.859 23.291Z" fill="#E8F1FE"/>
|
|
@@ -223,7 +224,7 @@ function openQRInBrowser(uri, statusPort, amount, token = "USDT", network = "BNB
|
|
|
223
224
|
|
|
224
225
|
function fmtAmount(v) {
|
|
225
226
|
if (v == null || v === '') return '';
|
|
226
|
-
//
|
|
227
|
+
// Render the full original amount (no rounding); trim trailing zeros and add thousands separators to the integer part
|
|
227
228
|
const s = String(v);
|
|
228
229
|
const neg = s.startsWith('-') ? '-' : '';
|
|
229
230
|
const body = neg ? s.slice(1) : s;
|
|
@@ -233,7 +234,7 @@ function openQRInBrowser(uri, statusPort, amount, token = "USDT", network = "BNB
|
|
|
233
234
|
return neg + intWithComma + (decPart ? '.' + decPart : '') + ' ' + TOKEN;
|
|
234
235
|
}
|
|
235
236
|
|
|
236
|
-
// ======
|
|
237
|
+
// ====== Page rendering helpers ======
|
|
237
238
|
|
|
238
239
|
function renderQR(data) {
|
|
239
240
|
const remaining = Math.max(0, EXPIRE_MS - (Date.now() - startTime));
|
|
@@ -250,22 +251,22 @@ function openQRInBrowser(uri, statusPort, amount, token = "USDT", network = "BNB
|
|
|
250
251
|
|
|
251
252
|
const expireMin = Math.ceil(remaining / 60000);
|
|
252
253
|
|
|
253
|
-
// QR wrap:
|
|
254
|
-
// canvas 200 + padding 12*2 = 224,
|
|
254
|
+
// QR wrap: rounded-rectangle path starts at 12 o'clock (top center) and goes clockwise.
|
|
255
|
+
// canvas 200 + padding 12*2 = 224, corner radius r=16, stroke offset 1px
|
|
255
256
|
const S = 224, R = 16, cx = S/2;
|
|
256
|
-
//
|
|
257
|
+
// Start at top center, draw clockwise back to the start (open path, no Z — avoids the dash wrapping around)
|
|
257
258
|
const qrPath = 'M' + cx + ',1 H' + (S-1-R) + ' A' + R + ',' + R + ' 0 0 1 ' + (S-1) + ',' + (1+R) +
|
|
258
259
|
' V' + (S-1-R) + ' A' + R + ',' + R + ' 0 0 1 ' + (S-1-R) + ',' + (S-1) +
|
|
259
260
|
' H' + (1+R) + ' A' + R + ',' + R + ' 0 0 1 1,' + (S-1-R) +
|
|
260
261
|
' V' + (1+R) + ' A' + R + ',' + R + ' 0 0 1 ' + (1+R) + ',1 H' + cx;
|
|
261
|
-
//
|
|
262
|
+
// Use pathLength=1000 so the dash math is exact (no hand-calculated drift)
|
|
262
263
|
const PL = 1000;
|
|
263
264
|
const progress = remaining / EXPIRE_MS;
|
|
264
265
|
const dashOffset = -PL * (1 - progress);
|
|
265
266
|
|
|
266
|
-
// USDT
|
|
267
|
+
// USDT icon SVG (green circle Tether glyph)
|
|
267
268
|
const USDT_ICON = '<svg class="usdt-icon" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg"><circle cx="9" cy="9" r="9" fill="#26A17B"/><path d="M10.1 9.6c-.06 0-.33.02-.73.02-.32 0-.58-.01-.67-.02-1.32-.06-2.3-.3-2.3-.58 0-.29.98-.52 2.3-.58v.93c.09.01.36.02.68.02.38 0 .66-.01.72-.02v-.93c1.31.06 2.29.3 2.29.58 0 .28-.98.52-2.29.58zm0-.87v-.83h2.05V6.5H5.88v1.4h2.05v.83c-1.48.07-2.6.38-2.6.75 0 .37 1.12.68 2.6.75v2.69h1.07v-2.69c1.48-.07 2.59-.38 2.59-.75 0-.37-1.11-.68-2.59-.75z" fill="#fff"/></svg>';
|
|
268
|
-
// BNB
|
|
269
|
+
// BNB icon SVG (yellow circle BNB glyph)
|
|
269
270
|
const BNB_ICON = '<svg class="usdt-icon" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg"><circle cx="9" cy="9" r="9" fill="#F3BA2F"/><path d="M9 4.5L7.2 6.3l-1.8-1.8L9 1.2l3.6 3.3-1.8 1.8L9 4.5zm-4.5 4.5L2.7 7.2 4.5 5.4l1.8 1.8L4.5 9zm4.5 4.5l-1.8-1.8-1.8 1.8L9 16.8l3.6-3.3-1.8-1.8L9 13.5zm4.5-4.5l1.8 1.8-1.8 1.8-1.8-1.8 1.8-1.8zM10.8 9L9 7.2 7.2 9 9 10.8 10.8 9z" fill="#fff"/></svg>';
|
|
270
271
|
const TOKEN_ICON = TOKEN === 'BNB' ? BNB_ICON : USDT_ICON;
|
|
271
272
|
|
|
@@ -307,7 +308,7 @@ function openQRInBrowser(uri, statusPort, amount, token = "USDT", network = "BNB
|
|
|
307
308
|
'<div class="result-sub">' + (error || 'Something went wrong.') + '<br>Please try again.</div></div>';
|
|
308
309
|
}
|
|
309
310
|
|
|
310
|
-
// ======
|
|
311
|
+
// ====== State machine ======
|
|
311
312
|
|
|
312
313
|
const FINAL = ['confirmed', 'rejected', 'failed', 'expired'];
|
|
313
314
|
let lastState = null;
|
|
@@ -339,7 +340,7 @@ function openQRInBrowser(uri, statusPort, amount, token = "USDT", network = "BNB
|
|
|
339
340
|
stopTimer();
|
|
340
341
|
} else {
|
|
341
342
|
body.innerHTML = renderQR(data);
|
|
342
|
-
//
|
|
343
|
+
// Render the QR code
|
|
343
344
|
const qrEl = document.getElementById('qr');
|
|
344
345
|
if (qrEl) {
|
|
345
346
|
new QRious({ element: qrEl, value: URI, size: 200, backgroundAlpha: 1, background: '#ffffff', foreground: '#000000', level: 'M' });
|
|
@@ -361,13 +362,13 @@ function openQRInBrowser(uri, statusPort, amount, token = "USDT", network = "BNB
|
|
|
361
362
|
closeTimer = setTimeout(tick, 1000);
|
|
362
363
|
}
|
|
363
364
|
|
|
364
|
-
//
|
|
365
|
+
// Initial render
|
|
365
366
|
render(null);
|
|
366
367
|
|
|
367
|
-
//
|
|
368
|
+
// Countdown refresh (update timer every second)
|
|
368
369
|
timerInterval = setInterval(() => {
|
|
369
370
|
const remaining = EXPIRE_MS - (Date.now() - startTime);
|
|
370
|
-
//
|
|
371
|
+
// Backend has entered an active state (connected / signing / tx-submitted); do not expire the page
|
|
371
372
|
const ACTIVE = ['connected', 'signing', 'tx_submitted'];
|
|
372
373
|
if (remaining <= 0 && !FINAL.includes(lastState) && !ACTIVE.includes(lastState)) {
|
|
373
374
|
lastState = 'expired';
|
|
@@ -375,7 +376,7 @@ function openQRInBrowser(uri, statusPort, amount, token = "USDT", network = "BNB
|
|
|
375
376
|
maybeAutoClose('expired');
|
|
376
377
|
return;
|
|
377
378
|
}
|
|
378
|
-
//
|
|
379
|
+
// Update the countdown label
|
|
379
380
|
const timerEl = document.querySelector('.timer');
|
|
380
381
|
if (timerEl) {
|
|
381
382
|
const expireMin = Math.ceil(Math.max(0, remaining) / 60000);
|
|
@@ -383,7 +384,7 @@ function openQRInBrowser(uri, statusPort, amount, token = "USDT", network = "BNB
|
|
|
383
384
|
const hintSpan = document.querySelector('.hint-bar span');
|
|
384
385
|
if (hintSpan) hintSpan.textContent = 'Expire in ' + expireMin + ' mins. This page will close automatically once the transfer is completed';
|
|
385
386
|
}
|
|
386
|
-
//
|
|
387
|
+
// Update the QR-border progress (negative offset → the dash recedes clockwise from 12 o'clock)
|
|
387
388
|
const progressEl = document.getElementById('qr-progress');
|
|
388
389
|
if (progressEl) {
|
|
389
390
|
const PL = 1000;
|
|
@@ -392,7 +393,7 @@ function openQRInBrowser(uri, statusPort, amount, token = "USDT", network = "BNB
|
|
|
392
393
|
}
|
|
393
394
|
}, 1000);
|
|
394
395
|
|
|
395
|
-
//
|
|
396
|
+
// Poll backend status
|
|
396
397
|
async function poll() {
|
|
397
398
|
if (stopped) return;
|
|
398
399
|
try {
|
|
@@ -426,7 +427,7 @@ function openQRInBrowser(uri, statusPort, amount, token = "USDT", network = "BNB
|
|
|
426
427
|
}
|
|
427
428
|
|
|
428
429
|
/**
|
|
429
|
-
*
|
|
430
|
+
* Initialise the WalletConnect SignClient
|
|
430
431
|
* @param {string} projectId - WalletConnect Cloud project ID
|
|
431
432
|
*/
|
|
432
433
|
export async function initSignClient(projectId) {
|
|
@@ -446,8 +447,9 @@ export async function initSignClient(projectId) {
|
|
|
446
447
|
),
|
|
447
448
|
]);
|
|
448
449
|
|
|
449
|
-
// WalletConnect relay
|
|
450
|
-
//
|
|
450
|
+
// WalletConnect relay occasionally emits null WebSocket frames, which crashes
|
|
451
|
+
// isJsonRpcPayload('id' in null). Filter null payloads at the connection layer so
|
|
452
|
+
// the error never reaches the provider / request chain.
|
|
451
453
|
try {
|
|
452
454
|
const conn = client.core.relayer.provider.connection;
|
|
453
455
|
const _origEmit = conn.emit.bind(conn);
|
|
@@ -460,7 +462,7 @@ export async function initSignClient(projectId) {
|
|
|
460
462
|
};
|
|
461
463
|
} catch {}
|
|
462
464
|
|
|
463
|
-
//
|
|
465
|
+
// Tear down stale sessions + pairings (await in parallel to ensure the relay is clean before reconnecting)
|
|
464
466
|
try {
|
|
465
467
|
const sessions = client.session.getAll();
|
|
466
468
|
if (sessions.length > 0) {
|
|
@@ -486,10 +488,10 @@ export async function initSignClient(projectId) {
|
|
|
486
488
|
}
|
|
487
489
|
|
|
488
490
|
/**
|
|
489
|
-
*
|
|
491
|
+
* Connect to a wallet: render the QR code and wait for the user to approve via scan.
|
|
490
492
|
* @param {SignClient} signClient
|
|
491
493
|
* @param {number} statusPort
|
|
492
|
-
* @param {string|null} amount -
|
|
494
|
+
* @param {string|null} amount - USDT amount to display (e.g. "0.66")
|
|
493
495
|
* @returns {{ session: object, peerAddress: string }}
|
|
494
496
|
*/
|
|
495
497
|
export async function connectWallet(signClient, statusPort, amount = null, token = "USDT", gasAmount = null) {
|
|
@@ -503,7 +505,7 @@ export async function connectWallet(signClient, statusPort, amount = null, token
|
|
|
503
505
|
},
|
|
504
506
|
});
|
|
505
507
|
|
|
506
|
-
//
|
|
508
|
+
// Generate the QR-code page (with status polling) and open it in the browser
|
|
507
509
|
openQRInBrowser(uri, statusPort, amount, token, "BNB Chain(BEP20) only", gasAmount);
|
|
508
510
|
console.error("QR code opened in browser. Scan it with your wallet app.");
|
|
509
511
|
console.error("Waiting for wallet approval...");
|
|
@@ -528,11 +530,11 @@ export async function connectWallet(signClient, statusPort, amount = null, token
|
|
|
528
530
|
}
|
|
529
531
|
|
|
530
532
|
/**
|
|
531
|
-
*
|
|
533
|
+
* Request an ERC-20 token transfer
|
|
532
534
|
* @param {SignClient} signClient
|
|
533
535
|
* @param {object} session - WalletConnect session
|
|
534
536
|
* @param {{ from: string, to: string, token: string, amount: string, decimals?: number }} params
|
|
535
|
-
* @returns {string}
|
|
537
|
+
* @returns {string} transaction hash
|
|
536
538
|
*/
|
|
537
539
|
export async function requestERC20Transfer(signClient, session, { from, to, token, amount, decimals = 18 }) {
|
|
538
540
|
const value = parseUnits(amount, decimals);
|
|
@@ -554,7 +556,7 @@ export async function requestERC20Transfer(signClient, session, { from, to, toke
|
|
|
554
556
|
from,
|
|
555
557
|
to: token,
|
|
556
558
|
data,
|
|
557
|
-
gas: "0xFDE8", // 65000 — ERC20
|
|
559
|
+
gas: "0xFDE8", // 65000 — ERC20.transfer contract call
|
|
558
560
|
},
|
|
559
561
|
],
|
|
560
562
|
},
|
|
@@ -568,11 +570,11 @@ export async function requestERC20Transfer(signClient, session, { from, to, toke
|
|
|
568
570
|
}
|
|
569
571
|
|
|
570
572
|
/**
|
|
571
|
-
*
|
|
573
|
+
* Request a native BNB transfer
|
|
572
574
|
* @param {SignClient} signClient
|
|
573
575
|
* @param {object} session
|
|
574
|
-
* @param {{ from: string, to: string, value: string }} params - value
|
|
575
|
-
* @returns {string}
|
|
576
|
+
* @param {{ from: string, to: string, value: string }} params - `value` is the BNB amount (e.g. "0.001")
|
|
577
|
+
* @returns {string} transaction hash
|
|
576
578
|
*/
|
|
577
579
|
export async function requestNativeTransfer(signClient, session, { from, to, value }) {
|
|
578
580
|
const weiValue = "0x" + parseUnits(value, 18).toString(16);
|
|
@@ -589,7 +591,7 @@ export async function requestNativeTransfer(signClient, session, { from, to, val
|
|
|
589
591
|
from,
|
|
590
592
|
to,
|
|
591
593
|
value: weiValue,
|
|
592
|
-
gas: "0x5208", // 21000 — BNB
|
|
594
|
+
gas: "0x5208", // 21000 — fixed gas for a native BNB transfer
|
|
593
595
|
},
|
|
594
596
|
],
|
|
595
597
|
},
|
|
@@ -605,11 +607,11 @@ export async function requestNativeTransfer(signClient, session, { from, to, val
|
|
|
605
607
|
const FINAL_LINGER_MS = 2000;
|
|
606
608
|
|
|
607
609
|
/**
|
|
608
|
-
*
|
|
609
|
-
* -
|
|
610
|
-
* -
|
|
611
|
-
* -
|
|
612
|
-
* -
|
|
610
|
+
* Higher-order wallet-connect helper: manages the full connection lifecycle.
|
|
611
|
+
* - Starts the status server
|
|
612
|
+
* - Waits for the user to scan and connect their wallet
|
|
613
|
+
* - Runs the caller's transaction logic
|
|
614
|
+
* - Always disconnects and cleans up, whether the body succeeded or failed
|
|
613
615
|
*
|
|
614
616
|
* @param {{ amount?: string, projectId?: string }} opts
|
|
615
617
|
* @param {(ctx: { signClient, session, peerAddress }) => Promise<void>} fn
|
|
@@ -629,7 +631,7 @@ export async function withWallet(opts, fn) {
|
|
|
629
631
|
|
|
630
632
|
await fn({ signClient, session, peerAddress });
|
|
631
633
|
|
|
632
|
-
//
|
|
634
|
+
// Success: persist mainWallet so subsequent withdraws can default to it
|
|
633
635
|
const config = loadConfig();
|
|
634
636
|
config.mainWallet = peerAddress;
|
|
635
637
|
saveConfig(config);
|
|
@@ -652,11 +654,11 @@ export async function withWallet(opts, fn) {
|
|
|
652
654
|
await new Promise((r) => setTimeout(r, FINAL_LINGER_MS));
|
|
653
655
|
stopStatusServer();
|
|
654
656
|
if (signClient) {
|
|
655
|
-
//
|
|
657
|
+
// Disconnect the current session
|
|
656
658
|
if (session) {
|
|
657
659
|
await disconnectSession(signClient, session);
|
|
658
660
|
}
|
|
659
|
-
//
|
|
661
|
+
// Disconnect every pairing so the wallet side has no lingering connection
|
|
660
662
|
try {
|
|
661
663
|
const pairings = signClient.core.pairing.pairings.getAll({ active: true });
|
|
662
664
|
await Promise.allSettled(
|
|
@@ -672,19 +674,19 @@ export async function withWallet(opts, fn) {
|
|
|
672
674
|
}
|
|
673
675
|
|
|
674
676
|
/**
|
|
675
|
-
*
|
|
677
|
+
* Normalise wallet error messages: map known Chinese / multilingual error strings to a unified English form.
|
|
676
678
|
* @param {Error} error
|
|
677
|
-
* @returns {Error}
|
|
679
|
+
* @returns {Error} the same error object with its `message` rewritten
|
|
678
680
|
*/
|
|
679
681
|
export function normalizeWalletError(error) {
|
|
680
682
|
const msg = error?.message || "";
|
|
681
683
|
const patterns = [
|
|
682
|
-
//
|
|
683
|
-
{ test:
|
|
684
|
-
//
|
|
685
|
-
{ test:
|
|
686
|
-
//
|
|
687
|
-
{ test:
|
|
684
|
+
// Rejection patterns (English error strings only — localised wallets fall through to WALLET_ERROR)
|
|
685
|
+
{ test: /User rejected|User denied|declined/i, replacement: "rejected" },
|
|
686
|
+
// Disconnect patterns
|
|
687
|
+
{ test: /disconnect.*DApp|session.*expired|session.*disconnected/i, replacement: "rejected" },
|
|
688
|
+
// Timeout patterns
|
|
689
|
+
{ test: /timed?\s*out|timeout/i, replacement: "timed out" },
|
|
688
690
|
];
|
|
689
691
|
for (const { test, replacement } of patterns) {
|
|
690
692
|
if (test.test(msg)) {
|
|
@@ -696,7 +698,7 @@ export function normalizeWalletError(error) {
|
|
|
696
698
|
}
|
|
697
699
|
|
|
698
700
|
/**
|
|
699
|
-
*
|
|
701
|
+
* Disconnect a WalletConnect session.
|
|
700
702
|
* @param {SignClient} signClient
|
|
701
703
|
* @param {object} session
|
|
702
704
|
*/
|
|
@@ -707,6 +709,6 @@ export async function disconnectSession(signClient, session) {
|
|
|
707
709
|
reason: { code: 6000, message: "Session complete" },
|
|
708
710
|
});
|
|
709
711
|
} catch {
|
|
710
|
-
//
|
|
712
|
+
// Silently ignore disconnect errors
|
|
711
713
|
}
|
|
712
714
|
}
|
package/src/x402.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* x402
|
|
2
|
+
* x402 protocol client: initialise an EVM signer and an x402Client.
|
|
3
3
|
*/
|
|
4
4
|
import { x402Client, wrapAxiosWithPayment, x402HTTPClient } from "@aeon-ai-pay/axios";
|
|
5
5
|
import { registerExactEvmScheme } from "@aeon-ai-pay/evm/exact/client";
|
|
@@ -11,8 +11,8 @@ import { BSC_RPC_URL } from "./constants.mjs";
|
|
|
11
11
|
import axios from "axios";
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
|
-
*
|
|
15
|
-
* @param {`0x${string}`} privateKey - EVM
|
|
14
|
+
* Build an x402 axios client with the EVM signer pre-registered.
|
|
15
|
+
* @param {`0x${string}`} privateKey - EVM private key
|
|
16
16
|
* @returns {{ api: AxiosInstance, client: x402Client, address: string, getOrderNo: () => string|null }}
|
|
17
17
|
*/
|
|
18
18
|
export function createX402Api(privateKey) {
|
|
@@ -39,8 +39,9 @@ export function createX402Api(privateKey) {
|
|
|
39
39
|
|
|
40
40
|
const axiosInstance = axios.create();
|
|
41
41
|
|
|
42
|
-
//
|
|
43
|
-
//
|
|
42
|
+
// Register the interceptor *before* wrapAxiosWithPayment so it can
|
|
43
|
+
// capture orderNo from the 402 response body (the server returns it
|
|
44
|
+
// on the first request).
|
|
44
45
|
let capturedOrderNo = null;
|
|
45
46
|
axiosInstance.interceptors.response.use(
|
|
46
47
|
(response) => response,
|
|
@@ -63,11 +64,13 @@ export function createX402Api(privateKey) {
|
|
|
63
64
|
}
|
|
64
65
|
|
|
65
66
|
/**
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
67
|
+
* Send the first x402 request (unsigned) and extract the real payment requirements
|
|
68
|
+
* from the 402 response.
|
|
69
|
+
* Also keeps the raw 402 response and the original request config so the caller can
|
|
70
|
+
* sign manually later.
|
|
71
|
+
* Field names follow the x402 v2 PaymentRequirements standard: asset, payTo, amount.
|
|
69
72
|
*
|
|
70
|
-
*
|
|
73
|
+
* Supports both GET (card path) and POST (image / Skill Boss path).
|
|
71
74
|
*
|
|
72
75
|
* @param {string} url
|
|
73
76
|
* @param {{ method?: "GET"|"POST", data?: any, headers?: object }} [options]
|
|
@@ -105,7 +108,7 @@ export async function fetchPaymentRequirements(url, options = {}) {
|
|
|
105
108
|
}
|
|
106
109
|
|
|
107
110
|
/**
|
|
108
|
-
*
|
|
111
|
+
* Decode the PAYMENT-RESPONSE response header (x402 v2).
|
|
109
112
|
* @param {object} headers - axios response headers
|
|
110
113
|
* @returns {object|null}
|
|
111
114
|
*/
|