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
@@ -80,54 +80,3 @@ let make = (~logger: Pino.t): Ecosystem.t => {
80
80
  },
81
81
  toRawEvent: _ => JsError.throwWithMessage("Raw events are not supported for SVM"),
82
82
  }
83
-
84
- module GetFinalizedSlot = {
85
- let route = Rpc.makeRpcRoute(
86
- "getSlot",
87
- S.tuple(s => {
88
- s.tag(0, {"commitment": "finalized"})
89
- ()
90
- }),
91
- S.int,
92
- )
93
- }
94
-
95
- let makeRPCSource = (~chainId, ~rpc: string, ~sourceFor: Source.sourceFor=Sync): Source.t => {
96
- let client = Rest.client(rpc)
97
-
98
- let urlHost = switch Utils.Url.getHostFromUrl(rpc) {
99
- | None =>
100
- JsError.throwWithMessage(
101
- `The RPC url for chain ${chainId->ChainId.toString} is in incorrect format. The RPC url needs to start with either http:// or https://`,
102
- )
103
- | Some(host) => host
104
- }
105
- let name = `RPC (${urlHost})`
106
-
107
- {
108
- name,
109
- sourceFor,
110
- chainId,
111
- poweredByHyperSync: false,
112
- pollingInterval: 10_000,
113
- getBlockHashes: (~blockNumbers as _, ~logger as _) =>
114
- JsError.throwWithMessage("Svm does not support getting block hashes"),
115
- getHeightOrThrow: async () => {
116
- let timerRef = Performance.now()
117
- let height = await GetFinalizedSlot.route->Rest.fetch((), ~client)
118
- let seconds = timerRef->Performance.secondsSince
119
- {Source.height, requestStats: [{Source.method: "getSlot", seconds}]}
120
- },
121
- getItemsOrThrow: (
122
- ~fromBlock as _,
123
- ~toBlock as _,
124
- ~addressSet as _,
125
- ~knownHeight as _,
126
- ~partitionId as _,
127
- ~selection as _,
128
- ~itemsTarget as _,
129
- ~retry as _,
130
- ~logger as _,
131
- ) => JsError.throwWithMessage("Svm does not support getting items"),
132
- }
133
- }
@@ -1,13 +1,8 @@
1
1
  // Generated by ReScript, PLEASE EDIT WITH CARE
2
2
 
3
- import * as Rpc from "./Rpc.res.mjs";
4
- import * as Rest from "../vendored/Rest.res.mjs";
5
- import * as Utils from "../Utils.res.mjs";
6
- import * as ChainId from "../ChainId.res.mjs";
7
3
  import * as Logging from "../Logging.res.mjs";
8
4
  import * as Internal from "../Internal.res.mjs";
9
5
  import * as BlockStore from "./BlockStore.res.mjs";
10
- import * as Performance from "../bindings/Performance.res.mjs";
11
6
  import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
12
7
  import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
13
8
  import * as S$RescriptSchema from "rescript-schema/src/S.res.mjs";
@@ -79,45 +74,6 @@ function make(logger) {
79
74
  };
80
75
  }
81
76
 
82
- let route = Rpc.makeRpcRoute("getSlot", S$RescriptSchema.tuple(s => {
83
- s.tag(0, {
84
- commitment: "finalized"
85
- });
86
- }), S$RescriptSchema.int);
87
-
88
- let GetFinalizedSlot = {
89
- route: route
90
- };
91
-
92
- function makeRPCSource(chainId, rpc, sourceForOpt) {
93
- let sourceFor = sourceForOpt !== undefined ? sourceForOpt : "Sync";
94
- let client = Rest.client(rpc, undefined);
95
- let host = Utils.Url.getHostFromUrl(rpc);
96
- let urlHost = host !== undefined ? host : Stdlib_JsError.throwWithMessage(`The RPC url for chain ` + ChainId.toString(chainId) + ` is in incorrect format. The RPC url needs to start with either http:// or https://`);
97
- let name = `RPC (` + urlHost + `)`;
98
- return {
99
- name: name,
100
- sourceFor: sourceFor,
101
- chainId: chainId,
102
- poweredByHyperSync: false,
103
- pollingInterval: 10000,
104
- getBlockHashes: (param, param$1) => Stdlib_JsError.throwWithMessage("Svm does not support getting block hashes"),
105
- getHeightOrThrow: async () => {
106
- let timerRef = Performance.now();
107
- let height = await Rest.fetch(route, undefined, client);
108
- let seconds = Performance.secondsSince(timerRef);
109
- return {
110
- height: height,
111
- requestStats: [{
112
- method: "getSlot",
113
- seconds: seconds
114
- }]
115
- };
116
- },
117
- getItemsOrThrow: (param, param$1, param$2, param$3, param$4, param$5, param$6, param$7, param$8) => Stdlib_JsError.throwWithMessage("Svm does not support getting items")
118
- };
119
- }
120
-
121
77
  let transactionFields = Internal.allSvmTransactionFields;
122
78
 
123
79
  export {
@@ -127,7 +83,5 @@ export {
127
83
  eventBlockFieldMask,
128
84
  attachAccountActivities,
129
85
  make,
130
- GetFinalizedSlot,
131
- makeRPCSource,
132
86
  }
133
87
  /* eventTransactionFieldMask Not a pure module */
@@ -10,13 +10,17 @@ type options = {
10
10
  addressStore: AddressStore.t,
11
11
  }
12
12
 
13
- let namedAccounts = (~idlNames: array<string>, ~accountArguments: array<string>): dict<
14
- Envio.svmInstructionAccount,
15
- > => {
13
+ let namedAccounts = (
14
+ ~slots: array<Internal.svmAccountSlot>,
15
+ ~accountArguments: array<string>,
16
+ ~programId: string,
17
+ ): dict<Envio.svmInstructionAccount> => {
16
18
  let out = Dict.make()
17
- idlNames->Array.forEachWithIndex((name, i) =>
18
- switch accountArguments->Array.get(i) {
19
- | Some(address) =>
19
+ slots->Array.forEachWithIndex((slot, i) =>
20
+ switch (slot, accountArguments->Array.get(i)) {
21
+ | (Unnamed, _) | (_, None) => ()
22
+ | (Optional(_), Some(address)) if address === programId => ()
23
+ | (Required(name), Some(address)) | (Optional(name), Some(address)) =>
20
24
  out->Dict.set(
21
25
  name,
22
26
  {
@@ -25,7 +29,6 @@ let namedAccounts = (~idlNames: array<string>, ~accountArguments: array<string>)
25
29
  instructionAccountIndex: i,
26
30
  },
27
31
  )
28
- | None => ()
29
32
  }
30
33
  )
31
34
  out
@@ -94,7 +97,11 @@ let toSvmInstruction = (
94
97
  if hasSelection("accounts") {
95
98
  out->setField(
96
99
  "accounts",
97
- namedAccounts(~idlNames=eventConfig.accounts, ~accountArguments=item.accounts),
100
+ namedAccounts(
101
+ ~slots=eventConfig.accounts,
102
+ ~accountArguments=item.accounts,
103
+ ~programId=item.programId,
104
+ ),
98
105
  )
99
106
  }
100
107
  if hasSelection("accountArguments") {
@@ -123,12 +130,14 @@ let make = (
123
130
  ): t => {
124
131
  let name = "SvmHyperSync"
125
132
 
133
+ let apiToken = apiToken->HyperSync.requireApiToken
134
+
126
135
  // The whole per-(instruction, chain) registration set crosses the boundary
127
136
  // once at construction; the client derives instruction selections, field
128
137
  // selections, Borsh decoders, and the routing index from it.
129
138
  let client = SvmHyperSyncClient.make(
130
139
  ~url=endpointUrl,
131
- ~apiToken?,
140
+ ~apiToken,
132
141
  ~httpReqTimeoutMillis=clientTimeoutMillis,
133
142
  ~programs=SvmHyperSyncClient.Registration.fromOnEventRegistrations(onEventRegistrations),
134
143
  ~addressStore,
@@ -257,5 +266,7 @@ let make = (
257
266
  {height, requestStats: [{method: "getHeight", seconds}]}
258
267
  },
259
268
  getItemsOrThrow,
269
+ createHeightSubscription: (~onHeight, ~onStatus) =>
270
+ HyperSyncSSE.subscribe(~hyperSyncUrl=endpointUrl, ~apiToken, ~onHeight, ~onStatus),
260
271
  }
261
272
  }
@@ -4,23 +4,42 @@ import * as Source from "./Source.res.mjs";
4
4
  import * as HyperSync from "./HyperSync.res.mjs";
5
5
  import * as Performance from "../bindings/Performance.res.mjs";
6
6
  import * as Stdlib_Null from "@rescript/runtime/lib/es6/Stdlib_Null.js";
7
+ import * as HyperSyncSSE from "./HyperSyncSSE.res.mjs";
7
8
  import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
8
9
  import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
9
10
  import * as SvmHyperSyncClient from "./SvmHyperSyncClient.res.mjs";
10
11
  import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
11
12
 
12
- function namedAccounts(idlNames, accountArguments) {
13
+ function namedAccounts(slots, accountArguments, programId) {
13
14
  let out = {};
14
- idlNames.forEach((name, i) => {
15
- let address = accountArguments[i];
16
- if (address !== undefined) {
17
- out[name] = {
18
- address: address,
19
- accountName: name,
20
- instructionAccountIndex: i
21
- };
15
+ slots.forEach((slot, i) => {
16
+ let match = accountArguments[i];
17
+ let name;
18
+ let address;
19
+ if (typeof slot !== "object") {
22
20
  return;
23
21
  }
22
+ if (slot.TAG === "Required") {
23
+ if (match === undefined) {
24
+ return;
25
+ }
26
+ name = slot._0;
27
+ address = match;
28
+ } else {
29
+ if (match === undefined) {
30
+ return;
31
+ }
32
+ if (match === programId) {
33
+ return;
34
+ }
35
+ name = slot._0;
36
+ address = match;
37
+ }
38
+ out[name] = {
39
+ address: address,
40
+ accountName: name,
41
+ instructionAccountIndex: i
42
+ };
24
43
  });
25
44
  return out;
26
45
  }
@@ -70,7 +89,7 @@ function toSvmInstruction(item, programName, instructionName, eventConfig, field
70
89
  setField(out, "args", args);
71
90
  }
72
91
  if (fieldSelection.instructionFields.has("accounts")) {
73
- setField(out, "accounts", namedAccounts(eventConfig.accounts, item.accounts));
92
+ setField(out, "accounts", namedAccounts(eventConfig.accounts, item.accounts, item.programId));
74
93
  }
75
94
  if (fieldSelection.instructionFields.has("accountArguments")) {
76
95
  setField(out, "accountArguments", item.accounts);
@@ -83,8 +102,10 @@ function toSvmInstruction(item, programName, instructionName, eventConfig, field
83
102
 
84
103
  function make(param) {
85
104
  let onEventRegistrations = param.onEventRegistrations;
105
+ let endpointUrl = param.endpointUrl;
86
106
  let chainId = param.chainId;
87
- let client = SvmHyperSyncClient.make(param.endpointUrl, param.apiToken, param.clientTimeoutMillis, undefined, undefined, SvmHyperSyncClient.Registration.fromOnEventRegistrations(onEventRegistrations), param.addressStore);
107
+ let apiToken = HyperSync.requireApiToken(param.apiToken);
108
+ let client = SvmHyperSyncClient.make(endpointUrl, apiToken, param.clientTimeoutMillis, undefined, undefined, SvmHyperSyncClient.Registration.fromOnEventRegistrations(onEventRegistrations), param.addressStore);
88
109
  let getItemsOrThrow = async (fromBlock, toBlock, addressSet, knownHeight, param, selection, itemsTarget, retry, param$1) => {
89
110
  let totalTimeRef = Performance.now();
90
111
  let pageFetchRef = Performance.now();
@@ -178,7 +199,8 @@ function make(param) {
178
199
  }]
179
200
  };
180
201
  },
181
- getItemsOrThrow: getItemsOrThrow
202
+ getItemsOrThrow: getItemsOrThrow,
203
+ createHeightSubscription: (onHeight, onStatus) => HyperSyncSSE.subscribe(endpointUrl, apiToken, onHeight, onStatus)
182
204
  };
183
205
  }
184
206
 
package/svm.schema.json CHANGED
@@ -66,6 +66,13 @@
66
66
  "items": {
67
67
  "$ref": "#/$defs/Chain"
68
68
  }
69
+ },
70
+ "programs": {
71
+ "description": "Solana programs to index. Programs are defined once for the whole project; `program_id` says where each one lives on every chain.",
72
+ "type": "array",
73
+ "items": {
74
+ "$ref": "#/$defs/Program"
75
+ }
69
76
  }
70
77
  },
71
78
  "additionalProperties": false,
@@ -224,20 +231,18 @@
224
231
  ]
225
232
  },
226
233
  "rpc": {
227
- "description": "RPC endpoint URL for connecting to the Svm cluster to fetch blockchain data. Required unless `experimental` is set, in which case it is ignored in favour of the experimental HyperSync source.",
234
+ "description": "RPC endpoint URL for the Svm cluster. Accepted but unused: instruction sync is served by HyperSync.",
228
235
  "type": [
229
236
  "string",
230
237
  "null"
231
238
  ]
232
239
  },
233
- "start_block": {
234
- "description": "The slot number at which the indexer should start ingesting data",
235
- "type": "integer",
236
- "format": "uint64",
237
- "minimum": 0
240
+ "start_slot": {
241
+ "description": "The slot at which the indexer should start ingesting data, or \"latest\" to start from the chain's current slot when the indexer is first deployed. Once resolved, the concrete slot 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.",
242
+ "$ref": "#/$defs/StartBlock"
238
243
  },
239
- "end_block": {
240
- "description": "The slot number at which the indexer should terminate.",
244
+ "end_slot": {
245
+ "description": "The slot at which the indexer should terminate.",
241
246
  "type": [
242
247
  "integer",
243
248
  "null"
@@ -254,11 +259,11 @@
254
259
  "format": "uint32",
255
260
  "minimum": 0
256
261
  },
257
- "experimental": {
258
- "description": "Experimental HyperSync-backed instruction indexing. This config shape Veil change in future releases.",
262
+ "hypersync_config": {
263
+ "description": "HyperSync Config for fetching historical instructions on this chain. Optional for the `solana` and `solana-devnet` chain ids, which default to their public HyperSync endpoints; required for any other chain id.",
259
264
  "anyOf": [
260
265
  {
261
- "$ref": "#/$defs/Experimental"
266
+ "$ref": "#/$defs/HypersyncConfig"
262
267
  },
263
268
  {
264
269
  "type": "null"
@@ -269,7 +274,7 @@
269
274
  "additionalProperties": false,
270
275
  "required": [
271
276
  "id",
272
- "start_block"
277
+ "start_slot"
273
278
  ]
274
279
  },
275
280
  "ChainId": {
@@ -293,31 +298,23 @@
293
298
  "solana-devnet"
294
299
  ]
295
300
  },
296
- "Experimental": {
297
- "type": "object",
298
- "properties": {
299
- "hypersync_config": {
300
- "description": "HyperSync Config for fetching historical instructions on this chain. Optional for the `solana` and `solana-devnet` chain ids, which default to their public HyperSync endpoints; required for any other chain id.",
301
- "anyOf": [
302
- {
303
- "$ref": "#/$defs/HypersyncConfig"
304
- },
305
- {
306
- "type": "null"
307
- }
308
- ]
301
+ "StartBlock": {
302
+ "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`.",
303
+ "anyOf": [
304
+ {
305
+ "type": "integer",
306
+ "format": "uint64",
307
+ "minimum": 0
309
308
  },
310
- "programs": {
311
- "description": "Solana programs to index on this chain.",
312
- "type": "array",
313
- "items": {
314
- "$ref": "#/$defs/Program"
315
- }
309
+ {
310
+ "$ref": "#/$defs/StartBlockTag"
316
311
  }
317
- },
318
- "additionalProperties": false,
319
- "required": [
320
- "programs"
312
+ ]
313
+ },
314
+ "StartBlockTag": {
315
+ "type": "string",
316
+ "enum": [
317
+ "latest"
321
318
  ]
322
319
  },
323
320
  "HypersyncConfig": {
@@ -341,8 +338,8 @@
341
338
  "type": "string"
342
339
  },
343
340
  "program_id": {
344
- "description": "Base58-encoded program id (32 bytes).",
345
- "type": "string"
341
+ "description": "Base58-encoded program id (32 bytes). A single value is allowed only when the config defines one chain; with several, give a mapping keyed by chain id that names every one of them, writing `_` for a chain the program is not deployed on.",
342
+ "$ref": "#/$defs/SvmProgramId"
346
343
  },
347
344
  "handler": {
348
345
  "description": "Optional relative path to a file where handlers are registered for the given program. If not provided, handlers can be auto-loaded from the src directory.",
@@ -359,7 +356,7 @@
359
356
  ]
360
357
  },
361
358
  "instructions": {
362
- "description": "Instructions to index. With `idl:`, omit this list to take the full usable IDL catalog. A YAML row overwrites the IDL instruction of the same name, or adds a new name to the catalog, and must set `discriminator` plus both `accounts` and `args`. Without `idl:`, omit `discriminator` to match every instruction of the program; set both `accounts` and `args`, or omit both.",
359
+ "description": "Instructions to index. With `idl:`, omit this list to take the full usable IDL catalog. A row whose name the IDL declares replaces that instruction, and must spell out `accounts` and `args`; every other row adds one. Give `accounts` where you want the slots named — an empty list names none, the same as leaving it out. `args` is not the same shape: setting it attaches a decoder that also filters, so leaving it out and setting it to `[]` are different asks.",
363
360
  "type": "array",
364
361
  "items": {
365
362
  "$ref": "#/$defs/Instruction"
@@ -372,6 +369,28 @@
372
369
  "program_id"
373
370
  ]
374
371
  },
372
+ "SvmProgramId": {
373
+ "title": "Program id",
374
+ "description": "Base58-encoded program id (32 bytes).\nA single value is allowed only when the config defines one chain.\nWith several chains, give a mapping keyed by chain id that names every one of them; write `_` for a chain the program is not deployed on.",
375
+ "anyOf": [
376
+ {
377
+ "type": "string"
378
+ },
379
+ {
380
+ "type": "object",
381
+ "additionalProperties": {
382
+ "type": "string"
383
+ }
384
+ }
385
+ ],
386
+ "examples": [
387
+ "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s",
388
+ {
389
+ "solana": "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s",
390
+ "solana-devnet": "_"
391
+ }
392
+ ]
393
+ },
375
394
  "Instruction": {
376
395
  "type": "object",
377
396
  "properties": {
@@ -380,24 +399,21 @@
380
399
  "type": "string"
381
400
  },
382
401
  "discriminator": {
383
- "description": "Hex-encoded instruction-data prefix used as the discriminator (\"0x\" optional), of any whole number of bytes; an 8-byte value matches the standard Anchor discriminator. With `idl:` on the program, this field is required on every YAML row. Without `idl:`, omit it to match every instruction of the program. Every instruction whose prefix an on-chain call carries receives it, so a program-wide entry fires alongside a keyed one, and two entries may share a prefix (say, the layouts before and after a program upgrade): each decodes with its own `args`, and one whose layout rejects the data is skipped for that call.",
384
- "type": [
385
- "string",
386
- "null"
387
- ]
402
+ "description": "0x-prefixed hex instruction-data prefix to dispatch on, of any whole number of bytes; an 8-byte value matches the standard Anchor discriminator. The empty prefix, written \"0x\", is carried by every call, so it is how a row matches every instruction of the program. This is the form `instruction.discriminator` reads back, so a config value and a handler comparison are the same string. Every instruction whose prefix an on-chain call carries receives it, so a program-wide entry fires alongside a keyed one, and two entries may share a prefix (say, the layouts before and after a program upgrade): each decodes with its own `args`, and one whose layout rejects the data is skipped for that call.",
403
+ "type": "string"
388
404
  },
389
405
  "accounts": {
390
- "description": "Optional positional account names. The Nth entry names account slot N on the dispatched instruction; surfaces as `instruction.accounts.<name>` when `fields.instruction` includes `accounts`.",
406
+ "description": "Optional positional account slots, in the order the program expects them. The Nth entry names account slot N on the dispatched instruction; named slots surface as `instruction.accounts.<name>` when `fields.instruction` includes `accounts`. The raw slots are available either way, so this only adds the names. Required on a row that replaces an instruction the IDL declares.",
391
407
  "type": [
392
408
  "array",
393
409
  "null"
394
410
  ],
395
411
  "items": {
396
- "type": "string"
412
+ "$ref": "#/$defs/SvmAccountSlot"
397
413
  }
398
414
  },
399
415
  "args": {
400
- "description": "Optional Borsh argument schema. Each entry names one arg and gives its type; the decoder walks the instruction data after the discriminator in declared order. Must be set together with `accounts` when either is present.",
416
+ "description": "Borsh argument schema. Each entry names one arg and gives its type; the decoder walks the instruction data after the discriminator in declared order, and a call whose data the layout rejects is skipped. Setting it is therefore also a filter: `[]` says the instruction takes no arguments, so only calls carrying nothing past the discriminator are indexed. Omit it instead to attach no decoder at all — every matched call is indexed and the payload stays raw, reachable as `instruction.data`. An `idl` always declares the layout of the instructions it names, empty included. Required on a row that replaces an instruction the IDL declares.",
401
417
  "type": [
402
418
  "array",
403
419
  "null"
@@ -409,7 +425,19 @@
409
425
  },
410
426
  "additionalProperties": false,
411
427
  "required": [
412
- "name"
428
+ "name",
429
+ "discriminator"
430
+ ]
431
+ },
432
+ "SvmAccountSlot": {
433
+ "title": "Account slot",
434
+ "description": "One positional account slot of the instruction.\n- `payer` names a slot the call always carries; it surfaces as `instruction.accounts.payer`.\n- `?authority` names an optional slot: the key is absent from `instruction.accounts` when the call leaves the slot out or fills it with the program id.\n- `_` holds a position without naming it, so the slots after it keep theirs. It is never surfaced and never filterable, and the list may not end with one.",
435
+ "type": "string",
436
+ "pattern": "^(?:_|\\??[A-Za-z0-9_]*[A-Za-z][A-Za-z0-9_]*)$",
437
+ "examples": [
438
+ "payer",
439
+ "?authority",
440
+ "_"
413
441
  ]
414
442
  },
415
443
  "ArgDef": {
@@ -432,7 +460,7 @@
432
460
  ]
433
461
  },
434
462
  "ArgType": {
435
- "description": "User-facing Borsh type grammar. Mirrors\n`hypersync_client_solana::decode::FieldType`. The YAML accepts either:\n- A bare string for primitives (`\"u64\"`, `\"pubkey\"`, `\"bool\"`, ...).\n- A tagged object for composites (`{ vec: u8 }`, `{ option: pubkey }`,\n `{ array: [u8, 32] }`, `{ defined: \"DataV2\" }`).\n- An object with `kind: struct` or `kind: enum` for nominal types\n declared inline on this field. Most users will use `defined` and\n declare the nominal types under the program's `types:` block (Anchor\n IDL shape) once that lands; for now inline `struct` / `enum` is the\n only way to express nominal shapes ad-hoc.",
463
+ "description": "User-facing Borsh type grammar. Mirrors\n`hypersync_client_solana::decode::FieldType`. The YAML accepts either:\n- A bare string for a primitive (`u64`, `pubkey`, `bool`, ...).\n- A one-key mapping for a composite (`{ vec: u8 }`, `{ option: pubkey }`,\n `{ array: [u8, 32] }`, `{ struct: [...] }`, `{ enum: [...] }`).\n\nA nominal type is declared inline with `struct` / `enum` at the field\nthat uses it. There is no way to name one and refer to it: attach an\n`idl` to the program when its types are shared between instructions.",
436
464
  "anyOf": [
437
465
  {
438
466
  "$ref": "#/$defs/ArgPrimitive"
@@ -516,20 +544,7 @@
516
544
  "additionalProperties": false
517
545
  },
518
546
  {
519
- "description": "Reference to a nominal type defined in the program-level\n`defined_types` registry (populated from an Anchor IDL `types:`\nblock).",
520
- "type": "object",
521
- "properties": {
522
- "defined": {
523
- "type": "string"
524
- }
525
- },
526
- "required": [
527
- "defined"
528
- ],
529
- "additionalProperties": false
530
- },
531
- {
532
- "description": "Inline-or-registry struct. Used as a nominal type definition in\nthe `defined_types` registry; rarely seen at the field level.",
547
+ "description": "A struct, declared inline at the field that uses it or held in the\n`defined_types` registry.",
533
548
  "type": "object",
534
549
  "properties": {
535
550
  "struct": {
@@ -545,7 +560,7 @@
545
560
  "additionalProperties": false
546
561
  },
547
562
  {
548
- "description": "Inline-or-registry enum. Same role as `Struct`: a nominal type\ndefinition in the `defined_types` registry.",
563
+ "description": "An enum, declared inline at the field that uses it or held in the\n`defined_types` registry. Borsh selects a variant by its position\nhere, with a one-byte tag.",
549
564
  "type": "object",
550
565
  "properties": {
551
566
  "enum": {