Skip to content

Commit 0085fa8

Browse files
authored
feat: added tx status, receipt and proof fetchers (#126)
* feat: added tx status, receipt and proof fetchers * chore(examples): safer receipt_id extraction * chore: set `ExecutedOptimistic` as a safer default for status fetch * refactor: reduced code duplication by adding `to_final_execution_outcome` method and created a separate `is_critical_transaction_status_error` * chore(examples): skip if `ci=true` * fix: clippy * fix: potential panic due to tx status in pending state * chore: pinned `deflate64` crate * chore: removed deflate64 version pin * fix(example): use archival url for fetching * refactor(examples): use `fetch_from_mainnet_archival()`
1 parent 76a2e0e commit 0085fa8

File tree

11 files changed

+656
-42
lines changed

11 files changed

+656
-42
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
[workspace]
22
resolver = "3"
33
members = ["api", "types"]
4-
rust-version = "1.86"
54

65
[workspace.package]
6+
rust-version = "1.86"
77
edition = "2024"
88
version = "0.8.5"
99
authors = [

api/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "near-api"
3-
rust-version = "1.85"
3+
rust-version.workspace = true
44
version.workspace = true
55
authors.workspace = true
66
license.workspace = true
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
use near_api::{
2+
Chain, Transaction,
3+
types::{AccountId, CryptoHash, Reference, TxExecutionStatus},
4+
};
5+
use testresult::TestResult;
6+
7+
/// This example queries mainnet with a known finalized transaction.
8+
///
9+
/// Sandbox does not support `EXPERIMENTAL_receipt` or `light_client_proof`,
10+
/// so this example is skipped when `CI=true`.
11+
///
12+
/// The transaction hash and sender can be overridden via environment variables:
13+
/// TX_HASH — the transaction hash to query
14+
/// TX_SENDER — the sender account ID
15+
#[tokio::main]
16+
async fn main() -> TestResult {
17+
if std::env::var("CI").is_ok() {
18+
println!("Skipping transaction_queries in CI (requires mainnet)");
19+
return Ok(());
20+
}
21+
22+
let sender: AccountId = std::env::var("TX_SENDER")
23+
.unwrap_or_else(|_| "omni.bridge.near".to_string())
24+
.parse()?;
25+
let tx_hash: CryptoHash = std::env::var("TX_HASH")
26+
.unwrap_or_else(|_| "GmvjRhbBwNCeekyZ4ezv43Zhs4U33kRTj6PRkFgKUKyJ".to_string())
27+
.parse()?;
28+
29+
let status = Transaction::status(sender.clone(), tx_hash)
30+
.fetch_from_mainnet_archival()
31+
.await?;
32+
println!(
33+
"[status] is_success={}, is_failure={}, gas_burnt={}",
34+
status.is_success(),
35+
status.is_failure(),
36+
status.total_gas_burnt,
37+
);
38+
39+
let status_final =
40+
Transaction::status_with_options(sender.clone(), tx_hash, TxExecutionStatus::Final)
41+
.fetch_from_mainnet_archival()
42+
.await?;
43+
println!(
44+
"[status_with_options(Final)] is_success={}, receipts={}",
45+
status_final.is_success(),
46+
status_final.receipt_outcomes().len(),
47+
);
48+
49+
let receipt_id = *status_final
50+
.outcome()
51+
.receipt_ids
52+
.first()
53+
.expect("transaction should have at least one receipt");
54+
let receipt = Transaction::receipt(receipt_id)
55+
.fetch_from_mainnet_archival()
56+
.await?;
57+
println!(
58+
"[receipt] id={}, receiver={}, predecessor={}",
59+
receipt.receipt_id, receipt.receiver_id, receipt.predecessor_id,
60+
);
61+
62+
let head_hash = Chain::block_hash()
63+
.at(Reference::Final)
64+
.fetch_from_mainnet_archival()
65+
.await?;
66+
let proof = Transaction::proof(sender, tx_hash, head_hash)
67+
.fetch_from_mainnet_archival()
68+
.await?;
69+
println!(
70+
"[proof] outcome_proof_id={}, block_proof_len={}, outcome_root_proof_len={}",
71+
proof.outcome_proof.id,
72+
proof.block_proof.len(),
73+
proof.outcome_root_proof.len(),
74+
);
75+
76+
println!("\nAll transaction query methods passed!");
77+
78+
Ok(())
79+
}

api/src/common/query/handlers/mod.rs

Lines changed: 68 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,22 @@
11
use borsh::BorshDeserialize;
22
use near_api_types::{
33
AccessKey, Account, AccountView, ContractCodeView, Data, PublicKey, RpcBlockResponse,
4-
RpcValidatorResponse, ViewStateResult, json::U64,
4+
RpcValidatorResponse, ViewStateResult, json::U64, transaction::result::ExecutionFinalResult,
55
};
6-
use near_openapi_client::types::RpcQueryResponse;
6+
use near_openapi_client::types::{RpcQueryResponse, RpcReceiptResponse, RpcTransactionResponse};
77
use serde::de::DeserializeOwned;
88
use std::marker::PhantomData;
99
use tracing::{info, trace, warn};
1010

1111
use crate::{
1212
advanced::{
1313
RpcType, block_rpc::SimpleBlockRpc, query_rpc::SimpleQueryRpc,
14-
validator_rpc::SimpleValidatorRpc,
14+
tx_rpc::TransactionStatusRpc, validator_rpc::SimpleValidatorRpc,
15+
},
16+
common::{
17+
query::{QUERY_EXECUTOR_TARGET, ResultWithMethod},
18+
send::to_final_execution_outcome,
1519
},
16-
common::query::{QUERY_EXECUTOR_TARGET, ResultWithMethod},
1720
errors::QueryError,
1821
};
1922
pub mod transformers;
@@ -497,6 +500,67 @@ impl ResponseHandler for RpcBlockHandler {
497500
}
498501
}
499502

503+
/// Handler that converts an [`RpcTransactionResponse`] into an [`ExecutionFinalResult`].
504+
///
505+
/// This reuses the same conversion logic from transaction sending: it extracts the
506+
/// `FinalExecutionOutcomeView` from the response and converts it using `TryFrom`.
507+
#[derive(Clone, Debug)]
508+
pub struct TransactionStatusHandler;
509+
510+
impl ResponseHandler for TransactionStatusHandler {
511+
type Response = ExecutionFinalResult;
512+
type Query = TransactionStatusRpc;
513+
514+
fn process_response(
515+
&self,
516+
response: Vec<RpcTransactionResponse>,
517+
) -> ResultWithMethod<Self::Response, <Self::Query as RpcType>::Error> {
518+
let response = response
519+
.into_iter()
520+
.next()
521+
.ok_or(QueryError::InternalErrorNoResponse)?;
522+
523+
let final_execution_outcome_view = to_final_execution_outcome(response);
524+
525+
info!(
526+
target: QUERY_EXECUTOR_TARGET,
527+
"Processed TransactionStatus response, tx hash: {:?}",
528+
final_execution_outcome_view.transaction_outcome.id,
529+
);
530+
531+
ExecutionFinalResult::try_from(final_execution_outcome_view)
532+
.map_err(|e| QueryError::ConversionError(Box::new(e)))
533+
}
534+
}
535+
536+
/// Handler that passes through the raw [`RpcReceiptResponse`] without transformation.
537+
#[derive(Clone, Debug)]
538+
pub struct ReceiptHandler;
539+
540+
impl ResponseHandler for ReceiptHandler {
541+
type Response = RpcReceiptResponse;
542+
type Query = crate::advanced::tx_rpc::ReceiptRpc;
543+
544+
fn process_response(
545+
&self,
546+
response: Vec<RpcReceiptResponse>,
547+
) -> ResultWithMethod<Self::Response, <Self::Query as RpcType>::Error> {
548+
let response = response
549+
.into_iter()
550+
.next()
551+
.ok_or(QueryError::InternalErrorNoResponse)?;
552+
553+
info!(
554+
target: QUERY_EXECUTOR_TARGET,
555+
"Processed Receipt response, receipt_id: {:?}, receiver: {:?}",
556+
response.receipt_id,
557+
response.receiver_id,
558+
);
559+
560+
Ok(response)
561+
}
562+
}
563+
500564
impl<T: RpcType> ResponseHandler for T {
501565
type Response = <T as RpcType>::Response;
502566
type Query = T;

api/src/common/query/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ pub mod block_rpc;
1414
pub mod handlers;
1515
pub mod query_request;
1616
pub mod query_rpc;
17+
pub mod tx_rpc;
1718
pub mod validator_rpc;
1819

1920
pub use handlers::*;

0 commit comments

Comments
 (0)