near_api/transactions.rs
1use std::sync::Arc;
2
3use near_api_types::{
4 AccountId, Action, CryptoHash, TxExecutionStatus, transaction::PrepopulateTransaction,
5};
6
7use crate::{
8 common::{
9 query::{
10 ReceiptHandler, RequestBuilder, TransactionStatusHandler,
11 tx_rpc::{
12 ReceiptRef, ReceiptRpc, TransactionProofRef, TransactionProofRpc,
13 TransactionStatusRef, TransactionStatusRpc,
14 },
15 },
16 send::{ExecuteSignedTransaction, Transactionable},
17 },
18 config::NetworkConfig,
19 errors::{ArgumentValidationError, ValidationError},
20 signer::Signer,
21};
22
23#[derive(Clone, Debug)]
24pub struct TransactionWithSign<T: Transactionable + 'static> {
25 pub tx: T,
26}
27
28impl<T: Transactionable> TransactionWithSign<T> {
29 pub fn with_signer(self, signer: Arc<Signer>) -> ExecuteSignedTransaction {
30 ExecuteSignedTransaction::new(self.tx, signer)
31 }
32}
33
34#[derive(Clone, Debug)]
35pub struct SelfActionBuilder {
36 pub actions: Vec<Action>,
37}
38
39impl Default for SelfActionBuilder {
40 fn default() -> Self {
41 Self::new()
42 }
43}
44
45impl SelfActionBuilder {
46 pub const fn new() -> Self {
47 Self {
48 actions: Vec::new(),
49 }
50 }
51
52 /// Adds an action to the transaction.
53 pub fn add_action(mut self, action: Action) -> Self {
54 self.actions.push(action);
55 self
56 }
57
58 /// Adds multiple actions to the transaction.
59 pub fn add_actions(mut self, actions: Vec<Action>) -> Self {
60 self.actions.extend(actions);
61 self
62 }
63
64 /// Signs the transaction with the given account id and signer related to it.
65 pub fn with_signer(
66 self,
67 signer_account_id: AccountId,
68 signer: Arc<Signer>,
69 ) -> ExecuteSignedTransaction {
70 ConstructTransaction::new(signer_account_id.clone(), signer_account_id)
71 .add_actions(self.actions)
72 .with_signer(signer)
73 }
74}
75
76/// A builder for constructing transactions using Actions.
77#[derive(Debug, Clone)]
78pub struct ConstructTransaction {
79 pub transaction: Result<PrepopulateTransaction, ArgumentValidationError>,
80}
81
82impl ConstructTransaction {
83 /// Pre-populates a transaction with the given signer and receiver IDs.
84 pub const fn new(signer_id: AccountId, receiver_id: AccountId) -> Self {
85 Self {
86 transaction: Ok(PrepopulateTransaction {
87 signer_id,
88 receiver_id,
89 actions: Vec::new(),
90 }),
91 }
92 }
93
94 pub fn with_deferred_error(mut self, error: ArgumentValidationError) -> Self {
95 self.transaction = Err(error);
96 self
97 }
98
99 /// Adds an action to the transaction.
100 pub fn add_action(mut self, action: Action) -> Self {
101 if let Ok(transaction) = &mut self.transaction {
102 transaction.actions.push(action);
103 }
104 self
105 }
106
107 /// Adds multiple actions to the transaction.
108 pub fn add_actions(mut self, actions: Vec<Action>) -> Self {
109 if let Ok(transaction) = &mut self.transaction {
110 transaction.actions.extend(actions);
111 }
112 self
113 }
114
115 /// Signs the transaction with the given signer.
116 pub fn with_signer(self, signer: Arc<Signer>) -> ExecuteSignedTransaction {
117 ExecuteSignedTransaction::new(self, signer)
118 }
119}
120
121#[async_trait::async_trait]
122impl Transactionable for ConstructTransaction {
123 fn prepopulated(&self) -> Result<PrepopulateTransaction, ArgumentValidationError> {
124 self.transaction.clone()
125 }
126
127 async fn validate_with_network(&self, _: &NetworkConfig) -> Result<(), ValidationError> {
128 if let Err(e) = &self.transaction {
129 return Err(e.to_owned().into());
130 }
131 Ok(())
132 }
133}
134
135/// Transaction related functionality.
136///
137/// This struct provides ability to interact with transactions.
138#[derive(Clone, Debug)]
139pub struct Transaction;
140
141impl Transaction {
142 /// Constructs a new transaction builder with the given signer and receiver IDs.
143 /// This pattern is useful for batching actions into a single transaction.
144 ///
145 /// This is the low level interface for constructing transactions.
146 /// It is designed to be used in scenarios where more control over the transaction process is required.
147 ///
148 /// # Example
149 ///
150 /// This example constructs a transaction with a two transfer actions.
151 ///
152 /// ```rust,no_run
153 /// use near_api::{*, types::{transaction::actions::{Action, TransferAction}, json::U128}};
154 ///
155 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
156 /// let signer = Signer::from_ledger()?;
157 ///
158 /// let transaction_result = Transaction::construct(
159 /// "sender.near".parse()?,
160 /// "receiver.near".parse()?
161 /// )
162 /// .add_action(Action::Transfer(
163 /// TransferAction {
164 /// deposit: NearToken::from_near(1),
165 /// },
166 /// ))
167 /// .add_action(Action::Transfer(
168 /// TransferAction {
169 /// deposit: NearToken::from_near(1),
170 /// },
171 /// ))
172 /// .with_signer(signer)
173 /// .send_to_mainnet()
174 /// .await?;
175 /// # Ok(())
176 /// # }
177 /// ```
178 pub const fn construct(signer_id: AccountId, receiver_id: AccountId) -> ConstructTransaction {
179 ConstructTransaction::new(signer_id, receiver_id)
180 }
181
182 /// Signs a transaction with the given signer.
183 ///
184 /// This provides ability to sign custom constructed pre-populated transactions.
185 ///
186 /// # Examples
187 ///
188 /// ```rust,no_run
189 /// use near_api::*;
190 ///
191 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
192 /// let signer = Signer::from_ledger()?;
193 /// # let unsigned_tx = todo!();
194 ///
195 /// let transaction_result = Transaction::use_transaction(
196 /// unsigned_tx,
197 /// signer
198 /// )
199 /// .send_to_mainnet()
200 /// .await?;
201 /// # Ok(())
202 /// # }
203 /// ```
204 pub fn use_transaction(
205 unsigned_tx: PrepopulateTransaction,
206 signer: Arc<Signer>,
207 ) -> ExecuteSignedTransaction {
208 ConstructTransaction::new(unsigned_tx.signer_id, unsigned_tx.receiver_id)
209 .add_actions(unsigned_tx.actions)
210 .with_signer(signer)
211 }
212
213 /// Sets up a query to fetch the current status of a transaction by its hash and sender account ID.
214 ///
215 /// Waits until the transaction has been optimistically executed ([`TxExecutionStatus::ExecutedOptimistic`]),
216 /// ensuring that outcome fields (gas usage, logs, status) are populated.
217 /// If you need to wait until the transaction reaches a different stage
218 /// (e.g., [`TxExecutionStatus::Final`] or [`TxExecutionStatus::None`]),
219 /// use [`Transaction::status_with_options`] instead.
220 ///
221 /// The returned result is an [`ExecutionFinalResult`](near_api_types::transaction::result::ExecutionFinalResult)
222 /// which provides details about gas usage, logs, and the execution status.
223 ///
224 /// # Example
225 ///
226 /// ```rust,no_run
227 /// use near_api::*;
228 ///
229 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
230 /// let tx_hash: CryptoHash = "EaNakSaXUTjbPsUJbuDdbuq3e6Ynmjo8zYUgDVqt1iTn".parse()?;
231 /// let sender: AccountId = "sender.near".parse()?;
232 ///
233 /// let result = Transaction::status(sender, tx_hash)
234 /// .fetch_from_mainnet()
235 /// .await?;
236 /// println!("Transaction success: {}", result.is_success());
237 /// # Ok(())
238 /// # }
239 /// ```
240 pub fn status(
241 sender_account_id: AccountId,
242 tx_hash: CryptoHash,
243 ) -> RequestBuilder<TransactionStatusHandler> {
244 Self::status_with_options(
245 sender_account_id,
246 tx_hash,
247 TxExecutionStatus::ExecutedOptimistic,
248 )
249 }
250
251 /// Sets up a query to fetch the status of a transaction, waiting until it reaches
252 /// the specified execution stage.
253 ///
254 /// Use [`TxExecutionStatus::None`] to return immediately with whatever state is available,
255 /// or [`TxExecutionStatus::Final`] to wait until the transaction is fully finalized.
256 ///
257 /// # Example
258 ///
259 /// ```rust,no_run
260 /// use near_api::{*, types::TxExecutionStatus};
261 ///
262 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
263 /// let tx_hash: CryptoHash = "EaNakSaXUTjbPsUJbuDdbuq3e6Ynmjo8zYUgDVqt1iTn".parse()?;
264 /// let sender: AccountId = "sender.near".parse()?;
265 ///
266 /// let result = Transaction::status_with_options(
267 /// sender,
268 /// tx_hash,
269 /// TxExecutionStatus::Final,
270 /// )
271 /// .fetch_from_mainnet()
272 /// .await?;
273 /// # Ok(())
274 /// # }
275 /// ```
276 pub fn status_with_options(
277 sender_account_id: AccountId,
278 tx_hash: CryptoHash,
279 wait_until: TxExecutionStatus,
280 ) -> RequestBuilder<TransactionStatusHandler> {
281 RequestBuilder::new(
282 TransactionStatusRpc,
283 TransactionStatusRef {
284 sender_account_id,
285 tx_hash,
286 wait_until,
287 },
288 TransactionStatusHandler,
289 )
290 }
291
292 /// Sets up a query to fetch a receipt by its ID.
293 ///
294 /// This uses the `EXPERIMENTAL_receipt` RPC method to retrieve the details of a specific receipt.
295 ///
296 /// # Example
297 ///
298 /// ```rust,no_run
299 /// use near_api::*;
300 ///
301 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
302 /// let receipt_id: CryptoHash = "EaNakSaXUTjbPsUJbuDdbuq3e6Ynmjo8zYUgDVqt1iTn".parse()?;
303 ///
304 /// let receipt = Transaction::receipt(receipt_id)
305 /// .fetch_from_mainnet()
306 /// .await?;
307 /// println!("Receipt receiver: {:?}", receipt.receiver_id);
308 /// # Ok(())
309 /// # }
310 /// ```
311 pub fn receipt(receipt_id: CryptoHash) -> RequestBuilder<ReceiptHandler> {
312 RequestBuilder::new(ReceiptRpc, ReceiptRef { receipt_id }, ReceiptHandler)
313 }
314
315 /// Sets up a query to fetch the light client execution proof for a transaction.
316 ///
317 /// This is used to verify a transaction's execution against a light client block header.
318 /// The `light_client_head` parameter specifies the block hash of the light client's latest known head.
319 ///
320 /// # Example
321 ///
322 /// ```rust,no_run
323 /// use near_api::*;
324 ///
325 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
326 /// let tx_hash: CryptoHash = "EaNakSaXUTjbPsUJbuDdbuq3e6Ynmjo8zYUgDVqt1iTn".parse()?;
327 /// let sender: AccountId = "sender.near".parse()?;
328 /// let head_hash: CryptoHash = "3i1SypXzBRhLMvpHmNJXpg18FgVW6jNFrFcUqBF5Wmit".parse()?;
329 ///
330 /// let proof = Transaction::proof(sender, tx_hash, head_hash)
331 /// .fetch_from_mainnet()
332 /// .await?;
333 /// println!("Proof block header: {:?}", proof.block_header_lite);
334 /// # Ok(())
335 /// # }
336 /// ```
337 pub fn proof(
338 sender_id: AccountId,
339 transaction_hash: CryptoHash,
340 light_client_head: CryptoHash,
341 ) -> RequestBuilder<TransactionProofRpc> {
342 RequestBuilder::new(
343 TransactionProofRpc,
344 TransactionProofRef {
345 sender_id,
346 transaction_hash,
347 light_client_head,
348 },
349 TransactionProofRpc,
350 )
351 }
352}