@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.
- package/LICENSE +201 -0
- package/README.md +486 -0
- package/dist/browser.cjs +6912 -0
- package/dist/browser.cjs.map +1 -0
- package/dist/browser.d.cts +6 -0
- package/dist/browser.d.ts +6 -0
- package/dist/browser.js +131 -0
- package/dist/browser.js.map +1 -0
- package/dist/cdn/transaction-decoder.global.js +296 -0
- package/dist/cdn/transaction-decoder.global.js.map +1 -0
- package/dist/chunk-GGBHTWJL.js +437 -0
- package/dist/chunk-GGBHTWJL.js.map +1 -0
- package/dist/chunk-GXZOF3QY.js +839 -0
- package/dist/chunk-GXZOF3QY.js.map +1 -0
- package/dist/chunk-LJ6ES5XF.js +776 -0
- package/dist/chunk-LJ6ES5XF.js.map +1 -0
- package/dist/chunk-XVHJWV5U.js +4925 -0
- package/dist/chunk-XVHJWV5U.js.map +1 -0
- package/dist/data.cjs +5518 -0
- package/dist/data.cjs.map +1 -0
- package/dist/data.d.cts +43 -0
- package/dist/data.d.ts +43 -0
- package/dist/data.js +55 -0
- package/dist/data.js.map +1 -0
- package/dist/index-BzXh7poJ.d.cts +524 -0
- package/dist/index-BzXh7poJ.d.ts +524 -0
- package/dist/index.cjs +6912 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +756 -0
- package/dist/index.d.ts +756 -0
- package/dist/index.js +131 -0
- package/dist/index.js.map +1 -0
- package/dist/server.cjs +5644 -0
- package/dist/server.cjs.map +1 -0
- package/dist/server.d.cts +217 -0
- package/dist/server.d.ts +217 -0
- package/dist/server.js +644 -0
- package/dist/server.js.map +1 -0
- package/dist/utils-CBAkjQh3.d.cts +108 -0
- package/dist/utils-xT9-km0r.d.ts +108 -0
- package/package.json +101 -0
- package/src/browser.ts +13 -0
- package/src/client/resolveAddresses.ts +157 -0
- package/src/core/addressCollector.ts +153 -0
- package/src/core/addressResolver.ts +135 -0
- package/src/core/dataModel.ts +888 -0
- package/src/core/instance.ts +33 -0
- package/src/core/integrateDecoder.ts +325 -0
- package/src/data.ts +70 -0
- package/src/decoder/GENERATOR_PROPOSAL.md +182 -0
- package/src/decoder/THREE_PHASE_EXAMPLE.md +108 -0
- package/src/decoder/aggregation.ts +218 -0
- package/src/decoder/browserCache.ts +237 -0
- package/src/decoder/cache/README.md +126 -0
- package/src/decoder/cache/index.ts +44 -0
- package/src/decoder/cache.ts +139 -0
- package/src/decoder/constants.ts +125 -0
- package/src/decoder/decodeTransaction.ts +292 -0
- package/src/decoder/errors.ts +95 -0
- package/src/decoder/events.ts +192 -0
- package/src/decoder/functionSignature.ts +344 -0
- package/src/decoder/getDataFromExternalSources.ts +248 -0
- package/src/decoder/graphqlWS.ts +22 -0
- package/src/decoder/interfaces.ts +185 -0
- package/src/decoder/keyValue.ts +5 -0
- package/src/decoder/kvCache.ts +241 -0
- package/src/decoder/lruCache.ts +184 -0
- package/src/decoder/lsp7Mint.test.ts +179 -0
- package/src/decoder/lsp7TransferBatch.test.ts +105 -0
- package/src/decoder/plugins/RegistryAbi.ts +562 -0
- package/src/decoder/plugins/enhanceBurntPix.ts +132 -0
- package/src/decoder/plugins/enhanceGraffiti.ts +70 -0
- package/src/decoder/plugins/enhanceLSP0ERC725Account.ts +179 -0
- package/src/decoder/plugins/enhanceLSP26FollowerSystem.ts +88 -0
- package/src/decoder/plugins/enhanceLSP6KeyManager.ts +231 -0
- package/src/decoder/plugins/enhanceLSP7DigitalAsset.ts +165 -0
- package/src/decoder/plugins/enhanceLSP8IdentifiableDigitalAsset.ts +170 -0
- package/src/decoder/plugins/enhanceLSP9Vault.ts +57 -0
- package/src/decoder/plugins/enhanceRetrieveAbi.ts +85 -0
- package/src/decoder/plugins/enhanceSetData.ts +135 -0
- package/src/decoder/plugins/index.ts +99 -0
- package/src/decoder/plugins/schemaDefault.ts +318 -0
- package/src/decoder/plugins/standardPlugin.ts +202 -0
- package/src/decoder/registry.ts +322 -0
- package/src/decoder/singleGQL.ts +293 -0
- package/src/decoder/transaction.ts +198 -0
- package/src/decoder/types.ts +465 -0
- package/src/decoder/utils.ts +212 -0
- package/src/example/usage.ts +172 -0
- package/src/index.ts +174 -0
- package/src/server/addressResolver.ts +68 -0
- package/src/server/caches.ts +209 -0
- package/src/server/decodeTransactionSync.ts +156 -0
- package/src/server/decodeTransactionsBatch.ts +207 -0
- package/src/server/finishDecoding.ts +116 -0
- package/src/server/index.ts +81 -0
- package/src/server/lsp23Resolver.test.ts +46 -0
- package/src/server/lsp23Resolver.ts +419 -0
- package/src/server/types.ts +168 -0
- package/src/server.ts +22 -0
- package/src/shared/addressResolver.ts +651 -0
- package/src/shared/cache.ts +144 -0
- package/src/shared/constants.ts +21 -0
- package/src/stubs/tty.ts +13 -0
- package/src/stubs/util.ts +42 -0
- package/src/types/index.ts +154 -0
- package/src/types/provider.ts +46 -0
- package/src/umd.ts +13 -0
- package/src/utils/debug.ts +49 -0
- 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
|
+
}
|