blockchain-api
Version:
API utilities for interacting with the Exatechl2 blockchain
256 lines (211 loc) • 7.35 kB
text/typescript
/**
* Contract Operations
*/
import { callRpc } from '../../core/rpc';
import { callExplorerApi } from '../../core/explorerApi';
import { getTransactionReceipt } from '../transactions/transactions';
import { getBlockByNumber, getLatestBlockNumber } from '../blocks/blocks';
import { ContractInfo } from '../../types';
import {
parseHexToDecimal,
} from '../../utils/formatters';
import {
registerSystemContract,
} from '../../utils/registry';
import {
isSystemContract,
checkIsContract,
getContractBytecode,
isTransactionObject
} from './contract-utils';
import { logger } from '../../utils/logger';
const initializeSystemContracts = () => {
const systemContracts = [
{ address: '0x00000000000000000000000000000000000a4b05', name: 'Core System Contract' }
];
systemContracts.forEach(contract => {
registerSystemContract(contract.address, contract.name);
});
};
initializeSystemContracts();
/**
* @param {string} address
* @returns {Promise<ContractInfo>}
*/
export async function getContractInfo(address: string): Promise<ContractInfo> {
try {
const isContractAddress = await checkIsContract(address);
if (!isContractAddress) {
throw new Error(`Address ${address} is not a contract`);
}
const bytecode = await getContractBytecode(address);
const balance = await callRpc<string>('eth_getBalance', [address, 'latest']);
if (isSystemContract(address.toLowerCase())) {
return {
address,
bytecode,
creator: "System Contract",
creationTx: null,
creationTimestamp: null,
balance,
isVerified: false
};
}
let creator = null;
let creationTx = null;
let creationTimestamp = null;
let isVerified = false;
try {
const contractFromExplorer = await callExplorerApi<any>(`contracts/${address}`);
if (contractFromExplorer && contractFromExplorer.data) {
const contractData = contractFromExplorer.data;
creator = contractData.creator_address || null;
creationTx = contractData.creation_transaction || null;
creationTimestamp = contractData.creation_timestamp || null;
isVerified = contractData.is_verified || false;
logger.debug(`Got contract info from Explorer DB for ${address}`);
}
} catch (e: any) {
logger.warn(`Could not fetch contract info from Explorer DB: ${e.message}`);
}
return {
address,
bytecode,
creator,
creationTx,
creationTimestamp,
balance,
isVerified
};
} catch (error) {
logger.error('Error getting contract info:', error);
throw error;
}
}
/**
*
* @param {string} address
* @returns {Promise<boolean>}
*/
export async function isContractVerified(address: string): Promise<boolean> {
try {
const isContractAddress = await checkIsContract(address);
if (!isContractAddress) {
return false;
}
try {
const contractFromExplorer = await callExplorerApi<any>(`contracts/${address}`);
if (contractFromExplorer && contractFromExplorer.data) {
return contractFromExplorer.data.is_verified || false;
}
} catch (e) {
logger.warn(`Could not fetch verification status from Explorer DB: ${e}`);
}
return false;
} catch (error) {
logger.error('Error checking contract verification:', error);
return false;
}
}
/**
*
* @param {string} address
* @returns {Promise<object | null>}
*/
export async function getContractAbi(address: string): Promise<object | null> {
try {
const isVerified = await isContractVerified(address);
if (!isVerified) {
return null;
}
try {
const abiFromExplorer = await callExplorerApi<any>(`contracts/${address}/abi`);
if (abiFromExplorer && abiFromExplorer.data && abiFromExplorer.data.abi) {
return JSON.parse(abiFromExplorer.data.abi);
}
} catch (e) {
logger.warn(`Could not fetch ABI from Explorer DB: ${e}`);
}
return null;
} catch (error) {
logger.error('Error getting contract ABI:', error);
return null;
}
}
/**
*
* @param {number} maxResults
* @param {number} maxBlocks
* @returns {Promise<string[]>}
*/
export async function findValidContracts(maxResults: number = 5, maxBlocks: number = 10): Promise<string[]> {
try {
const latestBlockHex = await getLatestBlockNumber();
const latestBlock = parseHexToDecimal(latestBlockHex);
const contractAddresses: string[] = [];
const processedAddresses = new Set<string>();
for (let i = 0; i < maxBlocks; i++) {
if (contractAddresses.length >= maxResults) {
break;
}
const blockNumber = latestBlock - i;
if (blockNumber < 0) break;
try {
const block = await getBlockByNumber(blockNumber, true);
if (block && block.transactions && Array.isArray(block.transactions)) {
const contractCreations = block.transactions
.filter(tx => isTransactionObject(tx) && !tx.to);
for (const tx of contractCreations) {
if (isTransactionObject(tx)) {
try {
const receipt = await getTransactionReceipt(tx.hash);
if (receipt && receipt.contractAddress && !processedAddresses.has(receipt.contractAddress)) {
const isContract = await checkIsContract(receipt.contractAddress);
if (isContract) {
contractAddresses.push(receipt.contractAddress);
processedAddresses.add(receipt.contractAddress);
if (contractAddresses.length >= maxResults) {
break;
}
}
}
} catch (e) {
logger.warn(`Error processing transaction ${tx.hash}:`, e);
}
}
}
if (contractAddresses.length < maxResults) {
const contractInteractions = block.transactions
.filter(tx => isTransactionObject(tx) && tx.to);
for (const tx of contractInteractions) {
if (isTransactionObject(tx) && tx.to) {
if (processedAddresses.has(tx.to)) {
continue;
}
try {
const isContract = await checkIsContract(tx.to);
if (isContract) {
contractAddresses.push(tx.to);
processedAddresses.add(tx.to);
if (contractAddresses.length >= maxResults) {
break;
}
}
} catch (e) {
logger.warn(`Error checking if address is contract ${tx.to}:`, e);
}
}
}
}
}
} catch (e) {
logger.warn(`Error processing block ${blockNumber}:`, e);
continue;
}
}
return contractAddresses;
} catch (error) {
logger.error('Error finding valid contracts:', error);
return [];
}
}