UNPKG

blockchain-api

Version:

API utilities for interacting with the Exatechl2 blockchain

419 lines (347 loc) 11.3 kB
import { callExplorerApi } from '../../core/explorerApi'; import { formatTokenAmount, truncateString } from '../../utils/formatters'; import { getTokenInfo } from '../tokens/tokens'; export interface TransactionMethod { signature: string; name: string; params?: any; } export const EVENT_SIGNATURES = { Transfer: '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', Approval: '0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925', Mint: '0x0f6798a560793a54c3bcfe86a93cde1e73087d944c0ea20544137d4121396885', Burn: '0xcc16f5dbb4873280815c1ee09dbd06736cffcc184412cf7a71a0fdb75d397ca5', Pause: '0x62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a258', Unpause: '0x5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa', OwnershipTransferred: '0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0', RoleGranted: '0x2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d', RoleRevoked: '0xf6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b', }; export const TOKEN_RELATED_EVENTS = [ EVENT_SIGNATURES.Transfer, EVENT_SIGNATURES.Mint, EVENT_SIGNATURES.Burn ]; /** * * @param topic * @returns */ export function parseAddressFromTopic(topic: string): string { if (!topic || typeof topic !== 'string') { return ''; } if (topic.length >= 42) { return '0x' + topic.substring(topic.length - 40).toLowerCase(); } return topic.toLowerCase(); } /** * * @param signature * @returns */ export function getEventType(signature: string): string { for (const [name, sig] of Object.entries(EVENT_SIGNATURES)) { if (sig.toLowerCase() === signature.toLowerCase()) { return name; } } return 'Unknown'; } /** * * * @param event * @returns */ export async function enhanceEvent(event: any): Promise<any> { if (!event) return null; const enhancedEvent = { ...event }; if (event.from_address || event.to_address || event.tx_hash) { enhancedEvent.transaction = { from: event.from_address, to: event.to_address, hash: event.tx_hash }; if (event.method_signature || event.method_name) { enhancedEvent.transaction.method = { signature: event.method_signature || null, name: event.method_name || 'unknown' }; } } enhancedEvent.technical = { signature: event.topics && event.topics[0] ? event.topics[0] : null, topics: event.topics || [], data: event.data || '0x', address: event.address || null, blockNumber: event.block_number, logIndex: event.log_index, method: event.method_signature ? { signature: event.method_signature, name: event.method_name || 'unknown' } : null }; if (event.topics && event.topics[0]) { const signature = event.topics[0]; const eventType = getEventType(signature); enhancedEvent.event_type = eventType; if (eventType === 'Transfer') { let from = ''; if (event.raw_from) { from = parseAddressFromTopic(event.raw_from); } else if (event.from_address) { from = event.from_address.toLowerCase(); } else if (event.topics && event.topics.length > 1) { from = parseAddressFromTopic(event.topics[1]); } let to = ''; if (event.raw_to) { to = parseAddressFromTopic(event.raw_to); } else if (event.to_address && event.address && event.to_address.toLowerCase() !== event.address.toLowerCase()) { to = event.to_address.toLowerCase(); } else if (event.topics && event.topics.length > 2) { to = parseAddressFromTopic(event.topics[2]); } const isTaxTransfer = from && to && from.toLowerCase() === to.toLowerCase(); let tokenSymbol = event.token_symbol || 'TOKEN'; let decimals = event.decimals || 18; if ((!event.token_symbol || event.token_symbol === 'TOKEN' || event.token_symbol === 'UNKNOWN') && event.address) { try { const tokenInfo = await getTokenInfo(event.address); if (tokenInfo) { tokenSymbol = tokenInfo.symbol || 'TOKEN'; decimals = tokenInfo.decimals || 18; } } catch (error: any) { console.warn(`Failed to get token info for ${event.address}: ${error.message}`); } } const value = formatTokenAmount(event.data, decimals); enhancedEvent.token_transfer = { from, to, value, token_address: event.address || event.token_address, token_symbol: tokenSymbol, is_tax: isTaxTransfer }; const shortFrom = truncateString(from); const shortTo = truncateString(to); if (isTaxTransfer) { enhancedEvent.display_type = 'Self-Transfer'; enhancedEvent.description = `Token Fee: ${value} ${tokenSymbol} retained as transaction fee`; enhancedEvent.transfer_type = 'fee'; } else { enhancedEvent.display_type = 'Transfer'; enhancedEvent.description = `Transfer ${value} ${tokenSymbol} from ${shortFrom} to ${shortTo}`; enhancedEvent.transfer_type = 'transfer'; } enhancedEvent.parameters = [ { name: 'signature', value: signature }, { name: 'topic1', value: event.topics[1] }, { name: 'topic2', value: event.topics[2] }, { name: 'data', value: event.data } ]; } else if (eventType === 'Approval') { const owner = event.topics && event.topics.length > 1 ? parseAddressFromTopic(event.topics[1]) : ''; const spender = event.topics && event.topics.length > 2 ? parseAddressFromTopic(event.topics[2]) : ''; let tokenSymbol = event.token_symbol || 'TOKEN'; let decimals = event.decimals || 18; if ((!event.token_symbol || event.token_symbol === 'TOKEN' || event.token_symbol === 'UNKNOWN') && event.address) { try { const tokenInfo = await getTokenInfo(event.address); if (tokenInfo) { tokenSymbol = tokenInfo.symbol || 'TOKEN'; decimals = tokenInfo.decimals || 18; } } catch (error: any) { console.warn(`Failed to get token info for ${event.address}: ${error.message}`); } } const value = formatTokenAmount(event.data, decimals); enhancedEvent.token_approval = { owner, spender, value, token_address: event.address, token_symbol: tokenSymbol }; const shortOwner = truncateString(owner); const shortSpender = truncateString(spender); enhancedEvent.description = `Approval: ${shortOwner} approved ${shortSpender} to spend ${value} ${tokenSymbol}`; enhancedEvent.display_type = 'Approval'; enhancedEvent.parameters = [ { name: 'signature', value: signature }, { name: 'topic1', value: event.topics[1] }, { name: 'topic2', value: event.topics[2] }, { name: 'data', value: event.data } ]; } else { enhancedEvent.display_type = eventType; } } return enhancedEvent; } /** * * @param events * @returns */ export async function enhanceEvents(events: any[]): Promise<any[]> { if (!events || !Array.isArray(events)) { return []; } const enhancedEvents = []; for (const event of events) { const enhanced = await enhanceEvent(event); enhancedEvents.push(enhanced); } return enhancedEvents; } /** * * @param response * @returns */ export async function enhanceEventResponse(response: any): Promise<any> { if (!response) return response; if (response.events) { const enhancedEvents = await enhanceEvents(response.events); return { ...response, events: enhancedEvents }; } else if (response.transfers) { const enhancedTransfers = await enhanceEvents(response.transfers); return { ...response, transfers: enhancedTransfers }; } else if (response.token_events) { const enhancedEvents = await enhanceEvents(response.token_events); return { ...response, token_events: enhancedEvents }; } else if (response.data && Array.isArray(response.data)) { const enhancedData = await enhanceEvents(response.data); return { ...response, data: enhancedData }; } return response; } /** * @param address * @param params * @returns */ export async function getAddressEvents(address: string, params: { page?: number; limit?: number; eventType?: string; } = {}): Promise<any> { const queryParams: Record<string, any> = { page: params.page || 1, limit: params.limit || 10 }; if (params.eventType) { queryParams.eventType = params.eventType; } const response = await callExplorerApi(`events/address/${address}`, queryParams); return await enhanceEventResponse(response); } /** * @param txHash * @param params * @returns */ export async function getTransactionEvents(txHash: string, params: { eventType?: string; } = {}): Promise<any> { const queryParams: Record<string, any> = {}; if (params.eventType) { queryParams.eventType = params.eventType; } const response = await callExplorerApi(`events/tx/${txHash}`, queryParams); return await enhanceEventResponse(response); } /** * * @param params * @returns */ export async function getAllEvents(params: { page?: number; limit?: number; eventType?: string; } = {}): Promise<any> { const queryParams: Record<string, any> = { page: params.page || 1, limit: params.limit || 10 }; if (params.eventType) { queryParams.eventType = params.eventType; } const response = await callExplorerApi('events', queryParams); return await enhanceEventResponse(response); } /** * * @param params * @returns */ export async function getTokenTransfers(params: { page?: number; limit?: number; } = {}): Promise<any> { const queryParams: Record<string, any> = { page: params.page || 1, limit: params.limit || 10 }; const response = await callExplorerApi('events/token-transfers', queryParams); return await enhanceEventResponse(response); } /** * * @param tokenAddress * @param params * @returns */ export async function getTokenTransfersByToken(tokenAddress: string, params: { page?: number; limit?: number; } = {}): Promise<any> { const queryParams: Record<string, any> = { page: params.page || 1, limit: params.limit || 10 }; const response = await callExplorerApi(`events/token-transfers/token/${tokenAddress}`, queryParams); return await enhanceEventResponse(response); } /** * * @param address * @param params * @returns */ export async function getTokenTransfersByAddress(address: string, params: { page?: number; limit?: number; } = {}): Promise<any> { const queryParams: Record<string, any> = { page: params.page || 1, limit: params.limit || 10 }; const response = await callExplorerApi(`events/token-transfers/address/${address}`, queryParams); return await enhanceEventResponse(response); }