@akta/algomd-rn 0.0.1-canary

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/package.json +46 -0
  2. package/src/components/ASA.tsx +221 -0
  3. package/src/components/ASASearch.tsx +79 -0
  4. package/src/components/Account.tsx +150 -0
  5. package/src/components/AccountSearch.tsx +75 -0
  6. package/src/components/AuctionListing.tsx +220 -0
  7. package/src/components/NFDProfile.tsx +173 -0
  8. package/src/components/NFDSearch.tsx +81 -0
  9. package/src/components/NFTListing.tsx +181 -0
  10. package/src/components/NFTSearch.tsx +68 -0
  11. package/src/components/Poll.tsx +234 -0
  12. package/src/components/PollSearch.tsx +79 -0
  13. package/src/components/RaffleListing.tsx +204 -0
  14. package/src/components/SelfFetching.tsx +358 -0
  15. package/src/components/TradeOffer.tsx +245 -0
  16. package/src/components/TradeSearch.tsx +103 -0
  17. package/src/components/TransactionDetails.tsx +242 -0
  18. package/src/components/TransactionSearch.tsx +102 -0
  19. package/src/components/index.ts +30 -0
  20. package/src/hooks/index.ts +12 -0
  21. package/src/hooks/useAlgomdData.ts +445 -0
  22. package/src/hooks/useAlgorandClient.ts +39 -0
  23. package/src/index.ts +83 -0
  24. package/src/provider/AlgomdProvider.tsx +45 -0
  25. package/src/provider/context.ts +18 -0
  26. package/src/provider/imageResolver.ts +12 -0
  27. package/src/provider/index.ts +5 -0
  28. package/src/provider/networks.ts +23 -0
  29. package/src/provider/types.ts +17 -0
  30. package/src/types/algorand.ts +205 -0
  31. package/src/types/index.ts +1 -0
  32. package/src/ui/CopyButton.tsx +40 -0
  33. package/src/ui/DataStates.tsx +41 -0
  34. package/src/ui/ProgressBar.tsx +42 -0
  35. package/src/ui/SearchSheet.tsx +134 -0
  36. package/src/ui/SizeContainer.tsx +25 -0
  37. package/src/ui/StatusBadge.tsx +51 -0
  38. package/src/ui/index.ts +6 -0
  39. package/src/utils/format.ts +70 -0
  40. package/src/utils/index.ts +2 -0
  41. package/src/utils/search.ts +65 -0
@@ -0,0 +1,445 @@
1
+ /**
2
+ * AlgoMD self-fetching data hooks.
3
+ *
4
+ * Each hook reads directly from algod/indexer (via @akta/sdk typed clients
5
+ * where available) and maps to the algomd-rn display types.
6
+ */
7
+
8
+ // These web APIs are available in Hermes / all modern JS engines but not in TS "esnext" lib
9
+ declare const TextDecoder: { new (): { decode(input?: ArrayBuffer | Uint8Array): string } }
10
+ declare function btoa(data: string): string
11
+
12
+ import { useQuery } from '@tanstack/react-query'
13
+ import { useAlgorandClient, useIndexerClient } from './useAlgorandClient'
14
+ import { useAlgomd } from '../provider/context'
15
+ import type { AlgorandClient } from '@algorandfoundation/algokit-utils'
16
+ import type {
17
+ AlgorandAccount,
18
+ ASA as ASAType,
19
+ TransactionDetails as TransactionDetailsType,
20
+ NFDProfile as NFDProfileType,
21
+ Poll as PollType,
22
+ PollOption,
23
+ RaffleListing as RaffleListingType,
24
+ AuctionListing as AuctionListingType,
25
+ TradeOffer as TradeOfferType,
26
+ NFTListing as NFTListingType,
27
+ } from '../types/algorand'
28
+
29
+ // ---------------------------------------------------------------------------
30
+ // Shared helper: fetch ASA details from algod
31
+ // ---------------------------------------------------------------------------
32
+
33
+ async function fetchASADetails(algorand: AlgorandClient, assetId: number | bigint): Promise<ASAType> {
34
+ try {
35
+ const info = await algorand.client.algod.getAssetByID(BigInt(assetId)).do()
36
+ const params = info.params
37
+ return {
38
+ id: Number(assetId),
39
+ name: params.name ?? `Asset #${assetId}`,
40
+ unitName: params.unitName ?? '',
41
+ total: Number(params.total),
42
+ decimals: Number(params.decimals),
43
+ defaultFrozen: params.defaultFrozen ?? false,
44
+ url: params.url,
45
+ creator: params.creator ?? '',
46
+ createdAt: new Date(),
47
+ verified: false,
48
+ }
49
+ } catch {
50
+ return {
51
+ id: Number(assetId),
52
+ name: `Asset #${assetId}`,
53
+ unitName: '???',
54
+ total: 0,
55
+ decimals: 0,
56
+ defaultFrozen: false,
57
+ creator: '',
58
+ createdAt: new Date(),
59
+ verified: false,
60
+ }
61
+ }
62
+ }
63
+
64
+ // ---------------------------------------------------------------------------
65
+ // On-chain hooks (algod / indexer)
66
+ // ---------------------------------------------------------------------------
67
+
68
+ export function useAccountData(address: string | undefined) {
69
+ const algorand = useAlgorandClient()
70
+ return useQuery({
71
+ queryKey: ['algomd', 'account', address],
72
+ queryFn: async (): Promise<AlgorandAccount> => {
73
+ if (!address) throw new Error('No address provided')
74
+ const info = await algorand.client.algod.accountInformation(address).do()
75
+ const assets: ASAType[] = (info.assets ?? []).map(
76
+ (a: { assetId: bigint; amount: bigint }) => ({
77
+ id: Number(a.assetId),
78
+ name: `Asset #${a.assetId}`,
79
+ unitName: '',
80
+ total: Number(a.amount),
81
+ decimals: 0,
82
+ defaultFrozen: false,
83
+ creator: '',
84
+ createdAt: new Date(),
85
+ verified: false,
86
+ }),
87
+ )
88
+ return {
89
+ id: address,
90
+ address,
91
+ balance: Number(info.amount),
92
+ assets,
93
+ apps: (info.appsLocalState ?? []).map(
94
+ (a: { id: bigint }) => ({
95
+ id: Number(a.id),
96
+ creator: '',
97
+ globalState: {},
98
+ localState: {},
99
+ params: {
100
+ globalNumUint: 0,
101
+ globalNumByteSlice: 0,
102
+ localNumUint: 0,
103
+ localNumByteSlice: 0,
104
+ },
105
+ createdAt: new Date(),
106
+ }),
107
+ ),
108
+ createdAt: new Date(),
109
+ isOnline: info.status === 'Online',
110
+ round: Number(info.round ?? 0),
111
+ }
112
+ },
113
+ enabled: !!address,
114
+ staleTime: 60_000,
115
+ })
116
+ }
117
+
118
+ export function useASAData(assetId: number | string | undefined) {
119
+ const algorand = useAlgorandClient()
120
+ return useQuery({
121
+ queryKey: ['algomd', 'asa', assetId],
122
+ queryFn: async (): Promise<ASAType> => {
123
+ if (!assetId) throw new Error('No asset ID provided')
124
+ return fetchASADetails(algorand, Number(assetId))
125
+ },
126
+ enabled: assetId != null,
127
+ staleTime: 60_000,
128
+ })
129
+ }
130
+
131
+ export function useTransactionData(txId: string | undefined) {
132
+ const indexer = useIndexerClient()
133
+ return useQuery({
134
+ queryKey: ['algomd', 'transaction', txId],
135
+ queryFn: async (): Promise<TransactionDetailsType> => {
136
+ if (!txId) throw new Error('No transaction ID provided')
137
+ if (!indexer) throw new Error('Indexer not configured')
138
+ const result = await indexer.lookupTransactionByID(txId).do()
139
+ const txn = result.transaction
140
+
141
+ const typeMap: Record<string, TransactionDetailsType['type']> = {
142
+ pay: 'payment',
143
+ axfer: 'asset-transfer',
144
+ appl: 'application-call',
145
+ acfg: 'asset-config',
146
+ keyreg: 'key-registration',
147
+ afrz: 'asset-freeze',
148
+ }
149
+
150
+ // algosdk v3 indexer returns note as Uint8Array, not base64 string
151
+ let note: string | undefined
152
+ if (txn.note) {
153
+ try {
154
+ note = new TextDecoder().decode(txn.note)
155
+ } catch {
156
+ note = undefined
157
+ }
158
+ }
159
+
160
+ return {
161
+ id: txId,
162
+ type: typeMap[txn.txType ?? ''] ?? 'payment',
163
+ from: txn.sender,
164
+ to: txn.paymentTransaction?.receiver ?? txn.assetTransferTransaction?.receiver,
165
+ amount: Number(
166
+ txn.paymentTransaction?.amount ?? txn.assetTransferTransaction?.amount ?? 0,
167
+ ),
168
+ fee: Number(txn.fee),
169
+ round: Number(txn.confirmedRound),
170
+ timestamp: new Date((txn.roundTime ?? 0) * 1000),
171
+ confirmed: !!txn.confirmedRound,
172
+ signature: (() => {
173
+ const sig = txn.signature?.sig
174
+ if (!sig) return ''
175
+ if (typeof sig === 'string') return sig
176
+ // Convert Uint8Array to base64 without Node Buffer
177
+ return btoa(Array.from(sig as Uint8Array, (b: number) => String.fromCharCode(b)).join(''))
178
+ })(),
179
+ note,
180
+ }
181
+ },
182
+ enabled: !!txId && !!indexer,
183
+ staleTime: 120_000,
184
+ })
185
+ }
186
+
187
+ export function useNFDProfileData(name: string | undefined) {
188
+ const { config } = useAlgomd()
189
+ const nfdApiUrl = config.nfdApiUrl ?? 'https://api.nf.domains'
190
+ return useQuery({
191
+ queryKey: ['algomd', 'nfd', name],
192
+ queryFn: async (): Promise<NFDProfileType> => {
193
+ if (!name) throw new Error('No NFD name provided')
194
+ const response = await fetch(`${nfdApiUrl}/nfd/${encodeURIComponent(name)}`)
195
+ if (!response.ok) throw new Error(`NFD lookup failed: ${response.status}`)
196
+ const nfd = await response.json()
197
+ return {
198
+ id: nfd.appID?.toString() ?? name,
199
+ name: nfd.name ?? name,
200
+ address: nfd.depositAccount ?? nfd.caAlgo?.[0] ?? '',
201
+ avatar: nfd.properties?.userDefined?.avatar,
202
+ bio: nfd.properties?.userDefined?.bio,
203
+ properties: nfd.properties?.userDefined ?? {},
204
+ verified: nfd.properties?.verified?.caAlgo === nfd.caAlgo?.[0],
205
+ createdAt: new Date(nfd.timeCreated ?? Date.now()),
206
+ }
207
+ },
208
+ enabled: !!name,
209
+ staleTime: 120_000,
210
+ })
211
+ }
212
+
213
+ // ---------------------------------------------------------------------------
214
+ // On-chain contract hooks (via @akta/sdk typed clients)
215
+ // ---------------------------------------------------------------------------
216
+
217
+ export function usePollData(appId: number | undefined) {
218
+ const algorand = useAlgorandClient()
219
+ return useQuery({
220
+ queryKey: ['algomd', 'poll', appId],
221
+ queryFn: async (): Promise<PollType> => {
222
+ if (!appId) throw new Error('No poll app ID provided')
223
+ const { PollSDK } = await import('@akta/sdk')
224
+ const sdk = new PollSDK({
225
+ algorand,
226
+ factoryParams: { appId: BigInt(appId) },
227
+ })
228
+ const state = await sdk.state()
229
+
230
+ const optionTexts = [state.question, state.optionOne, state.optionTwo, state.optionThree, state.optionFour, state.optionFive]
231
+ const optionVotes = [state.votesOne, state.votesTwo, state.votesThree, state.votesFour, state.votesFive]
232
+ const options: PollOption[] = []
233
+ const count = Number(state.optionCount)
234
+ for (let i = 0; i < count && i < 5; i++) {
235
+ options.push({
236
+ id: `${i + 1}`,
237
+ text: optionTexts[i + 1] || `Option ${i + 1}`,
238
+ votes: Number(optionVotes[i]),
239
+ votingPower: Number(optionVotes[i]),
240
+ })
241
+ }
242
+
243
+ const endTime = Number(state.endTime)
244
+ const now = Date.now() / 1000
245
+ const isExpired = endTime > 0 && endTime < now
246
+ const totalVotes = optionVotes.slice(0, count).reduce((sum, v) => sum + Number(v), 0)
247
+
248
+ return {
249
+ id: appId.toString(),
250
+ question: state.question,
251
+ options,
252
+ creator: '',
253
+ createdAt: new Date(),
254
+ expiresAt: endTime > 0 ? new Date(endTime * 1000) : undefined,
255
+ totalVotes,
256
+ status: isExpired ? 'ended' : 'active',
257
+ gating: Number(state.gateId) > 0
258
+ ? { type: 'asset-holding' as const, requirements: { assets: [{ assetId: Number(state.gateId), minimumBalance: 1 }] } }
259
+ : undefined,
260
+ }
261
+ },
262
+ enabled: appId != null,
263
+ staleTime: 30_000,
264
+ })
265
+ }
266
+
267
+ export function useRaffleData(appId: number | undefined) {
268
+ const algorand = useAlgorandClient()
269
+ return useQuery({
270
+ queryKey: ['algomd', 'raffle', appId],
271
+ queryFn: async (): Promise<RaffleListingType> => {
272
+ if (!appId) throw new Error('No raffle app ID provided')
273
+ const { RaffleSDK } = await import('@akta/sdk')
274
+ const sdk = new RaffleSDK({
275
+ algorand,
276
+ factoryParams: { appId: BigInt(appId) },
277
+ })
278
+ const state = await sdk.state()
279
+
280
+ const [prizeAsset, entryAsset] = await Promise.all([
281
+ fetchASADetails(algorand, Number(state.prize)),
282
+ fetchASADetails(algorand, Number(state.ticketAsset)),
283
+ ])
284
+
285
+ const startTs = Number(state.startTimestamp)
286
+ const endTs = Number(state.endTimestamp)
287
+ const now = Date.now() / 1000
288
+ let status: RaffleListingType['status']
289
+ if (startTs > now) status = 'upcoming'
290
+ else if (endTs > now) status = 'active'
291
+ else status = 'ended'
292
+
293
+ return {
294
+ id: appId.toString(),
295
+ title: `Raffle #${appId}`,
296
+ description: `Win ${prizeAsset.name}! Entry costs ${entryAsset.unitName || 'tokens'}.`,
297
+ pricePerEntry: 1,
298
+ entryAsset,
299
+ startTime: new Date(startTs * 1000),
300
+ endTime: new Date(endTs * 1000),
301
+ prizes: [prizeAsset],
302
+ entryCount: Number(state.entryCount),
303
+ ticketCount: Number(state.maxTickets),
304
+ creator: state.seller ?? '',
305
+ status,
306
+ }
307
+ },
308
+ enabled: appId != null,
309
+ staleTime: 30_000,
310
+ })
311
+ }
312
+
313
+ export function useAuctionData(appId: number | undefined) {
314
+ const algorand = useAlgorandClient()
315
+ return useQuery({
316
+ queryKey: ['algomd', 'auction', appId],
317
+ queryFn: async (): Promise<AuctionListingType> => {
318
+ if (!appId) throw new Error('No auction app ID provided')
319
+ const { AuctionSDK } = await import('@akta/sdk')
320
+ const sdk = new AuctionSDK({
321
+ algorand,
322
+ factoryParams: { appId: BigInt(appId) },
323
+ })
324
+ const state = await sdk.state()
325
+
326
+ const [prizeAsset, bidAsset] = await Promise.all([
327
+ fetchASADetails(algorand, Number(state.prize)),
328
+ fetchASADetails(algorand, Number(state.bidAsset)),
329
+ ])
330
+
331
+ const startTs = Number(state.startTimestamp)
332
+ const endTs = Number(state.endTimestamp)
333
+ const now = Date.now() / 1000
334
+ let status: AuctionListingType['status']
335
+ if (startTs > now) status = 'upcoming'
336
+ else if (endTs > now) status = 'active'
337
+ else status = 'ended'
338
+
339
+ const bidDecimals = bidAsset.decimals || 0
340
+ const divisor = Math.pow(10, bidDecimals)
341
+
342
+ return {
343
+ id: appId.toString(),
344
+ title: `Auction #${appId}`,
345
+ description: `Bid on ${prizeAsset.name} with ${bidAsset.unitName || 'tokens'}.`,
346
+ bidAsset,
347
+ currentHighestBid: Number(state.highestBid) / divisor,
348
+ minimumNextBid: (Number(state.highestBid) + Number(state.bidMinimumIncrease)) / divisor,
349
+ startTime: new Date(startTs * 1000),
350
+ endTime: new Date(endTs * 1000),
351
+ prizes: [prizeAsset],
352
+ bidFeePercentage: Number(state.bidFee) > 0 ? Number(state.bidFee) / 100 : undefined,
353
+ currentBidFeePool: 0,
354
+ bidCount: Number(state.bidID),
355
+ timeExtended: false,
356
+ creator: state.seller ?? '',
357
+ status,
358
+ }
359
+ },
360
+ enabled: appId != null,
361
+ staleTime: 30_000,
362
+ })
363
+ }
364
+
365
+ export function useNFTListingData(appId: number | undefined) {
366
+ const algorand = useAlgorandClient()
367
+ return useQuery({
368
+ queryKey: ['algomd', 'nftlisting', appId],
369
+ queryFn: async (): Promise<NFTListingType> => {
370
+ if (!appId) throw new Error('No NFT listing app ID provided')
371
+ const { MarketplaceSDK } = await import('@akta/sdk')
372
+ const marketplace = new MarketplaceSDK({
373
+ algorand,
374
+ factoryParams: {},
375
+ })
376
+ const listing = marketplace.getListing({ appId: BigInt(appId) })
377
+ const state = await listing.state()
378
+
379
+ const [nftAsset, paymentAsset] = await Promise.all([
380
+ fetchASADetails(algorand, Number(state.prize)),
381
+ fetchASADetails(algorand, Number(state.paymentAsset)),
382
+ ])
383
+
384
+ const payDecimals = paymentAsset.decimals || 0
385
+ const divisor = Math.pow(10, payDecimals)
386
+
387
+ return {
388
+ id: appId.toString(),
389
+ nft: nftAsset,
390
+ price: Number(state.price) / divisor,
391
+ priceAsset: paymentAsset,
392
+ currency: paymentAsset.unitName || 'ALGO',
393
+ seller: state.seller ?? '',
394
+ authenticityBadge: false,
395
+ quantity: 1,
396
+ reservedFor: state.reservedFor && state.reservedFor !== 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAY5HFKQ' ? state.reservedFor : undefined,
397
+ views: 0,
398
+ favorites: 0,
399
+ createdAt: new Date(),
400
+ listedAt: new Date(),
401
+ expiresAt: Number(state.expiration) > 0 ? new Date(Number(state.expiration) * 1000) : undefined,
402
+ }
403
+ },
404
+ enabled: appId != null,
405
+ staleTime: 60_000,
406
+ })
407
+ }
408
+
409
+ export function useTradeOfferData(appId: number | undefined, offerId: number | undefined) {
410
+ const algorand = useAlgorandClient()
411
+ return useQuery({
412
+ queryKey: ['algomd', 'trade', appId, offerId],
413
+ queryFn: async (): Promise<TradeOfferType> => {
414
+ if (appId == null || offerId == null) throw new Error('No trade offer ID provided')
415
+ const { HyperSwapSDK } = await import('@akta/sdk')
416
+ const sdk = new HyperSwapSDK({
417
+ algorand,
418
+ factoryParams: { appId: BigInt(appId) },
419
+ })
420
+ const offer = await sdk.getOffer({ id: BigInt(offerId) })
421
+
422
+ const stateMap: Record<number, TradeOfferType['status']> = {
423
+ 10: 'pending', // Offered
424
+ 20: 'pending', // Escrowing
425
+ 30: 'pending', // Disbursing
426
+ 40: 'accepted', // Completed
427
+ 50: 'expired', // Cancelled
428
+ 60: 'expired', // CancelCompleted
429
+ }
430
+
431
+ return {
432
+ id: offerId.toString(),
433
+ creator: '',
434
+ recipients: [],
435
+ offering: [],
436
+ requesting: [],
437
+ expiresAt: new Date(Number(offer.expiration) * 1000),
438
+ status: stateMap[offer.state] ?? 'pending',
439
+ createdAt: new Date(),
440
+ }
441
+ },
442
+ enabled: appId != null && offerId != null,
443
+ staleTime: 60_000,
444
+ })
445
+ }
@@ -0,0 +1,39 @@
1
+ import { useMemo } from 'react'
2
+ import { AlgorandClient } from '@algorandfoundation/algokit-utils'
3
+ import algosdk from 'algosdk'
4
+ import { useAlgomd } from '../provider/context'
5
+ import { NETWORK_DEFAULTS } from '../provider/networks'
6
+
7
+ /**
8
+ * Creates a memoized AlgorandClient from provider config.
9
+ */
10
+ export function useAlgorandClient(): AlgorandClient {
11
+ const { config } = useAlgomd()
12
+
13
+ return useMemo(() => {
14
+ const defaults = NETWORK_DEFAULTS[config.network]
15
+ const server = config.algodServer ?? defaults.algodServer
16
+ const port = config.algodPort ?? defaults.algodPort
17
+ const token = config.algodToken ?? defaults.algodToken ?? ''
18
+
19
+ return AlgorandClient.fromConfig({
20
+ algodConfig: { server, port, token },
21
+ })
22
+ }, [config.network, config.algodServer, config.algodPort, config.algodToken])
23
+ }
24
+
25
+ /**
26
+ * Creates a memoized Indexer client from provider config.
27
+ * Returns null if indexer is not configured.
28
+ */
29
+ export function useIndexerClient(): algosdk.Indexer | null {
30
+ const { config } = useAlgomd()
31
+
32
+ return useMemo(() => {
33
+ const defaults = NETWORK_DEFAULTS[config.network]
34
+ const server = config.indexerServer ?? defaults.indexerServer
35
+ if (!server) return null
36
+ const token = config.indexerToken ?? defaults.indexerToken ?? ''
37
+ return new algosdk.Indexer(token, server, '')
38
+ }, [config.network, config.indexerServer, config.indexerToken])
39
+ }
package/src/index.ts ADDED
@@ -0,0 +1,83 @@
1
+ // Provider
2
+ export { AlgomdProvider, useAlgomd, defaultResolveImageUrl, NETWORK_DEFAULTS } from './provider';
3
+ export type { AlgomdConfig, AlgomdNetwork } from './provider';
4
+
5
+ // Self-fetching Components
6
+ export {
7
+ Account,
8
+ ASA,
9
+ NFTListing,
10
+ NFDProfile,
11
+ TransactionDetails,
12
+ Poll,
13
+ RaffleListing,
14
+ AuctionListing,
15
+ TradeOffer,
16
+ // Display-only (no self-fetch)
17
+ AccountDisplay,
18
+ ASADisplay,
19
+ NFTListingDisplay,
20
+ NFDProfileDisplay,
21
+ TransactionDetailsDisplay,
22
+ PollDisplay,
23
+ RaffleListingDisplay,
24
+ AuctionListingDisplay,
25
+ TradeOfferDisplay,
26
+ // Search
27
+ AccountSearch,
28
+ ASASearch,
29
+ NFTSearch,
30
+ NFDSearch,
31
+ TransactionSearch,
32
+ PollSearch,
33
+ TradeSearch,
34
+ } from './components';
35
+
36
+ // Hooks
37
+ export {
38
+ useAlgorandClient,
39
+ useIndexerClient,
40
+ useAccountData,
41
+ useASAData,
42
+ useTransactionData,
43
+ useNFDProfileData,
44
+ usePollData,
45
+ useRaffleData,
46
+ useAuctionData,
47
+ useNFTListingData,
48
+ useTradeOfferData,
49
+ } from './hooks';
50
+
51
+ // Shared UI
52
+ export { SizeContainer, CopyButton, StatusBadge, ProgressBar, SearchSheet } from './ui';
53
+ export { LoadingSkeleton, ErrorState } from './ui/DataStates';
54
+
55
+ // Types
56
+ export type {
57
+ AlgorandAccount,
58
+ ASA as ASAType,
59
+ NFTListing as NFTListingType,
60
+ NFDProfile as NFDProfileType,
61
+ TransactionDetails as TransactionDetailsType,
62
+ RaffleListing as RaffleListingType,
63
+ AuctionListing as AuctionListingType,
64
+ TradeOffer as TradeOfferType,
65
+ Poll as PollType,
66
+ PollOption,
67
+ GatingInfo,
68
+ Application,
69
+ ComponentSize,
70
+ SearchResult,
71
+ SearchableEntity,
72
+ } from './types';
73
+
74
+ // Utilities
75
+ export {
76
+ formatAddress,
77
+ formatNumber,
78
+ formatCurrency,
79
+ formatDate,
80
+ formatRelativeTime,
81
+ formatAssetAmount,
82
+ searchEntities,
83
+ } from './utils';
@@ -0,0 +1,45 @@
1
+ import React, { useMemo } from 'react'
2
+ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
3
+ import { AlgomdContext, type AlgomdContextValue } from './context'
4
+ import type { AlgomdConfig } from './types'
5
+ import { defaultResolveImageUrl } from './imageResolver'
6
+
7
+ interface AlgomdProviderProps {
8
+ config: AlgomdConfig
9
+ /** Share cache with consuming app's react-query. If omitted, creates an internal one. */
10
+ queryClient?: QueryClient
11
+ children: React.ReactNode
12
+ }
13
+
14
+ const defaultQueryClient = new QueryClient({
15
+ defaultOptions: {
16
+ queries: {
17
+ staleTime: 60_000,
18
+ retry: 2,
19
+ },
20
+ },
21
+ })
22
+
23
+ export function AlgomdProvider({ config, queryClient, children }: AlgomdProviderProps) {
24
+ const contextValue = useMemo<AlgomdContextValue>(() => ({
25
+ config,
26
+ resolveImageUrl: config.resolveImageUrl ?? defaultResolveImageUrl,
27
+ }), [config])
28
+
29
+ const content = (
30
+ <AlgomdContext.Provider value={contextValue}>
31
+ {children}
32
+ </AlgomdContext.Provider>
33
+ )
34
+
35
+ // If a queryClient is provided (shared), don't wrap with another QueryClientProvider
36
+ if (queryClient) {
37
+ return content
38
+ }
39
+
40
+ return (
41
+ <QueryClientProvider client={defaultQueryClient}>
42
+ {content}
43
+ </QueryClientProvider>
44
+ )
45
+ }
@@ -0,0 +1,18 @@
1
+ import { createContext, useContext } from 'react'
2
+ import type { AlgomdConfig } from './types'
3
+ import { defaultResolveImageUrl } from './imageResolver'
4
+
5
+ export interface AlgomdContextValue {
6
+ config: AlgomdConfig
7
+ resolveImageUrl: (src: string, width: number, quality?: number) => string
8
+ }
9
+
10
+ export const AlgomdContext = createContext<AlgomdContextValue | null>(null)
11
+
12
+ export function useAlgomd(): AlgomdContextValue {
13
+ const ctx = useContext(AlgomdContext)
14
+ if (!ctx) {
15
+ throw new Error('useAlgomd must be used within an <AlgomdProvider>')
16
+ }
17
+ return ctx
18
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Default image URL resolver using Akita CDN proxy.
3
+ * Transforms IPFS and HTTP(S) URLs into optimized CDN URLs.
4
+ */
5
+ export function defaultResolveImageUrl(src: string, width: number, quality = 75): string {
6
+ if (!src.startsWith('ipfs://') && !src.startsWith('https://') && !src.startsWith('http://'))
7
+ return src
8
+ const format = src.includes('.gif') ? 'gif' : 'jpeg'
9
+ const params = [`${width}x`, `q${quality}`, format]
10
+ const resolved = src.replace('ipfs://', 'https://ipfs.akita.community/ipfs/')
11
+ return `https://imageproxy.akita.community/${params.join(',')}/${resolved}`
12
+ }
@@ -0,0 +1,5 @@
1
+ export { AlgomdProvider } from './AlgomdProvider'
2
+ export { useAlgomd } from './context'
3
+ export { defaultResolveImageUrl } from './imageResolver'
4
+ export { NETWORK_DEFAULTS } from './networks'
5
+ export type { AlgomdConfig, AlgomdNetwork } from './types'
@@ -0,0 +1,23 @@
1
+ import type { AlgomdNetwork } from './types'
2
+
3
+ export const NETWORK_DEFAULTS: Record<AlgomdNetwork, {
4
+ algodServer: string
5
+ algodPort?: number
6
+ algodToken?: string
7
+ indexerServer: string
8
+ indexerToken?: string
9
+ }> = {
10
+ mainnet: {
11
+ algodServer: 'https://mainnet-api.4160.nodely.dev',
12
+ indexerServer: 'https://mainnet-idx.4160.nodely.dev',
13
+ },
14
+ testnet: {
15
+ algodServer: 'https://testnet-api.4160.nodely.dev',
16
+ indexerServer: 'https://testnet-idx.4160.nodely.dev',
17
+ },
18
+ localnet: {
19
+ algodServer: 'http://localhost:4001',
20
+ algodToken: 'a'.repeat(64),
21
+ indexerServer: 'http://localhost:8980',
22
+ },
23
+ }
@@ -0,0 +1,17 @@
1
+ export type AlgomdNetwork = 'mainnet' | 'testnet' | 'localnet'
2
+
3
+ export interface AlgomdConfig {
4
+ /** Network - determines default algod/indexer URLs */
5
+ network: AlgomdNetwork
6
+ /** Override algod server URL */
7
+ algodServer?: string
8
+ algodPort?: number
9
+ algodToken?: string
10
+ /** Override indexer server URL (needed for Transaction) */
11
+ indexerServer?: string
12
+ indexerToken?: string
13
+ /** Override NFD API URL (default: https://api.nf.domains) */
14
+ nfdApiUrl?: string
15
+ /** Override image URL resolver (default: Akita CDN proxy) */
16
+ resolveImageUrl?: (src: string, width: number, quality?: number) => string
17
+ }