/* Available Async Functions Blockchain-related: 1) getBlockchainInfo(): Promise DSBlocks-related: 1) getNumDSBlocks(): Promise 2) getDSBlockDetails(blockNum: string): Promise 3) getLatest5DSBlocks(): Promise 4) getDSBlocksListing(pageNum: number): Promise 5) getMinerInfo(blockNum: string): Promise TxBlocks-related: 1) getNumTxBlocks(): Promise 2) getTxBlockObj(blockNum: number): Promise 3) getLatest5TxBlocks(): Promise 4) getTxBlocksListing(pageNum: number): Promise Transactions-related: 1) getLatest5ValidatedTransactions(): Promise 2) getTransactionOwner(txnHash: string): Promise 3) getTransactionDetails(txnHash: string): Promise 4) getTransactionsForTxBlock(blockNum: number): Promise 5) getTransactionsDetails(txnHashes: string[]): Promise 6) getRecentTransactions(): Promise 7) getLatest5PendingTransactions(): Promise Account-related: 1) getAccData(accAddr: string): Promise 2) getAccContracts(accAddr: string): Promise Contract-related: 1) getContractAddrFromTransaction(txnHash: string): Promise 2) getTxnIdFromContractData(contractData: ContractData): Promise 3) getContractData(contractAddr: string): Promise Isolated Server-related: 1) getISInfo(): Promise Util: 1) isIsolatedServer(): Promise 2) isContractAddr(addr: string): Promise */ // Mainnet: https://api.zilliqa.com // Testnet: https://dev-api.zilliqa.com // Isolated Server: https://zilliqa-isolated-server.zilliqa.com // Staging Isolated Server: https://stg-zilliqa-isolated-server.zilliqa.com // Seeds: https://stat.zilliqa.com/api/seeds import { Zilliqa } from "@zilliqa-js/zilliqa"; import { ContractObj } from "@zilliqa-js/contract/src/types"; import { BlockchainInfo, DsBlockObj, TxBlockObj, TxList, MinerInfo, TransactionObj, TransactionStatus, } from "@zilliqa-js/core/src/types"; import { DsBlockObjWithHashListing, TxBlockObjListing, TransactionDetails, ContractData, AccData, DsBlockObjWithHash, IISInfo, } from "src/typings/api"; import { hexAddrToZilAddr, stripHexPrefix } from "src/utils/Utils"; export class DataService { zilliqa: Zilliqa; networkUrl: string; constructor(networkUrl: string | null) { if (networkUrl) { this.networkUrl = networkUrl; this.zilliqa = new Zilliqa(networkUrl); } else { this.networkUrl = "https://api.zilliqa.com"; this.zilliqa = new Zilliqa("https:/api.zilliqa.com"); } } //================================================================================ // Blockchain-related //================================================================================ async getBlockchainInfo(): Promise { console.log("getting blockchain info"); const response = await this.zilliqa.blockchain.getBlockChainInfo(); if (response.error !== undefined) throw new Error(response.error.message); return response.result as BlockchainInfo; } //================================================================================ // DSBlocks-related //================================================================================ async getNumDSBlocks(): Promise { console.log("getting number of DS blocks"); const response = await this.zilliqa.blockchain.getNumDSBlocks(); if (response.error !== undefined) throw new Error(response.error.message); return parseInt(response.result, 10); } async getDSBlockDetails(blockNum: string): Promise { console.log("getting DS block details"); const response = await this.zilliqa.blockchain.getDSBlock( parseInt(blockNum, 10) ); if (response.error !== undefined) throw new Error(response.error.message); if (response.result.header.BlockNum !== blockNum) throw new Error("Invalid DS Block Number"); return response.result as DsBlockObj; } async getLatest5DSBlocks(): Promise { console.log("getting 5 ds blocks"); const response = await this.zilliqa.blockchain.getDSBlockListing(1); if (response.error !== undefined) throw new Error(response.error.message); const latest5DSBlockShort = response.result.data.slice(0, 5); // Map DSBlock with header info const output = await Promise.all( latest5DSBlockShort.map(async (blockShort) => { const response = await this.zilliqa.blockchain.getDSBlock( blockShort.BlockNum ); if (response.error !== undefined) throw new Error(response.error.message); return response.result as DsBlockObj; }) ); return output as DsBlockObj[]; } async getDSBlocksListing( pageNum: number ): Promise { console.log("getting ds blocks"); const response = await this.zilliqa.blockchain.getDSBlockListing(pageNum); if (response.error !== undefined) throw new Error(response.error.message); const blockShorts = response.result.data; // Map DSBlock with header info const blockData = await Promise.all( blockShorts.map(async (blockShort) => { const response = await this.zilliqa.blockchain.getDSBlock( blockShort.BlockNum ); if (response.error !== undefined) throw new Error(response.error.message); return { ...response.result, Hash: blockShort.Hash, } as DsBlockObjWithHash; }) ); return { maxPages: response.result.maxPages, data: blockData, }; } async getMinerInfo(blockNum: string): Promise { console.log("getting miner info"); const response = await this.zilliqa.blockchain.getMinerInfo(blockNum); console.log(response); if (response.error !== undefined) throw new Error(response.error.message); return response.result as MinerInfo; } //================================================================================ // TxBlocks-related //================================================================================ async getNumTxBlocks(): Promise { console.log("getting number of tx blocks"); const response = await this.zilliqa.blockchain.getNumTxBlocks(); if (response.error !== undefined) throw new Error(response.error.message); return parseInt(response.result, 10); } async getTxBlockObj(blockNum: number): Promise { console.log("getting tx block obj"); const response = await this.zilliqa.blockchain.getTxBlock(blockNum); if (response.error !== undefined) throw new Error(response.error.message); if (parseInt(response.result.header.BlockNum) !== blockNum) throw new Error("Invalid Tx Block Number"); return response.result as TxBlockObj; } async getLatest5TxBlocks(): Promise { console.log("getting 5 tx blocks"); const response = await this.zilliqa.blockchain.getTxBlockListing(1); if (response.error !== undefined) throw new Error(response.error.message); const blockShorts = response.result.data.slice(0, 5); // Map TxBlock with header info const output = await Promise.all( blockShorts.map(async (blockShort) => { const response = await this.zilliqa.blockchain.getTxBlock( blockShort.BlockNum ); if (response.error !== undefined) throw new Error(response.error.message); return response.result as TxBlockObj; }) ); return output as TxBlockObj[]; } async getTxBlocksListing(pageNum: number): Promise { console.log("getting tx blocks"); const response = await this.zilliqa.blockchain.getTxBlockListing(pageNum); if (response.error !== undefined) throw new Error(response.error.message); const blockShorts = response.result.data; // Map TxBlock with header info const blockData = (await Promise.all( blockShorts.map(async (blockShort) => { const response = await this.zilliqa.blockchain.getTxBlock( blockShort.BlockNum ); if (response.error !== undefined) throw new Error(response.error.message); return response.result as TxBlockObj; }) )) as TxBlockObj[]; return { maxPages: response.result.maxPages, data: blockData, }; } //================================================================================ // Transactions-related //================================================================================ async getLatest5ValidatedTransactions(): Promise { console.log("getting 5 validated tx"); const response = await this.zilliqa.blockchain.getRecentTransactions(); if (response.error !== undefined) throw new Error(response.error.message); const txnHashes = response.result.TxnHashes.slice(0, 5); // Map DSBlock with header info const output = await Promise.all( txnHashes.map( async (txnHash) => await this.getTransactionDetails(txnHash) ) ); return output as TransactionDetails[]; } async getTransactionOwner(txnHash: string): Promise { const txn = await this.zilliqa.blockchain.getTransaction( stripHexPrefix(txnHash) ); if (txn.senderAddress) return hexAddrToZilAddr(txn.senderAddress); else throw new Error("Invalid Transaction"); } async getTransactionDetails(txnHash: string): Promise { console.log("getting transaction details"); txnHash = stripHexPrefix(txnHash); const txn = await this.zilliqa.blockchain.getTransaction(txnHash); if ( txn.txParams && txn.txParams.toAddr === "0x0000000000000000000000000000000000000000" ) { const contractAddr = await this.getContractAddrFromTransaction(txnHash); // @ts-ignore return { txn: txn, hash: txnHash, contractAddr: contractAddr, isContractCreation: true, } as TransactionDetails; } else if (await this.isContractAddr(txn.txParams.toAddr)) { // @ts-ignore return { txn: txn, hash: txnHash, contractAddr: txn.txParams.toAddr, isContractCreation: false, } as TransactionDetails; } else { // @ts-ignore return { txn: txn, hash: txnHash } as TransactionDetails; } } async getTransactionsForTxBlock(blockNum: number): Promise { console.log("getting transactions for Tx block"); const response = await this.zilliqa.blockchain.getTransactionsForTxBlock( blockNum ); console.log(response); if (response.error !== undefined) throw new Error(response.error.message); return response.result.flat().filter((x) => x !== null) as string[]; } async getTransactionsDetails( txnHashes: string[] ): Promise { console.log("getting transactions details"); const output = await Promise.all( txnHashes.map( async (txnHash) => await this.getTransactionDetails(txnHash) ) ); return output as TransactionDetails[]; } async getRecentTransactions(): Promise { console.log("getting recent transactions"); const response = await this.zilliqa.blockchain.getRecentTransactions(); if (response.error !== undefined) throw new Error(response.error.message); return response.result as TxList; } async getLatest5PendingTransactions(): Promise { console.log("getting 5 pending tx"); // TODO: Fix the API call //const pendingTxns = await this.zilliqa.blockchain.getPendingTxns(); //return pendingTxns.Txns as TransactionStatus[]; return [] as TransactionStatus[]; } //================================================================================ // Account-related //================================================================================ async getAccData(accAddr: string): Promise { console.log("getting balance"); const response = await this.zilliqa.blockchain.getBalance(accAddr); if (response.error !== undefined) throw new Error(response.error.message); return response.result as AccData; } async getAccContracts(accAddr: string): Promise { console.log("getting smart contracts for addr"); const response = await this.zilliqa.blockchain.getSmartContracts(accAddr); if (response.error !== undefined) throw new Error(response.error.message); return response.result as ContractObj[]; } //================================================================================ // Isolated Server-related //================================================================================ async getISBlockNum(): Promise { console.log("getting isolated server tx block num"); const response = await fetch(this.networkUrl, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ id: "1", jsonrpc: "2.0", method: "GetBlocknum", params: [""], }), }); const parsedRes = await response.json(); return parseInt(parsedRes.result, 10) as number; } async getISInfo(): Promise { console.log("getting isolated server info"); const blockNum = await this.getISBlockNum(); const response = await this.zilliqa.blockchain.getMinimumGasPrice(); if (response.error !== undefined) throw new Error(response.error.message); return { blockNum: blockNum, minGasPrice: response.result, } as IISInfo; } async getISTransactionsForTxBlock(blockNum: number): Promise { console.log("getting transactions for Tx block"); const response = await fetch(this.networkUrl, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ id: "1", jsonrpc: "2.0", method: "GetTransactionsForTxBlock", params: [`${blockNum}`], }), }); const res = await response.json(); return res.result.map((txnObj: TransactionObj) => txnObj.ID) as string[]; } async getTransactionDetailsSimplified({ hash, toAddr, }: { hash: string; toAddr: string; }): Promise { hash = stripHexPrefix(hash); if (toAddr === "0x0000000000000000000000000000000000000000") { const contractAddr = await this.getContractAddrFromTransaction(hash); // @ts-ignore return { hash, contractAddr: contractAddr, isContractCreation: true, toAddr, } as TransactionDetails; } else if (await this.isContractAddr(toAddr)) { // @ts-ignore return { hash, contractAddr: toAddr, isContractCreation: false, toAddr, } as TransactionDetails; } else { // @ts-ignore return { hash, toAddr } as TransactionDetails; } } //================================================================================ // Contract-related //================================================================================ async getContractAddrFromTransaction(txnHash: string): Promise { console.log("getting smart contracts addr"); const response = await this.zilliqa.blockchain.getContractAddressFromTransactionID( txnHash ); if (response.error !== undefined) throw new Error(response.error.message); return response.result as string; } async getTxnIdFromContractData(contractData: ContractData): Promise { console.log("getting transaction id from contract data"); const _creationTxBlock = contractData.initParams.filter( (x) => x.vname === "_creation_block" )[0].value; const _contractAddr = contractData.initParams.filter( (x) => x.vname === "_this_address" )[0].value; if ( typeof _creationTxBlock !== "string" || typeof _contractAddr !== "string" ) throw new Error("Type Error: Tx BlockNum or Contract Address not string"); const txBlockTxns = await this.getTransactionsForTxBlock( parseInt(_creationTxBlock, 10) ); const contractAddrs = await Promise.all( txBlockTxns.map(async (txnHash: string) => { let contractAddr: string | null; try { contractAddr = await this.getContractAddrFromTransaction(txnHash); } catch (e) { contractAddr = null; } return "0x" + contractAddr; }) ); return "0x" + txBlockTxns[contractAddrs.indexOf(_contractAddr)]; } async getContractData(contractAddr: string): Promise { console.log("getting contract data"); const contractCode = async () => await this.zilliqa.blockchain.getSmartContractCode(contractAddr); const contractInit = async () => await this.zilliqa.blockchain.getSmartContractInit(contractAddr); const contractState = async () => await this.zilliqa.blockchain.getSmartContractState(contractAddr); const res = await Promise.all([ contractCode(), contractInit(), contractState(), ]); if (res[0].error !== undefined) throw new Error(res[0].error.message); if (res[1].error !== undefined) throw new Error(res[1].error.message); if (res[2].error !== undefined) throw new Error(res[2].error.message); const contractData = { code: res[0].result.code, initParams: res[1].result, state: res[2].result, }; return contractData as ContractData; } //================================================================================ // Util //================================================================================ /* Until we find a better way to differentiate an isolated server, we will differentiate based on the available API i.e. getBlockChainIfo */ async isIsolatedServer(): Promise { console.log("check whether connected to isolated server"); const response = await this.zilliqa.blockchain.getBlockChainInfo(); return !response.result; } /* Until we find a better way to differentiate an account address from a smart contract address, we will differentiate based on the the response error message if any */ async isContractAddr(addr: string): Promise { console.log("check whether is smart contract"); const response = await this.zilliqa.blockchain.getSmartContractInit(addr); if (!response.error) return true; else if (response.error.message === "Address not contract address") return false; else throw new Error("Invalid Address"); } }