@quackai/q402-mcp 0.5.3 → 0.5.5
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 +3 -3
- package/dist/index.js +28 -19
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
>
|
|
10
10
|
> **Trial-scope policy:** API keys minted under the free-trial program (`plan: "trial"`) are restricted to BNB Chain with USDC/USDT — server-side enforcement, returns `403 TRIAL_BNB_ONLY` otherwise. **Paid API keys see the full 9-chain matrix at all times.**
|
|
11
11
|
|
|
12
|
-
Claude can now reason about stablecoin payments end to end
|
|
12
|
+
AI agents — Claude (Desktop / Code), OpenAI Codex CLI, Cursor, Cline, and any other MCP-compatible client — can now reason about stablecoin payments end to end: quote a transfer across 9 chains, pick the cheapest route, and (optionally) settle the transaction over [Q402](https://q402.quackai.ai)'s EIP-7702 relayer infrastructure. The recipient receives the full amount; the sender pays $0 in gas.
|
|
13
13
|
|
|
14
14
|
---
|
|
15
15
|
|
|
@@ -120,8 +120,8 @@ The server has no client-specific code. If your client speaks stdio MCP, point i
|
|
|
120
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). |
|
|
121
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, scroll** (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. |
|
|
122
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.* |
|
|
123
|
-
| `q402_wallet_status` | private key | Per-chain EIP-7702 delegation status for the EOA derived from `Q402_PRIVATE_KEY`, across all 9 Q402 chains. Read-only — no on-chain action, no quota consumption.
|
|
124
|
-
| `q402_clear_delegation` |
|
|
123
|
+
| `q402_wallet_status` | private key | Per-chain EIP-7702 delegation status for the EOA derived from `Q402_PRIVATE_KEY`, across all 9 Q402 chains. Read-only — no on-chain action, no quota consumption. Useful as the diagnostic step before `q402_clear_delegation`. |
|
|
124
|
+
| `q402_clear_delegation` | private key | Clear the EIP-7702 delegation on a single chain for the configured wallet. Local signing with `Q402_PRIVATE_KEY`; Q402 sponsors the on-chain TX so the user pays $0 gas. After clearing, the next `q402_pay` on that chain recreates a fresh delegation automatically. |
|
|
125
125
|
|
|
126
126
|
`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.
|
|
127
127
|
|
package/dist/index.js
CHANGED
|
@@ -748,7 +748,7 @@ async function runPay(input) {
|
|
|
748
748
|
token: input.token
|
|
749
749
|
});
|
|
750
750
|
guardsApplied.push("mode=sandbox");
|
|
751
|
-
const setupHint = resolved.sandboxReason ?? describeSandboxReason(resolved.apiKey ?? "");
|
|
751
|
+
const setupHint = resolved.sandboxReason ?? describeSandboxReason(resolved.apiKey ?? "", resolved.scope);
|
|
752
752
|
return { result: result2, guardsApplied, setupHint };
|
|
753
753
|
}
|
|
754
754
|
const client = new Q402NodeClient({
|
|
@@ -765,17 +765,19 @@ async function runPay(input) {
|
|
|
765
765
|
guardsApplied.push("mode=live");
|
|
766
766
|
return { result, guardsApplied };
|
|
767
767
|
}
|
|
768
|
-
function describeSandboxReason(resolvedKey) {
|
|
768
|
+
function describeSandboxReason(resolvedKey, scope) {
|
|
769
769
|
const missing = [];
|
|
770
770
|
if (!resolvedKey.startsWith("q402_live_")) missing.push("a live API key (must start with q402_live_)");
|
|
771
771
|
if (!CONFIG.privateKey) missing.push("Q402_PRIVATE_KEY");
|
|
772
772
|
if (!CONFIG.realPaymentsRequested) missing.push("Q402_ENABLE_REAL_PAYMENTS=1");
|
|
773
773
|
if (missing.length === 0) return "Sandbox mode active (no env state change needed).";
|
|
774
|
-
|
|
774
|
+
const tier = scope === "trial" ? "Free Trial" : "Multichain";
|
|
775
|
+
const url = scope === "trial" ? "https://q402.quackai.ai/event" : "https://q402.quackai.ai/payment";
|
|
776
|
+
return "Sandbox mode is active because the following env vars are missing or not yet set: " + missing.join(", ") + `. Get a live ${tier} key at ${url}.`;
|
|
775
777
|
}
|
|
776
778
|
var PAY_TOOL = {
|
|
777
779
|
name: "q402_pay",
|
|
778
|
-
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 9-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 \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.
|
|
780
|
+
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 9-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 \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. After the first payment on a chain, follow-up payments on the same chain are faster and cheaper (Q402 reuses the wallet's setup); q402_clear_delegation resets it if the user ever asks. ALWAYS get explicit user confirmation of the exact recipient address, amount, chain, and token in conversation immediately before calling this tool.",
|
|
779
781
|
inputSchema: {
|
|
780
782
|
type: "object",
|
|
781
783
|
properties: {
|
|
@@ -901,7 +903,7 @@ async function runBatchPay(input) {
|
|
|
901
903
|
(r) => sandboxPay(chain, { to: r.to, amount: r.amount, token: input.token })
|
|
902
904
|
);
|
|
903
905
|
guardsApplied.push("mode=sandbox");
|
|
904
|
-
const reason = resolved.sandboxReason ?? describeSandboxReason2(resolved.apiKey ?? "");
|
|
906
|
+
const reason = resolved.sandboxReason ?? describeSandboxReason2(resolved.apiKey ?? "", resolved.scope);
|
|
905
907
|
return {
|
|
906
908
|
mode: "sandbox",
|
|
907
909
|
status: "sandbox",
|
|
@@ -950,17 +952,19 @@ async function runBatchPay(input) {
|
|
|
950
952
|
throw err;
|
|
951
953
|
}
|
|
952
954
|
}
|
|
953
|
-
function describeSandboxReason2(resolvedKey) {
|
|
955
|
+
function describeSandboxReason2(resolvedKey, scope) {
|
|
954
956
|
const missing = [];
|
|
955
957
|
if (!resolvedKey.startsWith("q402_live_")) missing.push("a live API key (must start with q402_live_)");
|
|
956
958
|
if (!CONFIG.privateKey) missing.push("Q402_PRIVATE_KEY");
|
|
957
959
|
if (!CONFIG.realPaymentsRequested) missing.push("Q402_ENABLE_REAL_PAYMENTS=1");
|
|
958
960
|
if (missing.length === 0) return "Sandbox mode active (no env state change needed).";
|
|
959
|
-
|
|
961
|
+
const tier = scope === "trial" ? "Free Trial" : "Multichain";
|
|
962
|
+
const url = scope === "trial" ? "https://q402.quackai.ai/event" : "https://q402.quackai.ai/payment";
|
|
963
|
+
return "Sandbox mode is active because the following env vars are missing or not yet set: " + missing.join(", ") + `. Get a live ${tier} key at ${url}.`;
|
|
960
964
|
}
|
|
961
965
|
var BATCH_PAY_TOOL = {
|
|
962
966
|
name: "q402_batch_pay",
|
|
963
|
-
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 7
|
|
967
|
+
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 7 batchable chains (avax, bnb, eth, mantle, injective, monad, scroll). 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. After the first batch on a chain, follow-up batches on the same chain are faster and cheaper (Q402 reuses the wallet's setup); q402_clear_delegation resets it if the user ever asks. 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.`,
|
|
964
968
|
inputSchema: {
|
|
965
969
|
type: "object",
|
|
966
970
|
properties: {
|
|
@@ -1271,19 +1275,24 @@ async function runWalletStatus() {
|
|
|
1271
1275
|
}
|
|
1272
1276
|
const url = `${CONFIG.relayBaseUrl.replace(/\/$/, "")}/wallet/delegation-status?address=${address}`;
|
|
1273
1277
|
let body;
|
|
1278
|
+
let res;
|
|
1274
1279
|
try {
|
|
1275
|
-
|
|
1280
|
+
res = await fetch(url);
|
|
1276
1281
|
body = await res.json();
|
|
1277
|
-
if (!res.ok) {
|
|
1278
|
-
return {
|
|
1279
|
-
address,
|
|
1280
|
-
error: typeof body === "object" && body && "error" in body ? String(body.error) : `HTTP ${res.status}`
|
|
1281
|
-
};
|
|
1282
|
-
}
|
|
1283
1282
|
} catch (e) {
|
|
1284
1283
|
return {
|
|
1285
1284
|
address,
|
|
1286
|
-
error:
|
|
1285
|
+
error: "RELAY_UNREACHABLE",
|
|
1286
|
+
hint: `Could not reach Q402 at ${url}: ${e instanceof Error ? e.message : String(e)}`
|
|
1287
|
+
};
|
|
1288
|
+
}
|
|
1289
|
+
if (!res.ok) {
|
|
1290
|
+
const errBody = body;
|
|
1291
|
+
return {
|
|
1292
|
+
address,
|
|
1293
|
+
error: errBody.error ?? `HTTP ${res.status}`,
|
|
1294
|
+
hint: errBody.hint ?? errBody.reason,
|
|
1295
|
+
retryAfterSec: errBody.retryAfterSec
|
|
1287
1296
|
};
|
|
1288
1297
|
}
|
|
1289
1298
|
const parsed = body;
|
|
@@ -1295,7 +1304,7 @@ async function runWalletStatus() {
|
|
|
1295
1304
|
}
|
|
1296
1305
|
var WALLET_STATUS_TOOL = {
|
|
1297
1306
|
name: "q402_wallet_status",
|
|
1298
|
-
description: "Report the EIP-7702 delegation status of your Q402 wallet (the EOA derived from Q402_PRIVATE_KEY) across all 9 Q402-supported chains. Returns per-chain { delegated, impl } and a one-line summary. Read-only \u2014 no signing, no on-chain TX, no quota consumption.
|
|
1307
|
+
description: "Report the EIP-7702 delegation status of your Q402 wallet (the EOA derived from Q402_PRIVATE_KEY) across all 9 Q402-supported chains. Returns per-chain { delegated, impl } and a one-line summary. Read-only \u2014 no signing, no on-chain TX, no quota consumption. Pair with q402_clear_delegation when the user wants to reset a specific chain. Requires Q402_PRIVATE_KEY in env (same as q402_pay).",
|
|
1299
1308
|
inputSchema: {
|
|
1300
1309
|
type: "object",
|
|
1301
1310
|
properties: {},
|
|
@@ -1429,7 +1438,7 @@ async function runClearDelegation(input) {
|
|
|
1429
1438
|
}
|
|
1430
1439
|
var CLEAR_DELEGATION_TOOL = {
|
|
1431
1440
|
name: "q402_clear_delegation",
|
|
1432
|
-
description: "Clear the EIP-7702 delegation on a Q402 chain for the configured wallet.
|
|
1441
|
+
description: "Clear the EIP-7702 delegation on a Q402 chain for the configured wallet. Call this only when the user explicitly asks to reset or clean up their Q402 wallet on a chain \u2014 the next q402_pay will recreate the delegation automatically, so calling clear right before another payment just wastes a TX. Pair with q402_wallet_status first to see which chains actually have an active delegation. Signing happens locally with Q402_PRIVATE_KEY (same key q402_pay uses); Q402 sponsors the on-chain TX so the user pays zero gas.",
|
|
1433
1442
|
inputSchema: {
|
|
1434
1443
|
type: "object",
|
|
1435
1444
|
properties: {
|
|
@@ -1446,7 +1455,7 @@ var CLEAR_DELEGATION_TOOL = {
|
|
|
1446
1455
|
|
|
1447
1456
|
// src/index.ts
|
|
1448
1457
|
var PACKAGE_NAME = "@quackai/q402-mcp";
|
|
1449
|
-
var PACKAGE_VERSION = "0.5.
|
|
1458
|
+
var PACKAGE_VERSION = "0.5.5";
|
|
1450
1459
|
function jsonText(value) {
|
|
1451
1460
|
return { type: "text", text: JSON.stringify(value, null, 2) };
|
|
1452
1461
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quackai/q402-mcp",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.5",
|
|
4
4
|
"description": "MCP server for Q402 — gasless USDC, USDT, and RLUSD payments across 9 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": [
|