@quackai/q402-mcp 0.8.19 → 0.8.20

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 (2) hide show
  1. package/dist/index.js +86 -14
  2. package/package.json +1 -1
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.19",
215
+ version: "0.8.20",
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: [
@@ -3414,7 +3414,7 @@ var YieldPositionsInputSchema = z15.object({
3414
3414
  });
3415
3415
  var YIELD_POSITIONS_TOOL = {
3416
3416
  name: "q402_yield_positions",
3417
- description: "READ-ONLY \u2014 show the Agent Wallet's current Q402 Yield (Aave) lending positions. Returns each position's protocol, chain, asset, market address, balance, principal, accrued interest, and supply APY, plus the aggregate total supplied in USD. Authenticated by the configured live Multichain API key \u2014 no private key required and no funds move. BNB CHAIN ONLY \u2014 Q402 Yield supports BNB Chain today. walletId is OPTIONAL: omit it and the server reads the owner's default Agent Wallet (resolved from the API key); pass one only when the owner holds more than one wallet. An optional chain filter is also accepted. Use this whenever the user asks 'how much am I earning?' or 'what are my open lending positions?'",
3417
+ description: "READ-ONLY \u2014 show the Agent Wallet's current Q402 Yield (Aave) lending positions. Returns each position's protocol, chain, asset, market address, CURRENT supplied position value (the live aToken balance, in token units), and live supply APY, plus the aggregate current value in USD. Authenticated by the configured live Multichain API key \u2014 no private key required and no funds move. BNB CHAIN ONLY \u2014 Q402 Yield supports BNB Chain today. DOES NOT report principal or accrued earnings as separate numbers \u2014 the aToken balance already includes accrued interest but is not broken out, so do NOT claim a specific 'earnings/profit/interest earned' figure from this tool; report only the current position value and the APY. walletId is OPTIONAL: omit it and the server reads the owner's default Agent Wallet (resolved from the API key); pass one only when the owner holds more than one wallet. An optional chain filter is also accepted. Use this whenever the user asks 'what is my position worth?', 'what's my current yield balance / APY?', or 'what are my open lending positions?'",
3418
3418
  inputSchema: {
3419
3419
  type: "object",
3420
3420
  properties: {
@@ -3483,7 +3483,7 @@ async function runYieldPositions(input) {
3483
3483
  const unavailableChains = Array.isArray(data.unavailableChains) ? data.unavailableChains : [];
3484
3484
  const hasUnavailable = data.unavailable === true || unavailableChains.length > 0;
3485
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.";
3486
+ const summary = positions.length ? `Total current position value: $${(data.totalSuppliedUsd ?? 0).toFixed(2)} across ${positions.length} position(s) (live aToken balance incl. accrued interest; earnings are not broken out).${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.";
3487
3487
  return {
3488
3488
  content: [
3489
3489
  { type: "text", text: summary },
@@ -3491,11 +3491,23 @@ async function runYieldPositions(input) {
3491
3491
  type: "text",
3492
3492
  text: JSON.stringify({
3493
3493
  walletId: data.walletId ?? walletId ?? null,
3494
+ // Surface only what's real: the current position value (live aToken
3495
+ // balance) + APY. `principal`/`accrued` come back null from this
3496
+ // Phase-0 read, so they're included ONLY when the server actually
3497
+ // populated them — never as a null placeholder that reads like a
3498
+ // real (zero) earnings figure.
3494
3499
  positions: positions.map((p) => ({
3495
- ...p,
3496
- supplyApyPct: Math.round(p.supplyApy * 100 * 100) / 100
3500
+ protocol: p.protocol,
3501
+ chain: p.chain,
3502
+ asset: p.asset,
3503
+ marketAddress: p.marketAddress,
3504
+ currentValue: p.balance,
3505
+ supplyApy: p.supplyApy,
3506
+ supplyApyPct: Math.round(p.supplyApy * 100 * 100) / 100,
3507
+ ...p.principal != null ? { principal: p.principal } : {},
3508
+ ...p.accrued != null ? { accrued: p.accrued } : {}
3497
3509
  })),
3498
- totalSuppliedUsd: data.totalSuppliedUsd ?? null,
3510
+ totalCurrentValueUsd: data.totalSuppliedUsd ?? null,
3499
3511
  asOf: data.asOf ?? null,
3500
3512
  unavailable: hasUnavailable,
3501
3513
  unavailableChains
@@ -3516,7 +3528,7 @@ var YieldDepositInputSchema = z16.object({
3516
3528
  "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)."
3517
3529
  ),
3518
3530
  idempotencyKey: z16.string().optional().describe(
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."
3531
+ '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. IMPORTANT: if a call returns status="uncertain" (timeout / unconfirmed broadcast), it echoes back the idempotencyKey it used \u2014 pass THAT exact value here to safely resume the same deposit instead of starting a new one.'
3520
3532
  ),
3521
3533
  confirm: z16.boolean().optional().describe(
3522
3534
  "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."
@@ -3524,7 +3536,7 @@ var YieldDepositInputSchema = z16.object({
3524
3536
  });
3525
3537
  var YIELD_DEPOSIT_TOOL = {
3526
3538
  name: "q402_yield_deposit",
3527
- description: "WRITE \u2014 MOVES FUNDS. Supplies the Agent Wallet's stablecoin (USDC / USDT) into Aave V3 (Q402 Yield) so it starts earning supply APY. Server-managed Agent Wallet path (Mode C): authenticated by the configured live Multichain API key \u2014 the server holds the encrypted key, signs the Aave supply, and sponsors gas. BNB CHAIN ONLY \u2014 Q402 Yield supports BNB Chain today; ETH / AVAX and other chains are not yet available. \n\nREQUIRES CONFIRMATION \u2014 like q402_pay, this tool refuses to execute unless `confirm: true` is set. Call it FIRST without confirm to get a one-line preview of exactly what will happen (amount, token, chain, wallet); show that to the user, get explicit approval, THEN re-call with confirm:true. Never set confirm:true on the user's behalf without that approval. \n\nSANDBOX BY DEFAULT \u2014 like q402_pay, no funds move unless a live Multichain key (q402_live_*) is configured AND Q402_ENABLE_REAL_PAYMENTS=1. Without both, confirm:true returns a sandbox preview (no on-chain supply) with a setup hint \u2014 confirm:true alone does NOT move real funds. \n\nUse q402_yield_reserves first to show available markets + APY, and q402_yield_positions afterward to confirm the supplied balance.",
3539
+ description: "WRITE \u2014 MOVES FUNDS. Supplies the Agent Wallet's stablecoin (USDC / USDT) into Aave V3 (Q402 Yield) so it starts earning supply APY. Server-managed Agent Wallet path (Mode C): authenticated by the configured live Multichain API key \u2014 the server holds the encrypted key, signs the Aave supply, and sponsors gas. BNB CHAIN ONLY \u2014 Q402 Yield supports BNB Chain today; ETH / AVAX and other chains are not yet available. \n\nREQUIRES CONFIRMATION \u2014 like q402_pay, this tool refuses to execute unless `confirm: true` is set. Call it FIRST without confirm to get a one-line preview of exactly what will happen (amount, token, chain, wallet); show that to the user, get explicit approval, THEN re-call with confirm:true. Never set confirm:true on the user's behalf without that approval. \n\nSANDBOX BY DEFAULT \u2014 like q402_pay, no funds move unless a live Multichain key (q402_live_*) is configured AND Q402_ENABLE_REAL_PAYMENTS=1. Without both, confirm:true returns a sandbox preview (no on-chain supply) with a setup hint \u2014 confirm:true alone does NOT move real funds. \n\nRETRY SAFETY \u2014 on a timeout or an unconfirmed broadcast the tool returns status=\"uncertain\" and echoes back the idempotencyKey it used. The deposit MAY have settled, so do NOT blindly call again \u2014 that starts a NEW deposit and can double-supply. To resume the SAME operation, re-call with idempotencyKey set to the echoed value; the server dedupes on it and replays the original result. \n\nUse q402_yield_reserves first to show available markets + APY, and q402_yield_positions afterward to confirm the supplied balance.",
3528
3540
  inputSchema: {
3529
3541
  type: "object",
3530
3542
  properties: {
@@ -3548,7 +3560,7 @@ var YIELD_DEPOSIT_TOOL = {
3548
3560
  },
3549
3561
  idempotencyKey: {
3550
3562
  type: "string",
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."
3563
+ 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. If a call returns status="uncertain", it echoes the idempotencyKey it used \u2014 pass that exact value back here to resume the same deposit rather than start a new one.'
3552
3564
  },
3553
3565
  confirm: {
3554
3566
  type: "boolean",
@@ -3631,13 +3643,43 @@ async function runYieldDeposit(input) {
3631
3643
  return {
3632
3644
  content: [{
3633
3645
  type: "text",
3634
- text: `Yield deposit failed: ${e instanceof Error ? e.message : String(e)}. Retry in a moment.`
3646
+ text: JSON.stringify({
3647
+ status: "uncertain",
3648
+ success: false,
3649
+ action: "yield_deposit",
3650
+ chain: input.chain,
3651
+ token: input.token,
3652
+ amount: input.amount,
3653
+ idempotencyKey,
3654
+ error: e instanceof Error ? e.message : String(e),
3655
+ message: `Network error before a confirmed response \u2014 the deposit may or may not have been submitted. Do NOT start a new deposit. To safely resume THIS operation, retry with idempotencyKey="${idempotencyKey}" (the server dedupes on it, so a retry that already landed replays the original result instead of double-supplying).`
3656
+ }, null, 2)
3635
3657
  }],
3636
3658
  isError: true
3637
3659
  };
3638
3660
  }
3639
3661
  const data = await res.json().catch(() => ({}));
3640
3662
  if (!res.ok) {
3663
+ if (res.status === 502) {
3664
+ return {
3665
+ content: [{
3666
+ type: "text",
3667
+ text: JSON.stringify({
3668
+ status: "uncertain",
3669
+ success: false,
3670
+ action: "yield_deposit",
3671
+ chain: input.chain,
3672
+ token: input.token,
3673
+ amount: input.amount,
3674
+ idempotencyKey,
3675
+ txHash: data.txHash ?? null,
3676
+ error: data.error ?? "settlement_uncertain",
3677
+ message: `Broadcast but unconfirmed \u2014 the deposit may have settled on-chain. Do NOT blindly start a new deposit. Verify on-chain first; if you must retry, reuse idempotencyKey="${idempotencyKey}" so the server resumes THIS operation (replays the original result) instead of supplying again.`
3678
+ }, null, 2)
3679
+ }],
3680
+ isError: true
3681
+ };
3682
+ }
3641
3683
  return {
3642
3684
  content: [{
3643
3685
  type: "text",
@@ -3666,7 +3708,7 @@ var YieldWithdrawInputSchema = z17.object({
3666
3708
  "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)."
3667
3709
  ),
3668
3710
  idempotencyKey: z17.string().optional().describe(
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."
3711
+ '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. IMPORTANT: if a call returns status="uncertain" (timeout / unconfirmed broadcast), it echoes back the idempotencyKey it used \u2014 pass THAT exact value here to safely resume the same withdrawal instead of starting a new one.'
3670
3712
  ),
3671
3713
  confirm: z17.boolean().optional().describe(
3672
3714
  "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."
@@ -3674,7 +3716,7 @@ var YieldWithdrawInputSchema = z17.object({
3674
3716
  });
3675
3717
  var YIELD_WITHDRAW_TOOL = {
3676
3718
  name: "q402_yield_withdraw",
3677
- description: 'WRITE \u2014 MOVES FUNDS. Withdraws the Agent Wallet\'s supplied stablecoin (USDC / USDT) out of Aave V3 (Q402 Yield) back to the Agent Wallet. Pass amount="max" to withdraw the FULL position. Server-managed Agent Wallet path (Mode C): authenticated by the configured live Multichain API key \u2014 the server holds the encrypted key, signs the Aave withdraw, and sponsors gas. BNB CHAIN ONLY \u2014 Q402 Yield supports BNB Chain today; ETH / AVAX and other chains are not yet available. \n\nREQUIRES CONFIRMATION \u2014 like q402_pay, this tool refuses to execute unless `confirm: true` is set. Call it FIRST without confirm to get a one-line preview of exactly what will happen (amount, token, chain, wallet); show that to the user, get explicit approval, THEN re-call with confirm:true. Never set confirm:true on the user\'s behalf without that approval. \n\nSANDBOX BY DEFAULT \u2014 like q402_pay, no funds move unless a live Multichain key (q402_live_*) is configured AND Q402_ENABLE_REAL_PAYMENTS=1. Without both, confirm:true returns a sandbox preview (no on-chain withdraw) with a setup hint \u2014 confirm:true alone does NOT move real funds. \n\nUse q402_yield_positions first to see the current position size (especially before an amount="max" withdrawal).',
3719
+ description: 'WRITE \u2014 MOVES FUNDS. Withdraws the Agent Wallet\'s supplied stablecoin (USDC / USDT) out of Aave V3 (Q402 Yield) back to the Agent Wallet. Pass amount="max" to withdraw the FULL position. Server-managed Agent Wallet path (Mode C): authenticated by the configured live Multichain API key \u2014 the server holds the encrypted key, signs the Aave withdraw, and sponsors gas. BNB CHAIN ONLY \u2014 Q402 Yield supports BNB Chain today; ETH / AVAX and other chains are not yet available. \n\nREQUIRES CONFIRMATION \u2014 like q402_pay, this tool refuses to execute unless `confirm: true` is set. Call it FIRST without confirm to get a one-line preview of exactly what will happen (amount, token, chain, wallet); show that to the user, get explicit approval, THEN re-call with confirm:true. Never set confirm:true on the user\'s behalf without that approval. \n\nSANDBOX BY DEFAULT \u2014 like q402_pay, no funds move unless a live Multichain key (q402_live_*) is configured AND Q402_ENABLE_REAL_PAYMENTS=1. Without both, confirm:true returns a sandbox preview (no on-chain withdraw) with a setup hint \u2014 confirm:true alone does NOT move real funds. \n\nRETRY SAFETY \u2014 on a timeout or an unconfirmed broadcast the tool returns status="uncertain" and echoes back the idempotencyKey it used. The withdrawal MAY have settled, so do NOT blindly call again \u2014 that starts a NEW withdrawal and can double-withdraw. To resume the SAME operation, re-call with idempotencyKey set to the echoed value; the server dedupes on it and replays the original result. \n\nUse q402_yield_positions first to see the current position size (especially before an amount="max" withdrawal).',
3678
3720
  inputSchema: {
3679
3721
  type: "object",
3680
3722
  properties: {
@@ -3698,7 +3740,7 @@ var YIELD_WITHDRAW_TOOL = {
3698
3740
  },
3699
3741
  idempotencyKey: {
3700
3742
  type: "string",
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."
3743
+ 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. If a call returns status="uncertain", it echoes the idempotencyKey it used \u2014 pass that exact value back here to resume the same withdrawal rather than start a new one.'
3702
3744
  },
3703
3745
  confirm: {
3704
3746
  type: "boolean",
@@ -3782,13 +3824,43 @@ async function runYieldWithdraw(input) {
3782
3824
  return {
3783
3825
  content: [{
3784
3826
  type: "text",
3785
- text: `Yield withdraw failed: ${e instanceof Error ? e.message : String(e)}. Retry in a moment.`
3827
+ text: JSON.stringify({
3828
+ status: "uncertain",
3829
+ success: false,
3830
+ action: "yield_withdraw",
3831
+ chain: input.chain,
3832
+ token: input.token,
3833
+ amount: input.amount,
3834
+ idempotencyKey,
3835
+ error: e instanceof Error ? e.message : String(e),
3836
+ message: `Network error before a confirmed response \u2014 the withdrawal may or may not have been submitted. Do NOT start a new withdrawal. To safely resume THIS operation, retry with idempotencyKey="${idempotencyKey}" (the server dedupes on it, so a retry that already landed replays the original result instead of double-withdrawing).`
3837
+ }, null, 2)
3786
3838
  }],
3787
3839
  isError: true
3788
3840
  };
3789
3841
  }
3790
3842
  const data = await res.json().catch(() => ({}));
3791
3843
  if (!res.ok) {
3844
+ if (res.status === 502) {
3845
+ return {
3846
+ content: [{
3847
+ type: "text",
3848
+ text: JSON.stringify({
3849
+ status: "uncertain",
3850
+ success: false,
3851
+ action: "yield_withdraw",
3852
+ chain: input.chain,
3853
+ token: input.token,
3854
+ amount: input.amount,
3855
+ idempotencyKey,
3856
+ txHash: data.txHash ?? null,
3857
+ error: data.error ?? "settlement_uncertain",
3858
+ message: `Broadcast but unconfirmed \u2014 the withdrawal may have settled on-chain. Do NOT blindly start a new withdrawal. Verify on-chain first; if you must retry, reuse idempotencyKey="${idempotencyKey}" so the server resumes THIS operation (replays the original result) instead of withdrawing again.`
3859
+ }, null, 2)
3860
+ }],
3861
+ isError: true
3862
+ };
3863
+ }
3792
3864
  return {
3793
3865
  content: [{
3794
3866
  type: "text",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quackai/q402-mcp",
3
- "version": "0.8.19",
3
+ "version": "0.8.20",
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": [