@quackai/q402-mcp 0.4.5 → 0.4.7

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 +21 -11
  2. package/dist/index.js +26 -14
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -76,8 +76,14 @@ command = "npx"
76
76
  args = ["-y", "@quackai/q402-mcp"]
77
77
  startup_timeout_sec = 20.0
78
78
  env = {
79
- # Two-key model (v0.4.5+): set whichever applies — both is best.
80
- # The server auto-routes by chain: BNB trial key, else multichain key.
79
+ # Two-key model (v0.4.7+): set whichever applies — both is best.
80
+ # Auto-routing rule (same for q402_pay AND q402_batch_pay):
81
+ # chain="bnb" + Q402_TRIAL_API_KEY set → Trial (free sponsored)
82
+ # anything else → Multichain (paid 8-chain)
83
+ # Batch ambiguity: when auto would land on Trial AND recipients.length > 5,
84
+ # q402_batch_pay returns status="ambiguous" WITHOUT executing so the agent
85
+ # can ask the user — pass keyScope="trial" (first 5), "multichain" (all
86
+ # paid), or call twice (5 free + remainder paid).
81
87
  # Both keys use the same q402_live_ prefix — the env var name is what
82
88
  # carries the scope, not the key string. Get the values from the
83
89
  # dashboard (each key has its own copy button per view).
@@ -112,7 +118,7 @@ The server has no client-specific code. If your client speaks stdio MCP, point i
112
118
  | `q402_quote` | none | Compare gas cost and supported tokens across chains. Read-only. |
113
119
  | `q402_balance` | API key | Verify the API key and report its plan tier + remaining quota credits (live vs sandbox). |
114
120
  | `q402_pay` | API key + private key + flag | Send a gasless payment to a single recipient. **Sandbox by default** — see [Sandbox vs live mode](#sandbox-vs-live-mode). |
115
- | `q402_batch_pay` | API key + private key + flag | Send a gasless payment to **multiple** recipients in one call on a single chain × token. Trial keys: 5 rows max. Paid keys: 20 rows max. **Supported chains: avax, bnb, eth, mantle, injective, monad** (default EIP-7702 mode). xlayer + stable are NOT batchable — use `q402_pay` in a loop for those. Same sandbox gating as `q402_pay`. **Rate-limit note:** the inner `/api/relay` budget (30/min per key) is consumed per row, so a paid 20-row batch leaves ~10 inner slots for the next minute. |
121
+ | `q402_batch_pay` | API key + private key + flag | Send a gasless payment to **multiple** recipients in one call on a single chain × token. Trial keys: 5 rows max. Paid keys: 20 rows max. **Auto-routing:** same rule as `q402_pay` (BNB + Trial key set ⇒ Trial, else Multichain). **Ambiguity gate:** 6+ recipient BNB batches with Trial set return `status="ambiguous"` instead of executing — the agent asks the user to pick `keyScope="trial"` (first 5), `"multichain"` (all paid), or two calls (5 free + remainder paid). **Supported chains: avax, bnb, eth, mantle, injective, monad** (default EIP-7702 mode). xlayer + stable are NOT batchable — use `q402_pay` in a loop for those. Same sandbox gating as `q402_pay`. **Rate-limit note:** the inner `/api/relay` budget (30/min per key) is consumed per row, so a paid 20-row batch leaves ~10 inner slots for the next minute. |
116
122
  | `q402_receipt` | none | Look up a Trust Receipt by `rct_…` id and locally verify its ECDSA signature against the relayer EOA. Returns the public settlement record + a `verified` boolean. *receiptId-only today; tx-hash lookup reserved for a future release.* |
117
123
 
118
124
  `q402_pay` and `q402_batch_pay` follow a "confirm in chat first" contract: the tool description instructs the model to never call it without explicit user approval of the recipient address(es), amount(s), chain, and token. For batch calls the user must approve the **full batch**, not the individual rows.
@@ -128,19 +134,23 @@ The server has no client-specific code. If your client speaks stdio MCP, point i
128
134
 
129
135
  ## Sandbox vs live mode
130
136
 
131
- By default the MCP server operates in **sandbox mode**: `q402_pay` returns a deterministic-looking fake transaction hash, no funds move, no gas-tank credit is consumed. That makes it safe to plug into any MCP client without worrying about an LLM hallucinating a payment.
137
+ By default the MCP server operates in **sandbox mode**: `q402_pay` returns a random fake transaction hash (32-byte hex, no on-chain broadcast), no funds move, no gas-tank credit is consumed. That makes it safe to plug into any MCP client without worrying about an LLM hallucinating a payment.
132
138
 
133
139
  To enable real on-chain transactions, the resolved API key must be live (`q402_live_*`), `Q402_PRIVATE_KEY` must be set, and `Q402_ENABLE_REAL_PAYMENTS=1`:
134
140
 
135
141
  ```bash
136
- # Two-key model (v0.4.5+) — set whichever applies. Both is best.
137
- # Auto-routing: chain="bnb" trial key (if set), otherwise multichain key.
142
+ # Two-key model (v0.4.7+) — set whichever applies. Both is best.
143
+ # Auto-routing (same for q402_pay AND q402_batch_pay):
144
+ # chain="bnb" + Q402_TRIAL_API_KEY set → Trial (free sponsored)
145
+ # anything else → Multichain (paid 8-chain)
146
+ # Batch ambiguity: 6+ recipient BNB batch with Trial set returns
147
+ # status="ambiguous" instead of executing — agent asks user to pick.
138
148
  # Override per call with keyScope: "auto" | "trial" | "multichain".
139
149
  Q402_TRIAL_API_KEY=q402_live_... # BNB-only sponsored Trial key (from /event)
140
150
  Q402_MULTICHAIN_API_KEY=q402_live_... # paid 8-chain key (per-chain Gas Tank)
141
151
 
142
152
  # Legacy fallback. Used for both scopes when the two above are unset —
143
- # pre-v0.4.5 users keep working without any config change.
153
+ # pre-v0.4.7 users keep working without any config change.
144
154
  Q402_API_KEY=q402_live_...
145
155
 
146
156
  Q402_PRIVATE_KEY=0xabc... # signer for the payer EOA
@@ -151,7 +161,7 @@ Anything missing for the resolved scope → automatic sandbox fallback with a hi
151
161
 
152
162
  > ⚠️ **Sandbox returns a deterministic-looking fake `txHash` and a synthetic success result.** A user who *expected* a live transfer (e.g. forgot to set `Q402_ENABLE_REAL_PAYMENTS=1`, mis-typed a scoped env var, or hit an impossible chain×scope combination like `keyScope: "trial"` + `chain: "monad"`) gets a "success" back and may believe funds actually moved.
153
163
  >
154
- > Two-layer mitigation: every sandbox response carries a `setupHint` field on the tool result describing **exactly why** sandbox was selected, and the `q402_balance` tool's `apiKeyKind: "missing"` makes the same diagnosis explicit. Always check `setupHint` on the first call from a new install. The deterministic `txHash` pattern (`0x` + 64 hex derived from `keccak256(chain, to, amount, token, "sandbox")`) is intentional so the agent can recognise it post-hoc, but the safer habit is to inspect `setupHint` before showing the user a success message.
164
+ > Two-layer mitigation: every sandbox response carries a `setupHint` field on the tool result describing **exactly why** sandbox was selected, and a `method: "sandbox"` field that makes the mode explicit independent of hash inspection. The `txHash` itself is a 32-byte random hex string — visually indistinguishable from a real hash but emitted only when no on-chain TX is broadcast so don't rely on hash forensics. Always check `setupHint` (or `method`) before showing the user a success message.
155
165
 
156
166
  ### Hard caps
157
167
 
@@ -170,9 +180,9 @@ Combined with the `confirm: true` argument the tool requires, this means the mod
170
180
 
171
181
  | Env var | Required for | Notes |
172
182
  |---|---|---|
173
- | `Q402_TRIAL_API_KEY` | live-pay (BNB) | BNB-only sponsored Trial key. Free at https://q402.quackai.ai/event. Used automatically for `chain="bnb"` when set. |
174
- | `Q402_MULTICHAIN_API_KEY` | live-pay (8-chain) | Paid 8-chain key. Get one at https://q402.quackai.ai/payment. Used for all non-BNB chains and for BNB when no Trial key is set. |
175
- | `Q402_API_KEY` | legacy fallback | Pre-v0.4.5 single-env path. Used for both scopes when the two above are unset. Keep set if you only have one key. |
183
+ | `Q402_TRIAL_API_KEY` | live-pay (BNB) | BNB-only sponsored Trial key. Free at https://q402.quackai.ai/event. Auto-routed for `chain="bnb"` in both `q402_pay` and `q402_batch_pay` (≤5 recipients) when set. 6+ recipient BNB batches return `status="ambiguous"` so the agent can ask the user how to split. |
184
+ | `Q402_MULTICHAIN_API_KEY` | live-pay (8-chain) | Paid 8-chain key. Get one at https://q402.quackai.ai/payment. Auto-routed for non-BNB chains AND for BNB when no Trial key is set. Cap: 20 recipients per batch. |
185
+ | `Q402_API_KEY` | legacy fallback | Pre-v0.4.7 single-env path. Used for both scopes when the two above are unset. Keep set if you only have one key. |
176
186
  | `Q402_PRIVATE_KEY` | live-pay | Signer for the payer EOA. **Never share. Never paste in chat.** |
177
187
  | `Q402_ENABLE_REAL_PAYMENTS` | live-pay | Set to `1` to opt in. Any other value (or unset) → sandbox. |
178
188
  | `Q402_MAX_AMOUNT_PER_CALL` | optional | USD-equivalent cap. Defaults to `5`. |
package/dist/index.js CHANGED
@@ -52,13 +52,12 @@ function loadConfig() {
52
52
  };
53
53
  }
54
54
  var CONFIG = loadConfig();
55
- function resolveApiKey(chain, scope = "auto", intent = "single") {
55
+ function resolveApiKey(chain, scope = "auto") {
56
56
  const effectiveScope = scope === "auto" ? (
57
- // Smart routing: batches default to multichain (trial cap=5 would
58
- // silently fail any 6+ recipient batch). Single payments default to
59
- // trial on BNB when a trial key is set, so the free sponsored
60
- // allotment gets used naturally.
61
- intent === "batch" ? "multichain" : chain === "bnb" && CONFIG.trialApiKey ? "trial" : "multichain"
57
+ // Unified rule for single + batch: BNB prefers Trial when set,
58
+ // everything else uses Multichain. Batch cap ambiguity is handled
59
+ // in batch-pay.ts BEFORE this resolver runs.
60
+ chain === "bnb" && CONFIG.trialApiKey ? "trial" : "multichain"
62
61
  ) : scope;
63
62
  if (effectiveScope === "trial") {
64
63
  if (chain !== "bnb") {
@@ -680,7 +679,7 @@ var PayInputSchema = z2.object({
680
679
  "Stablecoin symbol. USDC / USDT supported on most chains (Injective is USDT-only). RLUSD (Ripple USD, NY DFS regulated, decimals 18) is Ethereum-only."
681
680
  ),
682
681
  keyScope: z2.enum(["auto", "trial", "multichain"]).optional().describe(
683
- 'Which API key to use. "auto" (default) picks the Trial key for BNB when Q402_TRIAL_API_KEY is set, and the Multichain key otherwise. "trial" forces the BNB-only sponsored key. "multichain" forces the paid 8-chain key.'
682
+ 'Which API key to use. "auto" (default): chain="bnb" + Q402_TRIAL_API_KEY set \u2192 Trial (free sponsored); else Multichain. "trial" forces the BNB-only sponsored key. "multichain" forces the paid 8-chain key. Same rule applies to q402_batch_pay.'
684
683
  ),
685
684
  confirm: z2.literal(true).describe(
686
685
  "MUST be true. Prove the user explicitly approved this exact recipient and amount in the conversation right before this tool was called. Setting this to true on behalf of the user without confirmation is a violation of the tool contract."
@@ -721,7 +720,7 @@ async function runPay(input) {
721
720
  guardsApplied.push(`recipient_allowlist[${CONFIG.allowedRecipients.length}]`);
722
721
  }
723
722
  const scopeRequest = input.keyScope ?? "auto";
724
- const resolved = resolveApiKey(input.chain, scopeRequest, "single");
723
+ const resolved = resolveApiKey(input.chain, scopeRequest);
725
724
  guardsApplied.push(`scope=${resolved.scope}${resolved.fromLegacyFallback ? "(legacy)" : ""}`);
726
725
  const live = isLiveModeFor(resolved);
727
726
  if (!live) {
@@ -758,7 +757,7 @@ function describeSandboxReason(resolvedKey) {
758
757
  }
759
758
  var PAY_TOOL = {
760
759
  name: "q402_pay",
761
- description: "Send a gasless USDC, USDT, or RLUSD payment via Q402. Uses Q402_TRIAL_API_KEY (BNB-only sponsored Trial) when chain='bnb' and the Trial env is set, and Q402_MULTICHAIN_API_KEY (paid 8-chain) otherwise. 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 \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, and Q402_ENABLE_REAL_PAYMENTS=1. The recipient receives the full amount; the sender pays $0 in gas. ALWAYS get explicit user confirmation of the exact recipient address, amount, chain, and token in conversation immediately before calling this tool.",
760
+ description: "Send a gasless USDC, USDT, or RLUSD payment via Q402. Auto-routing: chain='bnb' + Q402_TRIAL_API_KEY set \u2192 Trial (free sponsored); anything else \u2192 Multichain (paid 8-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 \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, and Q402_ENABLE_REAL_PAYMENTS=1. The recipient receives the full amount; the sender pays $0 in gas. ALWAYS get explicit user confirmation of the exact recipient address, amount, chain, and token in conversation immediately before calling this tool.",
762
761
  inputSchema: {
763
762
  type: "object",
764
763
  properties: {
@@ -816,7 +815,7 @@ var BatchPayInputSchema = z3.object({
816
815
  `Array of {to, amount} pairs. All recipients share the same chain and token. Trial keys: max ${RECIPIENT_LIMIT_TRIAL} rows. Paid keys: max ${RECIPIENT_LIMIT_PAID} rows.`
817
816
  ),
818
817
  keyScope: z3.enum(["auto", "trial", "multichain"]).optional().describe(
819
- 'Which API key to use. "auto" (default) picks Trial for BNB when Q402_TRIAL_API_KEY is set, Multichain otherwise. Trial forces the BNB-only sponsored key; "multichain" forces the paid 8-chain key.'
818
+ 'Which API key to use. "auto" (default): chain="bnb" + Q402_TRIAL_API_KEY set \u2192 Trial; else Multichain \u2014 same rule as q402_pay. When auto would land on Trial AND recipients.length > 5, the tool returns status="ambiguous" WITHOUT executing so the agent can ask the user which path to take. Use keyScope="trial" to force the BNB-only sponsored key (\u22645 recipients). keyScope="multichain" forces the paid 8-chain key (\u226420 recipients).'
820
819
  ),
821
820
  confirm: z3.literal(true).describe(
822
821
  "MUST be true. The user must have explicitly approved this exact set of recipients, amounts, chain, and token in the conversation right before this tool was called. Setting confirm=true on behalf of the user without that approval is a violation of the tool contract."
@@ -863,7 +862,20 @@ async function runBatchPay(input) {
863
862
  guardsApplied.push(`recipient_allowlist[${CONFIG.allowedRecipients.length}]`);
864
863
  }
865
864
  const scopeRequest = input.keyScope ?? "auto";
866
- const resolved = resolveApiKey(input.chain, scopeRequest, "batch");
865
+ if (scopeRequest === "auto" && input.chain === "bnb" && CONFIG.trialApiKey && input.recipients.length > RECIPIENT_LIMIT_TRIAL) {
866
+ const overflow = input.recipients.length - RECIPIENT_LIMIT_TRIAL;
867
+ guardsApplied.push("batch_cap_ambiguous");
868
+ return {
869
+ mode: "none",
870
+ status: "ambiguous",
871
+ guardsApplied,
872
+ setupHint: `Batch of ${input.recipients.length} on BNB exceeds the Trial cap of ${RECIPIENT_LIMIT_TRIAL}. Ask the user to pick one and re-invoke q402_batch_pay with explicit keyScope:
873
+ \u2022 keyScope="trial" \u2014 keep only the first ${RECIPIENT_LIMIT_TRIAL} recipients (free, sponsored). Drop the remaining ${overflow}.
874
+ \u2022 keyScope="multichain" \u2014 send all ${input.recipients.length} on the paid Multichain key (charges the paid pool + Gas Tank).
875
+ \u2022 Split \u2014 two separate calls: keyScope="trial" with the first ${RECIPIENT_LIMIT_TRIAL} (free), then keyScope="multichain" with the remaining ${overflow} (paid). This maximises free Trial usage.`
876
+ };
877
+ }
878
+ const resolved = resolveApiKey(input.chain, scopeRequest);
867
879
  guardsApplied.push(`scope=${resolved.scope}${resolved.fromLegacyFallback ? "(legacy)" : ""}`);
868
880
  const live = isLiveModeFor(resolved);
869
881
  if (!live) {
@@ -930,7 +942,7 @@ function describeSandboxReason2(resolvedKey) {
930
942
  }
931
943
  var BATCH_PAY_TOOL = {
932
944
  name: "q402_batch_pay",
933
- description: `Send gasless payments to MULTIPLE recipients on a single chain \xD7 token in one call. Uses Q402_TRIAL_API_KEY for chain='bnb' when the Trial env is set, Q402_MULTICHAIN_API_KEY otherwise. Set keyScope='trial' or 'multichain' to force one. Trial keys: max ${RECIPIENT_LIMIT_TRIAL} recipients per call, BNB Chain + USDC/USDT only. Multichain keys: max ${RECIPIENT_LIMIT_PAID} recipients per call across 6 EIP-7702 default chains (avax, bnb, eth, mantle, injective, monad). xlayer + stable are NOT batchable \u2014 use q402_pay in a loop. SANDBOX BY DEFAULT \u2014 real on-chain TX only when the resolved key is live (q402_live_*), Q402_PRIVATE_KEY is set, and Q402_ENABLE_REAL_PAYMENTS=1. Every recipient receives the full amount; the sender pays $0 in gas for the entire batch. 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.`,
945
+ description: `Send gasless payments to MULTIPLE recipients on a single chain \xD7 token in one call. Auto-routing follows the same rule as q402_pay: chain='bnb' + Q402_TRIAL_API_KEY set \u2192 Trial; else Multichain. Trial keys: max ${RECIPIENT_LIMIT_TRIAL} recipients per call, BNB Chain + USDC/USDT only. Multichain keys: max ${RECIPIENT_LIMIT_PAID} recipients per call across 6 EIP-7702 default chains (avax, bnb, eth, mantle, injective, monad). xlayer + stable are NOT batchable \u2014 use q402_pay in a loop. AMBIGUITY GATE: when auto would land on Trial AND recipients.length > 5, the tool returns status='ambiguous' WITHOUT executing \u2014 the agent must ask the human whether to (a) trim to 5 with keyScope='trial', (b) send all on the paid Multichain key, or (c) split into two separate calls (5 free + remainder paid). Re-invoke with explicit keyScope after the choice. SANDBOX BY DEFAULT \u2014 real on-chain TX only when the resolved key is live (q402_live_*), Q402_PRIVATE_KEY is set, and Q402_ENABLE_REAL_PAYMENTS=1. Every recipient receives the full amount; the sender pays $0 in gas for the entire batch. 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.`,
934
946
  inputSchema: {
935
947
  type: "object",
936
948
  properties: {
@@ -971,7 +983,7 @@ var BATCH_PAY_TOOL = {
971
983
  keyScope: {
972
984
  type: "string",
973
985
  enum: ["auto", "trial", "multichain"],
974
- description: 'Which API key to use. "auto" (default) picks Trial for BNB when Q402_TRIAL_API_KEY is set, Multichain otherwise.'
986
+ description: 'Which API key to use. "auto" (default): BNB + trial key set \u2192 Trial; else Multichain. When auto would land on Trial AND recipients.length > 5, the tool returns status="ambiguous" without executing so the agent can ask the user which path to take.'
975
987
  },
976
988
  confirm: {
977
989
  type: "boolean",
@@ -1221,7 +1233,7 @@ var RECEIPT_TOOL = {
1221
1233
 
1222
1234
  // src/index.ts
1223
1235
  var PACKAGE_NAME = "@quackai/q402-mcp";
1224
- var PACKAGE_VERSION = "0.4.5";
1236
+ var PACKAGE_VERSION = "0.4.7";
1225
1237
  function jsonText(value) {
1226
1238
  return { type: "text", text: JSON.stringify(value, null, 2) };
1227
1239
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quackai/q402-mcp",
3
- "version": "0.4.5",
3
+ "version": "0.4.7",
4
4
  "description": "MCP server for Q402 — gasless USDC, USDT, and RLUSD payments across 8 EVM chains, 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": [