Skip to content

Commit

Permalink
feat: account live transactions (#53)
Browse files Browse the repository at this point in the history
* feat: add account live transactions
* chore: refactoring based on changes in main

---------

Co-authored-by: Neil Campbell <[email protected]>
  • Loading branch information
negar-abbasi and neilcampbell authored May 21, 2024
1 parent 97be076 commit d06fd8a
Show file tree
Hide file tree
Showing 19 changed files with 154 additions and 183 deletions.
3 changes: 2 additions & 1 deletion src/features/accounts/components/account-activity-tabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { useMemo } from 'react'
import { AccountAssetsHeld } from './account-assets-held'
import { Account } from '../models'
import { AccountTransactionHistory } from './account-transaction-history'
import { AccountLiveTransactions } from './account-live-transactions'
import {
accountActivityLabel,
accountLiveTransactionsTabId,
Expand Down Expand Up @@ -34,7 +35,7 @@ export function AccountActivityTabs({ account }: Props) {
{
id: accountLiveTransactionsTabId,
label: accountLiveTransactionsTabLabel,
children: '',
children: <AccountLiveTransactions address={account.address} />,
},
{
id: accountHistoricalTransactionsTabId,
Expand Down
25 changes: 25 additions & 0 deletions src/features/accounts/components/account-live-transactions.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { LiveTransactionsTable } from '@/features/transactions/components/live-transactions-table'
import { TransactionResult } from '@algorandfoundation/algokit-utils/types/indexer'
import { useCallback } from 'react'
import { Address } from '../data/types'
import { getAddressesForTransaction } from '../utils/get-account-address-for-transactions'
import { InnerTransaction, Transaction } from '@/features/transactions/models'
import { getAccountTransactionsTableSubRows } from '../utils/get-account-transactions-table-sub-rows'
import { transactionsTableColumns } from '@/features/transactions/components/transactions-table-columns'

type Props = {
address: Address
}

export function AccountLiveTransactions({ address }: Props) {
const filter = useCallback(
(transactionResult: TransactionResult) => {
const addressesForTransaction = getAddressesForTransaction(transactionResult)
return addressesForTransaction.includes(address)
},
[address]
)

const getSubRows = useCallback((row: Transaction | InnerTransaction) => getAccountTransactionsTableSubRows(address, row), [address])
return <LiveTransactionsTable filter={filter} getSubRows={getSubRows} columns={transactionsTableColumns} />
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
import { LazyLoadDataTable } from '@/features/common/components/lazy-load-data-table'
import { Address } from '../data/types'
import { useFetchNextAccountTransactionPage } from '../data/account-transaction-history'
import { accountTransactionsTableColumns } from './account-transaction-table-columns'
import { useCallback } from 'react'
import { getAccountTransactionsTableSubRows } from '../utils/get-account-transactions-table-sub-rows'
import { InnerTransaction, Transaction } from '@/features/transactions/models'
import { transactionsTableColumns } from '@/features/transactions/components/transactions-table-columns'

type Props = {
address: Address
}

export function AccountTransactionHistory({ address }: Props) {
const fetchNextPage = useFetchNextAccountTransactionPage(address)
const getSubRows = useCallback((row: Transaction | InnerTransaction) => getAccountTransactionsTableSubRows(address, row), [address])

return <LazyLoadDataTable columns={accountTransactionsTableColumns} fetchNextPage={fetchNextPage} />
return <LazyLoadDataTable columns={transactionsTableColumns} getSubRows={getSubRows} fetchNextPage={fetchNextPage} />
}

This file was deleted.

13 changes: 6 additions & 7 deletions src/features/accounts/data/account-transaction-history.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,8 @@ import { JotaiStore } from '@/features/common/data/types'
import { createTransactionsAtom, transactionResultsAtom } from '@/features/transactions/data'
import { atomEffect } from 'jotai-effect'
import { atom, useStore } from 'jotai'
import { extractTransactionsForAccount } from '../utils/extract-transaction-for-account'

const fetchAccountTransactionResults = async (address: Address, pageSize: number, nextPageToken?: string) => {
const getAccountTransactionResults = async (address: Address, pageSize: number, nextPageToken?: string) => {
const results = (await indexer
.searchForTransactions()
.address(address)
Expand Down Expand Up @@ -41,16 +40,16 @@ const createSyncEffect = (transactionResults: TransactionResult[]) => {
})
}

const creatAccountTransactionAtom = (store: JotaiStore, address: Address, pageSize: number, nextPageToken?: string) => {
const createAccountTransactionAtom = (store: JotaiStore, address: Address, pageSize: number, nextPageToken?: string) => {
return atom(async (get) => {
const { transactionResults, nextPageToken: newNextPageToken } = await fetchAccountTransactionResults(address, pageSize, nextPageToken)
const { transactionResults, nextPageToken: newNextPageToken } = await getAccountTransactionResults(address, pageSize, nextPageToken)

get(createSyncEffect(transactionResults))

const transactions = await get(createTransactionsAtom(store, transactionResults))
const transactionsForAccount = transactions.flatMap((transaction) => extractTransactionsForAccount(transaction, address))

return {
rows: transactionsForAccount,
rows: transactions,
nextPageToken: newNextPageToken,
}
})
Expand All @@ -60,6 +59,6 @@ export const useFetchNextAccountTransactionPage = (address: Address) => {
const store = useStore()

return useMemo(() => {
return (pageSize: number, nextPageToken?: string) => creatAccountTransactionAtom(store, address, pageSize, nextPageToken)
return (pageSize: number, nextPageToken?: string) => createAccountTransactionAtom(store, address, pageSize, nextPageToken)
}, [store, address])
}
36 changes: 0 additions & 36 deletions src/features/accounts/utils/extract-transaction-for-account.ts

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import algosdk from 'algosdk'
import { TransactionResult } from '@algorandfoundation/algokit-utils/types/indexer'
import { invariant } from '@/utils/invariant'
import { Address } from '../data/types'

export const getAddressesForTransaction = (transaction: TransactionResult): Address[] => {
const addresses = new Set<Address>()
addresses.add(transaction.sender)
if (transaction['tx-type'] === algosdk.TransactionType.pay) {
invariant(transaction['payment-transaction'], 'payment-transaction is not set')

addresses.add(transaction['payment-transaction']['receiver'])
if (transaction['payment-transaction']['close-remainder-to']) {
addresses.add(transaction['payment-transaction']['close-remainder-to'])
}
} else if (transaction['tx-type'] === algosdk.TransactionType.axfer) {
invariant(transaction['asset-transfer-transaction'], 'asset-transfer-transaction is not set')

addresses.add(transaction['asset-transfer-transaction']['receiver'])
if (transaction['asset-transfer-transaction']['close-to']) {
addresses.add(transaction['asset-transfer-transaction']['close-to'])
}
} else if (transaction['tx-type'] === algosdk.TransactionType.afrz) {
invariant(transaction['asset-freeze-transaction'], 'asset-freeze-transaction is not set')

addresses.add(transaction['asset-freeze-transaction']['address'])
} else if (transaction['tx-type'] === algosdk.TransactionType.acfg) {
invariant(transaction['asset-config-transaction'], 'asset-config-transaction is not set')
if (transaction['asset-config-transaction'].params?.manager) {
addresses.add(transaction['asset-config-transaction'].params?.manager)
}
if (transaction['asset-config-transaction'].params?.reserve) {
addresses.add(transaction['asset-config-transaction'].params?.reserve)
}
if (transaction['asset-config-transaction'].params?.freeze) {
addresses.add(transaction['asset-config-transaction'].params?.freeze)
}
if (transaction['asset-config-transaction'].params?.clawback) {
addresses.add(transaction['asset-config-transaction'].params?.clawback)
}
} else if (transaction['tx-type'] === algosdk.TransactionType.appl) {
invariant(transaction['application-transaction'], 'application-transaction is not set')

const innerTransactions = transaction['inner-txns'] ?? []
for (const innerTxn of innerTransactions) {
const innerAddresses = getAddressesForTransaction(innerTxn)
for (const address of innerAddresses) {
addresses.add(address)
}
}
}
return Array.from(addresses)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Transaction, InnerTransaction, TransactionType } from '@/features/transactions/models'
import { flattenInnerTransactions } from '@/utils/flatten-inner-transactions'
import { Address } from '../data/types'

export const getAccountTransactionsTableSubRows = (address: Address, transaction: Transaction | InnerTransaction) => {
if (transaction.type !== TransactionType.ApplicationCall || transaction.innerTransactions.length === 0) {
return []
}

return transaction.innerTransactions.filter((innerTransaction) => {
const txns = flattenInnerTransactions(innerTransaction)
return txns.some(({ transaction }) => {
return (
transaction.sender === address ||
(transaction.type === TransactionType.Payment &&
(transaction.receiver === address || transaction.closeRemainder?.to === address)) ||
(transaction.type === TransactionType.AssetTransfer &&
(transaction.receiver === address || transaction.closeRemainder?.to === address || transaction.clawbackFrom === address)) ||
(transaction.type === TransactionType.AssetConfig &&
(transaction.manager === address ||
transaction.clawback === address ||
transaction.reserve === address ||
transaction.freeze === address)) ||
(transaction.type === TransactionType.AssetFreeze && transaction.address === address)
)
})
})
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import { LiveTransactionsTable } from '@/features/transactions/components/live-t
import { TransactionResult } from '@algorandfoundation/algokit-utils/types/indexer'
import { flattenTransactionResult } from '@/features/transactions/utils/flatten-transaction-result'
import { TransactionType as AlgoSdkTransactionType } from 'algosdk'
import { applicationTransactionsTableColumns } from '../utils/application-transactions-table-columns'
import { Transaction, InnerTransaction } from '@/features/transactions/models'
import { getApplicationTransactionsTableSubRows } from '../utils/get-application-transactions-table-sub-rows'
import { transactionsTableColumns } from '@/features/transactions/components/transactions-table-columns'

type Props = {
applicationId: ApplicationId
Expand All @@ -28,5 +28,5 @@ export function ApplicationLiveTransactions({ applicationId }: Props) {
[applicationId]
)

return <LiveTransactionsTable filter={filter} getSubRows={getSubRows} columns={applicationTransactionsTableColumns} />
return <LiveTransactionsTable filter={filter} getSubRows={getSubRows} columns={transactionsTableColumns} />
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { LazyLoadDataTable } from '@/features/common/components/lazy-load-data-table'
import { ApplicationId } from '../data/types'
import { useFetchNextApplicationTransactionsPage } from '../data/application-transaction-history'
import { applicationTransactionsTableColumns } from '../utils/application-transactions-table-columns'
import { InnerTransaction, Transaction } from '@/features/transactions/models'
import { useCallback } from 'react'
import { getApplicationTransactionsTableSubRows } from '../utils/get-application-transactions-table-sub-rows'
import { transactionsTableColumns } from '@/features/transactions/components/transactions-table-columns'

type Props = {
applicationId: ApplicationId
Expand All @@ -22,5 +22,5 @@ export function ApplicationTransactionHistory({ applicationId }: Props) {
[applicationId]
)

return <LazyLoadDataTable columns={applicationTransactionsTableColumns} getSubRows={getSubRows} fetchNextPage={fetchNextPage} />
return <LazyLoadDataTable columns={transactionsTableColumns} getSubRows={getSubRows} fetchNextPage={fetchNextPage} />
}
9 changes: 6 additions & 3 deletions src/features/applications/data/application-boxes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { ApplicationBox, ApplicationBoxSummary } from '../models'
import { Buffer } from 'buffer'
import { loadable } from 'jotai/utils'

const fetchApplicationBoxes = async (applicationId: ApplicationId, pageSize: number, nextPageToken?: string) => {
const getApplicationBoxes = async (applicationId: ApplicationId, pageSize: number, nextPageToken?: string) => {
const results = await indexer
.searchForApplicationBoxes(applicationId)
.nextToken(nextPageToken ?? '')
Expand All @@ -19,9 +19,12 @@ const fetchApplicationBoxes = async (applicationId: ApplicationId, pageSize: num
} as const
}

const getApplicationBox = (applicationId: ApplicationId, boxName: string) =>
indexer.lookupApplicationBoxByIDandName(applicationId, Buffer.from(boxName, 'base64')).do()

const createApplicationBoxesAtom = (applicationId: ApplicationId, pageSize: number, nextPageToken?: string) => {
return atom(async () => {
const { boxes, nextPageToken: newNextPageToken } = await fetchApplicationBoxes(applicationId, pageSize, nextPageToken)
const { boxes, nextPageToken: newNextPageToken } = await getApplicationBoxes(applicationId, pageSize, nextPageToken)

return {
rows: boxes,
Expand All @@ -39,7 +42,7 @@ export const useFetchNextApplicationBoxPage = (applicationId: ApplicationId) =>
export const useApplicationBox = (applicationId: ApplicationId, boxName: string) => {
return useMemo(() => {
return atom(async () => {
const box = await indexer.lookupApplicationBoxByIDandName(applicationId, Buffer.from(boxName, 'base64')).do()
const box = await getApplicationBox(applicationId, boxName)
return box.get_obj_for_encoding(false) as ApplicationBox
})
}, [applicationId, boxName])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { createTransactionsAtom, transactionResultsAtom } from '@/features/trans
import { atomEffect } from 'jotai-effect'
import { atom, useStore } from 'jotai'

const fetchApplicationTransactionResults = async (applicationID: ApplicationId, pageSize: number, nextPageToken?: string) => {
const getApplicationTransactionResults = async (applicationID: ApplicationId, pageSize: number, nextPageToken?: string) => {
const results = (await indexer
.searchForTransactions()
.applicationID(applicationID)
Expand Down Expand Up @@ -42,7 +42,7 @@ const createSyncEffect = (transactionResults: TransactionResult[]) => {

const createApplicationTransactionsAtom = (store: JotaiStore, applicationID: ApplicationId, pageSize: number, nextPageToken?: string) => {
return atom(async (get) => {
const { transactionResults, nextPageToken: newNextPageToken } = await fetchApplicationTransactionResults(
const { transactionResults, nextPageToken: newNextPageToken } = await getApplicationTransactionResults(
applicationID,
pageSize,
nextPageToken
Expand Down
8 changes: 4 additions & 4 deletions src/features/applications/data/program-teal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import { useMemo } from 'react'
import { Buffer } from 'buffer'

export const useProgramTeal = (base64Program: string) => {
const [tealAtom, fetchTealAtom] = useMemo(() => {
const [tealAtom, getTealAtom] = useMemo(() => {
const tealAtom = atom<Promise<string> | undefined>(undefined)
const fetchTealAtom = atom(null, (get, set) => {
const getTealAtom = atom(null, (get, set) => {
if (get(tealAtom)) {
return
}
Expand All @@ -21,8 +21,8 @@ export const useProgramTeal = (base64Program: string) => {
.then((result) => result.result as string)
)
})
return [tealAtom, fetchTealAtom] as const
return [tealAtom, getTealAtom] as const
}, [base64Program])

return [useAtomValue(loadable(tealAtom)), useSetAtom(fetchTealAtom)] as const
return [useAtomValue(loadable(tealAtom)), useSetAtom(getTealAtom)] as const
}
Loading

0 comments on commit d06fd8a

Please sign in to comment.