envio 3.9.0 → 3.10.0-subgraph
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/evm.schema.json +26 -0
- package/fuel.schema.json +26 -0
- package/index.d.ts +41 -8
- package/package.json +6 -7
- package/src/Batch.res +9 -0
- package/src/ChainState.res +35 -39
- package/src/ChainState.res.mjs +14 -11
- package/src/ChainState.resi +15 -7
- package/src/Config.res +61 -44
- package/src/Config.res.mjs +56 -19
- package/src/Core.res +19 -0
- package/src/Core.res.mjs +11 -0
- package/src/CrossChainState.res +15 -23
- package/src/CrossChainState.res.mjs +10 -18
- package/src/CrossChainState.resi +4 -1
- package/src/Ecosystem.res +7 -2
- package/src/Envio.res +6 -4
- package/src/EventConfigBuilder.res +169 -26
- package/src/EventConfigBuilder.res.mjs +124 -15
- package/src/EventProcessing.res +6 -6
- package/src/EventProcessing.res.mjs +8 -6
- package/src/HandlerLoader.res +20 -8
- package/src/HandlerLoader.res.mjs +11 -2
- package/src/HandlerRegister.res +21 -4
- package/src/HandlerRegister.res.mjs +24 -5
- package/src/InMemoryStore.res +9 -7
- package/src/InMemoryStore.res.mjs +3 -3
- package/src/IndexerState.res +43 -6
- package/src/IndexerState.res.mjs +36 -7
- package/src/IndexerState.resi +4 -4
- package/src/Internal.res +16 -17
- package/src/Metrics.res +72 -6
- package/src/Metrics.res.mjs +30 -2
- package/src/Persistence.res +36 -30
- package/src/Persistence.res.mjs +1 -20
- package/src/PgStorage.res +104 -56
- package/src/PgStorage.res.mjs +57 -35
- package/src/PruneStaleHistory.res +85 -52
- package/src/PruneStaleHistory.res.mjs +63 -37
- package/src/Rollback.res +68 -45
- package/src/Rollback.res.mjs +38 -19
- package/src/RollbackCommit.res +7 -9
- package/src/RollbackCommit.res.mjs +5 -6
- package/src/SafeCheckpointTracking.res +11 -11
- package/src/SafeCheckpointTracking.res.mjs +7 -6
- package/src/SimulateItems.res +33 -18
- package/src/SimulateItems.res.mjs +26 -22
- package/src/Sink.res +38 -26
- package/src/Sink.res.mjs +21 -16
- package/src/TestIndexer.res +5 -5
- package/src/TestIndexer.res.mjs +1 -1
- package/src/UserContext.res +358 -51
- package/src/UserContext.res.mjs +271 -35
- package/src/Utils.res +170 -0
- package/src/Utils.res.mjs +153 -0
- package/src/Writing.res +2 -2
- package/src/Writing.res.mjs +1 -1
- package/src/bindings/ClickHouse.res +366 -698
- package/src/bindings/ClickHouse.res.mjs +371 -421
- package/src/bindings/ClickHouseSink.res +337 -0
- package/src/bindings/ClickHouseSink.res.mjs +246 -0
- package/src/bindings/EventSource.res +8 -2
- package/src/bindings/NodeJs.res +6 -0
- package/src/bindings/Vitest.res +23 -0
- package/src/bindings/Vitest.res.mjs +3 -0
- package/src/bindings/WebSocket.res +9 -10
- package/src/db/CheckpointBounds.res +53 -0
- package/src/db/CheckpointBounds.res.mjs +44 -0
- package/src/db/EntityFilter.res +36 -17
- package/src/db/EntityFilter.res.mjs +25 -14
- package/src/db/EntityHistory.res +47 -22
- package/src/db/EntityHistory.res.mjs +19 -12
- package/src/db/InternalTable.res +84 -35
- package/src/db/InternalTable.res.mjs +56 -23
- package/src/db/RollbackFloors.res +55 -0
- package/src/db/RollbackFloors.res.mjs +92 -0
- package/src/db/Table.res +22 -4
- package/src/db/Table.res.mjs +30 -9
- package/src/sources/Evm.res +2 -0
- package/src/sources/Evm.res.mjs +2 -0
- package/src/sources/EvmHyperSyncSource.res +3 -3
- package/src/sources/EvmHyperSyncSource.res.mjs +3 -3
- package/src/sources/EvmRpcWs.res +124 -0
- package/src/sources/EvmRpcWs.res.mjs +117 -0
- package/src/sources/Fuel.res +2 -0
- package/src/sources/Fuel.res.mjs +2 -0
- package/src/sources/FuelHyperSyncSource.res +1 -1
- package/src/sources/FuelHyperSyncSource.res.mjs +2 -1
- package/src/sources/HeightFeed.res +568 -0
- package/src/sources/HeightFeed.res.mjs +452 -0
- package/src/sources/HeightStream.res +257 -0
- package/src/sources/HeightStream.res.mjs +194 -0
- package/src/sources/HyperSync.res +5 -0
- package/src/sources/HyperSync.res.mjs +3 -0
- package/src/sources/HyperSync.resi +4 -0
- package/src/sources/HyperSyncSSE.res +49 -0
- package/src/sources/HyperSyncSSE.res.mjs +79 -0
- package/src/sources/RpcSource.res +1 -1
- package/src/sources/RpcSource.res.mjs +2 -2
- package/src/sources/Source.res +54 -1
- package/src/sources/Source.res.mjs +25 -0
- package/src/sources/SourceManager.res +220 -227
- package/src/sources/SourceManager.res.mjs +139 -164
- package/src/sources/SourceManager.resi +8 -0
- package/src/sources/Svm.res +2 -0
- package/src/sources/Svm.res.mjs +2 -0
- package/src/sources/SvmHyperSyncClient.res +70 -127
- package/src/sources/SvmHyperSyncClient.res.mjs +51 -25
- package/src/sources/SvmHyperSyncSource.res +18 -15
- package/src/sources/SvmHyperSyncSource.res.mjs +5 -4
- package/src/subgraph/blocks.ts +176 -0
- package/src/subgraph/calls.ts +217 -0
- package/src/subgraph/conformance.ts +102 -0
- package/src/subgraph/division.ts +133 -0
- package/src/subgraph/errors.ts +90 -0
- package/src/subgraph/graph-ts-types/VERSION +2 -0
- package/src/subgraph/graph-ts-types/chain/arweave.d.ts +70 -0
- package/src/subgraph/graph-ts-types/chain/cosmos.d.ts +327 -0
- package/src/subgraph/graph-ts-types/chain/ethereum.d.ts +233 -0
- package/src/subgraph/graph-ts-types/chain/near.d.ts +253 -0
- package/src/subgraph/graph-ts-types/chain/starknet.d.ts +32 -0
- package/src/subgraph/graph-ts-types/common/collections.d.ts +136 -0
- package/src/subgraph/graph-ts-types/common/conversion.d.ts +11 -0
- package/src/subgraph/graph-ts-types/common/datasource.d.ts +30 -0
- package/src/subgraph/graph-ts-types/common/eager-offset.d.ts +0 -0
- package/src/subgraph/graph-ts-types/common/json.d.ts +17 -0
- package/src/subgraph/graph-ts-types/common/numbers.d.ts +120 -0
- package/src/subgraph/graph-ts-types/common/value.d.ts +120 -0
- package/src/subgraph/graph-ts-types/common/yaml.d.ts +90 -0
- package/src/subgraph/graph-ts-types/global/global.d.ts +194 -0
- package/src/subgraph/graph-ts-types/helper-functions.d.ts +22 -0
- package/src/subgraph/graph-ts-types/index.d.ts +102 -0
- package/src/subgraph/graph-ts.ts +1948 -0
- package/src/subgraph/hosts.ts +148 -0
- package/src/subgraph/runtime.ts +863 -0
- package/src/subgraph/scope.ts +66 -0
- package/svm.schema.json +6 -81
- package/src/MemoryStorage.res +0 -729
- package/src/MemoryStorage.res.mjs +0 -586
- package/src/sources/HyperSyncHeightStream.res +0 -129
- package/src/sources/HyperSyncHeightStream.res.mjs +0 -110
- package/src/sources/RpcWebSocketHeightStream.res +0 -175
- package/src/sources/RpcWebSocketHeightStream.res.mjs +0 -180
|
@@ -14,19 +14,14 @@ module Registration = {
|
|
|
14
14
|
values: array<string>,
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
// from
|
|
17
|
+
// One `onInstruction`/`contractRegister` binding of an instruction on a
|
|
18
|
+
// chain: the handler-specific routing state and the fetch state queries are
|
|
19
|
+
// built from. Chain-scoped `index` is echoed back on routed items.
|
|
20
20
|
type input = {
|
|
21
|
-
// Chain-scoped sequential registration index, echoed back on routed items.
|
|
22
21
|
index: int,
|
|
23
|
-
instructionName: string,
|
|
24
|
-
contractName: string,
|
|
25
|
-
programId: string,
|
|
26
22
|
isWildcard: bool,
|
|
27
23
|
// Earliest slot this registration accepts; `None` is unrestricted.
|
|
28
24
|
startBlock: option<int>,
|
|
29
|
-
discriminator?: string,
|
|
30
25
|
isInner?: bool,
|
|
31
26
|
// DNF: outer array is OR of AND-groups.
|
|
32
27
|
accountFilters: array<array<accountFilter>>,
|
|
@@ -36,28 +31,73 @@ module Registration = {
|
|
|
36
31
|
accountActivityFields: array<string>,
|
|
37
32
|
logFields: array<string>,
|
|
38
33
|
instructionFields: array<string>,
|
|
39
|
-
|
|
40
|
-
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// One config instruction: its identity (data prefix of any length; absent
|
|
37
|
+
// matches every instruction of the program) and Borsh layout, shared by
|
|
38
|
+
// every registration bound to it.
|
|
39
|
+
type instruction = {
|
|
40
|
+
name: string,
|
|
41
|
+
discriminator?: string,
|
|
41
42
|
argsJson?: string,
|
|
43
|
+
registrations: array<input>,
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
type program = {
|
|
47
|
+
// The config's program name.
|
|
48
|
+
name: string,
|
|
49
|
+
programId: string,
|
|
42
50
|
definedTypesJson?: string,
|
|
51
|
+
instructions: array<instruction>,
|
|
43
52
|
}
|
|
44
53
|
|
|
54
|
+
// Groups a chain's registrations under the config instruction each was
|
|
55
|
+
// built from, so the Rust client holds one layout per instruction rather
|
|
56
|
+
// than a copy per registration.
|
|
45
57
|
let fromOnEventRegistrations = (
|
|
46
58
|
onEventRegistrations: array<Internal.svmOnEventRegistration>,
|
|
47
|
-
): array<
|
|
48
|
-
|
|
59
|
+
): array<program> => {
|
|
60
|
+
let programs: array<program> = []
|
|
61
|
+
onEventRegistrations->Array.forEach(reg => {
|
|
49
62
|
let eventConfig =
|
|
50
63
|
reg.eventConfig->(Utils.magic: Internal.eventConfig => Internal.svmInstructionEventConfig)
|
|
51
|
-
{
|
|
64
|
+
let program = switch programs->Array.find(p => p.name === eventConfig.contractName) {
|
|
65
|
+
| Some(program) => program
|
|
66
|
+
| None =>
|
|
67
|
+
let program = {
|
|
68
|
+
name: eventConfig.contractName,
|
|
69
|
+
programId: eventConfig.programId->SvmTypes.Pubkey.toString,
|
|
70
|
+
definedTypesJson: ?switch eventConfig.definedTypes {
|
|
71
|
+
| JSON.Null => None
|
|
72
|
+
| definedTypes => Some(definedTypes->JSON.stringify)
|
|
73
|
+
},
|
|
74
|
+
instructions: [],
|
|
75
|
+
}
|
|
76
|
+
programs->Array.push(program)->ignore
|
|
77
|
+
program
|
|
78
|
+
}
|
|
79
|
+
let instruction = switch program.instructions->Array.find(i => i.name === eventConfig.name) {
|
|
80
|
+
| Some(instruction) => instruction
|
|
81
|
+
| None =>
|
|
82
|
+
let instruction = {
|
|
83
|
+
name: eventConfig.name,
|
|
84
|
+
discriminator: ?eventConfig.discriminator,
|
|
85
|
+
argsJson: ?switch eventConfig.args {
|
|
86
|
+
| JSON.Null => None
|
|
87
|
+
| args => Some(args->JSON.stringify)
|
|
88
|
+
},
|
|
89
|
+
registrations: [],
|
|
90
|
+
}
|
|
91
|
+
program.instructions->Array.push(instruction)->ignore
|
|
92
|
+
instruction
|
|
93
|
+
}
|
|
94
|
+
instruction.registrations
|
|
95
|
+
->Array.push({
|
|
52
96
|
index: reg.index,
|
|
53
|
-
instructionName: eventConfig.name,
|
|
54
|
-
contractName: eventConfig.contractName,
|
|
55
|
-
programId: eventConfig.programId->SvmTypes.Pubkey.toString,
|
|
56
97
|
isWildcard: reg.isWildcard,
|
|
57
98
|
startBlock: reg.startBlock,
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
accountFilters: eventConfig.accountFilters->Array.map(group =>
|
|
99
|
+
isInner: ?reg.isInner,
|
|
100
|
+
accountFilters: reg.accountFilters->Array.map(group =>
|
|
61
101
|
group->Array.map(
|
|
62
102
|
(filter): accountFilter => {
|
|
63
103
|
position: filter.position,
|
|
@@ -70,71 +110,10 @@ module Registration = {
|
|
|
70
110
|
accountActivityFields: reg.fieldSelection.accountActivityFields->Utils.Set.toArray,
|
|
71
111
|
logFields: reg.fieldSelection.logFields->Utils.Set.toArray,
|
|
72
112
|
instructionFields: reg.fieldSelection.instructionFields->Utils.Set.toArray,
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
| JSON.Null => None
|
|
76
|
-
| args => Some(args->JSON.stringify)
|
|
77
|
-
},
|
|
78
|
-
definedTypesJson: ?switch eventConfig.definedTypes {
|
|
79
|
-
| JSON.Null => None
|
|
80
|
-
| definedTypes => Some(definedTypes->JSON.stringify)
|
|
81
|
-
},
|
|
82
|
-
}
|
|
113
|
+
})
|
|
114
|
+
->ignore
|
|
83
115
|
})
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
module QueryTypes = {
|
|
87
|
-
type blockField =
|
|
88
|
-
| @as("slot") Slot
|
|
89
|
-
| @as("blockhash") Blockhash
|
|
90
|
-
| @as("parent_slot") ParentSlot
|
|
91
|
-
| @as("parent_blockhash") ParentBlockhash
|
|
92
|
-
| @as("block_time") BlockTime
|
|
93
|
-
| @as("block_height") BlockHeight
|
|
94
|
-
|
|
95
|
-
type transactionField =
|
|
96
|
-
| @as("slot") Slot
|
|
97
|
-
| @as("transaction_index") TransactionIndex
|
|
98
|
-
| @as("signatures") Signatures
|
|
99
|
-
| @as("fee_payer") FeePayer
|
|
100
|
-
| @as("success") Success
|
|
101
|
-
| @as("err") Err
|
|
102
|
-
| @as("fee") Fee
|
|
103
|
-
| @as("compute_units_consumed") ComputeUnitsConsumed
|
|
104
|
-
| @as("account_keys") AccountKeys
|
|
105
|
-
| @as("recent_blockhash") RecentBlockhash
|
|
106
|
-
| @as("version") Version
|
|
107
|
-
| @as("loaded_addresses_writable") LoadedAddressesWritable
|
|
108
|
-
| @as("loaded_addresses_readonly") LoadedAddressesReadonly
|
|
109
|
-
|
|
110
|
-
type fieldSelection = {block?: array<blockField>, transaction?: array<transactionField>}
|
|
111
|
-
|
|
112
|
-
/** Filter for selecting instruction calls. All non-empty fields are AND-ed:
|
|
113
|
-
an instruction must match at least one value in every non-empty field.
|
|
114
|
-
|
|
115
|
-
Discriminator filters (d1..d8) take hex-encoded byte prefixes ("0x" optional).
|
|
116
|
-
Account filters (a0..a9) take base58 pubkey strings. */
|
|
117
|
-
type instructionSelection = {
|
|
118
|
-
executingAccount?: array<string>,
|
|
119
|
-
d1?: array<string>,
|
|
120
|
-
d2?: array<string>,
|
|
121
|
-
d4?: array<string>,
|
|
122
|
-
d8?: array<string>,
|
|
123
|
-
isInner?: bool,
|
|
124
|
-
/** Success of the parent transaction; absent matches both. */
|
|
125
|
-
txSuccess?: bool,
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// The `get` query surface, used only for block-data range queries; event
|
|
129
|
-
// fetching goes through `getEventItems`, which builds its query in Rust.
|
|
130
|
-
type query = {
|
|
131
|
-
fromSlot: int,
|
|
132
|
-
toSlot?: int,
|
|
133
|
-
instructionCalls?: array<instructionSelection>,
|
|
134
|
-
includeAllBlocks?: bool,
|
|
135
|
-
fields?: fieldSelection,
|
|
136
|
-
maxNumBlocks?: int,
|
|
137
|
-
maxNumInstructions?: int,
|
|
116
|
+
programs
|
|
138
117
|
}
|
|
139
118
|
}
|
|
140
119
|
|
|
@@ -146,37 +125,6 @@ module ResponseTypes = {
|
|
|
146
125
|
blockhash: string,
|
|
147
126
|
blockTime: Null.t<int>,
|
|
148
127
|
}
|
|
149
|
-
|
|
150
|
-
type instructionCall = {
|
|
151
|
-
slot: int,
|
|
152
|
-
transactionIndex: int,
|
|
153
|
-
instructionAddress: array<int>,
|
|
154
|
-
/** The invoked program's account. */
|
|
155
|
-
executingAccount: string,
|
|
156
|
-
accountArguments: array<string>,
|
|
157
|
-
data: string,
|
|
158
|
-
d1: Null.t<string>,
|
|
159
|
-
d2: Null.t<string>,
|
|
160
|
-
d4: Null.t<string>,
|
|
161
|
-
d8: Null.t<string>,
|
|
162
|
-
isInner: bool,
|
|
163
|
-
/** Success of the parent transaction, not of this invocation. */
|
|
164
|
-
txSuccess: bool,
|
|
165
|
-
/** Per-invocation failure reason (e.g. "custom program error: 0x1"). */
|
|
166
|
-
error: Null.t<string>,
|
|
167
|
-
computeUnitsConsumed: Null.t<bigint>,
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
type queryResponseData = {
|
|
171
|
-
blocks: array<block>,
|
|
172
|
-
instructionCalls: array<instructionCall>,
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
type queryResponse = {
|
|
176
|
-
nextSlot: int,
|
|
177
|
-
responseBytes: int,
|
|
178
|
-
data: queryResponseData,
|
|
179
|
-
}
|
|
180
128
|
}
|
|
181
129
|
|
|
182
130
|
module EventItems = {
|
|
@@ -213,12 +161,12 @@ module EventItems = {
|
|
|
213
161
|
path: array<int>,
|
|
214
162
|
programId: string,
|
|
215
163
|
accounts: array<string>,
|
|
216
|
-
data:
|
|
164
|
+
data: Uint8Array.t,
|
|
217
165
|
isInner: bool,
|
|
218
|
-
// Borsh-decoded args as a
|
|
219
|
-
//
|
|
220
|
-
//
|
|
221
|
-
|
|
166
|
+
// Borsh-decoded args as a JS value tree (wide integers as bigint).
|
|
167
|
+
// Non-null exactly when the routed registration selected `args`: an
|
|
168
|
+
// instruction its layout rejects is dropped in Rust.
|
|
169
|
+
args: Null.t<unknown>,
|
|
222
170
|
// Non-null only when the routed registration selected `fields.log`.
|
|
223
171
|
logs: Null.t<array<log>>,
|
|
224
172
|
}
|
|
@@ -234,16 +182,11 @@ module EventItems = {
|
|
|
234
182
|
}
|
|
235
183
|
}
|
|
236
184
|
|
|
237
|
-
type query = QueryTypes.query
|
|
238
|
-
type queryResponse = ResponseTypes.queryResponse
|
|
239
|
-
|
|
240
185
|
type t = {
|
|
241
186
|
getHeight: unit => promise<int>,
|
|
242
187
|
// Block-hash query construction, pagination, and cursor-backed skipped-slot
|
|
243
188
|
// coverage live in Rust.
|
|
244
189
|
getBlockHashes: (~blockNumbers: array<int>) => promise<(BlockStore.t, array<RequestStat.t>)>,
|
|
245
|
-
// Block-data range queries only; the store pages it returns are empty.
|
|
246
|
-
get: (~query: query) => promise<(queryResponse, TransactionStore.t, BlockStore.t)>,
|
|
247
190
|
// Returns the routed items plus pages of raw transactions and blocks (kept
|
|
248
191
|
// in Rust), keyed by (slot, transactionIndex) / slot, materialised at batch
|
|
249
192
|
// prep.
|
|
@@ -258,7 +201,7 @@ external classFromConfig: (
|
|
|
258
201
|
Core.svmHyperSyncClientCtor,
|
|
259
202
|
cfg,
|
|
260
203
|
string,
|
|
261
|
-
array<Registration.
|
|
204
|
+
array<Registration.program>,
|
|
262
205
|
AddressStore.t,
|
|
263
206
|
) => t = "fromConfig"
|
|
264
207
|
|
|
@@ -268,7 +211,7 @@ let make = (
|
|
|
268
211
|
~httpReqTimeoutMillis=?,
|
|
269
212
|
~retryBaseMs=?,
|
|
270
213
|
~retryCeilingMs=?,
|
|
271
|
-
~
|
|
214
|
+
~programs=[],
|
|
272
215
|
~addressStore,
|
|
273
216
|
) => {
|
|
274
217
|
let envioVersion = Utils.EnvioPackage.value.version
|
|
@@ -281,7 +224,7 @@ let make = (
|
|
|
281
224
|
?retryCeilingMs,
|
|
282
225
|
},
|
|
283
226
|
`hyperindex/${envioVersion}`,
|
|
284
|
-
|
|
227
|
+
programs,
|
|
285
228
|
addressStore,
|
|
286
229
|
)
|
|
287
230
|
}
|
|
@@ -4,24 +4,55 @@ import * as Core from "../Core.res.mjs";
|
|
|
4
4
|
import * as Utils from "../Utils.res.mjs";
|
|
5
5
|
|
|
6
6
|
function fromOnEventRegistrations(onEventRegistrations) {
|
|
7
|
-
|
|
7
|
+
let programs = [];
|
|
8
|
+
onEventRegistrations.forEach(reg => {
|
|
8
9
|
let eventConfig = reg.eventConfig;
|
|
9
|
-
let
|
|
10
|
-
let
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
10
|
+
let program = programs.find(p => p.name === eventConfig.contractName);
|
|
11
|
+
let program$1;
|
|
12
|
+
if (program !== undefined) {
|
|
13
|
+
program$1 = program;
|
|
14
|
+
} else {
|
|
15
|
+
let definedTypes = eventConfig.definedTypes;
|
|
16
|
+
let tmp;
|
|
17
|
+
tmp = definedTypes === null ? undefined : JSON.stringify(definedTypes);
|
|
18
|
+
let program_name = eventConfig.contractName;
|
|
19
|
+
let program_programId = eventConfig.programId;
|
|
20
|
+
let program_instructions = [];
|
|
21
|
+
let program$2 = {
|
|
22
|
+
name: program_name,
|
|
23
|
+
programId: program_programId,
|
|
24
|
+
definedTypesJson: tmp,
|
|
25
|
+
instructions: program_instructions
|
|
26
|
+
};
|
|
27
|
+
programs.push(program$2);
|
|
28
|
+
program$1 = program$2;
|
|
29
|
+
}
|
|
30
|
+
let instruction = program$1.instructions.find(i => i.name === eventConfig.name);
|
|
31
|
+
let instruction$1;
|
|
32
|
+
if (instruction !== undefined) {
|
|
33
|
+
instruction$1 = instruction;
|
|
34
|
+
} else {
|
|
35
|
+
let args = eventConfig.args;
|
|
36
|
+
let tmp$1;
|
|
37
|
+
tmp$1 = args === null ? undefined : JSON.stringify(args);
|
|
38
|
+
let instruction_name = eventConfig.name;
|
|
39
|
+
let instruction_discriminator = eventConfig.discriminator;
|
|
40
|
+
let instruction_registrations = [];
|
|
41
|
+
let instruction$2 = {
|
|
42
|
+
name: instruction_name,
|
|
43
|
+
discriminator: instruction_discriminator,
|
|
44
|
+
argsJson: tmp$1,
|
|
45
|
+
registrations: instruction_registrations
|
|
46
|
+
};
|
|
47
|
+
program$1.instructions.push(instruction$2);
|
|
48
|
+
instruction$1 = instruction$2;
|
|
49
|
+
}
|
|
50
|
+
instruction$1.registrations.push({
|
|
16
51
|
index: reg.index,
|
|
17
|
-
instructionName: eventConfig.name,
|
|
18
|
-
contractName: eventConfig.contractName,
|
|
19
|
-
programId: eventConfig.programId,
|
|
20
52
|
isWildcard: reg.isWildcard,
|
|
21
53
|
startBlock: reg.startBlock,
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
accountFilters: eventConfig.accountFilters.map(group => group.map(filter => ({
|
|
54
|
+
isInner: reg.isInner,
|
|
55
|
+
accountFilters: reg.accountFilters.map(group => group.map(filter => ({
|
|
25
56
|
position: filter.position,
|
|
26
57
|
values: filter.values
|
|
27
58
|
}))),
|
|
@@ -29,26 +60,22 @@ function fromOnEventRegistrations(onEventRegistrations) {
|
|
|
29
60
|
blockFields: Array.from(reg.fieldSelection.blockFields),
|
|
30
61
|
accountActivityFields: Array.from(reg.fieldSelection.accountActivityFields),
|
|
31
62
|
logFields: Array.from(reg.fieldSelection.logFields),
|
|
32
|
-
instructionFields: Array.from(reg.fieldSelection.instructionFields)
|
|
33
|
-
|
|
34
|
-
argsJson: tmp,
|
|
35
|
-
definedTypesJson: tmp$1
|
|
36
|
-
};
|
|
63
|
+
instructionFields: Array.from(reg.fieldSelection.instructionFields)
|
|
64
|
+
});
|
|
37
65
|
});
|
|
66
|
+
return programs;
|
|
38
67
|
}
|
|
39
68
|
|
|
40
69
|
let Registration = {
|
|
41
70
|
fromOnEventRegistrations: fromOnEventRegistrations
|
|
42
71
|
};
|
|
43
72
|
|
|
44
|
-
let QueryTypes = {};
|
|
45
|
-
|
|
46
73
|
let ResponseTypes = {};
|
|
47
74
|
|
|
48
75
|
let EventItems = {};
|
|
49
76
|
|
|
50
|
-
function make(url, apiToken, httpReqTimeoutMillis, retryBaseMs, retryCeilingMs,
|
|
51
|
-
let
|
|
77
|
+
function make(url, apiToken, httpReqTimeoutMillis, retryBaseMs, retryCeilingMs, programsOpt, addressStore) {
|
|
78
|
+
let programs = programsOpt !== undefined ? programsOpt : [];
|
|
52
79
|
let envioVersion = Utils.EnvioPackage.value.version;
|
|
53
80
|
return Core.getAddon().SvmHyperSyncClient.fromConfig({
|
|
54
81
|
url: url,
|
|
@@ -56,12 +83,11 @@ function make(url, apiToken, httpReqTimeoutMillis, retryBaseMs, retryCeilingMs,
|
|
|
56
83
|
httpReqTimeoutMillis: httpReqTimeoutMillis,
|
|
57
84
|
retryBaseMs: retryBaseMs,
|
|
58
85
|
retryCeilingMs: retryCeilingMs
|
|
59
|
-
}, `hyperindex/` + envioVersion,
|
|
86
|
+
}, `hyperindex/` + envioVersion, programs, addressStore);
|
|
60
87
|
}
|
|
61
88
|
|
|
62
89
|
export {
|
|
63
90
|
Registration,
|
|
64
|
-
QueryTypes,
|
|
65
91
|
ResponseTypes,
|
|
66
92
|
EventItems,
|
|
67
93
|
make,
|
|
@@ -10,10 +10,9 @@ type options = {
|
|
|
10
10
|
addressStore: AddressStore.t,
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
let namedAccounts = (
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
): dict<Envio.svmInstructionAccount> => {
|
|
13
|
+
let namedAccounts = (~idlNames: array<string>, ~accountArguments: array<string>): dict<
|
|
14
|
+
Envio.svmInstructionAccount,
|
|
15
|
+
> => {
|
|
17
16
|
let out = Dict.make()
|
|
18
17
|
idlNames->Array.forEachWithIndex((name, i) =>
|
|
19
18
|
switch accountArguments->Array.get(i) {
|
|
@@ -32,7 +31,10 @@ let namedAccounts = (
|
|
|
32
31
|
out
|
|
33
32
|
}
|
|
34
33
|
|
|
35
|
-
let selectedLog = (
|
|
34
|
+
let selectedLog = (
|
|
35
|
+
log: SvmHyperSyncClient.EventItems.log,
|
|
36
|
+
~logFields: Utils.Set.t<string>,
|
|
37
|
+
): Envio.svmLog => {
|
|
36
38
|
let out = Dict.make()
|
|
37
39
|
if logFields->Utils.Set.has("kind") {
|
|
38
40
|
switch log.kind->Null.toOption {
|
|
@@ -63,9 +65,11 @@ let toSvmInstruction = (
|
|
|
63
65
|
~fieldSelection: Internal.fieldSelection,
|
|
64
66
|
): Envio.svmInstruction => {
|
|
65
67
|
let hasSelection = name => fieldSelection.instructionFields->Utils.Set.has(name)
|
|
68
|
+
// A program-wide registration has no configured discriminator, so the whole
|
|
69
|
+
// data stands in, hex-encoded like a configured one would be.
|
|
66
70
|
let discriminator = switch eventConfig.discriminator {
|
|
67
71
|
| Some(d) => d
|
|
68
|
-
| None => item.data
|
|
72
|
+
| None => "0x" ++ item.data->NodeJs.Buffer.fromUint8Array->NodeJs.Buffer.toHex
|
|
69
73
|
}
|
|
70
74
|
let out = Dict.make()
|
|
71
75
|
out->setField("programName", programName)
|
|
@@ -83,8 +87,9 @@ let toSvmInstruction = (
|
|
|
83
87
|
if hasSelection("isInner") {
|
|
84
88
|
out->setField("isInner", item.isInner)
|
|
85
89
|
}
|
|
86
|
-
|
|
87
|
-
|
|
90
|
+
switch item.args->Null.toOption {
|
|
91
|
+
| Some(args) => out->setField("args", args)
|
|
92
|
+
| None => ()
|
|
88
93
|
}
|
|
89
94
|
if hasSelection("accounts") {
|
|
90
95
|
out->setField(
|
|
@@ -125,9 +130,7 @@ let make = (
|
|
|
125
130
|
~url=endpointUrl,
|
|
126
131
|
~apiToken?,
|
|
127
132
|
~httpReqTimeoutMillis=clientTimeoutMillis,
|
|
128
|
-
~
|
|
129
|
-
onEventRegistrations,
|
|
130
|
-
),
|
|
133
|
+
~programs=SvmHyperSyncClient.Registration.fromOnEventRegistrations(onEventRegistrations),
|
|
131
134
|
~addressStore,
|
|
132
135
|
)
|
|
133
136
|
|
|
@@ -198,7 +201,7 @@ let make = (
|
|
|
198
201
|
~fieldSelection=onEventRegistration.fieldSelection,
|
|
199
202
|
)
|
|
200
203
|
Internal.Event({
|
|
201
|
-
onEventRegistration,
|
|
204
|
+
onEventRegistration: (onEventRegistration :> Internal.onEventRegistration),
|
|
202
205
|
chainId,
|
|
203
206
|
blockNumber: item.slot,
|
|
204
207
|
// A slot orders by `(transactionIndex, path)` — the
|
|
@@ -236,15 +239,15 @@ let make = (
|
|
|
236
239
|
|
|
237
240
|
// Called through the client rather than passed as a value: the client is a
|
|
238
241
|
// napi class, so a detached method reference loses the instance it belongs to.
|
|
239
|
-
let getBlockHashes = HyperSync.makeGetBlockHashes(
|
|
240
|
-
|
|
242
|
+
let getBlockHashes = HyperSync.makeGetBlockHashes(~query=(~blockNumbers) =>
|
|
243
|
+
client.getBlockHashes(~blockNumbers)
|
|
241
244
|
)
|
|
242
245
|
|
|
243
246
|
{
|
|
244
247
|
name,
|
|
245
248
|
sourceFor: Sync,
|
|
246
249
|
chainId,
|
|
247
|
-
pollingInterval:
|
|
250
|
+
pollingInterval: HyperSync.pollingInterval,
|
|
248
251
|
poweredByHyperSync: true,
|
|
249
252
|
getBlockHashes,
|
|
250
253
|
getHeightOrThrow: async () => {
|
|
@@ -48,7 +48,7 @@ function setField(out, name, value) {
|
|
|
48
48
|
|
|
49
49
|
function toSvmInstruction(item, programName, instructionName, eventConfig, fieldSelection) {
|
|
50
50
|
let d = eventConfig.discriminator;
|
|
51
|
-
let discriminator = d !== undefined ? d : item.data;
|
|
51
|
+
let discriminator = d !== undefined ? d : "0x" + Buffer.from(item.data).toString("hex");
|
|
52
52
|
let out = {};
|
|
53
53
|
setField(out, "programName", programName);
|
|
54
54
|
setField(out, "instructionName", instructionName);
|
|
@@ -65,8 +65,9 @@ function toSvmInstruction(item, programName, instructionName, eventConfig, field
|
|
|
65
65
|
if (fieldSelection.instructionFields.has("isInner")) {
|
|
66
66
|
setField(out, "isInner", item.isInner);
|
|
67
67
|
}
|
|
68
|
-
|
|
69
|
-
|
|
68
|
+
let args = item.args;
|
|
69
|
+
if (args !== null) {
|
|
70
|
+
setField(out, "args", args);
|
|
70
71
|
}
|
|
71
72
|
if (fieldSelection.instructionFields.has("accounts")) {
|
|
72
73
|
setField(out, "accounts", namedAccounts(eventConfig.accounts, item.accounts));
|
|
@@ -163,7 +164,7 @@ function make(param) {
|
|
|
163
164
|
sourceFor: "Sync",
|
|
164
165
|
chainId: chainId,
|
|
165
166
|
poweredByHyperSync: true,
|
|
166
|
-
pollingInterval:
|
|
167
|
+
pollingInterval: HyperSync.pollingInterval,
|
|
167
168
|
getBlockHashes: getBlockHashes,
|
|
168
169
|
getHeightOrThrow: async () => {
|
|
169
170
|
let timer = Performance.now();
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A block handler's `block.timestamp`.
|
|
3
|
+
*
|
|
4
|
+
* envio hands a block handler only the block number, so the timestamp has to be
|
|
5
|
+
* fetched. Every handler invocation in a batch asks within the same microtask,
|
|
6
|
+
* so the requests are collected and answered by a single HyperSync range query
|
|
7
|
+
* rather than one round trip per block. An RPC endpoint, if one is configured,
|
|
8
|
+
* is the fallback for whatever HyperSync couldn't answer.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { createPublicClient, fallback, http } from "viem";
|
|
12
|
+
|
|
13
|
+
const API_TOKEN_ENV_VAR = "ENVIO_API_TOKEN";
|
|
14
|
+
|
|
15
|
+
const hypersyncUrl = (chainId: number) => `https://${chainId}.hypersync.xyz/query`;
|
|
16
|
+
|
|
17
|
+
type Waiter = {
|
|
18
|
+
resolve: (timestamp: bigint) => void;
|
|
19
|
+
reject: (error: unknown) => void;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/** Blocks asked for since the last flush, by chain then block number. */
|
|
23
|
+
let pending = new Map<number, Map<number, Waiter[]>>();
|
|
24
|
+
let flushScheduled = false;
|
|
25
|
+
|
|
26
|
+
let rpcUrls: string[] = [];
|
|
27
|
+
let client: ReturnType<typeof createPublicClient> | null = null;
|
|
28
|
+
|
|
29
|
+
export function configureBlockTimestamps(urls: string[]) {
|
|
30
|
+
rpcUrls = urls;
|
|
31
|
+
client = null;
|
|
32
|
+
pending = new Map();
|
|
33
|
+
flushScheduled = false;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function requestBlockTimestamp(chainId: number, blockNumber: number): Promise<bigint> {
|
|
37
|
+
return new Promise((resolve, reject) => {
|
|
38
|
+
let blocks = pending.get(chainId);
|
|
39
|
+
if (!blocks) {
|
|
40
|
+
blocks = new Map();
|
|
41
|
+
pending.set(chainId, blocks);
|
|
42
|
+
}
|
|
43
|
+
const waiters = blocks.get(blockNumber);
|
|
44
|
+
if (waiters) {
|
|
45
|
+
waiters.push({ resolve, reject });
|
|
46
|
+
} else {
|
|
47
|
+
blocks.set(blockNumber, [{ resolve, reject }]);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (!flushScheduled) {
|
|
51
|
+
flushScheduled = true;
|
|
52
|
+
queueMicrotask(() => {
|
|
53
|
+
flushScheduled = false;
|
|
54
|
+
const collected = pending;
|
|
55
|
+
pending = new Map();
|
|
56
|
+
for (const [chain, blocksForChain] of collected) {
|
|
57
|
+
void answer(chain, blocksForChain);
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async function answer(chainId: number, blocks: Map<number, Waiter[]>) {
|
|
65
|
+
let timestamps = new Map<number, bigint>();
|
|
66
|
+
|
|
67
|
+
// A batch can hold thousands of blocks, and one argument per block would
|
|
68
|
+
// overflow the call stack inside the try, where it would read as a HyperSync
|
|
69
|
+
// failure and fall back to one round trip per block.
|
|
70
|
+
let lowest = Infinity;
|
|
71
|
+
let highest = -Infinity;
|
|
72
|
+
for (const blockNumber of blocks.keys()) {
|
|
73
|
+
if (blockNumber < lowest) lowest = blockNumber;
|
|
74
|
+
if (blockNumber > highest) highest = blockNumber;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
timestamps = await fromHyperSync(chainId, lowest, highest);
|
|
79
|
+
} catch (error) {
|
|
80
|
+
if (rpcUrls.length === 0) {
|
|
81
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
82
|
+
for (const waiters of blocks.values()) {
|
|
83
|
+
for (const waiter of waiters) {
|
|
84
|
+
waiter.reject(
|
|
85
|
+
new Error(
|
|
86
|
+
`Envio Subgraph couldn't read a block timestamp from HyperSync: ${message}\n` +
|
|
87
|
+
`Set ${API_TOKEN_ENV_VAR} in .env or the environment — create one at\n` +
|
|
88
|
+
`https://envio.dev/app/api-tokens — or set ENVIO_SUBGRAPH_RPC to fall back to RPC.`,
|
|
89
|
+
),
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
for (const [blockNumber, waiters] of blocks) {
|
|
98
|
+
const known = timestamps.get(blockNumber);
|
|
99
|
+
if (known !== undefined) {
|
|
100
|
+
for (const waiter of waiters) waiter.resolve(known);
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
try {
|
|
104
|
+
const timestamp = await fromRpc(blockNumber);
|
|
105
|
+
for (const waiter of waiters) waiter.resolve(timestamp);
|
|
106
|
+
} catch (error) {
|
|
107
|
+
for (const waiter of waiters) waiter.reject(error);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
type HyperSyncResponse = {
|
|
113
|
+
data: { blocks?: { number: number; timestamp: string }[] }[];
|
|
114
|
+
next_block: number;
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
async function fromHyperSync(
|
|
118
|
+
chainId: number,
|
|
119
|
+
fromBlock: number,
|
|
120
|
+
toBlock: number,
|
|
121
|
+
): Promise<Map<number, bigint>> {
|
|
122
|
+
const token = process.env[API_TOKEN_ENV_VAR];
|
|
123
|
+
const timestamps = new Map<number, bigint>();
|
|
124
|
+
|
|
125
|
+
let cursor = fromBlock;
|
|
126
|
+
// HyperSync answers as much of the range as one response holds and points at
|
|
127
|
+
// where to resume, so a wide range takes more than one round trip.
|
|
128
|
+
while (cursor <= toBlock) {
|
|
129
|
+
const response = await fetch(hypersyncUrl(chainId), {
|
|
130
|
+
method: "POST",
|
|
131
|
+
headers: {
|
|
132
|
+
"content-type": "application/json",
|
|
133
|
+
...(token ? { authorization: `Bearer ${token}` } : {}),
|
|
134
|
+
},
|
|
135
|
+
body: JSON.stringify({
|
|
136
|
+
from_block: cursor,
|
|
137
|
+
to_block: toBlock + 1,
|
|
138
|
+
include_all_blocks: true,
|
|
139
|
+
field_selection: { block: ["number", "timestamp"] },
|
|
140
|
+
}),
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
if (!response.ok) {
|
|
144
|
+
throw new Error(`HyperSync returned ${response.status} ${await response.text()}`);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const body = (await response.json()) as HyperSyncResponse;
|
|
148
|
+
for (const page of body.data ?? []) {
|
|
149
|
+
for (const block of page.blocks ?? []) {
|
|
150
|
+
timestamps.set(block.number, BigInt(block.timestamp));
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (!body.next_block || body.next_block <= cursor) {
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
cursor = body.next_block;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return timestamps;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
async function fromRpc(blockNumber: number): Promise<bigint> {
|
|
164
|
+
if (rpcUrls.length === 0) {
|
|
165
|
+
throw new Error(
|
|
166
|
+
`Envio Subgraph couldn't read the timestamp of block ${blockNumber}: HyperSync didn't\n` +
|
|
167
|
+
`return it, and no RPC fallback is configured. Set ENVIO_SUBGRAPH_RPC in .env or the\n` +
|
|
168
|
+
`environment.`,
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
client ??= createPublicClient({
|
|
172
|
+
transport: fallback(rpcUrls.map((url) => http(url, { retryCount: 0 }))),
|
|
173
|
+
});
|
|
174
|
+
const block = await client.getBlock({ blockNumber: BigInt(blockNumber) });
|
|
175
|
+
return block.timestamp;
|
|
176
|
+
}
|