1use serde::{Deserialize, Serialize};
10use serde_big_array::BigArray;
11
12use crate::agreement::AgreementTxData;
13use crate::docclass::DocClassTxData;
14use crate::employment::EmploymentTxData;
15use crate::equity::EquityTxData;
16use crate::finance::FinanceTxData;
17use crate::healthcare::HealthcareTxData;
18use crate::legal::LegalTxData;
19use crate::messaging::MessagingTxData;
20use crate::policy_account::PolicyAccountTxData;
21use crate::property::PropertyTxData;
22use crate::staking::StakingTxData;
23use crate::tax::TaxTxData;
24use crate::{Address, Balance, ChainId, Hash, Nonce};
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
28#[repr(u8)]
29pub enum TxType {
30 Transfer = 0,
32 Nft = 1,
34 Token = 2,
36 ContractDeploy = 3,
38 ContractCall = 4,
40 Staking = 5,
42 Messaging = 6,
44 DocClass = 7,
46 Tax = 8,
48 Equity = 9,
50 Agreement = 10,
52 Legal = 11,
54 Property = 12,
56 Healthcare = 13,
58 Employment = 14,
60 Finance = 15,
62 PolicyAccount = 16,
64 NodeRegistry = 17,
66 StorageMetadata = 18,
68 NodeRegistryV2 = 19,
70 StorageMetadataV2 = 20,
72 InferenceAttestation = 21,
75 Education = 22,
78}
79
80impl TxType {
81 pub fn from_byte(b: u8) -> Option<Self> {
83 match b {
84 0 => Some(TxType::Transfer),
85 1 => Some(TxType::Nft),
86 2 => Some(TxType::Token),
87 3 => Some(TxType::ContractDeploy),
88 4 => Some(TxType::ContractCall),
89 5 => Some(TxType::Staking),
90 6 => Some(TxType::Messaging),
91 7 => Some(TxType::DocClass),
92 8 => Some(TxType::Tax),
93 9 => Some(TxType::Equity),
94 10 => Some(TxType::Agreement),
95 11 => Some(TxType::Legal),
96 12 => Some(TxType::Property),
97 13 => Some(TxType::Healthcare),
98 14 => Some(TxType::Employment),
99 15 => Some(TxType::Finance),
100 16 => Some(TxType::PolicyAccount),
101 17 => Some(TxType::NodeRegistry),
102 18 => Some(TxType::StorageMetadata),
103 19 => Some(TxType::NodeRegistryV2),
104 20 => Some(TxType::StorageMetadataV2),
105 21 => Some(TxType::InferenceAttestation),
106 22 => Some(TxType::Education),
107 _ => None,
108 }
109 }
110}
111
112#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
114pub struct Transaction {
115 pub chain_id: ChainId,
117 pub from: Address,
119 pub to: Address,
121 pub amount: Balance,
123 pub fee: Balance,
125 pub nonce: Nonce,
127}
128
129#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
131pub struct NftTxData {
132 pub collection_id: [u8; 32],
134 pub token_id: u64,
136 pub operation: NftOperation,
138 pub data: Vec<u8>,
140}
141
142#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
144#[repr(u8)]
145pub enum NftOperation {
146 CreateCollection = 0,
148 Mint = 1,
150 MintDocument = 2,
152 BatchMint = 3,
154 Transfer = 4,
156 Approve = 5,
158 SetApprovalForAll = 6,
160 Burn = 7,
162 UpdateMetadata = 8,
164 TransferCollectionOwnership = 9,
166 UpdateCollectionConfig = 10,
168 LockToken = 11,
170 UnlockToken = 12,
172}
173
174#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
176#[repr(u8)]
177pub enum TokenOperation {
178 Create = 0,
180 Mint = 1,
182 Burn = 2,
184 Transfer = 3,
186 Approve = 4,
188 TransferFrom = 5,
190 Pause = 6,
192 Unpause = 7,
194 TransferOwnership = 8,
196 AddMinter = 9,
198 RemoveMinter = 10,
200}
201
202impl TokenOperation {
203 pub fn from_byte(b: u8) -> Option<Self> {
205 match b {
206 0 => Some(TokenOperation::Create),
207 1 => Some(TokenOperation::Mint),
208 2 => Some(TokenOperation::Burn),
209 3 => Some(TokenOperation::Transfer),
210 4 => Some(TokenOperation::Approve),
211 5 => Some(TokenOperation::TransferFrom),
212 6 => Some(TokenOperation::Pause),
213 7 => Some(TokenOperation::Unpause),
214 8 => Some(TokenOperation::TransferOwnership),
215 9 => Some(TokenOperation::AddMinter),
216 10 => Some(TokenOperation::RemoveMinter),
217 _ => None,
218 }
219 }
220
221 pub fn requires_ownership(&self) -> bool {
223 matches!(
224 self,
225 TokenOperation::Pause
226 | TokenOperation::Unpause
227 | TokenOperation::TransferOwnership
228 | TokenOperation::AddMinter
229 | TokenOperation::RemoveMinter
230 )
231 }
232
233 pub fn requires_minter(&self) -> bool {
235 matches!(self, TokenOperation::Mint)
236 }
237}
238
239#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
241pub struct TokenTxData {
242 pub token_id: [u8; 32],
244 pub operation: TokenOperation,
246 pub data: Vec<u8>,
248}
249
250#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
252pub struct ContractDeployData {
253 pub code: Vec<u8>,
255 pub init_method: String,
257 pub init_args: Vec<u8>,
259 pub value: Balance,
261 pub gas_limit: u64,
263}
264
265#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
267pub struct ContractCallData {
268 pub contract: Address,
270 pub method: String,
272 pub args: Vec<u8>,
274 pub value: Balance,
276 pub gas_limit: u64,
278}
279
280impl NftOperation {
281 pub fn from_byte(b: u8) -> Option<Self> {
283 match b {
284 0 => Some(NftOperation::CreateCollection),
285 1 => Some(NftOperation::Mint),
286 2 => Some(NftOperation::MintDocument),
287 3 => Some(NftOperation::BatchMint),
288 4 => Some(NftOperation::Transfer),
289 5 => Some(NftOperation::Approve),
290 6 => Some(NftOperation::SetApprovalForAll),
291 7 => Some(NftOperation::Burn),
292 8 => Some(NftOperation::UpdateMetadata),
293 9 => Some(NftOperation::TransferCollectionOwnership),
294 10 => Some(NftOperation::UpdateCollectionConfig),
295 11 => Some(NftOperation::LockToken),
296 12 => Some(NftOperation::UnlockToken),
297 _ => None,
298 }
299 }
300
301 pub fn is_collection_creation(&self) -> bool {
303 matches!(self, NftOperation::CreateCollection)
304 }
305
306 pub fn requires_token_ownership(&self) -> bool {
308 matches!(
309 self,
310 NftOperation::Transfer
311 | NftOperation::Approve
312 | NftOperation::Burn
313 | NftOperation::UpdateMetadata
314 | NftOperation::LockToken
315 | NftOperation::UnlockToken
316 )
317 }
318
319 pub fn requires_collection_ownership(&self) -> bool {
321 matches!(
322 self,
323 NftOperation::Mint
324 | NftOperation::MintDocument
325 | NftOperation::BatchMint
326 | NftOperation::TransferCollectionOwnership
327 | NftOperation::UpdateCollectionConfig
328 )
329 }
330}
331
332#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
334pub struct TransactionV2 {
335 pub chain_id: ChainId,
337 pub from: Address,
339 pub fee: Balance,
341 pub nonce: Nonce,
343 pub payload: TxPayload,
345}
346
347#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
349pub enum TxPayload {
350 Transfer {
352 to: Address,
354 amount: Balance,
356 },
357 Nft(NftTxData),
359 Token(TokenTxData),
361 ContractDeploy(ContractDeployData),
363 ContractCall(ContractCallData),
365 Staking(StakingTxData),
367 Messaging(MessagingTxData),
369 DocClass(DocClassTxData),
371 Tax(TaxTxData),
373 Equity(EquityTxData),
375 Agreement(AgreementTxData),
377 Legal(LegalTxData),
379 Property(PropertyTxData),
381 Healthcare(HealthcareTxData),
383 Employment(EmploymentTxData),
385 Finance(FinanceTxData),
387 PolicyAccount(PolicyAccountTxData),
389 NodeRegistry(crate::node_registry::NodeRegistryTxData),
391 StorageMetadata(crate::storage_metadata::StorageMetadataTxData),
393 NodeRegistryV2(crate::node_registry::NodeRegistryV2TxData),
395 StorageMetadataV2(crate::storage_metadata::StorageMetadataV2TxData),
397 InferenceAttestation(crate::inference_attestation::InferenceAttestationTxData),
404 Education(crate::education::EducationTxData),
410}
411
412impl TransactionV2 {
413 pub fn transfer(
415 chain_id: ChainId,
416 from: Address,
417 to: Address,
418 amount: Balance,
419 fee: Balance,
420 nonce: Nonce,
421 ) -> Self {
422 Self {
423 chain_id,
424 from,
425 fee,
426 nonce,
427 payload: TxPayload::Transfer { to, amount },
428 }
429 }
430
431 pub fn nft(
433 chain_id: ChainId,
434 from: Address,
435 fee: Balance,
436 nonce: Nonce,
437 nft_data: NftTxData,
438 ) -> Self {
439 Self {
440 chain_id,
441 from,
442 fee,
443 nonce,
444 payload: TxPayload::Nft(nft_data),
445 }
446 }
447
448 pub fn token(
450 chain_id: ChainId,
451 from: Address,
452 fee: Balance,
453 nonce: Nonce,
454 token_data: TokenTxData,
455 ) -> Self {
456 Self {
457 chain_id,
458 from,
459 fee,
460 nonce,
461 payload: TxPayload::Token(token_data),
462 }
463 }
464
465 pub fn contract_deploy(
467 chain_id: ChainId,
468 from: Address,
469 fee: Balance,
470 nonce: Nonce,
471 deploy_data: ContractDeployData,
472 ) -> Self {
473 Self {
474 chain_id,
475 from,
476 fee,
477 nonce,
478 payload: TxPayload::ContractDeploy(deploy_data),
479 }
480 }
481
482 pub fn contract_call(
484 chain_id: ChainId,
485 from: Address,
486 fee: Balance,
487 nonce: Nonce,
488 call_data: ContractCallData,
489 ) -> Self {
490 Self {
491 chain_id,
492 from,
493 fee,
494 nonce,
495 payload: TxPayload::ContractCall(call_data),
496 }
497 }
498
499 pub fn staking(
501 chain_id: ChainId,
502 from: Address,
503 fee: Balance,
504 nonce: Nonce,
505 staking_data: StakingTxData,
506 ) -> Self {
507 Self {
508 chain_id,
509 from,
510 fee,
511 nonce,
512 payload: TxPayload::Staking(staking_data),
513 }
514 }
515
516 pub fn messaging(
518 chain_id: ChainId,
519 from: Address,
520 fee: Balance,
521 nonce: Nonce,
522 messaging_data: MessagingTxData,
523 ) -> Self {
524 Self {
525 chain_id,
526 from,
527 fee,
528 nonce,
529 payload: TxPayload::Messaging(messaging_data),
530 }
531 }
532
533 pub fn tx_type(&self) -> TxType {
535 match &self.payload {
536 TxPayload::Transfer { .. } => TxType::Transfer,
537 TxPayload::Nft(_) => TxType::Nft,
538 TxPayload::Token(_) => TxType::Token,
539 TxPayload::ContractDeploy(_) => TxType::ContractDeploy,
540 TxPayload::ContractCall(_) => TxType::ContractCall,
541 TxPayload::Staking(_) => TxType::Staking,
542 TxPayload::Messaging(_) => TxType::Messaging,
543 TxPayload::DocClass(_) => TxType::DocClass,
544 TxPayload::Tax(_) => TxType::Tax,
545 TxPayload::Equity(_) => TxType::Equity,
546 TxPayload::Agreement(_) => TxType::Agreement,
547 TxPayload::Legal(_) => TxType::Legal,
548 TxPayload::Property(_) => TxType::Property,
549 TxPayload::Healthcare(_) => TxType::Healthcare,
550 TxPayload::Employment(_) => TxType::Employment,
551 TxPayload::Finance(_) => TxType::Finance,
552 TxPayload::PolicyAccount(_) => TxType::PolicyAccount,
553 TxPayload::NodeRegistry(_) => TxType::NodeRegistry,
554 TxPayload::StorageMetadata(_) => TxType::StorageMetadata,
555 TxPayload::NodeRegistryV2(_) => TxType::NodeRegistryV2,
556 TxPayload::StorageMetadataV2(_) => TxType::StorageMetadataV2,
557 TxPayload::InferenceAttestation(_) => TxType::InferenceAttestation,
558 TxPayload::Education(_) => TxType::Education,
559 }
560 }
561
562 pub fn signing_hash(&self) -> Hash {
564 let bytes = bincode::serialize(self).expect("TransactionV2 serialization should not fail");
565 Hash::hash(&bytes)
566 }
567
568 pub fn to_bytes(&self) -> Vec<u8> {
570 bincode::serialize(self).expect("TransactionV2 serialization should not fail")
571 }
572
573 pub fn from_bytes(bytes: &[u8]) -> Result<Self, bincode::Error> {
575 bincode::deserialize(bytes)
576 }
577
578 pub fn recipient(&self) -> Option<Address> {
580 match &self.payload {
581 TxPayload::Transfer { to, .. } => Some(*to),
582 TxPayload::ContractCall(data) => Some(data.contract),
583 TxPayload::Nft(_) => None,
584 TxPayload::Token(_) => None,
585 TxPayload::ContractDeploy(_) => None,
586 TxPayload::Staking(_) => None,
587 TxPayload::Messaging(_) => None, TxPayload::DocClass(data) => Some(data.recipient),
589 TxPayload::Tax(data) => Some(data.recipient),
590 TxPayload::Equity(data) => Some(data.recipient),
591 TxPayload::Agreement(data) => Some(data.recipient),
592 TxPayload::Legal(data) => Some(data.recipient),
593 TxPayload::Property(data) => Some(data.recipient),
594 TxPayload::Healthcare(data) => Some(data.recipient),
595 TxPayload::Employment(data) => Some(data.recipient),
596 TxPayload::Finance(data) => Some(data.recipient),
597 TxPayload::PolicyAccount(data) => Some(data.recipient),
598 TxPayload::NodeRegistry(_) => None,
599 TxPayload::StorageMetadata(_) => None,
600 TxPayload::NodeRegistryV2(_) => None,
601 TxPayload::StorageMetadataV2(_) => None,
602 TxPayload::InferenceAttestation(_) => None, TxPayload::Education(data) => Some(data.recipient), }
605 }
606
607 pub fn amount(&self) -> Balance {
609 match &self.payload {
610 TxPayload::Transfer { amount, .. } => *amount,
611 TxPayload::ContractDeploy(data) => data.value,
612 TxPayload::ContractCall(data) => data.value,
613 TxPayload::Nft(_) => 0,
614 TxPayload::Token(_) => 0,
615 TxPayload::Staking(_) => 0,
616 TxPayload::Messaging(_) => 0, TxPayload::DocClass(_) => 0, TxPayload::Tax(_) => 0, TxPayload::Equity(_) => 0, TxPayload::Agreement(_) => 0, TxPayload::Legal(_) => 0, TxPayload::Property(_) => 0, TxPayload::Healthcare(_) => 0, TxPayload::Employment(_) => 0, TxPayload::Finance(_) => 0, TxPayload::PolicyAccount(_) => 0, TxPayload::NodeRegistry(_) => 0, TxPayload::StorageMetadata(_) => 0, TxPayload::NodeRegistryV2(_) => 0, TxPayload::StorageMetadataV2(_) => 0, TxPayload::InferenceAttestation(_) => 0, TxPayload::Education(_) => 0, }
634 }
635
636 pub fn to_legacy(&self) -> Option<Transaction> {
638 match &self.payload {
639 TxPayload::Transfer { to, amount } => Some(Transaction {
640 chain_id: self.chain_id,
641 from: self.from,
642 to: *to,
643 amount: *amount,
644 fee: self.fee,
645 nonce: self.nonce,
646 }),
647 _ => None,
648 }
649 }
650
651 pub fn deploy_data(&self) -> Option<&ContractDeployData> {
653 match &self.payload {
654 TxPayload::ContractDeploy(data) => Some(data),
655 _ => None,
656 }
657 }
658
659 pub fn call_data(&self) -> Option<&ContractCallData> {
661 match &self.payload {
662 TxPayload::ContractCall(data) => Some(data),
663 _ => None,
664 }
665 }
666
667 pub fn is_contract(&self) -> bool {
669 matches!(
670 self.payload,
671 TxPayload::ContractDeploy(_) | TxPayload::ContractCall(_)
672 )
673 }
674
675 pub fn token_data(&self) -> Option<&TokenTxData> {
677 match &self.payload {
678 TxPayload::Token(data) => Some(data),
679 _ => None,
680 }
681 }
682
683 pub fn staking_data(&self) -> Option<&StakingTxData> {
685 match &self.payload {
686 TxPayload::Staking(data) => Some(data),
687 _ => None,
688 }
689 }
690
691 pub fn is_staking(&self) -> bool {
693 matches!(self.payload, TxPayload::Staking(_))
694 }
695
696 pub fn messaging_data(&self) -> Option<&MessagingTxData> {
698 match &self.payload {
699 TxPayload::Messaging(data) => Some(data),
700 _ => None,
701 }
702 }
703
704 pub fn is_messaging(&self) -> bool {
706 matches!(self.payload, TxPayload::Messaging(_))
707 }
708
709 pub fn docclass_data(&self) -> Option<&DocClassTxData> {
711 match &self.payload {
712 TxPayload::DocClass(data) => Some(data),
713 _ => None,
714 }
715 }
716
717 pub fn is_docclass(&self) -> bool {
719 matches!(self.payload, TxPayload::DocClass(_))
720 }
721}
722
723impl Transaction {
724 pub fn new(
726 chain_id: ChainId,
727 from: Address,
728 to: Address,
729 amount: Balance,
730 fee: Balance,
731 nonce: Nonce,
732 ) -> Self {
733 Self {
734 chain_id,
735 from,
736 to,
737 amount,
738 fee,
739 nonce,
740 }
741 }
742
743 pub fn signing_hash(&self) -> Hash {
746 let bytes = bincode::serialize(self).expect("Transaction serialization should not fail");
748 Hash::hash(&bytes)
749 }
750
751 pub fn to_bytes(&self) -> Vec<u8> {
753 bincode::serialize(self).expect("Transaction serialization should not fail")
754 }
755
756 pub fn from_bytes(bytes: &[u8]) -> Result<Self, bincode::Error> {
758 bincode::deserialize(bytes)
759 }
760
761 pub fn total_cost(&self) -> Balance {
763 self.amount.saturating_add(self.fee)
764 }
765}
766
767#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
769pub enum TxInner {
770 Legacy(Transaction),
772 V2(TransactionV2),
774}
775
776#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
779pub struct SignedTransaction {
780 pub inner: TxInner,
782 #[serde(with = "BigArray")]
784 pub signature: [u8; 64],
785 pub public_key: [u8; 32],
787}
788
789impl SignedTransaction {
790 pub fn new(tx: Transaction, signature: [u8; 64], public_key: [u8; 32]) -> Self {
792 Self {
793 inner: TxInner::Legacy(tx),
794 signature,
795 public_key,
796 }
797 }
798
799 pub fn new_v2(tx: TransactionV2, signature: [u8; 64], public_key: [u8; 32]) -> Self {
801 Self {
802 inner: TxInner::V2(tx),
803 signature,
804 public_key,
805 }
806 }
807
808 pub fn tx_type(&self) -> TxType {
810 match &self.inner {
811 TxInner::Legacy(_) => TxType::Transfer,
812 TxInner::V2(tx) => tx.tx_type(),
813 }
814 }
815
816 pub fn is_nft(&self) -> bool {
818 self.tx_type() == TxType::Nft
819 }
820
821 pub fn is_token(&self) -> bool {
823 self.tx_type() == TxType::Token
824 }
825
826 pub fn nft_data(&self) -> Option<&NftTxData> {
828 match &self.inner {
829 TxInner::V2(tx) => match &tx.payload {
830 TxPayload::Nft(data) => Some(data),
831 _ => None,
832 },
833 _ => None,
834 }
835 }
836
837 pub fn token_data(&self) -> Option<&TokenTxData> {
839 match &self.inner {
840 TxInner::V2(tx) => match &tx.payload {
841 TxPayload::Token(data) => Some(data),
842 _ => None,
843 },
844 _ => None,
845 }
846 }
847
848 pub fn staking_data(&self) -> Option<&StakingTxData> {
850 match &self.inner {
851 TxInner::V2(tx) => match &tx.payload {
852 TxPayload::Staking(data) => Some(data),
853 _ => None,
854 },
855 _ => None,
856 }
857 }
858
859 pub fn is_staking(&self) -> bool {
861 self.tx_type() == TxType::Staking
862 }
863
864 pub fn messaging_data(&self) -> Option<&MessagingTxData> {
866 match &self.inner {
867 TxInner::V2(tx) => match &tx.payload {
868 TxPayload::Messaging(data) => Some(data),
869 _ => None,
870 },
871 _ => None,
872 }
873 }
874
875 pub fn is_messaging(&self) -> bool {
877 self.tx_type() == TxType::Messaging
878 }
879
880 pub fn hash(&self) -> Hash {
882 let bytes =
883 bincode::serialize(self).expect("SignedTransaction serialization should not fail");
884 Hash::hash(&bytes)
885 }
886
887 pub fn signing_hash(&self) -> Hash {
889 match &self.inner {
890 TxInner::Legacy(tx) => tx.signing_hash(),
891 TxInner::V2(tx) => tx.signing_hash(),
892 }
893 }
894
895 pub fn to_bytes(&self) -> Vec<u8> {
897 bincode::serialize(self).expect("SignedTransaction serialization should not fail")
898 }
899
900 pub fn from_bytes(bytes: &[u8]) -> Result<Self, bincode::Error> {
902 bincode::deserialize(bytes)
903 }
904
905 pub fn to_hex(&self) -> String {
907 hex::encode(self.to_bytes())
908 }
909
910 pub fn from_hex(s: &str) -> Result<Self, String> {
912 let s = s.strip_prefix("0x").unwrap_or(s);
913 let bytes = hex::decode(s).map_err(|e| e.to_string())?;
914 Self::from_bytes(&bytes).map_err(|e| e.to_string())
915 }
916
917 pub fn sender(&self) -> Address {
919 match &self.inner {
920 TxInner::Legacy(tx) => tx.from,
921 TxInner::V2(tx) => tx.from,
922 }
923 }
924
925 pub fn chain_id(&self) -> ChainId {
927 match &self.inner {
928 TxInner::Legacy(tx) => tx.chain_id,
929 TxInner::V2(tx) => tx.chain_id,
930 }
931 }
932
933 pub fn fee(&self) -> Balance {
935 match &self.inner {
936 TxInner::Legacy(tx) => tx.fee,
937 TxInner::V2(tx) => tx.fee,
938 }
939 }
940
941 pub fn nonce(&self) -> Nonce {
943 match &self.inner {
944 TxInner::Legacy(tx) => tx.nonce,
945 TxInner::V2(tx) => tx.nonce,
946 }
947 }
948
949 pub fn amount(&self) -> Balance {
951 match &self.inner {
952 TxInner::Legacy(tx) => tx.amount,
953 TxInner::V2(tx) => tx.amount(),
954 }
955 }
956
957 pub fn recipient(&self) -> Option<Address> {
959 match &self.inner {
960 TxInner::Legacy(tx) => Some(tx.to),
961 TxInner::V2(tx) => tx.recipient(),
962 }
963 }
964
965 pub fn signer_address(&self) -> Address {
967 Address::from_public_key(&self.public_key)
968 }
969
970 pub fn verify_signer(&self) -> bool {
972 self.signer_address() == self.sender()
973 }
974
975 pub fn legacy_tx(&self) -> Option<&Transaction> {
978 match &self.inner {
979 TxInner::Legacy(tx) => Some(tx),
980 TxInner::V2(_) => None,
981 }
982 }
983
984 pub fn inner(&self) -> &TxInner {
987 &self.inner
988 }
989}
990
991impl SignedTransaction {
993 #[deprecated(note = "Use sender(), fee(), nonce() etc. or inner() instead")]
996 pub fn tx(&self) -> &Transaction {
997 match &self.inner {
998 TxInner::Legacy(tx) => tx,
999 TxInner::V2(_) => panic!("Cannot access legacy tx field on V2 transaction"),
1000 }
1001 }
1002}
1003
1004#[cfg(test)]
1005mod tests {
1006 use super::*;
1007
1008 fn sample_tx() -> Transaction {
1009 Transaction::new(
1010 1, Address::from_hex("0x0000000000000000000000000000000000000001").unwrap(),
1012 Address::from_hex("0x0000000000000000000000000000000000000002").unwrap(),
1013 1000,
1014 10,
1015 0,
1016 )
1017 }
1018
1019 #[test]
1020 fn test_signing_hash_deterministic() {
1021 let tx = sample_tx();
1022 let h1 = tx.signing_hash();
1023 let h2 = tx.signing_hash();
1024 assert_eq!(h1, h2);
1025 }
1026
1027 #[test]
1028 fn test_different_nonce_different_hash() {
1029 let tx1 = sample_tx();
1030 let mut tx2 = sample_tx();
1031 tx2.nonce = 1;
1032 assert_ne!(tx1.signing_hash(), tx2.signing_hash());
1033 }
1034
1035 #[test]
1036 fn test_total_cost() {
1037 let tx = sample_tx();
1038 assert_eq!(tx.total_cost(), 1010);
1039 }
1040
1041 #[test]
1042 fn test_serialization_roundtrip() {
1043 let tx = sample_tx();
1044 let bytes = tx.to_bytes();
1045 let tx2 = Transaction::from_bytes(&bytes).unwrap();
1046 assert_eq!(tx, tx2);
1047 }
1048
1049 #[test]
1050 fn test_signed_tx_hex_roundtrip() {
1051 let tx = sample_tx();
1052 let signed = SignedTransaction::new(tx, [0u8; 64], [0u8; 32]);
1053 let hex = signed.to_hex();
1054 let signed2 = SignedTransaction::from_hex(&hex).unwrap();
1055 assert_eq!(signed, signed2);
1056 }
1057}