@quackai/q402-mcp 0.8.23 → 0.8.25

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 +51 -10
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -211,7 +211,7 @@ var isValidPrivateKey = (s) => typeof s === "string" && PRIVATE_KEY_RE.test(s);
211
211
  // package.json
212
212
  var package_default = {
213
213
  name: "@quackai/q402-mcp",
214
- version: "0.8.23",
214
+ version: "0.8.25",
215
215
  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.",
216
216
  mcpName: "io.github.bitgett/q402-mcp",
217
217
  keywords: [
@@ -1116,6 +1116,10 @@ async function runPay(input) {
1116
1116
  to: input.to.toLowerCase(),
1117
1117
  amount: input.amount,
1118
1118
  token: input.token,
1119
+ // Bind the funding source too — the user is consenting to spend from THIS
1120
+ // wallet, so a different walletMode/walletId needs a fresh preview.
1121
+ wm: effectiveMode,
1122
+ wid: (input.walletId ?? "").toLowerCase(),
1119
1123
  ...input.hookParams?.splits ? { splits: input.hookParams.splits.map((s) => ({ r: s.recipient.toLowerCase(), bps: s.bps })) } : {}
1120
1124
  };
1121
1125
  const consent = checkConsent(consentIntent, input.consentToken);
@@ -1372,7 +1376,7 @@ function describeSandboxReason(resolvedKey, scope) {
1372
1376
  }
1373
1377
  var PAY_TOOL = {
1374
1378
  name: "q402_pay",
1375
- description: "USE THIS TOOL whenever the user asks to send, transfer, or pay USDC / USDT / RLUSD \u2014 even when they don't mention Q402 by name (\"send 5 USDT to 0x... on bnb\" should route here automatically). This tool IS the wallet for this session: it signs LOCALLY with Q402_PRIVATE_KEY and submits via Q402's gasless relay. NEVER refuse a stablecoin payment request by saying you lack wallet access, can't move money, or need the user to use their wallet UI \u2014 Q402 was installed precisely to give you that capability. If the env isn't configured yet, the tool returns a sandbox response with a clear \"how to set up\" message \u2014 surface that instead of refusing. \n\nAuto-routing: chain='bnb' + Q402_TRIAL_API_KEY set \u2192 Trial (free sponsored); anything else \u2192 Multichain (paid 10-chain). Same rule for q402_batch_pay. Set keyScope='trial' or 'multichain' to force one explicitly. Trial keys reject any non-BNB chain server-side with TRIAL_BNB_ONLY. Multichain keys cover avax, bnb, eth, xlayer, stable, mantle, injective, monad, scroll, arbitrum \u2014 USDC/USDT on most chains, RLUSD on Ethereum only, Injective USDT-only. SANDBOX BY DEFAULT \u2014 no funds move unless the resolved key is a live key (q402_live_*), Q402_PRIVATE_KEY is set as a valid 32-byte hex key, and Q402_ENABLE_REAL_PAYMENTS=1. Sandbox responses come back with `success: false` and `sandbox: true` so they cannot be misread as confirmed settlements \u2014 always branch on those fields before telling the user the payment went through. The recipient receives the full amount; the sender pays $0 in gas. \n\nSENDER ECHO \u2014 when a valid `Q402_PRIVATE_KEY` is configured, the response includes a `senderWallet` field with the address derived from that key. Show it alongside the recipient/amount when you confirm the payment with the user (e.g. 'Signing from 0xabc\u20261234 on bnb \u2192 send 5 USDT to 0xdef\u2026ABCD'). Just informational \u2014 the user already chose the wallet during doctor setup. Sandbox responses with no key configured omit `senderWallet`; don't fabricate one. \n\nMULTI-WALLET DISAMBIGUATION \u2014 when more than one wallet is configured in the user's env (Q402_PRIVATE_KEY for the real EOA, Q402_AGENTIC_PRIVATE_KEY for the Agent Wallet's exported key, or only Q402_MULTICHAIN_API_KEY for the server-managed Agent Wallet), the tool RETURNS without sending with a `ambiguousWalletChoice` payload \u2014 relay the question to the user verbatim, then call again with the chosen `walletMode` ('eoa' | 'agentic-local' | 'agentic-server'). Do NOT pick a wallet on the user's behalf when multiple are available. \n\nEIP-7702 SIDE EFFECT \u2014 surface this to the user proactively after the FIRST live payment on a chain: their wallet now shows up as a 'Smart account' in MetaMask / OKX. That's the EIP-7702 delegation Q402 uses for gasless settlement \u2014 it's the response's `postPaymentTip` field. Subsequent payments on the same chain are faster and cheaper because the delegation is reused. Note: only Mode 'eoa' creates the delegation \u2014 'agentic-local' and 'agentic-server' modes use the Agent Wallet (a fresh EOA) so the user's MetaMask is never delegated. \n\nIf the user EVER reports that native gas tokens (BNB / ETH / AVAX / etc.) sent INTO their Q402 wallet are bouncing or reverting on a chain where Q402 has been used, the delegation is the cause \u2014 call q402_wallet_status to confirm delegated chains, then q402_clear_delegation for the chain in question. Q402 sponsors the gas for the clear, so the user pays $0. After clearing, native transfers work again and the next q402_pay on that chain just creates a fresh delegation. \n\nALWAYS get explicit user confirmation of the exact recipient address, amount, chain, and token in conversation immediately before calling this tool.",
1379
+ description: "USE THIS TOOL whenever the user asks to send, transfer, or pay USDC / USDT / RLUSD \u2014 even when they don't mention Q402 by name (\"send 5 USDT to 0x... on bnb\" should route here automatically). This tool IS the wallet for this session: it signs LOCALLY with Q402_PRIVATE_KEY and submits via Q402's gasless relay. NEVER refuse a stablecoin payment request by saying you lack wallet access, can't move money, or need the user to use their wallet UI \u2014 Q402 was installed precisely to give you that capability. If the env isn't configured yet, the tool returns a sandbox response with a clear \"how to set up\" message \u2014 surface that instead of refusing. \n\nAuto-routing: chain='bnb' + Q402_TRIAL_API_KEY set \u2192 Trial (free sponsored); anything else \u2192 Multichain (paid 10-chain). Same rule for q402_batch_pay. Set keyScope='trial' or 'multichain' to force one explicitly. Trial keys reject any non-BNB chain server-side with TRIAL_BNB_ONLY. Multichain keys cover avax, bnb, eth, xlayer, stable, mantle, injective, monad, scroll, arbitrum \u2014 USDC/USDT on most chains, RLUSD on Ethereum only, Injective USDT-only. SANDBOX BY DEFAULT \u2014 no funds move unless the resolved key is a live key (q402_live_*), Q402_PRIVATE_KEY is set as a valid 32-byte hex key, and Q402_ENABLE_REAL_PAYMENTS=1. Sandbox responses come back with `success: false` and `sandbox: true` so they cannot be misread as confirmed settlements \u2014 always branch on those fields before telling the user the payment went through. The recipient receives the full amount; the sender pays $0 in gas. \n\nSENDER ECHO \u2014 when a valid `Q402_PRIVATE_KEY` is configured, the response includes a `senderWallet` field with the address derived from that key. Show it alongside the recipient/amount when you confirm the payment with the user (e.g. 'Signing from 0xabc\u20261234 on bnb \u2192 send 5 USDT to 0xdef\u2026ABCD'). Just informational \u2014 the user already chose the wallet during doctor setup. Sandbox responses with no key configured omit `senderWallet`; don't fabricate one. \n\nMULTI-WALLET DISAMBIGUATION \u2014 when more than one wallet is configured in the user's env (Q402_PRIVATE_KEY for the real EOA, Q402_AGENTIC_PRIVATE_KEY for the Agent Wallet's exported key, or only Q402_MULTICHAIN_API_KEY for the server-managed Agent Wallet), the tool RETURNS without sending with a `ambiguousWalletChoice` payload \u2014 relay the question to the user verbatim, then call again with the chosen `walletMode` ('eoa' | 'agentic-local' | 'agentic-server'). Do NOT pick a wallet on the user's behalf when multiple are available. \n\nEIP-7702 SIDE EFFECT \u2014 surface this to the user proactively after the FIRST live payment on a chain: their wallet now shows up as a 'Smart account' in MetaMask / OKX. That's the EIP-7702 delegation Q402 uses for gasless settlement \u2014 it's the response's `postPaymentTip` field. Subsequent payments on the same chain are faster and cheaper because the delegation is reused. Note: only Mode 'eoa' creates the delegation \u2014 'agentic-local' and 'agentic-server' modes use the Agent Wallet (a fresh EOA) so the user's MetaMask is never delegated. \n\nIf the user EVER reports that native gas tokens (BNB / ETH / AVAX / etc.) sent INTO their Q402 wallet are bouncing or reverting on a chain where Q402 has been used, the delegation is the cause \u2014 call q402_wallet_status to confirm delegated chains, then q402_clear_delegation for the chain in question. Q402 sponsors the gas for the clear, so the user pays $0. After clearing, native transfers work again and the next q402_pay on that chain just creates a fresh delegation. \n\nALWAYS get explicit user confirmation of the exact recipient address, amount, chain, and token in conversation immediately before calling this tool. \n\nTWO-PHASE CONSENT: confirm:true alone does NOT send. Call this tool first WITHOUT consentToken \u2014 it returns status=\"needs_confirmation\" with a `preview` of the exact payment and a `consentToken`, and moves no money. Relay that preview to the user, get their explicit yes, then re-call with the SAME args plus that `consentToken` to execute. The token is re-derived from the params about to run, so a previewed payment can't be swapped for a different one.",
1376
1380
  inputSchema: {
1377
1381
  type: "object",
1378
1382
  properties: {
@@ -1530,7 +1534,10 @@ async function runBatchPay(input) {
1530
1534
  t: "batch",
1531
1535
  chain: input.chain,
1532
1536
  token: input.token,
1533
- recipients: input.recipients.map((r) => ({ to: r.to.toLowerCase(), amount: r.amount }))
1537
+ recipients: input.recipients.map((r) => ({ to: r.to.toLowerCase(), amount: r.amount })),
1538
+ // Bind the funding source too (see q402_pay).
1539
+ wm: input.walletMode ?? "",
1540
+ wid: (input.walletId ?? "").toLowerCase()
1534
1541
  };
1535
1542
  const consent = checkConsent(consentIntent, input.consentToken);
1536
1543
  if (!consent.ok) {
@@ -1888,7 +1895,9 @@ Send gasless payments to MULTIPLE recipients on a single chain \xD7 token in one
1888
1895
 
1889
1896
  MULTI-WALLET DISAMBIGUATION \u2014 when more than one wallet is configured in the user's env (Q402_PRIVATE_KEY for the real EOA, Q402_AGENTIC_PRIVATE_KEY for the Agent Wallet's exported key, or only Q402_MULTICHAIN_API_KEY for the server-managed Agent Wallet), the tool RETURNS WITHOUT firing with \`status='needs_wallet_choice'\` and an \`ambiguousWalletChoice\` payload \u2014 relay the question to the user verbatim, then call again with the chosen \`walletMode\` ('eoa' | 'agentic-local' | 'agentic-server'). Do NOT pick a wallet on the user's behalf when multiple are available. Server-mediated batches go through /api/wallet/agentic/batch and are paid-only (the trial key cannot batch).
1890
1897
 
1891
- ALWAYS get explicit user confirmation of the complete recipient + amount list, chain, and token in conversation immediately before calling this tool \u2014 the user must approve the full batch, not the individual rows.`,
1898
+ ALWAYS get explicit user confirmation of the complete recipient + amount list, chain, and token in conversation immediately before calling this tool \u2014 the user must approve the full batch, not the individual rows.
1899
+
1900
+ TWO-PHASE CONSENT: confirm:true alone does NOT send. Call this tool first WITHOUT consentToken \u2014 it returns status="needs_confirmation" with a \`setupHint\` preview of every recipient + amount and a \`consentToken\`, and moves no money. Relay that preview to the user, get an explicit yes, then re-call with the SAME args plus the \`consentToken\` to execute. The token is re-derived from the batch about to run, so the previewed batch can't be swapped.`,
1892
1901
  inputSchema: {
1893
1902
  type: "object",
1894
1903
  properties: {
@@ -3182,7 +3191,7 @@ var BridgeSendInputSchema = z11.object({
3182
3191
  });
3183
3192
  var BRIDGE_SEND_TOOL = {
3184
3193
  name: "q402_bridge_send",
3185
- description: "Execute a Chainlink CCIP USDC bridge across the 3-chain triangle (eth/avax/arbitrum) on behalf of the user's server-managed Agentic Wallet (Mode C). Sandbox-by-default \u2014 returns a synthetic messageId unless `sandbox: false` is passed AND Q402_ENABLE_REAL_PAYMENTS=1 AND a live Multichain API key is configured. The server signs ccipSend with the Agent Wallet's encrypted PK, auto-funds source-chain gas from the user's Gas Tank, and debits both the auto- fund cost and the CCIP fee per the bridge's settled receipt. REQUIRES CONFIRMATION \u2014 like q402_pay and q402_yield_deposit, a LIVE bridge (sandbox: false) refuses to execute unless confirm: true is set. Call it first WITHOUT confirm to get a one-line preview (src, dst, amount, fee token); show that to the user, get explicit approval, THEN re-call with sandbox: false AND confirm: true. Never set confirm: true on the user's behalf. Recommended flow: q402_bridge_quote first \u2192 preview + confirm cost with the user \u2192 q402_bridge_send with sandbox: false, confirm: true. Live mode needs a Multichain subscription; trial keys are rejected. If the bridge returns AGENT_WALLET_DELEGATED, clear the delegation first: server-managed Agent Wallets (Mode C / API key) use the Clear delegation button on the dashboard; local-key modes (Q402_PRIVATE_KEY set) can run q402_clear_delegation.",
3194
+ description: "Execute a Chainlink CCIP USDC bridge across the 3-chain triangle (eth/avax/arbitrum) on behalf of the user's server-managed Agentic Wallet (Mode C). Sandbox-by-default \u2014 returns a synthetic messageId unless `sandbox: false` is passed AND Q402_ENABLE_REAL_PAYMENTS=1 AND a live Multichain API key is configured. The server signs ccipSend with the Agent Wallet's encrypted PK, auto-funds source-chain gas from the user's Gas Tank, and debits both the auto- fund cost and the CCIP fee per the bridge's settled receipt. TWO-PHASE CONSENT \u2014 a LIVE bridge (sandbox: false) refuses to execute unless BOTH confirm: true AND a matching consentToken are set. Call it first WITHOUT consentToken to get a preview (src, dst, amount, fee token) plus a consentToken; show that to the user, get explicit approval, THEN re-call with sandbox: false, confirm: true, AND that consentToken. The token is re-derived from the bridge about to run, so the previewed bridge can't be swapped. Never fabricate a token. Recommended flow: q402_bridge_quote first \u2192 preview + confirm cost with the user \u2192 q402_bridge_send with sandbox: false, confirm: true, consentToken. Live mode needs a Multichain subscription; trial keys are rejected. If the bridge returns AGENT_WALLET_DELEGATED, clear the delegation first: server-managed Agent Wallets (Mode C / API key) use the Clear delegation button on the dashboard; local-key modes (Q402_PRIVATE_KEY set) can run q402_clear_delegation.",
3186
3195
  inputSchema: {
3187
3196
  type: "object",
3188
3197
  properties: {
@@ -3260,7 +3269,9 @@ async function runBridgeSend(input) {
3260
3269
  src: input.src,
3261
3270
  dst: input.dst,
3262
3271
  amount: input.amount,
3263
- feeToken: input.feeToken === "native" ? "native" : "LINK"
3272
+ feeToken: input.feeToken === "native" ? "native" : "LINK",
3273
+ // Bind the funding wallet too (see q402_pay).
3274
+ wid: (input.walletId ?? "").toLowerCase()
3264
3275
  };
3265
3276
  const consent = checkConsent(consentIntent, input.consentToken);
3266
3277
  if (input.confirm !== true || !consent.ok) {
@@ -3639,6 +3650,9 @@ var YieldDepositInputSchema = z16.object({
3639
3650
  ),
3640
3651
  confirm: z16.boolean().optional().describe(
3641
3652
  "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."
3653
+ ),
3654
+ consentToken: z16.string().optional().describe(
3655
+ "Two-phase consent. LEAVE UNSET on the first call: the tool previews the deposit (no funds move) and returns a consentToken. Relay the preview to the user, get an explicit yes, then re-call with confirm:true AND this consentToken. The token is re-derived from (chain, token, amount, wallet) and refused on mismatch, so a previewed deposit can't be swapped."
3642
3656
  )
3643
3657
  });
3644
3658
  var YIELD_DEPOSIT_TOOL = {
@@ -3672,6 +3686,10 @@ var YIELD_DEPOSIT_TOOL = {
3672
3686
  confirm: {
3673
3687
  type: "boolean",
3674
3688
  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."
3689
+ },
3690
+ consentToken: {
3691
+ type: "string",
3692
+ description: "Two-phase consent token. Leave unset on the first call to get a preview + token; re-call with confirm:true AND this token after the user approves. Bound to (chain, token, amount, wallet) and refused on mismatch."
3675
3693
  }
3676
3694
  },
3677
3695
  required: ["token", "amount"],
@@ -3690,12 +3708,20 @@ async function runYieldDeposit(input) {
3690
3708
  }
3691
3709
  const walletId = typeof input.walletId === "string" && input.walletId.length > 0 ? input.walletId.toLowerCase() : CONFIG.walletId ?? void 0;
3692
3710
  const idempotencyKey = typeof input.idempotencyKey === "string" && input.idempotencyKey.length > 0 ? input.idempotencyKey : hexlify2(randomBytes2(32));
3693
- if (input.confirm !== true) {
3711
+ const consentIntent = {
3712
+ t: "yield-deposit",
3713
+ chain: input.chain,
3714
+ token: input.token,
3715
+ amount: input.amount,
3716
+ walletId: walletId ?? null
3717
+ };
3718
+ const consent = checkConsent(consentIntent, input.consentToken);
3719
+ if (input.confirm !== true || !consent.ok) {
3694
3720
  const walletDesc = walletId ? `wallet ${walletId}` : "your default Agent Wallet";
3695
3721
  return {
3696
3722
  content: [{
3697
3723
  type: "text",
3698
- text: `Will supply ${input.amount} ${input.token} into Aave on ${input.chain} from ${walletDesc}. This MOVES FUNDS. Re-call with confirm:true to execute.`
3724
+ text: `Will supply ${input.amount} ${input.token} into Aave on ${input.chain} from ${walletDesc}. This MOVES FUNDS. Confirm with the user, then re-call with confirm:true AND consentToken="${consent.expected}".`
3699
3725
  }]
3700
3726
  };
3701
3727
  }
@@ -3819,6 +3845,9 @@ var YieldWithdrawInputSchema = z17.object({
3819
3845
  ),
3820
3846
  confirm: z17.boolean().optional().describe(
3821
3847
  "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."
3848
+ ),
3849
+ consentToken: z17.string().optional().describe(
3850
+ "Two-phase consent. LEAVE UNSET on the first call: the tool previews the withdrawal (no funds move) and returns a consentToken. Relay the preview to the user, get an explicit yes, then re-call with confirm:true AND this consentToken. The token is re-derived from (chain, token, amount, wallet)."
3822
3851
  )
3823
3852
  });
3824
3853
  var YIELD_WITHDRAW_TOOL = {
@@ -3852,6 +3881,10 @@ var YIELD_WITHDRAW_TOOL = {
3852
3881
  confirm: {
3853
3882
  type: "boolean",
3854
3883
  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."
3884
+ },
3885
+ consentToken: {
3886
+ type: "string",
3887
+ description: "Two-phase consent token. Leave unset on the first call to get a preview + token; re-call with confirm:true AND this token after the user approves. Bound to (chain, token, amount, wallet)."
3855
3888
  }
3856
3889
  },
3857
3890
  required: ["token", "amount"],
@@ -3871,12 +3904,20 @@ async function runYieldWithdraw(input) {
3871
3904
  const walletId = typeof input.walletId === "string" && input.walletId.length > 0 ? input.walletId.toLowerCase() : CONFIG.walletId ?? void 0;
3872
3905
  const idempotencyKey = typeof input.idempotencyKey === "string" && input.idempotencyKey.length > 0 ? input.idempotencyKey : hexlify3(randomBytes3(32));
3873
3906
  const amountDesc = input.amount === "max" ? "the FULL position" : `${input.amount} ${input.token}`;
3874
- if (input.confirm !== true) {
3907
+ const consentIntent = {
3908
+ t: "yield-withdraw",
3909
+ chain: input.chain,
3910
+ token: input.token,
3911
+ amount: input.amount,
3912
+ walletId: walletId ?? null
3913
+ };
3914
+ const consent = checkConsent(consentIntent, input.consentToken);
3915
+ if (input.confirm !== true || !consent.ok) {
3875
3916
  const walletDesc = walletId ? `wallet ${walletId}` : "your default Agent Wallet";
3876
3917
  return {
3877
3918
  content: [{
3878
3919
  type: "text",
3879
- text: `Will withdraw ${amountDesc} from Aave on ${input.chain} back to ${walletDesc}. This MOVES FUNDS. Re-call with confirm:true to execute.`
3920
+ text: `Will withdraw ${amountDesc} from Aave on ${input.chain} back to ${walletDesc}. This MOVES FUNDS. Confirm with the user, then re-call with confirm:true AND consentToken="${consent.expected}".`
3880
3921
  }]
3881
3922
  };
3882
3923
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quackai/q402-mcp",
3
- "version": "0.8.23",
3
+ "version": "0.8.25",
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": [