@lukso/transaction-view-headless 0.2.2-dev.a8c9315

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 (76) hide show
  1. package/.turbo/turbo-build.log +51 -0
  2. package/CHANGELOG.md +88 -0
  3. package/COMPONENT_ARCHITECTURE.md +190 -0
  4. package/COMPONENT_CONVERSION_ANALYSIS.md +371 -0
  5. package/CONTEXT_VARIANT_GUIDE.md +425 -0
  6. package/LAZY_LOADING_GUIDE.md +534 -0
  7. package/LICENSE +201 -0
  8. package/PLAYBACK_EXAMPLES.md +658 -0
  9. package/PLAYBACK_GUIDE.md +402 -0
  10. package/README.md +43 -0
  11. package/REGISTRY_SUMMARY.md +638 -0
  12. package/SUFFERING_ECONOMICS.md +346 -0
  13. package/SUFFERING_ECONOMICS.zip +0 -0
  14. package/VIEW_REGISTRATION_GUIDE.md +795 -0
  15. package/dist/composables/index.cjs +1321 -0
  16. package/dist/composables/index.cjs.map +1 -0
  17. package/dist/composables/index.d.cts +6 -0
  18. package/dist/composables/index.d.ts +6 -0
  19. package/dist/composables/index.js +1276 -0
  20. package/dist/composables/index.js.map +1 -0
  21. package/dist/index-CMLU1hKL.d.ts +374 -0
  22. package/dist/index-CSDjhiKV.d.cts +374 -0
  23. package/dist/index.cjs +1534 -0
  24. package/dist/index.cjs.map +1 -0
  25. package/dist/index.d.cts +93 -0
  26. package/dist/index.d.ts +93 -0
  27. package/dist/index.js +1463 -0
  28. package/dist/index.js.map +1 -0
  29. package/dist/registry/index.cjs +522 -0
  30. package/dist/registry/index.cjs.map +1 -0
  31. package/dist/registry/index.d.cts +46 -0
  32. package/dist/registry/index.d.ts +46 -0
  33. package/dist/registry/index.js +487 -0
  34. package/dist/registry/index.js.map +1 -0
  35. package/dist/types/index.cjs +19 -0
  36. package/dist/types/index.cjs.map +1 -0
  37. package/dist/types/index.d.cts +203 -0
  38. package/dist/types/index.d.ts +203 -0
  39. package/dist/types/index.js +1 -0
  40. package/dist/types/index.js.map +1 -0
  41. package/dist/utils/index.cjs +217 -0
  42. package/dist/utils/index.cjs.map +1 -0
  43. package/dist/utils/index.d.cts +47 -0
  44. package/dist/utils/index.d.ts +47 -0
  45. package/dist/utils/index.js +188 -0
  46. package/dist/utils/index.js.map +1 -0
  47. package/dist/view-loader-dD86QAlQ.d.cts +121 -0
  48. package/dist/view-loader-ii8AxiQR.d.ts +121 -0
  49. package/package.json +64 -0
  50. package/publish.log +0 -0
  51. package/src/composables/index.ts +59 -0
  52. package/src/composables/use-image-loader.ts +315 -0
  53. package/src/composables/use-transaction-playback.ts +396 -0
  54. package/src/composables/use-transaction-view.ts +309 -0
  55. package/src/config/lukso-config.ts +171 -0
  56. package/src/examples/context-aware-views.ts +300 -0
  57. package/src/examples/example-views.ts +197 -0
  58. package/src/examples/lit-plus-rn-strategy.ts +448 -0
  59. package/src/examples/lsp7-custom-matcher-examples.ts +174 -0
  60. package/src/examples/opt-in-context-examples.ts +372 -0
  61. package/src/examples/optimized-view-definitions.ts +408 -0
  62. package/src/examples/vue-to-lit-compilation.ts +358 -0
  63. package/src/hooks/useAddressQuery.ts +135 -0
  64. package/src/hooks/useAddressResolution.ts +437 -0
  65. package/src/index.ts +36 -0
  66. package/src/registry/index.ts +13 -0
  67. package/src/registry/view-loader.ts +305 -0
  68. package/src/registry/view-registry.ts +135 -0
  69. package/src/types/index.ts +16 -0
  70. package/src/types/view-contexts.ts +72 -0
  71. package/src/types/view-matching.ts +173 -0
  72. package/src/utils/context-matcher.ts +165 -0
  73. package/src/utils/index.ts +5 -0
  74. package/src/utils/view-matcher.ts +338 -0
  75. package/tsconfig.json +20 -0
  76. package/tsup.config.ts +34 -0
@@ -0,0 +1,309 @@
1
+ import type {
2
+ AddressIdentityCache,
3
+ DataKey,
4
+ DecoderResult,
5
+ EnhancedInfo,
6
+ } from '@lukso/transaction-decoder'
7
+ import {
8
+ fetchMultipleAddresses,
9
+ getGraphQLEndpoint,
10
+ } from '@lukso/transaction-decoder'
11
+ import type { Chain } from 'viem'
12
+ import { getGlobalLoader, ViewLoadingError } from '../registry/view-loader'
13
+ import { getGlobalRegistry } from '../registry/view-registry'
14
+ import type {
15
+ ViewMatch,
16
+ ViewMatchOptions,
17
+ ViewRegistry,
18
+ } from '../types/view-matching'
19
+
20
+ /**
21
+ * Options for the transaction view composable
22
+ */
23
+ export interface UseTransactionViewOptions extends Partial<ViewMatchOptions> {
24
+ /** Custom registry to use instead of global */
25
+ registry?: ViewRegistry
26
+ /** Whether to automatically load the best matching component */
27
+ autoLoad?: boolean
28
+ /** Fallback view ID to use if no matches found */
29
+ fallbackViewId?: string
30
+ /** Whether to resolve addresses for this view (uses transaction.addresses array) */
31
+ resolve?: boolean
32
+ /** Chain for address resolution (required if resolve: true) */
33
+ chain?: Chain
34
+ /** Optional cache adapter for address resolution */
35
+ addressCache?: AddressIdentityCache
36
+ }
37
+
38
+ /**
39
+ * Result from the transaction view composable
40
+ */
41
+ export interface UseTransactionViewResult {
42
+ /** All matching views sorted by score */
43
+ matches: ViewMatch[]
44
+ /** The best matching view (highest score) */
45
+ bestMatch: ViewMatch | null
46
+ /** The loaded component (if autoLoad is true) */
47
+ component: any
48
+ /** Loading state */
49
+ isLoading: boolean
50
+ /** Error state */
51
+ error: ViewLoadingError | null
52
+ /** Address resolution loading state */
53
+ isResolvingAddresses: boolean
54
+ /** Resolved address data map (if resolve: true) */
55
+ resolvedAddresses: Record<string, EnhancedInfo>
56
+ /** Function to manually load a specific match */
57
+ loadMatch: (match: ViewMatch) => Promise<any>
58
+ /** Function to load the best match */
59
+ loadBestMatch: () => Promise<any>
60
+ /** Function to refresh matches (useful if registry changes) */
61
+ refreshMatches: () => void
62
+ /** Function to manually trigger address resolution */
63
+ resolveAddresses: () => Promise<void>
64
+ }
65
+
66
+ /**
67
+ * Framework-agnostic composable for finding and loading transaction views
68
+ *
69
+ * This is the main entry point for the view system. It handles:
70
+ * - Finding matching views based on transaction data
71
+ * - Loading components using the appropriate strategy
72
+ * - Providing a consistent API across all frameworks
73
+ *
74
+ * Usage in Vue:
75
+ * ```ts
76
+ * const { bestMatch, component, isLoading, resolvedAddresses } = useTransactionView(transaction, {
77
+ * framework: 'lit',
78
+ * autoLoad: true,
79
+ * resolve: true, // Auto-resolve addresses from transaction.addresses array
80
+ * chain: lukso, // Required for address resolution
81
+ * addressCache: cache // Optional cache adapter
82
+ * })
83
+ * ```
84
+ *
85
+ * Usage in React/React Native:
86
+ * ```ts
87
+ * const { bestMatch, component, loadBestMatch, resolvedAddresses, isResolvingAddresses } = useTransactionView(transaction, {
88
+ * framework: 'react-native',
89
+ * resolve: true,
90
+ * chain: luksoTestnet,
91
+ * addressCache: myCache
92
+ * })
93
+ * useEffect(() => {
94
+ * loadBestMatch()
95
+ * }, [transaction])
96
+ * ```
97
+ */
98
+ export function useTransactionView(
99
+ transaction: DecoderResult,
100
+ options: UseTransactionViewOptions
101
+ ): UseTransactionViewResult {
102
+ const {
103
+ registry = getGlobalRegistry(),
104
+ autoLoad = false,
105
+ fallbackViewId,
106
+ framework = 'lit',
107
+ minScore = 0,
108
+ maxMatches,
109
+ includeFrameworkConfig = true,
110
+ resolve = false,
111
+ chain,
112
+ addressCache,
113
+ } = options
114
+
115
+ const loader = getGlobalLoader()
116
+
117
+ // Internal state (framework-specific implementations will handle reactivity)
118
+ let matches: ViewMatch[] = []
119
+ let bestMatch: ViewMatch | null = null
120
+ let component: any = null
121
+ let isLoading = false
122
+ let error: ViewLoadingError | null = null
123
+ let isResolvingAddresses = false
124
+ let resolvedAddresses: Record<string, EnhancedInfo> = {}
125
+
126
+ // Find matching views
127
+ const refreshMatches = () => {
128
+ const matchOptions: ViewMatchOptions = {
129
+ framework: framework as any,
130
+ minScore,
131
+ maxMatches,
132
+ includeFrameworkConfig,
133
+ }
134
+
135
+ matches = registry.findMatches(transaction, matchOptions)
136
+ bestMatch = matches[0] || null
137
+
138
+ // If no matches and we have a fallback, try to get it
139
+ if (!bestMatch && fallbackViewId) {
140
+ const fallbackView = registry.getView(fallbackViewId)
141
+ if (fallbackView && fallbackView.frameworks[framework]) {
142
+ bestMatch = {
143
+ view: fallbackView,
144
+ score: 0,
145
+ matchedCriteria: [],
146
+ frameworkConfig: includeFrameworkConfig
147
+ ? fallbackView.frameworks[framework]
148
+ : undefined,
149
+ }
150
+ }
151
+ }
152
+ }
153
+
154
+ // Load a specific match
155
+ const loadMatch = async (match: ViewMatch): Promise<any> => {
156
+ if (!match.frameworkConfig) {
157
+ throw new ViewLoadingError(
158
+ `No framework config for view "${match.view.id}" and framework "${framework}"`,
159
+ { type: 'dynamic-import', path: '' },
160
+ {} as any
161
+ )
162
+ }
163
+
164
+ isLoading = true
165
+ error = null
166
+
167
+ try {
168
+ const loadedComponent = await loader.loadFromMatch(match)
169
+ component = loadedComponent
170
+ return loadedComponent
171
+ } catch (err) {
172
+ error =
173
+ err instanceof ViewLoadingError
174
+ ? err
175
+ : new ViewLoadingError(
176
+ 'Unknown loading error',
177
+ match.frameworkConfig.loader,
178
+ match.frameworkConfig,
179
+ err instanceof Error ? err : new Error(String(err))
180
+ )
181
+ throw error
182
+ } finally {
183
+ isLoading = false
184
+ }
185
+ }
186
+
187
+ // Load the best match
188
+ const loadBestMatch = async (): Promise<any> => {
189
+ if (!bestMatch) {
190
+ throw new Error('No matching view found for transaction')
191
+ }
192
+ return loadMatch(bestMatch)
193
+ }
194
+
195
+ // Address resolution function
196
+ const resolveAddressesFunc = async (): Promise<void> => {
197
+ if (
198
+ !resolve ||
199
+ !transaction.addresses ||
200
+ transaction.addresses.length === 0
201
+ ) {
202
+ return
203
+ }
204
+
205
+ if (!chain) {
206
+ console.warn(
207
+ 'Address resolution requested but no chain provided. Pass chain option to resolve addresses.'
208
+ )
209
+ return
210
+ }
211
+
212
+ isResolvingAddresses = true
213
+
214
+ try {
215
+ // Convert addresses to DataKey format
216
+ const dataKeys: DataKey[] = transaction.addresses
217
+ .filter((addr) => addr && typeof addr === 'string')
218
+ .map((addr) => addr.toLowerCase() as DataKey)
219
+
220
+ if (dataKeys.length === 0) {
221
+ return
222
+ }
223
+
224
+ // Get GraphQL endpoint based on chain
225
+ const endpoint = getGraphQLEndpoint(chain)
226
+
227
+ // Use raw fetchMultipleAddresses with optional cache
228
+ const resolvedMap = await fetchMultipleAddresses(
229
+ dataKeys,
230
+ endpoint,
231
+ addressCache
232
+ )
233
+
234
+ // Convert Map to Record for easier consumption
235
+ const resolved: Record<string, EnhancedInfo> = {}
236
+ resolvedMap.forEach((enhancedInfo, dataKey) => {
237
+ resolved[dataKey] = enhancedInfo
238
+ })
239
+
240
+ resolvedAddresses = resolved
241
+ } catch (err) {
242
+ console.error('Address resolution failed:', err)
243
+ } finally {
244
+ isResolvingAddresses = false
245
+ }
246
+ }
247
+
248
+ // Initialize
249
+ refreshMatches()
250
+
251
+ // Auto-resolve addresses if requested
252
+ if (resolve) {
253
+ resolveAddressesFunc().catch((err) => {
254
+ console.error('Auto address resolution failed:', err)
255
+ })
256
+ }
257
+
258
+ // Auto-load if requested
259
+ if (autoLoad && bestMatch) {
260
+ loadBestMatch().catch((err) => {
261
+ console.error('Auto-load failed:', err)
262
+ })
263
+ }
264
+
265
+ return {
266
+ matches,
267
+ bestMatch,
268
+ component,
269
+ isLoading,
270
+ error,
271
+ isResolvingAddresses,
272
+ resolvedAddresses,
273
+ loadMatch,
274
+ loadBestMatch,
275
+ refreshMatches,
276
+ resolveAddresses: resolveAddressesFunc,
277
+ }
278
+ }
279
+
280
+ /**
281
+ * Simplified version that just returns the best matching view without loading
282
+ */
283
+ export function findBestTransactionView(
284
+ transaction: DecoderResult,
285
+ framework: 'lit' | 'rn',
286
+ registry?: ViewRegistry
287
+ ): ViewMatch | null {
288
+ const reg = registry || getGlobalRegistry()
289
+ const matches = reg.findMatches(transaction, {
290
+ framework,
291
+ includeFrameworkConfig: true,
292
+ })
293
+ return matches.length > 0 ? matches[0] : null
294
+ }
295
+
296
+ /**
297
+ * Get all matching views without loading
298
+ */
299
+ export function findAllTransactionViews(
300
+ transaction: DecoderResult,
301
+ framework: 'lit' | 'rn',
302
+ registry?: ViewRegistry
303
+ ): ViewMatch[] {
304
+ const reg = registry || getGlobalRegistry()
305
+ return reg.findMatches(transaction, {
306
+ framework,
307
+ includeFrameworkConfig: true,
308
+ })
309
+ }
@@ -0,0 +1,171 @@
1
+ /**
2
+ * Framework-agnostic LUKSO configuration system
3
+ * Works across Vue, React, and Lit components
4
+ */
5
+
6
+ import { signal } from '@preact/signals-core'
7
+ import type { Chain } from 'viem'
8
+
9
+ export interface LuksoConfig {
10
+ /** Chain ID for LUKSO network (42 for mainnet, 4201 for testnet) */
11
+ chainId: number
12
+ /** RPC endpoint for the LUKSO network */
13
+ rpcUrl: string
14
+ /** GraphQL endpoint for address resolution */
15
+ graphqlEndpoint?: string
16
+ /** IPFS gateway for profile images and metadata */
17
+ ipfsGateway?: string
18
+ /** Enable/disable address resolution */
19
+ enableAddressResolution?: boolean
20
+ /** Cache duration for resolved addresses (in milliseconds) */
21
+ cacheDuration?: number
22
+ }
23
+
24
+ const DEFAULT_CONFIG: LuksoConfig = {
25
+ chainId: 42, // LUKSO mainnet
26
+ rpcUrl: 'https://rpc.lukso.network',
27
+ graphqlEndpoint: '/api/graphql', // Use local API proxy
28
+ ipfsGateway: 'https://api.universalprofile.cloud/ipfs',
29
+ enableAddressResolution: true,
30
+ cacheDuration: 5 * 60 * 1000, // 5 minutes
31
+ }
32
+
33
+ let globalConfig: LuksoConfig = { ...DEFAULT_CONFIG }
34
+
35
+ /**
36
+ * Set global LUKSO configuration
37
+ * Call this once to configure all transaction components
38
+ */
39
+ export function setLuksoConfig(config: Partial<LuksoConfig>): void {
40
+ globalConfig = { ...globalConfig, ...config }
41
+
42
+ // Clear any existing address resolution cache when config changes
43
+ clearAddressCache()
44
+
45
+ if (typeof window !== 'undefined' && window.console) {
46
+ console.log('🌐 LUKSO Config Updated:', {
47
+ chainId: globalConfig.chainId,
48
+ network:
49
+ globalConfig.chainId === 42
50
+ ? 'mainnet'
51
+ : globalConfig.chainId === 4201
52
+ ? 'testnet'
53
+ : 'custom',
54
+ addressResolution: globalConfig.enableAddressResolution,
55
+ })
56
+ }
57
+ }
58
+
59
+ /**
60
+ * Get current LUKSO configuration
61
+ */
62
+ export function getLuksoConfig(): LuksoConfig {
63
+ return { ...globalConfig }
64
+ }
65
+
66
+ /**
67
+ * Quick helper to set mainnet configuration
68
+ */
69
+ export function useLuksoMainnet(): void {
70
+ setLuksoConfig({
71
+ chainId: 42,
72
+ rpcUrl: 'https://rpc.lukso.network',
73
+ })
74
+ }
75
+
76
+ /**
77
+ * Quick helper to set testnet configuration
78
+ */
79
+ export function useLuksoTestnet(): void {
80
+ setLuksoConfig({
81
+ chainId: 4201,
82
+ rpcUrl: 'https://rpc.testnet.lukso.network',
83
+ })
84
+ }
85
+
86
+ /**
87
+ * Address resolution cache
88
+ */
89
+ const addressCache = new Map<string, any>()
90
+ const cacheTimestamps = new Map<string, number>()
91
+
92
+ export function cacheAddress(address: string, data: any): void {
93
+ if (!globalConfig.enableAddressResolution) return
94
+
95
+ const key = address.toLowerCase()
96
+ addressCache.set(key, data)
97
+ cacheTimestamps.set(key, Date.now())
98
+ }
99
+
100
+ export function getCachedAddress(address: string): any | null {
101
+ if (!globalConfig.enableAddressResolution) return null
102
+
103
+ const key = address.toLowerCase()
104
+ const data = addressCache.get(key)
105
+ const timestamp = cacheTimestamps.get(key)
106
+
107
+ if (!data || !timestamp) return null
108
+
109
+ // Check if cache is expired
110
+ if (Date.now() - timestamp > globalConfig.cacheDuration!) {
111
+ addressCache.delete(key)
112
+ cacheTimestamps.delete(key)
113
+ return null
114
+ }
115
+
116
+ return data
117
+ }
118
+
119
+ export function clearAddressCache(): void {
120
+ addressCache.clear()
121
+ cacheTimestamps.clear()
122
+ }
123
+
124
+ /**
125
+ * Check if address resolution is enabled
126
+ */
127
+ export function isAddressResolutionEnabled(): boolean {
128
+ return globalConfig.enableAddressResolution ?? true
129
+ }
130
+
131
+ /**
132
+ * Current chain signal for reactive chain state
133
+ */
134
+ export const currentChainSignal = signal<Chain | null>(null)
135
+
136
+ /**
137
+ * Set current chain signal from viem chain object
138
+ */
139
+ export function setCurrentChain(chain: Chain): void {
140
+ currentChainSignal.value = chain
141
+
142
+ // Also update the legacy global config for backwards compatibility
143
+ setLuksoConfig({
144
+ chainId: chain.id,
145
+ rpcUrl: chain.rpcUrls.default.http[0],
146
+ })
147
+ }
148
+
149
+ /**
150
+ * Get current chain, falling back to chain derived from global config
151
+ */
152
+ export function getCurrentChain(): Chain | null {
153
+ return currentChainSignal.value
154
+ }
155
+
156
+ /**
157
+ * Initialize chain signal from global config
158
+ */
159
+ export async function initializeChainSignal(): Promise<void> {
160
+ if (currentChainSignal.value) return
161
+
162
+ try {
163
+ const { lukso, luksoTestnet } = await import('viem/chains')
164
+ const config = getLuksoConfig()
165
+
166
+ const chain = config.chainId === 42 ? lukso : luksoTestnet
167
+ currentChainSignal.value = chain
168
+ } catch (error) {
169
+ console.warn('Failed to initialize chain signal:', error)
170
+ }
171
+ }