@exponent-labs/exponent-vaults-fetcher 0.9.0

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.
@@ -0,0 +1,33 @@
1
+ import { web3 } from "@coral-xyz/anchor"
2
+
3
+ import { PROGRAM_ID } from "@exponent-labs/exponent-vaults-idl"
4
+
5
+ const EXPONENT_PRICES_SEED = new TextEncoder().encode("exponent_prices")
6
+
7
+ /** Seed used for withdrawal PDA derivation on-chain. */
8
+ export const WITHDRAWAL_ACCOUNT_SEED = "withdrawal" as const
9
+
10
+ /**
11
+ * Set of vault addresses to ignore during fetching.
12
+ * These accounts cannot be deserialized due to program updates and should be skipped.
13
+ */
14
+ export const IGNORED_VAULTS = new Set([
15
+ "Fo7vvPsSqNAYy2S7CpQoVFP8Ygnr22xB3fJVhde9GjcB",
16
+ "7apbV7QJHFhh5Ueczg35n6StyNdqeMmDdRwKDNCSRm7i",
17
+ "GsT257ZWjLboYAbWE1qDX2irHSyvCviQatyu3g2DSqmb",
18
+ "3KUQ8H5v4pLthqCsdPZuMnwjHVJcdkiFeWZGdX3Mr7MX",
19
+ "79aYbxCzLNFiF3tibivW6kva8B5qcFUAPAGweDuAyfMY",
20
+ "99RpUEbUMe1chVgpJNV16iXaJqdfvm459siFX9SA75nq",
21
+ "E957xxdpgsCbmqHo9AyHkLRTvgppxgQbZ9V8QXpQegg5",
22
+ "F9Tv2qBbS1YnsR4hYyaq6ztPUcTpht1ZT5Gb22DFwyg1",
23
+ "FKif7QNqt6cSaFj9cGL8eCz9cuSncKvbVNHxzd92r2hY",
24
+ "GfT6RcToh5mmkJ5DpPSBsBSJzun2WyVcsWXzNCtEfVUa",
25
+ "HUXvqaGbjX8kZ65ZhMhJgzfrjEX5By2gA8x9Z9mEFfQH",
26
+ "Hyi6LoTzcHpaQueSz4qqFdWeBydihpkRR8CqFvLWspqH",
27
+ ])
28
+
29
+ /** Singleton ExponentPrices account address */
30
+ export const EXPONENT_PRICES_ADDRESS = web3.PublicKey.findProgramAddressSync(
31
+ [EXPONENT_PRICES_SEED],
32
+ new web3.PublicKey(PROGRAM_ID),
33
+ )[0].toBase58()
package/src/fetcher.ts ADDED
@@ -0,0 +1,436 @@
1
+ import { AnchorProvider, Program, Wallet, web3 } from "@coral-xyz/anchor"
2
+
3
+ import { ExponentVaults, IDL, PROGRAM_ID } from "@exponent-labs/exponent-vaults-idl"
4
+ import { ExponentVaultsPDA } from "@exponent-labs/exponent-vaults-pda"
5
+
6
+ import { IGNORED_VAULTS } from "./constants"
7
+ import { transformAnchorData } from "./typeUtils"
8
+ import { ActionProposal, ExponentPrices, ExponentVault, WithdrawalAccount } from "./types"
9
+
10
+ const EXPONENT_PRICES_META_DISCRIMINATOR = Buffer.from([188, 97, 196, 52, 192, 41, 211, 196])
11
+ const MAX_PRICES = 32
12
+ const MAX_PRICE_MANAGERS = 8
13
+ const PRICE_NODE_REGISTERS = 4
14
+ const PRICE_USAGE_AMOUNT_REGISTER = 1
15
+
16
+ const PUBKEY_BYTES = 32
17
+ const U32_BYTES = 4
18
+ const U64_BYTES = 8
19
+ const ZERO_PUBKEY = new web3.PublicKey(new Uint8Array(PUBKEY_BYTES))
20
+
21
+ const EXPONENT_PRICE_SIZE = 224
22
+ const PRICE_NODE_SIZE = EXPONENT_PRICE_SIZE
23
+ const NODE_REGISTERS_SIZE = PRICE_NODE_REGISTERS * U32_BYTES
24
+ const PRICE_NODE_WRAPPED_SIZE = NODE_REGISTERS_SIZE + PRICE_NODE_SIZE
25
+
26
+ const EXPONENT_PRICES_MIN_SIZE =
27
+ 8 + // discriminator
28
+ 4 + // price_managers_len
29
+ 4 + // padding
30
+ MAX_PRICE_MANAGERS * PUBKEY_BYTES + // price_managers
31
+ 8 + // allocator.size
32
+ 4 + // allocator.bump_index
33
+ 4 + // allocator.free_list_head
34
+ MAX_PRICES * PRICE_NODE_WRAPPED_SIZE + // allocator nodes
35
+ 128 // prices_options
36
+
37
+ class MockWallet implements Wallet {
38
+ constructor(readonly payer: web3.Keypair) {
39
+ this.payer = payer
40
+ }
41
+ async signTransaction<T extends web3.Transaction | web3.VersionedTransaction>(tx: T): Promise<T> {
42
+ return tx
43
+ }
44
+ async signAllTransactions<T extends web3.Transaction | web3.VersionedTransaction>(txs: T[]): Promise<T[]> {
45
+ return txs
46
+ }
47
+ get publicKey(): web3.PublicKey {
48
+ return this.payer.publicKey
49
+ }
50
+ }
51
+
52
+ export class ExponentVaultsFetcher {
53
+ public readonly program: Program<ExponentVaults>
54
+ public readonly programId: web3.PublicKey
55
+
56
+ constructor(connection: web3.Connection, programId: web3.PublicKey | string = PROGRAM_ID) {
57
+ this.programId = new web3.PublicKey(programId)
58
+ const provider = new AnchorProvider(connection, new MockWallet(web3.Keypair.generate()))
59
+ const programIdl = {
60
+ ...(IDL as ExponentVaults),
61
+ address: this.programId.toBase58(),
62
+ } as ExponentVaults
63
+
64
+ this.program = new Program<ExponentVaults>(programIdl, provider)
65
+ }
66
+
67
+ fetchVault(address: web3.PublicKey) {
68
+ return this.program.account.exponentStrategyVault.fetch(address)
69
+ }
70
+
71
+ fetchWithdrawalAccount(address: web3.PublicKey) {
72
+ return this.program.account.withdrawalAccount.fetch(address)
73
+ }
74
+
75
+ fetchActionProposal(address: web3.PublicKey) {
76
+ return this.program.account.actionProposal.fetch(address)
77
+ }
78
+
79
+ async fetchAllVaults(filters?: web3.GetProgramAccountsFilter[]) {
80
+ const discriminator = this.program.account.exponentStrategyVault.coder.accounts.memcmp("exponentStrategyVault")
81
+ const baseFilters: web3.GetProgramAccountsFilter[] = discriminator
82
+ ? [{ memcmp: { offset: discriminator.offset, bytes: discriminator.bytes } }]
83
+ : []
84
+ const allFilters = filters ? [...baseFilters, ...filters] : baseFilters
85
+
86
+ const rawAccounts = await this.program.provider.connection.getProgramAccounts(this.programId, {
87
+ filters: allFilters,
88
+ })
89
+
90
+ const decoded: { publicKey: web3.PublicKey; account: any }[] = []
91
+ for (const { pubkey, account } of rawAccounts) {
92
+ if (IGNORED_VAULTS.has(pubkey.toBase58())) continue
93
+ // Guard against old/mismatched vault layouts: Borsh may read garbage vec lengths
94
+ // from misaligned data, causing uncatchable OOM via massive array allocation.
95
+ // The first vec (token_entries) is at byte offset 104 (8 discriminator + 3×32 pubkeys).
96
+ // If its u32 length is unreasonably large, the layout is wrong — skip to prevent OOM.
97
+ const TOKEN_ENTRIES_VEC_OFFSET = 104
98
+ if (account.data.length < TOKEN_ENTRIES_VEC_OFFSET + 4) continue
99
+ const tokenEntriesLen = account.data.readUInt32LE(TOKEN_ENTRIES_VEC_OFFSET)
100
+ if (tokenEntriesLen > 64) continue
101
+ try {
102
+ const data = this.program.account.exponentStrategyVault.coder.accounts.decode(
103
+ "exponentStrategyVault",
104
+ account.data,
105
+ )
106
+ decoded.push({ publicKey: pubkey, account: data })
107
+ } catch {
108
+ // skip accounts that fail to deserialize
109
+ }
110
+ }
111
+ return decoded
112
+ }
113
+
114
+ async fetchAllActionProposals(filters?: web3.GetProgramAccountsFilter[]) {
115
+ const discriminator = this.program.account.actionProposal.coder.accounts.memcmp("actionProposal")
116
+ const baseFilters: web3.GetProgramAccountsFilter[] = discriminator
117
+ ? [{ memcmp: { offset: discriminator.offset, bytes: discriminator.bytes } }]
118
+ : []
119
+ const allFilters = filters ? [...baseFilters, ...filters] : baseFilters
120
+
121
+ const rawAccounts = await this.program.provider.connection.getProgramAccounts(this.programId, {
122
+ filters: allFilters,
123
+ })
124
+
125
+ const decoded: { publicKey: web3.PublicKey; account: any }[] = []
126
+ for (const { pubkey, account } of rawAccounts) {
127
+ try {
128
+ const data = this.program.account.actionProposal.coder.accounts.decode("actionProposal", account.data)
129
+ decoded.push({ publicKey: pubkey, account: data })
130
+ } catch {
131
+ // skip accounts that fail to deserialize
132
+ }
133
+ }
134
+
135
+ return decoded
136
+ }
137
+
138
+ fetchAllWithdrawalAccounts(filters?: web3.GetProgramAccountsFilter[]) {
139
+ return this.program.account.withdrawalAccount.all(filters)
140
+ }
141
+
142
+ async fetchAllActionProposalsForVault(vaultAddress: web3.PublicKey, filters?: web3.GetProgramAccountsFilter[]) {
143
+ const vaultFilter: web3.GetProgramAccountsFilter = {
144
+ memcmp: {
145
+ offset: 8, // discriminator
146
+ bytes: vaultAddress.toBase58(),
147
+ },
148
+ }
149
+
150
+ return this.fetchAllActionProposals(filters ? [vaultFilter, ...filters] : [vaultFilter])
151
+ }
152
+
153
+ async fetchVaultDeserialized(address: web3.PublicKey): Promise<ExponentVault> {
154
+ if (IGNORED_VAULTS.has(address.toBase58())) {
155
+ throw new Error(`Vault ${address.toBase58()} is in the ignore list and cannot be fetched`)
156
+ }
157
+ const vault = await this.fetchVault(address)
158
+ return transformAnchorData(vault) as ExponentVault
159
+ }
160
+
161
+ async fetchActionProposalDeserialized(address: web3.PublicKey): Promise<ActionProposal> {
162
+ const proposal = await this.fetchActionProposal(address)
163
+ return transformAnchorData(proposal) as unknown as ActionProposal
164
+ }
165
+
166
+ async fetchWithdrawalAccountDeserialized(address: web3.PublicKey): Promise<WithdrawalAccount> {
167
+ const withdrawal = await this.fetchWithdrawalAccount(address)
168
+ return transformAnchorData(withdrawal)
169
+ }
170
+
171
+ async fetchAllVaultsDeserialized(filters?: web3.GetProgramAccountsFilter[]): Promise<
172
+ {
173
+ publicKey: web3.PublicKey
174
+ account: ExponentVault
175
+ }[]
176
+ > {
177
+ const accounts = await this.fetchAllVaults(filters)
178
+ return accounts.map(({ publicKey, account }) => ({
179
+ publicKey,
180
+ account: transformAnchorData(account) as ExponentVault,
181
+ }))
182
+ }
183
+
184
+ async fetchAllActionProposalsDeserialized(filters?: web3.GetProgramAccountsFilter[]): Promise<
185
+ {
186
+ publicKey: web3.PublicKey
187
+ account: ActionProposal
188
+ }[]
189
+ > {
190
+ const accounts = await this.fetchAllActionProposals(filters)
191
+ return accounts.map(({ publicKey, account }) => ({
192
+ publicKey,
193
+ account: transformAnchorData(account) as unknown as ActionProposal,
194
+ }))
195
+ }
196
+
197
+ async fetchAllActionProposalsForVaultDeserialized(
198
+ vaultAddress: web3.PublicKey,
199
+ filters?: web3.GetProgramAccountsFilter[],
200
+ ): Promise<
201
+ {
202
+ publicKey: web3.PublicKey
203
+ account: ActionProposal
204
+ }[]
205
+ > {
206
+ const accounts = await this.fetchAllActionProposalsForVault(vaultAddress, filters)
207
+ return accounts.map(({ publicKey, account }) => ({
208
+ publicKey,
209
+ account: transformAnchorData(account) as unknown as ActionProposal,
210
+ }))
211
+ }
212
+
213
+ async fetchAllWithdrawalAccountsDeserialized(filters?: web3.GetProgramAccountsFilter[]) {
214
+ const accounts = await this.fetchAllWithdrawalAccounts(filters)
215
+ return accounts.map(({ publicKey, account }) => ({
216
+ publicKey,
217
+ account: transformAnchorData(account),
218
+ }))
219
+ }
220
+
221
+ /**
222
+ * Fetch the singleton ExponentPrices account.
223
+ */
224
+ async fetchExponentPrices(): Promise<ExponentPrices> {
225
+ const address = new ExponentVaultsPDA(this.programId).exponentPrices()[0]
226
+ const accountInfo = await this.program.provider.connection.getAccountInfo(address)
227
+
228
+ if (!accountInfo) {
229
+ throw new Error(`ExponentPrices account not found: ${address.toBase58()}`)
230
+ }
231
+ if (!accountInfo.owner.equals(this.programId)) {
232
+ throw new Error(
233
+ `ExponentPrices owner mismatch: expected ${this.programId.toBase58()}, got ${accountInfo.owner.toBase58()}`,
234
+ )
235
+ }
236
+
237
+ const data = accountInfo.data
238
+ if (data.length < EXPONENT_PRICES_MIN_SIZE) {
239
+ throw new Error(
240
+ `ExponentPrices account too small: got ${data.length} bytes, expected at least ${EXPONENT_PRICES_MIN_SIZE}`,
241
+ )
242
+ }
243
+
244
+ const discriminator = data.subarray(0, 8)
245
+ if (!discriminator.equals(EXPONENT_PRICES_META_DISCRIMINATOR)) {
246
+ throw new Error("Invalid ExponentPrices discriminator")
247
+ }
248
+
249
+ let offset = 8
250
+
251
+ const readU32 = () => {
252
+ const value = data.readUInt32LE(offset)
253
+ offset += U32_BYTES
254
+ return value
255
+ }
256
+ const readU64 = () => {
257
+ const value = data.readBigUInt64LE(offset)
258
+ offset += U64_BYTES
259
+ return value
260
+ }
261
+
262
+ const managersLen = readU32()
263
+ offset += U32_BYTES // _padding0
264
+ if (managersLen > MAX_PRICE_MANAGERS) {
265
+ throw new Error(`Invalid ExponentPrices managers length: ${managersLen}`)
266
+ }
267
+ const managers: web3.PublicKey[] = []
268
+ for (let i = 0; i < managersLen; i++) {
269
+ const managerOffset = offset + i * PUBKEY_BYTES
270
+ managers.push(new web3.PublicKey(data.subarray(managerOffset, managerOffset + PUBKEY_BYTES)))
271
+ }
272
+ offset += MAX_PRICE_MANAGERS * PUBKEY_BYTES // price_managers
273
+
274
+ const allocatorSize = Number(readU64())
275
+ const bumpIndex = readU32()
276
+ const freeListHead = readU32()
277
+ if (allocatorSize > MAX_PRICES) {
278
+ throw new Error(`Invalid ExponentPrices allocator size: ${allocatorSize}`)
279
+ }
280
+ if (bumpIndex > MAX_PRICES + 1) {
281
+ throw new Error(`Invalid ExponentPrices bump index: ${bumpIndex}`)
282
+ }
283
+ if (freeListHead > MAX_PRICES + 1) {
284
+ throw new Error(`Invalid ExponentPrices free list head: ${freeListHead}`)
285
+ }
286
+
287
+ const nodesOffset = offset
288
+
289
+ const readNodeAt = (index: number) => {
290
+ if (index < 1 || index > MAX_PRICES) {
291
+ throw new Error(`Invalid ExponentPrices node index: ${index}`)
292
+ }
293
+ const nodeOffset = nodesOffset + (index - 1) * PRICE_NODE_WRAPPED_SIZE
294
+
295
+ const usageAmount = data.readUInt32LE(nodeOffset + PRICE_USAGE_AMOUNT_REGISTER * U32_BYTES)
296
+ const valueOffset = nodeOffset + NODE_REGISTERS_SIZE
297
+
298
+ const priceMintBytes = data.subarray(valueOffset, valueOffset + PUBKEY_BYTES)
299
+ const priceMint = new web3.PublicKey(priceMintBytes)
300
+ const active = priceMintBytes.some((b: number) => b !== 0)
301
+ const underlyingMint = new web3.PublicKey(
302
+ data.subarray(valueOffset + PUBKEY_BYTES, valueOffset + PUBKEY_BYTES * 2),
303
+ )
304
+
305
+ const priceOffset = valueOffset + PUBKEY_BYTES * 2
306
+ const n0 = data.readBigUInt64LE(priceOffset)
307
+ const n1 = data.readBigUInt64LE(priceOffset + 8)
308
+ const n2 = data.readBigUInt64LE(priceOffset + 16)
309
+ const n3 = data.readBigUInt64LE(priceOffset + 24)
310
+
311
+ const positionsAmount = data.readBigUInt64LE(valueOffset + 96)
312
+ const lastUpdatedAt = data.readBigUInt64LE(valueOffset + 104)
313
+ const lastUpdatedSlot = data.readBigUInt64LE(valueOffset + 112)
314
+ const priceType = data.readUInt8(valueOffset + 120)
315
+ const impliedApyBps = priceType === 3 ? data.readUInt32LE(valueOffset + 121) : null
316
+ const priceInterfaceAccounts = new web3.PublicKey(
317
+ data.subarray(valueOffset + 128, valueOffset + 128 + PUBKEY_BYTES),
318
+ )
319
+
320
+ return {
321
+ active,
322
+ usageAmount,
323
+ price: {
324
+ priceId: BigInt(index),
325
+ priceMint,
326
+ underlyingMint,
327
+ price: [[n0, n1, n2, n3]],
328
+ positionsAmount,
329
+ lastUpdatedAt,
330
+ lastUpdatedSlot,
331
+ priceType,
332
+ impliedApyBps,
333
+ impliedApy: impliedApyBps === null ? null : impliedApyBps / 1_000_000,
334
+ priceInterfaceAccounts,
335
+ interfaceAccounts: [],
336
+ },
337
+ }
338
+ }
339
+
340
+ const prices: ExponentPrices["prices"] = Array.from({ length: MAX_PRICES + 1 }, () => null)
341
+ let activePrices = 0
342
+ for (let nodeIndex = 1; nodeIndex <= MAX_PRICES; nodeIndex++) {
343
+ const parsed = readNodeAt(nodeIndex)
344
+ if (parsed.active) {
345
+ prices[nodeIndex] = parsed.price
346
+ activePrices += 1
347
+ }
348
+ }
349
+
350
+ if (activePrices !== allocatorSize) {
351
+ throw new Error(
352
+ `Corrupted ExponentPrices allocator: parsed ${activePrices} active entries, allocator has ${allocatorSize}`,
353
+ )
354
+ }
355
+
356
+ const activePriceEntries = prices.filter((entry) => entry !== null) as NonNullable<
357
+ ExponentPrices["prices"][number]
358
+ >[]
359
+ const uniqueInterfaceAccounts = [
360
+ ...new Map(
361
+ activePriceEntries.map((entry) => [entry.priceInterfaceAccounts.toBase58(), entry.priceInterfaceAccounts]),
362
+ ).values(),
363
+ ].filter((pubkey) => !pubkey.equals(ZERO_PUBKEY))
364
+
365
+ if (uniqueInterfaceAccounts.length > 0) {
366
+ const infos = await this.program.provider.connection.getMultipleAccountsInfo(uniqueInterfaceAccounts)
367
+ const decodedInterfaceAccounts = new Map<string, web3.PublicKey[]>()
368
+
369
+ for (let i = 0; i < uniqueInterfaceAccounts.length; i++) {
370
+ const key = uniqueInterfaceAccounts[i]
371
+ const info = infos[i]
372
+ if (!info) {
373
+ throw new Error(`PriceInterfaceAccounts account not found: ${key.toBase58()}`)
374
+ }
375
+ if (!info.owner.equals(this.programId)) {
376
+ throw new Error(
377
+ `PriceInterfaceAccounts owner mismatch: expected ${this.programId.toBase58()}, got ${info.owner.toBase58()}`,
378
+ )
379
+ }
380
+ decodedInterfaceAccounts.set(key.toBase58(), decodePriceInterfaceAccounts(info.data))
381
+ }
382
+
383
+ for (const entry of activePriceEntries) {
384
+ entry.interfaceAccounts = decodedInterfaceAccounts.get(entry.priceInterfaceAccounts.toBase58()) ?? []
385
+ }
386
+ }
387
+
388
+ return { managers, prices }
389
+ }
390
+
391
+ /**
392
+ * Get all unique price IDs required by a vault's token account positions.
393
+ * Returns a set of price IDs that should exist in an ExponentPrices account.
394
+ */
395
+ getRequiredPriceIds(vault: ExponentVault): Set<bigint> {
396
+ const priceIds = new Set<bigint>()
397
+
398
+ for (const position of vault.strategyPositions) {
399
+ if ("tokenAccount" in position) {
400
+ const entry = position.tokenAccount[0]
401
+ for (const balance of entry.balances) {
402
+ if ("simple" in balance.priceId) {
403
+ priceIds.add(balance.priceId.simple.priceId)
404
+ } else if ("multiply" in balance.priceId) {
405
+ for (const id of balance.priceId.multiply.priceIds) {
406
+ priceIds.add(id)
407
+ }
408
+ }
409
+ }
410
+ }
411
+ }
412
+
413
+ return priceIds
414
+ }
415
+ }
416
+
417
+ function decodePriceInterfaceAccounts(data: Buffer): web3.PublicKey[] {
418
+ if (data.length < 12) {
419
+ throw new Error(`PriceInterfaceAccounts account too small: ${data.length} bytes`)
420
+ }
421
+
422
+ const len = data.readUInt32LE(8)
423
+ const expectedSize = 8 + 4 + len * PUBKEY_BYTES
424
+ if (data.length < expectedSize) {
425
+ throw new Error(`PriceInterfaceAccounts account truncated: expected ${expectedSize} bytes, got ${data.length}`)
426
+ }
427
+
428
+ const interfaceAccounts: web3.PublicKey[] = []
429
+ let offset = 12
430
+ for (let i = 0; i < len; i++) {
431
+ interfaceAccounts.push(new web3.PublicKey(data.subarray(offset, offset + PUBKEY_BYTES)))
432
+ offset += PUBKEY_BYTES
433
+ }
434
+
435
+ return interfaceAccounts
436
+ }
package/src/index.ts ADDED
@@ -0,0 +1,4 @@
1
+ export { ExponentVaultsFetcher } from "./fetcher"
2
+ export * from "./types"
3
+ export * from "./constants"
4
+ export * from './typeUtils'
@@ -0,0 +1,108 @@
1
+ import { BN, web3 } from "@coral-xyz/anchor"
2
+
3
+ // Extract enum discriminant keys
4
+ type AnchorEnumKey<T> =
5
+ T extends Record<string, any>
6
+ ? {
7
+ [K in keyof T]: T[K] extends Record<string, never> ? K : never
8
+ }[keyof T]
9
+ : never
10
+
11
+ // Check if type is an Anchor enum
12
+ type IsAnchorEnum<T> =
13
+ T extends Record<string, any>
14
+ ? {
15
+ [K in keyof T]: T[K] extends Record<string, never> ? true : false
16
+ }[keyof T] extends true
17
+ ? true
18
+ : false
19
+ : false
20
+
21
+ // Check if type is a PublicKey (has toBase58, equals, etc.)
22
+ type IsPublicKey<T> = T extends { toBase58: any; equals: any; toBuffer: any } ? true : false
23
+
24
+ // Main recursive transformation with PublicKey preservation
25
+ type Transform<T> =
26
+ // Preserve PublicKey as-is
27
+ IsPublicKey<T> extends true
28
+ ? web3.PublicKey
29
+ : T extends BN
30
+ ? bigint
31
+ : T extends Array<infer U>
32
+ ? Array<Transform<U>>
33
+ : T extends Map<infer K, infer V>
34
+ ? Map<Transform<K>, Transform<V>>
35
+ : T extends Set<infer U>
36
+ ? Set<Transform<U>>
37
+ : T extends Uint8Array
38
+ ? Uint8Array
39
+ : T extends Date
40
+ ? Date
41
+ : T extends object
42
+ ? IsAnchorEnum<T> extends true
43
+ ? AnchorEnumKey<T>
44
+ : {
45
+ [K in keyof T]: Transform<T[K]>
46
+ }
47
+ : T
48
+
49
+ // Export the main utility
50
+ export type TransformedAnchorType<T> = Transform<T>
51
+
52
+ // Helper function to convert at runtime
53
+ export function transformAnchorData<T>(data: T): TransformedAnchorType<T> {
54
+ // Preserve PublicKey instances
55
+ if (data instanceof web3.PublicKey) {
56
+ return data as any
57
+ }
58
+
59
+ if (data instanceof BN) {
60
+ return BigInt(data.toString()) as any
61
+ }
62
+
63
+ if (data instanceof Date) {
64
+ return data as any
65
+ }
66
+
67
+ if (data instanceof Uint8Array) {
68
+ return data as any
69
+ }
70
+
71
+ if (Array.isArray(data)) {
72
+ return data.map(transformAnchorData) as any
73
+ }
74
+
75
+ if (data instanceof Map) {
76
+ const result = new Map()
77
+ data.forEach((value, key) => {
78
+ result.set(transformAnchorData(key), transformAnchorData(value))
79
+ })
80
+ return result as any
81
+ }
82
+
83
+ if (data instanceof Set) {
84
+ return new Set([...data].map(transformAnchorData)) as any
85
+ }
86
+
87
+ if (data && typeof data === "object") {
88
+ // Check if it's an Anchor enum
89
+ const keys = Object.keys(data)
90
+ if (keys.length === 1) {
91
+ const value = data[keys[0] as keyof typeof data]
92
+ if (value && typeof value === "object" && Object.keys(value).length === 0) {
93
+ return keys[0] as any
94
+ }
95
+ }
96
+
97
+ // Regular object - recursively transform
98
+ const result: any = {}
99
+ for (const key in data) {
100
+ if (Object.prototype.hasOwnProperty.call(data, key)) {
101
+ result[key] = transformAnchorData(data[key])
102
+ }
103
+ }
104
+ return result
105
+ }
106
+
107
+ return data as any
108
+ }