@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,44 @@
1
+ /**
2
+ * Cache implementations for the decoder
3
+ */
4
+
5
+ export {
6
+ type BrowserCacheOptions,
7
+ BrowserDecoderCache,
8
+ createBrowserCache,
9
+ } from '../browserCache'
10
+ // Re-export cache interface and types
11
+ export * from '../cache'
12
+ export {
13
+ createKVCache,
14
+ type KVCacheOptions,
15
+ KVDecoderCache,
16
+ type KVStore,
17
+ } from '../kvCache'
18
+ // Export cache implementations
19
+ export {
20
+ createLRUCache,
21
+ type LRUCacheOptions,
22
+ LRUDecoderCache,
23
+ } from '../lruCache'
24
+
25
+ import { createBrowserCache } from '../browserCache'
26
+ import type { DecoderCache } from '../cache'
27
+ import { createLRUCache } from '../lruCache'
28
+
29
+ /**
30
+ * Create a cache instance based on the environment
31
+ */
32
+ export function createAutoCache(): DecoderCache {
33
+ // Check for browser environment
34
+ if (typeof window !== 'undefined' && typeof caches !== 'undefined') {
35
+ try {
36
+ return createBrowserCache()
37
+ } catch {
38
+ // Fall through to LRU
39
+ }
40
+ }
41
+
42
+ // Default to LRU cache
43
+ return createLRUCache()
44
+ }
@@ -0,0 +1,139 @@
1
+ /**
2
+ * Cache interface for decoder with support for different backends
3
+ * (LRU, Cloudflare KV, Redis, etc.)
4
+ */
5
+
6
+ import type { Hex } from 'viem'
7
+
8
+ /**
9
+ * Cache entry with metadata
10
+ */
11
+ export interface CacheEntry<T> {
12
+ /** The cached value */
13
+ value: T
14
+ /** Timestamp when the entry expires (ms since epoch) */
15
+ expires?: number
16
+ /** Timestamp when the entry becomes stale (for stale-while-revalidate) */
17
+ stale?: number
18
+ /** Whether this entry resulted from an error */
19
+ isError?: boolean
20
+ }
21
+
22
+ /**
23
+ * Options for cache operations
24
+ */
25
+ export interface CacheOptions {
26
+ /** Time to live in milliseconds */
27
+ ttl?: number
28
+ /** TTL for error responses (typically shorter) */
29
+ errorTTL?: number
30
+ /** Time in ms after which the value is stale but still usable */
31
+ staleWhileRevalidate?: number
32
+ }
33
+
34
+ /**
35
+ * Generic cache interface that can be implemented by different backends
36
+ */
37
+ export interface DecoderCache {
38
+ /**
39
+ * Get a value from the cache
40
+ */
41
+ get<T>(key: string): Promise<CacheEntry<T> | undefined>
42
+
43
+ /**
44
+ * Set a value in the cache
45
+ */
46
+ set<T>(key: string, value: T, options?: CacheOptions): Promise<void>
47
+
48
+ /**
49
+ * Delete a value from the cache
50
+ */
51
+ delete(key: string): Promise<void>
52
+
53
+ /**
54
+ * Get multiple values at once (for batch operations)
55
+ */
56
+ getMany?<T>(keys: string[]): Promise<Map<string, CacheEntry<T>>>
57
+
58
+ /**
59
+ * Set multiple values at once (for batch operations)
60
+ */
61
+ setMany?<T>(
62
+ entries: Array<{ key: string; value: T; options?: CacheOptions }>
63
+ ): Promise<void>
64
+
65
+ /**
66
+ * Clear all entries (optional)
67
+ */
68
+ clear?(): Promise<void>
69
+
70
+ /**
71
+ * Get or set with automatic promise deduplication
72
+ * This is the main method for async operations
73
+ */
74
+ getOrSet<T>(
75
+ key: string,
76
+ factory: (signal?: AbortSignal) => Promise<T>,
77
+ options?: CacheOptions & { signal?: AbortSignal }
78
+ ): Promise<T>
79
+ }
80
+
81
+ /**
82
+ * In-flight promise tracking for deduplication
83
+ */
84
+ export class PromiseTracker<T> {
85
+ private promises = new Map<string, Promise<T>>()
86
+
87
+ /**
88
+ * Get or create a promise for the given key
89
+ */
90
+ async getOrCreate(key: string, factory: () => Promise<T>): Promise<T> {
91
+ const existing = this.promises.get(key)
92
+ if (existing) {
93
+ return existing
94
+ }
95
+
96
+ const promise = factory().finally(() => {
97
+ // Clean up after completion
98
+ this.promises.delete(key)
99
+ })
100
+
101
+ this.promises.set(key, promise)
102
+ return promise
103
+ }
104
+
105
+ /**
106
+ * Check if a promise is in flight
107
+ */
108
+ has(key: string): boolean {
109
+ return this.promises.has(key)
110
+ }
111
+
112
+ /**
113
+ * Clear all tracked promises
114
+ */
115
+ clear(): void {
116
+ this.promises.clear()
117
+ }
118
+ }
119
+
120
+ /**
121
+ * Helper to create cache keys
122
+ */
123
+ export function makeCacheKey(type: string, ...parts: (string | Hex)[]): string {
124
+ return `${type}:${parts.join(':')}`
125
+ }
126
+
127
+ /**
128
+ * Cache key prefixes for different resource types
129
+ */
130
+ export enum CacheKeyPrefix {
131
+ ABI = 'abi',
132
+ FUNCTION_SIGNATURE = 'sig',
133
+ ADDRESS = 'addr',
134
+ TOKEN = 'token',
135
+ PROFILE = 'profile',
136
+ EXTERNAL_DATA = 'ext',
137
+ SCHEMA = 'schema',
138
+ CONTRACT_METADATA = 'contract-meta',
139
+ }
@@ -0,0 +1,125 @@
1
+ import type { ERC725JSONSchema } from '@erc725/erc725.js'
2
+ import LSP1Schema from '@erc725/erc725.js/schemas/LSP1UniversalReceiverDelegate.json' with {
3
+ type: 'json',
4
+ }
5
+ import LSP3Schema from '@erc725/erc725.js/schemas/LSP3ProfileMetadata.json' with {
6
+ type: 'json',
7
+ }
8
+ import LSP4Schema from '@erc725/erc725.js/schemas/LSP4DigitalAsset.json' with {
9
+ type: 'json',
10
+ }
11
+ import LSP4LegacySchema from '@erc725/erc725.js/schemas/LSP4DigitalAssetLegacy.json' with {
12
+ type: 'json',
13
+ }
14
+ import LSP5Schema from '@erc725/erc725.js/schemas/LSP5ReceivedAssets.json' with {
15
+ type: 'json',
16
+ }
17
+ import LSP6Schema from '@erc725/erc725.js/schemas/LSP6KeyManager.json' with {
18
+ type: 'json',
19
+ }
20
+ import LSP8Schema from '@erc725/erc725.js/schemas/LSP8IdentifiableDigitalAsset.json' with {
21
+ type: 'json',
22
+ }
23
+ import LSP9Schema from '@erc725/erc725.js/schemas/LSP9Vault.json' with {
24
+ type: 'json',
25
+ }
26
+ import LSP10Schema from '@erc725/erc725.js/schemas/LSP10ReceivedVaults.json' with {
27
+ type: 'json',
28
+ }
29
+ import LSP12Schema from '@erc725/erc725.js/schemas/LSP12IssuedAssets.json' with {
30
+ type: 'json',
31
+ }
32
+ import LSP17Schema from '@erc725/erc725.js/schemas/LSP17ContractExtension.json' with {
33
+ type: 'json',
34
+ }
35
+
36
+ export const IPFS_GATEWAY = 'https://api.universalprofile.cloud/ipfs/'
37
+ export const FUNCTION_DICTIONARY_URL =
38
+ 'https://4bytesdictionary.universalprofile.cloud'
39
+ export const OPENCHAIN_DICTIONARY_URL =
40
+ 'https://api.openchain.xyz/signature-database/v1/lookup'
41
+ export const ARRAY_MAX = 1000
42
+
43
+ /**
44
+ * Concatenation of all the schemas
45
+ */
46
+ export const defaultSchema: ERC725JSONSchema[] = [
47
+ ...LSP8Schema,
48
+ ...LSP4Schema,
49
+ ...LSP5Schema,
50
+ ...LSP3Schema,
51
+ ...LSP1Schema,
52
+ ...LSP4LegacySchema,
53
+ ...LSP6Schema,
54
+ ...LSP9Schema,
55
+ ...LSP10Schema,
56
+ ...LSP12Schema,
57
+ ...LSP17Schema,
58
+ {
59
+ name: 'LSP28TheGrid',
60
+ key: '0x724141d9918ce69e6b8afcf53a91748466086ba2c74b94cab43c649ae2ac23ff',
61
+ keyType: 'Singleton',
62
+ valueType: 'bytes',
63
+ valueContent: 'VerifiableURI',
64
+ },
65
+ ]
66
+
67
+ /**
68
+ * The important standards for profile, lsp7 and lsp8
69
+ */
70
+ type Standards =
71
+ | 'LSP0ERC725Account'
72
+ | 'LSP7DigitalAsset'
73
+ | 'LSP8IdentifiableDigitalAsset'
74
+
75
+ /**
76
+ * Interface id entry for interface id items.
77
+ */
78
+ type StandardID = {
79
+ key: `0x${string}`
80
+ name: string
81
+ standard: Standards
82
+ }
83
+
84
+ /**
85
+ * Array of our standard interfaceIDs
86
+ */
87
+ export const ITEMS: StandardID[] = [
88
+ {
89
+ key: '0x24871b3d' as `0x${string}`,
90
+ name: 'isProfile',
91
+ standard: 'LSP0ERC725Account' as Standards,
92
+ },
93
+
94
+ {
95
+ key: '0xc52d6008' as `0x${string}`,
96
+ name: 'isLSP7',
97
+ standard: 'LSP7DigitalAsset' as Standards,
98
+ },
99
+ {
100
+ key: '0xb3c4928f' as `0x${string}`,
101
+ name: 'isLSP7',
102
+ standard: 'LSP7DigitalAsset' as Standards,
103
+ },
104
+ {
105
+ key: '0xdaa746b7' as `0x${string}`,
106
+ name: 'isLSP7',
107
+ standard: 'LSP7DigitalAsset' as Standards,
108
+ },
109
+
110
+ {
111
+ key: '0x3a271706' as `0x${string}`,
112
+ name: 'isLSP8',
113
+ standard: 'LSP8IdentifiableDigitalAsset' as Standards,
114
+ },
115
+ {
116
+ key: '0xecad9f75' as `0x${string}`,
117
+ name: 'isLSP8',
118
+ standard: 'LSP8IdentifiableDigitalAsset' as Standards,
119
+ },
120
+ {
121
+ key: '0x30dc5278' as `0x${string}`,
122
+ name: 'isLSP8',
123
+ standard: 'LSP8IdentifiableDigitalAsset' as Standards,
124
+ },
125
+ ]
@@ -0,0 +1,292 @@
1
+ import { type Signal, signal } from '@preact/signals-core'
2
+ import type { Hex, Transaction } from 'viem'
3
+ import { collectDataKeys } from '../core/addressCollector'
4
+ import { deepFreeze } from '../core/dataModel'
5
+ import { pluginRegistry } from '../decoder/registry'
6
+ import { decodeTransaction as decodeTransactionCore } from '../decoder/transaction'
7
+ import {
8
+ AsyncOperations,
9
+ type DecoderOptions,
10
+ type DecoderResult,
11
+ ErrorType,
12
+ } from '../decoder/types'
13
+ import { isAsyncOperationEnabled } from '../decoder/utils'
14
+ import { ERROR_CODES } from '../shared/constants'
15
+ import type { DataKey, TransactionState } from '../types'
16
+
17
+ /**
18
+ * Address resolver interface for batch resolution
19
+ */
20
+ export interface AddressResolver {
21
+ resolveAddresses(addresses: DataKey[]): Promise<void>
22
+ }
23
+
24
+ /**
25
+ * Result from decoding a transaction
26
+ */
27
+ export interface TransactionDecoderResult {
28
+ // Immediate sync result
29
+ immediate: DecoderResult
30
+
31
+ // Signal that updates as async decoding progresses
32
+ signal: Signal<TransactionState>
33
+ }
34
+
35
+ /**
36
+ * Decode a transaction with progressive enhancement
37
+ * @param transaction - Raw transaction data or array of transactions
38
+ * @param options - Decoder options
39
+ * @returns Immediate result and reactive signal (or array of them)
40
+ */
41
+ export async function decodeTransaction<
42
+ T extends DecoderResult | DecoderResult[],
43
+ >(
44
+ transaction: T,
45
+ options: DecoderOptions & { addressResolver?: AddressResolver }
46
+ ): Promise<
47
+ T extends Transaction[]
48
+ ? TransactionDecoderResult[]
49
+ : TransactionDecoderResult
50
+ > {
51
+ const isBatch = Array.isArray(transaction)
52
+ const transactions: DecoderResult[] = isBatch
53
+ ? (transaction as unknown as DecoderResult[])
54
+ : [transaction as DecoderResult]
55
+
56
+ const results = await decodeTransactionBatch(transactions, options)
57
+
58
+ // Return single result if single input
59
+ if (isBatch) {
60
+ return results as T extends Transaction[]
61
+ ? TransactionDecoderResult[]
62
+ : TransactionDecoderResult
63
+ }
64
+ return results[0] as T extends Transaction[]
65
+ ? TransactionDecoderResult[]
66
+ : TransactionDecoderResult
67
+ }
68
+
69
+ /**
70
+ * Internal batch decoder with three-phase approach
71
+ */
72
+ async function decodeTransactionBatch(
73
+ transactions: DecoderResult[],
74
+ options: DecoderOptions & { addressResolver?: AddressResolver }
75
+ ): Promise<TransactionDecoderResult[]> {
76
+ // Extract names of plugins provided in options (these will override registered ones)
77
+ const overridePluginNames =
78
+ options.plugins
79
+ ?.map((p) => p.name)
80
+ .filter((name): name is string => typeof name === 'string') || []
81
+
82
+ const overrideSchemaNames =
83
+ options.schemaPlugins
84
+ ?.map((p) => p.name)
85
+ .filter((name): name is string => typeof name === 'string') || []
86
+
87
+ // Get registered plugins, excluding those being overridden
88
+ const registeredPlugins = await pluginRegistry.getAll({
89
+ excludeNames: overridePluginNames,
90
+ })
91
+ const registeredSchemaPlugins =
92
+ pluginRegistry.getAllSchema(overrideSchemaNames)
93
+
94
+ // Merge: provided plugins override registered ones
95
+ const allPlugins = [...registeredPlugins, ...(options.plugins || [])]
96
+ const allSchemaPlugins = [
97
+ ...registeredSchemaPlugins,
98
+ ...(options.schemaPlugins || []),
99
+ ]
100
+
101
+ // Phase 1: Sync decode
102
+ const syncOptions: DecoderOptions = {
103
+ ...options,
104
+ plugins: allPlugins.filter(({ usesAsync }) => !usesAsync),
105
+ schemaPlugins: allSchemaPlugins,
106
+ async: false,
107
+ }
108
+
109
+ const results = await Promise.all(
110
+ transactions.map(async (tx) => {
111
+ const initialResult = await decodeTransactionCore(tx, syncOptions)
112
+ const initialData = deepFreeze(initialResult || createErrorResult(tx))
113
+
114
+ // Create signal with frozen data
115
+ const txSignal = signal<TransactionState>({
116
+ loading: false,
117
+ data: initialData as DecoderResult,
118
+ decodingStatus: initialResult ? 'decoded' : 'failed',
119
+ addresses: collectDataKeys(tx),
120
+ addressesResolved: false,
121
+ lastUpdated: Date.now(),
122
+ })
123
+
124
+ return {
125
+ transaction: tx,
126
+ immediate: initialData,
127
+ signal: txSignal,
128
+ }
129
+ })
130
+ )
131
+
132
+ // Check if we have any async plugins for phase 2
133
+ // For now, assume we have async plugins if async operations are enabled
134
+ // since plugins themselves no longer have the async property
135
+ const hasAsyncPlugins = true
136
+ const pluginsEnabled = isAsyncOperationEnabled(
137
+ options.async,
138
+ AsyncOperations.ENABLE_PLUGINS
139
+ )
140
+
141
+ if (hasAsyncPlugins && pluginsEnabled) {
142
+ // Phase 2: ABI retrieval (async plugins)
143
+ const asyncOptions: DecoderOptions = {
144
+ ...options,
145
+ plugins: allPlugins,
146
+ schemaPlugins: allSchemaPlugins,
147
+ async: true,
148
+ }
149
+
150
+ // Run async decoding for all transactions
151
+ for (const { transaction, signal: txSignal } of results) {
152
+ decodeTransactionCore(transaction, asyncOptions)
153
+ .then((enhanced) => {
154
+ if (enhanced) {
155
+ txSignal.value = {
156
+ ...txSignal.value,
157
+ data: deepFreeze(enhanced) as DecoderResult,
158
+ decodingStatus: 'enhanced',
159
+ addresses: collectDataKeys(enhanced),
160
+ lastUpdated: Date.now(),
161
+ }
162
+ }
163
+ })
164
+ .catch((error) => {
165
+ console.error('Async decode error:', error)
166
+ txSignal.value = {
167
+ ...txSignal.value,
168
+ decodingStatus: 'failed',
169
+ error: error instanceof Error ? error.message : 'Unknown error',
170
+ loading: false,
171
+ }
172
+ })
173
+ }
174
+ }
175
+
176
+ // Phase 3: Address resolution (if resolver provided and enabled)
177
+ const addressResolveEnabled = isAsyncOperationEnabled(
178
+ options.async,
179
+ AsyncOperations.ENABLE_ADDRESS_RESOLVE
180
+ )
181
+
182
+ if (options.addressResolver && addressResolveEnabled) {
183
+ // Collect all unique addresses from all transactions
184
+ const allAddresses = new Set<DataKey>()
185
+ for (const { signal: txSignal } of results) {
186
+ for (const addr of txSignal.value.addresses) {
187
+ allAddresses.add(addr)
188
+ }
189
+ }
190
+
191
+ // Batch resolve addresses
192
+ if (allAddresses.size > 0) {
193
+ options.addressResolver
194
+ .resolveAddresses(Array.from(allAddresses))
195
+ .then(() => {
196
+ // Update all signals to indicate addresses are resolved
197
+ for (const { signal: txSignal } of results) {
198
+ if (txSignal.value.decodingStatus !== 'failed') {
199
+ txSignal.value = {
200
+ ...txSignal.value,
201
+ addressesResolved: true,
202
+ decodingStatus: 'complete',
203
+ lastUpdated: Date.now(),
204
+ }
205
+ }
206
+ }
207
+ })
208
+ .catch((error) => {
209
+ console.error('Address resolution error:', error)
210
+ // Don't fail the transaction, just log the error
211
+ })
212
+ }
213
+ } else if (!hasAsyncPlugins || (!pluginsEnabled && !addressResolveEnabled)) {
214
+ // No async operations enabled, mark as complete
215
+ for (const { signal: txSignal } of results) {
216
+ if (txSignal.value.decodingStatus !== 'failed') {
217
+ txSignal.value = {
218
+ ...txSignal.value,
219
+ decodingStatus: 'complete',
220
+ }
221
+ }
222
+ }
223
+ }
224
+
225
+ return results.map((r) => ({
226
+ immediate: r.immediate,
227
+ signal: r.signal,
228
+ }))
229
+ }
230
+
231
+ function createErrorResult(transaction: DecoderResult): DecoderResult {
232
+ return {
233
+ ...transaction,
234
+ resultType: 'error' as const,
235
+ isDecoded: false,
236
+ errorType: ErrorType.ERROR,
237
+ sig: transaction.input?.slice(0, 10) as Hex,
238
+ error: {
239
+ code: ERROR_CODES.DECODING_FAILED,
240
+ message: 'Failed to decode transaction',
241
+ details: new Error('Decoder returned undefined'),
242
+ },
243
+ }
244
+ }
245
+
246
+ /**
247
+ * Decode a transaction and wait for complete result
248
+ * Convenience wrapper for when you need a promise
249
+ */
250
+ export async function decodeTransactionAsync<
251
+ T extends DecoderResult | DecoderResult[],
252
+ >(
253
+ transaction: T,
254
+ options: DecoderOptions & { addressResolver?: AddressResolver }
255
+ ): Promise<T extends Transaction[] ? DecoderResult[] : DecoderResult> {
256
+ const isBatch = Array.isArray(transaction)
257
+ const result = await decodeTransaction(transaction, options)
258
+ const results = isBatch
259
+ ? (result as TransactionDecoderResult[])
260
+ : [result as TransactionDecoderResult]
261
+
262
+ // Wait for all decodings to complete
263
+ const finalResults = await Promise.all(
264
+ results.map(
265
+ ({ signal }) =>
266
+ new Promise<DecoderResult>((resolve) => {
267
+ const checkComplete = () => {
268
+ const state = signal.value
269
+ if (
270
+ state.decodingStatus === 'complete' ||
271
+ state.decodingStatus === 'failed'
272
+ ) {
273
+ resolve(state.data as DecoderResult)
274
+ } else {
275
+ // Check again on next tick
276
+ setTimeout(checkComplete, 10)
277
+ }
278
+ }
279
+ checkComplete()
280
+ })
281
+ )
282
+ )
283
+
284
+ if (isBatch) {
285
+ return finalResults as T extends Transaction[]
286
+ ? DecoderResult[]
287
+ : DecoderResult
288
+ }
289
+ return finalResults[0] as T extends Transaction[]
290
+ ? DecoderResult[]
291
+ : DecoderResult
292
+ }
@@ -0,0 +1,95 @@
1
+ import { type Chain, decodeAbiParameters, slice } from 'viem'
2
+ import defaultPlugins from './plugins'
3
+ import { decodeTransaction } from './transaction'
4
+ import {
5
+ DecoderResult,
6
+ ErrorType,
7
+ type ResultError,
8
+ type SchemaPlugin,
9
+ } from './types'
10
+ import { createNamedArgs } from './utils'
11
+
12
+ const ERROR = {
13
+ STRING: '0x08c379a0', // keccak256('Error(string)').slice(0, 10)
14
+ PANIC: '0x4e487b71', // keccak256('Panic(uint)').slice(0, 10)
15
+ EMPTY_REVERT: '0xd8b98391', // keccak256('Revert()').slice(0, 10)
16
+ }
17
+
18
+ export const ErrorTypeMap: {
19
+ [ErrorType.INVALID]: 'transaction.error.types.invalid'
20
+ [ErrorType.PANIC]: 'transaction.error.types.panic'
21
+ [ErrorType.ERROR]: 'transaction.error.types.error'
22
+ [ErrorType.CUSTOM_ERROR]: 'transaction.error.types.custom_error'
23
+ [ErrorType.EMPTY_REVERT]: 'transaction.error.types.empty_revert'
24
+ [key: string]: string
25
+ } = {
26
+ [ErrorType.INVALID]: 'transaction.error.types.invalid',
27
+ [ErrorType.PANIC]: 'transaction.error.types.panic',
28
+ [ErrorType.ERROR]: 'transaction.error.types.error',
29
+ [ErrorType.CUSTOM_ERROR]: 'transaction.error.types.custom_error',
30
+ [ErrorType.EMPTY_REVERT]: 'transaction.error.types.empty_revert',
31
+ }
32
+
33
+ const CONTRACT_ERROR_THROWN = 'transactions.error.contract_error_thrown'
34
+
35
+ function populateEmptyRevertInfo(
36
+ input: `0x${string}`
37
+ ): ResultError | undefined {
38
+ const sig = slice(input, 0, 4)
39
+ switch (sig) {
40
+ case ERROR.EMPTY_REVERT:
41
+ return {
42
+ resultType: 'error',
43
+ errorType: ErrorType.EMPTY_REVERT,
44
+ summary: '',
45
+ input,
46
+ sig,
47
+ ...createNamedArgs([], []),
48
+ functionName: 'Revert',
49
+ devdocs: '',
50
+ }
51
+ case ERROR.STRING:
52
+ return {
53
+ resultType: 'error',
54
+ errorType: ErrorType.ERROR,
55
+ summary: 'Transaction reverted with string',
56
+ input,
57
+ sig,
58
+ ...createNamedArgs([], []),
59
+ functionName: 'Error',
60
+ devdocs: '',
61
+ }
62
+ case ERROR.PANIC: {
63
+ const value = decodeAbiParameters([{ type: 'uint' }], slice(input, 4))
64
+ return {
65
+ resultType: 'error',
66
+ errorType: ErrorType.PANIC,
67
+ summary: 'Transaction reverted due to Panic',
68
+ input,
69
+ sig,
70
+ ...createNamedArgs([value[0]], [{ type: 'uint', name: 'code' }]),
71
+ functionName: 'Panic',
72
+ devdocs: '',
73
+ }
74
+ }
75
+ }
76
+ }
77
+
78
+ export async function decodeError(chain: Chain, input: `0x${string}`) {
79
+ const decoded = populateEmptyRevertInfo(input)
80
+ if (decoded) {
81
+ return decoded
82
+ }
83
+ return decodeTransaction(
84
+ {
85
+ input,
86
+ } as DecoderResult,
87
+ {
88
+ chain,
89
+ preferError: true,
90
+ plugins: defaultPlugins,
91
+ schemaPlugins: [] as SchemaPlugin[],
92
+ wrappers: [], // Wrappers will be ignored because there are none.
93
+ }
94
+ )
95
+ }