@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/src/core/card-setup.ts
CHANGED
|
@@ -1,51 +1,78 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
|
|
1
|
+
import { apiPost, apiGet, ApiError } from "./api-client.js";
|
|
2
|
+
import {
|
|
3
|
+
getApiUrl,
|
|
4
|
+
setCardConfig,
|
|
5
|
+
getPendingCardSetupToken,
|
|
6
|
+
setPendingCardSetupToken,
|
|
7
|
+
} from "./config.js";
|
|
8
|
+
|
|
9
|
+
export interface CardSetupResult {
|
|
10
|
+
last4: string;
|
|
11
|
+
brand: string;
|
|
12
|
+
consumerToken: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface CardCapabilities {
|
|
16
|
+
spt_status: "enabled" | "unavailable" | "unknown";
|
|
17
|
+
message?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
let cachedCapabilities:
|
|
21
|
+
| { value: CardCapabilities; expiresAt: number; apiUrl: string }
|
|
22
|
+
| null = null;
|
|
23
|
+
|
|
24
|
+
function buildCardSetupUrl(token: string): string {
|
|
25
|
+
return `${getApiUrl()}/card/handoff/${token}`;
|
|
26
|
+
}
|
|
4
27
|
|
|
5
28
|
/**
|
|
6
|
-
*
|
|
7
|
-
* QR code + short URL for the user to scan.
|
|
29
|
+
* Create a new card setup session or resume the pending one from config.
|
|
8
30
|
*/
|
|
9
|
-
export async function
|
|
10
|
-
qr: string;
|
|
31
|
+
export async function getOrCreatePendingCardSetup(): Promise<{
|
|
11
32
|
url: string;
|
|
12
33
|
token: string;
|
|
34
|
+
isNew: boolean;
|
|
13
35
|
}> {
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
margin: 2,
|
|
24
|
-
});
|
|
36
|
+
const pendingToken = getPendingCardSetupToken();
|
|
37
|
+
if (pendingToken) {
|
|
38
|
+
const url = buildCardSetupUrl(pendingToken);
|
|
39
|
+
return {
|
|
40
|
+
url,
|
|
41
|
+
token: pendingToken,
|
|
42
|
+
isNew: false,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
25
45
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const
|
|
46
|
+
const { token } = await apiPost<{ url: string; token: string }>("/card/setup", {});
|
|
47
|
+
setPendingCardSetupToken(token);
|
|
48
|
+
const url = buildCardSetupUrl(token);
|
|
29
49
|
|
|
30
|
-
return {
|
|
50
|
+
return {
|
|
51
|
+
url,
|
|
52
|
+
token,
|
|
53
|
+
isNew: true,
|
|
54
|
+
};
|
|
31
55
|
}
|
|
32
56
|
|
|
33
57
|
/**
|
|
34
|
-
* Format the card setup prompt as
|
|
35
|
-
*
|
|
36
|
-
*
|
|
58
|
+
* Format the card setup prompt as a single text block.
|
|
59
|
+
*
|
|
60
|
+
* Some MCP clients only surface one text chunk or collapse multi-part tool
|
|
61
|
+
* output, which can hide the QR code entirely. Keeping everything in one
|
|
62
|
+
* message makes the browser handoff much more reliable.
|
|
37
63
|
*/
|
|
38
|
-
export function formatCardSetupBlocks(
|
|
64
|
+
export function formatCardSetupBlocks(url: string): string[] {
|
|
39
65
|
return [
|
|
40
|
-
"\n" + qr.trim(),
|
|
41
66
|
[
|
|
42
|
-
|
|
67
|
+
"Open this setup page to connect your card:",
|
|
43
68
|
"",
|
|
44
69
|
url,
|
|
45
70
|
"",
|
|
46
|
-
|
|
71
|
+
"The setup page will take you to Stripe to securely save the card.",
|
|
72
|
+
"",
|
|
73
|
+
`Tell the user: "Open this setup page to connect your card. Let me know when you're done."`,
|
|
47
74
|
`After they confirm, call wallet_setup({ action: "add-card" }) to complete setup.`,
|
|
48
|
-
`Crypto wallets (
|
|
75
|
+
`Crypto wallets (Tempo, Base, or Solana USDC) are also available via wallet_setup({ action: "create" }).`,
|
|
49
76
|
].join("\n"),
|
|
50
77
|
];
|
|
51
78
|
}
|
|
@@ -56,39 +83,89 @@ export function formatCardSetupBlocks(qr: string, url: string): string[] {
|
|
|
56
83
|
export async function pollCardSetup(
|
|
57
84
|
token: string,
|
|
58
85
|
timeoutMs = 120_000,
|
|
59
|
-
): Promise<
|
|
86
|
+
): Promise<CardSetupResult | null> {
|
|
60
87
|
const deadline = Date.now() + timeoutMs;
|
|
61
88
|
|
|
62
89
|
while (Date.now() < deadline) {
|
|
63
|
-
await new Promise((r) => setTimeout(r, 3000));
|
|
64
90
|
try {
|
|
65
91
|
const status = await apiGet<{
|
|
66
92
|
status: string;
|
|
67
93
|
card_last4?: string;
|
|
68
94
|
card_brand?: string;
|
|
69
95
|
consumer_token?: string;
|
|
96
|
+
payment_method_id?: string;
|
|
70
97
|
}>(`/card/status?token=${token}`);
|
|
71
98
|
|
|
72
99
|
if (status.status === "complete" && status.consumer_token) {
|
|
73
|
-
const card = {
|
|
100
|
+
const card: CardSetupResult = {
|
|
74
101
|
last4: status.card_last4 ?? "????",
|
|
75
102
|
brand: status.card_brand ?? "card",
|
|
76
103
|
consumerToken: status.consumer_token,
|
|
77
104
|
};
|
|
78
105
|
|
|
79
|
-
// Persist to config
|
|
106
|
+
// Persist to config (including paymentMethodId for mppx stripe charge)
|
|
80
107
|
setCardConfig({
|
|
81
108
|
consumerToken: card.consumerToken,
|
|
109
|
+
paymentMethodId: status.payment_method_id,
|
|
82
110
|
last4: card.last4,
|
|
83
111
|
brand: card.brand,
|
|
84
112
|
});
|
|
113
|
+
setPendingCardSetupToken(null);
|
|
85
114
|
|
|
86
115
|
return card;
|
|
87
116
|
}
|
|
88
|
-
} catch {
|
|
117
|
+
} catch (err) {
|
|
118
|
+
if (err instanceof ApiError && err.status === 404) {
|
|
119
|
+
setPendingCardSetupToken(null);
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
89
122
|
// Keep polling
|
|
90
123
|
}
|
|
124
|
+
|
|
125
|
+
const remainingMs = deadline - Date.now();
|
|
126
|
+
if (remainingMs <= 0) break;
|
|
127
|
+
await new Promise((r) => setTimeout(r, Math.min(3000, remainingMs)));
|
|
91
128
|
}
|
|
92
129
|
|
|
93
130
|
return null;
|
|
94
131
|
}
|
|
132
|
+
|
|
133
|
+
export async function getCardCapabilities(): Promise<CardCapabilities> {
|
|
134
|
+
const apiUrl = getApiUrl();
|
|
135
|
+
if (cachedCapabilities && cachedCapabilities.apiUrl === apiUrl && cachedCapabilities.expiresAt > Date.now()) {
|
|
136
|
+
return cachedCapabilities.value;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
try {
|
|
140
|
+
const capabilities = await apiGet<CardCapabilities>("/card/capabilities");
|
|
141
|
+
cachedCapabilities = {
|
|
142
|
+
value: capabilities,
|
|
143
|
+
apiUrl,
|
|
144
|
+
expiresAt: Date.now() + 30_000,
|
|
145
|
+
};
|
|
146
|
+
return capabilities;
|
|
147
|
+
} catch (err) {
|
|
148
|
+
if (err instanceof ApiError) {
|
|
149
|
+
const fallback = {
|
|
150
|
+
spt_status: "unknown",
|
|
151
|
+
message: err.message,
|
|
152
|
+
} satisfies CardCapabilities;
|
|
153
|
+
cachedCapabilities = {
|
|
154
|
+
value: fallback,
|
|
155
|
+
apiUrl,
|
|
156
|
+
expiresAt: Date.now() + 10_000,
|
|
157
|
+
};
|
|
158
|
+
return fallback;
|
|
159
|
+
}
|
|
160
|
+
const fallback = {
|
|
161
|
+
spt_status: "unknown",
|
|
162
|
+
message: "Could not determine card payment availability.",
|
|
163
|
+
} satisfies CardCapabilities;
|
|
164
|
+
cachedCapabilities = {
|
|
165
|
+
value: fallback,
|
|
166
|
+
apiUrl,
|
|
167
|
+
expiresAt: Date.now() + 10_000,
|
|
168
|
+
};
|
|
169
|
+
return fallback;
|
|
170
|
+
}
|
|
171
|
+
}
|
package/src/core/config.ts
CHANGED
|
@@ -9,7 +9,7 @@ export interface WalletEntry {
|
|
|
9
9
|
keyType: "evm" | "ows"; // key format: raw EVM key or OWS-managed wallet
|
|
10
10
|
key?: string; // private key (legacy / raw EVM only)
|
|
11
11
|
owsWalletId?: string; // OWS wallet reference (when keyType === "ows")
|
|
12
|
-
chains: string[]; // enabled chains: "tempo", "base"
|
|
12
|
+
chains: string[]; // enabled chains: "tempo", "base", "solana"
|
|
13
13
|
defaultChain?: string; // which chain to use by default for this wallet
|
|
14
14
|
label?: string; // user-friendly name
|
|
15
15
|
}
|
|
@@ -27,7 +27,9 @@ export interface Config {
|
|
|
27
27
|
userId: string | null;
|
|
28
28
|
wallets: WalletEntry[];
|
|
29
29
|
defaultWallet: string | null;
|
|
30
|
+
defaultPaymentMethod?: string;
|
|
30
31
|
card: CardConfig | null;
|
|
32
|
+
pendingCardSetupToken?: string | null;
|
|
31
33
|
favorites: string[];
|
|
32
34
|
/** Require user confirmation before spending. Default: true. Set false for headless/automated use. */
|
|
33
35
|
confirmBeforeSpend: boolean;
|
|
@@ -79,6 +81,7 @@ interface LegacyConfig {
|
|
|
79
81
|
wallets?: WalletEntry[];
|
|
80
82
|
defaultWallet?: string | null;
|
|
81
83
|
card?: CardConfig | null;
|
|
84
|
+
pendingCardSetupToken?: string | null;
|
|
82
85
|
}
|
|
83
86
|
|
|
84
87
|
/**
|
|
@@ -95,7 +98,9 @@ function migrateIfNeeded(raw: LegacyConfig): Config {
|
|
|
95
98
|
userId: raw.userId ?? null,
|
|
96
99
|
wallets: raw.wallets,
|
|
97
100
|
defaultWallet: raw.defaultWallet ?? null,
|
|
101
|
+
defaultPaymentMethod: raw.defaultPaymentMethod ?? undefined,
|
|
98
102
|
card: raw.card ?? null,
|
|
103
|
+
pendingCardSetupToken: (r.pendingCardSetupToken as string | null | undefined) ?? null,
|
|
99
104
|
favorites: r.favorites as string[] ?? [],
|
|
100
105
|
confirmBeforeSpend: r.confirmBeforeSpend !== false,
|
|
101
106
|
defaultTipAmount: typeof r.defaultTipAmount === "number" ? r.defaultTipAmount : 0,
|
|
@@ -167,7 +172,9 @@ function migrateIfNeeded(raw: LegacyConfig): Config {
|
|
|
167
172
|
userId: raw.userId ?? null,
|
|
168
173
|
wallets,
|
|
169
174
|
defaultWallet,
|
|
175
|
+
defaultPaymentMethod: raw.defaultPaymentMethod ?? undefined,
|
|
170
176
|
card,
|
|
177
|
+
pendingCardSetupToken: null,
|
|
171
178
|
favorites: [],
|
|
172
179
|
confirmBeforeSpend: true,
|
|
173
180
|
defaultTipAmount: 0,
|
|
@@ -192,6 +199,7 @@ export function getConfig(): Config {
|
|
|
192
199
|
wallets: [],
|
|
193
200
|
defaultWallet: null,
|
|
194
201
|
card: null,
|
|
202
|
+
pendingCardSetupToken: null,
|
|
195
203
|
favorites: [],
|
|
196
204
|
confirmBeforeSpend: true,
|
|
197
205
|
defaultTipAmount: 0,
|
|
@@ -350,7 +358,30 @@ export function getCardConfig(): CardConfig | null {
|
|
|
350
358
|
* Save card configuration after setup.
|
|
351
359
|
*/
|
|
352
360
|
export function setCardConfig(card: CardConfig | null): void {
|
|
353
|
-
|
|
361
|
+
const current = getConfig();
|
|
362
|
+
if (card) {
|
|
363
|
+
saveConfig({
|
|
364
|
+
card,
|
|
365
|
+
defaultPaymentMethod: "card",
|
|
366
|
+
pendingCardSetupToken: null,
|
|
367
|
+
});
|
|
368
|
+
} else {
|
|
369
|
+
saveConfig({
|
|
370
|
+
card,
|
|
371
|
+
pendingCardSetupToken: null,
|
|
372
|
+
defaultPaymentMethod: current.defaultPaymentMethod === "card"
|
|
373
|
+
? undefined
|
|
374
|
+
: current.defaultPaymentMethod,
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
export function getPendingCardSetupToken(): string | null {
|
|
380
|
+
return getConfig().pendingCardSetupToken ?? null;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
export function setPendingCardSetupToken(token: string | null): void {
|
|
384
|
+
saveConfig({ pendingCardSetupToken: token });
|
|
354
385
|
}
|
|
355
386
|
|
|
356
387
|
/**
|
package/src/core/formatters.ts
CHANGED
|
@@ -118,6 +118,8 @@ interface RunResultLike {
|
|
|
118
118
|
estimated_cost?: number;
|
|
119
119
|
input_tokens?: number;
|
|
120
120
|
tags?: string[];
|
|
121
|
+
consumption_mode?: string;
|
|
122
|
+
credit_pack_id?: string;
|
|
121
123
|
}
|
|
122
124
|
|
|
123
125
|
export function formatRunResult(result: RunResultLike, opts?: { paymentMethod?: string }): string {
|
|
@@ -147,6 +149,7 @@ export function formatRunResult(result: RunResultLike, opts?: { paymentMethod?:
|
|
|
147
149
|
|
|
148
150
|
// Summary line
|
|
149
151
|
const cost = result.cost ?? result.estimated_cost;
|
|
152
|
+
const usedCreditPack = result.consumption_mode === "credit_pack";
|
|
150
153
|
const status = result.status === "success" || result.status === "completed" ? "✓" : "✗";
|
|
151
154
|
const agent = result.agent_name ?? result.agent_id ?? "";
|
|
152
155
|
const costStr = cost != null ? `$${cost.toFixed(cost < 0.01 ? 6 : 2)}` : "";
|
|
@@ -157,7 +160,10 @@ export function formatRunResult(result: RunResultLike, opts?: { paymentMethod?:
|
|
|
157
160
|
if (result.error_code) {
|
|
158
161
|
lines.push(`Error: ${result.error_code}`);
|
|
159
162
|
}
|
|
160
|
-
if (
|
|
163
|
+
if (usedCreditPack) {
|
|
164
|
+
lines.push(`Covered by credit pack${result.credit_pack_id ? ` (${result.credit_pack_id})` : ""}`);
|
|
165
|
+
}
|
|
166
|
+
if (costStr && !usedCreditPack) {
|
|
161
167
|
lines.push(`Paid: ${costStr}${method ? ` via ${method}` : ""}`);
|
|
162
168
|
}
|
|
163
169
|
if (result.job_id) {
|
package/src/core/index.ts
CHANGED
package/src/core/ows-adapter.ts
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
import type { LocalAccount } from "viem/accounts";
|
|
12
12
|
import type { Hex } from "viem";
|
|
13
|
+
import type { Keypair } from "@solana/web3.js";
|
|
13
14
|
|
|
14
15
|
// Note: OWS provides encrypted key storage at rest (~/.ows/, AES-256-GCM).
|
|
15
16
|
// For EVM signing, we export the secp256k1 key and use viem's native
|
|
@@ -127,6 +128,33 @@ function findEvmAccount(
|
|
|
127
128
|
return evm;
|
|
128
129
|
}
|
|
129
130
|
|
|
131
|
+
function findSolanaAccount(
|
|
132
|
+
wallet: OwsWalletInfo,
|
|
133
|
+
): OwsAccountInfo {
|
|
134
|
+
const solana = wallet.accounts.find((a) => a.chainId.startsWith("solana"));
|
|
135
|
+
if (!solana) {
|
|
136
|
+
throw new Error(
|
|
137
|
+
`Wallet "${wallet.name}" (${wallet.id}) has no Solana account.`,
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
return solana;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function ed25519HexToBytes(privateKeyHex: string): Uint8Array {
|
|
144
|
+
return Uint8Array.from(Buffer.from(privateKeyHex, "hex"));
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function keypairFromEd25519Hex(privateKeyHex: string, KeypairCtor: typeof import("@solana/web3.js").Keypair): Keypair {
|
|
148
|
+
const bytes = ed25519HexToBytes(privateKeyHex);
|
|
149
|
+
if (bytes.length === 32) {
|
|
150
|
+
return KeypairCtor.fromSeed(bytes);
|
|
151
|
+
}
|
|
152
|
+
if (bytes.length === 64) {
|
|
153
|
+
return KeypairCtor.fromSecretKey(bytes);
|
|
154
|
+
}
|
|
155
|
+
throw new Error(`Unsupported ed25519 key length: ${bytes.length} bytes.`);
|
|
156
|
+
}
|
|
157
|
+
|
|
130
158
|
// ── Public API ───────────────────────────────────────────────────
|
|
131
159
|
|
|
132
160
|
/**
|
|
@@ -169,16 +197,45 @@ export async function owsAccountFromWalletId(
|
|
|
169
197
|
return privateKeyToAccount(`0x${keys.secp256k1}` as Hex);
|
|
170
198
|
}
|
|
171
199
|
|
|
200
|
+
export async function owsSolanaKeypairFromWalletId(
|
|
201
|
+
walletId: string,
|
|
202
|
+
): Promise<Keypair> {
|
|
203
|
+
const ows = await loadOws();
|
|
204
|
+
const wallet = ows.getWallet(walletId);
|
|
205
|
+
findSolanaAccount(wallet);
|
|
206
|
+
|
|
207
|
+
const exported = ows.exportWallet(walletId);
|
|
208
|
+
const keys = JSON.parse(exported) as { secp256k1?: string; ed25519?: string };
|
|
209
|
+
if (!keys.ed25519) {
|
|
210
|
+
throw new Error(`Wallet "${wallet.name}" has no ed25519 key for Solana signing.`);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const { Keypair } = await import("@solana/web3.js");
|
|
214
|
+
return keypairFromEd25519Hex(keys.ed25519, Keypair);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export async function getOwsWalletAddress(
|
|
218
|
+
walletId: string,
|
|
219
|
+
chain: "evm" | "solana" = "evm",
|
|
220
|
+
): Promise<string> {
|
|
221
|
+
const ows = await loadOws();
|
|
222
|
+
const wallet = ows.getWallet(walletId);
|
|
223
|
+
return chain === "solana"
|
|
224
|
+
? findSolanaAccount(wallet).address
|
|
225
|
+
: findEvmAccount(wallet).address;
|
|
226
|
+
}
|
|
227
|
+
|
|
172
228
|
/**
|
|
173
229
|
* Create a new OWS wallet and return its ID + EVM address.
|
|
174
230
|
*/
|
|
175
231
|
export async function createOwsWallet(
|
|
176
232
|
name: string,
|
|
233
|
+
chain: "evm" | "solana" = "evm",
|
|
177
234
|
): Promise<{ walletId: string; address: string }> {
|
|
178
235
|
const ows = await loadOws();
|
|
179
236
|
const wallet = ows.createWallet(name);
|
|
180
|
-
const
|
|
181
|
-
return { walletId: wallet.id, address:
|
|
237
|
+
const account = chain === "solana" ? findSolanaAccount(wallet) : findEvmAccount(wallet);
|
|
238
|
+
return { walletId: wallet.id, address: account.address };
|
|
182
239
|
}
|
|
183
240
|
|
|
184
241
|
/**
|
|
@@ -187,6 +244,7 @@ export async function createOwsWallet(
|
|
|
187
244
|
export async function importKeyToOws(
|
|
188
245
|
privateKey: string,
|
|
189
246
|
name: string,
|
|
247
|
+
chain: "evm" | "solana" = "evm",
|
|
190
248
|
): Promise<{ walletId: string; address: string }> {
|
|
191
249
|
const ows = await loadOws();
|
|
192
250
|
const normalizedKey = privateKey.startsWith("0x")
|
|
@@ -197,10 +255,10 @@ export async function importKeyToOws(
|
|
|
197
255
|
normalizedKey,
|
|
198
256
|
null,
|
|
199
257
|
null,
|
|
200
|
-
|
|
258
|
+
chain,
|
|
201
259
|
);
|
|
202
|
-
const
|
|
203
|
-
return { walletId: wallet.id, address:
|
|
260
|
+
const account = chain === "solana" ? findSolanaAccount(wallet) : findEvmAccount(wallet);
|
|
261
|
+
return { walletId: wallet.id, address: account.address };
|
|
204
262
|
}
|
|
205
263
|
|
|
206
264
|
/**
|
|
@@ -209,14 +267,22 @@ export async function importKeyToOws(
|
|
|
209
267
|
export async function listOwsWallets(): Promise<
|
|
210
268
|
Array<{ id: string; name: string; address: string }>
|
|
211
269
|
> {
|
|
270
|
+
return listOwsWalletsByChain("evm");
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
export async function listOwsWalletsByChain(
|
|
274
|
+
chain: "evm" | "solana" = "evm",
|
|
275
|
+
): Promise<Array<{ id: string; name: string; address: string }>> {
|
|
212
276
|
const ows = await loadOws();
|
|
213
277
|
const wallets = ows.listWallets();
|
|
214
278
|
const result: Array<{ id: string; name: string; address: string }> = [];
|
|
215
279
|
|
|
216
280
|
for (const w of wallets) {
|
|
217
|
-
const
|
|
218
|
-
|
|
219
|
-
|
|
281
|
+
const account = chain === "solana"
|
|
282
|
+
? w.accounts.find((a) => a.chainId.startsWith("solana"))
|
|
283
|
+
: w.accounts.find((a) => a.chainId.startsWith("eip155") || a.chainId.startsWith("evm"));
|
|
284
|
+
if (account) {
|
|
285
|
+
result.push({ id: w.id, name: w.name, address: account.address });
|
|
220
286
|
}
|
|
221
287
|
}
|
|
222
288
|
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { apiGet } from "./api-client.js";
|
|
2
|
+
import type { AgentRecord } from "./types.js";
|
|
3
|
+
|
|
4
|
+
export interface CreditPackOffer {
|
|
5
|
+
pack_id: string;
|
|
6
|
+
label: string;
|
|
7
|
+
included_units: number;
|
|
8
|
+
price_usd: string;
|
|
9
|
+
effective_price_per_unit_usd?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface CreditPackRecord {
|
|
13
|
+
id: string;
|
|
14
|
+
status: string;
|
|
15
|
+
unit_type: string;
|
|
16
|
+
included_units: number;
|
|
17
|
+
remaining_units: number;
|
|
18
|
+
price_usd: string;
|
|
19
|
+
purchased_at: string;
|
|
20
|
+
pack?: {
|
|
21
|
+
name?: string | null;
|
|
22
|
+
key?: string | null;
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface CreditPackProgramInfo {
|
|
27
|
+
unit_type?: string;
|
|
28
|
+
packs?: Array<{
|
|
29
|
+
key?: string;
|
|
30
|
+
name?: string;
|
|
31
|
+
included_units?: number;
|
|
32
|
+
price_usd?: string;
|
|
33
|
+
}>;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface CreditPackInventory {
|
|
37
|
+
consumer_principal: string | null;
|
|
38
|
+
offers: CreditPackOffer[];
|
|
39
|
+
balances: CreditPackRecord[];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function getCreditPackProgram(agent: AgentRecord): CreditPackProgramInfo | null {
|
|
43
|
+
const payment = (agent.payment ?? {}) as Record<string, unknown>;
|
|
44
|
+
const creditPacks = payment.credit_packs as CreditPackProgramInfo | null | undefined;
|
|
45
|
+
return creditPacks ?? null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export async function getCreditPackInventory(agentId: string): Promise<CreditPackInventory | null> {
|
|
49
|
+
try {
|
|
50
|
+
return await apiGet<CreditPackInventory>(`/agents/${agentId}/credit-packs`, { ensureConsumerPrincipal: true });
|
|
51
|
+
} catch {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function getActiveCreditPack(inventory: CreditPackInventory | null): CreditPackRecord | null {
|
|
57
|
+
if (!inventory) return null;
|
|
58
|
+
return inventory.balances
|
|
59
|
+
.filter((pack) => pack.status === "active" && pack.remaining_units > 0)
|
|
60
|
+
.sort((a, b) => new Date(a.purchased_at).getTime() - new Date(b.purchased_at).getTime())[0] ?? null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function formatCreditPackOffer(offer: CreditPackOffer): string {
|
|
64
|
+
const unitPrice = offer.effective_price_per_unit_usd
|
|
65
|
+
? ` ($${Number(offer.effective_price_per_unit_usd).toFixed(2)}/unit)`
|
|
66
|
+
: "";
|
|
67
|
+
return `${offer.label} — ${offer.included_units} units for $${offer.price_usd}${unitPrice}`;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function formatCreditPack(pack: CreditPackRecord): string {
|
|
71
|
+
const label = pack.pack?.name ?? pack.pack?.key ?? "Credit Pack";
|
|
72
|
+
const statusPrefix = pack.status === "active" ? "" : `${pack.status} • `;
|
|
73
|
+
return `${label}: ${statusPrefix}${pack.remaining_units}/${pack.included_units} ${pack.unit_type}s remaining`;
|
|
74
|
+
}
|