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
1,117 changes: 165 additions & 952 deletions ui/ts/components/ForkAuctionSection.tsx

Large diffs are not rendered by default.

83 changes: 83 additions & 0 deletions ui/ts/components/ImportedForkSettlementSection.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import type { ComponentChildren } from 'preact'
import { CurrencyValue } from './CurrencyValue.js'
import { SectionBlock } from './SectionBlock.js'
import { getImportedEscalationDepositClaimAmount } from '../lib/reportingDomain.js'
import type { ActiveReportingDetails, EscalationSide, ReportingOutcomeKey } from '../types/contracts.js'

type ImportedForkSettlementActionRenderProps = {
guardMessage: string | undefined
outcome: ReportingOutcomeKey
sideLabel: string
}

type ImportedForkSettlementSectionProps = {
activeReportingDetails: ActiveReportingDetails | undefined
disabled: boolean
onDepositSelectionChange: (outcome: ReportingOutcomeKey, depositIndex: bigint, checked: boolean) => void
renderSettlementAction: (props: ImportedForkSettlementActionRenderProps) => ComponentChildren
resolved: boolean
selectedDepositIndexesByOutcome: Record<ReportingOutcomeKey, bigint[]>
sides: Pick<EscalationSide, 'importedUserDeposits' | 'key' | 'label'>[]
}

export function ImportedForkSettlementSection({ activeReportingDetails, disabled, onDepositSelectionChange, renderSettlementAction, resolved, selectedDepositIndexesByOutcome, sides }: ImportedForkSettlementSectionProps) {
if (sides.length === 0) return undefined

return (
<SectionBlock density='compact' title='Settle Fork-Carried Escalation Deposits'>
<p className='detail'>Imported from the parent universe. Settle these positions in this child pool after finalization.</p>
{resolved ? undefined : <p className='detail'>Fork-carried escalation deposits can be settled after this child pool finalizes.</p>}
{sides.map(side => {
const selectedDepositIndexes = selectedDepositIndexesByOutcome[side.key]
const settlementGuardMessage = (() => {
if (!resolved) return 'Fork-carried escalation deposits can be settled after this child pool finalizes.'
if (selectedDepositIndexes.length === 0) return `Select at least one ${side.label.toLowerCase()} fork-carried deposit to settle.`
return undefined
})()
return (
<SectionBlock density='compact' headingLevel={4} key={side.key} title={side.label} variant='embedded'>
<div className='field'>
<span>Imported from parent universe</span>
<div className='escalation-selection-list'>
{side.importedUserDeposits.map(deposit => {
const selected = selectedDepositIndexes.includes(deposit.parentDepositIndex)
const claimAmount = getImportedEscalationDepositClaimAmount(activeReportingDetails, side.key, deposit)
return (
<label className='escalation-selection-item' key={deposit.parentDepositIndex.toString()}>
<input checked={selected} disabled={disabled} onChange={event => onDepositSelectionChange(side.key, deposit.parentDepositIndex, event.currentTarget.checked)} type='checkbox' />
<div className='escalation-selection-item-copy'>
<strong>Parent deposit #{deposit.parentDepositIndex.toString()}</strong>
<span>
Initially deposited: <CurrencyValue value={deposit.amount} suffix='REP' />
</span>
<span>
{claimAmount === undefined ? (
'Worth now: Pending final settlement'
) : (
<>
Worth now: <CurrencyValue value={claimAmount} suffix='REP' />
</>
)}
</span>
<span>
Imported ordering start: <CurrencyValue value={deposit.cumulativeAmount} suffix='REP' />
</span>
</div>
</label>
)
})}
</div>
</div>
<div className='actions'>
{renderSettlementAction({
guardMessage: settlementGuardMessage,
outcome: side.key,
sideLabel: side.label,
})}
</div>
</SectionBlock>
)
})}
</SectionBlock>
)
}
126 changes: 126 additions & 0 deletions ui/ts/components/TruthAuctionBidsSection.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import type { ComponentChildren } from 'preact'
import type { Address } from 'viem'
import { AddressValue } from './AddressValue.js'
import { CurrencyValue } from './CurrencyValue.js'
import { MetricField } from './MetricField.js'
import { PaginationControls } from './PaginationControls.js'
import { SectionBlock } from './SectionBlock.js'
import type { TruthAuctionBidRowViewModel, ViewerTruthAuctionBidRowViewModel } from '../lib/truthAuctionBidViewModels.js'

type TruthAuctionBidsSectionProps = {
aggregatedAuctionBidCountForLoadedTicks: bigint
hasMoreAggregatedAuctionBids: boolean
loadedTickCount: number
loadingAggregatedAuctionBids: boolean
onLoadNextAuctionBidPage: () => void
renderPriceValue: (value: bigint | undefined) => ComponentChildren
rows: TruthAuctionBidRowViewModel[]
}

type ViewerTruthAuctionBidsSectionProps = {
accountAddress: Address | undefined
hasMoreViewerBids: boolean
loadingTruthAuctionBook: boolean
onLoadNextViewerBidPage: () => void
onSettlementBidSelectionChange: (bidKey: string, checked: boolean) => void
renderPriceValue: (value: bigint | undefined) => ComponentChildren
rows: ViewerTruthAuctionBidRowViewModel[]
showSettlementActionColumn: boolean
}

function AuctionBidsHeader() {
return (
<div className='truth-auction-bid-row is-wide is-no-actions is-header'>
<span className='truth-auction-bid-row-label'>Price (ETH / REP)</span>
<span>Bidder</span>
<span>Bid Amount (ETH)</span>
<span>Loaded Depth (ETH)</span>
<span className='truth-auction-bid-row-status'>Status</span>
</div>
)
}

function ViewerBidsHeader({ showActions }: { showActions: boolean }) {
return (
<div className={`truth-auction-bid-row is-wallet ${showActions ? '' : 'is-no-actions'} is-header`}>
{showActions ? <span>Selected</span> : undefined}
<span className='truth-auction-bid-row-label'>Price (ETH / REP)</span>
<span>Bid Amount (ETH)</span>
<span className='truth-auction-bid-row-status'>Status</span>
</div>
)
}

export function TruthAuctionBidsSection({ aggregatedAuctionBidCountForLoadedTicks, hasMoreAggregatedAuctionBids, loadedTickCount, loadingAggregatedAuctionBids, onLoadNextAuctionBidPage, renderPriceValue, rows }: TruthAuctionBidsSectionProps) {
return (
<SectionBlock title='Truth Auction Bids'>
<div className='truth-auction-bid-coverage-summary'>
<MetricField label='Loaded Levels'>{loadedTickCount.toString()}</MetricField>
<MetricField label='Loaded Bids'>{rows.length.toString()}</MetricField>
<MetricField label='Coverage'>{`Showing ${rows.length.toString()} of ${aggregatedAuctionBidCountForLoadedTicks.toString()} bids across loaded levels`}</MetricField>
</div>
{loadingAggregatedAuctionBids ? <p className='detail'>Loading auction bids…</p> : undefined}
{!loadingAggregatedAuctionBids && loadedTickCount === 0 ? <p className='detail'>No active prices are currently visible for this auction.</p> : undefined}
{!loadingAggregatedAuctionBids && loadedTickCount > 0 && rows.length === 0 ? <p className='detail'>No bids are currently indexed for the loaded prices.</p> : undefined}
{rows.length === 0 ? undefined : (
<div className='truth-auction-bid-table'>
<AuctionBidsHeader />
{rows.map(row => (
<div className='truth-auction-bid-row is-wide is-no-actions' key={row.key}>
<span className='truth-auction-bid-row-label'>{renderPriceValue(row.price)}</span>
<div className='truth-auction-bid-row-address'>
<AddressValue address={row.bidder} />
</div>
<span>
<CurrencyValue value={row.ethAmount} suffix='ETH' />
</span>
<span>
<CurrencyValue value={row.cumulativeEth} suffix='ETH' />
</span>
<span className='truth-auction-bid-row-status'>
<span className={`truth-auction-status-pill ${row.statusToneClassName}`}>{row.statusLabel}</span>
</span>
</div>
))}
</div>
)}
{hasMoreAggregatedAuctionBids ? <PaginationControls hasNextPage={hasMoreAggregatedAuctionBids} onLoadMore={onLoadNextAuctionBidPage} loadMoreLabel='Load More Truth Auction Bids' /> : undefined}
</SectionBlock>
)
}

export function ViewerTruthAuctionBidsSection({ accountAddress, hasMoreViewerBids, loadingTruthAuctionBook, onLoadNextViewerBidPage, onSettlementBidSelectionChange, renderPriceValue, rows, showSettlementActionColumn }: ViewerTruthAuctionBidsSectionProps) {
return (
<SectionBlock title='My Bids'>
{accountAddress === undefined ? <p className='detail'>Connect a wallet to inspect your submitted truth auction bids.</p> : undefined}
{accountAddress !== undefined && loadingTruthAuctionBook ? <p className='detail'>Loading your bids…</p> : undefined}
{accountAddress !== undefined && !loadingTruthAuctionBook && rows.length === 0 ? <p className='detail'>No bids from this wallet are indexed for the current auction.</p> : undefined}
{rows.length === 0 ? undefined : (
<div className='truth-auction-bid-table'>
<ViewerBidsHeader showActions={showSettlementActionColumn} />
{rows.map(row => (
<div className={`truth-auction-bid-row is-wallet ${showSettlementActionColumn ? '' : 'is-no-actions'}`} key={row.key}>
{showSettlementActionColumn ? (
<div className='truth-auction-bid-row-actions'>
{(() => {
const settlementControl = row.settlementControl
if (settlementControl === undefined) return undefined
return <input disabled={settlementControl.disabled} type='checkbox' checked={settlementControl.checked} title={settlementControl.title} aria-label={settlementControl.ariaLabel} onChange={event => onSettlementBidSelectionChange(settlementControl.bidKey, event.currentTarget.checked)} />
})()}
</div>
) : undefined}
<span className='truth-auction-bid-row-label'>{renderPriceValue(row.price)}</span>
<span>
<CurrencyValue value={row.ethAmount} suffix='ETH' />
</span>
<span className='truth-auction-bid-row-status'>
<span className={`truth-auction-status-pill ${row.statusToneClassName}`}>{row.statusLabel}</span>
</span>
</div>
))}
</div>
)}
{accountAddress !== undefined && hasMoreViewerBids ? <PaginationControls hasNextPage={hasMoreViewerBids} onLoadMore={onLoadNextViewerBidPage} loadMoreLabel='Load More Of My Bids' /> : undefined}
</SectionBlock>
)
}
16 changes: 1 addition & 15 deletions ui/ts/components/TruthAuctionDepthChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,7 @@ import { useRef } from 'preact/hooks'
import { CurrencyValue } from './CurrencyValue.js'
import { formatCurrencyInputBalance, formatRoundedCurrencyBalance } from '../lib/formatters.js'
import { getVisualRatio } from '../lib/visualMetrics.js'

type TruthAuctionDisposition = {
label: string
tone: 'default' | 'danger' | 'success' | 'warning'
}

export type TruthAuctionDepthPoint = {
tick: bigint
price: bigint
currentTotalEth: bigint
cumulativeEth: bigint
disposition: TruthAuctionDisposition
isSelected: boolean
isPreviewTick: boolean
}
import type { TruthAuctionDepthPoint } from '../lib/truthAuctionBook.js'

type TruthAuctionDepthChartProps = {
clearingTick?: bigint
Expand Down
91 changes: 91 additions & 0 deletions ui/ts/components/TruthAuctionMarketViewSection.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import type { ComponentChildren } from 'preact'
import { CurrencyValue } from './CurrencyValue.js'
import { PaginationControls } from './PaginationControls.js'
import { SectionBlock } from './SectionBlock.js'
import { TruthAuctionDepthChart } from './TruthAuctionDepthChart.js'
import { getTruthAuctionDispositionClassName, type TruthAuctionDepthPoint } from '../lib/truthAuctionBook.js'
import { getVisualRatio } from '../lib/visualMetrics.js'

type TruthAuctionMarketViewSectionProps = {
clearingTick: bigint | undefined
hasMoreTickSummaries: boolean
loadingTruthAuctionBook: boolean
maxTickEth: bigint
onLoadNextTickPage: () => void
onSelectTick: (tick: bigint) => void
renderPriceValue: (value: bigint | undefined) => ComponentChildren
showDepthClearingTick: boolean
truthAuctionBookError: string | undefined
truthAuctionDepthPoints: TruthAuctionDepthPoint[]
}

function clampPercentage(value: bigint, maxValue: bigint) {
return (getVisualRatio({ value, maxValue }) ?? 0) * 100
}

export function TruthAuctionMarketViewSection({ clearingTick, hasMoreTickSummaries, loadingTruthAuctionBook, maxTickEth, onLoadNextTickPage, onSelectTick, renderPriceValue, showDepthClearingTick, truthAuctionBookError, truthAuctionDepthPoints }: TruthAuctionMarketViewSectionProps) {
return (
<SectionBlock title='Market View'>
{truthAuctionBookError === undefined ? undefined : <p className='detail truth-auction-book-error'>{truthAuctionBookError}</p>}
<div className='truth-auction-market-board'>
<div className='truth-auction-market-section truth-auction-depth-panel'>
<div className='truth-auction-depth-header'>
<div>
<h4>Visible Depth</h4>
</div>
</div>
{loadingTruthAuctionBook ? <p className='detail'>Loading order book…</p> : undefined}
{!loadingTruthAuctionBook && truthAuctionDepthPoints.length === 0 ? <p className='detail'>No live price levels are currently active for this auction.</p> : undefined}
{truthAuctionDepthPoints.length === 0 ? undefined : <TruthAuctionDepthChart onSelectTick={onSelectTick} points={truthAuctionDepthPoints} {...(showDepthClearingTick && clearingTick !== undefined ? { clearingTick } : {})} />}
</div>
<div className='truth-auction-market-detail-grid'>
<div className='truth-auction-market-section'>
<div className='truth-auction-panel-header'>
<div>
<h4>Price Ladder</h4>
</div>
</div>
<div className='truth-auction-ladder'>
{loadingTruthAuctionBook ? <p className='detail'>Loading price levels…</p> : undefined}
{!loadingTruthAuctionBook && truthAuctionDepthPoints.length === 0 ? <p className='detail'>No active levels are visible yet.</p> : undefined}
{truthAuctionDepthPoints.map(point => (
<button
aria-pressed={point.isSelected}
className={`truth-auction-price-row truth-auction-ladder-row ${getTruthAuctionDispositionClassName(point.disposition.tone)}${point.isSelected ? ' is-selected' : ''}${point.isPreviewTick ? ' is-preview' : ''}${clearingTick === point.tick ? ' is-clearing' : ''}`}
key={point.tick.toString()}
onClick={() => onSelectTick(point.tick)}
type='button'
>
<div className='truth-auction-price-row-bar' style={{ width: `${clampPercentage(point.currentTotalEth, maxTickEth)}%` }} />
<div className='truth-auction-price-row-copy'>
<div className='truth-auction-price-row-main'>
<div>
<strong>{renderPriceValue(point.price)}</strong>
<span className='truth-auction-price-row-price'>Price level</span>
</div>
<div className='truth-auction-price-row-badges'>
{clearingTick === point.tick ? <span className='truth-auction-ladder-helper'>Clearing level</span> : undefined}
{point.isPreviewTick ? <span className='truth-auction-ladder-helper'>Current form price</span> : undefined}
<span className={`truth-auction-status-pill ${getTruthAuctionDispositionClassName(point.disposition.tone)}`}>{point.disposition.label}</span>
</div>
</div>
<div className='truth-auction-price-row-meta'>
<span>
Current size <CurrencyValue value={point.currentTotalEth} suffix='ETH' />
</span>
<span className='truth-auction-ladder-row-cumulative'>
Loaded depth <CurrencyValue value={point.cumulativeEth} suffix='ETH' />
</span>
<span>{point.submissionCount.toString()} submissions</span>
</div>
</div>
</button>
))}
{hasMoreTickSummaries ? <PaginationControls hasNextPage={hasMoreTickSummaries} onLoadMore={onLoadNextTickPage} loadMoreLabel='Load More Price Levels' /> : undefined}
</div>
</div>
</div>
</div>
</SectionBlock>
)
}
59 changes: 59 additions & 0 deletions ui/ts/components/TruthAuctionSummaryCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import type { ComponentChildren } from 'preact'
import { CurrencyValue } from './CurrencyValue.js'
import { MetricField } from './MetricField.js'
import { SectionBlock } from './SectionBlock.js'

type TruthAuctionSummaryCardProps = {
badge: ComponentChildren
clearingPriceDisplay: ComponentChildren
displayedEthRaised: bigint
displayedRepSold: bigint
endsDisplay: ComponentChildren
ethRaiseCap: bigint
ethRaisedProgress: number
maxRepBeingSold: bigint
minBidSize: bigint
repSoldProgress: number
startedDisplay: ComponentChildren
winningThresholdPriceDisplay?: ComponentChildren | undefined
}

export function TruthAuctionSummaryCard({ badge, clearingPriceDisplay, displayedEthRaised, displayedRepSold, endsDisplay, ethRaiseCap, ethRaisedProgress, maxRepBeingSold, minBidSize, repSoldProgress, startedDisplay, winningThresholdPriceDisplay }: TruthAuctionSummaryCardProps) {
return (
<SectionBlock badge={badge} className='fork-workflow-summary-card truth-auction-summary-card' title='Truth Auction'>
<div className='fork-workflow-summary'>
<div className='fork-workflow-summary-primary truth-auction-summary-primary'>
<div className='fork-workflow-summary-stat-group truth-auction-progress-group'>
<div className='fork-workflow-summary-stat-copy truth-auction-progress-copy'>
<span>ETH Raised</span>
<strong>
<CurrencyValue value={displayedEthRaised} suffix='ETH' /> / <CurrencyValue value={ethRaiseCap} suffix='ETH' />
</strong>
</div>
<div className='truth-auction-progress-track'>
<div className='truth-auction-progress-fill is-eth' style={{ width: `${ethRaisedProgress}%` }} />
</div>
</div>
<div className='fork-workflow-summary-stat-group truth-auction-progress-group'>
<div className='fork-workflow-summary-stat-copy truth-auction-progress-copy'>
<span>REP Sold</span>
<strong>
<CurrencyValue value={displayedRepSold} suffix='REP' /> / <CurrencyValue value={maxRepBeingSold} suffix='REP' />
</strong>
</div>
<div className='truth-auction-progress-track'>
<div className='truth-auction-progress-fill is-rep' style={{ width: `${repSoldProgress}%` }} />
</div>
</div>
</div>
<div className='fork-workflow-summary-metrics'>
<MetricField label='Starts'>{startedDisplay}</MetricField>
<MetricField label='Clearing Price'>{clearingPriceDisplay}</MetricField>
<MetricField label='Min Bid'>{<CurrencyValue value={minBidSize} suffix='ETH' />}</MetricField>
<MetricField label='Ends'>{endsDisplay}</MetricField>
{winningThresholdPriceDisplay === undefined ? undefined : <MetricField label='Winning Threshold'>{winningThresholdPriceDisplay}</MetricField>}
</div>
</div>
</SectionBlock>
)
}
2 changes: 1 addition & 1 deletion ui/ts/hooks/useForkAuctionOperations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import {
} from '../contracts.js'
import { createConnectedReadClient, createWalletWriteClient } from '../lib/clients.js'
import { getErrorMessage } from '../lib/errors.js'
import { getTruthAuctionBidGuardMessage, getTruthAuctionTickAtPrice } from '../lib/forkAuction.js'
import { getTruthAuctionBidGuardMessage, getTruthAuctionTickAtPrice } from '../lib/truthAuctionBook.js'
import { getReportingOutcomeKey, parseAddressInput, parseBigIntListInput, parseReportingOutcomeInput, parseReportingOutcomeListInput, resolveOptionalAddressInput } from '../lib/inputs.js'
import { sameAddress } from '../lib/address.js'
import { createErrorActionFeedback, createPendingActionFeedback, createSuccessActionFeedback, createWarningActionFeedback } from '../lib/actionFeedback.js'
Expand Down
Loading
Loading