@agentwonderland/mcp 0.1.25 → 0.1.27

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.
Files changed (78) hide show
  1. package/dist/core/__tests__/api-client.test.d.ts +1 -0
  2. package/dist/core/__tests__/api-client.test.js +51 -0
  3. package/dist/core/__tests__/formatters.test.js +10 -0
  4. package/dist/core/__tests__/passes-api.test.d.ts +1 -0
  5. package/dist/core/__tests__/passes-api.test.js +27 -0
  6. package/dist/core/__tests__/payments.test.js +10 -6
  7. package/dist/core/__tests__/principal.test.js +41 -4
  8. package/dist/core/__tests__/solana-charge.test.d.ts +1 -0
  9. package/dist/core/__tests__/solana-charge.test.js +50 -0
  10. package/dist/core/api-client.d.ts +1 -0
  11. package/dist/core/api-client.js +8 -3
  12. package/dist/core/balances.d.ts +1 -0
  13. package/dist/core/balances.js +56 -0
  14. package/dist/core/base-charge.js +13 -6
  15. package/dist/core/formatters.d.ts +3 -2
  16. package/dist/core/formatters.js +7 -1
  17. package/dist/core/passes.d.ts +1 -1
  18. package/dist/core/passes.js +5 -2
  19. package/dist/core/payments.d.ts +1 -0
  20. package/dist/core/payments.js +20 -7
  21. package/dist/core/principal.d.ts +3 -0
  22. package/dist/core/principal.js +29 -1
  23. package/dist/core/settings.d.ts +20 -0
  24. package/dist/core/settings.js +19 -0
  25. package/dist/core/solana-charge.d.ts +5 -0
  26. package/dist/core/solana-charge.js +29 -7
  27. package/dist/core/tempo-charge.d.ts +7 -0
  28. package/dist/core/tempo-charge.js +84 -0
  29. package/dist/index.js +5 -7
  30. package/dist/prompts/index.js +1 -1
  31. package/dist/tools/__tests__/jobs.test.d.ts +1 -0
  32. package/dist/tools/__tests__/jobs.test.js +71 -0
  33. package/dist/tools/__tests__/run.test.d.ts +1 -0
  34. package/dist/tools/__tests__/run.test.js +149 -0
  35. package/dist/tools/__tests__/solve.test.d.ts +1 -0
  36. package/dist/tools/__tests__/solve.test.js +158 -0
  37. package/dist/tools/__tests__/wallet.test.d.ts +1 -0
  38. package/dist/tools/__tests__/wallet.test.js +230 -0
  39. package/dist/tools/_payment-confirmation.js +1 -1
  40. package/dist/tools/agent-info.js +14 -28
  41. package/dist/tools/jobs.js +82 -16
  42. package/dist/tools/passes.js +30 -14
  43. package/dist/tools/run.js +35 -20
  44. package/dist/tools/search.js +9 -8
  45. package/dist/tools/solve.js +45 -25
  46. package/dist/tools/wallet.js +35 -15
  47. package/package.json +2 -2
  48. package/src/core/__tests__/api-client.test.ts +78 -0
  49. package/src/core/__tests__/formatters.test.ts +12 -0
  50. package/src/core/__tests__/passes-api.test.ts +33 -0
  51. package/src/core/__tests__/payments.test.ts +17 -6
  52. package/src/core/__tests__/principal.test.ts +49 -4
  53. package/src/core/__tests__/solana-charge.test.ts +59 -0
  54. package/src/core/api-client.ts +16 -3
  55. package/src/core/balances.ts +63 -0
  56. package/src/core/base-charge.ts +13 -6
  57. package/src/core/formatters.ts +10 -3
  58. package/src/core/passes.ts +5 -2
  59. package/src/core/payments.ts +22 -7
  60. package/src/core/principal.ts +42 -1
  61. package/src/core/settings.ts +36 -0
  62. package/src/core/solana-charge.ts +43 -9
  63. package/src/core/tempo-charge.ts +104 -0
  64. package/src/index.ts +5 -7
  65. package/src/prompts/index.ts +1 -1
  66. package/src/tools/__tests__/jobs.test.ts +89 -0
  67. package/src/tools/__tests__/run.test.ts +176 -0
  68. package/src/tools/__tests__/solve.test.ts +186 -0
  69. package/src/tools/__tests__/wallet.test.ts +289 -0
  70. package/src/tools/_payment-confirmation.ts +1 -1
  71. package/src/tools/agent-info.ts +15 -38
  72. package/src/tools/jobs.ts +79 -17
  73. package/src/tools/passes.ts +30 -14
  74. package/src/tools/run.ts +38 -20
  75. package/src/tools/search.ts +10 -9
  76. package/src/tools/solve.ts +48 -25
  77. package/src/tools/wallet.ts +33 -17
  78. package/src/tools/observability.ts +0 -43
@@ -0,0 +1,63 @@
1
+ import { getSettings, type ChainSettings } from "./settings.js";
2
+
3
+ const ERC20_BALANCE_ABI = [
4
+ {
5
+ type: "function",
6
+ name: "balanceOf",
7
+ stateMutability: "view",
8
+ inputs: [{ name: "account", type: "address" }],
9
+ outputs: [{ name: "", type: "uint256" }],
10
+ },
11
+ ] as const;
12
+
13
+ async function fetchEvmUsdcBalance(
14
+ chain: ChainSettings,
15
+ address: string,
16
+ ): Promise<string | null> {
17
+ try {
18
+ const { createPublicClient, http, formatUnits } = await import("viem");
19
+ const client = createPublicClient({ transport: http(chain.rpcUrl) });
20
+ const raw = await client.readContract({
21
+ address: chain.usdc as `0x${string}`,
22
+ abi: ERC20_BALANCE_ABI,
23
+ functionName: "balanceOf",
24
+ args: [address as `0x${string}`],
25
+ });
26
+ return formatUnits(raw as bigint, 6);
27
+ } catch {
28
+ return null;
29
+ }
30
+ }
31
+
32
+ async function fetchSolanaUsdcBalance(
33
+ chain: ChainSettings,
34
+ address: string,
35
+ ): Promise<string | null> {
36
+ try {
37
+ const { Connection, PublicKey } = await import("@solana/web3.js");
38
+ const connection = new Connection(chain.rpcUrl, "confirmed");
39
+ const owner = new PublicKey(address);
40
+ const mint = new PublicKey(chain.usdc);
41
+ const accounts = await connection.getParsedTokenAccountsByOwner(owner, { mint });
42
+ let total = 0;
43
+ for (const { account } of accounts.value) {
44
+ const amount = (account.data as any)?.parsed?.info?.tokenAmount?.uiAmount;
45
+ if (typeof amount === "number") total += amount;
46
+ }
47
+ return total.toString();
48
+ } catch {
49
+ return null;
50
+ }
51
+ }
52
+
53
+ export async function fetchUsdcBalance(
54
+ chain: "tempo" | "base" | "solana",
55
+ address: string,
56
+ ): Promise<string | null> {
57
+ const settings = await getSettings();
58
+ if (!settings) return null;
59
+ const chainConfig = settings.chains[chain];
60
+ if (!chainConfig) return null;
61
+ if (chain === "solana") return fetchSolanaUsdcBalance(chainConfig, address);
62
+ return fetchEvmUsdcBalance(chainConfig, address);
63
+ }
@@ -10,7 +10,9 @@ import { toAtomicAmount } from "./amount-utils.js";
10
10
 
11
11
  // Base USDC (Circle native)
12
12
  const BASE_USDC = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913" as const;
13
+ const BASE_SEPOLIA_USDC = "0x036CbD53842c5426634e7929541eC2318f3dCF7e" as const;
13
14
  const BASE_CHAIN_ID = 8453;
15
+ const BASE_SEPOLIA_CHAIN_ID = 84532;
14
16
 
15
17
  // Standard ERC-20 transfer function ABI
16
18
  const ERC20_ABI = [
@@ -66,22 +68,27 @@ export function baseChargeClient(config: BaseChargeClientConfig) {
66
68
  const { request } = challenge;
67
69
  const amount = toAtomicAmount(request.amount, request.decimals ?? 6);
68
70
  const recipient = request.recipient as `0x${string}`;
69
- const currency = (request.currency ?? BASE_USDC) as `0x${string}`;
71
+ const chainId = request.chainId ?? BASE_CHAIN_ID;
72
+ const currency = (request.currency
73
+ ?? (chainId === BASE_SEPOLIA_CHAIN_ID ? BASE_SEPOLIA_USDC : BASE_USDC)) as `0x${string}`;
70
74
 
71
75
  // Dynamic imports to keep the module lightweight
72
76
  const { createWalletClient, createPublicClient, http } = await import("viem");
73
- const { base } = await import("viem/chains");
77
+ const { base, baseSepolia } = await import("viem/chains");
74
78
 
75
- const rpcUrl = config.rpcUrl ?? "https://mainnet.base.org";
79
+ const chain = chainId === BASE_SEPOLIA_CHAIN_ID ? baseSepolia : base;
80
+ const rpcUrl = config.rpcUrl ?? (chainId === BASE_SEPOLIA_CHAIN_ID
81
+ ? "https://sepolia.base.org"
82
+ : "https://mainnet.base.org");
76
83
 
77
84
  const walletClient = createWalletClient({
78
85
  account: config.account,
79
- chain: base,
86
+ chain,
80
87
  transport: http(rpcUrl),
81
88
  });
82
89
 
83
90
  const publicClient = createPublicClient({
84
- chain: base,
91
+ chain,
85
92
  transport: http(rpcUrl),
86
93
  });
87
94
 
@@ -99,7 +106,7 @@ export function baseChargeClient(config: BaseChargeClientConfig) {
99
106
  return Credential.serialize({
100
107
  challenge,
101
108
  payload: { hash, type: "hash" as const },
102
- source: `did:pkh:eip155:${BASE_CHAIN_ID}:${config.account.address}`,
109
+ source: `did:pkh:eip155:${chainId}:${config.account.address}`,
103
110
  });
104
111
  },
105
112
  });
@@ -112,14 +112,21 @@ interface RunResultLike {
112
112
  error_code?: string;
113
113
  latency_ms?: number;
114
114
  execution_latency_ms?: number;
115
- cost?: number;
116
- estimated_cost?: number;
115
+ cost?: number | string;
116
+ settled_amount?: number | string;
117
+ estimated_cost?: number | string;
117
118
  input_tokens?: number;
118
119
  tags?: string[];
119
120
  consumption_mode?: string;
120
121
  credit_pack_id?: string;
121
122
  }
122
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
+
123
130
  export function formatRunResult(result: RunResultLike, opts?: { paymentMethod?: string }): string {
124
131
  const lines: string[] = [];
125
132
 
@@ -146,7 +153,7 @@ export function formatRunResult(result: RunResultLike, opts?: { paymentMethod?:
146
153
  }
147
154
 
148
155
  // Summary line
149
- const cost = result.cost ?? result.estimated_cost;
156
+ const cost = toDisplayCost(result.cost ?? result.settled_amount ?? result.estimated_cost);
150
157
  const usedCreditPack = result.consumption_mode === "credit_pack";
151
158
  const status = result.status === "success" || result.status === "completed" ? "✓" : "✗";
152
159
  const agent = result.agent_name ?? result.agent_id ?? "";
@@ -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`, { ensureConsumerPrincipal: true });
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
  }
@@ -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> = {
@@ -68,7 +77,7 @@ async function initEvmMppForChain(
68
77
  chain: "tempo" | "base",
69
78
  ): Promise<typeof fetch | null> {
70
79
  try {
71
- const { Mppx, tempo } = await import("mppx/client");
80
+ const { Mppx } = await import("mppx/client");
72
81
  let account;
73
82
 
74
83
  if (wallet.keyType === "ows" && wallet.owsWalletId) {
@@ -83,13 +92,14 @@ async function initEvmMppForChain(
83
92
 
84
93
  const methods = [];
85
94
  if (chain === "tempo") {
86
- methods.push(tempo({ account }));
95
+ const { tempoChargeClient } = await import("./tempo-charge.js");
96
+ methods.push(tempoChargeClient({ account }));
87
97
  } else {
88
98
  const { baseChargeClient } = await import("./base-charge.js");
89
99
  methods.push(baseChargeClient({ account }));
90
100
  }
91
101
 
92
- const mppx = Mppx.create({ methods: methods as any });
102
+ const mppx = Mppx.create({ methods: methods as any, polyfill: false });
93
103
  return mppx.fetch.bind(mppx) as typeof fetch;
94
104
  } catch {
95
105
  return null;
@@ -106,6 +116,7 @@ async function initSolanaMpp(wallet: WalletEntry): Promise<typeof fetch | null>
106
116
  const { solanaChargeClient } = await import("./solana-charge.js");
107
117
  const mppx = Mppx.create({
108
118
  methods: [solanaChargeClient({ wallet })] as any,
119
+ polyfill: false,
109
120
  });
110
121
  return mppx.fetch.bind(mppx) as typeof fetch;
111
122
  } catch {
@@ -146,6 +157,7 @@ async function initCard(): Promise<typeof fetch | null> {
146
157
  return spt;
147
158
  },
148
159
  })] as any,
160
+ polyfill: false,
149
161
  });
150
162
  return mppx.fetch.bind(mppx) as typeof fetch;
151
163
  } catch {
@@ -177,6 +189,9 @@ async function initForChain(wallet: WalletEntry, chain: string): Promise<typeof
177
189
  export async function getPaymentFetch(method?: string): Promise<typeof fetch> {
178
190
  // Card payment
179
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
+ }
180
195
  const ck = cardCacheKey();
181
196
  clearStaleCardCache(ck ?? undefined);
182
197
  if (!ck) {
@@ -286,14 +301,14 @@ export function getConfiguredMethods(): string[] {
286
301
  }
287
302
  }
288
303
 
289
- // Add card if configured
290
- if (getCardConfig()) {
304
+ // Add card if configured (gated for launch)
305
+ if (ENABLE_CARD_PAYMENT && getCardConfig()) {
291
306
  methods.push("card");
292
307
  }
293
308
 
294
- // Respect defaultPaymentMethod — move it to front of list
309
+ // Respect defaultPaymentMethod — move it to front of list (ignore card when disabled)
295
310
  const defaultMethod = getConfig().defaultPaymentMethod;
296
- if (defaultMethod) {
311
+ if (defaultMethod && (defaultMethod !== "card" || ENABLE_CARD_PAYMENT)) {
297
312
  const idx = methods.indexOf(defaultMethod);
298
313
  if (idx > 0) {
299
314
  methods.splice(idx, 1);
@@ -1,4 +1,10 @@
1
- import { addWallet, getDefaultWallet, getWallets, type WalletEntry } from "./config.js";
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,6 +9,7 @@ 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";
@@ -75,24 +76,40 @@ interface SolanaChargeClientConfig {
75
76
  rpcUrl?: string;
76
77
  }
77
78
 
78
- async function resolveRecipientTokenAccount(
79
+ export async function resolveRecipientTokenAccount(
79
80
  connection: Connection,
80
81
  mint: PublicKey,
81
82
  recipient: PublicKey,
82
- ): Promise<PublicKey> {
83
+ ): Promise<{ tokenAccount: PublicKey; needsCreateAssociatedAccount: boolean }> {
83
84
  const accountInfo = await connection.getParsedAccountInfo(recipient, "confirmed");
84
85
  const owner = accountInfo.value?.owner;
85
86
  if (owner && owner.toBase58() === TOKEN_PROGRAM_ID.toBase58()) {
86
- return recipient;
87
+ return {
88
+ tokenAccount: recipient,
89
+ needsCreateAssociatedAccount: false,
90
+ };
87
91
  }
88
92
 
89
- return getAssociatedTokenAddressSync(
93
+ const associatedTokenAccount = getAssociatedTokenAddressSync(
90
94
  mint,
91
95
  recipient,
92
96
  false,
93
97
  TOKEN_PROGRAM_ID,
94
98
  ASSOCIATED_TOKEN_PROGRAM_ID,
95
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
+ };
96
113
  }
97
114
 
98
115
  export function solanaChargeClient(config: SolanaChargeClientConfig) {
@@ -114,27 +131,44 @@ export function solanaChargeClient(config: SolanaChargeClientConfig) {
114
131
  TOKEN_PROGRAM_ID,
115
132
  ASSOCIATED_TOKEN_PROGRAM_ID,
116
133
  );
117
- const destinationTokenAccount = await resolveRecipientTokenAccount(
134
+ const destination = await resolveRecipientTokenAccount(
118
135
  connection,
119
136
  mint,
120
137
  recipient,
121
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
+ }
122
153
 
123
- const instruction = createTransferCheckedInstruction(
154
+ instructions.push(createTransferCheckedInstruction(
124
155
  sourceTokenAccount,
125
156
  mint,
126
- destinationTokenAccount,
157
+ destination.tokenAccount,
127
158
  owner,
128
159
  amount,
129
160
  decimals,
130
- );
161
+ ));
131
162
 
132
163
  const { blockhash } = await connection.getLatestBlockhash("confirmed");
133
164
 
134
165
  const transaction = new Transaction({
135
166
  feePayer: owner,
136
167
  recentBlockhash: blockhash,
137
- }).add(instruction);
168
+ });
169
+ for (const instruction of instructions) {
170
+ transaction.add(instruction);
171
+ }
138
172
 
139
173
  const signature = await sendAndConfirmTransaction(connection, transaction, [keypair], {
140
174
  commitment: "confirmed",
@@ -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/index.ts CHANGED
@@ -14,7 +14,6 @@ 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";
18
17
 
19
18
  // ── Resources ────────────────────────────────────────────────────
20
19
  import { registerAgentResources } from "./resources/agents.js";
@@ -47,13 +46,13 @@ export async function startMcpServer(): Promise<void> {
47
46
  "5. Ask user to rate or tip after a successful run",
48
47
  "",
49
48
  "PAYMENT:",
50
- "- Credit/debit card is the default and easiest way to pay — open the setup page to connect, no funding needed.",
51
- "- Crypto wallets (Tempo USDC, Base USDC, Solana USDC) are available for advanced users.",
49
+ "- Crypto wallets (Tempo USDC, Base USDC, Solana USDC) are the core supported rails.",
50
+ "- Saved cards can also be used, but card-backed MPP depends on Stripe Shared Payment Token availability in the current environment.",
52
51
  "- Card and crypto are SEPARATE payment methods. Card charges a credit card; crypto sends USDC on-chain.",
53
- "- Card is set as the default payment method when configured.",
54
- "- run_agent() and solve() require pay_with explicitly.",
52
+ "- When a card is configured, it becomes the default payment method. Use wallet_status to confirm whether `Card MPP: ready` before relying on it.",
53
+ "- run_agent() and solve() auto-detect the default compatible payment method when pay_with is omitted.",
54
+ "- Include pay_with explicitly when you need a specific rail or want deterministic control over the payment method used.",
55
55
  "- Use wallet_status to see the exact payment methods the user has configured before calling a paid tool.",
56
- "- Use open_observability_dashboard() to open a secure web usage dashboard for runs/spend/rebates.",
57
56
  "- Payment is automatic once configured. Users are never charged for failed runs.",
58
57
  "- Do NOT ask the user to set up payment before they try to run an agent. Let them explore freely.",
59
58
  "- If a specific payment method fails, report the error clearly. Do NOT silently fall back to a different method.",
@@ -81,7 +80,6 @@ export async function startMcpServer(): Promise<void> {
81
80
  registerFavoriteTools(server);
82
81
  registerTipTools(server);
83
82
  registerPassTools(server);
84
- registerObservabilityTools(server);
85
83
 
86
84
  // Register resources
87
85
  registerAgentResources(server);
@@ -62,7 +62,7 @@ export function registerPrompts(server: McpServer) {
62
62
  "",
63
63
  "Steps:",
64
64
  "1. Use search_agents to find relevant agents",
65
- "2. Use compare_agents on the top 2-3 candidates",
65
+ "2. Use get_agent to inspect the top candidate (schema, pricing, rating)",
66
66
  "3. Recommend the best one based on price, rating, and success rate",
67
67
  "4. Ask if I want to run it",
68
68
  ].join("\n"),