envio 3.10.0 → 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 (42) hide show
  1. package/evm.schema.json +21 -4
  2. package/fuel.schema.json +21 -4
  3. package/index.d.ts +7 -6
  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 -18
  8. package/src/Config.res.mjs +69 -16
  9. package/src/EventConfigBuilder.res +14 -10
  10. package/src/EventConfigBuilder.res.mjs +6 -4
  11. package/src/HandlerRegister.res +12 -11
  12. package/src/HandlerRegister.res.mjs +6 -5
  13. package/src/Internal.res +20 -4
  14. package/src/Internal.res.mjs +9 -0
  15. package/src/Main.res +8 -1
  16. package/src/Main.res.mjs +3 -3
  17. package/src/Persistence.res +11 -1
  18. package/src/Persistence.res.mjs +6 -2
  19. package/src/PgStorage.res +1 -1
  20. package/src/PgStorage.res.mjs +1 -1
  21. package/src/SimulateItems.res +13 -6
  22. package/src/SimulateItems.res.mjs +7 -6
  23. package/src/TestIndexer.res +6 -5
  24. package/src/TestIndexer.res.mjs +5 -4
  25. package/src/db/InternalTable.res +1 -1
  26. package/src/db/InternalTable.res.mjs +2 -1
  27. package/src/sources/ChainSources.res +72 -0
  28. package/src/sources/ChainSources.res.mjs +55 -0
  29. package/src/sources/EvmHyperSyncSource.res +10 -22
  30. package/src/sources/EvmHyperSyncSource.res.mjs +9 -41
  31. package/src/sources/FuelHyperSyncSource.res +10 -18
  32. package/src/sources/FuelHyperSyncSource.res.mjs +8 -38
  33. package/src/sources/HyperSync.res +31 -0
  34. package/src/sources/HyperSync.res.mjs +31 -0
  35. package/src/sources/HyperSync.resi +16 -0
  36. package/src/sources/StartBlockResolver.res +173 -0
  37. package/src/sources/StartBlockResolver.res.mjs +134 -0
  38. package/src/sources/Svm.res +0 -51
  39. package/src/sources/Svm.res.mjs +0 -46
  40. package/src/sources/SvmHyperSyncSource.res +20 -9
  41. package/src/sources/SvmHyperSyncSource.res.mjs +34 -12
  42. package/svm.schema.json +80 -66
@@ -0,0 +1,173 @@
1
+ // Resolves a chain's `start_block: latest` to a concrete block number, once,
2
+ // right before the indexer's first-ever persisted state is written (see
3
+ // `Persistence.init`). Never runs on a normal resume (crash recovery, a plain
4
+ // restarted process): `envio_chains.start_block` is written once and read
5
+ // back verbatim from then on, so a resolved "latest" naturally stays fixed
6
+ // across downtime instead of jumping to a new head - any gap gets backfilled
7
+ // rather than skipped. The CLI's `-r` (`--restart`) flag is the exception: it
8
+ // forces `reset=true` in `Persistence.init`, which wipes the DB and runs this
9
+ // again, the same as any other fresh deploy.
10
+
11
+ // Sources built only to read the chain's height. No registrations exist yet at
12
+ // this point in startup - handler files load after persistence initializes -
13
+ // and none are needed to ask a backend how far it has got. These are not the
14
+ // sources the chain goes on to index with; those are built later, once
15
+ // registrations exist.
16
+ let makeProbeSources = (chainConfig: Config.chain, ~lowercaseAddresses): array<Source.t> => {
17
+ let addressStore = AddressStore.make(
18
+ ~ecosystem=chainConfig.ecosystem,
19
+ ~shouldChecksum=!lowercaseAddresses,
20
+ ~contracts=[],
21
+ )
22
+ ChainSources.make(~chainConfig, ~onEventRegistrations=[], ~addressStore, ~lowercaseAddresses)
23
+ }
24
+
25
+ // How long to keep asking a chain that won't answer.
26
+ type retry =
27
+ // The indexer. A chain it can't reach is the operator's to fix, and the
28
+ // process staying up is what gives them the chance to - the same thing it
29
+ // does for a chain that goes unreachable while it runs.
30
+ | UntilItAnswers
31
+ // `envio local db-migrate up/setup`. A one-shot command has nobody waiting to
32
+ // watch it recover, so every source gets one attempt and then it reports what
33
+ // each of them said.
34
+ | Once
35
+
36
+ // Sources that can serve historical sync, primaries first - the same ordering a
37
+ // backfill would use. A realtime-only source is left out: it isn't what this
38
+ // chain reads its history from.
39
+ let candidateSources = (sources: array<Source.t>) => {
40
+ let hasRealtime = sources->Array.some(source => source.sourceFor === Realtime)
41
+ let roleOf = (source: Source.t) =>
42
+ SourceManager.getSourceRole(~sourceFor=source.sourceFor, ~isRealtime=false, ~hasRealtime)
43
+ sources
44
+ ->Array.filter(source => roleOf(source)->Option.isSome)
45
+ ->Array.toSorted((a, b) =>
46
+ switch (roleOf(a), roleOf(b)) {
47
+ | (Some(Primary), Some(Secondary)) => Ordering.less
48
+ | (Some(Secondary), Some(Primary)) => Ordering.greater
49
+ | _ => Ordering.equal
50
+ }
51
+ )
52
+ }
53
+
54
+ // One attempt per source. Trying the next source is not a retry - it's the
55
+ // failover the config asked for - but no source is asked twice.
56
+ let readHeadOnceOrThrow = async (chainConfig: Config.chain, ~sources): int => {
57
+ let candidates = candidateSources(sources)
58
+ if candidates->Utils.Array.isEmpty {
59
+ // The condition `SourceManager.make` rejects on the retrying path. Sharing
60
+ // its wording keeps one misconfiguration from having two explanations - and
61
+ // "no source answered" would be untrue here, since none was asked.
62
+ JsError.throwWithMessage("Invalid configuration, no data-source for historical sync provided")
63
+ }
64
+ let failures = []
65
+ let head = ref(None)
66
+ for i in 0 to candidates->Array.length - 1 {
67
+ if head.contents->Option.isNone {
68
+ let source = candidates->Array.getUnsafe(i)
69
+ switch await source.getHeightOrThrow() {
70
+ | {height} => head := Some(height)
71
+ | exception exn =>
72
+ failures
73
+ ->Array.push(`${source.name}: ${exn->Utils.exnMessage->Option.getOr("unknown error")}`)
74
+ ->ignore
75
+ }
76
+ }
77
+ }
78
+ switch head.contents {
79
+ | Some(head) => head
80
+ | None =>
81
+ JsError.throwWithMessage(
82
+ `Chain ${chainConfig.id->ChainId.toString}: couldn't resolve the "latest" start block - no source answered a height request.${failures
83
+ ->Array.map(failure => `\n ${failure}`)
84
+ ->Array.join("")}`,
85
+ )
86
+ }
87
+ }
88
+
89
+ // Waiting for a height above 0 is the same question as "what is the head", and
90
+ // it comes with the runtime's own answer to a source that won't say: retry with
91
+ // backoff, a bound on how long any one request is waited for, and failover to
92
+ // `for: fallback` sources once the primary has been quiet for a stall window.
93
+ //
94
+ // It never gives up, which is what makes it safe to use here. A deadline would
95
+ // need a race, and the losing side of that race is a registered waiter and a
96
+ // live poll loop that nothing can reach to stop.
97
+ let resolveHeadOrThrow = async (
98
+ chainConfig: Config.chain,
99
+ ~lowercaseAddresses,
100
+ ~retry,
101
+ ~getHeightRetryInterval=?,
102
+ ~newBlockStallTimeout=?,
103
+ ): int => {
104
+ let sources = chainConfig->makeProbeSources(~lowercaseAddresses)
105
+ switch retry {
106
+ | Once => await chainConfig->readHeadOnceOrThrow(~sources)
107
+ | UntilItAnswers =>
108
+ let sourceManager = SourceManager.make(
109
+ ~sources,
110
+ ~isRealtime=false,
111
+ ~getHeightRetryInterval?,
112
+ ~newBlockStallTimeout?,
113
+ )
114
+ await sourceManager->SourceManager.waitForNewBlock(
115
+ ~knownHeight=0,
116
+ ~isRealtime=false,
117
+ ~reducedPolling=false,
118
+ )
119
+ }
120
+ }
121
+
122
+ // Sequential rather than `Promise.all`: a chain that fails validation must not
123
+ // leave sibling chains' height polling running behind the rejection.
124
+ let resolveAllOrThrow = async (
125
+ chainConfigs: array<Config.chain>,
126
+ ~lowercaseAddresses,
127
+ ~retry=UntilItAnswers,
128
+ ~getHeightRetryInterval=?,
129
+ ~newBlockStallTimeout=?,
130
+ ): array<Config.chain> => {
131
+ let resolved = []
132
+ for i in 0 to chainConfigs->Array.length - 1 {
133
+ let chainConfig = chainConfigs->Array.getUnsafe(i)
134
+ let chainConfig = switch chainConfig.startBlock {
135
+ | Config.Block(_) => chainConfig
136
+ | Config.Latest =>
137
+ let head = await chainConfig->resolveHeadOrThrow(
138
+ ~lowercaseAddresses,
139
+ ~retry,
140
+ ~getHeightRetryInterval?,
141
+ ~newBlockStallTimeout?,
142
+ )
143
+ let chainId = chainConfig.id->ChainId.toString
144
+ switch chainConfig.endBlock {
145
+ | Some(endBlock) if head > endBlock =>
146
+ JsError.throwWithMessage(
147
+ `Chain ${chainId}: the "latest" start block resolved to ${head->Int.toString}, which is past the configured end_block (${endBlock->Int.toString}). There is nothing to index - remove end_block, raise it above the chain's current head, or pin start_block to a fixed value instead of "latest".`,
148
+ )
149
+ | _ => ()
150
+ }
151
+ // Checked here, before anything is persisted: the same guard in
152
+ // `ChainState.makeInternal` would only fire after the resolved head is
153
+ // written to envio_chains, and then again on every resume.
154
+ chainConfig.contracts->Array.forEach(contract =>
155
+ switch contract.startBlock {
156
+ | Some(contractStartBlock) if contractStartBlock < head =>
157
+ JsError.throwWithMessage(
158
+ `Chain ${chainId}: contract "${contract.name}" has start_block ${contractStartBlock->Int.toString}, but the chain's "latest" start block resolved to ${head->Int.toString}. A contract can't start before its chain does - remove the contract's start_block, or pin the chain's start_block to a fixed value instead of "latest".`,
159
+ )
160
+ | _ => ()
161
+ }
162
+ )
163
+ Logging.info({
164
+ "msg": `Resolved the "latest" start block for chain ${chainId} to block ${head->Int.toString}.`,
165
+ "chainId": chainConfig.id,
166
+ "startBlock": head,
167
+ })
168
+ {...chainConfig, startBlock: Config.Block(head)}
169
+ }
170
+ resolved->Array.push(chainConfig)->ignore
171
+ }
172
+ resolved
173
+ }
@@ -0,0 +1,134 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as Utils from "../Utils.res.mjs";
4
+ import * as ChainId from "../ChainId.res.mjs";
5
+ import * as Logging from "../Logging.res.mjs";
6
+ import * as AddressStore from "./AddressStore.res.mjs";
7
+ import * as ChainSources from "./ChainSources.res.mjs";
8
+ import * as SourceManager from "./SourceManager.res.mjs";
9
+ import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
10
+ import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
11
+ import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
12
+
13
+ function makeProbeSources(chainConfig, lowercaseAddresses) {
14
+ let addressStore = AddressStore.make(chainConfig.ecosystem, !lowercaseAddresses, []);
15
+ return ChainSources.make(chainConfig, [], addressStore, lowercaseAddresses);
16
+ }
17
+
18
+ function candidateSources(sources) {
19
+ let hasRealtime = sources.some(source => source.sourceFor === "Realtime");
20
+ let roleOf = source => SourceManager.getSourceRole(source.sourceFor, false, hasRealtime);
21
+ return sources.filter(source => Stdlib_Option.isSome(roleOf(source))).toSorted((a, b) => {
22
+ let match = roleOf(a);
23
+ let match$1 = roleOf(b);
24
+ if (match !== undefined) {
25
+ if (match === "Primary") {
26
+ if (match$1 !== undefined) {
27
+ if (match$1 === "Primary") {
28
+ return 0;
29
+ } else {
30
+ return -1;
31
+ }
32
+ } else {
33
+ return 0;
34
+ }
35
+ } else if (match$1 !== undefined) {
36
+ if (match$1 === "Primary") {
37
+ return 1;
38
+ } else {
39
+ return 0;
40
+ }
41
+ } else {
42
+ return 0;
43
+ }
44
+ } else {
45
+ return 0;
46
+ }
47
+ });
48
+ }
49
+
50
+ async function readHeadOnceOrThrow(chainConfig, sources) {
51
+ let candidates = candidateSources(sources);
52
+ if (Utils.$$Array.isEmpty(candidates)) {
53
+ Stdlib_JsError.throwWithMessage("Invalid configuration, no data-source for historical sync provided");
54
+ }
55
+ let failures = [];
56
+ let head;
57
+ for (let i = 0, i_finish = candidates.length; i < i_finish; ++i) {
58
+ if (Stdlib_Option.isNone(head)) {
59
+ let source = candidates[i];
60
+ let exit = 0;
61
+ let val;
62
+ try {
63
+ val = await source.getHeightOrThrow();
64
+ exit = 1;
65
+ } catch (raw_exn) {
66
+ let exn = Primitive_exceptions.internalToException(raw_exn);
67
+ failures.push(source.name + `: ` + Stdlib_Option.getOr(Utils.exnMessage(exn), "unknown error"));
68
+ }
69
+ if (exit === 1) {
70
+ head = val.height;
71
+ }
72
+ }
73
+ }
74
+ let head$1 = head;
75
+ if (head$1 !== undefined) {
76
+ return head$1;
77
+ } else {
78
+ return Stdlib_JsError.throwWithMessage(`Chain ` + ChainId.toString(chainConfig.id) + `: couldn't resolve the "latest" start block - no source answered a height request.` + failures.map(failure => `\n ` + failure).join(""));
79
+ }
80
+ }
81
+
82
+ async function resolveHeadOrThrow(chainConfig, lowercaseAddresses, retry, getHeightRetryInterval, newBlockStallTimeout) {
83
+ let sources = makeProbeSources(chainConfig, lowercaseAddresses);
84
+ if (retry !== "UntilItAnswers") {
85
+ return await readHeadOnceOrThrow(chainConfig, sources);
86
+ }
87
+ let sourceManager = SourceManager.make(sources, false, newBlockStallTimeout, undefined, undefined, undefined, undefined, getHeightRetryInterval);
88
+ return await SourceManager.waitForNewBlock(sourceManager, 0, false, false);
89
+ }
90
+
91
+ async function resolveAllOrThrow(chainConfigs, lowercaseAddresses, retryOpt, getHeightRetryInterval, newBlockStallTimeout) {
92
+ let retry = retryOpt !== undefined ? retryOpt : "UntilItAnswers";
93
+ let resolved = [];
94
+ for (let i = 0, i_finish = chainConfigs.length; i < i_finish; ++i) {
95
+ let chainConfig = chainConfigs[i];
96
+ let match = chainConfig.startBlock;
97
+ let chainConfig$1;
98
+ if (match === "latest") {
99
+ let head = await resolveHeadOrThrow(chainConfig, lowercaseAddresses, retry, getHeightRetryInterval, newBlockStallTimeout);
100
+ let chainId = ChainId.toString(chainConfig.id);
101
+ let endBlock = chainConfig.endBlock;
102
+ if (endBlock !== undefined && head > endBlock) {
103
+ Stdlib_JsError.throwWithMessage(`Chain ` + chainId + `: the "latest" start block resolved to ` + head.toString() + `, which is past the configured end_block (` + endBlock.toString() + `). There is nothing to index - remove end_block, raise it above the chain's current head, or pin start_block to a fixed value instead of "latest".`);
104
+ }
105
+ chainConfig.contracts.forEach(contract => {
106
+ let contractStartBlock = contract.startBlock;
107
+ if (contractStartBlock !== undefined && contractStartBlock < head) {
108
+ return Stdlib_JsError.throwWithMessage(`Chain ` + chainId + `: contract "` + contract.name + `" has start_block ` + contractStartBlock.toString() + `, but the chain's "latest" start block resolved to ` + head.toString() + `. A contract can't start before its chain does - remove the contract's start_block, or pin the chain's start_block to a fixed value instead of "latest".`);
109
+ }
110
+ });
111
+ Logging.info({
112
+ msg: `Resolved the "latest" start block for chain ` + chainId + ` to block ` + head.toString() + `.`,
113
+ chainId: chainConfig.id,
114
+ startBlock: head
115
+ });
116
+ let newrecord = {...chainConfig};
117
+ newrecord.startBlock = head;
118
+ chainConfig$1 = newrecord;
119
+ } else {
120
+ chainConfig$1 = chainConfig;
121
+ }
122
+ resolved.push(chainConfig$1);
123
+ }
124
+ return resolved;
125
+ }
126
+
127
+ export {
128
+ makeProbeSources,
129
+ candidateSources,
130
+ readHeadOnceOrThrow,
131
+ resolveHeadOrThrow,
132
+ resolveAllOrThrow,
133
+ }
134
+ /* Utils Not a pure module */
@@ -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