Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .claude/settings.local.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@
"Bash(python3:*)",
"Bash(echo \"EXIT: $?\")",
"Bash(echo \"BIOME_EXIT: $?\")",
"Bash(bun tsc:*)"
"Bash(bun tsc:*)",
"Bash(echo \"Exit: $?\")",
"Bash(echo \"Done: $?\")"
]
}
}
18 changes: 18 additions & 0 deletions ui/css/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -579,6 +579,24 @@ button.currency-value.copyable:hover:not(:disabled) {
outline-offset: 2px;
}

.timestamp-value {
font: inherit;
color: inherit;
}

.timestamp-value.loading,
.timestamp-value.unavailable,
.timestamp-value.zero {
opacity: 0.72;
}

.timestamp-value-relative {
color: var(--muted);
font-weight: 400;
font-variant-numeric: tabular-nums;
white-space: nowrap;
}

.address-value.copyable {
border: 0;
padding: 0;
Expand Down
11 changes: 9 additions & 2 deletions ui/ts/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ import { isMainnetChain } from './lib/network.js'
import { createInitialTransactionState, markTransactionFinished, markTransactionRequested, markTransactionSubmitted } from './lib/transactionState.js'
import type { TransactionState } from './lib/transactionState.js'
import { DEPLOY_ROUTE, OPEN_ORACLE_ROUTE, SECURITY_POOLS_ROUTE, ZOLTAR_ROUTE } from './lib/routing.js'
import { formatTimestamp } from './lib/formatters.js'
import { formatUniverseCollectionLabel, formatUniverseLabel } from './lib/universe.js'
import { TransactionHashLink } from './components/TransactionHashLink.js'
import { TimestampValue } from './components/TimestampValue.js'

export function App() {
const transactionState = useSignal<TransactionState>(createInitialTransactionState())
Expand Down Expand Up @@ -125,6 +125,7 @@ export function App() {
openOracleInitialReportState,
openOracleReportDetails,
openOracleResult,
refreshPrice,
setOpenOracleCreateForm,
setOpenOracleForm,
settleReport,
Expand Down Expand Up @@ -330,6 +331,7 @@ export function App() {
onOpenLiquidationModal: (managerAddress, securityPoolAddress, vaultAddress) => openLiquidationModal(managerAddress, securityPoolAddress, vaultAddress),
onQueueLiquidation: (managerAddress, securityPoolAddress) => void queueLiquidation(managerAddress, securityPoolAddress),
loadingPoolOracleManager,
loadingSecurityPools,
onLoadPoolOracleManager: managerAddress => void loadPoolOracleManager(managerAddress),
onRequestPoolPrice: managerAddress => void requestPoolPrice(managerAddress),
onViewPendingReport: reportId => {
Expand Down Expand Up @@ -400,6 +402,7 @@ export function App() {
onCreateOpenOracleGame={() => void createOpenOracleGame()}
onDisputeReport={() => void disputeReport()}
onLoadOracleReport={reportId => void loadOracleReport(reportId)}
onRefreshPrice={refreshPrice}
onOpenOracleCreateFormChange={update => setOpenOracleCreateForm(current => ({ ...current, ...update }))}
onOpenOracleFormChange={update => setOpenOracleForm(current => ({ ...current, ...update }))}
onSettleReport={() => void settleReport()}
Expand Down Expand Up @@ -481,7 +484,11 @@ export function App() {
return (
<main>
<div className='page-notices'>
{showZoltarUniverseForkedWarning && zoltarUniverse !== undefined ? <div className='notice error'>{`${formatUniverseLabel(zoltarUniverse.universeId)} has forked on ${formatTimestamp(zoltarUniverse.forkTime)}.`}</div> : undefined}
{showZoltarUniverseForkedWarning && zoltarUniverse !== undefined ? (
<div className='notice error'>
{formatUniverseLabel(zoltarUniverse.universeId)} has forked on <TimestampValue timestamp={zoltarUniverse.forkTime} />.
</div>
) : undefined}
{showAugurPlaceHolderDeploymentWarning ? <div className='notice error'>Augur PLACEHOLDER contracts are not deployed yet. Deploy them before the application works.</div> : undefined}
{hasInjectedWallet ? undefined : <p className='notice warning'>No injected wallet detected.</p>}
{errorMessage === undefined ? undefined : <p className='notice error'>{errorMessage}</p>}
Expand Down
8 changes: 2 additions & 6 deletions ui/ts/components/AddressInfo.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { Address } from 'viem'
import { AddressValue } from './AddressValue.js'
import { MetricField } from './MetricField.js'

type AddressInfoProps = {
address: Address | undefined
Expand All @@ -8,10 +9,5 @@ type AddressInfoProps = {
}

export function AddressInfo({ address, label, unavailableLabel = 'Unknown' }: AddressInfoProps) {
return (
<div>
<span className='metric-label'>{label}</span>
<strong>{address === undefined ? unavailableLabel : <AddressValue address={address} />}</strong>
</div>
)
return <MetricField label={label}>{address === undefined ? unavailableLabel : <AddressValue address={address} />}</MetricField>
}
29 changes: 8 additions & 21 deletions ui/ts/components/ChildUniverseDetails.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { formatTimestamp } from '../lib/formatters.js'
import { TimestampValue } from './TimestampValue.js'
import { MetricField } from './MetricField.js'
import type { ZoltarChildUniverseSummary } from '../types/contracts.js'

type ChildUniverseDetailsProps = {
Expand All @@ -9,27 +10,13 @@ type ChildUniverseDetailsProps = {
export function ChildUniverseDetails({ child, showOutcomeIndex = false }: ChildUniverseDetailsProps) {
return (
<div className='workflow-vault-grid'>
<div>
<span className='metric-label'>Outcome</span>
<strong>{child.outcomeLabel}</strong>
</div>
{showOutcomeIndex ? (
<div>
<span className='metric-label'>Outcome Index</span>
<strong>{child.outcomeIndex.toString()}</strong>
</div>
) : undefined}
{child.exists ? (
<div>
<span className='metric-label'>Reputation Token</span>
<strong>{child.reputationToken}</strong>
</div>
) : undefined}
<MetricField label='Outcome'>{child.outcomeLabel}</MetricField>
{showOutcomeIndex ? <MetricField label='Outcome Index'>{child.outcomeIndex.toString()}</MetricField> : undefined}
{child.exists ? <MetricField label='Reputation Token'>{child.reputationToken}</MetricField> : undefined}
{child.forkTime !== 0n ? (
<div>
<span className='metric-label'>Fork Time</span>
<strong>{formatTimestamp(child.forkTime)}</strong>
</div>
<MetricField label='Fork Time'>
<TimestampValue timestamp={child.forkTime} />
</MetricField>
) : undefined}
</div>
)
Expand Down
118 changes: 37 additions & 81 deletions ui/ts/components/ForkAuctionSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ import { AddressValue } from './AddressValue.js'
import { CurrencyValue } from './CurrencyValue.js'
import { EnumDropdown } from './EnumDropdown.js'
import { LoadingText } from './LoadingText.js'
import { MetricField } from './MetricField.js'
import { Question } from './Question.js'
import { TransactionHashLink } from './TransactionHashLink.js'
import { UniverseLink } from './UniverseLink.js'
import { formatDuration, formatTimestamp } from '../lib/formatters.js'
import { TimestampValue } from './TimestampValue.js'
import { formatDuration } from '../lib/formatters.js'
import { AUCTION_TIME_SECONDS, estimateRepPurchased, getForkStageDescription, getOutcomeActionLabel, getSystemStateLabel, getTimeRemaining, MIGRATION_TIME_SECONDS } from '../lib/forkAuction.js'
import { isMainnetChain } from '../lib/network.js'
import { getReportingOutcomeLabel, REPORTING_OUTCOME_OPTIONS } from '../lib/reporting.js'
import { REPORTING_OUTCOME_DROPDOWN_OPTIONS, getReportingOutcomeLabel } from '../lib/reporting.js'
import type { ForkAuctionSectionProps } from '../types/components.js'

function getTruthAuctionWindow(details: ForkAuctionSectionProps['forkAuctionDetails']) {
Expand Down Expand Up @@ -112,91 +114,45 @@ export function ForkAuctionSection({
<div className='status-card'>
<p className='panel-label'>Fork Metrics</p>
<div className='escalation-metrics'>
<div>
<span className='metric-label'>REP At Fork</span>
<strong>
<CurrencyValue value={forkAuctionDetails.repAtFork} suffix='REP' />
</strong>
</div>
<div>
<span className='metric-label'>Migrated REP</span>
<strong>
<CurrencyValue value={forkAuctionDetails.migratedRep} suffix='REP' />
</strong>
</div>
<div>
<span className='metric-label'>Collateral</span>
<strong>
<CurrencyValue value={forkAuctionDetails.completeSetCollateralAmount} suffix='REP' />
</strong>
</div>
<div>
<span className='metric-label'>Fork Type</span>
<strong>{forkAuctionDetails.forkOwnSecurityPool ? 'Own escalation fork' : 'Parent/Zoltar fork'}</strong>
</div>
<div>
<span className='metric-label'>Migration Ends</span>
<strong>{forkAuctionDetails.migrationEndsAt === undefined ? 'Started/finished' : formatTimestamp(forkAuctionDetails.migrationEndsAt)}</strong>
</div>
<div>
<span className='metric-label'>Migration Time Left</span>
<strong>{migrationTimeRemaining === undefined ? formatDuration(MIGRATION_TIME_SECONDS) : formatDuration(migrationTimeRemaining)}</strong>
</div>
<MetricField label='REP At Fork'>
<CurrencyValue value={forkAuctionDetails.repAtFork} suffix='REP' />
</MetricField>
<MetricField label='Migrated REP'>
<CurrencyValue value={forkAuctionDetails.migratedRep} suffix='REP' />
</MetricField>
<MetricField label='Collateral'>
<CurrencyValue value={forkAuctionDetails.completeSetCollateralAmount} suffix='REP' />
</MetricField>
<MetricField label='Fork Type'>{forkAuctionDetails.forkOwnSecurityPool ? 'Own escalation fork' : 'Parent/Zoltar fork'}</MetricField>
<MetricField label='Migration Ends'>{forkAuctionDetails.migrationEndsAt === undefined ? 'Started/finished' : <TimestampValue timestamp={forkAuctionDetails.migrationEndsAt} />}</MetricField>
<MetricField label='Migration Time Left'>{migrationTimeRemaining === undefined ? formatDuration(MIGRATION_TIME_SECONDS) : formatDuration(migrationTimeRemaining)}</MetricField>
</div>
</div>

{forkAuctionDetails.truthAuction === undefined ? undefined : (
<div className='status-card'>
<p className='panel-label'>Truth Auction</p>
<div className='escalation-metrics'>
<div>
<span className='metric-label'>Auction Address</span>
<strong>
<AddressValue address={forkAuctionDetails.truthAuctionAddress} />
</strong>
</div>
<div>
<span className='metric-label'>Started</span>
<strong>{formatTimestamp(forkAuctionDetails.truthAuctionStartedAt)}</strong>
</div>
<div>
<span className='metric-label'>Ends</span>
<strong>{auctionWindow === undefined ? 'Not started' : formatTimestamp(auctionWindow.endsAt)}</strong>
</div>
<div>
<span className='metric-label'>Time Left</span>
<strong>{forkAuctionDetails.truthAuction.timeRemaining === undefined ? formatDuration(AUCTION_TIME_SECONDS) : formatDuration(forkAuctionDetails.truthAuction.timeRemaining)}</strong>
</div>
<div>
<span className='metric-label'>ETH Raised / Cap</span>
<strong>
<CurrencyValue value={forkAuctionDetails.truthAuction.ethRaised} suffix='ETH' /> / <CurrencyValue value={forkAuctionDetails.truthAuction.ethRaiseCap} suffix='ETH' />
</strong>
</div>
<div>
<span className='metric-label'>REP Purchased</span>
<strong>
<CurrencyValue value={forkAuctionDetails.truthAuction.totalRepPurchased} suffix='REP' />
</strong>
</div>
<div>
<span className='metric-label'>Clearing Tick</span>
<strong>{forkAuctionDetails.truthAuction.clearingTick?.toString() ?? 'Unavailable'}</strong>
</div>
<div>
<span className='metric-label'>Clearing Price</span>
<strong>
<CurrencyValue value={forkAuctionDetails.truthAuction.clearingPrice} suffix='REP' />
</strong>
</div>
<div>
<span className='metric-label'>Underfunded</span>
<strong>{forkAuctionDetails.truthAuction.underfunded ? 'Yes' : 'No'}</strong>
</div>
<div>
<span className='metric-label'>Finalized</span>
<strong>{forkAuctionDetails.truthAuction.finalized ? 'Yes' : 'No'}</strong>
</div>
<MetricField label='Auction Address'>
<AddressValue address={forkAuctionDetails.truthAuctionAddress} />
</MetricField>
<MetricField label='Started'>
<TimestampValue timestamp={forkAuctionDetails.truthAuctionStartedAt} />
</MetricField>
<MetricField label='Ends'>{auctionWindow === undefined ? 'Not started' : <TimestampValue timestamp={auctionWindow.endsAt} />}</MetricField>
<MetricField label='Time Left'>{forkAuctionDetails.truthAuction.timeRemaining === undefined ? formatDuration(AUCTION_TIME_SECONDS) : formatDuration(forkAuctionDetails.truthAuction.timeRemaining)}</MetricField>
<MetricField label='ETH Raised / Cap'>
<CurrencyValue value={forkAuctionDetails.truthAuction.ethRaised} suffix='ETH' /> / <CurrencyValue value={forkAuctionDetails.truthAuction.ethRaiseCap} suffix='ETH' />
</MetricField>
<MetricField label='REP Purchased'>
<CurrencyValue value={forkAuctionDetails.truthAuction.totalRepPurchased} suffix='REP' />
</MetricField>
<MetricField label='Clearing Tick'>{forkAuctionDetails.truthAuction.clearingTick?.toString() ?? 'Unavailable'}</MetricField>
<MetricField label='Clearing Price'>
<CurrencyValue value={forkAuctionDetails.truthAuction.clearingPrice} suffix='REP' />
</MetricField>
<MetricField label='Underfunded'>{forkAuctionDetails.truthAuction.underfunded ? 'Yes' : 'No'}</MetricField>
<MetricField label='Finalized'>{forkAuctionDetails.truthAuction.finalized ? 'Yes' : 'No'}</MetricField>
</div>
<p className='detail'>At the current clearing price, the entered bid amount would buy roughly {estimatedRep === undefined ? 'Unavailable' : <CurrencyValue value={estimatedRep} suffix='REP' />} ownership if it clears.</p>
</div>
Expand Down Expand Up @@ -238,7 +194,7 @@ export function ForkAuctionSection({

<label className='field'>
<span>Outcome</span>
<EnumDropdown options={REPORTING_OUTCOME_OPTIONS.map(option => ({ value: option.key, label: option.label }))} value={forkAuctionForm.selectedOutcome} onChange={selectedOutcome => onForkAuctionFormChange({ selectedOutcome })} />
<EnumDropdown options={REPORTING_OUTCOME_DROPDOWN_OPTIONS} value={forkAuctionForm.selectedOutcome} onChange={selectedOutcome => onForkAuctionFormChange({ selectedOutcome })} />
</label>

<label className='field'>
Expand Down
22 changes: 9 additions & 13 deletions ui/ts/components/ForkZoltarSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ import type { Address } from 'viem'
import { CurrencyValue } from './CurrencyValue.js'
import { EntityCard } from './EntityCard.js'
import { LoadingText } from './LoadingText.js'
import { MetricField } from './MetricField.js'
import { Question } from './Question.js'
import { sameCaseInsensitiveText } from '../lib/caseInsensitive.js'
import type { MarketDetails, ZoltarUniverseSummary } from '../types/contracts.js'

type ForkZoltarSectionProps = {
Expand Down Expand Up @@ -50,7 +52,7 @@ export function ForkZoltarSection({
const hasEnoughRep = rootUniverse !== undefined && zoltarForkRepBalance !== undefined && zoltarForkRepBalance >= rootUniverse.forkThreshold
const hasEnoughApproval = rootUniverse !== undefined && zoltarForkAllowance !== undefined && zoltarForkAllowance >= rootUniverse.forkThreshold
const selectedQuestionId = zoltarForkQuestionId.trim()
const selectedQuestion = selectedQuestionId === '' ? undefined : zoltarQuestions.find(question => question.questionId.toLowerCase() === selectedQuestionId.toLowerCase())
const selectedQuestion = selectedQuestionId === '' ? undefined : zoltarQuestions.find(question => sameCaseInsensitiveText(question.questionId, selectedQuestionId))
const showMissingQuestionMessage = !loadingZoltarQuestions && selectedQuestionId !== '' && selectedQuestion === undefined
const canFork = accountAddress !== undefined && isMainnet && rootUniverse !== undefined && !hasForked && !zoltarForkPending && selectedQuestion !== undefined && hasEnoughRep && hasEnoughApproval

Expand All @@ -69,18 +71,12 @@ export function ForkZoltarSection({
<>
<EntityCard title='Fork Zoltar' badge={hasForked ? <span className='badge blocked'>Forked</span> : undefined}>
<div className='workflow-metric-grid'>
<div>
<span className='metric-label'>Fork Threshold</span>
<strong>
<CurrencyValue loading={loadingZoltarForkAccess || rootUniverse === undefined} value={rootUniverse?.forkThreshold} suffix='REP' />
</strong>
</div>
<div>
<span className='metric-label'>REP Approved To Zoltar</span>
<strong>
<CurrencyValue loading={loadingZoltarForkAccess} value={zoltarForkAllowance} suffix='REP' />
</strong>
</div>
<MetricField label='Fork Threshold'>
<CurrencyValue loading={loadingZoltarForkAccess || rootUniverse === undefined} value={rootUniverse?.forkThreshold} suffix='REP' />
</MetricField>
<MetricField label='REP Approved To Zoltar'>
<CurrencyValue loading={loadingZoltarForkAccess} value={zoltarForkAllowance} suffix='REP' />
</MetricField>
</div>

<div className='form-grid'>
Expand Down
14 changes: 6 additions & 8 deletions ui/ts/components/MarketCreateQuestionSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ import { EnumDropdown, type EnumDropdownOption } from './EnumDropdown.js'
import { EntityCard } from './EntityCard.js'
import { FormInput } from './FormInput.js'
import { LoadingText } from './LoadingText.js'
import { Question } from './Question.js'
import { Question, getQuestionTitle } from './Question.js'
import { TransactionHashLink } from './TransactionHashLink.js'
import { MetricField } from './MetricField.js'
import { validateMarketForm } from '../lib/marketCreation.js'
import { clampScalarTickIndex, parseScalarFormInputs } from '../lib/scalarOutcome.js'
import type { MarketFormState } from '../types/app.js'
Expand Down Expand Up @@ -52,7 +53,7 @@ export function MarketCreateQuestionSection({ accountAddress, hasForked, isMainn
const selectedQuestionDetails = useMemo(() => (marketResult === undefined ? undefined : zoltarQuestions.find(question => question.questionId === marketResult.questionId)), [marketResult?.questionId, zoltarQuestions])
const scalarCreatePreviewDetails = getScalarCreatePreviewDetails(marketForm)
const marketFormValidation = validateMarketForm(marketForm)
const selectedQuestionTitle = selectedQuestionDetails === undefined ? 'Question' : typeof selectedQuestionDetails.title !== 'string' || selectedQuestionDetails.title.trim() === '' ? 'Untitled question' : selectedQuestionDetails.title
const selectedQuestionTitle = selectedQuestionDetails === undefined ? 'Question' : getQuestionTitle(selectedQuestionDetails)

useEffect(() => {
if (scalarCreatePreviewDetails === undefined) return
Expand Down Expand Up @@ -109,12 +110,9 @@ export function MarketCreateQuestionSection({ accountAddress, hasForked, isMainn
>
<div className='question-preview-body'>
{selectedQuestionDetails === undefined ? <p className='detail'>Question details are not loaded yet.</p> : <Question question={selectedQuestionDetails} showTitle={false} />}
<div>
<span className='metric-label'>Creation transaction hash</span>
<strong>
<TransactionHashLink hash={marketResult.createQuestionHash} />
</strong>
</div>
<MetricField label='Creation transaction hash'>
<TransactionHashLink hash={marketResult.createQuestionHash} />
</MetricField>
</div>
</EntityCard>
)}
Expand Down
Loading