@aeon-ai-pay/aigateway 0.2.4 → 0.2.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/docs/troubleshooting.md +1 -1
- package/package.json +1 -1
- package/skills/aigateway/SKILL.md +1 -1
- package/src/commands/wallet-init.mjs +46 -17
- package/src/coupon.mjs +53 -7
package/docs/troubleshooting.md
CHANGED
|
@@ -165,7 +165,7 @@ The skill version is locked to `metadata.version` in the frontmatter. If you edi
|
|
|
165
165
|
|
|
166
166
|
```yaml
|
|
167
167
|
metadata:
|
|
168
|
-
version: "0.2.
|
|
168
|
+
version: "0.2.5" # was 0.2.5
|
|
169
169
|
```
|
|
170
170
|
|
|
171
171
|
Then `npx skills add AEON-Project/aigateway -g -y -f` (force) or re-run postinstall.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aeon-ai-pay/aigateway",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.5",
|
|
4
4
|
"description": "AI Agents discover, invoke, and settle paid LLMs, APIs, and Skills — starting with Skill Boss. No manual key setup. No prepayment. Pay-per-call via x402 or Agent Card.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
TOPUP_PRESETS,
|
|
21
21
|
} from "../funding.mjs";
|
|
22
22
|
import { emitOk, logInfo } from "../output.mjs";
|
|
23
|
-
import { claimCoupon } from "../coupon.mjs";
|
|
23
|
+
import { checkCouponStatus, claimCoupon } from "../coupon.mjs";
|
|
24
24
|
|
|
25
25
|
export async function initWallet(opts) {
|
|
26
26
|
const config = loadConfig();
|
|
@@ -94,23 +94,52 @@ export async function initWallet(opts) {
|
|
|
94
94
|
// device id 在首次 init 时生成,后续 claim / 审计复用
|
|
95
95
|
const deviceId = getOrCreateDeviceId();
|
|
96
96
|
|
|
97
|
-
// AEON x BNB Chain AI Agent Campaign —
|
|
98
|
-
//
|
|
97
|
+
// AEON x BNB Chain AI Agent Campaign — 两步同步流程:
|
|
98
|
+
// 1) status check:已领 → 跳过,直接走后续流程
|
|
99
|
+
// 2) 未领 → 输出"申请中..." → claim 同步阻塞 → 输出 mint 结果
|
|
100
|
+
// 任何网络异常都安静 log,不阻塞 wallet-init 主流程。
|
|
99
101
|
let coupon = null;
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
102
|
+
const serviceUrl = resolve(opts.serviceUrl, "AIGATEWAY_SERVICE_URL", "serviceUrl");
|
|
103
|
+
if (serviceUrl && config.address) {
|
|
104
|
+
const status = await checkCouponStatus({ serviceUrl, userAddress: config.address });
|
|
105
|
+
if (status.ok && status.claimed) {
|
|
106
|
+
// 已领过 → 直接走后续流程
|
|
107
|
+
logInfo(`Coupon already claimed (status=${status.mintStatus}${status.mintTxHash ? ", tx=" + status.mintTxHash : ""}).`);
|
|
108
|
+
coupon = {
|
|
109
|
+
ok: status.mintStatus === "SUCCESS",
|
|
110
|
+
code: status.mintStatus === "SUCCESS" ? "ALREADY_CLAIMED_SUCCESS" : "ALREADY_CLAIMED_" + status.mintStatus,
|
|
111
|
+
tokenAddress: status.tokenAddress,
|
|
112
|
+
tokenAmount: status.tokenAmount,
|
|
113
|
+
txHash: status.mintTxHash,
|
|
114
|
+
campaignId: status.campaignId,
|
|
115
|
+
};
|
|
116
|
+
} else if (status.ok && !status.claimed) {
|
|
117
|
+
// 未领 → 申请,同步阻塞
|
|
118
|
+
logInfo("🎁 Claiming AEON x BNB campaign coupon, please wait...");
|
|
119
|
+
coupon = await claimCoupon({
|
|
120
|
+
serviceUrl,
|
|
121
|
+
userAddress: config.address,
|
|
122
|
+
deviceId,
|
|
123
|
+
appId,
|
|
124
|
+
});
|
|
125
|
+
if (coupon?.ok) {
|
|
126
|
+
logInfo(`✅ Coupon claimed: ${coupon.tokenAmount} credits (tx: ${coupon.txHash})`);
|
|
127
|
+
} else if (coupon?.code === "ALREADY_CLAIMED") {
|
|
128
|
+
// status 和 claim 之间有人抢着领过(罕见 race),按已领处理
|
|
129
|
+
logInfo("Coupon already claimed (race with status check).");
|
|
130
|
+
} else if (coupon?.code === "CAMPAIGN_QUOTA_EXHAUSTED") {
|
|
131
|
+
logInfo("⚠️ Coupon campaign quota exhausted.");
|
|
132
|
+
} else if (coupon?.code === "MINT_FAILED") {
|
|
133
|
+
logInfo(`❌ Coupon mint failed: ${coupon.errorMsg}`);
|
|
134
|
+
} else if (coupon?.code === "CLAIM_NETWORK_ERROR") {
|
|
135
|
+
logInfo(`Coupon claim network error: ${coupon.errorMsg}`);
|
|
136
|
+
} else if (coupon?.code) {
|
|
137
|
+
logInfo(`Coupon claim skipped: ${coupon.code} — ${coupon.errorMsg || ""}`);
|
|
138
|
+
}
|
|
139
|
+
} else {
|
|
140
|
+
// status check 自己网络失败 — 静默,不阻塞 wallet-init
|
|
141
|
+
logInfo(`Coupon status check unreachable: ${status.errorMsg}`);
|
|
142
|
+
coupon = { ok: false, code: status.code, errorMsg: status.errorMsg };
|
|
114
143
|
}
|
|
115
144
|
}
|
|
116
145
|
|
package/src/coupon.mjs
CHANGED
|
@@ -1,17 +1,63 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Coupon client — AEON x BNB Chain AI Agent Campaign.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* 两步流程:
|
|
5
|
+
* 1) checkCouponStatus(...) — GET /open/api/coupon/status → 已领过 / 未领
|
|
6
|
+
* 2) 仅未领时调用 claimCoupon(...) — POST /open/api/coupon/claim → 同步 mint 结果
|
|
7
|
+
*
|
|
5
8
|
* 返回归一化结构:
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
+
* checkCouponStatus:
|
|
10
|
+
* { ok: true, claimed: true, mintStatus, mintTxHash, tokenAddress, tokenAmount, campaignId, claimedAt }
|
|
11
|
+
* { ok: true, claimed: false, campaignId }
|
|
12
|
+
* { ok: false, code: "STATUS_NETWORK_ERROR", errorMsg }
|
|
13
|
+
*
|
|
14
|
+
* claimCoupon:
|
|
15
|
+
* { ok: true, code: "SUCCESS", tokenAmount, tokenAddress, txHash, campaignId }
|
|
16
|
+
* { ok: false, code: "ALREADY_CLAIMED" | "CAMPAIGN_QUOTA_EXHAUSTED" | "MINT_FAILED" | ...,
|
|
17
|
+
* errorMsg, status? }
|
|
18
|
+
* { ok: false, code: "CLAIM_NETWORK_ERROR", errorMsg }
|
|
9
19
|
*
|
|
10
|
-
*
|
|
11
|
-
* 让调用方(wallet-init)继续主流程,不要因为优惠券领取失败阻塞钱包初始化。
|
|
20
|
+
* 网络层错误吞掉,让调用方决定是否阻塞主流程。
|
|
12
21
|
*/
|
|
13
22
|
import axios from "axios";
|
|
14
23
|
|
|
24
|
+
/** 查询钱包是否已领过当前活动(不发起 mint) */
|
|
25
|
+
export async function checkCouponStatus({ serviceUrl, userAddress, campaignId }) {
|
|
26
|
+
if (!serviceUrl) {
|
|
27
|
+
return { ok: false, code: "SERVICE_URL_MISSING", errorMsg: "serviceUrl is required" };
|
|
28
|
+
}
|
|
29
|
+
if (!userAddress) {
|
|
30
|
+
return { ok: false, code: "INVALID_PARAM", errorMsg: "userAddress is required" };
|
|
31
|
+
}
|
|
32
|
+
const params = new URLSearchParams({ userAddress });
|
|
33
|
+
if (campaignId) params.set("campaignId", campaignId);
|
|
34
|
+
const url = `${serviceUrl}/open/api/coupon/status?${params}`;
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
const resp = await axios.get(url, { timeout: 10_000 });
|
|
38
|
+
const envelope = resp.data;
|
|
39
|
+
const result = envelope?.model || envelope?.data || envelope;
|
|
40
|
+
return {
|
|
41
|
+
ok: true,
|
|
42
|
+
claimed: !!result?.claimed,
|
|
43
|
+
campaignId: result?.campaignId,
|
|
44
|
+
mintStatus: result?.mintStatus || null,
|
|
45
|
+
mintTxHash: result?.mintTxHash || null,
|
|
46
|
+
tokenAddress: result?.tokenAddress || null,
|
|
47
|
+
tokenAmount: result?.tokenAmount ?? null,
|
|
48
|
+
errorMsg: result?.errorMsg || null,
|
|
49
|
+
claimedAt: result?.claimedAt || null,
|
|
50
|
+
};
|
|
51
|
+
} catch (e) {
|
|
52
|
+
return {
|
|
53
|
+
ok: false,
|
|
54
|
+
code: "STATUS_NETWORK_ERROR",
|
|
55
|
+
errorMsg: e.message,
|
|
56
|
+
status: e.response?.status,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
15
61
|
export async function claimCoupon({ serviceUrl, userAddress, deviceId, appId, campaignId }) {
|
|
16
62
|
if (!serviceUrl) {
|
|
17
63
|
return { ok: false, code: "SERVICE_URL_MISSING", errorMsg: "serviceUrl is required" };
|
|
@@ -30,7 +76,7 @@ export async function claimCoupon({ serviceUrl, userAddress, deviceId, appId, ca
|
|
|
30
76
|
|
|
31
77
|
try {
|
|
32
78
|
const resp = await axios.post(url, body, {
|
|
33
|
-
timeout:
|
|
79
|
+
timeout: 15_000,
|
|
34
80
|
headers: { "Content-Type": "application/json" },
|
|
35
81
|
});
|
|
36
82
|
// 服务端约定:HTTP 200 + APIResponse 包装 + data 字段 = CouponClaimResult
|