diff --git a/src/features/assets/components/asset-details.tsx b/src/features/assets/components/asset-details.tsx
index e992e4575..3e50d355a 100644
--- a/src/features/assets/components/asset-details.tsx
+++ b/src/features/assets/components/asset-details.tsx
@@ -15,8 +15,12 @@ import {
assetDefaultFrozenLabel,
assetDetailsLabel,
assetFreezeLabel,
+ assetHistoricalTransactionsTabId,
+ assetHistoricalTransactionsTabLabel,
assetIdLabel,
assetJsonLabel,
+ assetLiveTransactionsTabId,
+ assetLiveTransactionsTabLabel,
assetManagerLabel,
assetNameLabel,
assetReserveLabel,
@@ -30,6 +34,8 @@ import { AssetMedia } from './asset-media'
import { AssetTraits } from './asset-traits'
import { AssetMetadata } from './asset-metadata'
import { AssetTransactionHistory } from './asset-transaction-history'
+import { AssetLiveTransactions } from './asset-live-transactions'
+import { OverflowAutoTabsContent, Tabs, TabsList, TabsTrigger } from '@/features/common/components/tabs'
type Props = {
asset: Asset
@@ -157,9 +163,28 @@ export function AssetDetails({ asset }: Props) {
{assetTransactionsLabel}
-
+
+
+
+ {assetLiveTransactionsTabLabel}
+
+
+ {assetHistoricalTransactionsTabLabel}
+
+
+
+
+
+
+
+
+
>
diff --git a/src/features/assets/components/asset-live-transactions.tsx b/src/features/assets/components/asset-live-transactions.tsx
new file mode 100644
index 000000000..e9f78223e
--- /dev/null
+++ b/src/features/assets/components/asset-live-transactions.tsx
@@ -0,0 +1,30 @@
+import { AssetId } from '../data/types'
+import { useCallback } from 'react'
+import { LiveTransactionsTable } from '@/features/transactions/components/live-transactions-table'
+import { assetTransactionsTableColumns } from '../utils/asset-transactions-table-columns'
+import { TransactionResult } from '@algorandfoundation/algokit-utils/types/indexer'
+import { JotaiStore } from '@/features/common/data/types'
+import { createTransactionAtom } from '@/features/transactions/data'
+import { atom } from 'jotai'
+import { getAssetIdsForTransaction } from '@/features/transactions/utils/get-asset-ids-for-transaction'
+import { extractTransactionsForAsset } from '../utils/extract-transactions-for-asset'
+
+type Props = {
+ assetId: AssetId
+}
+
+export function AssetLiveTransactions({ assetId }: Props) {
+ const mapper = useCallback(
+ (store: JotaiStore, transactionResult: TransactionResult) => {
+ return atom(async (get) => {
+ const assetIdsForTransaction = getAssetIdsForTransaction(transactionResult)
+ if (!assetIdsForTransaction.includes(assetId)) return []
+
+ const transaction = await get(createTransactionAtom(store, transactionResult))
+ return extractTransactionsForAsset(transaction, assetId)
+ })
+ },
+ [assetId]
+ )
+ return
+}
diff --git a/src/features/assets/components/asset-transaction-history.tsx b/src/features/assets/components/asset-transaction-history.tsx
index eb81ec7c6..3b3df1f9d 100644
--- a/src/features/assets/components/asset-transaction-history.tsx
+++ b/src/features/assets/components/asset-transaction-history.tsx
@@ -1,13 +1,7 @@
import { LazyLoadDataTable } from '@/features/common/components/lazy-load-data-table'
import { AssetId } from '../data/types'
-import { Transaction, TransactionType } from '@/features/transactions/models'
-import { DisplayAlgo } from '@/features/common/components/display-algo'
-import { DisplayAssetAmount } from '@/features/common/components/display-asset-amount'
-import { cn } from '@/features/common/utils'
-import { TransactionLink } from '@/features/transactions/components/transaction-link'
-import { ellipseAddress } from '@/utils/ellipse-address'
-import { ColumnDef } from '@tanstack/react-table'
import { useFetchNextAssetTransactionsPage } from '../data/asset-transaction-history'
+import { assetTransactionsTableColumns } from '../utils/asset-transactions-table-columns'
type Props = {
assetId: AssetId
@@ -16,45 +10,5 @@ type Props = {
export function AssetTransactionHistory({ assetId }: Props) {
const fetchNextPage = useFetchNextAssetTransactionsPage(assetId)
- return
+ return
}
-
-const transactionsTableColumns: ColumnDef[] = [
- {
- header: 'Transaction Id',
- accessorKey: 'id',
- cell: (c) => {
- const value = c.getValue()
- return
- },
- },
- {
- accessorKey: 'sender',
- header: 'From',
- cell: (c) => ellipseAddress(c.getValue()),
- },
- {
- header: 'To',
- accessorFn: (transaction) => {
- if (transaction.type === TransactionType.Payment || transaction.type === TransactionType.AssetTransfer)
- return ellipseAddress(transaction.receiver)
- if (transaction.type === TransactionType.ApplicationCall) return transaction.applicationId
- if (transaction.type === TransactionType.AssetConfig) return transaction.assetId
- if (transaction.type === TransactionType.AssetFreeze) return ellipseAddress(transaction.address)
- },
- },
- {
- accessorKey: 'type',
- header: 'Type',
- },
- {
- header: 'Amount',
- accessorFn: (transaction) => transaction,
- cell: (c) => {
- const transaction = c.getValue()
- if (transaction.type === TransactionType.Payment) return
- if (transaction.type === TransactionType.AssetTransfer)
- return
- },
- },
-]
diff --git a/src/features/assets/components/labels.ts b/src/features/assets/components/labels.ts
index 443c9dffd..91a7e0b54 100644
--- a/src/features/assets/components/labels.ts
+++ b/src/features/assets/components/labels.ts
@@ -23,3 +23,7 @@ export const assetMetadataLabel = 'Asset Metadata'
export const assetJsonLabel = 'Asset JSON'
export const assetTransactionsLabel = 'Asset Transactions'
+export const assetLiveTransactionsTabId = 'live-transactions'
+export const assetLiveTransactionsTabLabel = 'Live Transactions'
+export const assetHistoricalTransactionsTabId = 'historical-transactions'
+export const assetHistoricalTransactionsTabLabel = 'Historical Transactions'
diff --git a/src/features/assets/data/asset-metadata.ts b/src/features/assets/data/asset-metadata.ts
index 28a720927..4f3af4187 100644
--- a/src/features/assets/data/asset-metadata.ts
+++ b/src/features/assets/data/asset-metadata.ts
@@ -75,7 +75,7 @@ export const createAssetMetadataResultAtom = (assetResult: AssetResult) =>
return null
}
- const results =
+ let results =
assetResult.params.manager && assetResult.params.manager !== ZERO_ADDRESS
? await indexer
.searchForTransactions()
@@ -85,19 +85,22 @@ export const createAssetMetadataResultAtom = (assetResult: AssetResult) =>
.addressRole('sender')
.limit(2) // Return 2 to cater for a destroy transaction and any potential eventual consistency delays between transactions and assets.
.do()
- .then((res) => res.transactions as TransactionResult[]) // Implicitly newest to oldest when filtering with an address
- : // The asset has been destroyed or is an immutable asset.
- // Fetch the entire acfg transaction history and reverse the order, so it's newest to oldest
- await executePaginatedRequest(
- (res: TransactionSearchResults) => res.transactions,
- (nextToken) => {
- let s = indexer.searchForTransactions().assetID(assetResult.index).txType('acfg')
- if (nextToken) {
- s = s.nextToken(nextToken)
- }
- return s
- }
- ).then((res) => res.reverse()) // reverse the order, so it's newest to oldest
+ .then((res) => res.transactions as TransactionResult[]) // Implicitly newest to oldest when filtering with an address.
+ : []
+ if (results.length === 0) {
+ // The asset has been destroyed, is an immutable asset, or the asset is mutable however has never been mutated.
+ // Fetch the entire acfg transaction history and reverse the order, so it's newest to oldest.
+ results = await executePaginatedRequest(
+ (res: TransactionSearchResults) => res.transactions,
+ (nextToken) => {
+ let s = indexer.searchForTransactions().assetID(assetResult.index).txType('acfg')
+ if (nextToken) {
+ s = s.nextToken(nextToken)
+ }
+ return s
+ }
+ ).then((res) => res.reverse()) // reverse the order, so it's newest to oldest
+ }
const assetConfigTransactionResults = results.flatMap(flattenTransactionResult).filter((t) => {
const isAssetConfigTransaction = t['tx-type'] === TransactionType.acfg
diff --git a/src/features/assets/data/asset-transaction-history.ts b/src/features/assets/data/asset-transaction-history.ts
index f5f41078e..7f6825176 100644
--- a/src/features/assets/data/asset-transaction-history.ts
+++ b/src/features/assets/data/asset-transaction-history.ts
@@ -6,6 +6,7 @@ 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 { extractTransactionsForAsset } from '../utils/extract-transactions-for-asset'
const fetchAssetTransactionResults = async (assetId: AssetId, pageSize: number, nextPageToken?: string) => {
const results = (await indexer
@@ -47,9 +48,10 @@ const createAssetTransactionsAtom = (store: JotaiStore, assetId: AssetId, pageSi
get(createSyncEffect(transactionResults))
const transactions = await get(createTransactionsAtom(store, transactionResults))
+ const transactionsForAsset = transactions.flatMap((transaction) => extractTransactionsForAsset(transaction, assetId))
return {
- rows: transactions,
+ rows: transactionsForAsset,
nextPageToken: newNextPageToken,
}
})
diff --git a/src/features/assets/pages/asset-page.test.tsx b/src/features/assets/pages/asset-page.test.tsx
index 94402ec83..0810ce51d 100644
--- a/src/features/assets/pages/asset-page.test.tsx
+++ b/src/features/assets/pages/asset-page.test.tsx
@@ -17,6 +17,7 @@ import {
assetReserveLabel,
assetTotalSupplyLabel,
assetTraitsLabel,
+ assetTransactionsLabel,
assetUrlLabel,
} from '../components/labels'
import { useParams } from 'react-router-dom'
@@ -147,6 +148,10 @@ describe('asset-page', () => {
{ term: 'Image Mimetype', description: 'image/png' },
],
})
+
+ const transactionTabList = component.getByRole('tablist', { name: assetTransactionsLabel })
+ expect(transactionTabList).toBeTruthy()
+ expect(transactionTabList.children.length).toBe(2)
})
}
)
@@ -608,6 +613,9 @@ describe('asset-page', () => {
const assetTraitsCard = component.queryByText(assetTraitsLabel)
expect(assetTraitsCard).toBeNull()
+
+ const transactionTabList = component.queryByRole('tablist', { name: assetTransactionsLabel })
+ expect(transactionTabList).toBeNull()
})
}
)
diff --git a/src/features/assets/utils/asset-transactions-table-columns.tsx b/src/features/assets/utils/asset-transactions-table-columns.tsx
new file mode 100644
index 000000000..454e40b40
--- /dev/null
+++ b/src/features/assets/utils/asset-transactions-table-columns.tsx
@@ -0,0 +1,57 @@
+import { InnerTransaction, Transaction, TransactionType } from '@/features/transactions/models'
+import { cn } from '@/features/common/utils'
+import { ellipseAddress } from '@/utils/ellipse-address'
+import { ColumnDef } from '@tanstack/react-table'
+import { DisplayAlgo } from '@/features/common/components/display-algo'
+import { DisplayAssetAmount } from '@/features/common/components/display-asset-amount'
+import { TransactionLink } from '@/features/transactions/components/transaction-link'
+import { ellipseId } from '@/utils/ellipse-id'
+import { asTo } from '@/features/common/mappers/to'
+
+export const assetTransactionsTableColumns: ColumnDef[] = [
+ {
+ header: 'Transaction Id',
+ accessorFn: (transaction) => transaction,
+ cell: (c) => {
+ const transaction = c.getValue()
+ return 'innerId' in transaction ? (
+
+ {ellipseId(transaction.id)}
+ (Inner)
+
+ ) : (
+
+ )
+ },
+ },
+ {
+ header: 'Round',
+ accessorKey: 'confirmedRound',
+ },
+ {
+ accessorKey: 'sender',
+ header: 'From',
+ cell: (c) => ellipseAddress(c.getValue()),
+ },
+ {
+ header: 'To',
+ accessorFn: asTo,
+ },
+ {
+ accessorKey: 'type',
+ header: 'Type',
+ },
+ {
+ header: 'Amount',
+ accessorFn: (transaction) => transaction,
+ cell: (c) => {
+ const transaction = c.getValue()
+ if (transaction.type === TransactionType.Payment) return
+ if (transaction.type === TransactionType.AssetTransfer)
+ return
+ },
+ },
+]
diff --git a/src/features/assets/utils/extract-transactions-for-asset.ts b/src/features/assets/utils/extract-transactions-for-asset.ts
new file mode 100644
index 000000000..4ab154bad
--- /dev/null
+++ b/src/features/assets/utils/extract-transactions-for-asset.ts
@@ -0,0 +1,20 @@
+import { Transaction, TransactionType } from '@/features/transactions/models'
+import { flattenInnerTransactions } from '@/utils/flatten-inner-transactions'
+
+export const extractTransactionsForAsset = (transaction: Transaction, assetIndex: number) => {
+ const flattenedTransactions = flattenInnerTransactions(transaction)
+ const results = []
+
+ for (const { transaction } of flattenedTransactions) {
+ if (transaction.type === TransactionType.AssetConfig && transaction.assetId === assetIndex) {
+ results.push(transaction)
+ }
+ if (transaction.type === TransactionType.AssetTransfer && transaction.asset.id === assetIndex) {
+ results.push(transaction)
+ }
+ if (transaction.type === TransactionType.AssetFreeze && transaction.assetId === assetIndex) {
+ results.push(transaction)
+ }
+ }
+ return results
+}
diff --git a/src/features/blocks/components/transactions.tsx b/src/features/blocks/components/transactions.tsx
index b66a061aa..1e00ccbdc 100644
--- a/src/features/blocks/components/transactions.tsx
+++ b/src/features/blocks/components/transactions.tsx
@@ -6,6 +6,7 @@ import { DataTable } from '@/features/common/components/data-table'
import { TransactionLink } from '@/features/transactions/components/transaction-link'
import { DisplayAssetAmount } from '@/features/common/components/display-asset-amount'
import { GroupLink } from '@/features/groups/components/group-link'
+import { asTo } from '@/features/common/mappers/to'
type Props = {
transactions: Transaction[]
@@ -31,13 +32,7 @@ export const columns: ColumnDef[] = [
},
{
header: 'To',
- accessorFn: (transaction) => {
- if (transaction.type === TransactionType.Payment || transaction.type === TransactionType.AssetTransfer)
- return ellipseAddress(transaction.receiver)
- if (transaction.type === TransactionType.ApplicationCall) return transaction.applicationId
- if (transaction.type === TransactionType.AssetConfig) return transaction.assetId
- if (transaction.type === TransactionType.AssetFreeze) return transaction.assetId
- },
+ accessorFn: asTo,
},
{
accessorKey: 'type',
diff --git a/src/features/blocks/data/block-result.ts b/src/features/blocks/data/block-result.ts
index 358675199..6efe7c4b9 100644
--- a/src/features/blocks/data/block-result.ts
+++ b/src/features/blocks/data/block-result.ts
@@ -50,7 +50,7 @@ export const createBlockExtractAtom = (round: Round) => {
})
}
-export const addStateExtractFromBlocksAtom = atom(
+export const addStateExtractedFromBlocksAtom = atom(
null,
(get, set, blockResults: BlockResult[], transactionResults: TransactionResult[], groupResults: GroupResult[]) => {
if (transactionResults.length > 0) {
@@ -108,7 +108,7 @@ const createBlockResultAtom = (round: Round) => {
})
// Don't need to sync the block, as it's synced by atomsInAtom, due to this atom returning the block
- set(addStateExtractFromBlocksAtom, [], transactionResults, groupResults)
+ set(addStateExtractedFromBlocksAtom, [], transactionResults, groupResults)
})()
})
diff --git a/src/features/blocks/data/latest-blocks.ts b/src/features/blocks/data/latest-blocks.ts
index 5bf5561ce..eb24997a2 100644
--- a/src/features/blocks/data/latest-blocks.ts
+++ b/src/features/blocks/data/latest-blocks.ts
@@ -1,8 +1,8 @@
import { atom, useAtom, useAtomValue } from 'jotai'
import { isDefined } from '@/utils/is-defined'
import { asBlockSummary } from '../mappers'
-import { transactionResultsAtom } from '@/features/transactions/data'
-import { asTransactionSummary } from '@/features/transactions/mappers/transaction-mappers'
+import { liveTransactionIdsAtom, transactionResultsAtom } from '@/features/transactions/data'
+import { asTransactionSummary } from '@/features/transactions/mappers'
import { atomEffect } from 'jotai-effect'
import { AlgorandSubscriber } from '@algorandfoundation/algokit-subscriber'
import { algod } from '@/features/common/data'
@@ -14,7 +14,7 @@ import { flattenTransactionResult } from '@/features/transactions/utils/flatten-
import { distinct } from '@/utils/distinct'
import { assetResultsAtom } from '@/features/assets/data'
import { BlockSummary } from '../models'
-import { blockResultsAtom, addStateExtractFromBlocksAtom, syncedRoundAtom } from './block-result'
+import { blockResultsAtom, addStateExtractedFromBlocksAtom, syncedRoundAtom } from './block-result'
import { GroupId, GroupResult } from '@/features/groups/data/types'
import { AssetId } from '@/features/assets/data/types'
@@ -161,7 +161,11 @@ const subscribeToBlocksEffect = atomEffect((get, set) => {
})
}
- set(addStateExtractFromBlocksAtom, blockResults, transactionResults, Array.from(groupResults.values()))
+ set(addStateExtractedFromBlocksAtom, blockResults, transactionResults, Array.from(groupResults.values()))
+
+ set(liveTransactionIdsAtom, (prev) => {
+ return transactionResults.map((txn) => txn.id).concat(prev)
+ })
})
subscriber.start()
diff --git a/src/features/blocks/mappers/index.ts b/src/features/blocks/mappers/index.ts
index 9cca97327..3e8feb725 100644
--- a/src/features/blocks/mappers/index.ts
+++ b/src/features/blocks/mappers/index.ts
@@ -1,7 +1,7 @@
import { Transaction, TransactionSummary } from '@/features/transactions/models'
import { Block, BlockSummary, CommonBlockProperties } from '../models'
import { BlockResult } from '../data/types'
-import { asTransactionsSummary } from '@/features/common/mappers'
+import { asTransactionsSummary } from '@/features/transactions/mappers'
const asCommonBlock = (block: BlockResult, transactions: Pick[]): CommonBlockProperties => {
return {
diff --git a/src/features/common/components/command.tsx b/src/features/common/components/command.tsx
index 9bba2bb5d..e1cd46280 100644
--- a/src/features/common/components/command.tsx
+++ b/src/features/common/components/command.tsx
@@ -1,5 +1,3 @@
-'use client'
-
import * as React from 'react'
import { type DialogProps } from '@radix-ui/react-dialog'
import { MagnifyingGlassIcon } from '@radix-ui/react-icons'
diff --git a/src/features/common/components/data-table.tsx b/src/features/common/components/data-table.tsx
index d645c6f9b..83e2b2eba 100644
--- a/src/features/common/components/data-table.tsx
+++ b/src/features/common/components/data-table.tsx
@@ -1,5 +1,3 @@
-'use client'
-
import { ColumnDef, flexRender, getCoreRowModel, useReactTable, getPaginationRowModel } from '@tanstack/react-table'
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/features/common/components/table'
import { DataTablePagination } from './data-table-pagination'
diff --git a/src/features/common/components/dialog.tsx b/src/features/common/components/dialog.tsx
index 83f67da3a..e6790fd0a 100644
--- a/src/features/common/components/dialog.tsx
+++ b/src/features/common/components/dialog.tsx
@@ -1,5 +1,3 @@
-'use client'
-
import * as React from 'react'
import * as DialogPrimitive from '@radix-ui/react-dialog'
import { Cross2Icon } from '@radix-ui/react-icons'
diff --git a/src/features/common/mappers/to.ts b/src/features/common/mappers/to.ts
new file mode 100644
index 000000000..8fd70de61
--- /dev/null
+++ b/src/features/common/mappers/to.ts
@@ -0,0 +1,10 @@
+import { Transaction, TransactionType } from '@/features/transactions/models'
+import { ellipseAddress } from '@/utils/ellipse-address'
+
+export const asTo = (transaction: Transaction) => {
+ if (transaction.type === TransactionType.Payment || transaction.type === TransactionType.AssetTransfer)
+ return ellipseAddress(transaction.receiver)
+ if (transaction.type === TransactionType.ApplicationCall) return transaction.applicationId
+ if (transaction.type === TransactionType.AssetConfig) return transaction.assetId
+ if (transaction.type === TransactionType.AssetFreeze) return ellipseAddress(transaction.address)
+}
diff --git a/src/features/groups/data/group-result.ts b/src/features/groups/data/group-result.ts
index 57a0b543f..6333b4b66 100644
--- a/src/features/groups/data/group-result.ts
+++ b/src/features/groups/data/group-result.ts
@@ -1,7 +1,7 @@
import { Round } from '@/features/blocks/data/types'
import { atom } from 'jotai'
import { GroupId, GroupResult } from './types'
-import { createBlockExtractAtom, addStateExtractFromBlocksAtom as addStateExtractedFromBlocksAtom } from '@/features/blocks/data'
+import { createBlockExtractAtom, addStateExtractedFromBlocksAtom } from '@/features/blocks/data'
import { invariant } from '@/utils/invariant'
import { atomEffect } from 'jotai-effect'
import { TransactionResult } from '@algorandfoundation/algokit-utils/types/indexer'
diff --git a/src/features/groups/mappers/index.ts b/src/features/groups/mappers/index.ts
index b00493053..261ef8a74 100644
--- a/src/features/groups/mappers/index.ts
+++ b/src/features/groups/mappers/index.ts
@@ -1,7 +1,7 @@
import { Group } from '../models'
import { GroupResult } from '../data/types'
-import { asTransactionsSummary } from '@/features/common/mappers'
import { Transaction } from '@/features/transactions/models'
+import { asTransactionsSummary } from '@/features/transactions/mappers'
export const asGroup = (groupResult: GroupResult, transactions: Transaction[]): Group => {
return {
diff --git a/src/features/transactions/components/live-transactions-table.tsx b/src/features/transactions/components/live-transactions-table.tsx
new file mode 100644
index 000000000..b19262228
--- /dev/null
+++ b/src/features/transactions/components/live-transactions-table.tsx
@@ -0,0 +1,85 @@
+import { ColumnDef, flexRender, getCoreRowModel, useReactTable } from '@tanstack/react-table'
+import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/features/common/components/table'
+import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../../common/components/select'
+import { useState } from 'react'
+import { InnerTransaction, Transaction } from '@/features/transactions/models'
+import { Atom } from 'jotai'
+import { TransactionResult } from '@algorandfoundation/algokit-utils/types/indexer'
+import { JotaiStore } from '@/features/common/data/types'
+import { useLiveTransactions } from '../data/live-transaction'
+
+interface Props {
+ columns: ColumnDef[]
+ mapper: (store: JotaiStore, transactionResult: TransactionResult) => Atom>
+}
+
+export function LiveTransactionsTable({ mapper, columns }: Props) {
+ const [maxRows, setMaxRows] = useState(10)
+ const transactions = useLiveTransactions(mapper, maxRows)
+ const table = useReactTable({
+ data: transactions,
+ columns,
+ getCoreRowModel: getCoreRowModel(),
+ manualPagination: true,
+ })
+
+ return (
+
+
+
+
+ {table.getHeaderGroups().map((headerGroup) => (
+
+ {headerGroup.headers.map((header) => {
+ return (
+
+ {header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())}
+
+ )
+ })}
+
+ ))}
+
+
+ {table.getRowModel().rows?.length ? (
+ table.getRowModel().rows.map((row) => (
+
+ {row.getVisibleCells().map((cell) => (
+ {flexRender(cell.column.columnDef.cell, cell.getContext())}
+ ))}
+
+ ))
+ ) : (
+
+
+ No results.
+
+
+ )}
+
+
+
+
+
+
+
Max rows
+
+
+
+
+
+ )
+}
+
+const maxRowsOptions = [10, 20, 30, 40, 50]
diff --git a/src/features/transactions/components/transactions-table.tsx b/src/features/transactions/components/transactions-table.tsx
index 532124e69..10a43f896 100644
--- a/src/features/transactions/components/transactions-table.tsx
+++ b/src/features/transactions/components/transactions-table.tsx
@@ -9,6 +9,7 @@ import { ColumnDef } from '@tanstack/react-table'
import { DataTable } from '@/features/common/components/data-table'
import { InnerTransactionLink } from './inner-transaction-link'
import { TransactionLink } from './transaction-link'
+import { asTo } from '@/features/common/mappers/to'
const graphConfig = {
indentationWidth: 20,
@@ -50,15 +51,7 @@ export const transactionsTableColumns: ColumnDef[] = [
},
{
header: 'To',
- accessorFn: (item) => item.transaction,
- cell: (c) => {
- const transaction = c.getValue()
- if (transaction.type === TransactionType.Payment || transaction.type === TransactionType.AssetTransfer)
- return ellipseAddress(transaction.receiver)
- if (transaction.type === TransactionType.ApplicationCall) return transaction.applicationId
- if (transaction.type === TransactionType.AssetConfig) return transaction.assetId
- if (transaction.type === TransactionType.AssetFreeze) return ellipseAddress(transaction.address)
- },
+ accessorFn: (item) => asTo(item.transaction),
},
{
accessorKey: 'transaction.type',
diff --git a/src/features/transactions/data/index.ts b/src/features/transactions/data/index.ts
index 256b36830..630d34da4 100644
--- a/src/features/transactions/data/index.ts
+++ b/src/features/transactions/data/index.ts
@@ -3,3 +3,4 @@ export * from './transaction'
export * from './latest-transactions'
export * from './inner-transaction'
export * from './logicsig-teal'
+export * from './live-transaction-ids'
diff --git a/src/features/transactions/data/live-transaction-ids.ts b/src/features/transactions/data/live-transaction-ids.ts
new file mode 100644
index 000000000..f7fb838c7
--- /dev/null
+++ b/src/features/transactions/data/live-transaction-ids.ts
@@ -0,0 +1,4 @@
+import { atom } from 'jotai'
+import { TransactionId } from './types'
+
+export const liveTransactionIdsAtom = atom([])
diff --git a/src/features/transactions/data/live-transaction.ts b/src/features/transactions/data/live-transaction.ts
new file mode 100644
index 000000000..effa69d7e
--- /dev/null
+++ b/src/features/transactions/data/live-transaction.ts
@@ -0,0 +1,57 @@
+import { useMemo } from 'react'
+import { getTransactionResultAtom, liveTransactionIdsAtom } from '@/features/transactions/data'
+import { InnerTransaction, Transaction } from '@/features/transactions/models'
+import { atomEffect } from 'jotai-effect'
+import { Atom, atom, useAtom, useAtomValue, useStore } from 'jotai'
+import { TransactionId } from '@/features/transactions/data/types'
+import { TransactionResult } from '@algorandfoundation/algokit-utils/types/indexer'
+import { JotaiStore } from '@/features/common/data/types'
+
+export const useLiveTransactions = (
+ mapper: (store: JotaiStore, transactionResult: TransactionResult) => Atom>,
+ maxRows: number
+) => {
+ const store = useStore()
+
+ const { liveTransactionsAtomEffect, liveTransactionsAtom } = useMemo(() => {
+ let syncedTransactionId: TransactionId | undefined = undefined
+ const liveTransactionsAtom = atom<(Transaction | InnerTransaction)[]>([])
+
+ const liveTransactionsAtomEffect = atomEffect((get, set) => {
+ ;(async () => {
+ const liveTransactionIds = get(liveTransactionIdsAtom)
+
+ const newTransactionResults: TransactionResult[] = []
+ for (const transactionId of liveTransactionIds) {
+ if (transactionId === syncedTransactionId) {
+ break
+ }
+ const transactionResultAtom = getTransactionResultAtom(store, transactionId)
+
+ const transactionResult = await get.peek(transactionResultAtom)
+ newTransactionResults.push(transactionResult)
+ }
+ syncedTransactionId = liveTransactionIds[0]
+
+ const newTransactions = (
+ await Promise.all(newTransactionResults.map((transactionResult) => get(mapper(store, transactionResult))))
+ ).flat()
+ if (newTransactions.length) {
+ set(liveTransactionsAtom, (prev) => {
+ return newTransactions.concat(prev).slice(0, maxRows)
+ })
+ }
+ })()
+ })
+
+ return {
+ liveTransactionsAtomEffect,
+ liveTransactionsAtom,
+ }
+ }, [store, mapper, maxRows])
+
+ useAtom(liveTransactionsAtomEffect)
+
+ const transactions = useAtomValue(liveTransactionsAtom)
+ return transactions
+}
diff --git a/src/features/transactions/mappers/index.ts b/src/features/transactions/mappers/index.ts
index 3fd158bd1..fd5e951b6 100644
--- a/src/features/transactions/mappers/index.ts
+++ b/src/features/transactions/mappers/index.ts
@@ -2,3 +2,4 @@ export * from './payment-transaction-mappers'
export * from './asset-transfer-transaction-mappers'
export * from './app-call-transaction-mappers'
export * from './transaction-mappers'
+export * from './transaction-summary'
diff --git a/src/features/common/mappers/index.ts b/src/features/transactions/mappers/transaction-summary.ts
similarity index 89%
rename from src/features/common/mappers/index.ts
rename to src/features/transactions/mappers/transaction-summary.ts
index 6c06f169c..704fb5247 100644
--- a/src/features/common/mappers/index.ts
+++ b/src/features/transactions/mappers/transaction-summary.ts
@@ -1,5 +1,5 @@
+import { TransactionsSummary } from '@/features/common/models'
import { Transaction, TransactionType } from '@/features/transactions/models'
-import { TransactionsSummary } from '../models'
export const asTransactionsSummary = (transactions: Pick[]): TransactionsSummary => {
return {
diff --git a/src/features/transactions/utils/get-asset-ids-for-transaction.ts b/src/features/transactions/utils/get-asset-ids-for-transaction.ts
index 018101213..d21fdc145 100644
--- a/src/features/transactions/utils/get-asset-ids-for-transaction.ts
+++ b/src/features/transactions/utils/get-asset-ids-for-transaction.ts
@@ -1,8 +1,9 @@
import algosdk from 'algosdk'
import { TransactionResult } from '@algorandfoundation/algokit-utils/types/indexer'
import { invariant } from '@/utils/invariant'
+import { AssetId } from '@/features/assets/data/types'
-export const getAssetIdsForTransaction = (transaction: TransactionResult): number[] => {
+export const getAssetIdsForTransaction = (transaction: TransactionResult): AssetId[] => {
if (transaction['tx-type'] === algosdk.TransactionType.axfer) {
invariant(transaction['asset-transfer-transaction'], 'asset-transfer-transaction is not set')
@@ -12,13 +13,10 @@ export const getAssetIdsForTransaction = (transaction: TransactionResult): numbe
invariant(transaction['application-transaction'], 'application-transaction is not set')
const innerTransactions = transaction['inner-txns'] ?? []
- return innerTransactions.reduce(
- (acc, innerTxn) => {
- const innerResult = getAssetIdsForTransaction(innerTxn)
- return acc.concat(innerResult)
- },
- transaction['application-transaction']['foreign-assets'] ?? ([] as number[])
- )
+ return innerTransactions.reduce((acc, innerTxn) => {
+ const innerResult = getAssetIdsForTransaction(innerTxn)
+ return acc.concat(innerResult)
+ }, [] as number[])
}
if (transaction['tx-type'] === algosdk.TransactionType.acfg) {
invariant(transaction['asset-config-transaction'], 'asset-config-transaction is not set')