/** * Component: TransactionsReceiptInfo * Author: Nearblocks Pte Ltd * License: Business Source License 1.1 * Description: Details of Transaction Receipt on Near Protocol. * @interface Props * @param {string} [network] - The network data to show, either mainnet or testnet * @param {Function} [t] - A function for internationalization (i18n) provided by the next-translate package. * @param {ReceiptsPropsInfo | any} [receipt] - receipt of the transaction. * @param {React.FC<{ * href: string; * children: React.ReactNode; * className?: string; * }>} Link - A React component for rendering links. */ /* INCLUDE: "includes/formats.jsx" */ function convertToMetricPrefix(numberStr) { const prefixes = ['', 'k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y']; // Metric prefixes let result = new Big(numberStr); let count = 0; while (result.abs().gte('1e3') && count < prefixes.length - 1) { result = result.div(1e3); count++; } // Check if the value is an integer or has more than two digits before the decimal point if (result.abs().lt(1e2) && result.toFixed(2) !== result.toFixed(0)) { result = result.toFixed(2); } else { result = result.toFixed(0); } return result.toString() + ' ' + prefixes[count]; } function formatNumber(value) { let bigValue = new Big(value); const suffixes = ['', 'K', 'M', 'B', 'T']; let suffixIndex = 0; while (bigValue.gte(10000) && suffixIndex < suffixes.length - 1) { bigValue = bigValue.div(1000); suffixIndex++; } const formattedValue = bigValue.toFixed(1).replace(/\.0+$/, ''); return `${formattedValue} ${suffixes[suffixIndex]}`; } function gasFee(gas, price) { const near = yoctoToNear(Big(gas).mul(Big(price)).toString(), true); return `${near}`; } function currency(number) { let absNumber = new Big(number).abs(); const suffixes = ['', 'K', 'M', 'B', 'T', 'Q']; let suffixIndex = 0; while (absNumber.gte(1000) && suffixIndex < suffixes.length - 1) { absNumber = absNumber.div(1000); // Divide using big.js's div method suffixIndex++; } const formattedNumber = absNumber.toFixed(2); // Format with 2 decimal places return ( (number < '0' ? '-' : '') + formattedNumber + ' ' + suffixes[suffixIndex] ); } function formatDate(dateString) { const inputDate = new Date(dateString); const days = [ 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', ]; const months = [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December', ]; const dayOfWeek = days[inputDate.getDay()]; const month = months[inputDate.getMonth()]; const day = inputDate.getDate(); const year = inputDate.getFullYear(); const formattedDate = dayOfWeek + ', ' + month + ' ' + day + ', ' + year; return formattedDate; } function formatCustomDate(inputDate) { var date = new Date(inputDate); // Array of month names var monthNames = [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec', ]; // Get month and day var month = monthNames[date.getMonth()]; var day = date.getDate(); // Create formatted date string in "MMM DD" format var formattedDate = month + ' ' + (day < 10 ? '0' + day : day); return formattedDate; } function shortenHex(address) { return `${address && address.substr(0, 6)}...${address.substr(-4)}`; } function capitalizeFirstLetter(string) { return string.charAt(0).toUpperCase() + string.slice(1); } function shortenToken(token) { return truncateString(token, 14, ''); } function shortenTokenSymbol(token) { return truncateString(token, 5, ''); } function gasPercentage(gasUsed, gasAttached) { if (!gasAttached) return 'N/A'; const formattedNumber = (Big(gasUsed).div(Big(gasAttached)) * 100).toFixed(2); return `${formattedNumber}%`; } function serialNumber(index, page, perPage) { return index + 1 + (page - 1) * perPage; } function capitalizeWords(str) { const words = str.split('_'); const capitalizedWords = words.map( (word) => word.charAt(0).toUpperCase() + word.slice(1), ); const result = capitalizedWords.join(' '); return result; } function toSnakeCase(str) { return str .replace(/[A-Z]/g, (match) => '_' + match.toLowerCase()) .replace(/^_/, ''); } function capitalize(str) { return str.charAt(0).toUpperCase() + str.slice(1); } function truncateString(str, maxLength, suffix) { if (str.length <= maxLength) { return str; } return str.substring(0, maxLength) + suffix; } function yoctoToNear(yocto, format) { const YOCTO_PER_NEAR = Big(10).pow(24).toString(); const near = Big(yocto).div(YOCTO_PER_NEAR).toString(); return format ? localFormat(near) : near; } function truncateString(str, maxLength, suffix) { if (str.length <= maxLength) { return str; } return str.substring(0, maxLength) + suffix; } function yoctoToNear(yocto, format) { const YOCTO_PER_NEAR = Big(10).pow(24).toString(); const near = Big(yocto).div(YOCTO_PER_NEAR).toString(); return format ? localFormat(near) : near; } function truncateString(str, maxLength, suffix) { if (str.length <= maxLength) { return str; } return str.substring(0, maxLength) + suffix; } function yoctoToNear(yocto, format) { const YOCTO_PER_NEAR = Big(10).pow(24).toString(); const near = Big(yocto).div(YOCTO_PER_NEAR).toString(); return format ? localFormat(near) : near; } /* END_INCLUDE: "includes/formats.jsx" */ /* INCLUDE: "includes/libs.jsx" */ function getConfig(network) { switch (network) { case 'mainnet': return { ownerId: 'nearblocks.near', nodeUrl: 'https://rpc.mainnet.near.org', backendUrl: 'https://api3.nearblocks.io/v1/', rpcUrl: 'https://beta.rpc.mainnet.near.org', appUrl: 'https://nearblocks.io/', }; case 'testnet': return { ownerId: 'nearblocks.testnet', nodeUrl: 'https://rpc.testnet.near.org', backendUrl: 'https://api3-testnet.nearblocks.io/v1/', rpcUrl: 'https://beta.rpc.testnet.near.org/', appUrl: 'https://testnet.nearblocks.io/', }; default: return {}; } } function debounce( delay, func, ) { let timer; let active = true; const debounced = (arg) => { if (active) { clearTimeout(timer); timer = setTimeout(() => { active && func(arg); timer = undefined; }, delay); } else { func(arg); } }; debounced.isPending = () => { return timer !== undefined; }; debounced.cancel = () => { active = false; }; debounced.flush = (arg) => func(arg); return debounced; } function timeAgo(unixTimestamp) { const currentTimestamp = Math.floor(Date.now() / 1000); const secondsAgo = currentTimestamp - unixTimestamp; if (secondsAgo < 5) { return 'Just now'; } else if (secondsAgo < 60) { return `${secondsAgo} seconds ago`; } else if (secondsAgo < 3600) { const minutesAgo = Math.floor(secondsAgo / 60); return `${minutesAgo} minute${minutesAgo > 1 ? 's' : ''} ago`; } else if (secondsAgo < 86400) { const hoursAgo = Math.floor(secondsAgo / 3600); return `${hoursAgo} hour${hoursAgo > 1 ? 's' : ''} ago`; } else if (secondsAgo < 2592000) { const daysAgo = Math.floor(secondsAgo / 86400); return `${daysAgo} day${daysAgo > 1 ? 's' : ''} ago`; } else if (secondsAgo < 31536000) { const monthsAgo = Math.floor(secondsAgo / 2592000); return `${monthsAgo} month${monthsAgo > 1 ? 's' : ''} ago`; } else { const yearsAgo = Math.floor(secondsAgo / 31536000); return `${yearsAgo} year${yearsAgo > 1 ? 's' : ''} ago`; } } function shortenAddress(address) { const string = String(address); if (string.length <= 20) return string; return `${string.substr(0, 10)}...${string.substr(-7)}`; } function urlHostName(url) { try { const domain = new URL(url); return domain?.hostname ?? null; } catch (e) { return null; } } function holderPercentage(supply, quantity) { return Math.min(Big(quantity).div(Big(supply)).mul(Big(100)).toFixed(2), 100); } function isAction(type) { const actions = [ 'DEPLOY_CONTRACT', 'TRANSFER', 'STAKE', 'ADD_KEY', 'DELETE_KEY', 'DELETE_ACCOUNT', ]; return actions.includes(type.toUpperCase()); } function isJson(string) { const str = string.replace(/\\/g, ''); try { JSON.parse(str); return false; } catch (e) { return false; } } function uniqueId() { return Math.floor(Math.random() * 1000); } function handleRateLimit( data, reFetch, Loading, ) { if (data.status === 429 || data.status === undefined) { const retryCount = 4; const delay = Math.pow(2, retryCount) * 1000; setTimeout(() => { reFetch(); }, delay); } else { if (Loading) { Loading(); } } } function mapFeilds(fields) { const args = {}; fields.forEach((fld) => { let value = fld.value; if (fld.type === 'number') { value = Number(value); } else if (fld.type === 'boolean') { value = value.trim().length > 0 && !['false', '0'].includes(value.toLowerCase()); } else if (fld.type === 'json') { value = JSON.parse(value); } else if (fld.type === 'null') { value = null; } (args )[fld.name] = value + ''; }); return args; } function localFormat(number) { const bigNumber = Big(number); const formattedNumber = bigNumber .toFixed(5) .replace(/(\d)(?=(\d{3})+\.)/g, '$1,'); // Add commas before the decimal point return formattedNumber.replace(/\.?0*$/, ''); // Remove trailing zeros and the dot } function formatWithCommas(number) { return number.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ','); } function localFormat(number) { const bigNumber = Big(number); const formattedNumber = bigNumber .toFixed(5) .replace(/(\d)(?=(\d{3})+\.)/g, '$1,'); // Add commas before the decimal point return formattedNumber.replace(/\.?0*$/, ''); // Remove trailing zeros and the dot } function formatWithCommas(number) { return number.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ','); } function handleRateLimit( data, reFetch, Loading, ) { if (data.status === 429 || data.status === undefined) { const retryCount = 4; const delay = Math.pow(2, retryCount) * 1000; setTimeout(() => { reFetch(); }, delay); } else { if (Loading) { Loading(); } } } function mapFeilds(fields) { const args = {}; fields.forEach((fld) => { let value = fld.value; if (fld.type === 'number') { value = Number(value); } else if (fld.type === 'boolean') { value = value.trim().length > 0 && !['false', '0'].includes(value.toLowerCase()); } else if (fld.type === 'json') { value = JSON.parse(value); } else if (fld.type === 'null') { value = null; } (args )[fld.name] = value + ''; }); return args; } function localFormat(number) { const bigNumber = Big(number); const formattedNumber = bigNumber .toFixed(5) .replace(/(\d)(?=(\d{3})+\.)/g, '$1,'); // Add commas before the decimal point return formattedNumber.replace(/\.?0*$/, ''); // Remove trailing zeros and the dot } function formatWithCommas(number) { return number.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ','); } function yoctoToNear(yocto, format) { const YOCTO_PER_NEAR = Big(10).pow(24).toString(); const near = Big(yocto).div(YOCTO_PER_NEAR).toString(); return format ? localFormat(near) : near; } function fiatValue(big, price) { const value = Big(big).mul(Big(price)); const stringValue = value.toFixed(6); // Set the desired maximum fraction digits const [integerPart, fractionalPart] = stringValue.split('.'); // Format integer part with commas const formattedIntegerPart = integerPart.replace( /\B(?=(\d{3})+(?!\d))/g, ',', ); // Combine formatted integer and fractional parts const formattedNumber = fractionalPart ? `${formattedIntegerPart}.${fractionalPart}` : formattedIntegerPart; return formattedNumber; } function nanoToMilli(nano) { return Big(nano).div(Big(10).pow(6)).round().toNumber(); } function truncateString(str, maxLength, suffix) { if (str.length <= maxLength) { return str; } return str.substring(0, maxLength) + suffix; } function getConfig(network) { switch (network) { case 'mainnet': return { ownerId: 'nearblocks.near', nodeUrl: 'https://rpc.mainnet.near.org', backendUrl: 'https://api3.nearblocks.io/v1/', rpcUrl: 'https://beta.rpc.mainnet.near.org', appUrl: 'https://nearblocks.io/', }; case 'testnet': return { ownerId: 'nearblocks.testnet', nodeUrl: 'https://rpc.testnet.near.org', backendUrl: 'https://api3-testnet.nearblocks.io/v1/', rpcUrl: 'https://beta.rpc.testnet.near.org/', appUrl: 'https://testnet.nearblocks.io/', }; default: return {}; } } function debounce( delay, func, ) { let timer; let active = true; const debounced = (arg) => { if (active) { clearTimeout(timer); timer = setTimeout(() => { active && func(arg); timer = undefined; }, delay); } else { func(arg); } }; debounced.isPending = () => { return timer !== undefined; }; debounced.cancel = () => { active = false; }; debounced.flush = (arg) => func(arg); return debounced; } function timeAgo(unixTimestamp) { const currentTimestamp = Math.floor(Date.now() / 1000); const secondsAgo = currentTimestamp - unixTimestamp; if (secondsAgo < 5) { return 'Just now'; } else if (secondsAgo < 60) { return `${secondsAgo} seconds ago`; } else if (secondsAgo < 3600) { const minutesAgo = Math.floor(secondsAgo / 60); return `${minutesAgo} minute${minutesAgo > 1 ? 's' : ''} ago`; } else if (secondsAgo < 86400) { const hoursAgo = Math.floor(secondsAgo / 3600); return `${hoursAgo} hour${hoursAgo > 1 ? 's' : ''} ago`; } else if (secondsAgo < 2592000) { const daysAgo = Math.floor(secondsAgo / 86400); return `${daysAgo} day${daysAgo > 1 ? 's' : ''} ago`; } else if (secondsAgo < 31536000) { const monthsAgo = Math.floor(secondsAgo / 2592000); return `${monthsAgo} month${monthsAgo > 1 ? 's' : ''} ago`; } else { const yearsAgo = Math.floor(secondsAgo / 31536000); return `${yearsAgo} year${yearsAgo > 1 ? 's' : ''} ago`; } } function shortenAddress(address) { const string = String(address); if (string.length <= 20) return string; return `${string.substr(0, 10)}...${string.substr(-7)}`; } function urlHostName(url) { try { const domain = new URL(url); return domain?.hostname ?? null; } catch (e) { return null; } } function holderPercentage(supply, quantity) { return Math.min(Big(quantity).div(Big(supply)).mul(Big(100)).toFixed(2), 100); } function isAction(type) { const actions = [ 'DEPLOY_CONTRACT', 'TRANSFER', 'STAKE', 'ADD_KEY', 'DELETE_KEY', 'DELETE_ACCOUNT', ]; return actions.includes(type.toUpperCase()); } function isJson(string) { const str = string.replace(/\\/g, ''); try { JSON.parse(str); return false; } catch (e) { return false; } } function uniqueId() { return Math.floor(Math.random() * 1000); } function handleRateLimit( data, reFetch, Loading, ) { if (data.status === 429 || data.status === undefined) { const retryCount = 4; const delay = Math.pow(2, retryCount) * 1000; setTimeout(() => { reFetch(); }, delay); } else { if (Loading) { Loading(); } } } function mapFeilds(fields) { const args = {}; fields.forEach((fld) => { let value = fld.value; if (fld.type === 'number') { value = Number(value); } else if (fld.type === 'boolean') { value = value.trim().length > 0 && !['false', '0'].includes(value.toLowerCase()); } else if (fld.type === 'json') { value = JSON.parse(value); } else if (fld.type === 'null') { value = null; } (args )[fld.name] = value + ''; }); return args; } function localFormat(number) { const bigNumber = Big(number); const formattedNumber = bigNumber .toFixed(5) .replace(/(\d)(?=(\d{3})+\.)/g, '$1,'); // Add commas before the decimal point return formattedNumber.replace(/\.?0*$/, ''); // Remove trailing zeros and the dot } function formatWithCommas(number) { return number.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ','); } function localFormat(number) { const bigNumber = Big(number); const formattedNumber = bigNumber .toFixed(5) .replace(/(\d)(?=(\d{3})+\.)/g, '$1,'); // Add commas before the decimal point return formattedNumber.replace(/\.?0*$/, ''); // Remove trailing zeros and the dot } function formatWithCommas(number) { return number.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ','); } function localFormat(number) { const bigNumber = Big(number); const formattedNumber = bigNumber .toFixed(5) .replace(/(\d)(?=(\d{3})+\.)/g, '$1,'); // Add commas before the decimal point return formattedNumber.replace(/\.?0*$/, ''); // Remove trailing zeros and the dot } function formatWithCommas(number) { return number.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ','); } /* END_INCLUDE: "includes/libs.jsx" */ function MainComponent(props) { const { receipt, network, Link } = props; const hashes = ['output', 'inspect']; const [pageHash, setHash] = useState('output'); const onTab = (index) => { setHash(hashes[index]); }; const [block, setBlock] = useState({} ); const [loading, setLoading] = useState(false); const config = getConfig(network); useEffect(() => { function fetchBlocks() { setLoading(true); if (receipt?.outcome?.blockHash) { asyncFetch(`${config.backendUrl}blocks/${receipt?.outcome.blockHash}`) .then( (res ) => { const resp = res?.body?.blocks?.[0]; if (res.status === 200) { setBlock({ author_account_id: resp.author_account_id, block_hash: resp.author_account_id, block_height: resp.block_height, block_timestamp: resp.block_timestamp, chunks_agg: resp.chunks_agg, gas_price: resp.gas_price, prev_block_hash: resp.author_account_id, receipts_agg: resp.receipts_agg, transactions_agg: resp.transactions_agg, }); setLoading(false); } else { handleRateLimit(res, fetchBlocks, () => setLoading(false)); } }, ) .catch(() => {}); } } fetchBlocks(); }, [receipt?.outcome?.blockHash, config.backendUrl]); let statusInfo; if (receipt?.outcome?.status?.type === 'successValue') { if (receipt?.outcome?.status?.value.length === 0) { statusInfo = ( <div className="bg-gray-100 rounded-md text-3f4246 p-5 font-medium my-3"> Empty result </div> ); } else { const args = receipt?.outcome?.status.value; const decodedArgs = Buffer.from(args, 'base64'); let prettyArgs; try { const parsedJSONArgs = JSON.parse(decodedArgs.toString()); prettyArgs = typeof parsedJSONArgs === 'boolean' ? JSON.stringify(parsedJSONArgs) : parsedJSONArgs; } catch { prettyArgs = Array.from(decodedArgs) .map((byte) => byte.toString(16).padStart(2, '0')) .join(''); } statusInfo = prettyArgs && typeof prettyArgs === 'object' ? ( <textarea readOnly rows={4} defaultValue={JSON.stringify(prettyArgs)} className="block appearance-none outline-none w-full border rounded-lg bg-gray-100 p-3 my-3 resize-y" ></textarea> ) : ( <div> <div className="bg-gray-100 rounded-md p-3 font-semibold my-3"> <div className="bg-inherit text-inherit font-inherit text-base border-none p-0"> <div className="max-h-52 overflow-auto"> <div className="p-4 h-full w-full">{prettyArgs}</div> </div> </div> </div> </div> ); } } else if (receipt?.outcome?.status?.type === 'failure') { statusInfo = ( <textarea readOnly rows={4} defaultValue={JSON.stringify(receipt.outcome.status.error, null, 2)} className="block appearance-none outline-none w-full border rounded-lg bg-gray-100 p-3 my-3 resize-y" ></textarea> ); } else if (receipt?.outcome?.status?.type === 'successReceiptId') { statusInfo = ( <div className="bg-gray-100 rounded-md my-3 p-5 font-medium"> <pre>{receipt?.outcome?.status?.receiptId}</pre> </div> ); } return ( <div className="pb-5 flex flex-col "> <Tabs.Root defaultValue={'output'}> <Tabs.List> {hashes && hashes.map((hash, index) => ( <Tabs.Trigger key={index} onClick={() => onTab(index)} className={`text-nearblue-600 text-sm mx-2.5 font-medium overflow-hidden inline-block cursor-pointer p-2 mb-3 mr-2 focus:outline-none ${ pageHash === hash ? 'rounded-lg bg-green-600 text-white' : 'hover:bg-neargray-800 bg-neargray-700 rounded-lg hover:text-nearblue-600' }`} value={hash} > {hash === 'output' ? <h2>Output</h2> : <h2>Inspect</h2>} </Tabs.Trigger> ))} </Tabs.List> <Tabs.Content value={hashes[0]}> <div className="flex flex-col my-4 mx-7"> <div className="flex justify-between"> <div className="flex flex-col w-full lg:w-1/2"> <div className=""> <h2 className="text-sm font-semibold ">Logs</h2> <div className="bg-gray-100 rounded-md p-5 font-medium my-3 overflow-x-auto "> {receipt?.outcome?.logs?.length === 0 ? ( 'No logs' ) : ( <pre>{receipt?.outcome?.logs.join('\n')}</pre> )} </div> </div> <div> <h2 className="text-sm font-semibold ">Result</h2> {statusInfo} </div> </div> </div> </div> </Tabs.Content> <Tabs.Content value={hashes[1]}> <table className="w-full my-4 mx-7"> <tbody> <tr> <td>Receipt ID</td> <td>{receipt?.id}</td> </tr> <tr> <td>Executed in Block</td> <td> <Link href={`/blocks/${receipt?.outcome?.blockHash}`} className="whitespace-nowrap" >{`#${block?.block_height}`}</Link> </td> </tr> <tr> <td>Predecessor ID</td> <td>{receipt?.predecessorId}</td> </tr> <tr> <td>Receiver ID</td> <td>{receipt?.receiverId}</td> </tr> <tr> <td>Attached Gas</td> <td>{receipt?.id}</td> </tr> <tr> <td>Gas Burned</td> <td> {!loading && receipt?.outcome?.gasBurnt ? convertToMetricPrefix(receipt?.outcome?.gasBurnt) : receipt?.outcome?.gasBurnt ?? ''} gas </td> </tr> <tr> <td>Tokens Burned</td> <td> {!loading && receipt?.outcome?.tokensBurnt ? yoctoToNear(receipt?.outcome?.tokensBurnt, true) : receipt?.outcome?.tokensBurnt ?? ''} Ⓝ </td> </tr> </tbody> </table> </Tabs.Content> </Tabs.Root> </div> ); } return MainComponent(props, context);