@secondlayer/subgraphs 4.1.4 → 4.1.5
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/dist/src/index.d.ts +4 -2
- package/dist/src/index.js +3070 -3066
- package/dist/src/index.js.map +7 -7
- package/dist/src/runtime/block-processor.js +706 -372
- package/dist/src/runtime/block-processor.js.map +7 -6
- package/dist/src/runtime/catchup.js +706 -372
- package/dist/src/runtime/catchup.js.map +7 -6
- package/dist/src/runtime/processor.js +706 -372
- package/dist/src/runtime/processor.js.map +7 -6
- package/dist/src/runtime/reindex.js +706 -372
- package/dist/src/runtime/reindex.js.map +7 -6
- package/dist/src/runtime/reorg.js +706 -372
- package/dist/src/runtime/reorg.js.map +7 -6
- package/dist/src/runtime/replay.js +12 -6
- package/dist/src/runtime/replay.js.map +4 -4
- package/dist/src/runtime/runner.js +604 -8
- package/dist/src/runtime/runner.js.map +6 -4
- package/dist/src/runtime/source-matcher.d.ts +8 -1
- package/dist/src/runtime/source-matcher.js +12 -5
- package/dist/src/runtime/source-matcher.js.map +3 -3
- package/dist/src/service.js +706 -372
- package/dist/src/service.js.map +7 -6
- package/dist/src/testing/index.js +604 -8
- package/dist/src/testing/index.js.map +6 -4
- package/package.json +1 -1
|
@@ -942,15 +942,614 @@ function decodeFunctionArgs(args) {
|
|
|
942
942
|
return args.map(decodeClarityValue);
|
|
943
943
|
}
|
|
944
944
|
|
|
945
|
+
// src/runtime/source-matcher.ts
|
|
946
|
+
var patternCache = new Map;
|
|
947
|
+
function matchAny(value, pattern) {
|
|
948
|
+
return Array.isArray(pattern) ? pattern.some((p) => matchPattern(value, p)) : matchPattern(value, pattern);
|
|
949
|
+
}
|
|
950
|
+
function matchPattern(value, pattern) {
|
|
951
|
+
if (!pattern.includes("*"))
|
|
952
|
+
return value === pattern;
|
|
953
|
+
let re = patternCache.get(pattern);
|
|
954
|
+
if (!re) {
|
|
955
|
+
const regex = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
|
|
956
|
+
re = new RegExp(`^${regex}$`);
|
|
957
|
+
patternCache.set(pattern, re);
|
|
958
|
+
}
|
|
959
|
+
return re.test(value);
|
|
960
|
+
}
|
|
961
|
+
var EMPTY_SET = new Set;
|
|
962
|
+
function factoryAllows(filter, contractId, factoryContracts) {
|
|
963
|
+
if (!filter.factory)
|
|
964
|
+
return true;
|
|
965
|
+
if (!contractId)
|
|
966
|
+
return false;
|
|
967
|
+
return (factoryContracts.get(filter.factory.from) ?? EMPTY_SET).has(contractId);
|
|
968
|
+
}
|
|
969
|
+
function readPath(payload, path) {
|
|
970
|
+
let cur = payload;
|
|
971
|
+
for (const key of path.split(".")) {
|
|
972
|
+
if (cur === null || typeof cur !== "object")
|
|
973
|
+
return;
|
|
974
|
+
cur = cur[key];
|
|
975
|
+
}
|
|
976
|
+
return cur;
|
|
977
|
+
}
|
|
978
|
+
function traitAllows(filter, contractId, traitContracts) {
|
|
979
|
+
const trait = filter.trait;
|
|
980
|
+
if (!trait)
|
|
981
|
+
return true;
|
|
982
|
+
if (!contractId)
|
|
983
|
+
return false;
|
|
984
|
+
return (traitContracts.get(trait) ?? EMPTY_SET).has(contractId);
|
|
985
|
+
}
|
|
986
|
+
function assetContract(assetId) {
|
|
987
|
+
return assetId?.split("::")[0];
|
|
988
|
+
}
|
|
989
|
+
function printContractId(data) {
|
|
990
|
+
if (!data)
|
|
991
|
+
return;
|
|
992
|
+
const id = (typeof data.contract_identifier === "string" ? data.contract_identifier : undefined) ?? (typeof data.contract_id === "string" ? data.contract_id : undefined);
|
|
993
|
+
return id || undefined;
|
|
994
|
+
}
|
|
995
|
+
function matchFilter(filter, transactions, eventsByTx, traitContracts, factoryContracts = new Map) {
|
|
996
|
+
const results = [];
|
|
997
|
+
switch (filter.type) {
|
|
998
|
+
case "stx_transfer":
|
|
999
|
+
case "stx_mint":
|
|
1000
|
+
case "stx_burn":
|
|
1001
|
+
case "stx_lock": {
|
|
1002
|
+
const eventType = `${filter.type}_event`;
|
|
1003
|
+
for (const tx of transactions) {
|
|
1004
|
+
const txEvents = eventsByTx.get(tx.tx_id) ?? [];
|
|
1005
|
+
const matched = txEvents.filter((e) => e.type === eventType);
|
|
1006
|
+
if (matched.length === 0)
|
|
1007
|
+
continue;
|
|
1008
|
+
const filtered = matched.filter((e) => {
|
|
1009
|
+
const data = e.data;
|
|
1010
|
+
if (!data)
|
|
1011
|
+
return false;
|
|
1012
|
+
if ("sender" in filter && filter.sender) {
|
|
1013
|
+
if (!matchPattern(data.sender, filter.sender))
|
|
1014
|
+
return false;
|
|
1015
|
+
}
|
|
1016
|
+
if ("recipient" in filter && filter.recipient) {
|
|
1017
|
+
if (!matchPattern(data.recipient, filter.recipient))
|
|
1018
|
+
return false;
|
|
1019
|
+
}
|
|
1020
|
+
if ("lockedAddress" in filter && filter.lockedAddress) {
|
|
1021
|
+
if (!matchPattern(data.locked_address, filter.lockedAddress))
|
|
1022
|
+
return false;
|
|
1023
|
+
}
|
|
1024
|
+
if ("minAmount" in filter && filter.minAmount !== undefined) {
|
|
1025
|
+
const amount = BigInt(data.amount ?? data.locked_amount ?? "0");
|
|
1026
|
+
if (amount < filter.minAmount)
|
|
1027
|
+
return false;
|
|
1028
|
+
}
|
|
1029
|
+
if ("maxAmount" in filter && filter.maxAmount !== undefined) {
|
|
1030
|
+
const amount = BigInt(data.amount ?? "0");
|
|
1031
|
+
if (amount > filter.maxAmount)
|
|
1032
|
+
return false;
|
|
1033
|
+
}
|
|
1034
|
+
return true;
|
|
1035
|
+
});
|
|
1036
|
+
if (filtered.length > 0) {
|
|
1037
|
+
results.push({ tx, events: filtered });
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
break;
|
|
1041
|
+
}
|
|
1042
|
+
case "ft_transfer":
|
|
1043
|
+
case "ft_mint":
|
|
1044
|
+
case "ft_burn": {
|
|
1045
|
+
const eventType = `${filter.type}_event`;
|
|
1046
|
+
for (const tx of transactions) {
|
|
1047
|
+
const txEvents = eventsByTx.get(tx.tx_id) ?? [];
|
|
1048
|
+
const matched = txEvents.filter((e) => {
|
|
1049
|
+
if (e.type !== eventType)
|
|
1050
|
+
return false;
|
|
1051
|
+
const data = e.data;
|
|
1052
|
+
if (!data)
|
|
1053
|
+
return false;
|
|
1054
|
+
if (filter.assetIdentifier) {
|
|
1055
|
+
const assetId = data.asset_identifier;
|
|
1056
|
+
if (!assetId || !matchPattern(assetId, filter.assetIdentifier))
|
|
1057
|
+
return false;
|
|
1058
|
+
}
|
|
1059
|
+
if (!traitAllows(filter, assetContract(data.asset_identifier), traitContracts))
|
|
1060
|
+
return false;
|
|
1061
|
+
if ("sender" in filter && filter.sender) {
|
|
1062
|
+
if (!matchPattern(data.sender, filter.sender))
|
|
1063
|
+
return false;
|
|
1064
|
+
}
|
|
1065
|
+
if ("recipient" in filter && filter.recipient) {
|
|
1066
|
+
if (!matchPattern(data.recipient, filter.recipient))
|
|
1067
|
+
return false;
|
|
1068
|
+
}
|
|
1069
|
+
if (filter.minAmount !== undefined) {
|
|
1070
|
+
const amount = BigInt(data.amount ?? "0");
|
|
1071
|
+
if (amount < filter.minAmount)
|
|
1072
|
+
return false;
|
|
1073
|
+
}
|
|
1074
|
+
return true;
|
|
1075
|
+
});
|
|
1076
|
+
if (matched.length > 0) {
|
|
1077
|
+
results.push({ tx, events: matched });
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
break;
|
|
1081
|
+
}
|
|
1082
|
+
case "nft_transfer":
|
|
1083
|
+
case "nft_mint":
|
|
1084
|
+
case "nft_burn": {
|
|
1085
|
+
const eventType = `${filter.type}_event`;
|
|
1086
|
+
for (const tx of transactions) {
|
|
1087
|
+
const txEvents = eventsByTx.get(tx.tx_id) ?? [];
|
|
1088
|
+
const matched = txEvents.filter((e) => {
|
|
1089
|
+
if (e.type !== eventType)
|
|
1090
|
+
return false;
|
|
1091
|
+
const data = e.data;
|
|
1092
|
+
if (!data)
|
|
1093
|
+
return false;
|
|
1094
|
+
if (filter.assetIdentifier) {
|
|
1095
|
+
const assetId = data.asset_identifier;
|
|
1096
|
+
if (!assetId || !matchPattern(assetId, filter.assetIdentifier))
|
|
1097
|
+
return false;
|
|
1098
|
+
}
|
|
1099
|
+
if (!traitAllows(filter, assetContract(data.asset_identifier), traitContracts))
|
|
1100
|
+
return false;
|
|
1101
|
+
if ("sender" in filter && filter.sender) {
|
|
1102
|
+
if (!matchPattern(data.sender, filter.sender))
|
|
1103
|
+
return false;
|
|
1104
|
+
}
|
|
1105
|
+
if ("recipient" in filter && filter.recipient) {
|
|
1106
|
+
if (!matchPattern(data.recipient, filter.recipient))
|
|
1107
|
+
return false;
|
|
1108
|
+
}
|
|
1109
|
+
return true;
|
|
1110
|
+
});
|
|
1111
|
+
if (matched.length > 0) {
|
|
1112
|
+
results.push({ tx, events: matched });
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
break;
|
|
1116
|
+
}
|
|
1117
|
+
case "contract_call": {
|
|
1118
|
+
for (const tx of transactions) {
|
|
1119
|
+
if (tx.type !== "contract_call")
|
|
1120
|
+
continue;
|
|
1121
|
+
if (filter.contractId) {
|
|
1122
|
+
if (!tx.contract_id || !matchAny(tx.contract_id, filter.contractId))
|
|
1123
|
+
continue;
|
|
1124
|
+
}
|
|
1125
|
+
if (!factoryAllows(filter, tx.contract_id, factoryContracts))
|
|
1126
|
+
continue;
|
|
1127
|
+
if (filter.functionName) {
|
|
1128
|
+
if (!tx.function_name || !matchPattern(tx.function_name, filter.functionName))
|
|
1129
|
+
continue;
|
|
1130
|
+
}
|
|
1131
|
+
if (filter.caller) {
|
|
1132
|
+
if (!matchPattern(tx.sender, filter.caller))
|
|
1133
|
+
continue;
|
|
1134
|
+
}
|
|
1135
|
+
if (!traitAllows(filter, tx.contract_id, traitContracts))
|
|
1136
|
+
continue;
|
|
1137
|
+
const txEvents = eventsByTx.get(tx.tx_id) ?? [];
|
|
1138
|
+
results.push({ tx, events: txEvents });
|
|
1139
|
+
}
|
|
1140
|
+
break;
|
|
1141
|
+
}
|
|
1142
|
+
case "contract_deploy": {
|
|
1143
|
+
for (const tx of transactions) {
|
|
1144
|
+
if (tx.type !== "smart_contract")
|
|
1145
|
+
continue;
|
|
1146
|
+
if (filter.deployer) {
|
|
1147
|
+
if (!matchPattern(tx.sender, filter.deployer))
|
|
1148
|
+
continue;
|
|
1149
|
+
}
|
|
1150
|
+
if (filter.contractName) {
|
|
1151
|
+
const name = tx.contract_id?.split(".")[1] ?? "";
|
|
1152
|
+
if (!matchPattern(name, filter.contractName))
|
|
1153
|
+
continue;
|
|
1154
|
+
}
|
|
1155
|
+
const txEvents = eventsByTx.get(tx.tx_id) ?? [];
|
|
1156
|
+
results.push({ tx, events: txEvents });
|
|
1157
|
+
}
|
|
1158
|
+
break;
|
|
1159
|
+
}
|
|
1160
|
+
case "print_event": {
|
|
1161
|
+
for (const tx of transactions) {
|
|
1162
|
+
const txEvents = eventsByTx.get(tx.tx_id) ?? [];
|
|
1163
|
+
const matched = txEvents.filter((e) => {
|
|
1164
|
+
if (e.type !== "smart_contract_event" && e.type !== "contract_event")
|
|
1165
|
+
return false;
|
|
1166
|
+
const data = e.data;
|
|
1167
|
+
if (!data)
|
|
1168
|
+
return false;
|
|
1169
|
+
if (data.topic !== "print")
|
|
1170
|
+
return false;
|
|
1171
|
+
const contractId = printContractId(data);
|
|
1172
|
+
if (filter.contractId) {
|
|
1173
|
+
if (!contractId || !matchAny(contractId, filter.contractId))
|
|
1174
|
+
return false;
|
|
1175
|
+
}
|
|
1176
|
+
if (!traitAllows(filter, contractId, traitContracts))
|
|
1177
|
+
return false;
|
|
1178
|
+
if (!factoryAllows(filter, contractId, factoryContracts))
|
|
1179
|
+
return false;
|
|
1180
|
+
return true;
|
|
1181
|
+
});
|
|
1182
|
+
if (matched.length > 0) {
|
|
1183
|
+
results.push({ tx, events: matched });
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
break;
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
return results;
|
|
1190
|
+
}
|
|
1191
|
+
function matchSources(sources, transactions, events, traitContracts = new Map, factoryContracts = new Map) {
|
|
1192
|
+
const eventsByTx = new Map;
|
|
1193
|
+
for (const event of events) {
|
|
1194
|
+
const list = eventsByTx.get(event.tx_id) ?? [];
|
|
1195
|
+
list.push(event);
|
|
1196
|
+
eventsByTx.set(event.tx_id, list);
|
|
1197
|
+
}
|
|
1198
|
+
const seen = new Set;
|
|
1199
|
+
const results = [];
|
|
1200
|
+
for (const [sourceName, filter] of Object.entries(sources)) {
|
|
1201
|
+
const matches = matchFilter(filter, transactions, eventsByTx, traitContracts, factoryContracts);
|
|
1202
|
+
for (const match of matches) {
|
|
1203
|
+
const dedupeKey = `${match.tx.tx_id}:${sourceName}`;
|
|
1204
|
+
if (!seen.has(dedupeKey)) {
|
|
1205
|
+
seen.add(dedupeKey);
|
|
1206
|
+
results.push({ ...match, sourceName });
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
return results;
|
|
1211
|
+
}
|
|
1212
|
+
|
|
945
1213
|
// src/runtime/runner.ts
|
|
946
1214
|
import { getErrorMessage } from "@secondlayer/shared";
|
|
947
1215
|
import { logger as logger3 } from "@secondlayer/shared/logger";
|
|
948
1216
|
import {
|
|
949
1217
|
clarityValueToJS,
|
|
950
|
-
deserializeCV as
|
|
1218
|
+
deserializeCV as deserializeCV4,
|
|
951
1219
|
toCamelCase as toCamelCase2
|
|
952
1220
|
} from "@secondlayer/stacks/clarity";
|
|
953
1221
|
|
|
1222
|
+
// src/print-schema.ts
|
|
1223
|
+
import {
|
|
1224
|
+
deserializeCV as deserializeCV3
|
|
1225
|
+
} from "@secondlayer/stacks/clarity";
|
|
1226
|
+
function camelizeDataKey(str) {
|
|
1227
|
+
return str.replace(/-([a-z0-9])/g, (_, c) => c.toUpperCase());
|
|
1228
|
+
}
|
|
1229
|
+
function cvToTree(cv) {
|
|
1230
|
+
switch (cv.type) {
|
|
1231
|
+
case "uint":
|
|
1232
|
+
case "int":
|
|
1233
|
+
return { kind: cv.type };
|
|
1234
|
+
case "true":
|
|
1235
|
+
case "false":
|
|
1236
|
+
return { kind: "bool" };
|
|
1237
|
+
case "address":
|
|
1238
|
+
case "contract":
|
|
1239
|
+
return { kind: "principal" };
|
|
1240
|
+
case "buffer":
|
|
1241
|
+
return { kind: "buffer", len: cv.value.length / 2 };
|
|
1242
|
+
case "ascii":
|
|
1243
|
+
return { kind: "ascii", len: cv.value.length };
|
|
1244
|
+
case "utf8":
|
|
1245
|
+
return { kind: "utf8", len: new TextEncoder().encode(cv.value).length };
|
|
1246
|
+
case "none":
|
|
1247
|
+
return { kind: "optional", inner: null };
|
|
1248
|
+
case "some":
|
|
1249
|
+
return { kind: "optional", inner: cvToTree(cv.value) };
|
|
1250
|
+
case "ok":
|
|
1251
|
+
return { kind: "response", ok: cvToTree(cv.value), err: null };
|
|
1252
|
+
case "err":
|
|
1253
|
+
return { kind: "response", ok: null, err: cvToTree(cv.value) };
|
|
1254
|
+
case "list": {
|
|
1255
|
+
let inner = null;
|
|
1256
|
+
for (const el of cv.value) {
|
|
1257
|
+
const t = cvToTree(el);
|
|
1258
|
+
inner = inner ? unify(inner, t) : t;
|
|
1259
|
+
}
|
|
1260
|
+
return { kind: "list", inner };
|
|
1261
|
+
}
|
|
1262
|
+
case "tuple": {
|
|
1263
|
+
const entries = new Map;
|
|
1264
|
+
for (const [k, v] of Object.entries(cv.value)) {
|
|
1265
|
+
entries.set(k, { tree: cvToTree(v), present: 1 });
|
|
1266
|
+
}
|
|
1267
|
+
return { kind: "tuple", count: 1, entries };
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
function unifyNullable(a, b) {
|
|
1272
|
+
if (!a)
|
|
1273
|
+
return b;
|
|
1274
|
+
if (!b)
|
|
1275
|
+
return a;
|
|
1276
|
+
return unify(a, b);
|
|
1277
|
+
}
|
|
1278
|
+
var UNION_KIND_ORDER = {
|
|
1279
|
+
uint: 0,
|
|
1280
|
+
int: 1,
|
|
1281
|
+
bool: 2,
|
|
1282
|
+
principal: 3,
|
|
1283
|
+
buffer: 4,
|
|
1284
|
+
ascii: 5,
|
|
1285
|
+
utf8: 6,
|
|
1286
|
+
list: 7,
|
|
1287
|
+
tuple: 8,
|
|
1288
|
+
response: 9
|
|
1289
|
+
};
|
|
1290
|
+
function makeUnion(members) {
|
|
1291
|
+
const sorted = [...members].sort((a, b) => (UNION_KIND_ORDER[a.kind] ?? 99) - (UNION_KIND_ORDER[b.kind] ?? 99));
|
|
1292
|
+
return { kind: "union", members: sorted };
|
|
1293
|
+
}
|
|
1294
|
+
function unify(a, b) {
|
|
1295
|
+
if (a.kind === "optional" || b.kind === "optional") {
|
|
1296
|
+
const ai = a.kind === "optional" ? a.inner : a;
|
|
1297
|
+
const bi = b.kind === "optional" ? b.inner : b;
|
|
1298
|
+
return { kind: "optional", inner: unifyNullable(ai, bi) };
|
|
1299
|
+
}
|
|
1300
|
+
if (a.kind === "union")
|
|
1301
|
+
return unionAdd(a.members, b);
|
|
1302
|
+
if (b.kind === "union")
|
|
1303
|
+
return unionAdd(b.members, a);
|
|
1304
|
+
if (a.kind !== b.kind)
|
|
1305
|
+
return makeUnion([a, b]);
|
|
1306
|
+
switch (a.kind) {
|
|
1307
|
+
case "uint":
|
|
1308
|
+
case "int":
|
|
1309
|
+
case "bool":
|
|
1310
|
+
case "principal":
|
|
1311
|
+
return a;
|
|
1312
|
+
case "buffer":
|
|
1313
|
+
case "ascii":
|
|
1314
|
+
case "utf8":
|
|
1315
|
+
return { kind: a.kind, len: Math.max(a.len, b.len) };
|
|
1316
|
+
case "list":
|
|
1317
|
+
return {
|
|
1318
|
+
kind: "list",
|
|
1319
|
+
inner: unifyNullable(a.inner, b.inner)
|
|
1320
|
+
};
|
|
1321
|
+
case "response": {
|
|
1322
|
+
const rb = b;
|
|
1323
|
+
return {
|
|
1324
|
+
kind: "response",
|
|
1325
|
+
ok: unifyNullable(a.ok, rb.ok),
|
|
1326
|
+
err: unifyNullable(a.err, rb.err)
|
|
1327
|
+
};
|
|
1328
|
+
}
|
|
1329
|
+
case "tuple": {
|
|
1330
|
+
const tb = b;
|
|
1331
|
+
const entries = new Map([...a.entries].map(([k, e]) => [k, { ...e }]));
|
|
1332
|
+
for (const [k, e] of tb.entries) {
|
|
1333
|
+
const existing = entries.get(k);
|
|
1334
|
+
entries.set(k, existing ? {
|
|
1335
|
+
tree: unify(existing.tree, e.tree),
|
|
1336
|
+
present: existing.present + e.present
|
|
1337
|
+
} : { ...e });
|
|
1338
|
+
}
|
|
1339
|
+
return { kind: "tuple", count: a.count + tb.count, entries };
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
function unionAdd(members, t) {
|
|
1344
|
+
if (t.kind === "union") {
|
|
1345
|
+
let acc = { kind: "union", members };
|
|
1346
|
+
for (const m of t.members)
|
|
1347
|
+
acc = unify(acc, m);
|
|
1348
|
+
return acc;
|
|
1349
|
+
}
|
|
1350
|
+
const next = [...members];
|
|
1351
|
+
for (let i = 0;i < next.length; i++) {
|
|
1352
|
+
const member = next[i];
|
|
1353
|
+
if (!member)
|
|
1354
|
+
continue;
|
|
1355
|
+
const merged = unify(member, t);
|
|
1356
|
+
if (merged.kind !== "union") {
|
|
1357
|
+
next[i] = merged;
|
|
1358
|
+
return makeUnion(next);
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
next.push(t);
|
|
1362
|
+
return makeUnion(next);
|
|
1363
|
+
}
|
|
1364
|
+
function wrapOptional(t) {
|
|
1365
|
+
return t.kind === "optional" ? t : { kind: "optional", inner: t };
|
|
1366
|
+
}
|
|
1367
|
+
function renderClarity(t) {
|
|
1368
|
+
if (!t)
|
|
1369
|
+
return "?";
|
|
1370
|
+
switch (t.kind) {
|
|
1371
|
+
case "uint":
|
|
1372
|
+
case "int":
|
|
1373
|
+
case "bool":
|
|
1374
|
+
case "principal":
|
|
1375
|
+
return t.kind;
|
|
1376
|
+
case "buffer":
|
|
1377
|
+
return `(buff ${t.len})`;
|
|
1378
|
+
case "ascii":
|
|
1379
|
+
return `(string-ascii ${t.len})`;
|
|
1380
|
+
case "utf8":
|
|
1381
|
+
return `(string-utf8 ${t.len})`;
|
|
1382
|
+
case "optional":
|
|
1383
|
+
return `(optional ${renderClarity(t.inner)})`;
|
|
1384
|
+
case "list":
|
|
1385
|
+
return `(list ${renderClarity(t.inner)})`;
|
|
1386
|
+
case "response":
|
|
1387
|
+
return `(response ${renderClarity(t.ok)} ${renderClarity(t.err)})`;
|
|
1388
|
+
case "tuple": {
|
|
1389
|
+
const parts = [...t.entries].map(([k, e]) => {
|
|
1390
|
+
const tree = e.present < t.count ? wrapOptional(e.tree) : e.tree;
|
|
1391
|
+
return `(${k} ${renderClarity(tree)})`;
|
|
1392
|
+
});
|
|
1393
|
+
return `(tuple ${parts.join(" ")})`;
|
|
1394
|
+
}
|
|
1395
|
+
case "union":
|
|
1396
|
+
return t.members.map(renderClarity).join(" | ");
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1399
|
+
function renderTs(t) {
|
|
1400
|
+
if (!t)
|
|
1401
|
+
return "unknown";
|
|
1402
|
+
switch (t.kind) {
|
|
1403
|
+
case "uint":
|
|
1404
|
+
case "int":
|
|
1405
|
+
return "bigint";
|
|
1406
|
+
case "bool":
|
|
1407
|
+
return "boolean";
|
|
1408
|
+
case "principal":
|
|
1409
|
+
case "buffer":
|
|
1410
|
+
case "ascii":
|
|
1411
|
+
case "utf8":
|
|
1412
|
+
return "string";
|
|
1413
|
+
case "optional":
|
|
1414
|
+
return t.inner ? `${renderTs(t.inner)} | null` : "unknown | null";
|
|
1415
|
+
case "list": {
|
|
1416
|
+
const inner = renderTs(t.inner);
|
|
1417
|
+
return inner.includes(" | ") ? `(${inner})[]` : `${inner}[]`;
|
|
1418
|
+
}
|
|
1419
|
+
case "response": {
|
|
1420
|
+
const sides = [...new Set([renderTs(t.ok), renderTs(t.err)])];
|
|
1421
|
+
return sides.join(" | ");
|
|
1422
|
+
}
|
|
1423
|
+
case "tuple": {
|
|
1424
|
+
const parts = [...t.entries].map(([k, e]) => {
|
|
1425
|
+
const opt = e.present < t.count ? "?" : "";
|
|
1426
|
+
return `${camelizeDataKey(k)}${opt}: ${renderTs(e.tree)}`;
|
|
1427
|
+
});
|
|
1428
|
+
return `{ ${parts.join("; ")} }`;
|
|
1429
|
+
}
|
|
1430
|
+
case "union":
|
|
1431
|
+
return [...new Set(t.members.map((m) => renderTs(m)))].join(" | ");
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1434
|
+
function toColumnType(t) {
|
|
1435
|
+
if (!t)
|
|
1436
|
+
return "jsonb";
|
|
1437
|
+
switch (t.kind) {
|
|
1438
|
+
case "uint":
|
|
1439
|
+
return "uint";
|
|
1440
|
+
case "int":
|
|
1441
|
+
return "int";
|
|
1442
|
+
case "bool":
|
|
1443
|
+
return "boolean";
|
|
1444
|
+
case "principal":
|
|
1445
|
+
return "principal";
|
|
1446
|
+
case "buffer":
|
|
1447
|
+
case "ascii":
|
|
1448
|
+
case "utf8":
|
|
1449
|
+
return "text";
|
|
1450
|
+
case "list":
|
|
1451
|
+
case "tuple":
|
|
1452
|
+
return "jsonb";
|
|
1453
|
+
case "optional":
|
|
1454
|
+
return toColumnType(t.inner);
|
|
1455
|
+
case "response":
|
|
1456
|
+
return t.ok ? toColumnType(t.ok) : "jsonb";
|
|
1457
|
+
case "union":
|
|
1458
|
+
return "jsonb";
|
|
1459
|
+
}
|
|
1460
|
+
}
|
|
1461
|
+
var MAX_DECODED_PER_TOPIC_NEWEST = 75;
|
|
1462
|
+
var MAX_DECODED_PER_TOPIC_OLDEST = 25;
|
|
1463
|
+
function inferPrintTopics(samples) {
|
|
1464
|
+
const groups = new Map;
|
|
1465
|
+
for (const s of samples) {
|
|
1466
|
+
const group = groups.get(s.topic);
|
|
1467
|
+
if (group)
|
|
1468
|
+
group.push(s);
|
|
1469
|
+
else
|
|
1470
|
+
groups.set(s.topic, [s]);
|
|
1471
|
+
}
|
|
1472
|
+
const out = [];
|
|
1473
|
+
for (const [topic, rows] of groups) {
|
|
1474
|
+
let first = Number.POSITIVE_INFINITY;
|
|
1475
|
+
let last = Number.NEGATIVE_INFINITY;
|
|
1476
|
+
for (const r of rows) {
|
|
1477
|
+
if (r.blockHeight < first)
|
|
1478
|
+
first = r.blockHeight;
|
|
1479
|
+
if (r.blockHeight > last)
|
|
1480
|
+
last = r.blockHeight;
|
|
1481
|
+
}
|
|
1482
|
+
const withHex = [...rows].filter((r) => r.rawHex !== null).sort((a, b) => b.blockHeight - a.blockHeight);
|
|
1483
|
+
const budget = MAX_DECODED_PER_TOPIC_NEWEST + MAX_DECODED_PER_TOPIC_OLDEST;
|
|
1484
|
+
const picked = withHex.length <= budget ? withHex : [
|
|
1485
|
+
...withHex.slice(0, MAX_DECODED_PER_TOPIC_NEWEST),
|
|
1486
|
+
...withHex.slice(-MAX_DECODED_PER_TOPIC_OLDEST)
|
|
1487
|
+
];
|
|
1488
|
+
const tuples = [];
|
|
1489
|
+
let decoded = 0;
|
|
1490
|
+
for (const p of picked) {
|
|
1491
|
+
try {
|
|
1492
|
+
const cv = deserializeCV3(p.rawHex);
|
|
1493
|
+
decoded++;
|
|
1494
|
+
if (cv.type === "tuple")
|
|
1495
|
+
tuples.push(cv);
|
|
1496
|
+
} catch {}
|
|
1497
|
+
}
|
|
1498
|
+
const nonTuple = decoded > 0 && tuples.length === 0;
|
|
1499
|
+
const fields = [];
|
|
1500
|
+
if (!nonTuple) {
|
|
1501
|
+
const stats = new Map;
|
|
1502
|
+
for (const t of tuples) {
|
|
1503
|
+
for (const [key, value] of Object.entries(t.value)) {
|
|
1504
|
+
if (key === "topic")
|
|
1505
|
+
continue;
|
|
1506
|
+
const tree = cvToTree(value);
|
|
1507
|
+
const existing = stats.get(key);
|
|
1508
|
+
if (existing) {
|
|
1509
|
+
existing.present++;
|
|
1510
|
+
existing.tree = unify(existing.tree, tree);
|
|
1511
|
+
if (value.type === "none")
|
|
1512
|
+
existing.noneCount++;
|
|
1513
|
+
if (value.type === "none" || value.type === "some") {
|
|
1514
|
+
existing.optionalSeen = true;
|
|
1515
|
+
}
|
|
1516
|
+
} else {
|
|
1517
|
+
stats.set(key, {
|
|
1518
|
+
tree,
|
|
1519
|
+
present: 1,
|
|
1520
|
+
noneCount: value.type === "none" ? 1 : 0,
|
|
1521
|
+
optionalSeen: value.type === "none" || value.type === "some"
|
|
1522
|
+
});
|
|
1523
|
+
}
|
|
1524
|
+
}
|
|
1525
|
+
}
|
|
1526
|
+
for (const [name, st] of [...stats].sort(([a], [b]) => a.localeCompare(b))) {
|
|
1527
|
+
const field = {
|
|
1528
|
+
name,
|
|
1529
|
+
camel_name: camelizeDataKey(name),
|
|
1530
|
+
clarity_type: renderClarity(st.tree),
|
|
1531
|
+
ts_type: renderTs(st.tree),
|
|
1532
|
+
column_type: toColumnType(st.tree),
|
|
1533
|
+
always_present: st.present === tuples.length
|
|
1534
|
+
};
|
|
1535
|
+
if (st.optionalSeen) {
|
|
1536
|
+
field.optional_some_rate = (st.present - st.noneCount) / st.present;
|
|
1537
|
+
}
|
|
1538
|
+
fields.push(field);
|
|
1539
|
+
}
|
|
1540
|
+
}
|
|
1541
|
+
out.push({
|
|
1542
|
+
topic,
|
|
1543
|
+
count: rows.length,
|
|
1544
|
+
first_height: first,
|
|
1545
|
+
last_height: last,
|
|
1546
|
+
non_tuple: nonTuple,
|
|
1547
|
+
fields
|
|
1548
|
+
});
|
|
1549
|
+
}
|
|
1550
|
+
return out.sort((a, b) => b.count - a.count);
|
|
1551
|
+
}
|
|
1552
|
+
|
|
954
1553
|
// src/runtime/print-validate.ts
|
|
955
1554
|
var COLUMN_TYPES = new Set([
|
|
956
1555
|
"text",
|
|
@@ -1032,9 +1631,6 @@ function validatePrintPayload(prints, topic, data) {
|
|
|
1032
1631
|
|
|
1033
1632
|
// src/runtime/runner.ts
|
|
1034
1633
|
var DEFAULT_ERROR_THRESHOLD = 50;
|
|
1035
|
-
function camelCase(str) {
|
|
1036
|
-
return str.replace(/-([a-z0-9])/g, (_, c) => c.toUpperCase());
|
|
1037
|
-
}
|
|
1038
1634
|
function camelizeKeys(obj) {
|
|
1039
1635
|
if (obj === null || obj === undefined)
|
|
1040
1636
|
return obj;
|
|
@@ -1044,7 +1640,7 @@ function camelizeKeys(obj) {
|
|
|
1044
1640
|
return obj.map(camelizeKeys);
|
|
1045
1641
|
const result = {};
|
|
1046
1642
|
for (const [k, v] of Object.entries(obj)) {
|
|
1047
|
-
result[
|
|
1643
|
+
result[camelizeDataKey(k)] = camelizeKeys(v);
|
|
1048
1644
|
}
|
|
1049
1645
|
return result;
|
|
1050
1646
|
}
|
|
@@ -1111,7 +1707,7 @@ function buildContractCallInput(filter, tx) {
|
|
|
1111
1707
|
return;
|
|
1112
1708
|
try {
|
|
1113
1709
|
const clean = hex.startsWith("0x") ? hex.slice(2) : hex;
|
|
1114
|
-
input[toCamelCase2(arg.name)] = decodeArg(arg.type,
|
|
1710
|
+
input[toCamelCase2(arg.name)] = decodeArg(arg.type, deserializeCV4(clean));
|
|
1115
1711
|
} catch {}
|
|
1116
1712
|
});
|
|
1117
1713
|
return input;
|
|
@@ -1234,7 +1830,7 @@ function buildEventPayload(filter, tx, event) {
|
|
|
1234
1830
|
const { topic: _, ...rest } = clarityObj ?? {};
|
|
1235
1831
|
const data = Object.keys(rest).length > 0 ? camelizeKeys(rest) : rawValue && typeof rawValue !== "object" ? rawValue : {};
|
|
1236
1832
|
return {
|
|
1237
|
-
contractId: decoded
|
|
1833
|
+
contractId: printContractId(decoded) ?? tx.contract_id ?? "",
|
|
1238
1834
|
topic,
|
|
1239
1835
|
data: data ?? {},
|
|
1240
1836
|
tx: txMeta
|
|
@@ -1274,380 +1870,118 @@ function buildEventPayload(filter, tx, event) {
|
|
|
1274
1870
|
}
|
|
1275
1871
|
async function runHandlers(subgraph, matched, ctx, opts) {
|
|
1276
1872
|
let processed = 0;
|
|
1277
|
-
let errors = 0;
|
|
1278
|
-
let skipped = 0;
|
|
1279
|
-
const threshold = opts?.errorThreshold ?? DEFAULT_ERROR_THRESHOLD;
|
|
1280
|
-
const filterLookup = new Map;
|
|
1281
|
-
if (!Array.isArray(subgraph.sources)) {
|
|
1282
|
-
for (const [name, filter] of Object.entries(subgraph.sources)) {
|
|
1283
|
-
filterLookup.set(name, filter);
|
|
1284
|
-
}
|
|
1285
|
-
}
|
|
1286
|
-
const units = [];
|
|
1287
|
-
for (const { tx, events, sourceName } of matched) {
|
|
1288
|
-
if (events.length === 0) {
|
|
1289
|
-
units.push({ tx, sourceName, event: null });
|
|
1290
|
-
} else {
|
|
1291
|
-
for (const event of events)
|
|
1292
|
-
units.push({ tx, sourceName, event });
|
|
1293
|
-
}
|
|
1294
|
-
}
|
|
1295
|
-
units.sort((a, b) => (a.tx.tx_index ?? 0) - (b.tx.tx_index ?? 0) || (a.event?.event_index ?? -1) - (b.event?.event_index ?? -1));
|
|
1296
|
-
for (const { tx, event, sourceName } of units) {
|
|
1297
|
-
if (errors >= threshold) {
|
|
1298
|
-
logger3.error("Subgraph error threshold reached, skipping remaining events", {
|
|
1299
|
-
subgraph: subgraph.name,
|
|
1300
|
-
errors,
|
|
1301
|
-
threshold
|
|
1302
|
-
});
|
|
1303
|
-
return { processed, errors, ...skipped > 0 ? { skipped } : {} };
|
|
1304
|
-
}
|
|
1305
|
-
const handler = subgraph.handlers[sourceName] ?? subgraph.handlers["*"] ?? null;
|
|
1306
|
-
if (!handler) {
|
|
1307
|
-
logger3.warn("No handler found for source", {
|
|
1308
|
-
subgraph: subgraph.name,
|
|
1309
|
-
sourceName,
|
|
1310
|
-
txId: tx.tx_id
|
|
1311
|
-
});
|
|
1312
|
-
continue;
|
|
1313
|
-
}
|
|
1314
|
-
ctx.setTx({
|
|
1315
|
-
txId: tx.tx_id,
|
|
1316
|
-
sender: tx.sender,
|
|
1317
|
-
type: tx.type,
|
|
1318
|
-
status: tx.status,
|
|
1319
|
-
contractId: tx.contract_id ?? null,
|
|
1320
|
-
functionName: tx.function_name ?? null
|
|
1321
|
-
});
|
|
1322
|
-
const filter = filterLookup.get(sourceName);
|
|
1323
|
-
const checkpoint = ctx.opsCheckpoint();
|
|
1324
|
-
try {
|
|
1325
|
-
let payload;
|
|
1326
|
-
if (event === null) {
|
|
1327
|
-
payload = filter ? buildEventPayload(filter, tx, null) : {
|
|
1328
|
-
tx: {
|
|
1329
|
-
txId: tx.tx_id,
|
|
1330
|
-
sender: tx.sender,
|
|
1331
|
-
type: tx.type,
|
|
1332
|
-
status: tx.status,
|
|
1333
|
-
contractId: tx.contract_id,
|
|
1334
|
-
functionName: tx.function_name
|
|
1335
|
-
}
|
|
1336
|
-
};
|
|
1337
|
-
} else if (filter) {
|
|
1338
|
-
payload = buildEventPayload(filter, tx, event);
|
|
1339
|
-
} else {
|
|
1340
|
-
const decoded = decodeEventData(event.data);
|
|
1341
|
-
payload = {
|
|
1342
|
-
...decoded,
|
|
1343
|
-
_eventId: event.id,
|
|
1344
|
-
_eventType: event.type,
|
|
1345
|
-
_eventIndex: event.event_index,
|
|
1346
|
-
tx: {
|
|
1347
|
-
txId: tx.tx_id,
|
|
1348
|
-
sender: tx.sender,
|
|
1349
|
-
type: tx.type,
|
|
1350
|
-
status: tx.status,
|
|
1351
|
-
contractId: tx.contract_id,
|
|
1352
|
-
functionName: tx.function_name
|
|
1353
|
-
}
|
|
1354
|
-
};
|
|
1355
|
-
}
|
|
1356
|
-
if (event !== null && filter?.type === "print_event" && filter.topic && payload.topic !== filter.topic) {
|
|
1357
|
-
continue;
|
|
1358
|
-
}
|
|
1359
|
-
if (event !== null && filter?.type === "print_event" && filter.prints) {
|
|
1360
|
-
const printPayload = payload;
|
|
1361
|
-
const verdict = validatePrintPayload(filter.prints, printPayload.topic ?? "", printPayload.data ?? {});
|
|
1362
|
-
if (!verdict.ok) {
|
|
1363
|
-
skipped++;
|
|
1364
|
-
logger3.warn("Print payload does not match declared prints schema", {
|
|
1365
|
-
subgraph: subgraph.name,
|
|
1366
|
-
sourceName,
|
|
1367
|
-
topic: printPayload.topic,
|
|
1368
|
-
txId: tx.tx_id,
|
|
1369
|
-
reason: verdict.reason
|
|
1370
|
-
});
|
|
1371
|
-
continue;
|
|
1372
|
-
}
|
|
1373
|
-
}
|
|
1374
|
-
await handler(payload, ctx);
|
|
1375
|
-
processed++;
|
|
1376
|
-
} catch (err) {
|
|
1377
|
-
ctx.rollbackTo(checkpoint);
|
|
1378
|
-
errors++;
|
|
1379
|
-
logger3.error("Subgraph handler error", {
|
|
1380
|
-
subgraph: subgraph.name,
|
|
1381
|
-
sourceName,
|
|
1382
|
-
txId: tx.tx_id,
|
|
1383
|
-
...event !== null ? { eventId: event.id, eventType: event.type } : {},
|
|
1384
|
-
error: getErrorMessage(err)
|
|
1385
|
-
});
|
|
1386
|
-
}
|
|
1387
|
-
}
|
|
1388
|
-
return { processed, errors, ...skipped > 0 ? { skipped } : {} };
|
|
1389
|
-
}
|
|
1390
|
-
|
|
1391
|
-
// src/runtime/source-matcher.ts
|
|
1392
|
-
var patternCache = new Map;
|
|
1393
|
-
function matchAny(value, pattern) {
|
|
1394
|
-
return Array.isArray(pattern) ? pattern.some((p) => matchPattern(value, p)) : matchPattern(value, pattern);
|
|
1395
|
-
}
|
|
1396
|
-
function matchPattern(value, pattern) {
|
|
1397
|
-
if (!pattern.includes("*"))
|
|
1398
|
-
return value === pattern;
|
|
1399
|
-
let re = patternCache.get(pattern);
|
|
1400
|
-
if (!re) {
|
|
1401
|
-
const regex = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
|
|
1402
|
-
re = new RegExp(`^${regex}$`);
|
|
1403
|
-
patternCache.set(pattern, re);
|
|
1404
|
-
}
|
|
1405
|
-
return re.test(value);
|
|
1406
|
-
}
|
|
1407
|
-
var EMPTY_SET = new Set;
|
|
1408
|
-
function factoryAllows(filter, contractId, factoryContracts) {
|
|
1409
|
-
if (!filter.factory)
|
|
1410
|
-
return true;
|
|
1411
|
-
if (!contractId)
|
|
1412
|
-
return false;
|
|
1413
|
-
return (factoryContracts.get(filter.factory.from) ?? EMPTY_SET).has(contractId);
|
|
1414
|
-
}
|
|
1415
|
-
function readPath(payload, path) {
|
|
1416
|
-
let cur = payload;
|
|
1417
|
-
for (const key of path.split(".")) {
|
|
1418
|
-
if (cur === null || typeof cur !== "object")
|
|
1419
|
-
return;
|
|
1420
|
-
cur = cur[key];
|
|
1421
|
-
}
|
|
1422
|
-
return cur;
|
|
1423
|
-
}
|
|
1424
|
-
function traitAllows(filter, contractId, traitContracts) {
|
|
1425
|
-
const trait = filter.trait;
|
|
1426
|
-
if (!trait)
|
|
1427
|
-
return true;
|
|
1428
|
-
if (!contractId)
|
|
1429
|
-
return false;
|
|
1430
|
-
return (traitContracts.get(trait) ?? EMPTY_SET).has(contractId);
|
|
1431
|
-
}
|
|
1432
|
-
function assetContract(assetId) {
|
|
1433
|
-
return assetId?.split("::")[0];
|
|
1434
|
-
}
|
|
1435
|
-
function matchFilter(filter, transactions, eventsByTx, traitContracts, factoryContracts = new Map) {
|
|
1436
|
-
const results = [];
|
|
1437
|
-
switch (filter.type) {
|
|
1438
|
-
case "stx_transfer":
|
|
1439
|
-
case "stx_mint":
|
|
1440
|
-
case "stx_burn":
|
|
1441
|
-
case "stx_lock": {
|
|
1442
|
-
const eventType = `${filter.type}_event`;
|
|
1443
|
-
for (const tx of transactions) {
|
|
1444
|
-
const txEvents = eventsByTx.get(tx.tx_id) ?? [];
|
|
1445
|
-
const matched = txEvents.filter((e) => e.type === eventType);
|
|
1446
|
-
if (matched.length === 0)
|
|
1447
|
-
continue;
|
|
1448
|
-
const filtered = matched.filter((e) => {
|
|
1449
|
-
const data = e.data;
|
|
1450
|
-
if (!data)
|
|
1451
|
-
return false;
|
|
1452
|
-
if ("sender" in filter && filter.sender) {
|
|
1453
|
-
if (!matchPattern(data.sender, filter.sender))
|
|
1454
|
-
return false;
|
|
1455
|
-
}
|
|
1456
|
-
if ("recipient" in filter && filter.recipient) {
|
|
1457
|
-
if (!matchPattern(data.recipient, filter.recipient))
|
|
1458
|
-
return false;
|
|
1459
|
-
}
|
|
1460
|
-
if ("lockedAddress" in filter && filter.lockedAddress) {
|
|
1461
|
-
if (!matchPattern(data.locked_address, filter.lockedAddress))
|
|
1462
|
-
return false;
|
|
1463
|
-
}
|
|
1464
|
-
if ("minAmount" in filter && filter.minAmount !== undefined) {
|
|
1465
|
-
const amount = BigInt(data.amount ?? data.locked_amount ?? "0");
|
|
1466
|
-
if (amount < filter.minAmount)
|
|
1467
|
-
return false;
|
|
1468
|
-
}
|
|
1469
|
-
if ("maxAmount" in filter && filter.maxAmount !== undefined) {
|
|
1470
|
-
const amount = BigInt(data.amount ?? "0");
|
|
1471
|
-
if (amount > filter.maxAmount)
|
|
1472
|
-
return false;
|
|
1473
|
-
}
|
|
1474
|
-
return true;
|
|
1475
|
-
});
|
|
1476
|
-
if (filtered.length > 0) {
|
|
1477
|
-
results.push({ tx, events: filtered });
|
|
1478
|
-
}
|
|
1479
|
-
}
|
|
1480
|
-
break;
|
|
1873
|
+
let errors = 0;
|
|
1874
|
+
let skipped = 0;
|
|
1875
|
+
const threshold = opts?.errorThreshold ?? DEFAULT_ERROR_THRESHOLD;
|
|
1876
|
+
const filterLookup = new Map;
|
|
1877
|
+
if (!Array.isArray(subgraph.sources)) {
|
|
1878
|
+
for (const [name, filter] of Object.entries(subgraph.sources)) {
|
|
1879
|
+
filterLookup.set(name, filter);
|
|
1481
1880
|
}
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
return false;
|
|
1491
|
-
const data = e.data;
|
|
1492
|
-
if (!data)
|
|
1493
|
-
return false;
|
|
1494
|
-
if (filter.assetIdentifier) {
|
|
1495
|
-
const assetId = data.asset_identifier;
|
|
1496
|
-
if (!assetId || !matchPattern(assetId, filter.assetIdentifier))
|
|
1497
|
-
return false;
|
|
1498
|
-
}
|
|
1499
|
-
if (!traitAllows(filter, assetContract(data.asset_identifier), traitContracts))
|
|
1500
|
-
return false;
|
|
1501
|
-
if ("sender" in filter && filter.sender) {
|
|
1502
|
-
if (!matchPattern(data.sender, filter.sender))
|
|
1503
|
-
return false;
|
|
1504
|
-
}
|
|
1505
|
-
if ("recipient" in filter && filter.recipient) {
|
|
1506
|
-
if (!matchPattern(data.recipient, filter.recipient))
|
|
1507
|
-
return false;
|
|
1508
|
-
}
|
|
1509
|
-
if (filter.minAmount !== undefined) {
|
|
1510
|
-
const amount = BigInt(data.amount ?? "0");
|
|
1511
|
-
if (amount < filter.minAmount)
|
|
1512
|
-
return false;
|
|
1513
|
-
}
|
|
1514
|
-
return true;
|
|
1515
|
-
});
|
|
1516
|
-
if (matched.length > 0) {
|
|
1517
|
-
results.push({ tx, events: matched });
|
|
1518
|
-
}
|
|
1519
|
-
}
|
|
1520
|
-
break;
|
|
1881
|
+
}
|
|
1882
|
+
const units = [];
|
|
1883
|
+
for (const { tx, events, sourceName } of matched) {
|
|
1884
|
+
if (events.length === 0) {
|
|
1885
|
+
units.push({ tx, sourceName, event: null });
|
|
1886
|
+
} else {
|
|
1887
|
+
for (const event of events)
|
|
1888
|
+
units.push({ tx, sourceName, event });
|
|
1521
1889
|
}
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1890
|
+
}
|
|
1891
|
+
units.sort((a, b) => (a.tx.tx_index ?? 0) - (b.tx.tx_index ?? 0) || (a.event?.event_index ?? -1) - (b.event?.event_index ?? -1));
|
|
1892
|
+
for (const { tx, event, sourceName } of units) {
|
|
1893
|
+
if (errors >= threshold) {
|
|
1894
|
+
logger3.error("Subgraph error threshold reached, skipping remaining events", {
|
|
1895
|
+
subgraph: subgraph.name,
|
|
1896
|
+
errors,
|
|
1897
|
+
threshold
|
|
1898
|
+
});
|
|
1899
|
+
return { processed, errors, ...skipped > 0 ? { skipped } : {} };
|
|
1900
|
+
}
|
|
1901
|
+
const handler = subgraph.handlers[sourceName] ?? subgraph.handlers["*"] ?? null;
|
|
1902
|
+
if (!handler) {
|
|
1903
|
+
logger3.warn("No handler found for source", {
|
|
1904
|
+
subgraph: subgraph.name,
|
|
1905
|
+
sourceName,
|
|
1906
|
+
txId: tx.tx_id
|
|
1907
|
+
});
|
|
1908
|
+
continue;
|
|
1909
|
+
}
|
|
1910
|
+
ctx.setTx({
|
|
1911
|
+
txId: tx.tx_id,
|
|
1912
|
+
sender: tx.sender,
|
|
1913
|
+
type: tx.type,
|
|
1914
|
+
status: tx.status,
|
|
1915
|
+
contractId: tx.contract_id ?? null,
|
|
1916
|
+
functionName: tx.function_name ?? null
|
|
1917
|
+
});
|
|
1918
|
+
const filter = filterLookup.get(sourceName);
|
|
1919
|
+
const checkpoint = ctx.opsCheckpoint();
|
|
1920
|
+
try {
|
|
1921
|
+
let payload;
|
|
1922
|
+
if (event === null) {
|
|
1923
|
+
payload = filter ? buildEventPayload(filter, tx, null) : {
|
|
1924
|
+
tx: {
|
|
1925
|
+
txId: tx.tx_id,
|
|
1926
|
+
sender: tx.sender,
|
|
1927
|
+
type: tx.type,
|
|
1928
|
+
status: tx.status,
|
|
1929
|
+
contractId: tx.contract_id,
|
|
1930
|
+
functionName: tx.function_name
|
|
1544
1931
|
}
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1932
|
+
};
|
|
1933
|
+
} else if (filter) {
|
|
1934
|
+
payload = buildEventPayload(filter, tx, event);
|
|
1935
|
+
} else {
|
|
1936
|
+
const decoded = decodeEventData(event.data);
|
|
1937
|
+
payload = {
|
|
1938
|
+
...decoded,
|
|
1939
|
+
_eventId: event.id,
|
|
1940
|
+
_eventType: event.type,
|
|
1941
|
+
_eventIndex: event.event_index,
|
|
1942
|
+
tx: {
|
|
1943
|
+
txId: tx.tx_id,
|
|
1944
|
+
sender: tx.sender,
|
|
1945
|
+
type: tx.type,
|
|
1946
|
+
status: tx.status,
|
|
1947
|
+
contractId: tx.contract_id,
|
|
1948
|
+
functionName: tx.function_name
|
|
1548
1949
|
}
|
|
1549
|
-
|
|
1550
|
-
});
|
|
1551
|
-
if (matched.length > 0) {
|
|
1552
|
-
results.push({ tx, events: matched });
|
|
1553
|
-
}
|
|
1950
|
+
};
|
|
1554
1951
|
}
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
case "contract_call": {
|
|
1558
|
-
for (const tx of transactions) {
|
|
1559
|
-
if (tx.type !== "contract_call")
|
|
1560
|
-
continue;
|
|
1561
|
-
if (filter.contractId) {
|
|
1562
|
-
if (!tx.contract_id || !matchAny(tx.contract_id, filter.contractId))
|
|
1563
|
-
continue;
|
|
1564
|
-
}
|
|
1565
|
-
if (!factoryAllows(filter, tx.contract_id, factoryContracts))
|
|
1566
|
-
continue;
|
|
1567
|
-
if (filter.functionName) {
|
|
1568
|
-
if (!tx.function_name || !matchPattern(tx.function_name, filter.functionName))
|
|
1569
|
-
continue;
|
|
1570
|
-
}
|
|
1571
|
-
if (filter.caller) {
|
|
1572
|
-
if (!matchPattern(tx.sender, filter.caller))
|
|
1573
|
-
continue;
|
|
1574
|
-
}
|
|
1575
|
-
if (!traitAllows(filter, tx.contract_id, traitContracts))
|
|
1576
|
-
continue;
|
|
1577
|
-
const txEvents = eventsByTx.get(tx.tx_id) ?? [];
|
|
1578
|
-
results.push({ tx, events: txEvents });
|
|
1952
|
+
if (event !== null && filter?.type === "print_event" && filter.topic && payload.topic !== filter.topic) {
|
|
1953
|
+
continue;
|
|
1579
1954
|
}
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1955
|
+
if (event !== null && filter?.type === "print_event" && filter.prints) {
|
|
1956
|
+
const printPayload = payload;
|
|
1957
|
+
const verdict = validatePrintPayload(filter.prints, printPayload.topic ?? "", printPayload.data ?? {});
|
|
1958
|
+
if (!verdict.ok) {
|
|
1959
|
+
skipped++;
|
|
1960
|
+
logger3.warn("Print payload does not match declared prints schema", {
|
|
1961
|
+
subgraph: subgraph.name,
|
|
1962
|
+
sourceName,
|
|
1963
|
+
topic: printPayload.topic,
|
|
1964
|
+
txId: tx.tx_id,
|
|
1965
|
+
reason: verdict.reason
|
|
1966
|
+
});
|
|
1585
1967
|
continue;
|
|
1586
|
-
if (filter.deployer) {
|
|
1587
|
-
if (!matchPattern(tx.sender, filter.deployer))
|
|
1588
|
-
continue;
|
|
1589
|
-
}
|
|
1590
|
-
if (filter.contractName) {
|
|
1591
|
-
const name = tx.contract_id?.split(".")[1] ?? "";
|
|
1592
|
-
if (!matchPattern(name, filter.contractName))
|
|
1593
|
-
continue;
|
|
1594
|
-
}
|
|
1595
|
-
const txEvents = eventsByTx.get(tx.tx_id) ?? [];
|
|
1596
|
-
results.push({ tx, events: txEvents });
|
|
1597
|
-
}
|
|
1598
|
-
break;
|
|
1599
|
-
}
|
|
1600
|
-
case "print_event": {
|
|
1601
|
-
for (const tx of transactions) {
|
|
1602
|
-
const txEvents = eventsByTx.get(tx.tx_id) ?? [];
|
|
1603
|
-
const matched = txEvents.filter((e) => {
|
|
1604
|
-
if (e.type !== "smart_contract_event" && e.type !== "contract_event")
|
|
1605
|
-
return false;
|
|
1606
|
-
const data = e.data;
|
|
1607
|
-
if (!data)
|
|
1608
|
-
return false;
|
|
1609
|
-
if (data.topic !== "print")
|
|
1610
|
-
return false;
|
|
1611
|
-
const printContractId = data.contract_identifier ?? data.contract_id;
|
|
1612
|
-
if (filter.contractId) {
|
|
1613
|
-
if (!printContractId || !matchAny(printContractId, filter.contractId))
|
|
1614
|
-
return false;
|
|
1615
|
-
}
|
|
1616
|
-
if (!traitAllows(filter, printContractId, traitContracts))
|
|
1617
|
-
return false;
|
|
1618
|
-
if (!factoryAllows(filter, printContractId, factoryContracts))
|
|
1619
|
-
return false;
|
|
1620
|
-
return true;
|
|
1621
|
-
});
|
|
1622
|
-
if (matched.length > 0) {
|
|
1623
|
-
results.push({ tx, events: matched });
|
|
1624
1968
|
}
|
|
1625
1969
|
}
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
const seen = new Set;
|
|
1639
|
-
const results = [];
|
|
1640
|
-
for (const [sourceName, filter] of Object.entries(sources)) {
|
|
1641
|
-
const matches = matchFilter(filter, transactions, eventsByTx, traitContracts, factoryContracts);
|
|
1642
|
-
for (const match of matches) {
|
|
1643
|
-
const dedupeKey = `${match.tx.tx_id}:${sourceName}`;
|
|
1644
|
-
if (!seen.has(dedupeKey)) {
|
|
1645
|
-
seen.add(dedupeKey);
|
|
1646
|
-
results.push({ ...match, sourceName });
|
|
1647
|
-
}
|
|
1970
|
+
await handler(payload, ctx);
|
|
1971
|
+
processed++;
|
|
1972
|
+
} catch (err) {
|
|
1973
|
+
ctx.rollbackTo(checkpoint);
|
|
1974
|
+
errors++;
|
|
1975
|
+
logger3.error("Subgraph handler error", {
|
|
1976
|
+
subgraph: subgraph.name,
|
|
1977
|
+
sourceName,
|
|
1978
|
+
txId: tx.tx_id,
|
|
1979
|
+
...event !== null ? { eventId: event.id, eventType: event.type } : {},
|
|
1980
|
+
error: getErrorMessage(err)
|
|
1981
|
+
});
|
|
1648
1982
|
}
|
|
1649
1983
|
}
|
|
1650
|
-
return
|
|
1984
|
+
return { processed, errors, ...skipped > 0 ? { skipped } : {} };
|
|
1651
1985
|
}
|
|
1652
1986
|
|
|
1653
1987
|
// src/runtime/block-processor.ts
|
|
@@ -1990,7 +2324,7 @@ function reconstructEvent(e) {
|
|
|
1990
2324
|
type: "contract_event",
|
|
1991
2325
|
data: {
|
|
1992
2326
|
topic: e.payload.topic,
|
|
1993
|
-
|
|
2327
|
+
contract_id: e.contract_id,
|
|
1994
2328
|
value: e.payload.value,
|
|
1995
2329
|
raw_value: e.payload.raw_value
|
|
1996
2330
|
}
|
|
@@ -3652,5 +3986,5 @@ export {
|
|
|
3652
3986
|
handleSubgraphReorg
|
|
3653
3987
|
};
|
|
3654
3988
|
|
|
3655
|
-
//# debugId=
|
|
3989
|
+
//# debugId=CCDDEB49D7B7E7B464756E2164756E21
|
|
3656
3990
|
//# sourceMappingURL=reorg.js.map
|