z-zero-mcp-server 1.1.1 → 1.1.2
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/dist/index.js +89 -6
- package/dist/playwright_bridge.d.ts +2 -2
- package/dist/playwright_bridge.js +115 -4
- package/dist/types.d.ts +13 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/wdk_backend.js +1 -1
- package/package.json +1 -1
- package/dist/api_backend.d.ts +0 -9
- package/dist/api_backend.js +0 -159
package/dist/index.js
CHANGED
|
@@ -270,7 +270,41 @@ server.tool("request_payment_token", "Request a temporary payment token for a sp
|
|
|
270
270
|
};
|
|
271
271
|
});
|
|
272
272
|
// ============================================================
|
|
273
|
-
// TOOL
|
|
273
|
+
// TOOL 4a: Get Merchant Hints (Agent Knowledge Base)
|
|
274
|
+
// Agent MUST call this before execute_payment if unsure about a checkout page.
|
|
275
|
+
// Returns domain-specific selectors and pre-steps from Z-ZERO cloud DB.
|
|
276
|
+
// ============================================================
|
|
277
|
+
server.tool("get_merchant_hints", "Fetch checkout hints for a specific domain from the Z-ZERO Knowledge Base. Call this BEFORE execute_payment when the checkout page is unusual, multi-step, or if a previous execute_payment attempt failed. Returns pre_steps (what to click first) and CSS selectors for card fields.", {
|
|
278
|
+
domain: zod_1.z
|
|
279
|
+
.string()
|
|
280
|
+
.describe("The main domain of the checkout page, e.g. 'amazon.com' or 'shopify.com'. Strip 'www.' prefix."),
|
|
281
|
+
}, async ({ domain }) => {
|
|
282
|
+
const ZZERO_API = process.env.Z_ZERO_API_BASE || "https://www.clawcard.store";
|
|
283
|
+
const INTERNAL_SECRET = process.env.Z_ZERO_INTERNAL_SECRET || "";
|
|
284
|
+
try {
|
|
285
|
+
const resp = await fetch(`${ZZERO_API}/api/checkout-hints?domain=${encodeURIComponent(domain)}`, {
|
|
286
|
+
headers: {
|
|
287
|
+
"x-internal-secret": INTERNAL_SECRET,
|
|
288
|
+
"x-mcp-version": version_js_2.CURRENT_MCP_VERSION,
|
|
289
|
+
},
|
|
290
|
+
});
|
|
291
|
+
const data = await resp.json();
|
|
292
|
+
return {
|
|
293
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
catch (err) {
|
|
297
|
+
return {
|
|
298
|
+
content: [{ type: "text", text: JSON.stringify({
|
|
299
|
+
found: false,
|
|
300
|
+
hints: null,
|
|
301
|
+
message: `Could not reach hints API: ${err?.message || "unknown error"}. Proceed with default Playwright selectors.`,
|
|
302
|
+
}, null, 2) }],
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
});
|
|
306
|
+
// ============================================================
|
|
307
|
+
// TOOL 4b: Execute payment (The "Invisible Hand")
|
|
274
308
|
// ============================================================
|
|
275
309
|
server.tool("execute_payment", "Execute a payment using a temporary token. This tool will securely fill the checkout form on the target website. You will NEVER see the real card number - it is handled securely in the background.", {
|
|
276
310
|
token: zod_1.z
|
|
@@ -284,7 +318,20 @@ server.tool("execute_payment", "Execute a payment using a temporary token. This
|
|
|
284
318
|
.number()
|
|
285
319
|
.optional()
|
|
286
320
|
.describe("The actual final amount on the checkout page. If different from token amount, system will auto-refund the difference."),
|
|
287
|
-
|
|
321
|
+
hints: zod_1.z
|
|
322
|
+
.object({
|
|
323
|
+
pre_steps: zod_1.z.array(zod_1.z.string()).optional(),
|
|
324
|
+
card_selector: zod_1.z.string().optional(),
|
|
325
|
+
exp_selector: zod_1.z.string().optional(),
|
|
326
|
+
exp_month_selector: zod_1.z.string().optional(),
|
|
327
|
+
exp_year_selector: zod_1.z.string().optional(),
|
|
328
|
+
cvv_selector: zod_1.z.string().optional(),
|
|
329
|
+
name_selector: zod_1.z.string().optional(),
|
|
330
|
+
submit_selector: zod_1.z.string().optional(),
|
|
331
|
+
})
|
|
332
|
+
.optional()
|
|
333
|
+
.describe("Optional hints from get_merchant_hints — selectors and pre-steps to guide Playwright. Use when default selectors fail or for complex multi-step checkouts."),
|
|
334
|
+
}, async ({ token, checkout_url, actual_amount, hints }) => {
|
|
288
335
|
// Step 1: Resolve token → card data (RAM only)
|
|
289
336
|
const cardData = await resolveTokenRemote(token);
|
|
290
337
|
if (cardData?.error === "AUTH_REQUIRED") {
|
|
@@ -334,8 +381,8 @@ server.tool("execute_payment", "Execute a payment using a temporary token. This
|
|
|
334
381
|
};
|
|
335
382
|
}
|
|
336
383
|
}
|
|
337
|
-
// Step 2: Use Playwright to inject card into checkout form
|
|
338
|
-
const result = await (0, playwright_bridge_js_1.fillCheckoutForm)(checkout_url, cardData);
|
|
384
|
+
// Step 2: Use Playwright to inject card into checkout form (with optional agent hints)
|
|
385
|
+
const result = await (0, playwright_bridge_js_1.fillCheckoutForm)(checkout_url, cardData, undefined, hints);
|
|
339
386
|
// Step 3: Burn the token ONLY if payment succeeded
|
|
340
387
|
// If merchant declines → keep token ACTIVE so webhook decline flow can refund correctly
|
|
341
388
|
if (result.success) {
|
|
@@ -343,7 +390,7 @@ server.tool("execute_payment", "Execute a payment using a temporary token. This
|
|
|
343
390
|
}
|
|
344
391
|
else {
|
|
345
392
|
// Payment failed — do NOT burn token
|
|
346
|
-
//
|
|
393
|
+
// Partner card issuer will fire a 'card.authorization.declined' webhook → refund handled there
|
|
347
394
|
return {
|
|
348
395
|
content: [
|
|
349
396
|
{
|
|
@@ -519,7 +566,11 @@ server.tool("show_api_key_status", "Show whether a Passport Key is currently con
|
|
|
519
566
|
server.tool("auto_pay_checkout", "[Phase 2] Autonomous Smart Routing checkout tool. Provide a checkout URL and this tool will:\n" +
|
|
520
567
|
"1. Scan the page to detect if it supports Web3 (Crypto) payments via window.ethereum or EIP-681 links.\n" +
|
|
521
568
|
"2. SCENARIO A (Web3): If detected, automatically send USDT on-chain via WDK (gas ~$0.001). No Visa card needed.\n" +
|
|
522
|
-
"3. SCENARIO B (Fiat): If no Web3 detected, scan DOM for total price, issue a JIT Visa card for exact amount, auto-fill form
|
|
569
|
+
"3. SCENARIO B (Fiat): If no Web3 detected, scan DOM for total price, issue a JIT Visa card for exact amount, auto-fill form.\n" +
|
|
570
|
+
"WARNING: For PHYSICAL GOODS (Shopify, multi-step checkout), this tool may return PRICE_NOT_FOUND because the final price " +
|
|
571
|
+
"(including shipping) only appears after filling th shipping info. In that case, use browser tools to navigate manually, " +
|
|
572
|
+
"call get_merchant_hints to get platform-specific card selectors, fill shipping info first, then call " +
|
|
573
|
+
"request_payment_token + execute_payment once card fields are visible and the final total is confirmed.", {
|
|
523
574
|
checkout_url: zod_1.z
|
|
524
575
|
.string()
|
|
525
576
|
.url()
|
|
@@ -715,6 +766,38 @@ It auto-detects Web3 checkout (pay on-chain with USDT) vs Fiat checkout (JIT Vis
|
|
|
715
766
|
- Web3 route: sends USDT directly on-chain to merchant wallet
|
|
716
767
|
- Fiat route: issues JIT card + fills checkout form automatically
|
|
717
768
|
Call \`auto_pay_checkout\` with just \`checkout_url\` and \`card_alias\`. The tool handles the rest.
|
|
769
|
+
|
|
770
|
+
## 🛒 Physical Goods / Multi-Step Checkout
|
|
771
|
+
|
|
772
|
+
For physical goods (clothing, shoes, collectibles, etc.) the final price includes shipping
|
|
773
|
+
and only appears AFTER filling the shipping form. \`auto_pay_checkout\` may return PRICE_NOT_FOUND.
|
|
774
|
+
|
|
775
|
+
### ⛔ Card Issuance Gate — wait for BOTH conditions before calling request_payment_token:
|
|
776
|
+
1. Shipping/personal info has been submitted successfully on this or a previous page
|
|
777
|
+
2. Card input fields (or payment iframe) are visible on the current page
|
|
778
|
+
|
|
779
|
+
If EITHER is false → DO NOT call \`request_payment_token\`. Keep navigating first.
|
|
780
|
+
|
|
781
|
+
### Platform Detection Signals
|
|
782
|
+
| Platform | Signals |
|
|
783
|
+
|----------|---------|
|
|
784
|
+
| Shopify | URL contains /checkouts/, "Powered by Shopify" in footer, cdn.shopify.com scripts |
|
|
785
|
+
| Etsy | URL contains etsy.com/checkout |
|
|
786
|
+
| WooCommerce | /checkout/ URL, woocommerce in body class |
|
|
787
|
+
|
|
788
|
+
### Recommended Flow for Physical Goods:
|
|
789
|
+
1. Navigate to product page using browser tools
|
|
790
|
+
2. Select variant (color, size, etc.) → Add to cart → Proceed to checkout
|
|
791
|
+
3. Fill shipping info (ask human via \`request_human_approval\` if not already known)
|
|
792
|
+
4. Wait for payment/card fields section to appear
|
|
793
|
+
5. Call \`get_merchant_hints\` with checkout domain (or \`_platform_shopify\` if Shopify detected)
|
|
794
|
+
6. Read the FINAL total (after shipping + tax — hover over/scroll to order summary)
|
|
795
|
+
7. Call \`request_payment_token\` with exact final amount
|
|
796
|
+
8. Call \`execute_payment\` with token + checkout URL + hints
|
|
797
|
+
|
|
798
|
+
### Known Limitations:
|
|
799
|
+
- Some sites use Cloudflare bot detection (e.g. TeePublic) → \`execute_payment\` may be blocked → inform user
|
|
800
|
+
- Card fields in iframes → \`execute_payment\` handles this automatically via \`iframe_selector\` in hints
|
|
718
801
|
`;
|
|
719
802
|
return {
|
|
720
803
|
contents: [
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type { CardData, PaymentResult } from "./types.js";
|
|
1
|
+
import type { CardData, PaymentResult, CheckoutHints } from "./types.js";
|
|
2
2
|
/**
|
|
3
3
|
* Detects and fills credit card form fields on a checkout page.
|
|
4
4
|
* Card data exists ONLY in RAM and is wiped after injection.
|
|
5
5
|
* Hard timeout of 60s prevents merchant page from hanging indefinitely.
|
|
6
6
|
*/
|
|
7
|
-
export declare function fillCheckoutForm(checkoutUrl: string, cardData: CardData, existingPage?: import("playwright").Page): Promise<PaymentResult>;
|
|
7
|
+
export declare function fillCheckoutForm(checkoutUrl: string, cardData: CardData, existingPage?: import("playwright").Page, hints?: CheckoutHints): Promise<PaymentResult>;
|
|
@@ -11,7 +11,7 @@ const CHECKOUT_HARD_TIMEOUT_MS = 60_000; // 60s absolute cap — prevents slow-l
|
|
|
11
11
|
* Card data exists ONLY in RAM and is wiped after injection.
|
|
12
12
|
* Hard timeout of 60s prevents merchant page from hanging indefinitely.
|
|
13
13
|
*/
|
|
14
|
-
async function fillCheckoutForm(checkoutUrl, cardData, existingPage) {
|
|
14
|
+
async function fillCheckoutForm(checkoutUrl, cardData, existingPage, hints) {
|
|
15
15
|
let browser = null;
|
|
16
16
|
let page;
|
|
17
17
|
if (existingPage) {
|
|
@@ -23,7 +23,7 @@ async function fillCheckoutForm(checkoutUrl, cardData, existingPage) {
|
|
|
23
23
|
page = await context.newPage();
|
|
24
24
|
}
|
|
25
25
|
try {
|
|
26
|
-
return await (0, with_timeout_js_1.withTimeout)(_fillCheckoutFormInner(page, checkoutUrl, cardData), CHECKOUT_HARD_TIMEOUT_MS, 'fillCheckoutForm', async () => {
|
|
26
|
+
return await (0, with_timeout_js_1.withTimeout)(_fillCheckoutFormInner(page, checkoutUrl, cardData, hints), CHECKOUT_HARD_TIMEOUT_MS, 'fillCheckoutForm', async () => {
|
|
27
27
|
console.error('[PLAYWRIGHT] ⚠️ Hard timeout hit — force-closing browser');
|
|
28
28
|
if (browser)
|
|
29
29
|
await browser.close().catch(() => { });
|
|
@@ -97,7 +97,7 @@ async function tryFillField(page, selectors, value) {
|
|
|
97
97
|
return false;
|
|
98
98
|
}
|
|
99
99
|
/** Internal implementation — called by fillCheckoutForm inside a timeout wrapper */
|
|
100
|
-
async function _fillCheckoutFormInner(page, checkoutUrl, cardData) {
|
|
100
|
+
async function _fillCheckoutFormInner(page, checkoutUrl, cardData, hints) {
|
|
101
101
|
try {
|
|
102
102
|
// ✅ FIX: Skip navigation if page already loaded (Single Browser reuse from auto_pay_checkout)
|
|
103
103
|
// Prevents double-navigate which would reload page and lose cart/session state.
|
|
@@ -107,6 +107,118 @@ async function _fillCheckoutFormInner(page, checkoutUrl, cardData) {
|
|
|
107
107
|
await page.goto(checkoutUrl, { waitUntil: "domcontentloaded", timeout: 20_000 });
|
|
108
108
|
}
|
|
109
109
|
// ============================================================
|
|
110
|
+
// STRATEGY 0: Pre-steps — click to open/reveal the payment form
|
|
111
|
+
// Agent provides selectors to click BEFORE filling (e.g. accordion, tab, modal)
|
|
112
|
+
// ============================================================
|
|
113
|
+
if (hints?.pre_steps && hints.pre_steps.length > 0) {
|
|
114
|
+
for (const selector of hints.pre_steps) {
|
|
115
|
+
try {
|
|
116
|
+
const el = await page.$(selector);
|
|
117
|
+
if (el && await el.isVisible()) {
|
|
118
|
+
await el.click();
|
|
119
|
+
await page.waitForTimeout(500); // brief pause for animation
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
catch { /* non-fatal — continue */ }
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
let filledFields = 0;
|
|
126
|
+
// ============================================================
|
|
127
|
+
// STRATEGY 0.5: Agent-provided selectors (from get_merchant_hints)
|
|
128
|
+
// Tried first — agent already read the DOM and knows exact locations.
|
|
129
|
+
// Supports iframe_selector: if provided, card fields are searched INSIDE
|
|
130
|
+
// matching iframes (e.g. Shopify "card-fields-iframe") rather than main page.
|
|
131
|
+
// Name on card is always searched on the main page (outside iframe).
|
|
132
|
+
// Falls through to Strategy 1 if nothing matched.
|
|
133
|
+
// ============================================================
|
|
134
|
+
if (hints && (hints.card_selector || hints.exp_selector || hints.cvv_selector)) {
|
|
135
|
+
const hintFillResults = [];
|
|
136
|
+
let cardContexts = [page];
|
|
137
|
+
if (hints.iframe_selector) {
|
|
138
|
+
const matchingFrames = page.frames().filter((f) => f.name().includes(hints.iframe_selector));
|
|
139
|
+
if (matchingFrames.length > 0)
|
|
140
|
+
cardContexts = matchingFrames;
|
|
141
|
+
}
|
|
142
|
+
// Card number
|
|
143
|
+
if (hints.card_selector) {
|
|
144
|
+
for (const ctx of cardContexts) {
|
|
145
|
+
const el = await ctx.$(hints.card_selector);
|
|
146
|
+
if (el) {
|
|
147
|
+
hintFillResults.push(await smartFill(el, cardData.number));
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
// Expiry combined (with space-padded slash for Shopify: "MM / YY")
|
|
153
|
+
if (hints.exp_selector) {
|
|
154
|
+
const expiryVal = `${cardData.exp_month} / ${cardData.exp_year.slice(-2)}`;
|
|
155
|
+
for (const ctx of cardContexts) {
|
|
156
|
+
const el = await ctx.$(hints.exp_selector);
|
|
157
|
+
if (el) {
|
|
158
|
+
hintFillResults.push(await smartFill(el, expiryVal));
|
|
159
|
+
break;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
// Expiry split month/year
|
|
164
|
+
if (!hints.exp_selector && hints.exp_month_selector) {
|
|
165
|
+
for (const ctx of cardContexts) {
|
|
166
|
+
const elM = await ctx.$(hints.exp_month_selector);
|
|
167
|
+
if (elM) {
|
|
168
|
+
hintFillResults.push(await smartFill(elM, cardData.exp_month));
|
|
169
|
+
break;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
if (hints.exp_year_selector) {
|
|
173
|
+
for (const ctx of cardContexts) {
|
|
174
|
+
const elY = await ctx.$(hints.exp_year_selector);
|
|
175
|
+
if (elY) {
|
|
176
|
+
hintFillResults.push(await smartFill(elY, cardData.exp_year));
|
|
177
|
+
break;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
// CVV
|
|
183
|
+
if (hints.cvv_selector) {
|
|
184
|
+
for (const ctx of cardContexts) {
|
|
185
|
+
const el = await ctx.$(hints.cvv_selector);
|
|
186
|
+
if (el) {
|
|
187
|
+
hintFillResults.push(await smartFill(el, cardData.cvv));
|
|
188
|
+
break;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
// Name on card — ALWAYS on main page (outside iframe for Shopify/most platforms)
|
|
193
|
+
if (hints.name_selector) {
|
|
194
|
+
const el = await page.$(hints.name_selector);
|
|
195
|
+
if (el) {
|
|
196
|
+
hintFillResults.push(await smartFill(el, cardData.name));
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
const hintSuccesses = hintFillResults.filter(Boolean).length;
|
|
200
|
+
if (hintSuccesses > 0) {
|
|
201
|
+
filledFields += hintSuccesses;
|
|
202
|
+
if (hints.submit_selector) {
|
|
203
|
+
try {
|
|
204
|
+
const btn = await page.$(hints.submit_selector);
|
|
205
|
+
if (btn && await btn.isVisible()) {
|
|
206
|
+
await btn.click();
|
|
207
|
+
await page.waitForTimeout(3000);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
catch { /* non-fatal */ }
|
|
211
|
+
}
|
|
212
|
+
const receiptId = `rcpt_${Date.now().toString(36)}`;
|
|
213
|
+
return {
|
|
214
|
+
success: true,
|
|
215
|
+
message: `Payment form filled via agent hints${hints.iframe_selector ? ' (iframe mode)' : ''}. ${filledFields} fields injected.`,
|
|
216
|
+
receipt_id: receiptId,
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
// hints provided but nothing matched → fall through to Strategy 1
|
|
220
|
+
}
|
|
221
|
+
// ============================================================
|
|
110
222
|
// STRATEGY 1: Standard HTML form fields
|
|
111
223
|
// Covers: plain forms, Shopify, WooCommerce, custom checkouts
|
|
112
224
|
// Priority: autocomplete (W3C) → name → platform-specific → placeholder → aria-label
|
|
@@ -179,7 +291,6 @@ async function _fillCheckoutFormInner(page, checkoutUrl, cardData) {
|
|
|
179
291
|
'input[aria-label*="name on card" i]',
|
|
180
292
|
],
|
|
181
293
|
};
|
|
182
|
-
let filledFields = 0;
|
|
183
294
|
// Card Number
|
|
184
295
|
if (await tryFillField(page, S.number, cardData.number))
|
|
185
296
|
filledFields++;
|
package/dist/types.d.ts
CHANGED
|
@@ -37,3 +37,16 @@ export interface PaymentResult {
|
|
|
37
37
|
receipt_id?: string;
|
|
38
38
|
amount?: number;
|
|
39
39
|
}
|
|
40
|
+
export interface CheckoutHints {
|
|
41
|
+
pre_steps?: string[];
|
|
42
|
+
iframe_selector?: string;
|
|
43
|
+
card_selector?: string;
|
|
44
|
+
exp_selector?: string;
|
|
45
|
+
exp_month_selector?: string;
|
|
46
|
+
exp_year_selector?: string;
|
|
47
|
+
cvv_selector?: string;
|
|
48
|
+
name_selector?: string;
|
|
49
|
+
submit_selector?: string;
|
|
50
|
+
payment_type?: string;
|
|
51
|
+
notes?: string;
|
|
52
|
+
}
|
package/dist/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const CURRENT_MCP_VERSION = "1.1.
|
|
1
|
+
export declare const CURRENT_MCP_VERSION = "1.1.2";
|
package/dist/version.js
CHANGED
|
@@ -3,4 +3,4 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.CURRENT_MCP_VERSION = void 0;
|
|
4
4
|
// Single source of truth for MCP version
|
|
5
5
|
// Imported by both index.ts and wdk_backend.ts to avoid circular dependency
|
|
6
|
-
exports.CURRENT_MCP_VERSION = "1.1.
|
|
6
|
+
exports.CURRENT_MCP_VERSION = "1.1.2";
|
package/dist/wdk_backend.js
CHANGED
|
@@ -145,7 +145,7 @@ async function getDepositAddressesRemote() {
|
|
|
145
145
|
// ──────────────────────────────────────────────────────────────────────────────
|
|
146
146
|
async function issueTokenRemote(cardAlias, amount, merchant) {
|
|
147
147
|
// WDK Flow (reversed from custodial):
|
|
148
|
-
// 1. Create
|
|
148
|
+
// 1. Create Partner card first (reservation)
|
|
149
149
|
// 2. Send USDT on-chain from WDK wallet → system wallet
|
|
150
150
|
// 3. Dashboard verifies on-chain tx, activates token
|
|
151
151
|
// This is safe: if on-chain tx fails, Dashboard auto-cancels the card reservation.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "z-zero-mcp-server",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.2",
|
|
4
4
|
"description": "Z-ZERO MCP Server — Secure JIT Virtual Card Payment tools for AI Agents (Claude, Cursor, AutoGPT) — Real Single-Use Visa Cards",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": "dist/index.js",
|
package/dist/api_backend.d.ts
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import type { CardData } from "./types.js";
|
|
2
|
-
export declare function listCardsRemote(): Promise<any>;
|
|
3
|
-
export declare function getBalanceRemote(cardAlias: string): Promise<any>;
|
|
4
|
-
export declare function getDepositAddressesRemote(): Promise<any>;
|
|
5
|
-
export declare function issueTokenRemote(cardAlias: string, amount: number, merchant: string): Promise<any | null>;
|
|
6
|
-
export declare function resolveTokenRemote(token: string): Promise<CardData | null>;
|
|
7
|
-
export declare function burnTokenRemote(token: string, receipt_id?: string): Promise<boolean>;
|
|
8
|
-
export declare function cancelTokenRemote(token: string): Promise<any>;
|
|
9
|
-
export declare function refundUnderspendRemote(token: string, actualSpent: number): Promise<void>;
|
package/dist/api_backend.js
DELETED
|
@@ -1,159 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
// Z-ZERO Unified API Backend
|
|
3
|
-
// Connects to the Dashboard Next.js API for all card and token operations.
|
|
4
|
-
// This preserves the "Issuer Abstraction Layer" - the bot never sees direct DB or Banking keys.
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.listCardsRemote = listCardsRemote;
|
|
7
|
-
exports.getBalanceRemote = getBalanceRemote;
|
|
8
|
-
exports.getDepositAddressesRemote = getDepositAddressesRemote;
|
|
9
|
-
exports.issueTokenRemote = issueTokenRemote;
|
|
10
|
-
exports.resolveTokenRemote = resolveTokenRemote;
|
|
11
|
-
exports.burnTokenRemote = burnTokenRemote;
|
|
12
|
-
exports.cancelTokenRemote = cancelTokenRemote;
|
|
13
|
-
exports.refundUnderspendRemote = refundUnderspendRemote;
|
|
14
|
-
const key_store_js_1 = require("./lib/key-store.js");
|
|
15
|
-
const API_BASE_URL = process.env.Z_ZERO_API_BASE_URL || "https://www.clawcard.store";
|
|
16
|
-
const INTERNAL_SECRET = process.env.Z_ZERO_INTERNAL_SECRET || "";
|
|
17
|
-
if (!(0, key_store_js_1.hasPassportKey)()) {
|
|
18
|
-
console.error("❌ ERROR: Z_ZERO_API_KEY (Passport Key) is missing!");
|
|
19
|
-
console.error("🔐 Get your key: https://www.clawcard.store/dashboard/agents");
|
|
20
|
-
console.error("🛠️ Or call the set_api_key MCP tool to set it without restarting.");
|
|
21
|
-
}
|
|
22
|
-
async function apiRequest(endpoint, method = 'GET', body = null) {
|
|
23
|
-
const PASSPORT_KEY = (0, key_store_js_1.getPassportKey)(); // ✅ Hot-swap: read key dynamically each request
|
|
24
|
-
if (!PASSPORT_KEY) {
|
|
25
|
-
return { error: "AUTH_REQUIRED", message: "Z_ZERO_API_KEY is missing. Call set_api_key tool or set it in MCP config and restart." };
|
|
26
|
-
}
|
|
27
|
-
const url = `${API_BASE_URL.replace(/\/$/, '')}${endpoint}`;
|
|
28
|
-
try {
|
|
29
|
-
const res = await fetch(url, {
|
|
30
|
-
method,
|
|
31
|
-
headers: {
|
|
32
|
-
"Authorization": `Bearer ${PASSPORT_KEY}`,
|
|
33
|
-
"Content-Type": "application/json",
|
|
34
|
-
},
|
|
35
|
-
body: body ? JSON.stringify(body) : null,
|
|
36
|
-
});
|
|
37
|
-
if (!res.ok) {
|
|
38
|
-
const err = await res.json().catch(() => ({ error: res.statusText }));
|
|
39
|
-
console.error(`[API ERROR] ${endpoint}:`, err.error);
|
|
40
|
-
return { error: "API_ERROR", message: err.error || res.statusText };
|
|
41
|
-
}
|
|
42
|
-
return await res.json();
|
|
43
|
-
}
|
|
44
|
-
catch (err) {
|
|
45
|
-
console.error(`[NETWORK ERROR] ${endpoint}:`, err.message);
|
|
46
|
-
return { error: "NETWORK_ERROR", message: err.message };
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
// Internal API calls that require INTERNAL_SECRET (resolve PAN, burn token)
|
|
50
|
-
async function internalApiRequest(endpoint, method, body) {
|
|
51
|
-
if (!INTERNAL_SECRET) {
|
|
52
|
-
console.error("[MCP] Z_ZERO_INTERNAL_SECRET is missing — cannot call secure endpoints");
|
|
53
|
-
return { error: "CONFIG_ERROR", message: "INTERNAL_SECRET not configured" };
|
|
54
|
-
}
|
|
55
|
-
const url = `${API_BASE_URL.replace(/\/$/, '')}${endpoint}`;
|
|
56
|
-
try {
|
|
57
|
-
const res = await fetch(url, {
|
|
58
|
-
method,
|
|
59
|
-
headers: {
|
|
60
|
-
"x-internal-secret": INTERNAL_SECRET,
|
|
61
|
-
"Content-Type": "application/json",
|
|
62
|
-
},
|
|
63
|
-
body: body ? JSON.stringify(body) : null,
|
|
64
|
-
});
|
|
65
|
-
if (!res.ok) {
|
|
66
|
-
const err = await res.json().catch(() => ({ error: res.statusText }));
|
|
67
|
-
console.error(`[INTERNAL API ERROR] ${endpoint}:`, err.error);
|
|
68
|
-
return { error: "API_ERROR", message: err.error || res.statusText };
|
|
69
|
-
}
|
|
70
|
-
return await res.json();
|
|
71
|
-
}
|
|
72
|
-
catch (err) {
|
|
73
|
-
console.error(`[NETWORK ERROR] ${endpoint}:`, err.message);
|
|
74
|
-
return { error: "NETWORK_ERROR", message: err.message };
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
async function listCardsRemote() {
|
|
78
|
-
return await apiRequest('/api/tokens/cards', 'GET');
|
|
79
|
-
}
|
|
80
|
-
async function getBalanceRemote(cardAlias) {
|
|
81
|
-
const data = await listCardsRemote();
|
|
82
|
-
if (data?.error)
|
|
83
|
-
return data;
|
|
84
|
-
const cards = data?.cards || [];
|
|
85
|
-
const card = cards.find((c) => c.alias === cardAlias);
|
|
86
|
-
if (!card)
|
|
87
|
-
return null;
|
|
88
|
-
return {
|
|
89
|
-
wallet_balance: card.balance,
|
|
90
|
-
currency: card.currency,
|
|
91
|
-
note: "This is the total wallet balance (human-level). Cards have 'limits' set at creation, not 'balances'. Use list_cards to see active token limits."
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
|
-
async function getDepositAddressesRemote() {
|
|
95
|
-
return await apiRequest('/api/tokens/cards', 'GET');
|
|
96
|
-
}
|
|
97
|
-
async function issueTokenRemote(cardAlias, amount, merchant) {
|
|
98
|
-
const data = await apiRequest('/api/tokens/issue', 'POST', {
|
|
99
|
-
card_alias: cardAlias,
|
|
100
|
-
amount,
|
|
101
|
-
merchant,
|
|
102
|
-
device_fingerprint: `mcp-host-${process.platform}-${process.arch}`,
|
|
103
|
-
network_id: process.env.NETWORK_ID || "unknown-local-net",
|
|
104
|
-
session_id: `sid-${Math.random().toString(36).substring(7)}`
|
|
105
|
-
});
|
|
106
|
-
if (!data)
|
|
107
|
-
return null;
|
|
108
|
-
// Forward API errors (402 insufficient, 429 max cards, etc.)
|
|
109
|
-
if (data.error)
|
|
110
|
-
return data;
|
|
111
|
-
// Adapt Dashboard API response to MCP expected format
|
|
112
|
-
return {
|
|
113
|
-
token: data.token,
|
|
114
|
-
card_alias: cardAlias,
|
|
115
|
-
amount: amount,
|
|
116
|
-
merchant: merchant,
|
|
117
|
-
created_at: Date.now(),
|
|
118
|
-
ttl_seconds: 1800,
|
|
119
|
-
used: false
|
|
120
|
-
};
|
|
121
|
-
}
|
|
122
|
-
async function resolveTokenRemote(token) {
|
|
123
|
-
// Uses INTERNAL_SECRET — this endpoint returns real PAN/CVV
|
|
124
|
-
const data = await internalApiRequest('/api/tokens/resolve', 'POST', { token });
|
|
125
|
-
if (!data || data.error)
|
|
126
|
-
return data;
|
|
127
|
-
return {
|
|
128
|
-
number: data.number,
|
|
129
|
-
exp_month: data.exp?.split('/')[0] || "12",
|
|
130
|
-
exp_year: "20" + (data.exp?.split('/')[1] || "30"),
|
|
131
|
-
cvv: data.cvv || "123",
|
|
132
|
-
name: data.name || "Z-ZERO AI AGENT",
|
|
133
|
-
authorized_amount: data.authorized_amount ? Number(data.authorized_amount) : undefined,
|
|
134
|
-
};
|
|
135
|
-
}
|
|
136
|
-
async function burnTokenRemote(token, receipt_id) {
|
|
137
|
-
// Uses INTERNAL_SECRET — burn endpoint requires it
|
|
138
|
-
const data = await internalApiRequest('/api/tokens/burn', 'POST', {
|
|
139
|
-
token,
|
|
140
|
-
receipt_id,
|
|
141
|
-
success: true
|
|
142
|
-
});
|
|
143
|
-
return !!data && !data.error;
|
|
144
|
-
}
|
|
145
|
-
async function cancelTokenRemote(token) {
|
|
146
|
-
const data = await apiRequest('/api/tokens/cancel', 'POST', { token });
|
|
147
|
-
if (data?.error)
|
|
148
|
-
return data;
|
|
149
|
-
return {
|
|
150
|
-
success: !!data,
|
|
151
|
-
refunded_amount: data?.refunded_amount || 0
|
|
152
|
-
};
|
|
153
|
-
}
|
|
154
|
-
async function refundUnderspendRemote(token, actualSpent) {
|
|
155
|
-
// Underspend is a complex feature that usually happens at the bank level,
|
|
156
|
-
// but for JIT tokens, the burn tool in the dashboard could handle this in the future.
|
|
157
|
-
// Currently we burn the full authorized amount.
|
|
158
|
-
console.log(`[MCP] Burned token ${token} after spending $${actualSpent}`);
|
|
159
|
-
}
|