@lukso/transaction-decoder 1.0.1-dev.0f1bea5

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 (110) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +486 -0
  3. package/dist/browser.cjs +6912 -0
  4. package/dist/browser.cjs.map +1 -0
  5. package/dist/browser.d.cts +6 -0
  6. package/dist/browser.d.ts +6 -0
  7. package/dist/browser.js +131 -0
  8. package/dist/browser.js.map +1 -0
  9. package/dist/cdn/transaction-decoder.global.js +296 -0
  10. package/dist/cdn/transaction-decoder.global.js.map +1 -0
  11. package/dist/chunk-GGBHTWJL.js +437 -0
  12. package/dist/chunk-GGBHTWJL.js.map +1 -0
  13. package/dist/chunk-GXZOF3QY.js +839 -0
  14. package/dist/chunk-GXZOF3QY.js.map +1 -0
  15. package/dist/chunk-LJ6ES5XF.js +776 -0
  16. package/dist/chunk-LJ6ES5XF.js.map +1 -0
  17. package/dist/chunk-XVHJWV5U.js +4925 -0
  18. package/dist/chunk-XVHJWV5U.js.map +1 -0
  19. package/dist/data.cjs +5518 -0
  20. package/dist/data.cjs.map +1 -0
  21. package/dist/data.d.cts +43 -0
  22. package/dist/data.d.ts +43 -0
  23. package/dist/data.js +55 -0
  24. package/dist/data.js.map +1 -0
  25. package/dist/index-BzXh7poJ.d.cts +524 -0
  26. package/dist/index-BzXh7poJ.d.ts +524 -0
  27. package/dist/index.cjs +6912 -0
  28. package/dist/index.cjs.map +1 -0
  29. package/dist/index.d.cts +756 -0
  30. package/dist/index.d.ts +756 -0
  31. package/dist/index.js +131 -0
  32. package/dist/index.js.map +1 -0
  33. package/dist/server.cjs +5644 -0
  34. package/dist/server.cjs.map +1 -0
  35. package/dist/server.d.cts +217 -0
  36. package/dist/server.d.ts +217 -0
  37. package/dist/server.js +644 -0
  38. package/dist/server.js.map +1 -0
  39. package/dist/utils-CBAkjQh3.d.cts +108 -0
  40. package/dist/utils-xT9-km0r.d.ts +108 -0
  41. package/package.json +101 -0
  42. package/src/browser.ts +13 -0
  43. package/src/client/resolveAddresses.ts +157 -0
  44. package/src/core/addressCollector.ts +153 -0
  45. package/src/core/addressResolver.ts +135 -0
  46. package/src/core/dataModel.ts +888 -0
  47. package/src/core/instance.ts +33 -0
  48. package/src/core/integrateDecoder.ts +325 -0
  49. package/src/data.ts +70 -0
  50. package/src/decoder/GENERATOR_PROPOSAL.md +182 -0
  51. package/src/decoder/THREE_PHASE_EXAMPLE.md +108 -0
  52. package/src/decoder/aggregation.ts +218 -0
  53. package/src/decoder/browserCache.ts +237 -0
  54. package/src/decoder/cache/README.md +126 -0
  55. package/src/decoder/cache/index.ts +44 -0
  56. package/src/decoder/cache.ts +139 -0
  57. package/src/decoder/constants.ts +125 -0
  58. package/src/decoder/decodeTransaction.ts +292 -0
  59. package/src/decoder/errors.ts +95 -0
  60. package/src/decoder/events.ts +192 -0
  61. package/src/decoder/functionSignature.ts +344 -0
  62. package/src/decoder/getDataFromExternalSources.ts +248 -0
  63. package/src/decoder/graphqlWS.ts +22 -0
  64. package/src/decoder/interfaces.ts +185 -0
  65. package/src/decoder/keyValue.ts +5 -0
  66. package/src/decoder/kvCache.ts +241 -0
  67. package/src/decoder/lruCache.ts +184 -0
  68. package/src/decoder/lsp7Mint.test.ts +179 -0
  69. package/src/decoder/lsp7TransferBatch.test.ts +105 -0
  70. package/src/decoder/plugins/RegistryAbi.ts +562 -0
  71. package/src/decoder/plugins/enhanceBurntPix.ts +132 -0
  72. package/src/decoder/plugins/enhanceGraffiti.ts +70 -0
  73. package/src/decoder/plugins/enhanceLSP0ERC725Account.ts +179 -0
  74. package/src/decoder/plugins/enhanceLSP26FollowerSystem.ts +88 -0
  75. package/src/decoder/plugins/enhanceLSP6KeyManager.ts +231 -0
  76. package/src/decoder/plugins/enhanceLSP7DigitalAsset.ts +165 -0
  77. package/src/decoder/plugins/enhanceLSP8IdentifiableDigitalAsset.ts +170 -0
  78. package/src/decoder/plugins/enhanceLSP9Vault.ts +57 -0
  79. package/src/decoder/plugins/enhanceRetrieveAbi.ts +85 -0
  80. package/src/decoder/plugins/enhanceSetData.ts +135 -0
  81. package/src/decoder/plugins/index.ts +99 -0
  82. package/src/decoder/plugins/schemaDefault.ts +318 -0
  83. package/src/decoder/plugins/standardPlugin.ts +202 -0
  84. package/src/decoder/registry.ts +322 -0
  85. package/src/decoder/singleGQL.ts +293 -0
  86. package/src/decoder/transaction.ts +198 -0
  87. package/src/decoder/types.ts +465 -0
  88. package/src/decoder/utils.ts +212 -0
  89. package/src/example/usage.ts +172 -0
  90. package/src/index.ts +174 -0
  91. package/src/server/addressResolver.ts +68 -0
  92. package/src/server/caches.ts +209 -0
  93. package/src/server/decodeTransactionSync.ts +156 -0
  94. package/src/server/decodeTransactionsBatch.ts +207 -0
  95. package/src/server/finishDecoding.ts +116 -0
  96. package/src/server/index.ts +81 -0
  97. package/src/server/lsp23Resolver.test.ts +46 -0
  98. package/src/server/lsp23Resolver.ts +419 -0
  99. package/src/server/types.ts +168 -0
  100. package/src/server.ts +22 -0
  101. package/src/shared/addressResolver.ts +651 -0
  102. package/src/shared/cache.ts +144 -0
  103. package/src/shared/constants.ts +21 -0
  104. package/src/stubs/tty.ts +13 -0
  105. package/src/stubs/util.ts +42 -0
  106. package/src/types/index.ts +154 -0
  107. package/src/types/provider.ts +46 -0
  108. package/src/umd.ts +13 -0
  109. package/src/utils/debug.ts +49 -0
  110. package/src/utils/json-bigint.ts +47 -0
@@ -0,0 +1,322 @@
1
+ import type { Hex } from 'viem'
2
+ import type { DecoderPlugin, Info, SchemaPlugin } from './types'
3
+
4
+ /**
5
+ * Plugin metadata for registry
6
+ */
7
+ export interface PluginMetadata {
8
+ name: string
9
+ plugin: DecoderPlugin
10
+ priority?: number
11
+ required?: boolean // Cannot be overridden, always runs first
12
+ description?: string
13
+ version?: string
14
+ usesAsync?: boolean // Whether this plugin performs async operations
15
+ }
16
+
17
+ /**
18
+ * Schema plugin metadata
19
+ */
20
+ export interface SchemaPluginMetadata {
21
+ name: string
22
+ plugin: SchemaPlugin
23
+ priority?: number
24
+ required?: boolean // Cannot be overridden, always runs first
25
+ description?: string
26
+ version?: string
27
+ }
28
+
29
+ /**
30
+ * Global plugin registry for auto-registration
31
+ */
32
+ class PluginRegistry {
33
+ private plugins: Map<string, PluginMetadata> = new Map()
34
+ private schemaPlugins: Map<string, SchemaPluginMetadata> = new Map()
35
+
36
+ /**
37
+ * Register a decoder plugin
38
+ * @param name - Unique plugin name
39
+ * @param plugin - Plugin instance
40
+ * @param options - Registration options
41
+ */
42
+ register(
43
+ name: string,
44
+ plugin: DecoderPlugin,
45
+ options?: {
46
+ priority?: number
47
+ required?: boolean
48
+ override?: boolean
49
+ description?: string
50
+ version?: string
51
+ usesAsync?: boolean
52
+ }
53
+ ): void {
54
+ const {
55
+ override = false,
56
+ priority = 0,
57
+ required = false,
58
+ description,
59
+ version,
60
+ usesAsync = false,
61
+ } = options || {}
62
+
63
+ const existing = this.plugins.get(name)
64
+
65
+ if (existing) {
66
+ if (existing.required) {
67
+ console.error(
68
+ `Plugin "${name}" is marked as required and cannot be overridden.`
69
+ )
70
+ return
71
+ }
72
+ if (!override) {
73
+ console.warn(
74
+ `Plugin "${name}" is already registered. Use override: true to replace.`
75
+ )
76
+ return
77
+ }
78
+ }
79
+
80
+ this.plugins.set(name, {
81
+ name,
82
+ plugin: Object.freeze(plugin),
83
+ priority,
84
+ required,
85
+ description,
86
+ version,
87
+ usesAsync,
88
+ })
89
+ }
90
+
91
+ /**
92
+ * Register a schema plugin
93
+ * @param name - Unique plugin name
94
+ * @param plugin - Schema plugin instance
95
+ * @param options - Registration options
96
+ */
97
+ registerSchema(
98
+ name: string,
99
+ plugin: SchemaPlugin,
100
+ options?: {
101
+ priority?: number
102
+ required?: boolean
103
+ override?: boolean
104
+ description?: string
105
+ version?: string
106
+ }
107
+ ): void {
108
+ const {
109
+ override = false,
110
+ priority = 0,
111
+ required = false,
112
+ description,
113
+ version,
114
+ } = options || {}
115
+
116
+ const existing = this.schemaPlugins.get(name)
117
+
118
+ if (existing) {
119
+ if (existing.required) {
120
+ console.error(
121
+ `Schema plugin "${name}" is marked as required and cannot be overridden.`
122
+ )
123
+ return
124
+ }
125
+ if (!override) {
126
+ console.warn(
127
+ `Schema plugin "${name}" is already registered. Use override: true to replace.`
128
+ )
129
+ return
130
+ }
131
+ }
132
+
133
+ this.schemaPlugins.set(name, {
134
+ name,
135
+ plugin: Object.freeze(plugin),
136
+ priority,
137
+ required,
138
+ description,
139
+ version,
140
+ })
141
+ }
142
+
143
+ /**
144
+ * Unregister a plugin
145
+ */
146
+ unregister(name: string): boolean {
147
+ return this.plugins.delete(name)
148
+ }
149
+
150
+ /**
151
+ * Get all registered decoder plugins (excluding overridden ones)
152
+ * @param options - Filter options
153
+ */
154
+ async getAll(options?: {
155
+ excludeNames?: string[]
156
+ syncOnly?: boolean
157
+ }): Promise<DecoderPlugin[]> {
158
+ const { excludeNames = [], syncOnly = false } = options || {}
159
+ const exclude = new Set(excludeNames)
160
+ let plugins = Array.from(this.plugins.values())
161
+
162
+ // Filter by syncOnly if requested
163
+ if (syncOnly) {
164
+ plugins = plugins.filter((meta) => !meta.usesAsync)
165
+ }
166
+
167
+ // Check if any required plugins are being excluded
168
+ for (const name of exclude) {
169
+ const plugin = this.plugins.get(name)
170
+ if (plugin?.required) {
171
+ console.warn(
172
+ `Warning: Plugin "${name}" is required and cannot be excluded. It will run first.`
173
+ )
174
+ }
175
+ }
176
+
177
+ // Separate required and optional plugins
178
+ return plugins
179
+ .sort((a, b) => (b.priority || 0) - (a.priority || 0))
180
+ .map(({ plugin }) => plugin)
181
+ }
182
+
183
+ /**
184
+ * Get all schema plugins (excluding overridden ones)
185
+ * @param excludeNames - Plugin names to exclude
186
+ */
187
+ getAllSchema(excludeNames?: string[]): SchemaPlugin[] {
188
+ const exclude = new Set(excludeNames || [])
189
+ const plugins = Array.from(this.schemaPlugins.values())
190
+
191
+ // Check if any required plugins are being excluded
192
+ for (const name of exclude) {
193
+ const plugin = this.schemaPlugins.get(name)
194
+ if (plugin?.required) {
195
+ console.warn(
196
+ `Warning: Schema plugin "${name}" is required and cannot be excluded. It will run first.`
197
+ )
198
+ }
199
+ }
200
+
201
+ // Separate required and optional plugins
202
+ const required = plugins
203
+ .filter((meta) => meta.required)
204
+ .sort((a, b) => (b.priority || 0) - (a.priority || 0))
205
+
206
+ const optional = plugins
207
+ .filter((meta) => !meta.required && !exclude.has(meta.name))
208
+ .sort((a, b) => (b.priority || 0) - (a.priority || 0))
209
+
210
+ // Required plugins always come first, then optional ones
211
+ return [...required, ...optional].map((meta) => meta.plugin)
212
+ }
213
+
214
+ /**
215
+ * Get a specific plugin by name
216
+ */
217
+ async get(name: string): Promise<DecoderPlugin | undefined> {
218
+ const meta = this.plugins.get(name)
219
+ return meta?.plugin
220
+ }
221
+
222
+ /**
223
+ * Get plugin metadata
224
+ */
225
+ getMetadata(name: string): PluginMetadata | undefined {
226
+ return this.plugins.get(name)
227
+ }
228
+
229
+ /**
230
+ * Get schema plugin metadata
231
+ */
232
+ getSchemaMetadata(name: string): SchemaPluginMetadata | undefined {
233
+ return this.schemaPlugins.get(name)
234
+ }
235
+
236
+ /**
237
+ * Check if a plugin is registered
238
+ */
239
+ has(name: string): boolean {
240
+ return this.plugins.has(name)
241
+ }
242
+
243
+ /**
244
+ * Clear all plugins
245
+ */
246
+ clear(): void {
247
+ this.plugins.clear()
248
+ this.schemaPlugins.clear()
249
+ }
250
+
251
+ /**
252
+ * Get the number of registered plugins
253
+ */
254
+ get size(): number {
255
+ return this.plugins.size + this.schemaPlugins.size
256
+ }
257
+
258
+ /**
259
+ * List all registered plugins with metadata
260
+ */
261
+ list(): { plugins: PluginMetadata[]; schemaPlugins: SchemaPluginMetadata[] } {
262
+ return {
263
+ plugins: Array.from(this.plugins.values()),
264
+ schemaPlugins: Array.from(this.schemaPlugins.values()),
265
+ }
266
+ }
267
+ }
268
+
269
+ // Freeze the PluginRegistry prototype to prevent modifications
270
+ Object.freeze(PluginRegistry.prototype)
271
+ Object.freeze(PluginRegistry)
272
+
273
+ // Global singleton instance
274
+ export const pluginRegistry = new PluginRegistry()
275
+
276
+ // Convenience function for plugin authors
277
+ export function registerPlugin(
278
+ name: string,
279
+ plugin: DecoderPlugin,
280
+ options?: {
281
+ priority?: number
282
+ required?: boolean
283
+ override?: boolean
284
+ description?: string
285
+ version?: string
286
+ usesAsync?: boolean
287
+ }
288
+ ): void {
289
+ pluginRegistry.register(name, plugin, options)
290
+ }
291
+
292
+ // Convenience function for schema plugin authors
293
+ export function registerSchemaPlugin(
294
+ name: string,
295
+ plugin: SchemaPlugin,
296
+ options?: {
297
+ priority?: number
298
+ required?: boolean
299
+ override?: boolean
300
+ description?: string
301
+ version?: string
302
+ }
303
+ ): void {
304
+ pluginRegistry.registerSchema(name, plugin, options)
305
+ }
306
+
307
+ // Helper to create a named schema plugin
308
+ export function createNamedSchemaPlugin(
309
+ name: string,
310
+ plugin: (key: Hex, value: Hex) => Promise<Info | undefined>
311
+ ): SchemaPlugin {
312
+ const namedPlugin = Object.assign(plugin, { name })
313
+ return namedPlugin as SchemaPlugin
314
+ }
315
+
316
+ // Helper to create a named decoder plugin
317
+ export function createNamedPlugin(
318
+ name: string,
319
+ plugin: Omit<DecoderPlugin, 'name'>
320
+ ): DecoderPlugin {
321
+ return { ...plugin, name }
322
+ }
@@ -0,0 +1,293 @@
1
+ import request, { gql } from 'graphql-request'
2
+ import { type Address, type Chain, getAddress } from 'viem'
3
+ import { lukso } from 'viem/chains'
4
+ import type { EnhancedInfo } from '../types'
5
+ import { getWSClient } from './graphqlWS'
6
+
7
+ /**
8
+ * Get controllers from a set of profile addresses
9
+ */
10
+ const singleProfileControllersGql = gql`
11
+ query MyQuery($profileAddress: String = "") {
12
+ Profile_by_pk(id: $profileAddress) {
13
+ controllers {
14
+ address
15
+ tags
16
+ permissions
17
+ }
18
+ }
19
+ }
20
+ `
21
+
22
+ /**
23
+ * Get a single profile from it's address
24
+ */
25
+ const singleProfileGql = gql`
26
+ query MyQuery($profileAddress: String = "") {
27
+ Profile_by_pk(id: $profileAddress) {
28
+ fullName
29
+ id
30
+ name
31
+ tags
32
+ description
33
+ links {
34
+ title
35
+ url
36
+ }
37
+ profileImages(
38
+ where: { error: { _is_null: true } }
39
+ order_by: { width: asc }
40
+ ) {
41
+ width
42
+ src
43
+ verified
44
+ }
45
+ backgroundImages(
46
+ where: { error: { _is_null: true } }
47
+ order_by: { width: asc }
48
+ ) {
49
+ width
50
+ src
51
+ verified
52
+ }
53
+ avatars(where: { error: { _is_null: true } }, order_by: { width: asc }) {
54
+ width
55
+ src
56
+ verified
57
+ }
58
+ followed {
59
+ follower {
60
+ id
61
+ name
62
+ }
63
+ }
64
+ followed_aggregate {
65
+ aggregate {
66
+ count
67
+ }
68
+ }
69
+ following_aggregate {
70
+ aggregate {
71
+ count
72
+ }
73
+ }
74
+ following {
75
+ followee {
76
+ id
77
+ name
78
+ }
79
+ }
80
+ controllers {
81
+ address
82
+ tags
83
+ permissions
84
+ }
85
+ }
86
+ }
87
+ `
88
+
89
+ /**
90
+ * Subscribe to a single profile by it's address
91
+ */
92
+ const singleProfileGqlSub = gql`
93
+ subscription MyQuery($profileAddress: String = "") {
94
+ Profile_by_pk(id: $profileAddress) {
95
+ fullName
96
+ id
97
+ name
98
+ tags
99
+ description
100
+ links {
101
+ title
102
+ url
103
+ }
104
+ profileImages(
105
+ where: { error: { _is_null: true } }
106
+ order_by: { width: asc }
107
+ ) {
108
+ width
109
+ src
110
+ verified
111
+ }
112
+ backgroundImages(
113
+ where: { error: { _is_null: true } }
114
+ order_by: { width: asc }
115
+ ) {
116
+ width
117
+ src
118
+ verified
119
+ }
120
+ avatars(where: { error: { _is_null: true } }, order_by: { width: asc }) {
121
+ width
122
+ src
123
+ verified
124
+ }
125
+ followed {
126
+ follower {
127
+ id
128
+ name
129
+ }
130
+ }
131
+ followed_aggregate {
132
+ aggregate {
133
+ count
134
+ }
135
+ }
136
+ following_aggregate {
137
+ aggregate {
138
+ count
139
+ }
140
+ }
141
+ following {
142
+ followee {
143
+ id
144
+ name
145
+ }
146
+ }
147
+ controllers {
148
+ address
149
+ tags
150
+ permissions
151
+ }
152
+ }
153
+ }
154
+ `
155
+
156
+ export type SubscribeResponse = {
157
+ data: {
158
+ Profile_by_pk: Record<string, unknown>
159
+ }
160
+ }
161
+
162
+ /**
163
+ * Subscribe to profile by it's address
164
+ *
165
+ * @param chain which chain to use
166
+ * @param address the address of the profile
167
+ * @param profile callback to receive the profile data
168
+ * @returns
169
+ */
170
+ export function subscribeToProfile(
171
+ chain: Chain,
172
+ address: Address,
173
+ profile?: (data: EnhancedInfo) => void
174
+ ) {
175
+ const wsClient = getWSClient(chain)
176
+ let current = undefined as EnhancedInfo | undefined
177
+ return wsClient.subscribe(
178
+ {
179
+ query: singleProfileGqlSub,
180
+ variables: { profileAddress: address.toLowerCase() },
181
+ },
182
+ {
183
+ next: async (data: SubscribeResponse) => {
184
+ const { data: response } = data
185
+ const { Profile_by_pk: prof } = response as {
186
+ Profile_by_pk: Record<string, unknown>
187
+ }
188
+ const {
189
+ id,
190
+ backgroundImages,
191
+ profileImages,
192
+ avatars,
193
+ links,
194
+ tags,
195
+ controllers,
196
+ ...rest
197
+ } = prof || {}
198
+ if (!id) {
199
+ console.error('Profile not found')
200
+ return
201
+ }
202
+ const result = {
203
+ address: getAddress(id as string),
204
+ backgroundImage: backgroundImages || [],
205
+ profileImage: profileImages || [],
206
+ avatar: avatars || [],
207
+ tags: tags || [],
208
+ links: links || [],
209
+ ...rest,
210
+ } as EnhancedInfo
211
+ if (!current || JSON.stringify(current) !== JSON.stringify(result)) {
212
+ profile?.(result)
213
+ current = result
214
+ }
215
+ },
216
+ error: (err: unknown) => {
217
+ console.error('Subscription error:', err)
218
+ },
219
+ complete: () => {},
220
+ }
221
+ )
222
+ }
223
+
224
+ /**
225
+ * Fetch the controllers for a profile address
226
+ *
227
+ * @param chain which chain to use
228
+ * @param address which profile to fetch
229
+ * @returns a list of controller addresses
230
+ */
231
+ export async function fetchProfileControllers(
232
+ chain: Chain,
233
+ address: Address
234
+ ): Promise<`0x${string}`[]> {
235
+ const response: {
236
+ Profile_by_pk: {
237
+ controllers?: { address: `0x${string}`; tags: string[] }[]
238
+ }
239
+ } = await request(
240
+ chain.id === lukso.id
241
+ ? 'https://envio.lukso-mainnet.universal.tech/v1/graphql'
242
+ : 'https://envio.lukso-testnet.universal.tech/v1/graphql',
243
+ singleProfileControllersGql,
244
+ {
245
+ profileAddress: address.toLowerCase(),
246
+ }
247
+ )
248
+ const { Profile_by_pk: { controllers = [] } = {} } = response
249
+ return controllers
250
+ .filter((c) => !c.tags?.includes('ISRECOVERY'))
251
+ .map((C) => C.address)
252
+ }
253
+
254
+ export async function fetchProfile(
255
+ chain: Chain,
256
+ address: Address
257
+ ): Promise<EnhancedInfo> {
258
+ const response: {
259
+ Profile_by_pk: Record<string, unknown>
260
+ } = await request(
261
+ chain.id === lukso.id
262
+ ? 'https://envio.lukso-mainnet.universal.tech/v1/graphql'
263
+ : 'https://envio.lukso-testnet.universal.tech/v1/graphql',
264
+ singleProfileGql,
265
+ {
266
+ profileAddress: address.toLowerCase(),
267
+ }
268
+ )
269
+ const { Profile_by_pk: profile } = response
270
+ const {
271
+ id,
272
+ backgroundImages,
273
+ profileImages,
274
+ avatars,
275
+ links,
276
+ tags,
277
+ controllers,
278
+ ...rest
279
+ } = profile || {}
280
+ if (!id) {
281
+ throw new Error('Profile not found')
282
+ }
283
+ return {
284
+ address: getAddress(id as string),
285
+ backgroundImage: backgroundImages || [],
286
+ profileImage: profileImages || [],
287
+ avatar: avatars || [],
288
+ tags: tags || [],
289
+ links: links || [],
290
+ ...rest,
291
+ __gqltype: 'Profile',
292
+ } as EnhancedInfo
293
+ }