Line data Source code
1 : import 'dart:typed_data';
2 :
3 : import 'package:collection/collection.dart';
4 : import 'package:walletkit_dart/src/crypto/utxo/entities/payments/pk_script_converter.dart';
5 : import 'package:walletkit_dart/src/crypto/utxo/utils/pubkey_to_address.dart';
6 : import 'package:walletkit_dart/src/crypto/utxo/entities/raw_transaction/input.dart';
7 : import 'package:walletkit_dart/src/crypto/utxo/entities/raw_transaction/output.dart';
8 : import 'package:walletkit_dart/src/utils/int.dart';
9 : import 'package:walletkit_dart/src/utils/var_uint.dart';
10 : import 'package:walletkit_dart/walletkit_dart.dart';
11 :
12 : const SEGWIT_FLAG = 0x01;
13 : const SEGWIT_MARKER = 0x00;
14 :
15 : sealed class RawTransaction {
16 : final int version;
17 : final List<Input> inputs;
18 : final List<Output> outputs;
19 :
20 : /// Mapping of UTXOs to generated inputs
21 : /// Non Null if returned from [buildUnsignedTransaction]
22 : final Map<ElectrumOutput, Input>? inputMap;
23 :
24 1 : BigInt get weight {
25 2 : return inputs.fold(
26 1 : 0.toBI,
27 3 : (prev, input) => prev + input.weight,
28 1 : ) +
29 2 : outputs.fold(
30 1 : 0.toBI,
31 3 : (prev, output) => prev + output.weight,
32 : );
33 : }
34 :
35 : Uint8List get bytes;
36 :
37 18 : String get asHex => bytes.toHex;
38 :
39 0 : int get size => bytes.length;
40 :
41 4 : BigInt get fee => totalInputValue - totalOutputValue;
42 :
43 : // Value of the first output
44 0 : BigInt get targetAmount => outputs.first.value;
45 :
46 1 : BigInt get totalInputValue {
47 2 : return inputs.fold(
48 1 : BigInt.zero,
49 3 : (prev, element) => prev + (element.value ?? BigInt.zero),
50 : );
51 : }
52 :
53 1 : BigInt get totalOutputValue {
54 2 : return outputs.fold(
55 1 : BigInt.zero,
56 3 : (prev, element) => prev + element.value,
57 : );
58 : }
59 :
60 0 : String get txid {
61 0 : final buffer = bytes;
62 0 : final hash = sha256Sha256Hash(buffer);
63 0 : return hash.rev.toHex;
64 : }
65 :
66 3 : Uint8List legacySigHash({
67 : required int index,
68 : required Uint8List prevScriptPubKey,
69 : required int hashType,
70 : }) {
71 3 : final copy = createCopy();
72 :
73 : // clear all scriptSigs
74 12 : for (int i = 0; i < copy.inputs.length; i++) {
75 15 : copy.inputs[i] = copy.inputs[i].addScript(
76 6 : scriptSig: Uint8List.fromList([]),
77 : );
78 : }
79 :
80 : // set scriptSig for inputIndex to prevScriptPubKeyHex
81 15 : copy.inputs[index] = copy.inputs[index].addScript(
82 : scriptSig: prevScriptPubKey,
83 : );
84 :
85 6 : final bytes = copy is EC8RawTransaction ? copy.bytesForSigning : copy.bytes;
86 :
87 9 : final buffer = Uint8List(bytes.length + 4);
88 : var offset = 0;
89 6 : offset += buffer.writeSlice(0, bytes);
90 9 : offset += buffer.bytes.writeUint32(offset, hashType);
91 :
92 3 : final hash = sha256Sha256Hash(buffer);
93 :
94 : return hash;
95 : }
96 :
97 6 : const RawTransaction({
98 : required this.version,
99 : required this.inputs,
100 : required this.outputs,
101 : this.inputMap,
102 : });
103 :
104 : RawTransaction createCopy();
105 :
106 : RawTransaction _addInputs(List<Input>? inputs);
107 :
108 4 : static RawTransaction build({
109 : required int version,
110 : required List<Output> outputs,
111 : required Map<ElectrumOutput, Input> inputMap,
112 : int? lockTime,
113 : int? validFrom,
114 : int? validUntil,
115 : }) {
116 4 : final inputs = inputMap.values;
117 4 : final btcInputs = inputs.whereType<BTCInput>();
118 4 : final btcOutputs = outputs.whereType<BTCOutput>();
119 :
120 7 : if (btcInputs.isNotEmpty && btcOutputs.isNotEmpty && lockTime != null) {
121 3 : return BTCRawTransaction(
122 : version: version,
123 3 : inputs: btcInputs.toList(),
124 3 : outputs: btcOutputs.toList(),
125 : inputMap: inputMap,
126 : lockTime: lockTime,
127 : );
128 : }
129 :
130 1 : final ec8Inputs = inputs.whereType<EC8Input>();
131 1 : final ec8Outputs = outputs.whereType<EC8Output>();
132 :
133 1 : if (ec8Inputs.isNotEmpty &&
134 1 : ec8Outputs.isNotEmpty &&
135 : validFrom != null &&
136 : validUntil != null) {
137 1 : return EC8RawTransaction(
138 : version: version,
139 1 : inputs: ec8Inputs.toList(),
140 1 : outputs: ec8Outputs.toList(),
141 : inputMap: inputMap,
142 : validFrom: validFrom,
143 : validUntil: validUntil,
144 : );
145 : }
146 :
147 0 : throw UnimplementedError();
148 : }
149 :
150 4 : RawTransaction sign({
151 : required Uint8List seed,
152 : required HDWalletPath walletPath,
153 : required UTXONetworkType networkType,
154 : }) {
155 : assert(
156 4 : inputMap != null,
157 : 'Cant sign transaction without inputs',
158 : );
159 :
160 4 : final signedInputs = signInputs(
161 4 : inputs: inputMap!,
162 : walletPath: walletPath,
163 : tx: this,
164 : networkType: networkType,
165 : seed: seed,
166 : );
167 :
168 4 : return _addInputs(signedInputs);
169 : }
170 : }
171 :
172 : ///
173 : /// Raw Transaction Implementation for Bitcoin
174 : ///
175 : class BTCRawTransaction extends RawTransaction {
176 : final int lockTime;
177 :
178 : @override
179 : final List<BTCInput> inputs;
180 :
181 : @override
182 : final List<BTCOutput> outputs;
183 :
184 5 : const BTCRawTransaction({
185 : required super.version,
186 : required this.lockTime,
187 : required this.inputs,
188 : required this.outputs,
189 : super.inputMap,
190 5 : }) : super(
191 : inputs: inputs,
192 : outputs: outputs,
193 : );
194 :
195 5 : bool get hasWitness {
196 20 : return inputs.any((input) => input.hasWitness);
197 : }
198 :
199 3 : Iterable<BTCInput> get segwitInputs {
200 12 : return inputs.where((input) => input.hasWitness);
201 : }
202 :
203 3 : Iterable<BTCInput> get nonSegwitInputs {
204 15 : return inputs.where((input) => input.hasWitness == false);
205 : }
206 :
207 2 : BTCRawTransaction createCopy() {
208 2 : return BTCRawTransaction(
209 2 : version: version,
210 2 : lockTime: lockTime,
211 2 : inputs: inputs,
212 2 : outputs: outputs,
213 : );
214 : }
215 :
216 3 : BTCRawTransaction _addInputs(
217 : List<Input>? inputs,
218 : ) {
219 6 : final signedInputs = inputs?.whereType<BTCInput>().toList() ?? this.inputs;
220 :
221 3 : return BTCRawTransaction(
222 3 : version: version,
223 3 : lockTime: lockTime,
224 : inputs: signedInputs,
225 3 : outputs: outputs,
226 : );
227 : }
228 :
229 2 : factory BTCRawTransaction.fromHex(String hex) {
230 2 : final buffer = hex.hexToBytes;
231 :
232 : var offset = 0;
233 :
234 : /// Version
235 4 : final (version, length) = buffer.bytes.readUint32(offset);
236 2 : offset += length;
237 :
238 : /// Segwit Flag
239 : final isSegwit =
240 10 : buffer[offset] == SEGWIT_MARKER && buffer[offset + 1] == SEGWIT_FLAG;
241 :
242 : if (isSegwit) {
243 2 : offset += 2;
244 : }
245 :
246 : /// Inputs
247 : final (inputLength, inputLengthByteLength) =
248 4 : buffer.bytes.readVarInt(offset);
249 2 : offset += inputLengthByteLength;
250 :
251 2 : final inputs = <BTCInput>[];
252 4 : for (int i = 0; i < inputLength; i++) {
253 4 : final input = BTCInput.fromBuffer(buffer.sublist(offset));
254 4 : offset += input.size;
255 2 : inputs.add(input);
256 : }
257 :
258 : /// Outputs
259 : final (outputLength, outputLengthByteLength) =
260 4 : buffer.bytes.readVarInt(offset);
261 2 : offset += outputLengthByteLength;
262 :
263 2 : final outputs = <BTCOutput>[];
264 :
265 4 : for (int i = 0; i < outputLength; i++) {
266 4 : final output = BTCOutput.fromBuffer(buffer.sublist(offset));
267 4 : offset += output.size;
268 2 : outputs.add(output);
269 : }
270 :
271 : /// Witness
272 : if (isSegwit) {
273 2 : List<(Uint8List, BTCInput)> wittnessScripts = [];
274 :
275 4 : for (final input in inputs) {
276 4 : final (emptyScript, emptyScriptLength) = buffer.bytes.readUint8(offset);
277 2 : if (emptyScript == 0x00) {
278 2 : offset += emptyScriptLength;
279 : continue;
280 : }
281 :
282 : final (wittnessScript, length) =
283 2 : readScriptWittness(buffer: buffer, offset: offset);
284 2 : wittnessScripts.add((wittnessScript, input));
285 2 : offset += length;
286 : }
287 :
288 4 : for (final (wittnessScript, input) in wittnessScripts) {
289 2 : final index = inputs.indexOf(input);
290 4 : inputs[index] = input.addScript(wittnessScript: wittnessScript);
291 : }
292 : }
293 :
294 : /// Locktime
295 4 : final (lockTime, _) = buffer.bytes.readUint32(offset);
296 :
297 2 : return BTCRawTransaction(
298 : version: version,
299 : lockTime: lockTime,
300 : inputs: inputs,
301 : outputs: outputs,
302 : );
303 : }
304 :
305 5 : Uint8List get bytes {
306 20 : final inputBuffers = inputs.map((input) => input.bytes);
307 : const inputLengthByte = 1;
308 5 : final inputsByteLength = inputBuffers.fold(
309 : 0,
310 15 : (prev, buffer) => prev + buffer.length,
311 : );
312 :
313 20 : final outputBuffers = outputs.map((output) => output.bytes);
314 : const outputLengthByte = 1;
315 5 : final outputsByteLength = outputBuffers.fold(
316 : 0,
317 15 : (prev, buffer) => prev + buffer.length,
318 : );
319 :
320 5 : var txByteLength = 4 +
321 5 : inputLengthByte +
322 5 : inputsByteLength +
323 5 : outputLengthByte +
324 5 : outputsByteLength +
325 : 4;
326 :
327 5 : if (hasWitness) {
328 3 : txByteLength += 1; // Segwit Flag
329 3 : txByteLength += 1; // Segwit Marker
330 :
331 9 : txByteLength += segwitInputs.fold(
332 : 0,
333 3 : (prev, input) {
334 6 : assert(input.isSegwit);
335 9 : return prev + input.wittnessScript.length;
336 : },
337 : );
338 9 : txByteLength += nonSegwitInputs.length; // Empty Script
339 : }
340 :
341 : ///
342 : /// Construct Buffer
343 : ///
344 5 : final buffer = Uint8List(txByteLength);
345 : var offset = 0;
346 :
347 : /// Version
348 20 : offset += buffer.bytes.writeUint32(offset, version);
349 :
350 : /// Segwit Flag
351 5 : if (hasWitness) {
352 9 : offset += buffer.bytes.writeUint8(offset, SEGWIT_MARKER);
353 9 : offset += buffer.bytes.writeUint8(offset, SEGWIT_FLAG);
354 : }
355 :
356 : /// Inputs
357 25 : offset += buffer.bytes.writeVarInt(offset, inputs.length);
358 10 : for (final input in inputs) {
359 15 : offset += buffer.writeSlice(offset, input.bytes);
360 : }
361 :
362 : /// Outputs
363 25 : offset += buffer.bytes.writeVarInt(offset, outputs.length);
364 10 : for (final output in outputs) {
365 15 : offset += buffer.writeSlice(offset, output.bytes);
366 : }
367 :
368 : /// Witness
369 5 : if (hasWitness)
370 6 : for (final input in inputs) {
371 3 : if (input.isSegwit) {
372 9 : offset += buffer.writeSlice(offset, input.wittnessScript);
373 : continue;
374 : }
375 :
376 9 : offset += buffer.bytes.writeUint8(offset, 0x00); // Empty Script
377 : }
378 :
379 : /// Locktime
380 20 : offset += buffer.bytes.writeUint32(offset, lockTime);
381 :
382 : return buffer;
383 : }
384 :
385 : ///
386 : /// BIP143 SigHash: https://github.com/bitcoin/bips/blob/master/bip-0143.mediawiki
387 : ///
388 3 : Uint8List bip143sigHash({
389 : required int index,
390 : required Uint8List prevScriptPubKey,
391 : required ElectrumOutput output,
392 : required int hashType,
393 : }) {
394 : ///
395 : /// Always use P2PKH in bip143 sigHash
396 : ///
397 3 : final converter = PublicKeyScriptConverter(prevScriptPubKey);
398 3 : final p2pkhScript = converter.p2pkhScript;
399 :
400 12 : final outputBuffers = outputs.map((output) => output.bytes);
401 3 : final txOutSize = outputBuffers.fold(
402 : 0,
403 9 : (prev, element) => prev + element.length,
404 : );
405 :
406 : ///
407 : /// Inputs
408 : ///
409 12 : var tBuffer = Uint8List(36 * inputs.length);
410 : var tOffset = 0;
411 6 : for (final input in inputs) {
412 9 : tOffset += tBuffer.writeSlice(tOffset, input.txid);
413 12 : tOffset += tBuffer.bytes.writeUint32(tOffset, input.vout);
414 : }
415 :
416 3 : final hashPrevouts = sha256Sha256Hash(tBuffer);
417 :
418 12 : tBuffer = Uint8List(4 * inputs.length);
419 : tOffset = 0;
420 6 : for (final input in inputs) {
421 12 : tOffset += tBuffer.bytes.writeUint32(tOffset, input.sequence);
422 : }
423 3 : final hashSequence = sha256Sha256Hash(tBuffer);
424 :
425 : /// Outputs
426 :
427 3 : tBuffer = Uint8List(txOutSize);
428 : tOffset = 0;
429 :
430 6 : for (final output in outputs) {
431 15 : tOffset += tBuffer.bytes.writeUint64(tOffset, output.value.toInt());
432 9 : tOffset += tBuffer.writeVarSlice(tOffset, output.scriptPubKey);
433 : }
434 3 : final hashOutputs = sha256Sha256Hash(tBuffer);
435 :
436 : /// Final Buffer
437 6 : final inputToSign = inputs[index];
438 3 : final prevScriptPubKeyLength = varSliceSize(p2pkhScript);
439 6 : tBuffer = Uint8List(156 + prevScriptPubKeyLength);
440 : tOffset = 0;
441 :
442 12 : tOffset += tBuffer.bytes.writeUint32(tOffset, version);
443 6 : tOffset += tBuffer.writeSlice(tOffset, hashPrevouts);
444 6 : tOffset += tBuffer.writeSlice(tOffset, hashSequence);
445 :
446 9 : tOffset += tBuffer.writeSlice(tOffset, inputToSign.txid);
447 12 : tOffset += tBuffer.bytes.writeUint32(tOffset, inputToSign.vout);
448 :
449 6 : tOffset += tBuffer.writeVarSlice(tOffset, p2pkhScript);
450 :
451 15 : tOffset += tBuffer.bytes.writeUint64(tOffset, output.value.toInt());
452 12 : tOffset += tBuffer.bytes.writeUint32(tOffset, inputToSign.sequence);
453 :
454 6 : tOffset += tBuffer.writeSlice(tOffset, hashOutputs);
455 :
456 12 : tOffset += tBuffer.bytes.writeUint32(tOffset, lockTime);
457 9 : tOffset += tBuffer.bytes.writeUint32(tOffset, hashType);
458 :
459 3 : final hash = sha256Sha256Hash(tBuffer);
460 :
461 : return hash;
462 : }
463 : }
464 :
465 : ///
466 : /// Raw Transaction Implementation for EC8
467 : ///
468 : class EC8RawTransaction extends RawTransaction {
469 : final List<EC8Input> inputs;
470 : final List<EC8Output> outputs;
471 :
472 : final int validFrom;
473 : final int validUntil;
474 :
475 2 : const EC8RawTransaction({
476 : required super.version,
477 : required this.inputs,
478 : required this.outputs,
479 : required this.validFrom,
480 : required this.validUntil,
481 : super.inputMap,
482 2 : }) : super(
483 : inputs: inputs,
484 : outputs: outputs,
485 : );
486 :
487 1 : String get txid {
488 1 : final buffer = bytesForTxId;
489 1 : final hash = sha256Sha256Hash(buffer);
490 2 : return hash.rev.toHex;
491 : }
492 :
493 1 : @override
494 : Uint8List get bytes {
495 4 : final inputBuffers = inputs.map((input) => input.bytes);
496 : const inputLengthByte = 1;
497 1 : final inputsByteLength = inputBuffers.fold(
498 : 0,
499 3 : (prev, buffer) => prev + buffer.length,
500 : );
501 :
502 4 : final outputBuffers = outputs.map((output) => output.bytes);
503 : const outputLengthByte = 1;
504 1 : final outputsByteLength = outputBuffers.fold(
505 : 0,
506 3 : (prev, buffer) => prev + buffer.length,
507 : );
508 :
509 1 : var txByteLength = 4 +
510 1 : inputLengthByte +
511 1 : inputsByteLength +
512 1 : outputLengthByte +
513 1 : outputsByteLength +
514 1 : 8 +
515 1 : 4 +
516 1 : 8 +
517 : 4;
518 :
519 : ///
520 : /// Construct Buffer
521 : ///
522 1 : final buffer = Uint8List(txByteLength);
523 : var offset = 0;
524 :
525 : /// Version
526 4 : offset += buffer.bytes.writeUint32(offset, version);
527 :
528 : /// Inputs
529 5 : offset += buffer.bytes.writeVarInt(offset, inputs.length);
530 2 : for (final input in inputs) {
531 3 : offset += buffer.writeSlice(offset, input.bytes);
532 : }
533 :
534 : /// Outputs
535 5 : offset += buffer.bytes.writeVarInt(offset, outputs.length);
536 2 : for (final output in outputs) {
537 3 : offset += buffer.writeSlice(offset, output.bytes);
538 : }
539 :
540 : /// Fee
541 5 : offset += buffer.bytes.writeUint64(offset, fee.toInt());
542 :
543 : /// Weight
544 5 : offset += buffer.bytes.writeUint32(offset, weight.toInt());
545 :
546 : /// ValidFrom
547 4 : offset += buffer.bytes.writeUint64(offset, validFrom);
548 :
549 : /// ValidUntil
550 4 : offset += buffer.bytes.writeUint32(offset, validUntil);
551 :
552 : return buffer;
553 : }
554 :
555 1 : Uint8List get bytesForTxId {
556 4 : final inputBuffers = inputs.map((input) => input.bytesForTxId);
557 : const inputLengthByte = 1;
558 1 : final inputsByteLength = inputBuffers.fold(
559 : 0,
560 3 : (prev, buffer) => prev + buffer.length,
561 : );
562 :
563 4 : final outputBuffers = outputs.map((output) => output.bytesForTxId);
564 : const outputLengthByte = 1;
565 1 : final outputsByteLength = outputBuffers.fold(
566 : 0,
567 3 : (prev, buffer) => prev + buffer.length,
568 : );
569 :
570 1 : var txByteLength = 4 +
571 1 : inputLengthByte +
572 1 : inputsByteLength +
573 1 : outputLengthByte +
574 1 : outputsByteLength +
575 1 : 8 +
576 1 : 4 +
577 1 : 8 +
578 : 4;
579 :
580 : ///
581 : /// Construct Buffer
582 : ///
583 1 : final buffer = Uint8List(txByteLength);
584 : var offset = 0;
585 :
586 : /// Version
587 4 : offset += buffer.bytes.writeUint32(offset, version);
588 :
589 : /// Inputs
590 5 : offset += buffer.bytes.writeVarInt(offset, inputs.length);
591 2 : for (final input in inputs) {
592 3 : offset += buffer.writeSlice(offset, input.bytesForTxId);
593 : }
594 :
595 : /// Outputs
596 5 : offset += buffer.bytes.writeVarInt(offset, outputs.length);
597 2 : for (final output in outputs) {
598 3 : offset += buffer.writeSlice(offset, output.bytesForTxId);
599 : }
600 :
601 : /// Fee
602 5 : offset += buffer.bytes.writeUint64(offset, fee.toInt());
603 :
604 : /// Weight
605 3 : offset += buffer.bytes.writeUint32(offset, 0);
606 :
607 : /// ValidFrom
608 4 : offset += buffer.bytes.writeUint64(offset, validFrom);
609 :
610 : /// ValidUntil
611 4 : offset += buffer.bytes.writeUint32(offset, validUntil);
612 :
613 : return buffer;
614 : }
615 :
616 : ///
617 : /// Doesnt include weight
618 : ///
619 1 : Uint8List get bytesForSigning {
620 : /// Double SHA256 Hash of all inputs
621 2 : final inputBuffers = inputs.map(
622 2 : (input) => input.bytesForSigning(
623 : withWeight: true,
624 : withScript: false,
625 : ),
626 : );
627 1 : final combinedInputBuffers = inputBuffers.fold(
628 1 : Uint8List(0),
629 3 : (prev, buffer) => Uint8List.fromList(prev + buffer),
630 : );
631 1 : final hashInputs = sha256Sha256Hash(combinedInputBuffers);
632 :
633 : /// Double SHA256 Hash of all outputs
634 4 : final outputBuffers = outputs.map((output) => output.bytes);
635 1 : final combinedOutputBuffers = outputBuffers.fold(
636 1 : Uint8List(0),
637 3 : (prev, buffer) => Uint8List.fromList(prev + buffer),
638 : );
639 1 : final hashOutputs = sha256Sha256Hash(combinedOutputBuffers);
640 :
641 : ///
642 : /// Input to be signed
643 : ///
644 :
645 : /// Input to be signed has a scriptSig all other inputs have empty scriptSigs
646 2 : final input = inputs.singleWhereOrNull(
647 4 : (input) => input.scriptSig.length != 0,
648 : );
649 : if (input == null) {
650 0 : throw Exception('No input to be signed');
651 : }
652 :
653 1 : final inputBytes = input.bytesForSigning(
654 : withWeight: false,
655 : withScript: true,
656 : );
657 :
658 : ///
659 : /// Construct Buffer
660 : ///
661 1 : final txByteLength = 4 +
662 2 : hashInputs.length +
663 2 : hashOutputs.length +
664 2 : inputBytes.length +
665 1 : 8 +
666 1 : 8 +
667 : 4;
668 1 : final buffer = Uint8List(txByteLength);
669 : var offset = 0;
670 :
671 : /// Version
672 4 : offset += buffer.bytes.writeUint32(offset, version);
673 :
674 : /// HashInputs
675 2 : offset += buffer.writeSlice(offset, hashInputs);
676 :
677 : /// Input to be signed
678 2 : offset += buffer.writeSlice(
679 : offset,
680 : inputBytes,
681 : );
682 :
683 : /// HashOutputs
684 2 : offset += buffer.writeSlice(offset, hashOutputs);
685 :
686 : /// Fee
687 5 : offset += buffer.bytes.writeUint64(offset, fee.toInt());
688 :
689 : /// ValidFrom
690 4 : offset += buffer.bytes.writeUint64(offset, validFrom);
691 :
692 : /// ValidUntil
693 4 : offset += buffer.bytes.writeUint32(offset, validUntil);
694 :
695 : return buffer;
696 : }
697 :
698 1 : EC8RawTransaction createCopy() {
699 1 : return EC8RawTransaction(
700 1 : version: version,
701 1 : inputs: inputs,
702 1 : outputs: outputs,
703 1 : validFrom: validFrom,
704 1 : validUntil: validUntil,
705 : );
706 : }
707 :
708 1 : EC8RawTransaction _addInputs(
709 : List<Input>? inputs,
710 : ) {
711 2 : final signedInputs = inputs?.whereType<EC8Input>().toList() ?? this.inputs;
712 :
713 1 : return EC8RawTransaction(
714 1 : version: version,
715 : inputs: signedInputs,
716 1 : outputs: outputs,
717 1 : validFrom: validFrom,
718 1 : validUntil: validUntil,
719 : );
720 : }
721 :
722 2 : factory EC8RawTransaction.fromHex(String hex) {
723 2 : final buffer = hex.hexToBytes;
724 :
725 : var offset = 0;
726 :
727 : /// Version
728 4 : final (version, length) = buffer.bytes.readUint32(offset);
729 2 : offset += length;
730 :
731 : /// Inputs
732 : final (inputLength, inputLengthByteLength) =
733 4 : buffer.bytes.readVarInt(offset);
734 2 : offset += inputLengthByteLength;
735 :
736 2 : final inputs = <EC8Input>[];
737 4 : for (int i = 0; i < inputLength; i++) {
738 4 : final input = EC8Input.fromBuffer(buffer.sublist(offset));
739 4 : offset += input.size;
740 2 : inputs.add(input);
741 : }
742 :
743 : /// Outputs
744 : final (outputLength, outputLengthByteLength) =
745 4 : buffer.bytes.readVarInt(offset);
746 2 : offset += outputLengthByteLength;
747 :
748 2 : final outputs = <EC8Output>[];
749 :
750 4 : for (int i = 0; i < outputLength; i++) {
751 4 : final output = EC8Output.fromBuffer(buffer.sublist(offset));
752 4 : offset += output.size;
753 2 : outputs.add(output);
754 : }
755 :
756 : /// Fee (is ignored since it is calculated from inputs and outputs)
757 4 : final (_, feeOffset) = buffer.bytes.readUint64(offset);
758 2 : offset += feeOffset;
759 :
760 : /// Weight (is ignored since it is calculated)
761 4 : final (_, weightOffset) = buffer.bytes.readUint32(offset);
762 2 : offset += weightOffset;
763 :
764 : /// ValidFrom
765 4 : final (validFrom, validFromOffset) = buffer.bytes.readUint64(offset);
766 2 : offset += validFromOffset;
767 :
768 : /// ValidUntil
769 4 : final (validUntil, validUntilOffset) = buffer.bytes.readUint32(offset);
770 2 : offset += validUntilOffset;
771 :
772 2 : return EC8RawTransaction(
773 : version: version,
774 : inputs: inputs,
775 : outputs: outputs,
776 : validFrom: validFrom,
777 : validUntil: validUntil,
778 : );
779 : }
780 : }
|