@meshsdk/core-cst 1.6.0-alpha.11

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.
@@ -0,0 +1,678 @@
1
+ import { Serialization, TxCBOR } from "@cardano-sdk/core";
2
+ import { HexBlob } from "@cardano-sdk/util";
3
+
4
+ import {
5
+ BuilderData,
6
+ NativeScript as CommonNativeScript,
7
+ Data,
8
+ DEFAULT_V1_COST_MODEL_LIST,
9
+ DEFAULT_V2_COST_MODEL_LIST,
10
+ DeserializedAddress,
11
+ DeserializedScript,
12
+ IDeserializer,
13
+ IMeshTxSerializer,
14
+ IResolver,
15
+ MeshTxBuilderBody,
16
+ PlutusScript,
17
+ Protocol,
18
+ PubKeyTxIn,
19
+ RequiredWith,
20
+ ScriptTxIn,
21
+ SimpleScriptTxIn,
22
+ TxIn,
23
+ } from "@meshsdk/common";
24
+
25
+ import { PrivateKey } from "../stricahq";
26
+ import {
27
+ Address,
28
+ CredentialType,
29
+ Ed25519PublicKeyHex,
30
+ Ed25519SignatureHex,
31
+ ExUnits,
32
+ NativeScript,
33
+ PlutusData,
34
+ PlutusLanguageVersion,
35
+ PlutusV1Script,
36
+ PlutusV2Script,
37
+ PlutusV3Script,
38
+ Redeemer,
39
+ Redeemers,
40
+ RedeemerTag,
41
+ Script,
42
+ Transaction,
43
+ TransactionBody,
44
+ TransactionId,
45
+ TransactionInput,
46
+ TransactionOutput,
47
+ TransactionWitnessSet,
48
+ Value,
49
+ VkeyWitness,
50
+ } from "../types";
51
+ import { toAddress, toNativeScript, toPlutusData, toValue } from "../utils";
52
+ import { hashScriptData } from "../utils/script-data-hash";
53
+ import { empty, mergeValue, negatives, subValue } from "../utils/value";
54
+
55
+ export class CardanoSDKSerializer implements IMeshTxSerializer {
56
+ private txBody: TransactionBody;
57
+ private txWitnessSet: TransactionWitnessSet;
58
+
59
+ private utxoContext: Map<TransactionInput, TransactionOutput> = new Map<
60
+ TransactionInput,
61
+ TransactionOutput
62
+ >();
63
+
64
+ private redeemerContext: Map<TransactionInput, Redeemer> = new Map<
65
+ TransactionInput,
66
+ Redeemer
67
+ >();
68
+
69
+ private scriptsProvided: Set<Script> = new Set<Script>();
70
+ private datumsProvided: Set<PlutusData> = new Set<PlutusData>();
71
+ private usedLanguages: Record<PlutusLanguageVersion, boolean> = {
72
+ [0]: false,
73
+ [1]: false,
74
+ [2]: false,
75
+ };
76
+
77
+ constructor() {
78
+ this.txBody = new TransactionBody(
79
+ Serialization.CborSet.fromCore([], TransactionInput.fromCore),
80
+ [],
81
+ BigInt(0),
82
+ undefined,
83
+ );
84
+ this.txWitnessSet = new TransactionWitnessSet();
85
+ }
86
+
87
+ serializeAddress(address: DeserializedAddress, networkId?: 0 | 1): string {
88
+ throw new Error("Method not implemented.");
89
+ }
90
+
91
+ serializeData(data: BuilderData): string {
92
+ throw new Error("Method not implemented.");
93
+ }
94
+
95
+ deserializer: IDeserializer = {
96
+ key: {
97
+ deserializeAddress: function (bech32: string): DeserializedAddress {
98
+ throw new Error("Function not implemented.");
99
+
100
+ // return {
101
+ // pubKeyHash: this.resolvePaymentKeyHash(address),
102
+ // scriptHash: this.resolvePlutusScriptHash(address),
103
+ // stakeCredentialHash: this.resolveStakeKeyHash(address),
104
+ // stakeScriptCredentialHash: this.resolveStakeScriptHash(address),
105
+ // };
106
+ },
107
+ },
108
+ script: {
109
+ deserializeNativeScript: function (
110
+ script: CommonNativeScript,
111
+ ): DeserializedScript {
112
+ throw new Error("Function not implemented.");
113
+ },
114
+ deserializePlutusScript: function (
115
+ script: PlutusScript,
116
+ ): DeserializedScript {
117
+ throw new Error("Function not implemented.");
118
+ },
119
+ },
120
+ };
121
+
122
+ resolver: IResolver = {
123
+ keys: {
124
+ // resolvePaymentKeyHash: function (bech32: string): string {
125
+ // const cardanoAddress = toAddress(bech32);
126
+ // return cardanoAddress.asEnterprise()?.getPaymentCredential().type ===
127
+ // CredentialType.KeyHash
128
+ // ? cardanoAddress.asEnterprise()!.getPaymentCredential().hash
129
+ // : "";
130
+ // },
131
+ // resolvePlutusScriptHash: function (bech32: string): string {
132
+ // const cardanoAddress = toAddress(bech32);
133
+ // return cardanoAddress.asEnterprise()?.getPaymentCredential().type ===
134
+ // CredentialType.ScriptHash
135
+ // ? cardanoAddress.asEnterprise()!.getPaymentCredential().hash
136
+ // : "";
137
+ // },
138
+ resolveStakeKeyHash: function (bech32: string): string {
139
+ const cardanoAddress = toAddress(bech32);
140
+ return cardanoAddress.asReward()?.getPaymentCredential().type ===
141
+ CredentialType.KeyHash
142
+ ? cardanoAddress.asReward()!.getPaymentCredential().hash
143
+ : "";
144
+ },
145
+ // resolveStakeScriptHash(bech32: string): string {
146
+ // const cardanoAddress = toAddress(bech32);
147
+ // return cardanoAddress.asReward()?.getPaymentCredential().type ===
148
+ // CredentialType.ScriptHash
149
+ // ? cardanoAddress.asReward()!.getPaymentCredential().hash
150
+ // : "";
151
+ // },
152
+ resolvePrivateKey: function (words: string[]): string {
153
+ throw new Error("Function not implemented.");
154
+ },
155
+ resolveRewardAddress: function (bech32: string): string {
156
+ throw new Error("Function not implemented.");
157
+ },
158
+ resolveEd25519KeyHash: function (bech32: string): string {
159
+ throw new Error("Function not implemented.");
160
+ },
161
+ },
162
+ tx: {
163
+ resolveTxHash: function (txHex: string): string {
164
+ return Transaction.fromCbor(TxCBOR(txHex)).getId();
165
+ },
166
+ },
167
+ data: {
168
+ resolveDataHash: function (data: Data): string {
169
+ throw new Error("Function not implemented.");
170
+ },
171
+ },
172
+ script: {
173
+ // resolveNativeScript: function (script: CommonNativeScript): string {
174
+ // return toNativeScript(script).toCbor();
175
+ // },
176
+ resolveScriptRef: function (
177
+ script: CommonNativeScript | PlutusScript,
178
+ ): string {
179
+ throw new Error("Function not implemented.");
180
+ },
181
+ },
182
+ pool: {
183
+ resolvePoolId: function (hash: string): string {
184
+ throw new Error("Function not implemented.");
185
+ },
186
+ },
187
+ };
188
+
189
+ serializeTxBody = (
190
+ txBuilderBody: MeshTxBuilderBody,
191
+ protocolParams: Protocol,
192
+ ): string => {
193
+ const {
194
+ inputs,
195
+ outputs,
196
+ extraInputs,
197
+ selectionThreshold,
198
+ collaterals,
199
+ referenceInputs,
200
+ mints,
201
+ changeAddress,
202
+ certificates,
203
+ validityRange,
204
+ requiredSignatures,
205
+ metadata,
206
+ } = txBuilderBody;
207
+
208
+ mints.sort((a, b) => a.policyId.localeCompare(b.policyId));
209
+ inputs.sort((a, b) => {
210
+ if (a.txIn.txHash === b.txIn.txHash) {
211
+ return a.txIn.txIndex - b.txIn.txIndex;
212
+ } else {
213
+ return a.txIn.txHash.localeCompare(b.txIn.txHash);
214
+ }
215
+ });
216
+
217
+ this.addAllInputs(inputs);
218
+ this.addAllCollateralInputs(collaterals);
219
+ this.buildWitnessSet();
220
+ this.balanceTx(changeAddress, requiredSignatures.length, protocolParams);
221
+ return new Transaction(this.txBody, this.txWitnessSet).toCbor();
222
+ };
223
+
224
+ addSigningKeys = (txHex: string, signingKeys: string[]): string => {
225
+ let cardanoTx = Transaction.fromCbor(TxCBOR(txHex));
226
+ let currentWitnessSet = cardanoTx.witnessSet();
227
+ let currentWitnessSetVkeys = currentWitnessSet.vkeys();
228
+ let currentWitnessSetVkeysValues: Serialization.VkeyWitness[] =
229
+ currentWitnessSetVkeys ? [...currentWitnessSetVkeys.values()] : [];
230
+ for (let i = 0; i < signingKeys.length; i++) {
231
+ let keyHex = signingKeys[i];
232
+ if (keyHex) {
233
+ if (keyHex.length === 68 && keyHex.substring(0, 4) === "5820") {
234
+ keyHex = keyHex.substring(4);
235
+ }
236
+ const cardanoSigner = new PrivateKey(Buffer.from(keyHex, "hex"), false);
237
+ const signature = cardanoSigner.sign(
238
+ Buffer.from(cardanoTx.getId(), "hex"),
239
+ );
240
+ currentWitnessSetVkeysValues.push(
241
+ new VkeyWitness(
242
+ Ed25519PublicKeyHex(
243
+ cardanoSigner.toPublicKey().toBytes().toString("hex"),
244
+ ),
245
+ Ed25519SignatureHex(signature.toString("hex")),
246
+ ),
247
+ );
248
+ }
249
+ }
250
+ currentWitnessSet.setVkeys(
251
+ Serialization.CborSet.fromCore(
252
+ currentWitnessSetVkeysValues.map((vkw) => vkw.toCore()),
253
+ VkeyWitness.fromCore,
254
+ ),
255
+ );
256
+ cardanoTx.setWitnessSet(currentWitnessSet);
257
+ return cardanoTx.toCbor();
258
+ };
259
+
260
+ private addAllInputs = (inputs: TxIn[]) => {
261
+ for (let i = 0; i < inputs.length; i += 1) {
262
+ const currentTxIn = inputs[i];
263
+ if (!currentTxIn) continue;
264
+ switch (currentTxIn.type) {
265
+ case "PubKey":
266
+ this.addTxIn(currentTxIn as RequiredWith<PubKeyTxIn, "txIn">);
267
+ break;
268
+ case "Script":
269
+ this.addScriptTxIn(
270
+ currentTxIn as RequiredWith<ScriptTxIn, "txIn" | "scriptTxIn">,
271
+ );
272
+ break;
273
+ }
274
+ }
275
+ };
276
+
277
+ private addTxIn = (currentTxIn: RequiredWith<PubKeyTxIn, "txIn">) => {
278
+ // First build Cardano tx in and add it to tx body
279
+ let cardanoTxIn = new TransactionInput(
280
+ TransactionId(currentTxIn.txIn.txHash),
281
+ BigInt(currentTxIn.txIn.txIndex),
282
+ );
283
+ const inputs = this.txBody.inputs();
284
+ const txInputsList: TransactionInput[] = [...inputs.values()];
285
+ if (
286
+ txInputsList.find((input) => {
287
+ input.index() == cardanoTxIn.index() &&
288
+ input.transactionId == cardanoTxIn.transactionId;
289
+ })
290
+ ) {
291
+ throw new Error("Duplicate input added to tx body");
292
+ }
293
+ txInputsList.push(cardanoTxIn);
294
+ inputs.setValues(txInputsList);
295
+ // We save the output to a mapping so that we can calculate change
296
+ const cardanoTxOut = new TransactionOutput(
297
+ Address.fromBech32(currentTxIn.txIn.address),
298
+ toValue(currentTxIn.txIn.amount),
299
+ );
300
+ this.utxoContext.set(cardanoTxIn, cardanoTxOut);
301
+ this.txBody.setInputs(inputs);
302
+ };
303
+
304
+ private addScriptTxIn = (
305
+ currentTxIn: RequiredWith<ScriptTxIn, "txIn" | "scriptTxIn">,
306
+ ) => {
307
+ // we can add the input in first, and handle the script info after
308
+ this.addTxIn({
309
+ type: "PubKey",
310
+ txIn: currentTxIn.txIn,
311
+ });
312
+ if (!currentTxIn.scriptTxIn.scriptSource) {
313
+ throw new Error("A script input had no script source");
314
+ }
315
+ if (!currentTxIn.scriptTxIn.datumSource) {
316
+ throw new Error("A script input had no datum source");
317
+ }
318
+ if (!currentTxIn.scriptTxIn.redeemer) {
319
+ throw new Error("A script input had no redeemer");
320
+ }
321
+ // Handle script info based on whether it's inlined or provided
322
+ if (currentTxIn.scriptTxIn.scriptSource.type === "Provided") {
323
+ switch (currentTxIn.scriptTxIn.scriptSource.script.version) {
324
+ case "V1": {
325
+ this.scriptsProvided.add(
326
+ Script.newPlutusV1Script(
327
+ PlutusV1Script.fromCbor(
328
+ HexBlob(currentTxIn.scriptTxIn.scriptSource.script.code),
329
+ ),
330
+ ),
331
+ );
332
+ this.usedLanguages[PlutusLanguageVersion.V1] = true;
333
+ break;
334
+ }
335
+ case "V2": {
336
+ this.scriptsProvided.add(
337
+ Script.newPlutusV2Script(
338
+ PlutusV2Script.fromCbor(
339
+ HexBlob(currentTxIn.scriptTxIn.scriptSource.script.code),
340
+ ),
341
+ ),
342
+ );
343
+ this.usedLanguages[PlutusLanguageVersion.V2] = true;
344
+ break;
345
+ }
346
+ case "V3": {
347
+ this.scriptsProvided.add(
348
+ Script.newPlutusV3Script(
349
+ PlutusV3Script.fromCbor(
350
+ HexBlob(currentTxIn.scriptTxIn.scriptSource.script.code),
351
+ ),
352
+ ),
353
+ );
354
+ this.usedLanguages[PlutusLanguageVersion.V3] = true;
355
+ break;
356
+ }
357
+ }
358
+ } else if (currentTxIn.scriptTxIn.scriptSource.type === "Inline") {
359
+ let referenceInputs = this.txBody.referenceInputs()
360
+ ? this.txBody.referenceInputs()!
361
+ : Serialization.CborSet.fromCore([], TransactionInput.fromCore);
362
+
363
+ let referenceInputsList = [...referenceInputs.values()];
364
+
365
+ referenceInputsList.push(
366
+ new TransactionInput(
367
+ TransactionId(currentTxIn.scriptTxIn.scriptSource.txHash),
368
+ BigInt(currentTxIn.scriptTxIn.scriptSource.txIndex),
369
+ ),
370
+ );
371
+
372
+ referenceInputs.setValues(referenceInputsList);
373
+
374
+ this.txBody.setReferenceInputs(referenceInputs);
375
+ switch (currentTxIn.scriptTxIn.scriptSource.version) {
376
+ case "V1": {
377
+ this.usedLanguages[PlutusLanguageVersion.V1] = true;
378
+ break;
379
+ }
380
+ case "V2": {
381
+ this.usedLanguages[PlutusLanguageVersion.V2] = true;
382
+ break;
383
+ }
384
+ case "V3": {
385
+ this.usedLanguages[PlutusLanguageVersion.V3] = true;
386
+ break;
387
+ }
388
+ }
389
+ }
390
+ if (currentTxIn.scriptTxIn.datumSource.type === "Provided") {
391
+ // TODO: handle json / raw datum
392
+ this.datumsProvided.add(
393
+ toPlutusData(currentTxIn.scriptTxIn.datumSource.data.content as Data), // TODO: handle json / raw datum
394
+ );
395
+ } else if (currentTxIn.scriptTxIn.datumSource.type === "Inline") {
396
+ let referenceInputs = this.txBody.referenceInputs()
397
+ ? this.txBody.referenceInputs()!
398
+ : Serialization.CborSet.fromCore([], TransactionInput.fromCore);
399
+
400
+ let referenceInputsList = [...referenceInputs.values()];
401
+
402
+ referenceInputsList.push(
403
+ new TransactionInput(
404
+ TransactionId(currentTxIn.txIn.txHash),
405
+ BigInt(currentTxIn.txIn.txIndex),
406
+ ),
407
+ );
408
+
409
+ referenceInputs.setValues(referenceInputsList);
410
+
411
+ this.txBody.setReferenceInputs(referenceInputs);
412
+ }
413
+ let cardanoTxIn = new TransactionInput(
414
+ TransactionId(currentTxIn.txIn.txHash),
415
+ BigInt(currentTxIn.txIn.txIndex),
416
+ );
417
+ // Keep track of all redeemers mapped to their inputs
418
+ // TODO: handle json / raw redeemer data
419
+ let exUnits = currentTxIn.scriptTxIn.redeemer.exUnits;
420
+
421
+ this.redeemerContext.set(
422
+ cardanoTxIn,
423
+ new Redeemer(
424
+ RedeemerTag.Spend,
425
+ BigInt(0),
426
+ toPlutusData(currentTxIn.scriptTxIn.redeemer.data.content as Data), // TODO: handle json / raw datum
427
+ new ExUnits(BigInt(exUnits.mem), BigInt(exUnits.steps)),
428
+ ),
429
+ );
430
+ };
431
+
432
+ private addSimpleScriptTxIn = (
433
+ currentTxIn: RequiredWith<SimpleScriptTxIn, "txIn" | "simpleScriptTxIn">,
434
+ ) => {};
435
+
436
+ private addAllCollateralInputs = (collaterals: PubKeyTxIn[]) => {
437
+ for (let i = 0; i < collaterals.length; i++) {
438
+ this.addCollateralInput(
439
+ collaterals[i] as RequiredWith<PubKeyTxIn, "txIn">,
440
+ );
441
+ }
442
+ };
443
+
444
+ private addCollateralInput = (
445
+ collateral: RequiredWith<PubKeyTxIn, "txIn">,
446
+ ) => {
447
+ // First build Cardano tx in and add it to tx body
448
+ let cardanoTxIn = new TransactionInput(
449
+ TransactionId(collateral.txIn.txHash),
450
+ BigInt(collateral.txIn.txIndex),
451
+ );
452
+ const collateralInputs = this.txBody.collateral()
453
+ ? this.txBody.collateral()!
454
+ : Serialization.CborSet.fromCore([], TransactionInput.fromCore);
455
+ const collateralInputsList: TransactionInput[] = [
456
+ ...collateralInputs.values(),
457
+ ];
458
+ if (
459
+ collateralInputsList.find((input) => {
460
+ input.index() == cardanoTxIn.index() &&
461
+ input.transactionId == cardanoTxIn.transactionId;
462
+ })
463
+ ) {
464
+ throw new Error("Duplicate input added to tx body");
465
+ }
466
+ collateralInputsList.push(cardanoTxIn);
467
+ collateralInputs.setValues(collateralInputsList);
468
+ // We save the output to a mapping so that we can calculate collateral return later
469
+ // TODO: set collateral return
470
+ const cardanoTxOut = new TransactionOutput(
471
+ Address.fromBech32(collateral.txIn.address),
472
+ toValue(collateral.txIn.amount),
473
+ );
474
+ this.utxoContext.set(cardanoTxIn, cardanoTxOut);
475
+ this.txBody.setCollateral(collateralInputs);
476
+ };
477
+
478
+ private buildWitnessSet = () => {
479
+ const inputs = this.txBody.inputs();
480
+ // Search through the inputs, and set each redeemer index to the correct one
481
+ for (let i = 0; i < inputs.size(); i += 1) {
482
+ const input = inputs.values().at(i);
483
+ if (input) {
484
+ let redeemer = this.redeemerContext.get(input);
485
+ if (redeemer) {
486
+ redeemer.setIndex(BigInt(i));
487
+ }
488
+ }
489
+ }
490
+ // Add redeemers to tx witness set
491
+ let redeemers = this.txWitnessSet.redeemers()
492
+ ? this.txWitnessSet.redeemers()!
493
+ : Redeemers.fromCore([]);
494
+ let redeemersList = [...redeemers.values()];
495
+ this.redeemerContext.forEach((redeemer) => {
496
+ redeemersList.push(redeemer);
497
+ });
498
+ redeemers.setValues(redeemersList);
499
+ this.txWitnessSet.setRedeemers(redeemers);
500
+
501
+ // Add provided scripts to tx witness set
502
+ let nativeScripts = this.txWitnessSet.nativeScripts()
503
+ ? this.txWitnessSet.nativeScripts()!
504
+ : Serialization.CborSet.fromCore([], NativeScript.fromCore);
505
+
506
+ let v1Scripts = this.txWitnessSet.plutusV1Scripts()
507
+ ? this.txWitnessSet.plutusV1Scripts()!
508
+ : Serialization.CborSet.fromCore([], PlutusV1Script.fromCore);
509
+
510
+ let v2Scripts = this.txWitnessSet.plutusV2Scripts()
511
+ ? this.txWitnessSet.plutusV2Scripts()!
512
+ : Serialization.CborSet.fromCore([], PlutusV2Script.fromCore);
513
+
514
+ let v3Scripts = this.txWitnessSet.plutusV3Scripts()
515
+ ? this.txWitnessSet.plutusV3Scripts()!
516
+ : Serialization.CborSet.fromCore([], PlutusV3Script.fromCore);
517
+
518
+ this.scriptsProvided.forEach((script) => {
519
+ if (script.asNative() !== undefined) {
520
+ let nativeScriptsList = [...nativeScripts.values()];
521
+ nativeScriptsList.push(script.asNative()!);
522
+ nativeScripts.setValues(nativeScriptsList);
523
+ } else if (script.asPlutusV1() !== undefined) {
524
+ let v1ScriptsList = [...v1Scripts.values()];
525
+ v1ScriptsList.push(script.asPlutusV1()!);
526
+ v1Scripts.setValues(v1ScriptsList);
527
+ } else if (script.asPlutusV2() !== undefined) {
528
+ let v2ScriptsList = [...v2Scripts.values()];
529
+ v2ScriptsList.push(script.asPlutusV2()!);
530
+ v2Scripts.setValues(v2ScriptsList);
531
+ } else if (script.asPlutusV3() !== undefined) {
532
+ let v3ScriptsList = [...v3Scripts.values()];
533
+ v3ScriptsList.push(script.asPlutusV3()!);
534
+ v3Scripts.setValues(v3ScriptsList);
535
+ }
536
+
537
+ this.txWitnessSet.setNativeScripts(nativeScripts);
538
+ this.txWitnessSet.setPlutusV1Scripts(v1Scripts);
539
+ this.txWitnessSet.setPlutusV2Scripts(v2Scripts);
540
+ this.txWitnessSet.setPlutusV3Scripts(v3Scripts);
541
+ });
542
+
543
+ // Add provided datums to tx witness set
544
+ let datums = this.txWitnessSet.plutusData()
545
+ ? this.txWitnessSet.plutusData()!
546
+ : Serialization.CborSet.fromCore([], PlutusData.fromCore);
547
+
548
+ this.datumsProvided.forEach((datum) => {
549
+ let datumsList = [...datums.values()];
550
+ datumsList.push(datum);
551
+ datums.setValues(datumsList);
552
+ });
553
+ this.txWitnessSet.setPlutusData(datums);
554
+
555
+ // After building tx witness set, we must hash it with the cost models
556
+ // and put the hash in the tx body
557
+ let costModelV1 = Serialization.CostModel.newPlutusV1(
558
+ DEFAULT_V1_COST_MODEL_LIST,
559
+ );
560
+ let costModelV2 = Serialization.CostModel.newPlutusV2(
561
+ DEFAULT_V2_COST_MODEL_LIST,
562
+ );
563
+ let costModels = new Serialization.Costmdls();
564
+
565
+ if (this.usedLanguages[PlutusLanguageVersion.V1]) {
566
+ costModels.insert(costModelV1);
567
+ }
568
+ if (this.usedLanguages[PlutusLanguageVersion.V2]) {
569
+ costModels.insert(costModelV2);
570
+ }
571
+ if (this.usedLanguages[PlutusLanguageVersion.V3]) {
572
+ // TODO: insert v3 cost models after conway HF
573
+ }
574
+ let scriptDataHash = hashScriptData(
575
+ costModels,
576
+ redeemers.size() > 0 ? [...redeemers.values()] : undefined,
577
+ datums.size() > 0 ? [...datums.values()] : undefined,
578
+ );
579
+ if (scriptDataHash) {
580
+ this.txBody.setScriptDataHash(scriptDataHash);
581
+ }
582
+ };
583
+
584
+ private balanceTx = (
585
+ changeAddress: string,
586
+ numberOfRequiredWitnesses: number,
587
+ protocolParams: Protocol,
588
+ ) => {
589
+ if (changeAddress === "") {
590
+ throw new Error("Can't balance tx without a change address");
591
+ }
592
+
593
+ // First we add up all input values
594
+ const inputs = this.txBody.inputs().values();
595
+ let remainingValue = new Value(BigInt(0));
596
+ for (let i = 0; i < inputs.length; i++) {
597
+ let input = inputs[i];
598
+ if (!input) {
599
+ throw new Error("Invalid input found");
600
+ }
601
+ const output = this.utxoContext.get(input);
602
+ if (!output) {
603
+ throw new Error(`Unable to resolve input: ${input.toCbor()}`);
604
+ }
605
+ remainingValue = mergeValue(remainingValue, output.amount());
606
+ }
607
+
608
+ // Then we add all withdrawal values
609
+ const withdrawals = this.txBody.withdrawals();
610
+ if (withdrawals) {
611
+ withdrawals.forEach((coin) => {
612
+ remainingValue = mergeValue(remainingValue, new Value(coin));
613
+ });
614
+ }
615
+
616
+ // Then we add all mint values
617
+ remainingValue = mergeValue(
618
+ remainingValue,
619
+ new Value(BigInt(0), this.txBody.mint()),
620
+ );
621
+
622
+ // We then take away any current outputs
623
+ const currentOutputs = this.txBody.outputs();
624
+ for (let i = 0; i < currentOutputs.length; i++) {
625
+ let output = currentOutputs.at(i);
626
+ if (output) {
627
+ remainingValue = subValue(remainingValue, output.amount());
628
+ }
629
+ }
630
+
631
+ // Add an initial change output, this is needed to generate dummy tx
632
+ // If inputs - outputs is negative, then throw error
633
+ if (remainingValue.coin() < 0 || !empty(negatives(remainingValue))) {
634
+ throw new Error(`Not enough funds to satisfy outputs`);
635
+ }
636
+
637
+ currentOutputs.push(
638
+ new TransactionOutput(Address.fromBech32(changeAddress), remainingValue),
639
+ );
640
+ this.txBody.setOutputs(currentOutputs);
641
+
642
+ // Create a dummy tx that we will use to calculate fees
643
+ this.txBody.setFee(BigInt("10000000"));
644
+ const dummyTx = this.createDummyTx(numberOfRequiredWitnesses);
645
+ const fee =
646
+ protocolParams.minFeeB +
647
+ (dummyTx.toCbor().length / 2) * Number(protocolParams.coinsPerUtxoSize);
648
+ this.txBody.setFee(BigInt(fee));
649
+
650
+ // The change output should be the last element in outputs
651
+ // so we can simply take away the calculated fees from it
652
+ const changeOutput = currentOutputs.pop();
653
+ if (!changeOutput) {
654
+ throw new Error(
655
+ "Somehow the output length was 0 after attempting to calculate fees",
656
+ );
657
+ }
658
+ changeOutput.amount().setCoin(changeOutput.amount().coin() - BigInt(fee));
659
+ currentOutputs.push(changeOutput);
660
+ this.txBody.setOutputs(currentOutputs);
661
+ };
662
+
663
+ private createDummyTx = (numberOfRequiredWitnesses: number): Transaction => {
664
+ let dummyWitnessSet = new TransactionWitnessSet();
665
+ const dummyVkeyWitnesses: [Ed25519PublicKeyHex, Ed25519SignatureHex][] = [];
666
+ for (let i = 0; i < numberOfRequiredWitnesses; i++) {
667
+ dummyVkeyWitnesses.push([
668
+ Ed25519PublicKeyHex("0".repeat(64)),
669
+ Ed25519SignatureHex("0".repeat(128)),
670
+ ]);
671
+ }
672
+ dummyWitnessSet.setVkeys(
673
+ Serialization.CborSet.fromCore(dummyVkeyWitnesses, VkeyWitness.fromCore),
674
+ );
675
+
676
+ return new Transaction(this.txBody, dummyWitnessSet);
677
+ };
678
+ }
@@ -0,0 +1,24 @@
1
+ import {
2
+ Bip32PrivateKey,
3
+ Bip32PublicKey,
4
+ PublicKey,
5
+ PrivateKey as StricahqPrivateKey,
6
+ } from "@stricahq/bip32ed25519";
7
+ import hash from "hash.js";
8
+
9
+ class PrivateKey extends StricahqPrivateKey {
10
+ constructor(privKey: Buffer, extended: Boolean = true) {
11
+ if (!extended) {
12
+ let extendedSecret = hash.sha512().update(privKey).digest();
13
+ if (extendedSecret[0] && extendedSecret[31]) {
14
+ extendedSecret[0] &= 0b1111_1000;
15
+ extendedSecret[31] &= 0b0011_1111;
16
+ extendedSecret[31] |= 0b0100_0000;
17
+ }
18
+ privKey = Buffer.from(extendedSecret);
19
+ }
20
+ super(privKey);
21
+ }
22
+ }
23
+
24
+ export { PrivateKey, PublicKey, Bip32PrivateKey, Bip32PublicKey };