@quackai/q402-mcp 0.8.17 → 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.
- package/README.md +6 -2
- package/dist/index.js +535 -37
- package/package.json +75 -75
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
|
-
**
|
|
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.
|
|
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: [
|
|
@@ -3335,10 +3335,481 @@ async function runBridgeGasTank(_input) {
|
|
|
3335
3335
|
};
|
|
3336
3336
|
}
|
|
3337
3337
|
|
|
3338
|
-
// src/tools/
|
|
3338
|
+
// src/tools/yield-reserves.ts
|
|
3339
3339
|
import { z as z14 } from "zod";
|
|
3340
|
-
var
|
|
3341
|
-
|
|
3340
|
+
var YieldReservesInputSchema = z14.object({
|
|
3341
|
+
chain: z14.enum(["bnb"]).optional().describe("Optional chain filter. Q402 Yield is BNB-only today \u2014 only 'bnb' is accepted. Omit to list all supported chains.")
|
|
3342
|
+
});
|
|
3343
|
+
var YIELD_RESERVES_TOOL = {
|
|
3344
|
+
name: "q402_yield_reserves",
|
|
3345
|
+
description: "READ-ONLY \u2014 list the Q402 Yield (Aave) lending markets the Agent Wallet can supply into. Returns each market's protocol, chain, asset, asset address, position token, market address, and current supply APY (shown as a %). No auth required and no funds move \u2014 this is purely a preview of available yield. BNB CHAIN ONLY \u2014 Q402 Yield supports BNB Chain today. Pass an optional `chain` to filter; omit it to see every supported chain. Use this whenever the user asks 'where can I earn yield?' or 'what's the lending APY on <asset>?' before supplying.",
|
|
3346
|
+
inputSchema: {
|
|
3347
|
+
type: "object",
|
|
3348
|
+
properties: {
|
|
3349
|
+
chain: {
|
|
3350
|
+
type: "string",
|
|
3351
|
+
enum: ["bnb"],
|
|
3352
|
+
description: "Optional chain filter. Q402 Yield is BNB-only today \u2014 only 'bnb' is accepted. Omit for all supported chains."
|
|
3353
|
+
}
|
|
3354
|
+
},
|
|
3355
|
+
additionalProperties: false
|
|
3356
|
+
}
|
|
3357
|
+
};
|
|
3358
|
+
async function runYieldReserves(input) {
|
|
3359
|
+
const url = new URL(`${CONFIG.relayBaseUrl}/wallet/agentic/yield/reserves`);
|
|
3360
|
+
if (input.chain) url.searchParams.set("chain", input.chain);
|
|
3361
|
+
let res;
|
|
3362
|
+
try {
|
|
3363
|
+
res = await fetch(url, {
|
|
3364
|
+
method: "GET",
|
|
3365
|
+
headers: { Accept: "application/json" },
|
|
3366
|
+
signal: AbortSignal.timeout(15e3)
|
|
3367
|
+
});
|
|
3368
|
+
} catch (e) {
|
|
3369
|
+
return {
|
|
3370
|
+
content: [{
|
|
3371
|
+
type: "text",
|
|
3372
|
+
text: `Yield reserves fetch failed: ${e instanceof Error ? e.message : String(e)}. Retry in a moment.`
|
|
3373
|
+
}],
|
|
3374
|
+
isError: true
|
|
3375
|
+
};
|
|
3376
|
+
}
|
|
3377
|
+
const data = await res.json().catch(() => ({}));
|
|
3378
|
+
if (!res.ok) {
|
|
3379
|
+
return {
|
|
3380
|
+
content: [{
|
|
3381
|
+
type: "text",
|
|
3382
|
+
text: `Yield reserves failed (HTTP ${res.status}): ${JSON.stringify(data)}`
|
|
3383
|
+
}],
|
|
3384
|
+
isError: true
|
|
3385
|
+
};
|
|
3386
|
+
}
|
|
3387
|
+
const markets = data.markets ?? [];
|
|
3388
|
+
const summary = markets.length ? markets.map((m) => `${m.label} (${m.chain}): ${(m.supplyApy * 100).toFixed(2)}% APY`).join("\n") : "No yield markets returned for the requested filter.";
|
|
3389
|
+
return {
|
|
3390
|
+
content: [
|
|
3391
|
+
{ type: "text", text: summary },
|
|
3392
|
+
{
|
|
3393
|
+
type: "text",
|
|
3394
|
+
text: JSON.stringify({
|
|
3395
|
+
supportedChains: data.supportedChains ?? [],
|
|
3396
|
+
markets: markets.map((m) => ({
|
|
3397
|
+
...m,
|
|
3398
|
+
supplyApyPct: Math.round(m.supplyApy * 100 * 100) / 100
|
|
3399
|
+
})),
|
|
3400
|
+
asOf: data.asOf ?? null
|
|
3401
|
+
}, null, 2)
|
|
3402
|
+
}
|
|
3403
|
+
]
|
|
3404
|
+
};
|
|
3405
|
+
}
|
|
3406
|
+
|
|
3407
|
+
// src/tools/yield-positions.ts
|
|
3408
|
+
import { z as z15 } from "zod";
|
|
3409
|
+
var YieldPositionsInputSchema = z15.object({
|
|
3410
|
+
walletId: z15.string().optional().describe(
|
|
3411
|
+
"Optional Agent Wallet address whose positions to read (max 10 per owner). Omit and the server defaults to the owner's default wallet (resolved from the API key); Q402_AGENT_WALLET_ADDRESS env fills it in when set."
|
|
3412
|
+
),
|
|
3413
|
+
chain: z15.enum(["bnb"]).optional().describe("Optional chain filter. Q402 Yield is BNB-only today \u2014 only 'bnb' is accepted. Omit for all supported chains.")
|
|
3414
|
+
});
|
|
3415
|
+
var YIELD_POSITIONS_TOOL = {
|
|
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?'",
|
|
3418
|
+
inputSchema: {
|
|
3419
|
+
type: "object",
|
|
3420
|
+
properties: {
|
|
3421
|
+
walletId: {
|
|
3422
|
+
type: "string",
|
|
3423
|
+
description: "Optional Agent Wallet address. Omit to read the owner's default wallet (the server resolves it from the API key); pass one only when the owner holds multiple wallets. Q402_AGENT_WALLET_ADDRESS env fills it in when set."
|
|
3424
|
+
},
|
|
3425
|
+
chain: {
|
|
3426
|
+
type: "string",
|
|
3427
|
+
enum: ["bnb"],
|
|
3428
|
+
description: "Optional chain filter. Q402 Yield is BNB-only today \u2014 only 'bnb' is accepted. Omit for all supported chains."
|
|
3429
|
+
}
|
|
3430
|
+
},
|
|
3431
|
+
additionalProperties: false
|
|
3432
|
+
}
|
|
3433
|
+
};
|
|
3434
|
+
async function runYieldPositions(input) {
|
|
3435
|
+
const resolved = resolveApiKey("eth", "multichain");
|
|
3436
|
+
if (!resolved.apiKey || !resolved.apiKey.startsWith("q402_live_")) {
|
|
3437
|
+
return {
|
|
3438
|
+
content: [{
|
|
3439
|
+
type: "text",
|
|
3440
|
+
text: JSON.stringify({
|
|
3441
|
+
configured: false,
|
|
3442
|
+
positions: null,
|
|
3443
|
+
setupHint: resolved.sandboxReason ?? "No live Q402 Multichain API key configured. Set Q402_MULTICHAIN_API_KEY to a q402_live_\u2026 key from https://q402.quackai.ai/payment, or run q402_doctor."
|
|
3444
|
+
}, null, 2)
|
|
3445
|
+
}],
|
|
3446
|
+
isError: true
|
|
3447
|
+
};
|
|
3448
|
+
}
|
|
3449
|
+
const walletId = typeof input.walletId === "string" && input.walletId.length > 0 ? input.walletId.toLowerCase() : CONFIG.walletId ?? void 0;
|
|
3450
|
+
const url = new URL(`${CONFIG.relayBaseUrl}/wallet/agentic/yield/positions`);
|
|
3451
|
+
if (walletId) url.searchParams.set("walletId", walletId);
|
|
3452
|
+
if (input.chain) url.searchParams.set("chain", input.chain);
|
|
3453
|
+
let res;
|
|
3454
|
+
try {
|
|
3455
|
+
res = await fetch(url, {
|
|
3456
|
+
method: "GET",
|
|
3457
|
+
headers: {
|
|
3458
|
+
Accept: "application/json",
|
|
3459
|
+
"x-api-key": resolved.apiKey
|
|
3460
|
+
},
|
|
3461
|
+
signal: AbortSignal.timeout(15e3)
|
|
3462
|
+
});
|
|
3463
|
+
} catch (e) {
|
|
3464
|
+
return {
|
|
3465
|
+
content: [{
|
|
3466
|
+
type: "text",
|
|
3467
|
+
text: `Yield positions fetch failed: ${e instanceof Error ? e.message : String(e)}. Retry in a moment.`
|
|
3468
|
+
}],
|
|
3469
|
+
isError: true
|
|
3470
|
+
};
|
|
3471
|
+
}
|
|
3472
|
+
const data = await res.json().catch(() => ({}));
|
|
3473
|
+
if (!res.ok) {
|
|
3474
|
+
return {
|
|
3475
|
+
content: [{
|
|
3476
|
+
type: "text",
|
|
3477
|
+
text: `Yield positions failed (HTTP ${res.status}): ${JSON.stringify(data)}`
|
|
3478
|
+
}],
|
|
3479
|
+
isError: true
|
|
3480
|
+
};
|
|
3481
|
+
}
|
|
3482
|
+
const positions = data.positions ?? [];
|
|
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.";
|
|
3487
|
+
return {
|
|
3488
|
+
content: [
|
|
3489
|
+
{ type: "text", text: summary },
|
|
3490
|
+
{
|
|
3491
|
+
type: "text",
|
|
3492
|
+
text: JSON.stringify({
|
|
3493
|
+
walletId: data.walletId ?? walletId ?? null,
|
|
3494
|
+
positions: positions.map((p) => ({
|
|
3495
|
+
...p,
|
|
3496
|
+
supplyApyPct: Math.round(p.supplyApy * 100 * 100) / 100
|
|
3497
|
+
})),
|
|
3498
|
+
totalSuppliedUsd: data.totalSuppliedUsd ?? null,
|
|
3499
|
+
asOf: data.asOf ?? null,
|
|
3500
|
+
unavailable: hasUnavailable,
|
|
3501
|
+
unavailableChains
|
|
3502
|
+
}, null, 2)
|
|
3503
|
+
}
|
|
3504
|
+
]
|
|
3505
|
+
};
|
|
3506
|
+
}
|
|
3507
|
+
|
|
3508
|
+
// src/tools/yield-deposit.ts
|
|
3509
|
+
import { hexlify as hexlify2, randomBytes as randomBytes2 } from "ethers";
|
|
3510
|
+
import { z as z16 } from "zod";
|
|
3511
|
+
var YieldDepositInputSchema = z16.object({
|
|
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."),
|
|
3513
|
+
token: z16.enum(["USDC", "USDT"]).describe("Stablecoin to supply into Aave. USDC or USDT."),
|
|
3514
|
+
amount: z16.string().regex(/^\d+(\.\d+)?$/, "amount must be a positive decimal string").describe('Human-readable decimal amount to supply, e.g. "100.00".'),
|
|
3515
|
+
walletId: z16.string().optional().describe(
|
|
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)."
|
|
3517
|
+
),
|
|
3518
|
+
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."
|
|
3520
|
+
),
|
|
3521
|
+
confirm: z16.boolean().optional().describe(
|
|
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."
|
|
3523
|
+
)
|
|
3524
|
+
});
|
|
3525
|
+
var YIELD_DEPOSIT_TOOL = {
|
|
3526
|
+
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.",
|
|
3528
|
+
inputSchema: {
|
|
3529
|
+
type: "object",
|
|
3530
|
+
properties: {
|
|
3531
|
+
chain: {
|
|
3532
|
+
type: "string",
|
|
3533
|
+
enum: ["bnb"],
|
|
3534
|
+
description: "Chain the Aave market lives on. Q402 Yield is BNB-only today \u2014 only 'bnb' is accepted."
|
|
3535
|
+
},
|
|
3536
|
+
token: {
|
|
3537
|
+
type: "string",
|
|
3538
|
+
enum: ["USDC", "USDT"],
|
|
3539
|
+
description: "Stablecoin to supply into Aave. USDC or USDT."
|
|
3540
|
+
},
|
|
3541
|
+
amount: {
|
|
3542
|
+
type: "string",
|
|
3543
|
+
description: 'Human-readable decimal amount to supply, e.g. "100.00".'
|
|
3544
|
+
},
|
|
3545
|
+
walletId: {
|
|
3546
|
+
type: "string",
|
|
3547
|
+
description: "Optional Agent Wallet address to supply from when the owner holds multiple wallets. Defaults to Q402_AGENT_WALLET_ADDRESS env, then the owner's default wallet on the server."
|
|
3548
|
+
},
|
|
3549
|
+
idempotencyKey: {
|
|
3550
|
+
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."
|
|
3552
|
+
},
|
|
3553
|
+
confirm: {
|
|
3554
|
+
type: "boolean",
|
|
3555
|
+
description: "MUST be true to actually supply funds \u2014 set only after the user explicitly approved this exact deposit in chat. Omit (or false) to preview without moving funds."
|
|
3556
|
+
}
|
|
3557
|
+
},
|
|
3558
|
+
required: ["token", "amount"],
|
|
3559
|
+
additionalProperties: false
|
|
3560
|
+
}
|
|
3561
|
+
};
|
|
3562
|
+
async function runYieldDeposit(input) {
|
|
3563
|
+
if (!(Number(input.amount) > 0)) {
|
|
3564
|
+
return {
|
|
3565
|
+
content: [{
|
|
3566
|
+
type: "text",
|
|
3567
|
+
text: `amount must be greater than zero (got "${input.amount}").`
|
|
3568
|
+
}],
|
|
3569
|
+
isError: true
|
|
3570
|
+
};
|
|
3571
|
+
}
|
|
3572
|
+
const walletId = typeof input.walletId === "string" && input.walletId.length > 0 ? input.walletId.toLowerCase() : CONFIG.walletId ?? void 0;
|
|
3573
|
+
const idempotencyKey = typeof input.idempotencyKey === "string" && input.idempotencyKey.length > 0 ? input.idempotencyKey : hexlify2(randomBytes2(32));
|
|
3574
|
+
if (input.confirm !== true) {
|
|
3575
|
+
const walletDesc = walletId ? `wallet ${walletId}` : "your default Agent Wallet";
|
|
3576
|
+
return {
|
|
3577
|
+
content: [{
|
|
3578
|
+
type: "text",
|
|
3579
|
+
text: `Will supply ${input.amount} ${input.token} into Aave on ${input.chain} from ${walletDesc}. This MOVES FUNDS. Re-call with confirm:true to execute.`
|
|
3580
|
+
}]
|
|
3581
|
+
};
|
|
3582
|
+
}
|
|
3583
|
+
const resolved = resolveApiKey(input.chain, "multichain");
|
|
3584
|
+
if (!resolved.apiKey || !resolved.apiKey.startsWith("q402_live_")) {
|
|
3585
|
+
return {
|
|
3586
|
+
content: [{
|
|
3587
|
+
type: "text",
|
|
3588
|
+
text: JSON.stringify({
|
|
3589
|
+
configured: false,
|
|
3590
|
+
status: "error",
|
|
3591
|
+
setupHint: resolved.sandboxReason ?? "No live Q402 Multichain API key configured. Set Q402_MULTICHAIN_API_KEY to a q402_live_\u2026 key from https://q402.quackai.ai/payment, or run q402_doctor."
|
|
3592
|
+
}, null, 2)
|
|
3593
|
+
}],
|
|
3594
|
+
isError: true
|
|
3595
|
+
};
|
|
3596
|
+
}
|
|
3597
|
+
if (!CONFIG.realPaymentsRequested) {
|
|
3598
|
+
return {
|
|
3599
|
+
content: [{
|
|
3600
|
+
type: "text",
|
|
3601
|
+
text: JSON.stringify({
|
|
3602
|
+
sandbox: true,
|
|
3603
|
+
success: false,
|
|
3604
|
+
status: "sandbox",
|
|
3605
|
+
action: "yield_deposit",
|
|
3606
|
+
chain: input.chain,
|
|
3607
|
+
token: input.token,
|
|
3608
|
+
amount: input.amount,
|
|
3609
|
+
walletId: walletId ?? null,
|
|
3610
|
+
setupHint: "Sandbox mode \u2014 set Q402_ENABLE_REAL_PAYMENTS=1 to fire a real Q402 Yield deposit. No funds moved."
|
|
3611
|
+
}, null, 2)
|
|
3612
|
+
}]
|
|
3613
|
+
};
|
|
3614
|
+
}
|
|
3615
|
+
let res;
|
|
3616
|
+
try {
|
|
3617
|
+
res = await fetch(`${CONFIG.relayBaseUrl}/wallet/agentic/yield/deposit`, {
|
|
3618
|
+
method: "POST",
|
|
3619
|
+
headers: { "Content-Type": "application/json" },
|
|
3620
|
+
body: JSON.stringify({
|
|
3621
|
+
apiKey: resolved.apiKey,
|
|
3622
|
+
chain: input.chain,
|
|
3623
|
+
token: input.token,
|
|
3624
|
+
amount: input.amount,
|
|
3625
|
+
idempotencyKey,
|
|
3626
|
+
...walletId ? { walletId } : {}
|
|
3627
|
+
}),
|
|
3628
|
+
signal: AbortSignal.timeout(6e4)
|
|
3629
|
+
});
|
|
3630
|
+
} catch (e) {
|
|
3631
|
+
return {
|
|
3632
|
+
content: [{
|
|
3633
|
+
type: "text",
|
|
3634
|
+
text: `Yield deposit failed: ${e instanceof Error ? e.message : String(e)}. Retry in a moment.`
|
|
3635
|
+
}],
|
|
3636
|
+
isError: true
|
|
3637
|
+
};
|
|
3638
|
+
}
|
|
3639
|
+
const data = await res.json().catch(() => ({}));
|
|
3640
|
+
if (!res.ok) {
|
|
3641
|
+
return {
|
|
3642
|
+
content: [{
|
|
3643
|
+
type: "text",
|
|
3644
|
+
text: `Yield deposit failed (HTTP ${res.status}): ${JSON.stringify(data)}`
|
|
3645
|
+
}],
|
|
3646
|
+
isError: true
|
|
3647
|
+
};
|
|
3648
|
+
}
|
|
3649
|
+
const summary = data.txHash ? `Supplied ${data.amount ?? input.amount} ${data.asset ?? input.token} into ${data.protocol ?? "Aave"} on ${data.chain ?? input.chain}. txHash ${data.txHash}.` : `Yield deposit submitted on ${data.chain ?? input.chain}.`;
|
|
3650
|
+
return {
|
|
3651
|
+
content: [
|
|
3652
|
+
{ type: "text", text: summary },
|
|
3653
|
+
{ type: "text", text: JSON.stringify(data, null, 2) }
|
|
3654
|
+
]
|
|
3655
|
+
};
|
|
3656
|
+
}
|
|
3657
|
+
|
|
3658
|
+
// src/tools/yield-withdraw.ts
|
|
3659
|
+
import { hexlify as hexlify3, randomBytes as randomBytes3 } from "ethers";
|
|
3660
|
+
import { z as z17 } from "zod";
|
|
3661
|
+
var YieldWithdrawInputSchema = z17.object({
|
|
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."),
|
|
3663
|
+
token: z17.enum(["USDC", "USDT"]).describe("Stablecoin to withdraw from Aave. USDC or USDT."),
|
|
3664
|
+
amount: z17.string().regex(/^(\d+(\.\d+)?|max)$/, 'amount must be a positive decimal string or "max"').describe('Human-readable decimal amount to withdraw, e.g. "100.00", or the literal "max" to withdraw the full position.'),
|
|
3665
|
+
walletId: z17.string().optional().describe(
|
|
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)."
|
|
3667
|
+
),
|
|
3668
|
+
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."
|
|
3670
|
+
),
|
|
3671
|
+
confirm: z17.boolean().optional().describe(
|
|
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."
|
|
3673
|
+
)
|
|
3674
|
+
});
|
|
3675
|
+
var YIELD_WITHDRAW_TOOL = {
|
|
3676
|
+
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).',
|
|
3678
|
+
inputSchema: {
|
|
3679
|
+
type: "object",
|
|
3680
|
+
properties: {
|
|
3681
|
+
chain: {
|
|
3682
|
+
type: "string",
|
|
3683
|
+
enum: ["bnb"],
|
|
3684
|
+
description: "Chain the Aave market lives on. Q402 Yield is BNB-only today \u2014 only 'bnb' is accepted."
|
|
3685
|
+
},
|
|
3686
|
+
token: {
|
|
3687
|
+
type: "string",
|
|
3688
|
+
enum: ["USDC", "USDT"],
|
|
3689
|
+
description: "Stablecoin to withdraw from Aave. USDC or USDT."
|
|
3690
|
+
},
|
|
3691
|
+
amount: {
|
|
3692
|
+
type: "string",
|
|
3693
|
+
description: 'Human-readable decimal amount to withdraw, e.g. "100.00", or the literal "max" to withdraw the full position.'
|
|
3694
|
+
},
|
|
3695
|
+
walletId: {
|
|
3696
|
+
type: "string",
|
|
3697
|
+
description: "Optional Agent Wallet address to withdraw to when the owner holds multiple wallets. Defaults to Q402_AGENT_WALLET_ADDRESS env, then the owner's default wallet on the server."
|
|
3698
|
+
},
|
|
3699
|
+
idempotencyKey: {
|
|
3700
|
+
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."
|
|
3702
|
+
},
|
|
3703
|
+
confirm: {
|
|
3704
|
+
type: "boolean",
|
|
3705
|
+
description: "MUST be true to actually withdraw funds \u2014 set only after the user explicitly approved this exact withdrawal in chat. Omit (or false) to preview without moving funds."
|
|
3706
|
+
}
|
|
3707
|
+
},
|
|
3708
|
+
required: ["token", "amount"],
|
|
3709
|
+
additionalProperties: false
|
|
3710
|
+
}
|
|
3711
|
+
};
|
|
3712
|
+
async function runYieldWithdraw(input) {
|
|
3713
|
+
if (input.amount !== "max" && !(Number(input.amount) > 0)) {
|
|
3714
|
+
return {
|
|
3715
|
+
content: [{
|
|
3716
|
+
type: "text",
|
|
3717
|
+
text: `amount must be greater than zero (got "${input.amount}"), or the literal "max".`
|
|
3718
|
+
}],
|
|
3719
|
+
isError: true
|
|
3720
|
+
};
|
|
3721
|
+
}
|
|
3722
|
+
const walletId = typeof input.walletId === "string" && input.walletId.length > 0 ? input.walletId.toLowerCase() : CONFIG.walletId ?? void 0;
|
|
3723
|
+
const idempotencyKey = typeof input.idempotencyKey === "string" && input.idempotencyKey.length > 0 ? input.idempotencyKey : hexlify3(randomBytes3(32));
|
|
3724
|
+
const amountDesc = input.amount === "max" ? "the FULL position" : `${input.amount} ${input.token}`;
|
|
3725
|
+
if (input.confirm !== true) {
|
|
3726
|
+
const walletDesc = walletId ? `wallet ${walletId}` : "your default Agent Wallet";
|
|
3727
|
+
return {
|
|
3728
|
+
content: [{
|
|
3729
|
+
type: "text",
|
|
3730
|
+
text: `Will withdraw ${amountDesc} from Aave on ${input.chain} back to ${walletDesc}. This MOVES FUNDS. Re-call with confirm:true to execute.`
|
|
3731
|
+
}]
|
|
3732
|
+
};
|
|
3733
|
+
}
|
|
3734
|
+
const resolved = resolveApiKey(input.chain, "multichain");
|
|
3735
|
+
if (!resolved.apiKey || !resolved.apiKey.startsWith("q402_live_")) {
|
|
3736
|
+
return {
|
|
3737
|
+
content: [{
|
|
3738
|
+
type: "text",
|
|
3739
|
+
text: JSON.stringify({
|
|
3740
|
+
configured: false,
|
|
3741
|
+
status: "error",
|
|
3742
|
+
setupHint: resolved.sandboxReason ?? "No live Q402 Multichain API key configured. Set Q402_MULTICHAIN_API_KEY to a q402_live_\u2026 key from https://q402.quackai.ai/payment, or run q402_doctor."
|
|
3743
|
+
}, null, 2)
|
|
3744
|
+
}],
|
|
3745
|
+
isError: true
|
|
3746
|
+
};
|
|
3747
|
+
}
|
|
3748
|
+
if (!CONFIG.realPaymentsRequested) {
|
|
3749
|
+
return {
|
|
3750
|
+
content: [{
|
|
3751
|
+
type: "text",
|
|
3752
|
+
text: JSON.stringify({
|
|
3753
|
+
sandbox: true,
|
|
3754
|
+
success: false,
|
|
3755
|
+
status: "sandbox",
|
|
3756
|
+
action: "yield_withdraw",
|
|
3757
|
+
chain: input.chain,
|
|
3758
|
+
token: input.token,
|
|
3759
|
+
amount: input.amount,
|
|
3760
|
+
walletId: walletId ?? null,
|
|
3761
|
+
setupHint: "Sandbox mode \u2014 set Q402_ENABLE_REAL_PAYMENTS=1 to fire a real Q402 Yield withdrawal. No funds moved."
|
|
3762
|
+
}, null, 2)
|
|
3763
|
+
}]
|
|
3764
|
+
};
|
|
3765
|
+
}
|
|
3766
|
+
let res;
|
|
3767
|
+
try {
|
|
3768
|
+
res = await fetch(`${CONFIG.relayBaseUrl}/wallet/agentic/yield/withdraw`, {
|
|
3769
|
+
method: "POST",
|
|
3770
|
+
headers: { "Content-Type": "application/json" },
|
|
3771
|
+
body: JSON.stringify({
|
|
3772
|
+
apiKey: resolved.apiKey,
|
|
3773
|
+
chain: input.chain,
|
|
3774
|
+
token: input.token,
|
|
3775
|
+
amount: input.amount,
|
|
3776
|
+
idempotencyKey,
|
|
3777
|
+
...walletId ? { walletId } : {}
|
|
3778
|
+
}),
|
|
3779
|
+
signal: AbortSignal.timeout(6e4)
|
|
3780
|
+
});
|
|
3781
|
+
} catch (e) {
|
|
3782
|
+
return {
|
|
3783
|
+
content: [{
|
|
3784
|
+
type: "text",
|
|
3785
|
+
text: `Yield withdraw failed: ${e instanceof Error ? e.message : String(e)}. Retry in a moment.`
|
|
3786
|
+
}],
|
|
3787
|
+
isError: true
|
|
3788
|
+
};
|
|
3789
|
+
}
|
|
3790
|
+
const data = await res.json().catch(() => ({}));
|
|
3791
|
+
if (!res.ok) {
|
|
3792
|
+
return {
|
|
3793
|
+
content: [{
|
|
3794
|
+
type: "text",
|
|
3795
|
+
text: `Yield withdraw failed (HTTP ${res.status}): ${JSON.stringify(data)}`
|
|
3796
|
+
}],
|
|
3797
|
+
isError: true
|
|
3798
|
+
};
|
|
3799
|
+
}
|
|
3800
|
+
const summary = data.txHash ? `Withdrew ${data.amount ?? amountDesc} ${data.asset ?? ""}`.trimEnd() + ` from ${data.protocol ?? "Aave"} on ${data.chain ?? input.chain}. txHash ${data.txHash}.` : `Yield withdraw submitted on ${data.chain ?? input.chain}.`;
|
|
3801
|
+
return {
|
|
3802
|
+
content: [
|
|
3803
|
+
{ type: "text", text: summary },
|
|
3804
|
+
{ type: "text", text: JSON.stringify(data, null, 2) }
|
|
3805
|
+
]
|
|
3806
|
+
};
|
|
3807
|
+
}
|
|
3808
|
+
|
|
3809
|
+
// src/tools/recurring-list.ts
|
|
3810
|
+
import { z as z18 } from "zod";
|
|
3811
|
+
var RecurringListInputSchema = z18.object({
|
|
3812
|
+
walletId: z18.string().optional().describe(
|
|
3342
3813
|
"Optional lowercased Agent Wallet address to list rules for when the user holds multiple wallets. Omit to use Q402_AGENT_WALLET_ADDRESS env, then the owner's default wallet."
|
|
3343
3814
|
)
|
|
3344
3815
|
});
|
|
@@ -3422,29 +3893,29 @@ async function runRecurringList(input = {}) {
|
|
|
3422
3893
|
}
|
|
3423
3894
|
|
|
3424
3895
|
// src/tools/recurring-create.ts
|
|
3425
|
-
import { z as
|
|
3896
|
+
import { z as z19 } from "zod";
|
|
3426
3897
|
var ADDRESS_RE = /^0x[0-9a-fA-F]{40}$/;
|
|
3427
3898
|
var AMOUNT_RE = /^\d+(\.\d{1,18})?$/;
|
|
3428
|
-
var RecurringCreateInputSchema =
|
|
3429
|
-
confirm:
|
|
3899
|
+
var RecurringCreateInputSchema = z19.object({
|
|
3900
|
+
confirm: z19.literal(true).describe(
|
|
3430
3901
|
'REQUIRED. Must be literally `true`. Authoring a recurring rule schedules future on-chain payments that the user does not click through one-by-one \u2014 the user has to explicitly say yes BEFORE this is called. Echo back the frequency + recipient + amount + chain + token + cancelWindow you intend to create, get a plain-language confirmation from the user (e.g. "yes, create the schedule"), and ONLY then call this with confirm: true. Mirrors the same guard q402_pay / q402_batch_pay use on one-shot sends.'
|
|
3431
3902
|
),
|
|
3432
|
-
frequency:
|
|
3903
|
+
frequency: z19.string().min(1).describe(
|
|
3433
3904
|
'Cadence string. One of: "hourly:N" (N=1..23), "daily", "weekly:{mon|tue|wed|thu|fri|sat|sun}", "monthly:N" (N=1..31), "monthly:last". Examples: "hourly:1" fires every hour, "weekly:fri" fires every Friday, "monthly:1" fires on the 1st of each month.'
|
|
3434
3905
|
),
|
|
3435
|
-
recipient:
|
|
3436
|
-
amount:
|
|
3906
|
+
recipient: z19.string().regex(ADDRESS_RE).describe("0x-prefixed 20-byte recipient address. Required."),
|
|
3907
|
+
amount: z19.string().regex(AMOUNT_RE).describe(
|
|
3437
3908
|
'Amount per fire, as a decimal string (e.g. "1.5", "0.0001"). Counted in the same unit as `token` (USDC or USDT, both 1:1 USD).'
|
|
3438
3909
|
),
|
|
3439
|
-
chain:
|
|
3910
|
+
chain: z19.enum(["bnb", "eth", "avax", "xlayer", "mantle", "injective", "monad", "scroll", "stable", "arbitrum"]).default("bnb").describe(
|
|
3440
3911
|
"Chain to fire the recurring TX on. Defaults to bnb. Recurring requires the paid Multichain subscription on EVERY chain, including bnb \u2014 Trial keys are rejected at create time with MULTICHAIN_REQUIRED. Trial keys can still pay one-shot via q402_pay on BNB."
|
|
3441
3912
|
),
|
|
3442
|
-
token:
|
|
3443
|
-
label:
|
|
3444
|
-
cancelWindowHours:
|
|
3913
|
+
token: z19.enum(["USDC", "USDT"]).default("USDT").describe("Stablecoin to send. USDC or USDT. Both peg to USD-1."),
|
|
3914
|
+
label: z19.string().max(64).optional().describe("Optional human-readable label (\u226464 chars). Shows up in q402_recurring_list and the dashboard."),
|
|
3915
|
+
cancelWindowHours: z19.number().min(0).optional().describe(
|
|
3445
3916
|
"Hours of advance notice before each fire during which the rule can be cancelled. 0 = fire immediately at the next slot, no alert. Capped at the cadence interval (e.g. \u2264 N-0.5h for hourly:N, \u226424 for daily). Defaults to 0."
|
|
3446
3917
|
),
|
|
3447
|
-
walletId:
|
|
3918
|
+
walletId: z19.string().optional().describe(
|
|
3448
3919
|
"Optional lowercased Agent Wallet address when the user holds multiple wallets. Defaults to Q402_AGENT_WALLET_ADDRESS env, then the owner's default wallet on the server."
|
|
3449
3920
|
)
|
|
3450
3921
|
});
|
|
@@ -3573,12 +4044,12 @@ async function runRecurringCreate(input) {
|
|
|
3573
4044
|
}
|
|
3574
4045
|
|
|
3575
4046
|
// src/tools/recurring-cancel.ts
|
|
3576
|
-
import { z as
|
|
3577
|
-
var RecurringCancelInputSchema =
|
|
3578
|
-
ruleId:
|
|
4047
|
+
import { z as z20 } from "zod";
|
|
4048
|
+
var RecurringCancelInputSchema = z20.object({
|
|
4049
|
+
ruleId: z20.string().min(1).describe(
|
|
3579
4050
|
"Rule id to cancel. Obtain from q402_recurring_list \u2014 each entry's `ruleId` field. Cancelling is immediate."
|
|
3580
4051
|
),
|
|
3581
|
-
walletId:
|
|
4052
|
+
walletId: z20.string().optional().describe(
|
|
3582
4053
|
"Optional lowercased Agent Wallet address when the user holds multiple wallets. Defaults to Q402_AGENT_WALLET_ADDRESS env, then the owner's default wallet."
|
|
3583
4054
|
)
|
|
3584
4055
|
});
|
|
@@ -3666,15 +4137,15 @@ async function runRecurringCancel(input) {
|
|
|
3666
4137
|
}
|
|
3667
4138
|
|
|
3668
4139
|
// src/tools/recurring-fires.ts
|
|
3669
|
-
import { z as
|
|
3670
|
-
var RecurringFiresInputSchema =
|
|
3671
|
-
ruleId:
|
|
4140
|
+
import { z as z21 } from "zod";
|
|
4141
|
+
var RecurringFiresInputSchema = z21.object({
|
|
4142
|
+
ruleId: z21.string().min(1).describe(
|
|
3672
4143
|
"Rule id whose fire history to fetch. Obtain from q402_recurring_list \u2014 each entry's `ruleId` field."
|
|
3673
4144
|
),
|
|
3674
|
-
limit:
|
|
4145
|
+
limit: z21.number().int().min(1).max(50).optional().describe(
|
|
3675
4146
|
"Max number of fires to return (newest first). Defaults to 50 (the server cap). Pass a smaller number when you only need the most recent few."
|
|
3676
4147
|
),
|
|
3677
|
-
walletId:
|
|
4148
|
+
walletId: z21.string().optional().describe(
|
|
3678
4149
|
"Optional lowercased Agent Wallet address when the user holds multiple wallets. Defaults to Q402_AGENT_WALLET_ADDRESS env, then the owner's default wallet."
|
|
3679
4150
|
)
|
|
3680
4151
|
});
|
|
@@ -3780,12 +4251,12 @@ async function runRecurringFires(input) {
|
|
|
3780
4251
|
}
|
|
3781
4252
|
|
|
3782
4253
|
// src/tools/recurring-pause.ts
|
|
3783
|
-
import { z as
|
|
3784
|
-
var RecurringPauseInputSchema =
|
|
3785
|
-
ruleId:
|
|
4254
|
+
import { z as z22 } from "zod";
|
|
4255
|
+
var RecurringPauseInputSchema = z22.object({
|
|
4256
|
+
ruleId: z22.string().min(1).describe(
|
|
3786
4257
|
"Rule id to pause. Obtain from q402_recurring_list \u2014 each entry's `ruleId` field. Pausing is immediate and reversible."
|
|
3787
4258
|
),
|
|
3788
|
-
walletId:
|
|
4259
|
+
walletId: z22.string().optional().describe(
|
|
3789
4260
|
"Optional lowercased Agent Wallet address when the user holds multiple wallets. Defaults to Q402_AGENT_WALLET_ADDRESS env, then the owner's default wallet."
|
|
3790
4261
|
)
|
|
3791
4262
|
});
|
|
@@ -3873,12 +4344,12 @@ async function runRecurringPause(input) {
|
|
|
3873
4344
|
}
|
|
3874
4345
|
|
|
3875
4346
|
// src/tools/recurring-resume.ts
|
|
3876
|
-
import { z as
|
|
3877
|
-
var RecurringResumeInputSchema =
|
|
3878
|
-
ruleId:
|
|
4347
|
+
import { z as z23 } from "zod";
|
|
4348
|
+
var RecurringResumeInputSchema = z23.object({
|
|
4349
|
+
ruleId: z23.string().min(1).describe(
|
|
3879
4350
|
"Rule id to resume. Obtain from q402_recurring_list \u2014 each entry's `ruleId` field. Resume is immediate; nextRunAt advances to the next valid slot."
|
|
3880
4351
|
),
|
|
3881
|
-
walletId:
|
|
4352
|
+
walletId: z23.string().optional().describe(
|
|
3882
4353
|
"Optional lowercased Agent Wallet address when the user holds multiple wallets. Defaults to Q402_AGENT_WALLET_ADDRESS env, then the owner's default wallet."
|
|
3883
4354
|
)
|
|
3884
4355
|
});
|
|
@@ -3966,12 +4437,12 @@ async function runRecurringResume(input) {
|
|
|
3966
4437
|
}
|
|
3967
4438
|
|
|
3968
4439
|
// src/tools/recurring-skip-next.ts
|
|
3969
|
-
import { z as
|
|
3970
|
-
var RecurringSkipNextInputSchema =
|
|
3971
|
-
ruleId:
|
|
4440
|
+
import { z as z24 } from "zod";
|
|
4441
|
+
var RecurringSkipNextInputSchema = z24.object({
|
|
4442
|
+
ruleId: z24.string().min(1).describe(
|
|
3972
4443
|
"Rule id whose next scheduled fire to skip. Obtain from q402_recurring_list \u2014 each entry's `ruleId` field."
|
|
3973
4444
|
),
|
|
3974
|
-
walletId:
|
|
4445
|
+
walletId: z24.string().optional().describe(
|
|
3975
4446
|
"Optional lowercased Agent Wallet address when the user holds multiple wallets. Defaults to Q402_AGENT_WALLET_ADDRESS env, then the owner's default wallet."
|
|
3976
4447
|
)
|
|
3977
4448
|
});
|
|
@@ -4096,7 +4567,18 @@ async function main() {
|
|
|
4096
4567
|
BRIDGE_QUOTE_TOOL,
|
|
4097
4568
|
BRIDGE_SEND_TOOL,
|
|
4098
4569
|
BRIDGE_HISTORY_TOOL,
|
|
4099
|
-
BRIDGE_GAS_TANK_TOOL
|
|
4570
|
+
BRIDGE_GAS_TANK_TOOL,
|
|
4571
|
+
// Q402 Yield surface — read-only Aave lending market list + the
|
|
4572
|
+
// Agent Wallet's own positions. No funds move; positions auths via
|
|
4573
|
+
// the live Multichain apiKey (x-api-key header), reserves is public.
|
|
4574
|
+
YIELD_RESERVES_TOOL,
|
|
4575
|
+
YIELD_POSITIONS_TOOL,
|
|
4576
|
+
// Q402 Yield WRITE surface — supply / withdraw the Agent Wallet's
|
|
4577
|
+
// stablecoin to/from Aave V3. MOVES FUNDS, so both gate on confirm:true
|
|
4578
|
+
// (like q402_pay). Mode C: apiKey in the body, server signs the supply
|
|
4579
|
+
// / withdraw with the encrypted key.
|
|
4580
|
+
YIELD_DEPOSIT_TOOL,
|
|
4581
|
+
YIELD_WITHDRAW_TOOL
|
|
4100
4582
|
]
|
|
4101
4583
|
}));
|
|
4102
4584
|
server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
@@ -4183,6 +4665,22 @@ async function main() {
|
|
|
4183
4665
|
const parsed = BridgeGasTankInputSchema.parse(args ?? {});
|
|
4184
4666
|
return await runBridgeGasTank(parsed);
|
|
4185
4667
|
}
|
|
4668
|
+
case "q402_yield_reserves": {
|
|
4669
|
+
const parsed = YieldReservesInputSchema.parse(args ?? {});
|
|
4670
|
+
return await runYieldReserves(parsed);
|
|
4671
|
+
}
|
|
4672
|
+
case "q402_yield_positions": {
|
|
4673
|
+
const parsed = YieldPositionsInputSchema.parse(args ?? {});
|
|
4674
|
+
return await runYieldPositions(parsed);
|
|
4675
|
+
}
|
|
4676
|
+
case "q402_yield_deposit": {
|
|
4677
|
+
const parsed = YieldDepositInputSchema.parse(args ?? {});
|
|
4678
|
+
return await runYieldDeposit(parsed);
|
|
4679
|
+
}
|
|
4680
|
+
case "q402_yield_withdraw": {
|
|
4681
|
+
const parsed = YieldWithdrawInputSchema.parse(args ?? {});
|
|
4682
|
+
return await runYieldWithdraw(parsed);
|
|
4683
|
+
}
|
|
4186
4684
|
default:
|
|
4187
4685
|
return {
|
|
4188
4686
|
isError: true,
|
package/package.json
CHANGED
|
@@ -1,75 +1,75 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@quackai/q402-mcp",
|
|
3
|
-
"version": "0.8.
|
|
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
|
-
"mcpName": "io.github.bitgett/q402-mcp",
|
|
6
|
-
"keywords": [
|
|
7
|
-
"mcp",
|
|
8
|
-
"model-context-protocol",
|
|
9
|
-
"claude",
|
|
10
|
-
"claude-desktop",
|
|
11
|
-
"claude-code",
|
|
12
|
-
"codex",
|
|
13
|
-
"openai-codex",
|
|
14
|
-
"cline",
|
|
15
|
-
"q402",
|
|
16
|
-
"x402",
|
|
17
|
-
"stablecoin",
|
|
18
|
-
"usdc",
|
|
19
|
-
"usdt",
|
|
20
|
-
"rlusd",
|
|
21
|
-
"ripple",
|
|
22
|
-
"gasless",
|
|
23
|
-
"eip-7702",
|
|
24
|
-
"payments",
|
|
25
|
-
"ai-agents"
|
|
26
|
-
],
|
|
27
|
-
"type": "module",
|
|
28
|
-
"main": "dist/index.js",
|
|
29
|
-
"bin": {
|
|
30
|
-
"q402-mcp": "dist/index.js"
|
|
31
|
-
},
|
|
32
|
-
"files": [
|
|
33
|
-
"dist",
|
|
34
|
-
"README.md",
|
|
35
|
-
"LICENSE"
|
|
36
|
-
],
|
|
37
|
-
"engines": {
|
|
38
|
-
"node": ">=18.18"
|
|
39
|
-
},
|
|
40
|
-
"scripts": {
|
|
41
|
-
"build": "tsup",
|
|
42
|
-
"dev": "tsup --watch",
|
|
43
|
-
"lint": "tsc --noEmit",
|
|
44
|
-
"prepublishOnly": "npm run lint && npm run build",
|
|
45
|
-
"start": "node dist/index.js"
|
|
46
|
-
},
|
|
47
|
-
"dependencies": {
|
|
48
|
-
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
49
|
-
"ethers": "^6.16.0",
|
|
50
|
-
"zod": "^3.23.8"
|
|
51
|
-
},
|
|
52
|
-
"devDependencies": {
|
|
53
|
-
"@types/node": "^20.11.0",
|
|
54
|
-
"tsup": "^8.3.0",
|
|
55
|
-
"typescript": "^5.5.0"
|
|
56
|
-
},
|
|
57
|
-
"repository": {
|
|
58
|
-
"type": "git",
|
|
59
|
-
"url": "git+https://github.com/bitgett/q402-mcp.git"
|
|
60
|
-
},
|
|
61
|
-
"homepage": "https://q402.quackai.ai/claude",
|
|
62
|
-
"bugs": {
|
|
63
|
-
"url": "https://github.com/bitgett/q402-mcp/issues"
|
|
64
|
-
},
|
|
65
|
-
"license": "Apache-2.0",
|
|
66
|
-
"author": "David Lee <davidlee@quackai.ai>",
|
|
67
|
-
"publishConfig": {
|
|
68
|
-
"access": "public"
|
|
69
|
-
},
|
|
70
|
-
"overrides": {
|
|
71
|
-
"ws": "^8.20.1",
|
|
72
|
-
"qs": "^6.15.2",
|
|
73
|
-
"hono": "^4.12.21"
|
|
74
|
-
}
|
|
75
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@quackai/q402-mcp",
|
|
3
|
+
"version": "0.8.19",
|
|
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
|
+
"mcpName": "io.github.bitgett/q402-mcp",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"mcp",
|
|
8
|
+
"model-context-protocol",
|
|
9
|
+
"claude",
|
|
10
|
+
"claude-desktop",
|
|
11
|
+
"claude-code",
|
|
12
|
+
"codex",
|
|
13
|
+
"openai-codex",
|
|
14
|
+
"cline",
|
|
15
|
+
"q402",
|
|
16
|
+
"x402",
|
|
17
|
+
"stablecoin",
|
|
18
|
+
"usdc",
|
|
19
|
+
"usdt",
|
|
20
|
+
"rlusd",
|
|
21
|
+
"ripple",
|
|
22
|
+
"gasless",
|
|
23
|
+
"eip-7702",
|
|
24
|
+
"payments",
|
|
25
|
+
"ai-agents"
|
|
26
|
+
],
|
|
27
|
+
"type": "module",
|
|
28
|
+
"main": "dist/index.js",
|
|
29
|
+
"bin": {
|
|
30
|
+
"q402-mcp": "dist/index.js"
|
|
31
|
+
},
|
|
32
|
+
"files": [
|
|
33
|
+
"dist",
|
|
34
|
+
"README.md",
|
|
35
|
+
"LICENSE"
|
|
36
|
+
],
|
|
37
|
+
"engines": {
|
|
38
|
+
"node": ">=18.18"
|
|
39
|
+
},
|
|
40
|
+
"scripts": {
|
|
41
|
+
"build": "tsup",
|
|
42
|
+
"dev": "tsup --watch",
|
|
43
|
+
"lint": "tsc --noEmit",
|
|
44
|
+
"prepublishOnly": "npm run lint && npm run build",
|
|
45
|
+
"start": "node dist/index.js"
|
|
46
|
+
},
|
|
47
|
+
"dependencies": {
|
|
48
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
49
|
+
"ethers": "^6.16.0",
|
|
50
|
+
"zod": "^3.23.8"
|
|
51
|
+
},
|
|
52
|
+
"devDependencies": {
|
|
53
|
+
"@types/node": "^20.11.0",
|
|
54
|
+
"tsup": "^8.3.0",
|
|
55
|
+
"typescript": "^5.5.0"
|
|
56
|
+
},
|
|
57
|
+
"repository": {
|
|
58
|
+
"type": "git",
|
|
59
|
+
"url": "git+https://github.com/bitgett/q402-mcp.git"
|
|
60
|
+
},
|
|
61
|
+
"homepage": "https://q402.quackai.ai/claude",
|
|
62
|
+
"bugs": {
|
|
63
|
+
"url": "https://github.com/bitgett/q402-mcp/issues"
|
|
64
|
+
},
|
|
65
|
+
"license": "Apache-2.0",
|
|
66
|
+
"author": "David Lee <davidlee@quackai.ai>",
|
|
67
|
+
"publishConfig": {
|
|
68
|
+
"access": "public"
|
|
69
|
+
},
|
|
70
|
+
"overrides": {
|
|
71
|
+
"ws": "^8.20.1",
|
|
72
|
+
"qs": "^6.15.2",
|
|
73
|
+
"hono": "^4.12.21"
|
|
74
|
+
}
|
|
75
|
+
}
|