diff --git a/crates/sui-graphql-client/src/lib.rs b/crates/sui-graphql-client/src/lib.rs index bba974252..bc2dc64ed 100644 --- a/crates/sui-graphql-client/src/lib.rs +++ b/crates/sui-graphql-client/src/lib.rs @@ -67,7 +67,9 @@ use query_types::ResolveSuinsQueryArgs; use query_types::ServiceConfig; use query_types::ServiceConfigQuery; use query_types::TransactionBlockArgs; +use query_types::TransactionBlockEffectsQuery; use query_types::TransactionBlockQuery; +use query_types::TransactionBlocksEffectsQuery; use query_types::TransactionBlocksQuery; use query_types::TransactionBlocksQueryArgs; use query_types::TransactionMetadata; @@ -85,6 +87,7 @@ use sui_types::types::MovePackage; use sui_types::types::Object; use sui_types::types::SignedTransaction; use sui_types::types::Transaction; +use sui_types::types::TransactionDigest; use sui_types::types::TransactionEffects; use sui_types::types::TransactionKind; use sui_types::types::TypeTag; @@ -1482,6 +1485,24 @@ impl Client { .map_err(|e| Error::msg(format!("Cannot decode transaction: {e}"))) } + /// Get a transaction's effects by its digest. + pub async fn transaction_effects( + &self, + digest: TransactionDigest, + ) -> Result, Error> { + let operation = TransactionBlockEffectsQuery::build(TransactionBlockArgs { + digest: digest.to_string(), + }); + let response = self.run_query(&operation).await?; + + response + .data + .and_then(|d| d.transaction_block) + .map(|tx| tx.try_into()) + .transpose() + .map_err(|e| Error::msg(format!("Cannot decode transaction: {e}"))) + } + /// Get a page of transactions based on the provided filters. pub async fn transactions<'a>( &self, @@ -1516,6 +1537,40 @@ impl Client { } } + /// Get a page of transactions' effects based on the provided filters. + pub async fn transactions_effects<'a>( + &self, + filter: Option>, + pagination_filter: PaginationFilter, + ) -> Result, Error> { + let (after, before, first, last) = self.pagination_filter(pagination_filter).await; + + let operation = TransactionBlocksEffectsQuery::build(TransactionBlocksQueryArgs { + after: after.as_deref(), + before: before.as_deref(), + filter, + first, + last, + }); + + let response = self.run_query(&operation).await?; + + if let Some(txb) = response.data { + let txc = txb.transaction_blocks; + let page_info = txc.page_info; + + let transactions = txc + .nodes + .into_iter() + .map(|n| n.try_into()) + .collect::>>()?; + let page = Page::new(page_info, transactions); + Ok(page) + } else { + Ok(Page::new_empty()) + } + } + /// Get a stream of transactions based on the (optional) transaction filter. pub async fn transactions_stream<'a>( &'a self, @@ -1528,6 +1583,18 @@ impl Client { ) } + /// Get a stream of transactions' effects based on the (optional) transaction filter. + pub async fn transactions_effects_stream<'a>( + &'a self, + filter: Option>, + streaming_direction: Direction, + ) -> impl Stream> + 'a { + stream_paginated_query( + move |pag_filter| self.transactions_effects(filter.clone(), pag_filter), + streaming_direction, + ) + } + /// Execute a transaction. pub async fn execute_tx( &self, @@ -1695,6 +1762,8 @@ mod tests { use crate::MAINNET_HOST; use crate::TESTNET_HOST; + const NUM_COINS_FROM_FAUCET: usize = 5; + fn test_client() -> Client { let network = std::env::var("NETWORK").unwrap_or_else(|_| "local".to_string()); match network.as_str() { @@ -1953,7 +2022,6 @@ mod tests { #[tokio::test] async fn test_coins_stream() { - const NUM_COINS_FROM_FAUCET: usize = 5; let client = test_client(); let faucet = match client.rpc_server() { LOCAL_HOST => FaucetClient::local(), @@ -1976,6 +2044,36 @@ mod tests { assert!(num_coins == NUM_COINS_FROM_FAUCET); } + #[tokio::test] + async fn test_transaction_effects_query() { + let client = test_client(); + let transactions = client + .transactions(None, PaginationFilter::default()) + .await + .unwrap(); + let tx_digest = transactions.data()[0].transaction.digest(); + let effects = client.transaction_effects(tx_digest).await.unwrap(); + assert!( + effects.is_some(), + "Transaction effects query failed for {} network.", + client.rpc_server(), + ); + } + + #[tokio::test] + async fn test_transactions_effects_query() { + let client = test_client(); + let txs_effects = client + .transactions_effects(None, PaginationFilter::default()) + .await; + assert!( + txs_effects.is_ok(), + "Transactions effects query failed for {} network. Error: {}", + client.rpc_server(), + txs_effects.unwrap_err() + ); + } + #[tokio::test] async fn test_transactions_query() { let client = test_client(); diff --git a/crates/sui-graphql-client/src/query_types/dry_run.rs b/crates/sui-graphql-client/src/query_types/dry_run.rs index 4e5641e1f..9d01d44f6 100644 --- a/crates/sui-graphql-client/src/query_types/dry_run.rs +++ b/crates/sui-graphql-client/src/query_types/dry_run.rs @@ -5,7 +5,8 @@ use sui_types::types::ObjectReference; use crate::query_types::schema; use crate::query_types::Address; -use crate::query_types::TransactionBlock; + +use super::transaction::TxBlockEffects; #[derive(cynic::QueryFragment, Debug)] #[cynic(schema = "rpc", graphql_type = "Query", variables = "DryRunArgs")] @@ -18,7 +19,7 @@ pub struct DryRunQuery { #[cynic(schema = "rpc", graphql_type = "DryRunResult")] pub struct DryRunResult { pub error: Option, - pub transaction: Option, + pub transaction: Option, } #[derive(cynic::QueryVariables, Debug)] diff --git a/crates/sui-graphql-client/src/query_types/mod.rs b/crates/sui-graphql-client/src/query_types/mod.rs index 0bd15de65..2bf88140f 100644 --- a/crates/sui-graphql-client/src/query_types/mod.rs +++ b/crates/sui-graphql-client/src/query_types/mod.rs @@ -100,7 +100,9 @@ pub use suins::ResolveSuinsQuery; pub use suins::ResolveSuinsQueryArgs; pub use transaction::TransactionBlock; pub use transaction::TransactionBlockArgs; +pub use transaction::TransactionBlockEffectsQuery; pub use transaction::TransactionBlockQuery; +pub use transaction::TransactionBlocksEffectsQuery; pub use transaction::TransactionBlocksQuery; pub use transaction::TransactionBlocksQueryArgs; pub use transaction::TransactionsFilter; diff --git a/crates/sui-graphql-client/src/query_types/transaction.rs b/crates/sui-graphql-client/src/query_types/transaction.rs index d8f5a4cb9..e95b4c6e5 100644 --- a/crates/sui-graphql-client/src/query_types/transaction.rs +++ b/crates/sui-graphql-client/src/query_types/transaction.rs @@ -5,6 +5,7 @@ use anyhow::Error; use base64ct::Encoding; use sui_types::types::SignedTransaction; use sui_types::types::Transaction; +use sui_types::types::TransactionEffects; use sui_types::types::UserSignature; use crate::query_types::schema; @@ -27,6 +28,17 @@ pub struct TransactionBlockQuery { pub transaction_block: Option, } +#[derive(cynic::QueryFragment, Debug)] +#[cynic( + schema = "rpc", + graphql_type = "Query", + variables = "TransactionBlockArgs" +)] +pub struct TransactionBlockEffectsQuery { + #[arguments(digest: $digest)] + pub transaction_block: Option, +} + #[derive(cynic::QueryFragment, Debug)] #[cynic( schema = "rpc", @@ -38,6 +50,16 @@ pub struct TransactionBlocksQuery { pub transaction_blocks: TransactionBlockConnection, } +#[derive(cynic::QueryFragment, Debug)] +#[cynic( + schema = "rpc", + graphql_type = "Query", + variables = "TransactionBlocksQueryArgs" +)] +pub struct TransactionBlocksEffectsQuery { + #[arguments(first: $first, after: $after, last: $last, before: $before, filter: $filter)] + pub transaction_blocks: TransactionBlockEffectsConnection, +} // =========================================================================== // Transaction Block(s) Query Args // =========================================================================== @@ -64,10 +86,15 @@ pub struct TransactionBlocksQueryArgs<'a> { #[cynic(schema = "rpc", graphql_type = "TransactionBlock")] pub struct TransactionBlock { pub bcs: Option, - pub effects: Option, pub signatures: Option>, } +#[derive(cynic::QueryFragment, Debug)] +#[cynic(schema = "rpc", graphql_type = "TransactionBlock")] +pub struct TxBlockEffects { + pub effects: Option, +} + #[derive(cynic::QueryFragment, Debug)] #[cynic(schema = "rpc", graphql_type = "TransactionBlockEffects")] pub struct TransactionBlockEffects { @@ -107,6 +134,13 @@ pub struct TransactionBlockConnection { pub page_info: PageInfo, } +#[derive(cynic::QueryFragment, Debug)] +#[cynic(schema = "rpc", graphql_type = "TransactionBlockConnection")] +pub struct TransactionBlockEffectsConnection { + pub nodes: Vec, + pub page_info: PageInfo, +} + impl TryFrom for SignedTransaction { type Error = anyhow::Error; @@ -139,3 +173,19 @@ impl TryFrom for SignedTransaction { } } } + +impl TryFrom for TransactionEffects { + type Error = anyhow::Error; + + fn try_from(value: TxBlockEffects) -> Result { + let effects = value + .effects + .map(|fx| base64ct::Base64::decode_vec(fx.bcs.unwrap().0.as_str())) + .transpose() + .map_err(|_| Error::msg("Cannot decode Base64 effects bcs bytes"))? + .map(|bcs| bcs::from_bytes::(&bcs)) + .transpose() + .map_err(|_| Error::msg("Cannot decode bcs bytes into TransactionEffects"))?; + effects.ok_or_else(|| Error::msg("Cannot decode effects")) + } +}