envio 3.6.1-subgraph → 3.7.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.
Files changed (87) hide show
  1. package/index.d.ts +197 -9
  2. package/package.json +6 -6
  3. package/src/Batch.res +64 -25
  4. package/src/Batch.res.mjs +69 -46
  5. package/src/ChainFetching.res +13 -17
  6. package/src/ChainFetching.res.mjs +9 -14
  7. package/src/ChainState.res +160 -119
  8. package/src/ChainState.res.mjs +75 -46
  9. package/src/ChainState.resi +15 -12
  10. package/src/Config.res +13 -9
  11. package/src/Config.res.mjs +5 -8
  12. package/src/Core.res +5 -0
  13. package/src/Ecosystem.res +0 -1
  14. package/src/Envio.res +16 -3
  15. package/src/EventConfigBuilder.res +155 -40
  16. package/src/EventConfigBuilder.res.mjs +76 -24
  17. package/src/EventProcessing.res +4 -8
  18. package/src/EventProcessing.res.mjs +4 -4
  19. package/src/EventUtils.res +3 -47
  20. package/src/FetchState.res +124 -104
  21. package/src/FetchState.res.mjs +116 -100
  22. package/src/HandlerRegister.res +108 -2
  23. package/src/HandlerRegister.res.mjs +59 -10
  24. package/src/HandlerRegister.resi +4 -0
  25. package/src/InMemoryStore.res +1 -6
  26. package/src/InMemoryStore.res.mjs +1 -3
  27. package/src/Internal.res +84 -24
  28. package/src/Internal.res.mjs +33 -2
  29. package/src/LazyLoader.res +12 -11
  30. package/src/LazyLoader.res.mjs +13 -7
  31. package/src/Main.res +22 -9
  32. package/src/Main.res.mjs +18 -5
  33. package/src/MemoryStorage.res +756 -0
  34. package/src/MemoryStorage.res.mjs +623 -0
  35. package/src/Persistence.res +4 -2
  36. package/src/PgStorage.res +24 -8
  37. package/src/PgStorage.res.mjs +15 -12
  38. package/src/ReorgDetection.res +0 -222
  39. package/src/ReorgDetection.res.mjs +0 -163
  40. package/src/Rollback.res +14 -15
  41. package/src/Rollback.res.mjs +4 -5
  42. package/src/SimulateItems.res +2 -2
  43. package/src/Utils.res +3 -0
  44. package/src/bindings/ClickHouse.res +16 -11
  45. package/src/bindings/ClickHouse.res.mjs +10 -10
  46. package/src/bindings/Vitest.res +1 -1
  47. package/src/sources/BlockStore.res +115 -5
  48. package/src/sources/BlockStore.res.mjs +25 -0
  49. package/src/sources/Evm.res +0 -1
  50. package/src/sources/Evm.res.mjs +0 -1
  51. package/src/sources/EvmHyperSyncSource.res +19 -84
  52. package/src/sources/EvmHyperSyncSource.res.mjs +24 -60
  53. package/src/sources/Fuel.res +24 -4
  54. package/src/sources/Fuel.res.mjs +23 -3
  55. package/src/sources/FuelHyperSync.res +8 -3
  56. package/src/sources/FuelHyperSync.res.mjs +5 -4
  57. package/src/sources/FuelHyperSync.resi +3 -1
  58. package/src/sources/FuelHyperSyncClient.res +7 -10
  59. package/src/sources/FuelHyperSyncSource.res +18 -45
  60. package/src/sources/FuelHyperSyncSource.res.mjs +18 -35
  61. package/src/sources/HyperSync.res +49 -229
  62. package/src/sources/HyperSync.res.mjs +74 -191
  63. package/src/sources/HyperSync.resi +11 -35
  64. package/src/sources/HyperSyncClient.res +9 -79
  65. package/src/sources/HyperSyncClient.res.mjs +2 -6
  66. package/src/sources/RequestStat.res +5 -0
  67. package/src/sources/RequestStat.res.mjs +2 -0
  68. package/src/sources/RpcSource.res +149 -43
  69. package/src/sources/RpcSource.res.mjs +104 -41
  70. package/src/sources/SimulateSource.res +8 -5
  71. package/src/sources/SimulateSource.res.mjs +6 -6
  72. package/src/sources/Source.res +89 -16
  73. package/src/sources/Source.res.mjs +50 -1
  74. package/src/sources/SourceManager.res +255 -50
  75. package/src/sources/SourceManager.res.mjs +149 -55
  76. package/src/sources/SourceManager.resi +2 -6
  77. package/src/sources/Svm.res +0 -7
  78. package/src/sources/Svm.res.mjs +0 -8
  79. package/src/sources/SvmHyperSyncClient.res +21 -16
  80. package/src/sources/SvmHyperSyncClient.res.mjs +3 -4
  81. package/src/sources/SvmHyperSyncSource.res +25 -117
  82. package/src/sources/SvmHyperSyncSource.res.mjs +8 -121
  83. package/src/subgraph/calls.ts +5 -1
  84. package/src/subgraph/division.ts +10 -0
  85. package/src/subgraph/graph-ts.ts +180 -4
  86. package/src/subgraph/runtime.ts +22 -4
  87. package/svm.schema.json +3 -2
@@ -10,17 +10,6 @@ type options = {
10
10
  addressStore: AddressStore.t,
11
11
  }
12
12
 
13
- // Synthesize a stable logIndex for an SVM instruction so the FetchState
14
- // ordering machinery (which compares by `(blockNumber, logIndex)`) sorts
15
- // instructions deterministically within a slot. The bit packing fits inside
16
- // JS's 53-bit safe-integer range: transactionIndex ≤ ~10k per slot,
17
- // instruction position ≤ 1000 per tx, depth ≤ ~10. Outer-only instructions
18
- // land at `tx * 65536`; inner ones append depth-weighted offsets.
19
- let synthLogIndex = (~transactionIndex, ~instructionAddress) => {
20
- let addrSum = instructionAddress->Array.reduce(0, (acc, n) => acc * 1024 + n + 1)
21
- transactionIndex * 65536 + addrSum
22
- }
23
-
24
13
  // Parse the Rust-decoded instruction (args/accounts arrive as JSON strings to
25
14
  // side-step napi-rs's lack of native JSON passthrough) into the public shape.
26
15
  let parseDecoded = (
@@ -68,7 +57,14 @@ let toSvmInstruction = (
68
57
  }
69
58
 
70
59
  let make = (
71
- {chainId, endpointUrl, apiToken, onEventRegistrations, clientTimeoutMillis, addressStore}: options,
60
+ {
61
+ chainId,
62
+ endpointUrl,
63
+ apiToken,
64
+ onEventRegistrations,
65
+ clientTimeoutMillis,
66
+ addressStore,
67
+ }: options,
72
68
  ): t => {
73
69
  let name = "SvmHyperSync"
74
70
 
@@ -112,6 +108,9 @@ let make = (
112
108
  ~addressSet,
113
109
  ) catch {
114
110
  | exn =>
111
+ // A rate limit or a replica behind the head is recoverable and has its own
112
+ // backoff and failover, so it must not be buried in a generic retry.
113
+ HyperSync.reraiseIfRecoverable(exn)
115
114
  throw(
116
115
  Source.GetItemsError(
117
116
  Source.FailedGettingItems({
@@ -133,17 +132,6 @@ let make = (
133
132
 
134
133
  let parsingRef = Performance.now()
135
134
 
136
- // Per-slot blockTime lookup from the response's `blocks` table, for the
137
- // batch's `latestFetchedBlockTimestamp`. Slots without a block row (rare;
138
- // usually skipped slots) fall back to `None`.
139
- let blockTimeBySlot = Dict.make()
140
- resp.blocks->Array.forEach(b => {
141
- switch b.blockTime {
142
- | Some(t) => blockTimeBySlot->Dict.set(b.slot->Int.toString, t)
143
- | None => ()
144
- }
145
- })
146
-
147
135
  let parsedQueueItems = resp.items->Array.map(item => {
148
136
  // Routing happened in Rust; the item references its registration by
149
137
  // chain-scoped index.
@@ -161,10 +149,13 @@ let make = (
161
149
  onEventRegistration,
162
150
  chainId,
163
151
  blockNumber: item.slot,
164
- logIndex: synthLogIndex(
165
- ~transactionIndex=item.transactionIndex,
166
- ~instructionAddress=item.instructionAddress,
167
- ),
152
+ // A slot orders by `(transactionIndex, instructionAddress)` — the
153
+ // transaction, then the instruction's position in its CPI tree. Both
154
+ // ride the item so the buffer comparator can order on the pair
155
+ // directly; no single integer can hold it (Solana allows a CPI depth
156
+ // of 5, which needs more bits than a JS integer is exact to).
157
+ logIndex: item.transactionIndex,
158
+ orderPath: item.instructionAddress,
168
159
  // The parent transaction is materialised from the store at batch prep.
169
160
  transactionIndex: item.transactionIndex,
170
161
  payload: payload->(Utils.magic: Envio.svmInstruction => Internal.eventPayload),
@@ -173,112 +164,29 @@ let make = (
173
164
 
174
165
  let parsingTimeElapsed = parsingRef->Performance.secondsSince
175
166
  let highestSlot = resp.nextSlot - 1
176
- let latestBlockTime =
177
- blockTimeBySlot
178
- ->Utils.Dict.dangerouslyGetNonOption(highestSlot->Int.toString)
179
- ->Option.getOr(0)
180
-
181
- // Best-effort (slot, blockhash) pairs from the blocks the server returned
182
- // for this range. Gaps (skipped slots, or slots without matched data) are
183
- // fine — reorg detection only compares hashes for slots it has observed.
184
- let blockHashes = resp.blocks->Array.map((b): ReorgDetection.blockData => {
185
- blockNumber: b.slot,
186
- blockHash: b.blockhash,
187
- })
188
167
 
189
168
  let totalTimeElapsed = totalTimeRef->Performance.secondsSince
190
169
 
191
170
  {
192
- latestFetchedBlockTimestamp: latestBlockTime,
193
171
  parsedQueueItems,
194
172
  // Raw transactions kept in Rust; materialised (selected fields) at batch prep.
195
173
  transactionStore: Some(transactionStore),
196
174
  // Raw blocks kept in Rust; materialised onto the payload at batch prep.
197
- blockStore: Some(blockStore),
175
+ // Their (slot, blockhash) pairs also drive reorg detection on merge.
176
+ blockStore,
198
177
  latestFetchedBlockNumber: highestSlot,
199
178
  stats: {totalTimeElapsed, parsingTimeElapsed, pageFetchTime},
200
179
  knownHeight,
201
- blockHashes,
202
180
  fromBlockQueried: fromBlock,
203
181
  requestStats,
204
182
  }
205
183
  }
206
184
 
207
- // Fetch (slot, blockhash, blockTime) for blocks in an inclusive slot range,
208
- // paginating on the server's `nextSlot` cursor. `toSlot` is exclusive on the
209
- // wire, so we request `maxSlot + 1`; the caller filters to the exact slots.
210
- let queryBlockDataRange = async (~fromSlot, ~toSlot) => {
211
- let blockDatas = []
212
- let requestStats = []
213
- let fromRef = ref(fromSlot)
214
- let keepGoing = ref(true)
215
- while keepGoing.contents {
216
- let query: SvmHyperSyncClient.query = {
217
- fromSlot: fromRef.contents,
218
- toSlot: toSlot + 1,
219
- includeAllBlocks: true,
220
- fields: {block: [Slot, Blockhash, BlockTime]},
221
- maxNumBlocks: 1000,
222
- }
223
- let timerRef = Performance.now()
224
- // Block-only query; the store pages are empty.
225
- let (resp, _, _) = await client.get(~query)
226
- requestStats
227
- ->Array.push({Source.method: "getBlockHashes", seconds: timerRef->Performance.secondsSince})
228
- ->ignore
229
- resp.data.blocks->Array.forEach(b =>
230
- blockDatas
231
- ->Array.push({
232
- ReorgDetection.blockNumber: b.slot,
233
- blockHash: b.blockhash,
234
- blockTimestamp: b.blockTime->Option.getOr(0),
235
- })
236
- ->ignore
237
- )
238
-
239
- // `nextSlot` is the (exclusive) resume cursor. Stop once it passes the
240
- // range, or fails to advance — the latter guards against an infinite loop.
241
- if resp.nextSlot > toSlot || resp.nextSlot <= fromRef.contents {
242
- keepGoing := false
243
- } else {
244
- fromRef := resp.nextSlot
245
- }
246
- }
247
- (blockDatas, requestStats)
248
- }
249
-
250
- let getBlockHashes = async (~blockNumbers, ~logger as _) =>
251
- switch blockNumbers->Array.get(0) {
252
- | None => {Source.result: Ok([]), requestStats: []}
253
- | Some(firstSlot) =>
254
- try {
255
- let minSlot = ref(firstSlot)
256
- let maxSlot = ref(firstSlot)
257
- let requested = Utils.Set.make()
258
- blockNumbers->Array.forEach(slot => {
259
- if slot < minSlot.contents {
260
- minSlot := slot
261
- }
262
- if slot > maxSlot.contents {
263
- maxSlot := slot
264
- }
265
- requested->Utils.Set.add(slot)->ignore
266
- })
267
- let (blockDatas, requestStats) = await queryBlockDataRange(
268
- ~fromSlot=minSlot.contents,
269
- ~toSlot=maxSlot.contents,
270
- )
271
- // Keep one entry per requested slot; drop duplicates and unrelated slots.
272
- {
273
- Source.result: Ok(
274
- blockDatas->Array.filter(data => requested->Utils.Set.delete(data.blockNumber)),
275
- ),
276
- requestStats,
277
- }
278
- } catch {
279
- | exn => {Source.result: Error(exn), requestStats: []}
280
- }
281
- }
185
+ // Called through the client rather than passed as a value: the client is a
186
+ // napi class, so a detached method reference loses the instance it belongs to.
187
+ let getBlockHashes = HyperSync.makeGetBlockHashes(
188
+ ~query=(~blockNumbers) => client.getBlockHashes(~blockNumbers),
189
+ )
282
190
 
283
191
  {
284
192
  name,
@@ -1,18 +1,13 @@
1
1
  // Generated by ReScript, PLEASE EDIT WITH CARE
2
2
 
3
3
  import * as Source from "./Source.res.mjs";
4
+ import * as HyperSync from "./HyperSync.res.mjs";
4
5
  import * as Performance from "../bindings/Performance.res.mjs";
5
- import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
6
6
  import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
7
7
  import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
8
8
  import * as SvmHyperSyncClient from "./SvmHyperSyncClient.res.mjs";
9
9
  import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
10
10
 
11
- function synthLogIndex(transactionIndex, instructionAddress) {
12
- let addrSum = Stdlib_Array.reduce(instructionAddress, 0, (acc, n) => ((acc << 10) + n | 0) + 1 | 0);
13
- return (transactionIndex << 16) + addrSum | 0;
14
- }
15
-
16
11
  function parseDecoded(d) {
17
12
  let args;
18
13
  try {
@@ -58,7 +53,7 @@ function toSvmInstruction(item, programName, instructionName) {
58
53
  function make(param) {
59
54
  let onEventRegistrations = param.onEventRegistrations;
60
55
  let chainId = param.chainId;
61
- let client = SvmHyperSyncClient.make(param.endpointUrl, param.apiToken, param.clientTimeoutMillis, undefined, undefined, undefined, SvmHyperSyncClient.Registration.fromOnEventRegistrations(onEventRegistrations), param.addressStore);
56
+ let client = SvmHyperSyncClient.make(param.endpointUrl, param.apiToken, param.clientTimeoutMillis, undefined, undefined, SvmHyperSyncClient.Registration.fromOnEventRegistrations(onEventRegistrations), param.addressStore);
62
57
  let getItemsOrThrow = async (fromBlock, toBlock, addressSet, knownHeight, param, selection, itemsTarget, retry, param$1) => {
63
58
  let totalTimeRef = Performance.now();
64
59
  let pageFetchRef = Performance.now();
@@ -76,6 +71,7 @@ function make(param) {
76
71
  match = await client.getEventItems(query, addressSet);
77
72
  } catch (raw_exn) {
78
73
  let exn = Primitive_exceptions.internalToException(raw_exn);
74
+ HyperSync.reraiseIfRecoverable(exn);
79
75
  throw {
80
76
  RE_EXN_ID: Source.GetItemsError,
81
77
  _1: {
@@ -98,14 +94,6 @@ function make(param) {
98
94
  seconds: pageFetchTime
99
95
  }];
100
96
  let parsingRef = Performance.now();
101
- let blockTimeBySlot = {};
102
- resp.blocks.forEach(b => {
103
- let t = b.blockTime;
104
- if (t !== undefined) {
105
- blockTimeBySlot[b.slot.toString()] = t;
106
- return;
107
- }
108
- });
109
97
  let parsedQueueItems = resp.items.map(item => {
110
98
  let onEventRegistration = onEventRegistrations[item.onEventRegistrationIndex];
111
99
  let eventConfig = onEventRegistration.eventConfig;
@@ -115,28 +103,22 @@ function make(param) {
115
103
  onEventRegistration: onEventRegistration,
116
104
  chainId: chainId,
117
105
  blockNumber: item.slot,
118
- logIndex: synthLogIndex(item.transactionIndex, item.instructionAddress),
106
+ logIndex: item.transactionIndex,
107
+ orderPath: item.instructionAddress,
119
108
  transactionIndex: item.transactionIndex,
120
109
  payload: payload
121
110
  };
122
111
  });
123
112
  let parsingTimeElapsed = Performance.secondsSince(parsingRef);
124
113
  let highestSlot = resp.nextSlot - 1 | 0;
125
- let latestBlockTime = Stdlib_Option.getOr(blockTimeBySlot[highestSlot.toString()], 0);
126
- let blockHashes = resp.blocks.map(b => ({
127
- blockHash: b.blockhash,
128
- blockNumber: b.slot
129
- }));
130
114
  let totalTimeElapsed = Performance.secondsSince(totalTimeRef);
131
115
  return {
132
116
  knownHeight: knownHeight,
133
- blockHashes: blockHashes,
134
117
  parsedQueueItems: parsedQueueItems,
135
118
  transactionStore: Primitive_option.some(match[1]),
136
- blockStore: Primitive_option.some(match[2]),
119
+ blockStore: match[2],
137
120
  fromBlockQueried: fromBlock,
138
121
  latestFetchedBlockNumber: highestSlot,
139
- latestFetchedBlockTimestamp: latestBlockTime,
140
122
  stats: {
141
123
  "total time elapsed (s)": totalTimeElapsed,
142
124
  "parsing time (s)": parsingTimeElapsed,
@@ -145,101 +127,7 @@ function make(param) {
145
127
  requestStats: requestStats
146
128
  };
147
129
  };
148
- let queryBlockDataRange = async (fromSlot, toSlot) => {
149
- let blockDatas = [];
150
- let requestStats = [];
151
- let fromRef = fromSlot;
152
- let keepGoing = true;
153
- while (keepGoing) {
154
- let query_toSlot = toSlot + 1 | 0;
155
- let query_includeAllBlocks = true;
156
- let query_fields = {
157
- block: [
158
- "slot",
159
- "blockhash",
160
- "block_time"
161
- ]
162
- };
163
- let query_maxNumBlocks = 1000;
164
- let query = {
165
- fromSlot: fromRef,
166
- toSlot: query_toSlot,
167
- includeAllBlocks: query_includeAllBlocks,
168
- fields: query_fields,
169
- maxNumBlocks: query_maxNumBlocks
170
- };
171
- let timerRef = Performance.now();
172
- let match = await client.get(query);
173
- let resp = match[0];
174
- requestStats.push({
175
- method: "getBlockHashes",
176
- seconds: Performance.secondsSince(timerRef)
177
- });
178
- resp.data.blocks.forEach(b => {
179
- blockDatas.push({
180
- blockHash: b.blockhash,
181
- blockNumber: b.slot,
182
- blockTimestamp: Stdlib_Option.getOr(b.blockTime, 0)
183
- });
184
- });
185
- if (resp.nextSlot > toSlot || resp.nextSlot <= fromRef) {
186
- keepGoing = false;
187
- } else {
188
- fromRef = resp.nextSlot;
189
- }
190
- };
191
- return [
192
- blockDatas,
193
- requestStats
194
- ];
195
- };
196
- let getBlockHashes = async (blockNumbers, param) => {
197
- let firstSlot = blockNumbers[0];
198
- if (firstSlot === undefined) {
199
- return {
200
- result: {
201
- TAG: "Ok",
202
- _0: []
203
- },
204
- requestStats: []
205
- };
206
- }
207
- try {
208
- let minSlot = {
209
- contents: firstSlot
210
- };
211
- let maxSlot = {
212
- contents: firstSlot
213
- };
214
- let requested = new Set();
215
- blockNumbers.forEach(slot => {
216
- if (slot < minSlot.contents) {
217
- minSlot.contents = slot;
218
- }
219
- if (slot > maxSlot.contents) {
220
- maxSlot.contents = slot;
221
- }
222
- requested.add(slot);
223
- });
224
- let match = await queryBlockDataRange(minSlot.contents, maxSlot.contents);
225
- return {
226
- result: {
227
- TAG: "Ok",
228
- _0: match[0].filter(data => requested.delete(data.blockNumber))
229
- },
230
- requestStats: match[1]
231
- };
232
- } catch (raw_exn) {
233
- let exn = Primitive_exceptions.internalToException(raw_exn);
234
- return {
235
- result: {
236
- TAG: "Error",
237
- _0: exn
238
- },
239
- requestStats: []
240
- };
241
- }
242
- };
130
+ let getBlockHashes = HyperSync.makeGetBlockHashes(blockNumbers => client.getBlockHashes(blockNumbers));
243
131
  return {
244
132
  name: "SvmHyperSync",
245
133
  sourceFor: "Sync",
@@ -264,9 +152,8 @@ function make(param) {
264
152
  }
265
153
 
266
154
  export {
267
- synthLogIndex,
268
155
  parseDecoded,
269
156
  toSvmInstruction,
270
157
  make,
271
158
  }
272
- /* SvmHyperSyncClient Not a pure module */
159
+ /* Source Not a pure module */
@@ -177,7 +177,11 @@ export function makeCallEffect(rpcUrls: string[]) {
177
177
  functionName: name,
178
178
  data: (result.data ?? "0x") as `0x${string}`,
179
179
  });
180
- const values = Array.isArray(decoded) ? decoded : [decoded];
180
+ // graph-ts returns one `ethereum.Value` per ABI output. A single
181
+ // tuple/struct is therefore `[tuple]`, not the flattened fields —
182
+ // generated bindings call `result[0].toTuple()`.
183
+ const outputs = (abi[0] as { outputs?: unknown[] }).outputs ?? [];
184
+ const values = outputs.length === 1 ? [decoded] : Array.isArray(decoded) ? decoded : [decoded];
181
185
  return JSON.stringify({ reverted: false, values: values.map(encodeArg) });
182
186
  } catch {
183
187
  // Output the declared signature can't decode counts as a failed call,
@@ -54,6 +54,16 @@ export function integerDivision(a: unknown, b: unknown): unknown {
54
54
  }
55
55
 
56
56
  export const DIVIDE_HELPER = "__envio_idiv";
57
+ export const RETAG_HELPER = "__envio_retag";
58
+
59
+ /** `changetype<Foo>(x)` → `__envio_retag(Foo, x)` when Foo is a class name. */
60
+ export function rewriteChangetype(source: string): string {
61
+ if (!source.includes("changetype<")) return source;
62
+ return source.replace(
63
+ /changetype\s*<\s*([A-Za-z_][A-Za-z0-9_]*)\s*>\s*\(/g,
64
+ `${RETAG_HELPER}($1, `,
65
+ );
66
+ }
57
67
 
58
68
  /** Returns the source unchanged when it divides nothing, or can't be parsed. */
59
69
  export function rewriteDivision(source: string): string {
@@ -653,6 +653,14 @@ export class Value {
653
653
  toArray(): Value[] {
654
654
  return this.data as Value[];
655
655
  }
656
+ // Official `ethereum.Value` exposes these; generated bindings call
657
+ // `result[0].toTuple()` on every struct/tuple return.
658
+ toTuple(): Value[] {
659
+ return this.toArray();
660
+ }
661
+ toTupleArray(): Value[][] {
662
+ return this.toArray().map(value => value.toArray());
663
+ }
656
664
  toMatrix(): Value[][] {
657
665
  return this.toArray().map((row) => row.toArray());
658
666
  }
@@ -769,8 +777,9 @@ export class Entity extends TypedMap<string, Value> {
769
777
  this.set(key, Value.fromNull());
770
778
  }
771
779
  merge(sources: Entity[]): this {
772
- for (const entity of sources) {
773
- for (const entry of entity.entries) {
780
+ // graph-ts assigns right to left, so the leftmost source wins a key.
781
+ for (let index = sources.length - 1; index >= 0; index--) {
782
+ for (const entry of sources[index].entries) {
774
783
  this.set(entry.key, entry.value);
775
784
  }
776
785
  }
@@ -1020,7 +1029,59 @@ export const store = strictNamespace("store", storeImpl);
1020
1029
  // ethereum
1021
1030
  // ---------------------------------------------------------------------------
1022
1031
 
1023
- export class EthereumValue extends Value {}
1032
+ /**
1033
+ * `ethereum.Value` names its factories after Solidity types rather than store
1034
+ * ones, and `graph codegen` emits those names for every contract-call argument.
1035
+ * They tag the same store kinds underneath — an ABI value's Solidity type is
1036
+ * already carried by the call signature.
1037
+ */
1038
+ export class EthereumValue extends Value {
1039
+ static fromSignedBigInt(value: BigInt_): Value {
1040
+ return Value.fromBigInt(value);
1041
+ }
1042
+ static fromUnsignedBigInt(value: BigInt_): Value {
1043
+ return Value.fromBigInt(value);
1044
+ }
1045
+ static fromFixedBytes(value: Bytes): Value {
1046
+ return Value.fromBytes(value);
1047
+ }
1048
+ static fromTuple(values: Value[]): Value {
1049
+ return Value.fromArray(values);
1050
+ }
1051
+ static fromFixedSizedArray(values: Value[]): Value {
1052
+ return Value.fromArray(values);
1053
+ }
1054
+ static fromTupleArray(values: Value[][]): Value {
1055
+ return Value.fromArray(values.map(EthereumValue.fromTuple));
1056
+ }
1057
+ static fromAddressArray(values: Address[]): Value {
1058
+ return Value.fromArray(values.map(Value.fromAddress));
1059
+ }
1060
+ static fromFixedBytesArray(values: Bytes[]): Value {
1061
+ return Value.fromArray(values.map(Value.fromBytes));
1062
+ }
1063
+ static fromSignedBigIntArray(values: BigInt_[]): Value {
1064
+ return Value.fromArray(values.map(Value.fromBigInt));
1065
+ }
1066
+ static fromUnsignedBigIntArray(values: BigInt_[]): Value {
1067
+ return Value.fromArray(values.map(Value.fromBigInt));
1068
+ }
1069
+ toTuple(): Value[] {
1070
+ return this.toArray();
1071
+ }
1072
+ toTupleArray(): Value[][] {
1073
+ return this.toArray().map(value => value.toArray());
1074
+ }
1075
+ toFixedBytes(): Bytes {
1076
+ return this.toBytes();
1077
+ }
1078
+ toAddressArray(): Address[] {
1079
+ return this.toArray().map(value => value.toAddress());
1080
+ }
1081
+ toFixedBytesArray(): Bytes[] {
1082
+ return this.toArray().map(value => value.toBytes());
1083
+ }
1084
+ }
1024
1085
 
1025
1086
  class SmartContractCall {
1026
1087
  constructor(
@@ -1119,7 +1180,9 @@ function callContract(
1119
1180
  }
1120
1181
 
1121
1182
  function toEthereumValue(value: unknown): EthereumValue {
1122
- return toValue(value) as EthereumValue;
1183
+ if (value instanceof EthereumValue) return value;
1184
+ const wrapped = toValue(value);
1185
+ return new EthereumValue(wrapped.kind, wrapped.data);
1123
1186
  }
1124
1187
 
1125
1188
  /** A graph-ts value as the plain JS an ABI encoder takes. */
@@ -1629,6 +1692,17 @@ export function changetype<T>(value: unknown): T {
1629
1692
  return value as T;
1630
1693
  }
1631
1694
 
1695
+ // `changetype<GeneratedTuple>(result[0].toTuple())` is a pointer reinterpret
1696
+ // in AssemblyScript; in JS the named getters live on the generated class.
1697
+ // The load hook rewrites that call to this helper so `.value` etc. resolve.
1698
+ export function retagChangetype(ctor: unknown, value: unknown): unknown {
1699
+ if (value !== null && typeof value === "object" && typeof ctor === "function") {
1700
+ const proto = (ctor as Function).prototype;
1701
+ if (proto) Object.setPrototypeOf(value, proto);
1702
+ }
1703
+ return changetype(value);
1704
+ }
1705
+
1632
1706
  /**
1633
1707
  * AssemblyScript's primitives are namespaces as well as types: `i32.MAX_VALUE`
1634
1708
  * reads a bound, `i32(x)` truncates to one. Both are ordinary source in a
@@ -1693,3 +1767,105 @@ export const assemblyScriptPrimitives: Record<string, unknown> = {
1693
1767
  f32: floatNamespace("f32", true),
1694
1768
  f64: floatNamespace("f64", false),
1695
1769
  };
1770
+
1771
+ // ---------------------------------------------------------------------------
1772
+ // Host namespaces the generated declarations re-export
1773
+ //
1774
+ // A mapping importing one of these fails at ESM link time if it isn't exported,
1775
+ // which is a missing-export error naming nothing useful. Exporting them means an
1776
+ // unsupported one refuses by name like everything else.
1777
+ // ---------------------------------------------------------------------------
1778
+
1779
+ const typeConversionImpl = {
1780
+ bytesToString: (bytes: Uint8Array) => new TextDecoder().decode(bytes),
1781
+ bytesToHex: (bytes: Uint8Array) => new ByteArray(bytes).toHexString(),
1782
+ bigIntToString: (value: Uint8Array) => new BigInt_(fromLittleEndian(value, true)).toString(),
1783
+ bigIntToHex: (value: Uint8Array) => new BigInt_(fromLittleEndian(value, true)).toHexString(),
1784
+ stringToH160: (value: string) => Address.fromString(value) as Bytes,
1785
+ bytesToBase58: (_: Uint8Array): string => {
1786
+ throw unsupported("typeConversion.bytesToBase58", "a mapping handler");
1787
+ },
1788
+ };
1789
+
1790
+ export const typeConversion = strictNamespace("typeConversion", typeConversionImpl);
1791
+
1792
+ const bigIntImpl = {
1793
+ plus: (x: BigInt_, y: BigInt_) => x.plus(y),
1794
+ minus: (x: BigInt_, y: BigInt_) => x.minus(y),
1795
+ times: (x: BigInt_, y: BigInt_) => x.times(y),
1796
+ dividedBy: (x: BigInt_, y: BigInt_) => x.div(y),
1797
+ dividedByDecimal: (x: BigInt_, y: BigDecimal) => x.toBigDecimal().div(y),
1798
+ mod: (x: BigInt_, y: BigInt_) => x.mod(y),
1799
+ pow: (x: BigInt_, exp: number) => x.pow(exp),
1800
+ fromString: (value: string) => BigInt_.fromString(value),
1801
+ bitOr: (x: BigInt_, y: BigInt_) => x.bitOr(y),
1802
+ bitAnd: (x: BigInt_, y: BigInt_) => x.bitAnd(y),
1803
+ leftShift: (x: BigInt_, bits: number) => x.leftShift(bits),
1804
+ rightShift: (x: BigInt_, bits: number) => x.rightShift(bits),
1805
+ };
1806
+
1807
+ export const bigInt = strictNamespace("bigInt", bigIntImpl);
1808
+
1809
+ const bigDecimalImpl = {
1810
+ plus: (x: BigDecimal, y: BigDecimal) => x.plus(y),
1811
+ minus: (x: BigDecimal, y: BigDecimal) => x.minus(y),
1812
+ times: (x: BigDecimal, y: BigDecimal) => x.times(y),
1813
+ dividedBy: (x: BigDecimal, y: BigDecimal) => x.div(y),
1814
+ equals: (x: BigDecimal, y: BigDecimal) => x.equals(y),
1815
+ toString: (value: BigDecimal) => value.toString(),
1816
+ fromString: (value: string) => BigDecimal.fromString(value),
1817
+ };
1818
+
1819
+ export const bigDecimal = strictNamespace("bigDecimal", bigDecimalImpl);
1820
+
1821
+ export function concat(a: ByteArray, b: ByteArray): ByteArray {
1822
+ return a.concat(b);
1823
+ }
1824
+
1825
+ export function parseCSV(csv: string): string[] {
1826
+ return csv.split(",").map(field => field.trim());
1827
+ }
1828
+
1829
+ /** Restores the multihash prefix an IPFS hash loses when stored in a bytes32. */
1830
+ export function addQm(a: ByteArray): ByteArray {
1831
+ return ByteArray.fromHexString("0x1220").concat(a);
1832
+ }
1833
+
1834
+ export enum YAMLValueKind {
1835
+ NULL = 0,
1836
+ BOOL = 1,
1837
+ NUMBER = 2,
1838
+ STRING = 3,
1839
+ ARRAY = 4,
1840
+ OBJECT = 5,
1841
+ TAGGED = 6,
1842
+ }
1843
+
1844
+ export class YAMLValue {
1845
+ constructor(_kind: YAMLValueKind, _data: unknown) {
1846
+ throw unsupported("the yaml API", "a mapping handler");
1847
+ }
1848
+ }
1849
+
1850
+ export class YAMLTaggedValue {
1851
+ constructor(_tag: string, _value: YAMLValue) {
1852
+ throw unsupported("the yaml API", "a mapping handler");
1853
+ }
1854
+ }
1855
+
1856
+ const yamlImpl = {
1857
+ fromBytes: (_: Bytes): YAMLValue => {
1858
+ throw unsupported("yaml.fromBytes", "a mapping handler");
1859
+ },
1860
+ try_fromBytes: (_: Bytes): unknown => {
1861
+ throw unsupported("yaml.try_fromBytes", "a mapping handler");
1862
+ },
1863
+ fromString: (_: string): YAMLValue => {
1864
+ throw unsupported("yaml.fromString", "a mapping handler");
1865
+ },
1866
+ try_fromString: (_: string): unknown => {
1867
+ throw unsupported("yaml.try_fromString", "a mapping handler");
1868
+ },
1869
+ };
1870
+
1871
+ export const yaml = strictNamespace("yaml", yamlImpl);