@lucid-evolution/tx-graph 0.0.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/LICENSE +21 -0
- package/README.md +108 -0
- package/dist/index.cjs +4292 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +477 -0
- package/dist/index.d.ts +477 -0
- package/dist/index.js +4229 -0
- package/dist/index.js.map +1 -0
- package/package.json +54 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,4229 @@
|
|
|
1
|
+
// src/parse.ts
|
|
2
|
+
import {
|
|
3
|
+
coreToOutRef,
|
|
4
|
+
coreToTxOutput,
|
|
5
|
+
fromCMLRedeemerTag,
|
|
6
|
+
getAddressDetails
|
|
7
|
+
} from "@lucid-evolution/utils";
|
|
8
|
+
|
|
9
|
+
// src/core.ts
|
|
10
|
+
import * as CML from "@anastasia-labs/cardano-multiplatform-lib-nodejs";
|
|
11
|
+
|
|
12
|
+
// src/parse.ts
|
|
13
|
+
var parseTransaction = (input, options = {}) => {
|
|
14
|
+
const tx = normalizeTransaction(input);
|
|
15
|
+
const body = tx.body();
|
|
16
|
+
const hash = CML.hash_transaction(body).to_hex();
|
|
17
|
+
const outputs = parseOutputs(hash, body.outputs());
|
|
18
|
+
const mint = parseMint(body.mint());
|
|
19
|
+
const splitMint = splitMintAssets(mint);
|
|
20
|
+
const auxiliaryDataHash = body.auxiliary_data_hash()?.to_hex();
|
|
21
|
+
return {
|
|
22
|
+
hash,
|
|
23
|
+
label: options.label,
|
|
24
|
+
status: options.status ?? "built",
|
|
25
|
+
cbor: tx.to_cbor_hex(),
|
|
26
|
+
sizeBytes: tx.to_cbor_bytes().length,
|
|
27
|
+
fee: body.fee().toString(),
|
|
28
|
+
validityInterval: {
|
|
29
|
+
validFrom: body.validity_interval_start()?.toString(),
|
|
30
|
+
validTo: body.ttl()?.toString()
|
|
31
|
+
},
|
|
32
|
+
inputs: parseInputList(body.inputs()),
|
|
33
|
+
referenceInputs: parseInputList(body.reference_inputs()),
|
|
34
|
+
collateralInputs: parseInputList(body.collateral_inputs()),
|
|
35
|
+
collateralReturn: parseOptionalOutput(body.collateral_return()),
|
|
36
|
+
totalCollateral: body.total_collateral()?.toString(),
|
|
37
|
+
outputs,
|
|
38
|
+
mint,
|
|
39
|
+
mintedAssets: splitMint.mintedAssets,
|
|
40
|
+
burnedAssets: splitMint.burnedAssets,
|
|
41
|
+
withdrawals: parseWithdrawals(body.withdrawals()),
|
|
42
|
+
certificates: parseCertificates(body.certs()),
|
|
43
|
+
redeemers: parseRedeemers(tx.witness_set().redeemers()),
|
|
44
|
+
requiredSigners: parseRequiredSigners(body.required_signers()),
|
|
45
|
+
auxiliaryDataHash
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
var parseTransactionCbor = (cbor, options = {}) => parseTransaction(cbor, options);
|
|
49
|
+
var normalizeTransaction = (input) => {
|
|
50
|
+
if (typeof input === "string") {
|
|
51
|
+
return CML.Transaction.from_cbor_hex(input);
|
|
52
|
+
}
|
|
53
|
+
if (hasToTransaction(input)) {
|
|
54
|
+
return input.toTransaction();
|
|
55
|
+
}
|
|
56
|
+
if (isCmlTransaction(input)) {
|
|
57
|
+
return input;
|
|
58
|
+
}
|
|
59
|
+
if (hasToCBOR(input)) {
|
|
60
|
+
return CML.Transaction.from_cbor_hex(input.toCBOR({ canonical: false }));
|
|
61
|
+
}
|
|
62
|
+
throw new TypeError("Unsupported transaction input");
|
|
63
|
+
};
|
|
64
|
+
var hasToTransaction = (input) => typeof input === "object" && input !== null && "toTransaction" in input && typeof input.toTransaction === "function";
|
|
65
|
+
var hasToCBOR = (input) => typeof input === "object" && input !== null && "toCBOR" in input && typeof input.toCBOR === "function";
|
|
66
|
+
var isCmlTransaction = (input) => typeof input === "object" && input !== null && "body" in input && "witness_set" in input && typeof input.body === "function" && typeof input.witness_set === "function";
|
|
67
|
+
var parseInputList = (inputs) => {
|
|
68
|
+
if (!inputs) return [];
|
|
69
|
+
const result = [];
|
|
70
|
+
for (let index = 0; index < inputs.len(); index++) {
|
|
71
|
+
result.push(coreToOutRef(inputs.get(index)));
|
|
72
|
+
}
|
|
73
|
+
return result;
|
|
74
|
+
};
|
|
75
|
+
var parseOutputs = (txHash, outputs) => {
|
|
76
|
+
const result = [];
|
|
77
|
+
for (let index = 0; index < outputs.len(); index++) {
|
|
78
|
+
result.push({
|
|
79
|
+
txHash,
|
|
80
|
+
outputIndex: index,
|
|
81
|
+
...parseTxOutput(outputs.get(index)),
|
|
82
|
+
resolution: "resolved",
|
|
83
|
+
tags: []
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
return result;
|
|
87
|
+
};
|
|
88
|
+
var parseOptionalOutput = (output) => output ? parseTxOutput(output) : void 0;
|
|
89
|
+
var parseTxOutput = (output) => {
|
|
90
|
+
const txOutput = coreToTxOutput(output);
|
|
91
|
+
const credentials = credentialsOf(txOutput.address);
|
|
92
|
+
return {
|
|
93
|
+
address: txOutput.address,
|
|
94
|
+
paymentCredential: credentials.paymentCredential,
|
|
95
|
+
stakeCredential: credentials.stakeCredential,
|
|
96
|
+
assets: traceAssets(txOutput.assets),
|
|
97
|
+
datumHash: txOutput.datumHash ?? void 0,
|
|
98
|
+
datum: txOutput.datum ?? void 0,
|
|
99
|
+
scriptRef: txOutput.scriptRef ? traceScriptRef(txOutput.scriptRef) : void 0
|
|
100
|
+
};
|
|
101
|
+
};
|
|
102
|
+
var credentialsOf = (address) => {
|
|
103
|
+
try {
|
|
104
|
+
const { paymentCredential, stakeCredential } = getAddressDetails(address);
|
|
105
|
+
return {
|
|
106
|
+
paymentCredential: paymentCredential ?? void 0,
|
|
107
|
+
stakeCredential: stakeCredential ?? void 0
|
|
108
|
+
};
|
|
109
|
+
} catch {
|
|
110
|
+
return {};
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
var traceAssets = (assets) => {
|
|
114
|
+
const result = {};
|
|
115
|
+
for (const unit of Object.keys(assets).sort()) {
|
|
116
|
+
result[unit] = assets[unit].toString();
|
|
117
|
+
}
|
|
118
|
+
return result;
|
|
119
|
+
};
|
|
120
|
+
var traceScriptRef = (script) => ({
|
|
121
|
+
type: script.type,
|
|
122
|
+
hash: scriptHash(script),
|
|
123
|
+
sizeBytes: script.script.length / 2
|
|
124
|
+
});
|
|
125
|
+
var scriptHash = (script) => {
|
|
126
|
+
try {
|
|
127
|
+
switch (script.type) {
|
|
128
|
+
case "Native":
|
|
129
|
+
return CML.NativeScript.from_cbor_hex(script.script).hash().to_hex();
|
|
130
|
+
case "PlutusV1":
|
|
131
|
+
return CML.PlutusScript.from_v1(
|
|
132
|
+
CML.PlutusV1Script.from_cbor_hex(script.script)
|
|
133
|
+
).hash().to_hex();
|
|
134
|
+
case "PlutusV2":
|
|
135
|
+
return CML.PlutusScript.from_v2(
|
|
136
|
+
CML.PlutusV2Script.from_cbor_hex(script.script)
|
|
137
|
+
).hash().to_hex();
|
|
138
|
+
case "PlutusV3":
|
|
139
|
+
return CML.PlutusScript.from_v3(
|
|
140
|
+
CML.PlutusV3Script.from_cbor_hex(script.script)
|
|
141
|
+
).hash().to_hex();
|
|
142
|
+
}
|
|
143
|
+
} catch {
|
|
144
|
+
return void 0;
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
var parseMint = (mint) => {
|
|
148
|
+
const assets = {};
|
|
149
|
+
if (!mint) return {};
|
|
150
|
+
const policies = mint.keys();
|
|
151
|
+
for (let policyIndex = 0; policyIndex < policies.len(); policyIndex++) {
|
|
152
|
+
const policy = policies.get(policyIndex);
|
|
153
|
+
const policyId = policy.to_hex();
|
|
154
|
+
const policyAssets = mint.get_assets(policy);
|
|
155
|
+
if (!policyAssets) continue;
|
|
156
|
+
const assetNames = policyAssets.keys();
|
|
157
|
+
for (let assetIndex = 0; assetIndex < assetNames.len(); assetIndex++) {
|
|
158
|
+
const assetName2 = assetNames.get(assetIndex);
|
|
159
|
+
const quantity = policyAssets.get(assetName2);
|
|
160
|
+
if (quantity === void 0 || quantity === 0n) continue;
|
|
161
|
+
assets[policyId + assetName2.to_js_value()] = quantity;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return traceAssets(assets);
|
|
165
|
+
};
|
|
166
|
+
var splitMintAssets = (mint) => {
|
|
167
|
+
const mintedAssets = {};
|
|
168
|
+
const burnedAssets = {};
|
|
169
|
+
for (const unit of Object.keys(mint).sort()) {
|
|
170
|
+
const quantity = BigInt(mint[unit]);
|
|
171
|
+
if (quantity > 0n) {
|
|
172
|
+
mintedAssets[unit] = quantity.toString();
|
|
173
|
+
} else if (quantity < 0n) {
|
|
174
|
+
burnedAssets[unit] = (-quantity).toString();
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return { mintedAssets, burnedAssets };
|
|
178
|
+
};
|
|
179
|
+
var parseWithdrawals = (withdrawals) => {
|
|
180
|
+
if (!withdrawals) return [];
|
|
181
|
+
const result = [];
|
|
182
|
+
const rewardAccounts = withdrawals.keys();
|
|
183
|
+
for (let index = 0; index < rewardAccounts.len(); index++) {
|
|
184
|
+
const rewardAccount = rewardAccounts.get(index);
|
|
185
|
+
const amount = withdrawals.get(rewardAccount);
|
|
186
|
+
if (amount === void 0) continue;
|
|
187
|
+
result.push({
|
|
188
|
+
rewardAddress: rewardAccount.to_address().to_bech32(void 0),
|
|
189
|
+
amount: amount.toString()
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
return result;
|
|
193
|
+
};
|
|
194
|
+
var parseCertificates = (certificates) => {
|
|
195
|
+
if (!certificates) return [];
|
|
196
|
+
const result = [];
|
|
197
|
+
for (let index = 0; index < certificates.len(); index++) {
|
|
198
|
+
const certificate = certificates.get(index);
|
|
199
|
+
const kind = certificate.kind();
|
|
200
|
+
result.push({
|
|
201
|
+
index,
|
|
202
|
+
kind,
|
|
203
|
+
kindName: certificateKindName(kind),
|
|
204
|
+
cbor: cborHex(certificate)
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
return result;
|
|
208
|
+
};
|
|
209
|
+
var certificateKindName = (kind) => {
|
|
210
|
+
for (const [name, value] of Object.entries(CML.CertificateKind)) {
|
|
211
|
+
if (typeof value === "number" && value === kind) return name;
|
|
212
|
+
}
|
|
213
|
+
return `Unknown(${kind})`;
|
|
214
|
+
};
|
|
215
|
+
var parseRequiredSigners = (signers) => {
|
|
216
|
+
if (!signers) return [];
|
|
217
|
+
const result = [];
|
|
218
|
+
for (let index = 0; index < signers.len(); index++) {
|
|
219
|
+
result.push(signers.get(index).to_hex());
|
|
220
|
+
}
|
|
221
|
+
return result;
|
|
222
|
+
};
|
|
223
|
+
var parseRedeemers = (redeemers) => {
|
|
224
|
+
if (!redeemers) return [];
|
|
225
|
+
return canonicalRedeemerEntries(redeemers).map(
|
|
226
|
+
(entry, redeemerListIndex) => ({
|
|
227
|
+
tag: fromCMLRedeemerTag(entry.tag),
|
|
228
|
+
index: entry.index.toString(),
|
|
229
|
+
redeemerListIndex,
|
|
230
|
+
data: entry.data.to_cbor_hex(),
|
|
231
|
+
exUnits: {
|
|
232
|
+
mem: entry.exUnits.mem().toString(),
|
|
233
|
+
steps: entry.exUnits.steps().toString()
|
|
234
|
+
}
|
|
235
|
+
})
|
|
236
|
+
);
|
|
237
|
+
};
|
|
238
|
+
var canonicalRedeemerEntries = (redeemers) => {
|
|
239
|
+
const entries = [];
|
|
240
|
+
const legacyRedeemers = redeemers.as_arr_legacy_redeemer();
|
|
241
|
+
if (legacyRedeemers) {
|
|
242
|
+
for (let index = 0; index < legacyRedeemers.len(); index++) {
|
|
243
|
+
const redeemer = legacyRedeemers.get(index);
|
|
244
|
+
const tag = redeemer.tag();
|
|
245
|
+
const redeemerIndex = redeemer.index();
|
|
246
|
+
entries.push({
|
|
247
|
+
tag,
|
|
248
|
+
index: redeemerIndex,
|
|
249
|
+
data: redeemer.data(),
|
|
250
|
+
exUnits: redeemer.ex_units(),
|
|
251
|
+
sortKey: redeemerKeyBytes(tag, redeemerIndex)
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
const mapRedeemers = redeemers.as_map_redeemer_key_to_redeemer_val();
|
|
256
|
+
if (mapRedeemers) {
|
|
257
|
+
const keys = mapRedeemers.keys();
|
|
258
|
+
for (let index = 0; index < keys.len(); index++) {
|
|
259
|
+
const key = keys.get(index);
|
|
260
|
+
const value = mapRedeemers.get(key);
|
|
261
|
+
if (!value) continue;
|
|
262
|
+
const tag = key.tag();
|
|
263
|
+
const redeemerIndex = key.index();
|
|
264
|
+
entries.push({
|
|
265
|
+
tag,
|
|
266
|
+
index: redeemerIndex,
|
|
267
|
+
data: value.data(),
|
|
268
|
+
exUnits: value.ex_units(),
|
|
269
|
+
sortKey: redeemerKeyBytes(tag, redeemerIndex)
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
return entries.sort(
|
|
274
|
+
(left, right) => compareBytes(left.sortKey, right.sortKey)
|
|
275
|
+
);
|
|
276
|
+
};
|
|
277
|
+
var redeemerKeyBytes = (tag, index) => CML.RedeemerKey.new(tag, index).to_canonical_cbor_bytes();
|
|
278
|
+
var compareBytes = (left, right) => {
|
|
279
|
+
if (left.length !== right.length) return left.length - right.length;
|
|
280
|
+
for (let index = 0; index < left.length; index++) {
|
|
281
|
+
if (left[index] !== right[index]) return left[index] - right[index];
|
|
282
|
+
}
|
|
283
|
+
return 0;
|
|
284
|
+
};
|
|
285
|
+
var cborHex = (value) => {
|
|
286
|
+
try {
|
|
287
|
+
return value.to_cbor_hex?.();
|
|
288
|
+
} catch {
|
|
289
|
+
return void 0;
|
|
290
|
+
}
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
// src/resolve.ts
|
|
294
|
+
import { getAddressDetails as getAddressDetails2 } from "@lucid-evolution/utils";
|
|
295
|
+
var createResolutionCache = () => {
|
|
296
|
+
const producedUtxos = /* @__PURE__ */ new Map();
|
|
297
|
+
const knownUtxos = /* @__PURE__ */ new Map();
|
|
298
|
+
const addProducedUtxos = (utxos) => {
|
|
299
|
+
for (const utxo of utxos) {
|
|
300
|
+
producedUtxos.set(outRefKey(utxo), normalizeTraceUtxo(utxo));
|
|
301
|
+
}
|
|
302
|
+
};
|
|
303
|
+
const addResolvedUtxos = (utxos) => {
|
|
304
|
+
for (const utxo of utxos) {
|
|
305
|
+
const traceUtxo = normalizeResolvedUtxo(utxo);
|
|
306
|
+
knownUtxos.set(outRefKey(traceUtxo), traceUtxo);
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
return {
|
|
310
|
+
addProducedUtxos,
|
|
311
|
+
addTransactionOutputs: (transaction) => addProducedUtxos(producedUtxosFromTransaction(transaction)),
|
|
312
|
+
addResolvedUtxos,
|
|
313
|
+
getProducedUtxo: (outRef) => producedUtxos.get(outRefKey(outRef)),
|
|
314
|
+
getKnownUtxo: (outRef) => knownUtxos.get(outRefKey(outRef)),
|
|
315
|
+
resolveOutRefs: (outRefs, options = {}) => resolveOutRefs(outRefs, {
|
|
316
|
+
...options,
|
|
317
|
+
producedUtxos,
|
|
318
|
+
knownUtxos
|
|
319
|
+
})
|
|
320
|
+
};
|
|
321
|
+
};
|
|
322
|
+
var outRefKey = (outRef) => `${outRef.txHash}#${outRef.outputIndex}`;
|
|
323
|
+
var parseOutRefKey = (key) => {
|
|
324
|
+
const separator = key.lastIndexOf("#");
|
|
325
|
+
if (separator < 1) throw new Error(`Invalid out ref key: ${key}`);
|
|
326
|
+
const outputIndex = Number(key.slice(separator + 1));
|
|
327
|
+
if (!Number.isInteger(outputIndex) || outputIndex < 0) {
|
|
328
|
+
throw new Error(`Invalid out ref key: ${key}`);
|
|
329
|
+
}
|
|
330
|
+
return {
|
|
331
|
+
txHash: key.slice(0, separator),
|
|
332
|
+
outputIndex
|
|
333
|
+
};
|
|
334
|
+
};
|
|
335
|
+
var producedUtxosFromTransaction = (transaction) => transaction.outputs.map((utxo) => normalizeTraceUtxo(utxo));
|
|
336
|
+
var toTraceUtxo = (utxo, options = {}) => ({
|
|
337
|
+
txHash: utxo.txHash,
|
|
338
|
+
outputIndex: utxo.outputIndex,
|
|
339
|
+
...toTraceTxOutput(utxo),
|
|
340
|
+
resolution: options.resolution ?? "resolved",
|
|
341
|
+
unresolvedReason: options.unresolvedReason,
|
|
342
|
+
tags: [...options.tags ?? []]
|
|
343
|
+
});
|
|
344
|
+
var toTraceTxOutput = (output) => {
|
|
345
|
+
const credentials = credentialsOf2(output.address);
|
|
346
|
+
return {
|
|
347
|
+
address: output.address,
|
|
348
|
+
paymentCredential: credentials.paymentCredential,
|
|
349
|
+
stakeCredential: credentials.stakeCredential,
|
|
350
|
+
assets: traceAssets2(output.assets),
|
|
351
|
+
datumHash: output.datumHash ?? void 0,
|
|
352
|
+
datum: output.datum ?? void 0,
|
|
353
|
+
scriptRef: output.scriptRef ? traceScriptRef2(output.scriptRef) : void 0
|
|
354
|
+
};
|
|
355
|
+
};
|
|
356
|
+
var unresolvedUtxo = (outRef, reason) => ({
|
|
357
|
+
txHash: outRef.txHash,
|
|
358
|
+
outputIndex: outRef.outputIndex,
|
|
359
|
+
address: "unresolved",
|
|
360
|
+
assets: {},
|
|
361
|
+
resolution: "unresolved",
|
|
362
|
+
unresolvedReason: reason,
|
|
363
|
+
tags: []
|
|
364
|
+
});
|
|
365
|
+
var genesisUtxo = (outRef) => ({
|
|
366
|
+
txHash: outRef.txHash,
|
|
367
|
+
outputIndex: outRef.outputIndex,
|
|
368
|
+
address: "genesis",
|
|
369
|
+
assets: {},
|
|
370
|
+
resolution: "genesis",
|
|
371
|
+
tags: ["genesis"]
|
|
372
|
+
});
|
|
373
|
+
var resolveOutRefs = async (outRefs, options) => {
|
|
374
|
+
const requested = uniqueOutRefs(outRefs);
|
|
375
|
+
const requestedKeys = new Set(requested.map(outRefKey));
|
|
376
|
+
const resolved = /* @__PURE__ */ new Map();
|
|
377
|
+
const sources = {};
|
|
378
|
+
const missReasons = /* @__PURE__ */ new Map();
|
|
379
|
+
const warnings = [];
|
|
380
|
+
const recordResolved = (utxo, source) => {
|
|
381
|
+
const traceUtxo = source === "genesis" ? normalizeGenesisUtxo(utxo) : normalizeResolvedUtxo(utxo);
|
|
382
|
+
const key = outRefKey(traceUtxo);
|
|
383
|
+
if (!requestedKeys.has(key) || resolved.has(key)) return;
|
|
384
|
+
resolved.set(key, traceUtxo);
|
|
385
|
+
sources[key] = source;
|
|
386
|
+
missReasons.delete(key);
|
|
387
|
+
};
|
|
388
|
+
for (const outRef of requested) {
|
|
389
|
+
if (!isGenesisOutRef(outRef)) continue;
|
|
390
|
+
const knownGenesisUtxo = options.knownUtxos?.get(outRefKey(outRef));
|
|
391
|
+
recordResolved(knownGenesisUtxo ?? genesisUtxo(outRef), "genesis");
|
|
392
|
+
}
|
|
393
|
+
for (const outRef of requested) {
|
|
394
|
+
const utxo = options.producedUtxos?.get(outRefKey(outRef));
|
|
395
|
+
if (utxo) recordResolved(utxo, "scenario-cache");
|
|
396
|
+
}
|
|
397
|
+
let missing = unresolvedOutRefs(requested, resolved);
|
|
398
|
+
if (missing.length > 0) {
|
|
399
|
+
if (options.provider) {
|
|
400
|
+
try {
|
|
401
|
+
const providerUtxos = await options.provider.getUtxosByOutRef(
|
|
402
|
+
missing.map(toProviderOutRef)
|
|
403
|
+
);
|
|
404
|
+
for (const utxo of providerUtxos) {
|
|
405
|
+
recordResolved(utxo, "provider");
|
|
406
|
+
}
|
|
407
|
+
for (const outRef of unresolvedOutRefs(missing, resolved)) {
|
|
408
|
+
missReasons.set(outRefKey(outRef), "provider-miss");
|
|
409
|
+
}
|
|
410
|
+
} catch (error) {
|
|
411
|
+
const message = errorMessage(error);
|
|
412
|
+
warnings.push({
|
|
413
|
+
code: "provider-resolution-error",
|
|
414
|
+
message
|
|
415
|
+
});
|
|
416
|
+
for (const outRef of missing) {
|
|
417
|
+
missReasons.set(outRefKey(outRef), "provider-error");
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
} else {
|
|
421
|
+
for (const outRef of missing) {
|
|
422
|
+
missReasons.set(outRefKey(outRef), "missing-provider");
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
missing = unresolvedOutRefs(requested, resolved);
|
|
427
|
+
for (const outRef of missing) {
|
|
428
|
+
const utxo = options.knownUtxos?.get(outRefKey(outRef));
|
|
429
|
+
if (utxo) recordResolved(utxo, "known-utxos");
|
|
430
|
+
}
|
|
431
|
+
missing = unresolvedOutRefs(requested, resolved);
|
|
432
|
+
if (missing.length > 0 && options.resolver) {
|
|
433
|
+
try {
|
|
434
|
+
const resolverUtxos = await options.resolver([...missing]);
|
|
435
|
+
for (const utxo of resolverUtxos) {
|
|
436
|
+
recordResolved(utxo, "resolver");
|
|
437
|
+
}
|
|
438
|
+
for (const outRef of unresolvedOutRefs(missing, resolved)) {
|
|
439
|
+
missReasons.set(outRefKey(outRef), "resolver-miss");
|
|
440
|
+
}
|
|
441
|
+
} catch (error) {
|
|
442
|
+
const message = errorMessage(error);
|
|
443
|
+
warnings.push({
|
|
444
|
+
code: "resolver-resolution-error",
|
|
445
|
+
message
|
|
446
|
+
});
|
|
447
|
+
for (const outRef of missing) {
|
|
448
|
+
missReasons.set(outRefKey(outRef), "resolver-error");
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
const utxos = outRefs.map((outRef) => {
|
|
453
|
+
const key = outRefKey(outRef);
|
|
454
|
+
const utxo = resolved.get(key);
|
|
455
|
+
if (utxo) return utxo;
|
|
456
|
+
sources[key] = "unresolved";
|
|
457
|
+
return unresolvedUtxo(outRef, missReasons.get(key) ?? "unresolved");
|
|
458
|
+
});
|
|
459
|
+
return { utxos, sources, warnings };
|
|
460
|
+
};
|
|
461
|
+
var uniqueOutRefs = (outRefs) => {
|
|
462
|
+
const seen = /* @__PURE__ */ new Set();
|
|
463
|
+
const result = [];
|
|
464
|
+
for (const outRef of outRefs) {
|
|
465
|
+
const key = outRefKey(outRef);
|
|
466
|
+
if (seen.has(key)) continue;
|
|
467
|
+
seen.add(key);
|
|
468
|
+
result.push(outRef);
|
|
469
|
+
}
|
|
470
|
+
return result;
|
|
471
|
+
};
|
|
472
|
+
var unresolvedOutRefs = (outRefs, resolved) => outRefs.filter((outRef) => !resolved.has(outRefKey(outRef)));
|
|
473
|
+
var toProviderOutRef = (outRef) => ({
|
|
474
|
+
txHash: outRef.txHash,
|
|
475
|
+
outputIndex: outRef.outputIndex
|
|
476
|
+
});
|
|
477
|
+
var isGenesisOutRef = (outRef) => /^0{64}$/.test(outRef.txHash);
|
|
478
|
+
var normalizeResolvedUtxo = (utxo) => {
|
|
479
|
+
const traceUtxo = isTraceUtxo(utxo) ? normalizeTraceUtxo({ ...utxo, resolution: "resolved" }) : toTraceUtxo(utxo);
|
|
480
|
+
return isGenesisOutRef(traceUtxo) ? normalizeGenesisUtxo(traceUtxo) : traceUtxo;
|
|
481
|
+
};
|
|
482
|
+
var normalizeGenesisUtxo = (utxo) => {
|
|
483
|
+
const traceUtxo = isTraceUtxo(utxo) ? normalizeTraceUtxo(utxo) : toTraceUtxo(utxo);
|
|
484
|
+
return normalizeTraceUtxo({
|
|
485
|
+
...traceUtxo,
|
|
486
|
+
resolution: "genesis",
|
|
487
|
+
unresolvedReason: void 0,
|
|
488
|
+
tags: [.../* @__PURE__ */ new Set(["genesis", ...traceUtxo.tags])]
|
|
489
|
+
});
|
|
490
|
+
};
|
|
491
|
+
var normalizeTraceUtxo = (utxo) => ({
|
|
492
|
+
...utxo,
|
|
493
|
+
assets: sortedTraceAssets(utxo.assets),
|
|
494
|
+
tags: [...utxo.tags].sort()
|
|
495
|
+
});
|
|
496
|
+
var isTraceUtxo = (utxo) => "resolution" in utxo && "tags" in utxo;
|
|
497
|
+
var credentialsOf2 = (address) => {
|
|
498
|
+
try {
|
|
499
|
+
const { paymentCredential, stakeCredential } = getAddressDetails2(address);
|
|
500
|
+
return {
|
|
501
|
+
paymentCredential: paymentCredential ?? void 0,
|
|
502
|
+
stakeCredential: stakeCredential ?? void 0
|
|
503
|
+
};
|
|
504
|
+
} catch {
|
|
505
|
+
return {};
|
|
506
|
+
}
|
|
507
|
+
};
|
|
508
|
+
var traceAssets2 = (assets) => {
|
|
509
|
+
const result = {};
|
|
510
|
+
for (const unit of Object.keys(assets).sort()) {
|
|
511
|
+
result[unit] = assets[unit].toString();
|
|
512
|
+
}
|
|
513
|
+
return result;
|
|
514
|
+
};
|
|
515
|
+
var sortedTraceAssets = (assets) => {
|
|
516
|
+
const result = {};
|
|
517
|
+
for (const unit of Object.keys(assets).sort()) {
|
|
518
|
+
result[unit] = assets[unit];
|
|
519
|
+
}
|
|
520
|
+
return result;
|
|
521
|
+
};
|
|
522
|
+
var traceScriptRef2 = (script) => ({
|
|
523
|
+
type: script.type,
|
|
524
|
+
hash: scriptHash2(script),
|
|
525
|
+
sizeBytes: script.script.length / 2
|
|
526
|
+
});
|
|
527
|
+
var scriptHash2 = (script) => {
|
|
528
|
+
try {
|
|
529
|
+
switch (script.type) {
|
|
530
|
+
case "Native":
|
|
531
|
+
return CML.NativeScript.from_cbor_hex(script.script).hash().to_hex();
|
|
532
|
+
case "PlutusV1":
|
|
533
|
+
return CML.PlutusScript.from_v1(
|
|
534
|
+
CML.PlutusV1Script.from_cbor_hex(script.script)
|
|
535
|
+
).hash().to_hex();
|
|
536
|
+
case "PlutusV2":
|
|
537
|
+
return CML.PlutusScript.from_v2(
|
|
538
|
+
CML.PlutusV2Script.from_cbor_hex(script.script)
|
|
539
|
+
).hash().to_hex();
|
|
540
|
+
case "PlutusV3":
|
|
541
|
+
return CML.PlutusScript.from_v3(
|
|
542
|
+
CML.PlutusV3Script.from_cbor_hex(script.script)
|
|
543
|
+
).hash().to_hex();
|
|
544
|
+
}
|
|
545
|
+
} catch {
|
|
546
|
+
return void 0;
|
|
547
|
+
}
|
|
548
|
+
};
|
|
549
|
+
var errorMessage = (error) => error instanceof Error ? error.message : String(error);
|
|
550
|
+
|
|
551
|
+
// src/render/common.ts
|
|
552
|
+
var buildRenderGraph = (trace) => {
|
|
553
|
+
const nodes = /* @__PURE__ */ new Map();
|
|
554
|
+
for (const transaction of trace.transactions) {
|
|
555
|
+
nodes.set(txNodeId(transaction.hash), {
|
|
556
|
+
id: txNodeId(transaction.hash),
|
|
557
|
+
kind: "transaction",
|
|
558
|
+
label: transactionLabel(transaction, trace)
|
|
559
|
+
});
|
|
560
|
+
}
|
|
561
|
+
for (const [key, utxo] of Object.entries(trace.utxos).sort(
|
|
562
|
+
([a], [b]) => a.localeCompare(b)
|
|
563
|
+
)) {
|
|
564
|
+
nodes.set(utxoNodeId(utxo), {
|
|
565
|
+
id: utxoNodeId(utxo),
|
|
566
|
+
kind: "utxo",
|
|
567
|
+
label: utxoLabel(key, utxo, trace),
|
|
568
|
+
unresolved: utxo.resolution === "unresolved",
|
|
569
|
+
genesis: utxo.resolution === "genesis"
|
|
570
|
+
});
|
|
571
|
+
}
|
|
572
|
+
for (const edge of sortedEdges(trace.edges)) {
|
|
573
|
+
ensureEndpointNode(nodes, edge.from, trace);
|
|
574
|
+
ensureEndpointNode(nodes, edge.to, trace);
|
|
575
|
+
}
|
|
576
|
+
return {
|
|
577
|
+
nodes: [...nodes.values()].sort(
|
|
578
|
+
(left, right) => left.id.localeCompare(right.id)
|
|
579
|
+
),
|
|
580
|
+
edges: sortedEdges(trace.edges)
|
|
581
|
+
};
|
|
582
|
+
};
|
|
583
|
+
var sortedEdges = (edges) => [...edges].sort(
|
|
584
|
+
(left, right) => edgeSortKey(left).localeCompare(edgeSortKey(right))
|
|
585
|
+
);
|
|
586
|
+
var edgeStyle = (kind) => {
|
|
587
|
+
switch (kind) {
|
|
588
|
+
case "spends":
|
|
589
|
+
return { label: "spends", color: "#374151", style: "solid" };
|
|
590
|
+
case "reads":
|
|
591
|
+
return { label: "reads", color: "#2563eb", style: "dashed" };
|
|
592
|
+
case "collateral":
|
|
593
|
+
return { label: "collateral", color: "#d97706", style: "dashed" };
|
|
594
|
+
case "collateralReturn":
|
|
595
|
+
return { label: "collateral return", color: "#f59e0b", style: "solid" };
|
|
596
|
+
case "produces":
|
|
597
|
+
return { label: "produces", color: "#15803d", style: "solid" };
|
|
598
|
+
case "mints":
|
|
599
|
+
return { label: "mints", color: "#7c3aed", style: "solid" };
|
|
600
|
+
case "burns":
|
|
601
|
+
return { label: "burns", color: "#dc2626", style: "solid" };
|
|
602
|
+
case "withdraws":
|
|
603
|
+
return { label: "withdraws", color: "#0891b2", style: "solid" };
|
|
604
|
+
case "certifies":
|
|
605
|
+
return { label: "certifies", color: "#4f46e5", style: "solid" };
|
|
606
|
+
case "requiresSigner":
|
|
607
|
+
return { label: "requires signer", color: "#6b7280", style: "dotted" };
|
|
608
|
+
}
|
|
609
|
+
};
|
|
610
|
+
var txNodeId = (txHash) => `tx:${txHash}`;
|
|
611
|
+
var utxoNodeId = (outRef) => `utxo:${outRefKey(outRef)}`;
|
|
612
|
+
var shortHash = (value, size = 8) => value.length <= size * 2 ? value : `${value.slice(0, size)}...${value.slice(-size)}`;
|
|
613
|
+
var assetName = (unit, trace) => {
|
|
614
|
+
const alias = trace.aliases.assets[unit];
|
|
615
|
+
if (alias) return alias;
|
|
616
|
+
if (unit === "lovelace") return "lovelace";
|
|
617
|
+
return shortHash(unit, 6);
|
|
618
|
+
};
|
|
619
|
+
var addressName = (address, trace) => trace.aliases.addresses[address] ?? shortHash(address, 10);
|
|
620
|
+
var formatAssets = (assets, trace, options = {}) => {
|
|
621
|
+
const units = Object.keys(assets).sort(assetUnitSort);
|
|
622
|
+
const maxAssets = options.maxAssets ?? 4;
|
|
623
|
+
const visible = units.slice(0, maxAssets);
|
|
624
|
+
const rendered = visible.map(
|
|
625
|
+
(unit) => `${assets[unit]} ${assetName(unit, trace)}`
|
|
626
|
+
);
|
|
627
|
+
if (units.length > visible.length) {
|
|
628
|
+
rendered.push(`+${units.length - visible.length} more`);
|
|
629
|
+
}
|
|
630
|
+
return rendered.length > 0 ? rendered.join("\\n") : "no assets";
|
|
631
|
+
};
|
|
632
|
+
var transactionLabel = (transaction, trace) => {
|
|
633
|
+
const lines = [
|
|
634
|
+
transaction.label ?? `tx ${shortHash(transaction.hash)}`,
|
|
635
|
+
`status: ${transaction.status}`,
|
|
636
|
+
`fee: ${transaction.fee}`
|
|
637
|
+
];
|
|
638
|
+
if (Object.keys(transaction.mintedAssets).length > 0) {
|
|
639
|
+
lines.push(`mint: ${formatAssets(transaction.mintedAssets, trace)}`);
|
|
640
|
+
}
|
|
641
|
+
if (Object.keys(transaction.burnedAssets).length > 0) {
|
|
642
|
+
lines.push(`burn: ${formatAssets(transaction.burnedAssets, trace)}`);
|
|
643
|
+
}
|
|
644
|
+
const warningCount = trace.warnings.filter(
|
|
645
|
+
(warning) => warning.txHash === transaction.hash
|
|
646
|
+
).length;
|
|
647
|
+
if (warningCount > 0) lines.push(`warnings: ${warningCount}`);
|
|
648
|
+
return lines.join("\\n");
|
|
649
|
+
};
|
|
650
|
+
var utxoLabel = (key, utxo, trace) => {
|
|
651
|
+
if (utxo.resolution === "genesis") {
|
|
652
|
+
const lines2 = ["genesis UTxO", key];
|
|
653
|
+
if (Object.keys(utxo.assets).length > 0) {
|
|
654
|
+
lines2.push(formatAssets(utxo.assets, trace));
|
|
655
|
+
}
|
|
656
|
+
lines2.push("external funding source");
|
|
657
|
+
return lines2.join("\\n");
|
|
658
|
+
}
|
|
659
|
+
const lines = [
|
|
660
|
+
utxo.tags.length > 0 ? `[${utxo.tags.join(", ")}]` : key,
|
|
661
|
+
formatAssets(utxo.assets, trace),
|
|
662
|
+
addressName(utxo.address, trace)
|
|
663
|
+
];
|
|
664
|
+
if (utxo.datum) {
|
|
665
|
+
lines.push("inline datum");
|
|
666
|
+
} else if (utxo.datumHash) {
|
|
667
|
+
lines.push(`datum ${shortHash(utxo.datumHash)}`);
|
|
668
|
+
}
|
|
669
|
+
if (utxo.scriptRef) {
|
|
670
|
+
lines.push(
|
|
671
|
+
`script ref ${utxo.scriptRef.type}${utxo.scriptRef.hash ? ` ${shortHash(utxo.scriptRef.hash)}` : ""}`
|
|
672
|
+
);
|
|
673
|
+
}
|
|
674
|
+
if (utxo.resolution === "unresolved") {
|
|
675
|
+
lines.push(`unresolved: ${utxo.unresolvedReason ?? "unknown"}`);
|
|
676
|
+
}
|
|
677
|
+
return lines.join("\\n");
|
|
678
|
+
};
|
|
679
|
+
var externalNodeLabel = (nodeId, trace) => {
|
|
680
|
+
if (nodeId.startsWith("asset:")) {
|
|
681
|
+
return assetName(nodeId.slice("asset:".length), trace);
|
|
682
|
+
}
|
|
683
|
+
if (nodeId.startsWith("withdrawal:")) {
|
|
684
|
+
return `withdrawal\\n${addressName(nodeId.slice("withdrawal:".length), trace)}`;
|
|
685
|
+
}
|
|
686
|
+
if (nodeId.startsWith("signer:")) {
|
|
687
|
+
return `signer\\n${shortHash(nodeId.slice("signer:".length))}`;
|
|
688
|
+
}
|
|
689
|
+
if (nodeId.startsWith("collateral-return:")) {
|
|
690
|
+
return `collateral return\\n${shortHash(
|
|
691
|
+
nodeId.slice("collateral-return:".length)
|
|
692
|
+
)}`;
|
|
693
|
+
}
|
|
694
|
+
if (nodeId.startsWith("certificate:")) {
|
|
695
|
+
const { txHash, index } = parseIndexedNode(
|
|
696
|
+
nodeId.slice("certificate:".length)
|
|
697
|
+
);
|
|
698
|
+
const certificate = trace.transactions.find((transaction) => transaction.hash === txHash)?.certificates.find((candidate) => candidate.index === index);
|
|
699
|
+
return certificate ? `certificate\\n${certificate.kindName}` : `certificate\\n${shortHash(txHash)}#${index}`;
|
|
700
|
+
}
|
|
701
|
+
return nodeId;
|
|
702
|
+
};
|
|
703
|
+
var ensureEndpointNode = (nodes, nodeId, trace) => {
|
|
704
|
+
if (nodes.has(nodeId)) return;
|
|
705
|
+
nodes.set(nodeId, {
|
|
706
|
+
id: nodeId,
|
|
707
|
+
kind: externalNodeKind(nodeId),
|
|
708
|
+
label: externalNodeLabel(nodeId, trace)
|
|
709
|
+
});
|
|
710
|
+
};
|
|
711
|
+
var externalNodeKind = (nodeId) => {
|
|
712
|
+
if (nodeId.startsWith("asset:")) return "asset";
|
|
713
|
+
if (nodeId.startsWith("withdrawal:")) return "withdrawal";
|
|
714
|
+
if (nodeId.startsWith("certificate:")) return "certificate";
|
|
715
|
+
if (nodeId.startsWith("signer:")) return "signer";
|
|
716
|
+
if (nodeId.startsWith("collateral-return:")) return "collateralReturn";
|
|
717
|
+
return "external";
|
|
718
|
+
};
|
|
719
|
+
var edgeSortKey = (edge) => `${edge.from}\0${edge.kind}\0${edge.to}`;
|
|
720
|
+
var assetUnitSort = (left, right) => {
|
|
721
|
+
if (left === "lovelace") return -1;
|
|
722
|
+
if (right === "lovelace") return 1;
|
|
723
|
+
return left.localeCompare(right);
|
|
724
|
+
};
|
|
725
|
+
var parseIndexedNode = (value) => {
|
|
726
|
+
const separator = value.lastIndexOf("#");
|
|
727
|
+
return {
|
|
728
|
+
txHash: separator >= 0 ? value.slice(0, separator) : value,
|
|
729
|
+
index: separator >= 0 ? Number(value.slice(separator + 1)) : 0
|
|
730
|
+
};
|
|
731
|
+
};
|
|
732
|
+
|
|
733
|
+
// src/render/options.ts
|
|
734
|
+
var resolveVisualRendererOptions = (options = {}) => {
|
|
735
|
+
const mode = options.mode ?? "overview";
|
|
736
|
+
return {
|
|
737
|
+
mode,
|
|
738
|
+
view: options.view ?? "flow",
|
|
739
|
+
privacy: { ...privacyByMode[mode], ...options.privacy },
|
|
740
|
+
budget: { ...budgetByMode[mode], ...options.budget }
|
|
741
|
+
};
|
|
742
|
+
};
|
|
743
|
+
var visualEdgeStyle = (kind) => {
|
|
744
|
+
switch (kind) {
|
|
745
|
+
case "spend":
|
|
746
|
+
return { color: "#374151", style: "solid" };
|
|
747
|
+
case "read":
|
|
748
|
+
return { color: "#2563eb", style: "dashed" };
|
|
749
|
+
case "collateral":
|
|
750
|
+
return { color: "#d97706", style: "dashed" };
|
|
751
|
+
case "collateralReturn":
|
|
752
|
+
return { color: "#f59e0b", style: "solid" };
|
|
753
|
+
case "produce":
|
|
754
|
+
return { color: "#15803d", style: "solid" };
|
|
755
|
+
case "mint":
|
|
756
|
+
return { color: "#7c3aed", style: "solid" };
|
|
757
|
+
case "burn":
|
|
758
|
+
return { color: "#dc2626", style: "solid" };
|
|
759
|
+
case "withdraw":
|
|
760
|
+
return { color: "#0891b2", style: "solid" };
|
|
761
|
+
case "certify":
|
|
762
|
+
return { color: "#4f46e5", style: "solid" };
|
|
763
|
+
case "sign":
|
|
764
|
+
return { color: "#6b7280", style: "dotted" };
|
|
765
|
+
case "diagnostic":
|
|
766
|
+
return { color: "#dc2626", style: "dashed" };
|
|
767
|
+
case "summary":
|
|
768
|
+
return { color: "#6b7280", style: "dashed" };
|
|
769
|
+
default:
|
|
770
|
+
return { color: "#6b7280", style: "solid" };
|
|
771
|
+
}
|
|
772
|
+
};
|
|
773
|
+
var privacyByMode = {
|
|
774
|
+
overview: {
|
|
775
|
+
hash: "short",
|
|
776
|
+
address: "alias",
|
|
777
|
+
datum: "marker",
|
|
778
|
+
redeemer: "label",
|
|
779
|
+
cbor: "hidden"
|
|
780
|
+
},
|
|
781
|
+
audit: {
|
|
782
|
+
hash: "short",
|
|
783
|
+
address: "alias",
|
|
784
|
+
datum: "hash",
|
|
785
|
+
redeemer: "constructor",
|
|
786
|
+
cbor: "prefix"
|
|
787
|
+
},
|
|
788
|
+
debug: {
|
|
789
|
+
hash: "full",
|
|
790
|
+
address: "full",
|
|
791
|
+
datum: "full",
|
|
792
|
+
redeemer: "full",
|
|
793
|
+
cbor: "prefix"
|
|
794
|
+
}
|
|
795
|
+
};
|
|
796
|
+
var budgetByMode = {
|
|
797
|
+
overview: {
|
|
798
|
+
maxVisibleInputs: 4,
|
|
799
|
+
maxVisibleOutputs: 4,
|
|
800
|
+
maxVisibleAssetsPerUtxo: 4,
|
|
801
|
+
maxVisibleSigners: 3,
|
|
802
|
+
maxVisibleWarnings: 3,
|
|
803
|
+
maxVisibleRedeemerFields: 2
|
|
804
|
+
},
|
|
805
|
+
audit: {
|
|
806
|
+
maxVisibleInputs: 12,
|
|
807
|
+
maxVisibleOutputs: 12,
|
|
808
|
+
maxVisibleAssetsPerUtxo: 12,
|
|
809
|
+
maxVisibleSigners: 8,
|
|
810
|
+
maxVisibleWarnings: 8,
|
|
811
|
+
maxVisibleRedeemerFields: 6
|
|
812
|
+
},
|
|
813
|
+
debug: {
|
|
814
|
+
maxVisibleInputs: Number.POSITIVE_INFINITY,
|
|
815
|
+
maxVisibleOutputs: Number.POSITIVE_INFINITY,
|
|
816
|
+
maxVisibleAssetsPerUtxo: Number.POSITIVE_INFINITY,
|
|
817
|
+
maxVisibleSigners: Number.POSITIVE_INFINITY,
|
|
818
|
+
maxVisibleWarnings: Number.POSITIVE_INFINITY,
|
|
819
|
+
maxVisibleRedeemerFields: Number.POSITIVE_INFINITY
|
|
820
|
+
}
|
|
821
|
+
};
|
|
822
|
+
|
|
823
|
+
// src/render/semantic-format.ts
|
|
824
|
+
var formatSemanticGraph = (graph, options) => {
|
|
825
|
+
const nodes = new Map(graph.nodes.map((node) => [node.id, node]));
|
|
826
|
+
const edges = [...graph.edges];
|
|
827
|
+
applyViewAnnotations(options, nodes);
|
|
828
|
+
addDiagnosticNodes(graph, options, nodes, edges);
|
|
829
|
+
addBudgetSummaries(options, nodes, edges);
|
|
830
|
+
const connectedNodeIds = /* @__PURE__ */ new Set();
|
|
831
|
+
for (const edge of edges) {
|
|
832
|
+
connectedNodeIds.add(edge.from);
|
|
833
|
+
connectedNodeIds.add(edge.to);
|
|
834
|
+
}
|
|
835
|
+
return {
|
|
836
|
+
nodes: [...nodes.values()].filter(
|
|
837
|
+
(node) => node.kind === "transaction" || node.kind === "diagnostic" || connectedNodeIds.has(node.id)
|
|
838
|
+
).sort((left, right) => left.id.localeCompare(right.id)),
|
|
839
|
+
edges: edges.sort((left, right) => left.id.localeCompare(right.id))
|
|
840
|
+
};
|
|
841
|
+
};
|
|
842
|
+
var semanticEdgeLabel = (trace, edge, options) => {
|
|
843
|
+
if (!edge.redeemerKey || !edge.action) return edge.label;
|
|
844
|
+
const base = `${edge.redeemerKey.tag} #${edge.redeemerKey.index}`;
|
|
845
|
+
if (options.privacy.redeemer === "hidden") return base;
|
|
846
|
+
if (options.mode === "overview" || options.privacy.redeemer === "label") {
|
|
847
|
+
return edge.action.label;
|
|
848
|
+
}
|
|
849
|
+
const redeemer = findRedeemer(trace, edge);
|
|
850
|
+
const parts = [base];
|
|
851
|
+
if (edge.action.label !== base) parts.push(edge.action.label);
|
|
852
|
+
parts.push(`redeemer list #${edge.redeemerKey.redeemerListIndex}`);
|
|
853
|
+
if (redeemer)
|
|
854
|
+
parts.push(`ex ${redeemer.exUnits.mem}/${redeemer.exUnits.steps}`);
|
|
855
|
+
if (options.mode === "debug" || options.privacy.redeemer === "constructor" || options.privacy.redeemer === "full") {
|
|
856
|
+
parts.push(
|
|
857
|
+
...Object.entries(edge.action.fields ?? {}).slice(0, options.budget.maxVisibleRedeemerFields).map(([label, value]) => `${label}: ${value}`)
|
|
858
|
+
);
|
|
859
|
+
}
|
|
860
|
+
if (options.mode === "debug") {
|
|
861
|
+
parts.push(`${edge.action.source}/${edge.action.confidence}`);
|
|
862
|
+
}
|
|
863
|
+
return parts.join(" \xB7 ");
|
|
864
|
+
};
|
|
865
|
+
var semanticChipLabels = (node, options) => {
|
|
866
|
+
if (node.rawRef.type === "assetPolicy") {
|
|
867
|
+
return [formatHash(node.rawRef.policyId, options)];
|
|
868
|
+
}
|
|
869
|
+
return node.chips.map((chip) => chip.label).filter((label) => node.kind !== "utxo" || label !== "resolved");
|
|
870
|
+
};
|
|
871
|
+
var visibleSemanticSections = (node, options) => {
|
|
872
|
+
if (options.mode !== "overview") return node.sections;
|
|
873
|
+
if (node.kind === "transaction") {
|
|
874
|
+
return node.sections.filter(
|
|
875
|
+
(section2) => ["facts", "inputs", "assets", "redeemers"].includes(section2.id)
|
|
876
|
+
);
|
|
877
|
+
}
|
|
878
|
+
return node.sections;
|
|
879
|
+
};
|
|
880
|
+
var visibleSemanticRows = (section2, _node, options) => {
|
|
881
|
+
const maxRows = section2.id === "assets" ? options.budget.maxVisibleAssetsPerUtxo : section2.id === "redeemers" ? options.budget.maxVisibleRedeemerFields : Number.POSITIVE_INFINITY;
|
|
882
|
+
const rows = section2.rows.slice(0, maxRows);
|
|
883
|
+
if (section2.rows.length <= rows.length) return rows;
|
|
884
|
+
return [
|
|
885
|
+
...rows,
|
|
886
|
+
{
|
|
887
|
+
id: `${section2.id}:more`,
|
|
888
|
+
label: "More",
|
|
889
|
+
value: `+${section2.rows.length - rows.length} more`,
|
|
890
|
+
tone: "neutral"
|
|
891
|
+
}
|
|
892
|
+
];
|
|
893
|
+
};
|
|
894
|
+
var semanticFieldValue = (row, options) => {
|
|
895
|
+
if (row.id.startsWith("asset:") && row.rawValue) {
|
|
896
|
+
const defaultShort = shortHash(row.rawValue, 6);
|
|
897
|
+
if (options.privacy.hash === "hidden" && (row.value === defaultShort || row.value === row.rawValue)) {
|
|
898
|
+
return "asset";
|
|
899
|
+
}
|
|
900
|
+
if (options.privacy.hash === "full" && row.value === defaultShort) {
|
|
901
|
+
return row.rawValue;
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
if (row.id === "address") {
|
|
905
|
+
switch (options.privacy.address) {
|
|
906
|
+
case "hidden":
|
|
907
|
+
return "hidden";
|
|
908
|
+
case "full":
|
|
909
|
+
return row.rawValue ?? row.value;
|
|
910
|
+
case "short":
|
|
911
|
+
return row.rawValue ? shortHash(row.rawValue, 10) : row.value;
|
|
912
|
+
case "alias":
|
|
913
|
+
return row.value;
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
if (row.id.startsWith("datum")) {
|
|
917
|
+
switch (options.privacy.datum) {
|
|
918
|
+
case "hidden":
|
|
919
|
+
return "hidden";
|
|
920
|
+
case "marker":
|
|
921
|
+
return "present";
|
|
922
|
+
case "full":
|
|
923
|
+
return row.rawValue ?? row.value;
|
|
924
|
+
default:
|
|
925
|
+
return row.value;
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
if (row.id === "keyHash" && row.rawValue) {
|
|
929
|
+
return formatHash(row.rawValue, options);
|
|
930
|
+
}
|
|
931
|
+
if (row.id === "scriptRef" && row.rawValue) {
|
|
932
|
+
if (options.privacy.hash === "hidden") {
|
|
933
|
+
return row.rawValue.split(" ")[0] ?? "script ref";
|
|
934
|
+
}
|
|
935
|
+
return options.privacy.hash === "full" ? row.rawValue : row.value;
|
|
936
|
+
}
|
|
937
|
+
return row.value;
|
|
938
|
+
};
|
|
939
|
+
var semanticFieldLabel = (row, options) => {
|
|
940
|
+
if (row.id.startsWith("asset:") && row.rawLabel) {
|
|
941
|
+
const defaultShort = shortHash(row.rawLabel, 6);
|
|
942
|
+
if (options.privacy.hash === "hidden" && (row.label === defaultShort || row.label === row.rawLabel)) {
|
|
943
|
+
return "asset";
|
|
944
|
+
}
|
|
945
|
+
if (options.privacy.hash === "full" && row.label === defaultShort) {
|
|
946
|
+
return row.rawLabel;
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
return row.label;
|
|
950
|
+
};
|
|
951
|
+
var semanticNodeTitle = (node, options) => {
|
|
952
|
+
if (node.rawRef.type === "transaction" && node.title === `tx ${shortHash(node.rawRef.txHash)}`) {
|
|
953
|
+
return `tx ${formatHash(node.rawRef.txHash, options)}`;
|
|
954
|
+
}
|
|
955
|
+
if (node.rawRef.type === "utxo" && node.title === outRefKey(node.rawRef.outRef)) {
|
|
956
|
+
return `UTxO ${formatOutRef(node.rawRef.outRef, options)}`;
|
|
957
|
+
}
|
|
958
|
+
if (node.rawRef.type === "assetPolicy" && node.title.startsWith("policy ")) {
|
|
959
|
+
return `policy ${formatHash(node.rawRef.policyId, options)}`;
|
|
960
|
+
}
|
|
961
|
+
return node.title;
|
|
962
|
+
};
|
|
963
|
+
var semanticNodeSubtitle = (node, options) => {
|
|
964
|
+
if (options.privacy.hash === "hidden") return void 0;
|
|
965
|
+
switch (node.rawRef.type) {
|
|
966
|
+
case "transaction":
|
|
967
|
+
return formatHash(node.rawRef.txHash, options);
|
|
968
|
+
case "utxo":
|
|
969
|
+
return formatOutRef(node.rawRef.outRef, options);
|
|
970
|
+
case "assetPolicy":
|
|
971
|
+
return formatHash(node.rawRef.policyId, options);
|
|
972
|
+
case "signer":
|
|
973
|
+
return formatHash(node.rawRef.keyHash, options);
|
|
974
|
+
case "withdrawal":
|
|
975
|
+
return formatAddress(node.rawRef.rewardAddress, node.subtitle, options);
|
|
976
|
+
case "certificate":
|
|
977
|
+
return `${formatHash(node.rawRef.txHash, options)}#${node.rawRef.index}`;
|
|
978
|
+
case "collateralReturn":
|
|
979
|
+
return formatHash(node.rawRef.txHash, options);
|
|
980
|
+
default:
|
|
981
|
+
return node.subtitle;
|
|
982
|
+
}
|
|
983
|
+
};
|
|
984
|
+
var isGenesisNode = (node) => node.kind === "utxo" && node.chips.some((chip) => chip.label === "genesis");
|
|
985
|
+
var isScriptNode = (node) => node.kind === "utxo" && (node.sections.some(
|
|
986
|
+
(section2) => section2.rows.some(
|
|
987
|
+
(row) => ["datum", "datumHash", "scriptRef"].includes(row.id)
|
|
988
|
+
)
|
|
989
|
+
) || node.chips.some((chip) => /script|datum|state/i.test(chip.label)));
|
|
990
|
+
var isScriptInteractionEdge = (edge, nodes) => ["spend", "read", "produce", "mint", "burn"].includes(edge.kind) && [edge.from, edge.to].some((id) => {
|
|
991
|
+
const node = nodes.get(id);
|
|
992
|
+
return node ? isScriptNode(node) : false;
|
|
993
|
+
}) || edge.kind === "collateral";
|
|
994
|
+
var addDiagnosticNodes = (graph, options, nodes, edges) => {
|
|
995
|
+
const byTx = /* @__PURE__ */ new Map();
|
|
996
|
+
for (const diagnostic of graph.diagnostics) {
|
|
997
|
+
const key = diagnostic.txHash ?? "global";
|
|
998
|
+
byTx.set(key, [...byTx.get(key) ?? [], diagnostic]);
|
|
999
|
+
}
|
|
1000
|
+
for (const [txHash, diagnostics] of byTx) {
|
|
1001
|
+
const visible = diagnostics.slice(0, options.budget.maxVisibleWarnings);
|
|
1002
|
+
for (const diagnostic of visible) {
|
|
1003
|
+
addDiagnosticNode(options, diagnostic, nodes, edges);
|
|
1004
|
+
}
|
|
1005
|
+
if (diagnostics.length > visible.length) {
|
|
1006
|
+
const hiddenCount = diagnostics.length - visible.length;
|
|
1007
|
+
const node = summaryNode(
|
|
1008
|
+
`summary:${txHash}:diagnostics`,
|
|
1009
|
+
`+${hiddenCount} diagnostics`,
|
|
1010
|
+
"warnings",
|
|
1011
|
+
hiddenCount
|
|
1012
|
+
);
|
|
1013
|
+
nodes.set(node.id, node);
|
|
1014
|
+
if (txHash !== "global") {
|
|
1015
|
+
edges.push(summaryEdge(node.id, `tx:${txHash}`, txHash, "warnings"));
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
};
|
|
1020
|
+
var applyViewAnnotations = (options, nodes) => {
|
|
1021
|
+
if (options.view !== "scriptInteraction") return;
|
|
1022
|
+
for (const [id, node] of nodes) {
|
|
1023
|
+
if (node.kind !== "utxo" || !isScriptNode(node)) continue;
|
|
1024
|
+
nodes.set(id, {
|
|
1025
|
+
...node,
|
|
1026
|
+
chips: [
|
|
1027
|
+
{ label: "script interaction", tone: "accent" },
|
|
1028
|
+
...node.chips.filter((chip) => chip.label !== "script interaction")
|
|
1029
|
+
]
|
|
1030
|
+
});
|
|
1031
|
+
}
|
|
1032
|
+
};
|
|
1033
|
+
var addDiagnosticNode = (options, diagnostic, nodes, edges) => {
|
|
1034
|
+
const node = {
|
|
1035
|
+
id: `diagnostic:${diagnostic.id}`,
|
|
1036
|
+
kind: "diagnostic",
|
|
1037
|
+
title: diagnostic.code,
|
|
1038
|
+
sections: [
|
|
1039
|
+
{
|
|
1040
|
+
id: "diagnostic",
|
|
1041
|
+
title: "Diagnostic",
|
|
1042
|
+
rows: [
|
|
1043
|
+
{
|
|
1044
|
+
id: "message",
|
|
1045
|
+
label: "Message",
|
|
1046
|
+
value: diagnosticMessage(diagnostic, options)
|
|
1047
|
+
}
|
|
1048
|
+
]
|
|
1049
|
+
}
|
|
1050
|
+
],
|
|
1051
|
+
ports: [],
|
|
1052
|
+
chips: [{ label: diagnostic.severity, tone: "danger" }],
|
|
1053
|
+
severity: diagnostic.severity === "error" ? "error" : "warning",
|
|
1054
|
+
rawRef: { type: "diagnostic", code: diagnostic.code }
|
|
1055
|
+
};
|
|
1056
|
+
nodes.set(node.id, node);
|
|
1057
|
+
if (diagnostic.txHash) {
|
|
1058
|
+
edges.push({
|
|
1059
|
+
id: `diagnostic-edge:${diagnostic.id}`,
|
|
1060
|
+
kind: "diagnostic",
|
|
1061
|
+
from: node.id,
|
|
1062
|
+
to: `tx:${diagnostic.txHash}`,
|
|
1063
|
+
label: diagnostic.severity,
|
|
1064
|
+
targetRef: {
|
|
1065
|
+
type: "diagnostic",
|
|
1066
|
+
txHash: diagnostic.txHash,
|
|
1067
|
+
code: diagnostic.code
|
|
1068
|
+
},
|
|
1069
|
+
rawRef: { type: "diagnostic", code: diagnostic.code }
|
|
1070
|
+
});
|
|
1071
|
+
}
|
|
1072
|
+
};
|
|
1073
|
+
var addBudgetSummaries = (options, nodes, edges) => {
|
|
1074
|
+
const hidden = /* @__PURE__ */ new Set();
|
|
1075
|
+
for (const txHash of transactionHashes(nodes)) {
|
|
1076
|
+
hideExcessEdges(
|
|
1077
|
+
edges,
|
|
1078
|
+
txHash,
|
|
1079
|
+
"inputs",
|
|
1080
|
+
options.budget.maxVisibleInputs,
|
|
1081
|
+
(edge) => edge.targetRef.txHash === txHash && ["spend", "read", "collateral"].includes(edge.kind),
|
|
1082
|
+
hidden,
|
|
1083
|
+
nodes
|
|
1084
|
+
);
|
|
1085
|
+
hideExcessEdges(
|
|
1086
|
+
edges,
|
|
1087
|
+
txHash,
|
|
1088
|
+
"outputs",
|
|
1089
|
+
options.budget.maxVisibleOutputs,
|
|
1090
|
+
(edge) => edge.targetRef.txHash === txHash && edge.kind === "produce",
|
|
1091
|
+
hidden,
|
|
1092
|
+
nodes
|
|
1093
|
+
);
|
|
1094
|
+
hideExcessEdges(
|
|
1095
|
+
edges,
|
|
1096
|
+
txHash,
|
|
1097
|
+
"signers",
|
|
1098
|
+
options.budget.maxVisibleSigners,
|
|
1099
|
+
(edge) => edge.targetRef.txHash === txHash && edge.kind === "sign",
|
|
1100
|
+
hidden,
|
|
1101
|
+
nodes
|
|
1102
|
+
);
|
|
1103
|
+
}
|
|
1104
|
+
for (let index = edges.length - 1; index >= 0; index--) {
|
|
1105
|
+
if (hidden.has(edges[index].id)) edges.splice(index, 1);
|
|
1106
|
+
}
|
|
1107
|
+
};
|
|
1108
|
+
var hideExcessEdges = (edges, txHash, group, maxVisible, matches, hidden, nodes) => {
|
|
1109
|
+
const visibleLimit = Number.isFinite(maxVisible) ? maxVisible : edges.length;
|
|
1110
|
+
const candidates = edges.filter(matches).sort((left, right) => left.id.localeCompare(right.id));
|
|
1111
|
+
const extra = candidates.slice(visibleLimit);
|
|
1112
|
+
for (const edge of extra) hidden.add(edge.id);
|
|
1113
|
+
if (extra.length === 0) return;
|
|
1114
|
+
const node = summaryNode(
|
|
1115
|
+
`summary:${txHash}:${group}`,
|
|
1116
|
+
`+${extra.length} ${group}`,
|
|
1117
|
+
group,
|
|
1118
|
+
extra.length
|
|
1119
|
+
);
|
|
1120
|
+
nodes.set(node.id, node);
|
|
1121
|
+
edges.push(
|
|
1122
|
+
group === "outputs" ? summaryEdge(`tx:${txHash}`, node.id, txHash, group) : summaryEdge(node.id, `tx:${txHash}`, txHash, group)
|
|
1123
|
+
);
|
|
1124
|
+
};
|
|
1125
|
+
var summaryNode = (id, title, group, count) => ({
|
|
1126
|
+
id,
|
|
1127
|
+
kind: "diagnostic",
|
|
1128
|
+
title,
|
|
1129
|
+
sections: [
|
|
1130
|
+
{
|
|
1131
|
+
id: "summary",
|
|
1132
|
+
title: "Collapsed",
|
|
1133
|
+
rows: [{ id: "count", label: "Count", value: String(count) }]
|
|
1134
|
+
}
|
|
1135
|
+
],
|
|
1136
|
+
ports: [],
|
|
1137
|
+
chips: [{ label: "collapsed", tone: "neutral" }],
|
|
1138
|
+
rawRef: { type: "diagnostic", code: "collapsed", count, group }
|
|
1139
|
+
});
|
|
1140
|
+
var summaryEdge = (from, to, txHash, group) => ({
|
|
1141
|
+
id: `summary-edge:${txHash}:${group}:${from}->${to}`,
|
|
1142
|
+
kind: "summary",
|
|
1143
|
+
from,
|
|
1144
|
+
to,
|
|
1145
|
+
label: group,
|
|
1146
|
+
targetRef: {
|
|
1147
|
+
type: "diagnostic",
|
|
1148
|
+
txHash,
|
|
1149
|
+
code: "collapsed",
|
|
1150
|
+
group
|
|
1151
|
+
},
|
|
1152
|
+
rawRef: { type: "diagnostic", code: "collapsed", group }
|
|
1153
|
+
});
|
|
1154
|
+
var transactionHashes = (nodes) => [...nodes.values()].filter(
|
|
1155
|
+
(node) => node.rawRef.type === "transaction"
|
|
1156
|
+
).map((node) => node.rawRef.txHash).sort();
|
|
1157
|
+
var formatOutRef = (outRef, options) => {
|
|
1158
|
+
if (options.privacy.hash === "hidden") return `#${outRef.outputIndex}`;
|
|
1159
|
+
return `${formatHash(outRef.txHash, options)}#${outRef.outputIndex}`;
|
|
1160
|
+
};
|
|
1161
|
+
var formatHash = (hash, options) => options.privacy.hash === "full" ? hash : options.privacy.hash === "hidden" ? "hidden" : shortHash(hash);
|
|
1162
|
+
var formatAddress = (rawAddress, aliasOrShort, options) => {
|
|
1163
|
+
switch (options.privacy.address) {
|
|
1164
|
+
case "hidden":
|
|
1165
|
+
return void 0;
|
|
1166
|
+
case "full":
|
|
1167
|
+
return rawAddress;
|
|
1168
|
+
case "short":
|
|
1169
|
+
return shortHash(rawAddress, 10);
|
|
1170
|
+
case "alias":
|
|
1171
|
+
return aliasOrShort ?? shortHash(rawAddress, 10);
|
|
1172
|
+
}
|
|
1173
|
+
};
|
|
1174
|
+
var diagnosticMessage = (diagnostic, options) => {
|
|
1175
|
+
let message = diagnostic.message;
|
|
1176
|
+
if (diagnostic.outRef) {
|
|
1177
|
+
message = message.replace(
|
|
1178
|
+
outRefKey(diagnostic.outRef),
|
|
1179
|
+
formatOutRef(diagnostic.outRef, options)
|
|
1180
|
+
);
|
|
1181
|
+
}
|
|
1182
|
+
if (diagnostic.txHash) {
|
|
1183
|
+
message = message.replaceAll(
|
|
1184
|
+
diagnostic.txHash,
|
|
1185
|
+
formatHash(diagnostic.txHash, options)
|
|
1186
|
+
);
|
|
1187
|
+
}
|
|
1188
|
+
return message;
|
|
1189
|
+
};
|
|
1190
|
+
var findRedeemer = (trace, edge) => {
|
|
1191
|
+
const key = edge.redeemerKey;
|
|
1192
|
+
if (!key) return void 0;
|
|
1193
|
+
return trace.transactions.find((transaction) => transaction.hash === edge.targetRef.txHash)?.redeemers.find(
|
|
1194
|
+
(redeemer) => redeemer.tag === key.tag && redeemer.index === key.index && redeemer.redeemerListIndex === key.redeemerListIndex
|
|
1195
|
+
);
|
|
1196
|
+
};
|
|
1197
|
+
|
|
1198
|
+
// src/render/semantic.ts
|
|
1199
|
+
var buildSemanticRenderGraph = (trace, options = {}) => {
|
|
1200
|
+
const nodes = /* @__PURE__ */ new Map();
|
|
1201
|
+
const edges = [];
|
|
1202
|
+
const mappedRedeemers = /* @__PURE__ */ new Set();
|
|
1203
|
+
for (const [key, utxo] of Object.entries(trace.utxos).sort(
|
|
1204
|
+
([left], [right]) => left.localeCompare(right)
|
|
1205
|
+
)) {
|
|
1206
|
+
nodes.set(utxoNodeId2(utxo), utxoNode(key, utxo, trace));
|
|
1207
|
+
}
|
|
1208
|
+
for (const transaction of trace.transactions) {
|
|
1209
|
+
nodes.set(
|
|
1210
|
+
transactionNodeId(transaction.hash),
|
|
1211
|
+
transactionNode(transaction, trace)
|
|
1212
|
+
);
|
|
1213
|
+
addTransactionEdges(trace, transaction, nodes, edges, mappedRedeemers);
|
|
1214
|
+
}
|
|
1215
|
+
const diagnostics = [
|
|
1216
|
+
...trace.warnings.map(visualDiagnostic),
|
|
1217
|
+
...unmappedRedeemerDiagnostics(trace, mappedRedeemers)
|
|
1218
|
+
];
|
|
1219
|
+
return {
|
|
1220
|
+
version: 1,
|
|
1221
|
+
nodes: [...nodes.values()].sort(
|
|
1222
|
+
(left, right) => left.id.localeCompare(right.id)
|
|
1223
|
+
),
|
|
1224
|
+
edges: edges.map((edge) => applyAction(trace, edge, options.redeemers ?? [])).sort((left, right) => left.id.localeCompare(right.id)),
|
|
1225
|
+
diagnostics,
|
|
1226
|
+
legend: defaultLegend,
|
|
1227
|
+
aliases: trace.aliases
|
|
1228
|
+
};
|
|
1229
|
+
};
|
|
1230
|
+
var labelRedeemer = (selector, label, intent) => (context) => {
|
|
1231
|
+
if (!matchesRedeemer(selector, context)) return void 0;
|
|
1232
|
+
return {
|
|
1233
|
+
label,
|
|
1234
|
+
...intent ? { intent } : {},
|
|
1235
|
+
source: "user",
|
|
1236
|
+
confidence: "high"
|
|
1237
|
+
};
|
|
1238
|
+
};
|
|
1239
|
+
var describeRedeemerByConstructor = (selector, label, intent) => (context) => {
|
|
1240
|
+
const { redeemer } = context;
|
|
1241
|
+
if (!matchesRedeemer(selector, context)) return void 0;
|
|
1242
|
+
if (constructorOf(redeemer.data) !== selector.constructor) return void 0;
|
|
1243
|
+
return {
|
|
1244
|
+
label,
|
|
1245
|
+
...intent ? { intent } : {},
|
|
1246
|
+
fields: { constructor: String(selector.constructor) },
|
|
1247
|
+
source: "constructor",
|
|
1248
|
+
confidence: "medium"
|
|
1249
|
+
};
|
|
1250
|
+
};
|
|
1251
|
+
var describeRedeemerWith = (describe) => (context) => describe(context);
|
|
1252
|
+
var addTransactionEdges = (trace, transaction, nodes, edges, mappedRedeemers) => {
|
|
1253
|
+
addInputEdges(
|
|
1254
|
+
trace,
|
|
1255
|
+
transaction,
|
|
1256
|
+
nodes,
|
|
1257
|
+
edges,
|
|
1258
|
+
mappedRedeemers,
|
|
1259
|
+
"spend",
|
|
1260
|
+
transaction.inputs
|
|
1261
|
+
);
|
|
1262
|
+
addInputEdges(
|
|
1263
|
+
trace,
|
|
1264
|
+
transaction,
|
|
1265
|
+
nodes,
|
|
1266
|
+
edges,
|
|
1267
|
+
mappedRedeemers,
|
|
1268
|
+
"read",
|
|
1269
|
+
transaction.referenceInputs
|
|
1270
|
+
);
|
|
1271
|
+
addInputEdges(
|
|
1272
|
+
trace,
|
|
1273
|
+
transaction,
|
|
1274
|
+
nodes,
|
|
1275
|
+
edges,
|
|
1276
|
+
mappedRedeemers,
|
|
1277
|
+
"collateral",
|
|
1278
|
+
transaction.collateralInputs
|
|
1279
|
+
);
|
|
1280
|
+
addOutputEdges(transaction, edges);
|
|
1281
|
+
addAssetPolicyEdges(
|
|
1282
|
+
trace,
|
|
1283
|
+
transaction,
|
|
1284
|
+
nodes,
|
|
1285
|
+
edges,
|
|
1286
|
+
mappedRedeemers,
|
|
1287
|
+
"mint"
|
|
1288
|
+
);
|
|
1289
|
+
addAssetPolicyEdges(
|
|
1290
|
+
trace,
|
|
1291
|
+
transaction,
|
|
1292
|
+
nodes,
|
|
1293
|
+
edges,
|
|
1294
|
+
mappedRedeemers,
|
|
1295
|
+
"burn"
|
|
1296
|
+
);
|
|
1297
|
+
addWithdrawalEdges(trace, transaction, nodes, edges, mappedRedeemers);
|
|
1298
|
+
addCertificateEdges(transaction, nodes, edges, mappedRedeemers);
|
|
1299
|
+
addSignerEdges(transaction, nodes, edges);
|
|
1300
|
+
addCollateralReturnEdge(transaction, nodes, edges);
|
|
1301
|
+
};
|
|
1302
|
+
var addInputEdges = (trace, transaction, nodes, edges, mappedRedeemers, kind, inputs) => {
|
|
1303
|
+
inputs.forEach((outRef, index) => {
|
|
1304
|
+
const inputKey = outRefKey(outRef);
|
|
1305
|
+
const from = utxoNodeId2(outRef);
|
|
1306
|
+
const to = transactionNodeId(transaction.hash);
|
|
1307
|
+
const redeemerKey = kind === "spend" ? redeemerKeyFor(transaction, "spend", index, mappedRedeemers) : void 0;
|
|
1308
|
+
ensureUtxoPlaceholder(trace, nodes, outRef);
|
|
1309
|
+
edges.push({
|
|
1310
|
+
id: `${to}:${kind}:${index}:${inputKey}`,
|
|
1311
|
+
kind,
|
|
1312
|
+
from,
|
|
1313
|
+
to,
|
|
1314
|
+
fromPort: utxoPortId(inputKey, "out"),
|
|
1315
|
+
toPort: transactionPortId(transaction.hash, kind, index),
|
|
1316
|
+
label: `${kind} #${index}`,
|
|
1317
|
+
...redeemerKey ? { redeemerKey } : {},
|
|
1318
|
+
targetRef: {
|
|
1319
|
+
type: "input",
|
|
1320
|
+
inputKind: kind,
|
|
1321
|
+
txHash: transaction.hash,
|
|
1322
|
+
index,
|
|
1323
|
+
outRef
|
|
1324
|
+
},
|
|
1325
|
+
rawRef: { type: "utxo", outRef }
|
|
1326
|
+
});
|
|
1327
|
+
});
|
|
1328
|
+
};
|
|
1329
|
+
var addOutputEdges = (transaction, edges) => {
|
|
1330
|
+
transaction.outputs.forEach((output, index) => {
|
|
1331
|
+
const outputKey = outRefKey(output);
|
|
1332
|
+
edges.push({
|
|
1333
|
+
id: `${transactionNodeId(transaction.hash)}:produce:${index}`,
|
|
1334
|
+
kind: "produce",
|
|
1335
|
+
from: transactionNodeId(transaction.hash),
|
|
1336
|
+
to: utxoNodeId2(output),
|
|
1337
|
+
fromPort: transactionPortId(transaction.hash, "produce", index),
|
|
1338
|
+
toPort: utxoPortId(outputKey, "in"),
|
|
1339
|
+
label: `output #${index}`,
|
|
1340
|
+
targetRef: {
|
|
1341
|
+
type: "output",
|
|
1342
|
+
txHash: transaction.hash,
|
|
1343
|
+
index,
|
|
1344
|
+
outRef: {
|
|
1345
|
+
txHash: output.txHash,
|
|
1346
|
+
outputIndex: output.outputIndex
|
|
1347
|
+
}
|
|
1348
|
+
},
|
|
1349
|
+
rawRef: {
|
|
1350
|
+
type: "utxo",
|
|
1351
|
+
outRef: {
|
|
1352
|
+
txHash: output.txHash,
|
|
1353
|
+
outputIndex: output.outputIndex
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
});
|
|
1357
|
+
});
|
|
1358
|
+
};
|
|
1359
|
+
var addAssetPolicyEdges = (trace, transaction, nodes, edges, mappedRedeemers, kind) => {
|
|
1360
|
+
const assets = kind === "mint" ? transaction.mintedAssets : transaction.burnedAssets;
|
|
1361
|
+
const byPolicy = assetsByPolicy(assets);
|
|
1362
|
+
const policyIds = sortedPolicyIds(transaction.mint);
|
|
1363
|
+
for (const [policyId, policyAssets] of Object.entries(byPolicy).sort(
|
|
1364
|
+
([a], [b]) => a.localeCompare(b)
|
|
1365
|
+
)) {
|
|
1366
|
+
const policyIndex = policyIds.indexOf(policyId);
|
|
1367
|
+
const index = policyIndex >= 0 ? policyIndex : 0;
|
|
1368
|
+
const redeemerKey = redeemerKeyFor(
|
|
1369
|
+
transaction,
|
|
1370
|
+
"mint",
|
|
1371
|
+
index,
|
|
1372
|
+
mappedRedeemers
|
|
1373
|
+
);
|
|
1374
|
+
const assetNode = assetPolicyNode(policyId, policyAssets, trace);
|
|
1375
|
+
nodes.set(
|
|
1376
|
+
assetNode.id,
|
|
1377
|
+
mergeAssetPolicyNode(nodes.get(assetNode.id), assetNode)
|
|
1378
|
+
);
|
|
1379
|
+
const edge = {
|
|
1380
|
+
id: `${transactionNodeId(transaction.hash)}:${kind}:${index}:${policyId}`,
|
|
1381
|
+
kind,
|
|
1382
|
+
from: kind === "mint" ? transactionNodeId(transaction.hash) : assetPolicyNodeId(policyId),
|
|
1383
|
+
to: kind === "mint" ? assetPolicyNodeId(policyId) : transactionNodeId(transaction.hash),
|
|
1384
|
+
fromPort: kind === "mint" ? transactionPortId(transaction.hash, "mint", index) : assetPolicyPortId(policyId),
|
|
1385
|
+
toPort: kind === "mint" ? assetPolicyPortId(policyId) : transactionPortId(transaction.hash, "burn", index),
|
|
1386
|
+
label: `${kind} #${index}`,
|
|
1387
|
+
...redeemerKey ? { redeemerKey } : {},
|
|
1388
|
+
targetRef: {
|
|
1389
|
+
type: "assetPolicy",
|
|
1390
|
+
txHash: transaction.hash,
|
|
1391
|
+
policyId,
|
|
1392
|
+
policyIndex: index,
|
|
1393
|
+
assets: policyAssets
|
|
1394
|
+
},
|
|
1395
|
+
rawRef: { type: "assetPolicy", policyId }
|
|
1396
|
+
};
|
|
1397
|
+
edges.push(edge);
|
|
1398
|
+
}
|
|
1399
|
+
};
|
|
1400
|
+
var addWithdrawalEdges = (trace, transaction, nodes, edges, mappedRedeemers) => {
|
|
1401
|
+
transaction.withdrawals.forEach((withdrawal, index) => {
|
|
1402
|
+
const node = withdrawalNode(withdrawal.rewardAddress, trace);
|
|
1403
|
+
const redeemerKey = redeemerKeyFor(
|
|
1404
|
+
transaction,
|
|
1405
|
+
"withdraw",
|
|
1406
|
+
index,
|
|
1407
|
+
mappedRedeemers
|
|
1408
|
+
);
|
|
1409
|
+
nodes.set(node.id, node);
|
|
1410
|
+
edges.push({
|
|
1411
|
+
id: `${transactionNodeId(transaction.hash)}:withdraw:${index}:${withdrawal.rewardAddress}`,
|
|
1412
|
+
kind: "withdraw",
|
|
1413
|
+
from: node.id,
|
|
1414
|
+
to: transactionNodeId(transaction.hash),
|
|
1415
|
+
fromPort: withdrawalPortId(withdrawal.rewardAddress),
|
|
1416
|
+
toPort: transactionPortId(transaction.hash, "withdraw", index),
|
|
1417
|
+
label: `withdraw #${index}`,
|
|
1418
|
+
...redeemerKey ? { redeemerKey } : {},
|
|
1419
|
+
targetRef: {
|
|
1420
|
+
type: "withdrawal",
|
|
1421
|
+
txHash: transaction.hash,
|
|
1422
|
+
index,
|
|
1423
|
+
rewardAddress: withdrawal.rewardAddress
|
|
1424
|
+
},
|
|
1425
|
+
rawRef: { type: "withdrawal", rewardAddress: withdrawal.rewardAddress }
|
|
1426
|
+
});
|
|
1427
|
+
});
|
|
1428
|
+
};
|
|
1429
|
+
var addCertificateEdges = (transaction, nodes, edges, mappedRedeemers) => {
|
|
1430
|
+
transaction.certificates.forEach((certificate) => {
|
|
1431
|
+
const node = certificateNode(
|
|
1432
|
+
transaction.hash,
|
|
1433
|
+
certificate.index,
|
|
1434
|
+
certificate.kindName
|
|
1435
|
+
);
|
|
1436
|
+
const redeemerKey = redeemerKeyFor(
|
|
1437
|
+
transaction,
|
|
1438
|
+
"publish",
|
|
1439
|
+
certificate.index,
|
|
1440
|
+
mappedRedeemers
|
|
1441
|
+
);
|
|
1442
|
+
nodes.set(node.id, node);
|
|
1443
|
+
edges.push({
|
|
1444
|
+
id: `${transactionNodeId(transaction.hash)}:cert:${certificate.index}`,
|
|
1445
|
+
kind: "certify",
|
|
1446
|
+
from: transactionNodeId(transaction.hash),
|
|
1447
|
+
to: node.id,
|
|
1448
|
+
fromPort: transactionPortId(
|
|
1449
|
+
transaction.hash,
|
|
1450
|
+
"certify",
|
|
1451
|
+
certificate.index
|
|
1452
|
+
),
|
|
1453
|
+
toPort: certificatePortId(transaction.hash, certificate.index),
|
|
1454
|
+
label: `cert #${certificate.index}`,
|
|
1455
|
+
...redeemerKey ? { redeemerKey } : {},
|
|
1456
|
+
targetRef: {
|
|
1457
|
+
type: "certificate",
|
|
1458
|
+
txHash: transaction.hash,
|
|
1459
|
+
index: certificate.index
|
|
1460
|
+
},
|
|
1461
|
+
rawRef: {
|
|
1462
|
+
type: "certificate",
|
|
1463
|
+
txHash: transaction.hash,
|
|
1464
|
+
index: certificate.index
|
|
1465
|
+
}
|
|
1466
|
+
});
|
|
1467
|
+
});
|
|
1468
|
+
};
|
|
1469
|
+
var addSignerEdges = (transaction, nodes, edges) => {
|
|
1470
|
+
transaction.requiredSigners.forEach((keyHash, index) => {
|
|
1471
|
+
const node = signerNode(keyHash);
|
|
1472
|
+
nodes.set(node.id, node);
|
|
1473
|
+
edges.push({
|
|
1474
|
+
id: `${transactionNodeId(transaction.hash)}:signer:${keyHash}`,
|
|
1475
|
+
kind: "sign",
|
|
1476
|
+
from: node.id,
|
|
1477
|
+
to: transactionNodeId(transaction.hash),
|
|
1478
|
+
fromPort: signerPortId(keyHash),
|
|
1479
|
+
toPort: transactionPortId(transaction.hash, "sign", index),
|
|
1480
|
+
label: "requires signer",
|
|
1481
|
+
targetRef: {
|
|
1482
|
+
type: "signer",
|
|
1483
|
+
txHash: transaction.hash,
|
|
1484
|
+
keyHash
|
|
1485
|
+
},
|
|
1486
|
+
rawRef: { type: "signer", keyHash }
|
|
1487
|
+
});
|
|
1488
|
+
});
|
|
1489
|
+
};
|
|
1490
|
+
var addCollateralReturnEdge = (transaction, nodes, edges) => {
|
|
1491
|
+
if (!transaction.collateralReturn) return;
|
|
1492
|
+
const node = collateralReturnNode(transaction);
|
|
1493
|
+
nodes.set(node.id, node);
|
|
1494
|
+
edges.push({
|
|
1495
|
+
id: `${transactionNodeId(transaction.hash)}:collateral-return`,
|
|
1496
|
+
kind: "collateralReturn",
|
|
1497
|
+
from: transactionNodeId(transaction.hash),
|
|
1498
|
+
to: node.id,
|
|
1499
|
+
fromPort: transactionPortId(transaction.hash, "collateralReturn", 0),
|
|
1500
|
+
toPort: collateralReturnPortId(transaction.hash),
|
|
1501
|
+
label: "collateral return",
|
|
1502
|
+
targetRef: {
|
|
1503
|
+
type: "collateralReturn",
|
|
1504
|
+
txHash: transaction.hash
|
|
1505
|
+
},
|
|
1506
|
+
rawRef: { type: "collateralReturn", txHash: transaction.hash }
|
|
1507
|
+
});
|
|
1508
|
+
};
|
|
1509
|
+
var transactionNode = (transaction, trace) => {
|
|
1510
|
+
const warningCount = trace.warnings.filter(
|
|
1511
|
+
(warning) => warning.txHash === transaction.hash
|
|
1512
|
+
).length;
|
|
1513
|
+
return {
|
|
1514
|
+
id: transactionNodeId(transaction.hash),
|
|
1515
|
+
kind: "transaction",
|
|
1516
|
+
title: transaction.label ?? `tx ${shortHash(transaction.hash)}`,
|
|
1517
|
+
subtitle: shortHash(transaction.hash),
|
|
1518
|
+
chips: [
|
|
1519
|
+
{ label: transaction.status, tone: statusTone(transaction.status) },
|
|
1520
|
+
...warningCount > 0 ? [{ label: `${warningCount} warnings`, tone: "warning" }] : []
|
|
1521
|
+
],
|
|
1522
|
+
sections: [
|
|
1523
|
+
section("facts", "Facts", [
|
|
1524
|
+
field("fee", "Fee", transaction.fee),
|
|
1525
|
+
field("size", "Size", `${transaction.sizeBytes} bytes`),
|
|
1526
|
+
...transaction.validityInterval.validFrom ? [
|
|
1527
|
+
field(
|
|
1528
|
+
"validFrom",
|
|
1529
|
+
"Valid from",
|
|
1530
|
+
transaction.validityInterval.validFrom
|
|
1531
|
+
)
|
|
1532
|
+
] : [],
|
|
1533
|
+
...transaction.validityInterval.validTo ? [field("validTo", "Valid to", transaction.validityInterval.validTo)] : []
|
|
1534
|
+
]),
|
|
1535
|
+
section("inputs", "Inputs", [
|
|
1536
|
+
field("spend", "Spend", String(transaction.inputs.length)),
|
|
1537
|
+
field("read", "Read", String(transaction.referenceInputs.length)),
|
|
1538
|
+
field(
|
|
1539
|
+
"collateral",
|
|
1540
|
+
"Collateral",
|
|
1541
|
+
String(transaction.collateralInputs.length)
|
|
1542
|
+
)
|
|
1543
|
+
]),
|
|
1544
|
+
section("assets", "Assets", [
|
|
1545
|
+
field(
|
|
1546
|
+
"mint",
|
|
1547
|
+
"Mint policies",
|
|
1548
|
+
String(sortedPolicyIds(transaction.mintedAssets).length)
|
|
1549
|
+
),
|
|
1550
|
+
field(
|
|
1551
|
+
"burn",
|
|
1552
|
+
"Burn policies",
|
|
1553
|
+
String(sortedPolicyIds(transaction.burnedAssets).length)
|
|
1554
|
+
)
|
|
1555
|
+
]),
|
|
1556
|
+
section("authority", "Authority", [
|
|
1557
|
+
field("signers", "Signers", String(transaction.requiredSigners.length)),
|
|
1558
|
+
field(
|
|
1559
|
+
"certificates",
|
|
1560
|
+
"Certificates",
|
|
1561
|
+
String(transaction.certificates.length)
|
|
1562
|
+
),
|
|
1563
|
+
field(
|
|
1564
|
+
"withdrawals",
|
|
1565
|
+
"Withdrawals",
|
|
1566
|
+
String(transaction.withdrawals.length)
|
|
1567
|
+
)
|
|
1568
|
+
]),
|
|
1569
|
+
section("redeemers", "Redeemers", [
|
|
1570
|
+
field("redeemers", "Redeemers", String(transaction.redeemers.length))
|
|
1571
|
+
])
|
|
1572
|
+
],
|
|
1573
|
+
ports: transactionPorts(transaction),
|
|
1574
|
+
...transaction.status === "failed" ? { severity: "error" } : {},
|
|
1575
|
+
rawRef: { type: "transaction", txHash: transaction.hash }
|
|
1576
|
+
};
|
|
1577
|
+
};
|
|
1578
|
+
var utxoNode = (key, utxo, trace) => {
|
|
1579
|
+
const title = utxo.resolution === "genesis" ? "genesis UTxO" : utxo.tags.length > 0 ? utxo.tags.join(", ") : key;
|
|
1580
|
+
return {
|
|
1581
|
+
id: utxoNodeId2(utxo),
|
|
1582
|
+
kind: "utxo",
|
|
1583
|
+
title,
|
|
1584
|
+
subtitle: key,
|
|
1585
|
+
chips: [
|
|
1586
|
+
{
|
|
1587
|
+
label: utxo.resolution,
|
|
1588
|
+
tone: utxo.resolution === "unresolved" ? "danger" : utxo.resolution === "genesis" ? "warning" : "success"
|
|
1589
|
+
},
|
|
1590
|
+
...utxo.tags.map((tag) => ({ label: tag, tone: "neutral" }))
|
|
1591
|
+
],
|
|
1592
|
+
sections: [
|
|
1593
|
+
section("owner", "Owner", [
|
|
1594
|
+
field(
|
|
1595
|
+
"address",
|
|
1596
|
+
"Address",
|
|
1597
|
+
addressName(utxo.address, trace),
|
|
1598
|
+
true,
|
|
1599
|
+
void 0,
|
|
1600
|
+
utxo.address
|
|
1601
|
+
)
|
|
1602
|
+
]),
|
|
1603
|
+
section(
|
|
1604
|
+
"assets",
|
|
1605
|
+
"Assets",
|
|
1606
|
+
Object.entries(utxo.assets).sort(([left], [right]) => assetSort(left, right)).map(
|
|
1607
|
+
([unit, amount]) => field(
|
|
1608
|
+
`asset:${unit}`,
|
|
1609
|
+
assetName(unit, trace),
|
|
1610
|
+
amount,
|
|
1611
|
+
false,
|
|
1612
|
+
void 0,
|
|
1613
|
+
void 0,
|
|
1614
|
+
unit
|
|
1615
|
+
)
|
|
1616
|
+
)
|
|
1617
|
+
),
|
|
1618
|
+
section("state", "State", [
|
|
1619
|
+
...utxo.datum ? [field("datum", "Datum", "inline datum")] : [],
|
|
1620
|
+
...utxo.datumHash ? [
|
|
1621
|
+
field(
|
|
1622
|
+
"datumHash",
|
|
1623
|
+
"Datum hash",
|
|
1624
|
+
shortHash(utxo.datumHash),
|
|
1625
|
+
true,
|
|
1626
|
+
void 0,
|
|
1627
|
+
utxo.datumHash
|
|
1628
|
+
)
|
|
1629
|
+
] : [],
|
|
1630
|
+
...utxo.scriptRef ? [
|
|
1631
|
+
field(
|
|
1632
|
+
"scriptRef",
|
|
1633
|
+
"Script ref",
|
|
1634
|
+
`${utxo.scriptRef.type}${utxo.scriptRef.hash ? ` ${shortHash(utxo.scriptRef.hash)}` : ""}`,
|
|
1635
|
+
false,
|
|
1636
|
+
void 0,
|
|
1637
|
+
utxo.scriptRef.hash ? `${utxo.scriptRef.type} ${utxo.scriptRef.hash}` : utxo.scriptRef.type
|
|
1638
|
+
)
|
|
1639
|
+
] : [],
|
|
1640
|
+
...utxo.unresolvedReason ? [
|
|
1641
|
+
field(
|
|
1642
|
+
"unresolved",
|
|
1643
|
+
"Unresolved",
|
|
1644
|
+
utxo.unresolvedReason,
|
|
1645
|
+
false,
|
|
1646
|
+
"danger"
|
|
1647
|
+
)
|
|
1648
|
+
] : []
|
|
1649
|
+
])
|
|
1650
|
+
],
|
|
1651
|
+
ports: [
|
|
1652
|
+
port(utxoPortId(key, "in"), utxoNodeId2(utxo), "left", "in"),
|
|
1653
|
+
port(utxoPortId(key, "out"), utxoNodeId2(utxo), "right", "out")
|
|
1654
|
+
],
|
|
1655
|
+
...utxo.resolution === "unresolved" ? { severity: "error" } : {},
|
|
1656
|
+
rawRef: {
|
|
1657
|
+
type: "utxo",
|
|
1658
|
+
outRef: { txHash: utxo.txHash, outputIndex: utxo.outputIndex }
|
|
1659
|
+
}
|
|
1660
|
+
};
|
|
1661
|
+
};
|
|
1662
|
+
var assetPolicyNode = (policyId, assets, trace) => ({
|
|
1663
|
+
id: assetPolicyNodeId(policyId),
|
|
1664
|
+
kind: "asset",
|
|
1665
|
+
title: `policy ${shortHash(policyId, 6)}`,
|
|
1666
|
+
sections: [
|
|
1667
|
+
section(
|
|
1668
|
+
"assets",
|
|
1669
|
+
"Assets",
|
|
1670
|
+
Object.entries(assets).sort(([left], [right]) => assetSort(left, right)).map(
|
|
1671
|
+
([unit]) => field(
|
|
1672
|
+
`asset:${unit}`,
|
|
1673
|
+
"Asset",
|
|
1674
|
+
assetName(unit, trace),
|
|
1675
|
+
false,
|
|
1676
|
+
void 0,
|
|
1677
|
+
unit
|
|
1678
|
+
)
|
|
1679
|
+
)
|
|
1680
|
+
)
|
|
1681
|
+
],
|
|
1682
|
+
ports: [
|
|
1683
|
+
port(
|
|
1684
|
+
assetPolicyPortId(policyId),
|
|
1685
|
+
assetPolicyNodeId(policyId),
|
|
1686
|
+
"left",
|
|
1687
|
+
"asset"
|
|
1688
|
+
)
|
|
1689
|
+
],
|
|
1690
|
+
chips: [{ label: shortHash(policyId, 6), tone: "accent" }],
|
|
1691
|
+
rawRef: { type: "assetPolicy", policyId }
|
|
1692
|
+
});
|
|
1693
|
+
var mergeAssetPolicyNode = (existing, next) => {
|
|
1694
|
+
if (!existing) return next;
|
|
1695
|
+
const rows = /* @__PURE__ */ new Map();
|
|
1696
|
+
for (const node of [existing, next]) {
|
|
1697
|
+
const assetRows = node.sections.find(
|
|
1698
|
+
(section2) => section2.id === "assets"
|
|
1699
|
+
)?.rows;
|
|
1700
|
+
for (const row of assetRows ?? []) {
|
|
1701
|
+
rows.set(row.id, row);
|
|
1702
|
+
}
|
|
1703
|
+
}
|
|
1704
|
+
return {
|
|
1705
|
+
...existing,
|
|
1706
|
+
sections: [
|
|
1707
|
+
section(
|
|
1708
|
+
"assets",
|
|
1709
|
+
"Assets",
|
|
1710
|
+
[...rows.values()].sort(
|
|
1711
|
+
(left, right) => left.id.localeCompare(right.id)
|
|
1712
|
+
)
|
|
1713
|
+
)
|
|
1714
|
+
]
|
|
1715
|
+
};
|
|
1716
|
+
};
|
|
1717
|
+
var withdrawalNode = (rewardAddress, trace) => ({
|
|
1718
|
+
id: withdrawalNodeId(rewardAddress),
|
|
1719
|
+
kind: "withdrawal",
|
|
1720
|
+
title: "withdrawal",
|
|
1721
|
+
subtitle: addressName(rewardAddress, trace),
|
|
1722
|
+
sections: [
|
|
1723
|
+
section("reward", "Reward address", [
|
|
1724
|
+
field(
|
|
1725
|
+
"address",
|
|
1726
|
+
"Address",
|
|
1727
|
+
addressName(rewardAddress, trace),
|
|
1728
|
+
true,
|
|
1729
|
+
void 0,
|
|
1730
|
+
rewardAddress
|
|
1731
|
+
)
|
|
1732
|
+
])
|
|
1733
|
+
],
|
|
1734
|
+
ports: [
|
|
1735
|
+
port(
|
|
1736
|
+
withdrawalPortId(rewardAddress),
|
|
1737
|
+
withdrawalNodeId(rewardAddress),
|
|
1738
|
+
"right",
|
|
1739
|
+
"withdraw"
|
|
1740
|
+
)
|
|
1741
|
+
],
|
|
1742
|
+
chips: [],
|
|
1743
|
+
rawRef: { type: "withdrawal", rewardAddress }
|
|
1744
|
+
});
|
|
1745
|
+
var certificateNode = (txHash, index, kindName) => ({
|
|
1746
|
+
id: certificateNodeId(txHash, index),
|
|
1747
|
+
kind: "certificate",
|
|
1748
|
+
title: kindName,
|
|
1749
|
+
subtitle: `certificate #${index}`,
|
|
1750
|
+
sections: [
|
|
1751
|
+
section("certificate", "Certificate", [field("kind", "Kind", kindName)])
|
|
1752
|
+
],
|
|
1753
|
+
ports: [
|
|
1754
|
+
port(
|
|
1755
|
+
certificatePortId(txHash, index),
|
|
1756
|
+
certificateNodeId(txHash, index),
|
|
1757
|
+
"left",
|
|
1758
|
+
"cert"
|
|
1759
|
+
)
|
|
1760
|
+
],
|
|
1761
|
+
chips: [],
|
|
1762
|
+
rawRef: { type: "certificate", txHash, index }
|
|
1763
|
+
});
|
|
1764
|
+
var signerNode = (keyHash) => ({
|
|
1765
|
+
id: signerNodeId(keyHash),
|
|
1766
|
+
kind: "signer",
|
|
1767
|
+
title: "signer",
|
|
1768
|
+
subtitle: shortHash(keyHash),
|
|
1769
|
+
sections: [
|
|
1770
|
+
section("signer", "Signer", [
|
|
1771
|
+
field(
|
|
1772
|
+
"keyHash",
|
|
1773
|
+
"Key hash",
|
|
1774
|
+
shortHash(keyHash),
|
|
1775
|
+
true,
|
|
1776
|
+
void 0,
|
|
1777
|
+
keyHash
|
|
1778
|
+
)
|
|
1779
|
+
])
|
|
1780
|
+
],
|
|
1781
|
+
ports: [port(signerPortId(keyHash), signerNodeId(keyHash), "right", "sign")],
|
|
1782
|
+
chips: [],
|
|
1783
|
+
rawRef: { type: "signer", keyHash }
|
|
1784
|
+
});
|
|
1785
|
+
var collateralReturnNode = (transaction) => ({
|
|
1786
|
+
id: collateralReturnNodeId(transaction.hash),
|
|
1787
|
+
kind: "collateralReturn",
|
|
1788
|
+
title: "collateral return",
|
|
1789
|
+
subtitle: shortHash(transaction.hash),
|
|
1790
|
+
sections: [
|
|
1791
|
+
section("assets", "Assets", [
|
|
1792
|
+
...Object.entries(transaction.collateralReturn?.assets ?? {}).sort(([left], [right]) => assetSort(left, right)).map(
|
|
1793
|
+
([unit, amount]) => field(
|
|
1794
|
+
`asset:${unit}`,
|
|
1795
|
+
unit,
|
|
1796
|
+
amount,
|
|
1797
|
+
false,
|
|
1798
|
+
void 0,
|
|
1799
|
+
void 0,
|
|
1800
|
+
unit
|
|
1801
|
+
)
|
|
1802
|
+
)
|
|
1803
|
+
])
|
|
1804
|
+
],
|
|
1805
|
+
ports: [
|
|
1806
|
+
port(
|
|
1807
|
+
collateralReturnPortId(transaction.hash),
|
|
1808
|
+
collateralReturnNodeId(transaction.hash),
|
|
1809
|
+
"left",
|
|
1810
|
+
"collateral return"
|
|
1811
|
+
)
|
|
1812
|
+
],
|
|
1813
|
+
chips: [],
|
|
1814
|
+
rawRef: { type: "collateralReturn", txHash: transaction.hash }
|
|
1815
|
+
});
|
|
1816
|
+
var ensureUtxoPlaceholder = (trace, nodes, outRef) => {
|
|
1817
|
+
const id = utxoNodeId2(outRef);
|
|
1818
|
+
if (nodes.has(id)) return;
|
|
1819
|
+
const key = outRefKey(outRef);
|
|
1820
|
+
nodes.set(
|
|
1821
|
+
id,
|
|
1822
|
+
utxoNode(
|
|
1823
|
+
key,
|
|
1824
|
+
{
|
|
1825
|
+
...outRef,
|
|
1826
|
+
address: "unresolved",
|
|
1827
|
+
assets: {},
|
|
1828
|
+
resolution: "unresolved",
|
|
1829
|
+
unresolvedReason: "missing-from-trace",
|
|
1830
|
+
tags: []
|
|
1831
|
+
},
|
|
1832
|
+
trace
|
|
1833
|
+
)
|
|
1834
|
+
);
|
|
1835
|
+
};
|
|
1836
|
+
var transactionPorts = (transaction) => [
|
|
1837
|
+
...transaction.inputs.map(
|
|
1838
|
+
(_input, index) => port(
|
|
1839
|
+
transactionPortId(transaction.hash, "spend", index),
|
|
1840
|
+
transactionNodeId(transaction.hash),
|
|
1841
|
+
"left",
|
|
1842
|
+
`spend #${index}`,
|
|
1843
|
+
"spend"
|
|
1844
|
+
)
|
|
1845
|
+
),
|
|
1846
|
+
...transaction.referenceInputs.map(
|
|
1847
|
+
(_input, index) => port(
|
|
1848
|
+
transactionPortId(transaction.hash, "read", index),
|
|
1849
|
+
transactionNodeId(transaction.hash),
|
|
1850
|
+
"left",
|
|
1851
|
+
`read #${index}`,
|
|
1852
|
+
"read"
|
|
1853
|
+
)
|
|
1854
|
+
),
|
|
1855
|
+
...transaction.collateralInputs.map(
|
|
1856
|
+
(_input, index) => port(
|
|
1857
|
+
transactionPortId(transaction.hash, "collateral", index),
|
|
1858
|
+
transactionNodeId(transaction.hash),
|
|
1859
|
+
"left",
|
|
1860
|
+
`collateral #${index}`,
|
|
1861
|
+
"collateral"
|
|
1862
|
+
)
|
|
1863
|
+
),
|
|
1864
|
+
...transaction.outputs.map(
|
|
1865
|
+
(_output, index) => port(
|
|
1866
|
+
transactionPortId(transaction.hash, "produce", index),
|
|
1867
|
+
transactionNodeId(transaction.hash),
|
|
1868
|
+
"right",
|
|
1869
|
+
`output #${index}`,
|
|
1870
|
+
"produce"
|
|
1871
|
+
)
|
|
1872
|
+
),
|
|
1873
|
+
...sortedPolicyIds(transaction.mintedAssets).map((policyId) => {
|
|
1874
|
+
const index = sortedPolicyIds(transaction.mint).indexOf(policyId);
|
|
1875
|
+
return port(
|
|
1876
|
+
transactionPortId(transaction.hash, "mint", index),
|
|
1877
|
+
transactionNodeId(transaction.hash),
|
|
1878
|
+
"bottom",
|
|
1879
|
+
`mint #${index}`,
|
|
1880
|
+
"mint"
|
|
1881
|
+
);
|
|
1882
|
+
}),
|
|
1883
|
+
...sortedPolicyIds(transaction.burnedAssets).map((policyId) => {
|
|
1884
|
+
const index = sortedPolicyIds(transaction.mint).indexOf(policyId);
|
|
1885
|
+
return port(
|
|
1886
|
+
transactionPortId(transaction.hash, "burn", index),
|
|
1887
|
+
transactionNodeId(transaction.hash),
|
|
1888
|
+
"bottom",
|
|
1889
|
+
`burn #${index}`,
|
|
1890
|
+
"burn"
|
|
1891
|
+
);
|
|
1892
|
+
}),
|
|
1893
|
+
...transaction.withdrawals.map(
|
|
1894
|
+
(_withdrawal, index) => port(
|
|
1895
|
+
transactionPortId(transaction.hash, "withdraw", index),
|
|
1896
|
+
transactionNodeId(transaction.hash),
|
|
1897
|
+
"left",
|
|
1898
|
+
`withdraw #${index}`,
|
|
1899
|
+
"withdraw"
|
|
1900
|
+
)
|
|
1901
|
+
),
|
|
1902
|
+
...transaction.certificates.map(
|
|
1903
|
+
(certificate) => port(
|
|
1904
|
+
transactionPortId(transaction.hash, "certify", certificate.index),
|
|
1905
|
+
transactionNodeId(transaction.hash),
|
|
1906
|
+
"right",
|
|
1907
|
+
`cert #${certificate.index}`,
|
|
1908
|
+
"certify"
|
|
1909
|
+
)
|
|
1910
|
+
),
|
|
1911
|
+
...transaction.requiredSigners.map(
|
|
1912
|
+
(_signer, index) => port(
|
|
1913
|
+
transactionPortId(transaction.hash, "sign", index),
|
|
1914
|
+
transactionNodeId(transaction.hash),
|
|
1915
|
+
"left",
|
|
1916
|
+
`signer #${index}`,
|
|
1917
|
+
"sign"
|
|
1918
|
+
)
|
|
1919
|
+
),
|
|
1920
|
+
...transaction.collateralReturn ? [
|
|
1921
|
+
port(
|
|
1922
|
+
transactionPortId(transaction.hash, "collateralReturn", 0),
|
|
1923
|
+
transactionNodeId(transaction.hash),
|
|
1924
|
+
"right",
|
|
1925
|
+
"collateral return",
|
|
1926
|
+
"collateralReturn"
|
|
1927
|
+
)
|
|
1928
|
+
] : []
|
|
1929
|
+
];
|
|
1930
|
+
var redeemerKeyFor = (transaction, tag, index, mappedRedeemers) => {
|
|
1931
|
+
const redeemer = transaction.redeemers.find(
|
|
1932
|
+
(candidate) => candidate.tag === tag && candidate.index === String(index)
|
|
1933
|
+
);
|
|
1934
|
+
if (!redeemer) return void 0;
|
|
1935
|
+
mappedRedeemers?.add(redeemerMapKey(transaction.hash, redeemer));
|
|
1936
|
+
return {
|
|
1937
|
+
tag: redeemer.tag,
|
|
1938
|
+
index: redeemer.index,
|
|
1939
|
+
redeemerListIndex: redeemer.redeemerListIndex
|
|
1940
|
+
};
|
|
1941
|
+
};
|
|
1942
|
+
var applyAction = (trace, edge, describers) => {
|
|
1943
|
+
const context = redeemerContext(trace, edge);
|
|
1944
|
+
if (!context) return edge;
|
|
1945
|
+
for (const describer of describers) {
|
|
1946
|
+
const action = describer(context);
|
|
1947
|
+
if (action) {
|
|
1948
|
+
return { ...edge, action, label: action.label };
|
|
1949
|
+
}
|
|
1950
|
+
}
|
|
1951
|
+
const fallback = genericAction(context.redeemer);
|
|
1952
|
+
return {
|
|
1953
|
+
...edge,
|
|
1954
|
+
action: fallback,
|
|
1955
|
+
label: fallback.label
|
|
1956
|
+
};
|
|
1957
|
+
};
|
|
1958
|
+
var redeemerContext = (trace, edge) => {
|
|
1959
|
+
if (!edge.redeemerKey) return void 0;
|
|
1960
|
+
const transaction = trace.transactions.find(
|
|
1961
|
+
(candidate) => candidate.hash === edge.targetRef.txHash
|
|
1962
|
+
);
|
|
1963
|
+
const redeemer = transaction?.redeemers.find(
|
|
1964
|
+
(candidate) => candidate.tag === edge.redeemerKey?.tag && candidate.index === edge.redeemerKey.index && candidate.redeemerListIndex === edge.redeemerKey.redeemerListIndex
|
|
1965
|
+
);
|
|
1966
|
+
return transaction && redeemer ? {
|
|
1967
|
+
trace,
|
|
1968
|
+
transaction,
|
|
1969
|
+
redeemer,
|
|
1970
|
+
target: edge.targetRef,
|
|
1971
|
+
edge
|
|
1972
|
+
} : void 0;
|
|
1973
|
+
};
|
|
1974
|
+
var genericAction = (redeemer) => ({
|
|
1975
|
+
label: `${redeemer.tag} #${redeemer.index}`,
|
|
1976
|
+
source: "generic",
|
|
1977
|
+
confidence: "fallback"
|
|
1978
|
+
});
|
|
1979
|
+
var matchesRedeemer = (selector, { transaction, redeemer, target }) => redeemer.tag === selector.tag && redeemer.index === String(selector.index) && (!selector.txHash || transaction.hash === selector.txHash) && (!selector.target || selector.target(target));
|
|
1980
|
+
var constructorOf = (data) => {
|
|
1981
|
+
try {
|
|
1982
|
+
const alternative = CML.PlutusData.from_cbor_hex(data).as_constr_plutus_data()?.alternative();
|
|
1983
|
+
if (alternative === void 0) return void 0;
|
|
1984
|
+
const value = Number(alternative);
|
|
1985
|
+
return Number.isSafeInteger(value) ? value : void 0;
|
|
1986
|
+
} catch {
|
|
1987
|
+
return void 0;
|
|
1988
|
+
}
|
|
1989
|
+
};
|
|
1990
|
+
var visualDiagnostic = (warning, index) => ({
|
|
1991
|
+
id: `diagnostic:${index}:${warning.code}`,
|
|
1992
|
+
severity: warning.code.includes("failed") ? "error" : "warning",
|
|
1993
|
+
code: warning.code,
|
|
1994
|
+
message: warning.message,
|
|
1995
|
+
...warning.txHash ? { txHash: warning.txHash } : {},
|
|
1996
|
+
...warning.outRef ? { outRef: warning.outRef } : {}
|
|
1997
|
+
});
|
|
1998
|
+
var unmappedRedeemerDiagnostics = (trace, mappedRedeemers) => trace.transactions.flatMap(
|
|
1999
|
+
(transaction) => transaction.redeemers.filter(
|
|
2000
|
+
(redeemer) => !mappedRedeemers.has(redeemerMapKey(transaction.hash, redeemer))
|
|
2001
|
+
).map((redeemer) => ({
|
|
2002
|
+
id: `diagnostic:${transaction.hash}:unmapped-redeemer:${redeemer.tag}:${redeemer.index}`,
|
|
2003
|
+
severity: "warning",
|
|
2004
|
+
code: "unmapped-redeemer",
|
|
2005
|
+
message: `Redeemer ${redeemer.tag} #${redeemer.index} in transaction ${transaction.hash} has no matching semantic edge`,
|
|
2006
|
+
txHash: transaction.hash,
|
|
2007
|
+
redeemerKey: {
|
|
2008
|
+
tag: redeemer.tag,
|
|
2009
|
+
index: redeemer.index,
|
|
2010
|
+
redeemerListIndex: redeemer.redeemerListIndex
|
|
2011
|
+
}
|
|
2012
|
+
}))
|
|
2013
|
+
);
|
|
2014
|
+
var redeemerMapKey = (txHash, redeemer) => `${txHash}\0${redeemer.tag}\0${redeemer.index}\0${redeemer.redeemerListIndex}`;
|
|
2015
|
+
var assetsByPolicy = (assets) => {
|
|
2016
|
+
const result = {};
|
|
2017
|
+
for (const [unit, amount] of Object.entries(assets)) {
|
|
2018
|
+
const policyId = policyIdOfUnit(unit);
|
|
2019
|
+
result[policyId] = {
|
|
2020
|
+
...result[policyId] ?? {},
|
|
2021
|
+
[unit]: amount
|
|
2022
|
+
};
|
|
2023
|
+
}
|
|
2024
|
+
return result;
|
|
2025
|
+
};
|
|
2026
|
+
var sortedPolicyIds = (assets) => [...new Set(Object.keys(assets).map(policyIdOfUnit))].sort();
|
|
2027
|
+
var policyIdOfUnit = (unit) => unit === "lovelace" ? "lovelace" : unit.slice(0, 56);
|
|
2028
|
+
var section = (id, title, rows) => ({
|
|
2029
|
+
id,
|
|
2030
|
+
title,
|
|
2031
|
+
rows: [...rows],
|
|
2032
|
+
collapsed: rows.length === 0
|
|
2033
|
+
});
|
|
2034
|
+
var field = (id, label, value, mono = false, tone, rawValue, rawLabel) => ({
|
|
2035
|
+
id,
|
|
2036
|
+
label,
|
|
2037
|
+
...rawLabel ? { rawLabel } : {},
|
|
2038
|
+
value,
|
|
2039
|
+
...rawValue ? { rawValue } : {},
|
|
2040
|
+
...mono ? { mono } : {},
|
|
2041
|
+
...tone ? { tone } : {}
|
|
2042
|
+
});
|
|
2043
|
+
var port = (id, nodeId, side, label, edgeKind) => ({
|
|
2044
|
+
id,
|
|
2045
|
+
nodeId,
|
|
2046
|
+
side,
|
|
2047
|
+
label,
|
|
2048
|
+
...edgeKind ? { edgeKind } : {}
|
|
2049
|
+
});
|
|
2050
|
+
var statusTone = (status) => {
|
|
2051
|
+
switch (status) {
|
|
2052
|
+
case "submitted":
|
|
2053
|
+
case "confirmed":
|
|
2054
|
+
return "success";
|
|
2055
|
+
case "failed":
|
|
2056
|
+
return "danger";
|
|
2057
|
+
case "built":
|
|
2058
|
+
case "signed":
|
|
2059
|
+
return "info";
|
|
2060
|
+
}
|
|
2061
|
+
};
|
|
2062
|
+
var assetSort = (left, right) => {
|
|
2063
|
+
if (left === "lovelace") return -1;
|
|
2064
|
+
if (right === "lovelace") return 1;
|
|
2065
|
+
return left.localeCompare(right);
|
|
2066
|
+
};
|
|
2067
|
+
var transactionNodeId = (txHash) => `tx:${txHash}`;
|
|
2068
|
+
var utxoNodeId2 = (outRef) => `utxo:${outRefKey(outRef)}`;
|
|
2069
|
+
var assetPolicyNodeId = (policyId) => `asset-policy:${policyId}`;
|
|
2070
|
+
var signerNodeId = (keyHash) => `signer:${keyHash}`;
|
|
2071
|
+
var withdrawalNodeId = (rewardAddress) => `withdrawal:${rewardAddress}`;
|
|
2072
|
+
var certificateNodeId = (txHash, index) => `certificate:${txHash}#${index}`;
|
|
2073
|
+
var collateralReturnNodeId = (txHash) => `collateral-return:${txHash}`;
|
|
2074
|
+
var transactionPortId = (txHash, kind, index) => `tx:${txHash}:port:${kind}:${index}`;
|
|
2075
|
+
var utxoPortId = (key, direction) => `utxo:${key}:port:${direction}`;
|
|
2076
|
+
var assetPolicyPortId = (policyId) => `asset-policy:${policyId}:port`;
|
|
2077
|
+
var signerPortId = (keyHash) => `signer:${keyHash}:port`;
|
|
2078
|
+
var withdrawalPortId = (rewardAddress) => `withdrawal:${rewardAddress}:port`;
|
|
2079
|
+
var certificatePortId = (txHash, index) => `certificate:${txHash}#${index}:port`;
|
|
2080
|
+
var collateralReturnPortId = (txHash) => `collateral-return:${txHash}:port`;
|
|
2081
|
+
var defaultLegend = {
|
|
2082
|
+
nodes: [
|
|
2083
|
+
{ kind: "transaction", label: "Transaction" },
|
|
2084
|
+
{ kind: "utxo", label: "UTxO" },
|
|
2085
|
+
{ kind: "asset", label: "Asset policy" },
|
|
2086
|
+
{ kind: "diagnostic", label: "Diagnostic" }
|
|
2087
|
+
],
|
|
2088
|
+
edges: [
|
|
2089
|
+
{ kind: "spend", label: "Spend input" },
|
|
2090
|
+
{ kind: "read", label: "Reference input" },
|
|
2091
|
+
{ kind: "collateral", label: "Collateral input" },
|
|
2092
|
+
{ kind: "produce", label: "Produced output" },
|
|
2093
|
+
{ kind: "mint", label: "Mint policy" },
|
|
2094
|
+
{ kind: "burn", label: "Burn policy" },
|
|
2095
|
+
{ kind: "withdraw", label: "Withdrawal" },
|
|
2096
|
+
{ kind: "certify", label: "Certificate" },
|
|
2097
|
+
{ kind: "sign", label: "Required signer" }
|
|
2098
|
+
]
|
|
2099
|
+
};
|
|
2100
|
+
|
|
2101
|
+
// src/render/dot.ts
|
|
2102
|
+
var traceToDot = (trace, options = {}) => {
|
|
2103
|
+
if (options.legacy) return traceToLegacyDot(trace, options);
|
|
2104
|
+
const resolved = resolveVisualRendererOptions(options);
|
|
2105
|
+
const graph = formatSemanticGraph(
|
|
2106
|
+
buildSemanticRenderGraph(trace, {
|
|
2107
|
+
redeemers: options.redeemers
|
|
2108
|
+
}),
|
|
2109
|
+
resolved
|
|
2110
|
+
);
|
|
2111
|
+
const semanticNodes = new Map(
|
|
2112
|
+
graph.nodes.map((node) => [node.id, node])
|
|
2113
|
+
);
|
|
2114
|
+
const nodeIds = new Map(
|
|
2115
|
+
graph.nodes.map((node, index) => [node.id, `n${index}`])
|
|
2116
|
+
);
|
|
2117
|
+
const lines = [
|
|
2118
|
+
`digraph ${dotString(options.graphName ?? "lucid-evolution-tx-graph")} {`,
|
|
2119
|
+
" rankdir=LR;",
|
|
2120
|
+
" splines=ortho;",
|
|
2121
|
+
" nodesep=0.45;",
|
|
2122
|
+
" ranksep=0.75;",
|
|
2123
|
+
' graph [fontname="Inter, Arial", bgcolor="white", pad=0.2];',
|
|
2124
|
+
' node [fontname="Inter, Arial", fontsize=10, shape=plain, margin=0];',
|
|
2125
|
+
' edge [fontname="Inter, Arial", fontsize=9, arrowsize=0.7];'
|
|
2126
|
+
];
|
|
2127
|
+
for (const node of graph.nodes) {
|
|
2128
|
+
lines.push(
|
|
2129
|
+
` ${dotString(nodeIds.get(node.id))} [${semanticNodeAttributes(
|
|
2130
|
+
node,
|
|
2131
|
+
resolved
|
|
2132
|
+
)}];`
|
|
2133
|
+
);
|
|
2134
|
+
}
|
|
2135
|
+
for (const edge of graph.edges) {
|
|
2136
|
+
const style = visualEdgeStyle(edge.kind);
|
|
2137
|
+
const scriptFocus = resolved.view === "scriptInteraction" && isScriptInteractionEdge(edge, semanticNodes);
|
|
2138
|
+
lines.push(
|
|
2139
|
+
` ${dotString(nodeIds.get(edge.from))} -> ${dotString(
|
|
2140
|
+
nodeIds.get(edge.to)
|
|
2141
|
+
)} [` + [
|
|
2142
|
+
`label=${dotString(semanticEdgeLabel(trace, edge, resolved))}`,
|
|
2143
|
+
`color=${dotString(style.color)}`,
|
|
2144
|
+
`fontcolor=${dotString(style.color)}`,
|
|
2145
|
+
`style=${dotString(style.style)}`,
|
|
2146
|
+
`penwidth=${scriptFocus ? "2.8" : edge.kind === "spend" || edge.kind === "produce" ? "1.6" : "1.1"}`
|
|
2147
|
+
].join(", ") + "];"
|
|
2148
|
+
);
|
|
2149
|
+
}
|
|
2150
|
+
lines.push("}");
|
|
2151
|
+
return `${lines.join("\n")}
|
|
2152
|
+
`;
|
|
2153
|
+
};
|
|
2154
|
+
var semanticNodeAttributes = (node, options) => [
|
|
2155
|
+
`label=<${semanticNodeLabel(node, options)}>`,
|
|
2156
|
+
`tooltip=${dotString(nodeTooltip(node, options))}`
|
|
2157
|
+
].join(", ");
|
|
2158
|
+
var semanticNodeLabel = (node, options) => {
|
|
2159
|
+
const palette = nodePalette(node);
|
|
2160
|
+
const subtitle = semanticNodeSubtitle(node, options);
|
|
2161
|
+
const chips = semanticChipLabels(node, options);
|
|
2162
|
+
const sections = visibleSemanticSections(node, options);
|
|
2163
|
+
return [
|
|
2164
|
+
`<TABLE BORDER="1" CELLBORDER="0" CELLSPACING="0" CELLPADDING="5" COLOR="${palette.border}">`,
|
|
2165
|
+
`<TR><TD COLSPAN="2" BGCOLOR="${palette.header}"><B>${htmlText(semanticNodeTitle(node, options))}</B>${subtitle ? `<BR/><FONT POINT-SIZE="9">${htmlText(subtitle)}</FONT>` : ""}</TD></TR>`,
|
|
2166
|
+
...chips.length > 0 ? [
|
|
2167
|
+
`<TR><TD COLSPAN="2" BGCOLOR="${palette.chip}"><FONT POINT-SIZE="9">${htmlText(
|
|
2168
|
+
chips.join(" | ")
|
|
2169
|
+
)}</FONT></TD></TR>`
|
|
2170
|
+
] : [],
|
|
2171
|
+
...sections.flatMap((section2) => sectionRows(section2, node, options)),
|
|
2172
|
+
"</TABLE>"
|
|
2173
|
+
].join("");
|
|
2174
|
+
};
|
|
2175
|
+
var sectionRows = (section2, node, options) => {
|
|
2176
|
+
const rows = visibleSemanticRows(section2, node, options);
|
|
2177
|
+
if (rows.length === 0) return [];
|
|
2178
|
+
return [
|
|
2179
|
+
`<TR><TD COLSPAN="2" ALIGN="LEFT" BGCOLOR="#f9fafb"><B>${htmlText(
|
|
2180
|
+
section2.title
|
|
2181
|
+
)}</B></TD></TR>`,
|
|
2182
|
+
...rows.map((row) => {
|
|
2183
|
+
const value = semanticFieldValue(row, options);
|
|
2184
|
+
const valueFace = row.mono ? ' FACE="monospace"' : "";
|
|
2185
|
+
return `<TR><TD ALIGN="LEFT"><FONT POINT-SIZE="9">${htmlText(
|
|
2186
|
+
semanticFieldLabel(row, options)
|
|
2187
|
+
)}</FONT></TD><TD ALIGN="LEFT"><FONT${valueFace}>${htmlText(
|
|
2188
|
+
value
|
|
2189
|
+
)}</FONT></TD></TR>`;
|
|
2190
|
+
})
|
|
2191
|
+
];
|
|
2192
|
+
};
|
|
2193
|
+
var nodeTooltip = (node, options) => [
|
|
2194
|
+
semanticNodeTitle(node, options),
|
|
2195
|
+
...semanticNodeSubtitle(node, options) ? [semanticNodeSubtitle(node, options)] : [],
|
|
2196
|
+
...node.sections.flatMap(
|
|
2197
|
+
(section2) => section2.rows.map(
|
|
2198
|
+
(row) => `${section2.title}.${semanticFieldLabel(row, options)}: ${semanticFieldValue(
|
|
2199
|
+
row,
|
|
2200
|
+
options
|
|
2201
|
+
)}`
|
|
2202
|
+
)
|
|
2203
|
+
)
|
|
2204
|
+
].join("\n");
|
|
2205
|
+
var nodePalette = (node) => {
|
|
2206
|
+
if (node.severity === "error") {
|
|
2207
|
+
return { header: "#fee2e2", chip: "#fef2f2", border: "#dc2626" };
|
|
2208
|
+
}
|
|
2209
|
+
if (isGenesisNode(node)) {
|
|
2210
|
+
return { header: "#fef3c7", chip: "#fffbeb", border: "#d97706" };
|
|
2211
|
+
}
|
|
2212
|
+
if (node.rawRef.type === "diagnostic" && node.rawRef.code === "collapsed") {
|
|
2213
|
+
return { header: "#f3f4f6", chip: "#f9fafb", border: "#6b7280" };
|
|
2214
|
+
}
|
|
2215
|
+
switch (node.kind) {
|
|
2216
|
+
case "transaction":
|
|
2217
|
+
return { header: "#eef2ff", chip: "#e0e7ff", border: "#6366f1" };
|
|
2218
|
+
case "utxo":
|
|
2219
|
+
return { header: "#ecfdf5", chip: "#d1fae5", border: "#10b981" };
|
|
2220
|
+
case "asset":
|
|
2221
|
+
return { header: "#f5f3ff", chip: "#ede9fe", border: "#7c3aed" };
|
|
2222
|
+
case "signer":
|
|
2223
|
+
return { header: "#f3f4f6", chip: "#f9fafb", border: "#6b7280" };
|
|
2224
|
+
case "withdrawal":
|
|
2225
|
+
return { header: "#ecfeff", chip: "#cffafe", border: "#0891b2" };
|
|
2226
|
+
case "certificate":
|
|
2227
|
+
return { header: "#eef2ff", chip: "#e0e7ff", border: "#4f46e5" };
|
|
2228
|
+
case "collateralReturn":
|
|
2229
|
+
return { header: "#fffbeb", chip: "#fef3c7", border: "#f59e0b" };
|
|
2230
|
+
case "diagnostic":
|
|
2231
|
+
return { header: "#fef2f2", chip: "#fee2e2", border: "#dc2626" };
|
|
2232
|
+
}
|
|
2233
|
+
};
|
|
2234
|
+
var htmlText = (value) => value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/\\n/g, "<BR/>").replace(/\n/g, "<BR/>");
|
|
2235
|
+
var traceToLegacyDot = (trace, options = {}) => {
|
|
2236
|
+
const graph = buildRenderGraph(trace);
|
|
2237
|
+
const lines = [
|
|
2238
|
+
`digraph ${dotString(options.graphName ?? "lucid-evolution-tx-graph")} {`,
|
|
2239
|
+
" rankdir=LR;",
|
|
2240
|
+
' graph [fontname="Inter, Arial", bgcolor="white"];',
|
|
2241
|
+
' node [fontname="Inter, Arial", fontsize=10, margin="0.08,0.06"];',
|
|
2242
|
+
' edge [fontname="Inter, Arial", fontsize=9, arrowsize=0.7];'
|
|
2243
|
+
];
|
|
2244
|
+
for (const node of graph.nodes) {
|
|
2245
|
+
lines.push(` ${dotString(node.id)} [${legacyNodeAttributes(node)}];`);
|
|
2246
|
+
}
|
|
2247
|
+
for (const edge of graph.edges) {
|
|
2248
|
+
const style = edgeStyle(edge.kind);
|
|
2249
|
+
lines.push(
|
|
2250
|
+
` ${dotString(edge.from)} -> ${dotString(edge.to)} [` + [
|
|
2251
|
+
`label=${dotString(style.label)}`,
|
|
2252
|
+
`color=${dotString(style.color)}`,
|
|
2253
|
+
`fontcolor=${dotString(style.color)}`,
|
|
2254
|
+
`style=${dotString(style.style)}`
|
|
2255
|
+
].join(", ") + "];"
|
|
2256
|
+
);
|
|
2257
|
+
}
|
|
2258
|
+
lines.push("}");
|
|
2259
|
+
return `${lines.join("\n")}
|
|
2260
|
+
`;
|
|
2261
|
+
};
|
|
2262
|
+
var legacyNodeAttributes = (node) => {
|
|
2263
|
+
const attrs = [
|
|
2264
|
+
`label=${dotString(node.label)}`,
|
|
2265
|
+
`shape=${dotString(legacyNodeShape(node))}`,
|
|
2266
|
+
`style=${dotString(legacyNodeStyle(node))}`,
|
|
2267
|
+
`fillcolor=${dotString(legacyNodeFill(node))}`,
|
|
2268
|
+
`color=${dotString(legacyNodeColor(node))}`
|
|
2269
|
+
];
|
|
2270
|
+
return attrs.join(", ");
|
|
2271
|
+
};
|
|
2272
|
+
var legacyNodeShape = (node) => {
|
|
2273
|
+
switch (node.kind) {
|
|
2274
|
+
case "transaction":
|
|
2275
|
+
return "box";
|
|
2276
|
+
case "utxo":
|
|
2277
|
+
return "box";
|
|
2278
|
+
case "asset":
|
|
2279
|
+
return "component";
|
|
2280
|
+
case "signer":
|
|
2281
|
+
return "octagon";
|
|
2282
|
+
case "withdrawal":
|
|
2283
|
+
case "certificate":
|
|
2284
|
+
case "collateralReturn":
|
|
2285
|
+
case "external":
|
|
2286
|
+
return "note";
|
|
2287
|
+
}
|
|
2288
|
+
};
|
|
2289
|
+
var legacyNodeStyle = (node) => node.kind === "utxo" ? "rounded,filled" : "filled";
|
|
2290
|
+
var legacyNodeFill = (node) => {
|
|
2291
|
+
if (node.unresolved) return "#fee2e2";
|
|
2292
|
+
if (node.genesis) return "#fef3c7";
|
|
2293
|
+
switch (node.kind) {
|
|
2294
|
+
case "transaction":
|
|
2295
|
+
return "#eef2ff";
|
|
2296
|
+
case "utxo":
|
|
2297
|
+
return "#ecfdf5";
|
|
2298
|
+
case "asset":
|
|
2299
|
+
return "#f5f3ff";
|
|
2300
|
+
case "signer":
|
|
2301
|
+
return "#f3f4f6";
|
|
2302
|
+
case "withdrawal":
|
|
2303
|
+
return "#ecfeff";
|
|
2304
|
+
case "certificate":
|
|
2305
|
+
return "#eef2ff";
|
|
2306
|
+
case "collateralReturn":
|
|
2307
|
+
return "#fffbeb";
|
|
2308
|
+
case "external":
|
|
2309
|
+
return "#f9fafb";
|
|
2310
|
+
}
|
|
2311
|
+
};
|
|
2312
|
+
var legacyNodeColor = (node) => node.unresolved ? "#dc2626" : node.genesis ? "#d97706" : "#9ca3af";
|
|
2313
|
+
var dotString = (value) => JSON.stringify(value.replace(/\\n/g, "\n"));
|
|
2314
|
+
|
|
2315
|
+
// src/render/measure.ts
|
|
2316
|
+
var MARGIN = 40;
|
|
2317
|
+
var TITLE_HEIGHT = 74;
|
|
2318
|
+
var LEGEND_HEIGHT = 132;
|
|
2319
|
+
var MIN_CANVAS_WIDTH = 940;
|
|
2320
|
+
var NODE_GAP_Y = 34;
|
|
2321
|
+
var RANK_GAP_X = 170;
|
|
2322
|
+
var LABEL_GAP = 8;
|
|
2323
|
+
var measureNode = (node, options) => {
|
|
2324
|
+
const width = nodeWidth(node);
|
|
2325
|
+
if (node.kind === "transaction") {
|
|
2326
|
+
return { width, height: measureTransactionNode(node, options) };
|
|
2327
|
+
}
|
|
2328
|
+
const sections = visibleSemanticSections(node, options);
|
|
2329
|
+
const rowCount = sections.reduce(
|
|
2330
|
+
(sum, section2) => sum + visibleSemanticRows(section2, node, options).length,
|
|
2331
|
+
0
|
|
2332
|
+
);
|
|
2333
|
+
const populatedSections = sections.filter(
|
|
2334
|
+
(section2) => visibleSemanticRows(section2, node, options).length > 0
|
|
2335
|
+
).length;
|
|
2336
|
+
const chips = semanticChipLabels(node, options);
|
|
2337
|
+
const chipRows = estimateChipRows(chips, width - 28);
|
|
2338
|
+
return {
|
|
2339
|
+
width,
|
|
2340
|
+
height: 58 + chipRows * 24 + populatedSections * 24 + rowCount * 23 + 22
|
|
2341
|
+
};
|
|
2342
|
+
};
|
|
2343
|
+
var measureLabel = (label) => ({
|
|
2344
|
+
width: Math.min(260, Math.max(54, label.length * 6.4 + 18)),
|
|
2345
|
+
height: 22
|
|
2346
|
+
});
|
|
2347
|
+
var visibleEdgeLabel = (trace, edge, options) => {
|
|
2348
|
+
if (edge.action || edge.redeemerKey) {
|
|
2349
|
+
return edge.action?.label ?? semanticEdgeLabel(trace, edge, options);
|
|
2350
|
+
}
|
|
2351
|
+
if (edge.kind === "diagnostic")
|
|
2352
|
+
return semanticEdgeLabel(trace, edge, options);
|
|
2353
|
+
if (edge.kind === "mint" || edge.kind === "burn") {
|
|
2354
|
+
return options.mode === "overview" ? void 0 : edge.label;
|
|
2355
|
+
}
|
|
2356
|
+
if (edge.kind === "withdraw" || edge.kind === "certify") return edge.label;
|
|
2357
|
+
if (edge.kind === "collateral" || edge.kind === "collateralReturn") {
|
|
2358
|
+
return options.mode === "debug" ? edge.label : void 0;
|
|
2359
|
+
}
|
|
2360
|
+
return void 0;
|
|
2361
|
+
};
|
|
2362
|
+
var nodeWidth = (node) => {
|
|
2363
|
+
switch (node.kind) {
|
|
2364
|
+
case "transaction":
|
|
2365
|
+
return 330;
|
|
2366
|
+
case "utxo":
|
|
2367
|
+
return 306;
|
|
2368
|
+
case "diagnostic":
|
|
2369
|
+
return 286;
|
|
2370
|
+
case "asset":
|
|
2371
|
+
return 270;
|
|
2372
|
+
case "withdrawal":
|
|
2373
|
+
case "certificate":
|
|
2374
|
+
case "collateralReturn":
|
|
2375
|
+
return 270;
|
|
2376
|
+
case "signer":
|
|
2377
|
+
return 240;
|
|
2378
|
+
}
|
|
2379
|
+
};
|
|
2380
|
+
var metricRowCount = (node, options) => {
|
|
2381
|
+
const metricRows = visibleSemanticSections(node, options).filter(
|
|
2382
|
+
(section2) => ["inputs", "assets", "authority", "redeemers"].includes(section2.id)
|
|
2383
|
+
).flatMap((section2) => visibleSemanticRows(section2, node, options)).filter((row) => row.value !== "0").length;
|
|
2384
|
+
return metricRows === 0 ? 0 : Math.ceil(metricRows / 4);
|
|
2385
|
+
};
|
|
2386
|
+
var measureTransactionNode = (node, options) => {
|
|
2387
|
+
const metricRows = metricRowCount(node, options);
|
|
2388
|
+
const facts = rowsForSection(node, options, "facts").slice(0, 4);
|
|
2389
|
+
const redeemers = rowsForSection(node, options, "redeemers").slice(0, 1);
|
|
2390
|
+
const detailRows = facts.length + redeemers.length;
|
|
2391
|
+
const metricsHeight = metricRows === 0 ? 0 : metricRows * 34 + 8;
|
|
2392
|
+
const detailsHeight = detailRows === 0 ? 0 : 27 + detailRows * 21;
|
|
2393
|
+
return 64 + metricsHeight + detailsHeight + 12;
|
|
2394
|
+
};
|
|
2395
|
+
var rowsForSection = (node, options, sectionId) => visibleSemanticSections(node, options).filter((section2) => section2.id === sectionId).flatMap((section2) => visibleSemanticRows(section2, node, options)).filter((row) => row.value !== "0");
|
|
2396
|
+
var estimateChipRows = (chips, availableWidth) => {
|
|
2397
|
+
if (chips.length === 0) return 0;
|
|
2398
|
+
let rows = 1;
|
|
2399
|
+
let cursor = 0;
|
|
2400
|
+
for (const chip of chips) {
|
|
2401
|
+
const width = Math.max(42, Math.min(18, chip.length) * 6 + 18) + 6;
|
|
2402
|
+
if (cursor > 0 && cursor + width > availableWidth) {
|
|
2403
|
+
rows += 1;
|
|
2404
|
+
cursor = 0;
|
|
2405
|
+
}
|
|
2406
|
+
cursor += width;
|
|
2407
|
+
}
|
|
2408
|
+
return rows;
|
|
2409
|
+
};
|
|
2410
|
+
|
|
2411
|
+
// src/render/layout.ts
|
|
2412
|
+
var layoutGraph = (trace, graph, options) => {
|
|
2413
|
+
const sizes = new Map(
|
|
2414
|
+
graph.nodes.map((node) => [node.id, measureNode(node, options)])
|
|
2415
|
+
);
|
|
2416
|
+
const ranks = rankNodes(trace, graph);
|
|
2417
|
+
const rankedNodes = /* @__PURE__ */ new Map();
|
|
2418
|
+
for (const node of graph.nodes) {
|
|
2419
|
+
const rank = ranks.get(node.id) ?? 0;
|
|
2420
|
+
rankedNodes.set(rank, [...rankedNodes.get(rank) ?? [], node]);
|
|
2421
|
+
}
|
|
2422
|
+
const orderedRanks = [...rankedNodes.keys()].sort(
|
|
2423
|
+
(left, right) => left - right
|
|
2424
|
+
);
|
|
2425
|
+
const rankWidths = new Map(
|
|
2426
|
+
orderedRanks.map((rank) => [
|
|
2427
|
+
rank,
|
|
2428
|
+
Math.max(
|
|
2429
|
+
...rankedNodes.get(rank).map((node) => sizes.get(node.id).width)
|
|
2430
|
+
)
|
|
2431
|
+
])
|
|
2432
|
+
);
|
|
2433
|
+
const rankHeights = new Map(
|
|
2434
|
+
orderedRanks.map((rank) => [
|
|
2435
|
+
rank,
|
|
2436
|
+
rankedNodes.get(rank).reduce((sum, node) => sum + sizes.get(node.id).height, 0) + Math.max(0, rankedNodes.get(rank).length - 1) * NODE_GAP_Y
|
|
2437
|
+
])
|
|
2438
|
+
);
|
|
2439
|
+
const maxRankHeight = Math.max(0, ...rankHeights.values());
|
|
2440
|
+
let x = MARGIN;
|
|
2441
|
+
const rankX = /* @__PURE__ */ new Map();
|
|
2442
|
+
for (const rank of orderedRanks) {
|
|
2443
|
+
rankX.set(rank, x);
|
|
2444
|
+
x += (rankWidths.get(rank) ?? 0) + RANK_GAP_X;
|
|
2445
|
+
}
|
|
2446
|
+
const positioned = /* @__PURE__ */ new Map();
|
|
2447
|
+
for (const rank of orderedRanks) {
|
|
2448
|
+
const nodes = [...rankedNodes.get(rank)].sort(
|
|
2449
|
+
(left, right) => nodeSortKey(left, graph).localeCompare(nodeSortKey(right, graph))
|
|
2450
|
+
);
|
|
2451
|
+
let y = MARGIN + TITLE_HEIGHT + 32 + Math.max(0, (maxRankHeight - (rankHeights.get(rank) ?? 0)) / 2);
|
|
2452
|
+
for (const node of nodes) {
|
|
2453
|
+
const size = sizes.get(node.id);
|
|
2454
|
+
positioned.set(node.id, {
|
|
2455
|
+
node,
|
|
2456
|
+
...size,
|
|
2457
|
+
x: rankX.get(rank) + ((rankWidths.get(rank) ?? size.width) - size.width) / 2,
|
|
2458
|
+
y
|
|
2459
|
+
});
|
|
2460
|
+
y += size.height + NODE_GAP_Y;
|
|
2461
|
+
}
|
|
2462
|
+
}
|
|
2463
|
+
const ports = portOffsets(graph, positioned);
|
|
2464
|
+
const provisional = {
|
|
2465
|
+
nodes: positioned,
|
|
2466
|
+
portOffsets: ports,
|
|
2467
|
+
edgeLabels: /* @__PURE__ */ new Map(),
|
|
2468
|
+
width: MIN_CANVAS_WIDTH,
|
|
2469
|
+
height: 260,
|
|
2470
|
+
offsetX: 0,
|
|
2471
|
+
offsetY: 0
|
|
2472
|
+
};
|
|
2473
|
+
const edgeLabels = placeEdgeLabels(trace, graph, options, provisional);
|
|
2474
|
+
const boxes = [
|
|
2475
|
+
...[...positioned.values()].map(nodeBox),
|
|
2476
|
+
...edgeLabels.values()
|
|
2477
|
+
];
|
|
2478
|
+
const maxX = Math.max(0, ...boxes.map((box) => box.x + box.width));
|
|
2479
|
+
const maxY = Math.max(0, ...boxes.map((box) => box.y + box.height));
|
|
2480
|
+
return {
|
|
2481
|
+
nodes: positioned,
|
|
2482
|
+
portOffsets: ports,
|
|
2483
|
+
edgeLabels,
|
|
2484
|
+
width: Math.max(MIN_CANVAS_WIDTH, Math.ceil(maxX + MARGIN)),
|
|
2485
|
+
height: Math.ceil(maxY + MARGIN + LEGEND_HEIGHT),
|
|
2486
|
+
offsetX: 0,
|
|
2487
|
+
offsetY: 0
|
|
2488
|
+
};
|
|
2489
|
+
};
|
|
2490
|
+
var nodeBox = (node) => ({
|
|
2491
|
+
x: node.x,
|
|
2492
|
+
y: node.y,
|
|
2493
|
+
width: node.width,
|
|
2494
|
+
height: node.height
|
|
2495
|
+
});
|
|
2496
|
+
var edgeAnchors = (edge, graph) => {
|
|
2497
|
+
const from = graph.nodes.get(edge.from);
|
|
2498
|
+
const to = graph.nodes.get(edge.to);
|
|
2499
|
+
if (!from || !to) return void 0;
|
|
2500
|
+
return {
|
|
2501
|
+
start: anchor(edge, "from", from, to, graph),
|
|
2502
|
+
end: anchor(edge, "to", to, from, graph)
|
|
2503
|
+
};
|
|
2504
|
+
};
|
|
2505
|
+
var edgePath = (edge, graph) => {
|
|
2506
|
+
const anchors = edgeAnchors(edge, graph);
|
|
2507
|
+
if (!anchors) return "";
|
|
2508
|
+
const { start, end } = anchors;
|
|
2509
|
+
const dx = end.x - start.x;
|
|
2510
|
+
const dy = end.y - start.y;
|
|
2511
|
+
if (Math.abs(dx) < 80) {
|
|
2512
|
+
const curve = Math.max(50, Math.abs(dy) / 2);
|
|
2513
|
+
const direction = dy >= 0 ? 1 : -1;
|
|
2514
|
+
return `M ${start.x} ${start.y} C ${start.x} ${start.y + curve * direction}, ${end.x} ${end.y - curve * direction}, ${end.x} ${end.y}`;
|
|
2515
|
+
}
|
|
2516
|
+
const midX = start.x + dx / 2;
|
|
2517
|
+
return `M ${start.x} ${start.y} L ${midX} ${start.y} L ${midX} ${end.y} L ${end.x} ${end.y}`;
|
|
2518
|
+
};
|
|
2519
|
+
var edgeLabelPoint = (edge, graph) => {
|
|
2520
|
+
const anchors = edgeAnchors(edge, graph);
|
|
2521
|
+
if (!anchors) return { x: 0, y: 0 };
|
|
2522
|
+
const { start, end } = anchors;
|
|
2523
|
+
return {
|
|
2524
|
+
x: (start.x + end.x) / 2,
|
|
2525
|
+
y: (start.y + end.y) / 2
|
|
2526
|
+
};
|
|
2527
|
+
};
|
|
2528
|
+
var rankNodes = (trace, graph) => {
|
|
2529
|
+
const txIndex = new Map(
|
|
2530
|
+
trace.transactions.map(
|
|
2531
|
+
(transaction, index) => [transaction.hash, index]
|
|
2532
|
+
)
|
|
2533
|
+
);
|
|
2534
|
+
const edgeTxIndices = /* @__PURE__ */ new Map();
|
|
2535
|
+
for (const edge of graph.edges) {
|
|
2536
|
+
const txHash = edge.targetRef.txHash;
|
|
2537
|
+
if (!txHash) continue;
|
|
2538
|
+
const index = txIndex.get(txHash);
|
|
2539
|
+
if (index === void 0) continue;
|
|
2540
|
+
for (const nodeId of [edge.from, edge.to]) {
|
|
2541
|
+
edgeTxIndices.set(nodeId, [...edgeTxIndices.get(nodeId) ?? [], index]);
|
|
2542
|
+
}
|
|
2543
|
+
}
|
|
2544
|
+
const ranks = /* @__PURE__ */ new Map();
|
|
2545
|
+
for (const node of graph.nodes) {
|
|
2546
|
+
const attachedTxs = edgeTxIndices.get(node.id) ?? [0];
|
|
2547
|
+
const attachedTx = Math.min(...attachedTxs);
|
|
2548
|
+
const latestAttachedTx = Math.max(...attachedTxs);
|
|
2549
|
+
switch (node.rawRef.type) {
|
|
2550
|
+
case "transaction": {
|
|
2551
|
+
ranks.set(
|
|
2552
|
+
node.id,
|
|
2553
|
+
(txIndex.get(node.rawRef.txHash) ?? attachedTx) * 2 + 1
|
|
2554
|
+
);
|
|
2555
|
+
break;
|
|
2556
|
+
}
|
|
2557
|
+
case "utxo": {
|
|
2558
|
+
const producer = txIndex.get(node.rawRef.outRef.txHash);
|
|
2559
|
+
ranks.set(
|
|
2560
|
+
node.id,
|
|
2561
|
+
producer === void 0 ? attachedTx * 2 : producer * 2 + 2
|
|
2562
|
+
);
|
|
2563
|
+
break;
|
|
2564
|
+
}
|
|
2565
|
+
case "assetPolicy":
|
|
2566
|
+
ranks.set(node.id, latestAttachedTx * 2 + 2);
|
|
2567
|
+
break;
|
|
2568
|
+
case "certificate":
|
|
2569
|
+
case "collateralReturn":
|
|
2570
|
+
ranks.set(node.id, attachedTx * 2 + 2);
|
|
2571
|
+
break;
|
|
2572
|
+
case "signer":
|
|
2573
|
+
case "withdrawal":
|
|
2574
|
+
ranks.set(node.id, attachedTx * 2);
|
|
2575
|
+
break;
|
|
2576
|
+
case "diagnostic":
|
|
2577
|
+
ranks.set(node.id, attachedTx * 2 + 1);
|
|
2578
|
+
break;
|
|
2579
|
+
}
|
|
2580
|
+
}
|
|
2581
|
+
return ranks;
|
|
2582
|
+
};
|
|
2583
|
+
var nodeSortKey = (node, graph) => {
|
|
2584
|
+
const attached = graph.edges.filter((edge) => edge.from === node.id || edge.to === node.id).map((edge) => targetRefSortIndex(edge.targetRef));
|
|
2585
|
+
const attachedIndex = attached.length === 0 ? 0 : Math.min(...attached);
|
|
2586
|
+
return [
|
|
2587
|
+
String(kindPriority(node)).padStart(2, "0"),
|
|
2588
|
+
String(attachedIndex).padStart(4, "0"),
|
|
2589
|
+
rawRefSortKey(node),
|
|
2590
|
+
node.id
|
|
2591
|
+
].join(":");
|
|
2592
|
+
};
|
|
2593
|
+
var targetRefSortIndex = (targetRef) => {
|
|
2594
|
+
switch (targetRef.type) {
|
|
2595
|
+
case "input":
|
|
2596
|
+
case "output":
|
|
2597
|
+
case "withdrawal":
|
|
2598
|
+
case "certificate":
|
|
2599
|
+
return targetRef.index;
|
|
2600
|
+
case "assetPolicy":
|
|
2601
|
+
return targetRef.policyIndex;
|
|
2602
|
+
case "signer":
|
|
2603
|
+
case "collateralReturn":
|
|
2604
|
+
return 0;
|
|
2605
|
+
case "diagnostic":
|
|
2606
|
+
return targetRef.count ?? 0;
|
|
2607
|
+
}
|
|
2608
|
+
};
|
|
2609
|
+
var kindPriority = (node) => {
|
|
2610
|
+
switch (node.kind) {
|
|
2611
|
+
case "diagnostic":
|
|
2612
|
+
return 0;
|
|
2613
|
+
case "withdrawal":
|
|
2614
|
+
return 1;
|
|
2615
|
+
case "signer":
|
|
2616
|
+
return 2;
|
|
2617
|
+
case "transaction":
|
|
2618
|
+
return 3;
|
|
2619
|
+
case "utxo":
|
|
2620
|
+
return 4;
|
|
2621
|
+
case "asset":
|
|
2622
|
+
return 5;
|
|
2623
|
+
case "certificate":
|
|
2624
|
+
return 6;
|
|
2625
|
+
case "collateralReturn":
|
|
2626
|
+
return 7;
|
|
2627
|
+
}
|
|
2628
|
+
};
|
|
2629
|
+
var rawRefSortKey = (node) => {
|
|
2630
|
+
switch (node.rawRef.type) {
|
|
2631
|
+
case "utxo":
|
|
2632
|
+
return outRefKey(node.rawRef.outRef);
|
|
2633
|
+
case "transaction":
|
|
2634
|
+
return node.rawRef.txHash;
|
|
2635
|
+
case "assetPolicy":
|
|
2636
|
+
return node.rawRef.policyId;
|
|
2637
|
+
case "signer":
|
|
2638
|
+
return node.rawRef.keyHash;
|
|
2639
|
+
case "withdrawal":
|
|
2640
|
+
return node.rawRef.rewardAddress;
|
|
2641
|
+
case "certificate":
|
|
2642
|
+
return `${node.rawRef.txHash}#${node.rawRef.index}`;
|
|
2643
|
+
case "collateralReturn":
|
|
2644
|
+
return node.rawRef.txHash;
|
|
2645
|
+
case "diagnostic":
|
|
2646
|
+
return node.rawRef.code;
|
|
2647
|
+
}
|
|
2648
|
+
};
|
|
2649
|
+
var placeEdgeLabels = (trace, formatted, options, graph) => {
|
|
2650
|
+
const nodeBoxes = [...graph.nodes.values()].map(
|
|
2651
|
+
(node) => inflate(nodeBox(node), LABEL_GAP)
|
|
2652
|
+
);
|
|
2653
|
+
const placed = [];
|
|
2654
|
+
const result = /* @__PURE__ */ new Map();
|
|
2655
|
+
for (const edge of formatted.edges) {
|
|
2656
|
+
const text = visibleEdgeLabel(trace, edge, options);
|
|
2657
|
+
if (!text) continue;
|
|
2658
|
+
const labelText = fit(text, options.mode === "debug" ? 42 : 28);
|
|
2659
|
+
const size = measureLabel(labelText);
|
|
2660
|
+
const origin = edgeLabelPoint(edge, graph);
|
|
2661
|
+
const candidates = labelCandidates(origin, size);
|
|
2662
|
+
const box = candidates.find(
|
|
2663
|
+
(candidate) => !nodeBoxes.some((node) => intersects(candidate, node)) && !placed.some(
|
|
2664
|
+
(label2) => intersects(candidate, inflate(label2, LABEL_GAP))
|
|
2665
|
+
)
|
|
2666
|
+
);
|
|
2667
|
+
if (!box) continue;
|
|
2668
|
+
const label = { ...box, edgeId: edge.id, text: labelText };
|
|
2669
|
+
placed.push(label);
|
|
2670
|
+
result.set(edge.id, label);
|
|
2671
|
+
}
|
|
2672
|
+
return result;
|
|
2673
|
+
};
|
|
2674
|
+
var labelCandidates = (origin, size) => {
|
|
2675
|
+
const x = origin.x - size.width / 2;
|
|
2676
|
+
const y = origin.y - size.height / 2;
|
|
2677
|
+
const offsets = [
|
|
2678
|
+
[0, -28],
|
|
2679
|
+
[0, 28],
|
|
2680
|
+
[0, -56],
|
|
2681
|
+
[0, 56],
|
|
2682
|
+
[-44, -28],
|
|
2683
|
+
[44, -28],
|
|
2684
|
+
[-44, 28],
|
|
2685
|
+
[44, 28],
|
|
2686
|
+
[0, -84],
|
|
2687
|
+
[0, 84]
|
|
2688
|
+
];
|
|
2689
|
+
return offsets.map(([dx, dy]) => ({
|
|
2690
|
+
x: x + dx,
|
|
2691
|
+
y: y + dy,
|
|
2692
|
+
width: size.width,
|
|
2693
|
+
height: size.height
|
|
2694
|
+
}));
|
|
2695
|
+
};
|
|
2696
|
+
var portOffsets = (graph, nodes) => {
|
|
2697
|
+
const edgeCountByPort = /* @__PURE__ */ new Map();
|
|
2698
|
+
for (const edge of graph.edges) {
|
|
2699
|
+
for (const portId of [edge.fromPort, edge.toPort]) {
|
|
2700
|
+
if (portId)
|
|
2701
|
+
edgeCountByPort.set(portId, (edgeCountByPort.get(portId) ?? 0) + 1);
|
|
2702
|
+
}
|
|
2703
|
+
}
|
|
2704
|
+
const offsets = /* @__PURE__ */ new Map();
|
|
2705
|
+
for (const layoutNode of nodes.values()) {
|
|
2706
|
+
const ports = layoutNode.node.ports.filter((port2) => edgeCountByPort.has(port2.id)).sort(
|
|
2707
|
+
(left, right) => portSortKey(left, layoutNode.node).localeCompare(
|
|
2708
|
+
portSortKey(right, layoutNode.node)
|
|
2709
|
+
)
|
|
2710
|
+
);
|
|
2711
|
+
const bySide = groupPortsBySide(ports);
|
|
2712
|
+
for (const sidePorts of bySide.values()) {
|
|
2713
|
+
sidePorts.forEach((port2, index) => {
|
|
2714
|
+
if (port2.side === "top" || port2.side === "bottom") {
|
|
2715
|
+
offsets.set(
|
|
2716
|
+
port2.id,
|
|
2717
|
+
(index + 1) * layoutNode.width / (sidePorts.length + 1)
|
|
2718
|
+
);
|
|
2719
|
+
} else {
|
|
2720
|
+
offsets.set(
|
|
2721
|
+
port2.id,
|
|
2722
|
+
60 + (index + 1) * Math.max(24, layoutNode.height - 92) / (sidePorts.length + 1)
|
|
2723
|
+
);
|
|
2724
|
+
}
|
|
2725
|
+
});
|
|
2726
|
+
}
|
|
2727
|
+
}
|
|
2728
|
+
return offsets;
|
|
2729
|
+
};
|
|
2730
|
+
var groupPortsBySide = (ports) => {
|
|
2731
|
+
const groups = /* @__PURE__ */ new Map();
|
|
2732
|
+
for (const port2 of ports) {
|
|
2733
|
+
groups.set(port2.side, [...groups.get(port2.side) ?? [], port2]);
|
|
2734
|
+
}
|
|
2735
|
+
return groups;
|
|
2736
|
+
};
|
|
2737
|
+
var portSortKey = (port2, node) => [
|
|
2738
|
+
String(portSidePriority(port2.side)).padStart(2, "0"),
|
|
2739
|
+
String(edgeKindPriority(port2.edgeKind)).padStart(2, "0"),
|
|
2740
|
+
port2.id.replace(
|
|
2741
|
+
/:(\d+)(?::|$)/g,
|
|
2742
|
+
(_match, index) => `:${index.padStart(4, "0")}:`
|
|
2743
|
+
),
|
|
2744
|
+
node.id
|
|
2745
|
+
].join(":");
|
|
2746
|
+
var portSidePriority = (side) => {
|
|
2747
|
+
switch (side) {
|
|
2748
|
+
case "left":
|
|
2749
|
+
return 0;
|
|
2750
|
+
case "right":
|
|
2751
|
+
return 1;
|
|
2752
|
+
case "top":
|
|
2753
|
+
return 2;
|
|
2754
|
+
case "bottom":
|
|
2755
|
+
return 3;
|
|
2756
|
+
default:
|
|
2757
|
+
return 4;
|
|
2758
|
+
}
|
|
2759
|
+
};
|
|
2760
|
+
var edgeKindPriority = (kind) => {
|
|
2761
|
+
switch (kind) {
|
|
2762
|
+
case "spend":
|
|
2763
|
+
return 0;
|
|
2764
|
+
case "read":
|
|
2765
|
+
return 1;
|
|
2766
|
+
case "collateral":
|
|
2767
|
+
return 2;
|
|
2768
|
+
case "produce":
|
|
2769
|
+
return 3;
|
|
2770
|
+
case "mint":
|
|
2771
|
+
return 4;
|
|
2772
|
+
case "withdraw":
|
|
2773
|
+
return 5;
|
|
2774
|
+
case "certify":
|
|
2775
|
+
return 6;
|
|
2776
|
+
case "sign":
|
|
2777
|
+
return 7;
|
|
2778
|
+
default:
|
|
2779
|
+
return 8;
|
|
2780
|
+
}
|
|
2781
|
+
};
|
|
2782
|
+
var anchor = (edge, endpoint, node, other, graph) => {
|
|
2783
|
+
const portId = endpoint === "from" ? edge.fromPort : edge.toPort;
|
|
2784
|
+
const port2 = portId ? node.node.ports.find((candidate) => candidate.id === portId) : void 0;
|
|
2785
|
+
const offset = portId ? graph.portOffsets.get(portId) : void 0;
|
|
2786
|
+
if (port2?.side === "top") {
|
|
2787
|
+
return { x: node.x + (offset ?? node.width / 2), y: node.y };
|
|
2788
|
+
}
|
|
2789
|
+
if (port2?.side === "bottom") {
|
|
2790
|
+
return { x: node.x + (offset ?? node.width / 2), y: node.y + node.height };
|
|
2791
|
+
}
|
|
2792
|
+
if (port2?.side === "left") {
|
|
2793
|
+
return { x: node.x, y: node.y + (offset ?? node.height / 2) };
|
|
2794
|
+
}
|
|
2795
|
+
if (port2?.side === "right") {
|
|
2796
|
+
return { x: node.x + node.width, y: node.y + (offset ?? node.height / 2) };
|
|
2797
|
+
}
|
|
2798
|
+
const nodeCenterX = node.x + node.width / 2;
|
|
2799
|
+
const otherCenterX = other.x + other.width / 2;
|
|
2800
|
+
return {
|
|
2801
|
+
x: otherCenterX >= nodeCenterX ? node.x + node.width : node.x,
|
|
2802
|
+
y: node.y + node.height / 2
|
|
2803
|
+
};
|
|
2804
|
+
};
|
|
2805
|
+
var inflate = (box, padding) => ({
|
|
2806
|
+
x: box.x - padding,
|
|
2807
|
+
y: box.y - padding,
|
|
2808
|
+
width: box.width + padding * 2,
|
|
2809
|
+
height: box.height + padding * 2
|
|
2810
|
+
});
|
|
2811
|
+
var intersects = (left, right) => left.x < right.x + right.width && left.x + left.width > right.x && left.y < right.y + right.height && left.y + left.height > right.y;
|
|
2812
|
+
var fit = (value, maxChars) => value.length <= maxChars ? value : `${value.slice(0, maxChars - 3)}...`;
|
|
2813
|
+
|
|
2814
|
+
// src/render/edge-renderer.ts
|
|
2815
|
+
var renderEdge = (trace, edge, index, graph, options, semanticNodes, semantic) => {
|
|
2816
|
+
if (!graph.nodes.has(edge.from) || !graph.nodes.has(edge.to)) return "";
|
|
2817
|
+
const path = edgePath(edge, graph);
|
|
2818
|
+
const style = visualEdgeStyle(edge.kind);
|
|
2819
|
+
const scriptFocus = options.view === "scriptInteraction" && isScriptInteractionEdge(edge, semanticNodes);
|
|
2820
|
+
const label = graph.edgeLabels.get(edge.id);
|
|
2821
|
+
return [
|
|
2822
|
+
`<g data-txg-id="${opaqueId(semantic, edge.id)}" data-txg-type="edge" class="txg-edge txg-${edge.kind}">`,
|
|
2823
|
+
`<title>${xmlText(semanticEdgeLabel(trace, edge, options))}</title>`,
|
|
2824
|
+
`<path class="txg-edge-hit" d="${path}" fill="none" stroke="transparent" stroke-width="12" pointer-events="stroke"/>`,
|
|
2825
|
+
`<path d="${path}" fill="none" stroke="${style.color}" stroke-width="${strokeWidth(edge, scriptFocus)}" marker-end="url(#txg-arrow)"${dashArray(style.style)}/>`,
|
|
2826
|
+
label ? renderEdgeLabel(
|
|
2827
|
+
label.x,
|
|
2828
|
+
label.y,
|
|
2829
|
+
label.width,
|
|
2830
|
+
label.height,
|
|
2831
|
+
label.text,
|
|
2832
|
+
style.color
|
|
2833
|
+
) : "",
|
|
2834
|
+
"</g>"
|
|
2835
|
+
].join("\n");
|
|
2836
|
+
};
|
|
2837
|
+
var renderEdgeLabel = (x, y, width, height, label, color) => [
|
|
2838
|
+
`<rect class="txg-edge-label" x="${x}" y="${y}" width="${width}" height="${height}" rx="5" fill="#ffffff" stroke="#cbd5e1"/>`,
|
|
2839
|
+
`<text x="${x + width / 2}" y="${y + 14}" text-anchor="middle" font-family="Inter, Arial" font-size="10" font-weight="700" fill="${color}">${xmlText(label)}</text>`
|
|
2840
|
+
].join("\n");
|
|
2841
|
+
var strokeWidth = (edge, scriptFocus) => {
|
|
2842
|
+
if (scriptFocus) return "2.8";
|
|
2843
|
+
if (edge.kind === "spend") return "2.2";
|
|
2844
|
+
if (edge.kind === "produce") return "1.9";
|
|
2845
|
+
if (edge.kind === "diagnostic") return "2";
|
|
2846
|
+
return "1.35";
|
|
2847
|
+
};
|
|
2848
|
+
var dashArray = (style) => style === "solid" ? "" : style === "dotted" ? ' stroke-dasharray="2 4"' : ' stroke-dasharray="6 5"';
|
|
2849
|
+
var opaqueId = (graph, id) => {
|
|
2850
|
+
const nodeIndex = graph.nodes.findIndex((node) => node.id === id);
|
|
2851
|
+
if (nodeIndex >= 0) return `node:${nodeIndex}`;
|
|
2852
|
+
const edgeIndex = graph.edges.findIndex((edge) => edge.id === id);
|
|
2853
|
+
return edgeIndex >= 0 ? `edge:${edgeIndex}` : id;
|
|
2854
|
+
};
|
|
2855
|
+
var xmlText = (value) => value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
2856
|
+
|
|
2857
|
+
// src/render/node-renderer.ts
|
|
2858
|
+
var renderNode = (layoutNode, graph, options, index, semantic) => {
|
|
2859
|
+
switch (layoutNode.node.kind) {
|
|
2860
|
+
case "transaction":
|
|
2861
|
+
return renderTransactionNode(layoutNode, graph, options, index, semantic);
|
|
2862
|
+
case "utxo":
|
|
2863
|
+
return renderUtxoNode(layoutNode, graph, options, index, semantic);
|
|
2864
|
+
default:
|
|
2865
|
+
return renderGenericNode(layoutNode, graph, options, index, semantic);
|
|
2866
|
+
}
|
|
2867
|
+
};
|
|
2868
|
+
var renderLegend = (graph, semantic) => {
|
|
2869
|
+
const y = graph.height - LEGEND_HEIGHT + 34;
|
|
2870
|
+
const itemWidth = 156;
|
|
2871
|
+
const items = edgeLegendItems(semantic);
|
|
2872
|
+
const perRow = Math.max(1, Math.floor((graph.width - 80) / itemWidth));
|
|
2873
|
+
return [
|
|
2874
|
+
`<g class="txg-legend">`,
|
|
2875
|
+
`<text x="40" y="${y}" font-family="Inter, Arial" font-size="12" font-weight="700" fill="#111827">Legend</text>`,
|
|
2876
|
+
...items.map(([label, title], index) => {
|
|
2877
|
+
const style = visualEdgeStyle(label);
|
|
2878
|
+
const x = 40 + index % perRow * itemWidth;
|
|
2879
|
+
const rowY = y + 24 + Math.floor(index / perRow) * 30;
|
|
2880
|
+
return [
|
|
2881
|
+
`<g>`,
|
|
2882
|
+
`<title>${xmlText2(title)}</title>`,
|
|
2883
|
+
`<line x1="${x}" y1="${rowY}" x2="${x + 30}" y2="${rowY}" stroke="${style.color}" stroke-width="2.2"${dashArray2(style.style)}/>`,
|
|
2884
|
+
`<text x="${x + 38}" y="${rowY + 4}" font-family="Inter, Arial" font-size="11" fill="#374151">${label}</text>`,
|
|
2885
|
+
`</g>`
|
|
2886
|
+
].join("\n");
|
|
2887
|
+
}),
|
|
2888
|
+
"</g>"
|
|
2889
|
+
].join("\n");
|
|
2890
|
+
};
|
|
2891
|
+
var edgeLegendItems = (semantic) => {
|
|
2892
|
+
const present = new Set(semantic.edges.map((edge) => edge.kind));
|
|
2893
|
+
const ordered = [
|
|
2894
|
+
"spend",
|
|
2895
|
+
"read",
|
|
2896
|
+
"collateral",
|
|
2897
|
+
"produce",
|
|
2898
|
+
"mint",
|
|
2899
|
+
"burn",
|
|
2900
|
+
"withdraw",
|
|
2901
|
+
"certify",
|
|
2902
|
+
"sign",
|
|
2903
|
+
"collateralReturn",
|
|
2904
|
+
"diagnostic",
|
|
2905
|
+
"summary"
|
|
2906
|
+
].filter((kind) => present.has(kind));
|
|
2907
|
+
return (ordered.length > 0 ? ordered : ["spend", "produce"]).map((kind) => [
|
|
2908
|
+
kind,
|
|
2909
|
+
edgeLegendTitle(kind)
|
|
2910
|
+
]);
|
|
2911
|
+
};
|
|
2912
|
+
var edgeLegendTitle = (kind) => {
|
|
2913
|
+
switch (kind) {
|
|
2914
|
+
case "spend":
|
|
2915
|
+
return "Consumes an input UTxO";
|
|
2916
|
+
case "read":
|
|
2917
|
+
return "Reads a reference input without consuming it";
|
|
2918
|
+
case "collateral":
|
|
2919
|
+
return "Uses collateral for script validation";
|
|
2920
|
+
case "produce":
|
|
2921
|
+
return "Creates an output UTxO";
|
|
2922
|
+
case "mint":
|
|
2923
|
+
return "Mints assets under a policy";
|
|
2924
|
+
case "burn":
|
|
2925
|
+
return "Burns assets under a policy";
|
|
2926
|
+
case "withdraw":
|
|
2927
|
+
return "Withdraws staking rewards";
|
|
2928
|
+
case "certify":
|
|
2929
|
+
return "Applies a certificate";
|
|
2930
|
+
case "sign":
|
|
2931
|
+
return "Requires a signer";
|
|
2932
|
+
case "collateralReturn":
|
|
2933
|
+
return "Returns collateral";
|
|
2934
|
+
case "diagnostic":
|
|
2935
|
+
return "Warning or error";
|
|
2936
|
+
default:
|
|
2937
|
+
return kind;
|
|
2938
|
+
}
|
|
2939
|
+
};
|
|
2940
|
+
var dashArray2 = (style) => style === "solid" ? "" : style === "dotted" ? ' stroke-dasharray="2 4"' : ' stroke-dasharray="6 5"';
|
|
2941
|
+
var renderTransactionNode = (layoutNode, graph, options, index, semantic) => {
|
|
2942
|
+
const { node, width, height } = layoutNode;
|
|
2943
|
+
const x = layoutNode.x + graph.offsetX;
|
|
2944
|
+
const y = layoutNode.y + graph.offsetY;
|
|
2945
|
+
const palette = nodePalette2(node);
|
|
2946
|
+
const title = fit2(semanticNodeTitle(node, options), 38);
|
|
2947
|
+
const subtitle = semanticNodeSubtitle(node, options);
|
|
2948
|
+
const status = node.chips[0]?.label ?? "tx";
|
|
2949
|
+
const metrics = transactionMetrics(node, options);
|
|
2950
|
+
const facts = sectionRows2(node, options, "facts").slice(0, 4);
|
|
2951
|
+
const redeemers = sectionRows2(node, options, "redeemers").slice(0, 1);
|
|
2952
|
+
let cursor = y + 64;
|
|
2953
|
+
const lines = baseNodeOpen(
|
|
2954
|
+
index,
|
|
2955
|
+
node,
|
|
2956
|
+
semantic,
|
|
2957
|
+
options,
|
|
2958
|
+
x,
|
|
2959
|
+
y,
|
|
2960
|
+
width,
|
|
2961
|
+
height,
|
|
2962
|
+
palette
|
|
2963
|
+
);
|
|
2964
|
+
lines.push(
|
|
2965
|
+
`<rect x="${x}" y="${y}" width="${width}" height="54" rx="8" fill="${palette.header}"/>`,
|
|
2966
|
+
`<text x="${x + 14}" y="${y + 21}" font-family="Inter, Arial" font-size="14" font-weight="700" fill="#0f172a">${xmlText2(title)}</text>`,
|
|
2967
|
+
`<text x="${x + 14}" y="${y + 40}" font-family="ui-monospace, SFMono-Regular, monospace" font-size="10" fill="#475569">${xmlText2(fit2(subtitle ?? "", 36))}</text>`,
|
|
2968
|
+
`<rect x="${x + width - 88}" y="${y + 13}" width="72" height="24" rx="12" fill="${statusFill(node)}" stroke="${statusStroke(node)}"/>`,
|
|
2969
|
+
`<text x="${x + width - 52}" y="${y + 29}" text-anchor="middle" font-family="Inter, Arial" font-size="10" font-weight="700" fill="${statusText(node)}">${xmlText2(status)}</text>`
|
|
2970
|
+
);
|
|
2971
|
+
if (metrics.length > 0) {
|
|
2972
|
+
lines.push(`<g class="txg-metrics">`);
|
|
2973
|
+
metrics.forEach((metric, metricIndex) => {
|
|
2974
|
+
const column = metricIndex % 4;
|
|
2975
|
+
const row = Math.floor(metricIndex / 4);
|
|
2976
|
+
const boxX = x + 14 + column * 74;
|
|
2977
|
+
const boxY = cursor + row * 34;
|
|
2978
|
+
lines.push(
|
|
2979
|
+
`<rect x="${boxX}" y="${boxY}" width="66" height="27" rx="6" fill="#f8fafc" stroke="#e5e7eb"/>`,
|
|
2980
|
+
`<text x="${boxX + 8}" y="${boxY + 11}" font-family="Inter, Arial" font-size="8" font-weight="700" fill="#64748b">${xmlText2(fit2(metric.label, 8))}</text>`,
|
|
2981
|
+
`<text x="${boxX + 8}" y="${boxY + 23}" font-family="Inter, Arial" font-size="12" font-weight="700" fill="#111827">${xmlText2(metric.value)}</text>`
|
|
2982
|
+
);
|
|
2983
|
+
});
|
|
2984
|
+
lines.push(`</g>`);
|
|
2985
|
+
cursor += Math.ceil(metrics.length / 4) * 34 + 8;
|
|
2986
|
+
}
|
|
2987
|
+
renderRows(lines, x, cursor, width, "Facts", [...facts, ...redeemers]);
|
|
2988
|
+
lines.push("</g>");
|
|
2989
|
+
return lines.join("\n");
|
|
2990
|
+
};
|
|
2991
|
+
var renderUtxoNode = (layoutNode, graph, options, index, semantic) => {
|
|
2992
|
+
const { node, width, height } = layoutNode;
|
|
2993
|
+
const x = layoutNode.x + graph.offsetX;
|
|
2994
|
+
const y = layoutNode.y + graph.offsetY;
|
|
2995
|
+
const palette = nodePalette2(node);
|
|
2996
|
+
const title = fit2(semanticNodeTitle(node, options), 34);
|
|
2997
|
+
const subtitle = semanticNodeSubtitle(node, options);
|
|
2998
|
+
const chips = usefulChips(node, options);
|
|
2999
|
+
const assetRows = sectionRows2(node, options, "assets");
|
|
3000
|
+
const stateRows = sectionRows2(node, options, "state");
|
|
3001
|
+
const ownerRows = sectionRows2(node, options, "owner");
|
|
3002
|
+
let cursor = y + 62;
|
|
3003
|
+
const lines = baseNodeOpen(
|
|
3004
|
+
index,
|
|
3005
|
+
node,
|
|
3006
|
+
semantic,
|
|
3007
|
+
options,
|
|
3008
|
+
x,
|
|
3009
|
+
y,
|
|
3010
|
+
width,
|
|
3011
|
+
height,
|
|
3012
|
+
palette
|
|
3013
|
+
);
|
|
3014
|
+
lines.push(
|
|
3015
|
+
`<rect x="${x}" y="${y}" width="${width}" height="52" rx="8" fill="${palette.header}"/>`,
|
|
3016
|
+
`<text x="${x + 14}" y="${y + 21}" font-family="Inter, Arial" font-size="13" font-weight="700" fill="#0f172a">${xmlText2(title)}</text>`,
|
|
3017
|
+
`<text x="${x + 14}" y="${y + 39}" font-family="ui-monospace, SFMono-Regular, monospace" font-size="10" fill="#475569">${xmlText2(fit2(subtitle ?? "", 38))}</text>`
|
|
3018
|
+
);
|
|
3019
|
+
if (chips.length > 0) {
|
|
3020
|
+
cursor = renderChips(lines, x + 14, cursor, chips, width - 28) + 8;
|
|
3021
|
+
}
|
|
3022
|
+
cursor = renderRows(lines, x, cursor, width, "Value", assetRows);
|
|
3023
|
+
if (stateRows.length > 0) {
|
|
3024
|
+
cursor = renderRows(lines, x, cursor + 2, width, "State", stateRows);
|
|
3025
|
+
}
|
|
3026
|
+
if (options.mode !== "overview" && ownerRows.length > 0) {
|
|
3027
|
+
cursor = renderRows(lines, x, cursor + 2, width, "Owner", ownerRows);
|
|
3028
|
+
}
|
|
3029
|
+
lines.push("</g>");
|
|
3030
|
+
return lines.join("\n");
|
|
3031
|
+
};
|
|
3032
|
+
var renderGenericNode = (layoutNode, graph, options, index, semantic) => {
|
|
3033
|
+
const { node, width, height } = layoutNode;
|
|
3034
|
+
const x = layoutNode.x + graph.offsetX;
|
|
3035
|
+
const y = layoutNode.y + graph.offsetY;
|
|
3036
|
+
const palette = nodePalette2(node);
|
|
3037
|
+
const title = fit2(semanticNodeTitle(node, options), 34);
|
|
3038
|
+
const subtitle = semanticNodeSubtitle(node, options);
|
|
3039
|
+
const chips = usefulChips(node, options);
|
|
3040
|
+
let cursor = y + 60;
|
|
3041
|
+
const lines = baseNodeOpen(
|
|
3042
|
+
index,
|
|
3043
|
+
node,
|
|
3044
|
+
semantic,
|
|
3045
|
+
options,
|
|
3046
|
+
x,
|
|
3047
|
+
y,
|
|
3048
|
+
width,
|
|
3049
|
+
height,
|
|
3050
|
+
palette
|
|
3051
|
+
);
|
|
3052
|
+
lines.push(
|
|
3053
|
+
`<rect x="${x}" y="${y}" width="${width}" height="50" rx="8" fill="${palette.header}"/>`,
|
|
3054
|
+
`<text x="${x + 14}" y="${y + 21}" font-family="Inter, Arial" font-size="13" font-weight="700" fill="#0f172a">${xmlText2(title)}</text>`
|
|
3055
|
+
);
|
|
3056
|
+
if (subtitle) {
|
|
3057
|
+
lines.push(
|
|
3058
|
+
`<text x="${x + 14}" y="${y + 38}" font-family="ui-monospace, SFMono-Regular, monospace" font-size="10" fill="#475569">${xmlText2(fit2(subtitle, 34))}</text>`
|
|
3059
|
+
);
|
|
3060
|
+
}
|
|
3061
|
+
if (chips.length > 0) {
|
|
3062
|
+
cursor = renderChips(lines, x + 14, cursor, chips, width - 28) + 8;
|
|
3063
|
+
}
|
|
3064
|
+
for (const section2 of visibleSemanticSections(node, options)) {
|
|
3065
|
+
const rows = visibleSemanticRows(section2, node, options).map((row) => ({
|
|
3066
|
+
...row,
|
|
3067
|
+
label: semanticFieldLabel(row, options),
|
|
3068
|
+
value: semanticFieldValue(row, options)
|
|
3069
|
+
}));
|
|
3070
|
+
if (rows.length === 0) continue;
|
|
3071
|
+
cursor = renderRows(lines, x, cursor, width, section2.title, rows);
|
|
3072
|
+
}
|
|
3073
|
+
lines.push("</g>");
|
|
3074
|
+
return lines.join("\n");
|
|
3075
|
+
};
|
|
3076
|
+
var baseNodeOpen = (index, node, semantic, options, x, y, width, height, palette) => [
|
|
3077
|
+
`<g id="txg-node-${index}" data-txg-id="${opaqueId2(semantic, node.id)}" data-txg-type="node" class="txg-node txg-${node.kind}">`,
|
|
3078
|
+
`<title>${xmlText2(nodeTitle(node, options))}</title>`,
|
|
3079
|
+
`<rect x="${x}" y="${y}" width="${width}" height="${height}" rx="8" fill="#ffffff" stroke="${palette.border}" stroke-width="${node.severity ? "1.8" : "1.1"}"/>`
|
|
3080
|
+
];
|
|
3081
|
+
var renderRows = (lines, x, y, width, title, rows) => {
|
|
3082
|
+
if (rows.length === 0) return y;
|
|
3083
|
+
lines.push(
|
|
3084
|
+
`<line x1="${x + 12}" y1="${y - 9}" x2="${x + width - 12}" y2="${y - 9}" stroke="#e5e7eb"/>`,
|
|
3085
|
+
`<text x="${x + 14}" y="${y}" font-family="Inter, Arial" font-size="9" font-weight="800" fill="#475569" letter-spacing="0">${xmlText2(title)}</text>`
|
|
3086
|
+
);
|
|
3087
|
+
let cursor = y + 19;
|
|
3088
|
+
for (const row of rows) {
|
|
3089
|
+
const label = fit2(row.label, 14);
|
|
3090
|
+
const value = fit2(row.value, row.mono ? 26 : 24);
|
|
3091
|
+
lines.push(
|
|
3092
|
+
`<text x="${x + 16}" y="${cursor}" font-family="Inter, Arial" font-size="10" fill="#64748b">${xmlText2(label)}</text>`,
|
|
3093
|
+
`<text x="${x + 118}" y="${cursor}" font-family="${row.mono ? "ui-monospace, SFMono-Regular, monospace" : "Inter, Arial"}" font-size="10" fill="#111827">${xmlText2(value)}</text>`
|
|
3094
|
+
);
|
|
3095
|
+
cursor += 21;
|
|
3096
|
+
}
|
|
3097
|
+
return cursor + 8;
|
|
3098
|
+
};
|
|
3099
|
+
var renderChips = (lines, x, y, chips, maxWidth) => {
|
|
3100
|
+
let cursorX = x;
|
|
3101
|
+
let cursorY = y;
|
|
3102
|
+
for (const chip of chips) {
|
|
3103
|
+
const label = fit2(chip.label, 18);
|
|
3104
|
+
const width = Math.max(42, label.length * 6 + 18);
|
|
3105
|
+
if (cursorX + width > x + maxWidth) {
|
|
3106
|
+
cursorX = x;
|
|
3107
|
+
cursorY += 24;
|
|
3108
|
+
}
|
|
3109
|
+
const palette = chipPalette(chip);
|
|
3110
|
+
lines.push(
|
|
3111
|
+
`<rect x="${cursorX}" y="${cursorY - 13}" width="${width}" height="19" rx="9.5" fill="${palette.fill}" stroke="${palette.stroke}"/>`,
|
|
3112
|
+
`<text x="${cursorX + width / 2}" y="${cursorY}" text-anchor="middle" font-family="Inter, Arial" font-size="9" font-weight="700" fill="${palette.text}">${xmlText2(label)}</text>`
|
|
3113
|
+
);
|
|
3114
|
+
cursorX += width + 6;
|
|
3115
|
+
}
|
|
3116
|
+
return cursorY + 10;
|
|
3117
|
+
};
|
|
3118
|
+
var transactionMetrics = (node, options) => visibleSemanticSections(node, options).filter(
|
|
3119
|
+
(section2) => ["inputs", "assets", "authority", "redeemers"].includes(section2.id)
|
|
3120
|
+
).flatMap(
|
|
3121
|
+
(section2) => visibleSemanticRows(section2, node, options).map((row) => ({
|
|
3122
|
+
...row,
|
|
3123
|
+
label: metricLabel(row.label)
|
|
3124
|
+
}))
|
|
3125
|
+
).filter((row) => row.value !== "0");
|
|
3126
|
+
var sectionRows2 = (node, options, sectionId) => visibleSemanticSections(node, options).find((section2) => section2.id === sectionId)?.rows.map((row) => ({
|
|
3127
|
+
...row,
|
|
3128
|
+
label: semanticFieldLabel(row, options),
|
|
3129
|
+
value: semanticFieldValue(row, options)
|
|
3130
|
+
})) ?? [];
|
|
3131
|
+
var usefulChips = (node, options) => {
|
|
3132
|
+
const labels = new Set(semanticChipLabels(node, options));
|
|
3133
|
+
const chips = node.chips.filter((chip) => {
|
|
3134
|
+
if (node.kind === "utxo" && chip.label === "resolved") return false;
|
|
3135
|
+
return labels.has(chip.label);
|
|
3136
|
+
});
|
|
3137
|
+
if (isGenesisNode(node) && !chips.some((chip) => chip.label === "genesis")) {
|
|
3138
|
+
return [{ label: "genesis", tone: "warning" }, ...chips];
|
|
3139
|
+
}
|
|
3140
|
+
return chips;
|
|
3141
|
+
};
|
|
3142
|
+
var metricLabel = (label) => {
|
|
3143
|
+
switch (label) {
|
|
3144
|
+
case "Mint policies":
|
|
3145
|
+
return "Mints";
|
|
3146
|
+
case "Certificates":
|
|
3147
|
+
return "Certs";
|
|
3148
|
+
case "Withdrawals":
|
|
3149
|
+
return "Wdrls";
|
|
3150
|
+
default:
|
|
3151
|
+
return label;
|
|
3152
|
+
}
|
|
3153
|
+
};
|
|
3154
|
+
var nodeTitle = (node, options) => [
|
|
3155
|
+
semanticNodeTitle(node, options),
|
|
3156
|
+
semanticNodeSubtitle(node, options),
|
|
3157
|
+
...usefulChips(node, options).map((chip) => chip.label)
|
|
3158
|
+
].filter(Boolean).join("\n");
|
|
3159
|
+
var opaqueId2 = (graph, id) => {
|
|
3160
|
+
const nodeIndex = graph.nodes.findIndex((node) => node.id === id);
|
|
3161
|
+
if (nodeIndex >= 0) return `node:${nodeIndex}`;
|
|
3162
|
+
const edgeIndex = graph.edges.findIndex((edge) => edge.id === id);
|
|
3163
|
+
return edgeIndex >= 0 ? `edge:${edgeIndex}` : id;
|
|
3164
|
+
};
|
|
3165
|
+
var nodePalette2 = (node) => {
|
|
3166
|
+
if (node.severity === "error")
|
|
3167
|
+
return { header: "#fee2e2", border: "#dc2626" };
|
|
3168
|
+
if (isGenesisNode(node)) return { header: "#fef3c7", border: "#d97706" };
|
|
3169
|
+
switch (node.kind) {
|
|
3170
|
+
case "transaction":
|
|
3171
|
+
return { header: "#eef2ff", border: "#6366f1" };
|
|
3172
|
+
case "utxo":
|
|
3173
|
+
return { header: "#ecfdf5", border: "#10b981" };
|
|
3174
|
+
case "asset":
|
|
3175
|
+
return { header: "#f5f3ff", border: "#7c3aed" };
|
|
3176
|
+
case "withdrawal":
|
|
3177
|
+
return { header: "#ecfeff", border: "#0891b2" };
|
|
3178
|
+
case "certificate":
|
|
3179
|
+
return { header: "#eef2ff", border: "#4f46e5" };
|
|
3180
|
+
case "collateralReturn":
|
|
3181
|
+
return { header: "#fffbeb", border: "#f59e0b" };
|
|
3182
|
+
case "signer":
|
|
3183
|
+
return { header: "#f8fafc", border: "#94a3b8" };
|
|
3184
|
+
case "diagnostic":
|
|
3185
|
+
return { header: "#fef2f2", border: "#dc2626" };
|
|
3186
|
+
}
|
|
3187
|
+
};
|
|
3188
|
+
var chipPalette = (chip) => {
|
|
3189
|
+
switch (chip.tone) {
|
|
3190
|
+
case "danger":
|
|
3191
|
+
return { fill: "#fee2e2", stroke: "#fca5a5", text: "#991b1b" };
|
|
3192
|
+
case "warning":
|
|
3193
|
+
return { fill: "#fef3c7", stroke: "#fbbf24", text: "#92400e" };
|
|
3194
|
+
case "success":
|
|
3195
|
+
return { fill: "#dcfce7", stroke: "#86efac", text: "#166534" };
|
|
3196
|
+
case "accent":
|
|
3197
|
+
return { fill: "#ede9fe", stroke: "#c4b5fd", text: "#5b21b6" };
|
|
3198
|
+
case "info":
|
|
3199
|
+
return { fill: "#dbeafe", stroke: "#93c5fd", text: "#1e40af" };
|
|
3200
|
+
default:
|
|
3201
|
+
return { fill: "#f8fafc", stroke: "#cbd5e1", text: "#334155" };
|
|
3202
|
+
}
|
|
3203
|
+
};
|
|
3204
|
+
var statusFill = (node) => node.severity === "error" ? "#fee2e2" : "#ffffff";
|
|
3205
|
+
var statusStroke = (node) => node.severity === "error" ? "#fca5a5" : "#c7d2fe";
|
|
3206
|
+
var statusText = (node) => node.severity === "error" ? "#991b1b" : "#3730a3";
|
|
3207
|
+
var fit2 = (value, maxChars) => value.length <= maxChars ? value : `${value.slice(0, maxChars - 3)}...`;
|
|
3208
|
+
var xmlText2 = (value) => value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
3209
|
+
|
|
3210
|
+
// src/render/svg.ts
|
|
3211
|
+
var MARGIN2 = 40;
|
|
3212
|
+
var traceToSvg = (trace, options = {}) => {
|
|
3213
|
+
const resolved = resolveVisualRendererOptions(options);
|
|
3214
|
+
const semantic = formatSemanticGraph(
|
|
3215
|
+
buildSemanticRenderGraph(trace, { redeemers: options.redeemers }),
|
|
3216
|
+
resolved
|
|
3217
|
+
);
|
|
3218
|
+
const layout = layoutGraph(trace, semantic, resolved);
|
|
3219
|
+
const title = options.title ?? "Lucid Evolution Transaction Graph";
|
|
3220
|
+
const diagnostics = semantic.nodes.filter(
|
|
3221
|
+
(node) => node.kind === "diagnostic"
|
|
3222
|
+
);
|
|
3223
|
+
const semanticNodes = new Map(
|
|
3224
|
+
[...layout.nodes.entries()].map(([id, layoutNode]) => [
|
|
3225
|
+
id,
|
|
3226
|
+
layoutNode.node
|
|
3227
|
+
])
|
|
3228
|
+
);
|
|
3229
|
+
return [
|
|
3230
|
+
`<svg xmlns="http://www.w3.org/2000/svg" role="img" width="${layout.width}" height="${layout.height}" viewBox="0 0 ${layout.width} ${layout.height}">`,
|
|
3231
|
+
`<title>${xmlText3(title)}</title>`,
|
|
3232
|
+
"<defs>",
|
|
3233
|
+
'<marker id="txg-arrow" markerWidth="10" markerHeight="10" refX="9" refY="3" orient="auto" markerUnits="strokeWidth"><path d="M0,0 L0,6 L9,3 z" fill="context-stroke"/></marker>',
|
|
3234
|
+
"</defs>",
|
|
3235
|
+
`<rect width="100%" height="100%" fill="#ffffff"/>`,
|
|
3236
|
+
renderTitle(title, trace, diagnostics.length),
|
|
3237
|
+
...semantic.edges.map(
|
|
3238
|
+
(edge, index) => renderEdge(trace, edge, index, layout, resolved, semanticNodes, semantic)
|
|
3239
|
+
),
|
|
3240
|
+
...[...layout.nodes.values()].sort((left, right) => left.node.id.localeCompare(right.node.id)).map(
|
|
3241
|
+
(node, index) => renderNode(node, layout, resolved, index, semantic)
|
|
3242
|
+
),
|
|
3243
|
+
renderLegend(layout, semantic),
|
|
3244
|
+
"</svg>"
|
|
3245
|
+
].join("\n");
|
|
3246
|
+
};
|
|
3247
|
+
var semanticSvgForTesting = (trace, options = {}) => {
|
|
3248
|
+
const resolved = resolveVisualRendererOptions(options);
|
|
3249
|
+
const semantic = formatSemanticGraph(
|
|
3250
|
+
buildSemanticRenderGraph(trace, { redeemers: options.redeemers }),
|
|
3251
|
+
resolved
|
|
3252
|
+
);
|
|
3253
|
+
return { semantic, nodes: semantic.nodes };
|
|
3254
|
+
};
|
|
3255
|
+
var renderTitle = (title, trace, diagnosticCount) => [
|
|
3256
|
+
`<text x="${MARGIN2}" y="34" font-family="Inter, Arial" font-size="20" font-weight="700" fill="#111827">${xmlText3(title)}</text>`,
|
|
3257
|
+
`<text x="${MARGIN2}" y="58" font-family="Inter, Arial" font-size="12" fill="#475569">${trace.transactions.length} transaction${trace.transactions.length === 1 ? "" : "s"} \xB7 ${Object.keys(trace.utxos).length} UTxOs \xB7 ${diagnosticCount} diagnostic${diagnosticCount === 1 ? "" : "s"}</text>`
|
|
3258
|
+
].join("\n");
|
|
3259
|
+
var xmlText3 = (value) => value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
3260
|
+
|
|
3261
|
+
// src/render/html.ts
|
|
3262
|
+
var traceToHtml = (trace, options = {}) => {
|
|
3263
|
+
const resolved = resolveVisualRendererOptions(options);
|
|
3264
|
+
const semantic = formatSemanticGraph(
|
|
3265
|
+
buildSemanticRenderGraph(trace, { redeemers: options.redeemers }),
|
|
3266
|
+
resolved
|
|
3267
|
+
);
|
|
3268
|
+
const title = options.title ?? "Lucid Evolution Transaction Graph";
|
|
3269
|
+
const nodeIds = new Map(
|
|
3270
|
+
semantic.nodes.map((node, index) => [node.id, `node:${index}`])
|
|
3271
|
+
);
|
|
3272
|
+
const metadata = {
|
|
3273
|
+
nodes: semantic.nodes.map(
|
|
3274
|
+
(node, index) => nodeMetadata(node, index, resolved, options.includeRawMetadata ?? false)
|
|
3275
|
+
),
|
|
3276
|
+
edges: semantic.edges.map(
|
|
3277
|
+
(edge, index) => edgeMetadata(
|
|
3278
|
+
trace,
|
|
3279
|
+
edge,
|
|
3280
|
+
index,
|
|
3281
|
+
nodeIds,
|
|
3282
|
+
resolved,
|
|
3283
|
+
options.includeRawMetadata ?? false
|
|
3284
|
+
)
|
|
3285
|
+
)
|
|
3286
|
+
};
|
|
3287
|
+
const filterKinds = [
|
|
3288
|
+
...new Set(semantic.edges.map((edge) => edge.kind))
|
|
3289
|
+
].sort();
|
|
3290
|
+
return [
|
|
3291
|
+
"<!doctype html>",
|
|
3292
|
+
'<html lang="en">',
|
|
3293
|
+
"<head>",
|
|
3294
|
+
'<meta charset="utf-8"/>',
|
|
3295
|
+
'<meta name="viewport" content="width=device-width, initial-scale=1"/>',
|
|
3296
|
+
`<title>${htmlText2(title)}</title>`,
|
|
3297
|
+
`<style>${cssText()}</style>`,
|
|
3298
|
+
"</head>",
|
|
3299
|
+
"<body>",
|
|
3300
|
+
'<main class="txg-shell">',
|
|
3301
|
+
'<aside class="txg-sidebar">',
|
|
3302
|
+
`<h1>${htmlText2(title)}</h1>`,
|
|
3303
|
+
'<input id="txg-search" type="search" placeholder="Search tx, UTxO, asset, action..." autocomplete="off"/>',
|
|
3304
|
+
'<div class="txg-filters">',
|
|
3305
|
+
...filterKinds.map(
|
|
3306
|
+
(kind) => `<label><input type="checkbox" data-kind="${kind}" checked/> ${kind}</label>`
|
|
3307
|
+
),
|
|
3308
|
+
"</div>",
|
|
3309
|
+
`<div class="txg-sidebar-legend"><h2>Legend</h2>${edgeLegendHtml(filterKinds)}</div>`,
|
|
3310
|
+
'<section id="txg-details" class="txg-details">Select a node or edge.</section>',
|
|
3311
|
+
"</aside>",
|
|
3312
|
+
'<section class="txg-canvas">',
|
|
3313
|
+
'<div class="txg-toolbar"><button id="txg-zoom-in">+</button><button id="txg-zoom-out">-</button><button id="txg-fit">Fit</button><button id="txg-reset">Reset</button><label>Inspector <select id="txg-detail-mode"><option value="overview">overview</option><option value="audit" selected>audit</option><option value="debug">debug</option></select></label><button id="txg-copy">Copy JSON</button></div>',
|
|
3314
|
+
`<div id="txg-viewport" class="txg-viewport">${traceToSvg(trace, options)}</div>`,
|
|
3315
|
+
"</section>",
|
|
3316
|
+
"</main>",
|
|
3317
|
+
`<script type="application/json" id="txg-data">${scriptJson(metadata)}</script>`,
|
|
3318
|
+
`<script>${clientScript()}</script>`,
|
|
3319
|
+
"</body>",
|
|
3320
|
+
"</html>"
|
|
3321
|
+
].join("\n");
|
|
3322
|
+
};
|
|
3323
|
+
var nodeMetadata = (node, index, resolved, includeRawMetadata) => ({
|
|
3324
|
+
id: `node:${index}`,
|
|
3325
|
+
graphId: includeRawMetadata ? node.id : void 0,
|
|
3326
|
+
kind: node.kind,
|
|
3327
|
+
title: semanticNodeTitle(node, resolved),
|
|
3328
|
+
subtitle: semanticNodeSubtitle(node, resolved),
|
|
3329
|
+
chips: semanticChipLabels(node, resolved),
|
|
3330
|
+
sections: visibleSemanticSections(node, resolved).map((section2) => ({
|
|
3331
|
+
title: section2.title,
|
|
3332
|
+
rows: visibleSemanticRows(section2, node, resolved).map((row) => ({
|
|
3333
|
+
label: semanticFieldLabel(row, resolved),
|
|
3334
|
+
value: semanticFieldValue(row, resolved)
|
|
3335
|
+
}))
|
|
3336
|
+
})),
|
|
3337
|
+
...includeRawMetadata ? { rawRef: node.rawRef } : {}
|
|
3338
|
+
});
|
|
3339
|
+
var edgeMetadata = (trace, edge, index, nodeIds, resolved, includeRawMetadata) => ({
|
|
3340
|
+
id: `edge:${index}`,
|
|
3341
|
+
graphId: includeRawMetadata ? edge.id : void 0,
|
|
3342
|
+
kind: edge.kind,
|
|
3343
|
+
title: semanticEdgeLabel(trace, edge, resolved),
|
|
3344
|
+
from: includeRawMetadata ? edge.from : nodeIds.get(edge.from),
|
|
3345
|
+
to: includeRawMetadata ? edge.to : nodeIds.get(edge.to),
|
|
3346
|
+
action: edge.action,
|
|
3347
|
+
redeemerKey: edge.redeemerKey,
|
|
3348
|
+
...includeRawMetadata ? { targetRef: edge.targetRef, rawRef: edge.rawRef } : {}
|
|
3349
|
+
});
|
|
3350
|
+
var edgeLegendHtml = (kinds) => kinds.map((kind) => {
|
|
3351
|
+
const style = visualEdgeStyle(kind);
|
|
3352
|
+
return [
|
|
3353
|
+
'<div class="txg-legend-row">',
|
|
3354
|
+
`<svg width="42" height="14" viewBox="0 0 42 14" aria-hidden="true"><line x1="2" y1="7" x2="36" y2="7" stroke="${style.color}" stroke-width="2.4"${htmlDashArray(style.style)}/></svg>`,
|
|
3355
|
+
`<span>${htmlText2(kind)}</span>`,
|
|
3356
|
+
"</div>"
|
|
3357
|
+
].join("");
|
|
3358
|
+
}).join("");
|
|
3359
|
+
var htmlDashArray = (style) => style === "solid" ? "" : style === "dotted" ? ' stroke-dasharray="2 4"' : ' stroke-dasharray="6 5"';
|
|
3360
|
+
var cssText = () => `
|
|
3361
|
+
html, body { height: 100%; }
|
|
3362
|
+
body { margin: 0; overflow: hidden; font-family: Inter, Arial, sans-serif; color: #111827; background: #f8fafc; }
|
|
3363
|
+
.txg-shell { display: grid; grid-template-columns: 320px 1fr; height: 100vh; min-height: 0; }
|
|
3364
|
+
.txg-sidebar { position: sticky; top: 0; max-height: 100vh; border-right: 1px solid #d1d5db; background: #ffffff; padding: 16px; overflow: auto; }
|
|
3365
|
+
.txg-sidebar h1 { font-size: 16px; margin: 0 0 14px; }
|
|
3366
|
+
#txg-search { box-sizing: border-box; width: 100%; padding: 8px 10px; border: 1px solid #cbd5e1; border-radius: 6px; }
|
|
3367
|
+
.txg-filters { display: grid; grid-template-columns: 1fr 1fr; gap: 6px 10px; margin: 14px 0; font-size: 12px; }
|
|
3368
|
+
.txg-sidebar-legend { border-top: 1px solid #e5e7eb; padding-top: 12px; margin-bottom: 12px; }
|
|
3369
|
+
.txg-sidebar-legend h2 { font-size: 12px; margin: 0 0 8px; color: #111827; }
|
|
3370
|
+
.txg-legend-row { display: inline-flex; align-items: center; gap: 6px; min-width: 132px; margin: 0 6px 6px 0; font-size: 11px; color: #374151; }
|
|
3371
|
+
.txg-details { border-top: 1px solid #e5e7eb; padding-top: 12px; font-size: 12px; line-height: 1.45; }
|
|
3372
|
+
.txg-details h2 { font-size: 14px; margin: 0 0 8px; }
|
|
3373
|
+
.txg-details table { border-collapse: collapse; width: 100%; margin: 8px 0; }
|
|
3374
|
+
.txg-details td { border-top: 1px solid #e5e7eb; padding: 4px 0; vertical-align: top; }
|
|
3375
|
+
.txg-details td:first-child { color: #64748b; width: 38%; }
|
|
3376
|
+
.txg-canvas { display: grid; grid-template-rows: auto 1fr; min-width: 0; min-height: 0; overflow: hidden; }
|
|
3377
|
+
.txg-toolbar { display: flex; gap: 8px; padding: 10px; border-bottom: 1px solid #d1d5db; background: #ffffff; }
|
|
3378
|
+
.txg-toolbar button { border: 1px solid #cbd5e1; background: #ffffff; border-radius: 6px; padding: 6px 10px; }
|
|
3379
|
+
.txg-toolbar label { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; color: #475569; }
|
|
3380
|
+
.txg-toolbar select { border: 1px solid #cbd5e1; border-radius: 6px; padding: 5px 8px; background: #ffffff; }
|
|
3381
|
+
.txg-viewport { min-height: 0; overflow: auto; padding: 18px; transform-origin: top left; cursor: grab; }
|
|
3382
|
+
.txg-viewport.txg-panning { cursor: grabbing; user-select: none; }
|
|
3383
|
+
.txg-viewport svg { background: #ffffff; box-shadow: 0 1px 4px rgba(15, 23, 42, 0.12); }
|
|
3384
|
+
.txg-hidden { opacity: 0.08; pointer-events: none; }
|
|
3385
|
+
.txg-match rect:first-of-type { stroke-width: 3px; }
|
|
3386
|
+
.txg-selected rect:first-of-type { stroke-width: 3px; stroke: #0f172a; }
|
|
3387
|
+
.txg-connected path { stroke-width: 3px; }
|
|
3388
|
+
.txg-details pre { white-space: pre-wrap; word-break: break-word; background: #f8fafc; border: 1px solid #e5e7eb; border-radius: 6px; padding: 8px; }
|
|
3389
|
+
@media (max-width: 800px) { body { overflow: auto; } .txg-shell { grid-template-columns: 1fr; height: auto; min-height: 100vh; } .txg-sidebar { position: static; max-height: none; border-right: 0; border-bottom: 1px solid #d1d5db; } .txg-canvas { min-height: 70vh; } }
|
|
3390
|
+
`;
|
|
3391
|
+
var clientScript = () => `
|
|
3392
|
+
const data = JSON.parse(document.getElementById("txg-data").textContent);
|
|
3393
|
+
let selected = null;
|
|
3394
|
+
let zoom = 1;
|
|
3395
|
+
const viewport = document.getElementById("txg-viewport");
|
|
3396
|
+
const svg = viewport.querySelector("svg");
|
|
3397
|
+
const details = document.getElementById("txg-details");
|
|
3398
|
+
const search = document.getElementById("txg-search");
|
|
3399
|
+
const detailMode = document.getElementById("txg-detail-mode");
|
|
3400
|
+
const checks = [...document.querySelectorAll("[data-kind]")];
|
|
3401
|
+
const byId = new Map([...data.nodes, ...data.edges].map((item) => [item.id, item]));
|
|
3402
|
+
const itemElements = new Map([...document.querySelectorAll(".txg-node,.txg-edge")].map((el) => [el.dataset.txgId, el]));
|
|
3403
|
+
|
|
3404
|
+
const setZoom = (nextZoom) => {
|
|
3405
|
+
zoom = Math.max(0.25, Math.min(2.5, nextZoom));
|
|
3406
|
+
viewport.style.zoom = zoom;
|
|
3407
|
+
};
|
|
3408
|
+
|
|
3409
|
+
const itemBox = (item) => {
|
|
3410
|
+
const el = item ? itemElements.get(item.id) : null;
|
|
3411
|
+
if (!el || !el.getBBox) return null;
|
|
3412
|
+
try { return el.getBBox(); } catch (_error) { return null; }
|
|
3413
|
+
};
|
|
3414
|
+
|
|
3415
|
+
const centerBox = (box) => {
|
|
3416
|
+
if (!box) return;
|
|
3417
|
+
viewport.scrollLeft = Math.max(0, (box.x + box.width / 2) * zoom - viewport.clientWidth / 2);
|
|
3418
|
+
viewport.scrollTop = Math.max(0, (box.y + box.height / 2) * zoom - viewport.clientHeight / 2);
|
|
3419
|
+
};
|
|
3420
|
+
|
|
3421
|
+
const primaryTransaction = () =>
|
|
3422
|
+
data.nodes.find((node) => node.kind === "transaction" && node.chips?.includes("failed")) ||
|
|
3423
|
+
[...data.nodes].reverse().find((node) => node.kind === "transaction") ||
|
|
3424
|
+
data.nodes[0];
|
|
3425
|
+
|
|
3426
|
+
const fitToView = (overview = false) => {
|
|
3427
|
+
if (!svg?.viewBox?.baseVal) return;
|
|
3428
|
+
const viewBox = svg.viewBox.baseVal;
|
|
3429
|
+
const widthZoom = (viewport.clientWidth - 36) / Math.max(1, viewBox.width);
|
|
3430
|
+
const readableFloor = viewport.clientWidth < 720 ? 0.55 : 0.72;
|
|
3431
|
+
setZoom(
|
|
3432
|
+
overview
|
|
3433
|
+
? Math.min(1, Math.max(0.35, widthZoom))
|
|
3434
|
+
: Math.min(1, Math.max(readableFloor, widthZoom)),
|
|
3435
|
+
);
|
|
3436
|
+
requestAnimationFrame(() => centerBox(itemBox(primaryTransaction())));
|
|
3437
|
+
};
|
|
3438
|
+
|
|
3439
|
+
const renderDetails = (item) => {
|
|
3440
|
+
selected = item;
|
|
3441
|
+
if (!item) { details.textContent = "Select a node or edge."; return; }
|
|
3442
|
+
const mode = detailMode.value;
|
|
3443
|
+
const summaryRows = [["kind", item.kind], ...(item.subtitle ? [["subtitle", item.subtitle]] : []), ...(item.chips?.length ? [["tags", item.chips.join(", ")]] : [])];
|
|
3444
|
+
const auditRows = item.sections ? item.sections.flatMap((section) => section.rows.map((row) => [section.title + "." + row.label, row.value])) : Object.entries(item).filter(([key]) => !["sections"].includes(key)).map(([key, value]) => [key, typeof value === "string" ? value : JSON.stringify(value)]);
|
|
3445
|
+
if (mode === "debug") {
|
|
3446
|
+
details.innerHTML = "<h2>" + escapeHtml(item.title || item.id) + "</h2><pre>" + escapeHtml(JSON.stringify(item, null, 2)) + "</pre>";
|
|
3447
|
+
return;
|
|
3448
|
+
}
|
|
3449
|
+
const rows = mode === "overview" ? summaryRows : [...summaryRows, ...auditRows];
|
|
3450
|
+
details.innerHTML = "<h2>" + escapeHtml(item.title || item.id) + "</h2>" + "<table>" + rows.map(([key, value]) => "<tr><td>" + escapeHtml(String(key)) + "</td><td>" + escapeHtml(String(value)) + "</td></tr>").join("") + "</table>";
|
|
3451
|
+
};
|
|
3452
|
+
|
|
3453
|
+
const textFor = (item) => JSON.stringify(item).toLowerCase();
|
|
3454
|
+
const applyFilters = () => {
|
|
3455
|
+
const query = search.value.toLowerCase();
|
|
3456
|
+
const enabled = new Set(checks.filter((check) => check.checked).map((check) => check.dataset.kind));
|
|
3457
|
+
const visibleEdgeIds = new Set(data.edges.filter((edge) => enabled.has(edge.kind) && (!query || textFor(edge).includes(query))).map((edge) => edge.id));
|
|
3458
|
+
const matchingNodeIds = new Set(data.nodes.filter((node) => query && textFor(node).includes(query)).map((node) => node.id));
|
|
3459
|
+
document.querySelectorAll(".txg-edge").forEach((edge) => {
|
|
3460
|
+
edge.classList.toggle("txg-hidden", !visibleEdgeIds.has(edge.dataset.txgId));
|
|
3461
|
+
});
|
|
3462
|
+
document.querySelectorAll(".txg-node").forEach((node) => {
|
|
3463
|
+
node.classList.toggle("txg-match", matchingNodeIds.has(node.dataset.txgId));
|
|
3464
|
+
});
|
|
3465
|
+
};
|
|
3466
|
+
|
|
3467
|
+
document.querySelectorAll(".txg-node,.txg-edge").forEach((el) => {
|
|
3468
|
+
el.addEventListener("click", () => {
|
|
3469
|
+
const item = byId.get(el.dataset.txgId) || null;
|
|
3470
|
+
renderDetails(item);
|
|
3471
|
+
document.querySelectorAll(".txg-selected,.txg-connected").forEach((candidate) => candidate.classList.remove("txg-selected", "txg-connected"));
|
|
3472
|
+
el.classList.add("txg-selected");
|
|
3473
|
+
if (item?.from) itemElements.get(item.from)?.classList.add("txg-connected");
|
|
3474
|
+
if (item?.to) itemElements.get(item.to)?.classList.add("txg-connected");
|
|
3475
|
+
if (item?.id?.startsWith("node:")) {
|
|
3476
|
+
data.edges.filter((edge) => edge.from === item.id || edge.to === item.id).forEach((edge) => itemElements.get(edge.id)?.classList.add("txg-connected"));
|
|
3477
|
+
}
|
|
3478
|
+
});
|
|
3479
|
+
});
|
|
3480
|
+
search.addEventListener("input", applyFilters);
|
|
3481
|
+
checks.forEach((check) => check.addEventListener("change", applyFilters));
|
|
3482
|
+
detailMode.addEventListener("change", () => renderDetails(selected));
|
|
3483
|
+
document.getElementById("txg-zoom-in").addEventListener("click", () => setZoom(zoom + 0.1));
|
|
3484
|
+
document.getElementById("txg-zoom-out").addEventListener("click", () => setZoom(zoom - 0.1));
|
|
3485
|
+
document.getElementById("txg-fit").addEventListener("click", () => fitToView(true));
|
|
3486
|
+
document.getElementById("txg-reset").addEventListener("click", () => { search.value = ""; checks.forEach((check) => check.checked = true); applyFilters(); fitToView(false); });
|
|
3487
|
+
document.getElementById("txg-copy").addEventListener("click", async () => { if (selected) await navigator.clipboard.writeText(JSON.stringify(selected, null, 2)); });
|
|
3488
|
+
|
|
3489
|
+
let panStart = null;
|
|
3490
|
+
viewport.addEventListener("pointerdown", (event) => {
|
|
3491
|
+
if (event.target.closest(".txg-node,.txg-edge")) return;
|
|
3492
|
+
panStart = { x: event.clientX, y: event.clientY, left: viewport.scrollLeft, top: viewport.scrollTop };
|
|
3493
|
+
viewport.classList.add("txg-panning");
|
|
3494
|
+
viewport.setPointerCapture(event.pointerId);
|
|
3495
|
+
});
|
|
3496
|
+
viewport.addEventListener("pointermove", (event) => {
|
|
3497
|
+
if (!panStart) return;
|
|
3498
|
+
viewport.scrollLeft = panStart.left - (event.clientX - panStart.x);
|
|
3499
|
+
viewport.scrollTop = panStart.top - (event.clientY - panStart.y);
|
|
3500
|
+
});
|
|
3501
|
+
viewport.addEventListener("pointerup", (event) => {
|
|
3502
|
+
panStart = null;
|
|
3503
|
+
viewport.classList.remove("txg-panning");
|
|
3504
|
+
viewport.releasePointerCapture(event.pointerId);
|
|
3505
|
+
});
|
|
3506
|
+
window.addEventListener("resize", () => fitToView(false));
|
|
3507
|
+
requestAnimationFrame(() => fitToView(false));
|
|
3508
|
+
|
|
3509
|
+
const escapeHtml = (value) => value.replace(/[&<>"]/g, (char) => ({ "&": "&", "<": "<", ">": ">", "\\"": """ }[char]));
|
|
3510
|
+
`;
|
|
3511
|
+
var htmlText2 = (value) => value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
3512
|
+
var scriptJson = (value) => JSON.stringify(value).replace(/</g, "\\u003c").replace(/>/g, "\\u003e").replace(/&/g, "\\u0026").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
|
|
3513
|
+
|
|
3514
|
+
// src/render/mermaid.ts
|
|
3515
|
+
var traceToMermaid = (trace, options = {}) => {
|
|
3516
|
+
if (options.legacy) return traceToLegacyMermaid(trace, options);
|
|
3517
|
+
const resolved = resolveVisualRendererOptions(options);
|
|
3518
|
+
const graph = formatSemanticGraph(
|
|
3519
|
+
buildSemanticRenderGraph(trace, {
|
|
3520
|
+
redeemers: options.redeemers
|
|
3521
|
+
}),
|
|
3522
|
+
resolved
|
|
3523
|
+
);
|
|
3524
|
+
const nodeIds = new Map(
|
|
3525
|
+
graph.nodes.map((node, index) => [node.id, `n${index}`])
|
|
3526
|
+
);
|
|
3527
|
+
const semanticNodes = new Map(
|
|
3528
|
+
graph.nodes.map((node) => [node.id, node])
|
|
3529
|
+
);
|
|
3530
|
+
const lines = [
|
|
3531
|
+
`flowchart ${options.direction ?? "LR"}`,
|
|
3532
|
+
" classDef tx fill:#eef2ff,stroke:#6366f1,color:#111827;",
|
|
3533
|
+
" classDef utxo fill:#ecfdf5,stroke:#10b981,color:#111827;",
|
|
3534
|
+
" classDef asset fill:#f5f3ff,stroke:#7c3aed,color:#111827;",
|
|
3535
|
+
" classDef authority fill:#f3f4f6,stroke:#6b7280,color:#111827;",
|
|
3536
|
+
" classDef governance fill:#eef2ff,stroke:#4f46e5,color:#111827;",
|
|
3537
|
+
" classDef collateral fill:#fffbeb,stroke:#f59e0b,color:#111827;",
|
|
3538
|
+
" classDef unresolved fill:#fee2e2,stroke:#dc2626,color:#111827;",
|
|
3539
|
+
" classDef genesis fill:#fef3c7,stroke:#d97706,color:#111827;"
|
|
3540
|
+
];
|
|
3541
|
+
const linkStyles = [];
|
|
3542
|
+
for (const node of graph.nodes) {
|
|
3543
|
+
const id = nodeIds.get(node.id);
|
|
3544
|
+
lines.push(` ${id}${semanticNodeShape(node, resolved)}`);
|
|
3545
|
+
lines.push(` class ${id} ${semanticNodeClass(node)};`);
|
|
3546
|
+
}
|
|
3547
|
+
for (const [index, edge] of graph.edges.entries()) {
|
|
3548
|
+
const style = visualEdgeStyle(edge.kind);
|
|
3549
|
+
const scriptFocus = resolved.view === "scriptInteraction" && isScriptInteractionEdge(edge, semanticNodes);
|
|
3550
|
+
lines.push(
|
|
3551
|
+
` ${nodeIds.get(edge.from)} -->|${mermaidText(
|
|
3552
|
+
semanticEdgeLabel(trace, edge, resolved)
|
|
3553
|
+
)}| ${nodeIds.get(edge.to)}`
|
|
3554
|
+
);
|
|
3555
|
+
linkStyles.push(
|
|
3556
|
+
` linkStyle ${index} stroke:${style.color},color:${style.color}${scriptFocus ? ",stroke-width:3px" : ""}${style.style === "solid" ? "" : ",stroke-dasharray: 5 5"};`
|
|
3557
|
+
);
|
|
3558
|
+
}
|
|
3559
|
+
return `${[...lines, ...linkStyles].join("\n")}
|
|
3560
|
+
`;
|
|
3561
|
+
};
|
|
3562
|
+
var semanticNodeShape = (node, options) => {
|
|
3563
|
+
const label = mermaidText(semanticNodeLabel2(node, options));
|
|
3564
|
+
switch (node.kind) {
|
|
3565
|
+
case "transaction":
|
|
3566
|
+
return `["${label}"]`;
|
|
3567
|
+
case "utxo":
|
|
3568
|
+
return node.severity === "error" ? `{{"${label}"}}` : `("${label}")`;
|
|
3569
|
+
case "asset":
|
|
3570
|
+
return `(["${label}"])`;
|
|
3571
|
+
case "signer":
|
|
3572
|
+
return `{{"${label}"}}`;
|
|
3573
|
+
case "withdrawal":
|
|
3574
|
+
case "certificate":
|
|
3575
|
+
case "collateralReturn":
|
|
3576
|
+
case "diagnostic":
|
|
3577
|
+
return `["${label}"]`;
|
|
3578
|
+
}
|
|
3579
|
+
};
|
|
3580
|
+
var semanticNodeLabel2 = (node, options) => {
|
|
3581
|
+
const subtitle = semanticNodeSubtitle(node, options);
|
|
3582
|
+
const chipLabels = semanticChipLabels(node, options);
|
|
3583
|
+
const chips = chipLabels.length > 0 ? chipLabels.join(" | ") : void 0;
|
|
3584
|
+
return [
|
|
3585
|
+
semanticNodeTitle(node, options),
|
|
3586
|
+
...subtitle ? [subtitle] : [],
|
|
3587
|
+
...chips ? [chips] : [],
|
|
3588
|
+
...visibleSemanticSections(node, options).map((section2) => mermaidSectionLine(section2, node, options)).filter((line) => Boolean(line))
|
|
3589
|
+
].join("<br/>");
|
|
3590
|
+
};
|
|
3591
|
+
var mermaidSectionLine = (section2, node, options) => {
|
|
3592
|
+
const rows = visibleSemanticRows(section2, node, options);
|
|
3593
|
+
if (rows.length === 0) return void 0;
|
|
3594
|
+
return `${section2.title}: ${rows.map((row) => mermaidRowText(section2, row, options)).join(", ")}`;
|
|
3595
|
+
};
|
|
3596
|
+
var mermaidRowText = (section2, row, options) => {
|
|
3597
|
+
const value = semanticFieldValue(row, options);
|
|
3598
|
+
return section2.id === "assets" && row.id.startsWith("asset:") ? `${value} ${semanticFieldLabel(row, options)}` : `${semanticFieldLabel(row, options)} ${value}`;
|
|
3599
|
+
};
|
|
3600
|
+
var semanticNodeClass = (node) => {
|
|
3601
|
+
if (node.severity === "error") return "unresolved";
|
|
3602
|
+
if (isGenesisNode(node)) return "genesis";
|
|
3603
|
+
if (node.rawRef.type === "diagnostic" && node.rawRef.code === "collapsed") {
|
|
3604
|
+
return "authority";
|
|
3605
|
+
}
|
|
3606
|
+
switch (node.kind) {
|
|
3607
|
+
case "transaction":
|
|
3608
|
+
return "tx";
|
|
3609
|
+
case "utxo":
|
|
3610
|
+
return "utxo";
|
|
3611
|
+
case "asset":
|
|
3612
|
+
return "asset";
|
|
3613
|
+
case "withdrawal":
|
|
3614
|
+
case "signer":
|
|
3615
|
+
return "authority";
|
|
3616
|
+
case "certificate":
|
|
3617
|
+
return "governance";
|
|
3618
|
+
case "collateralReturn":
|
|
3619
|
+
return "collateral";
|
|
3620
|
+
case "diagnostic":
|
|
3621
|
+
return "unresolved";
|
|
3622
|
+
}
|
|
3623
|
+
};
|
|
3624
|
+
var traceToLegacyMermaid = (trace, options = {}) => {
|
|
3625
|
+
const graph = buildRenderGraph(trace);
|
|
3626
|
+
const nodeIds = new Map(
|
|
3627
|
+
graph.nodes.map((node, index) => [node.id, `n${index}`])
|
|
3628
|
+
);
|
|
3629
|
+
const lines = [
|
|
3630
|
+
`flowchart ${options.direction ?? "LR"}`,
|
|
3631
|
+
" classDef tx fill:#eef2ff,stroke:#6366f1,color:#111827;",
|
|
3632
|
+
" classDef utxo fill:#ecfdf5,stroke:#10b981,color:#111827;",
|
|
3633
|
+
" classDef unresolved fill:#fee2e2,stroke:#dc2626,color:#111827;",
|
|
3634
|
+
" classDef genesis fill:#fef3c7,stroke:#d97706,color:#111827;",
|
|
3635
|
+
" classDef external fill:#f9fafb,stroke:#9ca3af,color:#111827;"
|
|
3636
|
+
];
|
|
3637
|
+
for (const node of graph.nodes) {
|
|
3638
|
+
lines.push(` ${nodeIds.get(node.id)}${legacyNodeShape2(node)}`);
|
|
3639
|
+
lines.push(
|
|
3640
|
+
` class ${nodeIds.get(node.id)} ${node.unresolved ? "unresolved" : node.genesis ? "genesis" : legacyNodeClass(node)};`
|
|
3641
|
+
);
|
|
3642
|
+
}
|
|
3643
|
+
for (const edge of graph.edges) {
|
|
3644
|
+
const style = edgeStyle(edge.kind);
|
|
3645
|
+
lines.push(
|
|
3646
|
+
` ${nodeIds.get(edge.from)} -->|${mermaidText(style.label)}| ${nodeIds.get(
|
|
3647
|
+
edge.to
|
|
3648
|
+
)}`
|
|
3649
|
+
);
|
|
3650
|
+
}
|
|
3651
|
+
return `${lines.join("\n")}
|
|
3652
|
+
`;
|
|
3653
|
+
};
|
|
3654
|
+
var legacyNodeShape2 = (node) => {
|
|
3655
|
+
const label = mermaidText(node.label);
|
|
3656
|
+
switch (node.kind) {
|
|
3657
|
+
case "transaction":
|
|
3658
|
+
return `["${label}"]`;
|
|
3659
|
+
case "utxo":
|
|
3660
|
+
return node.unresolved ? `{{"${label}"}}` : `("${label}")`;
|
|
3661
|
+
case "asset":
|
|
3662
|
+
return `(["${label}"])`;
|
|
3663
|
+
case "signer":
|
|
3664
|
+
return `{{"${label}"}}`;
|
|
3665
|
+
case "withdrawal":
|
|
3666
|
+
case "certificate":
|
|
3667
|
+
case "collateralReturn":
|
|
3668
|
+
case "external":
|
|
3669
|
+
return `["${label}"]`;
|
|
3670
|
+
}
|
|
3671
|
+
};
|
|
3672
|
+
var legacyNodeClass = (node) => node.kind === "transaction" ? "tx" : node.kind === "utxo" ? "utxo" : "external";
|
|
3673
|
+
var mermaidText = (value) => value.replace(/\\n/g, "<br/>").replace(/\n/g, "<br/>").replace(/\\/g, "\\\\").replace(/"/g, "'").replace(/\|/g, "/");
|
|
3674
|
+
|
|
3675
|
+
// src/provider-wrapper.ts
|
|
3676
|
+
var wrapProvider = (provider, graph, options = {}) => {
|
|
3677
|
+
graph.resolveWith((outRefs) => provider.getUtxosByOutRef(outRefs));
|
|
3678
|
+
return new Proxy(provider, {
|
|
3679
|
+
get(target, property, receiver) {
|
|
3680
|
+
if (property === "submitTx") {
|
|
3681
|
+
return (tx) => submitTx(target, graph, tx, options);
|
|
3682
|
+
}
|
|
3683
|
+
if (property === "evaluateTx") {
|
|
3684
|
+
return (tx, additionalUTxOs) => evaluateTx(target, graph, tx, additionalUTxOs, options);
|
|
3685
|
+
}
|
|
3686
|
+
const value = Reflect.get(target, property, receiver);
|
|
3687
|
+
return typeof value === "function" ? value.bind(target) : value;
|
|
3688
|
+
}
|
|
3689
|
+
});
|
|
3690
|
+
};
|
|
3691
|
+
var submitTx = async (provider, graph, tx, options) => {
|
|
3692
|
+
const parsed = await graph.record(tx, {
|
|
3693
|
+
label: options.submitLabel,
|
|
3694
|
+
status: "signed"
|
|
3695
|
+
});
|
|
3696
|
+
try {
|
|
3697
|
+
const txHash = await provider.submitTx(tx);
|
|
3698
|
+
await recordSubmitted(graph, tx, options.submitLabel, parsed.hash);
|
|
3699
|
+
if (txHash !== parsed.hash) {
|
|
3700
|
+
graph.addWarning({
|
|
3701
|
+
code: "provider-tx-hash-mismatch",
|
|
3702
|
+
message: `Provider returned transaction hash ${txHash}, but the submitted body hashes to ${parsed.hash}`,
|
|
3703
|
+
txHash: parsed.hash
|
|
3704
|
+
});
|
|
3705
|
+
}
|
|
3706
|
+
return txHash;
|
|
3707
|
+
} catch (error) {
|
|
3708
|
+
await recordFailed(graph, tx, options.submitLabel, error);
|
|
3709
|
+
throw error;
|
|
3710
|
+
}
|
|
3711
|
+
};
|
|
3712
|
+
var evaluateTx = async (provider, graph, tx, additionalUTxOs, options) => {
|
|
3713
|
+
if (options.recordEvaluate === false) {
|
|
3714
|
+
return provider.evaluateTx(tx, additionalUTxOs);
|
|
3715
|
+
}
|
|
3716
|
+
if (additionalUTxOs && additionalUTxOs.length > 0) {
|
|
3717
|
+
graph.addResolvedUtxos(additionalUTxOs);
|
|
3718
|
+
}
|
|
3719
|
+
await graph.record(tx, {
|
|
3720
|
+
label: options.evaluateLabel,
|
|
3721
|
+
status: "built"
|
|
3722
|
+
});
|
|
3723
|
+
try {
|
|
3724
|
+
const evaluation = await provider.evaluateTx(tx, additionalUTxOs);
|
|
3725
|
+
await recordEvaluation(graph, tx, options.evaluateLabel, evaluation);
|
|
3726
|
+
return evaluation;
|
|
3727
|
+
} catch (error) {
|
|
3728
|
+
await recordFailed(graph, tx, options.evaluateLabel, error);
|
|
3729
|
+
throw error;
|
|
3730
|
+
}
|
|
3731
|
+
};
|
|
3732
|
+
var recordSubmitted = async (graph, tx, label, txHash) => {
|
|
3733
|
+
try {
|
|
3734
|
+
await graph.record(tx, { label, status: "submitted" });
|
|
3735
|
+
} catch (error) {
|
|
3736
|
+
graph.addWarning(
|
|
3737
|
+
traceRecordWarning("trace-submit-record-error", txHash, error)
|
|
3738
|
+
);
|
|
3739
|
+
}
|
|
3740
|
+
};
|
|
3741
|
+
var recordEvaluation = async (graph, tx, label, evaluation) => {
|
|
3742
|
+
try {
|
|
3743
|
+
await graph.record(tx, {
|
|
3744
|
+
label,
|
|
3745
|
+
status: "built",
|
|
3746
|
+
evaluation: evaluation.map(traceEvaluationRedeemer)
|
|
3747
|
+
});
|
|
3748
|
+
} catch (error) {
|
|
3749
|
+
graph.addWarning(
|
|
3750
|
+
traceRecordWarning("trace-evaluate-record-error", void 0, error)
|
|
3751
|
+
);
|
|
3752
|
+
}
|
|
3753
|
+
};
|
|
3754
|
+
var recordFailed = async (graph, tx, label, error) => {
|
|
3755
|
+
try {
|
|
3756
|
+
await graph.record(tx, {
|
|
3757
|
+
label,
|
|
3758
|
+
status: "failed",
|
|
3759
|
+
failureMessage: errorMessage2(error)
|
|
3760
|
+
});
|
|
3761
|
+
} catch {
|
|
3762
|
+
}
|
|
3763
|
+
};
|
|
3764
|
+
var traceEvaluationRedeemer = (redeemer) => ({
|
|
3765
|
+
tag: redeemer.redeemer_tag,
|
|
3766
|
+
redeemerIndex: redeemer.redeemer_index,
|
|
3767
|
+
exUnits: {
|
|
3768
|
+
mem: redeemer.ex_units.mem.toString(),
|
|
3769
|
+
steps: redeemer.ex_units.steps.toString()
|
|
3770
|
+
}
|
|
3771
|
+
});
|
|
3772
|
+
var traceRecordWarning = (code, txHash, error) => ({
|
|
3773
|
+
code,
|
|
3774
|
+
message: errorMessage2(error),
|
|
3775
|
+
...txHash ? { txHash } : {}
|
|
3776
|
+
});
|
|
3777
|
+
var errorMessage2 = (error) => error instanceof Error ? error.message : String(error);
|
|
3778
|
+
|
|
3779
|
+
// src/graph.ts
|
|
3780
|
+
var createTxGraph = (options = {}) => {
|
|
3781
|
+
const transactions = [];
|
|
3782
|
+
const preloadedUtxos = [];
|
|
3783
|
+
let utxos = /* @__PURE__ */ new Map();
|
|
3784
|
+
let edges = [];
|
|
3785
|
+
let warnings = [];
|
|
3786
|
+
let spentBy = /* @__PURE__ */ new Map();
|
|
3787
|
+
let resolutionCache = createResolutionCache();
|
|
3788
|
+
const externalWarnings = [];
|
|
3789
|
+
const resolvers = options.resolver ? [options.resolver] : [];
|
|
3790
|
+
const aliases = {
|
|
3791
|
+
assets: sortRecord({
|
|
3792
|
+
...options.aliases?.assets ?? {},
|
|
3793
|
+
...options.assets ?? {}
|
|
3794
|
+
}),
|
|
3795
|
+
addresses: sortRecord({
|
|
3796
|
+
...options.aliases?.addresses ?? {},
|
|
3797
|
+
...options.addresses ?? {}
|
|
3798
|
+
})
|
|
3799
|
+
};
|
|
3800
|
+
const taggers = [...options.labels ?? []];
|
|
3801
|
+
const graph = {
|
|
3802
|
+
record: async (input, recordOptions = {}) => {
|
|
3803
|
+
const parsed = parseTransaction(input, recordOptions);
|
|
3804
|
+
const nextTransaction = withFailureMessage(parsed, recordOptions);
|
|
3805
|
+
const existingIndex = transactions.findIndex(
|
|
3806
|
+
(transaction2) => transaction2.hash === nextTransaction.hash
|
|
3807
|
+
);
|
|
3808
|
+
const transaction = existingIndex >= 0 ? mergeRecordedTransaction(
|
|
3809
|
+
transactions[existingIndex],
|
|
3810
|
+
nextTransaction
|
|
3811
|
+
) : nextTransaction;
|
|
3812
|
+
if (existingIndex >= 0) {
|
|
3813
|
+
transactions[existingIndex] = transaction;
|
|
3814
|
+
await rebuildTrace();
|
|
3815
|
+
} else {
|
|
3816
|
+
transactions.push(transaction);
|
|
3817
|
+
await appendTransactionToTrace(transaction);
|
|
3818
|
+
}
|
|
3819
|
+
return jsonClone(transaction);
|
|
3820
|
+
},
|
|
3821
|
+
recordCbor: async (cbor, recordOptions = {}) => graph.record(cbor, recordOptions),
|
|
3822
|
+
addResolvedUtxos: (resolvedUtxos) => {
|
|
3823
|
+
preloadedUtxos.push(...resolvedUtxos);
|
|
3824
|
+
resolutionCache.addResolvedUtxos(resolvedUtxos);
|
|
3825
|
+
},
|
|
3826
|
+
resolveWith: (resolver) => {
|
|
3827
|
+
resolvers.push(resolver);
|
|
3828
|
+
},
|
|
3829
|
+
addWarning: (warning) => {
|
|
3830
|
+
externalWarnings.push(jsonClone(warning));
|
|
3831
|
+
},
|
|
3832
|
+
wrapProvider: (provider, wrapperOptions = {}) => wrapProvider(provider, graph, wrapperOptions),
|
|
3833
|
+
toJSON: () => {
|
|
3834
|
+
const trace = {
|
|
3835
|
+
version: 1,
|
|
3836
|
+
...options.createdAt ? { createdAt: options.createdAt } : {},
|
|
3837
|
+
transactions: transactions.map(jsonClone),
|
|
3838
|
+
utxos: sortedUtxoRecord(utxos),
|
|
3839
|
+
edges: sortedEdges2(edges),
|
|
3840
|
+
warnings: [...warnings, ...externalWarnings].map(jsonClone),
|
|
3841
|
+
aliases
|
|
3842
|
+
};
|
|
3843
|
+
return jsonClone(trace);
|
|
3844
|
+
},
|
|
3845
|
+
toSemantic: (renderOptions = {}) => buildSemanticRenderGraph(graph.toJSON(), renderOptions),
|
|
3846
|
+
toDot: (renderOptions = {}) => traceToDot(graph.toJSON(), renderOptions),
|
|
3847
|
+
toHtml: (renderOptions = {}) => traceToHtml(graph.toJSON(), renderOptions),
|
|
3848
|
+
toMermaid: (renderOptions = {}) => traceToMermaid(graph.toJSON(), renderOptions),
|
|
3849
|
+
toSvg: (renderOptions = {}) => traceToSvg(graph.toJSON(), renderOptions)
|
|
3850
|
+
};
|
|
3851
|
+
const rebuildTrace = async () => {
|
|
3852
|
+
utxos = /* @__PURE__ */ new Map();
|
|
3853
|
+
edges = [];
|
|
3854
|
+
warnings = [];
|
|
3855
|
+
spentBy = /* @__PURE__ */ new Map();
|
|
3856
|
+
resolutionCache = createResolutionCache();
|
|
3857
|
+
resolutionCache.addResolvedUtxos(preloadedUtxos);
|
|
3858
|
+
for (const transaction of transactions) {
|
|
3859
|
+
await appendTransactionToTrace(transaction);
|
|
3860
|
+
}
|
|
3861
|
+
};
|
|
3862
|
+
const appendTransactionToTrace = async (transaction) => {
|
|
3863
|
+
await resolveTransactionInputs(transaction);
|
|
3864
|
+
recordDoubleSpends(transaction);
|
|
3865
|
+
addProducedOutputs(transaction);
|
|
3866
|
+
addEdges(transaction);
|
|
3867
|
+
if (transaction.status === "failed") {
|
|
3868
|
+
warnings.push({
|
|
3869
|
+
code: "failed-transaction",
|
|
3870
|
+
message: transaction.failureMessage ?? `Transaction ${transaction.hash} was recorded as failed`,
|
|
3871
|
+
txHash: transaction.hash
|
|
3872
|
+
});
|
|
3873
|
+
}
|
|
3874
|
+
};
|
|
3875
|
+
const resolveTransactionInputs = async (transaction) => {
|
|
3876
|
+
const allInputs = [
|
|
3877
|
+
...transaction.inputs,
|
|
3878
|
+
...transaction.referenceInputs,
|
|
3879
|
+
...transaction.collateralInputs
|
|
3880
|
+
];
|
|
3881
|
+
if (allInputs.length === 0) return;
|
|
3882
|
+
const resolution = await resolutionCache.resolveOutRefs(allInputs, {
|
|
3883
|
+
provider: options.provider,
|
|
3884
|
+
resolver: resolvers.length > 0 ? resolveWithUserResolvers : void 0
|
|
3885
|
+
});
|
|
3886
|
+
for (const warning of resolution.warnings) {
|
|
3887
|
+
warnings.push({ ...warning, txHash: transaction.hash });
|
|
3888
|
+
}
|
|
3889
|
+
for (const utxo of resolution.utxos) {
|
|
3890
|
+
setInputUtxo(tagUtxo(utxo, "input", transaction));
|
|
3891
|
+
}
|
|
3892
|
+
const resolvedByKey = new Map(
|
|
3893
|
+
resolution.utxos.map((utxo) => [outRefKey(utxo), utxo])
|
|
3894
|
+
);
|
|
3895
|
+
warnUnresolvedInputs(
|
|
3896
|
+
transaction,
|
|
3897
|
+
"spend",
|
|
3898
|
+
transaction.inputs,
|
|
3899
|
+
resolvedByKey
|
|
3900
|
+
);
|
|
3901
|
+
warnUnresolvedInputs(
|
|
3902
|
+
transaction,
|
|
3903
|
+
"reference",
|
|
3904
|
+
transaction.referenceInputs,
|
|
3905
|
+
resolvedByKey
|
|
3906
|
+
);
|
|
3907
|
+
warnUnresolvedInputs(
|
|
3908
|
+
transaction,
|
|
3909
|
+
"collateral",
|
|
3910
|
+
transaction.collateralInputs,
|
|
3911
|
+
resolvedByKey
|
|
3912
|
+
);
|
|
3913
|
+
};
|
|
3914
|
+
const resolveWithUserResolvers = async (outRefs) => {
|
|
3915
|
+
const resolved = [];
|
|
3916
|
+
let missing = [...outRefs];
|
|
3917
|
+
for (const resolver of resolvers) {
|
|
3918
|
+
if (missing.length === 0) break;
|
|
3919
|
+
const resolverUtxos = await resolver(missing);
|
|
3920
|
+
resolved.push(...resolverUtxos);
|
|
3921
|
+
const resolvedKeys = new Set(resolverUtxos.map(outRefKey));
|
|
3922
|
+
missing = missing.filter(
|
|
3923
|
+
(outRef) => !resolvedKeys.has(outRefKey(outRef))
|
|
3924
|
+
);
|
|
3925
|
+
}
|
|
3926
|
+
return resolved;
|
|
3927
|
+
};
|
|
3928
|
+
const addProducedOutputs = (transaction) => {
|
|
3929
|
+
const outputs = transaction.outputs.map(
|
|
3930
|
+
(utxo) => tagUtxo(utxo, "output", transaction)
|
|
3931
|
+
);
|
|
3932
|
+
for (const utxo of outputs) {
|
|
3933
|
+
utxos.set(outRefKey(utxo), jsonClone(utxo));
|
|
3934
|
+
}
|
|
3935
|
+
if (transaction.status !== "failed") {
|
|
3936
|
+
resolutionCache.addTransactionOutputs({ outputs });
|
|
3937
|
+
}
|
|
3938
|
+
updateStoredTransactionOutputs(transaction.hash, outputs);
|
|
3939
|
+
};
|
|
3940
|
+
const recordDoubleSpends = (transaction) => {
|
|
3941
|
+
if (transaction.status === "failed") return;
|
|
3942
|
+
const seenInTx = /* @__PURE__ */ new Set();
|
|
3943
|
+
for (const input of transaction.inputs) {
|
|
3944
|
+
const key = outRefKey(input);
|
|
3945
|
+
const previousTxHash = seenInTx.has(key) ? transaction.hash : spentBy.get(key);
|
|
3946
|
+
if (previousTxHash) {
|
|
3947
|
+
warnings.push({
|
|
3948
|
+
code: "duplicate-spend",
|
|
3949
|
+
message: previousTxHash === transaction.hash ? `Transaction ${transaction.hash} spends ${key} more than once` : `Transaction ${transaction.hash} spends ${key}, already spent by ${previousTxHash}`,
|
|
3950
|
+
txHash: transaction.hash,
|
|
3951
|
+
outRef: input
|
|
3952
|
+
});
|
|
3953
|
+
}
|
|
3954
|
+
seenInTx.add(key);
|
|
3955
|
+
if (!spentBy.has(key)) spentBy.set(key, transaction.hash);
|
|
3956
|
+
}
|
|
3957
|
+
};
|
|
3958
|
+
const setInputUtxo = (utxo) => {
|
|
3959
|
+
const key = outRefKey(utxo);
|
|
3960
|
+
const existing = utxos.get(key);
|
|
3961
|
+
if (existing?.resolution === "resolved" && utxo.resolution === "unresolved") {
|
|
3962
|
+
return;
|
|
3963
|
+
}
|
|
3964
|
+
utxos.set(key, jsonClone(utxo));
|
|
3965
|
+
};
|
|
3966
|
+
const warnUnresolvedInputs = (transaction, kind, inputs, resolvedByKey) => {
|
|
3967
|
+
for (const input of inputs) {
|
|
3968
|
+
const resolved = resolvedByKey.get(outRefKey(input));
|
|
3969
|
+
if (resolved?.resolution !== "unresolved") continue;
|
|
3970
|
+
warnings.push({
|
|
3971
|
+
code: `unresolved-${kind}-input`,
|
|
3972
|
+
message: `Transaction ${transaction.hash} has unresolved ${kind} input ${outRefKey(input)} (${resolved.unresolvedReason ?? "unresolved"})`,
|
|
3973
|
+
txHash: transaction.hash,
|
|
3974
|
+
outRef: input
|
|
3975
|
+
});
|
|
3976
|
+
}
|
|
3977
|
+
};
|
|
3978
|
+
const addEdges = (transaction) => {
|
|
3979
|
+
addInputEdges2("spends", transaction.inputs, transaction);
|
|
3980
|
+
addInputEdges2("reads", transaction.referenceInputs, transaction);
|
|
3981
|
+
addInputEdges2("collateral", transaction.collateralInputs, transaction);
|
|
3982
|
+
for (const output of transaction.outputs) {
|
|
3983
|
+
edges.push({
|
|
3984
|
+
kind: "produces",
|
|
3985
|
+
from: txNodeId2(transaction.hash),
|
|
3986
|
+
to: utxoNodeId3(output)
|
|
3987
|
+
});
|
|
3988
|
+
}
|
|
3989
|
+
if (transaction.collateralReturn) {
|
|
3990
|
+
edges.push({
|
|
3991
|
+
kind: "collateralReturn",
|
|
3992
|
+
from: txNodeId2(transaction.hash),
|
|
3993
|
+
to: collateralReturnNodeId2(transaction.hash)
|
|
3994
|
+
});
|
|
3995
|
+
}
|
|
3996
|
+
addAssetEdges("mints", transaction.hash, transaction.mintedAssets);
|
|
3997
|
+
addAssetEdges("burns", transaction.hash, transaction.burnedAssets);
|
|
3998
|
+
for (const withdrawal of transaction.withdrawals) {
|
|
3999
|
+
edges.push({
|
|
4000
|
+
kind: "withdraws",
|
|
4001
|
+
from: withdrawalNodeId2(withdrawal.rewardAddress),
|
|
4002
|
+
to: txNodeId2(transaction.hash)
|
|
4003
|
+
});
|
|
4004
|
+
}
|
|
4005
|
+
for (const certificate of transaction.certificates) {
|
|
4006
|
+
edges.push({
|
|
4007
|
+
kind: "certifies",
|
|
4008
|
+
from: txNodeId2(transaction.hash),
|
|
4009
|
+
to: certificateNodeId2(transaction.hash, certificate.index)
|
|
4010
|
+
});
|
|
4011
|
+
}
|
|
4012
|
+
for (const signer of transaction.requiredSigners) {
|
|
4013
|
+
edges.push({
|
|
4014
|
+
kind: "requiresSigner",
|
|
4015
|
+
from: signerNodeId2(signer),
|
|
4016
|
+
to: txNodeId2(transaction.hash)
|
|
4017
|
+
});
|
|
4018
|
+
}
|
|
4019
|
+
};
|
|
4020
|
+
const addInputEdges2 = (kind, inputs, transaction) => {
|
|
4021
|
+
for (const input of inputs) {
|
|
4022
|
+
edges.push({
|
|
4023
|
+
kind,
|
|
4024
|
+
from: utxoNodeId3(input),
|
|
4025
|
+
to: txNodeId2(transaction.hash)
|
|
4026
|
+
});
|
|
4027
|
+
}
|
|
4028
|
+
};
|
|
4029
|
+
const addAssetEdges = (kind, txHash, assets) => {
|
|
4030
|
+
for (const unit of Object.keys(assets).sort()) {
|
|
4031
|
+
edges.push(
|
|
4032
|
+
kind === "mints" ? {
|
|
4033
|
+
kind,
|
|
4034
|
+
from: txNodeId2(txHash),
|
|
4035
|
+
to: assetNodeId(unit)
|
|
4036
|
+
} : {
|
|
4037
|
+
kind,
|
|
4038
|
+
from: assetNodeId(unit),
|
|
4039
|
+
to: txNodeId2(txHash)
|
|
4040
|
+
}
|
|
4041
|
+
);
|
|
4042
|
+
}
|
|
4043
|
+
};
|
|
4044
|
+
const tagUtxo = (utxo, direction, transaction) => {
|
|
4045
|
+
if (taggers.length === 0) return utxo;
|
|
4046
|
+
const tags = new Set(utxo.tags);
|
|
4047
|
+
for (const tagger of taggers) {
|
|
4048
|
+
const nextTags = tagger({
|
|
4049
|
+
utxo,
|
|
4050
|
+
direction,
|
|
4051
|
+
transaction,
|
|
4052
|
+
graph: currentTrace()
|
|
4053
|
+
});
|
|
4054
|
+
for (const tag of normalizeTags(nextTags)) {
|
|
4055
|
+
tags.add(tag);
|
|
4056
|
+
}
|
|
4057
|
+
}
|
|
4058
|
+
return {
|
|
4059
|
+
...utxo,
|
|
4060
|
+
tags: [...tags].sort()
|
|
4061
|
+
};
|
|
4062
|
+
};
|
|
4063
|
+
const updateStoredTransactionOutputs = (txHash, outputs) => {
|
|
4064
|
+
const index = transactions.findIndex(
|
|
4065
|
+
(transaction) => transaction.hash === txHash
|
|
4066
|
+
);
|
|
4067
|
+
if (index >= 0) {
|
|
4068
|
+
transactions[index] = {
|
|
4069
|
+
...transactions[index],
|
|
4070
|
+
outputs: outputs.map(jsonClone)
|
|
4071
|
+
};
|
|
4072
|
+
}
|
|
4073
|
+
};
|
|
4074
|
+
const currentTrace = () => ({
|
|
4075
|
+
version: 1,
|
|
4076
|
+
...options.createdAt ? { createdAt: options.createdAt } : {},
|
|
4077
|
+
transactions: transactions.map(jsonClone),
|
|
4078
|
+
utxos: sortedUtxoRecord(utxos),
|
|
4079
|
+
edges: sortedEdges2(edges),
|
|
4080
|
+
warnings: [...warnings, ...externalWarnings].map(jsonClone),
|
|
4081
|
+
aliases
|
|
4082
|
+
});
|
|
4083
|
+
return graph;
|
|
4084
|
+
};
|
|
4085
|
+
var withFailureMessage = (transaction, options) => ({
|
|
4086
|
+
...transaction,
|
|
4087
|
+
...options.failureMessage ? { failureMessage: options.failureMessage } : {},
|
|
4088
|
+
...options.evaluation ? { evaluation: [...options.evaluation] } : {}
|
|
4089
|
+
});
|
|
4090
|
+
var mergeRecordedTransaction = (existing, next) => {
|
|
4091
|
+
const failureMessage = next.status === "failed" ? next.failureMessage ?? existing.failureMessage : void 0;
|
|
4092
|
+
const evaluation = next.evaluation ?? (next.status === "failed" ? void 0 : existing.evaluation);
|
|
4093
|
+
return {
|
|
4094
|
+
...next,
|
|
4095
|
+
label: next.label ?? existing.label,
|
|
4096
|
+
...failureMessage !== void 0 ? { failureMessage } : {},
|
|
4097
|
+
...evaluation !== void 0 ? { evaluation } : {}
|
|
4098
|
+
};
|
|
4099
|
+
};
|
|
4100
|
+
var sortedUtxoRecord = (utxos) => {
|
|
4101
|
+
const result = {};
|
|
4102
|
+
for (const key of [...utxos.keys()].sort()) {
|
|
4103
|
+
const utxo = utxos.get(key);
|
|
4104
|
+
if (utxo) result[key] = jsonClone(utxo);
|
|
4105
|
+
}
|
|
4106
|
+
return result;
|
|
4107
|
+
};
|
|
4108
|
+
var sortedEdges2 = (edges) => [...edges].sort((left, right) => {
|
|
4109
|
+
const leftKey = edgeSortKey2(left);
|
|
4110
|
+
const rightKey = edgeSortKey2(right);
|
|
4111
|
+
return leftKey.localeCompare(rightKey);
|
|
4112
|
+
});
|
|
4113
|
+
var edgeSortKey2 = (edge) => `${edge.from}\0${edge.kind}\0${edge.to}`;
|
|
4114
|
+
var sortRecord = (record) => {
|
|
4115
|
+
const result = {};
|
|
4116
|
+
for (const key of Object.keys(record).sort()) {
|
|
4117
|
+
result[key] = record[key];
|
|
4118
|
+
}
|
|
4119
|
+
return result;
|
|
4120
|
+
};
|
|
4121
|
+
var normalizeTags = (tags) => {
|
|
4122
|
+
if (!tags) return [];
|
|
4123
|
+
const values = Array.isArray(tags) ? tags : [tags];
|
|
4124
|
+
return values.map((tag) => tag.trim()).filter((tag) => tag.length > 0);
|
|
4125
|
+
};
|
|
4126
|
+
var jsonClone = (value) => JSON.parse(JSON.stringify(value));
|
|
4127
|
+
var txNodeId2 = (txHash) => `tx:${txHash}`;
|
|
4128
|
+
var utxoNodeId3 = (outRef) => `utxo:${outRefKey(outRef)}`;
|
|
4129
|
+
var assetNodeId = (unit) => `asset:${unit}`;
|
|
4130
|
+
var withdrawalNodeId2 = (rewardAddress) => `withdrawal:${rewardAddress}`;
|
|
4131
|
+
var certificateNodeId2 = (txHash, index) => `certificate:${txHash}#${index}`;
|
|
4132
|
+
var signerNodeId2 = (keyHash) => `signer:${keyHash}`;
|
|
4133
|
+
var collateralReturnNodeId2 = (txHash) => `collateral-return:${txHash}`;
|
|
4134
|
+
|
|
4135
|
+
// src/labels.ts
|
|
4136
|
+
var tagByAddress = (label, address) => ({ utxo }) => utxo.address === address ? label : void 0;
|
|
4137
|
+
var tagByCredential = (label, credential) => ({ utxo }) => credentialEquals(utxo.paymentCredential, credential) || credentialEquals(utxo.stakeCredential, credential) ? label : void 0;
|
|
4138
|
+
var tagByUnit = (label, unit) => ({ utxo }) => Object.hasOwn(utxo.assets, unit) ? label : void 0;
|
|
4139
|
+
var tagByPolicyId = (label, policyId) => ({ utxo }) => Object.keys(utxo.assets).some(
|
|
4140
|
+
(unit) => unit !== "lovelace" && unit.startsWith(policyId)
|
|
4141
|
+
) ? label : void 0;
|
|
4142
|
+
var tagByDatumHash = (label, datumHash) => ({ utxo }) => utxo.datumHash === datumHash ? label : void 0;
|
|
4143
|
+
var tagByDatum = (label, predicate) => (context) => context.utxo.datum && predicate(context.utxo.datum, context) ? label : void 0;
|
|
4144
|
+
var tagByScriptRef = (label, options = {}) => ({ utxo }) => {
|
|
4145
|
+
if (!utxo.scriptRef) return void 0;
|
|
4146
|
+
if (options.type && utxo.scriptRef.type !== options.type) {
|
|
4147
|
+
return void 0;
|
|
4148
|
+
}
|
|
4149
|
+
if (options.hash && utxo.scriptRef.hash !== options.hash) {
|
|
4150
|
+
return void 0;
|
|
4151
|
+
}
|
|
4152
|
+
return label;
|
|
4153
|
+
};
|
|
4154
|
+
var tagChange = (label, options) => {
|
|
4155
|
+
const addresses = new Set(
|
|
4156
|
+
[options.changeAddress, options.walletAddress].filter(
|
|
4157
|
+
(address) => typeof address === "string"
|
|
4158
|
+
)
|
|
4159
|
+
);
|
|
4160
|
+
for (const utxo of options.knownWalletUtxos ?? []) {
|
|
4161
|
+
addresses.add(utxo.address);
|
|
4162
|
+
}
|
|
4163
|
+
const knownOutRefs = new Set(
|
|
4164
|
+
(options.knownWalletUtxos ?? []).map((utxo) => outRefKey(utxo))
|
|
4165
|
+
);
|
|
4166
|
+
return ({ direction, utxo }) => {
|
|
4167
|
+
if (direction === "output" && addresses.has(utxo.address)) return label;
|
|
4168
|
+
if (direction === "input" && knownOutRefs.has(outRefKey(utxo))) {
|
|
4169
|
+
return label;
|
|
4170
|
+
}
|
|
4171
|
+
return void 0;
|
|
4172
|
+
};
|
|
4173
|
+
};
|
|
4174
|
+
var credentialEquals = (left, right) => left?.type === right.type && left.hash === right.hash;
|
|
4175
|
+
|
|
4176
|
+
// src/render/json.ts
|
|
4177
|
+
var traceToJSON = (trace) => ({
|
|
4178
|
+
version: 1,
|
|
4179
|
+
...trace.createdAt ? { createdAt: trace.createdAt } : {},
|
|
4180
|
+
transactions: trace.transactions.map(jsonClone2),
|
|
4181
|
+
utxos: Object.fromEntries(
|
|
4182
|
+
Object.entries(trace.utxos).sort(([left], [right]) => left.localeCompare(right)).map(([key, utxo]) => [key, jsonClone2(utxo)])
|
|
4183
|
+
),
|
|
4184
|
+
edges: sortedEdges(trace.edges),
|
|
4185
|
+
warnings: trace.warnings.map(jsonClone2),
|
|
4186
|
+
aliases: {
|
|
4187
|
+
assets: sortRecord2(trace.aliases.assets),
|
|
4188
|
+
addresses: sortRecord2(trace.aliases.addresses)
|
|
4189
|
+
}
|
|
4190
|
+
});
|
|
4191
|
+
var sortRecord2 = (record) => Object.fromEntries(
|
|
4192
|
+
Object.entries(record).sort(([left], [right]) => left.localeCompare(right))
|
|
4193
|
+
);
|
|
4194
|
+
var jsonClone2 = (value) => JSON.parse(JSON.stringify(value));
|
|
4195
|
+
export {
|
|
4196
|
+
buildSemanticRenderGraph,
|
|
4197
|
+
createResolutionCache,
|
|
4198
|
+
createTxGraph,
|
|
4199
|
+
describeRedeemerByConstructor,
|
|
4200
|
+
describeRedeemerWith,
|
|
4201
|
+
genesisUtxo,
|
|
4202
|
+
labelRedeemer,
|
|
4203
|
+
outRefKey,
|
|
4204
|
+
parseOutRefKey,
|
|
4205
|
+
parseTransaction,
|
|
4206
|
+
parseTransactionCbor,
|
|
4207
|
+
producedUtxosFromTransaction,
|
|
4208
|
+
resolveVisualRendererOptions,
|
|
4209
|
+
semanticSvgForTesting,
|
|
4210
|
+
tagByAddress,
|
|
4211
|
+
tagByCredential,
|
|
4212
|
+
tagByDatum,
|
|
4213
|
+
tagByDatumHash,
|
|
4214
|
+
tagByPolicyId,
|
|
4215
|
+
tagByScriptRef,
|
|
4216
|
+
tagByUnit,
|
|
4217
|
+
tagChange,
|
|
4218
|
+
toTraceTxOutput,
|
|
4219
|
+
toTraceUtxo,
|
|
4220
|
+
traceToDot,
|
|
4221
|
+
traceToHtml,
|
|
4222
|
+
traceToJSON,
|
|
4223
|
+
traceToMermaid,
|
|
4224
|
+
traceToSvg,
|
|
4225
|
+
unresolvedUtxo,
|
|
4226
|
+
visualEdgeStyle,
|
|
4227
|
+
wrapProvider
|
|
4228
|
+
};
|
|
4229
|
+
//# sourceMappingURL=index.js.map
|