@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.
- package/dist/index.js +86 -14
- 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.
|
|
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,
|
|
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
|
|
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
|
-
|
|
3496
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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.
|
|
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": [
|