@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
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* withdraw
|
|
2
|
+
* wallet-withdraw: move the session key's funds (USDT + BNB) back to the main wallet.
|
|
3
3
|
*/
|
|
4
4
|
import { createPublicClient, createWalletClient, http, parseUnits, formatUnits, encodeFunctionData } from "viem";
|
|
5
5
|
import { privateKeyToAccount } from "viem/accounts";
|
|
@@ -54,7 +54,7 @@ export async function withdraw(opts) {
|
|
|
54
54
|
|
|
55
55
|
const isWithdrawAll = !opts.amount;
|
|
56
56
|
|
|
57
|
-
//
|
|
57
|
+
// No funds at all
|
|
58
58
|
if (balance.usdtRaw === 0n && balance.bnbRaw === 0n) {
|
|
59
59
|
emitErr("wallet-withdraw", "NO_FUNDS", { message: "No funds to withdraw.", appId });
|
|
60
60
|
return;
|
|
@@ -63,9 +63,9 @@ export async function withdraw(opts) {
|
|
|
63
63
|
let usdtTxHash = null;
|
|
64
64
|
let bnbTxHash = null;
|
|
65
65
|
|
|
66
|
-
// 1.
|
|
66
|
+
// 1. Reclaim USDT (only when USDT balance > 0)
|
|
67
67
|
if (balance.usdtRaw > 0n) {
|
|
68
|
-
// USDT
|
|
68
|
+
// USDT transfer needs BNB for gas
|
|
69
69
|
if (balance.bnbRaw === 0n) {
|
|
70
70
|
emitErr("wallet-withdraw", "INSUFFICIENT_BNB", {
|
|
71
71
|
message: "No BNB for gas. Withdraw is a normal on-chain transfer and requires BNB to pay gas.",
|
|
@@ -119,7 +119,7 @@ export async function withdraw(opts) {
|
|
|
119
119
|
}
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
-
// 2.
|
|
122
|
+
// 2. Reclaim remaining BNB (only when withdrawing everything)
|
|
123
123
|
if (isWithdrawAll) {
|
|
124
124
|
const freshBalance = balance.usdtRaw > 0n
|
|
125
125
|
? await getBalanceByAddress(sessionAddress)
|
|
@@ -128,7 +128,7 @@ export async function withdraw(opts) {
|
|
|
128
128
|
if (freshBalance.bnbRaw > 0n) {
|
|
129
129
|
try {
|
|
130
130
|
const gasPrice = await publicClient.getGasPrice();
|
|
131
|
-
//
|
|
131
|
+
// Reserve a 20% buffer to absorb gas-price fluctuations
|
|
132
132
|
const gasCost = BNB_TRANSFER_GAS * (gasPrice * 120n / 100n);
|
|
133
133
|
const sendable = freshBalance.bnbRaw - gasCost;
|
|
134
134
|
|
|
@@ -159,7 +159,7 @@ export async function withdraw(opts) {
|
|
|
159
159
|
}
|
|
160
160
|
}
|
|
161
161
|
|
|
162
|
-
//
|
|
162
|
+
// Final balance lookup
|
|
163
163
|
let finalBalance;
|
|
164
164
|
try {
|
|
165
165
|
finalBalance = await getBalanceByAddress(sessionAddress);
|
package/src/config.mjs
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* Config management: ~/.aigateway/config.json
|
|
3
|
+
* Resolution priority: CLI args > env vars > config.json
|
|
4
4
|
*
|
|
5
|
-
* AEON AI Gateway
|
|
6
|
-
*
|
|
5
|
+
* AEON AI Gateway uses a single x402 service (ai-api.aeon.xyz);
|
|
6
|
+
* different capabilities (virtual card / Skill Boss calls) share the host
|
|
7
|
+
* but use distinct path prefixes.
|
|
7
8
|
*/
|
|
8
9
|
import { readFileSync, writeFileSync, mkdirSync, chmodSync } from "fs";
|
|
9
10
|
import { join } from "path";
|
|
@@ -31,7 +32,7 @@ export function saveConfig(config) {
|
|
|
31
32
|
}
|
|
32
33
|
|
|
33
34
|
/**
|
|
34
|
-
*
|
|
35
|
+
* Resolve a value with priority: cliValue > envKey > config[configKey]
|
|
35
36
|
*/
|
|
36
37
|
export function resolve(cliValue, envKey, configKey) {
|
|
37
38
|
if (cliValue) return cliValue;
|
package/src/error-codes.mjs
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Error-code registry — single source of truth shared by the CLI and the docs.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* 0
|
|
6
|
-
* 1
|
|
7
|
-
* 2
|
|
8
|
-
* 3
|
|
9
|
-
* 4
|
|
4
|
+
* Exit-code semantics:
|
|
5
|
+
* 0 success
|
|
6
|
+
* 1 user error (bad argument, insufficient balance, configuration, user reject)
|
|
7
|
+
* 2 timeout (polling, WalletConnect, signature, on-chain wait)
|
|
8
|
+
* 3 service / network
|
|
9
|
+
* 4 internal error
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
export const ERROR_CODES = {
|
|
13
|
-
// =====
|
|
13
|
+
// ===== User error (exit 1) =====
|
|
14
14
|
WALLET_NOT_CONFIGURED: { exit: 1, message: "Wallet not configured. Run: aigateway wallet-init" },
|
|
15
15
|
SERVICE_URL_MISSING: { exit: 1, message: "Service URL not configured." },
|
|
16
16
|
AMOUNT_INVALID: { exit: 1, message: "Invalid amount." },
|
|
@@ -25,13 +25,13 @@ export const ERROR_CODES = {
|
|
|
25
25
|
TOPUP_AMOUNT_TOO_SMALL: { exit: 1, message: "Top-up amount is below the minimum." },
|
|
26
26
|
PAYMENT_REJECTED: { exit: 1, message: "Payment approval was rejected. Please try again if you'd like to proceed." },
|
|
27
27
|
|
|
28
|
-
// =====
|
|
28
|
+
// ===== Timeout (exit 2) =====
|
|
29
29
|
PAYMENT_TIMEOUT: { exit: 2, message: "Payment approval timed out. Please try again." },
|
|
30
30
|
WC_SESSION_EXPIRED: { exit: 2, message: "WalletConnect session expired." },
|
|
31
31
|
POLL_TIMEOUT: { exit: 2, message: "Polling timed out. Card may still be provisioning." },
|
|
32
32
|
TX_TIMEOUT: { exit: 2, message: "On-chain transaction timed out." },
|
|
33
33
|
|
|
34
|
-
// =====
|
|
34
|
+
// ===== Service / network (exit 3) =====
|
|
35
35
|
SERVICE_UNAVAILABLE: { exit: 3, message: "Service unavailable or network error." },
|
|
36
36
|
PAYMENT_FETCH_FAILED: { exit: 3, message: "Failed to fetch payment requirements." },
|
|
37
37
|
BALANCE_CHECK_FAILED: { exit: 3, message: "Failed to check balance." },
|
|
@@ -44,7 +44,7 @@ export const ERROR_CODES = {
|
|
|
44
44
|
IMAGE_DOWNLOAD_FAILED: { exit: 3, message: "Image download failed." },
|
|
45
45
|
FUNDING_FAILED: { exit: 3, message: "Funding flow failed." },
|
|
46
46
|
|
|
47
|
-
// =====
|
|
47
|
+
// ===== Internal (exit 4) =====
|
|
48
48
|
INTERNAL_ERROR: { exit: 4, message: "Internal error." },
|
|
49
49
|
WALLET_ERROR: { exit: 1, message: "Wallet operation failed." },
|
|
50
50
|
};
|
package/src/output.mjs
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Unified output envelope: JSON on stdout + tiered stderr logs.
|
|
3
3
|
*
|
|
4
|
-
* stdout:
|
|
5
|
-
* -
|
|
6
|
-
* -
|
|
7
|
-
* stderr:
|
|
4
|
+
* stdout: a single line of final JSON (machine-readable)
|
|
5
|
+
* - success: { ok: true, command, version, data }
|
|
6
|
+
* - failure: { ok: false, command, version, error: { code, message, ...context } }
|
|
7
|
+
* stderr: progress logs (human-readable; agents can ignore them)
|
|
8
8
|
*
|
|
9
|
-
* --legacy-output
|
|
9
|
+
* --legacy-output mode: emit the pre-envelope shape so scripts / agents that
|
|
10
|
+
* already parse the old JSON can migrate gradually.
|
|
10
11
|
*/
|
|
11
12
|
|
|
12
13
|
import { readFileSync } from "fs";
|
|
@@ -30,10 +31,11 @@ export function isLegacyMode() { return LEGACY_MODE; }
|
|
|
30
31
|
export function isVerboseMode() { return VERBOSE_MODE; }
|
|
31
32
|
|
|
32
33
|
/**
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
* @param {
|
|
36
|
-
* @param {object}
|
|
34
|
+
* Emit a success result. Callers should let the function return naturally
|
|
35
|
+
* (do not call process.exit afterwards).
|
|
36
|
+
* @param {string} command - command name, e.g. "create-card" / "wallet-init"
|
|
37
|
+
* @param {object} data - placed under envelope.data
|
|
38
|
+
* @param {object} [legacyShape] - the legacy-mode payload; if omitted, `data` is used
|
|
37
39
|
*/
|
|
38
40
|
export function emitOk(command, data, legacyShape) {
|
|
39
41
|
if (LEGACY_MODE) {
|
|
@@ -44,10 +46,11 @@ export function emitOk(command, data, legacyShape) {
|
|
|
44
46
|
}
|
|
45
47
|
|
|
46
48
|
/**
|
|
47
|
-
*
|
|
49
|
+
* Emit an error and exit with the corresponding exit code.
|
|
48
50
|
* @param {string} command
|
|
49
|
-
* @param {string} code - ERROR_CODES
|
|
50
|
-
* @param {object} [details] -
|
|
51
|
+
* @param {string} code - key of ERROR_CODES
|
|
52
|
+
* @param {object} [details] - extra fields. `message` overrides the default message;
|
|
53
|
+
* `legacy` fully replaces the output in legacy mode.
|
|
51
54
|
*/
|
|
52
55
|
export function emitErr(command, code, details = {}) {
|
|
53
56
|
const info = ERROR_CODES[code] || ERROR_CODES.INTERNAL_ERROR;
|
|
@@ -69,17 +72,17 @@ export function emitErr(command, code, details = {}) {
|
|
|
69
72
|
process.exit(exit);
|
|
70
73
|
}
|
|
71
74
|
|
|
72
|
-
/**
|
|
75
|
+
/** Progress log (suppressed in quiet mode) */
|
|
73
76
|
export function logInfo(msg) {
|
|
74
77
|
if (!QUIET_MODE) console.error(msg);
|
|
75
78
|
}
|
|
76
79
|
|
|
77
|
-
/**
|
|
80
|
+
/** Verbose log (only emitted in verbose mode) */
|
|
78
81
|
export function logVerbose(msg) {
|
|
79
82
|
if (VERBOSE_MODE && !QUIET_MODE) console.error(msg);
|
|
80
83
|
}
|
|
81
84
|
|
|
82
|
-
/**
|
|
85
|
+
/** Error log (still emitted in quiet mode) */
|
|
83
86
|
export function logError(msg) {
|
|
84
87
|
console.error(msg);
|
|
85
88
|
}
|
package/src/sanitize.mjs
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* CLI
|
|
2
|
+
* Card output sanitisation: redact sensitive card data (truncate the full PAN to its last 4 digits, drop CVV, drop expiry).
|
|
3
|
+
* The CLI emits JSON for an agent to parse; the agent then renders the product-specific template to the user.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
//
|
|
6
|
+
// Fields whose value should be replaced with the last-4 representation
|
|
7
7
|
const CARD_NUMBER_KEYS = new Set([
|
|
8
8
|
"cardnumber", "cardno",
|
|
9
9
|
]);
|
|
10
10
|
|
|
11
|
-
//
|
|
11
|
+
// Fields that must be removed entirely
|
|
12
12
|
const REMOVE_KEYS = new Set([
|
|
13
13
|
"cvv", "cvv2", "cvc", "cvc2", "securitycode",
|
|
14
14
|
"expiry", "expirydate", "expiredate", "cardexpiry",
|
|
@@ -16,10 +16,10 @@ const REMOVE_KEYS = new Set([
|
|
|
16
16
|
]);
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
19
|
+
* Recursively sanitise an object:
|
|
20
|
+
* - cardNumber / cardNo → keep only the last four digits ("•••• 3398")
|
|
21
|
+
* - cvv / securityCode → drop
|
|
22
|
+
* - expiry / expireDate → drop
|
|
23
23
|
*/
|
|
24
24
|
export function sanitizeOutput(obj) {
|
|
25
25
|
if (obj === null || obj === undefined) return obj;
|
|
@@ -30,15 +30,15 @@ export function sanitizeOutput(obj) {
|
|
|
30
30
|
for (const [key, value] of Object.entries(obj)) {
|
|
31
31
|
const normalized = key.toLowerCase().replace(/[-_]/g, "");
|
|
32
32
|
|
|
33
|
-
//
|
|
33
|
+
// Drop sensitive fields (CVV, expiry, etc.)
|
|
34
34
|
if (REMOVE_KEYS.has(normalized)) continue;
|
|
35
35
|
|
|
36
|
-
//
|
|
36
|
+
// Card number: only keep the last 4 digits
|
|
37
37
|
if (CARD_NUMBER_KEYS.has(normalized)) {
|
|
38
38
|
if (typeof value === "string" && value.length >= 4) {
|
|
39
39
|
result[key] = "•••• " + value.slice(-4);
|
|
40
40
|
}
|
|
41
|
-
// value
|
|
41
|
+
// when value is null, do not emit this field
|
|
42
42
|
continue;
|
|
43
43
|
}
|
|
44
44
|
|
package/src/update-check.mjs
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Auto version check + silent background upgrade.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* 1.
|
|
6
|
-
* 2.
|
|
7
|
-
* 3.
|
|
8
|
-
*
|
|
4
|
+
* Strategy:
|
|
5
|
+
* 1. Synchronously poll the npm registry (`npm view`) — print a notice when a newer version is found.
|
|
6
|
+
* 2. Spawn a detached background process to run `npm install -g`.
|
|
7
|
+
* 3. After install, run postinstall.mjs (which re-installs the skill into every detected tool via the skills CLI).
|
|
8
|
+
* Does not block the main process.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { execFileSync, spawn } from "node:child_process";
|
|
@@ -13,11 +13,11 @@ import { execFileSync, spawn } from "node:child_process";
|
|
|
13
13
|
const PKG_NAME = "@aeon-ai-pay/aigateway";
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
|
-
*
|
|
16
|
+
* Called at CLI startup: synchronous version probe + detached upgrade.
|
|
17
17
|
* @param {string} currentVersion
|
|
18
18
|
*/
|
|
19
19
|
export function checkForUpdates(currentVersion) {
|
|
20
|
-
//
|
|
20
|
+
// Synchronous fast probe (short timeout so it does not block too long)
|
|
21
21
|
let latest;
|
|
22
22
|
try {
|
|
23
23
|
latest = execFileSync("npm", ["view", PKG_NAME, "version"], {
|
|
@@ -25,15 +25,15 @@ export function checkForUpdates(currentVersion) {
|
|
|
25
25
|
stdio: ["ignore", "pipe", "ignore"],
|
|
26
26
|
}).toString().trim();
|
|
27
27
|
} catch {
|
|
28
|
-
return; //
|
|
28
|
+
return; // network unavailable — silently skip
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
if (!latest || latest === currentVersion) return;
|
|
32
32
|
|
|
33
|
-
//
|
|
33
|
+
// A newer version exists — print a notice
|
|
34
34
|
console.error(`[update] ${PKG_NAME} ${currentVersion} → ${latest}, upgrading in background...`);
|
|
35
35
|
|
|
36
|
-
//
|
|
36
|
+
// Run the upgrade in a detached child, writing the result to a log file
|
|
37
37
|
const script = `
|
|
38
38
|
const { execFileSync } = require("child_process");
|
|
39
39
|
const { join } = require("path");
|