@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,81 @@
1
+ import { ServerAddressResolver } from './addressResolver'
2
+ import { ServerDecoderCaches } from './caches'
3
+ import { decodeTransactionSync } from './decodeTransactionSync'
4
+ import { decodeTransactionsBatch } from './decodeTransactionsBatch'
5
+ import { finishDecoding } from './finishDecoding'
6
+ import type {
7
+ ServerDecoder,
8
+ ServerDecoderConfig,
9
+ ServerDecoderOptions,
10
+ } from './types'
11
+
12
+ // Import plugins to ensure they are registered
13
+ import '../decoder/plugins'
14
+
15
+ /**
16
+ * Create a server-optimized decoder instance
17
+ */
18
+ export function createServerDecoder(
19
+ config: ServerDecoderConfig
20
+ ): ServerDecoder {
21
+ // Create caches
22
+ const caches = new ServerDecoderCaches({
23
+ transactionCacheSize: config.transactionCacheSize,
24
+ addressCacheSize: config.addressCacheSize,
25
+ abiCacheSize: config.abiCacheSize,
26
+ schemaCacheSize: config.schemaCacheSize,
27
+ })
28
+
29
+ // Create address resolver
30
+ const addressResolver = new ServerAddressResolver(
31
+ config.graphqlEndpoint,
32
+ config.defaultChain,
33
+ caches
34
+ )
35
+
36
+ // Return server decoder interface
37
+ return {
38
+ async decodeTransaction(transaction, options) {
39
+ const opts: ServerDecoderOptions = {
40
+ chain: config.defaultChain,
41
+ timeoutMs: config.defaultTimeout,
42
+ ...options,
43
+ }
44
+ return decodeTransactionSync(transaction, opts, caches)
45
+ },
46
+
47
+ async decodeTransactionsBatch(transactions, options) {
48
+ const opts: ServerDecoderOptions = {
49
+ chain: config.defaultChain,
50
+ timeoutMs: config.defaultTimeout,
51
+ ...options,
52
+ }
53
+ return decodeTransactionsBatch(transactions, opts, caches)
54
+ },
55
+
56
+ async finishDecoding(transaction, options) {
57
+ const opts: ServerDecoderOptions = {
58
+ chain: config.defaultChain,
59
+ timeoutMs: config.defaultTimeout,
60
+ ...options,
61
+ }
62
+ return finishDecoding(transaction, opts, caches)
63
+ },
64
+
65
+ clearCaches() {
66
+ caches.clear()
67
+ },
68
+
69
+ getCacheStats() {
70
+ return caches.getStats()
71
+ },
72
+
73
+ async warmAddressCache(addresses) {
74
+ await addressResolver.warmCache(addresses)
75
+ },
76
+
77
+ async resolveAddresses(addresses, timeoutMs = 3000) {
78
+ return addressResolver.resolveAddressesWithTimeout(addresses, timeoutMs)
79
+ },
80
+ }
81
+ }
@@ -0,0 +1,46 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import {
3
+ decodeDeploymentCalldata,
4
+ decodeDeploymentOutput,
5
+ } from './lsp23Resolver'
6
+
7
+ describe('LSP23 Resolver', () => {
8
+ it('should decode deployERC1167Proxies calldata', () => {
9
+ // Sample transaction from user's trace
10
+ const input =
11
+ '0x6a66a75300000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000066093407b6704b89793beffd0d8f0000000000000000000000000000000000000000000000000000000000000002602835628cabf5153dc5b88df586e3f52fd940693d9634d5d50a353cb1b8077234000000000000000000000000000000000000000000000000000000000000000000000000000000000000000052c90985af970d4e0dc26cb5d052505278af32a900000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000024c4d66de8000000000000000000000000000000000066093407b6704b89793beffd0d8f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a75684d7d048704a2db851d05ba0c3cbe226264c00000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000004c4d66de800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004400000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000075ef83ad9559033e6e941db7d7c495acdce616347d28e90c7ce47cbfcfcad3bc50cfc51aec37c55a4d0b1a65c6255c4bf2fbdf6277f3cc0730c45b828b6db8b474b80742de2bf82acb3630000a5467dfe7019bf2c7c5f7a707711b9d4cad118c84b80742de2bf82acb36300004c773efdab62bfccf329acda9018b7dac888e463df30dba06db6a30e65354d9a64c609861f089545ca58c6b4dbe31a5f338cb0e3df30dba06db6a30e65354d9a64c6098600000000000000000000000000000000df30dba06db6a30e65354d9a64c6098600000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000220000000000000000000000000000000000000000000000000000000000000026000000000000000000000000000000000000000000000000000000000000002a0000000000000000000000000000000000000000000000000000000000000005d00006f357c6a0020f8a1d63f95b24fbfcacf39976c7cbc8213cfd14d0ec50b1c3901f1c2cfe8f633697066733a2f2f516d627047734e516b56744e7375514a636e5437724458366d3676317a52675261516637364e48357a674e4e506e0000000000000000000000000000000000000000000000000000000000000000000014a5467dfe7019bf2c7c5f7a707711b9d4cad118c800000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000060080000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000007f3f06000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000014a5467dfe7019bf2c7c5f7a707711b9d4cad118c800000000000000000000000000000000000000000000000000000000000000000000000000000000000000144c773efdab62bfccf329acda9018b7dac888e463000000000000000000000000'
12
+
13
+ const decoded = decodeDeploymentCalldata(input)
14
+
15
+ expect(decoded).toBeDefined()
16
+ expect(decoded?.salt).toBe(
17
+ '0x2835628cabf5153dc5b88df586e3f52fd940693d9634d5d50a353cb1b8077234'
18
+ )
19
+ expect(decoded?.primaryImplementation.toLowerCase()).toBe(
20
+ '0x52c90985af970d4e0dc26cb5d052505278af32a9'
21
+ )
22
+ expect(decoded?.secondaryImplementation.toLowerCase()).toBe(
23
+ '0xa75684d7d048704a2db851d05ba0c3cbe226264c'
24
+ )
25
+ expect(decoded?.postDeploymentModule.toLowerCase()).toBe(
26
+ '0x000000000066093407b6704b89793beffd0d8f00'
27
+ )
28
+ expect(decoded?.initializationCalldata).toBeDefined()
29
+ expect(decoded?.initializationCalldata.length).toBeGreaterThan(0)
30
+ })
31
+
32
+ it('should decode deployERC1167Proxies output', () => {
33
+ const output =
34
+ '0x00000000000000000000000002e655f92f01bc7880807ec409f134b91bb28381000000000000000000000000497fdac9e42193f6f79651d2d1caf6ba5789e055'
35
+
36
+ const decoded = decodeDeploymentOutput(output)
37
+
38
+ expect(decoded).toBeDefined()
39
+ expect(decoded?.profileAddress.toLowerCase()).toBe(
40
+ '0x02e655f92f01bc7880807ec409f134b91bb28381'
41
+ )
42
+ expect(decoded?.urdAddress.toLowerCase()).toBe(
43
+ '0x497fdac9e42193f6f79651d2d1caf6ba5789e055'
44
+ )
45
+ })
46
+ })
@@ -0,0 +1,419 @@
1
+ import type { Address, Chain } from 'viem'
2
+ import {
3
+ bytesToHex,
4
+ decodeAbiParameters,
5
+ isHex,
6
+ parseAbiParameters,
7
+ } from 'viem'
8
+
9
+ /**
10
+ * LSP23 Factory address (same on all networks)
11
+ */
12
+ export const LSP23_FACTORY_ADDRESS =
13
+ '0x2300000a84d25df63081feaa37ba6b62c4c89a30' as const
14
+
15
+ /**
16
+ * deployERC1167Proxies function selector
17
+ */
18
+ export const DEPLOY_ERC1167_PROXIES_SELECTOR = '0x6a66a753' as const
19
+
20
+ /**
21
+ * LSP23 deployment data structure
22
+ */
23
+ export interface LSP23DeploymentData {
24
+ profileAddress: Address
25
+ urdAddress: Address
26
+ deployerAddress: Address // The controller that deployed this profile
27
+ salt: string
28
+ primaryImplementation: Address
29
+ secondaryImplementation: Address
30
+ postDeploymentModule: Address
31
+ initializationCalldata: string
32
+ deploymentCalldata: string // Full calldata to deployERC1167Proxies
33
+ transactionHash: string
34
+ blockNumber: bigint
35
+ blockTimestamp: number // Unix timestamp of the block
36
+ chainId: number
37
+ }
38
+
39
+ /**
40
+ * Contract deployment tuple structures
41
+ * Primary: (bytes32 salt, uint256 fundingAmount, address implementationContract, bytes initializeCalldata)
42
+ * Secondary: (uint256 fundingAmount, address implementationContract, bytes addInitializeCalldata, bool addConstructor, bytes constructorParams)
43
+ */
44
+
45
+ /**
46
+ * Decode deployERC1167Proxies calldata
47
+ *
48
+ * Function signature:
49
+ * deployERC1167Proxies(
50
+ * (bytes32 salt, uint256 fundingAmount, address implementationContract, bytes initializeCalldata) primaryContractDeployment,
51
+ * (uint256 fundingAmount, address implementationContract, bytes addInitializeCalldata, bool addConstructor, bytes constructorParams) secondaryContractDeployment,
52
+ * address postDeploymentModule,
53
+ * bytes postDeploymentModuleCalldata
54
+ * )
55
+ */
56
+ export function decodeDeploymentCalldata(input: string): {
57
+ salt: string
58
+ primaryImplementation: Address
59
+ secondaryImplementation: Address
60
+ postDeploymentModule: Address
61
+ initializationCalldata: string
62
+ } | null {
63
+ try {
64
+ // Remove function selector (first 4 bytes / 8 hex chars + 0x)
65
+ const params = `0x${input.slice(10)}` as `0x${string}`
66
+
67
+ // Decode the parameters
68
+ const decoded = decodeAbiParameters(
69
+ parseAbiParameters(
70
+ '(bytes32,uint256,address,bytes), (uint256,address,bytes,bool,bytes), address, bytes'
71
+ ),
72
+ params
73
+ )
74
+
75
+ const [
76
+ primaryDeployment,
77
+ secondaryDeployment,
78
+ postDeploymentModule,
79
+ postDeploymentModuleCalldata,
80
+ ] = decoded
81
+
82
+ // Primary deployment tuple: (bytes32 salt, uint256 fundingAmount, address implementationContract, bytes initializeCalldata)
83
+ const [salt, , primaryImplementation] = primaryDeployment as [
84
+ string,
85
+ bigint,
86
+ Address,
87
+ string,
88
+ ]
89
+
90
+ // Secondary deployment tuple: (uint256 fundingAmount, address implementationContract, bytes addInitializeCalldata, bool addConstructor, bytes constructorParams)
91
+ const [, secondaryImplementation] = secondaryDeployment as [
92
+ bigint,
93
+ Address,
94
+ string,
95
+ boolean,
96
+ string,
97
+ ]
98
+
99
+ if (!primaryImplementation || !secondaryImplementation) {
100
+ return null
101
+ }
102
+
103
+ return {
104
+ salt,
105
+ primaryImplementation,
106
+ secondaryImplementation,
107
+ postDeploymentModule: postDeploymentModule as Address,
108
+ initializationCalldata: postDeploymentModuleCalldata as string,
109
+ }
110
+ } catch (error) {
111
+ console.error('Failed to decode LSP23 deployment calldata:', error)
112
+ return null
113
+ }
114
+ }
115
+
116
+ /**
117
+ * Decode deployERC1167Proxies output to get deployed addresses
118
+ *
119
+ * Returns: (address primaryContractAddress, address secondaryContractAddress)
120
+ */
121
+ export function decodeDeploymentOutput(output: string): {
122
+ profileAddress: Address
123
+ urdAddress: Address
124
+ } | null {
125
+ try {
126
+ const decoded = decodeAbiParameters(
127
+ parseAbiParameters('address, address'),
128
+ output as `0x${string}`
129
+ )
130
+
131
+ return {
132
+ profileAddress: decoded[0],
133
+ urdAddress: decoded[1],
134
+ }
135
+ } catch (error) {
136
+ console.error('Failed to decode LSP23 deployment output:', error)
137
+ return null
138
+ }
139
+ }
140
+
141
+ /**
142
+ * Extract controller addresses from initialization calldata
143
+ *
144
+ * The initializationCalldata contains a setDataBatch call that sets up controller permissions.
145
+ * Function: setDataBatch(bytes32[] dataKeys, bytes[] dataValues)
146
+ *
147
+ * The dataKeys contain entries like:
148
+ * - AddressPermissions:Permissions:<controller-address>
149
+ *
150
+ * We extract controller addresses from these keys.
151
+ */
152
+ export function extractControllersFromInitCalldata(
153
+ initializationCalldata: string
154
+ ): Address[] {
155
+ try {
156
+ if (!initializationCalldata || initializationCalldata === '0x') {
157
+ return []
158
+ }
159
+
160
+ // The postDeploymentModuleCalldata is raw ABI-encoded (bytes32[], bytes[])
161
+ // No function selector - decode directly
162
+ const decoded = decodeAbiParameters(
163
+ parseAbiParameters('bytes32[], bytes[]'),
164
+ initializationCalldata as `0x${string}`
165
+ )
166
+
167
+ const [dataKeys, dataValues] = decoded
168
+ const controllers: Address[] = []
169
+
170
+ // Look for AddressPermissions[] keys
171
+ // The key is 0xdf30dba06db6a30e65354d9a64c60986 followed by zeros
172
+ // The controller address is in the corresponding value at the same index (encoded as bytes)
173
+ const ADDRESS_PERMISSIONS_ARRAY_PREFIX =
174
+ '0xdf30dba06db6a30e65354d9a64c60986'
175
+
176
+ for (let i = 0; i < dataKeys.length; i++) {
177
+ const keyStr = dataKeys[i].toLowerCase()
178
+ if (keyStr.startsWith(ADDRESS_PERMISSIONS_ARRAY_PREFIX)) {
179
+ // The value contains the controller address
180
+ const value = dataValues[i]
181
+
182
+ // Convert to hex if it's not already
183
+ const valueHex = isHex(value) ? value : bytesToHex(value as Uint8Array)
184
+
185
+ // The value should be a 20-byte address (40 hex chars + 0x = 42 total)
186
+ // or possibly 32 bytes with padding
187
+ if (valueHex.length === 42) {
188
+ // Exactly 20 bytes - this is the address
189
+ const addr = valueHex as Address
190
+ if (
191
+ addr !== '0x0000000000000000000000000000000000000000' &&
192
+ !controllers.some((c) => c.toLowerCase() === addr.toLowerCase())
193
+ ) {
194
+ controllers.push(addr)
195
+ }
196
+ }
197
+ }
198
+ }
199
+
200
+ return controllers
201
+ } catch (error) {
202
+ console.error('Failed to extract controllers from init calldata:', error)
203
+ return []
204
+ }
205
+ }
206
+
207
+ /**
208
+ * Hypersync query configuration for LSP23 deployments
209
+ */
210
+ export interface HypersyncQueryConfig {
211
+ /**
212
+ * Controller addresses (derived keys) that deployed profiles
213
+ * We only want LSP23 data for profiles WE created, not imported
214
+ */
215
+ controllerAddresses?: Address[]
216
+
217
+ /**
218
+ * Profile addresses to query deployment data for
219
+ * More efficient than querying by controllers when we already know the profiles
220
+ */
221
+ profileAddresses?: Address[]
222
+
223
+ /**
224
+ * Starting block (0 for genesis)
225
+ */
226
+ fromBlock?: number
227
+
228
+ /**
229
+ * Ending block (undefined for latest)
230
+ */
231
+ toBlock?: number
232
+
233
+ /**
234
+ * Chain ID to query
235
+ */
236
+ chainId: number
237
+ }
238
+
239
+ /**
240
+ * Build Hypersync query to find LSP23 deployments
241
+ *
242
+ * Strategy 1 (by profileAddresses - RECOMMENDED):
243
+ * Query CREATE2 traces where the created address matches our profile addresses.
244
+ * Then filter transactions to LSP23 factory and verify deployer is one of our controllers.
245
+ * This is more efficient when we already know the profile addresses from GraphQL.
246
+ *
247
+ * Strategy 2 (by controllerAddresses - LEGACY):
248
+ * Query transactions TO the LSP23 factory FROM our controller addresses.
249
+ * This scans the entire chain history for all deployments by our controllers.
250
+ */
251
+ export function buildLSP23DeploymentQuery(config: HypersyncQueryConfig) {
252
+ const {
253
+ controllerAddresses,
254
+ profileAddresses,
255
+ fromBlock = 0,
256
+ toBlock,
257
+ chainId,
258
+ } = config
259
+
260
+ // Prefer querying by profile addresses (more efficient)
261
+ if (profileAddresses && profileAddresses.length > 0) {
262
+ return {
263
+ from_block: fromBlock,
264
+ to_block: toBlock,
265
+ // Query CREATE2 traces where the created address is one of our profiles
266
+ traces: [
267
+ {
268
+ type: ['create2'],
269
+ to: profileAddresses.map((addr) => addr.toLowerCase()),
270
+ },
271
+ ],
272
+ // Also get the transactions to verify they're to the LSP23 factory
273
+ transactions: [
274
+ {
275
+ to: [LSP23_FACTORY_ADDRESS.toLowerCase()],
276
+ },
277
+ ],
278
+ field_selection: {
279
+ transaction: ['hash', 'from', 'to', 'input', 'output', 'block_number'],
280
+ trace: ['transaction_hash', 'type', 'from', 'to'],
281
+ block: ['number', 'timestamp'],
282
+ },
283
+ include_all_blocks: false,
284
+ }
285
+ }
286
+
287
+ // Fall back to querying by controller addresses (less efficient)
288
+ if (controllerAddresses && controllerAddresses.length > 0) {
289
+ return {
290
+ from_block: fromBlock,
291
+ to_block: toBlock,
292
+ // Query transactions to the LSP23 factory from our controllers
293
+ transactions: [
294
+ {
295
+ from: controllerAddresses.map((addr) => addr.toLowerCase()),
296
+ to: [LSP23_FACTORY_ADDRESS.toLowerCase()],
297
+ },
298
+ ],
299
+ // Also get traces to see the CREATE2 operations
300
+ traces: [
301
+ {
302
+ type: ['create2'],
303
+ // No filter on 'to' - we'll match by transaction hash
304
+ },
305
+ ],
306
+ field_selection: {
307
+ transaction: ['hash', 'from', 'to', 'input', 'output', 'block_number'],
308
+ trace: ['transaction_hash', 'type', 'from', 'to'],
309
+ block: ['number', 'timestamp'],
310
+ },
311
+ include_all_blocks: false,
312
+ }
313
+ }
314
+
315
+ throw new Error(
316
+ 'Either profileAddresses or controllerAddresses must be provided'
317
+ )
318
+ }
319
+
320
+ /**
321
+ * Parse Hypersync response to extract LSP23 deployment data
322
+ * @param response - Hypersync API response
323
+ * @param chainId - Chain ID
324
+ * @param controllerAddresses - Optional list of controller addresses to filter by (only return deployments from these deployers)
325
+ */
326
+ export function parseLSP23DeploymentsFromHypersync(
327
+ response: any,
328
+ chainId: number,
329
+ controllerAddresses?: Address[]
330
+ ): LSP23DeploymentData[] {
331
+ const deployments: LSP23DeploymentData[] = []
332
+
333
+ if (!response?.data) {
334
+ return deployments
335
+ }
336
+
337
+ // Build a map of block number -> timestamp
338
+ const blockTimestamps = new Map<number, number>()
339
+ for (const block of response.data.blocks || []) {
340
+ blockTimestamps.set(block.number, block.timestamp)
341
+ }
342
+
343
+ // Build a map of transaction hash -> transaction data
344
+ const txMap = new Map<string, any>()
345
+ for (const tx of response.data.transactions || []) {
346
+ if (
347
+ tx.to?.toLowerCase() === LSP23_FACTORY_ADDRESS.toLowerCase() &&
348
+ tx.input?.startsWith(DEPLOY_ERC1167_PROXIES_SELECTOR)
349
+ ) {
350
+ txMap.set(tx.hash.toLowerCase(), tx)
351
+ }
352
+ }
353
+
354
+ // Process traces to find CREATE2 operations
355
+ const create2Traces = new Map<string, Address>() // txHash -> created profile address
356
+
357
+ for (const trace of response.data.traces || []) {
358
+ if (trace.type === 'create2' && trace.to) {
359
+ create2Traces.set(
360
+ trace.transaction_hash.toLowerCase(),
361
+ trace.to as Address
362
+ )
363
+ }
364
+ }
365
+
366
+ // Create a Set of lowercase controller addresses for efficient filtering
367
+ const controllerSet = controllerAddresses
368
+ ? new Set(controllerAddresses.map((addr) => addr.toLowerCase()))
369
+ : null
370
+
371
+ // Match transactions with traces
372
+ for (const [txHash, profileAddress] of create2Traces) {
373
+ const tx = txMap.get(txHash)
374
+ if (!tx) continue
375
+
376
+ // If controller filtering is enabled, verify the deployer is one of our controllers
377
+ if (controllerSet && !controllerSet.has(tx.from.toLowerCase())) {
378
+ continue
379
+ }
380
+
381
+ // Decode the transaction
382
+ const decodedInput = decodeDeploymentCalldata(tx.input)
383
+ const decodedOutput = tx.output ? decodeDeploymentOutput(tx.output) : null
384
+
385
+ if (!decodedInput || !decodedOutput) continue
386
+
387
+ // Get block timestamp for this transaction
388
+ const blockTimestamp = blockTimestamps.get(tx.block_number) || 0
389
+
390
+ deployments.push({
391
+ profileAddress: decodedOutput.profileAddress,
392
+ urdAddress: decodedOutput.urdAddress,
393
+ deployerAddress: tx.from as Address, // The controller that deployed this profile
394
+ salt: decodedInput.salt,
395
+ primaryImplementation: decodedInput.primaryImplementation,
396
+ secondaryImplementation: decodedInput.secondaryImplementation,
397
+ postDeploymentModule: decodedInput.postDeploymentModule,
398
+ initializationCalldata: decodedInput.initializationCalldata,
399
+ deploymentCalldata: tx.input, // Full calldata to deployERC1167Proxies
400
+ transactionHash: tx.hash,
401
+ blockNumber: BigInt(tx.block_number),
402
+ blockTimestamp, // Unix timestamp of the block
403
+ chainId,
404
+ })
405
+ }
406
+
407
+ return deployments
408
+ }
409
+
410
+ /**
411
+ * Note: Hypersync querying should be done server-side to protect HYPERSYNC_TOKEN.
412
+ * Use the wallet-app API endpoint /api/lsp23Deployments instead of calling Hypersync directly.
413
+ *
414
+ * This module exports:
415
+ * - buildLSP23DeploymentQuery() - Build the Hypersync query structure
416
+ * - parseLSP23DeploymentsFromHypersync() - Parse Hypersync response
417
+ * - decodeDeploymentCalldata() - Decode LSP23 transaction input
418
+ * - decodeDeploymentOutput() - Decode LSP23 transaction output
419
+ */