@agentwonderland/mcp 0.1.23 → 0.1.25

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 (95) hide show
  1. package/dist/core/__tests__/amount-utils.test.d.ts +1 -0
  2. package/dist/core/__tests__/amount-utils.test.js +11 -0
  3. package/dist/core/__tests__/card-setup.test.d.ts +1 -0
  4. package/dist/core/__tests__/card-setup.test.js +99 -0
  5. package/dist/core/__tests__/formatters.test.d.ts +1 -0
  6. package/dist/core/__tests__/formatters.test.js +15 -0
  7. package/dist/core/__tests__/passes.test.d.ts +1 -0
  8. package/dist/core/__tests__/passes.test.js +82 -0
  9. package/dist/core/__tests__/payments.test.d.ts +1 -0
  10. package/dist/core/__tests__/payments.test.js +101 -0
  11. package/dist/core/__tests__/principal.test.d.ts +1 -0
  12. package/dist/core/__tests__/principal.test.js +67 -0
  13. package/dist/core/__tests__/spend-policy.test.d.ts +1 -0
  14. package/dist/core/__tests__/spend-policy.test.js +40 -0
  15. package/dist/core/amount-utils.d.ts +1 -0
  16. package/dist/core/amount-utils.js +4 -0
  17. package/dist/core/api-client.d.ts +9 -4
  18. package/dist/core/api-client.js +52 -22
  19. package/dist/core/base-charge.js +3 -2
  20. package/dist/core/card-setup.d.ts +20 -13
  21. package/dist/core/card-setup.js +85 -29
  22. package/dist/core/config.d.ts +22 -0
  23. package/dist/core/config.js +46 -2
  24. package/dist/core/formatters.d.ts +4 -3
  25. package/dist/core/formatters.js +10 -8
  26. package/dist/core/index.d.ts +2 -0
  27. package/dist/core/index.js +2 -0
  28. package/dist/core/ows-adapter.d.ts +10 -2
  29. package/dist/core/ows-adapter.js +54 -10
  30. package/dist/core/passes.d.ts +40 -0
  31. package/dist/core/passes.js +32 -0
  32. package/dist/core/payments.d.ts +8 -0
  33. package/dist/core/payments.js +111 -17
  34. package/dist/core/principal.d.ts +2 -0
  35. package/dist/core/principal.js +109 -0
  36. package/dist/core/solana-charge.d.ts +9 -0
  37. package/dist/core/solana-charge.js +96 -0
  38. package/dist/core/spend-policy.d.ts +12 -0
  39. package/dist/core/spend-policy.js +53 -0
  40. package/dist/core/types.d.ts +11 -2
  41. package/dist/index.js +11 -3
  42. package/dist/prompts/index.js +4 -2
  43. package/dist/resources/agents.js +1 -1
  44. package/dist/resources/wallet.js +8 -1
  45. package/dist/tools/__tests__/_payment-confirmation.test.d.ts +1 -0
  46. package/dist/tools/__tests__/_payment-confirmation.test.js +30 -0
  47. package/dist/tools/_payment-confirmation.d.ts +6 -0
  48. package/dist/tools/_payment-confirmation.js +28 -0
  49. package/dist/tools/agent-info.js +16 -2
  50. package/dist/tools/favorites.js +1 -1
  51. package/dist/tools/index.d.ts +1 -0
  52. package/dist/tools/index.js +1 -0
  53. package/dist/tools/observability.d.ts +2 -0
  54. package/dist/tools/observability.js +20 -0
  55. package/dist/tools/passes.d.ts +2 -0
  56. package/dist/tools/passes.js +157 -0
  57. package/dist/tools/run.js +127 -53
  58. package/dist/tools/solve.js +115 -51
  59. package/dist/tools/wallet.js +110 -59
  60. package/package.json +3 -1
  61. package/src/core/__tests__/amount-utils.test.ts +13 -0
  62. package/src/core/__tests__/card-setup.test.ts +118 -0
  63. package/src/core/__tests__/formatters.test.ts +17 -0
  64. package/src/core/__tests__/passes.test.ts +94 -0
  65. package/src/core/__tests__/payments.test.ts +122 -0
  66. package/src/core/__tests__/principal.test.ts +87 -0
  67. package/src/core/__tests__/spend-policy.test.ts +58 -0
  68. package/src/core/amount-utils.ts +5 -0
  69. package/src/core/api-client.ts +70 -23
  70. package/src/core/base-charge.ts +3 -2
  71. package/src/core/card-setup.ts +109 -34
  72. package/src/core/config.ts +74 -3
  73. package/src/core/formatters.ts +13 -9
  74. package/src/core/index.ts +2 -0
  75. package/src/core/ows-adapter.ts +74 -8
  76. package/src/core/passes.ts +74 -0
  77. package/src/core/payments.ts +130 -17
  78. package/src/core/principal.ts +128 -0
  79. package/src/core/solana-charge.ts +150 -0
  80. package/src/core/spend-policy.ts +69 -0
  81. package/src/core/types.ts +11 -2
  82. package/src/index.ts +11 -3
  83. package/src/prompts/index.ts +4 -2
  84. package/src/resources/agents.ts +1 -1
  85. package/src/resources/wallet.ts +8 -1
  86. package/src/tools/__tests__/_payment-confirmation.test.ts +45 -0
  87. package/src/tools/_payment-confirmation.ts +52 -0
  88. package/src/tools/agent-info.ts +25 -2
  89. package/src/tools/favorites.ts +1 -4
  90. package/src/tools/index.ts +1 -0
  91. package/src/tools/observability.ts +43 -0
  92. package/src/tools/passes.ts +228 -0
  93. package/src/tools/run.ts +174 -57
  94. package/src/tools/solve.ts +147 -59
  95. package/src/tools/wallet.ts +132 -62
@@ -1,23 +1,30 @@
1
+ export interface CardSetupResult {
2
+ last4: string;
3
+ brand: string;
4
+ consumerToken: string;
5
+ }
6
+ export interface CardCapabilities {
7
+ spt_status: "enabled" | "unavailable" | "unknown";
8
+ message?: string;
9
+ }
1
10
  /**
2
- * Initiate card setup creates a Stripe Checkout session and returns
3
- * QR code + short URL for the user to scan.
11
+ * Create a new card setup session or resume the pending one from config.
4
12
  */
5
- export declare function initiateCardSetup(): Promise<{
6
- qr: string;
13
+ export declare function getOrCreatePendingCardSetup(): Promise<{
7
14
  url: string;
8
15
  token: string;
16
+ isNew: boolean;
9
17
  }>;
10
18
  /**
11
- * Format the card setup prompt as multiple content blocks.
12
- * Block 1: QR code (may be collapsed in some clients)
13
- * Block 2: URL + instructions (short, always visible)
19
+ * Format the card setup prompt as a single text block.
20
+ *
21
+ * Some MCP clients only surface one text chunk or collapse multi-part tool
22
+ * output, which can hide the QR code entirely. Keeping everything in one
23
+ * message makes the browser handoff much more reliable.
14
24
  */
15
- export declare function formatCardSetupBlocks(qr: string, url: string): string[];
25
+ export declare function formatCardSetupBlocks(url: string): string[];
16
26
  /**
17
27
  * Poll for card setup completion. Returns the card details or null on timeout.
18
28
  */
19
- export declare function pollCardSetup(token: string, timeoutMs?: number): Promise<{
20
- last4: string;
21
- brand: string;
22
- consumerToken: string;
23
- } | null>;
29
+ export declare function pollCardSetup(token: string, timeoutMs?: number): Promise<CardSetupResult | null>;
30
+ export declare function getCardCapabilities(): Promise<CardCapabilities>;
@@ -1,41 +1,50 @@
1
- import QRCode from "qrcode";
2
- import { apiPost, apiGet } from "./api-client.js";
3
- import { getApiUrl, setCardConfig } from "./config.js";
1
+ import { apiPost, apiGet, ApiError } from "./api-client.js";
2
+ import { getApiUrl, setCardConfig, getPendingCardSetupToken, setPendingCardSetupToken, } from "./config.js";
3
+ let cachedCapabilities = null;
4
+ function buildCardSetupUrl(token) {
5
+ return `${getApiUrl()}/card/handoff/${token}`;
6
+ }
4
7
  /**
5
- * Initiate card setup creates a Stripe Checkout session and returns
6
- * QR code + short URL for the user to scan.
8
+ * Create a new card setup session or resume the pending one from config.
7
9
  */
8
- export async function initiateCardSetup() {
9
- const { url: _checkoutUrl, token } = await apiPost("/card/setup", {});
10
- // Short redirect URL keeps the QR code small and scannable
11
- const apiUrl = getApiUrl();
12
- const shortUrl = `${apiUrl}/card/link/${token}`;
13
- const rawQr = await QRCode.toString(shortUrl, {
14
- type: "utf8",
15
- errorCorrectionLevel: "L",
16
- margin: 2,
17
- });
18
- // Pad every line with consistent leading spaces so the QR aligns
19
- // properly in terminal renderers that add prefixes to the first line
20
- const qr = rawQr.split("\n").map((line) => " " + line).join("\n");
21
- return { qr, url: shortUrl, token };
10
+ export async function getOrCreatePendingCardSetup() {
11
+ const pendingToken = getPendingCardSetupToken();
12
+ if (pendingToken) {
13
+ const url = buildCardSetupUrl(pendingToken);
14
+ return {
15
+ url,
16
+ token: pendingToken,
17
+ isNew: false,
18
+ };
19
+ }
20
+ const { token } = await apiPost("/card/setup", {});
21
+ setPendingCardSetupToken(token);
22
+ const url = buildCardSetupUrl(token);
23
+ return {
24
+ url,
25
+ token,
26
+ isNew: true,
27
+ };
22
28
  }
23
29
  /**
24
- * Format the card setup prompt as multiple content blocks.
25
- * Block 1: QR code (may be collapsed in some clients)
26
- * Block 2: URL + instructions (short, always visible)
30
+ * Format the card setup prompt as a single text block.
31
+ *
32
+ * Some MCP clients only surface one text chunk or collapse multi-part tool
33
+ * output, which can hide the QR code entirely. Keeping everything in one
34
+ * message makes the browser handoff much more reliable.
27
35
  */
28
- export function formatCardSetupBlocks(qr, url) {
36
+ export function formatCardSetupBlocks(url) {
29
37
  return [
30
- "\n" + qr.trim(),
31
38
  [
32
- `IMPORTANT: You MUST present this link to the user so they can connect a payment card:`,
39
+ "Open this setup page to connect your card:",
33
40
  "",
34
41
  url,
35
42
  "",
36
- `Tell the user: "Scan the QR code above or open this link to connect your card. Let me know when you're done."`,
43
+ "The setup page will take you to Stripe to securely save the card.",
44
+ "",
45
+ `Tell the user: "Open this setup page to connect your card. Let me know when you're done."`,
37
46
  `After they confirm, call wallet_setup({ action: "add-card" }) to complete setup.`,
38
- `Crypto wallets (Base/Tempo USDC) are also available via wallet_setup({ action: "create" }).`,
47
+ `Crypto wallets (Tempo, Base, or Solana USDC) are also available via wallet_setup({ action: "create" }).`,
39
48
  ].join("\n"),
40
49
  ];
41
50
  }
@@ -45,7 +54,6 @@ export function formatCardSetupBlocks(qr, url) {
45
54
  export async function pollCardSetup(token, timeoutMs = 120_000) {
46
55
  const deadline = Date.now() + timeoutMs;
47
56
  while (Date.now() < deadline) {
48
- await new Promise((r) => setTimeout(r, 3000));
49
57
  try {
50
58
  const status = await apiGet(`/card/status?token=${token}`);
51
59
  if (status.status === "complete" && status.consumer_token) {
@@ -61,12 +69,60 @@ export async function pollCardSetup(token, timeoutMs = 120_000) {
61
69
  last4: card.last4,
62
70
  brand: card.brand,
63
71
  });
72
+ setPendingCardSetupToken(null);
64
73
  return card;
65
74
  }
66
75
  }
67
- catch {
76
+ catch (err) {
77
+ if (err instanceof ApiError && err.status === 404) {
78
+ setPendingCardSetupToken(null);
79
+ return null;
80
+ }
68
81
  // Keep polling
69
82
  }
83
+ const remainingMs = deadline - Date.now();
84
+ if (remainingMs <= 0)
85
+ break;
86
+ await new Promise((r) => setTimeout(r, Math.min(3000, remainingMs)));
70
87
  }
71
88
  return null;
72
89
  }
90
+ export async function getCardCapabilities() {
91
+ const apiUrl = getApiUrl();
92
+ if (cachedCapabilities && cachedCapabilities.apiUrl === apiUrl && cachedCapabilities.expiresAt > Date.now()) {
93
+ return cachedCapabilities.value;
94
+ }
95
+ try {
96
+ const capabilities = await apiGet("/card/capabilities");
97
+ cachedCapabilities = {
98
+ value: capabilities,
99
+ apiUrl,
100
+ expiresAt: Date.now() + 30_000,
101
+ };
102
+ return capabilities;
103
+ }
104
+ catch (err) {
105
+ if (err instanceof ApiError) {
106
+ const fallback = {
107
+ spt_status: "unknown",
108
+ message: err.message,
109
+ };
110
+ cachedCapabilities = {
111
+ value: fallback,
112
+ apiUrl,
113
+ expiresAt: Date.now() + 10_000,
114
+ };
115
+ return fallback;
116
+ }
117
+ const fallback = {
118
+ spt_status: "unknown",
119
+ message: "Could not determine card payment availability.",
120
+ };
121
+ cachedCapabilities = {
122
+ value: fallback,
123
+ apiUrl,
124
+ expiresAt: Date.now() + 10_000,
125
+ };
126
+ return fallback;
127
+ }
128
+ }
@@ -13,6 +13,17 @@ export interface CardConfig {
13
13
  last4: string;
14
14
  brand: string;
15
15
  }
16
+ export interface SpendPolicy {
17
+ maxPerTxUsd?: number;
18
+ maxPerDayUsd?: number;
19
+ requireConfirmationAboveUsd?: number;
20
+ }
21
+ export interface SpendLedgerEntry {
22
+ method: string;
23
+ amountUsd: number;
24
+ day: string;
25
+ timestamp: string;
26
+ }
16
27
  export interface Config {
17
28
  apiUrl: string;
18
29
  apiKey: string | null;
@@ -21,11 +32,16 @@ export interface Config {
21
32
  defaultWallet: string | null;
22
33
  defaultPaymentMethod?: string;
23
34
  card: CardConfig | null;
35
+ pendingCardSetupToken?: string | null;
24
36
  favorites: string[];
25
37
  /** Require user confirmation before spending. Default: true. Set false for headless/automated use. */
26
38
  confirmBeforeSpend: boolean;
27
39
  /** Auto-tip amount in USD for successful runs. Default: 0 (no auto-tip). */
28
40
  defaultTipAmount: number;
41
+ /** Optional per-method spend policies enforced client-side by MCP tools. */
42
+ spendPolicies?: Record<string, SpendPolicy>;
43
+ /** Daily spend ledger used to enforce max-per-day limits. */
44
+ spendLedger?: SpendLedgerEntry[];
29
45
  }
30
46
  /** All supported chain identifiers. */
31
47
  export declare const SUPPORTED_CHAINS: readonly ["tempo", "base", "solana"];
@@ -42,6 +58,10 @@ export declare function getApiKey(): string | null;
42
58
  export declare function isAuthenticated(): boolean;
43
59
  export declare function requiresSpendConfirmation(): boolean;
44
60
  export declare function getDefaultTipAmount(): number;
61
+ export declare function getSpendPolicy(method: string): SpendPolicy | null;
62
+ export declare function setSpendPolicy(method: string, policy: SpendPolicy): void;
63
+ export declare function getSpendLedger(): SpendLedgerEntry[];
64
+ export declare function saveSpendLedger(entries: SpendLedgerEntry[]): void;
45
65
  /**
46
66
  * Get all wallets from config + env var synthetic wallets.
47
67
  */
@@ -73,6 +93,8 @@ export declare function getCardConfig(): CardConfig | null;
73
93
  * Save card configuration after setup.
74
94
  */
75
95
  export declare function setCardConfig(card: CardConfig | null): void;
96
+ export declare function getPendingCardSetupToken(): string | null;
97
+ export declare function setPendingCardSetupToken(token: string | null): void;
76
98
  /**
77
99
  * Resolve a payment method string to a wallet + chain.
78
100
  * Accepts: wallet ID, chain name, or "card".
@@ -34,10 +34,14 @@ function migrateIfNeeded(raw) {
34
34
  userId: raw.userId ?? null,
35
35
  wallets: raw.wallets,
36
36
  defaultWallet: raw.defaultWallet ?? null,
37
+ defaultPaymentMethod: raw.defaultPaymentMethod ?? undefined,
37
38
  card: raw.card ?? null,
39
+ pendingCardSetupToken: r.pendingCardSetupToken ?? null,
38
40
  favorites: r.favorites ?? [],
39
41
  confirmBeforeSpend: r.confirmBeforeSpend !== false,
40
42
  defaultTipAmount: typeof r.defaultTipAmount === "number" ? r.defaultTipAmount : 0,
43
+ spendPolicies: r.spendPolicies,
44
+ spendLedger: r.spendLedger,
41
45
  };
42
46
  }
43
47
  // Build wallets from legacy flat fields
@@ -103,10 +107,14 @@ function migrateIfNeeded(raw) {
103
107
  userId: raw.userId ?? null,
104
108
  wallets,
105
109
  defaultWallet,
110
+ defaultPaymentMethod: raw.defaultPaymentMethod ?? undefined,
106
111
  card,
112
+ pendingCardSetupToken: null,
107
113
  favorites: [],
108
114
  confirmBeforeSpend: true,
109
115
  defaultTipAmount: 0,
116
+ spendPolicies: {},
117
+ spendLedger: [],
110
118
  };
111
119
  // Write migrated config (only if there was something to migrate)
112
120
  if (raw.tempoPrivateKey || raw.evmPrivateKey || raw.stripeConsumerToken) {
@@ -124,9 +132,12 @@ export function getConfig() {
124
132
  wallets: [],
125
133
  defaultWallet: null,
126
134
  card: null,
135
+ pendingCardSetupToken: null,
127
136
  favorites: [],
128
137
  confirmBeforeSpend: true,
129
138
  defaultTipAmount: 0,
139
+ spendPolicies: {},
140
+ spendLedger: [],
130
141
  };
131
142
  if (!existsSync(CONFIG_FILE)) {
132
143
  return defaults;
@@ -167,6 +178,22 @@ export function requiresSpendConfirmation() {
167
178
  export function getDefaultTipAmount() {
168
179
  return getConfig().defaultTipAmount;
169
180
  }
181
+ export function getSpendPolicy(method) {
182
+ const policies = getConfig().spendPolicies ?? {};
183
+ return policies[method] ?? null;
184
+ }
185
+ export function setSpendPolicy(method, policy) {
186
+ const config = getConfig();
187
+ const policies = config.spendPolicies ?? {};
188
+ policies[method] = policy;
189
+ saveConfig({ spendPolicies: policies });
190
+ }
191
+ export function getSpendLedger() {
192
+ return getConfig().spendLedger ?? [];
193
+ }
194
+ export function saveSpendLedger(entries) {
195
+ saveConfig({ spendLedger: entries });
196
+ }
170
197
  // ── Wallet helpers ─────────────────────────────────────────────────
171
198
  /**
172
199
  * Get all wallets from config + env var synthetic wallets.
@@ -263,13 +290,30 @@ export function getCardConfig() {
263
290
  * Save card configuration after setup.
264
291
  */
265
292
  export function setCardConfig(card) {
293
+ const current = getConfig();
266
294
  if (card) {
267
- saveConfig({ card, defaultPaymentMethod: "card" });
295
+ saveConfig({
296
+ card,
297
+ defaultPaymentMethod: "card",
298
+ pendingCardSetupToken: null,
299
+ });
268
300
  }
269
301
  else {
270
- saveConfig({ card });
302
+ saveConfig({
303
+ card,
304
+ pendingCardSetupToken: null,
305
+ defaultPaymentMethod: current.defaultPaymentMethod === "card"
306
+ ? undefined
307
+ : current.defaultPaymentMethod,
308
+ });
271
309
  }
272
310
  }
311
+ export function getPendingCardSetupToken() {
312
+ return getConfig().pendingCardSetupToken ?? null;
313
+ }
314
+ export function setPendingCardSetupToken(token) {
315
+ saveConfig({ pendingCardSetupToken: token });
316
+ }
273
317
  /**
274
318
  * Resolve a payment method string to a wallet + chain.
275
319
  * Accepts: wallet ID, chain name, or "card".
@@ -11,7 +11,7 @@ export declare function isFileOutput(output: unknown): output is {
11
11
  mime_type?: string;
12
12
  size_bytes?: number;
13
13
  };
14
- export declare function formatPrice(pricePer1kTokens?: string | null, pricingModel?: string | null): string;
14
+ export declare function formatPrice(pricePerRunUsd?: string | null): string;
15
15
  interface AgentLike {
16
16
  id?: string;
17
17
  name?: string;
@@ -19,8 +19,7 @@ interface AgentLike {
19
19
  avgRating?: number | null;
20
20
  ratingCount?: number;
21
21
  totalExecutions?: number;
22
- pricePer1kTokens?: string;
23
- pricingModel?: string;
22
+ pricePerRunUsd?: string;
24
23
  stats?: {
25
24
  completedJobs?: number;
26
25
  avgRating?: number | null;
@@ -43,6 +42,8 @@ interface RunResultLike {
43
42
  estimated_cost?: number;
44
43
  input_tokens?: number;
45
44
  tags?: string[];
45
+ consumption_mode?: string;
46
+ credit_pack_id?: string;
46
47
  }
47
48
  export declare function formatRunResult(result: RunResultLike, opts?: {
48
49
  paymentMethod?: string;
@@ -32,20 +32,18 @@ export function isFileOutput(output) {
32
32
  return output != null && typeof output === "object" && output.type === "file" && !!output.url;
33
33
  }
34
34
  // ── Price formatting ─────────────────────────────────────────────
35
- export function formatPrice(pricePer1kTokens, pricingModel) {
36
- if (!pricePer1kTokens)
35
+ export function formatPrice(pricePerRunUsd) {
36
+ if (!pricePerRunUsd)
37
37
  return "free";
38
- const p = parseFloat(pricePer1kTokens);
39
- if (pricingModel === "fixed")
40
- return `$${p.toFixed(2)}/req`;
41
- return `$${p.toFixed(3)}/1k tokens`;
38
+ const p = parseFloat(pricePerRunUsd);
39
+ return `$${p.toFixed(2)}/req`;
42
40
  }
43
41
  export function agentLine(agent) {
44
42
  const name = agent.name ?? "Unknown";
45
43
  const slug = agent.slug ? ` (${agent.slug})` : "";
46
44
  const rating = agent.avgRating ?? agent.stats?.avgRating ?? null;
47
45
  const jobs = agent.stats?.completedJobs ?? agent.totalExecutions ?? 0;
48
- const price = formatPrice(agent.pricePer1kTokens, agent.pricingModel);
46
+ const price = formatPrice(agent.pricePerRunUsd);
49
47
  const reliability = agent.successRate != null && Number(agent.successRate) < 1
50
48
  ? ` • ${(Number(agent.successRate) * 100).toFixed(0)}% reliable`
51
49
  : "";
@@ -110,6 +108,7 @@ export function formatRunResult(result, opts) {
110
108
  }
111
109
  // Summary line
112
110
  const cost = result.cost ?? result.estimated_cost;
111
+ const usedCreditPack = result.consumption_mode === "credit_pack";
113
112
  const status = result.status === "success" || result.status === "completed" ? "✓" : "✗";
114
113
  const agent = result.agent_name ?? result.agent_id ?? "";
115
114
  const costStr = cost != null ? `$${cost.toFixed(cost < 0.01 ? 6 : 2)}` : "";
@@ -119,7 +118,10 @@ export function formatRunResult(result, opts) {
119
118
  if (result.error_code) {
120
119
  lines.push(`Error: ${result.error_code}`);
121
120
  }
122
- if (costStr) {
121
+ if (usedCreditPack) {
122
+ lines.push(`Covered by credit pack${result.credit_pack_id ? ` (${result.credit_pack_id})` : ""}`);
123
+ }
124
+ if (costStr && !usedCreditPack) {
123
125
  lines.push(`Paid: ${costStr}${method ? ` via ${method}` : ""}`);
124
126
  }
125
127
  if (result.job_id) {
@@ -1,6 +1,8 @@
1
1
  export * from "./api-client.js";
2
2
  export * from "./config.js";
3
3
  export * from "./payments.js";
4
+ export * from "./principal.js";
5
+ export * from "./passes.js";
4
6
  export * from "./ows-adapter.js";
5
7
  export * from "./formatters.js";
6
8
  export * from "./types.js";
@@ -1,6 +1,8 @@
1
1
  export * from "./api-client.js";
2
2
  export * from "./config.js";
3
3
  export * from "./payments.js";
4
+ export * from "./principal.js";
5
+ export * from "./passes.js";
4
6
  export * from "./ows-adapter.js";
5
7
  export * from "./formatters.js";
6
8
  export * from "./types.js";
@@ -8,6 +8,7 @@
8
8
  * ALL imports are dynamic so the MCP server works without OWS installed.
9
9
  */
10
10
  import type { LocalAccount } from "viem/accounts";
11
+ import type { Keypair } from "@solana/web3.js";
11
12
  /**
12
13
  * Check whether the OWS native module can be loaded.
13
14
  */
@@ -19,17 +20,19 @@ export declare function isOwsAvailable(): Promise<boolean>;
19
20
  * @param chain - OWS chain identifier (default "evm")
20
21
  */
21
22
  export declare function owsAccountFromWalletId(walletId: string, _chain?: string): Promise<LocalAccount>;
23
+ export declare function owsSolanaKeypairFromWalletId(walletId: string): Promise<Keypair>;
24
+ export declare function getOwsWalletAddress(walletId: string, chain?: "evm" | "solana"): Promise<string>;
22
25
  /**
23
26
  * Create a new OWS wallet and return its ID + EVM address.
24
27
  */
25
- export declare function createOwsWallet(name: string): Promise<{
28
+ export declare function createOwsWallet(name: string, chain?: "evm" | "solana"): Promise<{
26
29
  walletId: string;
27
30
  address: string;
28
31
  }>;
29
32
  /**
30
33
  * Import an existing EVM private key into OWS.
31
34
  */
32
- export declare function importKeyToOws(privateKey: string, name: string): Promise<{
35
+ export declare function importKeyToOws(privateKey: string, name: string, chain?: "evm" | "solana"): Promise<{
33
36
  walletId: string;
34
37
  address: string;
35
38
  }>;
@@ -41,3 +44,8 @@ export declare function listOwsWallets(): Promise<Array<{
41
44
  name: string;
42
45
  address: string;
43
46
  }>>;
47
+ export declare function listOwsWalletsByChain(chain?: "evm" | "solana"): Promise<Array<{
48
+ id: string;
49
+ name: string;
50
+ address: string;
51
+ }>>;
@@ -38,6 +38,26 @@ function findEvmAccount(wallet) {
38
38
  }
39
39
  return evm;
40
40
  }
41
+ function findSolanaAccount(wallet) {
42
+ const solana = wallet.accounts.find((a) => a.chainId.startsWith("solana"));
43
+ if (!solana) {
44
+ throw new Error(`Wallet "${wallet.name}" (${wallet.id}) has no Solana account.`);
45
+ }
46
+ return solana;
47
+ }
48
+ function ed25519HexToBytes(privateKeyHex) {
49
+ return Uint8Array.from(Buffer.from(privateKeyHex, "hex"));
50
+ }
51
+ function keypairFromEd25519Hex(privateKeyHex, KeypairCtor) {
52
+ const bytes = ed25519HexToBytes(privateKeyHex);
53
+ if (bytes.length === 32) {
54
+ return KeypairCtor.fromSeed(bytes);
55
+ }
56
+ if (bytes.length === 64) {
57
+ return KeypairCtor.fromSecretKey(bytes);
58
+ }
59
+ throw new Error(`Unsupported ed25519 key length: ${bytes.length} bytes.`);
60
+ }
41
61
  // ── Public API ───────────────────────────────────────────────────
42
62
  /**
43
63
  * Check whether the OWS native module can be loaded.
@@ -73,38 +93,62 @@ export async function owsAccountFromWalletId(walletId, _chain) {
73
93
  const { privateKeyToAccount } = await import("viem/accounts");
74
94
  return privateKeyToAccount(`0x${keys.secp256k1}`);
75
95
  }
96
+ export async function owsSolanaKeypairFromWalletId(walletId) {
97
+ const ows = await loadOws();
98
+ const wallet = ows.getWallet(walletId);
99
+ findSolanaAccount(wallet);
100
+ const exported = ows.exportWallet(walletId);
101
+ const keys = JSON.parse(exported);
102
+ if (!keys.ed25519) {
103
+ throw new Error(`Wallet "${wallet.name}" has no ed25519 key for Solana signing.`);
104
+ }
105
+ const { Keypair } = await import("@solana/web3.js");
106
+ return keypairFromEd25519Hex(keys.ed25519, Keypair);
107
+ }
108
+ export async function getOwsWalletAddress(walletId, chain = "evm") {
109
+ const ows = await loadOws();
110
+ const wallet = ows.getWallet(walletId);
111
+ return chain === "solana"
112
+ ? findSolanaAccount(wallet).address
113
+ : findEvmAccount(wallet).address;
114
+ }
76
115
  /**
77
116
  * Create a new OWS wallet and return its ID + EVM address.
78
117
  */
79
- export async function createOwsWallet(name) {
118
+ export async function createOwsWallet(name, chain = "evm") {
80
119
  const ows = await loadOws();
81
120
  const wallet = ows.createWallet(name);
82
- const evmAccount = findEvmAccount(wallet);
83
- return { walletId: wallet.id, address: evmAccount.address };
121
+ const account = chain === "solana" ? findSolanaAccount(wallet) : findEvmAccount(wallet);
122
+ return { walletId: wallet.id, address: account.address };
84
123
  }
85
124
  /**
86
125
  * Import an existing EVM private key into OWS.
87
126
  */
88
- export async function importKeyToOws(privateKey, name) {
127
+ export async function importKeyToOws(privateKey, name, chain = "evm") {
89
128
  const ows = await loadOws();
90
129
  const normalizedKey = privateKey.startsWith("0x")
91
130
  ? privateKey.slice(2)
92
131
  : privateKey;
93
- const wallet = ows.importWalletPrivateKey(name, normalizedKey, null, null, "evm");
94
- const evmAccount = findEvmAccount(wallet);
95
- return { walletId: wallet.id, address: evmAccount.address };
132
+ const wallet = ows.importWalletPrivateKey(name, normalizedKey, null, null, chain);
133
+ const account = chain === "solana" ? findSolanaAccount(wallet) : findEvmAccount(wallet);
134
+ return { walletId: wallet.id, address: account.address };
96
135
  }
97
136
  /**
98
137
  * List all OWS wallets that have an EVM account.
99
138
  */
100
139
  export async function listOwsWallets() {
140
+ return listOwsWalletsByChain("evm");
141
+ }
142
+ export async function listOwsWalletsByChain(chain = "evm") {
101
143
  const ows = await loadOws();
102
144
  const wallets = ows.listWallets();
103
145
  const result = [];
104
146
  for (const w of wallets) {
105
- const evm = w.accounts.find((a) => a.chainId.startsWith("eip155") || a.chainId.startsWith("evm"));
106
- if (evm) {
107
- result.push({ id: w.id, name: w.name, address: evm.address });
147
+ const account = chain === "solana"
148
+ ? w.accounts.find((a) => a.chainId.startsWith("solana"))
149
+ : w.accounts.find((a) => a.chainId.startsWith("eip155") || a.chainId.startsWith("evm"));
150
+ if (account) {
151
+ result.push({ id: w.id, name: w.name, address: account.address });
108
152
  }
109
153
  }
110
154
  return result;
@@ -0,0 +1,40 @@
1
+ import type { AgentRecord } from "./types.js";
2
+ export interface CreditPackOffer {
3
+ pack_id: string;
4
+ label: string;
5
+ included_units: number;
6
+ price_usd: string;
7
+ effective_price_per_unit_usd?: string;
8
+ }
9
+ export interface CreditPackRecord {
10
+ id: string;
11
+ status: string;
12
+ unit_type: string;
13
+ included_units: number;
14
+ remaining_units: number;
15
+ price_usd: string;
16
+ purchased_at: string;
17
+ pack?: {
18
+ name?: string | null;
19
+ key?: string | null;
20
+ };
21
+ }
22
+ export interface CreditPackProgramInfo {
23
+ unit_type?: string;
24
+ packs?: Array<{
25
+ key?: string;
26
+ name?: string;
27
+ included_units?: number;
28
+ price_usd?: string;
29
+ }>;
30
+ }
31
+ export interface CreditPackInventory {
32
+ consumer_principal: string | null;
33
+ offers: CreditPackOffer[];
34
+ balances: CreditPackRecord[];
35
+ }
36
+ export declare function getCreditPackProgram(agent: AgentRecord): CreditPackProgramInfo | null;
37
+ export declare function getCreditPackInventory(agentId: string): Promise<CreditPackInventory | null>;
38
+ export declare function getActiveCreditPack(inventory: CreditPackInventory | null): CreditPackRecord | null;
39
+ export declare function formatCreditPackOffer(offer: CreditPackOffer): string;
40
+ export declare function formatCreditPack(pack: CreditPackRecord): string;
@@ -0,0 +1,32 @@
1
+ import { apiGet } from "./api-client.js";
2
+ export function getCreditPackProgram(agent) {
3
+ const payment = (agent.payment ?? {});
4
+ const creditPacks = payment.credit_packs;
5
+ return creditPacks ?? null;
6
+ }
7
+ export async function getCreditPackInventory(agentId) {
8
+ try {
9
+ return await apiGet(`/agents/${agentId}/credit-packs`, { ensureConsumerPrincipal: true });
10
+ }
11
+ catch {
12
+ return null;
13
+ }
14
+ }
15
+ export function getActiveCreditPack(inventory) {
16
+ if (!inventory)
17
+ return null;
18
+ return inventory.balances
19
+ .filter((pack) => pack.status === "active" && pack.remaining_units > 0)
20
+ .sort((a, b) => new Date(a.purchased_at).getTime() - new Date(b.purchased_at).getTime())[0] ?? null;
21
+ }
22
+ export function formatCreditPackOffer(offer) {
23
+ const unitPrice = offer.effective_price_per_unit_usd
24
+ ? ` ($${Number(offer.effective_price_per_unit_usd).toFixed(2)}/unit)`
25
+ : "";
26
+ return `${offer.label} — ${offer.included_units} units for $${offer.price_usd}${unitPrice}`;
27
+ }
28
+ export function formatCreditPack(pack) {
29
+ const label = pack.pack?.name ?? pack.pack?.key ?? "Credit Pack";
30
+ const statusPrefix = pack.status === "active" ? "" : `${pack.status} • `;
31
+ return `${label}: ${statusPrefix}${pack.remaining_units}/${pack.included_units} ${pack.unit_type}s remaining`;
32
+ }