@subsquid/solana-stream 0.2.0-2-0.a5e4b7 → 0.2.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.
Files changed (104) hide show
  1. package/README.md +4 -0
  2. package/lib/{schema.d.ts → archive/schema.d.ts} +22 -32
  3. package/lib/archive/schema.d.ts.map +1 -0
  4. package/lib/{schema.js → archive/schema.js} +39 -55
  5. package/lib/archive/schema.js.map +1 -0
  6. package/lib/archive/source.d.ts +15 -0
  7. package/lib/archive/source.d.ts.map +1 -0
  8. package/lib/archive/source.js +98 -0
  9. package/lib/archive/source.js.map +1 -0
  10. package/lib/data/fields.d.ts +8 -0
  11. package/lib/data/fields.d.ts.map +1 -0
  12. package/lib/data/fields.js +46 -0
  13. package/lib/data/fields.js.map +1 -0
  14. package/lib/data/model.d.ts +79 -0
  15. package/lib/data/model.d.ts.map +1 -0
  16. package/lib/data/model.js +44 -0
  17. package/lib/data/model.js.map +1 -0
  18. package/lib/data/partial.d.ts +26 -0
  19. package/lib/data/partial.d.ts.map +1 -0
  20. package/lib/data/partial.js +3 -0
  21. package/lib/data/partial.js.map +1 -0
  22. package/lib/data/request.d.ts +108 -0
  23. package/lib/data/request.d.ts.map +1 -0
  24. package/lib/data/request.js +3 -0
  25. package/lib/data/request.js.map +1 -0
  26. package/lib/data/type-util.d.ts +20 -0
  27. package/lib/data/type-util.d.ts.map +1 -0
  28. package/lib/data/type-util.js +3 -0
  29. package/lib/data/type-util.js.map +1 -0
  30. package/lib/index.d.ts +3 -3
  31. package/lib/index.d.ts.map +1 -1
  32. package/lib/index.js +3 -3
  33. package/lib/index.js.map +1 -1
  34. package/lib/instruction.d.ts +3 -2
  35. package/lib/instruction.d.ts.map +1 -1
  36. package/lib/instruction.js +2 -2
  37. package/lib/instruction.js.map +1 -1
  38. package/lib/rpc/client.d.ts +17 -0
  39. package/lib/rpc/client.d.ts.map +1 -0
  40. package/lib/rpc/client.js +15 -0
  41. package/lib/rpc/client.js.map +1 -0
  42. package/lib/rpc/filter.d.ts +4 -0
  43. package/lib/rpc/filter.d.ts.map +1 -0
  44. package/lib/rpc/filter.js +357 -0
  45. package/lib/rpc/filter.js.map +1 -0
  46. package/lib/rpc/mapping.d.ts +5 -0
  47. package/lib/rpc/mapping.d.ts.map +1 -0
  48. package/lib/rpc/mapping.js +12 -0
  49. package/lib/rpc/mapping.js.map +1 -0
  50. package/lib/rpc/project.d.ts +5 -0
  51. package/lib/rpc/project.d.ts.map +1 -0
  52. package/lib/rpc/project.js +60 -0
  53. package/lib/rpc/project.js.map +1 -0
  54. package/lib/rpc/source.d.ts +15 -0
  55. package/lib/rpc/source.d.ts.map +1 -0
  56. package/lib/rpc/source.js +82 -0
  57. package/lib/rpc/source.js.map +1 -0
  58. package/lib/source.d.ts +101 -17
  59. package/lib/source.d.ts.map +1 -1
  60. package/lib/source.js +269 -70
  61. package/lib/source.js.map +1 -1
  62. package/package.json +15 -9
  63. package/src/{schema.ts → archive/schema.ts} +35 -54
  64. package/src/archive/source.ts +105 -0
  65. package/src/data/fields.ts +50 -0
  66. package/src/data/model.ts +154 -0
  67. package/src/data/partial.ts +31 -0
  68. package/src/data/request.ts +142 -0
  69. package/src/data/type-util.ts +42 -0
  70. package/src/index.ts +3 -3
  71. package/src/instruction.ts +9 -4
  72. package/src/rpc/client.ts +26 -0
  73. package/src/rpc/filter.ts +368 -0
  74. package/src/rpc/mapping.ts +13 -0
  75. package/src/rpc/project.ts +61 -0
  76. package/src/rpc/source.ts +90 -0
  77. package/src/source.ts +368 -92
  78. package/lib/objects/index.d.ts +0 -4
  79. package/lib/objects/index.d.ts.map +0 -1
  80. package/lib/objects/index.js +0 -23
  81. package/lib/objects/index.js.map +0 -1
  82. package/lib/objects/items.d.ts +0 -83
  83. package/lib/objects/items.d.ts.map +0 -1
  84. package/lib/objects/items.js +0 -260
  85. package/lib/objects/items.js.map +0 -1
  86. package/lib/objects/relations.d.ts +0 -3
  87. package/lib/objects/relations.d.ts.map +0 -1
  88. package/lib/objects/relations.js +0 -82
  89. package/lib/objects/relations.js.map +0 -1
  90. package/lib/objects/types.d.ts +0 -76
  91. package/lib/objects/types.d.ts.map +0 -1
  92. package/lib/objects/types.js +0 -29
  93. package/lib/objects/types.js.map +0 -1
  94. package/lib/query.d.ts +0 -46
  95. package/lib/query.d.ts.map +0 -1
  96. package/lib/query.js +0 -96
  97. package/lib/query.js.map +0 -1
  98. package/lib/schema.d.ts.map +0 -1
  99. package/lib/schema.js.map +0 -1
  100. package/src/objects/index.ts +0 -3
  101. package/src/objects/items.ts +0 -296
  102. package/src/objects/relations.ts +0 -90
  103. package/src/objects/types.ts +0 -88
  104. package/src/query.ts +0 -149
@@ -1,23 +1,28 @@
1
+ import {Base58Bytes} from '@subsquid/solana-rpc-data'
1
2
  import {toHex} from '@subsquid/util-internal-hex'
2
- import {Bytes} from '@subsquid/util-internal'
3
3
  import bs58 from 'bs58'
4
+ import {Bytes} from './data/model'
5
+
4
6
 
5
7
  export const DATA_SYM = Symbol('DATA')
6
8
  export const D8_SYM = Symbol('D8')
7
9
 
10
+
8
11
  interface Instruction {
9
- data: Bytes
12
+ data: Base58Bytes
10
13
  [DATA_SYM]?: Uint8Array
11
14
  [D8_SYM]?: Bytes
12
15
  }
13
16
 
17
+
14
18
  export function getInstructionData(i: Instruction): Uint8Array {
15
19
  if (i[DATA_SYM]) return i[DATA_SYM]
16
- return (i[DATA_SYM] = bs58.decode(i.data))
20
+ return i[DATA_SYM] = bs58.decode(i.data)
17
21
  }
18
22
 
23
+
19
24
  export function getInstructionDescriptor(i: Instruction): string {
20
25
  if (i[D8_SYM]) return i[D8_SYM]
21
26
  let bytes = toHex(getInstructionData(i))
22
- return (i[D8_SYM] = bytes.slice(0, 18))
27
+ return i[D8_SYM] = bytes.slice(0, 18)
23
28
  }
@@ -0,0 +1,26 @@
1
+ import {RpcClient, RpcClientOptions} from '@subsquid/rpc-client'
2
+
3
+
4
+ export interface SolanaRpcClientOptions extends RpcClientOptions {
5
+ /**
6
+ * Retry on connection error.
7
+ *
8
+ * By default, retries indefinitely.
9
+ */
10
+ retryAttempts?: number
11
+ /**
12
+ * Unsafe integer check must be always enabled
13
+ */
14
+ fixUnsafeIntegers?: true
15
+ }
16
+
17
+
18
+ export class SolanaRpcClient extends RpcClient {
19
+ constructor(options: SolanaRpcClientOptions) {
20
+ super({
21
+ retryAttempts: Number.MAX_SAFE_INTEGER,
22
+ ...options,
23
+ fixUnsafeIntegers: true
24
+ })
25
+ }
26
+ }
@@ -0,0 +1,368 @@
1
+ import {
2
+ Balance,
3
+ Block,
4
+ Instruction,
5
+ LogMessage,
6
+ Reward,
7
+ TokenBalance,
8
+ Transaction
9
+ } from '@subsquid/solana-normalization'
10
+ import {bisect, def, groupBy, weakMemo} from '@subsquid/util-internal'
11
+ import {EntityFilter, FilterBuilder} from '@subsquid/util-internal-processor-tools'
12
+ import assert from 'assert'
13
+ import {
14
+ BalanceRequestRelations,
15
+ DataRequest,
16
+ InstructionRequestRelations,
17
+ LogRequestRelations,
18
+ TokenBalanceRequestRelations,
19
+ TransactionRequestRelations
20
+ } from '../data/request'
21
+ import {getInstructionDescriptor} from '../instruction'
22
+
23
+
24
+ function buildTransactionFilter(dataRequest: DataRequest): EntityFilter<Transaction, TransactionRequestRelations> {
25
+ let items = new EntityFilter()
26
+ for (let req of dataRequest.transactions ?? []) {
27
+ let filter = new FilterBuilder<Transaction>()
28
+ let where = req.where || {}
29
+ filter.getIn(tx => tx.accountKeys[0], where.feePayer)
30
+ items.add(filter, req.include || {})
31
+ }
32
+ return items
33
+ }
34
+
35
+
36
+ function buildInstructionFilter(dataRequest: DataRequest): EntityFilter<Instruction, InstructionRequestRelations> {
37
+ let items = new EntityFilter()
38
+ for (let req of dataRequest.instructions ?? []) {
39
+ let filter = new FilterBuilder<Instruction>()
40
+ let where = req.where || {}
41
+ filter.propIn('programId', where.programId)
42
+ filter.matchAny((i, d) => getInstructionDescriptor(i).startsWith(d), where.d1)
43
+ filter.matchAny((i, d) => getInstructionDescriptor(i).startsWith(d), where.d2)
44
+ filter.matchAny((i, d) => getInstructionDescriptor(i).startsWith(d), where.d4)
45
+ filter.getIn(getInstructionDescriptor, where.d8)
46
+ filter.getIn(i => i.accounts[0], where.a0)
47
+ filter.getIn(i => i.accounts[1], where.a1)
48
+ filter.getIn(i => i.accounts[2], where.a2)
49
+ filter.getIn(i => i.accounts[3], where.a3)
50
+ filter.getIn(i => i.accounts[4], where.a4)
51
+ filter.getIn(i => i.accounts[5], where.a5)
52
+ filter.getIn(i => i.accounts[6], where.a6)
53
+ filter.getIn(i => i.accounts[7], where.a7)
54
+ filter.getIn(i => i.accounts[8], where.a8)
55
+ filter.getIn(i => i.accounts[9], where.a9)
56
+ if (where.isCommitted != null) {
57
+ filter.propIn('isCommitted', [where.isCommitted])
58
+ }
59
+ items.add(filter, req.include ?? {})
60
+ }
61
+ return items
62
+ }
63
+
64
+
65
+ function buildLogFilter(dataRequest: DataRequest): EntityFilter<LogMessage, LogRequestRelations> {
66
+ let items = new EntityFilter()
67
+ for (let req of dataRequest.logs ?? []) {
68
+ let filter = new FilterBuilder<LogMessage>()
69
+ let where = req.where ?? {}
70
+ filter.propIn('programId', where.programId)
71
+ filter.propIn('kind', where.kind)
72
+ items.add(filter, req.include ?? {})
73
+ }
74
+ return items
75
+ }
76
+
77
+
78
+ function buildBalanceFilter(dataRequest: DataRequest): EntityFilter<Balance, BalanceRequestRelations> {
79
+ let items = new EntityFilter()
80
+ for (let req of dataRequest.balances ?? []) {
81
+ let filter = new FilterBuilder<Balance>()
82
+ let where = req.where || {}
83
+ filter.propIn('account', where.account)
84
+ items.add(filter, req.include || {})
85
+ }
86
+ return items
87
+ }
88
+
89
+
90
+ function buildTokenBalanceFilter(dataRequest: DataRequest): EntityFilter<TokenBalance, TokenBalanceRequestRelations> {
91
+ let items = new EntityFilter()
92
+ for (let req of dataRequest.tokenBalances ?? []) {
93
+ let filter = new FilterBuilder<TokenBalance>()
94
+ let where = req.where || {}
95
+ filter.propIn('account', where.account)
96
+ filter.propIn('preProgramId', where.preProgramId)
97
+ filter.propIn('postProgramId', where.postProgramId)
98
+ filter.propIn('preMint', where.preMint)
99
+ filter.propIn('postMint', where.postMint)
100
+ filter.propIn('preOwner', where.preOwner)
101
+ filter.propIn('postOwner', where.postOwner)
102
+ items.add(filter, req.include || {})
103
+ }
104
+ return items
105
+ }
106
+
107
+
108
+ function buildRewardsFilter(dataRequest: DataRequest): EntityFilter<Reward, {}> {
109
+ let items = new EntityFilter()
110
+ for (let req of dataRequest.rewards ?? []) {
111
+ let filter = new FilterBuilder<Reward>()
112
+ let where = req.where || {}
113
+ filter.propIn('pubkey', where.pubkey)
114
+ items.add(filter, {})
115
+ }
116
+ return items
117
+ }
118
+
119
+
120
+ const getItemFilters = weakMemo((req: DataRequest) => {
121
+ return {
122
+ transactions: buildTransactionFilter(req),
123
+ instructions: buildInstructionFilter(req),
124
+ logs: buildLogFilter(req),
125
+ balances: buildBalanceFilter(req),
126
+ tokenBalances: buildTokenBalanceFilter(req),
127
+ rewards: buildRewardsFilter(req)
128
+ }
129
+ })
130
+
131
+
132
+ class IncludeSet {
133
+ transactions = new Set<Transaction>
134
+ instructions = new Set<Instruction>
135
+ logs = new Set<LogMessage>
136
+ balances = new Set<Balance>
137
+ tokenBalances = new Set<TokenBalance>
138
+ rewards = new Set<Reward>
139
+ }
140
+
141
+
142
+ export function filterBlockItems(block: Block, req: DataRequest): void {
143
+ new BlockFilter(block, req).apply()
144
+ }
145
+
146
+
147
+ class BlockFilter {
148
+ private include = new IncludeSet()
149
+ private items: ReturnType<typeof getItemFilters>
150
+
151
+ constructor(private block: Block, req: DataRequest) {
152
+ this.items = getItemFilters(req)
153
+ }
154
+
155
+ @def
156
+ private transactions(): Map<number, Transaction> {
157
+ return new Map(this.block.transactions.map(tx => [tx.transactionIndex, tx]))
158
+ }
159
+
160
+ private getTransaction(idx: number): Transaction {
161
+ let tx = this.transactions().get(idx)
162
+ assert(tx)
163
+ return tx
164
+ }
165
+
166
+ @def
167
+ private logsByTx(): Map<number, LogMessage[]> {
168
+ return groupBy(this.block.logs, log => log.transactionIndex)
169
+ }
170
+
171
+ @def
172
+ private instructionsByTx(): Map<number, Instruction[]> {
173
+ return groupBy(this.block.instructions, i => i.transactionIndex)
174
+ }
175
+
176
+ @def
177
+ private balancesByTx(): Map<number, Balance[]> {
178
+ return groupBy(this.block.balances, b => b.transactionIndex)
179
+ }
180
+
181
+ @def
182
+ private tokenBalancesByTx(): Map<number, TokenBalance[]> {
183
+ return groupBy(this.block.tokenBalances, b => b.transactionIndex)
184
+ }
185
+
186
+ private filterTransactions(): void {
187
+ if (!this.items.transactions.present()) return
188
+ for (let tx of this.block.transactions) {
189
+ let rel = this.items.transactions.match(tx)
190
+ if (rel == null) continue
191
+ this.include.transactions.add(tx)
192
+ if (rel.logs) {
193
+ include(this.include.logs, this.logsByTx().get(tx.transactionIndex))
194
+ }
195
+ if (rel.instructions) {
196
+ include(this.include.instructions, this.instructionsByTx().get(tx.transactionIndex))
197
+ }
198
+ if (rel.balances) {
199
+ include(this.include.balances, this.balancesByTx().get(tx.transactionIndex))
200
+ }
201
+ if (rel.tokenBalances) {
202
+ include(this.include.tokenBalances, this.tokenBalancesByTx().get(tx.transactionIndex))
203
+ }
204
+ }
205
+ }
206
+
207
+ private filterInstructions(): void {
208
+ if (!this.items.instructions.present()) return
209
+ for (let i = 0; i < this.block.instructions.length; i++) {
210
+ let ins = this.block.instructions[i]
211
+ let rel = this.items.instructions.match(ins)
212
+ if (rel == null) continue
213
+ this.include.instructions.add(ins)
214
+ if (rel.innerInstructions) {
215
+ for (let j = i + 1; j < this.block.instructions.length; j++) {
216
+ let child = this.block.instructions[j]
217
+ if (
218
+ ins.transactionIndex == child.transactionIndex &&
219
+ isChildAddress(ins.instructionAddress, child.instructionAddress)
220
+ ) {
221
+ this.include.instructions.add(child)
222
+ } else {
223
+ break
224
+ }
225
+ }
226
+ }
227
+ if (rel.logs) {
228
+ let logs = this.logsByTx().get(ins.transactionIndex) ?? []
229
+ include(this.include.logs, findInstructionChildren(logs, ins.instructionAddress))
230
+ }
231
+ if (rel.transaction) {
232
+ this.include.transactions.add(this.getTransaction(ins.transactionIndex))
233
+ }
234
+ if (rel.transactionBalances) {
235
+ include(this.include.balances, this.balancesByTx().get(ins.transactionIndex))
236
+ }
237
+ if (rel.transactionTokenBalances) {
238
+ include(this.include.tokenBalances, this.tokenBalancesByTx().get(ins.transactionIndex))
239
+ }
240
+ if (rel.transactionInstructions) {
241
+ include(this.include.instructions, this.instructionsByTx().get(ins.transactionIndex))
242
+ }
243
+ }
244
+ }
245
+
246
+ private filterLogs(): void {
247
+ if (!this.items.logs.present()) return
248
+ for (let log of this.block.logs) {
249
+ let rel = this.items.logs.match(log)
250
+ if (rel == null) continue
251
+ this.include.logs.add(log)
252
+ if (rel.transaction) {
253
+ this.include.transactions.add(this.getTransaction(log.transactionIndex))
254
+ }
255
+ if (rel.instruction) {
256
+ this.include.instructions.add(this.getInstruction(log.transactionIndex, log.instructionAddress))
257
+ }
258
+ }
259
+ }
260
+
261
+ private getInstruction(transactionIdx: number, address: number[]): Instruction {
262
+ let pos = bisect(
263
+ this.block.instructions,
264
+ null,
265
+ ins => ins.transactionIndex - transactionIdx || addressCompare(ins.instructionAddress, address)
266
+ )
267
+ let ins = this.block.instructions[pos]
268
+ assert(
269
+ ins &&
270
+ ins.transactionIndex == transactionIdx &&
271
+ addressCompare(ins.instructionAddress, address) == 0
272
+ )
273
+ return ins
274
+ }
275
+
276
+ private filterTokenBalances(): void {
277
+ if (!this.items.tokenBalances.present()) return
278
+ for (let balance of this.block.tokenBalances) {
279
+ let rel = this.items.tokenBalances.match(balance)
280
+ if (rel == null) continue
281
+ this.include.tokenBalances.add(balance)
282
+ if (rel.transaction) {
283
+ this.include.transactions.add(this.getTransaction(balance.transactionIndex))
284
+ }
285
+ if (rel.transactionInstructions) {
286
+ include(this.include.instructions, this.instructionsByTx().get(balance.transactionIndex))
287
+ }
288
+ }
289
+ }
290
+
291
+ private filterBalances(): void {
292
+ if (!this.items.balances.present()) return
293
+ for (let balance of this.block.balances) {
294
+ let rel = this.items.balances.match(balance)
295
+ if (rel == null) continue
296
+ this.include.balances.add(balance)
297
+ if (rel.transaction) {
298
+ this.include.transactions.add(this.getTransaction(balance.transactionIndex))
299
+ }
300
+ if (rel.transactionInstructions) {
301
+ include(this.include.instructions, this.instructionsByTx().get(balance.transactionIndex))
302
+ }
303
+ }
304
+ }
305
+
306
+ private filterRewards(): void {
307
+ if (!this.items.rewards.present() || this.block.rewards == null) return
308
+ for (let reward of this.block.rewards) {
309
+ if (this.items.rewards.match(reward)) {
310
+ this.include.rewards.add(reward)
311
+ }
312
+ }
313
+ }
314
+
315
+ apply(): void {
316
+ this.filterTransactions()
317
+ this.filterInstructions()
318
+ this.filterLogs()
319
+ this.filterBalances()
320
+ this.filterTokenBalances()
321
+ this.filterRewards()
322
+ this.block.transactions = this.block.transactions.filter(i => this.include.transactions.has(i))
323
+ this.block.instructions = this.block.instructions.filter(i => this.include.instructions.has(i))
324
+ this.block.logs = this.block.logs.filter(i => this.include.logs.has(i))
325
+ this.block.balances = this.block.balances.filter(i => this.include.balances.has(i))
326
+ this.block.tokenBalances = this.block.tokenBalances.filter(i => this.include.tokenBalances.has(i))
327
+ this.block.rewards = this.block.rewards.filter(i => this.include.rewards.has(i))
328
+ }
329
+ }
330
+
331
+
332
+ function include<T>(set: Set<T>, items?: Iterable<T>): void {
333
+ if (items == null) return
334
+ for (let it of items) {
335
+ set.add(it)
336
+ }
337
+ }
338
+
339
+
340
+ function* findInstructionChildren<I extends {instructionAddress: number[]}>(
341
+ items: I[],
342
+ addr: number[],
343
+ ): Iterable<I> {
344
+ if (items.length == 0) return
345
+ let beg = bisect(items, addr, (ins, addr) => addressCompare(ins.instructionAddress, addr))
346
+ while (beg < items.length && isChildAddress(items[beg].instructionAddress, addr)) {
347
+ yield items[beg]
348
+ beg += 1
349
+ }
350
+ }
351
+
352
+
353
+ function isChildAddress(parent: number[], addr: number[]): boolean {
354
+ if (parent.length > addr.length) return false
355
+ for (let i = 0; i < parent.length; i++) {
356
+ if (parent[i] !== addr[i]) return false
357
+ }
358
+ return true
359
+ }
360
+
361
+
362
+ function addressCompare(a: number[], b: number[]): number {
363
+ for (let i = 0; i < Math.min(a.length, b.length); i++) {
364
+ let order = a[i] - b[i]
365
+ if (order) return order
366
+ }
367
+ return a.length - b.length
368
+ }
@@ -0,0 +1,13 @@
1
+ import {mapRpcBlock} from '@subsquid/solana-normalization'
2
+ import type * as rpc from '@subsquid/solana-rpc-data'
3
+ import {PartialBlock} from '../data/partial'
4
+ import {DataRequest} from '../data/request'
5
+ import {filterBlockItems} from './filter'
6
+ import {projectFields} from './project'
7
+
8
+
9
+ export function mapBlock(src: rpc.Block, req: DataRequest): PartialBlock {
10
+ let block = mapRpcBlock(src)
11
+ filterBlockItems(block, req)
12
+ return projectFields(block, req.fields || {})
13
+ }
@@ -0,0 +1,61 @@
1
+ import {Block} from '@subsquid/solana-normalization'
2
+ import {project} from '../data/fields'
3
+ import {FieldSelection} from '../data/model'
4
+ import {PartialBlock} from '../data/partial'
5
+ import {D8_SYM, DATA_SYM} from '../instruction'
6
+
7
+
8
+ export function projectFields(block: Block, fields: FieldSelection): PartialBlock {
9
+ return {
10
+ header: {
11
+ height: block.header.height,
12
+ hash: block.header.hash,
13
+ parentHash: block.header.parentHash,
14
+ ...project(fields.block, block.header)
15
+ },
16
+ transactions: block.transactions.map(tx => {
17
+ return {
18
+ transactionIndex: tx.transactionIndex,
19
+ ...project(fields.transaction, tx)
20
+ }
21
+ }),
22
+ instructions: block.instructions.map(i => {
23
+ let ins = {
24
+ transactionIndex: i.transactionIndex,
25
+ instructionAddress: i.instructionAddress,
26
+ ...project(fields.instruction, i)
27
+ }
28
+ ;(ins as any)[D8_SYM] = (i as any)[D8_SYM]
29
+ ;(ins as any)[DATA_SYM] = (i as any)[DATA_SYM]
30
+ return ins
31
+ }),
32
+ logs: block.logs.map(log => {
33
+ return {
34
+ transactionIndex: log.transactionIndex,
35
+ logIndex: log.logIndex,
36
+ instructionAddress: log.instructionAddress,
37
+ ...project(fields.log, log)
38
+ }
39
+ }),
40
+ balances: block.balances.map(b => {
41
+ return {
42
+ transactionIndex: b.transactionIndex,
43
+ account: b.account,
44
+ ...project(fields.balance, b)
45
+ }
46
+ }),
47
+ tokenBalances: block.tokenBalances.map(b => {
48
+ return {
49
+ transactionIndex: b.transactionIndex,
50
+ account: b.account,
51
+ ...project(fields.tokenBalance, b)
52
+ }
53
+ }),
54
+ rewards: block.rewards.map(r => {
55
+ return {
56
+ pubkey: r.pubkey,
57
+ ...project(fields.reward, r)
58
+ }
59
+ })
60
+ }
61
+ }
@@ -0,0 +1,90 @@
1
+ import {Base58Bytes, BlockInfo, findSlot, GetBlock, ingestFinalizedBlocks, Rpc} from '@subsquid/solana-rpc'
2
+ import {addErrorContext, wait} from '@subsquid/util-internal'
3
+ import {getRequestAt, mapRangeRequestList, RangeRequestList} from '@subsquid/util-internal-range'
4
+ import {PartialBlock} from '../data/partial'
5
+ import {DataRequest} from '../data/request'
6
+ import type {RpcSettings} from '../source'
7
+ import {mapBlock} from './mapping'
8
+
9
+
10
+ export class RpcDataSource {
11
+ private rpc: Rpc
12
+
13
+ constructor(private options: RpcSettings) {
14
+ this.rpc = new Rpc(options.client)
15
+ }
16
+
17
+ getFinalizedHeight(): Promise<number> {
18
+ return this.rpc.getFinalizedHeight()
19
+ }
20
+
21
+ async getBlockInfo(slot: number): Promise<BlockInfo | undefined> {
22
+ let attempts = 10
23
+ while (attempts) {
24
+ let block = await this.rpc.getBlockInfo('finalized', slot)
25
+ if (block || block === undefined) return block
26
+ await wait(100)
27
+ attempts -= 1
28
+ }
29
+ throw new Error(`Failed to getBlock with finalized commitment at slot ${slot} 10 times in a row`)
30
+ }
31
+
32
+ async getBlockHash(height: number): Promise<Base58Bytes | undefined> {
33
+ let headSlot = await this.rpc.getTopSlot('finalized')
34
+ let top = {
35
+ slot: headSlot,
36
+ height: await this.rpc.getFinalizedBlockHeight(headSlot)
37
+ }
38
+ if (top.height < height) return
39
+ let bottom = {slot: 0, height: 0}
40
+ let slot = await findSlot(this.rpc, height, bottom, top)
41
+ let block = await this.rpc.getFinalizedBlockInfo(slot)
42
+ return block.blockhash
43
+ }
44
+
45
+ async *getBlockStream(
46
+ requests: RangeRequestList<DataRequest>,
47
+ stopOnHead?: boolean | undefined
48
+ ): AsyncIterable<PartialBlock[]> {
49
+ let blockStream = ingestFinalizedBlocks({
50
+ requests: toRpcRequests(requests),
51
+ stopOnHead,
52
+ rpc: this.rpc,
53
+ headPollInterval: 5_000,
54
+ strideSize: this.options.strideSize ?? 5,
55
+ strideConcurrency: this.options.strideConcurrency ?? 5,
56
+ concurrentFetchThreshold: this.options.concurrentFetchThreshold ?? 50
57
+ })
58
+
59
+ for await (let batch of blockStream) {
60
+ let req = getRequestAt(requests, batch[0].height) || {}
61
+ yield batch.map(block => {
62
+ try {
63
+ return mapBlock(block, req)
64
+ } catch(err: any) {
65
+ throw addErrorContext(err, {
66
+ blockHash: block.hash,
67
+ blockHeight: block.height,
68
+ blockSlot: block.slot
69
+ })
70
+ }
71
+ })
72
+ }
73
+ }
74
+ }
75
+
76
+
77
+ function toRpcRequests(requests: RangeRequestList<DataRequest>) {
78
+ return mapRangeRequestList(requests, req => {
79
+ let rewards = req.rewards?.length
80
+ let transactions = req.instructions?.length
81
+ || req.transactions?.length
82
+ || req.tokenBalances?.length
83
+ || req.balances?.length
84
+ || req.logs?.length
85
+ return {
86
+ rewards: !!rewards,
87
+ transactions: !!transactions
88
+ }
89
+ })
90
+ }