@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.
@@ -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.4" # was 0.2.4
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.4",
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": {
@@ -26,7 +26,7 @@ description: >
26
26
  emoji: "🛰️"
27
27
  homepage: https://github.com/AEON-Project/aigateway
28
28
  metadata:
29
- version: "0.2.4"
29
+ version: "0.2.5"
30
30
  author: AEON-Project
31
31
  openclaw:
32
32
  requires:
@@ -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 — wallet 新建时尝试领取一次优惠券 token
98
- // 已领过 / 名额满 / 服务端不通 安静失败,不阻塞 wallet-init 主流程
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
- if (created) {
101
- const serviceUrl = resolve(opts.serviceUrl, "AIGATEWAY_SERVICE_URL", "serviceUrl");
102
- coupon = await claimCoupon({
103
- serviceUrl,
104
- userAddress: config.address,
105
- deviceId,
106
- appId,
107
- });
108
- if (coupon?.ok) {
109
- logInfo(`🎁 Claimed ${coupon.tokenAmount} coupon credits (tx: ${coupon.txHash})`);
110
- } else if (coupon?.code === "ALREADY_CLAIMED") {
111
- logInfo("Coupon already claimed before.");
112
- } else if (coupon?.code) {
113
- logInfo(`Coupon claim skipped: ${coupon.code} — ${coupon.errorMsg || ""}`);
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
- * 调用服务端 POST /open/api/coupon/claim 申请优惠券 token。
4
+ * 两步流程:
5
+ * 1) checkCouponStatus(...) — GET /open/api/coupon/status → 已领过 / 未领
6
+ * 2) 仅未领时调用 claimCoupon(...) — POST /open/api/coupon/claim → 同步 mint 结果
7
+ *
5
8
  * 返回归一化结构:
6
- * { ok: true, code: "SUCCESS", tokenAmount, tokenAddress, txHash, campaignId }
7
- * { ok: false, code: "ALREADY_CLAIMED" | "CAMPAIGN_QUOTA_EXHAUSTED" | "MINT_FAILED" | ...,
8
- * errorMsg, status? }
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
- * 错误吞掉(网络/超时/任何异常)返回 { ok: false, code: "CLAIM_NETWORK_ERROR", errorMsg },
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: 60_000,
79
+ timeout: 15_000,
34
80
  headers: { "Content-Type": "application/json" },
35
81
  });
36
82
  // 服务端约定:HTTP 200 + APIResponse 包装 + data 字段 = CouponClaimResult