blockchain-api
Version:
API utilities for interacting with the Exatechl2 blockchain
419 lines (347 loc) • 11.3 kB
text/typescript
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);
}