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