@agentwonderland/mcp 0.1.16 → 0.1.18

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.
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Initiate card setup — creates a Stripe Checkout session and returns
3
+ * QR code + short URL for the user to scan.
4
+ */
5
+ export declare function initiateCardSetup(): Promise<{
6
+ qr: string;
7
+ url: string;
8
+ token: string;
9
+ }>;
10
+ /**
11
+ * Format the card setup prompt as multiple content blocks.
12
+ * Block 1: QR code (may be collapsed in some clients)
13
+ * Block 2: URL + instructions (short, always visible)
14
+ */
15
+ export declare function formatCardSetupBlocks(qr: string, url: string): string[];
16
+ /**
17
+ * Poll for card setup completion. Returns the card details or null on timeout.
18
+ */
19
+ export declare function pollCardSetup(token: string, timeoutMs?: number): Promise<{
20
+ last4: string;
21
+ brand: string;
22
+ consumerToken: string;
23
+ } | null>;
@@ -0,0 +1,68 @@
1
+ import QRCode from "qrcode";
2
+ import { apiPost, apiGet } from "./api-client.js";
3
+ import { getApiUrl, setCardConfig } from "./config.js";
4
+ /**
5
+ * Initiate card setup — creates a Stripe Checkout session and returns
6
+ * QR code + short URL for the user to scan.
7
+ */
8
+ export async function initiateCardSetup() {
9
+ const { url: _checkoutUrl, token } = await apiPost("/card/setup", {});
10
+ // Short redirect URL keeps the QR code small and scannable
11
+ const apiUrl = getApiUrl();
12
+ const shortUrl = `${apiUrl}/card/link/${token}`;
13
+ const qr = await QRCode.toString(shortUrl, {
14
+ type: "utf8",
15
+ errorCorrectionLevel: "L",
16
+ margin: 2,
17
+ });
18
+ return { qr, url: shortUrl, token };
19
+ }
20
+ /**
21
+ * Format the card setup prompt as multiple content blocks.
22
+ * Block 1: QR code (may be collapsed in some clients)
23
+ * Block 2: URL + instructions (short, always visible)
24
+ */
25
+ export function formatCardSetupBlocks(qr, url) {
26
+ return [
27
+ qr.trim(),
28
+ [
29
+ `IMPORTANT: You MUST present this link to the user so they can connect a payment card:`,
30
+ "",
31
+ url,
32
+ "",
33
+ `Tell the user: "Scan the QR code above or open this link to connect your card. Let me know when you're done."`,
34
+ `After they confirm, call wallet_setup({ action: "add-card" }) to complete setup.`,
35
+ `Crypto wallets (Base/Tempo USDC) are also available via wallet_setup({ action: "create" }).`,
36
+ ].join("\n"),
37
+ ];
38
+ }
39
+ /**
40
+ * Poll for card setup completion. Returns the card details or null on timeout.
41
+ */
42
+ export async function pollCardSetup(token, timeoutMs = 120_000) {
43
+ const deadline = Date.now() + timeoutMs;
44
+ while (Date.now() < deadline) {
45
+ await new Promise((r) => setTimeout(r, 3000));
46
+ try {
47
+ const status = await apiGet(`/card/status?token=${token}`);
48
+ if (status.status === "complete" && status.consumer_token) {
49
+ const card = {
50
+ last4: status.card_last4 ?? "????",
51
+ brand: status.card_brand ?? "card",
52
+ consumerToken: status.consumer_token,
53
+ };
54
+ // Persist to config
55
+ setCardConfig({
56
+ consumerToken: card.consumerToken,
57
+ last4: card.last4,
58
+ brand: card.brand,
59
+ });
60
+ return card;
61
+ }
62
+ }
63
+ catch {
64
+ // Keep polling
65
+ }
66
+ }
67
+ return null;
68
+ }
package/dist/index.js CHANGED
@@ -30,17 +30,17 @@ export async function startMcpServer() {
30
30
  "The MCP auto-uploads to cloud storage and replaces with a URL. Never base64-encode.",
31
31
  "",
32
32
  "WORKFLOW:",
33
- "1. wallet_status() check payment is configured (Tempo USDC, Base USDC, or Card)",
34
- "2. search_agents() or solve() find agents for the task",
35
- "3. get_agent() check required input fields before running",
36
- "4. run_agent() or solve() with ALL required fields",
33
+ "1. search_agents() or solve() find agents for the task",
34
+ "2. get_agent() check required input fields before running",
35
+ "3. run_agent() or solve() with ALL required fields",
36
+ "4. If no payment method is set up, run_agent returns a QR code to connect a credit card — present it to the user",
37
37
  "5. Ask user to rate or tip after a successful run",
38
38
  "",
39
39
  "PAYMENT:",
40
- "- Automatic via configured wallet. User does nothing.",
41
- "- Supports Tempo USDC, Base USDC, and Stripe Card.",
42
- "- Input validation runs before payment invalid input is never charged.",
43
- "- Failed agent executions trigger automatic refunds to the consumer's wallet.",
40
+ "- Credit/debit card is the easiest way to pay — just scan a QR code to connect, no funding needed.",
41
+ "- Crypto wallets (Tempo USDC, Base USDC) are also supported for advanced users.",
42
+ "- Payment is automatic once configured. Users are never charged for failed runs.",
43
+ "- Do NOT ask the user to set up payment before they try to run an agent. Let them explore freely — payment setup is handled inline when they're ready to pay.",
44
44
  "",
45
45
  "REQUIRED FIELDS:",
46
46
  "Always check the agent's input schema (via get_agent) before calling run_agent.",
package/dist/tools/run.js CHANGED
@@ -5,6 +5,7 @@ import { getConfiguredMethods, hasWalletConfigured, getWalletAddress } from "../
5
5
  import { requiresSpendConfirmation, getDefaultTipAmount } from "../core/config.js";
6
6
  import { formatRunResult } from "../core/formatters.js";
7
7
  import { storeFeedbackToken } from "./_token-cache.js";
8
+ import { initiateCardSetup, formatCardSetupBlocks } from "../core/card-setup.js";
8
9
  const POLL_INTERVAL_MS = 3000;
9
10
  const POLL_MAX_MS = 120000; // 2 minutes
10
11
  async function pollJobUntilDone(jobId, agentName) {
@@ -45,9 +46,16 @@ export function registerRunTools(server) {
45
46
  confirmed: z.boolean().optional().describe("Set to true to confirm spending after seeing the price quote."),
46
47
  }, async ({ agent_id, input, pay_with, confirmed }) => {
47
48
  if (!hasWalletConfigured()) {
48
- return text("No wallet configured. Set one up first:\n\n" +
49
- ' wallet_setup({ action: "create", name: "my-wallet" })\n\n' +
50
- "Then fund it with USDC on Tempo and try again.");
49
+ try {
50
+ const { qr, url } = await initiateCardSetup();
51
+ const blocks = formatCardSetupBlocks(qr, url);
52
+ return multiText(...blocks);
53
+ }
54
+ catch {
55
+ return text("No payment method configured.\n\n" +
56
+ "To add a credit card: wallet_setup({ action: \"add-card\" })\n" +
57
+ "To use crypto: wallet_setup({ action: \"create\" })");
58
+ }
51
59
  }
52
60
  // Resolve agent and fetch details
53
61
  let agent;
@@ -1,5 +1,6 @@
1
1
  import { z } from "zod";
2
2
  import { apiGet, apiPost, apiPostWithPayment } from "../core/api-client.js";
3
+ import { initiateCardSetup, formatCardSetupBlocks } from "../core/card-setup.js";
3
4
  import { hasWalletConfigured, getConfiguredMethods, getAcceptedPaymentMethods, getWalletAddress, } from "../core/payments.js";
4
5
  import { requiresSpendConfirmation, getDefaultTipAmount } from "../core/config.js";
5
6
  import { agentList, formatRunResult } from "../core/formatters.js";
@@ -93,9 +94,16 @@ export function registerSolveTools(server) {
93
94
  .describe("Set to true to confirm spending after seeing the price quote."),
94
95
  }, async ({ intent, input, budget, pay_with, confirmed }) => {
95
96
  if (!hasWalletConfigured()) {
96
- return text("No wallet configured. Set one up first:\n\n" +
97
- ' wallet_setup({ action: "create", name: "my-wallet" })\n\n' +
98
- "Then fund it with USDC on Tempo and try again.");
97
+ try {
98
+ const { qr, url } = await initiateCardSetup();
99
+ const blocks = formatCardSetupBlocks(qr, url);
100
+ return { content: blocks.map((t) => ({ type: "text", text: t })) };
101
+ }
102
+ catch {
103
+ return text("No payment method configured.\n\n" +
104
+ "To add a credit card: wallet_setup({ action: \"add-card\" })\n" +
105
+ "To use crypto: wallet_setup({ action: \"create\" })");
106
+ }
99
107
  }
100
108
  const method = pay_with ?? getConfiguredMethods()[0];
101
109
  // Path 1: If authenticated, use the platform /solve route
@@ -1,9 +1,7 @@
1
1
  import { z } from "zod";
2
- import QRCode from "qrcode";
3
- import { getWallets, getCardConfig, setCardConfig, addWallet } from "../core/config.js";
4
- import { getApiUrl } from "../core/config.js";
2
+ import { getWallets, getCardConfig, addWallet } from "../core/config.js";
5
3
  import { getWalletAddress } from "../core/payments.js";
6
- import { apiPost, apiGet } from "../core/api-client.js";
4
+ import { initiateCardSetup, pollCardSetup } from "../core/card-setup.js";
7
5
  import { isOwsAvailable, createOwsWallet, importKeyToOws, listOwsWallets, } from "../core/ows-adapter.js";
8
6
  function text(t) {
9
7
  return { content: [{ type: "text", text: t }] };
@@ -52,64 +50,37 @@ export function registerWalletTools(server) {
52
50
  if (existing) {
53
51
  return text(`Card already connected: ${existing.brand} ****${existing.last4}\n\nTo replace it, remove the current card first.`);
54
52
  }
55
- // Check if there's a pending setup to complete
53
+ // Check if there's a pending setup to complete (user said they're done)
56
54
  if (pendingCardSetup) {
57
55
  const pendingToken = pendingCardSetup.token;
58
- // Poll for completion (up to 2 minutes)
59
- const deadline = Date.now() + 120_000;
60
- while (Date.now() < deadline) {
61
- await new Promise((r) => setTimeout(r, 3000));
62
- try {
63
- const status = await apiGet(`/card/status?token=${pendingToken}`);
64
- if (status.status === "complete" && status.consumer_token) {
65
- setCardConfig({
66
- consumerToken: status.consumer_token,
67
- last4: status.card_last4 ?? "????",
68
- brand: status.card_brand ?? "card",
69
- });
70
- pendingCardSetup = null;
71
- return text(`Connected! ${status.card_brand ?? "Card"} ****${status.card_last4} is ready for payments.`);
72
- }
73
- }
74
- catch {
75
- // Keep polling
76
- }
77
- }
56
+ const result = await pollCardSetup(pendingToken);
78
57
  pendingCardSetup = null;
58
+ if (result) {
59
+ return text(`Connected! ${result.brand} ****${result.last4} is ready for payments.`);
60
+ }
79
61
  return text("Card setup timed out. Run wallet_setup({ action: \"add-card\" }) to try again.");
80
62
  }
81
- // Create new card setup session
82
- let setupResult;
63
+ // Create new card setup session with QR code
83
64
  try {
84
- setupResult = await apiPost("/card/setup", {});
65
+ const { qr, url, token } = await initiateCardSetup();
66
+ pendingCardSetup = { token };
67
+ return {
68
+ content: [
69
+ { type: "text", text: qr.trim() },
70
+ { type: "text", text: [
71
+ `IMPORTANT: You MUST present this link to the user so they can connect a payment card:`,
72
+ "",
73
+ url,
74
+ "",
75
+ `Tell the user: "Scan the QR code above or open this link to connect your card. Let me know when you're done."`,
76
+ `After they confirm, call wallet_setup({ action: "add-card" }) to complete setup.`,
77
+ ].join("\n") },
78
+ ],
79
+ };
85
80
  }
86
81
  catch {
87
82
  return text("Error: Could not create card setup session. Try again later.");
88
83
  }
89
- // Store for the follow-up poll call
90
- pendingCardSetup = { token: setupResult.token };
91
- // Build a short link URL for a compact QR code
92
- const apiUrl = getApiUrl();
93
- const shortUrl = `${apiUrl}/card/link/${setupResult.token}`;
94
- // Generate QR code
95
- let qr;
96
- try {
97
- qr = await QRCode.toString(shortUrl, {
98
- type: "utf8",
99
- errorCorrectionLevel: "L",
100
- margin: 2,
101
- });
102
- }
103
- catch {
104
- qr = "";
105
- }
106
- return text([
107
- "Scan to connect a payment card:\n",
108
- qr,
109
- `Or open: ${shortUrl}`,
110
- "",
111
- "After entering your card, tell me and I'll confirm the connection.",
112
- ].join("\n"));
113
84
  }
114
85
  // ── Crypto wallet setup ──────────────────────────────────
115
86
  if (action === "import" && !key) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentwonderland/mcp",
3
- "version": "0.1.16",
3
+ "version": "0.1.18",
4
4
  "type": "module",
5
5
  "description": "MCP server for the Agent Wonderland AI agent marketplace",
6
6
  "bin": {
@@ -0,0 +1,90 @@
1
+ import QRCode from "qrcode";
2
+ import { apiPost, apiGet } from "./api-client.js";
3
+ import { getApiUrl, getCardConfig, setCardConfig } from "./config.js";
4
+
5
+ /**
6
+ * Initiate card setup — creates a Stripe Checkout session and returns
7
+ * QR code + short URL for the user to scan.
8
+ */
9
+ export async function initiateCardSetup(): Promise<{
10
+ qr: string;
11
+ url: string;
12
+ token: string;
13
+ }> {
14
+ const { url: _checkoutUrl, token } = await apiPost<{ url: string; token: string }>("/card/setup", {});
15
+
16
+ // Short redirect URL keeps the QR code small and scannable
17
+ const apiUrl = getApiUrl();
18
+ const shortUrl = `${apiUrl}/card/link/${token}`;
19
+
20
+ const qr = await QRCode.toString(shortUrl, {
21
+ type: "utf8",
22
+ errorCorrectionLevel: "L",
23
+ margin: 2,
24
+ });
25
+
26
+ return { qr, url: shortUrl, token };
27
+ }
28
+
29
+ /**
30
+ * Format the card setup prompt as multiple content blocks.
31
+ * Block 1: QR code (may be collapsed in some clients)
32
+ * Block 2: URL + instructions (short, always visible)
33
+ */
34
+ export function formatCardSetupBlocks(qr: string, url: string): string[] {
35
+ return [
36
+ qr.trim(),
37
+ [
38
+ `IMPORTANT: You MUST present this link to the user so they can connect a payment card:`,
39
+ "",
40
+ url,
41
+ "",
42
+ `Tell the user: "Scan the QR code above or open this link to connect your card. Let me know when you're done."`,
43
+ `After they confirm, call wallet_setup({ action: "add-card" }) to complete setup.`,
44
+ `Crypto wallets (Base/Tempo USDC) are also available via wallet_setup({ action: "create" }).`,
45
+ ].join("\n"),
46
+ ];
47
+ }
48
+
49
+ /**
50
+ * Poll for card setup completion. Returns the card details or null on timeout.
51
+ */
52
+ export async function pollCardSetup(
53
+ token: string,
54
+ timeoutMs = 120_000,
55
+ ): Promise<{ last4: string; brand: string; consumerToken: string } | null> {
56
+ const deadline = Date.now() + timeoutMs;
57
+
58
+ while (Date.now() < deadline) {
59
+ await new Promise((r) => setTimeout(r, 3000));
60
+ try {
61
+ const status = await apiGet<{
62
+ status: string;
63
+ card_last4?: string;
64
+ card_brand?: string;
65
+ consumer_token?: string;
66
+ }>(`/card/status?token=${token}`);
67
+
68
+ if (status.status === "complete" && status.consumer_token) {
69
+ const card = {
70
+ last4: status.card_last4 ?? "????",
71
+ brand: status.card_brand ?? "card",
72
+ consumerToken: status.consumer_token,
73
+ };
74
+
75
+ // Persist to config
76
+ setCardConfig({
77
+ consumerToken: card.consumerToken,
78
+ last4: card.last4,
79
+ brand: card.brand,
80
+ });
81
+
82
+ return card;
83
+ }
84
+ } catch {
85
+ // Keep polling
86
+ }
87
+ }
88
+
89
+ return null;
90
+ }
package/src/index.ts CHANGED
@@ -37,17 +37,17 @@ export async function startMcpServer(): Promise<void> {
37
37
  "The MCP auto-uploads to cloud storage and replaces with a URL. Never base64-encode.",
38
38
  "",
39
39
  "WORKFLOW:",
40
- "1. wallet_status() check payment is configured (Tempo USDC, Base USDC, or Card)",
41
- "2. search_agents() or solve() find agents for the task",
42
- "3. get_agent() check required input fields before running",
43
- "4. run_agent() or solve() with ALL required fields",
40
+ "1. search_agents() or solve() find agents for the task",
41
+ "2. get_agent() check required input fields before running",
42
+ "3. run_agent() or solve() with ALL required fields",
43
+ "4. If no payment method is set up, run_agent returns a QR code to connect a credit card — present it to the user",
44
44
  "5. Ask user to rate or tip after a successful run",
45
45
  "",
46
46
  "PAYMENT:",
47
- "- Automatic via configured wallet. User does nothing.",
48
- "- Supports Tempo USDC, Base USDC, and Stripe Card.",
49
- "- Input validation runs before payment invalid input is never charged.",
50
- "- Failed agent executions trigger automatic refunds to the consumer's wallet.",
47
+ "- Credit/debit card is the easiest way to pay — just scan a QR code to connect, no funding needed.",
48
+ "- Crypto wallets (Tempo USDC, Base USDC) are also supported for advanced users.",
49
+ "- Payment is automatic once configured. Users are never charged for failed runs.",
50
+ "- Do NOT ask the user to set up payment before they try to run an agent. Let them explore freely — payment setup is handled inline when they're ready to pay.",
51
51
  "",
52
52
  "REQUIRED FIELDS:",
53
53
  "Always check the agent's input schema (via get_agent) before calling run_agent.",
package/src/tools/run.ts CHANGED
@@ -6,6 +6,7 @@ import { getConfiguredMethods, hasWalletConfigured, getWalletAddress } from "../
6
6
  import { requiresSpendConfirmation, getDefaultTipAmount } from "../core/config.js";
7
7
  import { formatRunResult } from "../core/formatters.js";
8
8
  import { storeFeedbackToken, getFeedbackToken } from "./_token-cache.js";
9
+ import { initiateCardSetup, formatCardSetupBlocks } from "../core/card-setup.js";
9
10
 
10
11
  const POLL_INTERVAL_MS = 3000;
11
12
  const POLL_MAX_MS = 120000; // 2 minutes
@@ -69,11 +70,17 @@ export function registerRunTools(server: McpServer): void {
69
70
  },
70
71
  async ({ agent_id, input, pay_with, confirmed }) => {
71
72
  if (!hasWalletConfigured()) {
72
- return text(
73
- "No wallet configured. Set one up first:\n\n" +
74
- ' wallet_setup({ action: "create", name: "my-wallet" })\n\n' +
75
- "Then fund it with USDC on Tempo and try again."
76
- );
73
+ try {
74
+ const { qr, url } = await initiateCardSetup();
75
+ const blocks = formatCardSetupBlocks(qr, url);
76
+ return multiText(...blocks);
77
+ } catch {
78
+ return text(
79
+ "No payment method configured.\n\n" +
80
+ "To add a credit card: wallet_setup({ action: \"add-card\" })\n" +
81
+ "To use crypto: wallet_setup({ action: \"create\" })"
82
+ );
83
+ }
77
84
  }
78
85
 
79
86
  // Resolve agent and fetch details
@@ -1,6 +1,7 @@
1
1
  import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
2
  import { z } from "zod";
3
3
  import { apiGet, apiPost, apiPostWithPayment } from "../core/api-client.js";
4
+ import { initiateCardSetup, formatCardSetupBlocks } from "../core/card-setup.js";
4
5
  import {
5
6
  hasWalletConfigured,
6
7
  getConfiguredMethods,
@@ -119,11 +120,17 @@ export function registerSolveTools(server: McpServer): void {
119
120
  },
120
121
  async ({ intent, input, budget, pay_with, confirmed }) => {
121
122
  if (!hasWalletConfigured()) {
122
- return text(
123
- "No wallet configured. Set one up first:\n\n" +
124
- ' wallet_setup({ action: "create", name: "my-wallet" })\n\n' +
125
- "Then fund it with USDC on Tempo and try again."
126
- );
123
+ try {
124
+ const { qr, url } = await initiateCardSetup();
125
+ const blocks = formatCardSetupBlocks(qr, url);
126
+ return { content: blocks.map((t) => ({ type: "text" as const, text: t })) };
127
+ } catch {
128
+ return text(
129
+ "No payment method configured.\n\n" +
130
+ "To add a credit card: wallet_setup({ action: \"add-card\" })\n" +
131
+ "To use crypto: wallet_setup({ action: \"create\" })"
132
+ );
133
+ }
127
134
  }
128
135
 
129
136
  const method = pay_with ?? getConfiguredMethods()[0];
@@ -1,10 +1,8 @@
1
1
  import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
2
  import { z } from "zod";
3
- import QRCode from "qrcode";
4
3
  import { getWallets, getCardConfig, setCardConfig, addWallet } from "../core/config.js";
5
- import { getApiUrl } from "../core/config.js";
6
4
  import { getWalletAddress } from "../core/payments.js";
7
- import { apiPost, apiGet } from "../core/api-client.js";
5
+ import { initiateCardSetup, pollCardSetup } from "../core/card-setup.js";
8
6
  import {
9
7
  isOwsAvailable,
10
8
  createOwsWallet,
@@ -84,72 +82,39 @@ export function registerWalletTools(server: McpServer): void {
84
82
  return text(`Card already connected: ${existing.brand} ****${existing.last4}\n\nTo replace it, remove the current card first.`);
85
83
  }
86
84
 
87
- // Check if there's a pending setup to complete
85
+ // Check if there's a pending setup to complete (user said they're done)
88
86
  if (pendingCardSetup) {
89
87
  const pendingToken = pendingCardSetup.token;
90
- // Poll for completion (up to 2 minutes)
91
- const deadline = Date.now() + 120_000;
92
- while (Date.now() < deadline) {
93
- await new Promise((r) => setTimeout(r, 3000));
94
- try {
95
- const status = await apiGet<{
96
- status: string;
97
- card_last4?: string;
98
- card_brand?: string;
99
- consumer_token?: string;
100
- }>(`/card/status?token=${pendingToken}`);
101
-
102
- if (status.status === "complete" && status.consumer_token) {
103
- setCardConfig({
104
- consumerToken: status.consumer_token,
105
- last4: status.card_last4 ?? "????",
106
- brand: status.card_brand ?? "card",
107
- });
108
- pendingCardSetup = null;
109
- return text(`Connected! ${status.card_brand ?? "Card"} ****${status.card_last4} is ready for payments.`);
110
- }
111
- } catch {
112
- // Keep polling
113
- }
114
- }
88
+ const result = await pollCardSetup(pendingToken);
115
89
  pendingCardSetup = null;
90
+
91
+ if (result) {
92
+ return text(`Connected! ${result.brand} ****${result.last4} is ready for payments.`);
93
+ }
116
94
  return text("Card setup timed out. Run wallet_setup({ action: \"add-card\" }) to try again.");
117
95
  }
118
96
 
119
- // Create new card setup session
120
- let setupResult: { url: string; token: string };
97
+ // Create new card setup session with QR code
121
98
  try {
122
- setupResult = await apiPost<{ url: string; token: string }>("/card/setup", {});
99
+ const { qr, url, token } = await initiateCardSetup();
100
+ pendingCardSetup = { token };
101
+
102
+ return {
103
+ content: [
104
+ { type: "text" as const, text: qr.trim() },
105
+ { type: "text" as const, text: [
106
+ `IMPORTANT: You MUST present this link to the user so they can connect a payment card:`,
107
+ "",
108
+ url,
109
+ "",
110
+ `Tell the user: "Scan the QR code above or open this link to connect your card. Let me know when you're done."`,
111
+ `After they confirm, call wallet_setup({ action: "add-card" }) to complete setup.`,
112
+ ].join("\n") },
113
+ ],
114
+ };
123
115
  } catch {
124
116
  return text("Error: Could not create card setup session. Try again later.");
125
117
  }
126
-
127
- // Store for the follow-up poll call
128
- pendingCardSetup = { token: setupResult.token };
129
-
130
- // Build a short link URL for a compact QR code
131
- const apiUrl = getApiUrl();
132
- const shortUrl = `${apiUrl}/card/link/${setupResult.token}`;
133
-
134
- // Generate QR code
135
- let qr: string;
136
- try {
137
- qr = await QRCode.toString(shortUrl, {
138
- type: "utf8",
139
- errorCorrectionLevel: "L",
140
- margin: 2,
141
- });
142
- } catch {
143
- qr = "";
144
- }
145
-
146
- return text([
147
- "Scan to connect a payment card:\n",
148
- qr,
149
- `Or open: ${shortUrl}`,
150
- "",
151
- "After entering your card, tell me and I'll confirm the connection.",
152
- ].join("\n"));
153
118
  }
154
119
 
155
120
  // ── Crypto wallet setup ──────────────────────────────────