@quackai/q402-mcp 0.8.24 → 0.8.26

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 +55 -8
  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.24",
214
+ version: "0.8.26",
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,7 +1116,16 @@ async function runPay(input) {
1116
1116
  to: input.to.toLowerCase(),
1117
1117
  amount: input.amount,
1118
1118
  token: input.token,
1119
- ...input.hookParams?.splits ? { splits: input.hookParams.splits.map((s) => ({ r: s.recipient.toLowerCase(), bps: s.bps })) } : {}
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(),
1123
+ ...input.hookParams?.splits ? { splits: input.hookParams.splits.map((s) => ({ r: s.recipient.toLowerCase(), bps: s.bps })) } : {},
1124
+ // Bind the settlement-gating hooks too — a ConditionalOracle gate or a
1125
+ // ReputationGate materially changes WHEN/IF money moves, so dropping or
1126
+ // altering them after the preview must invalidate consent.
1127
+ ...input.hookParams?.condition ? { cond: { kind: input.hookParams.condition.kind, op: input.hookParams.condition.op, value: input.hookParams.condition.value, feed: input.hookParams.condition.feed ?? null } } : {},
1128
+ ...input.hookParams?.recipientAgentId ? { ragent: input.hookParams.recipientAgentId } : {}
1120
1129
  };
1121
1130
  const consent = checkConsent(consentIntent, input.consentToken);
1122
1131
  if (!consent.ok) {
@@ -1530,7 +1539,10 @@ async function runBatchPay(input) {
1530
1539
  t: "batch",
1531
1540
  chain: input.chain,
1532
1541
  token: input.token,
1533
- recipients: input.recipients.map((r) => ({ to: r.to.toLowerCase(), amount: r.amount }))
1542
+ recipients: input.recipients.map((r) => ({ to: r.to.toLowerCase(), amount: r.amount })),
1543
+ // Bind the funding source too (see q402_pay).
1544
+ wm: input.walletMode ?? "",
1545
+ wid: (input.walletId ?? "").toLowerCase()
1534
1546
  };
1535
1547
  const consent = checkConsent(consentIntent, input.consentToken);
1536
1548
  if (!consent.ok) {
@@ -3262,7 +3274,12 @@ async function runBridgeSend(input) {
3262
3274
  src: input.src,
3263
3275
  dst: input.dst,
3264
3276
  amount: input.amount,
3265
- feeToken: input.feeToken === "native" ? "native" : "LINK"
3277
+ feeToken: input.feeToken === "native" ? "native" : "LINK",
3278
+ // Bind the fee ceiling too — it bounds what the user actually pays, so it
3279
+ // must not be addable/changeable after the preview.
3280
+ maxFeeRaw: input.maxFeeRaw ?? null,
3281
+ // Bind the funding wallet too (see q402_pay).
3282
+ wid: (input.walletId ?? "").toLowerCase()
3266
3283
  };
3267
3284
  const consent = checkConsent(consentIntent, input.consentToken);
3268
3285
  if (input.confirm !== true || !consent.ok) {
@@ -3641,6 +3658,9 @@ var YieldDepositInputSchema = z16.object({
3641
3658
  ),
3642
3659
  confirm: z16.boolean().optional().describe(
3643
3660
  "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."
3661
+ ),
3662
+ consentToken: z16.string().optional().describe(
3663
+ "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."
3644
3664
  )
3645
3665
  });
3646
3666
  var YIELD_DEPOSIT_TOOL = {
@@ -3674,6 +3694,10 @@ var YIELD_DEPOSIT_TOOL = {
3674
3694
  confirm: {
3675
3695
  type: "boolean",
3676
3696
  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."
3697
+ },
3698
+ consentToken: {
3699
+ type: "string",
3700
+ 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."
3677
3701
  }
3678
3702
  },
3679
3703
  required: ["token", "amount"],
@@ -3692,12 +3716,20 @@ async function runYieldDeposit(input) {
3692
3716
  }
3693
3717
  const walletId = typeof input.walletId === "string" && input.walletId.length > 0 ? input.walletId.toLowerCase() : CONFIG.walletId ?? void 0;
3694
3718
  const idempotencyKey = typeof input.idempotencyKey === "string" && input.idempotencyKey.length > 0 ? input.idempotencyKey : hexlify2(randomBytes2(32));
3695
- if (input.confirm !== true) {
3719
+ const consentIntent = {
3720
+ t: "yield-deposit",
3721
+ chain: input.chain,
3722
+ token: input.token,
3723
+ amount: input.amount,
3724
+ walletId: walletId ?? null
3725
+ };
3726
+ const consent = checkConsent(consentIntent, input.consentToken);
3727
+ if (input.confirm !== true || !consent.ok) {
3696
3728
  const walletDesc = walletId ? `wallet ${walletId}` : "your default Agent Wallet";
3697
3729
  return {
3698
3730
  content: [{
3699
3731
  type: "text",
3700
- text: `Will supply ${input.amount} ${input.token} into Aave on ${input.chain} from ${walletDesc}. This MOVES FUNDS. Re-call with confirm:true to execute.`
3732
+ 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}".`
3701
3733
  }]
3702
3734
  };
3703
3735
  }
@@ -3821,6 +3853,9 @@ var YieldWithdrawInputSchema = z17.object({
3821
3853
  ),
3822
3854
  confirm: z17.boolean().optional().describe(
3823
3855
  "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."
3856
+ ),
3857
+ consentToken: z17.string().optional().describe(
3858
+ "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)."
3824
3859
  )
3825
3860
  });
3826
3861
  var YIELD_WITHDRAW_TOOL = {
@@ -3854,6 +3889,10 @@ var YIELD_WITHDRAW_TOOL = {
3854
3889
  confirm: {
3855
3890
  type: "boolean",
3856
3891
  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."
3892
+ },
3893
+ consentToken: {
3894
+ type: "string",
3895
+ 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)."
3857
3896
  }
3858
3897
  },
3859
3898
  required: ["token", "amount"],
@@ -3873,12 +3912,20 @@ async function runYieldWithdraw(input) {
3873
3912
  const walletId = typeof input.walletId === "string" && input.walletId.length > 0 ? input.walletId.toLowerCase() : CONFIG.walletId ?? void 0;
3874
3913
  const idempotencyKey = typeof input.idempotencyKey === "string" && input.idempotencyKey.length > 0 ? input.idempotencyKey : hexlify3(randomBytes3(32));
3875
3914
  const amountDesc = input.amount === "max" ? "the FULL position" : `${input.amount} ${input.token}`;
3876
- if (input.confirm !== true) {
3915
+ const consentIntent = {
3916
+ t: "yield-withdraw",
3917
+ chain: input.chain,
3918
+ token: input.token,
3919
+ amount: input.amount,
3920
+ walletId: walletId ?? null
3921
+ };
3922
+ const consent = checkConsent(consentIntent, input.consentToken);
3923
+ if (input.confirm !== true || !consent.ok) {
3877
3924
  const walletDesc = walletId ? `wallet ${walletId}` : "your default Agent Wallet";
3878
3925
  return {
3879
3926
  content: [{
3880
3927
  type: "text",
3881
- text: `Will withdraw ${amountDesc} from Aave on ${input.chain} back to ${walletDesc}. This MOVES FUNDS. Re-call with confirm:true to execute.`
3928
+ 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}".`
3882
3929
  }]
3883
3930
  };
3884
3931
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quackai/q402-mcp",
3
- "version": "0.8.24",
3
+ "version": "0.8.26",
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": [