@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,638 @@
|
|
|
1
|
+
# Transaction View Registry - Complete System
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
The view registry system provides a flexible, framework-agnostic way to register and select transaction views with automatic priority for native implementations.
|
|
6
|
+
|
|
7
|
+
## Key Features
|
|
8
|
+
|
|
9
|
+
### 1. **Framework Priority System**
|
|
10
|
+
```
|
|
11
|
+
Vue view: Base score + 0.15 bonus = Preferred in Vue apps
|
|
12
|
+
RN view: Base score + 0.15 bonus = Preferred in RN apps
|
|
13
|
+
Lit view: Base score + 0.00 bonus = Universal fallback
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
### 2. **Index-Based Playback**
|
|
17
|
+
```
|
|
18
|
+
Single Transaction:
|
|
19
|
+
Index 0 → transaction
|
|
20
|
+
Index 1 → transaction (same)
|
|
21
|
+
|
|
22
|
+
Batch Transaction:
|
|
23
|
+
Index 0 → summary/expandable
|
|
24
|
+
Index 1 → children[0]
|
|
25
|
+
Index 2 → children[1]
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### 3. **Automatic View Selection**
|
|
29
|
+
- Match by `standard`, `recordType`, `functionName`, `contractAddress`, `chainId`
|
|
30
|
+
- Context-aware matching (approval, feed, list, detail)
|
|
31
|
+
- Custom matcher functions
|
|
32
|
+
- Priority-based sorting
|
|
33
|
+
|
|
34
|
+
## Complete Architecture
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
┌─────────────────────────────────────────────────────────┐
|
|
38
|
+
│ Your Application │
|
|
39
|
+
├─────────────────────────────────────────────────────────┤
|
|
40
|
+
│ │
|
|
41
|
+
│ 1. Register enhancers (decoder plugins) │
|
|
42
|
+
│ 2. Register views (view definitions) │
|
|
43
|
+
│ 3. Use playback component │
|
|
44
|
+
│ │
|
|
45
|
+
└─────────────────┬───────────────────────────────────────┘
|
|
46
|
+
│
|
|
47
|
+
┌─────────────────▼───────────────────────────────────────┐
|
|
48
|
+
│ @lukso/transaction-view-headless │
|
|
49
|
+
│ │
|
|
50
|
+
│ ┌────────────────┐ ┌────────────────┐ │
|
|
51
|
+
│ │ View Registry │ │ View Matcher │ │
|
|
52
|
+
│ │ - register() │ │ - scoring │ │
|
|
53
|
+
│ │ - findMatch() │ │ - framework │ │
|
|
54
|
+
│ │ │ │ priority │ │
|
|
55
|
+
│ └────────────────┘ └────────────────┘ │
|
|
56
|
+
│ │
|
|
57
|
+
│ ┌────────────────────────────────────┐ │
|
|
58
|
+
│ │ Playback System │ │
|
|
59
|
+
│ │ - useTransactionPlayback() │ │
|
|
60
|
+
│ │ - createTransactionPlayback() │ │
|
|
61
|
+
│ │ - Index-based selection │ │
|
|
62
|
+
│ └────────────────────────────────────┘ │
|
|
63
|
+
└─────────────────┬───────────────────────────────────────┘
|
|
64
|
+
│
|
|
65
|
+
┌─────────┼─────────┐
|
|
66
|
+
│ │ │
|
|
67
|
+
┌───────▼───┐ ┌──▼─────┐ ┌─▼─────────┐
|
|
68
|
+
│ Lit │ │ Vue │ │React Native│
|
|
69
|
+
│ Component │ │Component│ │ Component │
|
|
70
|
+
└───────────┘ └────────┘ └────────────┘
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Quick Start
|
|
74
|
+
|
|
75
|
+
### 1. Register Views at Startup
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
// main.ts
|
|
79
|
+
import { getGlobalRegistry } from '@lukso/transaction-view-headless'
|
|
80
|
+
import type { ViewDefinition } from '@lukso/transaction-view-headless'
|
|
81
|
+
|
|
82
|
+
const registry = getGlobalRegistry()
|
|
83
|
+
|
|
84
|
+
const myViews: ViewDefinition[] = [
|
|
85
|
+
{
|
|
86
|
+
id: 'lsp7-transfer-custom',
|
|
87
|
+
name: 'Custom LSP7 Transfer',
|
|
88
|
+
priority: 120,
|
|
89
|
+
criteria: {
|
|
90
|
+
standard: 'LSP7',
|
|
91
|
+
recordType: 'lsp7-transfer'
|
|
92
|
+
},
|
|
93
|
+
frameworks: {
|
|
94
|
+
lit: {
|
|
95
|
+
loader: { type: 'custom-element', tagName: 'custom-lsp7-view' },
|
|
96
|
+
component: 'custom-lsp7-view'
|
|
97
|
+
},
|
|
98
|
+
vue: {
|
|
99
|
+
loader: { type: 'dynamic-import', path: './CustomLSP7.vue' },
|
|
100
|
+
component: 'CustomLSP7'
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
]
|
|
105
|
+
|
|
106
|
+
registry.registerMany(myViews)
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### 2. Use Playback Component
|
|
110
|
+
|
|
111
|
+
```vue
|
|
112
|
+
<template>
|
|
113
|
+
<TransactionPlayback
|
|
114
|
+
:transaction="transaction"
|
|
115
|
+
:index="1"
|
|
116
|
+
framework="vue"
|
|
117
|
+
/>
|
|
118
|
+
</template>
|
|
119
|
+
|
|
120
|
+
<script setup lang="ts">
|
|
121
|
+
import { TransactionPlayback } from '@lukso/transaction-view-core-vue'
|
|
122
|
+
import type { DecoderResult } from '@lukso/transaction-decoder'
|
|
123
|
+
|
|
124
|
+
const props = defineProps<{
|
|
125
|
+
transaction: DecoderResult
|
|
126
|
+
}>()
|
|
127
|
+
</script>
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Framework Selection Logic
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
// Example: LSP7 transfer with both Vue and Lit views registered
|
|
134
|
+
|
|
135
|
+
// Vue app
|
|
136
|
+
const result = useTransactionPlayback(transaction, 1, {
|
|
137
|
+
framework: 'vue' // Selects Vue view (score + 0.15 bonus)
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
// Angular app
|
|
141
|
+
const result = useTransactionPlayback(transaction, 1, {
|
|
142
|
+
framework: 'lit' // Selects Lit view (universal fallback)
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
// React Native app
|
|
146
|
+
const result = useTransactionPlayback(transaction, 1, {
|
|
147
|
+
framework: 'rn' // Would select RN view if registered (score + 0.15)
|
|
148
|
+
// Falls back to Lit if no RN view
|
|
149
|
+
})
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## Custom View Packages
|
|
153
|
+
|
|
154
|
+
Package structure for bundling views with enhancers:
|
|
155
|
+
|
|
156
|
+
```
|
|
157
|
+
@myorg/custom-lukso-views/
|
|
158
|
+
├── src/
|
|
159
|
+
│ ├── enhancers/ # Decoder plugins
|
|
160
|
+
│ ├── views/
|
|
161
|
+
│ │ ├── lit/ # Lit components (required)
|
|
162
|
+
│ │ ├── vue/ # Vue components (optional)
|
|
163
|
+
│ │ └── react-native/ # RN components (optional)
|
|
164
|
+
│ ├── registry/
|
|
165
|
+
│ │ ├── view-definitions.ts
|
|
166
|
+
│ │ └── register.ts
|
|
167
|
+
│ └── index.ts
|
|
168
|
+
└── package.json
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Usage:
|
|
172
|
+
```typescript
|
|
173
|
+
import {
|
|
174
|
+
registerCustomViews,
|
|
175
|
+
getCustomEnhancers
|
|
176
|
+
} from '@myorg/custom-lukso-views'
|
|
177
|
+
|
|
178
|
+
// Register decoder enhancers
|
|
179
|
+
const decoder = initializeDecoder({
|
|
180
|
+
enhancers: getCustomEnhancers()
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
// Register views
|
|
184
|
+
registerCustomViews({
|
|
185
|
+
loadVue: true, // Load native Vue views
|
|
186
|
+
loadLit: true // Load Lit fallbacks
|
|
187
|
+
})
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
## View Selection Examples
|
|
191
|
+
|
|
192
|
+
### Single Transaction
|
|
193
|
+
|
|
194
|
+
```typescript
|
|
195
|
+
const playback = createTransactionPlayback(transaction, {
|
|
196
|
+
framework: 'vue'
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
// For single transaction:
|
|
200
|
+
console.log(playback.isBatch) // false
|
|
201
|
+
console.log(playback.children.length) // 0
|
|
202
|
+
console.log(playback.indexCount) // 2
|
|
203
|
+
|
|
204
|
+
const view0 = playback.getViewAtIndex(0) // Returns transaction
|
|
205
|
+
const view1 = playback.getViewAtIndex(1) // Returns transaction (same)
|
|
206
|
+
const view2 = playback.getViewAtIndex(2) // Returns null (invalid)
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### Batch Transaction
|
|
210
|
+
|
|
211
|
+
```typescript
|
|
212
|
+
const playback = createTransactionPlayback(batchTransaction, {
|
|
213
|
+
framework: 'vue'
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
// For batch with 3 children:
|
|
217
|
+
console.log(playback.isBatch) // true
|
|
218
|
+
console.log(playback.children.length) // 3
|
|
219
|
+
console.log(playback.indexCount) // 4 (0 + 3 children)
|
|
220
|
+
|
|
221
|
+
const view0 = playback.getViewAtIndex(0) // Returns batch summary
|
|
222
|
+
const view1 = playback.getViewAtIndex(1) // Returns children[0]
|
|
223
|
+
const view2 = playback.getViewAtIndex(2) // Returns children[1]
|
|
224
|
+
const view3 = playback.getViewAtIndex(3) // Returns children[2]
|
|
225
|
+
const view4 = playback.getViewAtIndex(4) // Returns null (invalid)
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
## Scoring System
|
|
229
|
+
|
|
230
|
+
### Base Score (0-1)
|
|
231
|
+
Calculated from criteria matches:
|
|
232
|
+
- `recordType`: 0.25 weight
|
|
233
|
+
- `functionName`: 0.20 weight
|
|
234
|
+
- `context`: 0.20 weight
|
|
235
|
+
- `standard`: 0.15 weight
|
|
236
|
+
- `contractAddress`: 0.10 weight
|
|
237
|
+
- `customMatcher`: 0.05 weight (numeric scoring - see below)
|
|
238
|
+
- `contextRequirements`: 0.03 weight
|
|
239
|
+
- `chainId`: 0.02 weight
|
|
240
|
+
|
|
241
|
+
### Custom Matcher Scoring
|
|
242
|
+
Custom matchers return a **match count** (0-N) which is normalized:
|
|
243
|
+
- `normalizedScore = Math.min(matchCount / 10, 1)`
|
|
244
|
+
- `finalScore = weight * normalizedScore`
|
|
245
|
+
|
|
246
|
+
**Example:**
|
|
247
|
+
```typescript
|
|
248
|
+
customMatcher: (tx) => {
|
|
249
|
+
let matches = 0
|
|
250
|
+
if (tx.custom?.tokenType === 0) matches++ // +1
|
|
251
|
+
if (tx.custom?.isToken === true) matches++ // +1
|
|
252
|
+
if (BigInt(tx.value || 0) > 0) matches++ // +1
|
|
253
|
+
return matches // 0-3
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// If matches = 2:
|
|
257
|
+
// normalizedScore = Math.min(2 / 10, 1) = 0.2
|
|
258
|
+
// contribution = 0.05 * 0.2 = 0.01 (added to total score)
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
### Framework Bonus
|
|
262
|
+
- Vue: +0.15
|
|
263
|
+
- React Native: +0.15
|
|
264
|
+
- Lit: +0.00
|
|
265
|
+
|
|
266
|
+
### Priority Tiebreaker
|
|
267
|
+
When scores are equal, higher `priority` value wins.
|
|
268
|
+
|
|
269
|
+
### Example Scoring
|
|
270
|
+
|
|
271
|
+
```typescript
|
|
272
|
+
// View definitions
|
|
273
|
+
const vueView = {
|
|
274
|
+
id: 'lsp7-vue',
|
|
275
|
+
priority: 100,
|
|
276
|
+
criteria: { standard: 'LSP7', recordType: 'lsp7-transfer' },
|
|
277
|
+
frameworks: { lit: {...}, vue: {...} }
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const litView = {
|
|
281
|
+
id: 'lsp7-lit',
|
|
282
|
+
priority: 100,
|
|
283
|
+
criteria: { standard: 'LSP7', recordType: 'lsp7-transfer' },
|
|
284
|
+
frameworks: { lit: {...} }
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Vue app selection
|
|
288
|
+
framework: 'vue'
|
|
289
|
+
vueView score: 0.40 (base) + 0.15 (bonus) = 0.55 ✅ SELECTED
|
|
290
|
+
litView score: 0.40 (base) + 0.00 (no bonus) = 0.40
|
|
291
|
+
|
|
292
|
+
// Angular app selection
|
|
293
|
+
framework: 'lit'
|
|
294
|
+
vueView: Not available (no 'lit' in Vue app's framework list)
|
|
295
|
+
litView score: 0.40 (base) + 0.00 (lit never gets bonus) = 0.40 ✅ SELECTED
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
## Transaction Metadata System
|
|
299
|
+
|
|
300
|
+
### Overview
|
|
301
|
+
The `transaction.custom` field allows enhancers to populate metadata for view matching without requiring async operations during view selection.
|
|
302
|
+
|
|
303
|
+
### Architecture Flow
|
|
304
|
+
```
|
|
305
|
+
1. Enhancer Phase (Async)
|
|
306
|
+
└─> Fetch contract metadata (LSP4TokenType, etc.)
|
|
307
|
+
└─> Cache by contract address
|
|
308
|
+
└─> Populate transaction.custom
|
|
309
|
+
|
|
310
|
+
2. View Matching Phase (Sync)
|
|
311
|
+
└─> customMatcher reads transaction.custom
|
|
312
|
+
└─> Returns match count
|
|
313
|
+
└─> Score calculated and view selected
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
### Contract-Level Caching
|
|
317
|
+
When processing batches with multiple transactions to the same contract:
|
|
318
|
+
|
|
319
|
+
```typescript
|
|
320
|
+
// 5 transactions to contract 0xABC...
|
|
321
|
+
Transaction 1 → cache.getOrSet() → RPC call → cache SET
|
|
322
|
+
Transaction 2 → cache.getOrSet() → cache HIT ✓
|
|
323
|
+
Transaction 3 → cache.getOrSet() → cache HIT ✓
|
|
324
|
+
Transaction 4 → cache.getOrSet() → cache HIT ✓
|
|
325
|
+
Transaction 5 → cache.getOrSet() → cache HIT ✓
|
|
326
|
+
|
|
327
|
+
Total RPC calls: 1 (not 5!)
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
### Custom Field Structure
|
|
331
|
+
|
|
332
|
+
```typescript
|
|
333
|
+
// Populated by enhancer
|
|
334
|
+
result.custom = {
|
|
335
|
+
// Token metadata
|
|
336
|
+
tokenType: 0, // 0=Token, 1=NFT, 2=Collection
|
|
337
|
+
isToken: true,
|
|
338
|
+
isNFT: false,
|
|
339
|
+
isCollection: false,
|
|
340
|
+
|
|
341
|
+
// Operation metadata
|
|
342
|
+
operationType: 'mint', // 'mint' | 'burn' | 'transfer'
|
|
343
|
+
|
|
344
|
+
// Any custom data
|
|
345
|
+
myCustomFlag: true,
|
|
346
|
+
metadata: { /* ... */ }
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// Accessed in view matcher
|
|
350
|
+
customMatcher: (tx) => {
|
|
351
|
+
let matches = 0
|
|
352
|
+
if (tx.custom?.tokenType === 0) matches++
|
|
353
|
+
if (tx.custom?.isToken === true) matches++
|
|
354
|
+
if (tx.custom?.myCustomFlag === true) matches++
|
|
355
|
+
return matches
|
|
356
|
+
}
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
### Real-World Example
|
|
360
|
+
|
|
361
|
+
```typescript
|
|
362
|
+
// packages/decoder/src/decoder/plugins/enhanceLSP7DigitalAsset.ts
|
|
363
|
+
import { makeCacheKey, CacheKeyPrefix } from '../cache'
|
|
364
|
+
import { getPublicClient } from '../utils'
|
|
365
|
+
|
|
366
|
+
async function enhanceLSP7Metadata(
|
|
367
|
+
result: DecoderResult,
|
|
368
|
+
options: DecoderOptions
|
|
369
|
+
): Promise<void> {
|
|
370
|
+
if (!options.cache || !result.to) return
|
|
371
|
+
|
|
372
|
+
const cacheKey = makeCacheKey(
|
|
373
|
+
CacheKeyPrefix.CONTRACT_METADATA,
|
|
374
|
+
result.to,
|
|
375
|
+
'lsp4-token-type'
|
|
376
|
+
)
|
|
377
|
+
|
|
378
|
+
try {
|
|
379
|
+
const metadata = await options.cache.getOrSet(
|
|
380
|
+
cacheKey,
|
|
381
|
+
async () => {
|
|
382
|
+
const client = getPublicClient(options.chain)
|
|
383
|
+
const tokenTypeData = await client.readContract({
|
|
384
|
+
address: result.to,
|
|
385
|
+
abi: LSP7_ABI,
|
|
386
|
+
functionName: 'getData',
|
|
387
|
+
args: ['0xe0261fa95db2eb3b5439bd033cda66d56b96f92f243a8228fd87550ed7bdfdb3']
|
|
388
|
+
})
|
|
389
|
+
|
|
390
|
+
const typeValue = tokenTypeData ? parseInt(tokenTypeData as string, 16) : 0
|
|
391
|
+
|
|
392
|
+
return {
|
|
393
|
+
tokenType: typeValue,
|
|
394
|
+
isToken: typeValue === 0,
|
|
395
|
+
isNFT: typeValue === 1,
|
|
396
|
+
isCollection: typeValue === 2
|
|
397
|
+
}
|
|
398
|
+
},
|
|
399
|
+
{ ttl: 1000 * 60 * 60 * 24 } // 24 hour cache
|
|
400
|
+
)
|
|
401
|
+
|
|
402
|
+
result.custom = result.custom || {}
|
|
403
|
+
Object.assign(result.custom, metadata)
|
|
404
|
+
} catch (error) {
|
|
405
|
+
// Set defaults on error
|
|
406
|
+
result.custom = result.custom || {}
|
|
407
|
+
result.custom.tokenType = 0
|
|
408
|
+
result.custom.isToken = true
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
### View Registration with Metadata
|
|
414
|
+
|
|
415
|
+
```typescript
|
|
416
|
+
import type { ViewDefinition } from '@lukso/transaction-view-headless'
|
|
417
|
+
|
|
418
|
+
// Token-specific view
|
|
419
|
+
export const LSP7TokenTransferView: ViewDefinition = {
|
|
420
|
+
id: 'lsp7-token-transfer',
|
|
421
|
+
name: 'LSP7 Token Transfer',
|
|
422
|
+
priority: 100,
|
|
423
|
+
criteria: {
|
|
424
|
+
standard: 'LSP7',
|
|
425
|
+
functionName: 'transfer',
|
|
426
|
+
customMatcher: (tx) => {
|
|
427
|
+
let matches = 0
|
|
428
|
+
if (tx.custom?.tokenType === 0) matches++
|
|
429
|
+
if (tx.custom?.isToken === true) matches++
|
|
430
|
+
return matches
|
|
431
|
+
}
|
|
432
|
+
},
|
|
433
|
+
frameworks: {
|
|
434
|
+
lit: {
|
|
435
|
+
loader: { type: 'custom-element', tagName: 'lsp7-token-transfer-view' },
|
|
436
|
+
component: 'lsp7-token-transfer-view'
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// NFT-specific view
|
|
442
|
+
export const LSP7NFTTransferView: ViewDefinition = {
|
|
443
|
+
id: 'lsp7-nft-transfer',
|
|
444
|
+
name: 'LSP7 NFT Transfer',
|
|
445
|
+
priority: 100,
|
|
446
|
+
criteria: {
|
|
447
|
+
standard: 'LSP7',
|
|
448
|
+
functionName: 'transfer',
|
|
449
|
+
customMatcher: (tx) => {
|
|
450
|
+
let matches = 0
|
|
451
|
+
if (tx.custom?.tokenType === 1) matches++
|
|
452
|
+
if (tx.custom?.isNFT === true) matches++
|
|
453
|
+
return matches
|
|
454
|
+
}
|
|
455
|
+
},
|
|
456
|
+
frameworks: {
|
|
457
|
+
lit: {
|
|
458
|
+
loader: { type: 'custom-element', tagName: 'lsp7-nft-transfer-view' },
|
|
459
|
+
component: 'lsp7-nft-transfer-view'
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// Generic fallback (no customMatcher)
|
|
465
|
+
export const LSP7GenericTransferView: ViewDefinition = {
|
|
466
|
+
id: 'lsp7-generic-transfer',
|
|
467
|
+
name: 'LSP7 Transfer',
|
|
468
|
+
priority: 50, // Lower priority
|
|
469
|
+
criteria: {
|
|
470
|
+
standard: 'LSP7',
|
|
471
|
+
functionName: 'transfer'
|
|
472
|
+
// No customMatcher - matches all LSP7 transfers
|
|
473
|
+
},
|
|
474
|
+
frameworks: {
|
|
475
|
+
lit: {
|
|
476
|
+
loader: { type: 'custom-element', tagName: 'lsp7-transfer-view' },
|
|
477
|
+
component: 'lsp7-transfer-view'
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
### Matching Behavior
|
|
484
|
+
|
|
485
|
+
When a transaction is matched against these views:
|
|
486
|
+
|
|
487
|
+
```typescript
|
|
488
|
+
// Transaction with metadata
|
|
489
|
+
const tx = {
|
|
490
|
+
standard: 'LSP7',
|
|
491
|
+
functionName: 'transfer',
|
|
492
|
+
custom: { tokenType: 0, isToken: true }
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// Token view: customMatcher returns 2 → normalized 0.2 → score boosted
|
|
496
|
+
// NFT view: customMatcher returns 0 → no match
|
|
497
|
+
// Generic view: no customMatcher → base score only
|
|
498
|
+
|
|
499
|
+
// Winner: Token view (highest score)
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
```typescript
|
|
503
|
+
// Transaction without metadata
|
|
504
|
+
const tx = {
|
|
505
|
+
standard: 'LSP7',
|
|
506
|
+
functionName: 'transfer'
|
|
507
|
+
// custom field missing
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// Token view: customMatcher returns 0 → no match
|
|
511
|
+
// NFT view: customMatcher returns 0 → no match
|
|
512
|
+
// Generic view: no customMatcher → matches with base criteria
|
|
513
|
+
|
|
514
|
+
// Winner: Generic view (fallback)
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
### Best Practices
|
|
518
|
+
|
|
519
|
+
1. **Always provide fallback views** without customMatcher for graceful degradation
|
|
520
|
+
2. **Cache by contract address** to avoid duplicate RPC calls in batches
|
|
521
|
+
3. **Use descriptive metadata keys** (tokenType, isToken, etc.)
|
|
522
|
+
4. **Set defaults on error** to ensure views still match
|
|
523
|
+
5. **Keep customMatcher synchronous** - all async work in enhancers
|
|
524
|
+
|
|
525
|
+
## API Reference
|
|
526
|
+
|
|
527
|
+
### Registry API
|
|
528
|
+
```typescript
|
|
529
|
+
const registry = getGlobalRegistry()
|
|
530
|
+
|
|
531
|
+
// Register views
|
|
532
|
+
registry.register(view)
|
|
533
|
+
registry.registerMany(views)
|
|
534
|
+
|
|
535
|
+
// Query views
|
|
536
|
+
registry.getView(id)
|
|
537
|
+
registry.getAllViews()
|
|
538
|
+
registry.findMatches(transaction, options)
|
|
539
|
+
registry.getBestMatch(transaction, options)
|
|
540
|
+
|
|
541
|
+
// Manage registry
|
|
542
|
+
registry.unregister(id)
|
|
543
|
+
registry.clear()
|
|
544
|
+
registry.getStats()
|
|
545
|
+
```
|
|
546
|
+
|
|
547
|
+
### Playback API
|
|
548
|
+
```typescript
|
|
549
|
+
// Composable (single index)
|
|
550
|
+
const result = useTransactionPlayback(transaction, index, options)
|
|
551
|
+
|
|
552
|
+
// State (multiple indices)
|
|
553
|
+
const playback = createTransactionPlayback(transaction, options)
|
|
554
|
+
playback.getViewAtIndex(index)
|
|
555
|
+
playback.getAllViews()
|
|
556
|
+
|
|
557
|
+
// Helpers
|
|
558
|
+
isValidPlaybackIndex(transaction, index)
|
|
559
|
+
getValidPlaybackIndices(transaction)
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
## Documentation Files
|
|
563
|
+
|
|
564
|
+
- **[VIEW_REGISTRATION_GUIDE.md](./VIEW_REGISTRATION_GUIDE.md)** - Complete registration guide with custom package patterns
|
|
565
|
+
- **[PLAYBACK_GUIDE.md](./PLAYBACK_GUIDE.md)** - Playback system overview and API reference
|
|
566
|
+
- **[PLAYBACK_EXAMPLES.md](./PLAYBACK_EXAMPLES.md)** - Real-world code examples for Lit and Vue
|
|
567
|
+
- **[COMPONENT_ARCHITECTURE.md](../../docs/COMPONENT_ARCHITECTURE.md)** - System architecture overview
|
|
568
|
+
|
|
569
|
+
## Benefits
|
|
570
|
+
|
|
571
|
+
1. **Framework Flexibility**: Write once in Lit, use everywhere. Add native implementations when needed.
|
|
572
|
+
2. **Priority System**: Native views automatically preferred over fallbacks.
|
|
573
|
+
3. **Index-Based UI**: Maximum flexibility for batch display (summary, pagination, expansion).
|
|
574
|
+
4. **Extensibility**: Easy to add custom views and enhancers in separate packages.
|
|
575
|
+
5. **Type Safety**: Full TypeScript support throughout.
|
|
576
|
+
6. **Performance**: Lazy loading, tree-shaking, and efficient scoring.
|
|
577
|
+
|
|
578
|
+
## Migration Path
|
|
579
|
+
|
|
580
|
+
### From Manual Component Selection
|
|
581
|
+
```vue
|
|
582
|
+
<!-- Before -->
|
|
583
|
+
<LSP7Transfer v-if="isLSP7" />
|
|
584
|
+
<LSP8Transfer v-else-if="isLSP8" />
|
|
585
|
+
<GenericTransaction v-else />
|
|
586
|
+
|
|
587
|
+
<!-- After -->
|
|
588
|
+
<TransactionPlayback :transaction="transaction" :index="1" />
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
### From TransactionIframe
|
|
592
|
+
```vue
|
|
593
|
+
<!-- Before -->
|
|
594
|
+
<TransactionIframe :transaction="transaction" />
|
|
595
|
+
|
|
596
|
+
<!-- After -->
|
|
597
|
+
<TransactionPlayback
|
|
598
|
+
:transaction="transaction"
|
|
599
|
+
:index="1"
|
|
600
|
+
framework="lit"
|
|
601
|
+
/>
|
|
602
|
+
```
|
|
603
|
+
|
|
604
|
+
## Common Patterns
|
|
605
|
+
|
|
606
|
+
### 1. Always Show Main Transaction
|
|
607
|
+
```vue
|
|
608
|
+
<TransactionPlayback :index="1" />
|
|
609
|
+
```
|
|
610
|
+
|
|
611
|
+
### 2. Expandable Batch List
|
|
612
|
+
```vue
|
|
613
|
+
<TransactionPlayback :index="1" />
|
|
614
|
+
<div v-if="expanded">
|
|
615
|
+
<TransactionPlayback
|
|
616
|
+
v-for="i in childIndices"
|
|
617
|
+
:index="i"
|
|
618
|
+
/>
|
|
619
|
+
</div>
|
|
620
|
+
```
|
|
621
|
+
|
|
622
|
+
### 3. Context-Aware Selection
|
|
623
|
+
```typescript
|
|
624
|
+
const index = computed(() => {
|
|
625
|
+
if (context === 'approval') return 1 // Skip summaries
|
|
626
|
+
if (context === 'detail' && isBatch) return 0 // Show summary
|
|
627
|
+
return 1 // Default: main transaction
|
|
628
|
+
})
|
|
629
|
+
```
|
|
630
|
+
|
|
631
|
+
## Next Steps
|
|
632
|
+
|
|
633
|
+
1. **Register your views** using the patterns in VIEW_REGISTRATION_GUIDE.md
|
|
634
|
+
2. **Use playback components** in your UI (see PLAYBACK_EXAMPLES.md)
|
|
635
|
+
3. **Create custom packages** for reusable views + enhancers
|
|
636
|
+
4. **Test with debug mode** to verify view selection
|
|
637
|
+
|
|
638
|
+
The system is designed to grow with your needs - start with Lit fallbacks, add native implementations when performance matters!
|