@exponent-labs/exponent-vaults-fetcher 0.9.25 → 0.9.26
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/build/borshPreflight.d.ts +3 -0
- package/build/borshPreflight.js +199 -0
- package/build/borshPreflight.js.map +1 -0
- package/build/economicsAccounts.d.ts +9 -0
- package/build/economicsAccounts.js +66 -0
- package/build/economicsAccounts.js.map +1 -0
- package/build/economicsAccounts.test.d.ts +1 -0
- package/build/economicsAccounts.test.js +118 -0
- package/build/economicsAccounts.test.js.map +1 -0
- package/build/fetcher.js +29 -14
- package/build/fetcher.js.map +1 -1
- package/build/fetcher.test.js +183 -17
- package/build/fetcher.test.js.map +1 -1
- package/build/priceInterfaceAccounts.d.ts +0 -2
- package/build/priceInterfaceAccounts.js +1 -2
- package/build/priceInterfaceAccounts.js.map +1 -1
- package/build/typeUtils.d.ts +0 -1
- package/build/typeUtils.js +1 -2
- package/build/typeUtils.js.map +1 -1
- package/package.json +5 -4
- package/src/fetcher.test.ts +303 -19
- package/src/fetcher.ts +35 -22
package/src/fetcher.test.ts
CHANGED
|
@@ -2,9 +2,12 @@ import { web3 } from "@coral-xyz/anchor"
|
|
|
2
2
|
import assert from "node:assert/strict"
|
|
3
3
|
import test from "node:test"
|
|
4
4
|
|
|
5
|
+
import { ExponentVaultsPDA } from "@exponent-labs/exponent-vaults-pda"
|
|
6
|
+
|
|
5
7
|
import { ExponentVaultsFetcher } from "./fetcher"
|
|
6
8
|
|
|
7
9
|
const EXPONENT_PRICES_DISCRIMINATOR = Buffer.from([188, 97, 196, 52, 192, 41, 211, 196])
|
|
10
|
+
const PRICE_INTERFACE_ACCOUNTS_DISCRIMINATOR = Buffer.from([166, 196, 173, 204, 35, 6, 104, 230])
|
|
8
11
|
const PROGRAM_ID = new web3.PublicKey(Uint8Array.from({ length: 32 }, (_, index) => index + 1))
|
|
9
12
|
const PRICE_MINT = new web3.PublicKey(Uint8Array.from({ length: 32 }, (_, index) => index + 33))
|
|
10
13
|
|
|
@@ -21,36 +24,144 @@ const EXPONENT_PRICES_SIZE = PRICE_NODES_OFFSET + MAX_PRICES * PRICE_NODE_SIZE +
|
|
|
21
24
|
const PRICE_DECIMALS = 6
|
|
22
25
|
const UNDERLYING_DECIMALS = 9
|
|
23
26
|
const SY_PRICE_ID = 0x0807060504030201n
|
|
27
|
+
const ZERO_PUBKEY = new web3.PublicKey(new Uint8Array(PUBKEY_BYTES))
|
|
28
|
+
|
|
29
|
+
type ScopeConfigFixture = {
|
|
30
|
+
maximumAgeSeconds: bigint
|
|
31
|
+
priceChain: readonly [number, number, number, number]
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
type AccountFixture = {
|
|
35
|
+
data: Buffer
|
|
36
|
+
owner?: web3.PublicKey
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
type ConnectionOptions = {
|
|
40
|
+
accounts?: ReadonlyMap<string, AccountFixture | null>
|
|
41
|
+
rpcError?: Error
|
|
42
|
+
shortResponse?: boolean
|
|
43
|
+
}
|
|
24
44
|
|
|
25
|
-
function
|
|
45
|
+
function createPublicKey(seed: number): web3.PublicKey {
|
|
46
|
+
const bytes = new Uint8Array(PUBKEY_BYTES).fill(255)
|
|
47
|
+
new DataView(bytes.buffer).setUint32(0, seed, true)
|
|
48
|
+
return new web3.PublicKey(bytes)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function createExponentPricesData(priceInterfaceAccounts: readonly web3.PublicKey[], reservedByte = 0): Buffer {
|
|
26
52
|
const data = Buffer.alloc(EXPONENT_PRICES_SIZE)
|
|
27
53
|
EXPONENT_PRICES_DISCRIMINATOR.copy(data)
|
|
28
|
-
data.writeBigUInt64LE(
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
54
|
+
data.writeBigUInt64LE(BigInt(priceInterfaceAccounts.length), 8 + 8 + MAX_PRICE_MANAGERS * PUBKEY_BYTES)
|
|
55
|
+
|
|
56
|
+
for (const [index, priceInterfaceAccount] of priceInterfaceAccounts.entries()) {
|
|
57
|
+
const valueOffset = FIRST_PRICE_VALUE_OFFSET + index * PRICE_NODE_SIZE
|
|
58
|
+
PRICE_MINT.toBuffer().copy(data, valueOffset)
|
|
59
|
+
priceInterfaceAccount.toBuffer().copy(data, valueOffset + 128)
|
|
60
|
+
data.writeUInt8(PRICE_DECIMALS, valueOffset + 160)
|
|
61
|
+
data.writeUInt8(UNDERLYING_DECIMALS, valueOffset + 161)
|
|
62
|
+
data.fill(reservedByte, valueOffset + 162, valueOffset + 168)
|
|
63
|
+
data.writeBigUInt64LE(SY_PRICE_ID, valueOffset + 168)
|
|
64
|
+
data.fill(reservedByte, valueOffset + 176, valueOffset + EXPONENT_PRICE_SIZE)
|
|
65
|
+
}
|
|
66
|
+
|
|
36
67
|
return data
|
|
37
68
|
}
|
|
38
69
|
|
|
39
|
-
function
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
70
|
+
function createPriceInterfaceAccountsData(
|
|
71
|
+
interfaceAccounts: readonly web3.PublicKey[],
|
|
72
|
+
scopeConfig?: ScopeConfigFixture,
|
|
73
|
+
): Buffer {
|
|
74
|
+
const data = Buffer.alloc(12 + interfaceAccounts.length * PUBKEY_BYTES + (scopeConfig ? 17 : 0))
|
|
75
|
+
PRICE_INTERFACE_ACCOUNTS_DISCRIMINATOR.copy(data)
|
|
76
|
+
data.writeUInt32LE(interfaceAccounts.length, 8)
|
|
77
|
+
interfaceAccounts.forEach((account, index) => account.toBuffer().copy(data, 12 + index * PUBKEY_BYTES))
|
|
78
|
+
|
|
79
|
+
if (scopeConfig) {
|
|
80
|
+
const stateOffset = 12 + interfaceAccounts.length * PUBKEY_BYTES
|
|
81
|
+
data.writeUInt8(2, stateOffset)
|
|
82
|
+
scopeConfig.priceChain.forEach((price, index) => data.writeUInt16LE(price, stateOffset + 1 + index * 2))
|
|
83
|
+
data.writeBigUInt64LE(scopeConfig.maximumAgeSeconds, stateOffset + 9)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return data
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function createConnection(registryData: Buffer, options: ConnectionOptions = {}) {
|
|
90
|
+
const registryAddress = new ExponentVaultsPDA(PROGRAM_ID).exponentPrices()[0]
|
|
91
|
+
const singleAccountReads: web3.PublicKey[] = []
|
|
92
|
+
const multipleAccountReads: web3.PublicKey[][] = []
|
|
93
|
+
let activeMultipleAccountReads = 0
|
|
94
|
+
let maxConcurrentMultipleAccountReads = 0
|
|
95
|
+
|
|
96
|
+
const connection = {
|
|
97
|
+
async getAccountInfo(address: web3.PublicKey) {
|
|
98
|
+
singleAccountReads.push(address)
|
|
99
|
+
if (!address.equals(registryAddress)) {
|
|
100
|
+
throw new Error(`Unexpected getAccountInfo for PriceInterfaceAccounts: ${address.toBase58()}`)
|
|
101
|
+
}
|
|
102
|
+
return {
|
|
103
|
+
data: registryData,
|
|
104
|
+
executable: false,
|
|
105
|
+
lamports: 1,
|
|
106
|
+
owner: PROGRAM_ID,
|
|
107
|
+
rentEpoch: 0,
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
async getMultipleAccountsInfo(addresses: web3.PublicKey[]) {
|
|
111
|
+
multipleAccountReads.push(addresses)
|
|
112
|
+
activeMultipleAccountReads += 1
|
|
113
|
+
maxConcurrentMultipleAccountReads = Math.max(maxConcurrentMultipleAccountReads, activeMultipleAccountReads)
|
|
114
|
+
try {
|
|
115
|
+
await Promise.resolve()
|
|
116
|
+
if (options.rpcError) throw options.rpcError
|
|
117
|
+
|
|
118
|
+
const infos = addresses.map((address) => {
|
|
119
|
+
const fixture = options.accounts?.get(address.toBase58())
|
|
120
|
+
if (fixture === null || !fixture) return null
|
|
121
|
+
return {
|
|
122
|
+
data: fixture.data,
|
|
123
|
+
executable: false,
|
|
124
|
+
lamports: 1,
|
|
125
|
+
owner: fixture.owner ?? PROGRAM_ID,
|
|
126
|
+
rentEpoch: 0,
|
|
127
|
+
}
|
|
128
|
+
})
|
|
129
|
+
return options.shortResponse ? infos.slice(0, -1) : infos
|
|
130
|
+
} finally {
|
|
131
|
+
activeMultipleAccountReads -= 1
|
|
132
|
+
}
|
|
133
|
+
},
|
|
48
134
|
} as unknown as web3.Connection
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
connection,
|
|
138
|
+
multipleAccountReads,
|
|
139
|
+
get maxConcurrentMultipleAccountReads() {
|
|
140
|
+
return maxConcurrentMultipleAccountReads
|
|
141
|
+
},
|
|
142
|
+
registryAddress,
|
|
143
|
+
singleAccountReads,
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function createAccountFixtures(keys: readonly web3.PublicKey[]): Map<string, AccountFixture> {
|
|
148
|
+
return new Map(
|
|
149
|
+
keys.map((key, index) => [
|
|
150
|
+
key.toBase58(),
|
|
151
|
+
{
|
|
152
|
+
data: createPriceInterfaceAccountsData([createPublicKey(128 + index)], {
|
|
153
|
+
maximumAgeSeconds: BigInt(index + 1),
|
|
154
|
+
priceChain: [index + 1, index + 2, index + 3, index + 4],
|
|
155
|
+
}),
|
|
156
|
+
},
|
|
157
|
+
]),
|
|
158
|
+
)
|
|
49
159
|
}
|
|
50
160
|
|
|
51
161
|
test("decodes price metadata from exact wire offsets without consuming reserved bytes", async () => {
|
|
52
162
|
for (const reservedByte of [0xa5, 0x5a]) {
|
|
53
|
-
const
|
|
163
|
+
const { connection } = createConnection(createExponentPricesData([ZERO_PUBKEY], reservedByte))
|
|
164
|
+
const fetcher = new ExponentVaultsFetcher(connection, PROGRAM_ID)
|
|
54
165
|
const prices = await fetcher.fetchExponentPrices()
|
|
55
166
|
const price = prices.prices[1]
|
|
56
167
|
|
|
@@ -60,3 +171,176 @@ test("decodes price metadata from exact wire offsets without consuming reserved
|
|
|
60
171
|
assert.equal(price.syPriceId, SY_PRICE_ID)
|
|
61
172
|
}
|
|
62
173
|
})
|
|
174
|
+
|
|
175
|
+
test("fetches unique price interface accounts sequentially in maximum 100-key batches", async () => {
|
|
176
|
+
const cases = [
|
|
177
|
+
{ count: 0, expectedBatchSizes: [] },
|
|
178
|
+
{ count: 1, expectedBatchSizes: [1] },
|
|
179
|
+
{ count: 99, expectedBatchSizes: [99] },
|
|
180
|
+
{ count: 100, expectedBatchSizes: [100] },
|
|
181
|
+
{ count: 101, expectedBatchSizes: [100, 1] },
|
|
182
|
+
{ count: 200, expectedBatchSizes: [100, 100] },
|
|
183
|
+
{ count: 201, expectedBatchSizes: [100, 100, 1] },
|
|
184
|
+
{ count: 1024, expectedBatchSizes: [100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 24] },
|
|
185
|
+
]
|
|
186
|
+
|
|
187
|
+
for (const { count, expectedBatchSizes } of cases) {
|
|
188
|
+
const keys = Array.from({ length: count }, (_, index) => createPublicKey(index + 1))
|
|
189
|
+
const connectionState = createConnection(createExponentPricesData(keys), { accounts: createAccountFixtures(keys) })
|
|
190
|
+
|
|
191
|
+
const prices = await new ExponentVaultsFetcher(connectionState.connection, PROGRAM_ID).fetchExponentPrices()
|
|
192
|
+
|
|
193
|
+
assert.deepEqual(
|
|
194
|
+
connectionState.multipleAccountReads.map((batch) => batch.length),
|
|
195
|
+
expectedBatchSizes,
|
|
196
|
+
`count ${count}`,
|
|
197
|
+
)
|
|
198
|
+
assert.ok(
|
|
199
|
+
connectionState.multipleAccountReads.every((batch) => batch.length <= 100),
|
|
200
|
+
`count ${count}`,
|
|
201
|
+
)
|
|
202
|
+
assert.equal(connectionState.maxConcurrentMultipleAccountReads, count === 0 ? 0 : 1, `count ${count}`)
|
|
203
|
+
assert.deepEqual(
|
|
204
|
+
connectionState.singleAccountReads.map((address) => address.toBase58()),
|
|
205
|
+
[connectionState.registryAddress.toBase58()],
|
|
206
|
+
`count ${count}`,
|
|
207
|
+
)
|
|
208
|
+
assert.deepEqual(
|
|
209
|
+
connectionState.multipleAccountReads.flat().map((address) => address.toBase58()),
|
|
210
|
+
keys.map((key) => key.toBase58()),
|
|
211
|
+
`count ${count}`,
|
|
212
|
+
)
|
|
213
|
+
for (const [index, key] of keys.entries()) {
|
|
214
|
+
const price = prices.prices[index + 1]
|
|
215
|
+
assert.ok(price, `count ${count}, price ${index + 1}`)
|
|
216
|
+
assert.deepEqual(price.interfaceAccounts, [createPublicKey(128 + index)])
|
|
217
|
+
assert.deepEqual(price.scopeConfig, {
|
|
218
|
+
maximumAgeSeconds: BigInt(index + 1),
|
|
219
|
+
priceChain: [index + 1, index + 2, index + 3, index + 4],
|
|
220
|
+
})
|
|
221
|
+
assert.equal(price.priceInterfaceAccounts.toBase58(), key.toBase58())
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
})
|
|
225
|
+
|
|
226
|
+
test("deduplicates nonzero price interface accounts and hydrates every matching price", async () => {
|
|
227
|
+
const firstKey = createPublicKey(250)
|
|
228
|
+
const secondKey = createPublicKey(1)
|
|
229
|
+
const keys = [firstKey, ZERO_PUBKEY, firstKey, secondKey]
|
|
230
|
+
assert.notDeepEqual([firstKey.toBase58(), secondKey.toBase58()], [firstKey.toBase58(), secondKey.toBase58()].sort())
|
|
231
|
+
const { connection, multipleAccountReads, registryAddress, singleAccountReads } = createConnection(
|
|
232
|
+
createExponentPricesData(keys),
|
|
233
|
+
{ accounts: createAccountFixtures([firstKey, secondKey]) },
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
const prices = await new ExponentVaultsFetcher(connection, PROGRAM_ID).fetchExponentPrices()
|
|
237
|
+
|
|
238
|
+
assert.deepEqual(
|
|
239
|
+
singleAccountReads.map((address) => address.toBase58()),
|
|
240
|
+
[registryAddress.toBase58()],
|
|
241
|
+
)
|
|
242
|
+
assert.deepEqual(
|
|
243
|
+
multipleAccountReads.map((batch) => batch.map((key) => key.toBase58())),
|
|
244
|
+
[[firstKey.toBase58(), secondKey.toBase58()]],
|
|
245
|
+
)
|
|
246
|
+
assert.deepEqual(prices.prices[1]?.interfaceAccounts, [createPublicKey(128)])
|
|
247
|
+
assert.deepEqual(prices.prices[2]?.interfaceAccounts, [])
|
|
248
|
+
assert.deepEqual(prices.prices[3]?.interfaceAccounts, [createPublicKey(128)])
|
|
249
|
+
assert.deepEqual(prices.prices[4]?.interfaceAccounts, [createPublicKey(129)])
|
|
250
|
+
assert.strictEqual(prices.prices[1]?.interfaceAccounts, prices.prices[3]?.interfaceAccounts)
|
|
251
|
+
assert.ok(prices.prices[1]?.scopeConfig)
|
|
252
|
+
assert.strictEqual(prices.prices[1]?.scopeConfig, prices.prices[3]?.scopeConfig)
|
|
253
|
+
})
|
|
254
|
+
|
|
255
|
+
test("preserves PriceInterfaceAccounts validation errors and malformed RPC response failures", async () => {
|
|
256
|
+
const key = createPublicKey(1)
|
|
257
|
+
const registryData = createExponentPricesData([key])
|
|
258
|
+
|
|
259
|
+
await assert.rejects(
|
|
260
|
+
() => new ExponentVaultsFetcher(createConnection(registryData).connection, PROGRAM_ID).fetchExponentPrices(),
|
|
261
|
+
new Error(`PriceInterfaceAccounts account not found: ${key.toBase58()}`),
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
await assert.rejects(
|
|
265
|
+
() =>
|
|
266
|
+
new ExponentVaultsFetcher(
|
|
267
|
+
createConnection(registryData, {
|
|
268
|
+
accounts: new Map([
|
|
269
|
+
[key.toBase58(), { data: createPriceInterfaceAccountsData([]), owner: createPublicKey(2) }],
|
|
270
|
+
]),
|
|
271
|
+
}).connection,
|
|
272
|
+
PROGRAM_ID,
|
|
273
|
+
).fetchExponentPrices(),
|
|
274
|
+
new Error(
|
|
275
|
+
`PriceInterfaceAccounts owner mismatch: expected ${PROGRAM_ID.toBase58()}, got ${createPublicKey(2).toBase58()}`,
|
|
276
|
+
),
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
await assert.rejects(
|
|
280
|
+
() =>
|
|
281
|
+
new ExponentVaultsFetcher(
|
|
282
|
+
createConnection(registryData, { accounts: createAccountFixtures([key]), shortResponse: true }).connection,
|
|
283
|
+
PROGRAM_ID,
|
|
284
|
+
).fetchExponentPrices(),
|
|
285
|
+
new Error("PriceInterfaceAccounts batch size mismatch: requested 1, got 0"),
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
const rpcError = new Error("RPC unavailable")
|
|
289
|
+
await assert.rejects(
|
|
290
|
+
() =>
|
|
291
|
+
new ExponentVaultsFetcher(
|
|
292
|
+
createConnection(registryData, { accounts: createAccountFixtures([key]), rpcError }).connection,
|
|
293
|
+
PROGRAM_ID,
|
|
294
|
+
).fetchExponentPrices(),
|
|
295
|
+
rpcError,
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
await assert.rejects(
|
|
299
|
+
() =>
|
|
300
|
+
new ExponentVaultsFetcher(
|
|
301
|
+
createConnection(registryData, {
|
|
302
|
+
accounts: new Map([[key.toBase58(), { data: Buffer.alloc(1) }]]),
|
|
303
|
+
}).connection,
|
|
304
|
+
PROGRAM_ID,
|
|
305
|
+
).fetchExponentPrices(),
|
|
306
|
+
new Error("PriceInterfaceAccounts account too small: 1 bytes"),
|
|
307
|
+
)
|
|
308
|
+
})
|
|
309
|
+
|
|
310
|
+
test("reports a missing account at its indexed position in the second batch", async () => {
|
|
311
|
+
const keys = Array.from({ length: 102 }, (_, index) => createPublicKey(index + 1))
|
|
312
|
+
const accounts = createAccountFixtures(keys)
|
|
313
|
+
const missingKey = keys[101]
|
|
314
|
+
accounts.set(missingKey.toBase58(), null)
|
|
315
|
+
|
|
316
|
+
await assert.rejects(
|
|
317
|
+
() =>
|
|
318
|
+
new ExponentVaultsFetcher(
|
|
319
|
+
createConnection(createExponentPricesData(keys), { accounts }).connection,
|
|
320
|
+
PROGRAM_ID,
|
|
321
|
+
).fetchExponentPrices(),
|
|
322
|
+
new Error(`PriceInterfaceAccounts account not found: ${missingKey.toBase58()}`),
|
|
323
|
+
)
|
|
324
|
+
})
|
|
325
|
+
|
|
326
|
+
test("reports an owner mismatch at its indexed position in a later batch", async () => {
|
|
327
|
+
const keys = Array.from({ length: 202 }, (_, index) => createPublicKey(index + 1))
|
|
328
|
+
const accounts = createAccountFixtures(keys)
|
|
329
|
+
const mismatchedKey = keys[201]
|
|
330
|
+
const unexpectedOwner = createPublicKey(250)
|
|
331
|
+
accounts.set(mismatchedKey.toBase58(), {
|
|
332
|
+
data: createPriceInterfaceAccountsData([]),
|
|
333
|
+
owner: unexpectedOwner,
|
|
334
|
+
})
|
|
335
|
+
|
|
336
|
+
await assert.rejects(
|
|
337
|
+
() =>
|
|
338
|
+
new ExponentVaultsFetcher(
|
|
339
|
+
createConnection(createExponentPricesData(keys), { accounts }).connection,
|
|
340
|
+
PROGRAM_ID,
|
|
341
|
+
).fetchExponentPrices(),
|
|
342
|
+
new Error(
|
|
343
|
+
`PriceInterfaceAccounts owner mismatch: expected ${PROGRAM_ID.toBase58()}, got ${unexpectedOwner.toBase58()}`,
|
|
344
|
+
),
|
|
345
|
+
)
|
|
346
|
+
})
|
package/src/fetcher.ts
CHANGED
|
@@ -4,14 +4,15 @@ import { ExponentVaults, IDL, PROGRAM_ID } from "@exponent-labs/exponent-vaults-
|
|
|
4
4
|
import { ExponentVaultsPDA } from "@exponent-labs/exponent-vaults-pda"
|
|
5
5
|
|
|
6
6
|
import { IGNORED_VAULTS } from "./constants"
|
|
7
|
-
import { transformAnchorData } from "./typeUtils"
|
|
8
7
|
import { decodePriceInterfaceAccounts } from "./priceInterfaceAccounts"
|
|
8
|
+
import { transformAnchorData } from "./typeUtils"
|
|
9
9
|
import { ActionProposal, ExponentPrices, ExponentVault, WithdrawalAccount } from "./types"
|
|
10
10
|
|
|
11
11
|
const EXPONENT_PRICES_META_DISCRIMINATOR = Buffer.from([188, 97, 196, 52, 192, 41, 211, 196])
|
|
12
12
|
const MAX_PRICES = 1024
|
|
13
13
|
const MAX_PRICE_MANAGERS = 8
|
|
14
14
|
const MAX_AUTHORIZED_VAULT_CREATORS = 128
|
|
15
|
+
const MAX_GET_MULTIPLE_ACCOUNTS_INFO_KEYS = 100
|
|
15
16
|
const PRICE_NODE_REGISTERS = 4
|
|
16
17
|
const PRICE_USAGE_AMOUNT_REGISTER = 1
|
|
17
18
|
|
|
@@ -261,9 +262,9 @@ export class ExponentVaultsFetcher {
|
|
|
261
262
|
}))
|
|
262
263
|
}
|
|
263
264
|
|
|
264
|
-
async fetchAllWithdrawalAccountsDeserialized(
|
|
265
|
-
|
|
266
|
-
> {
|
|
265
|
+
async fetchAllWithdrawalAccountsDeserialized(
|
|
266
|
+
filters?: web3.GetProgramAccountsFilter[],
|
|
267
|
+
): Promise<DecodedAccount<WithdrawalAccount>[]> {
|
|
267
268
|
const accounts = await this.fetchAllWithdrawalAccounts(filters)
|
|
268
269
|
return accounts.map(({ publicKey, account }) => ({
|
|
269
270
|
publicKey,
|
|
@@ -432,20 +433,34 @@ export class ExponentVaultsFetcher {
|
|
|
432
433
|
const activePriceEntries = prices.filter((entry) => entry !== null) as NonNullable<
|
|
433
434
|
ExponentPrices["prices"][number]
|
|
434
435
|
>[]
|
|
435
|
-
const
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
)
|
|
436
|
+
const priceEntriesByInterfaceAccount = new Map<
|
|
437
|
+
string,
|
|
438
|
+
{ entries: NonNullable<ExponentPrices["prices"][number]>[]; key: web3.PublicKey }
|
|
439
|
+
>()
|
|
440
|
+
for (const entry of activePriceEntries) {
|
|
441
|
+
const key = entry.priceInterfaceAccounts
|
|
442
|
+
if (key.equals(ZERO_PUBKEY)) continue
|
|
443
|
+
|
|
444
|
+
const keyString = key.toBase58()
|
|
445
|
+
const group = priceEntriesByInterfaceAccount.get(keyString)
|
|
446
|
+
if (group) {
|
|
447
|
+
group.entries.push(entry)
|
|
448
|
+
} else {
|
|
449
|
+
priceEntriesByInterfaceAccount.set(keyString, { entries: [entry], key })
|
|
450
|
+
}
|
|
451
|
+
}
|
|
445
452
|
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
453
|
+
const priceInterfaceAccountGroups = [...priceEntriesByInterfaceAccount.values()]
|
|
454
|
+
for (let start = 0; start < priceInterfaceAccountGroups.length; start += MAX_GET_MULTIPLE_ACCOUNTS_INFO_KEYS) {
|
|
455
|
+
const groups = priceInterfaceAccountGroups.slice(start, start + MAX_GET_MULTIPLE_ACCOUNTS_INFO_KEYS)
|
|
456
|
+
const infos = await this.program.provider.connection.getMultipleAccountsInfo(groups.map((group) => group.key))
|
|
457
|
+
if (infos.length !== groups.length) {
|
|
458
|
+
throw new Error(`PriceInterfaceAccounts batch size mismatch: requested ${groups.length}, got ${infos.length}`)
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
for (let index = 0; index < groups.length; index++) {
|
|
462
|
+
const { entries, key } = groups[index]
|
|
463
|
+
const info = infos[index]
|
|
449
464
|
if (!info) {
|
|
450
465
|
throw new Error(`PriceInterfaceAccounts account not found: ${key.toBase58()}`)
|
|
451
466
|
}
|
|
@@ -456,11 +471,9 @@ export class ExponentVaultsFetcher {
|
|
|
456
471
|
}
|
|
457
472
|
|
|
458
473
|
const decoded = decodePriceInterfaceAccounts(info.data)
|
|
459
|
-
for (const entry of
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
entry.scopeConfig = decoded.scopeConfig
|
|
463
|
-
}
|
|
474
|
+
for (const entry of entries) {
|
|
475
|
+
entry.interfaceAccounts = decoded.interfaceAccounts
|
|
476
|
+
entry.scopeConfig = decoded.scopeConfig
|
|
464
477
|
}
|
|
465
478
|
}
|
|
466
479
|
}
|