near_kit/types/
transaction.rs1use borsh::{BorshDeserialize, BorshSerialize};
4
5use super::{AccountId, Action, CryptoHash, PublicKey, SecretKey, Signature};
6
7#[derive(Clone, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
9pub struct Transaction {
10 pub signer_id: AccountId,
12 pub public_key: PublicKey,
14 pub nonce: u64,
16 pub receiver_id: AccountId,
18 pub block_hash: CryptoHash,
20 pub actions: Vec<Action>,
22}
23
24impl Transaction {
25 pub fn new(
27 signer_id: AccountId,
28 public_key: PublicKey,
29 nonce: u64,
30 receiver_id: AccountId,
31 block_hash: CryptoHash,
32 actions: Vec<Action>,
33 ) -> Self {
34 Self {
35 signer_id,
36 public_key,
37 nonce,
38 receiver_id,
39 block_hash,
40 actions,
41 }
42 }
43
44 pub fn get_hash(&self) -> CryptoHash {
46 let bytes = borsh::to_vec(self).expect("transaction serialization should never fail");
47 CryptoHash::hash(&bytes)
48 }
49
50 pub fn get_hash_and_size(&self) -> (CryptoHash, usize) {
52 let bytes = borsh::to_vec(self).expect("transaction serialization should never fail");
53 (CryptoHash::hash(&bytes), bytes.len())
54 }
55
56 pub fn sign(self, signer: &SecretKey) -> SignedTransaction {
58 let hash = self.get_hash();
59 let signature = signer.sign(hash.as_bytes());
60 SignedTransaction {
61 transaction: self,
62 signature,
63 }
64 }
65
66 pub fn complete(self, signature: Signature) -> SignedTransaction {
83 SignedTransaction {
84 transaction: self,
85 signature,
86 }
87 }
88}
89
90#[derive(Clone, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
92pub struct SignedTransaction {
93 pub transaction: Transaction,
95 pub signature: Signature,
97}
98
99impl SignedTransaction {
100 pub fn get_hash(&self) -> CryptoHash {
102 self.transaction.get_hash()
103 }
104
105 pub fn to_bytes(&self) -> Vec<u8> {
107 borsh::to_vec(self).expect("signed transaction serialization should never fail")
108 }
109
110 pub fn to_base64(&self) -> String {
112 use base64::{Engine as _, engine::general_purpose::STANDARD};
113 STANDARD.encode(self.to_bytes())
114 }
115
116 pub fn from_bytes(bytes: &[u8]) -> Result<Self, crate::error::Error> {
128 borsh::from_slice(bytes).map_err(|e| {
129 crate::error::Error::InvalidTransaction(format!(
130 "Failed to deserialize signed transaction: {}",
131 e
132 ))
133 })
134 }
135
136 pub fn from_base64(s: &str) -> Result<Self, crate::error::Error> {
149 use base64::{Engine as _, engine::general_purpose::STANDARD};
150 let bytes = STANDARD.decode(s).map_err(|e| {
151 crate::error::Error::InvalidTransaction(format!("Invalid base64: {}", e))
152 })?;
153 Self::from_bytes(&bytes)
154 }
155}
156
157#[cfg(test)]
158mod tests {
159 use super::*;
160
161 #[test]
162 fn test_transaction_hash() {
163 let secret = SecretKey::generate_ed25519();
164 let public = secret.public_key();
165
166 let tx = Transaction::new(
167 "alice.testnet".parse().unwrap(),
168 public,
169 1,
170 "bob.testnet".parse().unwrap(),
171 CryptoHash::ZERO,
172 vec![],
173 );
174
175 let hash = tx.get_hash();
176 assert!(!hash.is_zero());
177 }
178
179 #[test]
180 fn test_sign_transaction() {
181 let secret = SecretKey::generate_ed25519();
182 let public = secret.public_key();
183
184 let tx = Transaction::new(
185 "alice.testnet".parse().unwrap(),
186 public,
187 1,
188 "bob.testnet".parse().unwrap(),
189 CryptoHash::ZERO,
190 vec![],
191 );
192
193 let signed = tx.sign(&secret);
194 assert!(!signed.to_bytes().is_empty());
195 }
196
197 #[test]
198 fn test_complete_matches_sign() {
199 let secret = SecretKey::generate_ed25519();
200 let public = secret.public_key();
201
202 let tx1 = Transaction::new(
203 "alice.testnet".parse().unwrap(),
204 public.clone(),
205 1,
206 "bob.testnet".parse().unwrap(),
207 CryptoHash::ZERO,
208 vec![],
209 );
210
211 let tx2 = tx1.clone();
212
213 let signed_normal = tx1.sign(&secret);
215
216 let hash = tx2.get_hash();
218 let signature = secret.sign(hash.as_bytes());
219 let signed_complete = tx2.complete(signature);
220
221 assert_eq!(signed_normal.get_hash(), signed_complete.get_hash());
222 assert_eq!(signed_normal.signature, signed_complete.signature);
223 assert_eq!(signed_normal.to_bytes(), signed_complete.to_bytes());
224 }
225}