@agentwonderland/mcp 0.1.22 → 0.1.24
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__/card-setup.test.d.ts +1 -0
- package/dist/core/__tests__/card-setup.test.js +99 -0
- package/dist/core/__tests__/formatters.test.d.ts +1 -0
- package/dist/core/__tests__/formatters.test.js +15 -0
- package/dist/core/__tests__/passes.test.d.ts +1 -0
- package/dist/core/__tests__/passes.test.js +82 -0
- package/dist/core/__tests__/payments.test.d.ts +1 -0
- package/dist/core/__tests__/payments.test.js +52 -0
- package/dist/core/__tests__/principal.test.d.ts +1 -0
- package/dist/core/__tests__/principal.test.js +67 -0
- package/dist/core/api-client.d.ts +9 -4
- package/dist/core/api-client.js +52 -22
- package/dist/core/card-setup.d.ts +20 -13
- package/dist/core/card-setup.js +87 -30
- package/dist/core/config.d.ts +4 -0
- package/dist/core/config.js +28 -1
- package/dist/core/formatters.d.ts +2 -0
- package/dist/core/formatters.js +5 -1
- package/dist/core/index.d.ts +2 -0
- package/dist/core/index.js +2 -0
- package/dist/core/ows-adapter.d.ts +10 -2
- package/dist/core/ows-adapter.js +54 -10
- package/dist/core/passes.d.ts +40 -0
- package/dist/core/passes.js +32 -0
- package/dist/core/payments.d.ts +8 -0
- package/dist/core/payments.js +121 -16
- package/dist/core/principal.d.ts +2 -0
- package/dist/core/principal.js +109 -0
- package/dist/core/solana-charge.d.ts +9 -0
- package/dist/core/solana-charge.js +95 -0
- package/dist/core/types.d.ts +10 -0
- package/dist/index.js +13 -4
- package/dist/prompts/index.js +1 -1
- package/dist/resources/wallet.js +8 -1
- package/dist/tools/__tests__/_payment-confirmation.test.d.ts +1 -0
- package/dist/tools/__tests__/_payment-confirmation.test.js +30 -0
- package/dist/tools/_payment-confirmation.d.ts +6 -0
- package/dist/tools/_payment-confirmation.js +28 -0
- package/dist/tools/agent-info.js +14 -0
- package/dist/tools/index.d.ts +1 -0
- package/dist/tools/index.js +1 -0
- package/dist/tools/passes.d.ts +2 -0
- package/dist/tools/passes.js +157 -0
- package/dist/tools/run.js +116 -49
- package/dist/tools/solve.js +102 -44
- package/dist/tools/wallet.js +85 -50
- package/package.json +3 -1
- package/src/core/__tests__/card-setup.test.ts +118 -0
- package/src/core/__tests__/formatters.test.ts +17 -0
- package/src/core/__tests__/passes.test.ts +94 -0
- package/src/core/__tests__/payments.test.ts +60 -0
- package/src/core/__tests__/principal.test.ts +87 -0
- package/src/core/api-client.ts +70 -23
- package/src/core/card-setup.ts +112 -35
- package/src/core/config.ts +33 -2
- package/src/core/formatters.ts +7 -1
- package/src/core/index.ts +2 -0
- package/src/core/ows-adapter.ts +74 -8
- package/src/core/passes.ts +74 -0
- package/src/core/payments.ts +140 -15
- package/src/core/principal.ts +128 -0
- package/src/core/solana-charge.ts +149 -0
- package/src/core/types.ts +10 -0
- package/src/index.ts +13 -4
- package/src/prompts/index.ts +1 -1
- package/src/resources/wallet.ts +8 -1
- package/src/tools/__tests__/_payment-confirmation.test.ts +45 -0
- package/src/tools/_payment-confirmation.ts +52 -0
- package/src/tools/agent-info.ts +23 -0
- package/src/tools/index.ts +1 -0
- package/src/tools/passes.ts +234 -0
- package/src/tools/run.ts +174 -53
- package/src/tools/solve.ts +149 -56
- package/src/tools/wallet.ts +102 -52
package/dist/core/payments.js
CHANGED
|
@@ -9,9 +9,21 @@
|
|
|
9
9
|
* Users can configure multiple wallets with different chains and select
|
|
10
10
|
* which to use per-request via `--pay-with <wallet-id|chain|card>`.
|
|
11
11
|
*/
|
|
12
|
-
import { getWallets, getDefaultWallet, getCardConfig, resolveWalletAndChain, getApiUrl, } from "./config.js";
|
|
12
|
+
import { getConfig, getWallets, getDefaultWallet, getCardConfig, resolveWalletAndChain, getApiUrl, } from "./config.js";
|
|
13
13
|
// Cache per wallet+chain combo to avoid re-initializing
|
|
14
14
|
const fetchCache = new Map();
|
|
15
|
+
const REGISTRY_METHOD_MAP = {
|
|
16
|
+
tempo: "tempo_usdc",
|
|
17
|
+
base: "base_usdc",
|
|
18
|
+
solana: "solana_usdc",
|
|
19
|
+
card: "stripe_card",
|
|
20
|
+
};
|
|
21
|
+
const METHOD_REGISTRY_MAP = {
|
|
22
|
+
tempo_usdc: "tempo",
|
|
23
|
+
base_usdc: "base",
|
|
24
|
+
solana_usdc: "solana",
|
|
25
|
+
stripe_card: "card",
|
|
26
|
+
};
|
|
15
27
|
// ── Helpers ─────────────────────────────────────────────────────
|
|
16
28
|
function normalizeKey(key) {
|
|
17
29
|
return (key.startsWith("0x") ? key : `0x${key}`);
|
|
@@ -19,6 +31,19 @@ function normalizeKey(key) {
|
|
|
19
31
|
function cacheKey(walletId, chain) {
|
|
20
32
|
return `${walletId}:${chain}`;
|
|
21
33
|
}
|
|
34
|
+
function cardCacheKey() {
|
|
35
|
+
const card = getCardConfig();
|
|
36
|
+
if (!card)
|
|
37
|
+
return null;
|
|
38
|
+
return `card:${getApiUrl()}:${card.consumerToken}:${card.paymentMethodId ?? ""}`;
|
|
39
|
+
}
|
|
40
|
+
function clearStaleCardCache(activeKey) {
|
|
41
|
+
for (const key of fetchCache.keys()) {
|
|
42
|
+
if (key.startsWith("card:") && key !== activeKey) {
|
|
43
|
+
fetchCache.delete(key);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
22
47
|
// ── Per-protocol initializers ───────────────────────────────────
|
|
23
48
|
async function initMpp(wallet) {
|
|
24
49
|
try {
|
|
@@ -43,6 +68,22 @@ async function initMpp(wallet) {
|
|
|
43
68
|
return null;
|
|
44
69
|
}
|
|
45
70
|
}
|
|
71
|
+
async function initSolanaMpp(wallet) {
|
|
72
|
+
try {
|
|
73
|
+
if (wallet.keyType !== "ows" && !wallet.key) {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
const { Mppx } = await import("mppx/client");
|
|
77
|
+
const { solanaChargeClient } = await import("./solana-charge.js");
|
|
78
|
+
const mppx = Mppx.create({
|
|
79
|
+
methods: [solanaChargeClient({ wallet })],
|
|
80
|
+
});
|
|
81
|
+
return mppx.fetch.bind(mppx);
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
46
87
|
async function initCard() {
|
|
47
88
|
const cardConfig = getCardConfig();
|
|
48
89
|
if (!cardConfig)
|
|
@@ -82,7 +123,10 @@ async function initCard() {
|
|
|
82
123
|
/**
|
|
83
124
|
* Initialize a payment-aware fetch for a given wallet + chain.
|
|
84
125
|
*/
|
|
85
|
-
async function initForChain(wallet,
|
|
126
|
+
async function initForChain(wallet, chain) {
|
|
127
|
+
if (chain === "solana") {
|
|
128
|
+
return initSolanaMpp(wallet);
|
|
129
|
+
}
|
|
86
130
|
return initMpp(wallet);
|
|
87
131
|
}
|
|
88
132
|
// ── Public API ──────────────────────────────────────────────────
|
|
@@ -95,7 +139,11 @@ async function initForChain(wallet, _chain) {
|
|
|
95
139
|
export async function getPaymentFetch(method) {
|
|
96
140
|
// Card payment
|
|
97
141
|
if (method === "card") {
|
|
98
|
-
const ck =
|
|
142
|
+
const ck = cardCacheKey();
|
|
143
|
+
clearStaleCardCache(ck ?? undefined);
|
|
144
|
+
if (!ck) {
|
|
145
|
+
throw new Error('Payment method "card" is not configured. Use the wallet_setup tool to configure a wallet');
|
|
146
|
+
}
|
|
99
147
|
if (fetchCache.has(ck))
|
|
100
148
|
return fetchCache.get(ck);
|
|
101
149
|
const pf = await initCard();
|
|
@@ -121,11 +169,21 @@ export async function getPaymentFetch(method) {
|
|
|
121
169
|
}
|
|
122
170
|
throw new Error(`Payment method "${method}" is not configured. Use the wallet_setup tool to configure a wallet`);
|
|
123
171
|
}
|
|
124
|
-
// Auto-detect: try
|
|
172
|
+
// Auto-detect: try configured methods in order (card first if default)
|
|
125
173
|
const configured = getConfiguredMethods();
|
|
174
|
+
const defaultMethod = getConfig().defaultPaymentMethod;
|
|
126
175
|
for (const m of configured) {
|
|
127
176
|
if (m === "card") {
|
|
128
|
-
const ck =
|
|
177
|
+
const ck = cardCacheKey();
|
|
178
|
+
clearStaleCardCache(ck ?? undefined);
|
|
179
|
+
if (!ck) {
|
|
180
|
+
if (m === defaultMethod) {
|
|
181
|
+
const others = configured.filter((x) => x !== m);
|
|
182
|
+
const altText = others.length > 0 ? ` Available alternatives: ${others.join(", ")}` : "";
|
|
183
|
+
throw new Error(`Card payment failed to initialize. Check your card with wallet_status.${altText}`);
|
|
184
|
+
}
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
129
187
|
if (fetchCache.has(ck))
|
|
130
188
|
return fetchCache.get(ck);
|
|
131
189
|
const pf = await initCard();
|
|
@@ -133,12 +191,23 @@ export async function getPaymentFetch(method) {
|
|
|
133
191
|
fetchCache.set(ck, pf);
|
|
134
192
|
return pf;
|
|
135
193
|
}
|
|
194
|
+
// If the default method fails, throw instead of silently falling back
|
|
195
|
+
if (m === defaultMethod) {
|
|
196
|
+
const others = configured.filter((x) => x !== m);
|
|
197
|
+
const altText = others.length > 0 ? ` Available alternatives: ${others.join(", ")}` : "";
|
|
198
|
+
throw new Error(`Card payment failed to initialize. Check your card with wallet_status.${altText}`);
|
|
199
|
+
}
|
|
136
200
|
continue;
|
|
137
201
|
}
|
|
138
202
|
// It's a chain name — resolve to wallet
|
|
139
203
|
const resolved = resolveWalletAndChain(m);
|
|
140
|
-
if (!resolved)
|
|
204
|
+
if (!resolved) {
|
|
205
|
+
if (m === defaultMethod) {
|
|
206
|
+
const others = configured.filter((x) => x !== m);
|
|
207
|
+
throw new Error(`Default payment method "${m}" is not configured.${others.length ? ` Alternatives: ${others.join(", ")}` : ""}`);
|
|
208
|
+
}
|
|
141
209
|
continue;
|
|
210
|
+
}
|
|
142
211
|
const ck = cacheKey(resolved.wallet.id, resolved.chain);
|
|
143
212
|
if (fetchCache.has(ck))
|
|
144
213
|
return fetchCache.get(ck);
|
|
@@ -182,8 +251,27 @@ export function getConfiguredMethods() {
|
|
|
182
251
|
if (getCardConfig()) {
|
|
183
252
|
methods.push("card");
|
|
184
253
|
}
|
|
254
|
+
// Respect defaultPaymentMethod — move it to front of list
|
|
255
|
+
const defaultMethod = getConfig().defaultPaymentMethod;
|
|
256
|
+
if (defaultMethod) {
|
|
257
|
+
const idx = methods.indexOf(defaultMethod);
|
|
258
|
+
if (idx > 0) {
|
|
259
|
+
methods.splice(idx, 1);
|
|
260
|
+
methods.unshift(defaultMethod);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
185
263
|
return methods;
|
|
186
264
|
}
|
|
265
|
+
/**
|
|
266
|
+
* Normalize a requested payment method into the MCP-visible method identifier.
|
|
267
|
+
* Accepts chain names, wallet IDs, or "card".
|
|
268
|
+
*/
|
|
269
|
+
export function normalizePaymentMethod(method) {
|
|
270
|
+
if (method === "card")
|
|
271
|
+
return "card";
|
|
272
|
+
const resolved = resolveWalletAndChain(method);
|
|
273
|
+
return resolved?.chain ?? null;
|
|
274
|
+
}
|
|
187
275
|
/**
|
|
188
276
|
* Human-friendly display name for a payment method identifier.
|
|
189
277
|
*/
|
|
@@ -203,13 +291,25 @@ export function paymentMethodDisplayName(method) {
|
|
|
203
291
|
*/
|
|
204
292
|
export function getAcceptedPaymentMethods() {
|
|
205
293
|
const methods = getConfiguredMethods();
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
294
|
+
return methods
|
|
295
|
+
.map((m) => REGISTRY_METHOD_MAP[m])
|
|
296
|
+
.filter(Boolean);
|
|
297
|
+
}
|
|
298
|
+
export function toRegistryPaymentMethod(method) {
|
|
299
|
+
const normalized = normalizePaymentMethod(method);
|
|
300
|
+
if (!normalized)
|
|
301
|
+
return null;
|
|
302
|
+
return REGISTRY_METHOD_MAP[normalized] ?? null;
|
|
303
|
+
}
|
|
304
|
+
export function getCompatiblePaymentMethods(agent, configuredMethods = getConfiguredMethods()) {
|
|
305
|
+
const acceptedPayments = agent?.payment?.accepted_payments;
|
|
306
|
+
if (!Array.isArray(acceptedPayments) || acceptedPayments.length === 0) {
|
|
307
|
+
return [...configuredMethods];
|
|
308
|
+
}
|
|
309
|
+
const acceptedMethods = new Set(acceptedPayments
|
|
310
|
+
.map((payment) => METHOD_REGISTRY_MAP[payment])
|
|
311
|
+
.filter(Boolean));
|
|
312
|
+
return configuredMethods.filter((method) => acceptedMethods.has(method));
|
|
213
313
|
}
|
|
214
314
|
/**
|
|
215
315
|
* Check whether any payment method is configured.
|
|
@@ -221,27 +321,32 @@ export function hasWalletConfigured() {
|
|
|
221
321
|
* Get address for a specific method, or the first configured one.
|
|
222
322
|
*/
|
|
223
323
|
export async function getWalletAddress(method) {
|
|
324
|
+
let chain;
|
|
224
325
|
let wallet;
|
|
225
326
|
if (method && method !== "card") {
|
|
226
327
|
const resolved = resolveWalletAndChain(method);
|
|
227
328
|
wallet = resolved?.wallet;
|
|
329
|
+
chain = resolved?.chain;
|
|
228
330
|
}
|
|
229
331
|
else if (!method) {
|
|
230
332
|
wallet = getDefaultWallet();
|
|
333
|
+
chain = wallet?.defaultChain ?? wallet?.chains[0];
|
|
231
334
|
}
|
|
232
335
|
if (!wallet)
|
|
233
336
|
return null;
|
|
234
337
|
// OWS-managed wallet: derive address via the adapter
|
|
235
338
|
if (wallet.keyType === "ows" && wallet.owsWalletId) {
|
|
236
339
|
try {
|
|
237
|
-
const {
|
|
238
|
-
|
|
239
|
-
return account.address;
|
|
340
|
+
const { getOwsWalletAddress } = await import("./ows-adapter.js");
|
|
341
|
+
return await getOwsWalletAddress(wallet.owsWalletId, chain === "solana" ? "solana" : "evm");
|
|
240
342
|
}
|
|
241
343
|
catch {
|
|
242
344
|
return null;
|
|
243
345
|
}
|
|
244
346
|
}
|
|
347
|
+
if (chain === "solana") {
|
|
348
|
+
return null;
|
|
349
|
+
}
|
|
245
350
|
// Raw EVM key path
|
|
246
351
|
if (!wallet.key)
|
|
247
352
|
return null;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { addWallet, getDefaultWallet, getWallets } from "./config.js";
|
|
2
|
+
import { getWalletAddress } from "./payments.js";
|
|
3
|
+
import { createOwsWallet, isOwsAvailable, listOwsWallets, } from "./ows-adapter.js";
|
|
4
|
+
const BASE_CHAIN_ID = "8453";
|
|
5
|
+
const SOLANA_CHAIN_ID = "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp";
|
|
6
|
+
const AUTO_IDENTITY_LABEL = "AW Identity";
|
|
7
|
+
function principalFromAddress(address, type) {
|
|
8
|
+
if (type === "evm") {
|
|
9
|
+
return `did:pkh:eip155:${BASE_CHAIN_ID}:${address.toLowerCase()}`;
|
|
10
|
+
}
|
|
11
|
+
return `did:pkh:${SOLANA_CHAIN_ID}:${address}`;
|
|
12
|
+
}
|
|
13
|
+
function walletSupportsEvm(wallet) {
|
|
14
|
+
return wallet.chains.some((chain) => chain !== "solana");
|
|
15
|
+
}
|
|
16
|
+
function walletSupportsSolana(wallet) {
|
|
17
|
+
return wallet.chains.includes("solana");
|
|
18
|
+
}
|
|
19
|
+
async function walletPrincipal(wallet) {
|
|
20
|
+
if (walletSupportsEvm(wallet)) {
|
|
21
|
+
const address = await getWalletAddress(wallet.id);
|
|
22
|
+
return address ? principalFromAddress(address, "evm") : null;
|
|
23
|
+
}
|
|
24
|
+
if (walletSupportsSolana(wallet)) {
|
|
25
|
+
const address = await getWalletAddress(wallet.id);
|
|
26
|
+
return address ? principalFromAddress(address, "solana") : null;
|
|
27
|
+
}
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
function preferredWallets() {
|
|
31
|
+
const wallets = getWallets();
|
|
32
|
+
const defaultWallet = getDefaultWallet();
|
|
33
|
+
const ordered = defaultWallet
|
|
34
|
+
? [defaultWallet, ...wallets.filter((wallet) => wallet.id !== defaultWallet.id)]
|
|
35
|
+
: wallets;
|
|
36
|
+
return ordered.sort((a, b) => {
|
|
37
|
+
const aScore = walletSupportsEvm(a) ? 0 : walletSupportsSolana(a) ? 1 : 2;
|
|
38
|
+
const bScore = walletSupportsEvm(b) ? 0 : walletSupportsSolana(b) ? 1 : 2;
|
|
39
|
+
return aScore - bScore;
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
async function createFallbackIdentityWallet() {
|
|
43
|
+
const { generatePrivateKey } = await import("viem/accounts");
|
|
44
|
+
const walletName = `aw-identity-${Date.now()}`;
|
|
45
|
+
const entry = {
|
|
46
|
+
id: walletName,
|
|
47
|
+
keyType: "evm",
|
|
48
|
+
key: generatePrivateKey(),
|
|
49
|
+
chains: ["tempo", "base"],
|
|
50
|
+
defaultChain: "base",
|
|
51
|
+
label: AUTO_IDENTITY_LABEL,
|
|
52
|
+
};
|
|
53
|
+
addWallet(entry);
|
|
54
|
+
return entry;
|
|
55
|
+
}
|
|
56
|
+
async function ensureIdentityWallet() {
|
|
57
|
+
const wallets = preferredWallets();
|
|
58
|
+
const existing = wallets.find((wallet) => walletSupportsEvm(wallet) || walletSupportsSolana(wallet));
|
|
59
|
+
if (existing)
|
|
60
|
+
return existing;
|
|
61
|
+
if (await isOwsAvailable()) {
|
|
62
|
+
const existingOwsWallets = await listOwsWallets();
|
|
63
|
+
const linked = existingOwsWallets[0];
|
|
64
|
+
if (linked) {
|
|
65
|
+
const entry = {
|
|
66
|
+
id: linked.name,
|
|
67
|
+
keyType: "ows",
|
|
68
|
+
owsWalletId: linked.id,
|
|
69
|
+
chains: ["tempo", "base"],
|
|
70
|
+
defaultChain: "base",
|
|
71
|
+
label: linked.name,
|
|
72
|
+
};
|
|
73
|
+
addWallet(entry);
|
|
74
|
+
return entry;
|
|
75
|
+
}
|
|
76
|
+
const walletName = `aw-identity-${Date.now()}`;
|
|
77
|
+
const created = await createOwsWallet(walletName, "evm");
|
|
78
|
+
const entry = {
|
|
79
|
+
id: walletName,
|
|
80
|
+
keyType: "ows",
|
|
81
|
+
owsWalletId: created.walletId,
|
|
82
|
+
chains: ["tempo", "base"],
|
|
83
|
+
defaultChain: "base",
|
|
84
|
+
label: AUTO_IDENTITY_LABEL,
|
|
85
|
+
};
|
|
86
|
+
addWallet(entry);
|
|
87
|
+
return entry;
|
|
88
|
+
}
|
|
89
|
+
return createFallbackIdentityWallet();
|
|
90
|
+
}
|
|
91
|
+
export async function getConsumerPrincipal() {
|
|
92
|
+
for (const wallet of preferredWallets()) {
|
|
93
|
+
const principal = await walletPrincipal(wallet);
|
|
94
|
+
if (principal)
|
|
95
|
+
return principal;
|
|
96
|
+
}
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
export async function ensureConsumerPrincipal() {
|
|
100
|
+
const existing = await getConsumerPrincipal();
|
|
101
|
+
if (existing)
|
|
102
|
+
return existing;
|
|
103
|
+
const identityWallet = await ensureIdentityWallet();
|
|
104
|
+
const principal = await walletPrincipal(identityWallet);
|
|
105
|
+
if (!principal) {
|
|
106
|
+
throw new Error("Could not derive a consumer principal from the configured identity wallet.");
|
|
107
|
+
}
|
|
108
|
+
return principal;
|
|
109
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { WalletEntry } from "./config.js";
|
|
2
|
+
export declare const SOLANA_USDC_MINT: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
|
|
3
|
+
export declare const SOLANA_CHAIN_ID: "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp";
|
|
4
|
+
interface SolanaChargeClientConfig {
|
|
5
|
+
wallet: WalletEntry;
|
|
6
|
+
rpcUrl?: string;
|
|
7
|
+
}
|
|
8
|
+
export declare function solanaChargeClient(config: SolanaChargeClientConfig): any;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom MPP payment method: Solana USDC charge (client-side).
|
|
3
|
+
*
|
|
4
|
+
* Sends an SPL Token transfer on Solana mainnet and returns the transaction
|
|
5
|
+
* signature as the payment credential.
|
|
6
|
+
*/
|
|
7
|
+
import { Credential, Method, z } from "mppx";
|
|
8
|
+
import { Connection, Keypair, PublicKey, Transaction, sendAndConfirmTransaction } from "@solana/web3.js";
|
|
9
|
+
import { ASSOCIATED_TOKEN_PROGRAM_ID, TOKEN_PROGRAM_ID, createTransferCheckedInstruction, getAssociatedTokenAddressSync, } from "@solana/spl-token";
|
|
10
|
+
export const SOLANA_USDC_MINT = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
|
|
11
|
+
export const SOLANA_CHAIN_ID = "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp";
|
|
12
|
+
const SOLANA_RPC = "https://api.mainnet-beta.solana.com";
|
|
13
|
+
const solanaChargeMethod = Method.from({
|
|
14
|
+
name: "solana",
|
|
15
|
+
intent: "charge",
|
|
16
|
+
schema: {
|
|
17
|
+
credential: {
|
|
18
|
+
payload: z.object({
|
|
19
|
+
signature: z.string(),
|
|
20
|
+
type: z.literal("signature"),
|
|
21
|
+
}),
|
|
22
|
+
},
|
|
23
|
+
request: z.pipe(z.object({
|
|
24
|
+
amount: z.string(),
|
|
25
|
+
currency: z.string(),
|
|
26
|
+
recipient: z.string(),
|
|
27
|
+
chainId: z.optional(z.string()),
|
|
28
|
+
decimals: z.optional(z.number()),
|
|
29
|
+
}), z.transform((value) => ({
|
|
30
|
+
...value,
|
|
31
|
+
methodDetails: { chainId: value.chainId ?? SOLANA_CHAIN_ID },
|
|
32
|
+
}))),
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
function keypairFromPrivateKeyHex(privateKeyHex) {
|
|
36
|
+
const normalized = privateKeyHex.startsWith("0x")
|
|
37
|
+
? privateKeyHex.slice(2)
|
|
38
|
+
: privateKeyHex;
|
|
39
|
+
const bytes = Uint8Array.from(Buffer.from(normalized, "hex"));
|
|
40
|
+
if (bytes.length === 32) {
|
|
41
|
+
return Keypair.fromSeed(bytes);
|
|
42
|
+
}
|
|
43
|
+
if (bytes.length === 64) {
|
|
44
|
+
return Keypair.fromSecretKey(bytes);
|
|
45
|
+
}
|
|
46
|
+
throw new Error(`Unsupported Solana private key length: ${bytes.length} bytes.`);
|
|
47
|
+
}
|
|
48
|
+
async function getKeypair(wallet) {
|
|
49
|
+
if (wallet.keyType === "ows" && wallet.owsWalletId) {
|
|
50
|
+
const { owsSolanaKeypairFromWalletId } = await import("./ows-adapter.js");
|
|
51
|
+
return owsSolanaKeypairFromWalletId(wallet.owsWalletId);
|
|
52
|
+
}
|
|
53
|
+
if (wallet.key) {
|
|
54
|
+
return keypairFromPrivateKeyHex(wallet.key);
|
|
55
|
+
}
|
|
56
|
+
throw new Error(`Wallet "${wallet.id}" cannot sign Solana transactions.`);
|
|
57
|
+
}
|
|
58
|
+
async function resolveRecipientTokenAccount(connection, mint, recipient) {
|
|
59
|
+
const accountInfo = await connection.getParsedAccountInfo(recipient, "confirmed");
|
|
60
|
+
const owner = accountInfo.value?.owner;
|
|
61
|
+
if (owner && owner.toBase58() === TOKEN_PROGRAM_ID.toBase58()) {
|
|
62
|
+
return recipient;
|
|
63
|
+
}
|
|
64
|
+
return getAssociatedTokenAddressSync(mint, recipient, false, TOKEN_PROGRAM_ID, ASSOCIATED_TOKEN_PROGRAM_ID);
|
|
65
|
+
}
|
|
66
|
+
export function solanaChargeClient(config) {
|
|
67
|
+
return Method.toClient(solanaChargeMethod, {
|
|
68
|
+
async createCredential({ challenge }) {
|
|
69
|
+
const { request } = challenge;
|
|
70
|
+
const amount = BigInt(request.amount);
|
|
71
|
+
const decimals = request.decimals ?? 6;
|
|
72
|
+
const mint = new PublicKey(request.currency ?? SOLANA_USDC_MINT);
|
|
73
|
+
const recipient = new PublicKey(request.recipient);
|
|
74
|
+
const keypair = await getKeypair(config.wallet);
|
|
75
|
+
const owner = keypair.publicKey;
|
|
76
|
+
const connection = new Connection(config.rpcUrl ?? SOLANA_RPC, "confirmed");
|
|
77
|
+
const sourceTokenAccount = getAssociatedTokenAddressSync(mint, owner, false, TOKEN_PROGRAM_ID, ASSOCIATED_TOKEN_PROGRAM_ID);
|
|
78
|
+
const destinationTokenAccount = await resolveRecipientTokenAccount(connection, mint, recipient);
|
|
79
|
+
const instruction = createTransferCheckedInstruction(sourceTokenAccount, mint, destinationTokenAccount, owner, amount, decimals);
|
|
80
|
+
const { blockhash } = await connection.getLatestBlockhash("confirmed");
|
|
81
|
+
const transaction = new Transaction({
|
|
82
|
+
feePayer: owner,
|
|
83
|
+
recentBlockhash: blockhash,
|
|
84
|
+
}).add(instruction);
|
|
85
|
+
const signature = await sendAndConfirmTransaction(connection, transaction, [keypair], {
|
|
86
|
+
commitment: "confirmed",
|
|
87
|
+
});
|
|
88
|
+
return Credential.serialize({
|
|
89
|
+
challenge,
|
|
90
|
+
payload: { signature, type: "signature" },
|
|
91
|
+
source: `did:pkh:${SOLANA_CHAIN_ID}:${owner.toBase58()}`,
|
|
92
|
+
});
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
}
|
package/dist/core/types.d.ts
CHANGED
|
@@ -21,6 +21,16 @@ export interface AgentRecord {
|
|
|
21
21
|
};
|
|
22
22
|
payment?: {
|
|
23
23
|
pricing?: Record<string, unknown>;
|
|
24
|
+
accepted_payments?: string[];
|
|
25
|
+
credit_packs?: {
|
|
26
|
+
unit_type?: string;
|
|
27
|
+
packs?: Array<{
|
|
28
|
+
key?: string;
|
|
29
|
+
name?: string;
|
|
30
|
+
included_units?: number;
|
|
31
|
+
price_usd?: string;
|
|
32
|
+
}>;
|
|
33
|
+
} | null;
|
|
24
34
|
[key: string]: unknown;
|
|
25
35
|
};
|
|
26
36
|
[key: string]: unknown;
|
package/dist/index.js
CHANGED
|
@@ -11,6 +11,7 @@ import { registerRateTools } from "./tools/rate.js";
|
|
|
11
11
|
import { registerWalletTools } from "./tools/wallet.js";
|
|
12
12
|
import { registerFavoriteTools } from "./tools/favorites.js";
|
|
13
13
|
import { registerTipTools } from "./tools/tip.js";
|
|
14
|
+
import { registerPassTools } from "./tools/passes.js";
|
|
14
15
|
// ── Resources ────────────────────────────────────────────────────
|
|
15
16
|
import { registerAgentResources } from "./resources/agents.js";
|
|
16
17
|
import { registerWalletResources } from "./resources/wallet.js";
|
|
@@ -33,14 +34,21 @@ export async function startMcpServer() {
|
|
|
33
34
|
"1. search_agents() or solve() — find agents for the task",
|
|
34
35
|
"2. get_agent() — check required input fields before running",
|
|
35
36
|
"3. run_agent() or solve() with ALL required fields",
|
|
36
|
-
"
|
|
37
|
+
"3a. If the agent offers discounted credit packs, use buy_agent_credit_pack() and list_agent_credit_packs() when helpful",
|
|
38
|
+
"4. If no payment method is set up, run_agent returns a setup page to connect a credit card — present it to the user",
|
|
37
39
|
"5. Ask user to rate or tip after a successful run",
|
|
38
40
|
"",
|
|
39
41
|
"PAYMENT:",
|
|
40
|
-
"- Credit/debit card is the easiest way to pay —
|
|
41
|
-
"- Crypto wallets (Tempo USDC, Base USDC) are
|
|
42
|
+
"- Credit/debit card is the default and easiest way to pay — open the setup page to connect, no funding needed.",
|
|
43
|
+
"- Crypto wallets (Tempo USDC, Base USDC, Solana USDC) are available for advanced users.",
|
|
44
|
+
"- Card and crypto are SEPARATE payment methods. Card charges a credit card; crypto sends USDC on-chain.",
|
|
45
|
+
"- Card is set as the default payment method when configured.",
|
|
46
|
+
"- If multiple valid payment methods are available for a run and pay_with is not specified, ask the user which one to use.",
|
|
47
|
+
"- For fully agentic execution, include pay_with explicitly.",
|
|
42
48
|
"- 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
|
|
49
|
+
"- Do NOT ask the user to set up payment before they try to run an agent. Let them explore freely.",
|
|
50
|
+
"- If a specific payment method fails, report the error clearly. Do NOT silently fall back to a different method.",
|
|
51
|
+
"- When payment fails, suggest alternatives using wallet_status.",
|
|
44
52
|
"",
|
|
45
53
|
"MANAGING PAYMENT METHODS:",
|
|
46
54
|
"- To remove a card: wallet_setup({ action: \"remove-card\" })",
|
|
@@ -61,6 +69,7 @@ export async function startMcpServer() {
|
|
|
61
69
|
registerWalletTools(server);
|
|
62
70
|
registerFavoriteTools(server);
|
|
63
71
|
registerTipTools(server);
|
|
72
|
+
registerPassTools(server);
|
|
64
73
|
// Register resources
|
|
65
74
|
registerAgentResources(server);
|
|
66
75
|
registerWalletResources(server);
|
package/dist/prompts/index.js
CHANGED
|
@@ -12,7 +12,7 @@ export function registerPrompts(server) {
|
|
|
12
12
|
"1. Check my wallet status with wallet_status",
|
|
13
13
|
"2. If no wallet is configured, help me create one with wallet_setup",
|
|
14
14
|
"3. Show me some popular agents with search_agents",
|
|
15
|
-
"4. Briefly explain how solve, run_agent, rating, and tipping work",
|
|
15
|
+
"4. Briefly explain how solve, run_agent, buy_agent_credit_pack, rating, and tipping work",
|
|
16
16
|
].join("\n"),
|
|
17
17
|
},
|
|
18
18
|
}],
|
package/dist/resources/wallet.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import { getWallets, getCardConfig } from "../core/config.js";
|
|
1
|
+
import { getWallets, getCardConfig, getPendingCardSetupToken } from "../core/config.js";
|
|
2
|
+
import { getCardCapabilities } from "../core/card-setup.js";
|
|
2
3
|
import { getWalletAddress, getConfiguredMethods } from "../core/payments.js";
|
|
3
4
|
export function registerWalletResources(server) {
|
|
4
5
|
server.resource("wallet-config", "aw://wallet", async () => {
|
|
5
6
|
const wallets = getWallets();
|
|
6
7
|
const card = getCardConfig();
|
|
8
|
+
const pendingCardSetupToken = getPendingCardSetupToken();
|
|
7
9
|
const methods = getConfiguredMethods();
|
|
8
10
|
const lines = ["Wallet Configuration", ""];
|
|
9
11
|
for (const w of wallets) {
|
|
@@ -13,6 +15,11 @@ export function registerWalletResources(server) {
|
|
|
13
15
|
}
|
|
14
16
|
if (card) {
|
|
15
17
|
lines.push(`Card: ${card.brand} ****${card.last4}`);
|
|
18
|
+
const capabilities = await getCardCapabilities();
|
|
19
|
+
lines.push(`Card MPP: ${capabilities.spt_status}${capabilities.message ? ` — ${capabilities.message}` : ""}`);
|
|
20
|
+
}
|
|
21
|
+
if (pendingCardSetupToken) {
|
|
22
|
+
lines.push("Card setup: pending confirmation");
|
|
16
23
|
}
|
|
17
24
|
lines.push("", `Configured methods: ${methods.join(", ") || "none"}`);
|
|
18
25
|
return {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { formatPaymentChoicePrompt, formatRunConfirmationCommand, formatSolveConfirmationCommand, makeSolvePendingKey, resolveConfirmationMethod, } from "../_payment-confirmation.js";
|
|
3
|
+
describe("payment confirmation helpers", () => {
|
|
4
|
+
it("prefers the explicit method, then pending, then configured default", () => {
|
|
5
|
+
expect(resolveConfirmationMethod("card", "tempo", ["tempo", "card"])).toBe("card");
|
|
6
|
+
expect(resolveConfirmationMethod(undefined, "card", ["tempo", "card"])).toBe("card");
|
|
7
|
+
expect(resolveConfirmationMethod(undefined, undefined, ["card", "tempo"])).toBe("card");
|
|
8
|
+
});
|
|
9
|
+
it("includes pay_with in run confirmation commands when present", () => {
|
|
10
|
+
expect(formatRunConfirmationCommand("agent_123", "card")).toContain('pay_with: "card"');
|
|
11
|
+
expect(formatRunConfirmationCommand("agent_123", undefined)).not.toContain("pay_with");
|
|
12
|
+
});
|
|
13
|
+
it("includes pay_with in solve confirmation commands when present", () => {
|
|
14
|
+
expect(formatSolveConfirmationCommand("translate", 1, "card")).toContain('pay_with: "card"');
|
|
15
|
+
expect(formatSolveConfirmationCommand("translate", 1, undefined)).not.toContain("pay_with");
|
|
16
|
+
});
|
|
17
|
+
it("uses a stable solve pending key for the same request", () => {
|
|
18
|
+
const input = { text: "hello", target_language: "es" };
|
|
19
|
+
expect(makeSolvePendingKey("translate", input, 1)).toBe(makeSolvePendingKey("translate", input, 1));
|
|
20
|
+
});
|
|
21
|
+
it("formats a payment choice prompt with explicit options", () => {
|
|
22
|
+
const prompt = formatPaymentChoicePrompt("Echo Agent", ["card", "tempo"], [
|
|
23
|
+
' run_agent({ agent_id: "agent_123", input: <same>, pay_with: "card" })',
|
|
24
|
+
' run_agent({ agent_id: "agent_123", input: <same>, pay_with: "tempo" })',
|
|
25
|
+
]);
|
|
26
|
+
expect(prompt).toContain("Multiple payment methods are available for Echo Agent.");
|
|
27
|
+
expect(prompt).toContain('Available methods: "card", "tempo"');
|
|
28
|
+
expect(prompt).toContain('pay_with: "card"');
|
|
29
|
+
});
|
|
30
|
+
});
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare function resolveConfirmationMethod(requestedMethod: string | undefined, pendingMethod: string | undefined, compatibleMethods: string[]): string | undefined;
|
|
2
|
+
export declare function formatPaymentLabel(method: string | undefined): string;
|
|
3
|
+
export declare function formatRunConfirmationCommand(agentId: string, method: string | undefined): string;
|
|
4
|
+
export declare function formatSolveConfirmationCommand(intent: string, budget: number, method: string | undefined): string;
|
|
5
|
+
export declare function makeSolvePendingKey(intent: string, input: Record<string, unknown>, budget: number): string;
|
|
6
|
+
export declare function formatPaymentChoicePrompt(subject: string, methods: string[], commands: string[]): string;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export function resolveConfirmationMethod(requestedMethod, pendingMethod, compatibleMethods) {
|
|
2
|
+
return requestedMethod ?? pendingMethod ?? compatibleMethods[0];
|
|
3
|
+
}
|
|
4
|
+
export function formatPaymentLabel(method) {
|
|
5
|
+
return method ?? "auto";
|
|
6
|
+
}
|
|
7
|
+
export function formatRunConfirmationCommand(agentId, method) {
|
|
8
|
+
const payWith = method ? `, pay_with: "${method}"` : "";
|
|
9
|
+
return ` run_agent({ agent_id: "${agentId}", input: <same>${payWith}, confirmed: true })`;
|
|
10
|
+
}
|
|
11
|
+
export function formatSolveConfirmationCommand(intent, budget, method) {
|
|
12
|
+
const payWith = method ? `, pay_with: "${method}"` : "";
|
|
13
|
+
return ` solve({ intent: "${intent}", input: <same>, budget: ${budget}${payWith}, confirmed: true })`;
|
|
14
|
+
}
|
|
15
|
+
export function makeSolvePendingKey(intent, input, budget) {
|
|
16
|
+
return JSON.stringify({ intent, input, budget });
|
|
17
|
+
}
|
|
18
|
+
export function formatPaymentChoicePrompt(subject, methods, commands) {
|
|
19
|
+
return [
|
|
20
|
+
`Multiple payment methods are available for ${subject}.`,
|
|
21
|
+
"",
|
|
22
|
+
"Ask the user which payment method they want to use, then call one of:",
|
|
23
|
+
...commands,
|
|
24
|
+
"",
|
|
25
|
+
`Available methods: ${methods.map((method) => `"${method}"`).join(", ")}`,
|
|
26
|
+
"For fully agentic execution, include pay_with explicitly.",
|
|
27
|
+
].join("\n");
|
|
28
|
+
}
|
package/dist/tools/agent-info.js
CHANGED
|
@@ -13,6 +13,7 @@ export function registerAgentInfoTools(server) {
|
|
|
13
13
|
const s = (a.stats ?? {});
|
|
14
14
|
const payment = (a.payment ?? {});
|
|
15
15
|
const _pricing = (payment.pricing ?? {});
|
|
16
|
+
const creditPacks = payment.credit_packs;
|
|
16
17
|
const lines = [
|
|
17
18
|
`${a.name}`,
|
|
18
19
|
`${stars(a.avgRating ?? s.avgRating)} (${s.ratingCount ?? 0} reviews) • ${compactNumber((s.completedJobs ?? a.totalExecutions ?? 0))} jobs`,
|
|
@@ -32,6 +33,19 @@ export function registerAgentInfoTools(server) {
|
|
|
32
33
|
const hint = outputTypeHint(a.tags);
|
|
33
34
|
return hint ? [`Output: ${hint}`] : [];
|
|
34
35
|
})(),
|
|
36
|
+
...(() => {
|
|
37
|
+
if (!creditPacks?.packs?.length)
|
|
38
|
+
return [];
|
|
39
|
+
const packLines = [
|
|
40
|
+
"",
|
|
41
|
+
`Discounted credit packs: ${creditPacks.unit_type ?? "run"}s`,
|
|
42
|
+
];
|
|
43
|
+
for (const pack of creditPacks.packs) {
|
|
44
|
+
packLines.push(` ${pack.name ?? pack.key}: ${pack.included_units ?? 0} units for $${pack.price_usd ?? "0.00"}`);
|
|
45
|
+
}
|
|
46
|
+
packLines.push(" Use buy_agent_credit_pack to purchase one.");
|
|
47
|
+
return packLines;
|
|
48
|
+
})(),
|
|
35
49
|
];
|
|
36
50
|
// Feedback summary (rating + tips)
|
|
37
51
|
const feedbackSummary = formatFeedbackSummary(s);
|
package/dist/tools/index.d.ts
CHANGED
package/dist/tools/index.js
CHANGED