profile_wallet
Generate a full portfolio profile for an Ethereum or Base wallet address, including token holdings, NFTs, DeFi positions, transaction summary, PnL, and risk score, paid per call via micropayment.
Instructions
Generate a full portfolio profile for an Ethereum/Base wallet address. Returns token holdings, NFTs, DeFi positions, transaction history summary, PnL estimate, and risk profile score. Costs 0.008 USDC per call (x402 micropayment on Base).
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| address | Yes | Ethereum or Base wallet address (0x...) | |
| chain | No | Filter by chain (e.g. "ethereum", "base", "arbitrum", "polygon"). Defaults to "all". |
Implementation Reference
- src/index.ts:395-416 (registration)Tool definition registration for 'profile_wallet' in the TOOLS array. Defines name, description, input schema (address required, chain optional), and cost info (0.008 USDC).
{ name: 'profile_wallet', description: 'Generate a full portfolio profile for an Ethereum/Base wallet address. ' + 'Returns token holdings, NFTs, DeFi positions, transaction history summary, ' + 'PnL estimate, and risk profile score. ' + 'Costs 0.008 USDC per call (x402 micropayment on Base).', inputSchema: { type: 'object', properties: { address: { type: 'string', description: 'Ethereum or Base wallet address (0x...)', }, chain: { type: 'string', description: 'Filter by chain (e.g. "ethereum", "base", "arbitrum", "polygon"). Defaults to "all".', }, }, required: ['address'], }, }, - src/index.ts:505-513 (handler)Handler case for 'profile_wallet' in the CallToolRequestSchema switch. Validates required 'address' param, then delegates to callApi('GET /api/wallet-profiler') with address and optional chain query params.
case 'profile_wallet': if (!params.address) { throw new McpError(ErrorCode.InvalidParams, 'profile_wallet requires: address'); } result = await callApi('/api/wallet-profiler', { address: params.address, chain: params.chain, }); break; - src/index.ts:114-180 (helper)Generic API call helper that builds the URL, executes via the x402-aware fetch (with auto-pay support), handles 402 payment responses, and returns structured ApiResponse.
async function callApi( endpoint: string, params: Record<string, string | number | undefined> = {} ): Promise<ApiResponse> { const url = new URL(`${API_BASE_URL}${endpoint}`); for (const [key, value] of Object.entries(params)) { if (value !== undefined && value !== '') { url.searchParams.set(key, String(value)); } } const fetchFn = await getX402Fetch(); let response: Response; const controller = new AbortController(); const fetchTimeout = setTimeout(() => controller.abort(), 30_000); try { response = await fetchFn(url.toString(), { headers: { 'Accept': 'application/json', 'User-Agent': `x402-api-mcp/${SERVER_VERSION}`, }, signal: controller.signal, }); } catch (err) { const isTimeout = err instanceof Error && err.name === 'AbortError'; throw new McpError( ErrorCode.InternalError, isTimeout ? `Request to ${endpoint} timed out after 30 seconds` : `Network error calling ${endpoint}: ${err instanceof Error ? err.message : String(err)}` ); } finally { clearTimeout(fetchTimeout); } if (response.status === 402) { // Clone before reading so we can fall back to text() if JSON parsing fails. // Without the clone, calling response.json() consumes the body; a subsequent // response.text() call then throws "body already used". const cloned = response.clone(); let paymentDetails: unknown; try { paymentDetails = await response.json(); } catch { paymentDetails = await cloned.text(); } return { status: 402, data: null, paymentRequired: true, paymentDetails }; } if (!response.ok) { const errorText = await response.text(); if (response.status === 400 || response.status === 422) { throw new McpError( ErrorCode.InvalidParams, `Invalid request to ${endpoint}: ${errorText}` ); } throw new McpError( ErrorCode.InternalError, `API error ${response.status} from ${endpoint}: ${errorText}` ); } const data = await response.json(); return { status: response.status, data }; }