@ottocode/sdk 0.1.204 → 0.1.206
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/package.json +1 -1
- package/src/core/src/index.ts +1 -1
- package/src/core/src/mcp/client.ts +28 -3
- package/src/core/src/mcp/index.ts +8 -0
- package/src/core/src/mcp/lazy-tools.ts +89 -0
- package/src/core/src/mcp/server-manager.ts +26 -0
- package/src/core/src/mcp/tools.ts +35 -2
- package/src/core/src/tools/loader.ts +23 -6
- package/src/index.ts +1 -1
- package/src/providers/src/setu-client.ts +86 -19
package/package.json
CHANGED
package/src/core/src/index.ts
CHANGED
|
@@ -30,7 +30,7 @@ export type { ProviderId, ModelInfo } from '../../types/src/index.ts';
|
|
|
30
30
|
// Tools
|
|
31
31
|
// =======================
|
|
32
32
|
export { discoverProjectTools } from './tools/loader';
|
|
33
|
-
export type { DiscoveredTool } from './tools/loader';
|
|
33
|
+
export type { DiscoveredTool, DiscoverResult } from './tools/loader';
|
|
34
34
|
export { setTerminalManager, getTerminalManager } from './tools/loader';
|
|
35
35
|
|
|
36
36
|
// Tool error handling utilities
|
|
@@ -166,15 +166,18 @@ export class MCPClientWrapper {
|
|
|
166
166
|
args: Record<string, unknown>,
|
|
167
167
|
): Promise<unknown> {
|
|
168
168
|
const result = await this.client.callTool({ name, arguments: args });
|
|
169
|
+
const images = extractImages(result.content);
|
|
169
170
|
if (result.isError) {
|
|
170
171
|
return {
|
|
171
172
|
ok: false,
|
|
172
173
|
error: formatContent(result.content),
|
|
174
|
+
...(images.length > 0 && { images }),
|
|
173
175
|
};
|
|
174
176
|
}
|
|
175
177
|
return {
|
|
176
178
|
ok: true,
|
|
177
179
|
result: formatContent(result.content),
|
|
180
|
+
...(images.length > 0 && { images }),
|
|
178
181
|
};
|
|
179
182
|
}
|
|
180
183
|
|
|
@@ -218,12 +221,34 @@ function formatContent(content: unknown): string {
|
|
|
218
221
|
if (item && typeof item === 'object' && 'text' in item) {
|
|
219
222
|
parts.push(String(item.text));
|
|
220
223
|
} else if (item && typeof item === 'object' && 'data' in item) {
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
+
const mimeType = (item as { mimeType?: string }).mimeType ?? 'unknown';
|
|
225
|
+
if (mimeType.startsWith('image/')) {
|
|
226
|
+
parts.push(`[image: ${mimeType}]`);
|
|
227
|
+
} else {
|
|
228
|
+
parts.push(`[binary data: ${mimeType}]`);
|
|
229
|
+
}
|
|
224
230
|
} else {
|
|
225
231
|
parts.push(JSON.stringify(item));
|
|
226
232
|
}
|
|
227
233
|
}
|
|
228
234
|
return parts.join('\n');
|
|
229
235
|
}
|
|
236
|
+
|
|
237
|
+
function extractImages(
|
|
238
|
+
content: unknown,
|
|
239
|
+
): Array<{ data: string; mimeType: string }> {
|
|
240
|
+
if (!Array.isArray(content)) return [];
|
|
241
|
+
const images: Array<{ data: string; mimeType: string }> = [];
|
|
242
|
+
for (const item of content) {
|
|
243
|
+
if (item && typeof item === 'object' && 'data' in item) {
|
|
244
|
+
const mimeType = (item as { mimeType?: string }).mimeType ?? 'unknown';
|
|
245
|
+
if (mimeType.startsWith('image/')) {
|
|
246
|
+
images.push({
|
|
247
|
+
data: String((item as { data: unknown }).data),
|
|
248
|
+
mimeType,
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
return images;
|
|
254
|
+
}
|
|
@@ -13,6 +13,14 @@ export { MCPServerManager } from './server-manager.ts';
|
|
|
13
13
|
|
|
14
14
|
export { convertMCPToolsToAISDK } from './tools.ts';
|
|
15
15
|
|
|
16
|
+
export {
|
|
17
|
+
getMCPToolBriefs,
|
|
18
|
+
buildLoadMCPToolsTool,
|
|
19
|
+
getMCPToolsRecord,
|
|
20
|
+
buildMCPToolCatalogDescription,
|
|
21
|
+
type MCPToolBrief,
|
|
22
|
+
} from './lazy-tools.ts';
|
|
23
|
+
|
|
16
24
|
export {
|
|
17
25
|
getMCPManager,
|
|
18
26
|
initializeMCP,
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { tool, type Tool } from 'ai';
|
|
2
|
+
import { z } from 'zod/v3';
|
|
3
|
+
import type { MCPServerManager } from './server-manager.ts';
|
|
4
|
+
import { convertMCPToolsToAISDK } from './tools.ts';
|
|
5
|
+
|
|
6
|
+
export type MCPToolBrief = {
|
|
7
|
+
name: string;
|
|
8
|
+
server: string;
|
|
9
|
+
description: string;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export function getMCPToolBriefs(manager: MCPServerManager): MCPToolBrief[] {
|
|
13
|
+
return manager.getTools().map(({ name, server, tool: t }) => ({
|
|
14
|
+
name,
|
|
15
|
+
server,
|
|
16
|
+
description: t.description ?? `MCP tool: ${t.name}`,
|
|
17
|
+
}));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function buildMCPToolCatalogDescription(briefs: MCPToolBrief[]): string {
|
|
21
|
+
if (briefs.length === 0) return 'No MCP tools available.';
|
|
22
|
+
const grouped = new Map<string, MCPToolBrief[]>();
|
|
23
|
+
for (const b of briefs) {
|
|
24
|
+
const list = grouped.get(b.server) ?? [];
|
|
25
|
+
list.push(b);
|
|
26
|
+
grouped.set(b.server, list);
|
|
27
|
+
}
|
|
28
|
+
const lines: string[] = [];
|
|
29
|
+
for (const [server, tools] of grouped) {
|
|
30
|
+
lines.push(`[${server}]`);
|
|
31
|
+
for (const t of tools) {
|
|
32
|
+
lines.push(` ${t.name}: ${t.description.slice(0, 120)}`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return lines.join('\n');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function buildLoadMCPToolsTool(briefs: MCPToolBrief[]): {
|
|
39
|
+
name: string;
|
|
40
|
+
tool: Tool;
|
|
41
|
+
} {
|
|
42
|
+
const catalog = buildMCPToolCatalogDescription(briefs);
|
|
43
|
+
const validNames = new Set(briefs.map((b) => b.name));
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
name: 'load_mcp_tools',
|
|
47
|
+
tool: tool({
|
|
48
|
+
description: `Load MCP tools by name so they become available for use in the next step. Call this with the tool names you need before using them.\n\nAvailable MCP tools:\n${catalog}`,
|
|
49
|
+
inputSchema: z.object({
|
|
50
|
+
tools: z
|
|
51
|
+
.array(z.string())
|
|
52
|
+
.describe(
|
|
53
|
+
'Array of MCP tool names to load (e.g. ["chrome__click", "chrome__screenshot"])',
|
|
54
|
+
),
|
|
55
|
+
}),
|
|
56
|
+
execute: async ({ tools: requested }) => {
|
|
57
|
+
const loaded: string[] = [];
|
|
58
|
+
const notFound: string[] = [];
|
|
59
|
+
for (const name of requested) {
|
|
60
|
+
if (validNames.has(name)) {
|
|
61
|
+
loaded.push(name);
|
|
62
|
+
} else {
|
|
63
|
+
notFound.push(name);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
ok: true,
|
|
68
|
+
loaded,
|
|
69
|
+
...(notFound.length > 0 ? { notFound } : {}),
|
|
70
|
+
message:
|
|
71
|
+
loaded.length > 0
|
|
72
|
+
? `Loaded ${loaded.length} tool(s). They are now available for use.`
|
|
73
|
+
: 'No valid tools to load.',
|
|
74
|
+
};
|
|
75
|
+
},
|
|
76
|
+
}),
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function getMCPToolsRecord(
|
|
81
|
+
manager: MCPServerManager,
|
|
82
|
+
): Record<string, Tool> {
|
|
83
|
+
const mcpTools = convertMCPToolsToAISDK(manager);
|
|
84
|
+
const record: Record<string, Tool> = {};
|
|
85
|
+
for (const { name, tool: t } of mcpTools) {
|
|
86
|
+
record[name] = t;
|
|
87
|
+
}
|
|
88
|
+
return record;
|
|
89
|
+
}
|
|
@@ -220,6 +220,7 @@ export class MCPServerManager {
|
|
|
220
220
|
|
|
221
221
|
this.serverScopes.set(config.name, config.scope ?? 'global');
|
|
222
222
|
const key = this.oauthKey(config.name);
|
|
223
|
+
await this.oauthStore.clearServer(key);
|
|
223
224
|
const provider = new OttoOAuthProvider(key, this.oauthStore, {
|
|
224
225
|
clientId: config.oauth?.clientId,
|
|
225
226
|
callbackPort: config.oauth?.callbackPort,
|
|
@@ -244,6 +245,7 @@ export class MCPServerManager {
|
|
|
244
245
|
|
|
245
246
|
if (provider.pendingAuthUrl) {
|
|
246
247
|
this.pendingAuth.set(config.name, provider.pendingAuthUrl);
|
|
248
|
+
this.waitForAuthAndReconnect(config.name, provider);
|
|
247
249
|
return provider.pendingAuthUrl;
|
|
248
250
|
}
|
|
249
251
|
return null;
|
|
@@ -295,6 +297,30 @@ export class MCPServerManager {
|
|
|
295
297
|
await this.stopServer(name);
|
|
296
298
|
}
|
|
297
299
|
|
|
300
|
+
async clearAuthData(
|
|
301
|
+
name: string,
|
|
302
|
+
scope?: 'global' | 'project',
|
|
303
|
+
projectRoot?: string,
|
|
304
|
+
): Promise<void> {
|
|
305
|
+
const provider = this.authProviders.get(name);
|
|
306
|
+
if (provider) {
|
|
307
|
+
await provider.clearCredentials();
|
|
308
|
+
provider.cleanup();
|
|
309
|
+
}
|
|
310
|
+
this.authProviders.delete(name);
|
|
311
|
+
if (scope) {
|
|
312
|
+
this.serverScopes.set(name, scope);
|
|
313
|
+
}
|
|
314
|
+
if (projectRoot) {
|
|
315
|
+
this.projectRoot = projectRoot;
|
|
316
|
+
}
|
|
317
|
+
const key = this.oauthKey(name);
|
|
318
|
+
await this.oauthStore.clearServer(key);
|
|
319
|
+
if (key !== name) {
|
|
320
|
+
await this.oauthStore.clearServer(name);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
298
324
|
async getAuthStatus(
|
|
299
325
|
name: string,
|
|
300
326
|
): Promise<{ authenticated: boolean; expiresAt?: number }> {
|
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
import { tool, type Tool } from 'ai';
|
|
2
|
+
import type { ToolResultOutput } from '@ai-sdk/provider-utils';
|
|
2
3
|
import { z } from 'zod/v3';
|
|
3
4
|
import type { MCPServerManager } from './server-manager.ts';
|
|
4
5
|
|
|
6
|
+
type MCPToolResult = {
|
|
7
|
+
ok: boolean;
|
|
8
|
+
result?: string;
|
|
9
|
+
error?: string;
|
|
10
|
+
images?: Array<{ data: string; mimeType: string }>;
|
|
11
|
+
};
|
|
12
|
+
|
|
5
13
|
export function convertMCPToolsToAISDK(
|
|
6
14
|
manager: MCPServerManager,
|
|
7
15
|
): Array<{ name: string; tool: Tool }> {
|
|
@@ -16,13 +24,38 @@ export function convertMCPToolsToAISDK(
|
|
|
16
24
|
) as z.ZodObject<z.ZodRawShape>,
|
|
17
25
|
async execute(args: Record<string, unknown>) {
|
|
18
26
|
try {
|
|
19
|
-
return await manager.callTool(name, args);
|
|
27
|
+
return (await manager.callTool(name, args)) as MCPToolResult;
|
|
20
28
|
} catch (err) {
|
|
21
29
|
return {
|
|
22
30
|
ok: false,
|
|
23
31
|
error: err instanceof Error ? err.message : String(err),
|
|
24
|
-
};
|
|
32
|
+
} satisfies MCPToolResult;
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
toModelOutput({ output }): ToolResultOutput {
|
|
36
|
+
const result = output as MCPToolResult;
|
|
37
|
+
if (result.images && result.images.length > 0) {
|
|
38
|
+
const parts: Array<
|
|
39
|
+
| { type: 'text'; text: string }
|
|
40
|
+
| { type: 'image-data'; data: string; mediaType: string }
|
|
41
|
+
> = [];
|
|
42
|
+
const text = result.ok ? result.result : result.error;
|
|
43
|
+
if (text) {
|
|
44
|
+
parts.push({ type: 'text', text });
|
|
45
|
+
}
|
|
46
|
+
for (const img of result.images) {
|
|
47
|
+
parts.push({
|
|
48
|
+
type: 'image-data',
|
|
49
|
+
data: img.data,
|
|
50
|
+
mediaType: img.mimeType,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
return { type: 'content', value: parts } as ToolResultOutput;
|
|
25
54
|
}
|
|
55
|
+
return {
|
|
56
|
+
type: 'json',
|
|
57
|
+
value: result as unknown as import('@ai-sdk/provider').JSONValue,
|
|
58
|
+
};
|
|
26
59
|
},
|
|
27
60
|
}),
|
|
28
61
|
}));
|
|
@@ -16,7 +16,12 @@ import { buildTerminalTool } from './builtin/terminal.ts';
|
|
|
16
16
|
import type { TerminalManager } from '../terminals/index.ts';
|
|
17
17
|
import { initializeSkills, buildSkillTool } from '../../../skills/index.ts';
|
|
18
18
|
import { getMCPManager } from '../mcp/index.ts';
|
|
19
|
-
import {
|
|
19
|
+
import {
|
|
20
|
+
getMCPToolBriefs,
|
|
21
|
+
buildLoadMCPToolsTool,
|
|
22
|
+
getMCPToolsRecord,
|
|
23
|
+
type MCPToolBrief,
|
|
24
|
+
} from '../mcp/lazy-tools.ts';
|
|
20
25
|
import fg from 'fast-glob';
|
|
21
26
|
import { dirname, isAbsolute, join } from 'node:path';
|
|
22
27
|
import { pathToFileURL } from 'node:url';
|
|
@@ -25,6 +30,11 @@ import { spawn as nodeSpawn } from 'node:child_process';
|
|
|
25
30
|
|
|
26
31
|
export type DiscoveredTool = { name: string; tool: Tool };
|
|
27
32
|
|
|
33
|
+
export type DiscoverResult = {
|
|
34
|
+
tools: DiscoveredTool[];
|
|
35
|
+
mcpToolsRecord: Record<string, Tool>;
|
|
36
|
+
};
|
|
37
|
+
|
|
28
38
|
type PluginParameter = {
|
|
29
39
|
type: 'string' | 'number' | 'boolean';
|
|
30
40
|
description?: string;
|
|
@@ -108,7 +118,7 @@ export function getTerminalManager(): TerminalManager | null {
|
|
|
108
118
|
export async function discoverProjectTools(
|
|
109
119
|
projectRoot: string,
|
|
110
120
|
globalConfigDir?: string,
|
|
111
|
-
): Promise<
|
|
121
|
+
): Promise<DiscoverResult> {
|
|
112
122
|
const tools = new Map<string, Tool>();
|
|
113
123
|
for (const { name, tool } of buildFsTools(projectRoot)) tools.set(name, tool);
|
|
114
124
|
for (const { name, tool } of buildGitTools(projectRoot))
|
|
@@ -148,10 +158,14 @@ export async function discoverProjectTools(
|
|
|
148
158
|
tools.set(skillTool.name, skillTool.tool);
|
|
149
159
|
|
|
150
160
|
const mcpManager = getMCPManager();
|
|
161
|
+
let mcpToolsRecord: Record<string, Tool> = {};
|
|
162
|
+
let mcpBriefs: MCPToolBrief[] = [];
|
|
151
163
|
if (mcpManager?.started) {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
164
|
+
mcpBriefs = getMCPToolBriefs(mcpManager);
|
|
165
|
+
if (mcpBriefs.length > 0) {
|
|
166
|
+
mcpToolsRecord = getMCPToolsRecord(mcpManager);
|
|
167
|
+
const loadTool = buildLoadMCPToolsTool(mcpBriefs);
|
|
168
|
+
tools.set(loadTool.name, loadTool.tool);
|
|
155
169
|
}
|
|
156
170
|
}
|
|
157
171
|
|
|
@@ -210,7 +224,10 @@ export async function discoverProjectTools(
|
|
|
210
224
|
|
|
211
225
|
await loadFromBase(globalConfigDir);
|
|
212
226
|
await loadFromBase(join(projectRoot, '.otto'));
|
|
213
|
-
return
|
|
227
|
+
return {
|
|
228
|
+
tools: Array.from(tools.entries()).map(([name, tool]) => ({ name, tool })),
|
|
229
|
+
mcpToolsRecord,
|
|
230
|
+
};
|
|
214
231
|
}
|
|
215
232
|
|
|
216
233
|
async function loadPlugin(
|
package/src/index.ts
CHANGED
|
@@ -218,7 +218,7 @@ export type { ProviderName, ModelConfig } from './core/src/index.ts';
|
|
|
218
218
|
|
|
219
219
|
// Tools
|
|
220
220
|
export { discoverProjectTools } from './core/src/index.ts';
|
|
221
|
-
export type { DiscoveredTool } from './core/src/index.ts';
|
|
221
|
+
export type { DiscoveredTool, DiscoverResult } from './core/src/index.ts';
|
|
222
222
|
export { setTerminalManager, getTerminalManager } from './core/src/index.ts';
|
|
223
223
|
export { buildFsTools } from './core/src/index.ts';
|
|
224
224
|
export { buildGitTools } from './core/src/index.ts';
|
|
@@ -78,6 +78,7 @@ export type SetuProviderOptions = {
|
|
|
78
78
|
promptCacheKey?: string;
|
|
79
79
|
promptCacheRetention?: 'in_memory' | '24h';
|
|
80
80
|
topupApprovalMode?: 'auto' | 'approval';
|
|
81
|
+
autoPayThresholdUsd?: number;
|
|
81
82
|
};
|
|
82
83
|
|
|
83
84
|
export type SetuAuth = {
|
|
@@ -163,6 +164,7 @@ export function createSetuFetch(
|
|
|
163
164
|
const promptCacheKey = options.promptCacheKey;
|
|
164
165
|
const promptCacheRetention = options.promptCacheRetention;
|
|
165
166
|
const topupApprovalMode = options.topupApprovalMode ?? 'auto';
|
|
167
|
+
const autoPayThresholdUsd = options.autoPayThresholdUsd ?? 0;
|
|
166
168
|
|
|
167
169
|
const baseFetch = globalThis.fetch.bind(globalThis);
|
|
168
170
|
|
|
@@ -232,39 +234,62 @@ export function createSetuFetch(
|
|
|
232
234
|
const amountUsd =
|
|
233
235
|
parseInt(requirement.maxAmountRequired, 10) / 1_000_000;
|
|
234
236
|
|
|
235
|
-
|
|
237
|
+
let walletUsdcBalance = 0;
|
|
238
|
+
if (autoPayThresholdUsd > 0) {
|
|
239
|
+
walletUsdcBalance = await getWalletUsdcBalance(walletAddress, rpcURL);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const canAutoPay =
|
|
243
|
+
autoPayThresholdUsd > 0 && walletUsdcBalance >= autoPayThresholdUsd;
|
|
244
|
+
|
|
245
|
+
const requestApproval = async () => {
|
|
246
|
+
if (!callbacks.onPaymentApproval) return;
|
|
236
247
|
const approval = await callbacks.onPaymentApproval({
|
|
237
248
|
amountUsd,
|
|
238
|
-
currentBalance:
|
|
249
|
+
currentBalance: walletUsdcBalance,
|
|
239
250
|
});
|
|
240
|
-
|
|
241
251
|
if (approval === 'cancel') {
|
|
242
252
|
callbacks.onPaymentError?.('Payment cancelled by user');
|
|
243
253
|
throw new Error('Setu: payment cancelled by user');
|
|
244
254
|
}
|
|
245
|
-
|
|
246
255
|
if (approval === 'fiat') {
|
|
247
256
|
const err = new Error('Setu: fiat payment selected');
|
|
248
257
|
(err as Error & { code: string }).code = 'SETU_FIAT_SELECTED';
|
|
249
258
|
throw err;
|
|
250
259
|
}
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
if (!canAutoPay && topupApprovalMode === 'approval') {
|
|
263
|
+
await requestApproval();
|
|
251
264
|
}
|
|
252
265
|
|
|
253
|
-
callbacks.onPaymentRequired?.(amountUsd,
|
|
254
|
-
|
|
255
|
-
const
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
266
|
+
callbacks.onPaymentRequired?.(amountUsd, walletUsdcBalance);
|
|
267
|
+
|
|
268
|
+
const doPayment = async () => {
|
|
269
|
+
const outcome = await handlePayment({
|
|
270
|
+
requirement,
|
|
271
|
+
keypair,
|
|
272
|
+
rpcURL,
|
|
273
|
+
baseURL,
|
|
274
|
+
baseFetch,
|
|
275
|
+
buildWalletHeaders,
|
|
276
|
+
maxAttempts: remainingPayments,
|
|
277
|
+
callbacks,
|
|
278
|
+
});
|
|
279
|
+
const newTotal = currentAttempts + outcome.attemptsUsed;
|
|
280
|
+
globalPaymentAttempts.set(walletAddress, newTotal);
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
if (canAutoPay) {
|
|
284
|
+
try {
|
|
285
|
+
await doPayment();
|
|
286
|
+
} catch (_autoPayErr) {
|
|
287
|
+
await requestApproval();
|
|
288
|
+
await doPayment();
|
|
289
|
+
}
|
|
290
|
+
} else {
|
|
291
|
+
await doPayment();
|
|
292
|
+
}
|
|
268
293
|
} finally {
|
|
269
294
|
releaseLock();
|
|
270
295
|
}
|
|
@@ -638,6 +663,48 @@ export function getPublicKeyFromPrivate(privateKey: string): string | null {
|
|
|
638
663
|
}
|
|
639
664
|
}
|
|
640
665
|
|
|
666
|
+
async function getWalletUsdcBalance(
|
|
667
|
+
walletAddress: string,
|
|
668
|
+
rpcUrl: string,
|
|
669
|
+
): Promise<number> {
|
|
670
|
+
try {
|
|
671
|
+
const usdcMint = rpcUrl.includes('devnet')
|
|
672
|
+
? USDC_MINT_DEVNET
|
|
673
|
+
: USDC_MINT_MAINNET;
|
|
674
|
+
const response = await fetch(rpcUrl, {
|
|
675
|
+
method: 'POST',
|
|
676
|
+
headers: { 'Content-Type': 'application/json' },
|
|
677
|
+
body: JSON.stringify({
|
|
678
|
+
jsonrpc: '2.0',
|
|
679
|
+
id: 1,
|
|
680
|
+
method: 'getTokenAccountsByOwner',
|
|
681
|
+
params: [walletAddress, { mint: usdcMint }, { encoding: 'jsonParsed' }],
|
|
682
|
+
}),
|
|
683
|
+
});
|
|
684
|
+
if (!response.ok) return 0;
|
|
685
|
+
const data = (await response.json()) as {
|
|
686
|
+
result?: {
|
|
687
|
+
value?: Array<{
|
|
688
|
+
account: {
|
|
689
|
+
data: {
|
|
690
|
+
parsed: {
|
|
691
|
+
info: { tokenAmount: { uiAmount: number } };
|
|
692
|
+
};
|
|
693
|
+
};
|
|
694
|
+
};
|
|
695
|
+
}>;
|
|
696
|
+
};
|
|
697
|
+
};
|
|
698
|
+
let total = 0;
|
|
699
|
+
for (const acct of data.result?.value ?? []) {
|
|
700
|
+
total += acct.account.data.parsed.info.tokenAmount.uiAmount ?? 0;
|
|
701
|
+
}
|
|
702
|
+
return total;
|
|
703
|
+
} catch {
|
|
704
|
+
return 0;
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
|
|
641
708
|
const USDC_MINT_MAINNET = 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v';
|
|
642
709
|
const USDC_MINT_DEVNET = '4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU';
|
|
643
710
|
|