diff --git a/.changeset/neat-goats-talk.md b/.changeset/neat-goats-talk.md new file mode 100644 index 000000000..d86cfc067 --- /dev/null +++ b/.changeset/neat-goats-talk.md @@ -0,0 +1,5 @@ +--- +'explorer': minor +--- + +The contracts route now uses explored except for the rates request. diff --git a/apps/explorer/app/contract/[id]/opengraph-image.tsx b/apps/explorer/app/contract/[id]/opengraph-image.tsx index 049c3c107..bb99a55a4 100644 --- a/apps/explorer/app/contract/[id]/opengraph-image.tsx +++ b/apps/explorer/app/contract/[id]/opengraph-image.tsx @@ -1,11 +1,16 @@ -import { humanBytes, humanDate } from '@siafoundation/units' +import { humanBytes } from '@siafoundation/units' import { getOGImage } from '../../../components/OGImageEntity' import { siaCentral } from '../../../config/siaCentral' import { truncate } from '@siafoundation/design-system' -import { lowerCase } from '@technically/lodash' import { siacoinToFiat } from '../../../lib/currency' import { CurrencyOption, currencyOptions } from '@siafoundation/react-core' import { to } from '@siafoundation/request' +import { explored } from '../../../config/explored' +import { blockHeightToHumanDate } from '../../../lib/time' +import { + CONTRACT_STATUS, + determineContractStatus, +} from '../../../lib/contracts' export const revalidate = 0 @@ -22,24 +27,20 @@ const currency = currencyOptions.find((c) => c.id === 'usd') as CurrencyOption export default async function Image({ params }) { const id = params?.id as string - const [[c], [r]] = await Promise.all([ - to( - siaCentral.contract({ - params: { - id, - }, - }) - ), - to( - siaCentral.exchangeRates({ - params: { - currencies: 'sc', - }, - }) - ), - ]) + const [[contract, contractError], [currentTip, currentTipError], [r]] = + await Promise.all([ + to(explored.contractByID({ params: { id } })), + to(explored.consensusTip()), + to( + siaCentral.exchangeRates({ + params: { + currencies: 'sc', + }, + }) + ), + ]) - if (!c || !c.contract) { + if (contractError || !contract || currentTipError || !currentTip) { return getOGImage( { id, @@ -54,19 +55,19 @@ export default async function Image({ params }) { const values = [ { label: 'data size', - value: humanBytes(c.contract.file_size), + value: humanBytes(contract.fileContract.filesize), }, { label: 'expiration', - value: humanDate(c.contract.expiration_timestamp, { - dateStyle: 'short', - timeStyle: 'short', - }), + value: blockHeightToHumanDate( + currentTip.height, + contract.fileContract.windowStart + ), }, { label: 'payout', value: siacoinToFiat( - c.contract.payout, + contract.fileContract.payout, r && { currency, rate: r.rates.sc.usd, @@ -75,18 +76,20 @@ export default async function Image({ params }) { }, ] + const contractStatus = determineContractStatus(contract) + return getOGImage( { id, - title: truncate(c.contract.id, 30), + title: truncate(contract.id, 30), subtitle: 'contract', - status: lowerCase(c.contract.status), + status: contractStatus, statusColor: - c.contract.status === 'obligationSucceeded' + contractStatus === CONTRACT_STATUS.IN_PROGRESS + ? 'amber' + : contractStatus === CONTRACT_STATUS.OBLIGATION_SUCCESSFUL ? 'green' - : c.contract.status === 'obligationFailed' - ? 'red' - : 'amber', + : 'red', initials: 'C', values, }, diff --git a/apps/explorer/app/contract/[id]/page.tsx b/apps/explorer/app/contract/[id]/page.tsx index 6b9a06534..75b5b1750 100644 --- a/apps/explorer/app/contract/[id]/page.tsx +++ b/apps/explorer/app/contract/[id]/page.tsx @@ -1,4 +1,3 @@ -import { SiaCentralContract } from '@siafoundation/sia-central-types' import { ContractView } from '../../../components/ContractView' import { Metadata } from 'next' import { routes } from '../../../config/routes' @@ -8,6 +7,7 @@ import { notFound } from 'next/navigation' import { stripPrefix, truncate } from '@siafoundation/design-system' import { to } from '@siafoundation/request' import { explored } from '../../../config/explored' +import { ChainIndex, ExplorerFileContract } from '@siafoundation/explored-types' export function generateMetadata({ params }): Metadata { const id = decodeURIComponent((params?.id as string) || '') @@ -24,15 +24,15 @@ export function generateMetadata({ params }): Metadata { export const revalidate = 0 export default async function Page({ params }) { - const id = params?.id as string - const [[c, error], [r]] = await Promise.all([ - to( - siaCentral.contract({ - params: { - id, - }, - }) - ), + const id = params?.id + + // Grab the contract and previous revisions data. + const [ + [rate, rateError], + [contract, contractError], + [previousRevisions, previousRevisionsError], + [currentTip, currentTipError], + ] = await Promise.all([ to( siaCentral.exchangeRates({ params: { @@ -40,100 +40,99 @@ export default async function Page({ params }) { }, }) ), + to(explored.contractByID({ params: { id } })), + to(explored.contractRevisions({ params: { id } })), + to(explored.consensusTip()), ]) - if (error) { - throw error - } + if (rateError) throw rateError + if (!rate) throw 'No rate found in successful request' + + if (contractError) throw contractError + if (!contract) return notFound() - const contract = c?.contract + if (previousRevisionsError) throw previousRevisionsError + if (!previousRevisions) + throw 'No previousRevisions found in successful request' - if (!contract) { - return notFound() - } + if (currentTipError) throw currentTipError + if (!currentTip) throw 'No currentTip found in successful request' - const formationTxnId = getFormationTxnId(contract) - const finalRevisionTxnId = contract?.transaction_id || '' + const formationTxnID = + previousRevisions[0].transactionID ?? contract.transactionID + const finalRevisionTxnID = + previousRevisions[previousRevisions.length - 1].transactionID ?? + contract.transactionID - const [[ft], [rt]] = await Promise.all([ + // Fetch our formation and finalRevision transaction information. + const [ + [formationTransaction, formationTransactionError], + [renewalTransaction, renewalTransactionError], + ] = await Promise.all([ to( - siaCentral.transaction({ + explored.transactionByID({ params: { - id: formationTxnId, + id: formationTxnID, }, }) ), to( - siaCentral.transaction({ + explored.transactionByID({ params: { - id: finalRevisionTxnId, + id: finalRevisionTxnID, }, }) ), ]) - const formationTransaction = ft?.transaction - const renewedFrom = formationTransaction?.contract_revisions?.[0] - const renewalTransaction = rt?.transaction - const renewedTo = renewalTransaction?.storage_contracts?.[0] + if (formationTransactionError) throw formationTransactionError + if (!formationTransaction) + throw 'No formation transaction found in successful reqeust' - // The following is a temporary addition to satisfy new Transaction - // component requirements for the Sia Central phase out on the - // transaction route. - const [ - [transaction, transactionError], - [transactionChainIndices, transactionChainIndicesError], - [currentTip, currentTipError], - ] = await Promise.all([ - to( - explored.transactionByID({ - params: { id: formationTransaction?.id || '' }, - }) - ), - to( - explored.transactionChainIndices({ - params: { id: formationTransaction?.id || '' }, - }) - ), - to(explored.consensusTip()), - ]) + if (renewalTransactionError) throw renewalTransactionError + if (!renewalTransaction) + throw 'No renewal transaction found in successful reqeust' - if (transactionError) throw transactionError - if (transactionChainIndicesError) throw transactionChainIndicesError - if (currentTipError) throw currentTipError - if (!transaction || !transactionChainIndices || !currentTip) return notFound() + const renewedFromID = + formationTransaction.fileContractRevisions?.[0].id ?? null + const renewedToID = renewalTransaction.fileContracts?.[0].id ?? null + + const [formationTxnChainIndices, formationTxnChainIndicesError] = await to< + ChainIndex[] + >( + explored.transactionChainIndices({ + params: { id: formationTransaction.id }, + }) + ) + + if (formationTxnChainIndicesError) throw formationTxnChainIndicesError + if (!formationTxnChainIndices) + return 'No formationTxnChainIndices found in successful request' // Use the first chainIndex from the above call to get our parent block. const [parentBlock, parentBlockError] = await to( - explored.blockByID({ params: { id: transactionChainIndices[0].id } }) + explored.blockByID({ params: { id: formationTxnChainIndices[0].id } }) ) if (parentBlockError) throw parentBlockError - if (!parentBlock) return notFound() + if (!parentBlock) return 'No parentBlock foudn in successful request' return ( ) } - -function getFormationTxnId(contract: SiaCentralContract) { - let id = contract?.transaction_id - if (contract?.previous_revisions?.length) { - id = - contract.previous_revisions[contract.previous_revisions?.length - 1] - .transaction_id - } - return id -} diff --git a/apps/explorer/components/Contract/ContractHeader.tsx b/apps/explorer/components/Contract/ContractHeader.tsx index c17bdfbdb..e23577b4e 100644 --- a/apps/explorer/components/Contract/ContractHeader.tsx +++ b/apps/explorer/components/Contract/ContractHeader.tsx @@ -2,46 +2,46 @@ import { Badge, ContractTimeline, LinkButton, + stripPrefix, } from '@siafoundation/design-system' import { ArrowLeft16, ArrowRight16 } from '@siafoundation/react-icons' -import { SiaCentralContract } from '@siafoundation/sia-central-types' -import { lowerCase } from '@technically/lodash' import { routes } from '../../config/routes' import { EntityHeading } from '../EntityHeading' -import { siaCentral } from '../../config/siaCentral' -import { to } from '@siafoundation/request' +import { ChainIndex, ExplorerFileContract } from '@siafoundation/explored-types' +import { determineContractStatus, CONTRACT_STATUS } from '../../lib/contracts' type Props = { - contract: SiaCentralContract - renewedToId?: string - renewedFromId?: string + currentHeight: number + contract: ExplorerFileContract + renewedToID: string | null + renewedFromID: string | null + formationTxnChainIndex: ChainIndex[] } export async function ContractHeader({ + currentHeight, contract, - renewedFromId, - renewedToId, + renewedFromID, + renewedToID, + formationTxnChainIndex, }: Props) { const { id } = contract - const [latest, error] = await to(siaCentral.blockLatest()) - if (error) { - console.error(error.stack) - } + const contractStatus = determineContractStatus(contract) return (
- {renewedFromId && renewedFromId !== id && ( + {renewedFromID && renewedFromID !== stripPrefix(id) && ( renewed from @@ -50,15 +50,19 @@ export async function ContractHeader({ - {lowerCase(contract.status)} + {contractStatus} - {renewedToId && renewedToId !== id && ( + {renewedToID && renewedToID !== stripPrefix(id) && ( renewed to @@ -66,22 +70,29 @@ export async function ContractHeader({ )}
- {latest?.block && ( + {currentHeight && (
{ return [ { label: 'file size', copyable: false, - value: humanBytes(contract.file_size), + value: humanBytes(contract.fileContract.filesize), }, { label: 'payout', copyable: false, - value: siacoinToFiat(contract.payout, exchange), - comment: humanSiacoin(contract.payout), + value: siacoinToFiat(contract.fileContract.payout, exchange), + comment: humanSiacoin(contract.fileContract.payout), }, { label: 'transaction ID', entityType: 'transaction', - entityValue: contract.transaction_id, + entityValue: contract.transactionID, }, { label: 'merkle root', - value: contract.merkle_root, + value: contract.fileContract.fileMerkleRoot, }, { label: 'unlock hash', - value: contract.unlock_hash, + value: contract.fileContract.unlockHash, }, { label: 'proof confirmed', copyable: false, - value: String(contract.proof_confirmed), + value: String(!!contract.proofTransactionID), }, { label: 'negotiation height', copyable: false, - value: contract.negotiation_height?.toLocaleString() || '-', + value: + ( + contract.confirmationIndex?.height || + formationTxnChainIndex[0].height + ).toLocaleString() || '-', }, { label: 'negotiation time', copyable: false, value: - contract.negotiation_timestamp !== '0001-01-01T00:00:00Z' - ? humanDate(contract.negotiation_timestamp, { - dateStyle: 'medium', - timeStyle: 'short', - }) - : '-', + blockHeightToHumanDate( + currentHeight, + contract.confirmationIndex?.height || + formationTxnChainIndex[0].height + ) || '-', }, { label: 'expiration height', copyable: false, - value: contract.expiration_height?.toLocaleString() || '-', + value: contract.fileContract.windowStart.toLocaleString() || '-', }, { label: 'expiration time', copyable: false, value: - contract.expiration_timestamp !== '0001-01-01T00:00:00Z' - ? humanDate(contract.expiration_timestamp, { - dateStyle: 'medium', - timeStyle: 'short', - }) - : '-', + blockHeightToHumanDate( + currentHeight, + contract.fileContract.windowStart + ) || '-', }, { label: 'proof height', copyable: false, - value: contract.proof_height - ? contract.proof_height.toLocaleString() + value: contract.proofIndex + ? contract.proofIndex.height.toLocaleString() : '-', }, { label: 'proof time', copyable: false, - value: - contract.proof_timestamp !== '0001-01-01T00:00:00Z' - ? humanDate(contract.proof_timestamp, { - dateStyle: 'medium', - timeStyle: 'short', - }) - : '-', + value: contract.proofIndex + ? blockHeightToHumanDate(currentHeight, contract.proofIndex.height) + : '-', }, { label: 'proof deadline height', copyable: false, - value: contract.proof_deadline?.toLocaleString() || '-', + value: contract.fileContract.windowEnd.toLocaleString() || '-', }, { label: 'proof deadline time', copyable: false, value: - contract.proof_deadline_timestamp !== '0001-01-01T00:00:00Z' - ? humanDate(contract.proof_deadline_timestamp, { - dateStyle: 'medium', - timeStyle: 'short', - }) - : '-', + blockHeightToHumanDate( + currentHeight, + contract.fileContract.windowEnd + ) || '-', }, { label: 'payout height', copyable: false, - value: contract.payout_height?.toLocaleString() || '-', + value: determinePayoutHeight(contract).toLocaleString() || '-', }, { label: 'payout time', copyable: false, value: - contract.payout_timestamp !== '0001-01-01T00:00:00Z' - ? humanDate(contract.payout_timestamp, { - dateStyle: 'medium', - timeStyle: 'short', - }) - : '-', + blockHeightToHumanDate( + currentHeight, + determinePayoutHeight(contract) + ) || '-', }, { label: 'revision number', - value: contract.revision_number.toLocaleString(), + value: contract.fileContract.revisionNumber.toLocaleString(), }, { label: 'previous revisions', copyable: false, - value: (contract.previous_revisions?.length || 0).toLocaleString(), + value: (previousRevisions && previousRevisions.length > 1 + ? previousRevisions.length - 1 + : 0 + ).toLocaleString(), }, ] as DatumProps[] - }, [contract, exchange]) + }, [ + contract, + exchange, + previousRevisions, + currentHeight, + formationTxnChainIndex, + ]) const validProofOutputs = useMemo(() => { if (isProperlyFormedNewContract(contract)) { @@ -157,14 +173,14 @@ export function Contract({ contract, rates, renewedFrom, renewedTo }: Props) { { label: 'renter payout: remaining renter allowance', initials: 'r', - sc: new BigNumber(renterPayoutValid.value), - hash: renterPayoutValid.output_id, + sc: renterPayoutValid && new BigNumber(renterPayoutValid.value), + hash: renterPayoutValid?.address, }, { label: 'host payout', initials: 'h', - sc: new BigNumber(hostPayoutValid.value), - hash: hostPayoutValid.output_id, + sc: hostPayoutValid && new BigNumber(hostPayoutValid.value), + hash: hostPayoutValid?.address, }, ] as EntityListItemProps[] } @@ -175,18 +191,20 @@ export function Contract({ contract, rates, renewedFrom, renewedTo }: Props) { { label: 'renter payout: remaining renter allowance', initials: 'r', - sc: new BigNumber(renterPayoutValid.value), - hash: renterPayoutValid.output_id, + sc: renterPayoutValid && new BigNumber(renterPayoutValid.value), + hash: renterPayoutValid?.address, }, { label: 'host payout', initials: 'h', - sc: new BigNumber(hostPayoutValid.value), - hash: hostPayoutValid.output_id, + sc: hostPayoutValid && new BigNumber(hostPayoutValid.value), + hash: hostPayoutValid?.address, }, ] as EntityListItemProps[] } - return contract?.valid_proof_outputs?.map(genericOutputListItem) || [] + return ( + contract.fileContract.validProofOutputs?.map(genericOutputListItem) || [] + ) }, [contract]) const missedProofOutputs = useMemo(() => { @@ -197,20 +215,20 @@ export function Contract({ contract, rates, renewedFrom, renewedTo }: Props) { { label: 'renter payout: remaining renter allowance', initials: 'r', - sc: new BigNumber(renterPayoutMissed.value), - hash: renterPayoutMissed.output_id, + sc: renterPayoutMissed && new BigNumber(renterPayoutMissed.value), + hash: renterPayoutMissed?.address, }, { label: `host payout: payout minus risked collateral and storage revenue`, initials: 'h', - sc: new BigNumber(hostPayoutMissed.value), - hash: hostPayoutMissed.output_id, + sc: hostPayoutMissed && new BigNumber(hostPayoutMissed.value), + hash: hostPayoutMissed?.address, }, { label: 'host burn: host revenue plus risked collateral', initials: 'b', - sc: new BigNumber(hostBurned.value), - hash: hostBurned.output_id, + sc: hostBurned && new BigNumber(hostBurned.value), + hash: hostBurned?.address, }, ] as EntityListItemProps[] } @@ -221,18 +239,20 @@ export function Contract({ contract, rates, renewedFrom, renewedTo }: Props) { { label: 'renter payout: remaining renter allowance', initials: 'r', - sc: new BigNumber(renterPayoutMissed.value), - hash: renterPayoutMissed.output_id, + sc: renterPayoutMissed && new BigNumber(renterPayoutMissed.value), + hash: renterPayoutMissed?.address, }, { label: `host payout: payout minus risked collateral and storage revenue`, initials: 'h', - sc: new BigNumber(hostPayoutMissed.value), - hash: hostPayoutMissed.output_id, + sc: hostPayoutMissed && new BigNumber(hostPayoutMissed.value), + hash: hostPayoutMissed?.address, }, ] as EntityListItemProps[] } - return contract?.missed_proof_outputs?.map(genericOutputListItem) || [] + return ( + contract.fileContract.missedProofOutputs?.map(genericOutputListItem) || [] + ) }, [contract]) return ( @@ -240,9 +260,11 @@ export function Contract({ contract, rates, renewedFrom, renewedTo }: Props) { panel={
{!!values?.length && (
@@ -274,13 +296,13 @@ export function Contract({ contract, rates, renewedFrom, renewedTo }: Props) { ) } -function isProperlyFormedNewContract(contract: SiaCentralContract) { +function isProperlyFormedNewContract(contract: ExplorerFileContract) { // renter payout, host payout - if (contract.valid_proof_outputs?.length !== 2) { + if (contract.fileContract.validProofOutputs?.length !== 2) { return false } // renter payout, host payout, and host burned - if (contract.missed_proof_outputs?.length !== 3) { + if (contract.fileContract.missedProofOutputs?.length !== 3) { return false } @@ -288,25 +310,26 @@ function isProperlyFormedNewContract(contract: SiaCentralContract) { getNewContractFormattedOutputs(contract) // renter payout valid and missed should be the same - if (renterPayoutValid.value !== renterPayoutMissed.value) { + if (renterPayoutValid?.value !== renterPayoutMissed?.value) { + // Does we need to catch undefined cases now? return false } // math.MaxUint64 with lost precision const mathMaxUint64 = 18446744073709552000 - if (contract.revision_number >= mathMaxUint64) { + if (contract.fileContract.revisionNumber >= mathMaxUint64) { return false } return true } -function isProperlyFormedRenewedContract(contract: SiaCentralContract) { +function isProperlyFormedRenewedContract(contract: ExplorerFileContract) { // renter payout, host payout - if (contract.valid_proof_outputs?.length !== 2) { + if (contract.fileContract.validProofOutputs?.length !== 2) { return false } // renter payout, host payout - if (contract.missed_proof_outputs?.length !== 2) { + if (contract.fileContract.missedProofOutputs?.length !== 2) { return false } @@ -318,48 +341,55 @@ function isProperlyFormedRenewedContract(contract: SiaCentralContract) { } = getRenewedContractFormattedOutputs(contract) // renter payout valid and missed should be the same - if (renterPayoutValid.value !== renterPayoutMissed.value) { + if (renterPayoutValid?.value !== renterPayoutMissed?.value) { return false } // host payout valid and missed should be the same - if (hostPayoutValid.value !== hostPayoutMissed.value) { + if (hostPayoutValid?.value !== hostPayoutMissed?.value) { return false } // math.MaxUint64 with lost precision const mathMaxUint64 = 18446744073709552000 - if (contract.revision_number !== mathMaxUint64) { + if (contract.fileContract.revisionNumber !== mathMaxUint64) { return false } return true } -function getNewContractFormattedOutputs(contract: SiaCentralContract) { +function getNewContractFormattedOutputs(contract: ExplorerFileContract) { return { - renterPayoutValid: contract.valid_proof_outputs[0], - renterPayoutMissed: contract.missed_proof_outputs[0], - hostPayoutValid: contract.valid_proof_outputs[1], - hostPayoutMissed: contract.missed_proof_outputs[1], - hostBurned: contract.missed_proof_outputs[2], + renterPayoutValid: contract.fileContract.validProofOutputs?.[0], + renterPayoutMissed: contract.fileContract.missedProofOutputs?.[0], + hostPayoutValid: contract.fileContract.validProofOutputs?.[1], + hostPayoutMissed: contract.fileContract.missedProofOutputs?.[1], + hostBurned: contract.fileContract.missedProofOutputs?.[2], } } -function getRenewedContractFormattedOutputs(contract: SiaCentralContract) { +function getRenewedContractFormattedOutputs(contract: ExplorerFileContract) { return { - renterPayoutValid: contract.valid_proof_outputs[0], - renterPayoutMissed: contract.missed_proof_outputs[0], - hostPayoutValid: contract.valid_proof_outputs[1], - hostPayoutMissed: contract.missed_proof_outputs[1], + renterPayoutValid: contract.fileContract.validProofOutputs?.[0], + renterPayoutMissed: contract.fileContract.missedProofOutputs?.[0], + hostPayoutValid: contract.fileContract.validProofOutputs?.[1], + hostPayoutMissed: contract.fileContract.missedProofOutputs?.[1], } } function genericOutputListItem( - o: SiaCentralPartialSiacoinOutput + siacoinOutput: SiacoinOutput ): EntityListItemProps { + const { value, address } = siacoinOutput return { - label: o.source ? o.source.replace(/_/g, ' ') : 'output', - sc: new BigNumber(o.value), - hash: o.output_id, + // label: o.source ? o.source.replace(/_/g, ' ') : 'output', + sc: new BigNumber(value), + hash: address, } } + +// The payout height is either the proofIndex.height + 144, if it exists, +// or the windowEnd + 144. +function determinePayoutHeight(contract: ExplorerFileContract) { + return (contract.proofIndex?.height || contract.fileContract.windowEnd) + 144 +} diff --git a/apps/explorer/components/ContractView/index.tsx b/apps/explorer/components/ContractView/index.tsx index 723656063..04d3ed5c4 100644 --- a/apps/explorer/components/ContractView/index.tsx +++ b/apps/explorer/components/ContractView/index.tsx @@ -1,37 +1,48 @@ import { Container, Separator } from '@siafoundation/design-system' import { Transaction } from '../Transaction' -import { - SiaCentralContract, - SiaCentralExchangeRates, -} from '@siafoundation/sia-central-types' +import { SiaCentralExchangeRates } from '@siafoundation/sia-central-types' import { Contract } from '../Contract' -import { ExplorerTransaction } from '@siafoundation/explored-types' +import { + ChainIndex, + ExplorerFileContract, + ExplorerTransaction, + FileContractID, +} from '@siafoundation/explored-types' import { TransactionHeaderData } from '../Transaction/TransactionHeader' type Props = { - contract: SiaCentralContract + previousRevisions: ExplorerFileContract[] | undefined + currentHeight: number + contract: ExplorerFileContract rates?: SiaCentralExchangeRates - renewedTo?: SiaCentralContract - renewedFrom?: SiaCentralContract + renewedToID: FileContractID | null + renewedFromID: FileContractID | null formationTransaction?: ExplorerTransaction + formationTxnChainIndex: ChainIndex[] formationTransactionHeaderData?: TransactionHeaderData } export function ContractView({ + previousRevisions, + currentHeight, contract, rates, - renewedFrom, - renewedTo, + renewedFromID, + renewedToID, formationTransaction, + formationTxnChainIndex, formationTransactionHeaderData, }: Props) { return ( <> diff --git a/apps/explorer/lib/contracts.ts b/apps/explorer/lib/contracts.ts new file mode 100644 index 000000000..5f5e07859 --- /dev/null +++ b/apps/explorer/lib/contracts.ts @@ -0,0 +1,21 @@ +import { ExplorerFileContract } from '@siafoundation/explored-types' + +export enum CONTRACT_STATUS { + IN_PROGRESS = 'in progress', + OBLIGATION_SUCCESSFUL = 'obligation successful', + OBLIGATION_FAILURE = 'obligation failure', +} +export function determineContractStatus({ + resolved, + valid, + fileContract: { validProofOutputs, missedProofOutputs }, +}: ExplorerFileContract) { + if (!resolved) return CONTRACT_STATUS.IN_PROGRESS + + const successful = + valid || validProofOutputs?.[1].value === missedProofOutputs?.[1].value + + return successful + ? CONTRACT_STATUS.OBLIGATION_SUCCESSFUL + : CONTRACT_STATUS.OBLIGATION_FAILURE +} diff --git a/apps/explorer/lib/time.ts b/apps/explorer/lib/time.ts new file mode 100644 index 000000000..35bd21802 --- /dev/null +++ b/apps/explorer/lib/time.ts @@ -0,0 +1,11 @@ +import { blockHeightToTime, humanDate } from '@siafoundation/units' + +export function blockHeightToHumanDate( + currentHeight: number, + timeInMS: number +) { + return humanDate(blockHeightToTime(currentHeight, timeInMS), { + dateStyle: 'medium', + timeStyle: 'short', + }) +}