Skip to content

Commit

Permalink
Fix date displayed with no Tx (#434)
Browse files Browse the repository at this point in the history
Co-authored-by: Anton Lykhoyda <[email protected]>
  • Loading branch information
Tbaut and Lykhoyda authored Nov 20, 2023
1 parent 39e7763 commit 643cfe3
Show file tree
Hide file tree
Showing 6 changed files with 207 additions and 211 deletions.
7 changes: 3 additions & 4 deletions packages/ui/src/components/CallInfo.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import Expander from './Expander'
import { styled } from '@mui/material/styles'
import { AggregatedData } from './Transactions/TransactionList'
import { AnyJson } from '@polkadot/types/types'
import { ReactNode, useMemo } from 'react'
import { useApi } from '../contexts/ApiContext'
Expand All @@ -12,11 +11,11 @@ import { Link } from './library'
import { usePjsLinks } from '../hooks/usePjsLinks'
import { Alert } from '@mui/material'
import { ApiPromise } from '@polkadot/api'
import { isTypeBalance } from '../utils/isTypeBalance'
import { isTypeAccount } from '../utils/isTypeAccount'
import { isTypeBalance, isTypeAccount } from '../utils'
import { CallDataInfoFromChain } from '../hooks/usePendingTx'

interface Props {
aggregatedData: Omit<AggregatedData, 'from' | 'timestamp'>
aggregatedData: Omit<CallDataInfoFromChain, 'from' | 'timestamp'>
expanded?: boolean
children?: ReactNode
className?: string
Expand Down
4 changes: 2 additions & 2 deletions packages/ui/src/components/Transactions/Transaction.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,16 @@ import { styled } from '@mui/material/styles'
import CallInfo from '../CallInfo'
import { MdOutlineGesture as GestureIcon } from 'react-icons/md'
import { HiOutlineQuestionMarkCircle as QuestionMarkIcon } from 'react-icons/hi2'
import { AggregatedData } from './TransactionList'
import { useCallback, useMemo } from 'react'
import { isProxyCall } from '../../utils'
import { AccountBadge } from '../../types'
import TransactionProgress from './TransactionProgress'
import { useModals } from '../../contexts/ModalsContext'
import { CallDataInfoFromChain } from '../../hooks/usePendingTx'

interface Props {
className?: string
aggregatedData: AggregatedData
aggregatedData: CallDataInfoFromChain
isProposer: boolean
possibleSigners: string[]
multisigSignatories: string[]
Expand Down
194 changes: 12 additions & 182 deletions packages/ui/src/components/Transactions/TransactionList.tsx
Original file line number Diff line number Diff line change
@@ -1,203 +1,29 @@
import { Box, CircularProgress, Paper } from '@mui/material'
import { useEffect, useState } from 'react'
import { styled } from '@mui/material/styles'
import { PendingTx, usePendingTx } from '../../hooks/usePendingTx'
import { usePendingTx } from '../../hooks/usePendingTx'
import { useMultiProxy } from '../../contexts/MultiProxyContext'
import { ApiPromise } from '@polkadot/api'
import { useApi } from '../../contexts/ApiContext'
import { getDifference, getDisplayArgs, getIntersection, isProxyCall } from '../../utils'
import { getDifference, getIntersection } from '../../utils'
import { useAccounts } from '../../contexts/AccountsContext'
import { ISanitizedCall, parseGenericCall } from '../../utils'
import { GenericCall } from '@polkadot/types'
import { AnyJson, AnyTuple } from '@polkadot/types/types'
import { MdOutlineFlare as FlareIcon } from 'react-icons/md'
import Transaction from './Transaction'
import { HexString } from '../../types'
import dayjs from 'dayjs'
import localizedFormat from 'dayjs/plugin/localizedFormat'
dayjs.extend(localizedFormat)

export interface AggregatedData {
callData?: HexString
hash?: string
name?: string
args?: AnyJson
info?: PendingTx['info']
from: string
timestamp: Date | undefined
}

type AggGroupedByDate = { [index: string]: AggregatedData[] }

const sortByLatest = (a: AggregatedData, b: AggregatedData) => {
if (!a.timestamp || !b.timestamp) return 0

return b.timestamp.valueOf() - a.timestamp.valueOf()
}

interface Props {
className?: string
}

export const getMultisigInfo = (c: ISanitizedCall): Partial<AggregatedData>[] => {
const result: Partial<AggregatedData>[] = []

const getCallResult = (c: ISanitizedCall) => {
if (typeof c.method !== 'string' && c.method.pallet === 'multisig') {
if (c.method.method === 'asMulti' && typeof c.args.call?.method !== 'string') {
result.push({
name: `${c.args.call?.method?.pallet}.${c.args.call?.method.method}`,
hash: c.args.call?.hash,
callData: c.args.callData as AggregatedData['callData']
})
} else {
result.push({
name: 'Unknown call',
hash: (c.args?.call_hash as Uint8Array).toString() || undefined,
callData: undefined
})
}
// this is not a multisig call
// try to dig deeper
} else {
if (c.args.calls) {
for (const call of c.args.calls) {
getCallResult(call)
}
} else if (c.args.call) {
getCallResult(c.args.call)
}
}
}

getCallResult(c)
return result
}

const getAgregatedDataPromise = (pendingTxData: PendingTx[], api: ApiPromise) =>
pendingTxData.map(async (pendingTx) => {
const blockHash = await api.rpc.chain.getBlockHash(pendingTx.info.when.height)
const signedBlock = await api.rpc.chain.getBlock(blockHash)

let date: Date | undefined

// get the timestamp which is an unsigned extrinsic set by the validator in each block
// the information for each of the contained extrinsics
signedBlock.block.extrinsics.some(({ method: { args, method, section } }) => {
// check for timestamp.set
if (section === 'timestamp' && method === 'set') {
// extract the Option<Moment> as Moment
const moment = args.toString()

// convert to date (unix ms since epoch in Moment - exactly as per the Rust code)
date = new Date(Number(moment))
return true
}

return false
})

const ext = signedBlock.block.extrinsics[pendingTx.info.when.index]

const decoded = parseGenericCall(ext.method as GenericCall, ext.registry)
// console.log('pendingTxData', pendingTxData)
// console.log('decoded', decoded)
const multisigInfos = getMultisigInfo(decoded) || {}

const info = multisigInfos.find(({ name, hash, callData }) => {
if (!!hash && hash === pendingTx.hash) {
return { name, hash, callData }
}

return false
})

if (!info) {
console.log('oops we didnot find the right extrinsic', multisigInfos, pendingTx.hash)
return
}

const { name, hash, callData } = info

let call: false | GenericCall<AnyTuple> = false
try {
call = !!callData && !!hash && ext.registry.createType('Call', callData)
} catch (error) {
console.error('Error in TransactionList')
console.error(error)
}

return {
callData,
hash: hash || pendingTx.hash,
name,
args: getDisplayArgs(call),
info: pendingTx.info,
from: pendingTx.from,
timestamp: date
} as AggregatedData
})

const TransactionList = ({ className }: Props) => {
const [aggregatedData, setAggregatedData] = useState<AggGroupedByDate>({})
const { selectedMultiProxy, getMultisigByAddress } = useMultiProxy()
const {
data: pendingTxData,
isLoading: isLoadingPendingTxs,
refresh
} = usePendingTx(selectedMultiProxy)
const { api } = useApi()
const { getMultisigByAddress } = useMultiProxy()
const { txWithCallDataByDate, isLoading: isLoadingPendingTxs, refresh } = usePendingTx()
const { ownAddressList } = useAccounts()

useEffect(() => {
if (!api) {
return
}

if (!pendingTxData || !pendingTxData.length) {
setAggregatedData({})
return
}

const agregatedDataPromise = getAgregatedDataPromise(pendingTxData, api)

Promise.all(agregatedDataPromise)
.then((res) => {
const definedTxs = res.filter((agg) => agg !== undefined) as AggregatedData[]
const timestampObj: AggGroupedByDate = {}

// remove the proxy transaction that aren't for the selected proxy
const relevantTxs = definedTxs.filter((agg) => {
if (!isProxyCall(agg.name) || !agg?.args || !(agg.args as any).real.Id) {
return true
}

return (agg.args as any).real.Id === selectedMultiProxy?.proxy
})

// sort by date, the newest first
const sorted = relevantTxs.sort(sortByLatest)

// populate the object
sorted.forEach((data) => {
const date = dayjs(data.timestamp).format('LL')
const previousData = timestampObj[date] || []
timestampObj[date] = [...previousData, data]
})

setAggregatedData(timestampObj)
})
.catch(console.error)
}, [aggregatedData.args, api, pendingTxData, selectedMultiProxy])

return (
<Box className={className}>
{isLoadingPendingTxs && (
<Box className="loader">
<CircularProgress />
</Box>
)}
{Object.entries(aggregatedData).length === 0 && !isLoadingPendingTxs && (
{Object.entries(txWithCallDataByDate).length === 0 && !isLoadingPendingTxs && (
<Paper className="noCall">
<FlareIcon
size={24}
Expand All @@ -206,16 +32,20 @@ const TransactionList = ({ className }: Props) => {
<div className="noCallText">You're all set!</div>
</Paper>
)}
{Object.entries(aggregatedData).length !== 0 &&
Object.entries(aggregatedData).map(([date, aggregatedData]) => {
{Object.entries(txWithCallDataByDate).length !== 0 &&
Object.entries(txWithCallDataByDate).map(([date, aggregatedData]) => {
return (
<TransactionWrapper key={date}>
<DateContainerStyled>{date}</DateContainerStyled>
{aggregatedData.map((agg, index) => {
const { callData, info, from } = agg
const multisig = getMultisigByAddress(from)

if (!info || !multisig?.threshold) return null
// if the "from" is not a multisig from the
// currently selected multiProxy or we have no info
if (!info || !multisig?.threshold) {
return null
}

const multisigSignatories = multisig?.signatories || []
// if the threshold is met, but the transaction is still not executed
Expand Down
4 changes: 2 additions & 2 deletions packages/ui/src/components/modals/ProposalSigning.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import { useAccounts } from '../../contexts/AccountsContext'
import { useApi } from '../../contexts/ApiContext'
import { useMultiProxy } from '../../contexts/MultiProxyContext'
import CallInfo from '../CallInfo'
import { AggregatedData } from '../Transactions/TransactionList'
import SignerSelection from '../select/SignerSelection'
import { SubmittableExtrinsic } from '@polkadot/api/types'
import { useToasts } from '../../contexts/ToastContext'
Expand All @@ -19,12 +18,13 @@ import { ModalCloseButton } from '../library/ModalCloseButton'
import { useGetSortAddress } from '../../hooks/useGetSortAddress'
import { useCheckBalance } from '../../hooks/useCheckBalance'
import BN from 'bn.js'
import { CallDataInfoFromChain } from '../../hooks/usePendingTx'

export interface SigningModalProps {
onClose: () => void
className?: string
possibleSigners: string[]
proposalData: AggregatedData
proposalData: CallDataInfoFromChain
onSuccess?: () => void
}

Expand Down
Loading

0 comments on commit 643cfe3

Please sign in to comment.