@aeon-ai-pay/aigateway 0.1.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/CHANGELOG.md +60 -0
- package/LICENSE +21 -0
- package/README.md +116 -0
- package/bin/cli.mjs +155 -0
- package/docs/env-vars.md +73 -0
- package/docs/exit-codes.md +65 -0
- package/docs/ide-setup.md +60 -0
- package/docs/output-schema.md +188 -0
- package/docs/recipes/cron-issue-cards.md +69 -0
- package/docs/recipes/error-recovery.md +53 -0
- package/docs/recipes/integrate-in-agent.md +108 -0
- package/docs/recipes/merchant-integration.md +243 -0
- package/docs/release-process.md +98 -0
- package/docs/troubleshooting.md +200 -0
- package/package.json +58 -0
- package/scripts/postinstall.mjs +40 -0
- package/skills/aigateway/SKILL.md +370 -0
- package/skills/aigateway/references/check-status.md +68 -0
- package/skills/aigateway/references/create-card.md +114 -0
- package/skills/aigateway/references/store.md +87 -0
- package/skills/aigateway/references/x402-protocol.md +143 -0
- package/src/balance.mjs +92 -0
- package/src/commands/clean.mjs +65 -0
- package/src/commands/create-card-status.mjs +67 -0
- package/src/commands/create-card.mjs +333 -0
- package/src/commands/create-image.mjs +428 -0
- package/src/commands/wallet-balance.mjs +47 -0
- package/src/commands/wallet-gas.mjs +99 -0
- package/src/commands/wallet-init.mjs +42 -0
- package/src/commands/wallet-topup.mjs +221 -0
- package/src/commands/wallet-withdraw.mjs +183 -0
- package/src/config.mjs +50 -0
- package/src/constants.mjs +22 -0
- package/src/error-codes.mjs +50 -0
- package/src/funding.mjs +216 -0
- package/src/output.mjs +85 -0
- package/src/sanitize.mjs +48 -0
- package/src/update-check.mjs +69 -0
- package/src/walletconnect.mjs +712 -0
- package/src/x402.mjs +120 -0
- package/templates/cline/.clinerules +53 -0
- package/templates/codex/AGENTS.md +56 -0
- package/templates/cursor/.cursor/rules/aigateway.mdc +60 -0
- package/templates/windsurf/.windsurfrules +48 -0
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* topup:充值 + 一次性 approve facilitator
|
|
3
|
+
*
|
|
4
|
+
* 流程:
|
|
5
|
+
* 1. 校验 session key 已存在(来自 aigateway wallet-init)
|
|
6
|
+
* 2. 检查 USDT 余额 + facilitator allowance
|
|
7
|
+
* 3. 如果 USDT < LOW_BALANCE_THRESHOLD(1 USDT)或 allowance == 0,触发 WalletConnect 充值
|
|
8
|
+
* - 充值 amount: TTY 模式交互选择 presets [5, 10, 20, 50] 或自定义;
|
|
9
|
+
* 非 TTY 模式需要传 --amount <usdt>,否则报 TOPUP_REQUIRED
|
|
10
|
+
* - 同时按需转 0.0003 BNB 用作 approve gas
|
|
11
|
+
* 4. session key 自己广播 ERC20.approve(facilitator, MaxUint256)
|
|
12
|
+
* 5. 重新查余额 / allowance,返回最终状态
|
|
13
|
+
*/
|
|
14
|
+
import { resolve } from "../config.mjs";
|
|
15
|
+
import { getWalletBalance, getAllowance } from "../balance.mjs";
|
|
16
|
+
import {
|
|
17
|
+
fundSessionKey,
|
|
18
|
+
approveFacilitator,
|
|
19
|
+
promptTopupAmount,
|
|
20
|
+
LOW_BALANCE_THRESHOLD,
|
|
21
|
+
MIN_TOPUP_USDT,
|
|
22
|
+
TOPUP_PRESETS,
|
|
23
|
+
} from "../funding.mjs";
|
|
24
|
+
import { WalletConnectError } from "../walletconnect.mjs";
|
|
25
|
+
import { emitOk, emitErr, logInfo } from "../output.mjs";
|
|
26
|
+
|
|
27
|
+
export async function topup(opts) {
|
|
28
|
+
logInfo("Topping up wallet: verifying readiness...");
|
|
29
|
+
const privateKey = resolve(opts.privateKey, "EVM_PRIVATE_KEY", "privateKey");
|
|
30
|
+
const appId = opts.appId;
|
|
31
|
+
|
|
32
|
+
if (!privateKey) {
|
|
33
|
+
emitErr("wallet-topup", "WALLET_NOT_CONFIGURED", {
|
|
34
|
+
message: "Wallet not configured. Run: aigateway wallet-init",
|
|
35
|
+
appId,
|
|
36
|
+
});
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
let address, usdt, bnb, bnbRaw;
|
|
41
|
+
try {
|
|
42
|
+
({ address, usdt, bnb, bnbRaw } = await getWalletBalance(privateKey));
|
|
43
|
+
} catch (e) {
|
|
44
|
+
emitErr("wallet-topup", "BALANCE_CHECK_FAILED", {
|
|
45
|
+
message: `Balance check failed: ${e.message}`,
|
|
46
|
+
appId,
|
|
47
|
+
});
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
const usdtNum = parseFloat(usdt);
|
|
51
|
+
logInfo(`Wallet: ${address}`);
|
|
52
|
+
logInfo(`Balance: ${usdt} USDT, ${bnb} BNB`);
|
|
53
|
+
logInfo(`App ID: ${appId}`);
|
|
54
|
+
|
|
55
|
+
logInfo("Checking facilitator allowance...");
|
|
56
|
+
let allowance;
|
|
57
|
+
try {
|
|
58
|
+
allowance = await getAllowance(address);
|
|
59
|
+
} catch (e) {
|
|
60
|
+
emitErr("wallet-topup", "ALLOWANCE_CHECK_FAILED", {
|
|
61
|
+
message: `Allowance check failed: ${e.message}`,
|
|
62
|
+
appId,
|
|
63
|
+
});
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
logInfo(`Allowance: ${allowance === 0n ? "0 (approve required)" : "already approved"}`);
|
|
67
|
+
|
|
68
|
+
const explicitTopup = opts.amount != null && String(opts.amount).trim() !== "";
|
|
69
|
+
const balanceLow = usdtNum < LOW_BALANCE_THRESHOLD;
|
|
70
|
+
const needTopup = balanceLow || explicitTopup;
|
|
71
|
+
const needApprove = allowance === 0n;
|
|
72
|
+
const needGas = needApprove && bnbRaw === 0n;
|
|
73
|
+
|
|
74
|
+
// 已就绪:余额够 + 已 approve
|
|
75
|
+
if (!needTopup && !needApprove) {
|
|
76
|
+
logInfo("Wallet already prepared (balance ≥ minimum, facilitator approved).");
|
|
77
|
+
const data = {
|
|
78
|
+
ready: true,
|
|
79
|
+
appId,
|
|
80
|
+
address,
|
|
81
|
+
initialUsdt: usdt,
|
|
82
|
+
usdt,
|
|
83
|
+
bnb,
|
|
84
|
+
allowance: allowance.toString(),
|
|
85
|
+
topup: null,
|
|
86
|
+
approveTx: null,
|
|
87
|
+
};
|
|
88
|
+
emitOk("wallet-topup", data, { ...data, success: true });
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// 决定充值金额
|
|
93
|
+
let topupAmount = null;
|
|
94
|
+
if (needTopup) {
|
|
95
|
+
if (balanceLow) {
|
|
96
|
+
logInfo(`USDT balance ${usdtNum} < ${LOW_BALANCE_THRESHOLD} USDT threshold; a top-up of ≥ ${MIN_TOPUP_USDT} USDT is required.`);
|
|
97
|
+
} else {
|
|
98
|
+
logInfo(`Explicit top-up requested via --amount (current balance ${usdtNum} USDT).`);
|
|
99
|
+
}
|
|
100
|
+
if (explicitTopup) {
|
|
101
|
+
const amt = Number(opts.amount);
|
|
102
|
+
if (!Number.isFinite(amt) || amt <= 0) {
|
|
103
|
+
emitErr("wallet-topup", "AMOUNT_INVALID", {
|
|
104
|
+
message: `Invalid --amount: ${opts.amount}`,
|
|
105
|
+
appId,
|
|
106
|
+
});
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
if (amt < MIN_TOPUP_USDT) {
|
|
110
|
+
emitErr("wallet-topup", "TOPUP_AMOUNT_TOO_SMALL", {
|
|
111
|
+
message: `--amount ${amt} USDT is below the ${MIN_TOPUP_USDT} USDT minimum.`,
|
|
112
|
+
minTopup: MIN_TOPUP_USDT,
|
|
113
|
+
appId,
|
|
114
|
+
});
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
topupAmount = String(opts.amount);
|
|
118
|
+
logInfo(`Using --amount: ${topupAmount} USDT`);
|
|
119
|
+
} else if (process.stdin.isTTY) {
|
|
120
|
+
topupAmount = await promptTopupAmount(MIN_TOPUP_USDT);
|
|
121
|
+
logInfo(`Selected top-up amount: ${topupAmount} USDT`);
|
|
122
|
+
} else {
|
|
123
|
+
emitErr("wallet-topup", "TOPUP_REQUIRED", {
|
|
124
|
+
message: `USDT balance ${usdt} is below the ${LOW_BALANCE_THRESHOLD} USDT threshold. Choose a top-up amount and rerun with --amount <usdt>.`,
|
|
125
|
+
threshold: LOW_BALANCE_THRESHOLD,
|
|
126
|
+
minTopup: MIN_TOPUP_USDT,
|
|
127
|
+
currentBalance: usdt,
|
|
128
|
+
address,
|
|
129
|
+
appId,
|
|
130
|
+
presets: TOPUP_PRESETS,
|
|
131
|
+
hint: "Rerun: aigateway wallet-topup --amount <usdt> --app-id <appId>",
|
|
132
|
+
});
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// WalletConnect 充值(USDT + 按需 BNB gas)
|
|
138
|
+
if (needTopup || needGas) {
|
|
139
|
+
const willTransfer = [];
|
|
140
|
+
if (needTopup) willTransfer.push(`${topupAmount} USDT`);
|
|
141
|
+
if (needGas) willTransfer.push("0.0003 BNB (approve gas)");
|
|
142
|
+
logInfo(`Funding flow triggered (${willTransfer.join(" + ")})...`);
|
|
143
|
+
logInfo("Opening WalletConnect QR — please scan with your wallet app.");
|
|
144
|
+
try {
|
|
145
|
+
await fundSessionKey({
|
|
146
|
+
sessionAddress: address,
|
|
147
|
+
usdtAmount: needTopup ? topupAmount : null,
|
|
148
|
+
needGas,
|
|
149
|
+
});
|
|
150
|
+
} catch (e) {
|
|
151
|
+
if (e instanceof WalletConnectError) {
|
|
152
|
+
emitErr("wallet-topup", e.code, { message: e.message, address, appId });
|
|
153
|
+
} else {
|
|
154
|
+
emitErr("wallet-topup", "FUNDING_FAILED", {
|
|
155
|
+
message: `Funding failed: ${e.message}`,
|
|
156
|
+
address,
|
|
157
|
+
appId,
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// session key 一次性 approve facilitator
|
|
165
|
+
let approveTx = null;
|
|
166
|
+
if (needApprove) {
|
|
167
|
+
let postBnbRaw = bnbRaw;
|
|
168
|
+
if (needGas) {
|
|
169
|
+
try {
|
|
170
|
+
const post = await getWalletBalance(privateKey);
|
|
171
|
+
postBnbRaw = post.bnbRaw;
|
|
172
|
+
} catch (e) {
|
|
173
|
+
logInfo(`Post-funding balance re-check failed: ${e.message}`);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
if (postBnbRaw === 0n) {
|
|
177
|
+
emitErr("wallet-topup", "INSUFFICIENT_BNB", {
|
|
178
|
+
message: "No BNB available for approve transaction. Run 'aigateway wallet-gas' to add BNB manually.",
|
|
179
|
+
address,
|
|
180
|
+
appId,
|
|
181
|
+
});
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
try {
|
|
185
|
+
approveTx = await approveFacilitator(privateKey);
|
|
186
|
+
} catch (e) {
|
|
187
|
+
emitErr("wallet-topup", "APPROVE_FAILED", {
|
|
188
|
+
message: `Pre-authorize failed: ${e.message}`,
|
|
189
|
+
address,
|
|
190
|
+
appId,
|
|
191
|
+
});
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// 最终查余额 + allowance
|
|
197
|
+
let finalUsdt = usdt;
|
|
198
|
+
let finalBnb = bnb;
|
|
199
|
+
let finalAllowance = allowance;
|
|
200
|
+
try {
|
|
201
|
+
const final = await getWalletBalance(privateKey);
|
|
202
|
+
finalUsdt = final.usdt;
|
|
203
|
+
finalBnb = final.bnb;
|
|
204
|
+
finalAllowance = await getAllowance(address);
|
|
205
|
+
} catch (e) {
|
|
206
|
+
logInfo(`Final balance/allowance check failed: ${e.message}`);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const data = {
|
|
210
|
+
ready: true,
|
|
211
|
+
appId,
|
|
212
|
+
address,
|
|
213
|
+
initialUsdt: usdt,
|
|
214
|
+
usdt: finalUsdt,
|
|
215
|
+
bnb: finalBnb,
|
|
216
|
+
allowance: finalAllowance.toString(),
|
|
217
|
+
topup: topupAmount,
|
|
218
|
+
approveTx,
|
|
219
|
+
};
|
|
220
|
+
emitOk("wallet-topup", data, { ...data, success: true });
|
|
221
|
+
}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* withdraw 命令:将 session key 中的资金转回主钱包(USDT + BNB)
|
|
3
|
+
*/
|
|
4
|
+
import { createPublicClient, createWalletClient, http, parseUnits, formatUnits, encodeFunctionData } from "viem";
|
|
5
|
+
import { privateKeyToAccount } from "viem/accounts";
|
|
6
|
+
import { bsc } from "viem/chains";
|
|
7
|
+
import { loadConfig } from "../config.mjs";
|
|
8
|
+
import { getBalanceByAddress } from "../balance.mjs";
|
|
9
|
+
import { BSC_RPC_URL, USDT_BSC, ERC20_TRANSFER_ABI } from "../constants.mjs";
|
|
10
|
+
import { emitOk, emitErr, logInfo } from "../output.mjs";
|
|
11
|
+
|
|
12
|
+
const BNB_TRANSFER_GAS = 21000n;
|
|
13
|
+
|
|
14
|
+
export async function withdraw(opts) {
|
|
15
|
+
logInfo("Reclaiming funds...");
|
|
16
|
+
const config = loadConfig();
|
|
17
|
+
const { appId } = opts;
|
|
18
|
+
|
|
19
|
+
if (!config.privateKey || !config.address) {
|
|
20
|
+
emitErr("wallet-withdraw", "WALLET_NOT_CONFIGURED", {
|
|
21
|
+
message: "No session key found. Nothing to withdraw.",
|
|
22
|
+
appId,
|
|
23
|
+
});
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const mainWallet = opts.to || config.mainWallet;
|
|
28
|
+
if (!mainWallet) {
|
|
29
|
+
emitErr("wallet-withdraw", "NO_MAIN_WALLET", {
|
|
30
|
+
message: "No main wallet address found. Use --to <address> to specify.",
|
|
31
|
+
appId,
|
|
32
|
+
});
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const sessionAddress = config.address;
|
|
37
|
+
const account = privateKeyToAccount(config.privateKey);
|
|
38
|
+
|
|
39
|
+
const publicClient = createPublicClient({
|
|
40
|
+
chain: bsc,
|
|
41
|
+
transport: http(BSC_RPC_URL, { timeout: 15000, retryCount: 2 }),
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const walletClient = createWalletClient({
|
|
45
|
+
account,
|
|
46
|
+
chain: bsc,
|
|
47
|
+
transport: http(BSC_RPC_URL),
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const balance = await getBalanceByAddress(sessionAddress);
|
|
51
|
+
logInfo(`Session key: ${sessionAddress}`);
|
|
52
|
+
logInfo(`Balance: ${balance.usdt} USDT, ${balance.bnb} BNB`);
|
|
53
|
+
logInfo(`Withdraw to: ${mainWallet}`);
|
|
54
|
+
|
|
55
|
+
const isWithdrawAll = !opts.amount;
|
|
56
|
+
|
|
57
|
+
// 无任何资金
|
|
58
|
+
if (balance.usdtRaw === 0n && balance.bnbRaw === 0n) {
|
|
59
|
+
emitErr("wallet-withdraw", "NO_FUNDS", { message: "No funds to withdraw.", appId });
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
let usdtTxHash = null;
|
|
64
|
+
let bnbTxHash = null;
|
|
65
|
+
|
|
66
|
+
// 1. 赎回 USDT(有 USDT 才执行)
|
|
67
|
+
if (balance.usdtRaw > 0n) {
|
|
68
|
+
// USDT 转账需要 BNB 作 gas
|
|
69
|
+
if (balance.bnbRaw === 0n) {
|
|
70
|
+
emitErr("wallet-withdraw", "INSUFFICIENT_BNB", {
|
|
71
|
+
message: "No BNB for gas. Withdraw is a normal on-chain transfer and requires BNB to pay gas.",
|
|
72
|
+
address: sessionAddress,
|
|
73
|
+
appId,
|
|
74
|
+
hint: "Run 'aigateway wallet-gas' to top up BNB via WalletConnect, then retry.",
|
|
75
|
+
});
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
let withdrawAmount = balance.usdtRaw;
|
|
80
|
+
if (opts.amount) {
|
|
81
|
+
const requested = parseUnits(opts.amount, 18);
|
|
82
|
+
if (requested > balance.usdtRaw) {
|
|
83
|
+
emitErr("wallet-withdraw", "AMOUNT_EXCEEDS_BALANCE", {
|
|
84
|
+
message: `Requested ${opts.amount} USDT but only ${balance.usdt} available.`,
|
|
85
|
+
requested: opts.amount,
|
|
86
|
+
available: balance.usdt,
|
|
87
|
+
appId,
|
|
88
|
+
});
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
withdrawAmount = requested;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
try {
|
|
95
|
+
const data = encodeFunctionData({
|
|
96
|
+
abi: ERC20_TRANSFER_ABI,
|
|
97
|
+
functionName: "transfer",
|
|
98
|
+
args: [mainWallet, withdrawAmount],
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
logInfo(`\nTransferring ${formatUnits(withdrawAmount, 18)} USDT → ${mainWallet}...`);
|
|
102
|
+
usdtTxHash = await walletClient.sendTransaction({ to: USDT_BSC, data });
|
|
103
|
+
logInfo(`USDT tx: ${usdtTxHash}`);
|
|
104
|
+
|
|
105
|
+
const receipt = await publicClient.waitForTransactionReceipt({
|
|
106
|
+
hash: usdtTxHash,
|
|
107
|
+
timeout: 60_000,
|
|
108
|
+
});
|
|
109
|
+
if (receipt.status !== "success") {
|
|
110
|
+
throw new Error("USDT transfer reverted");
|
|
111
|
+
}
|
|
112
|
+
logInfo("USDT reclaimed.");
|
|
113
|
+
} catch (error) {
|
|
114
|
+
emitErr("wallet-withdraw", "WITHDRAW_FAILED", {
|
|
115
|
+
message: `USDT withdraw failed: ${error.message}`,
|
|
116
|
+
appId,
|
|
117
|
+
});
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// 2. 赎回剩余 BNB(仅赎回全部时)
|
|
123
|
+
if (isWithdrawAll) {
|
|
124
|
+
const freshBalance = balance.usdtRaw > 0n
|
|
125
|
+
? await getBalanceByAddress(sessionAddress)
|
|
126
|
+
: balance;
|
|
127
|
+
|
|
128
|
+
if (freshBalance.bnbRaw > 0n) {
|
|
129
|
+
try {
|
|
130
|
+
const gasPrice = await publicClient.getGasPrice();
|
|
131
|
+
// 预留 20% buffer 应对 gas price 波动
|
|
132
|
+
const gasCost = BNB_TRANSFER_GAS * (gasPrice * 120n / 100n);
|
|
133
|
+
const sendable = freshBalance.bnbRaw - gasCost;
|
|
134
|
+
|
|
135
|
+
if (sendable > 0n) {
|
|
136
|
+
logInfo(`Transferring ${formatUnits(sendable, 18)} BNB → ${mainWallet}...`);
|
|
137
|
+
bnbTxHash = await walletClient.sendTransaction({
|
|
138
|
+
to: mainWallet,
|
|
139
|
+
value: sendable,
|
|
140
|
+
gas: BNB_TRANSFER_GAS,
|
|
141
|
+
gasPrice,
|
|
142
|
+
});
|
|
143
|
+
logInfo(`BNB tx: ${bnbTxHash}`);
|
|
144
|
+
|
|
145
|
+
const receipt = await publicClient.waitForTransactionReceipt({
|
|
146
|
+
hash: bnbTxHash,
|
|
147
|
+
timeout: 60_000,
|
|
148
|
+
});
|
|
149
|
+
if (receipt.status !== "success") {
|
|
150
|
+
throw new Error("BNB transfer reverted");
|
|
151
|
+
}
|
|
152
|
+
logInfo("BNB reclaimed.");
|
|
153
|
+
} else {
|
|
154
|
+
logInfo("BNB balance too small to cover transfer gas, skipping.");
|
|
155
|
+
}
|
|
156
|
+
} catch (error) {
|
|
157
|
+
logInfo(`Warning: BNB reclaim failed (${error.message}).`);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// 查询最终余额
|
|
163
|
+
let finalBalance;
|
|
164
|
+
try {
|
|
165
|
+
finalBalance = await getBalanceByAddress(sessionAddress);
|
|
166
|
+
} catch {
|
|
167
|
+
finalBalance = { usdt: "unknown", bnb: "unknown" };
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const data = {
|
|
171
|
+
appId,
|
|
172
|
+
to: mainWallet,
|
|
173
|
+
transactions: {
|
|
174
|
+
usdt: usdtTxHash,
|
|
175
|
+
bnb: bnbTxHash,
|
|
176
|
+
},
|
|
177
|
+
remaining: {
|
|
178
|
+
usdt: finalBalance.usdt,
|
|
179
|
+
bnb: finalBalance.bnb,
|
|
180
|
+
},
|
|
181
|
+
};
|
|
182
|
+
emitOk("wallet-withdraw", data, { success: true, ...data });
|
|
183
|
+
}
|
package/src/config.mjs
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 配置管理:~/.aigateway/config.json
|
|
3
|
+
* 优先级:CLI 参数 > 环境变量 > config.json
|
|
4
|
+
*
|
|
5
|
+
* AEON AI Gateway 统一使用同一个 x402 服务端(ai-api.aeon.xyz),
|
|
6
|
+
* 不同能力(虚拟卡 / Skill Boss 调用)走不同的路径前缀。
|
|
7
|
+
*/
|
|
8
|
+
import { readFileSync, writeFileSync, mkdirSync, chmodSync } from "fs";
|
|
9
|
+
import { join } from "path";
|
|
10
|
+
import { homedir } from "os";
|
|
11
|
+
|
|
12
|
+
const CONFIG_DIR = join(homedir(), ".aigateway");
|
|
13
|
+
const CONFIG_FILE = join(CONFIG_DIR, "config.json");
|
|
14
|
+
|
|
15
|
+
const DEFAULTS = {
|
|
16
|
+
serviceUrl: "https://ai-api.aeon.xyz",
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export function loadConfig() {
|
|
20
|
+
try {
|
|
21
|
+
return { ...DEFAULTS, ...JSON.parse(readFileSync(CONFIG_FILE, "utf-8")) };
|
|
22
|
+
} catch {
|
|
23
|
+
return { ...DEFAULTS };
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function saveConfig(config) {
|
|
28
|
+
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
29
|
+
writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), { mode: 0o600 });
|
|
30
|
+
chmodSync(CONFIG_FILE, 0o600);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* 解析配置值,优先级:cliValue > envKey > config[configKey]
|
|
35
|
+
*/
|
|
36
|
+
export function resolve(cliValue, envKey, configKey) {
|
|
37
|
+
if (cliValue) return cliValue;
|
|
38
|
+
if (process.env[envKey]) return process.env[envKey];
|
|
39
|
+
const cfg = loadConfig();
|
|
40
|
+
return cfg[configKey] || undefined;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function getConfigPath() {
|
|
44
|
+
return CONFIG_FILE;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function isSessionKeyMode() {
|
|
48
|
+
const config = loadConfig();
|
|
49
|
+
return config.mode === "session-key";
|
|
50
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export const MIN_AMOUNT = 0.6;
|
|
2
|
+
export const MAX_AMOUNT = 800;
|
|
3
|
+
export const POLL_INTERVAL = 5000;
|
|
4
|
+
export const MAX_POLLS = 42;
|
|
5
|
+
export const BSC_RPC_URL = "https://few-boldest-spring.bsc.quiknode.pro/ec468d8a1ea2c310457b2e2f4eea257e62ba3b1e/";
|
|
6
|
+
export const USDT_BSC = "0x55d398326f99059fF775485246999027B3197955";
|
|
7
|
+
export const FACILITATOR_ADDRESS = "0x555e3311a9893c9B17444C1Ff0d88192a57Ef13e";
|
|
8
|
+
export const DEFAULT_WC_PROJECT_ID = "1c5e29cd4b466f52393cd39d05ec265c";
|
|
9
|
+
export const WC_CONNECT_TIMEOUT_MS = 5 * 60 * 1000;
|
|
10
|
+
|
|
11
|
+
export const ERC20_TRANSFER_ABI = [
|
|
12
|
+
{
|
|
13
|
+
name: "transfer",
|
|
14
|
+
type: "function",
|
|
15
|
+
inputs: [
|
|
16
|
+
{ name: "to", type: "address" },
|
|
17
|
+
{ name: "amount", type: "uint256" },
|
|
18
|
+
],
|
|
19
|
+
outputs: [{ name: "success", type: "bool" }],
|
|
20
|
+
stateMutability: "nonpayable",
|
|
21
|
+
},
|
|
22
|
+
];
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 错误码常量表 —— CLI 与文档的单一事实源
|
|
3
|
+
*
|
|
4
|
+
* 退出码语义:
|
|
5
|
+
* 0 成功
|
|
6
|
+
* 1 用户错误(参数、余额、配置、用户拒绝)
|
|
7
|
+
* 2 超时(轮询、WalletConnect、签名、链上)
|
|
8
|
+
* 3 服务端 / 网络
|
|
9
|
+
* 4 内部错误
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
export const ERROR_CODES = {
|
|
13
|
+
// ===== 用户错误(exit 1)=====
|
|
14
|
+
WALLET_NOT_CONFIGURED: { exit: 1, message: "Wallet not configured. Run: aigateway wallet-init" },
|
|
15
|
+
SERVICE_URL_MISSING: { exit: 1, message: "Service URL not configured." },
|
|
16
|
+
AMOUNT_INVALID: { exit: 1, message: "Invalid amount." },
|
|
17
|
+
AMOUNT_OUT_OF_RANGE: { exit: 1, message: "Amount is outside the allowed range." },
|
|
18
|
+
AMOUNT_EXCEEDS_BALANCE: { exit: 1, message: "Requested amount exceeds available balance." },
|
|
19
|
+
INSUFFICIENT_USDT: { exit: 1, message: "Insufficient USDT balance." },
|
|
20
|
+
INSUFFICIENT_BNB: { exit: 1, message: "Insufficient BNB for gas." },
|
|
21
|
+
NO_FUNDS: { exit: 1, message: "No funds available." },
|
|
22
|
+
NO_MAIN_WALLET: { exit: 1, message: "No main wallet address configured. Use --to <address>." },
|
|
23
|
+
MISSING_PROMPT: { exit: 1, message: "Missing --prompt. Provide a non-empty image prompt." },
|
|
24
|
+
TOPUP_REQUIRED: { exit: 1, message: "Wallet top-up required. Choose an amount and rerun with --topup-amount <usdt>." },
|
|
25
|
+
TOPUP_AMOUNT_TOO_SMALL: { exit: 1, message: "Top-up amount is below the minimum." },
|
|
26
|
+
PAYMENT_REJECTED: { exit: 1, message: "Payment approval was rejected. Please try again if you'd like to proceed." },
|
|
27
|
+
|
|
28
|
+
// ===== 超时(exit 2)=====
|
|
29
|
+
PAYMENT_TIMEOUT: { exit: 2, message: "Payment approval timed out. Please try again." },
|
|
30
|
+
WC_SESSION_EXPIRED: { exit: 2, message: "WalletConnect session expired." },
|
|
31
|
+
POLL_TIMEOUT: { exit: 2, message: "Polling timed out. Card may still be provisioning." },
|
|
32
|
+
TX_TIMEOUT: { exit: 2, message: "On-chain transaction timed out." },
|
|
33
|
+
|
|
34
|
+
// ===== 服务/网络(exit 3)=====
|
|
35
|
+
SERVICE_UNAVAILABLE: { exit: 3, message: "Service unavailable or network error." },
|
|
36
|
+
PAYMENT_FETCH_FAILED: { exit: 3, message: "Failed to fetch payment requirements." },
|
|
37
|
+
BALANCE_CHECK_FAILED: { exit: 3, message: "Failed to check balance." },
|
|
38
|
+
ALLOWANCE_CHECK_FAILED: { exit: 3, message: "Failed to check allowance." },
|
|
39
|
+
TX_REVERTED: { exit: 3, message: "On-chain transaction reverted." },
|
|
40
|
+
WITHDRAW_FAILED: { exit: 3, message: "Withdraw transaction failed." },
|
|
41
|
+
APPROVE_FAILED: { exit: 3, message: "Pre-authorization (approve) failed." },
|
|
42
|
+
INVALID_PAYMENT_AMOUNT: { exit: 3, message: "Server returned invalid payment amount." },
|
|
43
|
+
PAYMENT_FAILED: { exit: 3, message: "Payment request failed." },
|
|
44
|
+
IMAGE_DOWNLOAD_FAILED: { exit: 3, message: "Image download failed." },
|
|
45
|
+
FUNDING_FAILED: { exit: 3, message: "Funding flow failed." },
|
|
46
|
+
|
|
47
|
+
// ===== 内部(exit 4)=====
|
|
48
|
+
INTERNAL_ERROR: { exit: 4, message: "Internal error." },
|
|
49
|
+
WALLET_ERROR: { exit: 1, message: "Wallet operation failed." },
|
|
50
|
+
};
|