envio 3.0.2-svm-alpha.0 → 3.0.2-svm-alpha.1
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.
- package/index.d.ts +12 -0
- package/package.json +6 -6
- package/src/Config.res +2 -0
- package/src/Config.res.mjs +1 -1
- package/src/Envio.res +9 -0
- package/src/EventConfigBuilder.res +2 -0
- package/src/EventConfigBuilder.res.mjs +2 -1
- package/src/Hasura.res +43 -0
- package/src/Hasura.res.mjs +38 -0
- package/src/Internal.res +1 -0
- package/src/sources/HyperSyncSolanaClient.res +22 -0
- package/src/sources/HyperSyncSolanaSource.res +51 -0
- package/src/sources/HyperSyncSolanaSource.res.mjs +50 -3
- package/svm.schema.json +7 -0
package/index.d.ts
CHANGED
|
@@ -1000,6 +1000,15 @@ export type SvmInstruction = {
|
|
|
1000
1000
|
readonly decoded?: SvmDecodedInstruction;
|
|
1001
1001
|
};
|
|
1002
1002
|
|
|
1003
|
+
export type SvmTokenBalance = {
|
|
1004
|
+
readonly account?: string;
|
|
1005
|
+
readonly mint?: string;
|
|
1006
|
+
readonly owner?: string;
|
|
1007
|
+
/** u64 decimal string. Cast with BigInt(...) for arithmetic. */
|
|
1008
|
+
readonly preAmount?: string;
|
|
1009
|
+
readonly postAmount?: string;
|
|
1010
|
+
};
|
|
1011
|
+
|
|
1003
1012
|
/** Parent transaction surfaced when an instruction's
|
|
1004
1013
|
* `include_transaction` flag is `true`. */
|
|
1005
1014
|
export type SvmTransaction = {
|
|
@@ -1013,6 +1022,9 @@ export type SvmTransaction = {
|
|
|
1013
1022
|
readonly accountKeys: readonly string[];
|
|
1014
1023
|
readonly recentBlockhash?: string;
|
|
1015
1024
|
readonly version?: string;
|
|
1025
|
+
/** SPL Token / Token-2022 balance snapshots for this transaction.
|
|
1026
|
+
* Present when `include_token_balances` is `true`. */
|
|
1027
|
+
readonly tokenBalances?: readonly SvmTokenBalance[];
|
|
1016
1028
|
};
|
|
1017
1029
|
|
|
1018
1030
|
export type SvmLog = {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "envio",
|
|
3
|
-
"version": "3.0.2-svm-alpha.
|
|
3
|
+
"version": "3.0.2-svm-alpha.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "A latency and sync speed optimized, developer friendly blockchain data indexer.",
|
|
6
6
|
"bin": "./bin.mjs",
|
|
@@ -70,10 +70,10 @@
|
|
|
70
70
|
"tsx": "4.21.0"
|
|
71
71
|
},
|
|
72
72
|
"optionalDependencies": {
|
|
73
|
-
"envio-linux-x64": "3.0.2-svm-alpha.
|
|
74
|
-
"envio-linux-x64-musl": "3.0.2-svm-alpha.
|
|
75
|
-
"envio-linux-arm64": "3.0.2-svm-alpha.
|
|
76
|
-
"envio-darwin-x64": "3.0.2-svm-alpha.
|
|
77
|
-
"envio-darwin-arm64": "3.0.2-svm-alpha.
|
|
73
|
+
"envio-linux-x64": "3.0.2-svm-alpha.1",
|
|
74
|
+
"envio-linux-x64-musl": "3.0.2-svm-alpha.1",
|
|
75
|
+
"envio-linux-arm64": "3.0.2-svm-alpha.1",
|
|
76
|
+
"envio-darwin-x64": "3.0.2-svm-alpha.1",
|
|
77
|
+
"envio-darwin-arm64": "3.0.2-svm-alpha.1"
|
|
78
78
|
}
|
|
79
79
|
}
|
package/src/Config.res
CHANGED
|
@@ -694,6 +694,7 @@ let fromPublic = (publicConfigJson: JSON.t) => {
|
|
|
694
694
|
"discriminatorByteLen": int,
|
|
695
695
|
"includeTransaction": bool,
|
|
696
696
|
"includeLogs": bool,
|
|
697
|
+
"includeTokenBalances": bool,
|
|
697
698
|
"accountFilters": option<
|
|
698
699
|
array<{"position": int, "values": array<string>}>,
|
|
699
700
|
>,
|
|
@@ -723,6 +724,7 @@ let fromPublic = (publicConfigJson: JSON.t) => {
|
|
|
723
724
|
~discriminatorByteLen=svm["discriminatorByteLen"],
|
|
724
725
|
~includeTransaction=svm["includeTransaction"],
|
|
725
726
|
~includeLogs=svm["includeLogs"],
|
|
727
|
+
~includeTokenBalances=svm["includeTokenBalances"],
|
|
726
728
|
~accountFilters,
|
|
727
729
|
~isInner=svm["isInner"],
|
|
728
730
|
~isWildcard=false,
|
package/src/Config.res.mjs
CHANGED
|
@@ -515,7 +515,7 @@ function fromPublic(publicConfigJson) {
|
|
|
515
515
|
position: af.position,
|
|
516
516
|
values: af.values
|
|
517
517
|
}));
|
|
518
|
-
return EventConfigBuilder.buildSvmInstructionEventConfig(contractName, eventName, programId, svm.discriminator, svm.discriminatorByteLen, svm.includeTransaction, svm.includeLogs, accountFilters, svm.isInner, false, undefined, undefined, Stdlib_Option.getOr(svm.accounts, []), Stdlib_Option.getOr(svm.args, null), svmDefinedTypes, startBlock);
|
|
518
|
+
return EventConfigBuilder.buildSvmInstructionEventConfig(contractName, eventName, programId, svm.discriminator, svm.discriminatorByteLen, svm.includeTransaction, svm.includeLogs, svm.includeTokenBalances, accountFilters, svm.isInner, false, undefined, undefined, Stdlib_Option.getOr(svm.accounts, []), Stdlib_Option.getOr(svm.args, null), svmDefinedTypes, startBlock);
|
|
519
519
|
}
|
|
520
520
|
});
|
|
521
521
|
} else {
|
package/src/Envio.res
CHANGED
|
@@ -58,6 +58,14 @@ type svmInstruction = {
|
|
|
58
58
|
decoded?: svmDecodedInstruction,
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
+
type svmTokenBalance = {
|
|
62
|
+
account?: SvmTypes.Pubkey.t,
|
|
63
|
+
mint?: SvmTypes.Pubkey.t,
|
|
64
|
+
owner?: SvmTypes.Pubkey.t,
|
|
65
|
+
preAmount?: string,
|
|
66
|
+
postAmount?: string,
|
|
67
|
+
}
|
|
68
|
+
|
|
61
69
|
type svmTransaction = {
|
|
62
70
|
signatures: array<string>,
|
|
63
71
|
feePayer?: SvmTypes.Pubkey.t,
|
|
@@ -68,6 +76,7 @@ type svmTransaction = {
|
|
|
68
76
|
accountKeys: array<SvmTypes.Pubkey.t>,
|
|
69
77
|
recentBlockhash?: string,
|
|
70
78
|
version?: string,
|
|
79
|
+
tokenBalances?: array<svmTokenBalance>,
|
|
71
80
|
}
|
|
72
81
|
|
|
73
82
|
type svmLog = {
|
|
@@ -492,6 +492,7 @@ let buildSvmInstructionEventConfig = (
|
|
|
492
492
|
~discriminatorByteLen: int,
|
|
493
493
|
~includeTransaction: bool,
|
|
494
494
|
~includeLogs: bool,
|
|
495
|
+
~includeTokenBalances: bool,
|
|
495
496
|
~accountFilters: array<Internal.svmAccountFilter>,
|
|
496
497
|
~isInner: option<bool>,
|
|
497
498
|
~isWildcard: bool,
|
|
@@ -526,6 +527,7 @@ let buildSvmInstructionEventConfig = (
|
|
|
526
527
|
discriminatorByteLen,
|
|
527
528
|
includeTransaction,
|
|
528
529
|
includeLogs,
|
|
530
|
+
includeTokenBalances,
|
|
529
531
|
accountFilters,
|
|
530
532
|
isInner,
|
|
531
533
|
accounts,
|
|
@@ -350,7 +350,7 @@ function buildEvmEventConfig(contractName, eventName, sighash, params, isWildcar
|
|
|
350
350
|
};
|
|
351
351
|
}
|
|
352
352
|
|
|
353
|
-
function buildSvmInstructionEventConfig(contractName, instructionName, programId, discriminator, discriminatorByteLen, includeTransaction, includeLogs, accountFilters, isInner, isWildcard, handler, contractRegister, accountsOpt, argsOpt, definedTypesOpt, startBlock) {
|
|
353
|
+
function buildSvmInstructionEventConfig(contractName, instructionName, programId, discriminator, discriminatorByteLen, includeTransaction, includeLogs, includeTokenBalances, accountFilters, isInner, isWildcard, handler, contractRegister, accountsOpt, argsOpt, definedTypesOpt, startBlock) {
|
|
354
354
|
let accounts = accountsOpt !== undefined ? accountsOpt : [];
|
|
355
355
|
let args = argsOpt !== undefined ? argsOpt : null;
|
|
356
356
|
let definedTypes = definedTypesOpt !== undefined ? definedTypesOpt : null;
|
|
@@ -372,6 +372,7 @@ function buildSvmInstructionEventConfig(contractName, instructionName, programId
|
|
|
372
372
|
discriminatorByteLen: discriminatorByteLen,
|
|
373
373
|
includeTransaction: includeTransaction,
|
|
374
374
|
includeLogs: includeLogs,
|
|
375
|
+
includeTokenBalances: includeTokenBalances,
|
|
375
376
|
accountFilters: accountFilters,
|
|
376
377
|
isInner: isInner,
|
|
377
378
|
accounts: accounts,
|
package/src/Hasura.res
CHANGED
|
@@ -40,6 +40,19 @@ let clearMetadataRoute = Rest.route(() => {
|
|
|
40
40
|
responses,
|
|
41
41
|
})
|
|
42
42
|
|
|
43
|
+
let reloadMetadataRoute = Rest.route(() => {
|
|
44
|
+
method: Post,
|
|
45
|
+
path: "",
|
|
46
|
+
input: s => {
|
|
47
|
+
let _ = s.field("type", S.literal("reload_metadata"))
|
|
48
|
+
{
|
|
49
|
+
"args": s.field("args", S.json(~validate=false)),
|
|
50
|
+
"auth": s->auth,
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
responses,
|
|
54
|
+
})
|
|
55
|
+
|
|
43
56
|
let trackTablesRoute = Rest.route(() => {
|
|
44
57
|
method: Post,
|
|
45
58
|
path: "",
|
|
@@ -110,6 +123,31 @@ let clearHasuraMetadata = async (~endpoint, ~auth) => {
|
|
|
110
123
|
}
|
|
111
124
|
}
|
|
112
125
|
|
|
126
|
+
let reloadHasuraMetadata = async (~endpoint, ~auth) => {
|
|
127
|
+
try {
|
|
128
|
+
let result = await reloadMetadataRoute->Rest.fetch(
|
|
129
|
+
{
|
|
130
|
+
"auth": auth,
|
|
131
|
+
"args": {
|
|
132
|
+
"reload_sources": ["default"],
|
|
133
|
+
}->(Utils.magic: 'a => JSON.t),
|
|
134
|
+
},
|
|
135
|
+
~client=Rest.client(endpoint),
|
|
136
|
+
)
|
|
137
|
+
let msg = switch result {
|
|
138
|
+
| QuerySucceeded => "Hasura metadata reloaded"
|
|
139
|
+
| AlreadyDone => "Hasura metadata reload acknowledged"
|
|
140
|
+
}
|
|
141
|
+
Logging.trace(msg)
|
|
142
|
+
} catch {
|
|
143
|
+
| exn =>
|
|
144
|
+
Logging.error({
|
|
145
|
+
"msg": `There was an issue reloading hasura metadata - table tracking may race with schema creation.`,
|
|
146
|
+
"err": exn->Utils.prettifyExn,
|
|
147
|
+
})
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
113
151
|
let trackTables = async (~endpoint, ~auth, ~pgSchema, ~tableNames: array<string>) => {
|
|
114
152
|
try {
|
|
115
153
|
let result = await trackTablesRoute->Rest.fetch(
|
|
@@ -242,6 +280,11 @@ let trackDatabase = async (
|
|
|
242
280
|
|
|
243
281
|
let _ = await clearHasuraMetadata(~endpoint, ~auth)
|
|
244
282
|
|
|
283
|
+
// Force Hasura to re-introspect the source schema before tracking, otherwise
|
|
284
|
+
// freshly-created user tables may be invisible to pg_track_tables and the call
|
|
285
|
+
// returns `metadata-warnings` (HTTP 400), leaving tracking permanently broken.
|
|
286
|
+
await reloadHasuraMetadata(~endpoint, ~auth)
|
|
287
|
+
|
|
245
288
|
await trackTables(~endpoint, ~auth, ~pgSchema, ~tableNames)
|
|
246
289
|
|
|
247
290
|
for i in 0 to tableNames->Array.length - 1 {
|
package/src/Hasura.res.mjs
CHANGED
|
@@ -46,6 +46,21 @@ function clearMetadataRoute() {
|
|
|
46
46
|
};
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
function reloadMetadataRoute() {
|
|
50
|
+
return {
|
|
51
|
+
method: "POST",
|
|
52
|
+
path: "",
|
|
53
|
+
input: s => {
|
|
54
|
+
s.field("type", S$RescriptSchema.literal("reload_metadata"));
|
|
55
|
+
return {
|
|
56
|
+
args: s.field("args", S$RescriptSchema.json(false)),
|
|
57
|
+
auth: auth(s)
|
|
58
|
+
};
|
|
59
|
+
},
|
|
60
|
+
responses: responses
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
49
64
|
function trackTablesRoute() {
|
|
50
65
|
return {
|
|
51
66
|
method: "POST",
|
|
@@ -112,6 +127,26 @@ async function clearHasuraMetadata(endpoint, auth) {
|
|
|
112
127
|
}
|
|
113
128
|
}
|
|
114
129
|
|
|
130
|
+
async function reloadHasuraMetadata(endpoint, auth) {
|
|
131
|
+
try {
|
|
132
|
+
let result = await Rest.fetch(reloadMetadataRoute, {
|
|
133
|
+
auth: auth,
|
|
134
|
+
args: {
|
|
135
|
+
reload_sources: ["default"]
|
|
136
|
+
}
|
|
137
|
+
}, Rest.client(endpoint, undefined));
|
|
138
|
+
let tmp;
|
|
139
|
+
tmp = result === "QuerySucceeded" ? "Hasura metadata reloaded" : "Hasura metadata reload acknowledged";
|
|
140
|
+
return Logging.trace(tmp);
|
|
141
|
+
} catch (raw_exn) {
|
|
142
|
+
let exn = Primitive_exceptions.internalToException(raw_exn);
|
|
143
|
+
return Logging.error({
|
|
144
|
+
msg: `There was an issue reloading hasura metadata - table tracking may race with schema creation.`,
|
|
145
|
+
err: Utils.prettifyExn(exn)
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
115
150
|
async function trackTables(endpoint, auth, pgSchema, tableNames) {
|
|
116
151
|
try {
|
|
117
152
|
let result = await Rest.fetch(trackTablesRoute, {
|
|
@@ -202,6 +237,7 @@ async function trackDatabase(endpoint, auth, pgSchema, userEntities, aggregateEn
|
|
|
202
237
|
]);
|
|
203
238
|
Logging.info("Tracking tables in Hasura");
|
|
204
239
|
await clearHasuraMetadata(endpoint, auth);
|
|
240
|
+
await reloadHasuraMetadata(endpoint, auth);
|
|
205
241
|
await trackTables(endpoint, auth, pgSchema, tableNames);
|
|
206
242
|
for (let i = 0, i_finish = tableNames.length; i < i_finish; ++i) {
|
|
207
243
|
let tableName = tableNames[i];
|
|
@@ -231,10 +267,12 @@ export {
|
|
|
231
267
|
auth,
|
|
232
268
|
responses,
|
|
233
269
|
clearMetadataRoute,
|
|
270
|
+
reloadMetadataRoute,
|
|
234
271
|
trackTablesRoute,
|
|
235
272
|
rawBodyRoute,
|
|
236
273
|
sendOperation,
|
|
237
274
|
clearHasuraMetadata,
|
|
275
|
+
reloadHasuraMetadata,
|
|
238
276
|
trackTables,
|
|
239
277
|
createSelectPermission,
|
|
240
278
|
createEntityRelationship,
|
package/src/Internal.res
CHANGED
|
@@ -439,6 +439,7 @@ type svmInstructionEventConfig = {
|
|
|
439
439
|
discriminatorByteLen: int,
|
|
440
440
|
includeTransaction: bool,
|
|
441
441
|
includeLogs: bool,
|
|
442
|
+
includeTokenBalances: bool,
|
|
442
443
|
accountFilters: array<svmAccountFilter>,
|
|
443
444
|
/** `None` matches both outer and inner (CPI-invoked) instructions. */
|
|
444
445
|
isInner: option<bool>,
|
|
@@ -65,11 +65,21 @@ module QueryTypes = {
|
|
|
65
65
|
| @as("kind") Kind
|
|
66
66
|
| @as("message") Message
|
|
67
67
|
|
|
68
|
+
type tokenBalanceField =
|
|
69
|
+
| @as("slot") Slot
|
|
70
|
+
| @as("transaction_index") TransactionIndex
|
|
71
|
+
| @as("account") Account
|
|
72
|
+
| @as("mint") Mint
|
|
73
|
+
| @as("owner") Owner
|
|
74
|
+
| @as("pre_amount") PreAmount
|
|
75
|
+
| @as("post_amount") PostAmount
|
|
76
|
+
|
|
68
77
|
type fieldSelection = {
|
|
69
78
|
block?: array<blockField>,
|
|
70
79
|
transaction?: array<transactionField>,
|
|
71
80
|
instruction?: array<instructionField>,
|
|
72
81
|
log?: array<logField>,
|
|
82
|
+
tokenBalance?: array<tokenBalanceField>,
|
|
73
83
|
}
|
|
74
84
|
|
|
75
85
|
/** Filter for selecting instructions. All non-empty fields are AND-ed: an
|
|
@@ -96,6 +106,7 @@ module QueryTypes = {
|
|
|
96
106
|
isInner?: bool,
|
|
97
107
|
includeTransaction?: bool,
|
|
98
108
|
includeLogs?: bool,
|
|
109
|
+
includeTokenBalances?: bool,
|
|
99
110
|
}
|
|
100
111
|
|
|
101
112
|
type transactionSelection = {
|
|
@@ -183,11 +194,22 @@ module ResponseTypes = {
|
|
|
183
194
|
message?: string,
|
|
184
195
|
}
|
|
185
196
|
|
|
197
|
+
type tokenBalance = {
|
|
198
|
+
slot: int,
|
|
199
|
+
transactionIndex?: int,
|
|
200
|
+
account?: string,
|
|
201
|
+
mint?: string,
|
|
202
|
+
owner?: string,
|
|
203
|
+
preAmount?: string,
|
|
204
|
+
postAmount?: string,
|
|
205
|
+
}
|
|
206
|
+
|
|
186
207
|
type queryResponseData = {
|
|
187
208
|
blocks: array<block>,
|
|
188
209
|
transactions: array<transaction>,
|
|
189
210
|
instructions: array<instruction>,
|
|
190
211
|
logs: array<log>,
|
|
212
|
+
tokenBalances: array<tokenBalance>,
|
|
191
213
|
}
|
|
192
214
|
|
|
193
215
|
type queryResponse = {
|
|
@@ -68,6 +68,7 @@ let buildInstructionSelections = (
|
|
|
68
68
|
isInner: ?cfg.isInner,
|
|
69
69
|
includeTransaction: cfg.includeTransaction,
|
|
70
70
|
includeLogs: cfg.includeLogs,
|
|
71
|
+
includeTokenBalances: cfg.includeTokenBalances,
|
|
71
72
|
}: HyperSyncSolanaClient.QueryTypes.instructionSelection
|
|
72
73
|
),
|
|
73
74
|
)
|
|
@@ -224,6 +225,16 @@ let toSvmTransaction = (
|
|
|
224
225
|
version: ?tx.version,
|
|
225
226
|
}
|
|
226
227
|
|
|
228
|
+
let toSvmTokenBalance = (
|
|
229
|
+
tb: HyperSyncSolanaClient.ResponseTypes.tokenBalance,
|
|
230
|
+
): Envio.svmTokenBalance => {
|
|
231
|
+
account: ?tb.account->Option.map(SvmTypes.Pubkey.fromStringUnsafe),
|
|
232
|
+
mint: ?tb.mint->Option.map(SvmTypes.Pubkey.fromStringUnsafe),
|
|
233
|
+
owner: ?tb.owner->Option.map(SvmTypes.Pubkey.fromStringUnsafe),
|
|
234
|
+
preAmount: ?tb.preAmount,
|
|
235
|
+
postAmount: ?tb.postAmount,
|
|
236
|
+
}
|
|
237
|
+
|
|
227
238
|
// Probe the discriminator byte-length ordering longest-first. Stops at the
|
|
228
239
|
// first router hit. Falls back to the `_none` key (program-wide handler) when
|
|
229
240
|
// no discriminator-keyed handler matches.
|
|
@@ -296,6 +307,8 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientMaxRetries, clien
|
|
|
296
307
|
// startup; reused on every decoded instruction.
|
|
297
308
|
let schemaHandlesByProgram = buildSchemaHandles(eventConfigs)
|
|
298
309
|
|
|
310
|
+
let needsTokenBalances = eventConfigs->Belt.Array.some(cfg => cfg.includeTokenBalances)
|
|
311
|
+
|
|
299
312
|
let getItemsOrThrow = async (
|
|
300
313
|
~fromBlock,
|
|
301
314
|
~toBlock,
|
|
@@ -311,10 +324,22 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientMaxRetries, clien
|
|
|
311
324
|
let pageFetchRef = Hrtime.makeTimer()
|
|
312
325
|
|
|
313
326
|
let instructionSelections = buildInstructionSelections(eventConfigs)
|
|
327
|
+
let fields = if needsTokenBalances {
|
|
328
|
+
Some(
|
|
329
|
+
(
|
|
330
|
+
{
|
|
331
|
+
tokenBalance: [Slot, TransactionIndex, Account, Mint, Owner, PreAmount, PostAmount],
|
|
332
|
+
}: HyperSyncSolanaClient.QueryTypes.fieldSelection
|
|
333
|
+
),
|
|
334
|
+
)
|
|
335
|
+
} else {
|
|
336
|
+
None
|
|
337
|
+
}
|
|
314
338
|
let query: HyperSyncSolanaClient.query = {
|
|
315
339
|
fromSlot: fromBlock,
|
|
316
340
|
toSlot: ?toBlock,
|
|
317
341
|
instructions: instructionSelections,
|
|
342
|
+
fields: ?fields,
|
|
318
343
|
}
|
|
319
344
|
|
|
320
345
|
Prometheus.SourceRequestCount.increment(
|
|
@@ -374,6 +399,21 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientMaxRetries, clien
|
|
|
374
399
|
}
|
|
375
400
|
})
|
|
376
401
|
|
|
402
|
+
let tokenBalancesByTx = Dict.make()
|
|
403
|
+
if needsTokenBalances {
|
|
404
|
+
resp.data.tokenBalances->Belt.Array.forEach(tb => {
|
|
405
|
+
switch tb.transactionIndex {
|
|
406
|
+
| Some(txIdx) =>
|
|
407
|
+
let key = tb.slot->Int.toString ++ ":" ++ txIdx->Int.toString
|
|
408
|
+
switch tokenBalancesByTx->Dict.get(key) {
|
|
409
|
+
| Some(existing) => existing->Array.push(tb)
|
|
410
|
+
| None => tokenBalancesByTx->Dict.set(key, [tb])
|
|
411
|
+
}
|
|
412
|
+
| None => ()
|
|
413
|
+
}
|
|
414
|
+
})
|
|
415
|
+
}
|
|
416
|
+
|
|
377
417
|
let parsedQueueItems = []
|
|
378
418
|
resp.data.instructions->Belt.Array.forEach(instr => {
|
|
379
419
|
let programId = instr.programId->SvmTypes.Pubkey.fromStringUnsafe
|
|
@@ -399,6 +439,17 @@ let make = ({chain, endpointUrl, apiToken, eventConfigs, clientMaxRetries, clien
|
|
|
399
439
|
instr.slot->Int.toString ++ ":" ++ instr.transactionIndex->Int.toString
|
|
400
440
|
let maybeTx =
|
|
401
441
|
txByKey->Utils.Dict.dangerouslyGetNonOption(txKey)->Option.map(toSvmTransaction)
|
|
442
|
+
let maybeTx = if eventConfig.includeTokenBalances {
|
|
443
|
+
maybeTx->Option.map(tx => {
|
|
444
|
+
let maybeBalances =
|
|
445
|
+
tokenBalancesByTx
|
|
446
|
+
->Utils.Dict.dangerouslyGetNonOption(txKey)
|
|
447
|
+
->Option.map(bals => bals->Array.map(toSvmTokenBalance))
|
|
448
|
+
{...tx, tokenBalances: ?maybeBalances}
|
|
449
|
+
})
|
|
450
|
+
} else {
|
|
451
|
+
maybeTx
|
|
452
|
+
}
|
|
402
453
|
let logKey =
|
|
403
454
|
instr.slot->Int.toString ++
|
|
404
455
|
":" ++
|
|
@@ -118,7 +118,8 @@ function buildInstructionSelections(eventConfigs) {
|
|
|
118
118
|
a5: a5,
|
|
119
119
|
isInner: cfg.isInner,
|
|
120
120
|
includeTransaction: cfg.includeTransaction,
|
|
121
|
-
includeLogs: cfg.includeLogs
|
|
121
|
+
includeLogs: cfg.includeLogs,
|
|
122
|
+
includeTokenBalances: cfg.includeTokenBalances
|
|
122
123
|
};
|
|
123
124
|
});
|
|
124
125
|
}
|
|
@@ -230,6 +231,16 @@ function toSvmTransaction(tx) {
|
|
|
230
231
|
};
|
|
231
232
|
}
|
|
232
233
|
|
|
234
|
+
function toSvmTokenBalance(tb) {
|
|
235
|
+
return {
|
|
236
|
+
account: Stdlib_Option.map(tb.account, prim => prim),
|
|
237
|
+
mint: Stdlib_Option.map(tb.mint, prim => prim),
|
|
238
|
+
owner: Stdlib_Option.map(tb.owner, prim => prim),
|
|
239
|
+
preAmount: tb.preAmount,
|
|
240
|
+
postAmount: tb.postAmount
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
|
|
233
244
|
function probeRouter(router, programId, instr, byteLengthsDesc, contractAddress, indexingAddresses) {
|
|
234
245
|
let probe = dN => {
|
|
235
246
|
let tag = EventRouter.getSvmEventId(programId, dN);
|
|
@@ -279,15 +290,28 @@ function make(param) {
|
|
|
279
290
|
orderingByProgram[o.programId] = o.byteLengthsDesc;
|
|
280
291
|
});
|
|
281
292
|
let schemaHandlesByProgram = buildSchemaHandles(eventConfigs);
|
|
293
|
+
let needsTokenBalances = Belt_Array.some(eventConfigs, cfg => cfg.includeTokenBalances);
|
|
282
294
|
let getItemsOrThrow = async (fromBlock, toBlock, param, indexingAddresses, knownHeight, param$1, param$2, retry, logger) => {
|
|
283
295
|
let totalTimeRef = Hrtime.makeTimer();
|
|
284
296
|
let pageFetchRef = Hrtime.makeTimer();
|
|
285
297
|
let instructionSelections = buildInstructionSelections(eventConfigs);
|
|
298
|
+
let fields = needsTokenBalances ? ({
|
|
299
|
+
tokenBalance: [
|
|
300
|
+
"slot",
|
|
301
|
+
"transaction_index",
|
|
302
|
+
"account",
|
|
303
|
+
"mint",
|
|
304
|
+
"owner",
|
|
305
|
+
"pre_amount",
|
|
306
|
+
"post_amount"
|
|
307
|
+
]
|
|
308
|
+
}) : undefined;
|
|
286
309
|
let query_instructions = instructionSelections;
|
|
287
310
|
let query = {
|
|
288
311
|
fromSlot: fromBlock,
|
|
289
312
|
toSlot: toBlock,
|
|
290
|
-
instructions: query_instructions
|
|
313
|
+
instructions: query_instructions,
|
|
314
|
+
fields: fields
|
|
291
315
|
};
|
|
292
316
|
Prometheus.SourceRequestCount.increment(name, chain, "getInstructions");
|
|
293
317
|
let resp;
|
|
@@ -335,6 +359,22 @@ function make(param) {
|
|
|
335
359
|
logsByKey[key] = [log];
|
|
336
360
|
}
|
|
337
361
|
});
|
|
362
|
+
let tokenBalancesByTx = {};
|
|
363
|
+
if (needsTokenBalances) {
|
|
364
|
+
Belt_Array.forEach(resp.data.tokenBalances, tb => {
|
|
365
|
+
let txIdx = tb.transactionIndex;
|
|
366
|
+
if (txIdx === undefined) {
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
let key = tb.slot.toString() + ":" + txIdx.toString();
|
|
370
|
+
let existing = tokenBalancesByTx[key];
|
|
371
|
+
if (existing !== undefined) {
|
|
372
|
+
existing.push(tb);
|
|
373
|
+
} else {
|
|
374
|
+
tokenBalancesByTx[key] = [tb];
|
|
375
|
+
}
|
|
376
|
+
});
|
|
377
|
+
}
|
|
338
378
|
let parsedQueueItems = [];
|
|
339
379
|
Belt_Array.forEach(resp.data.instructions, instr => {
|
|
340
380
|
let programId = instr.programId;
|
|
@@ -344,6 +384,12 @@ function make(param) {
|
|
|
344
384
|
if (maybeConfig !== undefined) {
|
|
345
385
|
let txKey = instr.slot.toString() + ":" + instr.transactionIndex.toString();
|
|
346
386
|
let maybeTx = Stdlib_Option.map(txByKey[txKey], toSvmTransaction);
|
|
387
|
+
let maybeTx$1 = maybeConfig.includeTokenBalances ? Stdlib_Option.map(maybeTx, tx => {
|
|
388
|
+
let maybeBalances = Stdlib_Option.map(tokenBalancesByTx[txKey], bals => bals.map(toSvmTokenBalance));
|
|
389
|
+
let newrecord = {...tx};
|
|
390
|
+
newrecord.tokenBalances = maybeBalances;
|
|
391
|
+
return newrecord;
|
|
392
|
+
}) : maybeTx;
|
|
347
393
|
let logKey = instr.slot.toString() + ":" + instr.transactionIndex.toString() + ":" + serializeInstructionAddress(instr.instructionAddress);
|
|
348
394
|
let maybeLogs = Stdlib_Option.map(logsByKey[logKey], logs => logs.map(log => ({
|
|
349
395
|
kind: Stdlib_Option.getOr(log.kind, ""),
|
|
@@ -352,7 +398,7 @@ function make(param) {
|
|
|
352
398
|
let payload_contractName = maybeConfig.contractName;
|
|
353
399
|
let payload_eventName = maybeConfig.name;
|
|
354
400
|
let payload_instruction = toSvmInstruction(instr, schemaHandlesByProgram);
|
|
355
|
-
let payload_transaction = maybeConfig.includeTransaction ? maybeTx : undefined;
|
|
401
|
+
let payload_transaction = maybeConfig.includeTransaction ? maybeTx$1 : undefined;
|
|
356
402
|
let payload_logs = maybeConfig.includeLogs ? maybeLogs : undefined;
|
|
357
403
|
let payload_slot = instr.slot;
|
|
358
404
|
let payload_block = {
|
|
@@ -435,6 +481,7 @@ export {
|
|
|
435
481
|
decodeIfPossible,
|
|
436
482
|
toSvmInstruction,
|
|
437
483
|
toSvmTransaction,
|
|
484
|
+
toSvmTokenBalance,
|
|
438
485
|
probeRouter,
|
|
439
486
|
make,
|
|
440
487
|
}
|
package/svm.schema.json
CHANGED
|
@@ -287,6 +287,13 @@
|
|
|
287
287
|
"null"
|
|
288
288
|
]
|
|
289
289
|
},
|
|
290
|
+
"include_token_balances": {
|
|
291
|
+
"description": "When true, also fetch SPL Token / Token-2022 balance snapshots for the parent transaction. Implies include_transaction: true.",
|
|
292
|
+
"type": [
|
|
293
|
+
"boolean",
|
|
294
|
+
"null"
|
|
295
|
+
]
|
|
296
|
+
},
|
|
290
297
|
"accounts": {
|
|
291
298
|
"description": "Optional positional account names. The Nth entry names account slot N on the dispatched instruction; surfaces as `event.instruction.decoded.accounts.<name>`. Accounts beyond the named list become `extra_accounts`.",
|
|
292
299
|
"type": [
|