@agentwonderland/mcp 0.1.24 → 0.1.26
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__/amount-utils.test.d.ts +1 -0
- package/dist/core/__tests__/amount-utils.test.js +11 -0
- package/dist/core/__tests__/api-client.test.d.ts +1 -0
- package/dist/core/__tests__/api-client.test.js +51 -0
- package/dist/core/__tests__/formatters.test.js +10 -0
- package/dist/core/__tests__/passes-api.test.d.ts +1 -0
- package/dist/core/__tests__/passes-api.test.js +27 -0
- package/dist/core/__tests__/payments.test.js +59 -6
- package/dist/core/__tests__/principal.test.js +41 -4
- package/dist/core/__tests__/solana-charge.test.d.ts +1 -0
- package/dist/core/__tests__/solana-charge.test.js +50 -0
- package/dist/core/__tests__/spend-policy.test.d.ts +1 -0
- package/dist/core/__tests__/spend-policy.test.js +40 -0
- package/dist/core/amount-utils.d.ts +1 -0
- package/dist/core/amount-utils.js +4 -0
- package/dist/core/api-client.d.ts +1 -0
- package/dist/core/api-client.js +8 -3
- package/dist/core/balances.d.ts +1 -0
- package/dist/core/balances.js +56 -0
- package/dist/core/base-charge.js +16 -8
- package/dist/core/config.d.ts +19 -0
- package/dist/core/config.js +22 -0
- package/dist/core/formatters.d.ts +5 -5
- package/dist/core/formatters.js +12 -8
- package/dist/core/passes.d.ts +1 -1
- package/dist/core/passes.js +5 -2
- package/dist/core/payments.d.ts +1 -0
- package/dist/core/payments.js +32 -9
- package/dist/core/principal.d.ts +3 -0
- package/dist/core/principal.js +29 -1
- package/dist/core/settings.d.ts +20 -0
- package/dist/core/settings.js +19 -0
- package/dist/core/solana-charge.d.ts +5 -0
- package/dist/core/solana-charge.js +31 -8
- package/dist/core/spend-policy.d.ts +12 -0
- package/dist/core/spend-policy.js +53 -0
- package/dist/core/tempo-charge.d.ts +7 -0
- package/dist/core/tempo-charge.js +84 -0
- package/dist/core/types.d.ts +1 -2
- package/dist/index.js +9 -5
- package/dist/prompts/index.js +4 -2
- package/dist/resources/agents.js +1 -1
- package/dist/tools/__tests__/jobs.test.d.ts +1 -0
- package/dist/tools/__tests__/jobs.test.js +71 -0
- package/dist/tools/__tests__/run.test.d.ts +1 -0
- package/dist/tools/__tests__/run.test.js +149 -0
- package/dist/tools/__tests__/solve.test.d.ts +1 -0
- package/dist/tools/__tests__/solve.test.js +158 -0
- package/dist/tools/__tests__/wallet.test.d.ts +1 -0
- package/dist/tools/__tests__/wallet.test.js +230 -0
- package/dist/tools/_payment-confirmation.js +1 -1
- package/dist/tools/agent-info.js +2 -2
- package/dist/tools/favorites.js +1 -1
- package/dist/tools/jobs.js +8 -1
- package/dist/tools/observability.d.ts +2 -0
- package/dist/tools/observability.js +20 -0
- package/dist/tools/passes.js +11 -6
- package/dist/tools/run.js +45 -29
- package/dist/tools/solve.js +53 -40
- package/dist/tools/wallet.js +58 -22
- package/package.json +2 -2
- package/src/core/__tests__/amount-utils.test.ts +13 -0
- package/src/core/__tests__/api-client.test.ts +78 -0
- package/src/core/__tests__/formatters.test.ts +12 -0
- package/src/core/__tests__/passes-api.test.ts +33 -0
- package/src/core/__tests__/payments.test.ts +79 -6
- package/src/core/__tests__/principal.test.ts +49 -4
- package/src/core/__tests__/solana-charge.test.ts +59 -0
- package/src/core/__tests__/spend-policy.test.ts +58 -0
- package/src/core/amount-utils.ts +5 -0
- package/src/core/api-client.ts +16 -3
- package/src/core/balances.ts +63 -0
- package/src/core/base-charge.ts +16 -8
- package/src/core/config.ts +45 -0
- package/src/core/formatters.ts +16 -11
- package/src/core/passes.ts +5 -2
- package/src/core/payments.ts +37 -9
- package/src/core/principal.ts +42 -1
- package/src/core/settings.ts +36 -0
- package/src/core/solana-charge.ts +45 -10
- package/src/core/spend-policy.ts +69 -0
- package/src/core/tempo-charge.ts +104 -0
- package/src/core/types.ts +1 -2
- package/src/index.ts +9 -5
- package/src/prompts/index.ts +4 -2
- package/src/resources/agents.ts +1 -1
- package/src/tools/__tests__/jobs.test.ts +89 -0
- package/src/tools/__tests__/run.test.ts +176 -0
- package/src/tools/__tests__/solve.test.ts +186 -0
- package/src/tools/__tests__/wallet.test.ts +289 -0
- package/src/tools/_payment-confirmation.ts +1 -1
- package/src/tools/agent-info.ts +2 -2
- package/src/tools/favorites.ts +1 -4
- package/src/tools/jobs.ts +10 -1
- package/src/tools/observability.ts +43 -0
- package/src/tools/passes.ts +12 -12
- package/src/tools/run.ts +50 -41
- package/src/tools/solve.ts +58 -52
- package/src/tools/wallet.ts +60 -24
package/src/core/formatters.ts
CHANGED
|
@@ -36,11 +36,10 @@ export function isFileOutput(output: unknown): output is { type: "file"; url: st
|
|
|
36
36
|
|
|
37
37
|
// ── Price formatting ─────────────────────────────────────────────
|
|
38
38
|
|
|
39
|
-
export function formatPrice(
|
|
40
|
-
if (!
|
|
41
|
-
const p = parseFloat(
|
|
42
|
-
|
|
43
|
-
return `$${p.toFixed(3)}/1k tokens`;
|
|
39
|
+
export function formatPrice(pricePerRunUsd?: string | null): string {
|
|
40
|
+
if (!pricePerRunUsd) return "free";
|
|
41
|
+
const p = parseFloat(pricePerRunUsd);
|
|
42
|
+
return `$${p.toFixed(2)}/req`;
|
|
44
43
|
}
|
|
45
44
|
|
|
46
45
|
// ── Agent line (compact, one line per agent) ─────────────────────
|
|
@@ -52,8 +51,7 @@ interface AgentLike {
|
|
|
52
51
|
avgRating?: number | null;
|
|
53
52
|
ratingCount?: number;
|
|
54
53
|
totalExecutions?: number;
|
|
55
|
-
|
|
56
|
-
pricingModel?: string;
|
|
54
|
+
pricePerRunUsd?: string;
|
|
57
55
|
stats?: { completedJobs?: number; avgRating?: number | null };
|
|
58
56
|
[key: string]: unknown;
|
|
59
57
|
}
|
|
@@ -63,7 +61,7 @@ export function agentLine(agent: AgentLike): string {
|
|
|
63
61
|
const slug = agent.slug ? ` (${agent.slug})` : "";
|
|
64
62
|
const rating = agent.avgRating ?? agent.stats?.avgRating ?? null;
|
|
65
63
|
const jobs = agent.stats?.completedJobs ?? agent.totalExecutions ?? 0;
|
|
66
|
-
const price = formatPrice(agent.
|
|
64
|
+
const price = formatPrice(agent.pricePerRunUsd);
|
|
67
65
|
const reliability = agent.successRate != null && Number(agent.successRate) < 1
|
|
68
66
|
? ` • ${(Number(agent.successRate) * 100).toFixed(0)}% reliable`
|
|
69
67
|
: "";
|
|
@@ -114,14 +112,21 @@ interface RunResultLike {
|
|
|
114
112
|
error_code?: string;
|
|
115
113
|
latency_ms?: number;
|
|
116
114
|
execution_latency_ms?: number;
|
|
117
|
-
cost?: number;
|
|
118
|
-
|
|
115
|
+
cost?: number | string;
|
|
116
|
+
settled_amount?: number | string;
|
|
117
|
+
estimated_cost?: number | string;
|
|
119
118
|
input_tokens?: number;
|
|
120
119
|
tags?: string[];
|
|
121
120
|
consumption_mode?: string;
|
|
122
121
|
credit_pack_id?: string;
|
|
123
122
|
}
|
|
124
123
|
|
|
124
|
+
function toDisplayCost(value: number | string | null | undefined): number | null {
|
|
125
|
+
if (value == null) return null;
|
|
126
|
+
const numeric = typeof value === "number" ? value : Number.parseFloat(value);
|
|
127
|
+
return Number.isFinite(numeric) ? numeric : null;
|
|
128
|
+
}
|
|
129
|
+
|
|
125
130
|
export function formatRunResult(result: RunResultLike, opts?: { paymentMethod?: string }): string {
|
|
126
131
|
const lines: string[] = [];
|
|
127
132
|
|
|
@@ -148,7 +153,7 @@ export function formatRunResult(result: RunResultLike, opts?: { paymentMethod?:
|
|
|
148
153
|
}
|
|
149
154
|
|
|
150
155
|
// Summary line
|
|
151
|
-
const cost = result.cost ?? result.estimated_cost;
|
|
156
|
+
const cost = toDisplayCost(result.cost ?? result.settled_amount ?? result.estimated_cost);
|
|
152
157
|
const usedCreditPack = result.consumption_mode === "credit_pack";
|
|
153
158
|
const status = result.status === "success" || result.status === "completed" ? "✓" : "✗";
|
|
154
159
|
const agent = result.agent_name ?? result.agent_id ?? "";
|
package/src/core/passes.ts
CHANGED
|
@@ -45,9 +45,12 @@ export function getCreditPackProgram(agent: AgentRecord): CreditPackProgramInfo
|
|
|
45
45
|
return creditPacks ?? null;
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
export async function getCreditPackInventory(agentId: string): Promise<CreditPackInventory | null> {
|
|
48
|
+
export async function getCreditPackInventory(agentId: string, method?: string): Promise<CreditPackInventory | null> {
|
|
49
49
|
try {
|
|
50
|
-
return await apiGet<CreditPackInventory>(`/agents/${agentId}/credit-packs`, {
|
|
50
|
+
return await apiGet<CreditPackInventory>(`/agents/${agentId}/credit-packs`, {
|
|
51
|
+
ensureConsumerPrincipal: true,
|
|
52
|
+
principalMethod: method,
|
|
53
|
+
});
|
|
51
54
|
} catch {
|
|
52
55
|
return null;
|
|
53
56
|
}
|
package/src/core/payments.ts
CHANGED
|
@@ -21,6 +21,15 @@ import {
|
|
|
21
21
|
} from "./config.js";
|
|
22
22
|
import type { AgentRecord } from "./types.js";
|
|
23
23
|
|
|
24
|
+
// Feature flag: disable card payment for launch. Flip to true once Stripe
|
|
25
|
+
// approves the live SPT issuer. Config/card state is still persisted so this
|
|
26
|
+
// can be re-enabled without reconfiguring wallets.
|
|
27
|
+
const ENABLE_CARD_PAYMENT = false;
|
|
28
|
+
|
|
29
|
+
export function isCardPaymentEnabled(): boolean {
|
|
30
|
+
return ENABLE_CARD_PAYMENT;
|
|
31
|
+
}
|
|
32
|
+
|
|
24
33
|
// Cache per wallet+chain combo to avoid re-initializing
|
|
25
34
|
const fetchCache = new Map<string, typeof fetch>();
|
|
26
35
|
const REGISTRY_METHOD_MAP: Record<string, string> = {
|
|
@@ -63,9 +72,12 @@ function clearStaleCardCache(activeKey?: string): void {
|
|
|
63
72
|
|
|
64
73
|
// ── Per-protocol initializers ───────────────────────────────────
|
|
65
74
|
|
|
66
|
-
async function
|
|
75
|
+
async function initEvmMppForChain(
|
|
76
|
+
wallet: WalletEntry,
|
|
77
|
+
chain: "tempo" | "base",
|
|
78
|
+
): Promise<typeof fetch | null> {
|
|
67
79
|
try {
|
|
68
|
-
const { Mppx
|
|
80
|
+
const { Mppx } = await import("mppx/client");
|
|
69
81
|
let account;
|
|
70
82
|
|
|
71
83
|
if (wallet.keyType === "ows" && wallet.owsWalletId) {
|
|
@@ -78,8 +90,16 @@ async function initMpp(wallet: WalletEntry): Promise<typeof fetch | null> {
|
|
|
78
90
|
return null;
|
|
79
91
|
}
|
|
80
92
|
|
|
81
|
-
const
|
|
82
|
-
|
|
93
|
+
const methods = [];
|
|
94
|
+
if (chain === "tempo") {
|
|
95
|
+
const { tempoChargeClient } = await import("./tempo-charge.js");
|
|
96
|
+
methods.push(tempoChargeClient({ account }));
|
|
97
|
+
} else {
|
|
98
|
+
const { baseChargeClient } = await import("./base-charge.js");
|
|
99
|
+
methods.push(baseChargeClient({ account }));
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const mppx = Mppx.create({ methods: methods as any, polyfill: false });
|
|
83
103
|
return mppx.fetch.bind(mppx) as typeof fetch;
|
|
84
104
|
} catch {
|
|
85
105
|
return null;
|
|
@@ -96,6 +116,7 @@ async function initSolanaMpp(wallet: WalletEntry): Promise<typeof fetch | null>
|
|
|
96
116
|
const { solanaChargeClient } = await import("./solana-charge.js");
|
|
97
117
|
const mppx = Mppx.create({
|
|
98
118
|
methods: [solanaChargeClient({ wallet })] as any,
|
|
119
|
+
polyfill: false,
|
|
99
120
|
});
|
|
100
121
|
return mppx.fetch.bind(mppx) as typeof fetch;
|
|
101
122
|
} catch {
|
|
@@ -136,6 +157,7 @@ async function initCard(): Promise<typeof fetch | null> {
|
|
|
136
157
|
return spt;
|
|
137
158
|
},
|
|
138
159
|
})] as any,
|
|
160
|
+
polyfill: false,
|
|
139
161
|
});
|
|
140
162
|
return mppx.fetch.bind(mppx) as typeof fetch;
|
|
141
163
|
} catch {
|
|
@@ -150,7 +172,10 @@ async function initForChain(wallet: WalletEntry, chain: string): Promise<typeof
|
|
|
150
172
|
if (chain === "solana") {
|
|
151
173
|
return initSolanaMpp(wallet);
|
|
152
174
|
}
|
|
153
|
-
|
|
175
|
+
if (chain === "tempo" || chain === "base") {
|
|
176
|
+
return initEvmMppForChain(wallet, chain);
|
|
177
|
+
}
|
|
178
|
+
return null;
|
|
154
179
|
}
|
|
155
180
|
|
|
156
181
|
// ── Public API ──────────────────────────────────────────────────
|
|
@@ -164,6 +189,9 @@ async function initForChain(wallet: WalletEntry, chain: string): Promise<typeof
|
|
|
164
189
|
export async function getPaymentFetch(method?: string): Promise<typeof fetch> {
|
|
165
190
|
// Card payment
|
|
166
191
|
if (method === "card") {
|
|
192
|
+
if (!ENABLE_CARD_PAYMENT) {
|
|
193
|
+
throw new Error('Card payments are temporarily unavailable. Use a crypto wallet (tempo, base, or solana).');
|
|
194
|
+
}
|
|
167
195
|
const ck = cardCacheKey();
|
|
168
196
|
clearStaleCardCache(ck ?? undefined);
|
|
169
197
|
if (!ck) {
|
|
@@ -273,14 +301,14 @@ export function getConfiguredMethods(): string[] {
|
|
|
273
301
|
}
|
|
274
302
|
}
|
|
275
303
|
|
|
276
|
-
// Add card if configured
|
|
277
|
-
if (getCardConfig()) {
|
|
304
|
+
// Add card if configured (gated for launch)
|
|
305
|
+
if (ENABLE_CARD_PAYMENT && getCardConfig()) {
|
|
278
306
|
methods.push("card");
|
|
279
307
|
}
|
|
280
308
|
|
|
281
|
-
// Respect defaultPaymentMethod — move it to front of list
|
|
309
|
+
// Respect defaultPaymentMethod — move it to front of list (ignore card when disabled)
|
|
282
310
|
const defaultMethod = getConfig().defaultPaymentMethod;
|
|
283
|
-
if (defaultMethod) {
|
|
311
|
+
if (defaultMethod && (defaultMethod !== "card" || ENABLE_CARD_PAYMENT)) {
|
|
284
312
|
const idx = methods.indexOf(defaultMethod);
|
|
285
313
|
if (idx > 0) {
|
|
286
314
|
methods.splice(idx, 1);
|
package/src/core/principal.ts
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
addWallet,
|
|
3
|
+
getDefaultWallet,
|
|
4
|
+
getWallets,
|
|
5
|
+
resolveWalletAndChain,
|
|
6
|
+
type WalletEntry,
|
|
7
|
+
} from "./config.js";
|
|
2
8
|
import { getWalletAddress } from "./payments.js";
|
|
3
9
|
import {
|
|
4
10
|
createOwsWallet,
|
|
@@ -39,6 +45,12 @@ async function walletPrincipal(wallet: WalletEntry): Promise<string | null> {
|
|
|
39
45
|
return null;
|
|
40
46
|
}
|
|
41
47
|
|
|
48
|
+
async function principalForChain(chain: string): Promise<string | null> {
|
|
49
|
+
const address = await getWalletAddress(chain);
|
|
50
|
+
if (!address) return null;
|
|
51
|
+
return principalFromAddress(address, chain === "solana" ? "solana" : "evm");
|
|
52
|
+
}
|
|
53
|
+
|
|
42
54
|
function preferredWallets(): WalletEntry[] {
|
|
43
55
|
const wallets = getWallets();
|
|
44
56
|
const defaultWallet = getDefaultWallet();
|
|
@@ -115,6 +127,21 @@ export async function getConsumerPrincipal(): Promise<string | null> {
|
|
|
115
127
|
return null;
|
|
116
128
|
}
|
|
117
129
|
|
|
130
|
+
export async function getBaseRebatePrincipal(): Promise<string | null> {
|
|
131
|
+
return (await principalForChain("base")) ?? principalForChain("tempo");
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export async function getConsumerPrincipalForMethod(method?: string): Promise<string | null> {
|
|
135
|
+
if (!method || method === "card") {
|
|
136
|
+
return getConsumerPrincipal();
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const resolved = resolveWalletAndChain(method);
|
|
140
|
+
if (!resolved) return null;
|
|
141
|
+
|
|
142
|
+
return principalForChain(resolved.chain);
|
|
143
|
+
}
|
|
144
|
+
|
|
118
145
|
export async function ensureConsumerPrincipal(): Promise<string> {
|
|
119
146
|
const existing = await getConsumerPrincipal();
|
|
120
147
|
if (existing) return existing;
|
|
@@ -126,3 +153,17 @@ export async function ensureConsumerPrincipal(): Promise<string> {
|
|
|
126
153
|
}
|
|
127
154
|
return principal;
|
|
128
155
|
}
|
|
156
|
+
|
|
157
|
+
export async function ensureConsumerPrincipalForMethod(method?: string): Promise<string> {
|
|
158
|
+
const existing = await getConsumerPrincipalForMethod(method);
|
|
159
|
+
if (existing) return existing;
|
|
160
|
+
|
|
161
|
+
if (method && method !== "card") {
|
|
162
|
+
throw new Error(
|
|
163
|
+
`Could not derive a consumer principal for payment method "${method}". ` +
|
|
164
|
+
"Check wallet_status and confirm that chain is configured for the active wallet.",
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return ensureConsumerPrincipal();
|
|
169
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { getApiUrl } from "./config.js";
|
|
2
|
+
|
|
3
|
+
export interface ChainSettings {
|
|
4
|
+
chainId: number | string;
|
|
5
|
+
usdc: string;
|
|
6
|
+
rpcUrl: string;
|
|
7
|
+
explorer?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface PublicSettings {
|
|
11
|
+
network: "testnet" | "mainnet";
|
|
12
|
+
stripe: { mode: "test" | "live" };
|
|
13
|
+
chains: {
|
|
14
|
+
tempo: ChainSettings;
|
|
15
|
+
base: ChainSettings;
|
|
16
|
+
solana: ChainSettings & { cluster: "mainnet-beta" | "devnet" };
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
let cache: { data: PublicSettings; expiresAt: number } | null = null;
|
|
21
|
+
const TTL_MS = 5 * 60 * 1000;
|
|
22
|
+
|
|
23
|
+
export async function getSettings(): Promise<PublicSettings | null> {
|
|
24
|
+
const now = Date.now();
|
|
25
|
+
if (cache && cache.expiresAt > now) return cache.data;
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
const res = await fetch(`${getApiUrl()}/settings`);
|
|
29
|
+
if (!res.ok) return cache?.data ?? null;
|
|
30
|
+
const data = await res.json() as PublicSettings;
|
|
31
|
+
cache = { data, expiresAt: now + TTL_MS };
|
|
32
|
+
return data;
|
|
33
|
+
} catch {
|
|
34
|
+
return cache?.data ?? null;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -9,10 +9,12 @@ import { Connection, Keypair, PublicKey, Transaction, sendAndConfirmTransaction
|
|
|
9
9
|
import {
|
|
10
10
|
ASSOCIATED_TOKEN_PROGRAM_ID,
|
|
11
11
|
TOKEN_PROGRAM_ID,
|
|
12
|
+
createAssociatedTokenAccountInstruction,
|
|
12
13
|
createTransferCheckedInstruction,
|
|
13
14
|
getAssociatedTokenAddressSync,
|
|
14
15
|
} from "@solana/spl-token";
|
|
15
16
|
import type { WalletEntry } from "./config.js";
|
|
17
|
+
import { toAtomicAmount } from "./amount-utils.js";
|
|
16
18
|
|
|
17
19
|
export const SOLANA_USDC_MINT = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" as const;
|
|
18
20
|
export const SOLANA_CHAIN_ID = "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp" as const;
|
|
@@ -74,31 +76,47 @@ interface SolanaChargeClientConfig {
|
|
|
74
76
|
rpcUrl?: string;
|
|
75
77
|
}
|
|
76
78
|
|
|
77
|
-
async function resolveRecipientTokenAccount(
|
|
79
|
+
export async function resolveRecipientTokenAccount(
|
|
78
80
|
connection: Connection,
|
|
79
81
|
mint: PublicKey,
|
|
80
82
|
recipient: PublicKey,
|
|
81
|
-
): Promise<PublicKey> {
|
|
83
|
+
): Promise<{ tokenAccount: PublicKey; needsCreateAssociatedAccount: boolean }> {
|
|
82
84
|
const accountInfo = await connection.getParsedAccountInfo(recipient, "confirmed");
|
|
83
85
|
const owner = accountInfo.value?.owner;
|
|
84
86
|
if (owner && owner.toBase58() === TOKEN_PROGRAM_ID.toBase58()) {
|
|
85
|
-
return
|
|
87
|
+
return {
|
|
88
|
+
tokenAccount: recipient,
|
|
89
|
+
needsCreateAssociatedAccount: false,
|
|
90
|
+
};
|
|
86
91
|
}
|
|
87
92
|
|
|
88
|
-
|
|
93
|
+
const associatedTokenAccount = getAssociatedTokenAddressSync(
|
|
89
94
|
mint,
|
|
90
95
|
recipient,
|
|
91
96
|
false,
|
|
92
97
|
TOKEN_PROGRAM_ID,
|
|
93
98
|
ASSOCIATED_TOKEN_PROGRAM_ID,
|
|
94
99
|
);
|
|
100
|
+
const associatedAccountInfo = await connection.getParsedAccountInfo(associatedTokenAccount, "confirmed");
|
|
101
|
+
const associatedOwner = associatedAccountInfo.value?.owner;
|
|
102
|
+
if (associatedOwner && associatedOwner.toBase58() === TOKEN_PROGRAM_ID.toBase58()) {
|
|
103
|
+
return {
|
|
104
|
+
tokenAccount: associatedTokenAccount,
|
|
105
|
+
needsCreateAssociatedAccount: false,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
tokenAccount: associatedTokenAccount,
|
|
111
|
+
needsCreateAssociatedAccount: true,
|
|
112
|
+
};
|
|
95
113
|
}
|
|
96
114
|
|
|
97
115
|
export function solanaChargeClient(config: SolanaChargeClientConfig) {
|
|
98
116
|
return Method.toClient(solanaChargeMethod as any, {
|
|
99
117
|
async createCredential({ challenge }: any) {
|
|
100
118
|
const { request } = challenge;
|
|
101
|
-
const amount =
|
|
119
|
+
const amount = toAtomicAmount(request.amount, request.decimals ?? 6);
|
|
102
120
|
const decimals = request.decimals ?? 6;
|
|
103
121
|
const mint = new PublicKey(request.currency ?? SOLANA_USDC_MINT);
|
|
104
122
|
const recipient = new PublicKey(request.recipient);
|
|
@@ -113,27 +131,44 @@ export function solanaChargeClient(config: SolanaChargeClientConfig) {
|
|
|
113
131
|
TOKEN_PROGRAM_ID,
|
|
114
132
|
ASSOCIATED_TOKEN_PROGRAM_ID,
|
|
115
133
|
);
|
|
116
|
-
const
|
|
134
|
+
const destination = await resolveRecipientTokenAccount(
|
|
117
135
|
connection,
|
|
118
136
|
mint,
|
|
119
137
|
recipient,
|
|
120
138
|
);
|
|
139
|
+
const instructions = [];
|
|
140
|
+
|
|
141
|
+
if (destination.needsCreateAssociatedAccount) {
|
|
142
|
+
instructions.push(
|
|
143
|
+
createAssociatedTokenAccountInstruction(
|
|
144
|
+
owner,
|
|
145
|
+
destination.tokenAccount,
|
|
146
|
+
recipient,
|
|
147
|
+
mint,
|
|
148
|
+
TOKEN_PROGRAM_ID,
|
|
149
|
+
ASSOCIATED_TOKEN_PROGRAM_ID,
|
|
150
|
+
),
|
|
151
|
+
);
|
|
152
|
+
}
|
|
121
153
|
|
|
122
|
-
|
|
154
|
+
instructions.push(createTransferCheckedInstruction(
|
|
123
155
|
sourceTokenAccount,
|
|
124
156
|
mint,
|
|
125
|
-
|
|
157
|
+
destination.tokenAccount,
|
|
126
158
|
owner,
|
|
127
159
|
amount,
|
|
128
160
|
decimals,
|
|
129
|
-
);
|
|
161
|
+
));
|
|
130
162
|
|
|
131
163
|
const { blockhash } = await connection.getLatestBlockhash("confirmed");
|
|
132
164
|
|
|
133
165
|
const transaction = new Transaction({
|
|
134
166
|
feePayer: owner,
|
|
135
167
|
recentBlockhash: blockhash,
|
|
136
|
-
})
|
|
168
|
+
});
|
|
169
|
+
for (const instruction of instructions) {
|
|
170
|
+
transaction.add(instruction);
|
|
171
|
+
}
|
|
137
172
|
|
|
138
173
|
const signature = await sendAndConfirmTransaction(connection, transaction, [keypair], {
|
|
139
174
|
commitment: "confirmed",
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getSpendLedger,
|
|
3
|
+
getSpendPolicy,
|
|
4
|
+
saveSpendLedger,
|
|
5
|
+
type SpendLedgerEntry,
|
|
6
|
+
} from "./config.js";
|
|
7
|
+
|
|
8
|
+
const LEDGER_RETENTION_DAYS = 35;
|
|
9
|
+
|
|
10
|
+
function utcDay(now: Date): string {
|
|
11
|
+
return now.toISOString().slice(0, 10);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function pruneLedger(entries: SpendLedgerEntry[], now: Date): SpendLedgerEntry[] {
|
|
15
|
+
const cutoff = new Date(now);
|
|
16
|
+
cutoff.setUTCDate(cutoff.getUTCDate() - LEDGER_RETENTION_DAYS);
|
|
17
|
+
return entries.filter((entry) => new Date(entry.timestamp) >= cutoff);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function getDailySpend(method: string, now = new Date()): number {
|
|
21
|
+
const day = utcDay(now);
|
|
22
|
+
return getSpendLedger()
|
|
23
|
+
.filter((entry) => entry.method === method && entry.day === day)
|
|
24
|
+
.reduce((sum, entry) => sum + entry.amountUsd, 0);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function canSpend(params: {
|
|
28
|
+
method: string;
|
|
29
|
+
amountUsd: number;
|
|
30
|
+
}): { ok: true } | { ok: false; message: string } {
|
|
31
|
+
const policy = getSpendPolicy(params.method);
|
|
32
|
+
if (!policy) return { ok: true };
|
|
33
|
+
|
|
34
|
+
if (policy.maxPerTxUsd != null && params.amountUsd > policy.maxPerTxUsd) {
|
|
35
|
+
return {
|
|
36
|
+
ok: false,
|
|
37
|
+
message: `Transaction blocked by spend policy: $${params.amountUsd.toFixed(2)} exceeds max_per_tx of $${policy.maxPerTxUsd.toFixed(2)} for "${params.method}".`,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (policy.maxPerDayUsd != null) {
|
|
42
|
+
const spentToday = getDailySpend(params.method);
|
|
43
|
+
if (spentToday + params.amountUsd > policy.maxPerDayUsd) {
|
|
44
|
+
return {
|
|
45
|
+
ok: false,
|
|
46
|
+
message: `Transaction blocked by spend policy: daily spend would be $${(spentToday + params.amountUsd).toFixed(2)} > $${policy.maxPerDayUsd.toFixed(2)} for "${params.method}".`,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return { ok: true };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function requiresPolicyConfirmation(method: string, amountUsd: number): boolean {
|
|
55
|
+
const policy = getSpendPolicy(method);
|
|
56
|
+
if (!policy?.requireConfirmationAboveUsd) return false;
|
|
57
|
+
return amountUsd >= policy.requireConfirmationAboveUsd;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function recordSpend(method: string, amountUsd: number, now = new Date()): void {
|
|
61
|
+
const entries = pruneLedger(getSpendLedger(), now);
|
|
62
|
+
entries.push({
|
|
63
|
+
method,
|
|
64
|
+
amountUsd,
|
|
65
|
+
day: utcDay(now),
|
|
66
|
+
timestamp: now.toISOString(),
|
|
67
|
+
});
|
|
68
|
+
saveSpendLedger(entries);
|
|
69
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom MPP payment method: Tempo EVM USDC charge (client-side).
|
|
3
|
+
*
|
|
4
|
+
* Sends a standard ERC-20 transfer on Tempo and returns the tx hash as the
|
|
5
|
+
* payment credential.
|
|
6
|
+
*/
|
|
7
|
+
import { Method, Credential, z } from "mppx";
|
|
8
|
+
import type { LocalAccount } from "viem/accounts";
|
|
9
|
+
import { toAtomicAmount } from "./amount-utils.js";
|
|
10
|
+
|
|
11
|
+
const TEMPO_USDC = "0x20c000000000000000000000b9537d11c60e8b50" as const;
|
|
12
|
+
const PATH_USD = "0x20c0000000000000000000000000000000000000" as const;
|
|
13
|
+
const TEMPO_MAINNET_CHAIN_ID = 4217;
|
|
14
|
+
const TEMPO_TESTNET_CHAIN_ID = 42431;
|
|
15
|
+
|
|
16
|
+
const ERC20_ABI = [
|
|
17
|
+
{
|
|
18
|
+
type: "function",
|
|
19
|
+
name: "transfer",
|
|
20
|
+
inputs: [
|
|
21
|
+
{ name: "to", type: "address" },
|
|
22
|
+
{ name: "value", type: "uint256" },
|
|
23
|
+
],
|
|
24
|
+
outputs: [{ name: "", type: "bool" }],
|
|
25
|
+
stateMutability: "nonpayable",
|
|
26
|
+
},
|
|
27
|
+
] as const;
|
|
28
|
+
|
|
29
|
+
const tempoChargeMethod = Method.from({
|
|
30
|
+
name: "tempo" as const,
|
|
31
|
+
intent: "charge" as const,
|
|
32
|
+
schema: {
|
|
33
|
+
credential: {
|
|
34
|
+
payload: z.object({
|
|
35
|
+
hash: z.string(),
|
|
36
|
+
type: z.literal("hash"),
|
|
37
|
+
}),
|
|
38
|
+
},
|
|
39
|
+
request: z.pipe(
|
|
40
|
+
z.object({
|
|
41
|
+
amount: z.string(),
|
|
42
|
+
currency: z.string(),
|
|
43
|
+
recipient: z.string(),
|
|
44
|
+
chainId: z.optional(z.number()),
|
|
45
|
+
decimals: z.optional(z.number()),
|
|
46
|
+
}),
|
|
47
|
+
z.transform((v: any) => ({
|
|
48
|
+
...v,
|
|
49
|
+
methodDetails: {
|
|
50
|
+
chainId: v.chainId ?? TEMPO_MAINNET_CHAIN_ID,
|
|
51
|
+
},
|
|
52
|
+
})),
|
|
53
|
+
),
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
interface TempoChargeClientConfig {
|
|
58
|
+
account: LocalAccount;
|
|
59
|
+
rpcUrl?: string;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function tempoChargeClient(config: TempoChargeClientConfig) {
|
|
63
|
+
return Method.toClient(tempoChargeMethod as any, {
|
|
64
|
+
async createCredential({ challenge }: any) {
|
|
65
|
+
const { request } = challenge;
|
|
66
|
+
const amount = toAtomicAmount(request.amount, request.decimals ?? 6);
|
|
67
|
+
const recipient = request.recipient as `0x${string}`;
|
|
68
|
+
const currency = (request.currency ?? TEMPO_USDC) as `0x${string}`;
|
|
69
|
+
const chainId = request.methodDetails?.chainId ?? request.chainId ?? TEMPO_MAINNET_CHAIN_ID;
|
|
70
|
+
|
|
71
|
+
const { createWalletClient, createPublicClient, http } = await import("viem");
|
|
72
|
+
const { tempo, tempoModerato } = await import("viem/chains");
|
|
73
|
+
|
|
74
|
+
const chain = chainId === TEMPO_TESTNET_CHAIN_ID ? tempoModerato : tempo;
|
|
75
|
+
const rpcUrl = config.rpcUrl ?? chain.rpcUrls.default.http[0];
|
|
76
|
+
|
|
77
|
+
const walletClient = createWalletClient({
|
|
78
|
+
account: config.account,
|
|
79
|
+
chain,
|
|
80
|
+
transport: http(rpcUrl),
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
const publicClient = createPublicClient({
|
|
84
|
+
chain,
|
|
85
|
+
transport: http(rpcUrl),
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
const hash = await walletClient.writeContract({
|
|
89
|
+
address: currency,
|
|
90
|
+
abi: ERC20_ABI,
|
|
91
|
+
functionName: "transfer",
|
|
92
|
+
args: [recipient, amount],
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
await publicClient.waitForTransactionReceipt({ hash });
|
|
96
|
+
|
|
97
|
+
return Credential.serialize({
|
|
98
|
+
challenge,
|
|
99
|
+
payload: { hash, type: "hash" as const },
|
|
100
|
+
source: `did:pkh:eip155:${chainId}:${config.account.address}`,
|
|
101
|
+
});
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
}
|
package/src/core/types.ts
CHANGED
|
@@ -6,8 +6,7 @@ export interface AgentRecord {
|
|
|
6
6
|
id: string;
|
|
7
7
|
name: string;
|
|
8
8
|
description?: string;
|
|
9
|
-
|
|
10
|
-
pricingModel?: string;
|
|
9
|
+
pricePerRunUsd?: string;
|
|
11
10
|
avgRating?: number | null;
|
|
12
11
|
totalExecutions?: number;
|
|
13
12
|
successRate?: number | string | null;
|
package/src/index.ts
CHANGED
|
@@ -14,6 +14,7 @@ import { registerWalletTools } from "./tools/wallet.js";
|
|
|
14
14
|
import { registerFavoriteTools } from "./tools/favorites.js";
|
|
15
15
|
import { registerTipTools } from "./tools/tip.js";
|
|
16
16
|
import { registerPassTools } from "./tools/passes.js";
|
|
17
|
+
import { registerObservabilityTools } from "./tools/observability.js";
|
|
17
18
|
|
|
18
19
|
// ── Resources ────────────────────────────────────────────────────
|
|
19
20
|
import { registerAgentResources } from "./resources/agents.js";
|
|
@@ -46,12 +47,14 @@ export async function startMcpServer(): Promise<void> {
|
|
|
46
47
|
"5. Ask user to rate or tip after a successful run",
|
|
47
48
|
"",
|
|
48
49
|
"PAYMENT:",
|
|
49
|
-
"-
|
|
50
|
-
"-
|
|
50
|
+
"- Crypto wallets (Tempo USDC, Base USDC, Solana USDC) are the core supported rails.",
|
|
51
|
+
"- Saved cards can also be used, but card-backed MPP depends on Stripe Shared Payment Token availability in the current environment.",
|
|
51
52
|
"- Card and crypto are SEPARATE payment methods. Card charges a credit card; crypto sends USDC on-chain.",
|
|
52
|
-
"-
|
|
53
|
-
"-
|
|
54
|
-
"-
|
|
53
|
+
"- When a card is configured, it becomes the default payment method. Use wallet_status to confirm whether `Card MPP: ready` before relying on it.",
|
|
54
|
+
"- run_agent() and solve() auto-detect the default compatible payment method when pay_with is omitted.",
|
|
55
|
+
"- Include pay_with explicitly when you need a specific rail or want deterministic control over the payment method used.",
|
|
56
|
+
"- Use wallet_status to see the exact payment methods the user has configured before calling a paid tool.",
|
|
57
|
+
"- Use open_observability_dashboard() to open a secure web usage dashboard for runs/spend/rebates.",
|
|
55
58
|
"- Payment is automatic once configured. Users are never charged for failed runs.",
|
|
56
59
|
"- Do NOT ask the user to set up payment before they try to run an agent. Let them explore freely.",
|
|
57
60
|
"- If a specific payment method fails, report the error clearly. Do NOT silently fall back to a different method.",
|
|
@@ -79,6 +82,7 @@ export async function startMcpServer(): Promise<void> {
|
|
|
79
82
|
registerFavoriteTools(server);
|
|
80
83
|
registerTipTools(server);
|
|
81
84
|
registerPassTools(server);
|
|
85
|
+
registerObservabilityTools(server);
|
|
82
86
|
|
|
83
87
|
// Register resources
|
|
84
88
|
registerAgentResources(server);
|
package/src/prompts/index.ts
CHANGED
|
@@ -18,7 +18,7 @@ export function registerPrompts(server: McpServer) {
|
|
|
18
18
|
"1. Check my wallet status with wallet_status",
|
|
19
19
|
"2. If no wallet is configured, help me create one with wallet_setup",
|
|
20
20
|
"3. Show me some popular agents with search_agents",
|
|
21
|
-
"4. Briefly explain how solve, run_agent, buy_agent_credit_pack, rating, and tipping work",
|
|
21
|
+
"4. Briefly explain how solve, run_agent, buy_agent_credit_pack, pay_with selection, rating, and tipping work",
|
|
22
22
|
].join("\n"),
|
|
23
23
|
},
|
|
24
24
|
}],
|
|
@@ -86,7 +86,7 @@ export function registerPrompts(server: McpServer) {
|
|
|
86
86
|
text: [
|
|
87
87
|
`Complete this task on Agent Wonderland within $${budget || "1.00"}: "${task}"`,
|
|
88
88
|
"",
|
|
89
|
-
"Use the solve tool with the budget parameter. Show me what agent was selected and why.",
|
|
89
|
+
"Use the solve tool with the budget parameter and an explicit pay_with method. Show me what agent was selected and why.",
|
|
90
90
|
].join("\n"),
|
|
91
91
|
},
|
|
92
92
|
}],
|
|
@@ -108,6 +108,8 @@ export function registerPrompts(server: McpServer) {
|
|
|
108
108
|
text: [
|
|
109
109
|
`Run agent ${agent_id} with this input: ${input}`,
|
|
110
110
|
"",
|
|
111
|
+
"Include an explicit pay_with method when you call run_agent.",
|
|
112
|
+
"",
|
|
111
113
|
"After getting the result:",
|
|
112
114
|
"1. Summarize the output",
|
|
113
115
|
"2. Assess the quality",
|
package/src/resources/agents.ts
CHANGED
|
@@ -9,7 +9,7 @@ export function registerAgentResources(server: McpServer) {
|
|
|
9
9
|
const lines = agents.map(a => {
|
|
10
10
|
const rating = stars(a.reputationScore);
|
|
11
11
|
const jobs = compactNumber(a.totalExecutions);
|
|
12
|
-
const price = formatPrice(a.
|
|
12
|
+
const price = formatPrice(a.pricePerRunUsd);
|
|
13
13
|
return `${a.name} ${rating} ${jobs} jobs | ${price}/req — ${a.description || ""}`;
|
|
14
14
|
});
|
|
15
15
|
return {
|