diff --git a/src/features/applications/components/application-details.tsx b/src/features/applications/components/application-details.tsx index f95fa0e7d..a938d4ec3 100644 --- a/src/features/applications/components/application-details.tsx +++ b/src/features/applications/components/application-details.tsx @@ -15,14 +15,22 @@ import { applicationGlobalStateByteLabel, applicationGlobalStateLabel, applicationGlobalStateUintLabel, + applicationHistoricalTransactionsTabId, + applicationHistoricalTransactionsTabLabel, applicationIdLabel, + applicationLiveTransactionsTabId, + applicationLiveTransactionsTabLabel, applicationLocalStateByteLabel, applicationLocalStateUintLabel, + applicationTransactionsLabel, } from './labels' import { isDefined } from '@/utils/is-defined' import { ApplicationProgram } from './application-program' import { ApplicationGlobalStateTable } from './application-global-state-table' import { ApplicationBoxes } from './application-boxes' +import { OverflowAutoTabsContent, Tabs, TabsList, TabsTrigger } from '@/features/common/components/tabs' +import { ApplicationLiveTransactions } from './application-live-transactions' +import { ApplicationTransactionHistory } from './application-transaction-history' type Props = { application: Application @@ -106,6 +114,33 @@ export function ApplicationDetails({ application }: Props) { + + +

{applicationTransactionsLabel}

+ + + + {applicationLiveTransactionsTabLabel} + + + {applicationHistoricalTransactionsTabLabel} + + + + + + + + + +
+
) } diff --git a/src/features/applications/components/application-live-transactions.tsx b/src/features/applications/components/application-live-transactions.tsx new file mode 100644 index 000000000..444732b40 --- /dev/null +++ b/src/features/applications/components/application-live-transactions.tsx @@ -0,0 +1,32 @@ +import { ApplicationId } from '../data/types' +import { useCallback } from 'react' +import { LiveTransactionsTable } from '@/features/transactions/components/live-transactions-table' +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' + +type Props = { + applicationId: ApplicationId +} + +export function ApplicationLiveTransactions({ applicationId }: Props) { + const filter = useCallback( + (transactionResult: TransactionResult) => { + const flattenedTransactionResults = flattenTransactionResult(transactionResult) + return flattenedTransactionResults.some( + (txn) => txn['tx-type'] === AlgoSdkTransactionType.appl && txn['application-transaction']?.['application-id'] === applicationId + ) + }, + [applicationId] + ) + + const getSubRows = useCallback( + (row: Transaction | InnerTransaction) => getApplicationTransactionsTableSubRows(applicationId, row), + [applicationId] + ) + + return +} diff --git a/src/features/applications/components/application-transaction-history.tsx b/src/features/applications/components/application-transaction-history.tsx new file mode 100644 index 000000000..15c78a848 --- /dev/null +++ b/src/features/applications/components/application-transaction-history.tsx @@ -0,0 +1,26 @@ +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' + +type Props = { + applicationId: ApplicationId +} + +export function ApplicationTransactionHistory({ applicationId }: Props) { + // TODO: for the future + // How we handle getSubRows isn't the best practice. Ideally, we should create a new view model, for example, TransactionForApplication + // and then fetchNextPage should return a list of TransactionForApplication + // TransactionForApplication should be similar to Transaction, but the InnerTransactions should be only transactions that are related to the application + // This way, getSubRows simply return the innerTransactions + const fetchNextPage = useFetchNextApplicationTransactionsPage(applicationId) + const getSubRows = useCallback( + (row: Transaction | InnerTransaction) => getApplicationTransactionsTableSubRows(applicationId, row), + [applicationId] + ) + + return +} diff --git a/src/features/applications/components/labels.ts b/src/features/applications/components/labels.ts index b9cffdb34..51f38a284 100644 --- a/src/features/applications/components/labels.ts +++ b/src/features/applications/components/labels.ts @@ -19,3 +19,9 @@ export const applicationBoxesLabel = 'Boxes' export const applicationBoxNameLabel = 'Box Name' export const applicationBoxValueLabel = 'Box Value' + +export const applicationTransactionsLabel = 'Activity' +export const applicationLiveTransactionsTabId = 'live-transactions' +export const applicationLiveTransactionsTabLabel = 'Live Transactions' +export const applicationHistoricalTransactionsTabId = 'historical-transactions' +export const applicationHistoricalTransactionsTabLabel = 'Historical Transactions' diff --git a/src/features/applications/data/application-transaction-history.ts b/src/features/applications/data/application-transaction-history.ts new file mode 100644 index 000000000..13cc552f0 --- /dev/null +++ b/src/features/applications/data/application-transaction-history.ts @@ -0,0 +1,68 @@ +import { ApplicationId } from './types' +import { indexer } from '@/features/common/data' +import { TransactionResult, TransactionSearchResults } from '@algorandfoundation/algokit-utils/types/indexer' +import { useMemo } from 'react' +import { JotaiStore } from '@/features/common/data/types' +import { createTransactionsAtom, transactionResultsAtom } from '@/features/transactions/data' +import { atomEffect } from 'jotai-effect' +import { atom, useStore } from 'jotai' + +const fetchApplicationTransactionResults = async (applicationID: ApplicationId, pageSize: number, nextPageToken?: string) => { + const results = (await indexer + .searchForTransactions() + .applicationID(applicationID) + .nextToken(nextPageToken ?? '') + .limit(pageSize) + .do()) as TransactionSearchResults + return { + transactionResults: results.transactions, + nextPageToken: results['next-token'], + } as const +} + +const createSyncEffect = (transactionResults: TransactionResult[]) => { + return atomEffect((_, set) => { + ;(async () => { + try { + set(transactionResultsAtom, (prev) => { + const next = new Map(prev) + transactionResults.forEach((transactionResult) => { + if (!next.has(transactionResult.id)) { + next.set(transactionResult.id, atom(transactionResult)) + } + }) + return next + }) + } catch (e) { + // Ignore any errors as there is nothing to sync + } + })() + }) +} + +const createApplicationTransactionsAtom = (store: JotaiStore, applicationID: ApplicationId, pageSize: number, nextPageToken?: string) => { + return atom(async (get) => { + const { transactionResults, nextPageToken: newNextPageToken } = await fetchApplicationTransactionResults( + applicationID, + pageSize, + nextPageToken + ) + + get(createSyncEffect(transactionResults)) + + const transactions = await get(createTransactionsAtom(store, transactionResults)) + + return { + rows: transactions, + nextPageToken: newNextPageToken, + } + }) +} + +export const useFetchNextApplicationTransactionsPage = (applicationID: ApplicationId) => { + const store = useStore() + + return useMemo(() => { + return (pageSize: number, nextPageToken?: string) => createApplicationTransactionsAtom(store, applicationID, pageSize, nextPageToken) + }, [store, applicationID]) +} diff --git a/src/features/applications/utils/application-transactions-table-columns.tsx b/src/features/applications/utils/application-transactions-table-columns.tsx new file mode 100644 index 000000000..933584483 --- /dev/null +++ b/src/features/applications/utils/application-transactions-table-columns.tsx @@ -0,0 +1,55 @@ +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 { TransactionLink } from '@/features/transactions/components/transaction-link' +import { asTo } from '@/features/common/mappers/to' +import { InnerTransactionLink } from '@/features/transactions/components/inner-transaction-link' + +const indentationWidth = 20 + +export const applicationTransactionsTableColumns: ColumnDef[] = [ + { + header: 'Transaction Id', + accessorFn: (transaction) => transaction, + cell: ({ row, getValue }) => { + const transaction = getValue() + return ( +
+ {'innerId' in transaction ? ( + + ) : ( + + )} +
+ ) + }, + }, + { + header: 'Round', + accessorKey: 'confirmedRound', + }, + { + accessorKey: 'sender', + header: 'From', + cell: (c) => ellipseAddress(c.getValue()), + }, + { + header: 'To', + accessorFn: asTo, + }, + { + header: 'Fee', + accessorFn: (transaction) => transaction, + cell: (c) => { + const transaction = c.getValue() + if (transaction.type === TransactionType.ApplicationCall) + return + }, + }, +] diff --git a/src/features/applications/utils/get-application-transactions-table-sub-rows.ts b/src/features/applications/utils/get-application-transactions-table-sub-rows.ts new file mode 100644 index 000000000..3988c93fb --- /dev/null +++ b/src/features/applications/utils/get-application-transactions-table-sub-rows.ts @@ -0,0 +1,13 @@ +import { Transaction, InnerTransaction, TransactionType } from '@/features/transactions/models' +import { flattenInnerTransactions } from '@/utils/flatten-inner-transactions' + +export const getApplicationTransactionsTableSubRows = (applicationId: number, 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: txn }) => txn.type === TransactionType.ApplicationCall && txn.applicationId === applicationId) + }) +} diff --git a/src/features/assets/components/asset-live-transactions.tsx b/src/features/assets/components/asset-live-transactions.tsx index e9f78223e..0d4e3d039 100644 --- a/src/features/assets/components/asset-live-transactions.tsx +++ b/src/features/assets/components/asset-live-transactions.tsx @@ -3,28 +3,22 @@ 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' +import { InnerTransaction, Transaction } from '@/features/transactions/models' +import { getAssetTransactionsTableSubRows } from '../utils/get-asset-transactions-table-sub-rows' 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) - }) + const filter = useCallback( + (transactionResult: TransactionResult) => { + const assetIdsForTransaction = getAssetIdsForTransaction(transactionResult) + return assetIdsForTransaction.includes(assetId) }, [assetId] ) - return + const getSubRows = useCallback((row: Transaction | InnerTransaction) => getAssetTransactionsTableSubRows(assetId, row), [assetId]) + return } diff --git a/src/features/assets/components/asset-transaction-history.tsx b/src/features/assets/components/asset-transaction-history.tsx index 3b3df1f9d..9e38ee7cd 100644 --- a/src/features/assets/components/asset-transaction-history.tsx +++ b/src/features/assets/components/asset-transaction-history.tsx @@ -2,6 +2,9 @@ import { LazyLoadDataTable } from '@/features/common/components/lazy-load-data-t import { AssetId } from '../data/types' import { useFetchNextAssetTransactionsPage } from '../data/asset-transaction-history' import { assetTransactionsTableColumns } from '../utils/asset-transactions-table-columns' +import { Transaction, InnerTransaction } from '@/features/transactions/models' +import { useCallback } from 'react' +import { getAssetTransactionsTableSubRows } from '../utils/get-asset-transactions-table-sub-rows' type Props = { assetId: AssetId @@ -9,6 +12,7 @@ type Props = { export function AssetTransactionHistory({ assetId }: Props) { const fetchNextPage = useFetchNextAssetTransactionsPage(assetId) + const getSubRows = useCallback((row: Transaction | InnerTransaction) => getAssetTransactionsTableSubRows(assetId, row), [assetId]) - return + return } diff --git a/src/features/assets/data/asset-transaction-history.ts b/src/features/assets/data/asset-transaction-history.ts index 7f6825176..f5f41078e 100644 --- a/src/features/assets/data/asset-transaction-history.ts +++ b/src/features/assets/data/asset-transaction-history.ts @@ -6,7 +6,6 @@ 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 @@ -48,10 +47,9 @@ 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: transactionsForAsset, + rows: transactions, nextPageToken: newNextPageToken, } }) diff --git a/src/features/assets/utils/asset-transactions-table-columns.tsx b/src/features/assets/utils/asset-transactions-table-columns.tsx index 454e40b40..6b4655440 100644 --- a/src/features/assets/utils/asset-transactions-table-columns.tsx +++ b/src/features/assets/utils/asset-transactions-table-columns.tsx @@ -1,29 +1,30 @@ 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' +import { InnerTransactionLink } from '@/features/transactions/components/inner-transaction-link' +const indentationWidth = 20 export const assetTransactionsTableColumns: ColumnDef[] = [ { header: 'Transaction Id', accessorFn: (transaction) => transaction, - cell: (c) => { - const transaction = c.getValue() - return 'innerId' in transaction ? ( - { + const transaction = getValue() + return ( +
- {ellipseId(transaction.id)} - (Inner) - - ) : ( - + {'innerId' in transaction ? ( + + ) : ( + + )} +
) }, }, @@ -49,7 +50,6 @@ export const assetTransactionsTableColumns: ColumnDef 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 deleted file mode 100644 index 4ab154bad..000000000 --- a/src/features/assets/utils/extract-transactions-for-asset.ts +++ /dev/null @@ -1,20 +0,0 @@ -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/assets/utils/get-asset-transactions-table-sub-rows.ts b/src/features/assets/utils/get-asset-transactions-table-sub-rows.ts new file mode 100644 index 000000000..b2ddf54b0 --- /dev/null +++ b/src/features/assets/utils/get-asset-transactions-table-sub-rows.ts @@ -0,0 +1,20 @@ +import { Transaction, InnerTransaction, TransactionType } from '@/features/transactions/models' +import { flattenInnerTransactions } from '@/utils/flatten-inner-transactions' +import { AssetId } from '../data/types' + +export const getAssetTransactionsTableSubRows = (assetId: AssetId, 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.type === TransactionType.AssetTransfer && transaction.asset.id === assetId) || + (transaction.type === TransactionType.AssetConfig && transaction.assetId === assetId) || + (transaction.type === TransactionType.AssetFreeze && transaction.assetId === assetId) + ) + }) + }) +} diff --git a/src/features/common/components/lazy-load-data-table/lazy-load-data-table.tsx b/src/features/common/components/lazy-load-data-table/lazy-load-data-table.tsx index 1a6d8c4d2..e3e60b345 100644 --- a/src/features/common/components/lazy-load-data-table/lazy-load-data-table.tsx +++ b/src/features/common/components/lazy-load-data-table/lazy-load-data-table.tsx @@ -1,4 +1,4 @@ -import { ColumnDef, flexRender, getCoreRowModel, useReactTable } from '@tanstack/react-table' +import { ColumnDef, flexRender, getCoreRowModel, getExpandedRowModel, useReactTable } from '@tanstack/react-table' import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/features/common/components/table' import { useCallback, useMemo, useState } from 'react' import { Atom } from 'jotai' @@ -9,9 +9,10 @@ import { Loader2 as Loader } from 'lucide-react' interface Props { columns: ColumnDef[] fetchNextPage: (pageSize: number, nextPageToken?: string) => Atom>> + getSubRows?: (row: TData) => TData[] } -export function LazyLoadDataTable({ columns, fetchNextPage }: Props) { +export function LazyLoadDataTable({ columns, fetchNextPage, getSubRows }: Props) { const [pageSize, setPageSize] = useState(10) const { useLoadablePage } = useMemo( () => @@ -41,8 +42,13 @@ export function LazyLoadDataTable({ columns, fetchNextPage }: Pro const table = useReactTable({ data: page?.rows ?? [], + state: { + expanded: true, + }, + getSubRows: getSubRows, columns, getCoreRowModel: getCoreRowModel(), + getExpandedRowModel: getExpandedRowModel(), manualPagination: true, }) diff --git a/src/features/transactions/components/live-transactions-table.tsx b/src/features/transactions/components/live-transactions-table.tsx index b19262228..ebd4a2186 100644 --- a/src/features/transactions/components/live-transactions-table.tsx +++ b/src/features/transactions/components/live-transactions-table.tsx @@ -1,26 +1,30 @@ -import { ColumnDef, flexRender, getCoreRowModel, useReactTable } from '@tanstack/react-table' +import { ColumnDef, flexRender, getCoreRowModel, getExpandedRowModel, 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> + filter: (transactionResult: TransactionResult) => boolean + getSubRows?: (row: Transaction | InnerTransaction) => InnerTransaction[] } -export function LiveTransactionsTable({ mapper, columns }: Props) { +export function LiveTransactionsTable({ filter, columns, getSubRows }: Props) { const [maxRows, setMaxRows] = useState(10) - const transactions = useLiveTransactions(mapper, maxRows) + const transactions = useLiveTransactions(filter, maxRows) const table = useReactTable({ data: transactions, columns, getCoreRowModel: getCoreRowModel(), manualPagination: true, + getSubRows: getSubRows, + getExpandedRowModel: getExpandedRowModel(), + state: { + expanded: true, + }, }) return ( diff --git a/src/features/transactions/data/live-transaction.ts b/src/features/transactions/data/live-transaction.ts index effa69d7e..28f790cd4 100644 --- a/src/features/transactions/data/live-transaction.ts +++ b/src/features/transactions/data/live-transaction.ts @@ -1,16 +1,12 @@ import { useMemo } from 'react' -import { getTransactionResultAtom, liveTransactionIdsAtom } from '@/features/transactions/data' +import { createTransactionAtom, 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 { 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 -) => { +export const useLiveTransactions = (filter: (transactionResult: TransactionResult) => boolean, maxRows: number) => { const store = useStore() const { liveTransactionsAtomEffect, liveTransactionsAtom } = useMemo(() => { @@ -33,9 +29,9 @@ export const useLiveTransactions = ( } syncedTransactionId = liveTransactionIds[0] - const newTransactions = ( - await Promise.all(newTransactionResults.map((transactionResult) => get(mapper(store, transactionResult)))) - ).flat() + const newTransactions = await Promise.all( + newTransactionResults.filter(filter).map(async (transactionResult) => await get(createTransactionAtom(store, transactionResult))) + ) if (newTransactions.length) { set(liveTransactionsAtom, (prev) => { return newTransactions.concat(prev).slice(0, maxRows) @@ -48,7 +44,7 @@ export const useLiveTransactions = ( liveTransactionsAtomEffect, liveTransactionsAtom, } - }, [store, mapper, maxRows]) + }, [store, filter, maxRows]) useAtom(liveTransactionsAtomEffect) diff --git a/src/features/transactions/mappers/app-call-transaction-mappers.ts b/src/features/transactions/mappers/app-call-transaction-mappers.ts index 7af24872b..0e2f5e0e3 100644 --- a/src/features/transactions/mappers/app-call-transaction-mappers.ts +++ b/src/features/transactions/mappers/app-call-transaction-mappers.ts @@ -22,7 +22,9 @@ const mapCommonAppCallTransactionProperties = ( return { ...mapCommonTransactionProperties(transactionResult), type: TransactionType.ApplicationCall, - applicationId: transactionResult['application-transaction']['application-id'], + applicationId: transactionResult['application-transaction']['application-id'] + ? transactionResult['application-transaction']['application-id'] + : transactionResult['created-application-index']!, applicationArgs: transactionResult['application-transaction']['application-args'] ?? [], applicationAccounts: transactionResult['application-transaction'].accounts ?? [], foreignApps: transactionResult['application-transaction']['foreign-apps'] ?? [],