Skip to content

Commit 68074c9

Browse files
p2arthurneilcampbell
authored andcommitted
feat: add support for rendering arc62 circulating supply of an asset
1 parent 38a7830 commit 68074c9

File tree

18 files changed

+381
-15
lines changed

18 files changed

+381
-15
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ dist
1212
dist-ssr
1313
*.local
1414
.env
15+
.npmrc
1516

1617
# Editor directories and files
1718
.DS_Store

.vscode/settings.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,8 @@
1111
},
1212
"[rust]": {
1313
"editor.defaultFormatter": "rust-lang.rust-analyzer"
14+
},
15+
"[typescriptreact]": {
16+
"editor.defaultFormatter": "esbenp.prettier-vscode"
1417
}
1518
}

package.json

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -102,10 +102,16 @@
102102
"zod-form-data": "^2.0.2"
103103
},
104104
"devDependencies": {
105+
"@commitlint/cli": "^18.4.3",
106+
"@commitlint/config-conventional": "^18.4.3",
105107
"@eslint/js": "^9.15.0",
106108
"@makerx/prettier-config": "^2.0.0",
107109
"@makerx/ts-config": "^1.0.1",
108110
"@makerx/ts-dossier": "^3.0.1",
111+
"@semantic-release/changelog": "^6.0.3",
112+
"@semantic-release/exec": "^6.0.3",
113+
"@semantic-release/git": "^10.0.1",
114+
"@semantic-release/npm": "^12.0.2",
109115
"@tauri-apps/cli": "^2.0.0",
110116
"@testing-library/jest-dom": "^6.5.0",
111117
"@testing-library/react": "^14.2.2",
@@ -136,15 +142,9 @@
136142
"tailwindcss": "^3.4.1",
137143
"typescript": "^5.6.3",
138144
"typescript-eslint": "^8.15.0",
139-
"@semantic-release/changelog": "^6.0.3",
140145
"vite": "^7.0.5",
141146
"vite-plugin-node-polyfills": "^0.24.0",
142-
"vitest": "^3.2.4",
143-
"@semantic-release/exec": "^6.0.3",
144-
"@semantic-release/git": "^10.0.1",
145-
"@semantic-release/npm": "^12.0.2",
146-
"@commitlint/cli": "^18.4.3",
147-
"@commitlint/config-conventional": "^18.4.3"
147+
"vitest": "^3.2.4"
148148
},
149149
"overrides": {
150150
"ws@>7.0.0 <7.5.9": "7.5.10",
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { algorandClient } from '@/features/common/data/algo-client'
2+
import {
3+
betanetId,
4+
localnetId,
5+
mainnetId,
6+
testnetId,
7+
fnetId,
8+
BETANET_FEE_SINK_ADDRESS,
9+
FNET_FEE_SINK_ADDRESS,
10+
MAINNET_FEE_SINK_ADDRESS,
11+
selectedNetworkAtomId,
12+
TESTNET_FEE_SINK_ADDRESS,
13+
} from '@/features/network/data'
14+
import { settingsStore } from '@/features/settings/data'
15+
import { atom } from 'jotai'
16+
import { Address } from './types'
17+
18+
export const fundedAccountAtom = atom<Promise<Address | undefined>>(async () => {
19+
const selectedNetworkId = settingsStore.get(selectedNetworkAtomId)
20+
if (selectedNetworkId === mainnetId) {
21+
return MAINNET_FEE_SINK_ADDRESS
22+
}
23+
if (selectedNetworkId === testnetId) {
24+
return TESTNET_FEE_SINK_ADDRESS
25+
}
26+
if (selectedNetworkId === betanetId) {
27+
return BETANET_FEE_SINK_ADDRESS
28+
}
29+
if (selectedNetworkId === fnetId) {
30+
return FNET_FEE_SINK_ADDRESS
31+
}
32+
if (selectedNetworkId === localnetId) {
33+
return (await algorandClient.account.localNetDispenser()).addr.toString()
34+
}
35+
36+
return undefined
37+
})
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export * from './account'
22
export * from './account-result'
3+
export * from './funded-account'

src/features/assets/components/asset-details.tsx

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import {
2727
assetActivityLabel,
2828
assetUnitNameLabel,
2929
assetUrlLabel,
30+
circulatingSupplyLabel,
3031
} from './labels'
3132
import { Badge } from '@/features/common/components/badge'
3233
import { AssetMedia } from './asset-media'
@@ -85,6 +86,12 @@ export function AssetDetails({ asset }: Props) {
8586
dt: assetTotalSupplyLabel,
8687
dd: `${new Decimal(asset.total.toString()).div(new Decimal(10).pow(asset.decimals))} ${asset.unitName ?? ''}`,
8788
},
89+
asset.circulatingSupply !== undefined
90+
? {
91+
dt: circulatingSupplyLabel,
92+
dd: `${new Decimal(asset.circulatingSupply.toString()).div(new Decimal(10).pow(asset.decimals))} ${asset.unitName ?? ''}`,
93+
}
94+
: undefined,
8895
{
8996
dt: assetDecimalsLabel,
9097
dd: asset.decimals,
@@ -109,7 +116,18 @@ export function AssetDetails({ asset }: Props) {
109116
}
110117
: undefined,
111118
],
112-
[asset.id, asset.name, asset.standardsUsed, asset.type, asset.unitName, asset.total, asset.decimals, asset.defaultFrozen, asset.url]
119+
[
120+
asset.id,
121+
asset.name,
122+
asset.standardsUsed,
123+
asset.type,
124+
asset.unitName,
125+
asset.total,
126+
asset.decimals,
127+
asset.defaultFrozen,
128+
asset.url,
129+
asset.circulatingSupply,
130+
]
113131
).filter(isDefined)
114132

115133
const assetAddresses = useMemo(

src/features/assets/components/labels.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,5 @@ export const assetLiveTransactionsTabId = 'live-transactions'
2626
export const assetLiveTransactionsTabLabel = 'Live Transactions'
2727
export const assetHistoricalTransactionsTabId = 'historical-transactions'
2828
export const assetHistoricalTransactionsTabLabel = 'Historical Transactions'
29+
30+
export const circulatingSupplyLabel = 'Circulating Supply'

src/features/assets/data/asset-metadata.ts

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { flattenTransactionResult } from '@/features/transactions/utils/flatten-transaction-result'
22
import { TransactionType } from 'algosdk'
3-
import { Arc3MetadataResult, Arc69MetadataResult, AssetMetadataResult, AssetResult } from './types'
3+
import { Arc3MetadataResult, Arc62MetadataResult, Arc69MetadataResult, AssetMetadataResult, AssetResult } from './types'
44
import { getArc19Url, isArc19Url } from '../utils/arc19'
55
import { getArc3Url, isArc3Url } from '../utils/arc3'
66
import { base64ToUtf8 } from '@/utils/base64-to-utf8'
@@ -14,6 +14,8 @@ import { TransactionResult } from '@/features/transactions/data/types'
1414
import algosdk from 'algosdk'
1515
import { uint8ArrayToBase64 } from '@/utils/uint8-array-to-base64'
1616
import { indexerTransactionToTransactionResult } from '@/features/transactions/mappers/indexer-transaction-mappers'
17+
import { getArc62AppId } from '../utils/arc62'
18+
import { createAssetCirculatingSupplyAtom } from './circulating-supply'
1719

1820
// Currently, we support ARC-3, 19 and 69. Their specs can be found here https://github.com/algorandfoundation/ARCs/tree/main/ARCs
1921
// ARCs are community standard, therefore, there are edge cases
@@ -24,10 +26,12 @@ import { indexerTransactionToTransactionResult } from '@/features/transactions/m
2426
// - ARC-19 doesn't specify the metadata format but generally people use the ARC-3 format
2527
const createAssetMetadataResult = async (
2628
assetResult: AssetResult,
29+
get: Getter,
2730
latestAssetCreateOrReconfigureTransaction?: TransactionResult
2831
): Promise<AssetMetadataResult> => {
2932
let arc69MetadataResult: Arc69MetadataResult | undefined = undefined
3033
let arc3MetadataResult: Arc3MetadataResult | undefined = undefined
34+
let arc62MetadataResult: Arc62MetadataResult | undefined = undefined
3135

3236
// Get ARC-69 metadata if applicable
3337
if (latestAssetCreateOrReconfigureTransaction && latestAssetCreateOrReconfigureTransaction.note) {
@@ -59,6 +63,17 @@ const createAssetMetadataResult = async (
5963
metadata_url: gatewayMetadataUrl,
6064
metadata,
6165
}
66+
67+
const arc62AppId = getArc62AppId(arc3MetadataResult)
68+
69+
if (arc62AppId) {
70+
const circulatingSupply = await get(createAssetCirculatingSupplyAtom(arc62AppId, assetResult.index))
71+
if (circulatingSupply !== undefined) {
72+
arc62MetadataResult = {
73+
circulatingSupply,
74+
}
75+
}
76+
}
6277
} catch (error) {
6378
if (error instanceof SyntaxError) {
6479
// If the metadata url points to an image or video, we construct the faux arc3 metadata, so we can still display the image.
@@ -88,10 +103,11 @@ const createAssetMetadataResult = async (
88103
}
89104
}
90105

91-
if (arc3MetadataResult || arc69MetadataResult) {
106+
if (arc3MetadataResult || arc69MetadataResult || arc62MetadataResult) {
92107
return {
93108
arc3: arc3MetadataResult,
94109
arc69: arc69MetadataResult,
110+
arc62: arc62MetadataResult,
95111
}
96112
}
97113

@@ -110,7 +126,7 @@ const noteToArc69Metadata = (note: string | undefined) => {
110126
return undefined
111127
}
112128

113-
const getAssetMetadataResult = async (_: Getter, __: Setter, assetResult: AssetResult) => {
129+
const getAssetMetadataResult = async (get: Getter, __: Setter, assetResult: AssetResult) => {
114130
if (assetResult.index === 0n) {
115131
return null
116132
}
@@ -152,7 +168,7 @@ const getAssetMetadataResult = async (_: Getter, __: Setter, assetResult: AssetR
152168
return null
153169
}
154170

155-
return await createAssetMetadataResult(assetResult, assetConfigTransactionResults[0])
171+
return await createAssetMetadataResult(assetResult, get, assetConfigTransactionResults[0])
156172
}
157173

158174
export const [assetMetadataResultsAtom, getAssetMetadataResultAtom] = readOnlyAtomCache(
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import algosdk from 'algosdk'
2+
import { atom } from 'jotai'
3+
import { getApplicationResultAtom } from '@/features/applications/data'
4+
import { fundedAccountAtom } from '@/features/accounts/data'
5+
import { algorandClient } from '@/features/common/data/algo-client'
6+
7+
export const createAssetCirculatingSupplyAtom = (applicationId: bigint, assetId: bigint) => {
8+
return atom(async (get) => {
9+
const app = await get(getApplicationResultAtom(applicationId))
10+
if (!app) return undefined
11+
12+
const fundedAddress = await get(fundedAccountAtom)
13+
if (!fundedAddress) return undefined
14+
15+
const result = await algorandClient
16+
.newGroup()
17+
.addAppCallMethodCall({
18+
appId: applicationId,
19+
method: algosdk.ABIMethod.fromSignature('arc62_get_circulating_supply(uint64)uint64'),
20+
args: [assetId],
21+
sender: fundedAddress,
22+
})
23+
.simulate({ skipSignatures: true, allowUnnamedResources: true })
24+
25+
const returnValue = result?.returns?.[0]?.returnValue
26+
if (returnValue == null) return undefined
27+
if (typeof returnValue === 'bigint') return returnValue
28+
if (typeof returnValue === 'string' || typeof returnValue === 'number') return BigInt(returnValue)
29+
return undefined
30+
})
31+
}

src/features/assets/data/types.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ export type Arc3MetadataResult = {
3030
animation_url_mimetype?: string
3131
properties?: Arc16MetadataProperties
3232
extra_metadata?: string
33+
['arc-62']?: Arc62MetadataResult
3334
[key: string]: unknown
3435
}
3536
}
@@ -51,7 +52,12 @@ export type Arc69MetadataResult = {
5152
}
5253
}
5354

55+
export type Arc62MetadataResult = {
56+
circulatingSupply: bigint
57+
}
58+
5459
export type AssetMetadataResult = {
5560
arc3?: Arc3MetadataResult
5661
arc69?: Arc69MetadataResult
62+
arc62?: Arc62MetadataResult
5763
} | null

0 commit comments

Comments
 (0)