UNPKG

@vechain/sdk-network

Version:

This module serves as the standard interface connecting decentralized applications (dApps) and users to the VeChainThor blockchain

249 lines (232 loc) 8.24 kB
import { type TransactionReceiptLogsRPC, type TransactionReceiptRPC, type TransactionRPC } from './types'; import { fromTransactionType, Hex, HexUInt, Quantity, TransactionType, ZERO_BYTES } from '@vechain/sdk-core'; import { getNumberOfLogsAheadOfTransactionIntoBlockExpanded, getTransactionIndexIntoBlock } from '../../helpers/transaction/transaction-helpers'; import { blocksFormatter } from '../blocks'; import { type ExpandedBlockDetail, type TransactionDetailNoRaw, type TransactionReceipt, type TransactionsExpandedBlockDetail } from '../../../../thor-client'; /** * Maps VeChain transaction types to Ethereum transaction types. * - VeChain Type 0 (legacy) maps to Ethereum Type 0 (legacy) -> '0x0' * - VeChain Type 81 (0x51, EIP1559) maps to Ethereum Type 2 (EIP-1559) -> '0x2' * * @param vechainType - The VeChain transaction type (0 or 81) * @returns The Ethereum transaction type as a hex string ('0x0' or '0x2') */ const mapVeChainTypeToEthereumType = ( vechainType: number | undefined | null ): '0x0' | '0x2' => { // If vechainType is undefined, null, or not a number, default to 0 (legacy) if ( vechainType === undefined || vechainType === null || typeof vechainType !== 'number' ) { return '0x0'; } // Type 81 (EIP1559) in VeChain corresponds to Type 2 (EIP-1559) in Ethereum if (vechainType === fromTransactionType(TransactionType.EIP1559)) { return '0x2'; } // Default to legacy transaction type (0x0) return '0x0'; }; /** * Output formatter for Transaction details. * * @param tx - The Transaction details to be formatted. * @param blockHash - The hash of the block where the transaction is in. * @param blockNumber - The number of the block where the transaction is in. * @param chainId - The chain ID of the network. * @param txIndex - The index of the transaction in the block. * * @returns The RPC standard formatted transaction. */ const _formatTransactionToRPC = ( tx: TransactionDetailNoRaw | TransactionsExpandedBlockDetail, blockHash: string, blockNumber: number, chainId: string, txIndex: number ): TransactionRPC => { // Default to legacy transaction type if 'type' property doesn't exist const txType = 'type' in tx ? tx.type : 0; return { // Supported fields blockHash, blockNumber: Quantity.of(blockNumber).toString(), from: tx.origin, gas: Quantity.of(HexUInt.of(tx.gas).bi).toString(), chainId, hash: tx.id, nonce: tx.nonce as string, transactionIndex: Quantity.of(txIndex).toString(), /** * `input`, `to`, `value` are being referred to the first clause. * VeChain supports multiple clauses in one transaction, thus the actual data should be obtained by looking into each clause. * Due to the single clause limitation of Ethereum, we assume the first clause is the clause from which we obtain the data. */ input: tx.clauses[0]?.data ?? '', to: tx.clauses[0]?.to ?? null, value: tx.clauses[0]?.value !== undefined ? Quantity.of(HexUInt.of(tx.clauses[0].value).bi).toString() : '', type: mapVeChainTypeToEthereumType(txType), maxFeePerGas: tx.maxFeePerGas !== undefined && tx.maxFeePerGas !== null ? Quantity.of(HexUInt.of(tx.maxFeePerGas).bi).toString() : undefined, maxPriorityFeePerGas: tx.maxPriorityFeePerGas !== undefined && tx.maxPriorityFeePerGas !== null ? Quantity.of(HexUInt.of(tx.maxPriorityFeePerGas).bi).toString() : undefined, // Unsupported fields gasPrice: '0x0', v: '0x0', r: '0x0', s: '0x0', accessList: [], yParity: '0x0' }; }; /** * Output formatter for Transaction details. * It converts the Transaction details into the RPC standard. * * @param tx - The Transaction details to be formatted. * @param chainId - The chain ID of the network. * @param txIndex - The index of the transaction in the block. * * @returns The RPC standard formatted transaction. */ const formatToRPCStandard = ( tx: TransactionDetailNoRaw, chainId: string, txIndex: number ): TransactionRPC => { return _formatTransactionToRPC( tx, tx.meta.blockID, tx.meta.blockNumber, chainId, txIndex ); }; /** * Output formatter for Transaction details from expanded block. * It converts the Transaction details into the RPC standard. * * @param tx - The Transaction details to be formatted. * @param block - The block details to be formatted. * @param chainId - The chain ID of the network. * @param txIndex - The index of the transaction in the block. * * @returns The RPC standard formatted transaction. */ const formatExpandedBlockToRPCStandard = ( tx: TransactionsExpandedBlockDetail, block: ExpandedBlockDetail, txIndex: number, chainId: string ): TransactionRPC => { return _formatTransactionToRPC( tx, block.id, block.number, chainId, txIndex ); }; /** * Output formatter for Transaction Receipt details. * It converts the Transaction Receipt details, Transaction details and block into the RPC standard. * * @param transactionHash - The hash of the transaction to be formatted. * @param receipt - The Transaction Receipt to be formatted. * @param transaction - The Transaction details to be formatted. * @param blockContainsTransaction - The block contains the transaction to be formatted. * @param chainId - The chain ID of the network. */ function formatTransactionReceiptToRPCStandard( transactionHash: string, receipt: TransactionReceipt, transaction: TransactionDetailNoRaw, blockContainsTransaction: ExpandedBlockDetail, chainId: string ): TransactionReceiptRPC { // Get transaction index const transactionIndex = getTransactionIndexIntoBlock( blocksFormatter.formatToRPCStandard(blockContainsTransaction, chainId), transactionHash ); // Format transaction receipt logs const logIndexOffset = getNumberOfLogsAheadOfTransactionIntoBlockExpanded( blockContainsTransaction, transactionHash, chainId ); const logs: TransactionReceiptLogsRPC[] = []; let logIndex = logIndexOffset; receipt.outputs.forEach((output) => { output.events.forEach((event) => { logs.push({ blockHash: receipt.meta.blockID, blockNumber: Quantity.of(receipt.meta.blockNumber).toString(), transactionHash: receipt.meta.txID as string, address: event.address, topics: event.topics.map((topic) => topic), data: event.data, removed: false, transactionIndex: Quantity.of(transactionIndex).toString(), logIndex: Quantity.of(logIndex).toString() }); logIndex++; }); }); // Default to legacy transaction type if 'type' property doesn't exist const txType = 'type' in transaction ? transaction.type : 0; return { blockHash: receipt.meta.blockID, blockNumber: Quantity.of(receipt.meta.blockNumber).toString(), contractAddress: receipt.outputs.length > 0 ? receipt.outputs[0].contractAddress : null, from: transaction.origin, gasUsed: Quantity.of(receipt.gasUsed).toString(), logs, status: receipt.reverted ? '0x0' : '0x1', to: transaction.clauses[0].to, transactionHash: receipt.meta.txID as string, transactionIndex: Quantity.of(transactionIndex).toString(), // Incompatible fields logsBloom: Hex.of(ZERO_BYTES(256)).toString(), cumulativeGasUsed: '0x0', effectiveGasPrice: '0x0', type: mapVeChainTypeToEthereumType(txType) }; } export { formatToRPCStandard, formatExpandedBlockToRPCStandard, formatTransactionReceiptToRPCStandard };