@agentwonderland/mcp 0.1.45 → 0.1.47
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/core/__tests__/payments.test.js +55 -1
- package/dist/core/__tests__/principal.test.js +18 -0
- package/dist/core/config.d.ts +16 -0
- package/dist/core/config.js +37 -0
- package/dist/core/link-cli.d.ts +27 -0
- package/dist/core/link-cli.js +259 -0
- package/dist/core/mpp-client.d.ts +13 -1
- package/dist/core/mpp-client.js +45 -2
- package/dist/core/payments.js +135 -10
- package/dist/core/principal.js +2 -2
- package/dist/core/version.d.ts +1 -1
- package/dist/core/version.js +1 -1
- package/dist/index.js +6 -4
- package/dist/tools/__tests__/search.test.d.ts +1 -0
- package/dist/tools/__tests__/search.test.js +66 -0
- package/dist/tools/__tests__/wallet.test.js +153 -0
- package/dist/tools/passes.js +1 -1
- package/dist/tools/run.js +1 -1
- package/dist/tools/search.js +33 -0
- package/dist/tools/solve.js +1 -1
- package/dist/tools/wallet.js +195 -7
- package/package.json +1 -1
- package/src/core/__tests__/payments.test.ts +78 -1
- package/src/core/__tests__/principal.test.ts +23 -0
- package/src/core/config.ts +56 -0
- package/src/core/link-cli.ts +300 -0
- package/src/core/mpp-client.ts +69 -2
- package/src/core/payments.ts +153 -11
- package/src/core/principal.ts +2 -2
- package/src/core/version.ts +1 -1
- package/src/index.ts +6 -4
- package/src/tools/__tests__/search.test.ts +78 -0
- package/src/tools/__tests__/wallet.test.ts +190 -0
- package/src/tools/passes.ts +1 -1
- package/src/tools/run.ts +1 -1
- package/src/tools/search.ts +40 -0
- package/src/tools/solve.ts +1 -1
- package/src/tools/wallet.ts +229 -6
package/dist/tools/solve.js
CHANGED
|
@@ -126,7 +126,7 @@ export function registerSolveTools(server) {
|
|
|
126
126
|
.trim()
|
|
127
127
|
.min(1)
|
|
128
128
|
.optional()
|
|
129
|
-
.describe("Payment method — wallet ID, chain name (tempo, base,
|
|
129
|
+
.describe("Payment method — wallet ID, chain name (tempo, base, solana), 'link', or 'card'. Auto-detected if omitted."),
|
|
130
130
|
confirmed: z
|
|
131
131
|
.boolean()
|
|
132
132
|
.optional()
|
package/dist/tools/wallet.js
CHANGED
|
@@ -1,24 +1,66 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { getWallets, getCardConfig, setCardConfig, addWallet, getPendingCardSetupToken, getSpendPolicy, setSpendPolicy, } from "../core/config.js";
|
|
2
|
+
import { getWallets, getCardConfig, getLinkConfig, getPendingLinkSetup, setPendingLinkSetup, setLinkConfig, setCardConfig, addWallet, getPendingCardSetupToken, getSpendPolicy, setDefaultPaymentMethod, setSpendPolicy, } from "../core/config.js";
|
|
3
3
|
import { getWalletAddress, isCardPaymentEnabled } from "../core/payments.js";
|
|
4
4
|
import { fetchUsdcBalance } from "../core/balances.js";
|
|
5
5
|
import { getSettings } from "../core/settings.js";
|
|
6
6
|
import { getOrCreatePendingCardSetup, formatCardSetupBlocks, getCardCapabilities, pollCardSetup, } from "../core/card-setup.js";
|
|
7
|
+
import { getLinkCliAuthStatus, listLinkPaymentMethods, openLinkPaymentMethodAdd, startLinkCliLogin, } from "../core/link-cli.js";
|
|
7
8
|
import { isOwsAvailable, createOwsWallet, importKeyToOws, listOwsWallets, listOwsWalletsByChain, installOws, platformSupportsOws, } from "../core/ows-adapter.js";
|
|
8
9
|
import { ensureConsumerPrincipal, getConsumerPrincipal } from "../core/principal.js";
|
|
9
10
|
import { MCP_PACKAGE_VERSION } from "../core/version.js";
|
|
10
11
|
function text(t) {
|
|
11
12
|
return { content: [{ type: "text", text: t }] };
|
|
12
13
|
}
|
|
14
|
+
function formatLinkApprovalPrompt(pending) {
|
|
15
|
+
return [
|
|
16
|
+
"Approve Agent Wonderland in Link:",
|
|
17
|
+
"",
|
|
18
|
+
pending.verificationUrl,
|
|
19
|
+
"",
|
|
20
|
+
`Verification phrase: ${pending.phrase}`,
|
|
21
|
+
"",
|
|
22
|
+
"After approving, run wallet_setup({ action: \"add-link\" }) again and I will finish connecting the payment method.",
|
|
23
|
+
].join("\n");
|
|
24
|
+
}
|
|
25
|
+
function isFreshLinkSetup(pending) {
|
|
26
|
+
const createdAt = Date.parse(pending.createdAt);
|
|
27
|
+
if (!Number.isFinite(createdAt))
|
|
28
|
+
return false;
|
|
29
|
+
return Date.now() - createdAt < 10 * 60 * 1000;
|
|
30
|
+
}
|
|
31
|
+
function formatPaymentSetupMenu() {
|
|
32
|
+
return [
|
|
33
|
+
"Set up a payment method:",
|
|
34
|
+
"",
|
|
35
|
+
"1. Link card/bank (recommended)",
|
|
36
|
+
" wallet_setup({ action: \"add-link\" })",
|
|
37
|
+
"",
|
|
38
|
+
"2. Tempo USDC",
|
|
39
|
+
" wallet_setup({ action: \"create\", chain: \"tempo\" })",
|
|
40
|
+
"",
|
|
41
|
+
"3. Base USDC",
|
|
42
|
+
" wallet_setup({ action: \"create\", chain: \"base\" })",
|
|
43
|
+
"",
|
|
44
|
+
"4. Solana USDC",
|
|
45
|
+
" wallet_setup({ action: \"create\", chain: \"solana\" })",
|
|
46
|
+
"",
|
|
47
|
+
"5. Import an existing wallet",
|
|
48
|
+
" wallet_setup({ action: \"import\", chain: \"base\", key: \"<private key>\" })",
|
|
49
|
+
].join("\n");
|
|
50
|
+
}
|
|
51
|
+
function setDefaultCryptoPaymentMethod(chain) {
|
|
52
|
+
setDefaultPaymentMethod(chain === "solana" ? "solana" : chain === "base" ? "base" : "tempo");
|
|
53
|
+
}
|
|
13
54
|
export function registerWalletTools(server) {
|
|
14
55
|
// ── wallet_status (extracted from check_wallet) ─────────────────
|
|
15
56
|
server.tool("wallet_status", "Check payment readiness. Shows all configured wallets, their chains, addresses, and card status.", {}, async () => {
|
|
16
57
|
const wallets = getWallets();
|
|
17
58
|
const card = getCardConfig();
|
|
59
|
+
const link = getLinkConfig();
|
|
18
60
|
const pendingCardSetupToken = getPendingCardSetupToken();
|
|
19
61
|
const consumerPrincipal = await getConsumerPrincipal();
|
|
20
|
-
if (wallets.length === 0 && !card && !pendingCardSetupToken) {
|
|
21
|
-
return text(
|
|
62
|
+
if (wallets.length === 0 && !card && !link && !pendingCardSetupToken) {
|
|
63
|
+
return text(`No payment methods configured.\n\n${formatPaymentSetupMenu()}`);
|
|
22
64
|
}
|
|
23
65
|
const settings = await getSettings();
|
|
24
66
|
const networkLabel = settings ? ` (${settings.network})` : "";
|
|
@@ -61,6 +103,14 @@ export function registerWalletTools(server) {
|
|
|
61
103
|
lines.push(` Card MPP: unknown — ${capabilities.message ?? "Could not determine card payment readiness."}`);
|
|
62
104
|
}
|
|
63
105
|
}
|
|
106
|
+
if (link) {
|
|
107
|
+
const auth = await getLinkCliAuthStatus();
|
|
108
|
+
const label = link.label ? ` (${link.label})` : "";
|
|
109
|
+
lines.push(` Link${label}: ${link.paymentMethodId}`);
|
|
110
|
+
lines.push(auth.authenticated
|
|
111
|
+
? " Link CLI: authenticated"
|
|
112
|
+
: " Link CLI: not authenticated — run npx @stripe/link-cli auth login");
|
|
113
|
+
}
|
|
64
114
|
if (pendingCardSetupToken) {
|
|
65
115
|
lines.push(" Card setup: pending confirmation");
|
|
66
116
|
}
|
|
@@ -77,10 +127,10 @@ export function registerWalletTools(server) {
|
|
|
77
127
|
return text(lines.join("\n"));
|
|
78
128
|
});
|
|
79
129
|
// ── wallet_setup ────────────────────────────────────────────────
|
|
80
|
-
server.tool("wallet_setup", "Set up or manage
|
|
130
|
+
server.tool("wallet_setup", "Set up or manage an Agent Wonderland payment method. Use 'start' for a guided setup menu. Link card/bank is recommended for most users. 'create' makes a new crypto wallet (encrypted via OWS if available, otherwise plaintext — run 'enable-ows' to upgrade). 'import' takes an existing private key. 'enable-ows' installs the Open Wallet Standard native module for encrypted at-rest storage. Tempo/Base share one EVM key; Solana uses a separate ed25519 key. NEVER delete or rotate keys programmatically; direct users to edit ~/.agentwonderland/config.json or ~/.ows/ manually.", {
|
|
81
131
|
action: z
|
|
82
|
-
.enum(["create", "import", "add-card", "remove-card", "enable-ows"])
|
|
83
|
-
.describe("'create' a wallet, 'import' an existing key, 'enable-ows'
|
|
132
|
+
.enum(["start", "create", "import", "add-card", "remove-card", "add-link", "remove-link", "enable-ows"])
|
|
133
|
+
.describe("'start' shows the guided payment setup menu, 'add-link' connects Link card/bank, 'create' makes a crypto wallet, 'import' imports an existing key, 'enable-ows' installs encrypted key storage"),
|
|
84
134
|
name: z
|
|
85
135
|
.string()
|
|
86
136
|
.optional()
|
|
@@ -91,7 +141,138 @@ export function registerWalletTools(server) {
|
|
|
91
141
|
.describe("Private key hex string (required for 'import', ignored for 'create')"),
|
|
92
142
|
chain: z.enum(["tempo", "base", "solana"]).optional()
|
|
93
143
|
.describe("Primary chain (default: tempo). Tempo/Base use a shared EVM wallet; Solana uses a separate OWS wallet."),
|
|
94
|
-
|
|
144
|
+
link_payment_method_id: z.string().optional()
|
|
145
|
+
.describe("Link payment method ID from `npx @stripe/link-cli payment-methods list`; used with action 'add-link'."),
|
|
146
|
+
link_payment_method: z.string().optional()
|
|
147
|
+
.describe("Friendly Link payment method selector, such as a card/bank name or last4; used with action 'add-link'."),
|
|
148
|
+
}, async ({ action, name, key, chain, link_payment_method_id, link_payment_method }) => {
|
|
149
|
+
if (action === "start") {
|
|
150
|
+
return text(formatPaymentSetupMenu());
|
|
151
|
+
}
|
|
152
|
+
// ── Link setup flow ──────────────────────────────────────
|
|
153
|
+
if (action === "add-link") {
|
|
154
|
+
let auth = await getLinkCliAuthStatus();
|
|
155
|
+
if (!auth.authenticated) {
|
|
156
|
+
const pending = getPendingLinkSetup();
|
|
157
|
+
if (pending && isFreshLinkSetup(pending)) {
|
|
158
|
+
return text(formatLinkApprovalPrompt(pending));
|
|
159
|
+
}
|
|
160
|
+
if (pending) {
|
|
161
|
+
setPendingLinkSetup(null);
|
|
162
|
+
}
|
|
163
|
+
try {
|
|
164
|
+
const login = await startLinkCliLogin();
|
|
165
|
+
setPendingLinkSetup({
|
|
166
|
+
verificationUrl: login.verificationUrl,
|
|
167
|
+
phrase: login.phrase,
|
|
168
|
+
createdAt: new Date().toISOString(),
|
|
169
|
+
});
|
|
170
|
+
auth = await getLinkCliAuthStatus();
|
|
171
|
+
if (!auth.authenticated) {
|
|
172
|
+
return text(formatLinkApprovalPrompt({
|
|
173
|
+
verificationUrl: login.verificationUrl,
|
|
174
|
+
phrase: login.phrase,
|
|
175
|
+
}));
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
catch (err) {
|
|
179
|
+
return text([
|
|
180
|
+
"Link authentication could not start from the MCP session.",
|
|
181
|
+
`Reason: ${err instanceof Error ? err.message : String(err)}`,
|
|
182
|
+
"",
|
|
183
|
+
"Run wallet_setup({ action: \"add-link\" }) again to retry.",
|
|
184
|
+
].join("\n"));
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
setPendingLinkSetup(null);
|
|
188
|
+
const explicitLinkSelector = link_payment_method_id ?? link_payment_method;
|
|
189
|
+
if (explicitLinkSelector) {
|
|
190
|
+
const methods = await listLinkPaymentMethods();
|
|
191
|
+
const normalizedSelector = explicitLinkSelector.toLowerCase().trim();
|
|
192
|
+
const exact = methods.find((method) => method.id === explicitLinkSelector);
|
|
193
|
+
const matches = exact
|
|
194
|
+
? [exact]
|
|
195
|
+
: methods.filter((method) => method.searchText?.includes(normalizedSelector));
|
|
196
|
+
if (matches.length === 0 && !link_payment_method_id) {
|
|
197
|
+
return text([
|
|
198
|
+
`No Link payment method matched "${explicitLinkSelector}".`,
|
|
199
|
+
"",
|
|
200
|
+
"Available methods:",
|
|
201
|
+
...methods.map((method) => ` ${method.label ?? method.id}${method.label ? ` (${method.id})` : ""}`),
|
|
202
|
+
].join("\n"));
|
|
203
|
+
}
|
|
204
|
+
if (matches.length > 1) {
|
|
205
|
+
return text([
|
|
206
|
+
`Multiple Link payment methods matched "${explicitLinkSelector}". Choose one:`,
|
|
207
|
+
...matches.map((method) => ` ${method.label ?? method.id}${method.label ? ` (${method.id})` : ""}`),
|
|
208
|
+
"",
|
|
209
|
+
"Then run:",
|
|
210
|
+
" wallet_setup({ action: \"add-link\", link_payment_method: \"<name or last4>\" })",
|
|
211
|
+
].join("\n"));
|
|
212
|
+
}
|
|
213
|
+
const selected = matches[0] ?? { id: link_payment_method_id, label: name };
|
|
214
|
+
setLinkConfig({
|
|
215
|
+
paymentMethodId: selected.id,
|
|
216
|
+
label: name ?? selected.label,
|
|
217
|
+
});
|
|
218
|
+
const principal = await ensureConsumerPrincipal();
|
|
219
|
+
return text([
|
|
220
|
+
"Link payment method connected.",
|
|
221
|
+
` Payment method: ${selected.label ?? selected.id}${selected.label ? ` (${selected.id})` : ""}`,
|
|
222
|
+
` Consumer principal: ${principal}`,
|
|
223
|
+
"",
|
|
224
|
+
"Use pay_with: \"link\" for agent runs.",
|
|
225
|
+
].join("\n"));
|
|
226
|
+
}
|
|
227
|
+
let methods = await listLinkPaymentMethods();
|
|
228
|
+
if (methods.length === 0) {
|
|
229
|
+
try {
|
|
230
|
+
await openLinkPaymentMethodAdd();
|
|
231
|
+
methods = await listLinkPaymentMethods();
|
|
232
|
+
}
|
|
233
|
+
catch (err) {
|
|
234
|
+
return text([
|
|
235
|
+
"No Link payment methods are available yet.",
|
|
236
|
+
`Reason: ${err instanceof Error ? err.message : String(err)}`,
|
|
237
|
+
"",
|
|
238
|
+
"Run wallet_setup({ action: \"add-link\" }) again after adding a payment method in Link.",
|
|
239
|
+
].join("\n"));
|
|
240
|
+
}
|
|
241
|
+
if (methods.length === 0) {
|
|
242
|
+
return text("Link payment-method setup was started. Run wallet_setup({ action: \"add-link\" }) again after adding a payment method in Link.");
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
if (methods.length === 1) {
|
|
246
|
+
const method = methods[0];
|
|
247
|
+
setLinkConfig({
|
|
248
|
+
paymentMethodId: method.id,
|
|
249
|
+
label: method.label,
|
|
250
|
+
});
|
|
251
|
+
const principal = await ensureConsumerPrincipal();
|
|
252
|
+
return text([
|
|
253
|
+
"Link payment method connected.",
|
|
254
|
+
` Payment method: ${method.id}${method.label ? ` (${method.label})` : ""}`,
|
|
255
|
+
` Consumer principal: ${principal}`,
|
|
256
|
+
"",
|
|
257
|
+
"Use pay_with: \"link\" for agent runs.",
|
|
258
|
+
].join("\n"));
|
|
259
|
+
}
|
|
260
|
+
return text([
|
|
261
|
+
"Multiple Link payment methods found. Choose one:",
|
|
262
|
+
...methods.map((method) => ` ${method.label ?? method.id}${method.label ? ` (${method.id})` : ""}`),
|
|
263
|
+
"",
|
|
264
|
+
"Then run:",
|
|
265
|
+
" wallet_setup({ action: \"add-link\", link_payment_method: \"<name or last4>\" })",
|
|
266
|
+
].join("\n"));
|
|
267
|
+
}
|
|
268
|
+
if (action === "remove-link") {
|
|
269
|
+
const existing = getLinkConfig();
|
|
270
|
+
if (!existing) {
|
|
271
|
+
return text("No Link payment method is currently connected.");
|
|
272
|
+
}
|
|
273
|
+
setLinkConfig(null);
|
|
274
|
+
return text(`Removed Link payment method ${existing.paymentMethodId}.`);
|
|
275
|
+
}
|
|
95
276
|
// ── Card setup flow ──────────────────────────────────────
|
|
96
277
|
if (action === "add-card") {
|
|
97
278
|
if (!isCardPaymentEnabled()) {
|
|
@@ -206,6 +387,7 @@ export function registerWalletTools(server) {
|
|
|
206
387
|
chainStatus = "Already linked to Agent Wonderland.";
|
|
207
388
|
}
|
|
208
389
|
}
|
|
390
|
+
setDefaultCryptoPaymentMethod(defaultCh);
|
|
209
391
|
const principal = await ensureConsumerPrincipal();
|
|
210
392
|
return text([
|
|
211
393
|
"Found existing OWS wallet:",
|
|
@@ -239,6 +421,7 @@ export function registerWalletTools(server) {
|
|
|
239
421
|
defaultChain: "solana",
|
|
240
422
|
label: walletName,
|
|
241
423
|
});
|
|
424
|
+
setDefaultCryptoPaymentMethod("solana");
|
|
242
425
|
const principal = await ensureConsumerPrincipal();
|
|
243
426
|
const owsNudge = platformSupportsOws()
|
|
244
427
|
? "\n\n⚠ Key stored in plaintext at ~/.agentwonderland/config.json. Run wallet_setup({ action: \"enable-ows\" }) to install encrypted at-rest storage — takes ~30s."
|
|
@@ -266,6 +449,7 @@ export function registerWalletTools(server) {
|
|
|
266
449
|
defaultChain: defaultCh === "solana" ? "tempo" : defaultCh,
|
|
267
450
|
label: walletName,
|
|
268
451
|
});
|
|
452
|
+
setDefaultCryptoPaymentMethod(defaultCh);
|
|
269
453
|
const principal = await ensureConsumerPrincipal();
|
|
270
454
|
const owsNudge = platformSupportsOws()
|
|
271
455
|
? "\n\n⚠ Key stored in plaintext at ~/.agentwonderland/config.json. Run wallet_setup({ action: \"enable-ows\" }) to install encrypted at-rest storage — takes ~30s."
|
|
@@ -293,6 +477,7 @@ export function registerWalletTools(server) {
|
|
|
293
477
|
defaultChain: defaultCh,
|
|
294
478
|
label: walletName,
|
|
295
479
|
});
|
|
480
|
+
setDefaultCryptoPaymentMethod(defaultCh);
|
|
296
481
|
const principal = await ensureConsumerPrincipal();
|
|
297
482
|
return text([
|
|
298
483
|
`Wallet created [encrypted]:`,
|
|
@@ -321,6 +506,7 @@ export function registerWalletTools(server) {
|
|
|
321
506
|
defaultChain: defaultCh,
|
|
322
507
|
label: walletName,
|
|
323
508
|
});
|
|
509
|
+
setDefaultCryptoPaymentMethod(defaultCh);
|
|
324
510
|
const principal = await ensureConsumerPrincipal();
|
|
325
511
|
return text([
|
|
326
512
|
`Key imported to OWS [encrypted]:`,
|
|
@@ -349,6 +535,7 @@ export function registerWalletTools(server) {
|
|
|
349
535
|
defaultChain: "solana",
|
|
350
536
|
label: walletName,
|
|
351
537
|
});
|
|
538
|
+
setDefaultCryptoPaymentMethod("solana");
|
|
352
539
|
const principal = await ensureConsumerPrincipal();
|
|
353
540
|
const owsNudge = platformSupportsOws()
|
|
354
541
|
? "\n\n⚠ Key stored in plaintext at ~/.agentwonderland/config.json. Run wallet_setup({ action: \"enable-ows\" }) to install encrypted at-rest storage — takes ~30s."
|
|
@@ -382,6 +569,7 @@ export function registerWalletTools(server) {
|
|
|
382
569
|
defaultChain: defaultCh,
|
|
383
570
|
label: walletName,
|
|
384
571
|
});
|
|
572
|
+
setDefaultCryptoPaymentMethod(defaultCh);
|
|
385
573
|
const principal = await ensureConsumerPrincipal();
|
|
386
574
|
const owsNudge = platformSupportsOws()
|
|
387
575
|
? "\n\n⚠ Key stored in plaintext at ~/.agentwonderland/config.json. Run wallet_setup({ action: \"enable-ows\" }) to install encrypted at-rest storage — takes ~30s."
|
package/package.json
CHANGED
|
@@ -8,21 +8,25 @@ let currentCard = {
|
|
|
8
8
|
last4: "1111",
|
|
9
9
|
brand: "visa",
|
|
10
10
|
};
|
|
11
|
+
let currentLink: { paymentMethodId: string; label?: string } | null = null;
|
|
11
12
|
|
|
12
13
|
let currentDefaultWallet: any;
|
|
13
14
|
let currentWallets: any[] = [];
|
|
14
15
|
let currentResolvedMethod: { wallet: any; chain: string } | null = null;
|
|
16
|
+
let currentDefaultPaymentMethod = "card";
|
|
15
17
|
|
|
16
18
|
const createdFetches = [vi.fn(), vi.fn(), vi.fn(), vi.fn()];
|
|
17
19
|
const mockMppxCreate = vi.fn();
|
|
18
20
|
const mockStripe = vi.fn((opts: unknown) => opts);
|
|
19
21
|
const mockTempoChargeClient = vi.fn((..._args: unknown[]) => "tempo_method");
|
|
20
22
|
const mockBaseChargeClient = vi.fn((..._args: unknown[]) => "base_method");
|
|
23
|
+
const mockCreateLinkSharedPaymentToken = vi.fn(async (_config: unknown) => "spt_test");
|
|
21
24
|
|
|
22
25
|
vi.mock("../config.js", () => ({
|
|
23
26
|
getApiUrl: () => "http://api.test",
|
|
24
27
|
getCardConfig: () => currentCard,
|
|
25
|
-
|
|
28
|
+
getLinkConfig: () => currentLink,
|
|
29
|
+
getConfig: () => ({ defaultPaymentMethod: currentDefaultPaymentMethod }),
|
|
26
30
|
getDefaultWallet: () => currentDefaultWallet,
|
|
27
31
|
getWallets: () => currentWallets,
|
|
28
32
|
resolveWalletAndChain: () => currentResolvedMethod,
|
|
@@ -43,10 +47,15 @@ vi.mock("../base-charge.js", () => ({
|
|
|
43
47
|
baseChargeClient: (config: unknown) => mockBaseChargeClient(config),
|
|
44
48
|
}));
|
|
45
49
|
|
|
50
|
+
vi.mock("../link-cli.js", () => ({
|
|
51
|
+
createLinkSharedPaymentToken: (config: unknown) => mockCreateLinkSharedPaymentToken(config),
|
|
52
|
+
}));
|
|
53
|
+
|
|
46
54
|
describe("payment method initialization", () => {
|
|
47
55
|
beforeEach(() => {
|
|
48
56
|
vi.clearAllMocks();
|
|
49
57
|
vi.resetModules();
|
|
58
|
+
delete process.env.AGENTWONDERLAND_LINK_APPROVAL_LIMIT_CENTS;
|
|
50
59
|
currentCard = {
|
|
51
60
|
consumerToken: "consumer_one",
|
|
52
61
|
paymentMethodId: "pm_one",
|
|
@@ -56,6 +65,8 @@ describe("payment method initialization", () => {
|
|
|
56
65
|
currentDefaultWallet = undefined;
|
|
57
66
|
currentWallets = [];
|
|
58
67
|
currentResolvedMethod = null;
|
|
68
|
+
currentLink = null;
|
|
69
|
+
currentDefaultPaymentMethod = "card";
|
|
59
70
|
mockMppxCreate
|
|
60
71
|
.mockReturnValueOnce({ fetch: createdFetches[0] })
|
|
61
72
|
.mockReturnValueOnce({ fetch: createdFetches[1] })
|
|
@@ -116,4 +127,70 @@ describe("payment method initialization", () => {
|
|
|
116
127
|
expect.objectContaining({ methods: ["tempo_method"], polyfill: false }),
|
|
117
128
|
);
|
|
118
129
|
});
|
|
130
|
+
|
|
131
|
+
it("initializes Stripe SPT method when Link is requested", async () => {
|
|
132
|
+
currentLink = {
|
|
133
|
+
paymentMethodId: "csmrpd_link_123",
|
|
134
|
+
label: "Visa ****4242",
|
|
135
|
+
};
|
|
136
|
+
currentDefaultPaymentMethod = "link";
|
|
137
|
+
|
|
138
|
+
const { getPaymentFetch } = await import("../payments.js");
|
|
139
|
+
await getPaymentFetch("link");
|
|
140
|
+
|
|
141
|
+
expect(mockStripe).toHaveBeenCalledWith(
|
|
142
|
+
expect.objectContaining({ paymentMethod: "csmrpd_link_123" }),
|
|
143
|
+
);
|
|
144
|
+
expect(mockMppxCreate).toHaveBeenCalledWith(
|
|
145
|
+
expect.objectContaining({
|
|
146
|
+
methods: [expect.objectContaining({ paymentMethod: "csmrpd_link_123" })],
|
|
147
|
+
polyfill: false,
|
|
148
|
+
}),
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
const stripeConfig = mockStripe.mock.calls[0]?.[0] as {
|
|
152
|
+
createToken: (params: {
|
|
153
|
+
amount: string;
|
|
154
|
+
currency: string;
|
|
155
|
+
expiresAt: number;
|
|
156
|
+
metadata?: Record<string, string>;
|
|
157
|
+
networkId: string;
|
|
158
|
+
}) => Promise<string>;
|
|
159
|
+
};
|
|
160
|
+
await stripeConfig.createToken({
|
|
161
|
+
amount: "25",
|
|
162
|
+
currency: "usd",
|
|
163
|
+
expiresAt: 1778290000,
|
|
164
|
+
networkId: "profile_test",
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
expect(mockCreateLinkSharedPaymentToken).toHaveBeenCalledWith(
|
|
168
|
+
expect.objectContaining({
|
|
169
|
+
amount: "10000",
|
|
170
|
+
currency: "usd",
|
|
171
|
+
networkId: "profile_test",
|
|
172
|
+
paymentMethodId: "csmrpd_link_123",
|
|
173
|
+
}),
|
|
174
|
+
);
|
|
175
|
+
const linkTokenRequest = mockCreateLinkSharedPaymentToken.mock.calls[0]?.[0] as { context: string } | undefined;
|
|
176
|
+
expect(linkTokenRequest?.context).toContain("up to USD 100.00");
|
|
177
|
+
expect(linkTokenRequest?.context).toContain("quoted at USD 0.25");
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it("advertises Link as Stripe SPT compatibility", async () => {
|
|
181
|
+
currentLink = {
|
|
182
|
+
paymentMethodId: "csmrpd_link_123",
|
|
183
|
+
};
|
|
184
|
+
currentDefaultPaymentMethod = "link";
|
|
185
|
+
|
|
186
|
+
const { getAcceptedPaymentMethods, getCompatiblePaymentMethods } = await import("../payments.js");
|
|
187
|
+
|
|
188
|
+
expect(getAcceptedPaymentMethods()).toEqual(["stripe_card", "card"]);
|
|
189
|
+
expect(getCompatiblePaymentMethods({
|
|
190
|
+
payment: { accepted_payments: ["stripe_card"] },
|
|
191
|
+
} as any)).toEqual(["link"]);
|
|
192
|
+
expect(getCompatiblePaymentMethods({
|
|
193
|
+
payment: { accepted_payments: ["card"] },
|
|
194
|
+
} as any)).toEqual(["link"]);
|
|
195
|
+
});
|
|
119
196
|
});
|
|
@@ -114,6 +114,29 @@ describe("consumer principal helpers", () => {
|
|
|
114
114
|
);
|
|
115
115
|
});
|
|
116
116
|
|
|
117
|
+
it("uses the default consumer principal for Link payments", async () => {
|
|
118
|
+
state.wallets = [
|
|
119
|
+
{ id: "aw-main", keyType: "ows", owsWalletId: "ows-main", chains: ["tempo", "base"], defaultChain: "base" },
|
|
120
|
+
];
|
|
121
|
+
state.addresses = {
|
|
122
|
+
"aw-main": "0xBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB",
|
|
123
|
+
base: "0xBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB",
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const { getConsumerPrincipalForMethod } = await import("../principal.js");
|
|
127
|
+
const principal = await getConsumerPrincipalForMethod("link");
|
|
128
|
+
|
|
129
|
+
expect(principal).toBe("did:pkh:eip155:8453:0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb");
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it("creates a fallback identity wallet for Link payments when no wallet exists", async () => {
|
|
133
|
+
const { ensureConsumerPrincipalForMethod } = await import("../principal.js");
|
|
134
|
+
const principal = await ensureConsumerPrincipalForMethod("link");
|
|
135
|
+
|
|
136
|
+
expect(principal).toMatch(/^did:pkh:eip155:8453:0x[a-f0-9]{40}$/);
|
|
137
|
+
expect(state.addedWallets).toHaveLength(1);
|
|
138
|
+
});
|
|
139
|
+
|
|
117
140
|
it("returns the Base rebate principal when an EVM wallet is available", async () => {
|
|
118
141
|
state.wallets = [
|
|
119
142
|
{ id: "aw-main", keyType: "ows", owsWalletId: "ows-main", chains: ["tempo", "base", "solana"], defaultChain: "tempo" },
|
package/src/core/config.ts
CHANGED
|
@@ -21,6 +21,17 @@ export interface CardConfig {
|
|
|
21
21
|
brand: string;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
export interface LinkConfig {
|
|
25
|
+
paymentMethodId: string;
|
|
26
|
+
label?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface PendingLinkSetup {
|
|
30
|
+
verificationUrl: string;
|
|
31
|
+
phrase: string;
|
|
32
|
+
createdAt: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
24
35
|
export interface SpendPolicy {
|
|
25
36
|
maxPerTxUsd?: number;
|
|
26
37
|
maxPerDayUsd?: number;
|
|
@@ -42,6 +53,8 @@ export interface Config {
|
|
|
42
53
|
defaultWallet: string | null;
|
|
43
54
|
defaultPaymentMethod?: string;
|
|
44
55
|
card: CardConfig | null;
|
|
56
|
+
link: LinkConfig | null;
|
|
57
|
+
pendingLinkSetup?: PendingLinkSetup | null;
|
|
45
58
|
pendingCardSetupToken?: string | null;
|
|
46
59
|
favorites: string[];
|
|
47
60
|
/** Require user confirmation before spending. Default: true. Set false for headless/automated use. */
|
|
@@ -98,6 +111,8 @@ interface LegacyConfig {
|
|
|
98
111
|
wallets?: WalletEntry[];
|
|
99
112
|
defaultWallet?: string | null;
|
|
100
113
|
card?: CardConfig | null;
|
|
114
|
+
link?: LinkConfig | null;
|
|
115
|
+
pendingLinkSetup?: PendingLinkSetup | null;
|
|
101
116
|
pendingCardSetupToken?: string | null;
|
|
102
117
|
spendPolicies?: Record<string, SpendPolicy>;
|
|
103
118
|
spendLedger?: SpendLedgerEntry[];
|
|
@@ -119,6 +134,8 @@ function migrateIfNeeded(raw: LegacyConfig): Config {
|
|
|
119
134
|
defaultWallet: raw.defaultWallet ?? null,
|
|
120
135
|
defaultPaymentMethod: raw.defaultPaymentMethod ?? undefined,
|
|
121
136
|
card: raw.card ?? null,
|
|
137
|
+
link: raw.link ?? null,
|
|
138
|
+
pendingLinkSetup: raw.link ? null : (raw.pendingLinkSetup ?? null),
|
|
122
139
|
pendingCardSetupToken: (r.pendingCardSetupToken as string | null | undefined) ?? null,
|
|
123
140
|
favorites: r.favorites as string[] ?? [],
|
|
124
141
|
confirmBeforeSpend: r.confirmBeforeSpend !== false,
|
|
@@ -195,6 +212,8 @@ function migrateIfNeeded(raw: LegacyConfig): Config {
|
|
|
195
212
|
defaultWallet,
|
|
196
213
|
defaultPaymentMethod: raw.defaultPaymentMethod ?? undefined,
|
|
197
214
|
card,
|
|
215
|
+
link: raw.link ?? null,
|
|
216
|
+
pendingLinkSetup: raw.link ? null : (raw.pendingLinkSetup ?? null),
|
|
198
217
|
pendingCardSetupToken: null,
|
|
199
218
|
favorites: [],
|
|
200
219
|
confirmBeforeSpend: true,
|
|
@@ -222,6 +241,8 @@ export function getConfig(): Config {
|
|
|
222
241
|
wallets: [],
|
|
223
242
|
defaultWallet: null,
|
|
224
243
|
card: null,
|
|
244
|
+
link: null,
|
|
245
|
+
pendingLinkSetup: null,
|
|
225
246
|
pendingCardSetupToken: null,
|
|
226
247
|
favorites: [],
|
|
227
248
|
confirmBeforeSpend: true,
|
|
@@ -286,6 +307,10 @@ export function setSpendPolicy(method: string, policy: SpendPolicy): void {
|
|
|
286
307
|
saveConfig({ spendPolicies: policies });
|
|
287
308
|
}
|
|
288
309
|
|
|
310
|
+
export function setDefaultPaymentMethod(defaultPaymentMethod: string | undefined): void {
|
|
311
|
+
saveConfig({ defaultPaymentMethod });
|
|
312
|
+
}
|
|
313
|
+
|
|
289
314
|
export function getSpendLedger(): SpendLedgerEntry[] {
|
|
290
315
|
return getConfig().spendLedger ?? [];
|
|
291
316
|
}
|
|
@@ -399,6 +424,14 @@ export function getCardConfig(): CardConfig | null {
|
|
|
399
424
|
return getConfig().card;
|
|
400
425
|
}
|
|
401
426
|
|
|
427
|
+
export function getLinkConfig(): LinkConfig | null {
|
|
428
|
+
return getConfig().link;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
export function getPendingLinkSetup(): PendingLinkSetup | null {
|
|
432
|
+
return getConfig().pendingLinkSetup ?? null;
|
|
433
|
+
}
|
|
434
|
+
|
|
402
435
|
/**
|
|
403
436
|
* Save card configuration after setup.
|
|
404
437
|
*/
|
|
@@ -421,6 +454,29 @@ export function setCardConfig(card: CardConfig | null): void {
|
|
|
421
454
|
}
|
|
422
455
|
}
|
|
423
456
|
|
|
457
|
+
export function setLinkConfig(link: LinkConfig | null): void {
|
|
458
|
+
const current = getConfig();
|
|
459
|
+
if (link) {
|
|
460
|
+
saveConfig({
|
|
461
|
+
link,
|
|
462
|
+
defaultPaymentMethod: "link",
|
|
463
|
+
pendingLinkSetup: null,
|
|
464
|
+
});
|
|
465
|
+
} else {
|
|
466
|
+
saveConfig({
|
|
467
|
+
link,
|
|
468
|
+
pendingLinkSetup: null,
|
|
469
|
+
defaultPaymentMethod: current.defaultPaymentMethod === "link"
|
|
470
|
+
? undefined
|
|
471
|
+
: current.defaultPaymentMethod,
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
export function setPendingLinkSetup(pendingLinkSetup: PendingLinkSetup | null): void {
|
|
477
|
+
saveConfig({ pendingLinkSetup });
|
|
478
|
+
}
|
|
479
|
+
|
|
424
480
|
export function getPendingCardSetupToken(): string | null {
|
|
425
481
|
return getConfig().pendingCardSetupToken ?? null;
|
|
426
482
|
}
|