Skip to content

Commit

Permalink
refactor logic into hook for checking hold up time (solana-labs#1096)
Browse files Browse the repository at this point in the history
  • Loading branch information
sayantank authored Oct 18, 2022
1 parent e3301bb commit 201e71e
Show file tree
Hide file tree
Showing 2 changed files with 127 additions and 45 deletions.
62 changes: 17 additions & 45 deletions components/ProposalExecutionCard.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { ProgramAccount, ProposalTransaction } from '@solana/spl-governance'
import classNames from 'classnames'
import { useEffect, useState, useRef } from 'react'
import dayjs from 'dayjs'
Expand All @@ -12,43 +11,7 @@ import useProposal from '@hooks/useProposal'
import { ntext } from '@utils/ntext'
import Button from '@components/Button'
import { diffTime } from '@components/ProposalRemainingVotingTime'

function parseTransactions(
transactions: ProgramAccount<ProposalTransaction>[]
) {
const executed: ProgramAccount<ProposalTransaction>[] = []
const ready: ProgramAccount<ProposalTransaction>[] = []
const notReady: ProgramAccount<ProposalTransaction>[] = []
let minHoldUpTime: number | null = null

for (const transaction of transactions) {
const holdUpTime = transaction.account.holdUpTime

if (transaction.account.executedAt) {
executed.push(transaction)
} else if (!holdUpTime || holdUpTime <= 0) {
ready.push(transaction)
} else {
notReady.push(transaction)

if (holdUpTime) {
if (minHoldUpTime === null || holdUpTime < minHoldUpTime) {
minHoldUpTime = holdUpTime
}
}
}
}

// Order instructions by instruction index
return {
executed,
ready: ready.sort(
(a, b) => a.account.instructionIndex - b.account.instructionIndex
),
notReady,
minHoldUpTime,
}
}
import useProposalTransactions from '@hooks/useProposalTransactions'

interface Props {
className?: string
Expand All @@ -63,29 +26,38 @@ export default function ProposalExecutionCard(props: Props) {
const timer = useRef<undefined | number>()

const allTransactions = Object.values(instructions)
const { executed, ready, notReady, minHoldUpTime } = parseTransactions(
allTransactions

const proposalTransactions = useProposalTransactions(
allTransactions,
proposal
)

useEffect(() => {
if (typeof window !== 'undefined' && minHoldUpTime) {
if (
typeof window !== 'undefined' &&
proposalTransactions &&
proposalTransactions.nextExecuteAt
) {
timer.current = window.setInterval(() => {
const end = dayjs(1000 * (dayjs().unix() + minHoldUpTime))
const end = dayjs(1000 * proposalTransactions.nextExecuteAt!)
setTimeLeft(diffTime(false, dayjs(), end))
}, 1000)
}

return () => clearInterval(timer.current)
}, [minHoldUpTime])
}, [proposalTransactions?.nextExecuteAt])

if (
allTransactions.length === 0 ||
!proposal ||
allTransactions.length === executed.length
!proposalTransactions ||
allTransactions.length === proposalTransactions.executed.length
) {
return null
}

const { ready, notReady, executed, nextExecuteAt } = proposalTransactions

return (
<div
className={classNames(
Expand All @@ -98,7 +70,7 @@ export default function ProposalExecutionCard(props: Props) {
>
<div className="flex items-center flex-col">
<h3 className="mb-0">
{minHoldUpTime !== null
{nextExecuteAt !== null
? 'Execution Hold Up Time'
: 'Execute Proposal'}
</h3>
Expand Down
110 changes: 110 additions & 0 deletions hooks/useProposalTransactions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import {
ProgramAccount,
Proposal,
ProposalTransaction,
} from '@solana/spl-governance'
import { useEffect, useState } from 'react'

function parseTransactions(
transactions: ProgramAccount<ProposalTransaction>[],
proposal: ProgramAccount<Proposal>
) {
const executed: ProgramAccount<ProposalTransaction>[] = []
const ready: ProgramAccount<ProposalTransaction>[] = []
const notReady: ProgramAccount<ProposalTransaction>[] = []

let nextExecuteAt: number | null = null

for (const transaction of transactions) {
const holdUpTime = transaction.account.holdUpTime

// already executed
if (transaction.account.executedAt) {
executed.push(transaction)
}
// doesn't have a hold up time
else if (!holdUpTime || holdUpTime <= 0) {
ready.push(transaction)
}
// has a hold up time, so check if it's ready
else {
const votingCompletedAt = proposal.account.votingCompletedAt
if (votingCompletedAt) {
const canExecuteAt = votingCompletedAt.toNumber() + holdUpTime
const now = new Date().getTime() / 1000 // unix timestamp in seconds

// ready to execute
if (now > canExecuteAt) {
ready.push(transaction)
}
// not ready to execute
else {
notReady.push(transaction)
// find the soonest transaction to execute
if (!nextExecuteAt || canExecuteAt < nextExecuteAt)
nextExecuteAt = canExecuteAt
}
}
}
}

return {
executed,
// Order instructions by instruction index
ready: ready.sort(
(a, b) => a.account.instructionIndex - b.account.instructionIndex
),
notReady,
nextExecuteAt,
}
}

export default function useProposalTransactions(
allTransactions: ProgramAccount<ProposalTransaction>[],
proposal?: ProgramAccount<Proposal>
) {
if (!proposal) return null

const [executed, setExecuted] = useState<
ProgramAccount<ProposalTransaction>[]
>([])
const [ready, setReady] = useState<ProgramAccount<ProposalTransaction>[]>([])
const [notReady, setNotReady] = useState<
ProgramAccount<ProposalTransaction>[]
>([])

const [nextExecuteAt, setNextExecuteAt] = useState<number | null>(null)

useEffect(() => {
let interval: NodeJS.Timeout | null = null

if (allTransactions.length !== executed.length) {
interval = setInterval(() => {
const { executed, ready, notReady, nextExecuteAt } = parseTransactions(
allTransactions,
proposal
)
setExecuted(executed)
setReady(ready)
setNotReady(notReady)
setNextExecuteAt(nextExecuteAt)
}, 1000)
} else {
if (interval) {
clearInterval(interval)
interval = null
}
}

return () => {
if (interval) clearInterval(interval)
}
}, [executed])

return {
executed,
ready,
notReady,
nextExecuteAt,
}
}

0 comments on commit 201e71e

Please sign in to comment.