envio 3.15.0 → 3.16.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 (186) hide show
  1. package/README.md +40 -34
  2. package/evm.schema.json +11 -2
  3. package/fuel.schema.json +10 -1
  4. package/index.d.ts +6 -0
  5. package/licenses/README.md +1 -1
  6. package/package.json +10 -9
  7. package/src/Batch.res.mjs +5 -5
  8. package/src/BatchProcessing.res +1 -0
  9. package/src/BatchProcessing.res.mjs +1 -0
  10. package/src/Bin.res +7 -0
  11. package/src/Bin.res.mjs +8 -0
  12. package/src/ChainFetching.res +450 -16
  13. package/src/ChainFetching.res.mjs +403 -32
  14. package/src/ChainState.res +124 -7
  15. package/src/ChainState.res.mjs +128 -67
  16. package/src/ChainState.resi +26 -0
  17. package/src/Clock.res +57 -0
  18. package/src/Clock.res.mjs +72 -0
  19. package/src/Config.res +31 -3
  20. package/src/Config.res.mjs +15 -4
  21. package/src/ContractRegisterContext.res +3 -6
  22. package/src/ContractRegisterContext.res.mjs +4 -2
  23. package/src/Core.res +95 -36
  24. package/src/Core.res.mjs +90 -1
  25. package/src/CrossChainState.res +12 -2
  26. package/src/CrossChainState.res.mjs +3 -3
  27. package/src/Ecosystem.res +7 -4
  28. package/src/Ecosystem.res.mjs +8 -8
  29. package/src/EffectCacheLookup.res +98 -0
  30. package/src/EffectCacheLookup.res.mjs +116 -0
  31. package/src/EffectState.res +10 -2
  32. package/src/EffectState.res.mjs +8 -4
  33. package/src/EffectState.resi +3 -1
  34. package/src/EntityId.res +2 -7
  35. package/src/EntityId.res.mjs +1 -8
  36. package/src/EntityTables.res +4 -1
  37. package/src/EntityTables.res.mjs +1 -1
  38. package/src/Env.res +20 -0
  39. package/src/Env.res.mjs +16 -0
  40. package/src/EventProcessing.res +38 -1
  41. package/src/EventProcessing.res.mjs +28 -1
  42. package/src/FetchState.res +408 -73
  43. package/src/FetchState.res.mjs +361 -91
  44. package/src/FinalizeBackfill.res +2 -4
  45. package/src/FinalizeBackfill.res.mjs +3 -4
  46. package/src/HandlerLoader.res +39 -15
  47. package/src/HandlerLoader.res.mjs +27 -8
  48. package/src/HandlerRegister.res +4 -0
  49. package/src/HandlerRegister.res.mjs +5 -2
  50. package/src/HandlerRegister.resi +2 -0
  51. package/src/InMemoryTable.res +17 -16
  52. package/src/InMemoryTable.res.mjs +14 -15
  53. package/src/IndexerState.res +28 -4
  54. package/src/IndexerState.res.mjs +19 -6
  55. package/src/IndexerState.resi +4 -0
  56. package/src/Internal.res +61 -49
  57. package/src/Internal.res.mjs +36 -80
  58. package/src/LoadLayer.res +54 -15
  59. package/src/LoadLayer.res.mjs +75 -45
  60. package/src/LoadLayer.resi +26 -0
  61. package/src/Main.res +14 -1
  62. package/src/Main.res.mjs +4 -3
  63. package/src/Persistence.res +3 -23
  64. package/src/Persistence.res.mjs +3 -3
  65. package/src/PreloadLookahead.res +202 -0
  66. package/src/PreloadLookahead.res.mjs +218 -0
  67. package/src/Rollback.res +0 -1
  68. package/src/Rollback.res.mjs +1 -1
  69. package/src/RunScope.res +3 -0
  70. package/src/SafeCheckpointTracking.res +4 -4
  71. package/src/SafeCheckpointTracking.res.mjs +1 -1
  72. package/src/SimulateItems.res +42 -26
  73. package/src/SimulateItems.res.mjs +40 -16
  74. package/src/StagedRows.res +106 -0
  75. package/src/StagedRows.res.mjs +102 -0
  76. package/src/Staging.res +32 -14
  77. package/src/Staging.res.mjs +17 -20
  78. package/src/Storage.res +303 -948
  79. package/src/Storage.res.mjs +229 -708
  80. package/src/SubgraphLog.res +19 -0
  81. package/src/SubgraphLog.res.mjs +26 -0
  82. package/src/Supervisor.res +75 -56
  83. package/src/Supervisor.res.mjs +55 -26
  84. package/src/SyncWrites.res +107 -0
  85. package/src/SyncWrites.res.mjs +98 -0
  86. package/src/TestIndexer.res +22 -27
  87. package/src/TestIndexer.res.mjs +20 -17
  88. package/src/Throttler.res +15 -8
  89. package/src/Throttler.res.mjs +9 -6
  90. package/src/Throttler.resi +1 -1
  91. package/src/UserContext.res +419 -46
  92. package/src/UserContext.res.mjs +318 -30
  93. package/src/Utils.res +17 -6
  94. package/src/Utils.res.mjs +12 -6
  95. package/src/Writing.res +0 -1
  96. package/src/Writing.res.mjs +1 -1
  97. package/src/bindings/ClickHouse.res +0 -322
  98. package/src/bindings/ClickHouse.res.mjs +0 -338
  99. package/src/bindings/StorageClient.res +108 -174
  100. package/src/bindings/StorageClient.res.mjs +19 -8
  101. package/src/bindings/Vitest.res +0 -26
  102. package/src/bindings/Vitest.res.mjs +0 -3
  103. package/src/db/Table.res +24 -150
  104. package/src/db/Table.res.mjs +38 -272
  105. package/src/sources/AddressSet.res +2 -0
  106. package/src/sources/AddressStore.res +3 -0
  107. package/src/sources/CallSource.res +274 -0
  108. package/src/sources/CallSource.res.mjs +196 -0
  109. package/src/sources/ChainSources.res +2 -0
  110. package/src/sources/ChainSources.res.mjs +2 -2
  111. package/src/sources/EvmChain.res +10 -1
  112. package/src/sources/EvmChain.res.mjs +6 -3
  113. package/src/sources/EvmHyperSyncSource.res +2 -2
  114. package/src/sources/EvmHyperSyncSource.res.mjs +1 -1
  115. package/src/sources/EvmRpcClient.res +13 -3
  116. package/src/sources/EvmRpcClient.res.mjs +4 -3
  117. package/src/sources/EvmRpcWs.res +4 -3
  118. package/src/sources/EvmRpcWs.res.mjs +2 -2
  119. package/src/sources/HeightFeed.res +31 -10
  120. package/src/sources/HeightFeed.res.mjs +10 -6
  121. package/src/sources/HeightStream.res +32 -17
  122. package/src/sources/HeightStream.res.mjs +10 -10
  123. package/src/sources/HyperSync.res +2 -1
  124. package/src/sources/HyperSync.res.mjs +2 -1
  125. package/src/sources/HyperSyncSSE.res +4 -6
  126. package/src/sources/HyperSyncSSE.res.mjs +2 -2
  127. package/src/sources/RpcSource.res +10 -1
  128. package/src/sources/RpcSource.res.mjs +4 -3
  129. package/src/sources/Source.res +3 -0
  130. package/src/sources/SourceManager.res +84 -77
  131. package/src/sources/SourceManager.res.mjs +17 -20
  132. package/src/sources/SourceManager.resi +3 -4
  133. package/src/sources/StartBlockResolver.res +1 -0
  134. package/src/sources/StartBlockResolver.res.mjs +3 -2
  135. package/src/sources/SvmHyperSyncSource.res +2 -2
  136. package/src/sources/SvmHyperSyncSource.res.mjs +1 -1
  137. package/src/subgraph/abi-types.ts +84 -0
  138. package/src/subgraph/aggregations.ts +745 -0
  139. package/src/subgraph/as-package.ts +143 -0
  140. package/src/subgraph/assemblyscript-regex.ts +67 -0
  141. package/src/subgraph/assemblyscript.ts +731 -0
  142. package/src/subgraph/blocks.ts +398 -0
  143. package/src/subgraph/calls-source.ts +702 -0
  144. package/src/subgraph/calls.ts +258 -0
  145. package/src/subgraph/conformance.ts +106 -0
  146. package/src/subgraph/errors.ts +85 -0
  147. package/src/subgraph/graft.ts +73 -0
  148. package/src/subgraph/graph-cli-worker.mjs +35 -0
  149. package/src/subgraph/graph-cli.ts +900 -0
  150. package/src/subgraph/graph-node-decimal.ts +199 -0
  151. package/src/subgraph/graph-ts-enums.ts +28 -0
  152. package/src/subgraph/graph-ts-types/VERSION +2 -0
  153. package/src/subgraph/graph-ts-types/chain/arweave.d.ts +70 -0
  154. package/src/subgraph/graph-ts-types/chain/cosmos.d.ts +327 -0
  155. package/src/subgraph/graph-ts-types/chain/ethereum.d.ts +233 -0
  156. package/src/subgraph/graph-ts-types/chain/near.d.ts +253 -0
  157. package/src/subgraph/graph-ts-types/chain/starknet.d.ts +32 -0
  158. package/src/subgraph/graph-ts-types/common/collections.d.ts +136 -0
  159. package/src/subgraph/graph-ts-types/common/conversion.d.ts +11 -0
  160. package/src/subgraph/graph-ts-types/common/datasource.d.ts +30 -0
  161. package/src/subgraph/graph-ts-types/common/eager-offset.d.ts +0 -0
  162. package/src/subgraph/graph-ts-types/common/json.d.ts +17 -0
  163. package/src/subgraph/graph-ts-types/common/numbers.d.ts +120 -0
  164. package/src/subgraph/graph-ts-types/common/value.d.ts +120 -0
  165. package/src/subgraph/graph-ts-types/common/yaml.d.ts +90 -0
  166. package/src/subgraph/graph-ts-types/global/global.d.ts +194 -0
  167. package/src/subgraph/graph-ts-types/helper-functions.d.ts +22 -0
  168. package/src/subgraph/graph-ts-types/index.d.ts +102 -0
  169. package/src/subgraph/graph-ts.ts +4339 -0
  170. package/src/subgraph/hosts.ts +554 -0
  171. package/src/subgraph/log.ts +28 -0
  172. package/src/subgraph/receipts.ts +184 -0
  173. package/src/subgraph/register-slots.ts +93 -0
  174. package/src/subgraph/rollup.ts +109 -0
  175. package/src/subgraph/rpc.ts +337 -0
  176. package/src/subgraph/runtime.ts +2059 -0
  177. package/src/subgraph/scope.ts +176 -0
  178. package/src/subgraph/stats.ts +42 -0
  179. package/src/subgraph/template-instances.ts +66 -0
  180. package/src/subgraph/toolchain.ts +327 -0
  181. package/src/subgraph/transient.ts +176 -0
  182. package/svm.schema.json +11 -2
  183. package/src/PgWriting.res +0 -36
  184. package/src/PgWriting.res.mjs +0 -34
  185. package/src/db/IndexDefinition.res +0 -47
  186. package/src/db/IndexDefinition.res.mjs +0 -67
@@ -0,0 +1,4339 @@
1
+ /**
2
+ * What `@graphprotocol/graph-ts` resolves to at runtime inside a subgraph
3
+ * project. Types still resolve to the real package, so the editor and `tsc`
4
+ * see exactly what a subgraph developer sees today; only module resolution is
5
+ * swapped, and only for the mapping graph.
6
+ *
7
+ * Everything `graph codegen` emits sits on this surface — entity classes over
8
+ * `Entity`/`TypedMap`/`Value` and `store`, contract bindings over
9
+ * `ethereum.SmartContract`, template classes over `DataSourceTemplate` — so
10
+ * the project's real `generated/` runs on top of it unchanged.
11
+ */
12
+
13
+ import { appendFileSync, mkdirSync, readFileSync } from "node:fs";
14
+ import path from "node:path";
15
+ import BigNumber from "bignumber.js";
16
+ import { type Document, isAlias, isMap, isScalar, isSeq, parseDocument } from "yaml";
17
+ import type * as GraphTsTypes from "./graph-ts-types/index.js";
18
+ import * as GraphNodeDecimal from "./graph-node-decimal.ts";
19
+ import { subgraphAbiDecode, subgraphKeccak256 } from "../Core.res.mjs";
20
+ import { notify as notifyLog, warn } from "./log.ts";
21
+ import { operators } from "./assemblyscript.ts";
22
+ import { currentScope, type Scope } from "./scope.ts";
23
+ import { timeseriesId } from "./rollup.ts";
24
+ import { Level as LogLevel, ValueKind as EthereumValueKind } from "./graph-ts-enums.ts";
25
+ import { parseSignature, splitTypes } from "./abi-types.ts";
26
+ import {
27
+ PROTOTYPE_PASSTHROUGH,
28
+ strictNamespace,
29
+ unsupported,
30
+ unknown,
31
+ } from "./errors.ts";
32
+
33
+ const toHex = (bytes: Uint8Array): string =>
34
+ "0x" + Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength).toString("hex");
35
+ const hexToBytes = (hex: string): Uint8Array => new Uint8Array(Buffer.from(hex.replace(/^0x/, ""), "hex"));
36
+ /** `ty` decoded with the addon (alloy, as graph-node decodes it). */
37
+ const abiDecode = (ty: string, data: Uint8Array, params: boolean): unknown =>
38
+ subgraphAbiDecode(ty, data, params);
39
+
40
+ // ---------------------------------------------------------------------------
41
+ // Byte values
42
+ // ---------------------------------------------------------------------------
43
+
44
+ /**
45
+ * graph-ts stores every integer as a little-endian byte array, signed values in
46
+ * two's complement — so the byte order and the sign both have to be spelled out
47
+ * here rather than borrowed from a hex string, which reads big-endian.
48
+ */
49
+ /** An AssemblyScript i64 literal reaches the shim as a plain JS number. */
50
+ function asBigInt(value: bigint | number): bigint {
51
+ return typeof value === "bigint" ? value : BigInt(Math.trunc(value));
52
+ }
53
+
54
+ function toLittleEndian(value: bigint, size: number): Uint8Array {
55
+ const out = new Uint8Array(size);
56
+ let rest = BigInt.asUintN(size * 8, value);
57
+ for (let index = 0; index < size; index++) {
58
+ out[index] = Number(rest & 0xffn);
59
+ rest >>= 8n;
60
+ }
61
+ return out;
62
+ }
63
+
64
+ function fromLittleEndian(bytes: Uint8Array, signed: boolean): bigint {
65
+ let magnitude = 0n;
66
+ for (let index = bytes.length - 1; index >= 0; index--) {
67
+ magnitude = (magnitude << 8n) | BigInt(bytes[index]);
68
+ }
69
+ return signed ? BigInt.asIntN(bytes.length * 8, magnitude) : magnitude;
70
+ }
71
+
72
+ /** The smallest byte count that still holds `value` in two's complement. */
73
+ function byteWidth(value: bigint): number {
74
+ let size = 1;
75
+ while (BigInt.asIntN(size * 8, value) !== value) size++;
76
+ return size;
77
+ }
78
+
79
+ /**
80
+ * graph-ts' `BigInt.toU32()`/`toU64()`: the BigInt's own `width` bytes of two's
81
+ * complement, zero-padded to `size` — so the host's -1 (the byte 0xff) is 255
82
+ * while `BigInt.fromI32(-1)` (four bytes) is 4294967295 — overflowing only when
83
+ * a byte past `size` is set.
84
+ */
85
+ function unsignedFromSignedBytes(value: bigint, width: number, size: number, target: string): bigint {
86
+ if (value >= 0n) return fitted(value, 0n, (1n << BigInt(size * 8)) - 1n, target);
87
+ if (width > size) {
88
+ throw new Error(`Envio Subgraph: ${value} does not fit in an ${target}.`);
89
+ }
90
+ return BigInt.asUintN(width * 8, value);
91
+ }
92
+
93
+ /**
94
+ * graph-ts' `ByteArray.toI32()`/`toI64()`, which BigInt's defer to: the bytes
95
+ * past the width must be the sign padding, and the low bytes are read as two's
96
+ * complement. So a uint32 of 2^32 - 1 (`ff ff ff ff 00`) is -1: a value fits
97
+ * from -2^(8·size) to 2^(8·size) - 1 and wraps into the signed range.
98
+ */
99
+ function signedFromBytes(value: bigint, size: number, target: string): bigint {
100
+ const bound = 1n << BigInt(size * 8);
101
+ if (value < -bound || value >= bound) {
102
+ throw new Error(`Envio Subgraph: ${value} does not fit in an ${target}.`);
103
+ }
104
+ return BigInt.asIntN(size * 8, value);
105
+ }
106
+
107
+ function fitted(value: bigint, min: bigint, max: bigint, target: string): bigint {
108
+ if (value < min || value > max) {
109
+ throw new Error(`Envio Subgraph: ${value} does not fit in an ${target}.`);
110
+ }
111
+ return value;
112
+ }
113
+
114
+ const utf8Decoder = new TextDecoder("utf-8");
115
+
116
+ const BASE58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
117
+
118
+ function toBase58(bytes: Uint8Array): string {
119
+ let zeros = 0;
120
+ while (zeros < bytes.length && bytes[zeros] === 0) zeros++;
121
+ // Repeated division of the big-endian number by 58, in base 256.
122
+ const digits: number[] = [];
123
+ for (let index = zeros; index < bytes.length; index++) {
124
+ let carry = bytes[index];
125
+ for (let digit = 0; digit < digits.length; digit++) {
126
+ carry += digits[digit] << 8;
127
+ digits[digit] = carry % 58;
128
+ carry = Math.floor(carry / 58);
129
+ }
130
+ while (carry > 0) {
131
+ digits.push(carry % 58);
132
+ carry = Math.floor(carry / 58);
133
+ }
134
+ }
135
+ let out = "1".repeat(zeros);
136
+ for (let digit = digits.length - 1; digit >= 0; digit--) out += BASE58_ALPHABET[digits[digit]];
137
+ return out;
138
+ }
139
+
140
+ /** `I8.parseInt(pair, 16)` as AssemblyScript reads it, kept to a byte. */
141
+ function parseHexPair(pair: string): number {
142
+ let at = 0;
143
+ let sign = 1;
144
+ if (pair[0] === "-" || pair[0] === "+") {
145
+ sign = pair[0] === "-" ? -1 : 1;
146
+ at = 1;
147
+ }
148
+ let value = 0;
149
+ for (; at < pair.length; at++) {
150
+ const digit = parseInt(pair[at], 16);
151
+ if (Number.isNaN(digit)) break;
152
+ value = value * 16 + digit;
153
+ }
154
+ return (sign * value) & 0xff;
155
+ }
156
+
157
+ const HEX_DIGITS = /^[0-9a-fA-F]*$/;
158
+
159
+ /**
160
+ * graph-ts' `ByteArray.fromHexString`: it asserts on the whole string, strips
161
+ * one `0x`, then reads each pair with `I8.parseInt(pair, 16)`, which stops at
162
+ * the first character that isn't a hex digit: Croc pads "0x00000000" on the
163
+ * left with `0`s and the pair that holds the `x` reads as 0. Plain hex, which
164
+ * is every hash and address envio hands a mapping, decodes natively.
165
+ */
166
+ function hexBytes(hex: string): Uint8Array {
167
+ if (hex.length % 2 !== 0) throw new Error(`input ${hex} has odd length`);
168
+ const digits = hex.startsWith("0x") ? hex.slice(2) : hex;
169
+ if (HEX_DIGITS.test(digits)) return Buffer.from(digits, "hex");
170
+ const bytes = new Uint8Array(digits.length / 2);
171
+ for (let i = 0; i < bytes.length; i++) bytes[i] = parseHexPair(digits.slice(2 * i, 2 * i + 2));
172
+ return bytes;
173
+ }
174
+
175
+ export class ByteArray extends Uint8Array {
176
+ static fromHexString(hex: string): ByteArray {
177
+ return new ByteArray(hexBytes(hex));
178
+ }
179
+ static fromUTF8(input: string): ByteArray {
180
+ return new ByteArray(new TextEncoder().encode(input));
181
+ }
182
+ static fromI32(value: number): ByteArray {
183
+ return new ByteArray(toLittleEndian(BigInt(Math.trunc(value)), 4));
184
+ }
185
+ static fromBigInt(value: BigInt_): ByteArray {
186
+ return new ByteArray(toLittleEndian(value.valueOf() as bigint, value.byteLength));
187
+ }
188
+ static fromUint8Array(bytes: Uint8Array): ByteArray {
189
+ return new ByteArray(bytes);
190
+ }
191
+ static fromU32(value: number): ByteArray {
192
+ return ByteArray.fromI32(value);
193
+ }
194
+ static fromI64(value: bigint | number): ByteArray {
195
+ return new ByteArray(toLittleEndian(asBigInt(value), 8));
196
+ }
197
+ static fromU64(value: bigint): ByteArray {
198
+ return ByteArray.fromI64(value);
199
+ }
200
+ // graph-ts builds it as `ByteArray.fromI32(0)`: four zero bytes.
201
+ static empty(): ByteArray {
202
+ return ByteArray.fromI32(0);
203
+ }
204
+ toHex(): string {
205
+ return this.toHexString();
206
+ }
207
+ toHexString(): string {
208
+ return toHex(this as Uint8Array);
209
+ }
210
+ /**
211
+ * graph-node's `bytes_to_string`: UTF-8, invalid sequences replaced, and the
212
+ * trailing NULs of a fixed-size buffer trimmed — a bytes32 symbol (MKR) reads
213
+ * as `MKR`, and Aave's `id.toString() == "POOL"` holds.
214
+ */
215
+ toString(): string {
216
+ return utf8Decoder.decode(this).replace(/\u0000+$/, "");
217
+ }
218
+ /**
219
+ * graph-node's `bytes_to_base58`: Bitcoin-alphabet base58, each leading zero
220
+ * byte a leading `1`. Turns a bytes32 digest behind a `Qm` prefix into the
221
+ * CID a subgraph hands to `ipfs.cat` or a file data source.
222
+ */
223
+ toBase58(): string {
224
+ return toBase58(this);
225
+ }
226
+ toU32(): number {
227
+ return Number(fitted(fromLittleEndian(this, false), 0n, 4294967295n, "u32"));
228
+ }
229
+ toI32(): number {
230
+ return Number(signedFromBytes(fromLittleEndian(this, true), 4, "i32"));
231
+ }
232
+ toBigInt(): BigInt_ {
233
+ return new BigInt_(fromLittleEndian(this, true));
234
+ }
235
+ concat(other: ByteArray): ByteArray {
236
+ const out = new ByteArray(this.length + other.length);
237
+ out.set(this, 0);
238
+ out.set(other, this.length);
239
+ return out;
240
+ }
241
+ equals(other: ByteArray): boolean {
242
+ return this.toHexString() === other.toHexString();
243
+ }
244
+ notEqual(other: ByteArray): boolean {
245
+ return !this.equals(other);
246
+ }
247
+ concatI32(other: number): ByteArray {
248
+ return this.concat(ByteArray.fromI32(other));
249
+ }
250
+ toI64(): bigint {
251
+ return signedFromBytes(fromLittleEndian(this, true), 8, "i64");
252
+ }
253
+ toU64(): bigint {
254
+ return fitted(fromLittleEndian(this, false), 0n, 2n ** 64n - 1n, "u64");
255
+ }
256
+ }
257
+
258
+ export class Bytes extends ByteArray {
259
+ static fromHexString(hex: string): Bytes {
260
+ return new Bytes(hexBytes(hex));
261
+ }
262
+ static fromUTF8(input: string): Bytes {
263
+ return new Bytes(ByteArray.fromUTF8(input));
264
+ }
265
+ static fromByteArray(byteArray: ByteArray): Bytes {
266
+ return new Bytes(byteArray);
267
+ }
268
+ static fromI32(value: number): Bytes {
269
+ return new Bytes(ByteArray.fromI32(value));
270
+ }
271
+ static fromUint8Array(bytes: Uint8Array): Bytes {
272
+ return new Bytes(bytes);
273
+ }
274
+ static fromU32(value: number): Bytes {
275
+ return new Bytes(ByteArray.fromU32(value));
276
+ }
277
+ static fromI64(value: bigint): Bytes {
278
+ return new Bytes(ByteArray.fromI64(value));
279
+ }
280
+ static fromU64(value: bigint): Bytes {
281
+ return new Bytes(ByteArray.fromU64(value));
282
+ }
283
+ static fromBigInt(value: BigInt_): Bytes {
284
+ return new Bytes(ByteArray.fromBigInt(value));
285
+ }
286
+ static empty(): Bytes {
287
+ return new Bytes(ByteArray.empty());
288
+ }
289
+ }
290
+
291
+ export class Address extends Bytes {
292
+ static fromString(address: string): Address {
293
+ // graph-node's `string_to_h160` trims a leading `0x` and parses the rest,
294
+ // so the prefix is optional (Livepeer's constants are bare hex).
295
+ if (!/^(0x)?[0-9a-fA-F]{40}$/.test(address)) {
296
+ throw new Error(`Address.fromString: ${address} is not a valid 20-byte hex address`);
297
+ }
298
+ return new Address(ByteArray.fromHexString((address.startsWith("0x") ? address : "0x" + address).toLowerCase()));
299
+ }
300
+ static fromBytes(bytes: Bytes): Address {
301
+ if (bytes.length !== 20) {
302
+ throw new Error(
303
+ `Envio Subgraph: Address.fromBytes needs 20 bytes, got ${bytes.length}.`,
304
+ );
305
+ }
306
+ return new Address(bytes);
307
+ }
308
+ static zero(): Address {
309
+ return Address.fromString("0x0000000000000000000000000000000000000000");
310
+ }
311
+ // graph-ts renders addresses lowercase, and id/derived-key parity across the
312
+ // two indexers depends on it.
313
+ toHexString(): string {
314
+ return super.toHexString().toLowerCase();
315
+ }
316
+ }
317
+
318
+ // ---------------------------------------------------------------------------
319
+ // Numbers
320
+ // ---------------------------------------------------------------------------
321
+
322
+ /**
323
+ * Below apiVersion 0.0.5 graph-node reads a null operand handed to a host
324
+ * function as an empty value, so `x.plus(null)` is `x`. From 0.0.5 it traps,
325
+ * which is a TypeError here.
326
+ */
327
+ let legacyNullOperands = false;
328
+
329
+ export function setLegacyNullOperands(on: boolean) {
330
+ legacyNullOperands = on;
331
+ }
332
+
333
+ function operandOf(other: BigInt_): bigint {
334
+ return legacyNullOperands && (other === null || other === undefined) ? 0n : other.value;
335
+ }
336
+
337
+ function decimalOperandOf(other: BigDecimal): GraphNodeDecimal.Decimal {
338
+ return legacyNullOperands && (other === null || other === undefined)
339
+ ? BigDecimal.zero().decimal
340
+ : other.decimal;
341
+ }
342
+
343
+ class BigInt_ extends Uint8Array {
344
+ readonly value: bigint;
345
+ /**
346
+ * How many bytes graph-ts holds this value in. A BigInt IS its bytes there,
347
+ * and the count depends on how it was made: `fromI32` keeps the i32's four,
348
+ * `fromU32` five (a sign byte follows the unsigned ones), `fromI64` eight,
349
+ * `fromU64` nine; whatever comes from an event or arithmetic is graph-node's
350
+ * minimal two's complement. Unset means minimal.
351
+ */
352
+ width?: number;
353
+
354
+ // `new BigInt(0)` is AssemblyScript's other spelling of zero: the class is a
355
+ // Uint8Array and the argument its byte length, so a mapping that passes a
356
+ // number gets that many zero bytes — zero, whatever the length.
357
+ constructor(value: bigint | number) {
358
+ // The bytes are deliberately left empty. Arithmetic, comparison and every
359
+ // `to*` run off `value`; materialising the two's-complement representation
360
+ // on construction cost ~7% of indexing CPU, and nothing in a mapping reads
361
+ // a BigInt as bytes — graph codegen never emits it, and the conversions
362
+ // that would (`toHexString`, `toI32`) are overridden here.
363
+ super(0);
364
+ this.value = typeof value === "bigint" ? value : 0n;
365
+ }
366
+
367
+ // Inherited TypedArray operations (`map`, `slice`, `subarray`) would
368
+ // otherwise call this constructor with a length.
369
+ static get [Symbol.species](): Uint8ArrayConstructor {
370
+ return Uint8Array;
371
+ }
372
+
373
+ // graph codegen types a small uint as `i32`, so a mapping hands one straight
374
+ // to `fromI32` — but envio decodes every integer ABI type as a bigint, so what
375
+ // arrives is already a BigInt.
376
+ static fromI32(value: number | bigint | BigInt_): BigInt_ {
377
+ const raw = typeof value === "object" ? (value as BigInt_).valueOf() : value;
378
+ if (typeof raw !== "bigint") {
379
+ const made = new BigInt_(BigInt(Math.trunc(raw as number)));
380
+ made.width = 4;
381
+ return made;
382
+ }
383
+ return BigInt_.withWidth(raw, 4);
384
+ }
385
+ // A u32 parameter: the bits of whatever 32-bit integer the mapping passed.
386
+ static fromU32(value: number): BigInt_ {
387
+ const made = new BigInt_(BigInt(Math.trunc(value) >>> 0));
388
+ made.width = 5;
389
+ return made;
390
+ }
391
+ static fromI64(value: bigint | number): BigInt_ {
392
+ return BigInt_.withWidth(BigInt(value), 8);
393
+ }
394
+ static fromU64(value: bigint | number): BigInt_ {
395
+ return BigInt_.withWidth(BigInt(value), 9);
396
+ }
397
+ private static withWidth(value: bigint, width: number): BigInt_ {
398
+ const made = new BigInt_(value);
399
+ made.width = Math.max(width, byteWidth(value));
400
+ return made;
401
+ }
402
+ /** The byte count graph-ts reports (see `width`). */
403
+ get byteLength(): number {
404
+ return this.width ?? byteWidth(this.value);
405
+ }
406
+ get length(): number {
407
+ return this.byteLength;
408
+ }
409
+ static fromString(value: string): BigInt_ {
410
+ return new BigInt_(BigInt(value));
411
+ }
412
+ static fromByteArray(bytes: ByteArray): BigInt_ {
413
+ return BigInt_.fromSignedBytes(bytes as Bytes);
414
+ }
415
+ static fromUnsignedBytes(bytes: ByteArray): BigInt_ {
416
+ const value = fromLittleEndian(bytes, false);
417
+ return BigInt_.withWidth(value, bytes.length + 1);
418
+ }
419
+ static fromSignedBytes(bytes: Bytes): BigInt_ {
420
+ const value = fromLittleEndian(bytes, true);
421
+ return BigInt_.withWidth(value, bytes.length);
422
+ }
423
+ static zero(): BigInt_ {
424
+ return BigInt_.fromI32(0);
425
+ }
426
+ static compare(a: BigInt_, b: BigInt_): number {
427
+ return a.value < b.value ? -1 : a.value > b.value ? 1 : 0;
428
+ }
429
+
430
+ // Uint8Array.valueOf returns the array itself; graph-ts BigInt has no
431
+ // valueOf at all, so widening keeps both callers honest.
432
+ valueOf(): any {
433
+ return this.value;
434
+ }
435
+ toString(): string {
436
+ return this.value.toString();
437
+ }
438
+ toHex(): string {
439
+ return this.toHexString();
440
+ }
441
+ // graph-node's `big_int_to_hex`: the magnitude's bytes in hex, leading
442
+ // zeros trimmed, the sign dropped.
443
+ toHexString(): string {
444
+ return `0x${(this.value < 0n ? -this.value : this.value).toString(16)}`;
445
+ }
446
+ toI32(): number {
447
+ return Number(signedFromBytes(this.value, 4, "i32"));
448
+ }
449
+ toU32(): number {
450
+ return Number(unsignedFromSignedBytes(this.value, this.byteLength, 4, "u32"));
451
+ }
452
+ toI64(): bigint {
453
+ return signedFromBytes(this.value, 8, "i64");
454
+ }
455
+ toBigDecimal(): BigDecimal {
456
+ return new BigDecimal(new BigNumber(this.value.toString()));
457
+ }
458
+
459
+ plus(other: BigInt_): BigInt_ {
460
+ return new BigInt_(this.value + operandOf(other));
461
+ }
462
+ minus(other: BigInt_): BigInt_ {
463
+ return new BigInt_(this.value - operandOf(other));
464
+ }
465
+ times(other: BigInt_): BigInt_ {
466
+ return new BigInt_(this.value * operandOf(other));
467
+ }
468
+ div(other: BigInt_): BigInt_ {
469
+ return new BigInt_(this.value / operandOf(other));
470
+ }
471
+ mod(other: BigInt_): BigInt_ {
472
+ return new BigInt_(this.value % operandOf(other));
473
+ }
474
+ pow(exponent: number): BigInt_ {
475
+ return new BigInt_(this.value ** BigInt(exponent));
476
+ }
477
+ neg(): BigInt_ {
478
+ return new BigInt_(-this.value);
479
+ }
480
+ abs(): BigInt_ {
481
+ return new BigInt_(this.value < 0n ? -this.value : this.value);
482
+ }
483
+ equals(other: BigInt_): boolean {
484
+ return this.value === operandOf(other);
485
+ }
486
+ notEqual(other: BigInt_): boolean {
487
+ return this.value !== operandOf(other);
488
+ }
489
+ lt(other: BigInt_): boolean {
490
+ return this.value < operandOf(other);
491
+ }
492
+ le(other: BigInt_): boolean {
493
+ return this.value <= operandOf(other);
494
+ }
495
+ gt(other: BigInt_): boolean {
496
+ return this.value > operandOf(other);
497
+ }
498
+ ge(other: BigInt_): boolean {
499
+ return this.value >= operandOf(other);
500
+ }
501
+ isZero(): boolean {
502
+ return this.value === 0n;
503
+ }
504
+ isI32(): boolean {
505
+ return this.value >= -2147483648n && this.value <= 2147483647n;
506
+ }
507
+ toU64(): bigint {
508
+ return unsignedFromSignedBytes(this.value, this.byteLength, 8, "u64");
509
+ }
510
+ sqrt(): BigInt_ {
511
+ if (this.value < 0n) {
512
+ throw new Error("BigInt.sqrt of a negative value");
513
+ }
514
+ let guess = this.value;
515
+ let next = (guess + 1n) / 2n;
516
+ while (next < guess) {
517
+ guess = next;
518
+ next = (guess + this.value / guess) / 2n;
519
+ }
520
+ return new BigInt_(guess);
521
+ }
522
+ divDecimal(other: BigDecimal): BigDecimal {
523
+ return this.toBigDecimal().div(other);
524
+ }
525
+ bitAnd(other: BigInt_): BigInt_ {
526
+ return new BigInt_(this.value & other.value);
527
+ }
528
+ bitOr(other: BigInt_): BigInt_ {
529
+ return new BigInt_(this.value | other.value);
530
+ }
531
+ leftShift(bits: number): BigInt_ {
532
+ return new BigInt_(this.value << BigInt(bits));
533
+ }
534
+ rightShift(bits: number): BigInt_ {
535
+ return new BigInt_(this.value >> BigInt(bits));
536
+ }
537
+ }
538
+
539
+ export { BigInt_ as BigInt };
540
+
541
+ /**
542
+ * Carried by each BigNumber `value` handed to envio's store: the decimal it was
543
+ * made from, so an entity read back from memory isn't printed and parsed again.
544
+ */
545
+ const DECIMAL = Symbol("envio.subgraph.decimal");
546
+
547
+ /** Mappings parse the same few literals over and over: `"1"`, `"10"`, `"0"`. */
548
+ const parsedDecimals = new Map<string, GraphNodeDecimal.Decimal>();
549
+
550
+ function parseDecimal(text: string): GraphNodeDecimal.Decimal {
551
+ let decimal = parsedDecimals.get(text);
552
+ if (decimal === undefined) {
553
+ decimal = GraphNodeDecimal.parse(text);
554
+ if (parsedDecimals.size >= 1024) parsedDecimals.clear();
555
+ parsedDecimals.set(text, decimal);
556
+ }
557
+ return decimal;
558
+ }
559
+
560
+ /**
561
+ * Every value is held as graph-node holds it: normalized to 34 significant
562
+ * digits by `graph-node-decimal` after every operation, and once more as it
563
+ * would be crossing back into graph-node's host (`BigDecimal::new`), which is
564
+ * what a later operation, `toString` or the store sees. `value` is the same
565
+ * number for envio's store, made only when something asks for it.
566
+ */
567
+ /**
568
+ * Set for the one `new BigDecimal` that wraps what an operation of
569
+ * graph-node-decimal returned: normal already, but for `renormalize`'s case.
570
+ * graph-ts' constructor takes one argument, so the flag says it instead.
571
+ */
572
+ let constructingOperationResult = false;
573
+ function operationResult(decimal: GraphNodeDecimal.Decimal): BigDecimal {
574
+ constructingOperationResult = true;
575
+ return new BigDecimal(decimal);
576
+ }
577
+
578
+ /**
579
+ * Powers of a value built by multiplying by it one step at a time, which is
580
+ * how Uniswap v3's `bigDecimalExponated` computes `1.0001^tick` in createTick
581
+ * — up to 887k steps for a full-range tick, every pool's first such position
582
+ * repeating the same ones, and every replay of the handler again. Each step's
583
+ * product is kept: `[base, base·base, (base^2)·base, …]` as `multiply`
584
+ * returned it, so a later step is the same product, rounded the same way,
585
+ * without the work. Bounded in all; past the bound a step is computed as any
586
+ * other product.
587
+ */
588
+ type PowerChain = { key: string; products: GraphNodeDecimal.Decimal[] };
589
+ const powerChains = new Map<string, PowerChain>();
590
+ const POWER_CHAIN_LIMIT = 1_000_000;
591
+ const CHAIN_KEPT_FROM = 64;
592
+ let chainedProducts = 0;
593
+ const sameDecimal = (x: GraphNodeDecimal.Decimal, y: GraphNodeDecimal.Decimal) =>
594
+ x.int === y.int && x.scale === y.scale;
595
+
596
+ /** `a · b` when one is a step of a chain the other is the base of, or `a` is `b`. */
597
+ function chainedProduct(a: BigDecimal, b: BigDecimal): BigDecimal | undefined {
598
+ let chain: PowerChain;
599
+ let step: number;
600
+ let at: GraphNodeDecimal.Decimal;
601
+ const aPower = a._power;
602
+ const bPower = b._power;
603
+ if (aPower !== undefined && sameDecimal(b.decimal, aPower.chain.products[0])) {
604
+ ({ chain, step } = aPower);
605
+ at = a.decimal;
606
+ } else if (bPower !== undefined && sameDecimal(a.decimal, bPower.chain.products[0])) {
607
+ ({ chain, step } = bPower);
608
+ at = b.decimal;
609
+ } else if (sameDecimal(a.decimal, b.decimal)) {
610
+ const key = `${a.decimal.int}e${a.decimal.scale}`;
611
+ chain = powerChains.get(key) ?? { key, products: [a.decimal] };
612
+ step = 0;
613
+ at = a.decimal;
614
+ } else {
615
+ return undefined;
616
+ }
617
+ const next = step + 1;
618
+ let product = chain.products[next];
619
+ if (product === undefined) {
620
+ product = GraphNodeDecimal.multiply(at, chain.products[0]);
621
+ if (next === chain.products.length && chainedProducts < POWER_CHAIN_LIMIT) {
622
+ chain.products.push(product);
623
+ chainedProducts++;
624
+ // A square on its own is no chain: only one that keeps going is kept
625
+ // for the next computation of the same powers.
626
+ if (next === CHAIN_KEPT_FROM) powerChains.set(chain.key, chain);
627
+ }
628
+ }
629
+ const made = operationResult(product);
630
+ made._power = { chain, step: next };
631
+ return made;
632
+ }
633
+
634
+ export class BigDecimal {
635
+ private _decimal: GraphNodeDecimal.Decimal;
636
+ /** The two fields as a mapping last assigned them, if it assigned one. */
637
+ private _fields: GraphNodeDecimal.Decimal | undefined;
638
+ private _value: BigNumber | undefined;
639
+ /** This value's place in a power chain, if a chained product made it. */
640
+ _power: { chain: PowerChain; step: number } | undefined;
641
+
642
+ constructor(value: GraphNodeDecimal.Decimal | BigNumber | BigInt_ | string | number) {
643
+ this._power = undefined;
644
+ const operationResult = constructingOperationResult;
645
+ constructingOperationResult = false;
646
+ this._decimal = operationResult
647
+ ? GraphNodeDecimal.renormalize(value as GraphNodeDecimal.Decimal)
648
+ : value instanceof BigInt_
649
+ ? GraphNodeDecimal.fromInteger(value.value)
650
+ : value instanceof BigNumber
651
+ ? ((value as BigNumber & { [DECIMAL]?: GraphNodeDecimal.Decimal })[DECIMAL] ??
652
+ GraphNodeDecimal.parse(value.toFixed()))
653
+ : typeof value === "object"
654
+ ? GraphNodeDecimal.normalize(value)
655
+ : parseDecimal(String(value));
656
+ }
657
+
658
+ get decimal(): GraphNodeDecimal.Decimal {
659
+ return this._decimal;
660
+ }
661
+
662
+ get value(): BigNumber {
663
+ if (this._value === undefined) {
664
+ const value = new BigNumber(GraphNodeDecimal.format(this.decimal));
665
+ (value as BigNumber & { [DECIMAL]?: GraphNodeDecimal.Decimal })[DECIMAL] = this.decimal;
666
+ this._value = value;
667
+ }
668
+ return this._value;
669
+ }
670
+
671
+ static fromString(value: string): BigDecimal {
672
+ return new BigDecimal(value);
673
+ }
674
+ static zero(): BigDecimal {
675
+ return new BigDecimal(0);
676
+ }
677
+ static compare(a: BigDecimal, b: BigDecimal): number {
678
+ return GraphNodeDecimal.compare(a.decimal, b.decimal);
679
+ }
680
+
681
+ toString(): string {
682
+ return GraphNodeDecimal.format(this.decimal);
683
+ }
684
+ toBigInt(): BigInt_ {
685
+ return BigInt_.fromString(this.value.integerValue(BigNumber.ROUND_DOWN).toFixed());
686
+ }
687
+ plus(other: BigDecimal): BigDecimal {
688
+ return operationResult(GraphNodeDecimal.add(this.decimal, decimalOperandOf(other)));
689
+ }
690
+ minus(other: BigDecimal): BigDecimal {
691
+ return operationResult(GraphNodeDecimal.subtract(this.decimal, decimalOperandOf(other)));
692
+ }
693
+ times(other: BigDecimal): BigDecimal {
694
+ if (other instanceof BigDecimal) {
695
+ const chained = chainedProduct(this, other);
696
+ if (chained !== undefined) return chained;
697
+ }
698
+ return operationResult(GraphNodeDecimal.multiply(this.decimal, decimalOperandOf(other)));
699
+ }
700
+ div(other: BigDecimal): BigDecimal {
701
+ return operationResult(GraphNodeDecimal.divide(this.decimal, decimalOperandOf(other)));
702
+ }
703
+ equals(other: BigDecimal): boolean {
704
+ return GraphNodeDecimal.compare(this.decimal, decimalOperandOf(other)) === 0;
705
+ }
706
+ notEqual(other: BigDecimal): boolean {
707
+ return GraphNodeDecimal.compare(this.decimal, decimalOperandOf(other)) !== 0;
708
+ }
709
+ lt(other: BigDecimal): boolean {
710
+ return GraphNodeDecimal.compare(this.decimal, decimalOperandOf(other)) < 0;
711
+ }
712
+ le(other: BigDecimal): boolean {
713
+ return GraphNodeDecimal.compare(this.decimal, decimalOperandOf(other)) <= 0;
714
+ }
715
+ gt(other: BigDecimal): boolean {
716
+ return GraphNodeDecimal.compare(this.decimal, decimalOperandOf(other)) > 0;
717
+ }
718
+ ge(other: BigDecimal): boolean {
719
+ return GraphNodeDecimal.compare(this.decimal, decimalOperandOf(other)) >= 0;
720
+ }
721
+ // graph-ts negates by subtracting from zero, which normalizes like any other
722
+ // operation.
723
+ neg(): BigDecimal {
724
+ return BigDecimal.zero().minus(this);
725
+ }
726
+ truncate(decimals: number): BigDecimal {
727
+ return new BigDecimal(this.value.decimalPlaces(decimals, BigNumber.ROUND_DOWN));
728
+ }
729
+ // graph-ts stores a decimal as two public fields, `digits * 10 ** exp`, in
730
+ // graph-node's normalized form. A mapping may assign either — graph-node's
731
+ // host-exports test lowers `exp` — and every host operation reads them back
732
+ // through `BigDecimal::new`, which normalizes.
733
+ get digits(): BigInt_ {
734
+ return new BigInt_((this._fields ?? this._decimal).int);
735
+ }
736
+ set digits(digits: BigInt_) {
737
+ this.assignFields({ int: digits.value, scale: (this._fields ?? this._decimal).scale });
738
+ }
739
+ get exp(): BigInt_ {
740
+ return BigInt_.fromI32(-(this._fields ?? this._decimal).scale);
741
+ }
742
+ set exp(exp: BigInt_) {
743
+ this.assignFields({ int: (this._fields ?? this._decimal).int, scale: -Number(exp.value) });
744
+ }
745
+ private assignFields(fields: GraphNodeDecimal.Decimal): void {
746
+ this._fields = fields;
747
+ this._decimal = GraphNodeDecimal.normalize(fields);
748
+ this._value = undefined;
749
+ this._power = undefined;
750
+ }
751
+ }
752
+
753
+ // ---------------------------------------------------------------------------
754
+ // TypedMap / Value / Entity
755
+ // ---------------------------------------------------------------------------
756
+
757
+ export class Wrapped<T> {
758
+ constructor(public inner: T) {}
759
+ }
760
+
761
+ export class Result<V, E> {
762
+ constructor(
763
+ public _value: Wrapped<V> | null = null,
764
+ public _error: Wrapped<E> | null = null,
765
+ ) {}
766
+ get isOk(): boolean {
767
+ return this._value !== null;
768
+ }
769
+ get isError(): boolean {
770
+ return this._error !== null;
771
+ }
772
+ get value(): V {
773
+ if (this._value === null) throw new Error("Trying to get a value from an error result");
774
+ return this._value.inner;
775
+ }
776
+ get error(): E {
777
+ if (this._error === null) throw new Error("Trying to get an error from a successful result");
778
+ return this._error.inner;
779
+ }
780
+ }
781
+
782
+ function tryParse<V>(parse: () => V): Result<V, boolean> {
783
+ try {
784
+ return new Result(new Wrapped(parse()));
785
+ } catch {
786
+ return new Result<V, boolean>(null, new Wrapped(true));
787
+ }
788
+ }
789
+
790
+ export class TypedMapEntry<K, V> {
791
+ constructor(
792
+ public key: K,
793
+ public value: V,
794
+ ) {}
795
+ }
796
+
797
+ // The map's storage. graph-ts' `TypedMap.entries` is a plain field, and the
798
+ // class graph codegen writes for an entity with a field called `entries`
799
+ // overrides it with an accessor: AssemblyScript's own methods keep reading the
800
+ // field, while a mapping's `rs.entries = []` reaches the accessor. A JavaScript
801
+ // field would be overwritten by that assignment, wiping the entity's id.
802
+ const MAP_ENTRIES = Symbol("envio.subgraph.typedMapEntries");
803
+
804
+ function entriesOf<K, V>(map: TypedMap<K, V>): TypedMapEntry<K, V>[] {
805
+ return (map as any)[MAP_ENTRIES];
806
+ }
807
+
808
+ /** A loaded entity whose schema declares a field called `entries`. */
809
+ function hasEntriesField<K, V>(map: TypedMap<K, V>): boolean {
810
+ const entityType = (map as any)[ENTITY_TYPE];
811
+ return (
812
+ typeof entityType === "string" &&
813
+ currentScope().schema.entityFields[entityType]?.includes("entries") === true
814
+ );
815
+ }
816
+
817
+ /**
818
+ * AssemblyScript's `==` on a `Bytes`, `BigInt` or `BigDecimal` key compares the
819
+ * values, which is how Lido looks a log's topic up in a `TypedMap<Bytes, …>`
820
+ * built from other `Bytes` instances.
821
+ */
822
+ function sameKey(a: unknown, b: unknown): boolean {
823
+ if (a === b) return true;
824
+ if (typeof a === "object" && a !== null && typeof b === "object" && b !== null) {
825
+ const equals = (a as { equals?: (other: unknown) => unknown }).equals;
826
+ if (typeof equals === "function" && a instanceof Uint8Array === b instanceof Uint8Array) {
827
+ return equals.call(a, b) === true;
828
+ }
829
+ }
830
+ return false;
831
+ }
832
+
833
+ export class TypedMap<K, V> {
834
+ constructor() {
835
+ Object.defineProperty(this, MAP_ENTRIES, { value: [], writable: true });
836
+ }
837
+
838
+ // A loaded entity keeps graph-ts' prototype (`load()`'s `changetype<T | null>`
839
+ // isn't retagged), so this accessor stands where the generated one would
840
+ // for a schema field called `entries`, and answers as it would.
841
+ get entries(): TypedMapEntry<K, V>[] {
842
+ if (hasEntriesField(this)) {
843
+ const stored = (this as unknown as Entity).get("entries");
844
+ return (stored === null ? null : valueToNative(stored)) as TypedMapEntry<K, V>[];
845
+ }
846
+ return entriesOf(this);
847
+ }
848
+ set entries(value: TypedMapEntry<K, V>[]) {
849
+ if (hasEntriesField(this)) {
850
+ (this as unknown as Entity).set("entries", nativeToValue(value));
851
+ return;
852
+ }
853
+ (this as any)[MAP_ENTRIES] = value;
854
+ }
855
+
856
+ set(key: K, value: V): void {
857
+ const entry = this.getEntry(key);
858
+ if (entry) {
859
+ entry.value = value;
860
+ } else {
861
+ entriesOf(this).push(new TypedMapEntry(key, value));
862
+ }
863
+ }
864
+ getEntry(key: K): TypedMapEntry<K, V> | null {
865
+ const entries = entriesOf(this);
866
+ for (let index = 0; index < entries.length; index++) {
867
+ if (sameKey(entries[index].key, key)) return entries[index];
868
+ }
869
+ return null;
870
+ }
871
+ get(key: K): V | null {
872
+ const entry = this.getEntry(key);
873
+ return entry ? entry.value : null;
874
+ }
875
+ mustGetEntry(key: K): TypedMapEntry<K, V> {
876
+ const entry = this.getEntry(key);
877
+ if (entry === null) {
878
+ throw new Error(`TypedMap does not contain an entry for key ${String(key)}`);
879
+ }
880
+ return entry;
881
+ }
882
+ mustGet(key: K): V {
883
+ const value = this.get(key);
884
+ if (value === null) {
885
+ throw new Error(`TypedMap does not contain a value for key ${String(key)}`);
886
+ }
887
+ return value;
888
+ }
889
+ isSet(key: K): boolean {
890
+ return this.getEntry(key) !== null;
891
+ }
892
+ }
893
+
894
+ export enum ValueKind {
895
+ STRING = 0,
896
+ INT = 1,
897
+ BIGDECIMAL = 2,
898
+ BOOL = 3,
899
+ ARRAY = 4,
900
+ NULL = 5,
901
+ BYTES = 6,
902
+ BIGINT = 7,
903
+ INT8 = 8,
904
+ TIMESTAMP = 9,
905
+ }
906
+
907
+ export class Value {
908
+ constructor(
909
+ public kind: ValueKind,
910
+ public data: any,
911
+ ) {}
912
+
913
+ static fromString(value: string): Value {
914
+ return new Value(ValueKind.STRING, value);
915
+ }
916
+ // AssemblyScript compiles a `null` passed as a value type (a nullable
917
+ // `Timestamp` field's generated `set x(value: i64)`) to 0.
918
+ static fromI32(value: number): Value {
919
+ return new Value(ValueKind.INT, value ?? 0);
920
+ }
921
+ static fromI64(value: bigint): Value {
922
+ return new Value(ValueKind.INT8, value ?? 0n);
923
+ }
924
+ static fromBigInt(value: BigInt_): Value {
925
+ return new Value(ValueKind.BIGINT, value);
926
+ }
927
+ static fromBigDecimal(value: BigDecimal): Value {
928
+ return new Value(ValueKind.BIGDECIMAL, value);
929
+ }
930
+ static fromBoolean(value: boolean): Value {
931
+ return new Value(ValueKind.BOOL, value ?? false);
932
+ }
933
+ static fromBytes(value: Bytes): Value {
934
+ return new Value(ValueKind.BYTES, value);
935
+ }
936
+ static fromAddress(value: Address): Value {
937
+ return new Value(ValueKind.BYTES, value);
938
+ }
939
+ static fromTimestamp(value: bigint): Value {
940
+ return new Value(ValueKind.TIMESTAMP, value ?? 0n);
941
+ }
942
+ static fromNull(): Value {
943
+ return new Value(ValueKind.NULL, null);
944
+ }
945
+ static fromArray(values: Value[]): Value {
946
+ return new Value(ValueKind.ARRAY, values);
947
+ }
948
+ static fromStringArray(values: string[]): Value {
949
+ return Value.fromArray(values.map(Value.fromString));
950
+ }
951
+ static fromBytesArray(values: Bytes[]): Value {
952
+ return Value.fromArray(values.map(Value.fromBytes));
953
+ }
954
+ static fromBigIntArray(values: BigInt_[]): Value {
955
+ return Value.fromArray(values.map(Value.fromBigInt));
956
+ }
957
+ static fromBigDecimalArray(values: BigDecimal[]): Value {
958
+ return Value.fromArray(values.map(Value.fromBigDecimal));
959
+ }
960
+ static fromBooleanArray(values: boolean[]): Value {
961
+ return Value.fromArray(values.map(Value.fromBoolean));
962
+ }
963
+ static fromI32Array(values: number[]): Value {
964
+ return Value.fromArray(values.map(Value.fromI32));
965
+ }
966
+ static fromI64Array(values: bigint[]): Value {
967
+ return Value.fromArray(values.map(Value.fromI64));
968
+ }
969
+ static fromAddressArray(values: Address[]): Value {
970
+ return Value.fromArray(values.map(Value.fromAddress));
971
+ }
972
+ static fromMatrix(values: Value[][]): Value {
973
+ return Value.fromArray(values.map(Value.fromArray));
974
+ }
975
+ static fromStringMatrix(values: string[][]): Value {
976
+ return Value.fromMatrix(values.map((row) => row.map(Value.fromString)));
977
+ }
978
+ static fromBytesMatrix(values: Bytes[][]): Value {
979
+ return Value.fromMatrix(values.map((row) => row.map(Value.fromBytes)));
980
+ }
981
+ static fromAddressMatrix(values: Address[][]): Value {
982
+ return Value.fromMatrix(values.map((row) => row.map(Value.fromAddress)));
983
+ }
984
+ static fromBigIntMatrix(values: BigInt_[][]): Value {
985
+ return Value.fromMatrix(values.map((row) => row.map(Value.fromBigInt)));
986
+ }
987
+ static fromBooleanMatrix(values: boolean[][]): Value {
988
+ return Value.fromMatrix(values.map((row) => row.map(Value.fromBoolean)));
989
+ }
990
+ static fromI32Matrix(values: number[][]): Value {
991
+ return Value.fromMatrix(values.map((row) => row.map(Value.fromI32)));
992
+ }
993
+ static fromI64Matrix(values: bigint[][]): Value {
994
+ return Value.fromMatrix(values.map((row) => row.map(Value.fromI64)));
995
+ }
996
+ static fromTimestampMatrix(values: bigint[][]): Value {
997
+ return Value.fromMatrix(values.map((row) => row.map(Value.fromTimestamp)));
998
+ }
999
+
1000
+ // The accessors are deliberately lenient about kind: envio stores a
1001
+ // subgraph's `Bytes` as lowercase hex text, so a value read back from the
1002
+ // store carries STRING where the generated getter asks for bytes.
1003
+ toString(): string {
1004
+ if (this.data instanceof Bytes || this.data instanceof ByteArray) {
1005
+ return this.data.toHexString();
1006
+ }
1007
+ return String(this.data);
1008
+ }
1009
+ toStringArray(): string[] {
1010
+ return (this.data as Value[]).map((value) => value.toString());
1011
+ }
1012
+ toBytes(): Bytes {
1013
+ if (this.data instanceof Bytes) return this.data;
1014
+ if (this.data instanceof ByteArray) return Bytes.fromByteArray(this.data);
1015
+ if (Array.isArray(this.data)) return pointerBytes(this.data);
1016
+ return Bytes.fromHexString(String(this.data));
1017
+ }
1018
+ toBytesArray(): Bytes[] {
1019
+ return (this.data as Value[]).map((value) => value.toBytes());
1020
+ }
1021
+ toAddress(): Address {
1022
+ return Address.fromString(this.toBytes().toHexString());
1023
+ }
1024
+ toBigInt(): BigInt_ {
1025
+ if (this.data instanceof BigInt_) return this.data;
1026
+ if (typeof this.data === "bigint") return new BigInt_(this.data);
1027
+ // `Value.fromBigInt(x!)` of a null `x` (a mapping asking an event for a
1028
+ // key it misspelt, GMX's "callbakGasLimit"): the `!` doesn't stop it in
1029
+ // graph-node, the Value carries a null pointer, and the BigInt behind it
1030
+ // reads as no bytes, i.e. 0.
1031
+ if (this.data == null) return BigInt_.zero();
1032
+ return BigInt_.fromString(String(this.data));
1033
+ }
1034
+ toBigIntArray(): BigInt_[] {
1035
+ return (this.data as Value[]).map((value) => value.toBigInt());
1036
+ }
1037
+ toBigDecimal(): BigDecimal {
1038
+ if (this.data instanceof BigDecimal) return this.data;
1039
+ return BigDecimal.fromString(String(this.data));
1040
+ }
1041
+ toBigDecimalArray(): BigDecimal[] {
1042
+ return (this.data as Value[]).map((value) => value.toBigDecimal());
1043
+ }
1044
+ toBoolean(): boolean {
1045
+ return Boolean(this.data);
1046
+ }
1047
+ toBooleanArray(): boolean[] {
1048
+ return (this.data as Value[]).map((value) => value.toBoolean());
1049
+ }
1050
+ toI32(): number {
1051
+ return Number(this.data);
1052
+ }
1053
+ toI32Array(): number[] {
1054
+ return (this.data as Value[]).map((value) => value.toI32());
1055
+ }
1056
+ toI64(): bigint {
1057
+ return BigInt(this.data as any);
1058
+ }
1059
+ toTimestamp(): bigint {
1060
+ return BigInt(this.data as any);
1061
+ }
1062
+ toArray(): Value[] {
1063
+ return this.data as Value[];
1064
+ }
1065
+ toMatrix(): Value[][] {
1066
+ return this.toArray().map((row) => row.toArray());
1067
+ }
1068
+ toI64Array(): bigint[] {
1069
+ return this.toArray().map((value) => value.toI64());
1070
+ }
1071
+ toTimestampArray(): bigint[] {
1072
+ return this.toArray().map((value) => value.toTimestamp());
1073
+ }
1074
+ toAddressMatrix(): Address[][] {
1075
+ return this.toMatrix().map((row) => row.map((value) => value.toAddress()));
1076
+ }
1077
+ toStringMatrix(): string[][] {
1078
+ return this.toMatrix().map((row) => row.map((value) => value.toString()));
1079
+ }
1080
+ toBytesMatrix(): Bytes[][] {
1081
+ return this.toMatrix().map((row) => row.map((value) => value.toBytes()));
1082
+ }
1083
+ toBigIntMatrix(): BigInt_[][] {
1084
+ return this.toMatrix().map((row) => row.map((value) => value.toBigInt()));
1085
+ }
1086
+ toBooleanMatrix(): boolean[][] {
1087
+ return this.toMatrix().map((row) => row.map((value) => value.toBoolean()));
1088
+ }
1089
+ toI32Matrix(): number[][] {
1090
+ return this.toMatrix().map((row) => row.map((value) => value.toI32()));
1091
+ }
1092
+ toI64Matrix(): bigint[][] {
1093
+ return this.toMatrix().map((row) => row.map((value) => value.toI64()));
1094
+ }
1095
+ displayData(): string {
1096
+ if (this.data instanceof ByteArray) return this.data.toHexString();
1097
+ return String(this.data);
1098
+ }
1099
+ displayKind(): string {
1100
+ return ValueKind[this.kind] ?? String(this.kind);
1101
+ }
1102
+ }
1103
+
1104
+ /** A stored `Value` as the scalar a generated entity getter would return. */
1105
+ function valueToNative(value: Value): unknown {
1106
+ switch (value.kind) {
1107
+ case ValueKind.NULL:
1108
+ return null;
1109
+ case ValueKind.BYTES:
1110
+ return value.toBytes();
1111
+ case ValueKind.BIGINT:
1112
+ return value.toBigInt();
1113
+ case ValueKind.BIGDECIMAL:
1114
+ return value.toBigDecimal();
1115
+ case ValueKind.INT8:
1116
+ case ValueKind.TIMESTAMP:
1117
+ return value.toI64();
1118
+ case ValueKind.ARRAY:
1119
+ return value.toArray().map(valueToNative);
1120
+ default:
1121
+ return value.data;
1122
+ }
1123
+ }
1124
+
1125
+ function nativeToValue(native: unknown): Value {
1126
+ if (native === null || native === undefined) return Value.fromNull();
1127
+ if (native instanceof Value) return native;
1128
+ if (native instanceof Bytes || native instanceof ByteArray) {
1129
+ return Value.fromBytes(Bytes.fromByteArray(native));
1130
+ }
1131
+ if (native instanceof BigInt_) return Value.fromBigInt(native);
1132
+ if (native instanceof BigDecimal) return Value.fromBigDecimal(native);
1133
+ if (typeof native === "string") return Value.fromString(native);
1134
+ if (typeof native === "boolean") return Value.fromBoolean(native);
1135
+ if (typeof native === "bigint") return Value.fromI64(native);
1136
+ if (typeof native === "number") return Value.fromI32(native);
1137
+ if (Array.isArray(native)) return Value.fromArray(native.map(nativeToValue));
1138
+ return Value.fromString(String(native));
1139
+ }
1140
+
1141
+ export class Entity extends TypedMap<string, Value> {
1142
+ setString(key: string, value: string): void {
1143
+ this.set(key, Value.fromString(value));
1144
+ }
1145
+ setI32(key: string, value: number): void {
1146
+ this.set(key, Value.fromI32(value));
1147
+ }
1148
+ setBigInt(key: string, value: BigInt_): void {
1149
+ this.set(key, Value.fromBigInt(value));
1150
+ }
1151
+ setBytes(key: string, value: Bytes): void {
1152
+ this.set(key, Value.fromBytes(value));
1153
+ }
1154
+ setBoolean(key: string, value: boolean): void {
1155
+ this.set(key, Value.fromBoolean(value));
1156
+ }
1157
+ setBigDecimal(key: string, value: BigDecimal): void {
1158
+ this.set(key, Value.fromBigDecimal(value));
1159
+ }
1160
+ getString(key: string): string {
1161
+ return this.mustGet(key).toString();
1162
+ }
1163
+ getI32(key: string): number {
1164
+ return this.mustGet(key).toI32();
1165
+ }
1166
+ getBigInt(key: string): BigInt_ {
1167
+ return this.mustGet(key).toBigInt();
1168
+ }
1169
+ getBytes(key: string): Bytes {
1170
+ return this.mustGet(key).toBytes();
1171
+ }
1172
+ getBoolean(key: string): boolean {
1173
+ return this.mustGet(key).toBoolean();
1174
+ }
1175
+ getBigDecimal(key: string): BigDecimal {
1176
+ return this.mustGet(key).toBigDecimal();
1177
+ }
1178
+ unset(key: string): void {
1179
+ this.set(key, Value.fromNull());
1180
+ }
1181
+ merge(sources: Entity[]): this {
1182
+ // graph-ts assigns each source in turn, so the last one wins a shared key.
1183
+ for (const source of sources) {
1184
+ for (const entry of entriesOf(source)) {
1185
+ this.set(entry.key, entry.value);
1186
+ }
1187
+ }
1188
+ return this;
1189
+ }
1190
+ }
1191
+
1192
+ /**
1193
+ * Tail of the entity prototype chain: instance -> generated prototype ->
1194
+ * Entity -> TypedMap -> here.
1195
+ *
1196
+ * `graph codegen`'s `changetype<Pair | null>(store.get(...))` gives a loaded
1197
+ * entity the generated prototype, whose getters read its fields. An entity read
1198
+ * without one — `store.get` called directly, or cast to a class the file can't
1199
+ * reach — falls through to here, to the stored field typed by its `Value` kind.
1200
+ * A name the entity doesn't hold is still refused.
1201
+ */
1202
+ /**
1203
+ * The entity type a loaded row came from. `changetype` erased the generated
1204
+ * prototype, and with it the `save()` that knows which table to write back to,
1205
+ * so the type is remembered on the instance instead.
1206
+ */
1207
+ const ENTITY_TYPE = Symbol("envio.entityType");
1208
+
1209
+ /** An id as the store keys rows by: text, hex when the schema declares Bytes. */
1210
+ function entityId(id: Value): string {
1211
+ return id.kind === ValueKind.BYTES ? id.toBytes().toHexString() : id.toString();
1212
+ }
1213
+
1214
+ // graph-cli's codegen names the member of a field called after a reserved word
1215
+ // with a trailing `_` (packages/cli/src/codegen/util.ts `handleReservedWord`).
1216
+ const RESERVED_WORDS = new Set([
1217
+ "await", "break", "case", "catch", "class", "const", "continue", "debugger", "delete", "do",
1218
+ "else", "enum", "export", "extends", "false", "finally", "function", "if", "implements",
1219
+ "import", "in", "interface", "let", "new", "package", "private", "protected", "public",
1220
+ "return", "super", "switch", "static", "this", "throw", "true", "try", "typeof", "var",
1221
+ "while", "with", "yield",
1222
+ ]);
1223
+
1224
+ /** The field a generated member reads, for an entity whose prototype is gone. */
1225
+ function memberField(entityType: unknown, prop: string): string {
1226
+ if (typeof entityType !== "string" || !prop.endsWith("_") || !RESERVED_WORDS.has(prop.slice(0, -1))) {
1227
+ return prop;
1228
+ }
1229
+ const declared = currentScope().schema.entityFields[entityType];
1230
+ return declared?.includes(prop.slice(0, -1)) && !declared.includes(prop) ? prop.slice(0, -1) : prop;
1231
+ }
1232
+
1233
+ const entityTail = new Proxy(Object.create(null), {
1234
+ get(_target, prop, receiver) {
1235
+ if (typeof prop === "symbol" || PROTOTYPE_PASSTHROUGH.has(prop as string)) {
1236
+ return undefined;
1237
+ }
1238
+ if (prop === "save" && receiver instanceof Entity) {
1239
+ const entityType = (receiver as any)[ENTITY_TYPE];
1240
+ if (typeof entityType === "string") {
1241
+ return () => {
1242
+ const id = receiver.get("id");
1243
+ if (id === null) {
1244
+ throw new Error(`Cannot save ${entityType} entity without an ID`);
1245
+ }
1246
+ storeImpl.set(entityType, entityId(id), receiver);
1247
+ };
1248
+ }
1249
+ }
1250
+ if (receiver instanceof Entity) {
1251
+ prop = memberField((receiver as any)[ENTITY_TYPE], prop);
1252
+ }
1253
+ const stored = receiver instanceof Entity ? receiver.get(prop as string) : null;
1254
+ if (stored !== null) {
1255
+ return valueToNative(stored);
1256
+ }
1257
+ // A derived field is a query the generated getter would have run, so it is
1258
+ // answered here for the same reason `save()` is: `changetype` erased the
1259
+ // prototype that carried it.
1260
+ if (receiver instanceof Entity) {
1261
+ const entityType = (receiver as any)[ENTITY_TYPE];
1262
+ const declared =
1263
+ typeof entityType === "string"
1264
+ ? currentScope().schema.entityFieldTypes[entityType]?.[prop as string]
1265
+ : undefined;
1266
+ if (declared?.derivedFrom) {
1267
+ const id = receiver.get("id");
1268
+ if (id === null) {
1269
+ throw new Error(
1270
+ `Cannot load ${entityType}.${String(prop)} on an entity without an ID`,
1271
+ );
1272
+ }
1273
+ const owner = entityId(id);
1274
+ return { load: () => storeImpl.loadRelated(entityType, owner, prop as string) };
1275
+ }
1276
+ }
1277
+ // graph-node's store returns every column; envio's returns what the mapping
1278
+ // wrote, so a field nothing has set is simply absent. It is still a field,
1279
+ // and reading it is a null check — ENS opens with one.
1280
+ const entityType = (receiver as any)?.[ENTITY_TYPE];
1281
+ if (typeof entityType === "string") {
1282
+ const declared = currentScope().schema.entityFields[entityType];
1283
+ if (declared?.includes(prop as string)) {
1284
+ return null;
1285
+ }
1286
+ }
1287
+ // Only an entity read is a schema field access. A plain TypedMap — what
1288
+ // JSONValue.toObject() hands back — has to keep ordinary JS semantics, or
1289
+ // every host probe for `toJSON` or `then` raises from unrelated code.
1290
+ if (!(receiver instanceof Entity)) {
1291
+ return undefined;
1292
+ }
1293
+ throw unknown(`the entity member ${String(prop)}`, "a mapping handler");
1294
+ },
1295
+ set(_target, prop, value, receiver) {
1296
+ // `entries` is TypedMap's own storage, and its class-field initializer is a
1297
+ // plain assignment — which walks the prototype chain and lands here before
1298
+ // the instance has the property. Routing it into `set()` would leave the
1299
+ // map with nowhere to store anything.
1300
+ if (typeof prop === "symbol" || prop === "entries" || !(receiver instanceof Entity)) {
1301
+ return Reflect.defineProperty(receiver as object, prop, {
1302
+ value,
1303
+ writable: true,
1304
+ enumerable: true,
1305
+ configurable: true,
1306
+ });
1307
+ }
1308
+ receiver.set(memberField((receiver as any)[ENTITY_TYPE], prop), nativeToValue(value));
1309
+ return true;
1310
+ },
1311
+ });
1312
+
1313
+ Object.setPrototypeOf(TypedMap.prototype, entityTail);
1314
+
1315
+ // ---------------------------------------------------------------------------
1316
+ // store
1317
+ // ---------------------------------------------------------------------------
1318
+
1319
+ /**
1320
+ * What converting one entity type's rows needs from the schema, worked out the
1321
+ * first time it is needed: every load and every save converts a row, and the
1322
+ * schema doesn't change under a running indexer.
1323
+ */
1324
+ type EntityLayout = {
1325
+ /** Every field the type declares, when the schema lists them. */
1326
+ fields: Set<string> | undefined;
1327
+ timestampFields: Set<string>;
1328
+ refFields: Set<string>;
1329
+ listFields: Set<string>;
1330
+ /** A stored column as the field it reads back into, and that field's kind. */
1331
+ columns: Map<string, { key: string; kind: string | undefined; timestamp: boolean }>;
1332
+ };
1333
+
1334
+ const entityLayouts = new WeakMap<Scope["schema"], Map<string, EntityLayout>>();
1335
+
1336
+ function entityLayout(schema: Scope["schema"], entityType: string): EntityLayout {
1337
+ let layouts = entityLayouts.get(schema);
1338
+ if (!layouts) {
1339
+ layouts = new Map();
1340
+ entityLayouts.set(schema, layouts);
1341
+ }
1342
+ let layout = layouts.get(entityType);
1343
+ if (!layout) {
1344
+ const declared = schema.entityFields[entityType];
1345
+ layout = {
1346
+ fields: declared === undefined ? undefined : new Set(declared),
1347
+ timestampFields: new Set(schema.timestampFields[entityType] ?? []),
1348
+ refFields: new Set(schema.entityRefFields[entityType] ?? []),
1349
+ listFields: new Set(schema.entityListFields[entityType] ?? []),
1350
+ columns: new Map(),
1351
+ };
1352
+ layouts.set(entityType, layout);
1353
+ }
1354
+ return layout;
1355
+ }
1356
+
1357
+ function columnOf(schema: Scope["schema"], entityType: string, layout: EntityLayout, column: string) {
1358
+ let known = layout.columns.get(column);
1359
+ if (!known) {
1360
+ const key =
1361
+ column.endsWith("_id") && layout.refFields.has(column.slice(0, -3)) ? column.slice(0, -3) : column;
1362
+ known = {
1363
+ key,
1364
+ kind: declaredKind(schema, (schema.entityFieldTypes[entityType] ?? {})[key]),
1365
+ timestamp: layout.timestampFields.has(key),
1366
+ };
1367
+ layout.columns.set(column, known);
1368
+ }
1369
+ return known;
1370
+ }
1371
+
1372
+ /** graph-ts `Value`s -> the plain scalars envio stores. */
1373
+ let warnedPointerBytes = false;
1374
+
1375
+ /**
1376
+ * An Array that AssemblyScript reads as a Uint8Array — `graph init`'s
1377
+ * `changetype<Bytes[]>(event.params.structs)` — is its backing buffer, the
1378
+ * two classes sharing the ArrayBufferView header: one little-endian four-byte
1379
+ * pointer per element. graph-node's allocator picked those addresses; these
1380
+ * are stand-ins, as many bytes, and the run says so once.
1381
+ */
1382
+ function pointerBytes(elements: unknown[]): Bytes {
1383
+ if (!warnedPointerBytes) {
1384
+ warnedPointerBytes = true;
1385
+ warn(
1386
+ "Envio Subgraph: a mapping stores an array (an ethereum.Tuple, often) as Bytes, as graph init's " +
1387
+ "`changetype<Bytes[]>(event.params.<structs>)` does. graph-node stores the wasm memory addresses of " +
1388
+ "its elements there; envio stores as many bytes of stand-in addresses, so those values differ.",
1389
+ );
1390
+ }
1391
+ const bytes = new Bytes(elements.length * 4);
1392
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
1393
+ elements.forEach((element, index) => view.setUint32(index * 4, Number(operators.address(element)) >>> 0, true));
1394
+ return bytes;
1395
+ }
1396
+
1397
+ function toRow(entityType: string, entity: Entity, id?: string): Record<string, unknown> {
1398
+ const { schema } = currentScope();
1399
+ // graph-ts holds a relation as the related entity's id under the field's own
1400
+ // name; envio's column for it is `<field>_id`.
1401
+ const { fields, timestampFields, refFields, listFields } = entityLayout(schema, entityType);
1402
+ const row: Record<string, unknown> = {};
1403
+ for (const entry of entriesOf(entity)) {
1404
+ // A field the schema doesn't define isn't stored: graph-node only writes
1405
+ // the declared columns, and before apiVersion 0.0.8 lets a mapping set
1406
+ // others (`check_invalid_fields` refuses them from 0.0.8 on).
1407
+ if (fields !== undefined && !fields.has(entry.key)) continue;
1408
+ const column = refFields.has(entry.key) ? `${entry.key}_id` : entry.key;
1409
+ const declared = schema.entityFieldTypes[entityType]?.[entry.key];
1410
+ // An id, a relation and a stored list of entities hold ids: bytes when the
1411
+ // id they name is `Bytes`, text otherwise. Every other `Bytes` field is a
1412
+ // bytes column, which is what `bytes_type: uint8array` makes of it.
1413
+ const idColumn =
1414
+ entry.key === "id" || refFields.has(entry.key) || listFields.has(entry.key)
1415
+ ? declaredKind(schema, declared) === "Bytes"
1416
+ ? "bytes"
1417
+ : "text"
1418
+ : undefined;
1419
+ if (
1420
+ declared?.nullableElements &&
1421
+ entry.value.kind === ValueKind.ARRAY &&
1422
+ entry.value.toArray().some((item) => item.kind === ValueKind.NULL || item.data == null)
1423
+ ) {
1424
+ throw unsupported(
1425
+ "a null element in a list field",
1426
+ `${entityType}[${id}].${entry.key}`,
1427
+ `graph-node stores ${entityType}.${entry.key} ([${declared.kind}]) with the null in it, and envio's store ` +
1428
+ "holds list elements that aren't null",
1429
+ );
1430
+ }
1431
+ row[column] =
1432
+ timestampFields.has(entry.key) && entry.value.kind !== ValueKind.NULL
1433
+ ? new Date(Number(entry.value.toTimestamp() / 1000n))
1434
+ : fromValue(entry.value, idColumn);
1435
+ }
1436
+ return row;
1437
+ }
1438
+
1439
+ function fromValue(value: Value, idColumn?: "bytes" | "text"): unknown {
1440
+ switch (value.kind) {
1441
+ case ValueKind.NULL:
1442
+ return null;
1443
+ case ValueKind.BYTES:
1444
+ return idColumn === "text" ? value.toBytes().toHexString() : Uint8Array.from(value.toBytes());
1445
+ case ValueKind.BIGINT:
1446
+ return value.toBigInt().valueOf();
1447
+ case ValueKind.INT8:
1448
+ case ValueKind.TIMESTAMP:
1449
+ return BigInt(value.data as any);
1450
+ case ValueKind.BIGDECIMAL:
1451
+ return value.toBigDecimal().value;
1452
+ case ValueKind.ARRAY:
1453
+ return value.toArray().map((item) => fromValue(item, idColumn));
1454
+ case ValueKind.STRING:
1455
+ return idColumn === "bytes" ? graphIdToStoredId("Bytes", withoutNul(value.data)) : withoutNul(value.data);
1456
+ default:
1457
+ return value.data;
1458
+ }
1459
+ }
1460
+
1461
+ /**
1462
+ * graph-node drops every NUL from a string that crosses from the mapping to the
1463
+ * host ("Strip null characters since they are not accepted by Postgres",
1464
+ * `FromAscObj<AscString> for String`), so a bytes32 symbol padded with NULs and
1465
+ * decoded as a string is stored without them. Postgres refuses 0x00 in text.
1466
+ */
1467
+ function withoutNul<T>(text: T): T {
1468
+ // A String field a mapping filled with null (graph-ts' `logType`) is not text.
1469
+ return typeof text === "string" && text.includes("\u0000") ? (text.replaceAll("\u0000", "") as T) : text;
1470
+ }
1471
+
1472
+ /** envio rows -> graph-ts `Value`s. */
1473
+ function toEntity(entityType: string, row: Record<string, unknown> | undefined | null): Entity | null {
1474
+ if (row === undefined || row === null) {
1475
+ return null;
1476
+ }
1477
+ const { schema } = currentScope();
1478
+ const layout = entityLayout(schema, entityType);
1479
+ const entity = new Entity();
1480
+ Object.defineProperty(entity, ENTITY_TYPE, { value: entityType });
1481
+ for (const column of Object.keys(row)) {
1482
+ const value = row[column];
1483
+ const { key, kind, timestamp } = columnOf(schema, entityType, layout, column);
1484
+ entity.set(
1485
+ key,
1486
+ timestamp && value !== null && value !== undefined
1487
+ ? Value.fromTimestamp(BigInt((value as Date).getTime()) * 1000n)
1488
+ : toValue(value, kind),
1489
+ );
1490
+ }
1491
+ return entity;
1492
+ }
1493
+
1494
+ /**
1495
+ * What a mapping expects a field to read back as. A relation carries the id of
1496
+ * the entity it points at, so its type is that entity's own `id` type — which is
1497
+ * how `Token.load(pool.token0)` gets `Bytes` rather than a string.
1498
+ */
1499
+ function declaredKind(
1500
+ schema: { entityFieldTypes: Record<string, Record<string, { kind: string; target?: string; list?: boolean }>> },
1501
+ field: { kind: string; target?: string; list?: boolean } | undefined,
1502
+ ): string | undefined {
1503
+ if (!field) return undefined;
1504
+ // For a list, this is the element's kind — a list of entity ids carries the
1505
+ // ids themselves, so each one is the target entity's id type.
1506
+ if (field.kind !== "Entity") return field.kind;
1507
+ const target = field.target ? schema.entityFieldTypes[field.target] : undefined;
1508
+ return target?.id?.kind;
1509
+ }
1510
+
1511
+ function toValue(value: unknown, kind?: string): Value {
1512
+ if (value === null || value === undefined) return Value.fromNull();
1513
+ if (Array.isArray(value)) return Value.fromArray(value.map((item) => toValue(item, kind)));
1514
+ switch (kind) {
1515
+ case "Bytes":
1516
+ // A bytes column reads back as the bytes themselves.
1517
+ if (value instanceof Uint8Array) return Value.fromBytes(Bytes.fromUint8Array(value));
1518
+ return typeof value === "string" ? Value.fromBytes(Bytes.fromHexString(value)) : Value.fromNull();
1519
+ case "BigInt":
1520
+ case "Int8":
1521
+ return Value.fromBigInt(typeof value === "bigint" ? new BigInt_(value) : BigInt_.fromString(String(value)));
1522
+ case "BigDecimal":
1523
+ return Value.fromBigDecimal(
1524
+ value instanceof BigNumber ? new BigDecimal(value) : BigDecimal.fromString(String(value)),
1525
+ );
1526
+ case "Int":
1527
+ return Value.fromI32(Number(value));
1528
+ case "Boolean":
1529
+ return Value.fromBoolean(Boolean(value));
1530
+ default:
1531
+ break;
1532
+ }
1533
+ if (typeof value === "string") return Value.fromString(value);
1534
+ if (typeof value === "boolean") return Value.fromBoolean(value);
1535
+ if (typeof value === "number") return Value.fromI32(value);
1536
+ if (typeof value === "bigint") return Value.fromBigInt(new BigInt_(value));
1537
+ if (Array.isArray(value)) return Value.fromArray(value.map((item) => toValue(item)));
1538
+ if (value instanceof BigNumber) return Value.fromBigDecimal(new BigDecimal(value));
1539
+ return Value.fromString(String(value));
1540
+ }
1541
+
1542
+ function entityContext(entityType: string) {
1543
+ const { context, schema } = currentScope();
1544
+ const accessor = schema.entityAccessors[entityType];
1545
+ const table = accessor === undefined ? undefined : context[accessor];
1546
+ if (!table) {
1547
+ throw unknown(`the entity ${entityType}`, "a mapping handler");
1548
+ }
1549
+ return table;
1550
+ }
1551
+
1552
+ /**
1553
+ * What the register passes have written, at the position of the event that
1554
+ * wrote it. Nothing is in envio's store at fetch time, so this is the register
1555
+ * pass's only notion of state: a handler that loads an entity an earlier
1556
+ * creating handler saved — Aave's `handleReserveInitialized` before
1557
+ * `ATokenContract.create()`, Venus's `getOrCreateMarket` — sees it, as it
1558
+ * would on graph-node, instead of null. A read only sees writes from strictly
1559
+ * earlier events on its chain; anything evicted past the cap, or written by a
1560
+ * handler that creates nothing (and so gets no register pass), reads null as
1561
+ * before.
1562
+ */
1563
+ const registerStore = new Map<string, { position: number; row: Record<string, unknown> | null }[]>();
1564
+ /** Least recently used entities go first: a factory's own entity stays hot. */
1565
+ const REGISTER_STORE_LIMIT = 50_000;
1566
+ const REGISTER_STORE_VERSIONS = 4;
1567
+ /**
1568
+ * Writes of one block beyond these are dropped oldest first. The passes of a
1569
+ * block's creation rounds read out of log order — a round-1 event logged before
1570
+ * the deployer reads the deployer's write, below a few later writes of its own
1571
+ * round — so the versions a block adds cannot evict the ones below it.
1572
+ */
1573
+ const REGISTER_STORE_BLOCK_VERSIONS = 64;
1574
+
1575
+ function registerStoreKey(chainId: number, entityType: string, id: string): string {
1576
+ return `${chainId}\u0000${entityType}\u0000${id}`;
1577
+ }
1578
+
1579
+ let registerMissHook: ((scope: Scope, key: string) => void) | null = null;
1580
+
1581
+ /** Called on a register-store miss; may suspend the pass to wait on another. */
1582
+ export function installRegisterMissHook(hook: typeof registerMissHook) {
1583
+ registerMissHook = hook;
1584
+ }
1585
+
1586
+ function registerRead(scope: Scope, entityType: string, id: string, sameBlock: boolean, wait = true) {
1587
+ const key = registerStoreKey(scope.dataSource.chainId, entityType, id);
1588
+ if (scope.writes?.has(key)) return toEntity(entityType, scope.writes.get(key));
1589
+ const position = scope.position ?? 0;
1590
+ const versions = registerStore.get(key) ?? [];
1591
+ if (versions.length > 0) {
1592
+ registerStore.delete(key);
1593
+ registerStore.set(key, versions);
1594
+ }
1595
+ for (let index = versions.length - 1; index >= 0; index--) {
1596
+ const version = versions[index];
1597
+ if (version.position >= position) continue;
1598
+ if (sameBlock && Math.floor(version.position / POSITION_BLOCK) !== Math.floor(position / POSITION_BLOCK)) {
1599
+ return null;
1600
+ }
1601
+ return toEntity(entityType, version.row);
1602
+ }
1603
+ if (wait) registerMissHook?.(scope, key);
1604
+ return null;
1605
+ }
1606
+
1607
+ /**
1608
+ * An event's place in its chain: block, then the round graph-node runs it in
1609
+ * (the block's own triggers, then those of the data sources they created, and
1610
+ * so on), then log index.
1611
+ */
1612
+ export const ROUND_SPAN = 1_000_000;
1613
+ export const POSITION_BLOCK = 16 * ROUND_SPAN;
1614
+
1615
+ /**
1616
+ * Every chain's lowest partition frontier, as a position: no fetch starts
1617
+ * below it, so no register pass still to run reads there. A data source
1618
+ * created at a block gets a partition of its own that starts at that block
1619
+ * while the others fetch ahead, so its passes read versions far below the
1620
+ * newest.
1621
+ */
1622
+ const registerFloors = new Map<number, number>();
1623
+ export function setRegisterFrontier(chainId: number, block: number): void {
1624
+ registerFloors.set(chainId, block * POSITION_BLOCK);
1625
+ }
1626
+ /** Versions of one entity above the floor, past which the oldest go anyway. */
1627
+ const REGISTER_STORE_MAX_VERSIONS = 512;
1628
+
1629
+ /**
1630
+ * Adds one write to an entity's versions, in position order. Above `floor`
1631
+ * every version stays and below it only the newest, which a pass at the floor
1632
+ * reads. Without one, the newest few versions of earlier blocks stay, and a
1633
+ * bounded run of the newest block's own.
1634
+ */
1635
+ export function addRegisterVersion(
1636
+ versions: { position: number; row: Record<string, unknown> | null }[],
1637
+ version: { position: number; row: Record<string, unknown> | null },
1638
+ floor?: number,
1639
+ ): void {
1640
+ const at = versions.findIndex((other) => other.position >= version.position);
1641
+ if (at === -1) versions.push(version);
1642
+ else if (versions[at].position === version.position) versions[at] = version;
1643
+ else versions.splice(at, 0, version);
1644
+ if (floor !== undefined) {
1645
+ let below = 0;
1646
+ while (below + 1 < versions.length && versions[below + 1].position < floor) below++;
1647
+ if (below > 0) versions.splice(0, below);
1648
+ if (versions.length > REGISTER_STORE_MAX_VERSIONS) versions.splice(0, versions.length - REGISTER_STORE_MAX_VERSIONS);
1649
+ return;
1650
+ }
1651
+ const newest = Math.floor(versions[versions.length - 1].position / POSITION_BLOCK);
1652
+ const blockStart = versions.findIndex((other) => Math.floor(other.position / POSITION_BLOCK) === newest);
1653
+ const dropOlder = blockStart - REGISTER_STORE_VERSIONS;
1654
+ if (dropOlder > 0) versions.splice(0, dropOlder);
1655
+ const keptOlder = Math.min(blockStart, REGISTER_STORE_VERSIONS);
1656
+ const dropBlock = versions.length - keptOlder - REGISTER_STORE_BLOCK_VERSIONS;
1657
+ if (dropBlock > 0) versions.splice(keptOlder, dropBlock);
1658
+ }
1659
+
1660
+ /** Keeps a completed register round's writes for the passes after it. */
1661
+ export function commitRegisterWrites(scope: Scope): void {
1662
+ const writes = scope.writes;
1663
+ scope.writes = undefined;
1664
+ if (!writes || writes.size === 0) return;
1665
+ const position = scope.position ?? 0;
1666
+ for (const [key, row] of writes) {
1667
+ let versions = registerStore.get(key);
1668
+ if (versions) {
1669
+ registerStore.delete(key);
1670
+ } else {
1671
+ versions = [];
1672
+ if (registerStore.size >= REGISTER_STORE_LIMIT) {
1673
+ registerStore.delete(registerStore.keys().next().value!);
1674
+ }
1675
+ }
1676
+ registerStore.set(key, versions);
1677
+ addRegisterVersion(versions, { position, row }, registerFloors.get(scope.dataSource.chainId));
1678
+ }
1679
+ }
1680
+
1681
+ /** Between test indexers, whose chains restart at block zero. */
1682
+ export function resetRegisterStore(): void {
1683
+ registerStore.clear();
1684
+ registerFloors.clear();
1685
+ }
1686
+
1687
+ /**
1688
+ * A field a new entity never set is null in graph-node's store; envio's write
1689
+ * needs it spelled out — a list column refuses a missing value.
1690
+ */
1691
+ function withUnsetAsNull(scope: Scope, entityType: string, row: Record<string, unknown>) {
1692
+ for (const column of storedColumnsOfEntity(scope, entityType)) {
1693
+ if (!(column in row)) row[column] = null;
1694
+ }
1695
+ return row;
1696
+ }
1697
+
1698
+ const columnsBySchema = new WeakMap<object, Map<string, string[]>>();
1699
+
1700
+ /** An entity's stored columns: a reference field's is `<field>_id`, a derived one has none. */
1701
+ function storedColumnsOfEntity(scope: Scope, entityType: string): string[] {
1702
+ let byType = columnsBySchema.get(scope.schema);
1703
+ if (!byType) {
1704
+ byType = new Map();
1705
+ columnsBySchema.set(scope.schema, byType);
1706
+ }
1707
+ let columns = byType.get(entityType);
1708
+ if (!columns) {
1709
+ const refFields = new Set(scope.schema.entityRefFields[entityType] ?? []);
1710
+ columns = Object.entries(scope.schema.entityFieldTypes[entityType] ?? {})
1711
+ .filter(([, declared]) => !declared.derivedFrom)
1712
+ .map(([field]) => (refFields.has(field) ? `${field}_id` : field));
1713
+ byType.set(entityType, columns);
1714
+ }
1715
+ return columns;
1716
+ }
1717
+
1718
+ /**
1719
+ * graph-ts names an entity by a string, a `Bytes` id by its hex; graph-node
1720
+ * stores that id as the bytes, so `0xAB` and `0xab` are one entity. `kind` is
1721
+ * the column's declared kind, a relation's being its target's id kind.
1722
+ */
1723
+ function graphIdToStoredId(kind: string | undefined, id: string): unknown {
1724
+ if (kind !== "Bytes") return id;
1725
+ const digits = id.startsWith("0x") || id.startsWith("0X") ? id.slice(2) : id;
1726
+ if (digits.length % 2 !== 0 || !HEX_DIGITS.test(digits)) {
1727
+ throw new Error(`Envio Subgraph: \`${id}\` names an entity whose id is Bytes, and isn't hex.`);
1728
+ }
1729
+ return new Uint8Array(Buffer.from(digits, "hex"));
1730
+ }
1731
+
1732
+ function storedIdToGraphId(id: unknown): string {
1733
+ return id instanceof Uint8Array ? toHex(id) : String(id);
1734
+ }
1735
+
1736
+ function entityIdToStoredId(scope: Scope, entityType: string, id: string): unknown {
1737
+ return graphIdToStoredId(scope.schema.entityFieldTypes[entityType]?.id?.kind, withoutNul(id));
1738
+ }
1739
+
1740
+ /**
1741
+ * graph-node's EntityCache::set validates a save: an update that sets every
1742
+ * non-nullable field passes as it is (a null counts as set), otherwise it is
1743
+ * merged onto the stored entity — where a null removes the field — and the
1744
+ * first non-nullable field still missing, in schema order, fails the handler.
1745
+ * `store.set` supplies the id itself, and a timeseries point's timestamp too.
1746
+ */
1747
+ function assertRequiredFields(
1748
+ scope: Scope,
1749
+ entityType: string,
1750
+ id: string,
1751
+ data: Entity,
1752
+ stored: Record<string, unknown> | undefined,
1753
+ supplied?: string,
1754
+ ) {
1755
+ const declared = scope.schema.entityFieldTypes[entityType] ?? {};
1756
+ const required = (scope.schema.entityFields[entityType] ?? Object.keys(declared)).filter(
1757
+ (field) => field !== "id" && field !== supplied && declared[field]?.required && !declared[field]?.derivedFrom,
1758
+ );
1759
+ const update = new Map(entriesOf(data).map((entry) => [entry.key as string, entry.value]));
1760
+ if (required.every((field) => update.has(field))) return;
1761
+ const refFields = new Set(scope.schema.entityRefFields[entityType] ?? []);
1762
+ const missing = required.find((field) => {
1763
+ const value = update.get(field);
1764
+ if (value !== undefined) return value.kind === ValueKind.NULL;
1765
+ const column = refFields.has(field) ? `${field}_id` : field;
1766
+ return stored?.[column] === undefined || stored[column] === null;
1767
+ });
1768
+ if (missing !== undefined) {
1769
+ throw new Error(`Entity ${entityType}[${id}]: missing value for non-nullable field \`${missing}\``);
1770
+ }
1771
+ }
1772
+
1773
+ function registerWrite(scope: Scope, entityType: string, id: string, row: Record<string, unknown> | null) {
1774
+ scope.writes ??= new Map();
1775
+ scope.writes.set(registerStoreKey(scope.dataSource.chainId, entityType, id), row);
1776
+ }
1777
+
1778
+ /**
1779
+ * A file data source reaches the types its manifest entry lists, as graph-node
1780
+ * enforces, and only what it wrote itself. Returns whether the entity is one it
1781
+ * wrote; `false` for a scope that isn't a file data source's.
1782
+ */
1783
+ function fileAccess(scope: Scope, entityType: string, id: string): boolean | null {
1784
+ const file = scope.file;
1785
+ if (!file) return null;
1786
+ if (!file.entities.has(entityType)) {
1787
+ throw new Error(
1788
+ `entity type \`${entityType}\` is not on the 'entities' list for data source ` +
1789
+ `\`${scope.dataSource.name}\`. Hint: Add \`${entityType}\` to the 'entities' list, which ` +
1790
+ `currently is: \`${[...file.entities].join(", ")}\`.`,
1791
+ );
1792
+ }
1793
+ return file.written.has(`${entityType}\u0000${id}`);
1794
+ }
1795
+
1796
+ const storeImpl = {
1797
+ get(entityType: string, id: string): Entity | null {
1798
+ const scope = currentScope();
1799
+ const storedId = entityIdToStoredId(scope, entityType, id);
1800
+ id = storedIdToGraphId(storedId);
1801
+ if (fileAccess(scope, entityType, id) === false) return null;
1802
+ if (scope.mode === "register") return registerRead(scope, entityType, id, false);
1803
+ return toEntity(entityType, entityContext(entityType).getSync(storedId));
1804
+ },
1805
+ get_in_block(entityType: string, id: string): Entity | null {
1806
+ const scope = currentScope();
1807
+ const storedId = entityIdToStoredId(scope, entityType, id);
1808
+ id = storedIdToGraphId(storedId);
1809
+ if (fileAccess(scope, entityType, id) === false) return null;
1810
+ if (scope.mode === "register") return registerRead(scope, entityType, id, true);
1811
+ return toEntity(entityType, entityContext(entityType).getInBlockSync(storedId));
1812
+ },
1813
+ set(entityType: string, id: string, data: Entity): void {
1814
+ const scope = currentScope();
1815
+ const storedId = entityIdToStoredId(scope, entityType, id);
1816
+ id = storedIdToGraphId(storedId);
1817
+ const own = fileAccess(scope, entityType, id);
1818
+ if (scope.file) scope.file.written.add(`${entityType}\u0000${id}`);
1819
+ const row = toRow(entityType, data, id);
1820
+ row.id = storedId;
1821
+ // graph-node merges a save onto the stored entity (EntityCache::set is an
1822
+ // Update, merged by merge_remove_null_fields): a field this instance
1823
+ // doesn't set keeps its stored value, an explicit null clears it. ENS's
1824
+ // handleAddrChanged saves a fresh `new Resolver(id)` and relies on it.
1825
+ if (scope.mode === "register") {
1826
+ const stored = registerRead(scope, entityType, id, false, false);
1827
+ return registerWrite(scope, entityType, id, stored ? { ...toRow(entityType, stored), ...row } : row);
1828
+ }
1829
+ const table = entityContext(entityType);
1830
+ if (scope.schema.timeseriesEntities.includes(entityType)) {
1831
+ // graph-node overrides whatever id and timestamp the mapping set.
1832
+ const sequence = scope.timeseriesSequence ?? 0;
1833
+ scope.timeseriesSequence = sequence + 1;
1834
+ row.id = timeseriesId(scope.blockNumber, (scope.timeseriesBase ?? 0) + sequence);
1835
+ row.timestamp = new Date(Number(scope.blockSeconds?.() ?? 0) * 1000);
1836
+ assertRequiredFields(scope, entityType, row.id as string, data, undefined, "timestamp");
1837
+ return table.set(withUnsetAsNull(scope, entityType, row));
1838
+ }
1839
+ // A file data source's first write of an id is a new entity of its own,
1840
+ // whatever a chain handler stored under the same id.
1841
+ // A save that sets every column leaves the merge nothing to keep, so the
1842
+ // store isn't read: for an insert-only entity that read was a database
1843
+ // lookup and a second preload round per event.
1844
+ const replacesWhole =
1845
+ !scope.schema.skipDuplicateEntities.includes(entityType) &&
1846
+ storedColumnsOfEntity(scope, entityType).every((column) => column in row);
1847
+ const stored = own === false || replacesWhole ? undefined : table.getSync(storedId);
1848
+ assertRequiredFields(scope, entityType, id, data, stored);
1849
+ // graph-node inserts a skipDuplicates type with ON CONFLICT DO NOTHING, so
1850
+ // an id an earlier block stored keeps that block's write; saves within
1851
+ // this block merge in its entity cache first.
1852
+ if (stored && scope.schema.skipDuplicateEntities.includes(entityType) && !table.getInBlockSync(storedId)) return;
1853
+ table.set(stored ? { ...stored, ...row } : withUnsetAsNull(scope, entityType, row));
1854
+ },
1855
+ remove(entityType: string, id: string): void {
1856
+ const scope = currentScope();
1857
+ const storedId = entityIdToStoredId(scope, entityType, id);
1858
+ id = storedIdToGraphId(storedId);
1859
+ if (fileAccess(scope, entityType, id) === false) return;
1860
+ if (scope.mode === "register") return registerWrite(scope, entityType, id, null);
1861
+ entityContext(entityType).deleteUnsafe(storedId);
1862
+ },
1863
+ /**
1864
+ * `graph codegen` names the entity the derived field is declared on and the
1865
+ * field's own name, so the lookup it stands for — the other entity's table,
1866
+ * filtered on the field that points back — is resolved from the schema.
1867
+ */
1868
+ loadRelated(entityType: string, id: string, field: string): Entity[] {
1869
+ const { mode, schema, file } = currentScope();
1870
+ if (mode === "register") return [];
1871
+ if (file && !file.entities.has(entityType)) fileAccess(currentScope(), entityType, id);
1872
+ const declared = schema.entityFieldTypes[entityType]?.[field];
1873
+ if (!declared?.target || !declared.derivedFrom) {
1874
+ throw unknown(`the derived field ${entityType}.${field}`, "a mapping handler");
1875
+ }
1876
+ const target = declared.target;
1877
+ // graph-node answers with every entity whose list holds the id; envio's
1878
+ // lookups only match a column equal to a value, so the list would read as
1879
+ // empty — refused rather than answered wrong.
1880
+ if (schema.entityListFields[target]?.includes(declared.derivedFrom)) {
1881
+ throw unsupported(
1882
+ `loading ${entityType}.${field}, derived from the list field ${target}.${declared.derivedFrom}`,
1883
+ "a mapping handler",
1884
+ );
1885
+ }
1886
+ const back = schema.entityRefFields[target]?.includes(declared.derivedFrom)
1887
+ ? `${declared.derivedFrom}_id`
1888
+ : declared.derivedFrom;
1889
+ const backKind = declaredKind(schema, schema.entityFieldTypes[target]?.[declared.derivedFrom]);
1890
+ const rows = entityContext(target).getWhereSync({
1891
+ [back]: { _eq: graphIdToStoredId(backKind, withoutNul(id)) },
1892
+ });
1893
+ // A file data source sees the related entities it wrote, and no others.
1894
+ const visible = file
1895
+ ? rows.filter(
1896
+ (row: any) => file.entities.has(target) && file.written.has(`${target}\u0000${storedIdToGraphId(row.id)}`),
1897
+ )
1898
+ : rows;
1899
+ // graph-node answers in key order (`EntityCache::load_related` walks a
1900
+ // BTreeSet of entity keys): by id, as the id's type orders.
1901
+ const numeric = schema.entityFieldTypes[target]?.id?.kind === "Int8";
1902
+ return [...visible].sort(numeric ? byNumericId : byId).map((row: any) => toEntity(target, row) as Entity);
1903
+ },
1904
+ };
1905
+
1906
+ // A Bytes id's lowercase hex orders as its bytes do.
1907
+ function byId(a: { id: unknown }, b: { id: unknown }): number {
1908
+ return codePointOrder(storedIdToGraphId(a.id), storedIdToGraphId(b.id));
1909
+ }
1910
+
1911
+ function byNumericId(a: { id: unknown }, b: { id: unknown }): number {
1912
+ const x = BigInt(String(a.id));
1913
+ const y = BigInt(String(b.id));
1914
+ return x < y ? -1 : x > y ? 1 : 0;
1915
+ }
1916
+
1917
+ export const store = strictNamespace("store", storeImpl);
1918
+
1919
+ // ---------------------------------------------------------------------------
1920
+ // ethereum
1921
+ // ---------------------------------------------------------------------------
1922
+
1923
+ /**
1924
+ * An ABI value, tagged with its Solidity kind the way graph-ts tags it — a
1925
+ * mapping may switch on `kind`, and each accessor refuses a value of another
1926
+ * kind rather than coerce it.
1927
+ */
1928
+ export class EthereumValue {
1929
+ constructor(
1930
+ public kind: EthereumValueKind,
1931
+ public data: any,
1932
+ ) {}
1933
+
1934
+ static fromAddress(value: Address): EthereumValue {
1935
+ return new EthereumValue(EthereumValueKind.ADDRESS, value);
1936
+ }
1937
+ static fromBoolean(value: boolean): EthereumValue {
1938
+ return new EthereumValue(EthereumValueKind.BOOL, value);
1939
+ }
1940
+ static fromBytes(value: Bytes): EthereumValue {
1941
+ return new EthereumValue(EthereumValueKind.BYTES, value);
1942
+ }
1943
+ static fromFixedBytes(value: Bytes): EthereumValue {
1944
+ return new EthereumValue(EthereumValueKind.FIXED_BYTES, value);
1945
+ }
1946
+ static fromI32(value: number): EthereumValue {
1947
+ return new EthereumValue(EthereumValueKind.INT, BigInt_.fromI32(value));
1948
+ }
1949
+ static fromSignedBigInt(value: BigInt_): EthereumValue {
1950
+ return new EthereumValue(EthereumValueKind.INT, value);
1951
+ }
1952
+ static fromUnsignedBigInt(value: BigInt_): EthereumValue {
1953
+ return new EthereumValue(EthereumValueKind.UINT, value);
1954
+ }
1955
+ static fromString(value: string): EthereumValue {
1956
+ return new EthereumValue(EthereumValueKind.STRING, value);
1957
+ }
1958
+ static fromArray(values: EthereumValue[]): EthereumValue {
1959
+ return new EthereumValue(EthereumValueKind.ARRAY, values);
1960
+ }
1961
+ static fromFixedSizedArray(values: EthereumValue[]): EthereumValue {
1962
+ return new EthereumValue(EthereumValueKind.FIXED_ARRAY, values);
1963
+ }
1964
+ static fromTuple(values: EthereumValue[]): EthereumValue {
1965
+ return new EthereumValue(
1966
+ EthereumValueKind.TUPLE,
1967
+ values instanceof EthereumTuple ? values : EthereumTuple.from(values),
1968
+ );
1969
+ }
1970
+ static fromMatrix = arrayOf(EthereumValue.fromArray);
1971
+ static fromTupleArray = arrayOf(EthereumValue.fromTuple);
1972
+ static fromTupleMatrix = arrayOf(EthereumValue.fromTupleArray);
1973
+ static fromBooleanArray = arrayOf(EthereumValue.fromBoolean);
1974
+ static fromBytesArray = arrayOf(EthereumValue.fromBytes);
1975
+ static fromFixedBytesArray = arrayOf(EthereumValue.fromFixedBytes);
1976
+ static fromAddressArray = arrayOf(EthereumValue.fromAddress);
1977
+ static fromStringArray = arrayOf(EthereumValue.fromString);
1978
+ static fromI32Array = arrayOf(EthereumValue.fromI32);
1979
+ static fromSignedBigIntArray = arrayOf(EthereumValue.fromSignedBigInt);
1980
+ static fromUnsignedBigIntArray = arrayOf(EthereumValue.fromUnsignedBigInt);
1981
+ static fromBooleanMatrix = arrayOf(EthereumValue.fromBooleanArray);
1982
+ static fromBytesMatrix = arrayOf(EthereumValue.fromBytesArray);
1983
+ static fromFixedBytesMatrix = arrayOf(EthereumValue.fromFixedBytesArray);
1984
+ static fromAddressMatrix = arrayOf(EthereumValue.fromAddressArray);
1985
+ static fromStringMatrix = arrayOf(EthereumValue.fromStringArray);
1986
+ static fromI32Matrix = arrayOf(EthereumValue.fromI32Array);
1987
+ static fromSignedBigIntMatrix = arrayOf(EthereumValue.fromSignedBigIntArray);
1988
+ static fromUnsignedBigIntMatrix = arrayOf(EthereumValue.fromUnsignedBigIntArray);
1989
+
1990
+ // graph-ts declares the comparison operators only to abort.
1991
+ lt(_: unknown): boolean {
1992
+ throw new Error("Less than operator isn't supported in Value");
1993
+ }
1994
+ gt(_: unknown): boolean {
1995
+ throw new Error("Greater than operator isn't supported in Value");
1996
+ }
1997
+ toAddress(): Address {
1998
+ return expectKind(this, "an address", EthereumValueKind.ADDRESS);
1999
+ }
2000
+ toBoolean(): boolean {
2001
+ return expectKind(this, "a boolean", EthereumValueKind.BOOL);
2002
+ }
2003
+ toBytes(): Bytes {
2004
+ return expectKind(this, "bytes", EthereumValueKind.FIXED_BYTES, EthereumValueKind.BYTES);
2005
+ }
2006
+ toI32(): number {
2007
+ return this.toBigInt().toI32();
2008
+ }
2009
+ toBigInt(): BigInt_ {
2010
+ return expectKind(this, "an int or uint", EthereumValueKind.INT, EthereumValueKind.UINT);
2011
+ }
2012
+ toString(): string {
2013
+ return expectKind(this, "a string", EthereumValueKind.STRING);
2014
+ }
2015
+ toArray(): EthereumValue[] {
2016
+ return expectKind(this, "an array", EthereumValueKind.ARRAY, EthereumValueKind.FIXED_ARRAY);
2017
+ }
2018
+ toTuple(): EthereumTuple {
2019
+ return expectKind(this, "a tuple", EthereumValueKind.TUPLE);
2020
+ }
2021
+ toMatrix(): EthereumValue[][] {
2022
+ return this.toArray().map((value) => value.toArray());
2023
+ }
2024
+ toTupleArray<T>(): T[] {
2025
+ return this.toArray().map((value) => value.toTuple() as unknown as T);
2026
+ }
2027
+ toTupleMatrix<T>(): T[][] {
2028
+ return this.toArray().map((value) => value.toTupleArray<T>());
2029
+ }
2030
+ toBooleanArray(): boolean[] {
2031
+ return this.toArray().map((value) => value.toBoolean());
2032
+ }
2033
+ toBytesArray(): Bytes[] {
2034
+ return this.toArray().map((value) => value.toBytes());
2035
+ }
2036
+ toAddressArray(): Address[] {
2037
+ return this.toArray().map((value) => value.toAddress());
2038
+ }
2039
+ toStringArray(): string[] {
2040
+ return this.toArray().map((value) => value.toString());
2041
+ }
2042
+ toI32Array(): number[] {
2043
+ return this.toArray().map((value) => value.toI32());
2044
+ }
2045
+ toBigIntArray(): BigInt_[] {
2046
+ return this.toArray().map((value) => value.toBigInt());
2047
+ }
2048
+ toBooleanMatrix(): boolean[][] {
2049
+ return this.toArray().map((value) => value.toBooleanArray());
2050
+ }
2051
+ toBytesMatrix(): Bytes[][] {
2052
+ return this.toArray().map((value) => value.toBytesArray());
2053
+ }
2054
+ toAddressMatrix(): Address[][] {
2055
+ return this.toArray().map((value) => value.toAddressArray());
2056
+ }
2057
+ toStringMatrix(): string[][] {
2058
+ return this.toArray().map((value) => value.toStringArray());
2059
+ }
2060
+ toI32Matrix(): number[][] {
2061
+ return this.toArray().map((value) => value.toI32Array());
2062
+ }
2063
+ toBigIntMatrix(): BigInt_[][] {
2064
+ return this.toArray().map((value) => value.toBigIntArray());
2065
+ }
2066
+ }
2067
+
2068
+ function expectKind(value: EthereumValue, what: string, ...kinds: EthereumValueKind[]): any {
2069
+ if (!kinds.includes(value.kind)) throw new Error(`Ethereum value is not ${what}`);
2070
+ return value.data;
2071
+ }
2072
+
2073
+ function arrayOf<T>(from: (value: T) => EthereumValue) {
2074
+ return (values: T[]): EthereumValue => EthereumValue.fromArray(values.map(from));
2075
+ }
2076
+
2077
+ /** Generated struct classes extend this and read their fields by index. */
2078
+ class EthereumTuple extends Array<EthereumValue> {}
2079
+
2080
+ class SmartContractCall {
2081
+ constructor(
2082
+ public contractName: string,
2083
+ public contractAddress: Address,
2084
+ public functionName: string,
2085
+ public functionSignature: string,
2086
+ public functionParams: EthereumValue[],
2087
+ ) {}
2088
+ }
2089
+
2090
+ export class CallResult<T> {
2091
+ // Generated bindings signal a revert with a bare `new ethereum.CallResult()`.
2092
+ constructor(
2093
+ public reverted: boolean = true,
2094
+ private _value: T | null = null,
2095
+ ) {}
2096
+ get value(): T {
2097
+ if (this.reverted) {
2098
+ throw new Error("accessed value of a reverted call, please check the `reverted` field");
2099
+ }
2100
+ return this._value as T;
2101
+ }
2102
+ static fromValue<T>(value: T): CallResult<T> {
2103
+ return new CallResult(false, value);
2104
+ }
2105
+ static fromNullable<T>(value: T | null): CallResult<T> {
2106
+ return new CallResult(value === null, value);
2107
+ }
2108
+ }
2109
+
2110
+ class SmartContract {
2111
+ constructor(
2112
+ public _name: string,
2113
+ public _address: Address,
2114
+ ) {}
2115
+
2116
+ call(name: string, functionSignature: string | EthereumValue[], params?: EthereumValue[]): EthereumValue[] {
2117
+ const [signature, values] = signatureAndParams(this, name, functionSignature, params);
2118
+ return callContract(this, signature, values, false) as EthereumValue[];
2119
+ }
2120
+
2121
+ tryCall(
2122
+ name: string,
2123
+ functionSignature: string | EthereumValue[],
2124
+ params?: EthereumValue[],
2125
+ ): CallResult<EthereumValue[]> {
2126
+ const [signature, values] = signatureAndParams(this, name, functionSignature, params);
2127
+ return callContract(this, signature, values, true) as CallResult<EthereumValue[]>;
2128
+ }
2129
+ }
2130
+
2131
+ /**
2132
+ * Bindings from graph-cli before 0.18 call by name alone,
2133
+ * `super.call("ownerOf", [params])`, and graph-node takes the first function of
2134
+ * that name in the ABI (chain/ethereum/src/runtime/runtime_adapter.rs,
2135
+ * `contract.function(name)` when the call has no signature). Only the runtime
2136
+ * has the manifest's ABIs, so it installs the lookup.
2137
+ */
2138
+ let legacySignatureHook: ((abiName: string, functionName: string) => string) | null = null;
2139
+
2140
+ export function installLegacySignatureHook(hook: typeof legacySignatureHook) {
2141
+ legacySignatureHook = hook;
2142
+ }
2143
+
2144
+ function signatureAndParams(
2145
+ contract: SmartContract,
2146
+ name: string,
2147
+ functionSignature: string | EthereumValue[],
2148
+ params: EthereumValue[] | undefined,
2149
+ ): [string, EthereumValue[]] {
2150
+ if (typeof functionSignature === "string") return [functionSignature, params ?? []];
2151
+ if (!legacySignatureHook) {
2152
+ throw unsupported("contract calls by function name without the manifest's ABIs", `${contract._name}.${name}`);
2153
+ }
2154
+ return [legacySignatureHook(contract._name, name), functionSignature];
2155
+ }
2156
+
2157
+ /**
2158
+ * Contract calls go through the shim's call hook, installed by the runtime so
2159
+ * this module stays free of envio imports. A transport failure is not a revert:
2160
+ * the call effect asks again until it answers, so a flaky RPC never fabricates
2161
+ * `{reverted: true}` data.
2162
+ */
2163
+ let callHook:
2164
+ | ((call: SmartContractCall) => { reverted: boolean; value: unknown[] | null })
2165
+ | null = null;
2166
+
2167
+ export function installCallHook(hook: typeof callHook) {
2168
+ callHook = hook;
2169
+ }
2170
+
2171
+ function callContract(
2172
+ contract: SmartContract,
2173
+ functionSignature: string,
2174
+ params: EthereumValue[],
2175
+ isTry: boolean,
2176
+ ) {
2177
+ if (!callHook) {
2178
+ throw unsupported(
2179
+ "contract calls without a configured RPC endpoint",
2180
+ `${contract._name}.${functionSignature}`,
2181
+ );
2182
+ }
2183
+ const call = new SmartContractCall(
2184
+ contract._name,
2185
+ contract._address,
2186
+ parseSignature(functionSignature).name,
2187
+ functionSignature,
2188
+ params,
2189
+ );
2190
+ // A suspend thrown by the underlying effect must escape `try_` too: it isn't
2191
+ // a revert, it's "not resolved yet".
2192
+ const result = callHook(call);
2193
+ const values = () => {
2194
+ const outputs = outputConverters(functionSignature);
2195
+ return (result.value ?? []).map((raw, index) => outputs[index](raw));
2196
+ };
2197
+ if (isTry) {
2198
+ return result.reverted ? new CallResult(true, null) : CallResult.fromValue(values());
2199
+ }
2200
+ if (result.reverted) {
2201
+ throw new Error(`Call to ${contract._name}.${functionSignature} reverted`);
2202
+ }
2203
+ return values();
2204
+ }
2205
+
2206
+ const outputConverters = memoised((signature) =>
2207
+ splitTypes(parseSignature(signature).outputs).map(ethereumConverter),
2208
+ );
2209
+
2210
+ /** An `ethereum.Value` as the plain JS an ABI encoder takes. */
2211
+ export function ethereumValueToJs(value: EthereumValue): unknown {
2212
+ switch (value.kind) {
2213
+ case EthereumValueKind.ADDRESS:
2214
+ case EthereumValueKind.BYTES:
2215
+ case EthereumValueKind.FIXED_BYTES:
2216
+ return (value.data as Bytes).toHexString();
2217
+ case EthereumValueKind.INT:
2218
+ case EthereumValueKind.UINT:
2219
+ return (value.data as BigInt_).valueOf();
2220
+ case EthereumValueKind.ARRAY:
2221
+ case EthereumValueKind.FIXED_ARRAY:
2222
+ case EthereumValueKind.TUPLE:
2223
+ return (value.data as EthereumValue[]).map(ethereumValueToJs);
2224
+ default:
2225
+ return value.data;
2226
+ }
2227
+ }
2228
+
2229
+ /**
2230
+ * One ABI token's encoding, and whether it is dynamic (encoded after the heads
2231
+ * of the sequence holding it, which points at it) or inline in its head.
2232
+ */
2233
+ type EncodedToken = { dynamic: boolean; bytes: Uint8Array };
2234
+
2235
+ const WORD = 32;
2236
+ const wordOf = (value: bigint): Uint8Array => {
2237
+ const digits = value.toString(16);
2238
+ if (value < 0n || digits.length > WORD * 2) throw new Error(`${value} doesn't fit a word`);
2239
+ return hexToBytes(digits.padStart(WORD * 2, "0"));
2240
+ };
2241
+ const padded = (bytes: Uint8Array, left: boolean): Uint8Array => {
2242
+ const out = new Uint8Array(Math.ceil(bytes.length / WORD) * WORD || (left ? WORD : 0));
2243
+ out.set(bytes, left ? out.length - bytes.length : 0);
2244
+ return out;
2245
+ };
2246
+ const joined = (parts: Uint8Array[]): Uint8Array => {
2247
+ const out = new Uint8Array(parts.reduce((total, part) => total + part.length, 0));
2248
+ let at = 0;
2249
+ for (const part of parts) {
2250
+ out.set(part, at);
2251
+ at += part.length;
2252
+ }
2253
+ return out;
2254
+ };
2255
+
2256
+ function integerWord(value: BigInt_, signed: boolean): Uint8Array {
2257
+ const n = value.valueOf() as bigint;
2258
+ const [min, max] = signed ? [-(2n ** 255n), 2n ** 255n - 1n] : [0n, 2n ** 256n - 1n];
2259
+ if (n < min || n > max) throw new Error(`ethereum.encode: ${n} doesn't fit an ${signed ? "int" : "uint"}256`);
2260
+ return wordOf(BigInt.asUintN(256, n));
2261
+ }
2262
+
2263
+ /** Heads in order, then the tails of the dynamic tokens the heads point at. */
2264
+ function encodedSequence(items: EthereumValue[]): Uint8Array {
2265
+ const tokens = items.map(encodedToken);
2266
+ let offset = tokens.reduce((total, token) => total + (token.dynamic ? WORD : token.bytes.length), 0);
2267
+ const heads: Uint8Array[] = [];
2268
+ const tails: Uint8Array[] = [];
2269
+ for (const token of tokens) {
2270
+ if (!token.dynamic) {
2271
+ heads.push(token.bytes);
2272
+ continue;
2273
+ }
2274
+ heads.push(wordOf(BigInt(offset)));
2275
+ tails.push(token.bytes);
2276
+ offset += token.bytes.length;
2277
+ }
2278
+ return joined([...heads, ...tails]);
2279
+ }
2280
+
2281
+ /**
2282
+ * Each token encodes by its own kind, the way alloy's `DynSolValue` (and
2283
+ * ethabi's `Token` before it) does. An array of unlike values, which no
2284
+ * Solidity type spells, still encodes element by element: Reserve hashes a
2285
+ * timelock batch as such an array's encoding minus its offset and length.
2286
+ */
2287
+ function encodedToken(value: EthereumValue): EncodedToken {
2288
+ switch (value.kind) {
2289
+ case EthereumValueKind.ADDRESS:
2290
+ case EthereumValueKind.BOOL:
2291
+ case EthereumValueKind.INT:
2292
+ case EthereumValueKind.UINT:
2293
+ return { dynamic: false, bytes: staticWord(value) };
2294
+ case EthereumValueKind.FIXED_BYTES: {
2295
+ const bytes = value.data as Bytes;
2296
+ if (bytes.length > WORD) throw new Error(`ethereum.encode: fixed bytes of ${bytes.length} bytes`);
2297
+ const word = new Uint8Array(WORD);
2298
+ word.set(bytes);
2299
+ return { dynamic: false, bytes: word };
2300
+ }
2301
+ case EthereumValueKind.STRING:
2302
+ case EthereumValueKind.BYTES: {
2303
+ const bytes =
2304
+ value.kind === EthereumValueKind.STRING ? new TextEncoder().encode(value.data as string) : (value.data as Bytes);
2305
+ return { dynamic: true, bytes: joined([wordOf(BigInt(bytes.length)), padded(bytes, false)]) };
2306
+ }
2307
+ case EthereumValueKind.ARRAY: {
2308
+ const items = value.data as EthereumValue[];
2309
+ return { dynamic: true, bytes: joined([wordOf(BigInt(items.length)), encodedSequence(items)]) };
2310
+ }
2311
+ case EthereumValueKind.FIXED_ARRAY:
2312
+ case EthereumValueKind.TUPLE: {
2313
+ const items = value.data as EthereumValue[];
2314
+ return { dynamic: items.some((item) => encodedToken(item).dynamic), bytes: encodedSequence(items) };
2315
+ }
2316
+ default:
2317
+ throw new Error(`ethereum.encode can't encode a value of kind ${value.kind}`);
2318
+ }
2319
+ }
2320
+
2321
+ function staticWord(value: EthereumValue): Uint8Array {
2322
+ switch (value.kind) {
2323
+ case EthereumValueKind.ADDRESS:
2324
+ return padded(value.data as Bytes, true);
2325
+ case EthereumValueKind.BOOL:
2326
+ return wordOf(value.data ? 1n : 0n);
2327
+ case EthereumValueKind.INT:
2328
+ return integerWord(value.data as BigInt_, true);
2329
+ default:
2330
+ return integerWord(value.data as BigInt_, false);
2331
+ }
2332
+ }
2333
+
2334
+ /**
2335
+ * A graph-ts chain object as the runtime fills it in: `shaped` sets each field
2336
+ * off the decoded payload, so the constructor a mapping would call is skipped.
2337
+ */
2338
+ function blank<T>(Class: abstract new (...args: any[]) => T): T {
2339
+ return Reflect.construct(Class, []) as T;
2340
+ }
2341
+
2342
+ class EthereumBlock {
2343
+ declare hash: Bytes;
2344
+ declare parentHash: Bytes;
2345
+ declare unclesHash: Bytes;
2346
+ declare author: Address;
2347
+ declare stateRoot: Bytes;
2348
+ declare transactionsRoot: Bytes;
2349
+ declare receiptsRoot: Bytes;
2350
+ declare number: BigInt_;
2351
+ declare gasUsed: BigInt_;
2352
+ declare gasLimit: BigInt_;
2353
+ declare timestamp: BigInt_;
2354
+ declare difficulty: BigInt_;
2355
+ declare totalDifficulty: BigInt_;
2356
+ declare size: BigInt_ | null;
2357
+ declare baseFeePerGas: BigInt_ | null;
2358
+
2359
+ constructor(
2360
+ hash: Bytes,
2361
+ parentHash: Bytes,
2362
+ unclesHash: Bytes,
2363
+ author: Address,
2364
+ stateRoot: Bytes,
2365
+ transactionsRoot: Bytes,
2366
+ receiptsRoot: Bytes,
2367
+ number: BigInt_,
2368
+ gasUsed: BigInt_,
2369
+ gasLimit: BigInt_,
2370
+ timestamp: BigInt_,
2371
+ difficulty: BigInt_,
2372
+ totalDifficulty: BigInt_,
2373
+ size: BigInt_ | null,
2374
+ baseFeePerGas: BigInt_ | null,
2375
+ ) {
2376
+ if (arguments.length === 0) return;
2377
+ Object.assign(this, {
2378
+ hash,
2379
+ parentHash,
2380
+ unclesHash,
2381
+ author,
2382
+ stateRoot,
2383
+ transactionsRoot,
2384
+ receiptsRoot,
2385
+ number,
2386
+ gasUsed,
2387
+ gasLimit,
2388
+ timestamp,
2389
+ difficulty,
2390
+ totalDifficulty,
2391
+ size,
2392
+ baseFeePerGas,
2393
+ });
2394
+ }
2395
+ }
2396
+
2397
+ class EthereumTransaction {
2398
+ declare hash: Bytes;
2399
+ declare index: BigInt_;
2400
+ declare from: Address;
2401
+ declare to: Address | null;
2402
+ declare value: BigInt_;
2403
+ declare gasLimit: BigInt_;
2404
+ /**
2405
+ * graph-ts before 0.22's name for the slot graph-node fills with the gas
2406
+ * limit (`gas_limit` in every `AscEthereumTransaction_0_0_*`).
2407
+ */
2408
+ declare gasUsed: BigInt_;
2409
+ declare gasPrice: BigInt_;
2410
+ declare input: Bytes;
2411
+ declare nonce: BigInt_;
2412
+
2413
+ constructor(
2414
+ hash: Bytes,
2415
+ index: BigInt_,
2416
+ from: Address,
2417
+ to: Address | null,
2418
+ value: BigInt_,
2419
+ gasLimit: BigInt_,
2420
+ gasPrice: BigInt_,
2421
+ input: Bytes,
2422
+ nonce: BigInt_,
2423
+ ) {
2424
+ if (arguments.length === 0) return;
2425
+ Object.assign(this, { hash, index, from, to, value, gasLimit, gasPrice, input, nonce });
2426
+ }
2427
+ }
2428
+
2429
+ const NOT_DECODED = Symbol("envio.subgraph.notDecoded");
2430
+
2431
+ class EthereumEventParam {
2432
+ name: string;
2433
+ private _value: EthereumValue | typeof NOT_DECODED;
2434
+ private _raw: unknown = undefined;
2435
+ private _type = "";
2436
+
2437
+ constructor(name: string, value: EthereumValue) {
2438
+ this.name = name;
2439
+ this._value = value;
2440
+ }
2441
+
2442
+ /**
2443
+ * One of the runtime's own events' parameters, converted the first time it
2444
+ * is read: a handler reads a few of them, and graph codegen's getters reach
2445
+ * each one through the whole `parameters` array.
2446
+ */
2447
+ static decoded(name: string, type: string, raw: unknown): EthereumEventParam {
2448
+ const param = new EthereumEventParam(name, undefined as unknown as EthereumValue);
2449
+ param._value = NOT_DECODED;
2450
+ param._type = type;
2451
+ param._raw = raw;
2452
+ return param;
2453
+ }
2454
+
2455
+ get value(): EthereumValue {
2456
+ if (this._value === NOT_DECODED) {
2457
+ this._value = ethereumConverter(this._type)(this._raw);
2458
+ this._raw = undefined;
2459
+ }
2460
+ return this._value;
2461
+ }
2462
+ set value(value: EthereumValue) {
2463
+ this._value = value;
2464
+ this._raw = undefined;
2465
+ }
2466
+ }
2467
+
2468
+ /**
2469
+ * Only reachable through `event.receipt.logs`: envio's receipt selection
2470
+ * carries the scalars, so the log list is read when the mapping asks.
2471
+ */
2472
+ class EthereumLog {
2473
+ constructor(
2474
+ public address: Address,
2475
+ public topics: Bytes[],
2476
+ public data: Bytes,
2477
+ public blockHash: Bytes,
2478
+ public blockNumber: Bytes,
2479
+ public transactionHash: Bytes,
2480
+ public transactionIndex: BigInt_,
2481
+ public logIndex: BigInt_,
2482
+ public transactionLogIndex: BigInt_,
2483
+ public logType: string,
2484
+ public removed: { inner: boolean } | null,
2485
+ ) {}
2486
+ }
2487
+
2488
+ /** A transaction's logs as `receipts.ts` read them, in graph-ts' shape. */
2489
+ function receiptLogsOf(json: string, blockHash: Bytes, blockNumber: number): EthereumLog[] {
2490
+ const raw = JSON.parse(json) as {
2491
+ address: string;
2492
+ topics: string[];
2493
+ data: string;
2494
+ logIndex: number;
2495
+ transactionIndex: number;
2496
+ transactionHash: string;
2497
+ }[];
2498
+ return raw.map(
2499
+ (log) =>
2500
+ new EthereumLog(
2501
+ Address.fromString(log.address),
2502
+ log.topics.map((topic) => Bytes.fromHexString(topic)),
2503
+ Bytes.fromHexString(log.data),
2504
+ blockHash,
2505
+ // graph-ts types it `Bytes`; graph-node fills it with the number's
2506
+ // signed little-endian bytes.
2507
+ Bytes.fromHexString("0x" + bigintToLittleEndianHex(BigInt(blockNumber))),
2508
+ Bytes.fromHexString(log.transactionHash),
2509
+ // graph-node builds these from the log's u64s: minimal two's
2510
+ // complement bytes, not an i32's four.
2511
+ new BigInt_(BigInt(log.transactionIndex)),
2512
+ new BigInt_(BigInt(log.logIndex)),
2513
+ // Where no client sets it, graph-node falls back to the log index.
2514
+ new BigInt_(BigInt(log.logIndex)),
2515
+ null as unknown as string,
2516
+ new Wrapped(false),
2517
+ ),
2518
+ );
2519
+ }
2520
+
2521
+ function bigintToLittleEndianHex(value: bigint): string {
2522
+ const bytes: string[] = [];
2523
+ let rest = value;
2524
+ do {
2525
+ bytes.push((Number(rest & 0xffn)).toString(16).padStart(2, "0"));
2526
+ rest >>= 8n;
2527
+ } while (rest > 0n);
2528
+ // Signed: a set top bit would read as negative, so a zero byte follows.
2529
+ if (parseInt(bytes[bytes.length - 1], 16) & 0x80) bytes.push("00");
2530
+ return bytes.join("");
2531
+ }
2532
+
2533
+ class EthereumTransactionReceipt {
2534
+ declare transactionHash: Bytes;
2535
+ declare transactionIndex: BigInt_;
2536
+ declare blockHash: Bytes;
2537
+ declare blockNumber: BigInt_;
2538
+ declare cumulativeGasUsed: BigInt_;
2539
+ declare gasUsed: BigInt_;
2540
+ declare contractAddress: Address;
2541
+ declare logs: EthereumLog[];
2542
+ declare status: BigInt_;
2543
+ declare root: Bytes;
2544
+ declare logsBloom: Bytes;
2545
+
2546
+ constructor(
2547
+ transactionHash: Bytes,
2548
+ transactionIndex: BigInt_,
2549
+ blockHash: Bytes,
2550
+ blockNumber: BigInt_,
2551
+ cumulativeGasUsed: BigInt_,
2552
+ gasUsed: BigInt_,
2553
+ contractAddress: Address,
2554
+ logs: EthereumLog[],
2555
+ status: BigInt_,
2556
+ root: Bytes,
2557
+ logsBloom: Bytes,
2558
+ ) {
2559
+ if (arguments.length === 0) return;
2560
+ Object.assign(this, {
2561
+ transactionHash,
2562
+ transactionIndex,
2563
+ blockHash,
2564
+ blockNumber,
2565
+ cumulativeGasUsed,
2566
+ gasUsed,
2567
+ contractAddress,
2568
+ logs,
2569
+ status,
2570
+ root,
2571
+ logsBloom,
2572
+ });
2573
+ }
2574
+ }
2575
+
2576
+ /** What every call to one function shares. */
2577
+ export type CallKind = {
2578
+ /** `data source "X" → "function(...)"`, for what a refusal names. */
2579
+ location: string;
2580
+ inputs: { name: string; type: string }[];
2581
+ outputs: { name: string; type: string }[];
2582
+ };
2583
+
2584
+ const FROM_ENVIO_CALL = Symbol("envio.subgraph.call");
2585
+
2586
+ /** What a mapping-built call is, for anything that names where it came from. */
2587
+ const BUILT_CALL: CallKind = { location: "an ethereum.Call the mapping built", inputs: [], outputs: [] };
2588
+
2589
+ /**
2590
+ * graph-ts' `ethereum.Call`, over one call item. A handler typed with a
2591
+ * generated call class receives an instance of that class — it extends this
2592
+ * one — so graph codegen's `inputs` and `outputs` getters read `inputValues` and
2593
+ * `outputValues` as written. Deferred like an event: a mapping that reads one
2594
+ * input shouldn't pay to convert the block, the transaction and every value.
2595
+ */
2596
+ class EthereumCall {
2597
+ private _raw: any = null;
2598
+ private _kind: CallKind = BUILT_CALL;
2599
+ private _to: Address | undefined;
2600
+ private _from: Address | undefined;
2601
+ private _block: EthereumBlock | undefined;
2602
+ private _transaction: EthereumTransaction | undefined;
2603
+ private _inputValues: EthereumEventParam[] | undefined;
2604
+ private _outputValues: EthereumEventParam[] | undefined;
2605
+
2606
+ constructor(
2607
+ to: Address,
2608
+ from: Address,
2609
+ block: EthereumBlock,
2610
+ transaction: EthereumTransaction,
2611
+ inputValues: EthereumEventParam[],
2612
+ outputValues: EthereumEventParam[],
2613
+ ) {
2614
+ if ((to as unknown) === FROM_ENVIO_CALL) {
2615
+ this._raw = from;
2616
+ this._kind = block as unknown as CallKind;
2617
+ return;
2618
+ }
2619
+ this._to = to;
2620
+ this._from = from;
2621
+ this._block = block;
2622
+ this._transaction = transaction;
2623
+ this._inputValues = inputValues ?? [];
2624
+ this._outputValues = outputValues ?? [];
2625
+ }
2626
+
2627
+ /** The called contract. */
2628
+ get to(): Address {
2629
+ return (this._to ??= Address.fromString(this._raw.srcAddress));
2630
+ }
2631
+ set to(value: Address) {
2632
+ this._to = value;
2633
+ }
2634
+ /** The immediate caller, which is not the transaction's sender for an internal call. */
2635
+ get from(): Address {
2636
+ return (this._from ??= Address.fromString(this._raw.params.callFrom));
2637
+ }
2638
+ set from(value: Address) {
2639
+ this._from = value;
2640
+ }
2641
+ get block(): EthereumBlock {
2642
+ return (this._block ??= shaped(blank(EthereumBlock), BLOCK_SHAPE, this._raw.block));
2643
+ }
2644
+ set block(value: EthereumBlock) {
2645
+ this._block = value;
2646
+ }
2647
+ get transaction(): EthereumTransaction {
2648
+ return (this._transaction ??= shaped(blank(EthereumTransaction), TRANSACTION_SHAPE, this._raw.transaction));
2649
+ }
2650
+ set transaction(value: EthereumTransaction) {
2651
+ this._transaction = value;
2652
+ }
2653
+ get inputValues(): EthereumEventParam[] {
2654
+ return (this._inputValues ??= decodeCallValues(this._kind.inputs, this._raw.params.callInput, true));
2655
+ }
2656
+ set inputValues(value: EthereumEventParam[]) {
2657
+ this._inputValues = value;
2658
+ }
2659
+ get outputValues(): EthereumEventParam[] {
2660
+ return (this._outputValues ??= decodeCallValues(this._kind.outputs, this._raw.params.callOutput, false));
2661
+ }
2662
+ set outputValues(value: EthereumEventParam[]) {
2663
+ this._outputValues = value;
2664
+ }
2665
+ }
2666
+
2667
+ /** The call the runtime hands a handler, as the generated class the handler declares. */
2668
+ export function makeCall(raw: unknown, kind: CallKind, CallClass: Function): EthereumCall {
2669
+ return Reflect.construct(EthereumCall, [FROM_ENVIO_CALL, raw, kind], CallClass) as EthereumCall;
2670
+ }
2671
+
2672
+ /**
2673
+ * A call's inputs or outputs, which the ABI types and the bytes carry. An input
2674
+ * is the calldata past its 4-byte selector; an output is the return data.
2675
+ */
2676
+ function decodeCallValues(
2677
+ params: { name: string; type: string }[],
2678
+ data: string,
2679
+ isInput: boolean,
2680
+ ): EthereumEventParam[] {
2681
+ if (params.length === 0) return [];
2682
+ const bytes = hexToBytes(isInput ? data.slice(10) : data);
2683
+ const decoded = abiDecode(`(${params.map((param) => param.type).join(",")})`, bytes, true) as unknown[];
2684
+ return params.map(
2685
+ (param, index) => new EthereumEventParam(param.name, ethereumConverter(param.type)(decoded[index])),
2686
+ );
2687
+ }
2688
+
2689
+ /**
2690
+ * Whether a call's input decodes against its function — graph-node skips a call
2691
+ * whose calldata doesn't (it warns), and fails on an output that doesn't.
2692
+ */
2693
+ export function callInputDecodes(kind: CallKind, input: string): boolean {
2694
+ try {
2695
+ decodeCallValues(kind.inputs, input, true);
2696
+ return true;
2697
+ } catch {
2698
+ return false;
2699
+ }
2700
+ }
2701
+
2702
+ /** One of an event's parameters, as the translator read it off the ABI. */
2703
+ export type EventInput = { name: string; key: string; type: string; indexed?: boolean };
2704
+
2705
+ /** What every occurrence of one kind of event shares. */
2706
+ export type EventKind = {
2707
+ /** `data source "X" → "Event"`, for what a refusal names. */
2708
+ location: string;
2709
+ inputs: EventInput[];
2710
+ hasReceipt: boolean;
2711
+ };
2712
+
2713
+ /**
2714
+ * graph-ts' block, transaction and receipt fields, each paired with the name
2715
+ * envio decodes it under and the type it arrives as. The names diverge both
2716
+ * ways — graph-ts' `author` is envio's `miner`, a transaction's `gasLimit` is
2717
+ * envio's `gas` — so a mapping reading the graph-ts name would get `undefined`.
2718
+ */
2719
+ type Shape<T> = { [K in keyof T]-?: [rawName: string, kind: "bytes" | "address" | "bigint"] };
2720
+
2721
+ const BLOCK_SHAPE: Shape<EthereumBlock> = {
2722
+ hash: ["hash", "bytes"],
2723
+ parentHash: ["parentHash", "bytes"],
2724
+ unclesHash: ["sha3Uncles", "bytes"],
2725
+ author: ["miner", "address"],
2726
+ stateRoot: ["stateRoot", "bytes"],
2727
+ transactionsRoot: ["transactionsRoot", "bytes"],
2728
+ receiptsRoot: ["receiptsRoot", "bytes"],
2729
+ number: ["number", "bigint"],
2730
+ gasUsed: ["gasUsed", "bigint"],
2731
+ gasLimit: ["gasLimit", "bigint"],
2732
+ timestamp: ["timestamp", "bigint"],
2733
+ difficulty: ["difficulty", "bigint"],
2734
+ totalDifficulty: ["totalDifficulty", "bigint"],
2735
+ size: ["size", "bigint"],
2736
+ baseFeePerGas: ["baseFeePerGas", "bigint"],
2737
+ };
2738
+
2739
+ const TRANSACTION_SHAPE: Shape<EthereumTransaction> = {
2740
+ hash: ["hash", "bytes"],
2741
+ index: ["transactionIndex", "bigint"],
2742
+ from: ["from", "address"],
2743
+ to: ["to", "address"],
2744
+ value: ["value", "bigint"],
2745
+ gasLimit: ["gas", "bigint"],
2746
+ gasUsed: ["gas", "bigint"],
2747
+ gasPrice: ["gasPrice", "bigint"],
2748
+ input: ["input", "bytes"],
2749
+ nonce: ["nonce", "bigint"],
2750
+ };
2751
+
2752
+ /**
2753
+ * envio carries the receipt scalars on the transaction, and the block ones on
2754
+ * the block; the log list it doesn't carry at all.
2755
+ */
2756
+ const RECEIPT_SHAPE: Shape<Omit<EthereumTransactionReceipt, "blockHash" | "blockNumber" | "logs">> = {
2757
+ transactionHash: ["hash", "bytes"],
2758
+ transactionIndex: ["transactionIndex", "bigint"],
2759
+ cumulativeGasUsed: ["cumulativeGasUsed", "bigint"],
2760
+ gasUsed: ["gasUsed", "bigint"],
2761
+ contractAddress: ["contractAddress", "address"],
2762
+ status: ["status", "bigint"],
2763
+ root: ["root", "bytes"],
2764
+ logsBloom: ["logsBloom", "bytes"],
2765
+ };
2766
+
2767
+ /** A shaped object's raw payload and the fields read or assigned so far. */
2768
+ const SHAPED_FIELDS = Symbol("envio.shapedFields");
2769
+ type ShapedFields = { raw: Record<string, unknown> | undefined; values: Record<string, unknown> };
2770
+ const shapedPrototypes = new WeakSet<object>();
2771
+
2772
+ function shapedField(value: unknown, kind: "bytes" | "address" | "bigint"): unknown {
2773
+ return value === null || value === undefined
2774
+ ? null
2775
+ : kind === "bytes"
2776
+ ? Bytes.fromHexString(value as string)
2777
+ : kind === "address"
2778
+ ? Address.fromString(value as string)
2779
+ : new BigInt_(typeof value === "bigint" ? value : BigInt(value as number));
2780
+ }
2781
+
2782
+ /**
2783
+ * Each field of `shape` becomes an accessor on the class that converts the
2784
+ * raw value the first time it is read: a handler reads a few of a block's or
2785
+ * transaction's fields, and converting all of them — the roots, the
2786
+ * transaction's whole input as hex — for every event was a large share of
2787
+ * indexing CPU. An assignment, the constructor's included, holds its value.
2788
+ */
2789
+ function shapeFields(prototype: object, shape: object): void {
2790
+ shapedPrototypes.add(prototype);
2791
+ for (const [graphName, [rawName, kind]] of Object.entries(shape) as [string, [string, "bytes" | "address" | "bigint"]][]) {
2792
+ Object.defineProperty(prototype, graphName, {
2793
+ configurable: true,
2794
+ get(this: { [SHAPED_FIELDS]?: ShapedFields }) {
2795
+ const fields = this[SHAPED_FIELDS];
2796
+ if (fields === undefined) return undefined;
2797
+ const values = fields.values;
2798
+ if (graphName in values) return values[graphName];
2799
+ return (values[graphName] = shapedField(fields.raw?.[rawName], kind));
2800
+ },
2801
+ set(this: { [SHAPED_FIELDS]?: ShapedFields }, value: unknown) {
2802
+ (this[SHAPED_FIELDS] ??= { raw: undefined, values: {} }).values[graphName] = value;
2803
+ },
2804
+ });
2805
+ }
2806
+ }
2807
+
2808
+ function shaped<T extends object>(
2809
+ into: T,
2810
+ shape: Partial<Shape<T>>,
2811
+ raw: Record<string, unknown> | undefined,
2812
+ ): T {
2813
+ const prototype = Object.getPrototypeOf(into);
2814
+ if (!shapedPrototypes.has(prototype)) shapeFields(prototype, shape);
2815
+ (into as { [SHAPED_FIELDS]?: ShapedFields })[SHAPED_FIELDS] = { raw, values: {} };
2816
+ return into;
2817
+ }
2818
+
2819
+ type Converter<T> = (raw: unknown) => T;
2820
+
2821
+ /**
2822
+ * Converters are compiled once per ABI type string or signature: every event
2823
+ * parameter and call output goes through one on every handler run, and a
2824
+ * replayed handler runs again.
2825
+ */
2826
+ function memoised<V>(compile: (key: string) => V): (key: string) => V {
2827
+ const compiled = new Map<string, V>();
2828
+ return (key) => {
2829
+ let value = compiled.get(key);
2830
+ if (value === undefined) {
2831
+ value = compile(key);
2832
+ compiled.set(key, value);
2833
+ }
2834
+ return value;
2835
+ };
2836
+ }
2837
+
2838
+ const tupleComponents = (type: string) => splitTypes(type.slice(1, type.lastIndexOf(")")));
2839
+
2840
+ /**
2841
+ * envio's decoder hands a `bytesN` over as the whole 32-byte word it was
2842
+ * encoded in, topic or body; graph-node keeps its first N bytes
2843
+ * (runtime/wasm/src/to_from/external.rs).
2844
+ */
2845
+ function fixedBytes(type: string): Converter<Bytes> {
2846
+ const end = 2 + 2 * Number(type.slice("bytes".length));
2847
+ return (raw) => Bytes.fromHexString((raw as string).slice(0, end));
2848
+ }
2849
+
2850
+ /**
2851
+ * The ABI type an event parameter's value arrives as. An indexed `string`,
2852
+ * `bytes`, array or tuple is only its topic, the keccak hash, which graph-node
2853
+ * hands over as a 32-byte `FixedBytes` and graph codegen reads as `bytes32`.
2854
+ */
2855
+ function arrivesAs(input: EventInput): string {
2856
+ const { type } = input;
2857
+ const hashed = type === "string" || type === "bytes" || type.startsWith("(") || type.endsWith("]");
2858
+ return input.indexed && hashed ? "bytes32" : type;
2859
+ }
2860
+
2861
+ /**
2862
+ * A decoded ABI value as the `ethereum.Value` graph-node would hand the
2863
+ * mapping, typed by the ABI rather than by its JS shape: a `string` holding
2864
+ * `0x…` is still a string.
2865
+ */
2866
+ const ethereumConverter: (type: string) => Converter<EthereumValue> = memoised((type) => {
2867
+ if (type.endsWith("]")) {
2868
+ const element = ethereumConverter(type.slice(0, type.lastIndexOf("[")));
2869
+ const wrap = /\[\d+\]$/.test(type) ? EthereumValue.fromFixedSizedArray : EthereumValue.fromArray;
2870
+ return (raw) => wrap((raw as unknown[]).map((value) => element(value)));
2871
+ }
2872
+ if (type.startsWith("(")) {
2873
+ const components = tupleComponents(type).map(ethereumConverter);
2874
+ return (raw) => {
2875
+ // envio's decoder keys a tuple by its components' names when the ABI
2876
+ // gives them, in ABI order; the addon and unnamed ABIs give an array.
2877
+ const values = Array.isArray(raw) ? raw : Object.values(raw as object);
2878
+ return EthereumValue.fromTuple(components.map((component, index) => component(values[index])));
2879
+ };
2880
+ }
2881
+ if (type === "address") return (raw) => EthereumValue.fromAddress(Address.fromString(raw as string));
2882
+ if (type === "bool") return (raw) => EthereumValue.fromBoolean(raw as boolean);
2883
+ if (type === "string") return (raw) => EthereumValue.fromString(raw as string);
2884
+ if (type === "bytes") return (raw) => EthereumValue.fromBytes(Bytes.fromHexString(raw as string));
2885
+ if (type.startsWith("bytes")) {
2886
+ const bytes = fixedBytes(type);
2887
+ return (raw) => EthereumValue.fromFixedBytes(bytes(raw));
2888
+ }
2889
+ // envio's event decoder hands the narrow integer types over as numbers.
2890
+ const fromInt = type.startsWith("uint") ? EthereumValue.fromUnsignedBigInt : EthereumValue.fromSignedBigInt;
2891
+ return (raw) => fromInt(new BigInt_(BigInt(raw as bigint | number | string)));
2892
+ });
2893
+
2894
+ /** Anything that fits in 32 bits is an `i32` in a mapping; wider is a BigInt. */
2895
+ const SMALL_INT = /^u?int(8|16|24|32)$/;
2896
+
2897
+ /** A decoded ABI value as the graph-ts type `graph codegen` would give it. */
2898
+ const graphConverter: (type: string) => Converter<unknown> = memoised((type) => {
2899
+ const convert = graphValue(type);
2900
+ return (raw) => (raw === null || raw === undefined ? raw : convert(raw));
2901
+ });
2902
+
2903
+ function graphValue(type: string): Converter<unknown> {
2904
+ if (type.endsWith("]")) {
2905
+ const element = graphConverter(type.slice(0, type.lastIndexOf("[")));
2906
+ return (raw) => (raw as unknown[]).map((value) => element(value));
2907
+ }
2908
+ if (type.startsWith("(")) {
2909
+ const tuple = ethereumConverter(type);
2910
+ return (raw) => tuple(raw).toTuple();
2911
+ }
2912
+ if (type === "address") return (raw) => Address.fromString(raw as string);
2913
+ if (type === "bool" || type === "string") return (raw) => raw;
2914
+ if (type === "bytes") return (raw) => Bytes.fromHexString(raw as string);
2915
+ if (type.startsWith("bytes")) return fixedBytes(type);
2916
+ if (SMALL_INT.test(type)) return (raw) => Number(raw);
2917
+ return (raw) => new BigInt_(raw as bigint);
2918
+ }
2919
+
2920
+ /**
2921
+ * graph-ts' `ethereum.Event`, over one decoded envio event. A handler typed with
2922
+ * a generated event class receives an instance of that class — it extends this
2923
+ * one — so graph codegen's own `params` getters read `parameters` as written.
2924
+ *
2925
+ * Everything is deferred: a mapping that reads two parameters shouldn't pay to
2926
+ * convert the block, the transaction and every parameter — and envio runs each
2927
+ * handler twice over the same payload, so anything eager is paid twice.
2928
+ */
2929
+ const FROM_ENVIO = Symbol("envio.subgraph.event");
2930
+
2931
+ /** What a mapping-built event is, for anything that names where it came from. */
2932
+ const BUILT_EVENT: EventKind = { location: "an ethereum.Event the mapping built", inputs: [], hasReceipt: false };
2933
+
2934
+ /** The event the runtime hands a handler, as the class the handler declares. */
2935
+ export function makeEvent(raw: unknown, kind: EventKind, EventClass: Function): EthereumEvent {
2936
+ return Reflect.construct(EthereumEvent, [FROM_ENVIO, raw, kind], EventClass) as EthereumEvent;
2937
+ }
2938
+
2939
+ class EthereumEvent {
2940
+ private _raw: any = null;
2941
+ private _kind: EventKind = BUILT_EVENT;
2942
+ private _address: Address | undefined;
2943
+ private _logIndex: BigInt_ | undefined;
2944
+ private _transactionLogIndex: BigInt_ | undefined;
2945
+ private _logType: string | null = null;
2946
+ private _block: EthereumBlock | undefined;
2947
+ private _transaction: EthereumTransaction | undefined;
2948
+ private _receipt: EthereumTransactionReceipt | null | undefined;
2949
+ private _parameters: EthereumEventParam[] | undefined;
2950
+ private _params: Record<string, unknown> | undefined;
2951
+
2952
+ /**
2953
+ * graph-ts' own, for a mapping that builds an event — Notional synthesizes
2954
+ * one in a block handler. The runtime goes through `makeEvent` instead.
2955
+ */
2956
+ constructor(
2957
+ address: Address,
2958
+ logIndex: BigInt_,
2959
+ transactionLogIndex: BigInt_,
2960
+ logType: string | null,
2961
+ block: EthereumBlock,
2962
+ transaction: EthereumTransaction,
2963
+ parameters: EthereumEventParam[],
2964
+ receipt: EthereumTransactionReceipt | null,
2965
+ ) {
2966
+ if ((address as unknown) === FROM_ENVIO) {
2967
+ this._raw = logIndex;
2968
+ this._kind = transactionLogIndex as unknown as EventKind;
2969
+ return;
2970
+ }
2971
+ this._address = address;
2972
+ this._logIndex = logIndex;
2973
+ this._transactionLogIndex = transactionLogIndex;
2974
+ this._logType = logType;
2975
+ this._block = block;
2976
+ this._transaction = transaction;
2977
+ this._parameters = parameters ?? [];
2978
+ this._receipt = receipt ?? null;
2979
+ }
2980
+
2981
+ get address(): Address {
2982
+ return (this._address ??= Address.fromString(this._raw.srcAddress));
2983
+ }
2984
+ set address(value: Address) {
2985
+ this._address = value;
2986
+ }
2987
+ get logIndex(): BigInt_ {
2988
+ // graph-node builds it from the log's U256 index: minimal two's
2989
+ // complement bytes, not an i32's four.
2990
+ return (this._logIndex ??= new BigInt_(BigInt(this._raw.logIndex)));
2991
+ }
2992
+ set logIndex(value: BigInt_) {
2993
+ this._logIndex = value;
2994
+ }
2995
+ get block(): EthereumBlock {
2996
+ return (this._block ??= shaped(blank(EthereumBlock), BLOCK_SHAPE, this._raw.block));
2997
+ }
2998
+ set block(value: EthereumBlock) {
2999
+ this._block = value;
3000
+ }
3001
+ get transaction(): EthereumTransaction {
3002
+ return (this._transaction ??= shaped(blank(EthereumTransaction), TRANSACTION_SHAPE, this._raw.transaction));
3003
+ }
3004
+ set transaction(value: EthereumTransaction) {
3005
+ this._transaction = value;
3006
+ }
3007
+ get receipt(): EthereumTransactionReceipt | null {
3008
+ if (this._receipt !== undefined) return this._receipt;
3009
+ if (!this._kind.hasReceipt) return null;
3010
+ const receipt = shaped(blank(EthereumTransactionReceipt), RECEIPT_SHAPE, this._raw.transaction);
3011
+ receipt.blockHash = this.block.hash;
3012
+ receipt.blockNumber = this.block.number;
3013
+ // The transaction's other logs aren't part of envio's event: read from
3014
+ // HyperSync (or the RPC) when the mapping first asks.
3015
+ const blockHash = receipt.blockHash;
3016
+ const blockNumber = Number(this._raw.block.number);
3017
+ const transactionIndex = Number(this._raw.transaction?.transactionIndex ?? this._raw.transactionIndex);
3018
+ const transactionHash = this._raw.transaction?.hash ?? this._raw.transactionHash;
3019
+ let logs: EthereumLog[] | undefined;
3020
+ Object.defineProperty(receipt, "logs", {
3021
+ enumerable: true,
3022
+ configurable: true,
3023
+ get: () =>
3024
+ (logs ??= receiptLogsOf(
3025
+ hostsOrThrow().receiptLogs(JSON.stringify({ blockNumber, transactionIndex, transactionHash })),
3026
+ blockHash,
3027
+ blockNumber,
3028
+ )),
3029
+ set: (value: EthereumLog[]) => {
3030
+ logs = value;
3031
+ },
3032
+ });
3033
+ return (this._receipt = receipt);
3034
+ }
3035
+ set receipt(value: EthereumTransactionReceipt | null) {
3036
+ this._receipt = value;
3037
+ }
3038
+ // graph-node reads it off the log, where no Ethereum client sets it.
3039
+ get logType(): string | null {
3040
+ return this._logType;
3041
+ }
3042
+ set logType(value: string | null) {
3043
+ this._logType = value;
3044
+ }
3045
+ get parameters(): EthereumEventParam[] {
3046
+ return (this._parameters ??= this._kind.inputs.map(
3047
+ (input) =>
3048
+ EthereumEventParam.decoded(input.name, arrivesAs(input), this._raw.params?.[input.key]),
3049
+ ));
3050
+ }
3051
+ set parameters(value: EthereumEventParam[]) {
3052
+ this._parameters = value;
3053
+ this._params = undefined;
3054
+ }
3055
+ /**
3056
+ * By name, for a handler not typed with a generated class — a generated
3057
+ * class's own getter wins otherwise. Named as graph codegen names them, so
3058
+ * an unnamed parameter reads as `param{index}`.
3059
+ */
3060
+ get params(): Record<string, unknown> {
3061
+ if (this._params) return this._params;
3062
+ const params: Record<string, unknown> = {};
3063
+ if (this._raw === null) {
3064
+ for (const parameter of this.parameters) params[parameter.name] = parameter.value;
3065
+ return (this._params = params);
3066
+ }
3067
+ this._kind.inputs.forEach((input, index) => {
3068
+ params[input.name || `param${index}`] = graphConverter(arrivesAs(input))(this._raw.params?.[input.key]);
3069
+ });
3070
+ return (this._params = params);
3071
+ }
3072
+ // graph-node answers the log's index in its block here (trigger.rs,
3073
+ // `transaction_log_index`): Geth never set the per-transaction one, and
3074
+ // subgraphs read it only to tell logs apart.
3075
+ get transactionLogIndex(): BigInt_ {
3076
+ return (this._transactionLogIndex ??= this.logIndex);
3077
+ }
3078
+ set transactionLogIndex(value: BigInt_) {
3079
+ this._transactionLogIndex = value;
3080
+ }
3081
+ }
3082
+
3083
+ const ethereumImpl = {
3084
+ Value: EthereumValue,
3085
+ ValueKind: EthereumValueKind,
3086
+ SmartContract,
3087
+ SmartContractCall,
3088
+ CallResult,
3089
+ Block: EthereumBlock,
3090
+ Transaction: EthereumTransaction,
3091
+ TransactionReceipt: EthereumTransactionReceipt,
3092
+ Log: EthereumLog,
3093
+ Event: EthereumEvent,
3094
+ EventParam: EthereumEventParam,
3095
+ Call: EthereumCall,
3096
+ Tuple: EthereumTuple,
3097
+ call(call: SmartContractCall): EthereumValue[] | null {
3098
+ const contract = new SmartContract(call.contractName, call.contractAddress);
3099
+ const result = callContract(contract, call.functionSignature, call.functionParams, true);
3100
+ return (result as CallResult<EthereumValue[]>).reverted
3101
+ ? null
3102
+ : (result as CallResult<EthereumValue[]>).value;
3103
+ },
3104
+ // graph-node's `ethereum_decode`: alloy's `abi_decode` of the one type.
3105
+ decode(types: string, data: Bytes): EthereumValue | null {
3106
+ try {
3107
+ return ethereumConverter(types)(abiDecode(types, data, false));
3108
+ } catch {
3109
+ return null;
3110
+ }
3111
+ },
3112
+ // graph-node's `ethereum_decode_params` (apiVersion 0.0.10): alloy's
3113
+ // `abi_decode_params`, which reads a top-level tuple's fields as a sequence
3114
+ // of parameters, as calldata holds them, with no offset word before them.
3115
+ decodeParams(types: string, data: Bytes): EthereumValue | null {
3116
+ try {
3117
+ return ethereumConverter(types)(abiDecode(types, data, true));
3118
+ } catch {
3119
+ return null;
3120
+ }
3121
+ },
3122
+ // graph-node's `DynSolValue::abi_encode` (`ethabi::encode(&[token])` before
3123
+ // alloy), which is Solidity's `abi.encode` of the one value. A token carries
3124
+ // no integer width and every integer takes a full word.
3125
+ encode(value: EthereumValue): Bytes | null {
3126
+ try {
3127
+ return new Bytes(encodedSequence([value]));
3128
+ } catch {
3129
+ return null;
3130
+ }
3131
+ },
3132
+ getBalance(address: Address): BigInt_ {
3133
+ return BigInt_.fromString(hostsOrThrow().getBalance(address.toHexString()));
3134
+ },
3135
+ hasCode(address: Address): Wrapped<boolean> {
3136
+ return new Wrapped(hostsOrThrow().hasCode(address.toHexString()));
3137
+ },
3138
+ };
3139
+
3140
+ export const ethereum = strictNamespace("ethereum", ethereumImpl);
3141
+
3142
+ // graph-ts before 0.18 (apiVersion 0.0.3 and older) exported these at the top
3143
+ // level, and the mappings and generated code of that age import them so.
3144
+ export {
3145
+ EthereumValueKind,
3146
+ EthereumTuple,
3147
+ EthereumBlock,
3148
+ EthereumTransaction,
3149
+ EthereumCall,
3150
+ EthereumEvent,
3151
+ EthereumEventParam,
3152
+ SmartContract,
3153
+ };
3154
+
3155
+ /**
3156
+ * `matchstick-as`' test helper, which also resolves to this module (a mapping
3157
+ * file that holds test fixtures imports it beside its handlers — Forta's do).
3158
+ * Same defaults as matchstick-as/assembly/defaults.
3159
+ */
3160
+ export function newMockEvent(): EthereumEvent {
3161
+ const address = Address.fromString("0xA16081F360e3847006dB660bae1c6d1b2e17eC2A");
3162
+ const hash = Bytes.fromHexString("0xA16081F360e3847006dB660bae1c6d1b2e17eC2A");
3163
+ const one = BigInt_.fromI32(1);
3164
+ const ten = BigInt_.fromI32(10);
3165
+ const block = new EthereumBlock(hash, hash, hash, address, hash, hash, hash, one, ten, ten, one, one, one, one, null);
3166
+ const transaction = new EthereumTransaction(hash, one, address, address, one, one, one, hash, one);
3167
+ return new EthereumEvent(address, one, one, "mock_log_type", block, transaction, [], null);
3168
+ }
3169
+
3170
+ // ---------------------------------------------------------------------------
3171
+ // dataSource + templates
3172
+ // ---------------------------------------------------------------------------
3173
+
3174
+ let registerHook: ((templateName: string, address: string) => void) | null = null;
3175
+ let handlerCreateHook: ((templateName: string, address: string) => void) | null = null;
3176
+
3177
+ export function installRegisterHook(hook: typeof registerHook) {
3178
+ registerHook = hook;
3179
+ }
3180
+
3181
+ /** Sees each create() a handler (not a register pass) makes. */
3182
+ export function installHandlerCreateHook(hook: typeof handlerCreateHook) {
3183
+ handlerCreateHook = hook;
3184
+ }
3185
+
3186
+ /** The manifest's file templates: `create` queues these instead of registering an address. */
3187
+ let fileTemplates = new Map<string, "ipfs" | "arweave">();
3188
+
3189
+ export function installFileTemplates(templates: Map<string, "ipfs" | "arweave">) {
3190
+ fileTemplates = templates;
3191
+ }
3192
+
3193
+ /** A context, as a string that two equal contexts share (graph-node dedupes on it). */
3194
+ function contextKey(context: DataSourceContext | undefined): string {
3195
+ if (!context || context.entries.length === 0) return "";
3196
+ const entries = context.entries.map((entry) => [entry.key, entry.value.kind, String(entry.value.data)]);
3197
+ return JSON.stringify(entries.sort((a, b) => (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0)));
3198
+ }
3199
+
3200
+ /** Two creations of the same file with the same context are one data source. */
3201
+ export function fileCreateId(create: { template: string; source: string; context?: unknown }): string {
3202
+ const key = contextKey(create.context as DataSourceContext | undefined);
3203
+ return key === "" ? `${create.template}:${create.source}` : `${create.template}:${create.source}:${key}`;
3204
+ }
3205
+
3206
+ function createFile(name: string, params: string[], context?: DataSourceContext): void {
3207
+ const scope = currentScope();
3208
+ const source = params[0];
3209
+ if (!source) {
3210
+ throw new Error(`${name}.create() was called without a file parameter`);
3211
+ }
3212
+ // Only the handler pass runs a file's handler; the register pass exists to
3213
+ // collect contract addresses.
3214
+ if (scope.mode === "register") return;
3215
+ const create = { template: name, source, context };
3216
+ const key = fileCreateId(create);
3217
+ scope.fileCreates ??= new Map();
3218
+ if (!scope.fileCreates.has(key)) scope.fileCreates.set(key, create);
3219
+ }
3220
+
3221
+ export class DataSourceTemplate {
3222
+ static create(name: string, params: string[]): void {
3223
+ if (fileTemplates.has(name)) return createFile(name, params);
3224
+ const { mode, registered } = currentScope();
3225
+ const address = params[0];
3226
+ if (!address) {
3227
+ throw new Error(`${name}.create() was called without an address parameter`);
3228
+ }
3229
+ const key = `${name}:${address.toLowerCase()}`;
3230
+ if (mode !== "register") return handlerCreateHook?.(name, address);
3231
+ // Replays rerun the mapping from the top, so registration is deduped
3232
+ // rather than repeated.
3233
+ if (registered.has(key)) return;
3234
+ registered.add(key);
3235
+ registerHook?.(name, address);
3236
+ }
3237
+ static createWithContext(name: string, params: string[], context: DataSourceContext): void {
3238
+ if (fileTemplates.has(name)) return createFile(name, params, context);
3239
+ const { mode, dataSource } = currentScope();
3240
+ if (mode === "register" && entriesOf(context).length > 0 && params[0]) {
3241
+ rememberContext(dataSource.chainId, name, params[0], contextEntries(name, context));
3242
+ }
3243
+ DataSourceTemplate.create(name, params);
3244
+ }
3245
+ }
3246
+
3247
+ /**
3248
+ * The context a template's data source was created with, which its handlers
3249
+ * read through `dataSource.context()`. The created address is all envio keeps
3250
+ * of a data source, so the context is kept here, by chain, template and
3251
+ * address — and in the project's `.envio` directory, so a restart that loads
3252
+ * the addresses from storage finds it again.
3253
+ */
3254
+ type ContextEntries = Record<string, { type: string; data: string }>;
3255
+ const dynamicContexts = new Map<string, ContextEntries>();
3256
+ let contextsLoaded = false;
3257
+
3258
+ function contextFile(): string {
3259
+ return path.join(process.cwd(), ".envio", "subgraph-data-source-contexts.jsonl");
3260
+ }
3261
+
3262
+ function dynamicContextKey(chainId: number, template: string, address: string): string {
3263
+ return `${chainId}:${template}:${address.toLowerCase()}`;
3264
+ }
3265
+
3266
+ function loadContexts() {
3267
+ if (contextsLoaded) return;
3268
+ contextsLoaded = true;
3269
+ try {
3270
+ for (const line of readFileSync(contextFile(), "utf8").split("\n")) {
3271
+ if (!line) continue;
3272
+ const { key, entries } = JSON.parse(line);
3273
+ dynamicContexts.set(key, entries);
3274
+ }
3275
+ } catch {
3276
+ // none saved yet
3277
+ }
3278
+ }
3279
+
3280
+ function rememberContext(chainId: number, template: string, address: string, entries: ContextEntries) {
3281
+ loadContexts();
3282
+ const key = dynamicContextKey(chainId, template, address);
3283
+ if (JSON.stringify(dynamicContexts.get(key)) === JSON.stringify(entries)) return;
3284
+ dynamicContexts.set(key, entries);
3285
+ try {
3286
+ mkdirSync(path.dirname(contextFile()), { recursive: true });
3287
+ appendFileSync(contextFile(), JSON.stringify({ key, entries }) + "\n");
3288
+ } catch {
3289
+ // in memory still serves this run
3290
+ }
3291
+ }
3292
+
3293
+ /** The context `createWithContext` was given, for the handler pass of that data source. */
3294
+ export function contextOf(chainId: number, template: string, address: string): ContextEntries | undefined {
3295
+ loadContexts();
3296
+ return dynamicContexts.get(dynamicContextKey(chainId, template, address));
3297
+ }
3298
+
3299
+ /** The manifest's own typed entries (`contextValue`) run the other way. */
3300
+ function contextEntries(template: string, context: DataSourceContext): ContextEntries {
3301
+ const out: ContextEntries = {};
3302
+ for (const entry of context.entries) {
3303
+ const value = entry.value;
3304
+ const key = entry.key;
3305
+ switch (value.kind) {
3306
+ case ValueKind.STRING:
3307
+ out[key] = { type: "String", data: value.toString() };
3308
+ break;
3309
+ case ValueKind.INT:
3310
+ out[key] = { type: "Int", data: String(value.toI32()) };
3311
+ break;
3312
+ case ValueKind.INT8:
3313
+ out[key] = { type: "Int8", data: String(value.data) };
3314
+ break;
3315
+ case ValueKind.BOOL:
3316
+ out[key] = { type: "Bool", data: String(value.toBoolean()) };
3317
+ break;
3318
+ case ValueKind.BIGINT:
3319
+ out[key] = { type: "BigInt", data: value.toBigInt().toString() };
3320
+ break;
3321
+ case ValueKind.BIGDECIMAL:
3322
+ out[key] = { type: "BigDecimal", data: value.toBigDecimal().toString() };
3323
+ break;
3324
+ case ValueKind.BYTES:
3325
+ out[key] = { type: "Bytes", data: value.toBytes().toHexString() };
3326
+ break;
3327
+ default:
3328
+ throw unsupported(
3329
+ `${template}.createWithContext() with a ${ValueKind[value.kind]} value on "${key}"`,
3330
+ "a mapping handler",
3331
+ );
3332
+ }
3333
+ }
3334
+ return out;
3335
+ }
3336
+
3337
+ export class DataSourceContext extends Entity {}
3338
+
3339
+ /** A manifest `context` entry, typed the way graph-node types it. */
3340
+ function contextValue(kind: string, data: string, key: string): Value {
3341
+ switch (kind) {
3342
+ case "Bool":
3343
+ case "Boolean":
3344
+ return Value.fromBoolean(data === "true");
3345
+ case "String":
3346
+ return Value.fromString(data);
3347
+ case "Int":
3348
+ return Value.fromI32(Number(data));
3349
+ case "Int8":
3350
+ return Value.fromI64(BigInt(data));
3351
+ case "BigInt":
3352
+ return Value.fromBigInt(BigInt_.fromString(data));
3353
+ case "BigDecimal":
3354
+ return Value.fromBigDecimal(BigDecimal.fromString(data));
3355
+ case "Bytes":
3356
+ return Value.fromBytes(Bytes.fromHexString(data));
3357
+ default:
3358
+ throw unknown(`the context value type ${kind} on "${key}"`, "a mapping handler");
3359
+ }
3360
+ }
3361
+
3362
+ const dataSourceImpl = {
3363
+ // graph-node's DataSourceDetails holds `ds.address().unwrap_or_default()`:
3364
+ // nothing for a data source without `source.address`, and a file data
3365
+ // source's source text (offchain.rs `Source::address`).
3366
+ address(): Address {
3367
+ const scope = currentScope();
3368
+ if (scope.file) return new Address(ByteArray.fromUTF8(scope.dataSource.address));
3369
+ if (scope.dataSource.wildcard) return new Address(new Uint8Array(0));
3370
+ return Address.fromString(scope.dataSource.address);
3371
+ },
3372
+ network(): string {
3373
+ return currentScope().dataSource.network;
3374
+ },
3375
+ context(): DataSourceContext {
3376
+ const context = new DataSourceContext();
3377
+ const created = currentScope().file?.context as DataSourceContext | undefined;
3378
+ if (created) {
3379
+ for (const entry of created.entries) context.set(entry.key, entry.value);
3380
+ }
3381
+ for (const [key, entry] of Object.entries(currentScope().dataSource.context ?? {})) {
3382
+ context.set(key, contextValue(entry.type, entry.data, key));
3383
+ }
3384
+ return context;
3385
+ },
3386
+ // The address a template was created with, which is the only string param
3387
+ // an EVM template ever carries.
3388
+ stringParam(): string {
3389
+ return currentScope().dataSource.address;
3390
+ },
3391
+ create: DataSourceTemplate.create,
3392
+ createWithContext: DataSourceTemplate.createWithContext,
3393
+ };
3394
+
3395
+ export const dataSource = strictNamespace("dataSource", dataSourceImpl);
3396
+
3397
+ // ---------------------------------------------------------------------------
3398
+ // log / crypto / json
3399
+ // ---------------------------------------------------------------------------
3400
+
3401
+ function interpolate(message: string, args: string[]): string {
3402
+ let index = 0;
3403
+ return message.replace(/\{\}/g, () => args[index++] ?? "{}");
3404
+ }
3405
+
3406
+ // The register pass runs the mapping against a store that reads null, so what
3407
+ // it would log describes a world that doesn't exist; the handler pass logs it
3408
+ // for real.
3409
+ function userLog(level: "debug" | "info" | "warn" | "error", message: string, args: string[]) {
3410
+ const scope = currentScope();
3411
+ if (scope.mode === "register") return;
3412
+ const line = interpolate(message, args);
3413
+ // A preload run is silent: its run proper logs.
3414
+ if (!scope.context.isPreload) notifyLog(`u${level}`, line);
3415
+ scope.context.log?.[level](line);
3416
+ }
3417
+
3418
+ const logImpl = {
3419
+ Level: LogLevel,
3420
+ log(level: LogLevel, msg: string) {
3421
+ switch (level) {
3422
+ case LogLevel.CRITICAL:
3423
+ return logImpl.critical(msg);
3424
+ case LogLevel.ERROR:
3425
+ return logImpl.error(msg);
3426
+ case LogLevel.WARNING:
3427
+ return logImpl.warning(msg);
3428
+ case LogLevel.DEBUG:
3429
+ return logImpl.debug(msg);
3430
+ default:
3431
+ return logImpl.info(msg);
3432
+ }
3433
+ },
3434
+ debug(message: string, args: string[] = []) {
3435
+ userLog("debug", message, args);
3436
+ },
3437
+ info(message: string, args: string[] = []) {
3438
+ userLog("info", message, args);
3439
+ },
3440
+ warning(message: string, args: string[] = []) {
3441
+ userLog("warn", message, args);
3442
+ },
3443
+ error(message: string, args: string[] = []) {
3444
+ userLog("error", message, args);
3445
+ },
3446
+ // graph-node halts the subgraph on critical.
3447
+ critical(message: string, args: string[] = []): never {
3448
+ throw new Error(interpolate(message, args));
3449
+ },
3450
+ };
3451
+
3452
+ export const log = strictNamespace("log", logImpl);
3453
+
3454
+ const cryptoImpl = {
3455
+ keccak256(input: ByteArray): ByteArray {
3456
+ return new ByteArray(subgraphKeccak256(input));
3457
+ },
3458
+ };
3459
+
3460
+ export const crypto = strictNamespace("crypto", cryptoImpl);
3461
+
3462
+ const ethereumUtilsImpl = {
3463
+ getCreate2Address(from: Bytes, salt: Bytes, initCodeHash: Bytes): Bytes {
3464
+ const preimage =
3465
+ "0xff" +
3466
+ from.toHexString().slice(2) +
3467
+ salt.toHexString().slice(2) +
3468
+ initCodeHash.toHexString().slice(2);
3469
+ return Bytes.fromHexString(
3470
+ toHex(subgraphKeccak256(hexToBytes(preimage))).slice(26),
3471
+ );
3472
+ },
3473
+ };
3474
+
3475
+ export const EthereumUtils = strictNamespace("EthereumUtils", ethereumUtilsImpl);
3476
+
3477
+ export enum JSONValueKind {
3478
+ NULL = 0,
3479
+ BOOL = 1,
3480
+ NUMBER = 2,
3481
+ STRING = 3,
3482
+ ARRAY = 4,
3483
+ OBJECT = 5,
3484
+ }
3485
+
3486
+ // graph-node's serde_json is built with `arbitrary_precision` and without
3487
+ // `preserve_order` (its Cargo.toml; no indexmap in Cargo.lock): a number's
3488
+ // payload is its literal text (`to_from/external.rs`), and an object is a
3489
+ // BTreeMap, so its entries come in key order.
3490
+ class JsonNumberText {
3491
+ constructor(readonly text: string) {}
3492
+ }
3493
+
3494
+ const keepNumberText = (_key: string, value: unknown, context?: { source?: string }) =>
3495
+ typeof value === "number" ? new JsonNumberText(context?.source ?? String(value)) : value;
3496
+
3497
+ /** Rust's `String` order: UTF-8 bytes, which is code point order. */
3498
+ function codePointOrder(a: string, b: string): number {
3499
+ const length = Math.min(a.length, b.length);
3500
+ for (let index = 0; index < length; index++) {
3501
+ const x = a.codePointAt(index)!;
3502
+ const y = b.codePointAt(index)!;
3503
+ if (x !== y) return x - y;
3504
+ if (x > 0xffff) index++;
3505
+ }
3506
+ return a.length - b.length;
3507
+ }
3508
+
3509
+ const I64_MIN = -(2n ** 63n);
3510
+ const I64_MAX = 2n ** 63n - 1n;
3511
+ const U64_MAX = 2n ** 64n - 1n;
3512
+
3513
+ /** Rust's `from_str` for an integer type: decimal digits only, in range. */
3514
+ function parseInteger(decimal: string, type: string, min: bigint, max: bigint): bigint {
3515
+ const parsed = /^[+-]?\d+$/.test(decimal) ? BigInt(decimal) : null;
3516
+ if (parsed === null || parsed < min || parsed > max) {
3517
+ throw new Error(`JSON \`${decimal}\` cannot be parsed as ${type}`);
3518
+ }
3519
+ return parsed;
3520
+ }
3521
+
3522
+ export class JSONValue {
3523
+ constructor(
3524
+ public kind: JSONValueKind,
3525
+ public data: any,
3526
+ ) {}
3527
+ toString(): string {
3528
+ return String(this.data);
3529
+ }
3530
+ toBool(): boolean {
3531
+ return Boolean(this.data);
3532
+ }
3533
+ isNull(): boolean {
3534
+ return this.kind === JSONValueKind.NULL;
3535
+ }
3536
+ toU64(): bigint {
3537
+ return jsonImpl.toU64(String(this.data));
3538
+ }
3539
+ toI64(): bigint {
3540
+ return jsonImpl.toI64(String(this.data));
3541
+ }
3542
+ toF64(): number {
3543
+ return jsonImpl.toF64(String(this.data));
3544
+ }
3545
+ toBigInt(): BigInt_ {
3546
+ return jsonImpl.toBigInt(String(this.data));
3547
+ }
3548
+ toArray(): JSONValue[] {
3549
+ return (this.data as unknown[]).map(fromJson);
3550
+ }
3551
+ toObject(): TypedMap<string, JSONValue> {
3552
+ const map = new TypedMap<string, JSONValue>();
3553
+ const keys = Object.keys(this.data as object).sort(codePointOrder);
3554
+ map.entries = keys.map((key) => new TypedMapEntry(key, fromJson((this.data as any)[key])));
3555
+ return map;
3556
+ }
3557
+ }
3558
+
3559
+ function fromJson(value: unknown): JSONValue {
3560
+ if (value === null) return new JSONValue(JSONValueKind.NULL, null);
3561
+ if (typeof value === "boolean") return new JSONValue(JSONValueKind.BOOL, value);
3562
+ if (value instanceof JsonNumberText) return new JSONValue(JSONValueKind.NUMBER, value.text);
3563
+ if (typeof value === "number") return new JSONValue(JSONValueKind.NUMBER, String(value));
3564
+ if (typeof value === "string") return new JSONValue(JSONValueKind.STRING, value);
3565
+ if (Array.isArray(value)) return new JSONValue(JSONValueKind.ARRAY, value);
3566
+ return new JSONValue(JSONValueKind.OBJECT, value);
3567
+ }
3568
+
3569
+ // serde_json's `from_slice` refuses invalid UTF-8 and a byte order mark.
3570
+ const strictUtf8 = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true });
3571
+
3572
+ const jsonImpl = {
3573
+ fromBytes(bytes: Bytes): JSONValue {
3574
+ return jsonImpl.fromString(strictUtf8.decode(bytes));
3575
+ },
3576
+ fromString(input: string): JSONValue {
3577
+ return fromJson(JSON.parse(input, keepNumberText));
3578
+ },
3579
+ toI64(decimal: string): bigint {
3580
+ return parseInteger(decimal, "i64", I64_MIN, I64_MAX);
3581
+ },
3582
+ toU64(decimal: string): bigint {
3583
+ return parseInteger(decimal, "u64", 0n, U64_MAX);
3584
+ },
3585
+ toF64(decimal: string): number {
3586
+ return Number(decimal);
3587
+ },
3588
+ toBigInt(decimal: string): BigInt_ {
3589
+ if (!/^[+-]?\d+$/.test(decimal)) throw new Error(`JSON \`${decimal}\` is not a decimal string`);
3590
+ return BigInt_.fromString(decimal);
3591
+ },
3592
+ try_fromString(input: string): Result<JSONValue, boolean> {
3593
+ return tryParse(() => jsonImpl.fromString(input));
3594
+ },
3595
+ try_fromBytes(bytes: Bytes): Result<JSONValue, boolean> {
3596
+ return tryParse(() => jsonImpl.fromBytes(bytes));
3597
+ },
3598
+ };
3599
+
3600
+ export const json = strictNamespace("json", jsonImpl);
3601
+
3602
+ /**
3603
+ * The host ops that reach outside the chain: each returns synchronously here
3604
+ * and suspends underneath, so the mapping keeps graph-ts' shape.
3605
+ */
3606
+ export type Hosts = {
3607
+ ipfsCat: (hash: string) => string | null;
3608
+ ipfsMap: (hash: string, callback: string, userData: Value, flags: string[]) => void;
3609
+ arweaveData: (txId: string) => string | null;
3610
+ ensName: (hash: string) => string | null;
3611
+ getBalance: (address: string) => string;
3612
+ hasCode: (address: string) => boolean;
3613
+ blockTimestamp: (blockNumber: number) => string;
3614
+ /** The block's header as JSON, fields named as envio decodes them. */
3615
+ blockHeader: (blockNumber: number) => string;
3616
+ /** The logs of one transaction as JSON, given `{ blockNumber, transactionIndex, transactionHash }`. */
3617
+ receiptLogs: (transaction: string) => string;
3618
+ };
3619
+
3620
+ let hosts: Hosts | null = null;
3621
+
3622
+ export function installHosts(installed: Hosts) {
3623
+ hosts = installed;
3624
+ }
3625
+
3626
+ function hostsOrThrow(): Hosts {
3627
+ if (!hosts) {
3628
+ throw new Error("Envio Subgraph host ops were used before the runtime installed them.");
3629
+ }
3630
+ return hosts;
3631
+ }
3632
+
3633
+ export function decodeBase64(encoded: string | null): Bytes | null {
3634
+ return encoded === null ? null : new Bytes(Buffer.from(encoded, "base64"));
3635
+ }
3636
+
3637
+ const ipfsImpl = {
3638
+ cat(hash: string): Bytes | null {
3639
+ return decodeBase64(hostsOrThrow().ipfsCat(hash));
3640
+ },
3641
+ map(hash: string, callback: string, userData: Value, flags: string[]): void {
3642
+ hostsOrThrow().ipfsMap(hash, callback, userData, flags);
3643
+ },
3644
+ mapJSON(hash: string, callback: string, userData: Value): void {
3645
+ hostsOrThrow().ipfsMap(hash, callback, userData, ["json"]);
3646
+ },
3647
+ };
3648
+
3649
+ export const ipfs = strictNamespace("ipfs", ipfsImpl);
3650
+
3651
+ const arweaveImpl = {
3652
+ transactionData(txId: string): Bytes | null {
3653
+ return decodeBase64(hostsOrThrow().arweaveData(txId));
3654
+ },
3655
+ };
3656
+
3657
+ export const arweave = strictNamespace("arweave", arweaveImpl);
3658
+
3659
+ const ensImpl = {
3660
+ nameByHash(hash: string): string | null {
3661
+ return hostsOrThrow().ensName(hash);
3662
+ },
3663
+ };
3664
+
3665
+ export const ens = strictNamespace("ens", ensImpl);
3666
+
3667
+ /** The whole block a call item carries, for a `filter: call` block handler. */
3668
+ export function makeCallBlock(raw: unknown): EthereumBlock {
3669
+ return shaped(blank(EthereumBlock), BLOCK_SHAPE, raw as Record<string, unknown>);
3670
+ }
3671
+
3672
+ /**
3673
+ * graph-ts hands a block handler an `ethereum.Block`. A data source's handler
3674
+ * selects the fields its mapping reads, so `selected` already carries them;
3675
+ * any other field is fetched when the mapping first reads it: `timestamp` on
3676
+ * its own, narrowly; any other as the whole header, once. A header fetched
3677
+ * that way is only served from below the chain's reorg window (blocks.ts).
3678
+ */
3679
+ export function makeBlockHandlerBlock(
3680
+ blockNumber: number,
3681
+ selected?: Record<string, unknown>,
3682
+ ): EthereumBlock {
3683
+ // A simulated block carries no fields: they are read as for a template.
3684
+ if (selected?.hash != null) return shaped(blank(EthereumBlock), BLOCK_SHAPE, selected);
3685
+ return new LazyBlock(blockNumber, selected?.timestamp);
3686
+ }
3687
+
3688
+ // The getters live on the prototype: a template's handler builds one of these
3689
+ // per block, and defining them on each instance cost more than the handler did.
3690
+ class LazyBlock extends EthereumBlock {
3691
+ #blockNumber: number;
3692
+ #timestamp: unknown;
3693
+ #header: EthereumBlock | undefined;
3694
+
3695
+ constructor(blockNumber: number, timestamp: unknown) {
3696
+ // No arguments skips the constructor's fields, as `blank` does.
3697
+ super(...([] as unknown as ConstructorParameters<typeof EthereumBlock>));
3698
+ this.#blockNumber = blockNumber;
3699
+ this.#timestamp = timestamp;
3700
+ // graph-node builds it from the header's u64: minimal two's complement
3701
+ // bytes, as an event's block number is.
3702
+ this.number = new BigInt_(BigInt(blockNumber));
3703
+ }
3704
+
3705
+ #fetched(): EthereumBlock {
3706
+ return (this.#header ??= shaped(
3707
+ blank(EthereumBlock),
3708
+ BLOCK_SHAPE,
3709
+ JSON.parse(hostsOrThrow().blockHeader(this.#blockNumber)),
3710
+ ));
3711
+ }
3712
+
3713
+ static {
3714
+ Object.defineProperty(this.prototype, "timestamp", {
3715
+ configurable: true,
3716
+ // A header fetched for another field already carries it.
3717
+ get(this: LazyBlock) {
3718
+ return this.#timestamp != null
3719
+ ? new BigInt_(BigInt(this.#timestamp as number))
3720
+ : this.#header
3721
+ ? this.#header.timestamp
3722
+ : BigInt_.fromString(hostsOrThrow().blockTimestamp(this.#blockNumber));
3723
+ },
3724
+ });
3725
+ for (const field of Object.keys(BLOCK_SHAPE) as (keyof EthereumBlock)[]) {
3726
+ if (field === "number" || field === "timestamp") continue;
3727
+ Object.defineProperty(this.prototype, field, {
3728
+ configurable: true,
3729
+ get(this: LazyBlock) {
3730
+ return this.#fetched()[field];
3731
+ },
3732
+ });
3733
+ }
3734
+ }
3735
+ }
3736
+
3737
+ /**
3738
+ * AssemblyScript reinterprets the pointer and the layouts match, so nothing
3739
+ * happens at runtime — which leaves the value with whatever prototype it was
3740
+ * built with. A helper that returns `changetype<ByteArray>(new Uint8Array(n))`
3741
+ * then reaches the mapping without any of ByteArray's methods; ENS's
3742
+ * `byteArrayFromHex` is written that way, and so is every subgraph that copied
3743
+ * it. A plain Uint8Array can only have been meant as one of graph-ts' byte
3744
+ * types, so it is retagged as the most derived one — `Bytes` adds no instance
3745
+ * members over `ByteArray`, so this satisfies both spellings.
3746
+ */
3747
+ export function changetype<T>(value: unknown): T {
3748
+ if (value instanceof Uint8Array && Object.getPrototypeOf(value) === Uint8Array.prototype) {
3749
+ Object.setPrototypeOf(value, Bytes.prototype);
3750
+ }
3751
+ return value as T;
3752
+ }
3753
+
3754
+ // `changetype<GeneratedTuple>(result[0].toTuple())` is a pointer reinterpret
3755
+ // in AssemblyScript; in JS the named getters live on the generated class.
3756
+ // The load hook rewrites that call to this helper so `.value` etc. resolve.
3757
+ export function retagChangetype(ctor: unknown, value: unknown, keepInstances = false): unknown {
3758
+ if (value !== null && typeof value === "object" && typeof ctor === "function") {
3759
+ if (keepInstances) {
3760
+ // An `as` cast to a class the value already is, or extends, is an
3761
+ // upcast: the value keeps the getters of its own class.
3762
+ if (value instanceof (ctor as Function)) return value;
3763
+ // Across to a sibling — GMX casts one EventLog1 to EventLog2 for a
3764
+ // helper — AssemblyScript reads the same object through the other
3765
+ // class and leaves the original as it was, so the original isn't
3766
+ // retagged: the cast reads a copy.
3767
+ const current = Object.getPrototypeOf(value)?.constructor;
3768
+ if (typeof current === "function" && !((ctor as Function).prototype instanceof current)) {
3769
+ return Object.defineProperties(
3770
+ Object.create((ctor as Function).prototype),
3771
+ Object.getOwnPropertyDescriptors(value),
3772
+ );
3773
+ }
3774
+ }
3775
+ const proto = (ctor as Function).prototype;
3776
+ if (proto) Object.setPrototypeOf(value, proto);
3777
+ }
3778
+ return changetype(value);
3779
+ }
3780
+
3781
+ /**
3782
+ * `x.toTupleArray<Struct>()`: each element is a `Struct` in AssemblyScript, and
3783
+ * graph codegen's getters live on that class. A matrix nests the arrays.
3784
+ */
3785
+ export function retagEach(ctor: unknown, values: unknown): unknown {
3786
+ if (!Array.isArray(values)) return values;
3787
+ for (const value of values) {
3788
+ if (value instanceof EthereumTuple) retagChangetype(ctor, value, true);
3789
+ else if (Array.isArray(value)) retagEach(ctor, value);
3790
+ }
3791
+ return values;
3792
+ }
3793
+
3794
+ /**
3795
+ * AssemblyScript's primitives are namespaces as well as types: `i32.MAX_VALUE`
3796
+ * reads a bound, `i32(x)` truncates to one. Both are ordinary source in a
3797
+ * mapping and neither exists in JavaScript.
3798
+ *
3799
+ * The 64-bit pair carries values a double can't hold, so it works in bigints;
3800
+ * everything else stays a number, which is what the rest of the shim converts.
3801
+ */
3802
+ function integerNamespace(name: string, bits: number, signed: boolean) {
3803
+ const min = signed ? -(2 ** (bits - 1)) : 0;
3804
+ const max = signed ? 2 ** (bits - 1) - 1 : 2 ** bits - 1;
3805
+ const cast = (value: number) => {
3806
+ const truncated = Math.trunc(Number(value)) || 0;
3807
+ const span = 2 ** bits;
3808
+ const wrapped = ((truncated % span) + span) % span;
3809
+ return wrapped > max ? wrapped - span : wrapped;
3810
+ };
3811
+ return strictNamespace(
3812
+ name,
3813
+ Object.assign(cast, { MIN_VALUE: min, MAX_VALUE: max, parseInt: (text: string, radix = 0) => cast(parseAsInteger(text, radix) as number) }),
3814
+ );
3815
+ }
3816
+
3817
+ /** AssemblyScript's `I32.parseInt(text, radix)`: radix 0 reads a 0x/0b/0o prefix; garbage is 0. */
3818
+ function parseAsInteger(text: string, radix: number): number {
3819
+ let rest = String(text).trim();
3820
+ let sign = 1;
3821
+ if (rest.startsWith("-")) {
3822
+ sign = -1;
3823
+ rest = rest.slice(1);
3824
+ } else if (rest.startsWith("+")) rest = rest.slice(1);
3825
+ let base = radix;
3826
+ const prefixed = /^0([xXbBoO])/.exec(rest);
3827
+ if (prefixed && (base === 0 || base === { x: 16, b: 2, o: 8 }[prefixed[1].toLowerCase() as "x" | "b" | "o"])) {
3828
+ base = { x: 16, b: 2, o: 8 }[prefixed[1].toLowerCase() as "x" | "b" | "o"];
3829
+ rest = rest.slice(2);
3830
+ }
3831
+ if (base === 0) base = 10;
3832
+ const parsed = Number.parseInt(rest, base);
3833
+ return Number.isNaN(parsed) ? 0 : sign * parsed;
3834
+ }
3835
+
3836
+ function bigIntegerNamespace(name: string, signed: boolean) {
3837
+ const min = signed ? -(2n ** 63n) : 0n;
3838
+ const max = signed ? 2n ** 63n - 1n : 2n ** 64n - 1n;
3839
+ const cast = (value: unknown) => {
3840
+ const raw = typeof value === "bigint" ? value : BigInt(Math.trunc(Number(value)) || 0);
3841
+ // AssemblyScript truncates a cast to the target width rather than widening.
3842
+ return signed ? BigInt.asIntN(64, raw) : BigInt.asUintN(64, raw);
3843
+ };
3844
+ return strictNamespace(
3845
+ name,
3846
+ Object.assign(cast, { MIN_VALUE: min, MAX_VALUE: max, parseInt: (text: string, radix = 0) => cast(BigInt(parseAsInteger(text, radix))) }),
3847
+ );
3848
+ }
3849
+
3850
+ function floatNamespace(name: string, single: boolean) {
3851
+ const cast = (value: unknown) => (single ? Math.fround(Number(value)) : Number(value));
3852
+ return strictNamespace(
3853
+ name,
3854
+ Object.assign(cast, {
3855
+ // AssemblyScript's MIN_VALUE is the most negative finite value, not the
3856
+ // smallest positive one JavaScript names.
3857
+ MIN_VALUE: single ? -3.4028234663852886e38 : -Number.MAX_VALUE,
3858
+ MAX_VALUE: single ? 3.4028234663852886e38 : Number.MAX_VALUE,
3859
+ EPSILON: single ? 1.1920928955078125e-7 : Number.EPSILON,
3860
+ MIN_SAFE_INTEGER: single ? -16777215 : Number.MIN_SAFE_INTEGER,
3861
+ MAX_SAFE_INTEGER: single ? 16777215 : Number.MAX_SAFE_INTEGER,
3862
+ NaN: Number.NaN,
3863
+ POSITIVE_INFINITY: Number.POSITIVE_INFINITY,
3864
+ NEGATIVE_INFINITY: Number.NEGATIVE_INFINITY,
3865
+ parseFloat: (text: string) => cast(Number.parseFloat(text)),
3866
+ isNaN: (value: number) => Number.isNaN(cast(value)),
3867
+ isFinite: (value: number) => Number.isFinite(cast(value)),
3868
+ isInteger: (value: number) => Number.isInteger(cast(value)),
3869
+ isSafeInteger: (value: number) => {
3870
+ const v = cast(value);
3871
+ return Number.isInteger(v) && Math.abs(v) <= (single ? 16777215 : Number.MAX_SAFE_INTEGER);
3872
+ },
3873
+ }),
3874
+ );
3875
+ }
3876
+
3877
+ // ---------------------------------------------------------------------------
3878
+ // The rest of graph-ts 0.38's export list
3879
+ // ---------------------------------------------------------------------------
3880
+
3881
+ /**
3882
+ * graph-ts declares these as AssemblyScript type aliases (`type Timestamp =
3883
+ * i64`). AssemblyScript erases them, but the JavaScript the addon makes of a
3884
+ * mapping still imports them by name (xmtp's `import { Timestamp } from
3885
+ * "@graphprotocol/graph-ts"`), and an ES module import of a name the module
3886
+ * lacks is a link error. They hold nothing; a mapping only writes them as types.
3887
+ */
3888
+ export const Timestamp = undefined;
3889
+ export const Int8 = undefined;
3890
+ export const ValuePayload = undefined;
3891
+ export const JSONValuePayload = undefined;
3892
+ export const YAMLValuePayload = undefined;
3893
+
3894
+ /** Starknet's field element: `Felt.intoBigInt()` reads its bytes as a number. */
3895
+ export class Felt extends Bytes {
3896
+ intoBigInt(): BigInt_ {
3897
+ return BigInt_.fromUnsignedBytes(new ByteArray(this).reverse() as ByteArray);
3898
+ }
3899
+ }
3900
+
3901
+ /** The discriminants graph-node reads an AssemblyScript object's class by. */
3902
+ export enum TypeId {
3903
+ String = 0,
3904
+ ArrayBuffer = 1,
3905
+ Int8Array = 2,
3906
+ Int16Array = 3,
3907
+ Int32Array = 4,
3908
+ Int64Array = 5,
3909
+ Uint8Array = 6,
3910
+ Uint16Array = 7,
3911
+ Uint32Array = 8,
3912
+ Uint64Array = 9,
3913
+ Float32Array = 10,
3914
+ Float64Array = 11,
3915
+ BigDecimal = 12,
3916
+ ArrayBool = 13,
3917
+ ArrayUint8Array = 14,
3918
+ ArrayEthereumValue = 15,
3919
+ ArrayStoreValue = 16,
3920
+ ArrayJsonValue = 17,
3921
+ ArrayString = 18,
3922
+ YamlTypedMapValueValue = 5503,
3923
+ YamlArrayValue = 5504,
3924
+ YamlArrayTypedMapEntryValueValue = 5505,
3925
+ YamlWrappedValue = 5506,
3926
+ YamlResultValueBool = 5507,
3927
+ }
3928
+
3929
+ /** AssemblyScript's `idof<T>()` for a graph-node type: a number the host reads. Nothing here reads it. */
3930
+ export function id_of_type(typeId: TypeId): number {
3931
+ return typeId;
3932
+ }
3933
+
3934
+ /** Hands the host an object by pointer; there is no wasm memory here to allocate in. */
3935
+ export function allocate(_size: number): number {
3936
+ throw unsupported("allocate(), which hands the graph-node host a raw memory block", "a mapping handler");
3937
+ }
3938
+
3939
+ /** A chain whose subgraphs this indexes no part of: naming a member is the error. */
3940
+ function otherEcosystem(name: string, ecosystem: string) {
3941
+ return new Proxy(
3942
+ {},
3943
+ {
3944
+ get(_target, prop) {
3945
+ if (typeof prop === "symbol" || prop === "then" || prop === "toJSON") return undefined;
3946
+ throw unsupported(`${ecosystem} subgraphs — Envio Subgraph indexes EVM chains`, `${name}.${String(prop)}`);
3947
+ },
3948
+ },
3949
+ );
3950
+ }
3951
+
3952
+ // Typed as graph-ts' own, so a mapping's types agree and only a read refuses.
3953
+ export const near = otherEcosystem("near", "NEAR") as unknown as typeof GraphTsTypes.near;
3954
+ export const cosmos = otherEcosystem("cosmos", "Cosmos") as unknown as typeof GraphTsTypes.cosmos;
3955
+ export const starknet = otherEcosystem("starknet", "Starknet") as unknown as typeof GraphTsTypes.starknet;
3956
+
3957
+ /**
3958
+ * AssemblyScript's fixed-length array, which as-base64 builds its lookup table
3959
+ * from: `StaticArray.fromArray<u8>([...])`. A JavaScript Array holds the same.
3960
+ */
3961
+ class StaticArray<T> extends Array<T> {
3962
+ static fromArray<T>(values: T[]): StaticArray<T> {
3963
+ const array = new StaticArray<T>(values.length);
3964
+ for (let index = 0; index < values.length; index++) array[index] = values[index];
3965
+ return array;
3966
+ }
3967
+ }
3968
+
3969
+ export const assemblyScriptPrimitives: Record<string, unknown> = {
3970
+ StaticArray,
3971
+ i8: integerNamespace("i8", 8, true),
3972
+ u8: integerNamespace("u8", 8, false),
3973
+ i16: integerNamespace("i16", 16, true),
3974
+ u16: integerNamespace("u16", 16, false),
3975
+ i32: integerNamespace("i32", 32, true),
3976
+ u32: integerNamespace("u32", 32, false),
3977
+ isize: integerNamespace("isize", 32, true),
3978
+ usize: integerNamespace("usize", 32, false),
3979
+ i64: bigIntegerNamespace("i64", true),
3980
+ u64: bigIntegerNamespace("u64", false),
3981
+ f32: floatNamespace("f32", true),
3982
+ f64: floatNamespace("f64", false),
3983
+ };
3984
+ // The wrapper classes (`I32.parseInt(...)`, `U64.MAX_VALUE`) are the same
3985
+ // namespaces under their capitalised names.
3986
+ for (const name of Object.keys(assemblyScriptPrimitives)) {
3987
+ if (/^[iuf]\d+$/.test(name)) assemblyScriptPrimitives[name.toUpperCase()] = assemblyScriptPrimitives[name];
3988
+ }
3989
+
3990
+ // ---------------------------------------------------------------------------
3991
+ // Host namespaces the generated declarations re-export
3992
+ //
3993
+ // A mapping importing one of these fails at ESM link time if it isn't exported,
3994
+ // which is a missing-export error naming nothing useful. Exporting them means an
3995
+ // unsupported one refuses by name like everything else.
3996
+ // ---------------------------------------------------------------------------
3997
+
3998
+ /** graph-ts' BigInt, whether the caller passed one or the bytes of one. */
3999
+ function asGraphBigInt(value: Uint8Array): BigInt_ {
4000
+ return value instanceof BigInt_ ? value : new BigInt_(fromLittleEndian(value, true));
4001
+ }
4002
+
4003
+ const typeConversionImpl = {
4004
+ bytesToString: (bytes: Uint8Array) => new ByteArray(bytes).toString(),
4005
+ bytesToHex: (bytes: Uint8Array) => new ByteArray(bytes).toHexString(),
4006
+ // graph-ts types the argument as the byte array a BigInt is in
4007
+ // AssemblyScript. This one carries its value instead of laying those bytes
4008
+ // out (see `BigInt_`), so reading it as bytes would answer zero for every
4009
+ // input.
4010
+ bigIntToString: (value: Uint8Array) => asGraphBigInt(value).toString(),
4011
+ bigIntToHex: (value: Uint8Array) => asGraphBigInt(value).toHexString(),
4012
+ stringToH160: (value: string) => Address.fromString(value) as Bytes,
4013
+ bytesToBase58: (_: Uint8Array): string => {
4014
+ throw unsupported("typeConversion.bytesToBase58", "a mapping handler");
4015
+ },
4016
+ };
4017
+
4018
+ export const typeConversion = strictNamespace("typeConversion", typeConversionImpl);
4019
+
4020
+ const bigIntImpl = {
4021
+ plus: (x: BigInt_, y: BigInt_) => x.plus(y),
4022
+ minus: (x: BigInt_, y: BigInt_) => x.minus(y),
4023
+ times: (x: BigInt_, y: BigInt_) => x.times(y),
4024
+ dividedBy: (x: BigInt_, y: BigInt_) => x.div(y),
4025
+ dividedByDecimal: (x: BigInt_, y: BigDecimal) => x.toBigDecimal().div(y),
4026
+ mod: (x: BigInt_, y: BigInt_) => x.mod(y),
4027
+ pow: (x: BigInt_, exp: number) => x.pow(exp),
4028
+ fromString: (value: string) => BigInt_.fromString(value),
4029
+ bitOr: (x: BigInt_, y: BigInt_) => x.bitOr(y),
4030
+ bitAnd: (x: BigInt_, y: BigInt_) => x.bitAnd(y),
4031
+ leftShift: (x: BigInt_, bits: number) => x.leftShift(bits),
4032
+ rightShift: (x: BigInt_, bits: number) => x.rightShift(bits),
4033
+ };
4034
+
4035
+ export const bigInt = strictNamespace("bigInt", bigIntImpl);
4036
+
4037
+ const bigDecimalImpl = {
4038
+ plus: (x: BigDecimal, y: BigDecimal) => x.plus(y),
4039
+ minus: (x: BigDecimal, y: BigDecimal) => x.minus(y),
4040
+ times: (x: BigDecimal, y: BigDecimal) => x.times(y),
4041
+ dividedBy: (x: BigDecimal, y: BigDecimal) => x.div(y),
4042
+ equals: (x: BigDecimal, y: BigDecimal) => x.equals(y),
4043
+ toString: (value: BigDecimal) => value.toString(),
4044
+ fromString: (value: string) => BigDecimal.fromString(value),
4045
+ };
4046
+
4047
+ export const bigDecimal = strictNamespace("bigDecimal", bigDecimalImpl);
4048
+
4049
+ export function concat(a: ByteArray, b: ByteArray): ByteArray {
4050
+ return a.concat(b);
4051
+ }
4052
+
4053
+ export function parseCSV(csv: string): string[] {
4054
+ return csv.split(",").map(field => field.trim());
4055
+ }
4056
+
4057
+ /** Restores the multihash prefix an IPFS hash loses when stored in a bytes32. */
4058
+ export function addQm(a: ByteArray): ByteArray {
4059
+ return ByteArray.fromHexString("0x1220").concat(a);
4060
+ }
4061
+
4062
+ export enum YAMLValueKind {
4063
+ NULL = 0,
4064
+ BOOL = 1,
4065
+ NUMBER = 2,
4066
+ STRING = 3,
4067
+ ARRAY = 4,
4068
+ OBJECT = 5,
4069
+ TAGGED = 6,
4070
+ }
4071
+
4072
+ const yamlIndex: ProxyHandler<YAMLValue> = {
4073
+ get(target, prop, receiver) {
4074
+ if (typeof prop === "symbol" || prop in target) return Reflect.get(target, prop, receiver);
4075
+ const held =
4076
+ target.kind === YAMLValueKind.ARRAY
4077
+ ? /^\d+$/.test(prop) && Number(prop) < (target.data as unknown[]).length
4078
+ : target.kind === YAMLValueKind.OBJECT && target.toObject().get(YAMLValue.newString(prop)) !== null;
4079
+ return held ? target.get(prop) : undefined;
4080
+ },
4081
+ };
4082
+
4083
+ /**
4084
+ * graph-ts' YAML value, over graph-node's `serde_yaml::Value`: numbers are kept
4085
+ * as the text they print as, an object's keys are YAML values themselves, and a
4086
+ * `!tag` wraps its value.
4087
+ */
4088
+ export class YAMLValue {
4089
+ constructor(
4090
+ public kind: YAMLValueKind,
4091
+ public data: any,
4092
+ ) {
4093
+ // graph-ts' `@operator('[]')` makes `value['dataSources']['0']` a `get`;
4094
+ // in JavaScript it is a property read.
4095
+ return new Proxy(this, yamlIndex);
4096
+ }
4097
+
4098
+ static newNull(): YAMLValue {
4099
+ return new YAMLValue(YAMLValueKind.NULL, 0);
4100
+ }
4101
+ static newBool(data: boolean): YAMLValue {
4102
+ return new YAMLValue(YAMLValueKind.BOOL, data);
4103
+ }
4104
+ static newI64(data: bigint): YAMLValue {
4105
+ return new YAMLValue(YAMLValueKind.NUMBER, data.toString());
4106
+ }
4107
+ static newU64(data: bigint): YAMLValue {
4108
+ return new YAMLValue(YAMLValueKind.NUMBER, data.toString());
4109
+ }
4110
+ static newF64(data: number): YAMLValue {
4111
+ return new YAMLValue(YAMLValueKind.NUMBER, String(data));
4112
+ }
4113
+ static newBigInt(data: BigInt_): YAMLValue {
4114
+ return new YAMLValue(YAMLValueKind.STRING, data.toString());
4115
+ }
4116
+ static newString(data: string): YAMLValue {
4117
+ return new YAMLValue(YAMLValueKind.STRING, data);
4118
+ }
4119
+ static newArray(data: YAMLValue[]): YAMLValue {
4120
+ return new YAMLValue(YAMLValueKind.ARRAY, data);
4121
+ }
4122
+ static newObject(data: TypedMap<YAMLValue, YAMLValue>): YAMLValue {
4123
+ const map = new YAMLMap();
4124
+ for (const entry of data.entries) map.set(entry.key, entry.value);
4125
+ return new YAMLValue(YAMLValueKind.OBJECT, map);
4126
+ }
4127
+ static newTagged(tag: string, value: YAMLValue): YAMLValue {
4128
+ return new YAMLValue(YAMLValueKind.TAGGED, new YAMLTaggedValue(tag, value));
4129
+ }
4130
+
4131
+ isNull(): boolean {
4132
+ return this.kind === YAMLValueKind.NULL;
4133
+ }
4134
+ isBool(): boolean {
4135
+ return this.kind === YAMLValueKind.BOOL;
4136
+ }
4137
+ isNumber(): boolean {
4138
+ return this.kind === YAMLValueKind.NUMBER;
4139
+ }
4140
+ isString(): boolean {
4141
+ return this.kind === YAMLValueKind.STRING;
4142
+ }
4143
+ isArray(): boolean {
4144
+ return this.kind === YAMLValueKind.ARRAY;
4145
+ }
4146
+ isObject(): boolean {
4147
+ return this.kind === YAMLValueKind.OBJECT;
4148
+ }
4149
+ isTagged(): boolean {
4150
+ return this.kind === YAMLValueKind.TAGGED;
4151
+ }
4152
+
4153
+ toBool(): boolean {
4154
+ assertYaml(this.isBool(), "YAML value is not a boolean");
4155
+ return this.data === true;
4156
+ }
4157
+ toNumber(): string {
4158
+ assertYaml(this.isNumber(), "YAML value is not a number");
4159
+ return this.data as string;
4160
+ }
4161
+ toI64(): bigint {
4162
+ return BigInt.asIntN(64, BigInt(this.toNumber()));
4163
+ }
4164
+ toU64(): bigint {
4165
+ return BigInt.asUintN(64, BigInt(this.toNumber()));
4166
+ }
4167
+ toF64(): number {
4168
+ return Number(this.toNumber());
4169
+ }
4170
+ toBigInt(): BigInt_ {
4171
+ assertYaml(this.isNumber() || this.isString(), "YAML value is not numeric");
4172
+ return BigInt_.fromString(this.data as string);
4173
+ }
4174
+ toString(): string {
4175
+ assertYaml(this.isString(), "YAML value is not a string");
4176
+ return this.data as string;
4177
+ }
4178
+ toArray(): YAMLValue[] {
4179
+ assertYaml(this.isArray(), "YAML value is not an array");
4180
+ return this.data as YAMLValue[];
4181
+ }
4182
+ toObject(): TypedMap<YAMLValue, YAMLValue> {
4183
+ assertYaml(this.isObject(), "YAML value is not an object");
4184
+ return this.data as TypedMap<YAMLValue, YAMLValue>;
4185
+ }
4186
+ toTagged(): YAMLTaggedValue {
4187
+ assertYaml(this.isTagged(), "YAML value is not tagged");
4188
+ return this.data as YAMLTaggedValue;
4189
+ }
4190
+
4191
+ /** graph-ts' `==` overload: structural, so a `TypedMap` keyed by YAML values finds its key. */
4192
+ equals(other: YAMLValue): boolean {
4193
+ return YAMLValue.eq(this, other);
4194
+ }
4195
+ static eq(a: YAMLValue, b: YAMLValue): boolean {
4196
+ if (a.isBool() && b.isBool()) return a.data === b.data;
4197
+ if (a.isNumber() && b.isNumber()) return a.data === b.data;
4198
+ if (a.isString() && b.isString()) return a.data === b.data;
4199
+ if (a.isArray() && b.isArray()) {
4200
+ const left = a.toArray();
4201
+ const right = b.toArray();
4202
+ return left.length === right.length && left.every((item, index) => YAMLValue.eq(item, right[index]));
4203
+ }
4204
+ if (a.isObject() && b.isObject()) {
4205
+ const left = a.toObject();
4206
+ const right = b.toObject();
4207
+ return (
4208
+ left.entries.length === right.entries.length &&
4209
+ left.entries.every((entry) => {
4210
+ const other = right.get(entry.key);
4211
+ return other !== null && YAMLValue.eq(entry.value, other);
4212
+ })
4213
+ );
4214
+ }
4215
+ if (a.isTagged() && b.isTagged()) return YAMLTaggedValue.eq(a.toTagged(), b.toTagged());
4216
+ return false;
4217
+ }
4218
+ static ne(a: YAMLValue | null, b: YAMLValue | null): boolean {
4219
+ if (!a || !b) return true;
4220
+ return !YAMLValue.eq(a, b);
4221
+ }
4222
+
4223
+ /** `value["key"]` on an object, `value["0"]` on an array; aborts when the key is absent. */
4224
+ get(index: string): YAMLValue {
4225
+ assertYaml(this.isArray() || this.isObject(), "YAML value can not be accessed by index");
4226
+ if (this.isArray()) {
4227
+ const item = this.toArray()[Number.parseInt(index, 10)];
4228
+ assertYaml(item !== undefined, "Array index out of range");
4229
+ return item;
4230
+ }
4231
+ return this.toObject().mustGet(YAMLValue.newString(index));
4232
+ }
4233
+ }
4234
+
4235
+ /** AssemblyScript's `==` on YAML keys compares their content, which is how `toObject().get(newString("k"))` finds "k". */
4236
+ class YAMLMap extends TypedMap<YAMLValue, YAMLValue> {
4237
+ getEntry(key: YAMLValue): TypedMapEntry<YAMLValue, YAMLValue> | null {
4238
+ return this.entries.find((entry) => YAMLValue.eq(entry.key, key)) ?? null;
4239
+ }
4240
+ }
4241
+
4242
+ export class YAMLTaggedValue {
4243
+ constructor(
4244
+ public tag: string,
4245
+ public value: YAMLValue,
4246
+ ) {}
4247
+ equals(other: YAMLTaggedValue): boolean {
4248
+ return YAMLTaggedValue.eq(this, other);
4249
+ }
4250
+ static eq(a: YAMLTaggedValue, b: YAMLTaggedValue): boolean {
4251
+ return a.tag === b.tag && YAMLValue.eq(a.value, b.value);
4252
+ }
4253
+ static ne(a: YAMLTaggedValue | null, b: YAMLTaggedValue | null): boolean {
4254
+ if (!a || !b) return true;
4255
+ return !YAMLTaggedValue.eq(a, b);
4256
+ }
4257
+ }
4258
+
4259
+ function assertYaml(condition: boolean, message: string): void {
4260
+ if (!condition) throw new Error(message);
4261
+ }
4262
+
4263
+ /** graph-node refuses a document over this many bytes. */
4264
+ const YAML_MAX_SIZE_BYTES = 10_000_000;
4265
+
4266
+ /**
4267
+ * A parsed node as graph-node hands it to the mapping. Numbers keep the text
4268
+ * serde_yaml prints them as (an integer as its digits, a float with its
4269
+ * fraction), a mapping key is a YAML value of its own, and an alias is its
4270
+ * target.
4271
+ */
4272
+ function yamlFromNode(node: unknown, document: Document): YAMLValue {
4273
+ if (isAlias(node)) {
4274
+ return yamlFromNode(node.resolve(document), document);
4275
+ }
4276
+ if (isMap(node)) {
4277
+ const map = new YAMLMap();
4278
+ for (const pair of node.items) {
4279
+ map.set(yamlFromNode(pair.key, document), yamlFromNode(pair.value, document));
4280
+ }
4281
+ return taggedOr(node, YAMLValue.newObject(map));
4282
+ }
4283
+ if (isSeq(node)) {
4284
+ return taggedOr(node, YAMLValue.newArray(node.items.map((item) => yamlFromNode(item, document))));
4285
+ }
4286
+ if (isScalar(node)) {
4287
+ const value = node.value;
4288
+ let result: YAMLValue;
4289
+ if (value === null || value === undefined) result = YAMLValue.newNull();
4290
+ else if (typeof value === "boolean") result = YAMLValue.newBool(value);
4291
+ else if (typeof value === "bigint") result = new YAMLValue(YAMLValueKind.NUMBER, value.toString());
4292
+ else if (typeof value === "number") {
4293
+ const source = (node as { srcToken?: { source?: string } }).srcToken?.source ?? "";
4294
+ // YAML `1.0` is the float 1.0, which serde_yaml prints as such.
4295
+ const isFloat = /[.eE]/.test(source) && Number.isFinite(value);
4296
+ const text = Number.isNaN(value)
4297
+ ? "NaN"
4298
+ : value === Infinity
4299
+ ? "inf"
4300
+ : value === -Infinity
4301
+ ? "-inf"
4302
+ : isFloat && Number.isInteger(value)
4303
+ ? `${value}.0`
4304
+ : String(value);
4305
+ result = new YAMLValue(YAMLValueKind.NUMBER, text);
4306
+ } else result = YAMLValue.newString(String(value));
4307
+ return taggedOr(node, result);
4308
+ }
4309
+ return YAMLValue.newNull();
4310
+ }
4311
+
4312
+ /** A local tag (`!Ref x`) wraps its value; the core tags (`!!str`) are the value itself. */
4313
+ function taggedOr(node: unknown, value: YAMLValue): YAMLValue {
4314
+ const tag = (node as { tag?: string }).tag;
4315
+ if (typeof tag === "string" && tag.startsWith("!") && !tag.startsWith("!!")) {
4316
+ return YAMLValue.newTagged(tag, value);
4317
+ }
4318
+ return value;
4319
+ }
4320
+
4321
+ function parseYaml(text: string): YAMLValue {
4322
+ if (Buffer.byteLength(text) > YAML_MAX_SIZE_BYTES) {
4323
+ throw new Error(`YAML size exceeds max size of ${YAML_MAX_SIZE_BYTES} bytes`);
4324
+ }
4325
+ const document = parseDocument(text, { intAsBigInt: true, keepSourceTokens: true });
4326
+ if (document.errors.length > 0) {
4327
+ throw new Error(`failed to parse YAML from bytes: ${document.errors[0].message}`);
4328
+ }
4329
+ return yamlFromNode(document.contents, document);
4330
+ }
4331
+
4332
+ const yamlImpl = {
4333
+ fromBytes: (bytes: Bytes): YAMLValue => parseYaml(utf8Decoder.decode(bytes)),
4334
+ try_fromBytes: (bytes: Bytes): Result<YAMLValue, boolean> => tryParse(() => parseYaml(utf8Decoder.decode(bytes))),
4335
+ fromString: (text: string): YAMLValue => parseYaml(text),
4336
+ try_fromString: (text: string): Result<YAMLValue, boolean> => tryParse(() => parseYaml(text)),
4337
+ };
4338
+
4339
+ export const yaml = strictNamespace("yaml", yamlImpl);