Skip to content

Commit e93c5c4

Browse files
authored
chore: refactor assets based on latest discussions (#26)
1 parent 68ca8a0 commit e93c5c4

File tree

16 files changed

+237
-279
lines changed

16 files changed

+237
-279
lines changed

src/features/assets/data/asset.ts

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import { indexer } from '@/features/common/data'
2+
import { AssetLookupResult } from '@algorandfoundation/algokit-utils/types/indexer'
3+
import { atom, useAtomValue, useStore } from 'jotai'
4+
import { JotaiStore } from '@/features/common/data/types'
5+
import { atomEffect } from 'jotai-effect'
6+
import { AssetIndex, assetsAtom } from '.'
7+
import { useMemo } from 'react'
8+
import { loadable } from 'jotai/utils'
9+
import { asAsset } from '../mappers'
10+
11+
const fetchAssetResultAtomBuilder = (assetIndex: AssetIndex) =>
12+
atom(async (_get) => {
13+
return await indexer
14+
.lookupAssetByID(assetIndex)
15+
.includeAll(true)
16+
.do()
17+
.then((result) => {
18+
return (result as AssetLookupResult).asset
19+
})
20+
})
21+
22+
export const getAssetAtomBuilder = (store: JotaiStore, assetIndex: AssetIndex) => {
23+
const fetchAssetResultAtom = fetchAssetResultAtomBuilder(assetIndex)
24+
25+
const syncEffect = atomEffect((get, set) => {
26+
;(async () => {
27+
try {
28+
const assetResult = await get(fetchAssetResultAtom)
29+
set(assetsAtom, (prev) => {
30+
return prev.set(assetResult.index, assetResult)
31+
})
32+
} catch (e) {
33+
// Ignore any errors as there is nothing to sync
34+
}
35+
})()
36+
})
37+
38+
return atom(async (get) => {
39+
const assets = store.get(assetsAtom)
40+
const asset = assets.get(assetIndex)
41+
if (asset) {
42+
return asAsset(asset)
43+
}
44+
45+
get(syncEffect)
46+
47+
const assetResult = await get(fetchAssetResultAtom)
48+
return asAsset(assetResult)
49+
})
50+
}
51+
52+
export const getAssetsAtomBuilder = (store: JotaiStore, assetIndexes: AssetIndex[]) => {
53+
return atom((get) => {
54+
return Promise.all(assetIndexes.map((assetIndex) => get(getAssetAtomBuilder(store, assetIndex))))
55+
})
56+
}
57+
58+
export const useAssetAtom = (assetIndex: AssetIndex) => {
59+
const store = useStore()
60+
return useMemo(() => {
61+
return getAssetAtomBuilder(store, assetIndex)
62+
}, [store, assetIndex])
63+
}
64+
65+
export const useLoadableAsset = (assetIndex: AssetIndex) => {
66+
return useAtomValue(loadable(useAssetAtom(assetIndex)))
67+
}

src/features/assets/data/index.ts

Lines changed: 18 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -1,82 +1,23 @@
1-
import { atom, useAtomValue, useStore } from 'jotai'
2-
import { useMemo } from 'react'
3-
import { AssetLookupResult, AssetResult } from '@algorandfoundation/algokit-utils/types/indexer'
4-
import { atomEffect } from 'jotai-effect'
5-
import { loadable } from 'jotai/utils'
6-
import { JotaiStore } from '@/features/common/data/types'
7-
import { indexer } from '@/features/common/data'
1+
import { atom } from 'jotai'
2+
import { AssetResult } from '@algorandfoundation/algokit-utils/types/indexer'
83
import { AssetIndex } from './types'
94
import { ZERO_ADDRESS } from '@/features/common/constants'
10-
import { asError, is404 } from '@/utils/error'
11-
12-
const deletedAssetBuilder = (assetIndex: AssetIndex) => {
13-
return {
14-
index: assetIndex,
15-
deleted: true,
16-
params: {
17-
creator: ZERO_ADDRESS,
18-
decimals: 0,
19-
total: 0,
20-
name: 'DELETED',
21-
'unit-name': 'DELETED',
22-
},
23-
} as AssetResult
24-
}
5+
export * from './types'
6+
export * from './asset'
257

268
// TODO: Size should be capped at some limit, so memory usage doesn't grow indefinitely
27-
export const assetsAtom = atom<Map<AssetIndex, AssetResult>>(new Map())
28-
29-
export const fetchAssetAtomBuilder = (store: JotaiStore, assetIndex: AssetIndex) => {
30-
const syncEffect = atomEffect((get, set) => {
31-
;(async () => {
32-
try {
33-
const asset = await get(assetAtom)
34-
set(assetsAtom, (prev) => {
35-
return new Map([...prev, [asset.index, asset]])
36-
})
37-
} catch (e) {
38-
// Ignore any errors as there is nothing to sync
39-
}
40-
})()
41-
})
42-
const assetAtom = atom((get) => {
43-
const assets = store.get(assetsAtom)
44-
const cachedAsset = assets.get(assetIndex)
45-
if (cachedAsset) {
46-
return cachedAsset
47-
}
48-
49-
get(syncEffect)
50-
51-
return indexer
52-
.lookupAssetByID(assetIndex)
53-
.do()
54-
.then((result) => {
55-
return (result as AssetLookupResult).asset
56-
})
57-
.catch((e: unknown) => {
58-
if (is404(asError(e))) {
59-
return deletedAssetBuilder(assetIndex)
60-
}
61-
throw e
62-
})
63-
})
64-
return assetAtom
65-
}
66-
67-
export const fetchAssetsAtomBuilder = (store: JotaiStore, assetIndexes: AssetIndex[]) => {
68-
return atom(async (get) => {
69-
return await Promise.all(assetIndexes.map((assetIndex) => get(fetchAssetAtomBuilder(store, assetIndex))))
70-
})
71-
}
72-
73-
export const useAssetAtom = (assetIndex: AssetIndex) => {
74-
const store = useStore()
75-
return useMemo(() => {
76-
return fetchAssetAtomBuilder(store, assetIndex)
77-
}, [store, assetIndex])
78-
}
799

80-
export const useLoadableAsset = (assetIndex: AssetIndex) => {
81-
return useAtomValue(loadable(useAssetAtom(assetIndex)))
82-
}
10+
export const algoAssetResult = {
11+
index: 0,
12+
'created-at-round': 0,
13+
params: {
14+
creator: ZERO_ADDRESS,
15+
decimals: 6,
16+
total: 10_000_000_000,
17+
name: 'ALGO',
18+
'unit-name': 'ALGO',
19+
url: 'https://www.algorand.foundation',
20+
},
21+
} as AssetResult
22+
23+
export const assetsAtom = atom<Map<AssetIndex, AssetResult>>(new Map([[algoAssetResult.index, algoAssetResult]]))
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import { AssetResult } from '@algorandfoundation/algokit-utils/types/indexer'
2-
import { AssetModel } from '../models'
2+
import { Asset } from '../models'
33

4-
export const asAsset = (assetResult: AssetResult): AssetModel => {
4+
export const asAsset = (assetResult: AssetResult): Asset => {
55
return {
66
id: assetResult.index,
77
name: assetResult.params.name,
88
total: assetResult.params.total,
99
decimals: assetResult.params.decimals,
1010
unitName: assetResult.params['unit-name'],
11+
clawback: assetResult.params.clawback,
1112
}
1213
}
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
export type AssetModel = {
1+
export type Asset = {
22
id: number
33
name?: string
44
total: number | bigint
55
decimals: number | bigint
66
unitName?: string
7+
clawback?: string
78
}

src/features/common/components/display-asset-amount.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
import { AssetModel } from '@/features/assets/models'
1+
import { Asset } from '@/features/assets/models'
22
import { cn } from '../utils'
33
import Decimal from 'decimal.js'
44

55
type Props = {
66
amount: number | bigint
7-
asset: AssetModel
7+
asset: Asset
88
className?: string
99
}
1010

src/features/transactions/components/transaction-view-visual.test.tsx

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { asAppCallTransaction, asAssetTransferTransaction, asPaymentTransaction
77
import { AssetResult, TransactionResult } from '@algorandfoundation/algokit-utils/types/indexer'
88
import { assetResultMother } from '@/tests/object-mother/asset-result'
99
import { useParams } from 'react-router-dom'
10+
import { asAsset } from '@/features/assets/mappers'
1011

1112
// This file maintain the snapshot test for the TransactionViewVisual component
1213
// To add new test case:
@@ -24,15 +25,15 @@ describe('payment-transaction-view-visual', () => {
2425
describe.each([
2526
transactionResultMother['mainnet-FBORGSDC4ULLWHWZUMUFIYQLSDC26HGLTFD7EATQDY37FHCIYBBQ']().build(),
2627
transactionResultMother['mainnet-ILDCD5Z64CYSLEZIHBG5DVME2ITJI2DIVZAPDPEWPCYMTRA5SVGA']().build(),
27-
])('when rendering transaction $id', (transaction: TransactionResult) => {
28+
])('when rendering transaction $id', (transactionResult: TransactionResult) => {
2829
it('should match snapshot', () => {
29-
const model = asPaymentTransaction(transaction)
30+
const model = asPaymentTransaction(transactionResult)
3031

3132
return executeComponentTest(
3233
() => render(<TransactionViewVisual transaction={model} />),
3334
async (component) => {
3435
expect(prettyDOM(component.container, prettyDomMaxLength, { highlight: false })).toMatchFileSnapshot(
35-
`__snapshots__/payment-transaction-view-visual.${transaction.id}.html`
36+
`__snapshots__/payment-transaction-view-visual.${transactionResult.id}.html`
3637
)
3738
}
3839
)
@@ -54,7 +55,7 @@ describe('asset-transfer-transaction-view-visual', () => {
5455
'when rendering transaction $transactionResult.id',
5556
({ transactionResult, assetResult }: { transactionResult: TransactionResult; assetResult: AssetResult }) => {
5657
it('should match snapshot', () => {
57-
const transaction = asAssetTransferTransaction(transactionResult, assetResult)
58+
const transaction = asAssetTransferTransaction(transactionResult, asAsset(assetResult))
5859

5960
return executeComponentTest(
6061
() => render(<TransactionViewVisual transaction={transaction} />),
@@ -89,7 +90,7 @@ describe('application-call-view-visual', () => {
8990
it('should match snapshot', () => {
9091
vi.mocked(useParams).mockImplementation(() => ({ transactionId: transactionResult.id }))
9192

92-
const model = asAppCallTransaction(transactionResult, assetResults)
93+
const model = asAppCallTransaction(transactionResult, assetResults.map(asAsset))
9394

9495
return executeComponentTest(
9596
() => render(<TransactionViewVisual transaction={model} />),

src/features/transactions/data/index.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { TransactionId } from './types'
1111
import { indexer, algod } from '@/features/common/data'
1212
import { JotaiStore } from '@/features/common/data/types'
1313
import { asTransactionModel } from '../mappers/transaction-mappers'
14-
import { fetchAssetAtomBuilder, fetchAssetsAtomBuilder } from '@/features/assets/data'
14+
import { getAssetAtomBuilder, getAssetsAtomBuilder } from '@/features/assets/data'
1515
import { InnerTransactionModel, TransactionModel, TransactionType as TransactionTypeModel } from '../models'
1616

1717
// TODO: Size should be capped at some limit, so memory usage doesn't grow indefinitely
@@ -79,8 +79,8 @@ export const fetchTransactionsModelAtomBuilder = (
7979
)
8080

8181
const assets = new Map(
82-
(await get(fetchAssetsAtomBuilder(store, assetIds))).map((a) => {
83-
return [a.index, a] as const
82+
(await get(getAssetsAtomBuilder(store, assetIds))).map((a) => {
83+
return [a.id, a] as const
8484
})
8585
)
8686

@@ -98,22 +98,22 @@ export const fetchTransactionsModelAtomBuilder = (
9898

9999
export const fetchTransactionModelAtomBuilder = (
100100
store: JotaiStore,
101-
transaction: TransactionResult | Atom<TransactionResult | Promise<TransactionResult>>
101+
transactionResult: TransactionResult | Atom<TransactionResult | Promise<TransactionResult>>
102102
) => {
103103
return atom(async (get) => {
104-
const txn = 'id' in transaction ? transaction : await get(transaction)
105-
return await asTransactionModel(txn, (assetId: number) => get(fetchAssetAtomBuilder(store, assetId)))
104+
const txn = 'id' in transactionResult ? transactionResult : await get(transactionResult)
105+
return await asTransactionModel(txn, (assetId: number) => get(getAssetAtomBuilder(store, assetId)))
106106
})
107107
}
108108

109109
export const fetchInnerTransactionModelAtomBuilder = (
110110
store: JotaiStore,
111-
transaction: TransactionResult | Atom<TransactionResult | Promise<TransactionResult>>,
111+
transactionResult: TransactionResult | Atom<TransactionResult | Promise<TransactionResult>>,
112112
innerId: string
113113
) => {
114114
return atom(async (get) => {
115-
const txn = 'id' in transaction ? transaction : await get(transaction)
116-
const transactionModel = await asTransactionModel(txn, (assetId: number) => get(fetchAssetAtomBuilder(store, assetId)))
115+
const txn = 'id' in transactionResult ? transactionResult : await get(transactionResult)
116+
const transactionModel = await asTransactionModel(txn, (assetId: number) => get(getAssetAtomBuilder(store, assetId)))
117117
if (transactionModel.type !== TransactionTypeModel.ApplicationCall) {
118118
throw new Error('Only application call transactions have inner transactions')
119119
}

0 commit comments

Comments
 (0)