@quackai/q402-mcp 0.8.18 → 0.8.19

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.
Files changed (3) hide show
  1. package/README.md +6 -2
  2. package/dist/index.js +16 -11
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -156,7 +156,7 @@ Then export the values in `~/.zshrc` / `~/.bashrc`. See the [Codex config refere
156
156
 
157
157
  ## Tools exposed
158
158
 
159
- **20 tools** — read-only by default; live mode needs an API key + signing path + `Q402_ENABLE_REAL_PAYMENTS=1`.
159
+ **24 tools** — read-only by default; live mode needs an API key + signing path + `Q402_ENABLE_REAL_PAYMENTS=1`.
160
160
 
161
161
  | Tool | Auth | Purpose |
162
162
  |---|---|---|
@@ -180,8 +180,12 @@ Then export the values in `~/.zshrc` / `~/.bashrc`. See the [Codex config refere
180
180
  | `q402_bridge_send` | live mode | Execute a CCIP bridge from the user's Agent Wallet. Mode C only (server-managed). Sandbox-by-default; `sandbox: false` + live Multichain key + `Q402_ENABLE_REAL_PAYMENTS=1` fires a real on-chain bridge. |
181
181
  | `q402_bridge_history` | not yet wired | Pointer to the dashboard. Returns `{ implemented: false, dashboardUrl, dashboardPath }` — read-only guidance until owner-sig auth lands in MCP. |
182
182
  | `q402_bridge_gas_tank` | not yet wired | Static guidance + dashboard pointer for the Bridge Gas Tank top-up flow. Live balance lookup needs owner-sig auth (dashboard for now). |
183
+ | `q402_yield_reserves` | none | List Q402 Yield (Aave V3) lending markets — protocol, chain, asset, market address, supply APY. BNB Chain only today. |
184
+ | `q402_yield_positions` | api key | Show the Agent Wallet's open Q402 Yield positions (balance, principal, accrued interest, APY) + total supplied in USD. Mode C. |
185
+ | `q402_yield_deposit` | live mode | Supply the Agent Wallet's USDC/USDT into Aave V3 (Q402 Yield) to earn supply APY. Mode C, BNB-only. Requires `confirm: true`; sandbox-by-default. |
186
+ | `q402_yield_withdraw` | live mode | Withdraw supplied USDC/USDT out of Aave V3 back to the Agent Wallet (`amount: "max"` = full position). Mode C, BNB-only. Requires `confirm: true`; sandbox-by-default. |
183
187
 
184
- `q402_pay` + `q402_batch_pay` + `q402_bridge_send` require explicit in-chat confirmation. Batch confirmation = full batch, not per-row.
188
+ `q402_pay` + `q402_batch_pay` + `q402_bridge_send` + `q402_yield_deposit` + `q402_yield_withdraw` require explicit in-chat confirmation. Batch confirmation = full batch, not per-row.
185
189
 
186
190
  > ℹ️ `q402_pay` expects a 0x address — ENS isn't resolved server-side. Resolve client-side first.
187
191
  > Per-chain Gas Tank balances + full TX history live in the [dashboard](https://q402.quackai.ai/dashboard) (wallet-signature only).
package/dist/index.js CHANGED
@@ -212,7 +212,7 @@ var isValidPrivateKey = (s) => typeof s === "string" && PRIVATE_KEY_RE.test(s);
212
212
  // package.json
213
213
  var package_default = {
214
214
  name: "@quackai/q402-mcp",
215
- version: "0.8.18",
215
+ version: "0.8.19",
216
216
  description: "MCP server for Q402 \u2014 gasless USDC/USDT/RLUSD payments on 10 EVM chains + Chainlink CCIP USDC bridge on the eth/avax/arbitrum triangle, callable from Claude (Desktop / Code), OpenAI Codex CLI, and any other Model Context Protocol client.",
217
217
  mcpName: "io.github.bitgett/q402-mcp",
218
218
  keywords: [
@@ -3480,7 +3480,10 @@ async function runYieldPositions(input) {
3480
3480
  };
3481
3481
  }
3482
3482
  const positions = data.positions ?? [];
3483
- const summary = positions.length ? `Total supplied: $${(data.totalSuppliedUsd ?? 0).toFixed(2)} across ${positions.length} position(s).` : "No open Q402 Yield positions for this wallet.";
3483
+ const unavailableChains = Array.isArray(data.unavailableChains) ? data.unavailableChains : [];
3484
+ const hasUnavailable = data.unavailable === true || unavailableChains.length > 0;
3485
+ const unavailableNote = hasUnavailable ? ` Some chains could not be read this cycle${unavailableChains.length ? ` (${unavailableChains.join(", ")})` : ""} \u2014 this snapshot may be incomplete; retry before treating a balance as zero.` : "";
3486
+ const summary = positions.length ? `Total supplied: $${(data.totalSuppliedUsd ?? 0).toFixed(2)} across ${positions.length} position(s).${unavailableNote}` : hasUnavailable ? `No positions returned, but a read failure occurred${unavailableChains.length ? ` on ${unavailableChains.join(", ")}` : ""} \u2014 this is NOT a confirmed empty position; retry.` : "No open Q402 Yield positions for this wallet.";
3484
3487
  return {
3485
3488
  content: [
3486
3489
  { type: "text", text: summary },
@@ -3493,7 +3496,9 @@ async function runYieldPositions(input) {
3493
3496
  supplyApyPct: Math.round(p.supplyApy * 100 * 100) / 100
3494
3497
  })),
3495
3498
  totalSuppliedUsd: data.totalSuppliedUsd ?? null,
3496
- asOf: data.asOf ?? null
3499
+ asOf: data.asOf ?? null,
3500
+ unavailable: hasUnavailable,
3501
+ unavailableChains
3497
3502
  }, null, 2)
3498
3503
  }
3499
3504
  ]
@@ -3501,7 +3506,7 @@ async function runYieldPositions(input) {
3501
3506
  }
3502
3507
 
3503
3508
  // src/tools/yield-deposit.ts
3504
- import { id } from "ethers";
3509
+ import { hexlify as hexlify2, randomBytes as randomBytes2 } from "ethers";
3505
3510
  import { z as z16 } from "zod";
3506
3511
  var YieldDepositInputSchema = z16.object({
3507
3512
  chain: z16.enum(["bnb"]).default("bnb").describe("Chain the Aave market lives on. Q402 Yield is BNB-only today \u2014 only 'bnb' is accepted."),
@@ -3511,7 +3516,7 @@ var YieldDepositInputSchema = z16.object({
3511
3516
  "Optional Agent Wallet address to supply from (max 10 per owner). Omit to use Q402_AGENT_WALLET_ADDRESS env, then the owner's default wallet (resolved server-side from the API key)."
3512
3517
  ),
3513
3518
  idempotencyKey: z16.string().optional().describe(
3514
- "Optional durable idempotency key for this logical deposit. When omitted the tool derives a STABLE key from (walletId, chain, token, amount) so a lost response can be safely retried without double-supplying. Pass your own only if you need two genuinely distinct same-amount deposits to be treated separately."
3519
+ "Optional durable idempotency key for this logical deposit. When omitted the tool generates a FRESH random key per invocation, so each call executes a distinct deposit (two intentional same-amount deposits both run, and a re-deposit after a withdrawal is NOT replayed). Pass your own STABLE key only when you want retry-safety: re-calling with the same key replays the first result instead of double-supplying."
3515
3520
  ),
3516
3521
  confirm: z16.boolean().optional().describe(
3517
3522
  "MUST be true to actually supply funds. Set this only after the user has explicitly approved this exact deposit (amount, token, chain, wallet) in the conversation. When omitted or false the tool previews the action and does NOT move any funds."
@@ -3543,7 +3548,7 @@ var YIELD_DEPOSIT_TOOL = {
3543
3548
  },
3544
3549
  idempotencyKey: {
3545
3550
  type: "string",
3546
- description: "Optional durable idempotency key. Omit and the tool derives a stable key from (walletId, chain, token, amount) so a lost response is safe to retry without double-supplying. Pass your own only to force two same-amount deposits apart."
3551
+ description: "Optional durable idempotency key. Omit and the tool generates a FRESH random key per invocation, so every call executes a distinct deposit. Pass your own STABLE key only for opt-in retry-safety \u2014 re-calling with the same key replays the first result instead of double-supplying."
3547
3552
  },
3548
3553
  confirm: {
3549
3554
  type: "boolean",
@@ -3565,7 +3570,7 @@ async function runYieldDeposit(input) {
3565
3570
  };
3566
3571
  }
3567
3572
  const walletId = typeof input.walletId === "string" && input.walletId.length > 0 ? input.walletId.toLowerCase() : CONFIG.walletId ?? void 0;
3568
- const idempotencyKey = typeof input.idempotencyKey === "string" && input.idempotencyKey.length > 0 ? input.idempotencyKey : id(`yield:supply:${walletId ?? "default"}:${input.chain}:${input.token}:${input.amount}`);
3573
+ const idempotencyKey = typeof input.idempotencyKey === "string" && input.idempotencyKey.length > 0 ? input.idempotencyKey : hexlify2(randomBytes2(32));
3569
3574
  if (input.confirm !== true) {
3570
3575
  const walletDesc = walletId ? `wallet ${walletId}` : "your default Agent Wallet";
3571
3576
  return {
@@ -3651,7 +3656,7 @@ async function runYieldDeposit(input) {
3651
3656
  }
3652
3657
 
3653
3658
  // src/tools/yield-withdraw.ts
3654
- import { id as id2 } from "ethers";
3659
+ import { hexlify as hexlify3, randomBytes as randomBytes3 } from "ethers";
3655
3660
  import { z as z17 } from "zod";
3656
3661
  var YieldWithdrawInputSchema = z17.object({
3657
3662
  chain: z17.enum(["bnb"]).default("bnb").describe("Chain the Aave market lives on. Q402 Yield is BNB-only today \u2014 only 'bnb' is accepted."),
@@ -3661,7 +3666,7 @@ var YieldWithdrawInputSchema = z17.object({
3661
3666
  "Optional Agent Wallet address to withdraw to (max 10 per owner). Omit to use Q402_AGENT_WALLET_ADDRESS env, then the owner's default wallet (resolved server-side from the API key)."
3662
3667
  ),
3663
3668
  idempotencyKey: z17.string().optional().describe(
3664
- "Optional durable idempotency key for this logical withdrawal. When omitted the tool derives a STABLE key from (walletId, chain, token, amount) so a lost response can be safely retried without double-withdrawing. Pass your own only if you need two genuinely distinct same-amount withdrawals to be treated separately."
3669
+ "Optional durable idempotency key for this logical withdrawal. When omitted the tool generates a FRESH random key per invocation, so each call executes a distinct withdrawal (a re-deposit followed by another `withdraw max` is NOT replayed). Pass your own STABLE key only when you want retry-safety: re-calling with the same key replays the first result instead of double-withdrawing."
3665
3670
  ),
3666
3671
  confirm: z17.boolean().optional().describe(
3667
3672
  "MUST be true to actually withdraw funds. Set this only after the user has explicitly approved this exact withdrawal (amount, token, chain, wallet) in the conversation. When omitted or false the tool previews the action and does NOT move any funds."
@@ -3693,7 +3698,7 @@ var YIELD_WITHDRAW_TOOL = {
3693
3698
  },
3694
3699
  idempotencyKey: {
3695
3700
  type: "string",
3696
- description: "Optional durable idempotency key. Omit and the tool derives a stable key from (walletId, chain, token, amount) so a lost response is safe to retry without double-withdrawing. Pass your own only to force two same-amount withdrawals apart."
3701
+ description: "Optional durable idempotency key. Omit and the tool generates a FRESH random key per invocation, so every call executes a distinct withdrawal. Pass your own STABLE key only for opt-in retry-safety \u2014 re-calling with the same key replays the first result instead of double-withdrawing."
3697
3702
  },
3698
3703
  confirm: {
3699
3704
  type: "boolean",
@@ -3715,7 +3720,7 @@ async function runYieldWithdraw(input) {
3715
3720
  };
3716
3721
  }
3717
3722
  const walletId = typeof input.walletId === "string" && input.walletId.length > 0 ? input.walletId.toLowerCase() : CONFIG.walletId ?? void 0;
3718
- const idempotencyKey = typeof input.idempotencyKey === "string" && input.idempotencyKey.length > 0 ? input.idempotencyKey : id2(`yield:withdraw:${walletId ?? "default"}:${input.chain}:${input.token}:${input.amount}`);
3723
+ const idempotencyKey = typeof input.idempotencyKey === "string" && input.idempotencyKey.length > 0 ? input.idempotencyKey : hexlify3(randomBytes3(32));
3719
3724
  const amountDesc = input.amount === "max" ? "the FULL position" : `${input.amount} ${input.token}`;
3720
3725
  if (input.confirm !== true) {
3721
3726
  const walletDesc = walletId ? `wallet ${walletId}` : "your default Agent Wallet";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quackai/q402-mcp",
3
- "version": "0.8.18",
3
+ "version": "0.8.19",
4
4
  "description": "MCP server for Q402 — gasless USDC/USDT/RLUSD payments on 10 EVM chains + Chainlink CCIP USDC bridge on the eth/avax/arbitrum triangle, callable from Claude (Desktop / Code), OpenAI Codex CLI, and any other Model Context Protocol client.",
5
5
  "mcpName": "io.github.bitgett/q402-mcp",
6
6
  "keywords": [