@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.
- package/.turbo/turbo-build.log +51 -0
- package/CHANGELOG.md +88 -0
- package/COMPONENT_ARCHITECTURE.md +190 -0
- package/COMPONENT_CONVERSION_ANALYSIS.md +371 -0
- package/CONTEXT_VARIANT_GUIDE.md +425 -0
- package/LAZY_LOADING_GUIDE.md +534 -0
- package/LICENSE +201 -0
- package/PLAYBACK_EXAMPLES.md +658 -0
- package/PLAYBACK_GUIDE.md +402 -0
- package/README.md +43 -0
- package/REGISTRY_SUMMARY.md +638 -0
- package/SUFFERING_ECONOMICS.md +346 -0
- package/SUFFERING_ECONOMICS.zip +0 -0
- package/VIEW_REGISTRATION_GUIDE.md +795 -0
- package/dist/composables/index.cjs +1321 -0
- package/dist/composables/index.cjs.map +1 -0
- package/dist/composables/index.d.cts +6 -0
- package/dist/composables/index.d.ts +6 -0
- package/dist/composables/index.js +1276 -0
- package/dist/composables/index.js.map +1 -0
- package/dist/index-CMLU1hKL.d.ts +374 -0
- package/dist/index-CSDjhiKV.d.cts +374 -0
- package/dist/index.cjs +1534 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +93 -0
- package/dist/index.d.ts +93 -0
- package/dist/index.js +1463 -0
- package/dist/index.js.map +1 -0
- package/dist/registry/index.cjs +522 -0
- package/dist/registry/index.cjs.map +1 -0
- package/dist/registry/index.d.cts +46 -0
- package/dist/registry/index.d.ts +46 -0
- package/dist/registry/index.js +487 -0
- package/dist/registry/index.js.map +1 -0
- package/dist/types/index.cjs +19 -0
- package/dist/types/index.cjs.map +1 -0
- package/dist/types/index.d.cts +203 -0
- package/dist/types/index.d.ts +203 -0
- package/dist/types/index.js +1 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/index.cjs +217 -0
- package/dist/utils/index.cjs.map +1 -0
- package/dist/utils/index.d.cts +47 -0
- package/dist/utils/index.d.ts +47 -0
- package/dist/utils/index.js +188 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/view-loader-dD86QAlQ.d.cts +121 -0
- package/dist/view-loader-ii8AxiQR.d.ts +121 -0
- package/package.json +64 -0
- package/publish.log +0 -0
- package/src/composables/index.ts +59 -0
- package/src/composables/use-image-loader.ts +315 -0
- package/src/composables/use-transaction-playback.ts +396 -0
- package/src/composables/use-transaction-view.ts +309 -0
- package/src/config/lukso-config.ts +171 -0
- package/src/examples/context-aware-views.ts +300 -0
- package/src/examples/example-views.ts +197 -0
- package/src/examples/lit-plus-rn-strategy.ts +448 -0
- package/src/examples/lsp7-custom-matcher-examples.ts +174 -0
- package/src/examples/opt-in-context-examples.ts +372 -0
- package/src/examples/optimized-view-definitions.ts +408 -0
- package/src/examples/vue-to-lit-compilation.ts +358 -0
- package/src/hooks/useAddressQuery.ts +135 -0
- package/src/hooks/useAddressResolution.ts +437 -0
- package/src/index.ts +36 -0
- package/src/registry/index.ts +13 -0
- package/src/registry/view-loader.ts +305 -0
- package/src/registry/view-registry.ts +135 -0
- package/src/types/index.ts +16 -0
- package/src/types/view-contexts.ts +72 -0
- package/src/types/view-matching.ts +173 -0
- package/src/utils/context-matcher.ts +165 -0
- package/src/utils/index.ts +5 -0
- package/src/utils/view-matcher.ts +338 -0
- package/tsconfig.json +20 -0
- package/tsup.config.ts +34 -0
|
@@ -0,0 +1,795 @@
|
|
|
1
|
+
# View Registration Guide
|
|
2
|
+
|
|
3
|
+
This guide explains how to register transaction views in the registry, with patterns for creating custom view packages that bundle views with decoder enhancers.
|
|
4
|
+
|
|
5
|
+
## Framework Priority System
|
|
6
|
+
|
|
7
|
+
The registry automatically prefers native framework implementations:
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
Vue view: Base score + 0.15 bonus = preferred in Vue apps
|
|
11
|
+
RN view: Base score + 0.15 bonus = preferred in RN apps
|
|
12
|
+
Lit view: Base score + 0.00 bonus = universal fallback
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
**Example:** When both Vue and Lit views exist for LSP7 transfers:
|
|
16
|
+
- Vue app requests `framework: 'vue'` → Vue view gets 0.15 bonus → Vue view selected
|
|
17
|
+
- Angular app requests `framework: 'lit'` → Only Lit view available → Lit view selected
|
|
18
|
+
|
|
19
|
+
## Basic View Registration
|
|
20
|
+
|
|
21
|
+
### 1. Register Views at App Startup
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
// main.ts or app initialization
|
|
25
|
+
import { getGlobalRegistry } from '@lukso/transaction-view-headless'
|
|
26
|
+
import type { ViewDefinition } from '@lukso/transaction-view-headless'
|
|
27
|
+
|
|
28
|
+
const registry = getGlobalRegistry()
|
|
29
|
+
|
|
30
|
+
// Register a single view
|
|
31
|
+
const lsp7View: ViewDefinition = {
|
|
32
|
+
id: 'lsp7-transfer-custom',
|
|
33
|
+
name: 'Custom LSP7 Transfer',
|
|
34
|
+
priority: 100,
|
|
35
|
+
criteria: {
|
|
36
|
+
standard: 'LSP7',
|
|
37
|
+
recordType: 'lsp7-transfer'
|
|
38
|
+
},
|
|
39
|
+
frameworks: {
|
|
40
|
+
lit: {
|
|
41
|
+
loader: { type: 'custom-element', tagName: 'custom-lsp7-view' },
|
|
42
|
+
component: 'custom-lsp7-view'
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
registry.register(lsp7View)
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### 2. Register Multiple Views
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
import { getGlobalRegistry } from '@lukso/transaction-view-headless'
|
|
54
|
+
|
|
55
|
+
const registry = getGlobalRegistry()
|
|
56
|
+
|
|
57
|
+
const myViews: ViewDefinition[] = [
|
|
58
|
+
{ id: 'view-1', /* ... */ },
|
|
59
|
+
{ id: 'view-2', /* ... */ },
|
|
60
|
+
{ id: 'view-3', /* ... */ }
|
|
61
|
+
]
|
|
62
|
+
|
|
63
|
+
// Bulk register
|
|
64
|
+
registry.registerMany(myViews)
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### 3. Framework-Specific Registration
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
// Vue app - register native Vue views
|
|
71
|
+
const vueViews: ViewDefinition[] = [
|
|
72
|
+
{
|
|
73
|
+
id: 'lsp7-transfer-vue',
|
|
74
|
+
name: 'LSP7 Transfer (Vue)',
|
|
75
|
+
priority: 100,
|
|
76
|
+
criteria: {
|
|
77
|
+
standard: 'LSP7',
|
|
78
|
+
recordType: 'lsp7-transfer'
|
|
79
|
+
},
|
|
80
|
+
frameworks: {
|
|
81
|
+
lit: {
|
|
82
|
+
loader: { type: 'custom-element', tagName: 'lsp7-transfer-lit' },
|
|
83
|
+
component: 'lsp7-transfer-lit'
|
|
84
|
+
},
|
|
85
|
+
vue: {
|
|
86
|
+
loader: { type: 'dynamic-import', path: './components/LSP7Transfer.vue' },
|
|
87
|
+
component: 'LSP7Transfer'
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
]
|
|
92
|
+
|
|
93
|
+
registry.registerMany(vueViews)
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Creating a Custom View Package
|
|
97
|
+
|
|
98
|
+
### Package Structure
|
|
99
|
+
|
|
100
|
+
```
|
|
101
|
+
@myorg/custom-lukso-views/
|
|
102
|
+
├── src/
|
|
103
|
+
│ ├── enhancers/ # Decoder enhancers
|
|
104
|
+
│ │ ├── enhanceCustomToken.ts
|
|
105
|
+
│ │ └── index.ts
|
|
106
|
+
│ ├── views/
|
|
107
|
+
│ │ ├── lit/ # Lit web components
|
|
108
|
+
│ │ │ ├── custom-token-view.ts
|
|
109
|
+
│ │ │ └── index.ts
|
|
110
|
+
│ │ ├── vue/ # Vue components (optional)
|
|
111
|
+
│ │ │ ├── CustomTokenView.vue
|
|
112
|
+
│ │ │ └── index.ts
|
|
113
|
+
│ │ └── react-native/ # RN components (optional)
|
|
114
|
+
│ │ ├── CustomTokenView.tsx
|
|
115
|
+
│ │ └── index.ts
|
|
116
|
+
│ ├── registry/ # View definitions
|
|
117
|
+
│ │ ├── view-definitions.ts
|
|
118
|
+
│ │ └── register.ts
|
|
119
|
+
│ └── index.ts # Main export
|
|
120
|
+
├── package.json
|
|
121
|
+
└── README.md
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Example Package Implementation
|
|
125
|
+
|
|
126
|
+
#### 1. Decoder Enhancer (`src/enhancers/enhanceCustomToken.ts`)
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
import type { DecoderPlugin } from '@lukso/transaction-decoder'
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Enhancer for custom token transactions
|
|
133
|
+
*/
|
|
134
|
+
export const enhanceCustomToken: DecoderPlugin = {
|
|
135
|
+
name: 'CustomToken',
|
|
136
|
+
|
|
137
|
+
async enhance(transaction, context) {
|
|
138
|
+
// Check if this is our custom token
|
|
139
|
+
const contractAddress = transaction.to?.toLowerCase()
|
|
140
|
+
const isCustomToken = contractAddress === '0x123...' // Your token address
|
|
141
|
+
|
|
142
|
+
if (!isCustomToken) return transaction
|
|
143
|
+
|
|
144
|
+
// Add custom data
|
|
145
|
+
return {
|
|
146
|
+
...transaction,
|
|
147
|
+
standard: 'CustomToken',
|
|
148
|
+
recordType: 'custom-token-transfer',
|
|
149
|
+
enhancedInfo: {
|
|
150
|
+
...transaction.enhancedInfo,
|
|
151
|
+
customData: await fetchCustomTokenData(contractAddress)
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
async function fetchCustomTokenData(address: string) {
|
|
158
|
+
// Fetch custom metadata
|
|
159
|
+
return {
|
|
160
|
+
// ... custom data
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
#### 2. Lit View (`src/views/lit/custom-token-view.ts`)
|
|
166
|
+
|
|
167
|
+
```typescript
|
|
168
|
+
import { LitElement, html, css } from 'lit'
|
|
169
|
+
import { customElement, property } from 'lit/decorators.js'
|
|
170
|
+
|
|
171
|
+
@customElement('custom-token-view')
|
|
172
|
+
export class CustomTokenView extends LitElement {
|
|
173
|
+
@property({ type: Object })
|
|
174
|
+
transaction: any
|
|
175
|
+
|
|
176
|
+
static styles = css`
|
|
177
|
+
:host {
|
|
178
|
+
display: block;
|
|
179
|
+
padding: 16px;
|
|
180
|
+
}
|
|
181
|
+
`
|
|
182
|
+
|
|
183
|
+
render() {
|
|
184
|
+
return html`
|
|
185
|
+
<div class="custom-token">
|
|
186
|
+
<h3>Custom Token Transfer</h3>
|
|
187
|
+
<p>Amount: ${this.transaction.value}</p>
|
|
188
|
+
<!-- Custom rendering -->
|
|
189
|
+
</div>
|
|
190
|
+
`
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
#### 3. Vue View (Optional) (`src/views/vue/CustomTokenView.vue`)
|
|
196
|
+
|
|
197
|
+
```vue
|
|
198
|
+
<template>
|
|
199
|
+
<div class="custom-token">
|
|
200
|
+
<h3>Custom Token Transfer</h3>
|
|
201
|
+
<p>Amount: {{ transaction.value }}</p>
|
|
202
|
+
<!-- Custom rendering -->
|
|
203
|
+
</div>
|
|
204
|
+
</template>
|
|
205
|
+
|
|
206
|
+
<script setup lang="ts">
|
|
207
|
+
import type { DecoderResult } from '@lukso/transaction-decoder'
|
|
208
|
+
|
|
209
|
+
const props = defineProps<{
|
|
210
|
+
transaction: DecoderResult
|
|
211
|
+
}>()
|
|
212
|
+
</script>
|
|
213
|
+
|
|
214
|
+
<style scoped>
|
|
215
|
+
.custom-token {
|
|
216
|
+
padding: 16px;
|
|
217
|
+
}
|
|
218
|
+
</style>
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
#### 4. View Definitions (`src/registry/view-definitions.ts`)
|
|
222
|
+
|
|
223
|
+
```typescript
|
|
224
|
+
import type { ViewDefinition } from '@lukso/transaction-view-headless'
|
|
225
|
+
|
|
226
|
+
export const customTokenViews: ViewDefinition[] = [
|
|
227
|
+
{
|
|
228
|
+
id: 'custom-token-transfer',
|
|
229
|
+
name: 'Custom Token Transfer',
|
|
230
|
+
description: 'View for our custom token transfers',
|
|
231
|
+
priority: 120, // Higher priority than generic views
|
|
232
|
+
criteria: {
|
|
233
|
+
standard: 'CustomToken',
|
|
234
|
+
recordType: 'custom-token-transfer',
|
|
235
|
+
contractAddress: '0x123...' // Specific to your token
|
|
236
|
+
},
|
|
237
|
+
frameworks: {
|
|
238
|
+
lit: {
|
|
239
|
+
loader: { type: 'custom-element', tagName: 'custom-token-view' },
|
|
240
|
+
component: 'custom-token-view',
|
|
241
|
+
package: '@myorg/custom-lukso-views'
|
|
242
|
+
},
|
|
243
|
+
vue: {
|
|
244
|
+
loader: {
|
|
245
|
+
type: 'dynamic-import',
|
|
246
|
+
path: '@myorg/custom-lukso-views/vue/CustomTokenView.vue'
|
|
247
|
+
},
|
|
248
|
+
component: 'CustomTokenView',
|
|
249
|
+
package: '@myorg/custom-lukso-views'
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
]
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
#### 5. Registration Helper (`src/registry/register.ts`)
|
|
257
|
+
|
|
258
|
+
```typescript
|
|
259
|
+
import { getGlobalRegistry } from '@lukso/transaction-view-headless'
|
|
260
|
+
import { customTokenViews } from './view-definitions'
|
|
261
|
+
import { enhanceCustomToken } from '../enhancers'
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Register custom token views and enhancers
|
|
265
|
+
* Call this once at app startup
|
|
266
|
+
*/
|
|
267
|
+
export function registerCustomTokenViews(options?: {
|
|
268
|
+
/** Whether to load Lit components */
|
|
269
|
+
loadLit?: boolean
|
|
270
|
+
/** Whether to load Vue components */
|
|
271
|
+
loadVue?: boolean
|
|
272
|
+
/** Whether to load React Native components */
|
|
273
|
+
loadReactNative?: boolean
|
|
274
|
+
}) {
|
|
275
|
+
const {
|
|
276
|
+
loadLit = true,
|
|
277
|
+
loadVue = false,
|
|
278
|
+
loadReactNative = false
|
|
279
|
+
} = options || {}
|
|
280
|
+
|
|
281
|
+
// Register view definitions
|
|
282
|
+
const registry = getGlobalRegistry()
|
|
283
|
+
registry.registerMany(customTokenViews)
|
|
284
|
+
|
|
285
|
+
// Load framework-specific components
|
|
286
|
+
if (loadLit) {
|
|
287
|
+
// Import Lit components to register custom elements
|
|
288
|
+
import('../views/lit')
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (loadVue) {
|
|
292
|
+
// Vue components loaded on-demand via dynamic import
|
|
293
|
+
// No need to do anything here, loader handles it
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if (loadReactNative) {
|
|
297
|
+
// React Native components loaded on-demand
|
|
298
|
+
// No need to do anything here, loader handles it
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
console.log('✓ Custom token views registered')
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Register custom token decoder enhancers
|
|
306
|
+
* Add this plugin to your decoder configuration
|
|
307
|
+
*/
|
|
308
|
+
export function getCustomTokenEnhancers() {
|
|
309
|
+
return [enhanceCustomToken]
|
|
310
|
+
}
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
#### 6. Main Export (`src/index.ts`)
|
|
314
|
+
|
|
315
|
+
```typescript
|
|
316
|
+
// Export registration functions
|
|
317
|
+
export {
|
|
318
|
+
registerCustomTokenViews,
|
|
319
|
+
getCustomTokenEnhancers
|
|
320
|
+
} from './registry/register'
|
|
321
|
+
|
|
322
|
+
// Export view definitions (for inspection)
|
|
323
|
+
export { customTokenViews } from './registry/view-definitions'
|
|
324
|
+
|
|
325
|
+
// Export enhancers (for manual registration)
|
|
326
|
+
export { enhanceCustomToken } from './enhancers'
|
|
327
|
+
|
|
328
|
+
// Export Lit components
|
|
329
|
+
export * from './views/lit'
|
|
330
|
+
|
|
331
|
+
// Export Vue components (tree-shakeable)
|
|
332
|
+
export { default as CustomTokenView } from './views/vue/CustomTokenView.vue'
|
|
333
|
+
|
|
334
|
+
// Export types
|
|
335
|
+
export type { CustomTokenData } from './types'
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
### Usage in Your App
|
|
339
|
+
|
|
340
|
+
#### Vue App
|
|
341
|
+
|
|
342
|
+
```typescript
|
|
343
|
+
// main.ts
|
|
344
|
+
import { createApp } from 'vue'
|
|
345
|
+
import { initializeDecoder } from '@lukso/transaction-decoder'
|
|
346
|
+
import {
|
|
347
|
+
registerCustomTokenViews,
|
|
348
|
+
getCustomTokenEnhancers
|
|
349
|
+
} from '@myorg/custom-lukso-views'
|
|
350
|
+
|
|
351
|
+
// Initialize decoder with custom enhancers
|
|
352
|
+
const decoder = initializeDecoder({
|
|
353
|
+
enhancers: [
|
|
354
|
+
...getCustomTokenEnhancers(),
|
|
355
|
+
// ... other enhancers
|
|
356
|
+
]
|
|
357
|
+
})
|
|
358
|
+
|
|
359
|
+
// Register custom views (load Vue + Lit)
|
|
360
|
+
registerCustomTokenViews({
|
|
361
|
+
loadVue: true, // Load native Vue views (gets priority bonus)
|
|
362
|
+
loadLit: true // Load Lit fallbacks
|
|
363
|
+
})
|
|
364
|
+
|
|
365
|
+
const app = createApp(App)
|
|
366
|
+
app.mount('#app')
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
#### React Native App
|
|
370
|
+
|
|
371
|
+
```typescript
|
|
372
|
+
// index.tsx
|
|
373
|
+
import { AppRegistry } from 'react-native'
|
|
374
|
+
import { initializeDecoder } from '@lukso/transaction-decoder'
|
|
375
|
+
import {
|
|
376
|
+
registerCustomTokenViews,
|
|
377
|
+
getCustomTokenEnhancers
|
|
378
|
+
} from '@myorg/custom-lukso-views'
|
|
379
|
+
|
|
380
|
+
// Initialize decoder with custom enhancers
|
|
381
|
+
const decoder = initializeDecoder({
|
|
382
|
+
enhancers: getCustomTokenEnhancers()
|
|
383
|
+
})
|
|
384
|
+
|
|
385
|
+
// Register custom views (load RN only)
|
|
386
|
+
registerCustomTokenViews({
|
|
387
|
+
loadReactNative: true, // Load native RN views
|
|
388
|
+
loadLit: false // Skip Lit for RN
|
|
389
|
+
})
|
|
390
|
+
|
|
391
|
+
AppRegistry.registerComponent('App', () => App)
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
#### Angular/Svelte/Vanilla (Lit Only)
|
|
395
|
+
|
|
396
|
+
```typescript
|
|
397
|
+
// main.ts
|
|
398
|
+
import { initializeDecoder } from '@lukso/transaction-decoder'
|
|
399
|
+
import {
|
|
400
|
+
registerCustomTokenViews,
|
|
401
|
+
getCustomTokenEnhancers
|
|
402
|
+
} from '@myorg/custom-lukso-views'
|
|
403
|
+
|
|
404
|
+
// Initialize decoder with custom enhancers
|
|
405
|
+
const decoder = initializeDecoder({
|
|
406
|
+
enhancers: getCustomTokenEnhancers()
|
|
407
|
+
})
|
|
408
|
+
|
|
409
|
+
// Register custom views (Lit only)
|
|
410
|
+
registerCustomTokenViews({
|
|
411
|
+
loadLit: true // Lit works everywhere
|
|
412
|
+
})
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
## Advanced Patterns
|
|
416
|
+
|
|
417
|
+
### 1. Lazy Loading Views
|
|
418
|
+
|
|
419
|
+
```typescript
|
|
420
|
+
// Only register view definitions, load components on-demand
|
|
421
|
+
const lazyViews: ViewDefinition[] = [
|
|
422
|
+
{
|
|
423
|
+
id: 'heavy-view',
|
|
424
|
+
name: 'Heavy View',
|
|
425
|
+
priority: 100,
|
|
426
|
+
criteria: { recordType: 'heavy-transaction' },
|
|
427
|
+
frameworks: {
|
|
428
|
+
lit: {
|
|
429
|
+
loader: {
|
|
430
|
+
type: 'dynamic-import',
|
|
431
|
+
path: () => import('./heavy-view') // Lazy loaded!
|
|
432
|
+
},
|
|
433
|
+
component: 'heavy-view'
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
]
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
### 2. Context-Specific Views
|
|
441
|
+
|
|
442
|
+
```typescript
|
|
443
|
+
const contextViews: ViewDefinition[] = [
|
|
444
|
+
{
|
|
445
|
+
id: 'approval-view',
|
|
446
|
+
name: 'Approval View',
|
|
447
|
+
priority: 150,
|
|
448
|
+
criteria: {
|
|
449
|
+
recordType: 'lsp7-transfer',
|
|
450
|
+
context: 'approval' // Only matches approval context
|
|
451
|
+
},
|
|
452
|
+
frameworks: {
|
|
453
|
+
lit: { /* ... */ }
|
|
454
|
+
}
|
|
455
|
+
},
|
|
456
|
+
{
|
|
457
|
+
id: 'feed-view',
|
|
458
|
+
name: 'Feed View',
|
|
459
|
+
priority: 140,
|
|
460
|
+
criteria: {
|
|
461
|
+
recordType: 'lsp7-transfer',
|
|
462
|
+
context: ['feed', 'list'] // Matches feed OR list
|
|
463
|
+
},
|
|
464
|
+
frameworks: {
|
|
465
|
+
lit: { /* ... */ }
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
]
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
### 3. Chain-Specific Views
|
|
472
|
+
|
|
473
|
+
```typescript
|
|
474
|
+
const chainViews: ViewDefinition[] = [
|
|
475
|
+
{
|
|
476
|
+
id: 'mainnet-only-view',
|
|
477
|
+
name: 'Mainnet Only',
|
|
478
|
+
priority: 120,
|
|
479
|
+
criteria: {
|
|
480
|
+
recordType: 'special-transaction',
|
|
481
|
+
chainId: 42 // LUKSO mainnet only
|
|
482
|
+
},
|
|
483
|
+
frameworks: {
|
|
484
|
+
lit: { /* ... */ }
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
]
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
### 4. Custom Matcher Logic (Numeric Scoring)
|
|
491
|
+
|
|
492
|
+
Custom matchers now return a **match count** (number) instead of boolean, allowing for more granular scoring:
|
|
493
|
+
|
|
494
|
+
```typescript
|
|
495
|
+
const advancedViews: ViewDefinition[] = [
|
|
496
|
+
{
|
|
497
|
+
id: 'lsp7-token-transfer',
|
|
498
|
+
name: 'LSP7 Token Transfer',
|
|
499
|
+
priority: 100,
|
|
500
|
+
criteria: {
|
|
501
|
+
standard: 'LSP7',
|
|
502
|
+
functionName: 'transfer',
|
|
503
|
+
customMatcher: (transaction) => {
|
|
504
|
+
let matches = 0
|
|
505
|
+
// Check metadata populated by enhancer
|
|
506
|
+
if (transaction.custom?.tokenType === 0) matches++
|
|
507
|
+
if (transaction.custom?.isToken === true) matches++
|
|
508
|
+
// Check transaction properties
|
|
509
|
+
if (BigInt(transaction.value || 0) > 0) matches++
|
|
510
|
+
return matches // 0-3 score (normalized to 0-1)
|
|
511
|
+
}
|
|
512
|
+
},
|
|
513
|
+
frameworks: {
|
|
514
|
+
lit: { /* ... */ }
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
]
|
|
518
|
+
```
|
|
519
|
+
|
|
520
|
+
**Key Points:**
|
|
521
|
+
- Return `0` for no match (view won't be selected)
|
|
522
|
+
- Return `1-10` for matching conditions (higher = better match)
|
|
523
|
+
- Scores are normalized: `Math.min(matchCount / 10, 1)`
|
|
524
|
+
- Can access `transaction.custom` for enhancer-populated metadata
|
|
525
|
+
|
|
526
|
+
#### Example: Token Type Differentiation
|
|
527
|
+
|
|
528
|
+
```typescript
|
|
529
|
+
// LSP7 Token view (tokenType === 0)
|
|
530
|
+
const tokenView: ViewDefinition = {
|
|
531
|
+
id: 'lsp7-token-transfer',
|
|
532
|
+
name: 'LSP7 Token Transfer',
|
|
533
|
+
priority: 100,
|
|
534
|
+
criteria: {
|
|
535
|
+
standard: 'LSP7',
|
|
536
|
+
functionName: 'transfer',
|
|
537
|
+
customMatcher: (tx) => {
|
|
538
|
+
let matches = 0
|
|
539
|
+
if (tx.custom?.tokenType === 0) matches++
|
|
540
|
+
if (tx.custom?.isToken === true) matches++
|
|
541
|
+
return matches // Returns 2 if token, 0 if not
|
|
542
|
+
}
|
|
543
|
+
},
|
|
544
|
+
frameworks: { /* ... */ }
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
// LSP7 NFT view (tokenType === 1)
|
|
548
|
+
const nftView: ViewDefinition = {
|
|
549
|
+
id: 'lsp7-nft-transfer',
|
|
550
|
+
name: 'LSP7 NFT Transfer',
|
|
551
|
+
priority: 100,
|
|
552
|
+
criteria: {
|
|
553
|
+
standard: 'LSP7',
|
|
554
|
+
functionName: 'transfer',
|
|
555
|
+
customMatcher: (tx) => {
|
|
556
|
+
let matches = 0
|
|
557
|
+
if (tx.custom?.tokenType === 1) matches++
|
|
558
|
+
if (tx.custom?.isNFT === true) matches++
|
|
559
|
+
return matches // Returns 2 if NFT, 0 if not
|
|
560
|
+
}
|
|
561
|
+
},
|
|
562
|
+
frameworks: { /* ... */ }
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
// Generic fallback (no customMatcher)
|
|
566
|
+
const genericView: ViewDefinition = {
|
|
567
|
+
id: 'lsp7-generic-transfer',
|
|
568
|
+
name: 'LSP7 Transfer',
|
|
569
|
+
priority: 50, // Lower priority
|
|
570
|
+
criteria: {
|
|
571
|
+
standard: 'LSP7',
|
|
572
|
+
functionName: 'transfer'
|
|
573
|
+
// No customMatcher - matches all LSP7 transfers
|
|
574
|
+
},
|
|
575
|
+
frameworks: { /* ... */ }
|
|
576
|
+
}
|
|
577
|
+
```
|
|
578
|
+
|
|
579
|
+
#### Populating Metadata in Enhancers
|
|
580
|
+
|
|
581
|
+
To use `transaction.custom` for view matching, populate it in your enhancer:
|
|
582
|
+
|
|
583
|
+
```typescript
|
|
584
|
+
// In your decoder enhancer
|
|
585
|
+
export const enhanceLSP7Metadata: EnhancerCallback = async (
|
|
586
|
+
result,
|
|
587
|
+
pluginOptions,
|
|
588
|
+
options
|
|
589
|
+
) => {
|
|
590
|
+
if (!result.to || !options.cache) return result
|
|
591
|
+
|
|
592
|
+
// Use cache to avoid redundant RPC calls for batch operations
|
|
593
|
+
const metadata = await options.cache.getOrSet(
|
|
594
|
+
`contract-meta:${result.to}:lsp4-token-type`,
|
|
595
|
+
async () => {
|
|
596
|
+
const client = getPublicClient(options.chain)
|
|
597
|
+
const tokenTypeData = await client.readContract({
|
|
598
|
+
address: result.to,
|
|
599
|
+
abi: LSP7_ABI,
|
|
600
|
+
functionName: 'getData',
|
|
601
|
+
args: ['0xe0261fa95db2eb3b5439bd033cda66d56b96f92f243a8228fd87550ed7bdfdb3']
|
|
602
|
+
})
|
|
603
|
+
|
|
604
|
+
const typeValue = tokenTypeData ? parseInt(tokenTypeData as string, 16) : 0
|
|
605
|
+
|
|
606
|
+
return {
|
|
607
|
+
tokenType: typeValue,
|
|
608
|
+
isToken: typeValue === 0,
|
|
609
|
+
isNFT: typeValue === 1,
|
|
610
|
+
isCollection: typeValue === 2
|
|
611
|
+
}
|
|
612
|
+
},
|
|
613
|
+
{ ttl: 1000 * 60 * 60 * 24 } // 24 hour cache
|
|
614
|
+
)
|
|
615
|
+
|
|
616
|
+
// Populate custom field for view matching
|
|
617
|
+
result.custom = result.custom || {}
|
|
618
|
+
Object.assign(result.custom, metadata)
|
|
619
|
+
|
|
620
|
+
return result
|
|
621
|
+
}
|
|
622
|
+
```
|
|
623
|
+
|
|
624
|
+
**Benefits:**
|
|
625
|
+
- Metadata fetched once per contract (cached)
|
|
626
|
+
- Shared across batch operations (5 transactions = 1 RPC call)
|
|
627
|
+
- Synchronous view matching (no async in customMatcher)
|
|
628
|
+
- Graceful degradation (fallback views when metadata missing)
|
|
629
|
+
|
|
630
|
+
## Package Distribution
|
|
631
|
+
|
|
632
|
+
### NPM Package
|
|
633
|
+
|
|
634
|
+
```json
|
|
635
|
+
// package.json
|
|
636
|
+
{
|
|
637
|
+
"name": "@myorg/custom-lukso-views",
|
|
638
|
+
"version": "1.0.0",
|
|
639
|
+
"type": "module",
|
|
640
|
+
"main": "./dist/index.js",
|
|
641
|
+
"types": "./dist/index.d.ts",
|
|
642
|
+
"exports": {
|
|
643
|
+
".": "./dist/index.js",
|
|
644
|
+
"./lit": "./dist/views/lit/index.js",
|
|
645
|
+
"./vue": "./dist/views/vue/index.js",
|
|
646
|
+
"./react-native": "./dist/views/react-native/index.js",
|
|
647
|
+
"./enhancers": "./dist/enhancers/index.js",
|
|
648
|
+
"./registry": "./dist/registry/index.js"
|
|
649
|
+
},
|
|
650
|
+
"peerDependencies": {
|
|
651
|
+
"@lukso/transaction-decoder": "^1.0.0",
|
|
652
|
+
"@lukso/transaction-view-headless": "^1.0.0",
|
|
653
|
+
"lit": "^3.0.0",
|
|
654
|
+
"vue": "^3.0.0"
|
|
655
|
+
},
|
|
656
|
+
"peerDependenciesMeta": {
|
|
657
|
+
"vue": {
|
|
658
|
+
"optional": true
|
|
659
|
+
},
|
|
660
|
+
"react-native": {
|
|
661
|
+
"optional": true
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
```
|
|
666
|
+
|
|
667
|
+
### Installation
|
|
668
|
+
|
|
669
|
+
```bash
|
|
670
|
+
# Install the package
|
|
671
|
+
npm install @myorg/custom-lukso-views
|
|
672
|
+
|
|
673
|
+
# For Vue apps (peer deps)
|
|
674
|
+
npm install vue @lukso/transaction-view-headless
|
|
675
|
+
|
|
676
|
+
# For React Native apps
|
|
677
|
+
npm install react-native @lukso/transaction-view-headless
|
|
678
|
+
```
|
|
679
|
+
|
|
680
|
+
## Best Practices
|
|
681
|
+
|
|
682
|
+
### 1. Priority Guidelines
|
|
683
|
+
- **Core LUKSO standards** (LSP7, LSP8): 100-110
|
|
684
|
+
- **Custom tokens/extensions**: 120-130
|
|
685
|
+
- **Specific contracts**: 140-150
|
|
686
|
+
- **Context-specific views**: 150-160
|
|
687
|
+
- **Fallbacks**: 10-50
|
|
688
|
+
|
|
689
|
+
### 2. Framework Support Strategy
|
|
690
|
+
- **Always provide Lit**: Universal fallback for all web frameworks
|
|
691
|
+
- **Add Vue optionally**: When you want Vue-specific optimizations
|
|
692
|
+
- **Add RN optionally**: When you want native mobile performance
|
|
693
|
+
|
|
694
|
+
### 3. View ID Naming
|
|
695
|
+
- Use descriptive, unique IDs: `custom-token-transfer-mainnet`
|
|
696
|
+
- Include namespace for custom views: `@myorg/special-view`
|
|
697
|
+
- Avoid conflicts with core views
|
|
698
|
+
|
|
699
|
+
### 4. Registration Timing
|
|
700
|
+
- Register views **before** rendering any transactions
|
|
701
|
+
- Register enhancers **when configuring the decoder**
|
|
702
|
+
- Use a single `registerViews()` call at app startup
|
|
703
|
+
|
|
704
|
+
### 5. Testing
|
|
705
|
+
```typescript
|
|
706
|
+
import { getGlobalRegistry } from '@lukso/transaction-view-headless'
|
|
707
|
+
|
|
708
|
+
// Test view registration
|
|
709
|
+
const registry = getGlobalRegistry()
|
|
710
|
+
registerCustomTokenViews()
|
|
711
|
+
|
|
712
|
+
const stats = registry.getStats()
|
|
713
|
+
console.log('Registered views:', stats.totalViews)
|
|
714
|
+
console.log('By framework:', stats.viewsByFramework)
|
|
715
|
+
|
|
716
|
+
// Test view selection
|
|
717
|
+
const matches = registry.findMatches(mockTransaction, {
|
|
718
|
+
framework: 'vue',
|
|
719
|
+
minScore: 0.5
|
|
720
|
+
})
|
|
721
|
+
console.log('Best match:', matches[0]?.view.id)
|
|
722
|
+
```
|
|
723
|
+
|
|
724
|
+
## Migration from Old System
|
|
725
|
+
|
|
726
|
+
### Before (Manual component selection)
|
|
727
|
+
```vue
|
|
728
|
+
<template>
|
|
729
|
+
<LSP7Transfer v-if="isLSP7" />
|
|
730
|
+
<LSP8Transfer v-else-if="isLSP8" />
|
|
731
|
+
<GenericTransaction v-else />
|
|
732
|
+
</template>
|
|
733
|
+
```
|
|
734
|
+
|
|
735
|
+
### After (Registry-based)
|
|
736
|
+
```vue
|
|
737
|
+
<template>
|
|
738
|
+
<TransactionPlayback
|
|
739
|
+
:transaction="transaction"
|
|
740
|
+
:index="1"
|
|
741
|
+
framework="vue"
|
|
742
|
+
/>
|
|
743
|
+
</template>
|
|
744
|
+
```
|
|
745
|
+
|
|
746
|
+
The registry handles all view selection automatically!
|
|
747
|
+
|
|
748
|
+
## Troubleshooting
|
|
749
|
+
|
|
750
|
+
### Issue: Custom view not selected
|
|
751
|
+
|
|
752
|
+
**Solution:** Check priority and criteria:
|
|
753
|
+
```typescript
|
|
754
|
+
const registry = getGlobalRegistry()
|
|
755
|
+
const matches = registry.findMatches(transaction, {
|
|
756
|
+
framework: 'vue',
|
|
757
|
+
minScore: 0
|
|
758
|
+
})
|
|
759
|
+
console.log('All matches:', matches.map(m => ({
|
|
760
|
+
id: m.view.id,
|
|
761
|
+
score: m.score,
|
|
762
|
+
matched: m.matchedCriteria
|
|
763
|
+
})))
|
|
764
|
+
```
|
|
765
|
+
|
|
766
|
+
### Issue: Lit view selected instead of Vue view
|
|
767
|
+
|
|
768
|
+
**Solution:** Ensure Vue view is registered and framework is 'vue':
|
|
769
|
+
```typescript
|
|
770
|
+
// Check if Vue view exists
|
|
771
|
+
const view = registry.getView('my-vue-view')
|
|
772
|
+
console.log('Has Vue framework:', !!view?.frameworks.vue)
|
|
773
|
+
|
|
774
|
+
// Request with correct framework
|
|
775
|
+
const result = useTransactionPlayback(transaction, 1, {
|
|
776
|
+
framework: 'vue' // NOT 'lit'
|
|
777
|
+
})
|
|
778
|
+
```
|
|
779
|
+
|
|
780
|
+
### Issue: Components not loading
|
|
781
|
+
|
|
782
|
+
**Solution:** Ensure components are imported:
|
|
783
|
+
```typescript
|
|
784
|
+
// For Lit components
|
|
785
|
+
import '@myorg/custom-lukso-views/lit'
|
|
786
|
+
|
|
787
|
+
// For Vue components (lazy loaded automatically)
|
|
788
|
+
// No import needed, loader handles it
|
|
789
|
+
```
|
|
790
|
+
|
|
791
|
+
## Next Steps
|
|
792
|
+
|
|
793
|
+
- See [PLAYBACK_GUIDE.md](./PLAYBACK_GUIDE.md) for playback system
|
|
794
|
+
- See [PLAYBACK_EXAMPLES.md](./PLAYBACK_EXAMPLES.md) for usage examples
|
|
795
|
+
- See [COMPONENT_ARCHITECTURE.md](../../docs/COMPONENT_ARCHITECTURE.md) for architecture
|