@quackai/q402-mcp 0.8.9 → 0.8.10

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 (3) hide show
  1. package/README.md +1 -1
  2. package/dist/index.js +129 -30
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -177,7 +177,7 @@ Then export the values in `~/.zshrc` / `~/.bashrc`. See the [Codex config refere
177
177
  | `q402_recurring_skip_next` | api key | Skip only the next scheduled fire. |
178
178
  | `q402_recurring_cancel` | api key | Permanently stop a rule. |
179
179
  | `q402_bridge_quote` | none | Quote a Chainlink CCIP USDC bridge across eth/avax/arbitrum. Returns LINK + native fee. |
180
- | `q402_bridge_send` | sandbox only | Plan-and-quote a bridge end-to-end. `sandbox: false` returns `isError:true` with a dashboard pointer until session-binding lands. |
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
183
 
package/dist/index.js CHANGED
@@ -211,7 +211,7 @@ var isValidPrivateKey = (s) => typeof s === "string" && PRIVATE_KEY_RE.test(s);
211
211
 
212
212
  // src/version.ts
213
213
  var PACKAGE_NAME = "@quackai/q402-mcp";
214
- var PACKAGE_VERSION = "0.8.9";
214
+ var PACKAGE_VERSION = "0.8.10";
215
215
 
216
216
  // src/tools/quote.ts
217
217
  import { z } from "zod";
@@ -2897,23 +2897,23 @@ var BridgeSendInputSchema = z11.object({
2897
2897
  src: z11.enum(["eth", "avax", "arbitrum"]).describe("Source chain"),
2898
2898
  dst: z11.enum(["eth", "avax", "arbitrum"]).describe("Destination chain"),
2899
2899
  // `^\d+$` accepts "0" which is a no-op bridge. Require at least one
2900
- // non-zero digit (^\d*[1-9]\d*$ also accepts e.g. "10", "100") so an
2901
- // accidental amount=0 fails locally instead of returning a synthetic
2902
- // "success" envelope in sandbox.
2900
+ // non-zero digit so an accidental amount=0 fails locally instead of
2901
+ // returning a synthetic "success" envelope in sandbox.
2903
2902
  amount: z11.string().regex(/^\d*[1-9]\d*$/).describe("USDC amount in raw 6-decimal units, > 0"),
2904
- walletId: z11.string().describe("Agentic Wallet ID (from q402_agentic_info)"),
2905
- feeToken: z11.enum(["LINK", "native", "auto"]).optional().describe("Fee token. 'auto' picks cheaper of the two; defaults to LINK."),
2906
- sandbox: z11.boolean().optional().describe("Sandbox mode (default true). Set to false for live bridge.")
2903
+ walletId: z11.string().optional().describe("Agentic Wallet ID (from q402_agentic_info). Optional \u2014 defaults to the owner's default Agent Wallet."),
2904
+ feeToken: z11.enum(["LINK", "native"]).optional().describe("Fee token. Defaults to LINK (~10% cheaper than native)."),
2905
+ sandbox: z11.boolean().optional().describe("Sandbox mode (default true). Set to false for a live on-chain bridge."),
2906
+ maxFeeRaw: z11.string().regex(/^\d+$/).optional().describe("Optional client-side fee cap in raw 18-dec wei. Server still clamps to its 10% slippage ceiling; clients may LOWER but not RAISE.")
2907
2907
  }).refine((d) => d.src !== d.dst, {
2908
2908
  // Local Zod rejection saves a network round-trip + a Q402 backend log
2909
- // entry. The /api/ccip/send route also rejects same-chain bridges but
2910
- // the agent only finds out after burning a request.
2909
+ // entry. The bridge route also rejects same-chain bridges but the
2910
+ // agent only finds out after burning a request.
2911
2911
  message: "src must differ from dst",
2912
2912
  path: ["dst"]
2913
2913
  });
2914
2914
  var BRIDGE_SEND_TOOL = {
2915
2915
  name: "q402_bridge_send",
2916
- description: "PLAN-AND-QUOTE TOOL (sandbox-only at this release). Execute a Chainlink CCIP USDC bridge plan between two of the 3 supported chains (eth/avax/arbitrum). Returns a synthetic messageId so the agent can preview the bridge end-to-end without moving funds. LIVE EXECUTION via MCP is NOT yet wired \u2014 it requires an owner-EIP-712 signature on the `ccip.bridge` intent which the MCP CLI cannot produce (the dashboard or a future session-binding flow is the live path). Calling with `sandbox: false` returns isError:true regardless of Q402_ENABLE_REAL_PAYMENTS. Recommended flow: q402_bridge_quote \u2192 show user the cost \u2192 if they want to bridge live, point them at https://q402.quackai.ai/dashboard \u2192 Agent tab \u2192 Bridge.",
2916
+ 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. Recommended flow: q402_bridge_quote first \u2192 confirm cost with the user \u2192 q402_bridge_send with sandbox: false. Live mode needs a Multichain subscription; trial keys are rejected. If the bridge returns AGENT_WALLET_DELEGATED, run q402_clear_delegation on the source chain first.",
2917
2917
  inputSchema: {
2918
2918
  type: "object",
2919
2919
  properties: {
@@ -2929,50 +2929,149 @@ var BRIDGE_SEND_TOOL = {
2929
2929
  },
2930
2930
  amount: {
2931
2931
  type: "string",
2932
- pattern: "^[0-9]+$",
2933
- description: "USDC amount in raw 6-decimal units (e.g. '1000000' = 1 USDC). Integer string only."
2932
+ pattern: "^\\d*[1-9]\\d*$",
2933
+ description: "USDC amount in raw 6-decimal units (e.g. '1000000' = 1 USDC). Integer string, > 0."
2934
2934
  },
2935
2935
  walletId: {
2936
2936
  type: "string",
2937
- description: "Agentic Wallet ID (from q402_agentic_info)."
2937
+ description: "Agentic Wallet ID (from q402_agentic_info). Optional \u2014 defaults to the owner's default Agent Wallet when omitted."
2938
2938
  },
2939
2939
  feeToken: {
2940
2940
  type: "string",
2941
- enum: ["LINK", "native", "auto"],
2942
- description: "Fee token. Default: LINK (~10% cheaper). 'auto' picks cheaper at quote time."
2941
+ enum: ["LINK", "native"],
2942
+ description: "Fee token. Default: LINK (~10% cheaper)."
2943
2943
  },
2944
2944
  sandbox: {
2945
2945
  type: "boolean",
2946
- description: "Sandbox-only at the current release. Passing false returns isError:true with a pointer to the dashboard's Bridge UI \u2014 live MCP execution lands once session-binding is plumbed."
2946
+ description: "Sandbox mode (default true). Set to false for a real on-chain bridge."
2947
+ },
2948
+ maxFeeRaw: {
2949
+ type: "string",
2950
+ pattern: "^[0-9]+$",
2951
+ description: "Optional client-side fee cap in raw 18-dec wei."
2947
2952
  }
2948
2953
  },
2949
- required: ["src", "dst", "amount", "walletId"]
2954
+ required: ["src", "dst", "amount"]
2950
2955
  }
2951
2956
  };
2957
+ function sandbox(input, note) {
2958
+ return {
2959
+ sandbox: true,
2960
+ messageId: "0x" + "00".repeat(32),
2961
+ txHash: "0x" + "00".repeat(32),
2962
+ note,
2963
+ src: input.src,
2964
+ dst: input.dst,
2965
+ amount: input.amount
2966
+ };
2967
+ }
2952
2968
  async function runBridgeSend(input) {
2953
- const sandbox = input.sandbox !== false;
2954
- if (sandbox) {
2969
+ if (input.sandbox !== false) {
2970
+ return {
2971
+ content: [{
2972
+ type: "text",
2973
+ text: JSON.stringify(
2974
+ sandbox(input, "Sandbox response. Pass `sandbox: false` AND set Q402_ENABLE_REAL_PAYMENTS=1 (with a live Q402_MULTICHAIN_API_KEY) to fire a real bridge."),
2975
+ null,
2976
+ 2
2977
+ )
2978
+ }]
2979
+ };
2980
+ }
2981
+ const resolved = resolveApiKey(input.src, "multichain");
2982
+ if (!resolved.apiKey) {
2983
+ return {
2984
+ content: [{
2985
+ type: "text",
2986
+ text: JSON.stringify(
2987
+ sandbox(input, resolved.sandboxReason ?? "No live Multichain API key configured. Set Q402_MULTICHAIN_API_KEY to a q402_live_\u2026 key from https://q402.quackai.ai/payment."),
2988
+ null,
2989
+ 2
2990
+ )
2991
+ }]
2992
+ };
2993
+ }
2994
+ if (!resolved.apiKey.startsWith("q402_live_")) {
2995
+ return {
2996
+ content: [{
2997
+ type: "text",
2998
+ text: JSON.stringify(
2999
+ sandbox(input, "Resolved API key is not a live key. Set Q402_MULTICHAIN_API_KEY to a q402_live_\u2026 key."),
3000
+ null,
3001
+ 2
3002
+ )
3003
+ }]
3004
+ };
3005
+ }
3006
+ if (!CONFIG.realPaymentsRequested) {
3007
+ return {
3008
+ content: [{
3009
+ type: "text",
3010
+ text: JSON.stringify(
3011
+ sandbox(input, "Q402_ENABLE_REAL_PAYMENTS is not set to 1. Set the env var to fire a real bridge."),
3012
+ null,
3013
+ 2
3014
+ )
3015
+ }]
3016
+ };
3017
+ }
3018
+ const walletId = typeof input.walletId === "string" && input.walletId.length > 0 ? input.walletId.toLowerCase() : CONFIG.walletId ?? void 0;
3019
+ const feeToken = input.feeToken === "native" ? "native" : "LINK";
3020
+ let resp;
3021
+ try {
3022
+ resp = await fetch(`${CONFIG.relayBaseUrl}/wallet/agentic/bridge`, {
3023
+ method: "POST",
3024
+ headers: { "Content-Type": "application/json" },
3025
+ body: JSON.stringify({
3026
+ apiKey: resolved.apiKey,
3027
+ ...walletId ? { walletId } : {},
3028
+ src: input.src,
3029
+ dst: input.dst,
3030
+ amount: input.amount,
3031
+ feeToken,
3032
+ ...input.maxFeeRaw ? { maxFeeRaw: input.maxFeeRaw } : {}
3033
+ }),
3034
+ signal: AbortSignal.timeout(9e4)
3035
+ });
3036
+ } catch (e) {
3037
+ return {
3038
+ content: [{
3039
+ type: "text",
3040
+ text: `Bridge fetch failed: ${e instanceof Error ? e.message : String(e)}. Retry; if the relay 60s budget timed out, the bridge may still be in flight \u2014 check the dashboard's Bridge History or CCIP Explorer before re-firing.`
3041
+ }],
3042
+ isError: true
3043
+ };
3044
+ }
3045
+ const data = await resp.json().catch(() => ({}));
3046
+ if (!resp.ok || !data.success) {
2955
3047
  return {
2956
3048
  content: [{
2957
3049
  type: "text",
2958
3050
  text: JSON.stringify({
2959
- sandbox: true,
2960
- messageId: "0x" + "00".repeat(32),
2961
- txHash: "0x" + "00".repeat(32),
2962
- note: "Sandbox response. To execute a real bridge, pass `sandbox: false` AND ensure the server has Q402_ENABLE_REAL_PAYMENTS=1.",
2963
- src: input.src,
2964
- dst: input.dst,
2965
- amount: input.amount
3051
+ sandbox: false,
3052
+ httpStatus: resp.status,
3053
+ ...data
2966
3054
  }, null, 2)
2967
- }]
3055
+ }],
3056
+ isError: true
2968
3057
  };
2969
3058
  }
2970
3059
  return {
2971
3060
  content: [{
2972
3061
  type: "text",
2973
- text: "Live CCIP bridge via MCP is not yet wired \u2014 agents can quote and plan via q402_bridge_quote and q402_bridge_send (sandbox), but actual execution must happen via https://q402.quackai.ai/dashboard for now. Live MCP execution lands in a follow-up release."
2974
- }],
2975
- isError: true
3062
+ text: JSON.stringify({
3063
+ sandbox: false,
3064
+ success: true,
3065
+ messageId: data.messageId,
3066
+ txHash: data.txHash,
3067
+ feeWhole: data.feeWhole,
3068
+ feeToken: data.feeToken,
3069
+ ccipExplorer: data.ccipExplorer,
3070
+ srcExplorer: data.srcExplorer,
3071
+ ...data.agentFundTxHash ? { agentFundTxHash: data.agentFundTxHash, agentFundEth: data.agentFundEth } : {},
3072
+ note: "Bridge submitted on source chain. CCIP delivery to destination chain typically takes 8-25 minutes. Track via the ccipExplorer URL above."
3073
+ }, null, 2)
3074
+ }]
2976
3075
  };
2977
3076
  }
2978
3077
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quackai/q402-mcp",
3
- "version": "0.8.9",
3
+ "version": "0.8.10",
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": [