@secondlayer/subgraphs 7.0.4 → 7.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +50 -7
- package/dist/index.js +11 -5
- package/dist/index.js.map +4 -4
- package/dist/runtime/replay.js +262 -27
- package/dist/runtime/replay.js.map +9 -7
- package/dist/schema/index.d.ts +18 -0
- package/dist/schema/index.js +11 -5
- package/dist/schema/index.js.map +3 -3
- package/dist/testing/index.d.ts +50 -6
- package/dist/testing/index.js +79 -14
- package/dist/testing/index.js.map +5 -5
- package/dist/types.d.ts +18 -0
- package/dist/validate.d.ts +18 -0
- package/dist/validate.js +7 -1
- package/dist/validate.js.map +3 -3
- package/dist/verification.d.ts +18 -0
- package/dist/verification.js +5 -5
- package/dist/verification.js.map +1 -1
- package/dist/verify/index.d.ts +18 -0
- package/dist/verify/index.js +70 -13
- package/dist/verify/index.js.map +4 -4
- package/package.json +4 -4
package/dist/runtime/replay.js
CHANGED
|
@@ -17,7 +17,7 @@ import { createHash } from "node:crypto";
|
|
|
17
17
|
// package.json
|
|
18
18
|
var package_default = {
|
|
19
19
|
name: "@secondlayer/subgraphs",
|
|
20
|
-
version: "7.0
|
|
20
|
+
version: "7.2.0",
|
|
21
21
|
description: "Describe the Stacks data you want as one TypeScript file; it fills itself and survives reorgs.",
|
|
22
22
|
license: "MIT",
|
|
23
23
|
repository: {
|
|
@@ -77,9 +77,9 @@ var package_default = {
|
|
|
77
77
|
prepublishOnly: "bun run build"
|
|
78
78
|
},
|
|
79
79
|
dependencies: {
|
|
80
|
-
"@secondlayer/shared": "^13.
|
|
81
|
-
"@secondlayer/stacks": "^6.
|
|
82
|
-
"@secondlayer/verify": "^0.
|
|
80
|
+
"@secondlayer/shared": "^13.1.0",
|
|
81
|
+
"@secondlayer/stacks": "^6.4.0",
|
|
82
|
+
"@secondlayer/verify": "^0.6.0",
|
|
83
83
|
esbuild: "^0.28.2",
|
|
84
84
|
kysely: "0.28.17",
|
|
85
85
|
zod: "^4.3.6"
|
|
@@ -1134,13 +1134,181 @@ function buildHttpClient() {
|
|
|
1134
1134
|
return createInternalIndexHttpClient();
|
|
1135
1135
|
}
|
|
1136
1136
|
|
|
1137
|
-
// src/runtime/
|
|
1138
|
-
import { getSourceDb as
|
|
1137
|
+
// src/runtime/chain-data-reader.ts
|
|
1138
|
+
import { getSourceDb as getSourceDb2 } from "@secondlayer/shared/db";
|
|
1139
1139
|
import { resolveTraitContractIds } from "@secondlayer/shared/db/queries/contracts";
|
|
1140
|
+
import { hasNoLocalChain } from "@secondlayer/shared/db/queries/instance";
|
|
1141
|
+
function databaseChainDataReader(db) {
|
|
1142
|
+
return {
|
|
1143
|
+
async sbtcEvents(fromHeight, toHeight, topics) {
|
|
1144
|
+
if (topics.length === 0)
|
|
1145
|
+
return [];
|
|
1146
|
+
return db.selectFrom("sbtc_events").selectAll().where("block_height", ">=", fromHeight).where("block_height", "<=", toHeight).where("canonical", "=", true).where("topic", "in", [...topics]).orderBy("block_height").orderBy("event_index").execute();
|
|
1147
|
+
},
|
|
1148
|
+
traitContractIds(trait, asOfBlock) {
|
|
1149
|
+
return resolveTraitContractIds(db, trait, asOfBlock);
|
|
1150
|
+
},
|
|
1151
|
+
settlementsConfirmedAfter(after) {
|
|
1152
|
+
return db.selectFrom("sbtc_settlements as s").innerJoin("sbtc_events as e", (join) => join.onRef("e.sweep_txid", "=", "s.sweep_txid").on("e.topic", "=", "withdrawal-accept").on("e.canonical", "=", true)).innerJoin("blocks as b", (join) => join.onRef("b.height", "=", "e.block_height").on("b.canonical", "=", true)).where("s.settlement_confirmed", "=", true).where("s.confirmed_at", ">", after).select([
|
|
1153
|
+
"s.sweep_txid",
|
|
1154
|
+
"s.request_id",
|
|
1155
|
+
"s.btc_confirmations",
|
|
1156
|
+
"s.block_height as btc_block_height",
|
|
1157
|
+
"s.confirmed_at",
|
|
1158
|
+
"e.tx_id",
|
|
1159
|
+
"e.block_height as stacks_block_height",
|
|
1160
|
+
"e.amount",
|
|
1161
|
+
"e.sender",
|
|
1162
|
+
"b.hash as block_hash",
|
|
1163
|
+
"b.timestamp as block_timestamp"
|
|
1164
|
+
]).execute();
|
|
1165
|
+
}
|
|
1166
|
+
};
|
|
1167
|
+
}
|
|
1168
|
+
var THROUGH_TIP = Number.MAX_SAFE_INTEGER;
|
|
1169
|
+
var traitCaches = new WeakMap;
|
|
1170
|
+
function traitCacheFor(client) {
|
|
1171
|
+
let cache = traitCaches.get(client);
|
|
1172
|
+
if (!cache) {
|
|
1173
|
+
cache = new Map;
|
|
1174
|
+
traitCaches.set(client, cache);
|
|
1175
|
+
}
|
|
1176
|
+
return cache;
|
|
1177
|
+
}
|
|
1178
|
+
function toSbtcEventRow(e) {
|
|
1179
|
+
return {
|
|
1180
|
+
topic: e.topic,
|
|
1181
|
+
sender: e.sender,
|
|
1182
|
+
bitcoin_txid: e.bitcoin_txid,
|
|
1183
|
+
request_id: e.request_id === null ? null : String(e.request_id),
|
|
1184
|
+
amount: e.amount,
|
|
1185
|
+
sweep_txid: e.sweep_txid,
|
|
1186
|
+
block_height: String(e.block_height),
|
|
1187
|
+
tx_id: e.tx_id,
|
|
1188
|
+
event_index: e.event_index
|
|
1189
|
+
};
|
|
1190
|
+
}
|
|
1191
|
+
function oneWithdrawalPerSweep(rows) {
|
|
1192
|
+
const bySweep = new Map;
|
|
1193
|
+
for (const row of rows) {
|
|
1194
|
+
if (!row.sweep_txid || !row.confirmed_at)
|
|
1195
|
+
continue;
|
|
1196
|
+
const seen = bySweep.get(row.sweep_txid);
|
|
1197
|
+
if (!seen || row.request_id < seen.request_id) {
|
|
1198
|
+
bySweep.set(row.sweep_txid, row);
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
return [...bySweep.values()];
|
|
1202
|
+
}
|
|
1203
|
+
function hostedChainDataReader(client) {
|
|
1204
|
+
return {
|
|
1205
|
+
async sbtcEvents(fromHeight, toHeight, topics) {
|
|
1206
|
+
if (topics.length === 0)
|
|
1207
|
+
return [];
|
|
1208
|
+
const rows = await client.fetchSbtcEvents(fromHeight, toHeight, topics);
|
|
1209
|
+
return rows.map(toSbtcEventRow);
|
|
1210
|
+
},
|
|
1211
|
+
async traitContractIds(trait, asOfBlock) {
|
|
1212
|
+
const cache = traitCacheFor(client);
|
|
1213
|
+
let entry = cache.get(trait);
|
|
1214
|
+
if (!entry || asOfBlock > entry.coveredThrough) {
|
|
1215
|
+
const rows = await client.listContractsByTrait(trait);
|
|
1216
|
+
const members = rows.map((r) => ({
|
|
1217
|
+
contractId: r.contract_id,
|
|
1218
|
+
blockHeight: Number(r.block_height)
|
|
1219
|
+
}));
|
|
1220
|
+
entry = {
|
|
1221
|
+
members,
|
|
1222
|
+
coveredThrough: Math.max(asOfBlock, ...members.map((m) => m.blockHeight))
|
|
1223
|
+
};
|
|
1224
|
+
cache.set(trait, entry);
|
|
1225
|
+
}
|
|
1226
|
+
return entry.members.filter((m) => m.blockHeight <= asOfBlock).map((m) => m.contractId);
|
|
1227
|
+
},
|
|
1228
|
+
async settlementsConfirmedAfter(after) {
|
|
1229
|
+
const withdrawals = oneWithdrawalPerSweep(await client.fetchSettledWithdrawals(after));
|
|
1230
|
+
const out = [];
|
|
1231
|
+
for (const w of withdrawals) {
|
|
1232
|
+
const accepts = await client.fetchSbtcEvents(0, THROUGH_TIP, ["withdrawal-accept"], { requestId: w.request_id });
|
|
1233
|
+
const accept = accepts.filter((e) => e.sweep_txid === w.sweep_txid).at(-1);
|
|
1234
|
+
if (!accept)
|
|
1235
|
+
continue;
|
|
1236
|
+
const [block] = await client.walkBlocks(accept.block_height, accept.block_height);
|
|
1237
|
+
if (!block)
|
|
1238
|
+
continue;
|
|
1239
|
+
out.push({
|
|
1240
|
+
sweep_txid: w.sweep_txid,
|
|
1241
|
+
request_id: w.request_id,
|
|
1242
|
+
btc_confirmations: w.btc_confirmations ?? 0,
|
|
1243
|
+
btc_block_height: w.btc_block_height,
|
|
1244
|
+
confirmed_at: new Date(w.confirmed_at),
|
|
1245
|
+
tx_id: accept.tx_id,
|
|
1246
|
+
stacks_block_height: accept.block_height,
|
|
1247
|
+
amount: accept.amount,
|
|
1248
|
+
sender: accept.sender,
|
|
1249
|
+
block_hash: block.block_hash,
|
|
1250
|
+
block_timestamp: isoToUnixSeconds(block.block_time)
|
|
1251
|
+
});
|
|
1252
|
+
}
|
|
1253
|
+
return out;
|
|
1254
|
+
}
|
|
1255
|
+
};
|
|
1256
|
+
}
|
|
1257
|
+
function chainDataReader(opts) {
|
|
1258
|
+
let resolved;
|
|
1259
|
+
const reader = () => {
|
|
1260
|
+
resolved ??= (async () => {
|
|
1261
|
+
const localDb = opts?.localDb ?? getSourceDb2();
|
|
1262
|
+
if (await hasNoLocalChain(getSourceDb2())) {
|
|
1263
|
+
return hostedChainDataReader(opts?.httpClient ?? sharedHttpClient());
|
|
1264
|
+
}
|
|
1265
|
+
return databaseChainDataReader(localDb);
|
|
1266
|
+
})();
|
|
1267
|
+
return resolved;
|
|
1268
|
+
};
|
|
1269
|
+
return {
|
|
1270
|
+
sbtcEvents: async (...args) => (await reader()).sbtcEvents(...args),
|
|
1271
|
+
traitContractIds: async (...args) => (await reader()).traitContractIds(...args),
|
|
1272
|
+
settlementsConfirmedAfter: async (...args) => (await reader()).settlementsConfirmedAfter(...args)
|
|
1273
|
+
};
|
|
1274
|
+
}
|
|
1275
|
+
var processHttpClient;
|
|
1276
|
+
function sharedHttpClient() {
|
|
1277
|
+
processHttpClient ??= buildHttpClient();
|
|
1278
|
+
return processHttpClient;
|
|
1279
|
+
}
|
|
1140
1280
|
|
|
1141
1281
|
// src/runtime/decoder-bound.ts
|
|
1142
1282
|
import { committedHeight } from "@secondlayer/shared";
|
|
1143
|
-
import { getSourceDb as
|
|
1283
|
+
import { getSourceDb as getSourceDb3 } from "@secondlayer/shared/db";
|
|
1284
|
+
|
|
1285
|
+
// src/runtime/clarity.ts
|
|
1286
|
+
import { cvToValue, deserializeCV } from "@secondlayer/stacks/clarity";
|
|
1287
|
+
function decodeClarityValue(hex) {
|
|
1288
|
+
try {
|
|
1289
|
+
const cleanHex = hex.startsWith("0x") ? hex.slice(2) : hex;
|
|
1290
|
+
const cv = deserializeCV(cleanHex);
|
|
1291
|
+
return cvToValue(cv);
|
|
1292
|
+
} catch {
|
|
1293
|
+
return hex;
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
function decodeEventData(data) {
|
|
1297
|
+
if (typeof data === "string" && data.startsWith("0x") && data.length > 10) {
|
|
1298
|
+
return decodeClarityValue(data);
|
|
1299
|
+
}
|
|
1300
|
+
if (Array.isArray(data)) {
|
|
1301
|
+
return data.map(decodeEventData);
|
|
1302
|
+
}
|
|
1303
|
+
if (typeof data === "object" && data !== null) {
|
|
1304
|
+
const decoded = {};
|
|
1305
|
+
for (const [key, value] of Object.entries(data)) {
|
|
1306
|
+
decoded[key] = decodeEventData(value);
|
|
1307
|
+
}
|
|
1308
|
+
return decoded;
|
|
1309
|
+
}
|
|
1310
|
+
return data;
|
|
1311
|
+
}
|
|
1144
1312
|
|
|
1145
1313
|
// src/runtime/source-matcher.ts
|
|
1146
1314
|
var patternCache = new Map;
|
|
@@ -1183,6 +1351,41 @@ function printContractId(data) {
|
|
|
1183
1351
|
const id = (typeof data.contract_identifier === "string" ? data.contract_identifier : undefined) ?? (typeof data.contract_id === "string" ? data.contract_id : undefined);
|
|
1184
1352
|
return id || undefined;
|
|
1185
1353
|
}
|
|
1354
|
+
var decodedPrints = new WeakMap;
|
|
1355
|
+
function decodedPrintTuple(event, data) {
|
|
1356
|
+
if (decodedPrints.has(event))
|
|
1357
|
+
return decodedPrints.get(event) ?? null;
|
|
1358
|
+
const raw = data.raw_value;
|
|
1359
|
+
const value = typeof raw === "string" && raw.startsWith("0x") ? decodeClarityValue(raw) : null;
|
|
1360
|
+
const tuple = value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
1361
|
+
decodedPrints.set(event, tuple);
|
|
1362
|
+
return tuple;
|
|
1363
|
+
}
|
|
1364
|
+
function printFieldEquals(decoded, key, expected) {
|
|
1365
|
+
const value = decoded[key];
|
|
1366
|
+
let actual;
|
|
1367
|
+
if (typeof value === "string")
|
|
1368
|
+
actual = value;
|
|
1369
|
+
else if (typeof value === "bigint" || typeof value === "boolean")
|
|
1370
|
+
actual = String(value);
|
|
1371
|
+
else
|
|
1372
|
+
return false;
|
|
1373
|
+
return typeof expected === "string" ? actual === expected : expected.includes(actual);
|
|
1374
|
+
}
|
|
1375
|
+
function printFieldsAllow(filter, event, data) {
|
|
1376
|
+
if (!filter.topic && !filter.where)
|
|
1377
|
+
return true;
|
|
1378
|
+
const decoded = decodedPrintTuple(event, data);
|
|
1379
|
+
if (!decoded)
|
|
1380
|
+
return false;
|
|
1381
|
+
if (filter.topic && !printFieldEquals(decoded, "topic", filter.topic))
|
|
1382
|
+
return false;
|
|
1383
|
+
for (const [key, expected] of Object.entries(filter.where ?? {})) {
|
|
1384
|
+
if (!printFieldEquals(decoded, key, expected))
|
|
1385
|
+
return false;
|
|
1386
|
+
}
|
|
1387
|
+
return true;
|
|
1388
|
+
}
|
|
1186
1389
|
function matchFilter(filter, transactions, eventsByTx, traitContracts, factoryContracts = new Map, vmEventsByTx = new Map) {
|
|
1187
1390
|
const results = [];
|
|
1188
1391
|
switch (filter.type) {
|
|
@@ -1419,7 +1622,7 @@ function matchFilter(filter, transactions, eventsByTx, traitContracts, factoryCo
|
|
|
1419
1622
|
return false;
|
|
1420
1623
|
if (!factoryAllows(filter, contractId, factoryContracts))
|
|
1421
1624
|
return false;
|
|
1422
|
-
return
|
|
1625
|
+
return printFieldsAllow(filter, e, data);
|
|
1423
1626
|
});
|
|
1424
1627
|
if (matched.length > 0) {
|
|
1425
1628
|
results.push({ tx, events: matched });
|
|
@@ -1539,11 +1742,21 @@ function referencedTraits(chainSubs) {
|
|
|
1539
1742
|
}
|
|
1540
1743
|
return [...traits];
|
|
1541
1744
|
}
|
|
1745
|
+
function readerFor(opts) {
|
|
1746
|
+
if (opts?.reader)
|
|
1747
|
+
return opts.reader;
|
|
1748
|
+
if (opts?.sourceDb)
|
|
1749
|
+
return databaseChainDataReader(opts.sourceDb);
|
|
1750
|
+
return chainDataReader();
|
|
1751
|
+
}
|
|
1542
1752
|
async function buildTraitContracts(chainSubs, asOfBlock, opts) {
|
|
1543
|
-
const
|
|
1753
|
+
const traits = referencedTraits(chainSubs);
|
|
1754
|
+
if (traits.length === 0)
|
|
1755
|
+
return new Map;
|
|
1756
|
+
const reader = readerFor(opts);
|
|
1544
1757
|
const resolved = new Map;
|
|
1545
|
-
for (const trait of
|
|
1546
|
-
const ids = await
|
|
1758
|
+
for (const trait of traits) {
|
|
1759
|
+
const ids = await reader.traitContractIds(trait, asOfBlock);
|
|
1547
1760
|
resolved.set(trait, new Set(ids));
|
|
1548
1761
|
}
|
|
1549
1762
|
return resolved;
|
|
@@ -1626,10 +1839,35 @@ async function emitChainOutbox(db, matches, keyMeta, blockHeight, blockHash, blo
|
|
|
1626
1839
|
const result = await db.insertInto("webhook_outbox").values(rows).onConflict((oc) => oc.columns(["webhook_id", "dedup_key"]).doNothing()).executeTakeFirst();
|
|
1627
1840
|
return Number(result.numInsertedOrUpdatedRows ?? 0);
|
|
1628
1841
|
}
|
|
1629
|
-
|
|
1842
|
+
function sbtcSubsOf(chainSubs) {
|
|
1843
|
+
return chainSubs.filter((sub) => triggersOf(sub).some((t) => isSbtcTriggerType(t.type)));
|
|
1844
|
+
}
|
|
1845
|
+
function sbtcTopicsOf(sbtcSubs) {
|
|
1846
|
+
const topics = new Set;
|
|
1847
|
+
for (const sub of sbtcSubs) {
|
|
1848
|
+
for (const trigger of triggersOf(sub)) {
|
|
1849
|
+
const topic = SBTC_TRIGGER_TO_TOPIC[trigger.type];
|
|
1850
|
+
if (topic)
|
|
1851
|
+
topics.add(topic);
|
|
1852
|
+
}
|
|
1853
|
+
}
|
|
1854
|
+
return [...topics];
|
|
1855
|
+
}
|
|
1856
|
+
async function loadSbtcEventsForRange(chainSubs, fromHeight, toHeight, opts) {
|
|
1857
|
+
const byHeight = new Map;
|
|
1858
|
+
const topics = sbtcTopicsOf(sbtcSubsOf(chainSubs));
|
|
1630
1859
|
if (topics.length === 0)
|
|
1631
|
-
return
|
|
1632
|
-
|
|
1860
|
+
return byHeight;
|
|
1861
|
+
const rows = await readerFor(opts).sbtcEvents(fromHeight, toHeight, topics);
|
|
1862
|
+
for (const row of rows) {
|
|
1863
|
+
const height = Number(row.block_height);
|
|
1864
|
+
const list = byHeight.get(height);
|
|
1865
|
+
if (list)
|
|
1866
|
+
list.push(row);
|
|
1867
|
+
else
|
|
1868
|
+
byHeight.set(height, [row]);
|
|
1869
|
+
}
|
|
1870
|
+
return byHeight;
|
|
1633
1871
|
}
|
|
1634
1872
|
function toAmountBigInt(v) {
|
|
1635
1873
|
return v === undefined ? undefined : BigInt(v);
|
|
@@ -1714,18 +1952,10 @@ function buildSbtcEventPayload(triggerType, row) {
|
|
|
1714
1952
|
};
|
|
1715
1953
|
}
|
|
1716
1954
|
async function emitSbtcOutbox(db, chainSubs, blockHeight, blockHash, blockTime, opts) {
|
|
1717
|
-
const sbtcSubs = chainSubs
|
|
1955
|
+
const sbtcSubs = sbtcSubsOf(chainSubs);
|
|
1718
1956
|
if (sbtcSubs.length === 0)
|
|
1719
1957
|
return 0;
|
|
1720
|
-
const
|
|
1721
|
-
for (const sub of sbtcSubs) {
|
|
1722
|
-
for (const trigger of triggersOf(sub)) {
|
|
1723
|
-
const topic = SBTC_TRIGGER_TO_TOPIC[trigger.type];
|
|
1724
|
-
if (topic)
|
|
1725
|
-
neededTopics.add(topic);
|
|
1726
|
-
}
|
|
1727
|
-
}
|
|
1728
|
-
const sbtcRows = await loadSbtcEventsForBlock(opts?.sourceDb ?? getSourceDb3(), blockHeight, [...neededTopics]);
|
|
1958
|
+
const sbtcRows = opts?.rows ?? (await loadSbtcEventsForRange(sbtcSubs, blockHeight, blockHeight, opts)).get(blockHeight) ?? [];
|
|
1729
1959
|
if (sbtcRows.length === 0)
|
|
1730
1960
|
return 0;
|
|
1731
1961
|
const replayId = opts?.replayId;
|
|
@@ -1810,12 +2040,16 @@ async function replayChainWebhook(db, sub, input, opts) {
|
|
|
1810
2040
|
const replayId = deterministicReplayId(sub.id, input.fromBlock, input.toBlock, input.replayIdSuffix);
|
|
1811
2041
|
const { sources, keyMeta } = buildSourcesMap([sub]);
|
|
1812
2042
|
const source = opts?.source ?? new PublicApiBlockSource(buildHttpClient(), referencedEventTypes([sub]));
|
|
2043
|
+
const reader = chainDataReader();
|
|
1813
2044
|
let scanned = 0;
|
|
1814
2045
|
let enqueued = 0;
|
|
1815
2046
|
for (let from = input.fromBlock;from <= input.toBlock; from += CHAIN_REPLAY_BATCH) {
|
|
1816
2047
|
const to = Math.min(from + CHAIN_REPLAY_BATCH - 1, input.toBlock);
|
|
1817
2048
|
const blocks = await source.loadBlockRange(from, to);
|
|
1818
|
-
const traitContracts = await buildTraitContracts([sub], to);
|
|
2049
|
+
const traitContracts = await buildTraitContracts([sub], to, { reader });
|
|
2050
|
+
const sbtcByHeight = await loadSbtcEventsForRange([sub], from, to, {
|
|
2051
|
+
reader
|
|
2052
|
+
});
|
|
1819
2053
|
for (let h = from;h <= to; h++) {
|
|
1820
2054
|
const bd = blocks.get(h);
|
|
1821
2055
|
if (!bd)
|
|
@@ -1829,7 +2063,8 @@ async function replayChainWebhook(db, sub, input, opts) {
|
|
|
1829
2063
|
});
|
|
1830
2064
|
}
|
|
1831
2065
|
enqueued += await emitSbtcOutbox(db, [sub], h, bd.block.hash, blockTime, {
|
|
1832
|
-
replayId
|
|
2066
|
+
replayId,
|
|
2067
|
+
rows: sbtcByHeight.get(h) ?? []
|
|
1833
2068
|
});
|
|
1834
2069
|
}
|
|
1835
2070
|
}
|
|
@@ -1850,5 +2085,5 @@ export {
|
|
|
1850
2085
|
replayWebhook
|
|
1851
2086
|
};
|
|
1852
2087
|
|
|
1853
|
-
//# debugId=
|
|
2088
|
+
//# debugId=719BB77ECD650B4564756E2164756E21
|
|
1854
2089
|
//# sourceMappingURL=replay.js.map
|