envio 3.10.0-subgraph → 3.11.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 (76) hide show
  1. package/evm.schema.json +21 -4
  2. package/fuel.schema.json +21 -4
  3. package/index.d.ts +1 -1
  4. package/package.json +6 -6
  5. package/src/ChainState.res +9 -73
  6. package/src/ChainState.res.mjs +6 -58
  7. package/src/Config.res +73 -27
  8. package/src/Config.res.mjs +71 -21
  9. package/src/Core.res +0 -15
  10. package/src/Core.res.mjs +0 -11
  11. package/src/EventConfigBuilder.res +14 -10
  12. package/src/EventConfigBuilder.res.mjs +6 -4
  13. package/src/EventProcessing.res +6 -6
  14. package/src/EventProcessing.res.mjs +6 -8
  15. package/src/HandlerLoader.res +8 -20
  16. package/src/HandlerLoader.res.mjs +2 -11
  17. package/src/HandlerRegister.res +12 -11
  18. package/src/HandlerRegister.res.mjs +6 -5
  19. package/src/Internal.res +20 -4
  20. package/src/Internal.res.mjs +9 -0
  21. package/src/Main.res +8 -1
  22. package/src/Main.res.mjs +3 -3
  23. package/src/Persistence.res +11 -1
  24. package/src/Persistence.res.mjs +6 -2
  25. package/src/PgStorage.res +1 -1
  26. package/src/PgStorage.res.mjs +1 -1
  27. package/src/SimulateItems.res +13 -6
  28. package/src/SimulateItems.res.mjs +7 -6
  29. package/src/TestIndexer.res +6 -5
  30. package/src/TestIndexer.res.mjs +5 -4
  31. package/src/UserContext.res +51 -358
  32. package/src/UserContext.res.mjs +35 -271
  33. package/src/db/InternalTable.res +1 -1
  34. package/src/db/InternalTable.res.mjs +2 -1
  35. package/src/sources/ChainSources.res +72 -0
  36. package/src/sources/ChainSources.res.mjs +55 -0
  37. package/src/sources/EvmHyperSyncSource.res +10 -22
  38. package/src/sources/EvmHyperSyncSource.res.mjs +9 -41
  39. package/src/sources/FuelHyperSyncSource.res +10 -18
  40. package/src/sources/FuelHyperSyncSource.res.mjs +8 -38
  41. package/src/sources/HyperSync.res +31 -0
  42. package/src/sources/HyperSync.res.mjs +31 -0
  43. package/src/sources/HyperSync.resi +16 -0
  44. package/src/sources/StartBlockResolver.res +173 -0
  45. package/src/sources/StartBlockResolver.res.mjs +134 -0
  46. package/src/sources/Svm.res +0 -51
  47. package/src/sources/Svm.res.mjs +0 -46
  48. package/src/sources/SvmHyperSyncSource.res +20 -9
  49. package/src/sources/SvmHyperSyncSource.res.mjs +34 -12
  50. package/svm.schema.json +78 -63
  51. package/src/subgraph/blocks.ts +0 -176
  52. package/src/subgraph/calls.ts +0 -217
  53. package/src/subgraph/conformance.ts +0 -102
  54. package/src/subgraph/division.ts +0 -133
  55. package/src/subgraph/errors.ts +0 -90
  56. package/src/subgraph/graph-ts-types/VERSION +0 -2
  57. package/src/subgraph/graph-ts-types/chain/arweave.d.ts +0 -70
  58. package/src/subgraph/graph-ts-types/chain/cosmos.d.ts +0 -327
  59. package/src/subgraph/graph-ts-types/chain/ethereum.d.ts +0 -233
  60. package/src/subgraph/graph-ts-types/chain/near.d.ts +0 -253
  61. package/src/subgraph/graph-ts-types/chain/starknet.d.ts +0 -32
  62. package/src/subgraph/graph-ts-types/common/collections.d.ts +0 -136
  63. package/src/subgraph/graph-ts-types/common/conversion.d.ts +0 -11
  64. package/src/subgraph/graph-ts-types/common/datasource.d.ts +0 -30
  65. package/src/subgraph/graph-ts-types/common/eager-offset.d.ts +0 -0
  66. package/src/subgraph/graph-ts-types/common/json.d.ts +0 -17
  67. package/src/subgraph/graph-ts-types/common/numbers.d.ts +0 -120
  68. package/src/subgraph/graph-ts-types/common/value.d.ts +0 -120
  69. package/src/subgraph/graph-ts-types/common/yaml.d.ts +0 -90
  70. package/src/subgraph/graph-ts-types/global/global.d.ts +0 -194
  71. package/src/subgraph/graph-ts-types/helper-functions.d.ts +0 -22
  72. package/src/subgraph/graph-ts-types/index.d.ts +0 -102
  73. package/src/subgraph/graph-ts.ts +0 -1948
  74. package/src/subgraph/hosts.ts +0 -148
  75. package/src/subgraph/runtime.ts +0 -863
  76. package/src/subgraph/scope.ts +0 -66
package/evm.schema.json CHANGED
@@ -494,10 +494,8 @@
494
494
  "minimum": 0
495
495
  },
496
496
  "start_block": {
497
- "description": "The block at which the indexer should start ingesting data",
498
- "type": "integer",
499
- "format": "uint64",
500
- "minimum": 0
497
+ "description": "The block at which the indexer should start ingesting data, or \"latest\" to start from the chain's current head block when the indexer is first deployed. Once resolved, the concrete block is persisted and reused every time the indexer resumes normally (for example recovering from a crash), so downtime is backfilled instead of skipped. Running `envio start`/`dev` with -r (--restart) resets this like any other config change: \"latest\" resolves again, against the head at that time.",
498
+ "$ref": "#/$defs/StartBlock"
501
499
  },
502
500
  "end_block": {
503
501
  "description": "The block at which the indexer should terminate.",
@@ -685,6 +683,25 @@
685
683
  "url"
686
684
  ]
687
685
  },
686
+ "StartBlock": {
687
+ "description": "A chain's configured start block: either a concrete block number or the\nliteral \"latest\". Config parsing never touches the network, so `Latest`\nstays unresolved here — it's resolved once at runtime, right before the\nindexer's first-ever persisted state is written, and never re-resolved\non a normal resume (see packages/envio/src/sources/StartBlockResolver.res).\nNote: this repo's `-r`/`--restart` CLI flag wipes the DB and re-deploys\nfrom scratch, so it re-resolves \"latest\" too — \"resume\" here means the\nopposite: recovering from a crash or process restart without `-r`.",
688
+ "anyOf": [
689
+ {
690
+ "type": "integer",
691
+ "format": "uint64",
692
+ "minimum": 0
693
+ },
694
+ {
695
+ "$ref": "#/$defs/StartBlockTag"
696
+ }
697
+ ]
698
+ },
699
+ "StartBlockTag": {
700
+ "type": "string",
701
+ "enum": [
702
+ "latest"
703
+ ]
704
+ },
688
705
  "ChainContract": {
689
706
  "type": "object",
690
707
  "properties": {
package/fuel.schema.json CHANGED
@@ -328,10 +328,8 @@
328
328
  ]
329
329
  },
330
330
  "start_block": {
331
- "description": "The block at which the indexer should start ingesting data",
332
- "type": "integer",
333
- "format": "uint64",
334
- "minimum": 0
331
+ "description": "The block at which the indexer should start ingesting data, or \"latest\" to start from the chain's current head block when the indexer is first deployed. Once resolved, the concrete block is persisted and reused every time the indexer resumes normally (for example recovering from a crash), so downtime is backfilled instead of skipped. Running `envio start`/`dev` with -r (--restart) resets this like any other config change: \"latest\" resolves again, against the head at that time.",
332
+ "$ref": "#/$defs/StartBlock"
335
333
  },
336
334
  "end_block": {
337
335
  "description": "The block at which the indexer should terminate.",
@@ -388,6 +386,25 @@
388
386
  "start_block"
389
387
  ]
390
388
  },
389
+ "StartBlock": {
390
+ "description": "A chain's configured start block: either a concrete block number or the\nliteral \"latest\". Config parsing never touches the network, so `Latest`\nstays unresolved here — it's resolved once at runtime, right before the\nindexer's first-ever persisted state is written, and never re-resolved\non a normal resume (see packages/envio/src/sources/StartBlockResolver.res).\nNote: this repo's `-r`/`--restart` CLI flag wipes the DB and re-deploys\nfrom scratch, so it re-resolves \"latest\" too — \"resume\" here means the\nopposite: recovering from a crash or process restart without `-r`.",
391
+ "anyOf": [
392
+ {
393
+ "type": "integer",
394
+ "format": "uint64",
395
+ "minimum": 0
396
+ },
397
+ {
398
+ "$ref": "#/$defs/StartBlockTag"
399
+ }
400
+ ]
401
+ },
402
+ "StartBlockTag": {
403
+ "type": "string",
404
+ "enum": [
405
+ "latest"
406
+ ]
407
+ },
391
408
  "HyperfuelConfig": {
392
409
  "type": "object",
393
410
  "properties": {
package/index.d.ts CHANGED
@@ -1377,7 +1377,7 @@ type SvmNamedAccounts<
1377
1377
  Acc extends Readonly<Record<string, unknown>>,
1378
1378
  Fields extends SvmFieldsSelection,
1379
1379
  > = {
1380
- readonly [K in keyof Acc & string]: SvmInstructionAccount<Fields, K>;
1380
+ readonly [K in keyof Acc]: SvmInstructionAccount<Fields, K & string>;
1381
1381
  };
1382
1382
 
1383
1383
  /** The parent transaction of a {@link SvmInstruction}, narrowed to the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "envio",
3
- "version": "3.10.0-subgraph",
3
+ "version": "3.11.0",
4
4
  "type": "module",
5
5
  "description": "A latency and sync speed optimized, developer friendly blockchain data indexer.",
6
6
  "bin": "./bin.mjs",
@@ -68,10 +68,10 @@
68
68
  "tsx": "4.21.0"
69
69
  },
70
70
  "optionalDependencies": {
71
- "envio-linux-x64": "3.10.0-subgraph",
72
- "envio-linux-x64-musl": "3.10.0-subgraph",
73
- "envio-linux-arm64": "3.10.0-subgraph",
74
- "envio-darwin-x64": "3.10.0-subgraph",
75
- "envio-darwin-arm64": "3.10.0-subgraph"
71
+ "envio-linux-x64": "3.11.0",
72
+ "envio-linux-x64-musl": "3.11.0",
73
+ "envio-linux-arm64": "3.11.0",
74
+ "envio-darwin-x64": "3.11.0",
75
+ "envio-darwin-arm64": "3.11.0"
76
76
  }
77
77
  }
@@ -203,7 +203,9 @@ let makeInternal = (
203
203
 
204
204
  chainConfig.contracts->Array.forEach(contract => {
205
205
  switch contract.startBlock {
206
- | Some(startBlock) if startBlock < chainConfig.startBlock =>
206
+ // Against the resolved `~startBlock`, which came from storage:
207
+ // `chainConfig.startBlock` still says whatever config.yaml said.
208
+ | Some(contractStartBlock) if contractStartBlock < startBlock =>
207
209
  JsError.throwWithMessage(
208
210
  `The start block for contract "${contract.name}" is less than the chain start block. This is not supported yet.`,
209
211
  )
@@ -262,78 +264,12 @@ let makeInternal = (
262
264
  })
263
265
 
264
266
  // Create sources lazily here - this is where API token validation happens
265
- let chainId = chainConfig.id
266
- let sources = switch chainConfig.sourceConfig {
267
- | Config.EvmSourceConfig({hypersync, rpcs}) =>
268
- let evmRpcs: array<EvmChain.rpc> = rpcs->Array.map((rpc): EvmChain.rpc => {
269
- let syncConfig = rpc.syncConfig
270
- let ws = rpc.ws
271
- let headers = rpc.headers
272
- {
273
- url: rpc.url,
274
- sourceFor: rpc.sourceFor,
275
- ?syncConfig,
276
- ?ws,
277
- ?headers,
278
- }
279
- })
280
- EvmChain.makeSources(
281
- ~chainId,
282
- ~onEventRegistrations=onEventRegistrations->(
283
- Utils.magic: array<Internal.onEventRegistration> => array<Internal.evmOnEventRegistration>
284
- ),
285
- ~hyperSync=hypersync,
286
- ~rpcs=evmRpcs,
287
- ~lowercaseAddresses,
288
- ~addressStore,
289
- )
290
- | Config.FuelSourceConfig({hypersync}) => [
291
- FuelHyperSyncSource.make({
292
- chainId,
293
- endpointUrl: hypersync,
294
- apiToken: Env.envioApiToken,
295
- onEventRegistrations,
296
- addressStore,
297
- }),
298
- ]
299
- | Config.SvmSourceConfig({hypersync, rpc}) =>
300
- switch (hypersync, rpc) {
301
- | (None, None) =>
302
- JsError.throwWithMessage(`Chain ${chainId->ChainId.toString} has no SVM data source`)
303
- | (None, Some(rpc)) => [Svm.makeRPCSource(~chainId, ~rpc)]
304
- | (Some(hypersyncUrl), _) =>
305
- // HyperSync drives instruction sync. A configured RPC is ignored for now
306
- // (RPC fallback isn't wired up yet).
307
- let apiToken = Env.envioApiToken
308
- [
309
- SvmHyperSyncSource.make({
310
- chainId,
311
- endpointUrl: hypersyncUrl,
312
- apiToken,
313
- onEventRegistrations: onEventRegistrations->(
314
- Utils.magic: array<Internal.onEventRegistration> => array<
315
- Internal.svmOnEventRegistration,
316
- >
317
- ),
318
- clientTimeoutMillis: Env.hyperSyncClientTimeoutMillis,
319
- addressStore,
320
- }),
321
- ]
322
- }
323
- | Config.SimulateSourceConfig({items, endBlock, ?transactionStore, ?blockStore}) => [
324
- SimulateSource.make(
325
- ~items,
326
- ~endBlock,
327
- ~chainId,
328
- ~addressStore,
329
- ~ecosystem=config.ecosystem.name,
330
- ~transactionStore,
331
- ~blockStore,
332
- ),
333
- ]
334
- // For tests: use ready-to-use sources directly
335
- | Config.CustomSources(sources) => sources
336
- }
267
+ let sources = ChainSources.make(
268
+ ~chainConfig,
269
+ ~onEventRegistrations,
270
+ ~addressStore,
271
+ ~lowercaseAddresses,
272
+ )
337
273
 
338
274
  let blockStore = BlockStore.make(
339
275
  ~ecosystem=config.ecosystem.name,
@@ -1,32 +1,28 @@
1
1
  // Generated by ReScript, PLEASE EDIT WITH CARE
2
2
 
3
- import * as Env from "./Env.res.mjs";
4
3
  import * as Svm from "./sources/Svm.res.mjs";
5
4
  import * as Core from "./Core.res.mjs";
6
5
  import * as Batch from "./Batch.res.mjs";
7
6
  import * as Utils from "./Utils.res.mjs";
8
7
  import * as ChainId from "./ChainId.res.mjs";
9
8
  import * as Logging from "./Logging.res.mjs";
10
- import * as EvmChain from "./sources/EvmChain.res.mjs";
11
9
  import * as FieldMask from "./sources/FieldMask.res.mjs";
12
10
  import * as BlockStore from "./sources/BlockStore.res.mjs";
13
11
  import * as FetchState from "./FetchState.res.mjs";
14
12
  import * as AddressRows from "./AddressRows.res.mjs";
15
13
  import * as Stdlib_Null from "@rescript/runtime/lib/es6/Stdlib_Null.js";
16
14
  import * as AddressStore from "./sources/AddressStore.res.mjs";
15
+ import * as ChainSources from "./sources/ChainSources.res.mjs";
17
16
  import * as EntityTables from "./EntityTables.res.mjs";
18
17
  import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
19
18
  import * as Primitive_int from "@rescript/runtime/lib/es6/Primitive_int.js";
20
19
  import * as SourceManager from "./sources/SourceManager.res.mjs";
21
20
  import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
22
- import * as SimulateSource from "./sources/SimulateSource.res.mjs";
23
21
  import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
24
22
  import * as ContractMapping from "./ContractMapping.res.mjs";
25
23
  import * as Primitive_float from "@rescript/runtime/lib/es6/Primitive_float.js";
26
24
  import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
27
25
  import * as TransactionStore from "./sources/TransactionStore.res.mjs";
28
- import * as SvmHyperSyncSource from "./sources/SvmHyperSyncSource.res.mjs";
29
- import * as FuelHyperSyncSource from "./sources/FuelHyperSyncSource.res.mjs";
30
26
  import * as SafeCheckpointTracking from "./SafeCheckpointTracking.res.mjs";
31
27
 
32
28
  function configStorageRows(chainConfig, ecosystem, contractMapping) {
@@ -128,8 +124,8 @@ function makeFromDbState(chainConfig, resumedChainState, reorgCheckpoints, isInR
128
124
  });
129
125
  let onEventRegistrations = match.onEventRegistrations;
130
126
  chainConfig.contracts.forEach(contract => {
131
- let startBlock = contract.startBlock;
132
- if (startBlock !== undefined && startBlock < chainConfig.startBlock) {
127
+ let contractStartBlock = contract.startBlock;
128
+ if (contractStartBlock !== undefined && contractStartBlock < startBlock) {
133
129
  return Stdlib_JsError.throwWithMessage(`The start block for contract "` + contract.name + `" is less than the chain start block. This is not supported yet.`);
134
130
  }
135
131
  });
@@ -152,55 +148,7 @@ function makeFromDbState(chainConfig, resumedChainState, reorgCheckpoints, isInR
152
148
  return reorgCheckpoint;
153
149
  }
154
150
  });
155
- let chainId$1 = chainConfig.id;
156
- let sources = chainConfig.sourceConfig;
157
- let sources$1;
158
- switch (sources.TAG) {
159
- case "EvmSourceConfig" :
160
- let evmRpcs = sources.rpcs.map(rpc => {
161
- let syncConfig = rpc.syncConfig;
162
- let ws = rpc.ws;
163
- let headers = rpc.headers;
164
- return {
165
- url: rpc.url,
166
- sourceFor: rpc.sourceFor,
167
- syncConfig: syncConfig,
168
- ws: ws,
169
- headers: headers
170
- };
171
- });
172
- sources$1 = EvmChain.makeSources(chainId$1, onEventRegistrations, sources.hypersync, evmRpcs, lowercaseAddresses, addressStore);
173
- break;
174
- case "FuelSourceConfig" :
175
- sources$1 = [FuelHyperSyncSource.make({
176
- chainId: chainId$1,
177
- endpointUrl: sources.hypersync,
178
- apiToken: Env.envioApiToken,
179
- onEventRegistrations: onEventRegistrations,
180
- addressStore: addressStore
181
- })];
182
- break;
183
- case "SvmSourceConfig" :
184
- let rpc = sources.rpc;
185
- let hypersync = sources.hypersync;
186
- sources$1 = hypersync !== undefined ? [SvmHyperSyncSource.make({
187
- chainId: chainId$1,
188
- endpointUrl: hypersync,
189
- apiToken: Env.envioApiToken,
190
- onEventRegistrations: onEventRegistrations,
191
- clientTimeoutMillis: Env.hyperSyncClientTimeoutMillis,
192
- addressStore: addressStore
193
- })] : (
194
- rpc !== undefined ? [Svm.makeRPCSource(chainId$1, rpc, undefined)] : Stdlib_JsError.throwWithMessage(`Chain ` + ChainId.toString(chainId$1) + ` has no SVM data source`)
195
- );
196
- break;
197
- case "SimulateSourceConfig" :
198
- sources$1 = [SimulateSource.make(sources.items, sources.endBlock, chainId$1, addressStore, config.ecosystem.name, Primitive_option.some(sources.transactionStore), Primitive_option.some(sources.blockStore))];
199
- break;
200
- case "CustomSources" :
201
- sources$1 = sources._0;
202
- break;
203
- }
151
+ let sources = ChainSources.make(chainConfig, onEventRegistrations, addressStore, lowercaseAddresses);
204
152
  let blockStore = BlockStore.make(config.ecosystem.name, !lowercaseAddresses);
205
153
  if (Utils.$$Array.notEmpty(chainReorgCheckpoints)) {
206
154
  let seedPage = BlockStore.fromJs(chainReorgCheckpoints.map(cp => ({
@@ -211,7 +159,7 @@ function makeFromDbState(chainConfig, resumedChainState, reorgCheckpoints, isInR
211
159
  }
212
160
  let firstEventBlock$1 = fetchState.firstEventBlock;
213
161
  let chainDensity = firstEventBlock$1 !== undefined && progressBlockNumber > firstEventBlock$1 && numEventsProcessed > 0 ? numEventsProcessed / (progressBlockNumber - firstEventBlock$1 | 0) : undefined;
214
- return make(chainConfig, fetchState, onEventRegistrations, addressStore, SourceManager.make(sources$1, isRealtime, undefined, undefined, undefined, reducedPollingInterval, undefined, undefined), progressBlockNumber, Primitive_option.some(SafeCheckpointTracking.make(maxReorgDepth, config.shouldRollbackOnReorg, chainReorgCheckpoints)), config.shouldRollbackOnReorg, maxReorgDepth, numEventsProcessed, Primitive_option.some(timestampCaughtUpToHeadOrEndblock), undefined, Primitive_option.some(TransactionStore.make(config.ecosystem.name, !lowercaseAddresses)), Primitive_option.some(chainDensity), Primitive_option.some(blockStore), config.reorgThresholdReadyTolerance, EntityTables.perChain(config.userEntities), logger);
162
+ return make(chainConfig, fetchState, onEventRegistrations, addressStore, SourceManager.make(sources, isRealtime, undefined, undefined, undefined, reducedPollingInterval, undefined, undefined), progressBlockNumber, Primitive_option.some(SafeCheckpointTracking.make(maxReorgDepth, config.shouldRollbackOnReorg, chainReorgCheckpoints)), config.shouldRollbackOnReorg, maxReorgDepth, numEventsProcessed, Primitive_option.some(timestampCaughtUpToHeadOrEndblock), undefined, Primitive_option.some(TransactionStore.make(config.ecosystem.name, !lowercaseAddresses)), Primitive_option.some(chainDensity), Primitive_option.some(blockStore), config.reorgThresholdReadyTolerance, EntityTables.perChain(config.userEntities), logger);
215
163
  }
216
164
 
217
165
  function logger(cs) {
@@ -959,4 +907,4 @@ export {
959
907
  markReady,
960
908
  rollback,
961
909
  }
962
- /* Env Not a pure module */
910
+ /* Svm Not a pure module */
package/src/Config.res CHANGED
@@ -26,10 +26,18 @@ type evmRpcConfig = {
26
26
  headers: option<dict<string>>,
27
27
  }
28
28
 
29
+ // Unboxed so the runtime value is exactly what the public config JSON holds -
30
+ // a number, or the string "latest" - which is why `startBlockSchema` only has
31
+ // to validate it rather than convert it.
32
+ @unboxed
33
+ type startBlock =
34
+ | Block(int)
35
+ | @as("latest") Latest
36
+
29
37
  type sourceConfig =
30
38
  | EvmSourceConfig({hypersync: option<string>, rpcs: array<evmRpcConfig>})
31
39
  | FuelSourceConfig({hypersync: string})
32
- | SvmSourceConfig({hypersync: option<string>, rpc: option<string>})
40
+ | SvmSourceConfig({hypersync: string})
33
41
  // A `simulate` run: the items the test fed in, parsed against the chain's
34
42
  // registrations. The source itself is built with the chain's address store,
35
43
  // like every other source, so it can apply the same gates.
@@ -46,7 +54,11 @@ type chain = {
46
54
  name: string,
47
55
  id: ChainId.t,
48
56
  ecosystem: Ecosystem.name,
49
- startBlock: int,
57
+ // What config.yaml says, never rewritten. Once `Latest` is resolved against
58
+ // the chain's head the block lives in the database
59
+ // (`envio_chains.start_block`), and that is what every consumer past startup
60
+ // reads.
61
+ startBlock: startBlock,
50
62
  endBlock?: int,
51
63
  maxReorgDepth: int,
52
64
  blockLag: int,
@@ -127,9 +139,6 @@ type t = {
127
139
  userEntitiesByName: dict<Internal.entityConfig>,
128
140
  userEntities: array<Internal.entityConfig>,
129
141
  allEnums: array<Table.enumConfig<Table.enum>>,
130
- // Set only in subgraph mode: the translated manifest, which is what makes
131
- // the subgraph runtime take over handler registration.
132
- subgraph: option<JSON.t>,
133
142
  }
134
143
 
135
144
  type rpcSourceFor = | @as("sync") Sync | @as("fallback") Fallback | @as("realtime") Realtime
@@ -160,10 +169,36 @@ let chainContractSchema = S.schema(s =>
160
169
  }
161
170
  )
162
171
 
172
+ // For everything downstream of `StartBlockResolver`, which rewrites `Latest`
173
+ // into the chain's head before storage is initialized. The throw is an
174
+ // invariant check, not a case a user can reach.
175
+ let startBlockOrThrow = (chain: chain) =>
176
+ switch chain.startBlock {
177
+ | Block(startBlock) => startBlock
178
+ | Latest =>
179
+ JsError.throwWithMessage(
180
+ `Chain ${chain.id->ChainId.toString}: the "latest" start block was read before it was resolved. This is a bug in envio - please report it.`,
181
+ )
182
+ }
183
+
184
+ // For the paths that have no chain to read a head from - the test indexer and
185
+ // simulated items - where `Latest` never gets resolved and every simulated
186
+ // block should be in range.
187
+ let startBlockOrZero = (chain: chain) =>
188
+ switch chain.startBlock {
189
+ | Block(startBlock) => startBlock
190
+ | Latest => 0
191
+ }
192
+
193
+ let startBlockSchema = S.union([
194
+ S.int->S.shape(n => Block(n)),
195
+ S.literal("latest")->S.shape(_ => Latest),
196
+ ])
197
+
163
198
  let publicConfigChainSchema = S.schema(s =>
164
199
  {
165
200
  "id": s.matches(ChainId.schema),
166
- "startBlock": s.matches(S.int),
201
+ "startBlock": s.matches(startBlockSchema),
167
202
  "endBlock": s.matches(S.option(S.int)),
168
203
  "maxReorgDepth": s.matches(S.option(S.int)),
169
204
  "blockLag": s.matches(S.option(S.int)),
@@ -171,23 +206,37 @@ let publicConfigChainSchema = S.schema(s =>
171
206
  "hypersync": s.matches(S.option(S.string)),
172
207
  "rpcs": s.matches(S.option(S.array(rpcConfigSchema))),
173
208
  // SVM source config
174
- "rpc": s.matches(S.option(S.string)),
175
209
  // Per-chain contract data (addresses and optional start block)
176
210
  "contracts": s.matches(S.option(S.dict(chainContractSchema))),
177
211
  }
178
212
  )
179
213
 
214
+ type svmAccountSlotItem = {"name": option<string>, "optional": option<bool>}
215
+
216
+ let svmAccountSlotSchema: S.t<svmAccountSlotItem> = S.schema(s =>
217
+ {
218
+ "name": s.matches(S.option(S.string)),
219
+ "optional": s.matches(S.option(S.bool)),
220
+ }
221
+ )
222
+
223
+ let svmAccountSlotFromItem = (slot: svmAccountSlotItem): Internal.svmAccountSlot =>
224
+ switch (slot["name"], slot["optional"]) {
225
+ | (None, _) => Unnamed
226
+ | (Some(name), Some(true)) => Optional(name)
227
+ | (Some(name), _) => Required(name)
228
+ }
229
+
180
230
  let svmEventDescriptorSchema = S.schema(s =>
181
231
  {
182
232
  "discriminator": s.matches(S.option(S.string)),
183
- "accounts": s.matches(S.option(S.array(S.string))),
233
+ "accounts": s.matches(S.option(S.array(svmAccountSlotSchema))),
184
234
  "args": s.matches(S.option(S.json(~validate=false))),
185
235
  }
186
236
  )
187
237
 
188
238
  let svmAbiSchema = S.schema(s =>
189
239
  {
190
- "programId": s.matches(S.string),
191
240
  "definedTypes": s.matches(S.json(~validate=false)),
192
241
  "source": s.matches(S.string),
193
242
  }
@@ -549,7 +598,6 @@ let publicConfigSchema = S.schema(s =>
549
598
  "svm": s.matches(S.option(publicConfigEcosystemSchema)),
550
599
  "enums": s.matches(S.option(S.dict(S.array(S.string)))),
551
600
  "entities": s.matches(S.option(S.array(entityJsonSchema))),
552
- "subgraph": s.matches(S.option(S.json(~validate=false))),
553
601
  }
554
602
  )
555
603
 
@@ -644,7 +692,6 @@ let fromPublic = (publicConfigJson: JSON.t) => {
644
692
  "eventSignatures": array<string>,
645
693
  "events": option<array<_>>,
646
694
  "svmAbi": option<{
647
- "programId": string,
648
695
  "definedTypes": JSON.t,
649
696
  "source": string,
650
697
  }>,
@@ -664,7 +711,6 @@ let fromPublic = (publicConfigJson: JSON.t) => {
664
711
  contractConfig->(
665
712
  Utils.magic: _ => {
666
713
  "svmAbi": option<{
667
- "programId": string,
668
714
  "definedTypes": JSON.t,
669
715
  "source": string,
670
716
  }>,
@@ -740,7 +786,7 @@ let fromPublic = (publicConfigJson: JSON.t) => {
740
786
  Utils.magic: _ => {
741
787
  "svm": option<{
742
788
  "discriminator": option<string>,
743
- "accounts": option<array<string>>,
789
+ "accounts": option<array<svmAccountSlotItem>>,
744
790
  "args": option<JSON.t>,
745
791
  }>,
746
792
  }
@@ -757,7 +803,7 @@ let fromPublic = (publicConfigJson: JSON.t) => {
757
803
  ~instructionName=eventName,
758
804
  ~programId,
759
805
  ~discriminator=svm["discriminator"],
760
- ~accounts=svm["accounts"]->Option.getOr([]),
806
+ ~accounts=svm["accounts"]->Option.getOr([])->Array.map(svmAccountSlotFromItem),
761
807
  ~args=svm["args"]->Option.getOr(JSON.Null),
762
808
  ~definedTypes=svmDefinedTypes,
763
809
  ) :> Internal.eventConfig)
@@ -811,6 +857,15 @@ let fromPublic = (publicConfigJson: JSON.t) => {
811
857
  let contracts =
812
858
  contractDataByName
813
859
  ->Dict.toArray
860
+ // Svm programs are defined once for the project and placed per chain by
861
+ // `program_id`. A program the config left off this chain has nothing to
862
+ // index here, and no dynamic registration can add it later.
863
+ ->Array.filter(((capitalizedName, _)) =>
864
+ switch ecosystemName {
865
+ | Ecosystem.Svm => chainContracts->Dict.get(capitalizedName)->Option.isSome
866
+ | _ => true
867
+ }
868
+ )
814
869
  ->Array.map(((capitalizedName, contractData)) => {
815
870
  let chainContract = chainContracts->Dict.get(capitalizedName)
816
871
  let rawAddresses =
@@ -918,14 +973,11 @@ let fromPublic = (publicConfigJson: JSON.t) => {
918
973
  JsError.throwWithMessage(`Chain ${chainName} is missing hypersync endpoint in config`)
919
974
  }
920
975
  | Ecosystem.Svm =>
921
- let hypersync = publicChainConfig["hypersync"]
922
- let rpc = publicChainConfig["rpc"]
923
- if hypersync->Option.isNone && rpc->Option.isNone {
924
- JsError.throwWithMessage(
925
- `Chain ${chainName} is missing a data source: provide either an rpc endpoint or an experimental hypersync config`,
926
- )
976
+ switch publicChainConfig["hypersync"] {
977
+ | Some(hypersync) => SvmSourceConfig({hypersync: hypersync})
978
+ | None =>
979
+ JsError.throwWithMessage(`Chain ${chainName} is missing hypersync endpoint in config`)
927
980
  }
928
- SvmSourceConfig({hypersync, rpc})
929
981
  }
930
982
 
931
983
  {
@@ -1035,7 +1087,6 @@ let fromPublic = (publicConfigJson: JSON.t) => {
1035
1087
  userEntitiesByName,
1036
1088
  userEntities,
1037
1089
  allEnums,
1038
- subgraph: publicConfig["subgraph"],
1039
1090
  }
1040
1091
  }
1041
1092
 
@@ -1173,7 +1224,6 @@ let stripSensitiveData = (json: JSON.t): JSON.t => {
1173
1224
  switch chainJson {
1174
1225
  | Object(chain) => {
1175
1226
  chain->Utils.Dict.deleteInPlace("rpcs")
1176
- chain->Utils.Dict.deleteInPlace("rpc")
1177
1227
  chain->Utils.Dict.deleteInPlace("hypersync")
1178
1228
  }
1179
1229
  | _ => ()
@@ -1189,10 +1239,6 @@ let stripSensitiveData = (json: JSON.t): JSON.t => {
1189
1239
  stripChains(obj->Dict.get("evm"))
1190
1240
  stripChains(obj->Dict.get("fuel"))
1191
1241
  stripChains(obj->Dict.get("svm"))
1192
- // The subgraph blob is how the runtime finds mappings. None of it
1193
- // describes stored data — that's already in `evm` / `entities` — so a
1194
- // specVersion bump or a mapping-path edit must not force a reset.
1195
- obj->Utils.Dict.deleteInPlace("subgraph")
1196
1242
  }
1197
1243
  | _ => ()
1198
1244
  }