From be7f0e01ff567d6210959c12d689011022d2b26d Mon Sep 17 00:00:00 2001 From: Patrick Kuo Date: Fri, 15 Jul 2022 21:21:31 +0100 Subject: [PATCH] Refactor sui-client for Sui Rust SDK and Rust SDK example project (#3102) * refactor and polish up sui-client * tic tac toe cli wip * add code comments * some refactoring and re-exporting sui deps in sui-client * docs * move sdk example to crate folder * fix toml * add workspace hack * address PR commits rename sui-client to sui-sdk renamed SuiRpcClient to SuiClient as we might support embedded client in the future removed client apis, implementing the functions in SuiClient directly instead. * rename sui-client to sui-sdk * Move examples to sui-sdk/examples added event subscription added SDK docs and in doc examples * minor fix * fix typo --- Cargo.lock | 48 ++- Cargo.toml | 3 +- crates/sui-cluster-test/Cargo.toml | 3 +- crates/sui-cluster-test/src/main.rs | 4 +- crates/sui-core/Cargo.toml | 2 +- crates/sui-core/src/event_handler.rs | 43 +-- crates/sui-core/src/gateway_state.rs | 2 +- .../src/unit_tests/event_handler_tests.rs | 11 +- crates/sui-faucet/Cargo.toml | 2 +- crates/sui-faucet/src/faucet/mod.rs | 2 +- crates/sui-faucet/src/faucet/simple_faucet.rs | 2 +- crates/sui-gateway/Cargo.toml | 3 +- crates/sui-gateway/src/rpc_gateway_client.rs | 62 +-- .../src/unit_tests/rpc_server_tests.rs | 15 +- crates/sui-json-rpc-api/src/client.rs | 37 -- .../Cargo.toml | 2 +- .../src/lib.rs} | 76 +++- .../src/unit_tests/rpc_types_tests.rs} | 2 +- crates/sui-json-rpc/Cargo.toml | 4 +- .../src/lib.rs => sui-json-rpc/src/api.rs} | 57 +-- crates/sui-json-rpc/src/bcs_api.rs | 10 +- crates/sui-json-rpc/src/event_api.rs | 10 +- crates/sui-json-rpc/src/gateway_api.rs | 23 +- crates/sui-json-rpc/src/lib.rs | 1 + crates/sui-json-rpc/src/read_api.rs | 10 +- crates/sui-open-rpc-macros/src/lib.rs | 2 +- crates/sui-open-rpc/Cargo.toml | 2 +- .../src/generate_json_rpc_spec.rs | 16 +- crates/sui-sdk/Cargo.toml | 36 ++ crates/sui-sdk/README.md | 38 ++ crates/sui-sdk/examples/event_subscription.rs | 15 + crates/sui-sdk/examples/get_owned_objects.rs | 15 + crates/sui-sdk/examples/tic_tac_toe.rs | 346 +++++++++++++++++ crates/sui-sdk/examples/transfer_coins.rs | 44 +++ .../src/keystore.rs => sui-sdk/src/crypto.rs} | 0 crates/sui-sdk/src/lib.rs | 362 ++++++++++++++++++ crates/sui/Cargo.toml | 3 +- crates/sui/src/client_commands.rs | 8 +- crates/sui/src/config/mod.rs | 2 +- crates/sui/src/keytool.rs | 2 +- crates/sui/src/sui_commands.rs | 6 +- crates/sui/src/unit_tests/cli_tests.rs | 4 +- crates/sui/tests/full_node_tests.rs | 8 +- crates/test-utils/Cargo.toml | 3 +- crates/test-utils/src/network.rs | 10 +- doc/src/build/rust-sdk.md | 106 +++++ 46 files changed, 1188 insertions(+), 274 deletions(-) delete mode 100644 crates/sui-json-rpc-api/src/client.rs rename crates/{sui-json-rpc-api => sui-json-rpc-types}/Cargo.toml (97%) rename crates/{sui-json-rpc-api/src/rpc_types.rs => sui-json-rpc-types/src/lib.rs} (95%) rename crates/{sui-json-rpc-api/src/unit_tests/gateway_types_tests.rs => sui-json-rpc-types/src/unit_tests/rpc_types_tests.rs} (98%) rename crates/{sui-json-rpc-api/src/lib.rs => sui-json-rpc/src/api.rs} (84%) create mode 100644 crates/sui-sdk/Cargo.toml create mode 100644 crates/sui-sdk/README.md create mode 100644 crates/sui-sdk/examples/event_subscription.rs create mode 100644 crates/sui-sdk/examples/get_owned_objects.rs create mode 100644 crates/sui-sdk/examples/tic_tac_toe.rs create mode 100644 crates/sui-sdk/examples/transfer_coins.rs rename crates/{sui-json-rpc-api/src/keystore.rs => sui-sdk/src/crypto.rs} (100%) create mode 100644 crates/sui-sdk/src/lib.rs create mode 100644 doc/src/build/rust-sdk.md diff --git a/Cargo.lock b/Cargo.lock index c29af50debaf9..c0716e32a0741 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6358,9 +6358,10 @@ dependencies = [ "sui-framework", "sui-gateway", "sui-json", - "sui-json-rpc-api", + "sui-json-rpc-types", "sui-node", "sui-quorum-driver", + "sui-sdk", "sui-swarm", "sui-types", "telemetry-subscribers", @@ -6447,7 +6448,8 @@ dependencies = [ "sui-config", "sui-faucet", "sui-json", - "sui-json-rpc-api", + "sui-json-rpc-types", + "sui-sdk", "sui-types", "telemetry-subscribers", "tempfile", @@ -6521,7 +6523,7 @@ dependencies = [ "sui-config", "sui-framework", "sui-json", - "sui-json-rpc-api", + "sui-json-rpc-types", "sui-network", "sui-storage", "sui-types", @@ -6550,7 +6552,7 @@ dependencies = [ "serde 1.0.139", "sui", "sui-config", - "sui-json-rpc-api", + "sui-json-rpc-types", "sui-types", "telemetry-subscribers", "test-utils", @@ -6615,8 +6617,9 @@ dependencies = [ "sui-framework", "sui-json", "sui-json-rpc", - "sui-json-rpc-api", + "sui-json-rpc-types", "sui-node", + "sui-sdk", "sui-types", "telemetry-subscribers", "test-utils", @@ -6656,12 +6659,14 @@ dependencies = [ "futures", "jsonrpsee", "jsonrpsee-core", + "jsonrpsee-proc-macros", "prometheus", "serde 1.0.139", "sui-core", "sui-json", - "sui-json-rpc-api", + "sui-json-rpc-types", "sui-open-rpc", + "sui-open-rpc-macros", "sui-types", "tokio", "tracing", @@ -6669,7 +6674,7 @@ dependencies = [ ] [[package]] -name = "sui-json-rpc-api" +name = "sui-json-rpc-types" version = "0.0.0" dependencies = [ "anyhow", @@ -6748,7 +6753,7 @@ dependencies = [ "sui-config", "sui-json", "sui-json-rpc", - "sui-json-rpc-api", + "sui-json-rpc-types", "sui-types", "test-utils", "tokio", @@ -6779,6 +6784,30 @@ dependencies = [ "workspace-hack 0.1.0", ] +[[package]] +name = "sui-sdk" +version = "0.0.0" +dependencies = [ + "anyhow", + "async-recursion", + "async-trait", + "bcs", + "clap 3.1.18", + "dirs", + "ed25519-dalek", + "futures", + "futures-core", + "jsonrpsee", + "serde 1.0.139", + "serde_json", + "sui-json", + "sui-json-rpc", + "sui-json-rpc-types", + "sui-types", + "tokio", + "workspace-hack 0.1.0", +] + [[package]] name = "sui-storage" version = "0.1.0" @@ -7166,8 +7195,9 @@ dependencies = [ "sui-framework", "sui-gateway", "sui-json-rpc", - "sui-json-rpc-api", + "sui-json-rpc-types", "sui-node", + "sui-sdk", "sui-swarm", "sui-types", "tempfile", diff --git a/Cargo.toml b/Cargo.toml index 07f3a521664ec..26088c0d08760 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,12 +14,13 @@ members = [ "crates/sui-gateway", "crates/sui-json", "crates/sui-json-rpc", - "crates/sui-json-rpc-api", + "crates/sui-json-rpc-types", "crates/sui-network", "crates/sui-node", "crates/sui-open-rpc", "crates/sui-open-rpc-macros", "crates/sui-quorum-driver", + "crates/sui-sdk", "crates/sui-storage", "crates/sui-swarm", "crates/sui-tool", diff --git a/crates/sui-cluster-test/Cargo.toml b/crates/sui-cluster-test/Cargo.toml index 17fc5a3074ddb..39a451583b50d 100644 --- a/crates/sui-cluster-test/Cargo.toml +++ b/crates/sui-cluster-test/Cargo.toml @@ -18,8 +18,9 @@ telemetry-subscribers = { git = "https://github.com/MystenLabs/mysten-infra", re sui-faucet = { path = "../sui-faucet" } sui = { path = "../sui" } -sui-json-rpc-api = { path = "../sui-json-rpc-api" } +sui-json-rpc-types= { path = "../sui-json-rpc-types" } sui-types = { path = "../sui-types" } +sui-sdk = { path = "../sui-sdk" } sui-json = { path = "../sui-json" } sui-config = { path = "../sui-config" } workspace-hack = { path = "../workspace-hack"} diff --git a/crates/sui-cluster-test/src/main.rs b/crates/sui-cluster-test/src/main.rs index 80cf2aee90ce4..4e60ef5223113 100644 --- a/crates/sui-cluster-test/src/main.rs +++ b/crates/sui-cluster-test/src/main.rs @@ -11,8 +11,8 @@ use sui::config::{Config, GatewayType, SuiClientConfig}; use sui_config::SUI_KEYSTORE_FILENAME; use sui_faucet::FaucetResponse; use sui_json::SuiJsonValue; -use sui_json_rpc_api::keystore::KeystoreType; -use sui_json_rpc_api::rpc_types::{GetObjectDataResponse, SuiExecutionStatus, TransactionResponse}; +use sui_json_rpc_types::{GetObjectDataResponse, SuiExecutionStatus, TransactionResponse}; +use sui_sdk::crypto::KeystoreType; use sui_types::{ base_types::{encode_bytes_hex, ObjectID, SuiAddress}, crypto::get_key_pair, diff --git a/crates/sui-core/Cargo.toml b/crates/sui-core/Cargo.toml index 0d423f9ebb2b2..77b86e3080f57 100644 --- a/crates/sui-core/Cargo.toml +++ b/crates/sui-core/Cargo.toml @@ -36,7 +36,7 @@ sui-types = { path = "../sui-types" } sui-storage = { path = "../sui-storage" } sui-config = { path = "../sui-config" } sui-json = { path = "../sui-json" } -sui-json-rpc-api = { path = "../sui-json-rpc-api" } +sui-json-rpc-types = { path = "../sui-json-rpc-types" } move-binary-format = { git = "https://github.com/move-language/move", rev = "7733658048a8bc80e9ba415b8c99aed9234eaa5f" } move-bytecode-utils = { git = "https://github.com/move-language/move", rev = "7733658048a8bc80e9ba415b8c99aed9234eaa5f" } diff --git a/crates/sui-core/src/event_handler.rs b/crates/sui-core/src/event_handler.rs index 3b11210c08496..ff6a4ddf40d99 100644 --- a/crates/sui-core/src/event_handler.rs +++ b/crates/sui-core/src/event_handler.rs @@ -1,12 +1,10 @@ // Copyright (c) 2022, Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -use std::collections::BTreeMap; use std::sync::Arc; use move_bytecode_utils::module_cache::SyncModuleCache; -use serde_json::Value; -use sui_json_rpc_api::rpc_types::{SuiMoveStruct, SuiMoveValue}; +use sui_json_rpc_types::SuiMoveStruct; use tokio_stream::Stream; use tracing::{debug, error, trace}; @@ -93,8 +91,8 @@ impl EventHandler { let move_struct = Event::move_event_to_move_struct(type_, contents, &self.module_cache)?; // Convert into `SuiMoveStruct` which is a mirror of MoveStruct but with additional type supports, (e.g. ascii::String). - let sui_move_struct = move_struct.into(); - Some(to_json_value(sui_move_struct).map_err(|e| { + let sui_move_struct = SuiMoveStruct::from(move_struct); + Some(sui_move_struct.to_json_value().map_err(|e| { SuiError::ObjectSerializationError { error: e.to_string(), } @@ -116,38 +114,3 @@ impl EventHandler { self.event_streamer.subscribe(filter) } } - -fn to_json_value(move_struct: SuiMoveStruct) -> Result { - // Unwrap MoveStructs - let unwrapped = match move_struct { - SuiMoveStruct::Runtime(values) => { - let values = values - .into_iter() - .map(|value| match value { - SuiMoveValue::Struct(move_struct) => to_json_value(move_struct), - SuiMoveValue::Vector(values) => to_json_value(SuiMoveStruct::Runtime(values)), - _ => serde_json::to_value(&value), - }) - .collect::, _>>()?; - serde_json::to_value(&values) - } - // We only care about values here, assuming struct type information is known at the client side. - SuiMoveStruct::WithTypes { type_: _, fields } | SuiMoveStruct::WithFields(fields) => { - let fields = fields - .into_iter() - .map(|(key, value)| { - let value = match value { - SuiMoveValue::Struct(move_struct) => to_json_value(move_struct), - SuiMoveValue::Vector(values) => { - to_json_value(SuiMoveStruct::Runtime(values)) - } - _ => serde_json::to_value(&value), - }; - value.map(|value| (key, value)) - }) - .collect::, _>>()?; - serde_json::to_value(&fields) - } - }?; - serde_json::to_value(&unwrapped) -} diff --git a/crates/sui-core/src/gateway_state.rs b/crates/sui-core/src/gateway_state.rs index 7166713dfa88d..a9a93474ec608 100644 --- a/crates/sui-core/src/gateway_state.rs +++ b/crates/sui-core/src/gateway_state.rs @@ -42,7 +42,7 @@ use crate::{ authority_client::AuthorityAPI, query_helpers::QueryHelpers, }; use sui_json::{resolve_move_function_args, SuiJsonCallArg, SuiJsonValue}; -use sui_json_rpc_api::rpc_types::{ +use sui_json_rpc_types::{ GetObjectDataResponse, GetRawObjectDataResponse, MergeCoinResponse, MoveCallParams, PublishResponse, RPCTransactionRequestParams, SplitCoinResponse, SuiMoveObject, SuiObject, SuiObjectInfo, SuiTransactionEffects, SuiTypeTag, TransactionEffectsResponse, diff --git a/crates/sui-core/src/unit_tests/event_handler_tests.rs b/crates/sui-core/src/unit_tests/event_handler_tests.rs index 942aa5e6f7ada..78111c16c92c5 100644 --- a/crates/sui-core/src/unit_tests/event_handler_tests.rs +++ b/crates/sui-core/src/unit_tests/event_handler_tests.rs @@ -14,8 +14,8 @@ use move_core_types::{ use serde::Deserialize; use serde::Serialize; use serde_json::json; +use sui_json_rpc_types::SuiMoveStruct; -use crate::event_handler::to_json_value; use sui_types::base_types::{ObjectID, SequenceNumber}; use sui_types::gas_coin::GasCoin; use sui_types::SUI_FRAMEWORK_ADDRESS; @@ -33,10 +33,11 @@ fn test_to_json_value() { ], }; let event_bytes = bcs::to_bytes(&move_event).unwrap(); - let sui_move_struct = MoveStruct::simple_deserialize(&event_bytes, &TestEvent::layout()) - .unwrap() - .into(); - let json_value = to_json_value(sui_move_struct).unwrap(); + let sui_move_struct: SuiMoveStruct = + MoveStruct::simple_deserialize(&event_bytes, &TestEvent::layout()) + .unwrap() + .into(); + let json_value = sui_move_struct.to_json_value().unwrap(); assert_eq!( Some(&json!(1000000)), diff --git a/crates/sui-faucet/Cargo.toml b/crates/sui-faucet/Cargo.toml index db70ab686a57d..84ce0d9053c2a 100644 --- a/crates/sui-faucet/Cargo.toml +++ b/crates/sui-faucet/Cargo.toml @@ -20,7 +20,7 @@ tower-http = { version = "0.3.4", features = ["cors"] } http = { version = "0.2.8" } sui = { path = "../sui" } -sui-json-rpc-api = { path = "../sui-json-rpc-api" } +sui-json-rpc-types= { path = "../sui-json-rpc-types" } sui-types = { path = "../sui-types" } sui-config = { path = "../sui-config" } telemetry-subscribers = { git = "https://github.com/MystenLabs/mysten-infra", rev = "40dae350a699f59f2e296152e02c78765db34e68" } diff --git a/crates/sui-faucet/src/faucet/mod.rs b/crates/sui-faucet/src/faucet/mod.rs index 5ad13200b0a5c..2c070bc41b156 100644 --- a/crates/sui-faucet/src/faucet/mod.rs +++ b/crates/sui-faucet/src/faucet/mod.rs @@ -3,7 +3,7 @@ use crate::FaucetError; use async_trait::async_trait; use serde::{Deserialize, Serialize}; -use sui_json_rpc_api::rpc_types::SuiParsedObject; +use sui_json_rpc_types::SuiParsedObject; use sui_types::{ base_types::{ObjectID, SuiAddress}, gas_coin::GasCoin, diff --git a/crates/sui-faucet/src/faucet/simple_faucet.rs b/crates/sui-faucet/src/faucet/simple_faucet.rs index 3d461a4242835..56dfd41f500d6 100644 --- a/crates/sui-faucet/src/faucet/simple_faucet.rs +++ b/crates/sui-faucet/src/faucet/simple_faucet.rs @@ -4,7 +4,7 @@ use anyhow::anyhow; use async_trait::async_trait; use sui::client_commands::WalletContext; -use sui_json_rpc_api::rpc_types::{SuiExecutionStatus, SuiParsedObject}; +use sui_json_rpc_types::{SuiExecutionStatus, SuiParsedObject}; use sui_types::{ base_types::{ObjectID, SuiAddress}, gas_coin::GasCoin, diff --git a/crates/sui-gateway/Cargo.toml b/crates/sui-gateway/Cargo.toml index a317a20a0a180..ed0c591a3b2e7 100644 --- a/crates/sui-gateway/Cargo.toml +++ b/crates/sui-gateway/Cargo.toml @@ -22,7 +22,8 @@ sui-config = { path = "../sui-config" } sui-types = { path = "../sui-types" } sui-json = { path = "../sui-json" } sui-json-rpc = { path = "../sui-json-rpc" } -sui-json-rpc-api = { path = "../sui-json-rpc-api" } +sui-json-rpc-types= { path = "../sui-json-rpc-types" } +sui-sdk = { path = "../sui-sdk" } sui-node = { path = "../sui-node" } mysten-network = { git = "https://github.com/MystenLabs/mysten-infra", rev = "40dae350a699f59f2e296152e02c78765db34e68" } diff --git a/crates/sui-gateway/src/rpc_gateway_client.rs b/crates/sui-gateway/src/rpc_gateway_client.rs index d22dc9a60b74e..13eae8022e820 100644 --- a/crates/sui-gateway/src/rpc_gateway_client.rs +++ b/crates/sui-gateway/src/rpc_gateway_client.rs @@ -3,31 +3,26 @@ use anyhow::Error; use async_trait::async_trait; + +use sui_sdk::SuiClient; use tokio::runtime::Handle; use sui_core::gateway_state::{GatewayAPI, GatewayTxSeqNumber}; use sui_json::SuiJsonValue; -use sui_json_rpc_api::client::SuiRpcClient; -use sui_json_rpc_api::rpc_types::{ +use sui_json_rpc_types::{ GetObjectDataResponse, GetRawObjectDataResponse, RPCTransactionRequestParams, SuiObjectInfo, - SuiTypeTag, TransactionEffectsResponse, TransactionResponse, + SuiTypeTag, TransactionBytes, TransactionEffectsResponse, TransactionResponse, }; -use sui_json_rpc_api::RpcBcsApiClient; -use sui_json_rpc_api::RpcGatewayApiClient; -use sui_json_rpc_api::RpcTransactionBuilderClient; -use sui_json_rpc_api::TransactionBytes; -use sui_json_rpc_api::WalletSyncApiClient; use sui_types::base_types::{ObjectID, SuiAddress, TransactionDigest}; use sui_types::messages::{Transaction, TransactionData}; use sui_types::sui_serde::Base64; pub struct RpcGatewayClient { - client: SuiRpcClient, + client: SuiClient, } -use sui_json_rpc_api::RpcReadApiClient; impl RpcGatewayClient { pub fn new(server_url: String) -> Result { Ok(Self { - client: SuiRpcClient::new(&server_url)?, + client: SuiClient::new_http_client(&server_url)?, }) } } @@ -42,7 +37,6 @@ impl GatewayAPI for RpcGatewayClient { Ok(self .client - .quorum_driver() .execute_transaction(tx_bytes, signature_bytes, pub_key) .await?) } @@ -57,8 +51,7 @@ impl GatewayAPI for RpcGatewayClient { ) -> Result { let bytes: TransactionBytes = self .client - .transaction_builder() - .public_transfer_object(signer, object_id, gas, gas_budget, recipient) + .transfer_object(signer, object_id, gas, gas_budget, recipient) .await?; bytes.to_data() } @@ -73,17 +66,13 @@ impl GatewayAPI for RpcGatewayClient { ) -> Result { let bytes: TransactionBytes = self .client - .transaction_builder() .transfer_sui(signer, sui_object_id, gas_budget, recipient, amount) .await?; bytes.to_data() } async fn sync_account_state(&self, account_addr: SuiAddress) -> Result<(), Error> { - self.client - .wallet_sync_api() - .sync_account_state(account_addr) - .await?; + self.client.sync_account_state(account_addr).await?; Ok(()) } @@ -100,7 +89,6 @@ impl GatewayAPI for RpcGatewayClient { ) -> Result { let bytes: TransactionBytes = self .client - .transaction_builder() .move_call( signer, package_object_id, @@ -128,7 +116,6 @@ impl GatewayAPI for RpcGatewayClient { .collect(); let bytes: TransactionBytes = self .client - .transaction_builder() .publish(signer, package_bytes, gas, gas_budget) .await?; bytes.to_data() @@ -144,7 +131,6 @@ impl GatewayAPI for RpcGatewayClient { ) -> Result { let bytes: TransactionBytes = self .client - .transaction_builder() .split_coin(signer, coin_object_id, split_amounts, gas, gas_budget) .await?; bytes.to_data() @@ -160,7 +146,6 @@ impl GatewayAPI for RpcGatewayClient { ) -> Result { let bytes: TransactionBytes = self .client - .transaction_builder() .merge_coin(signer, primary_coin, coin_to_merge, gas, gas_budget) .await?; bytes.to_data() @@ -175,48 +160,37 @@ impl GatewayAPI for RpcGatewayClient { ) -> Result { let bytes: TransactionBytes = self .client - .transaction_builder() .batch_transaction(signer, single_transaction_params, gas, gas_budget) .await?; bytes.to_data() } async fn get_object(&self, object_id: ObjectID) -> Result { - Ok(self.client.read_api().get_object(object_id).await?) + Ok(self.client.get_object(object_id).await?) } async fn get_raw_object(&self, object_id: ObjectID) -> Result { - Ok(self.client.read_api().get_raw_object(object_id).await?) + Ok(self.client.get_raw_object(object_id).await?) } async fn get_objects_owned_by_address( &self, address: SuiAddress, ) -> Result, Error> { - Ok(self - .client - .read_api() - .get_objects_owned_by_address(address) - .await?) + Ok(self.client.get_objects_owned_by_address(address).await?) } async fn get_objects_owned_by_object( &self, object_id: ObjectID, ) -> Result, Error> { - Ok(self - .client - .read_api() - .get_objects_owned_by_object(object_id) - .await?) + Ok(self.client.get_objects_owned_by_object(object_id).await?) } fn get_total_transaction_number(&self) -> Result { let handle = Handle::current(); let _ = handle.enter(); - Ok(futures::executor::block_on( - self.client.read_api().get_total_transaction_number(), - )?) + futures::executor::block_on(self.client.get_total_transaction_number()) } fn get_transactions_in_range( @@ -226,9 +200,7 @@ impl GatewayAPI for RpcGatewayClient { ) -> Result, Error> { let handle = Handle::current(); let _ = handle.enter(); - Ok(futures::executor::block_on( - self.client.read_api().get_transactions_in_range(start, end), - )?) + futures::executor::block_on(self.client.get_transactions_in_range(start, end)) } fn get_recent_transactions( @@ -237,15 +209,13 @@ impl GatewayAPI for RpcGatewayClient { ) -> Result, Error> { let handle = Handle::current(); let _ = handle.enter(); - Ok(futures::executor::block_on( - self.client.read_api().get_recent_transactions(count), - )?) + futures::executor::block_on(self.client.get_recent_transactions(count)) } async fn get_transaction( &self, digest: TransactionDigest, ) -> Result { - Ok(self.client.read_api().get_transaction(digest).await?) + Ok(self.client.get_transaction(digest).await?) } } diff --git a/crates/sui-gateway/src/unit_tests/rpc_server_tests.rs b/crates/sui-gateway/src/unit_tests/rpc_server_tests.rs index 543e52b42bf96..a9faa3880503c 100644 --- a/crates/sui-gateway/src/unit_tests/rpc_server_tests.rs +++ b/crates/sui-gateway/src/unit_tests/rpc_server_tests.rs @@ -7,14 +7,13 @@ use sui_config::SUI_KEYSTORE_FILENAME; use sui_core::gateway_state::GatewayTxSeqNumber; use sui_framework::build_move_package_to_bytes; use sui_json::SuiJsonValue; -use sui_json_rpc_api::keystore::{Keystore, SuiKeystore}; -use sui_json_rpc_api::rpc_types::{ - GetObjectDataResponse, TransactionEffectsResponse, TransactionResponse, +use sui_json_rpc::api::{ + RpcGatewayApiClient, RpcReadApiClient, RpcTransactionBuilderClient, WalletSyncApiClient, }; -use sui_json_rpc_api::{ - RpcGatewayApiClient, RpcReadApiClient, RpcTransactionBuilderClient, TransactionBytes, - WalletSyncApiClient, +use sui_json_rpc_types::{ + GetObjectDataResponse, TransactionBytes, TransactionEffectsResponse, TransactionResponse, }; +use sui_sdk::crypto::{Keystore, SuiKeystore}; use sui_types::sui_serde::Base64; use sui_types::{ base_types::{ObjectID, TransactionDigest}, @@ -44,7 +43,7 @@ async fn test_public_transfer_object() -> Result<(), anyhow::Error> { let objects = http_client.get_objects_owned_by_address(*address).await?; let tx_data: TransactionBytes = http_client - .public_transfer_object( + .transfer_object( *address, objects.first().unwrap().object_id, Some(objects.last().unwrap().object_id), @@ -192,7 +191,7 @@ async fn test_get_transaction() -> Result<(), anyhow::Error> { let mut tx_responses = Vec::new(); for oref in &objects[..objects.len() - 1] { let tx_data: TransactionBytes = http_client - .public_transfer_object(*address, oref.object_id, Some(gas_id), 1000, *address) + .transfer_object(*address, oref.object_id, Some(gas_id), 1000, *address) .await?; let keystore = diff --git a/crates/sui-json-rpc-api/src/client.rs b/crates/sui-json-rpc-api/src/client.rs deleted file mode 100644 index 092cbccf123b6..0000000000000 --- a/crates/sui-json-rpc-api/src/client.rs +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) 2022, Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -use jsonrpsee::http_client::{HttpClient, HttpClientBuilder}; - -pub use crate::RpcFullNodeReadApiClient; -pub use crate::RpcGatewayApiClient; -pub use crate::RpcReadApiClient; -pub use crate::RpcTransactionBuilderClient; -use crate::WalletSyncApiClient; - -pub struct SuiRpcClient { - client: HttpClient, -} - -impl SuiRpcClient { - pub fn new(server_url: &str) -> Result { - let client = HttpClientBuilder::default().build(server_url)?; - Ok(Self { client }) - } - - pub fn read_api(&self) -> &impl RpcReadApiClient { - &self.client - } - pub fn quorum_driver(&self) -> &impl RpcGatewayApiClient { - &self.client - } - pub fn wallet_sync_api(&self) -> &impl WalletSyncApiClient { - &self.client - } - pub fn full_node_read_api(&self) -> &impl RpcFullNodeReadApiClient { - &self.client - } - pub fn transaction_builder(&self) -> &impl RpcTransactionBuilderClient { - &self.client - } -} diff --git a/crates/sui-json-rpc-api/Cargo.toml b/crates/sui-json-rpc-types/Cargo.toml similarity index 97% rename from crates/sui-json-rpc-api/Cargo.toml rename to crates/sui-json-rpc-types/Cargo.toml index 50404660ad786..faba02cb407b9 100644 --- a/crates/sui-json-rpc-api/Cargo.toml +++ b/crates/sui-json-rpc-types/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "sui-json-rpc-api" +name = "sui-json-rpc-types" version = "0.0.0" authors = ["Mysten Labs "] license = "Apache-2.0" diff --git a/crates/sui-json-rpc-api/src/rpc_types.rs b/crates/sui-json-rpc-types/src/lib.rs similarity index 95% rename from crates/sui-json-rpc-api/src/rpc_types.rs rename to crates/sui-json-rpc-types/src/lib.rs index 4dd901e51ce3e..a97f80d48946a 100644 --- a/crates/sui-json-rpc-api/src/rpc_types.rs +++ b/crates/sui-json-rpc-types/src/lib.rs @@ -29,7 +29,7 @@ use sui_types::base_types::{ ObjectDigest, ObjectID, ObjectInfo, ObjectRef, SequenceNumber, SuiAddress, TransactionDigest, }; use sui_types::committee::EpochId; -use sui_types::crypto::{AuthorityStrongQuorumSignInfo, Signature}; +use sui_types::crypto::{AuthorityStrongQuorumSignInfo, SignableBytes, Signature}; use sui_types::error::SuiError; use sui_types::event::EventType; use sui_types::event::{Event, TransferType}; @@ -46,8 +46,10 @@ use sui_types::object::{Data, MoveObject, Object, ObjectFormatOptions, ObjectRea use sui_types::sui_serde::{Base64, Encoding}; #[cfg(test)] -#[path = "unit_tests/gateway_types_tests.rs"] -mod gateway_types_tests; +#[path = "unit_tests/rpc_types_tests.rs"] +mod rpc_types_tests; + +pub type GatewayTxSeqNumber = u64; #[derive(Serialize, Deserialize, Debug, JsonSchema)] pub struct TransactionEffectsResponse { @@ -514,7 +516,7 @@ impl Display for PublishResponse { pub type GetObjectDataResponse = SuiObjectRead; pub type GetRawObjectDataResponse = SuiObjectRead; -#[derive(Serialize, Deserialize, JsonSchema)] +#[derive(Serialize, Deserialize, Debug, JsonSchema)] #[serde(tag = "status", content = "details", rename = "ObjectRead")] pub enum SuiObjectRead { Exists(SuiObject), @@ -673,6 +675,45 @@ pub enum SuiMoveStruct { WithFields(BTreeMap), } +impl SuiMoveStruct { + pub fn to_json_value(self) -> Result { + // Unwrap MoveStructs + let unwrapped = match self { + SuiMoveStruct::Runtime(values) => { + let values = values + .into_iter() + .map(|value| match value { + SuiMoveValue::Struct(move_struct) => move_struct.to_json_value(), + SuiMoveValue::Vector(values) => { + SuiMoveStruct::Runtime(values).to_json_value() + } + _ => serde_json::to_value(&value), + }) + .collect::, _>>()?; + serde_json::to_value(&values) + } + // We only care about values here, assuming struct type information is known at the client side. + SuiMoveStruct::WithTypes { type_: _, fields } | SuiMoveStruct::WithFields(fields) => { + let fields = fields + .into_iter() + .map(|(key, value)| { + let value = match value { + SuiMoveValue::Struct(move_struct) => move_struct.to_json_value(), + SuiMoveValue::Vector(values) => { + SuiMoveStruct::Runtime(values).to_json_value() + } + _ => serde_json::to_value(&value), + }; + value.map(|value| (key, value)) + }) + .collect::, _>>()?; + serde_json::to_value(&fields) + } + }?; + serde_json::to_value(&unwrapped) + } +} + impl Display for SuiMoveStruct { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { let mut writer = String::new(); @@ -1514,3 +1555,30 @@ impl TryInto for SuiEventFilter { }) } } + +#[serde_as] +#[derive(Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "camelCase")] +pub struct TransactionBytes { + pub tx_bytes: Base64, + pub gas: SuiObjectRef, + pub input_objects: Vec, +} + +impl TransactionBytes { + pub fn from_data(data: TransactionData) -> Result { + Ok(Self { + tx_bytes: Base64::from_bytes(&data.to_bytes()), + gas: data.gas().into(), + input_objects: data + .input_objects()? + .into_iter() + .map(SuiInputObjectKind::from) + .collect(), + }) + } + + pub fn to_data(self) -> Result { + TransactionData::from_signable_bytes(&self.tx_bytes.to_vec()?) + } +} diff --git a/crates/sui-json-rpc-api/src/unit_tests/gateway_types_tests.rs b/crates/sui-json-rpc-types/src/unit_tests/rpc_types_tests.rs similarity index 98% rename from crates/sui-json-rpc-api/src/unit_tests/gateway_types_tests.rs rename to crates/sui-json-rpc-types/src/unit_tests/rpc_types_tests.rs index a9e493c56dfb4..f2c202703e6c4 100644 --- a/crates/sui-json-rpc-api/src/unit_tests/gateway_types_tests.rs +++ b/crates/sui-json-rpc-types/src/unit_tests/rpc_types_tests.rs @@ -6,7 +6,7 @@ use move_core_types::ident_str; use move_core_types::language_storage::StructTag; use move_core_types::value::{MoveStruct, MoveValue}; -use crate::rpc_types::{SuiMoveStruct, SuiMoveValue}; +use crate::{SuiMoveStruct, SuiMoveValue}; use sui_types::base_types::SequenceNumber; use sui_types::base_types::{ObjectID, SuiAddress}; use sui_types::gas_coin::GasCoin; diff --git a/crates/sui-json-rpc/Cargo.toml b/crates/sui-json-rpc/Cargo.toml index 5dd441aace9ba..f75c514aeebfe 100644 --- a/crates/sui-json-rpc/Cargo.toml +++ b/crates/sui-json-rpc/Cargo.toml @@ -9,6 +9,7 @@ edition = "2021" [dependencies] jsonrpsee = { version = "0.14.0", features = ["full"] } jsonrpsee-core = "0.14.0" +jsonrpsee-proc-macros = "0.14.0" prometheus = "0.13.1" anyhow = "1.0.58" tracing = "0.1.35" @@ -22,5 +23,6 @@ sui-core = { path = "../sui-core" } sui-types = { path = "../sui-types" } sui-json = { path = "../sui-json" } sui-open-rpc = { path = "../sui-open-rpc" } -sui-json-rpc-api = { path = "../sui-json-rpc-api" } +sui-open-rpc-macros = { path = "../sui-open-rpc-macros" } +sui-json-rpc-types= { path = "../sui-json-rpc-types" } workspace-hack = { path = "../workspace-hack"} diff --git a/crates/sui-json-rpc-api/src/lib.rs b/crates/sui-json-rpc/src/api.rs similarity index 84% rename from crates/sui-json-rpc-api/src/lib.rs rename to crates/sui-json-rpc/src/api.rs index 8928fa788afeb..b12d6809d83d1 100644 --- a/crates/sui-json-rpc-api/src/lib.rs +++ b/crates/sui-json-rpc/src/api.rs @@ -1,33 +1,17 @@ // Copyright (c) 2022, Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -use crate::rpc_types::SuiEventEnvelope; -use crate::rpc_types::SuiEventFilter; -use crate::rpc_types::{ - GetObjectDataResponse, GetRawObjectDataResponse, RPCTransactionRequestParams, - SuiInputObjectKind, SuiObjectInfo, SuiObjectRef, SuiTypeTag, TransactionEffectsResponse, - TransactionResponse, -}; use jsonrpsee::core::RpcResult; use jsonrpsee_proc_macros::rpc; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; -use serde_with::serde_as; use sui_json::SuiJsonValue; -use sui_open_rpc::Module; +use sui_json_rpc_types::{ + GatewayTxSeqNumber, GetObjectDataResponse, GetRawObjectDataResponse, + RPCTransactionRequestParams, SuiEventEnvelope, SuiEventFilter, SuiObjectInfo, SuiTypeTag, + TransactionBytes, TransactionEffectsResponse, TransactionResponse, +}; use sui_open_rpc_macros::open_rpc; +use sui_types::base_types::{ObjectID, SuiAddress, TransactionDigest}; use sui_types::sui_serde::Base64; -use sui_types::{ - base_types::{ObjectID, SuiAddress, TransactionDigest}, - crypto::SignableBytes, - messages::TransactionData, -}; - -pub mod client; -pub mod keystore; -pub mod rpc_types; - -type GatewayTxSeqNumber = u64; #[open_rpc(namespace = "sui", tag = "Gateway Transaction Execution API")] #[rpc(server, client, namespace = "sui")] @@ -135,7 +119,7 @@ pub trait RpcTransactionBuilder { /// Create a transaction to transfer an object from one address to another. The object's type /// must allow public transfers #[method(name = "transferObject")] - async fn public_transfer_object( + async fn transfer_object( &self, signer: SuiAddress, object_id: ObjectID, @@ -217,33 +201,6 @@ pub trait RpcBcsApi { async fn get_raw_object(&self, object_id: ObjectID) -> RpcResult; } -#[serde_as] -#[derive(Serialize, Deserialize, JsonSchema)] -#[serde(rename_all = "camelCase")] -pub struct TransactionBytes { - pub tx_bytes: Base64, - pub gas: SuiObjectRef, - pub input_objects: Vec, -} - -impl TransactionBytes { - pub fn from_data(data: TransactionData) -> Result { - Ok(Self { - tx_bytes: Base64::from_bytes(&data.to_bytes()), - gas: data.gas().into(), - input_objects: data - .input_objects()? - .into_iter() - .map(SuiInputObjectKind::from) - .collect(), - }) - } - - pub fn to_data(self) -> Result { - TransactionData::from_signable_bytes(&self.tx_bytes.to_vec()?) - } -} - #[open_rpc(namespace = "sui", tag = "Event Subscription")] #[rpc(server, client, namespace = "sui")] pub trait EventStreamingApi { diff --git a/crates/sui-json-rpc/src/bcs_api.rs b/crates/sui-json-rpc/src/bcs_api.rs index dcd1aabc422e2..aff00d72598cc 100644 --- a/crates/sui-json-rpc/src/bcs_api.rs +++ b/crates/sui-json-rpc/src/bcs_api.rs @@ -3,20 +3,18 @@ use std::sync::Arc; +use crate::api::RpcBcsApiServer; +use crate::SuiRpcModule; use anyhow::anyhow; use async_trait::async_trait; use jsonrpsee::core::RpcResult; use jsonrpsee::RpcModule; - use sui_core::authority::AuthorityState; use sui_core::gateway_state::GatewayClient; +use sui_json_rpc_types::GetRawObjectDataResponse; use sui_open_rpc::Module; use sui_types::base_types::ObjectID; -use crate::SuiRpcModule; -use sui_json_rpc_api::rpc_types::GetRawObjectDataResponse; -use sui_json_rpc_api::RpcBcsApiServer; - pub struct BcsApiImpl { client: ClientStateAdaptor, } @@ -69,6 +67,6 @@ impl SuiRpcModule for BcsApiImpl { } fn rpc_doc_module() -> Module { - sui_json_rpc_api::RpcBcsApiOpenRpc::module_doc() + crate::api::RpcBcsApiOpenRpc::module_doc() } } diff --git a/crates/sui-json-rpc/src/event_api.rs b/crates/sui-json-rpc/src/event_api.rs index d86ac57445b9c..de7aa75e7b869 100644 --- a/crates/sui-json-rpc/src/event_api.rs +++ b/crates/sui-json-rpc/src/event_api.rs @@ -1,5 +1,7 @@ // Copyright (c) 2022, Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 +use crate::api::EventReadApiServer; +use crate::api::EventStreamingApiServer; use crate::SuiRpcModule; use async_trait::async_trait; use futures::{StreamExt, TryStream}; @@ -12,9 +14,7 @@ use std::fmt::Display; use std::sync::Arc; use sui_core::authority::AuthorityState; use sui_core::event_handler::EventHandler; -use sui_json_rpc_api::rpc_types::{SuiEvent, SuiEventEnvelope, SuiEventFilter}; -use sui_json_rpc_api::EventReadApiServer; -use sui_json_rpc_api::EventStreamingApiServer; +use sui_json_rpc_types::{SuiEvent, SuiEventEnvelope, SuiEventFilter}; use sui_open_rpc::Module; use sui_types::base_types::{ObjectID, SuiAddress, TransactionDigest}; use tracing::warn; @@ -89,7 +89,7 @@ impl SuiRpcModule for EventStreamingApiImpl { } fn rpc_doc_module() -> Module { - sui_json_rpc_api::EventStreamingApiOpenRpc::module_doc() + crate::api::EventStreamingApiOpenRpc::module_doc() } } @@ -176,6 +176,6 @@ impl SuiRpcModule for EventReadApiImpl { } fn rpc_doc_module() -> Module { - sui_json_rpc_api::EventReadApiOpenRpc::module_doc() + crate::api::EventReadApiOpenRpc::module_doc() } } diff --git a/crates/sui-json-rpc/src/gateway_api.rs b/crates/sui-json-rpc/src/gateway_api.rs index d88491f1eb56e..32f48a9b5c066 100644 --- a/crates/sui-json-rpc/src/gateway_api.rs +++ b/crates/sui-json-rpc/src/gateway_api.rs @@ -8,16 +8,15 @@ use jsonrpsee::core::RpcResult; use jsonrpsee_core::server::rpc_module::RpcModule; use tracing::debug; +use crate::api::{ + RpcGatewayApiServer, RpcReadApiServer, RpcTransactionBuilderServer, WalletSyncApiServer, +}; use crate::SuiRpcModule; use sui_core::gateway_state::{GatewayClient, GatewayTxSeqNumber}; use sui_json::SuiJsonValue; -use sui_json_rpc_api::rpc_types::{ - GetObjectDataResponse, SuiObjectInfo, TransactionEffectsResponse, TransactionResponse, -}; -use sui_json_rpc_api::rpc_types::{RPCTransactionRequestParams, SuiTypeTag}; -use sui_json_rpc_api::{ - RpcGatewayApiServer, RpcReadApiServer, RpcTransactionBuilderServer, TransactionBytes, - WalletSyncApiServer, +use sui_json_rpc_types::{ + GetObjectDataResponse, RPCTransactionRequestParams, SuiObjectInfo, SuiTypeTag, + TransactionBytes, TransactionEffectsResponse, TransactionResponse, }; use sui_open_rpc::Module; use sui_types::sui_serde::Base64; @@ -93,7 +92,7 @@ impl SuiRpcModule for RpcGatewayImpl { } fn rpc_doc_module() -> Module { - sui_json_rpc_api::RpcGatewayApiOpenRpc::module_doc() + crate::api::RpcGatewayApiOpenRpc::module_doc() } } @@ -112,7 +111,7 @@ impl SuiRpcModule for GatewayWalletSyncApiImpl { } fn rpc_doc_module() -> Module { - sui_json_rpc_api::WalletSyncApiOpenRpc::module_doc() + crate::api::WalletSyncApiOpenRpc::module_doc() } } @@ -171,13 +170,13 @@ impl SuiRpcModule for GatewayReadApiImpl { } fn rpc_doc_module() -> Module { - sui_json_rpc_api::RpcReadApiOpenRpc::module_doc() + crate::api::RpcReadApiOpenRpc::module_doc() } } #[async_trait] impl RpcTransactionBuilderServer for TransactionBuilderImpl { - async fn public_transfer_object( + async fn transfer_object( &self, signer: SuiAddress, object_id: ObjectID, @@ -308,6 +307,6 @@ impl SuiRpcModule for TransactionBuilderImpl { } fn rpc_doc_module() -> Module { - sui_json_rpc_api::RpcTransactionBuilderOpenRpc::module_doc() + crate::api::RpcTransactionBuilderOpenRpc::module_doc() } } diff --git a/crates/sui-json-rpc/src/lib.rs b/crates/sui-json-rpc/src/lib.rs index 3eb6e43a14163..6271d11a3a6ae 100644 --- a/crates/sui-json-rpc/src/lib.rs +++ b/crates/sui-json-rpc/src/lib.rs @@ -16,6 +16,7 @@ use std::time::Instant; use sui_open_rpc::{Module, Project}; use tracing::info; +pub mod api; pub mod bcs_api; pub mod event_api; pub mod gateway_api; diff --git a/crates/sui-json-rpc/src/read_api.rs b/crates/sui-json-rpc/src/read_api.rs index d4811e7ed0feb..c44b642fff15e 100644 --- a/crates/sui-json-rpc/src/read_api.rs +++ b/crates/sui-json-rpc/src/read_api.rs @@ -1,6 +1,8 @@ // Copyright (c) 2022, Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 +use crate::api::RpcFullNodeReadApiServer; +use crate::api::RpcReadApiServer; use crate::SuiRpcModule; use anyhow::anyhow; use async_trait::async_trait; @@ -9,11 +11,9 @@ use jsonrpsee_core::server::rpc_module::RpcModule; use std::sync::Arc; use sui_core::authority::AuthorityState; use sui_core::gateway_state::GatewayTxSeqNumber; -use sui_json_rpc_api::rpc_types::{ +use sui_json_rpc_types::{ GetObjectDataResponse, SuiObjectInfo, SuiTransactionEffects, TransactionEffectsResponse, }; -use sui_json_rpc_api::RpcFullNodeReadApiServer; -use sui_json_rpc_api::RpcReadApiServer; use sui_open_rpc::Module; use sui_types::base_types::{ObjectID, SuiAddress, TransactionDigest}; use sui_types::object::Owner; @@ -115,7 +115,7 @@ impl SuiRpcModule for ReadApi { } fn rpc_doc_module() -> Module { - sui_json_rpc_api::RpcReadApiOpenRpc::module_doc() + crate::api::RpcReadApiOpenRpc::module_doc() } } @@ -171,6 +171,6 @@ impl SuiRpcModule for FullNodeApi { } fn rpc_doc_module() -> Module { - sui_json_rpc_api::RpcFullNodeReadApiOpenRpc::module_doc() + crate::api::RpcFullNodeReadApiOpenRpc::module_doc() } } diff --git a/crates/sui-open-rpc-macros/src/lib.rs b/crates/sui-open-rpc-macros/src/lib.rs index d93288bbea0a2..02ebdc3860a1e 100644 --- a/crates/sui-open-rpc-macros/src/lib.rs +++ b/crates/sui-open-rpc-macros/src/lib.rs @@ -71,7 +71,7 @@ pub fn open_rpc(attr: TokenStream, item: TokenStream) -> TokenStream { #trait_data pub struct #open_rpc_name; impl #open_rpc_name { - pub fn module_doc() -> Module{ + pub fn module_doc() -> sui_open_rpc::Module{ let mut builder = sui_open_rpc::RpcModuleDocBuilder::new(); #(#methods)* builder.build() diff --git a/crates/sui-open-rpc/Cargo.toml b/crates/sui-open-rpc/Cargo.toml index e8c3a5315d1d7..ab619a85c02d2 100644 --- a/crates/sui-open-rpc/Cargo.toml +++ b/crates/sui-open-rpc/Cargo.toml @@ -21,7 +21,7 @@ hyper = { version = "0.14.20", features = ["full"] } sui = { path = "../sui" } sui-json-rpc = { path = "../sui-json-rpc" } -sui-json-rpc-api = { path = "../sui-json-rpc-api" } +sui-json-rpc-types = { path = "../sui-json-rpc-types" } sui-json = { path = "../sui-json" } sui-types = { path = "../sui-types" } sui-config = { path = "../sui-config" } diff --git a/crates/sui-open-rpc/src/generate_json_rpc_spec.rs b/crates/sui-open-rpc/src/generate_json_rpc_spec.rs index 67f7d155dea30..d96a43c9b678d 100644 --- a/crates/sui-open-rpc/src/generate_json_rpc_spec.rs +++ b/crates/sui-open-rpc/src/generate_json_rpc_spec.rs @@ -19,20 +19,20 @@ use sui::client_commands::{EXAMPLE_NFT_DESCRIPTION, EXAMPLE_NFT_NAME, EXAMPLE_NF use sui_config::genesis_config::GenesisConfig; use sui_config::SUI_CLIENT_CONFIG; use sui_json::SuiJsonValue; +use sui_json_rpc::api::EventReadApiOpenRpc; +use sui_json_rpc::api::EventStreamingApiOpenRpc; +use sui_json_rpc::api::RpcReadApiClient; +use sui_json_rpc::api::RpcTransactionBuilderClient; +use sui_json_rpc::api::WalletSyncApiClient; use sui_json_rpc::bcs_api::BcsApiImpl; use sui_json_rpc::gateway_api::{GatewayWalletSyncApiImpl, RpcGatewayImpl, TransactionBuilderImpl}; use sui_json_rpc::read_api::{FullNodeApi, ReadApi}; use sui_json_rpc::sui_rpc_doc; use sui_json_rpc::SuiRpcModule; -use sui_json_rpc_api::rpc_types::{ - GetObjectDataResponse, SuiObjectInfo, TransactionEffectsResponse, TransactionResponse, +use sui_json_rpc_types::{ + GetObjectDataResponse, SuiObjectInfo, TransactionBytes, TransactionEffectsResponse, + TransactionResponse, }; -use sui_json_rpc_api::EventReadApiOpenRpc; -use sui_json_rpc_api::EventStreamingApiOpenRpc; -use sui_json_rpc_api::RpcReadApiClient; -use sui_json_rpc_api::RpcTransactionBuilderClient; -use sui_json_rpc_api::TransactionBytes; -use sui_json_rpc_api::WalletSyncApiClient; use sui_types::base_types::{ObjectID, SuiAddress}; use sui_types::sui_serde::{Base64, Encoding}; use sui_types::SUI_FRAMEWORK_ADDRESS; diff --git a/crates/sui-sdk/Cargo.toml b/crates/sui-sdk/Cargo.toml new file mode 100644 index 0000000000000..f894a93a64672 --- /dev/null +++ b/crates/sui-sdk/Cargo.toml @@ -0,0 +1,36 @@ +[package] +name = "sui-sdk" +version = "0.0.0" +authors = ["Mysten Labs "] +license = "Apache-2.0" +publish = false +edition = "2021" + +[dependencies] +anyhow = "1.0.58" +async-trait = "0.1.56" +jsonrpsee = { version = "0.14.0", features = ["full"] } +ed25519-dalek = { version = "1.0.1", features = ["batch", "serde"] } +serde = { version = "1.0.138", features = ["derive"] } +serde_json = "1.0.80" +futures-core = "0.3.21" +futures = "0.3.21" + +sui-json-rpc = { path = "../sui-json-rpc" } +sui-json-rpc-types= { path = "../sui-json-rpc-types" } +sui-types = { path = "../sui-types" } +sui-json = { path = "../sui-json" } + +workspace-hack = { path = "../workspace-hack"} + +[dev-dependencies] +clap = { version = "3.1.17", features = ["derive"] } +dirs = "4.0.0" +tokio = "1.19.2" +bcs = "0.1.3" +async-recursion = "1.0.0" + +[[example]] +name = "tic-tac-toe" +path = "examples/tic_tac_toe.rs" +test = false \ No newline at end of file diff --git a/crates/sui-sdk/README.md b/crates/sui-sdk/README.md new file mode 100644 index 0000000000000..79108a6520510 --- /dev/null +++ b/crates/sui-sdk/README.md @@ -0,0 +1,38 @@ +# Rust SDK examples + +Examples of interacting with the move contract using the Sui Rust SDK. + +## Tic Tac Toe + +### Demo quick start + +#### 1. Prepare the environment + * Install `sui` and `rpc-server` binaries following the [installation doc](https://github.com/MystenLabs/sui/blob/main/doc/src/build/install.md#binaries). + * [Connect to devnet](https://github.com/MystenLabs/sui/blob/main/doc/src/build/cli-client.md#connect-to-devnet). + * Make sure you have two addresses with gas, you can use the new-address command to create new addresses `sui client new-address`, + you can skip this step if you are going to play with a friend :) + * [Request gas tokens](https://github.com/MystenLabs/sui/blob/main/doc/src/explore/devnet.md#request-gas-tokens) for all addresses that will be used to join the game. + +#### 2. Publish the move contract + * [Download the Sui source code](https://github.com/MystenLabs/sui/blob/main/doc/src/build/install.md#source-code) + * Publish the [`games` package](https://github.com/MystenLabs/sui/tree/main/sui_programmability/examples/games) + using the Sui client and copy down the package object ID. + ```shell + sui client publish --path /path-to-sui-source-code/sui_programmability/examples/games --gas-budget 10000 + ``` +#### 3. Create a new tic-tac-toe game + * run the following command in the sui source code folder to start a new game. + ```shell + cargo run --example tic-tac-toe -- --game-package-id <> new-game + ``` + this will create a game for the first two addresses in your keystore by default. If you want to specify the identity of each player, +you can use the following command + ```shell + cargo run --example tic-tac-toe -- --game-package-id <> new-game --player-x <> --player-o <> + ``` + * Copy the game id and pass it to your friend to join the game. +#### 4. Joining the game + * run the following command in the sui source code folder to join the game. + ```shell + cargo run --example tic-tac-toe -- --game-package-id <> join-game --my-identity <
> --game-id <> + ``` \ No newline at end of file diff --git a/crates/sui-sdk/examples/event_subscription.rs b/crates/sui-sdk/examples/event_subscription.rs new file mode 100644 index 0000000000000..7edaea5af1635 --- /dev/null +++ b/crates/sui-sdk/examples/event_subscription.rs @@ -0,0 +1,15 @@ +// Copyright (c) 2022, Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +use futures::StreamExt; +use sui_sdk::rpc_types::SuiEventFilter; +use sui_sdk::SuiClient; + +#[tokio::main] +async fn main() -> Result<(), anyhow::Error> { + let sui = SuiClient::new_ws_client("ws://127.0.0.1:9001").await?; + let mut subscribe_all = sui.subscribe_event(SuiEventFilter::All(vec![])).await?; + loop { + println!("{:?}", subscribe_all.next().await); + } +} diff --git a/crates/sui-sdk/examples/get_owned_objects.rs b/crates/sui-sdk/examples/get_owned_objects.rs new file mode 100644 index 0000000000000..b9bdf02745575 --- /dev/null +++ b/crates/sui-sdk/examples/get_owned_objects.rs @@ -0,0 +1,15 @@ +// Copyright (c) 2022, Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +use std::str::FromStr; +use sui_sdk::types::base_types::SuiAddress; +use sui_sdk::SuiClient; + +#[tokio::main] +async fn main() -> Result<(), anyhow::Error> { + let sui = SuiClient::new_http_client("https://gateway.devnet.sui.io:443")?; + let address = SuiAddress::from_str("0xec11cad080d0496a53bafcea629fcbcfff2a9866")?; + let objects = sui.get_objects_owned_by_address(address).await?; + println!("{:?}", objects); + Ok(()) +} diff --git a/crates/sui-sdk/examples/tic_tac_toe.rs b/crates/sui-sdk/examples/tic_tac_toe.rs new file mode 100644 index 0000000000000..577fba709ed46 --- /dev/null +++ b/crates/sui-sdk/examples/tic_tac_toe.rs @@ -0,0 +1,346 @@ +// Copyright (c) 2022, Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +use anyhow::anyhow; +use async_recursion::async_recursion; +use clap::Parser; +use clap::Subcommand; +use serde::Deserialize; +use std::io::{stdin, stdout, Write}; +use std::path::PathBuf; +use std::str::FromStr; +use std::thread; +use std::time::Duration; +use sui_sdk::{ + crypto::{Keystore, SuiKeystore}, + json::SuiJsonValue, + types::{ + base_types::{ObjectID, SuiAddress}, + crypto::SignableBytes, + id::VersionedID, + messages::TransactionData, + sui_serde::Base64, + }, + SuiClient, +}; + +#[tokio::main] +async fn main() -> Result<(), anyhow::Error> { + let opts: TicTacToeOpts = TicTacToeOpts::parse(); + let keystore_path = opts.keystore_path.unwrap_or_else(default_keystore_path); + let keystore = SuiKeystore::load_or_create(&keystore_path)?; + + let game = TicTacToe { + game_package_id: opts.game_package_id, + client: SuiClient::new_http_client(&opts.rpc_server_url)?, + keystore, + }; + + match opts.subcommand { + TicTacToeCommand::NewGame { player_x, player_o } => { + game.create_game(player_x, player_o).await?; + } + TicTacToeCommand::JoinGame { + my_identity, + game_id, + } => { + game.join_game(game_id, my_identity).await?; + } + } + + Ok(()) +} + +struct TicTacToe { + game_package_id: ObjectID, + client: SuiClient, + keystore: SuiKeystore, +} + +impl TicTacToe { + async fn create_game( + &self, + player_x: Option, + player_o: Option, + ) -> Result<(), anyhow::Error> { + // Default player identity to first and second keys in the keystore if not provided. + let player_x = player_x.unwrap_or_else(|| self.keystore.addresses()[0]); + let player_o = player_o.unwrap_or_else(|| self.keystore.addresses()[1]); + + // Force a sync of signer's state in gateway. + self.client.sync_account_state(player_x).await?; + + // Create a move call transaction using the TransactionBuilder API. + let create_game_call = self + .client + .move_call( + player_x, + self.game_package_id, + "shared_tic_tac_toe".to_string(), + "create_game".to_string(), + vec![], + vec![ + SuiJsonValue::from_str(&player_x.to_string())?, + SuiJsonValue::from_str(&player_o.to_string())?, + ], + None, // The gateway server will pick a gas object belong to the signer if not provided. + 1000, + ) + .await?; + + // Sign the transaction. + let transaction_bytes = create_game_call.tx_bytes.to_vec()?; + + // You can do some extra verification here to make sure the transaction created is correct before signing. + let _transaction = TransactionData::from_signable_bytes(&transaction_bytes)?; + + // Create a signature using the keystore. + let signature = self.keystore.sign(&player_x, &transaction_bytes)?; + let signature_base64 = Base64::from_bytes(signature.signature_bytes()); + let pub_key = Base64::from_bytes(signature.public_key_bytes()); + + // Execute the transaction. + let response = self + .client + .execute_transaction(create_game_call.tx_bytes, signature_base64, pub_key) + .await?; + + // We know `create_game` move function will create 1 object. + let game_id = response + .to_effect_response()? + .effects + .created + .first() + .unwrap() + .reference + .object_id; + + println!("Created new game, game id : [{}]", game_id); + println!("Player X : {}", player_x); + println!("Player O : {}", player_o); + + self.join_game(game_id, player_x).await?; + Ok(()) + } + + async fn join_game( + &self, + game_id: ObjectID, + my_identity: SuiAddress, + ) -> Result<(), anyhow::Error> { + let game_state = self.fetch_game_state(game_id).await?; + if game_state.o_address == my_identity { + println!("You are player O") + } else if game_state.x_address == my_identity { + println!("You are player X") + } else { + return Err(anyhow!("You are not invited to the game.")); + } + self.next_turn(my_identity, game_state).await + } + + #[async_recursion] + async fn next_turn( + &self, + my_identity: SuiAddress, + game_state: TicTacToeState, + ) -> Result<(), anyhow::Error> { + game_state.print_game_board(); + + // return if game ended. + if game_state.game_status != 0 { + println!("Game ended."); + match game_state.game_status { + 1 => println!("Player X won!"), + 2 => println!("Player O won!"), + 3 => println!("It's a draw!"), + _ => {} + } + return Ok(()); + } + + if game_state.is_my_turn(my_identity) { + println!("It's your turn!"); + let row = get_row_col_input(true) - 1; + let col = get_row_col_input(false) - 1; + + // Create a move call transaction using the TransactionBuilder API. + let place_mark_call = self + .client + .move_call( + my_identity, + self.game_package_id, + "shared_tic_tac_toe".to_string(), + "place_mark".to_string(), + vec![], + vec![ + SuiJsonValue::from_str(&game_state.id.object_id().to_hex_literal())?, + SuiJsonValue::from_str(&row.to_string())?, + SuiJsonValue::from_str(&col.to_string())?, + ], + None, + 1000, + ) + .await?; + + // Sign the transaction. + let transaction_bytes = place_mark_call.tx_bytes.to_vec()?; + let signature = self.keystore.sign(&my_identity, &transaction_bytes)?; + let signature_base64 = Base64::from_bytes(signature.signature_bytes()); + let pub_key = Base64::from_bytes(signature.public_key_bytes()); + + // Execute the transaction. + let response = self + .client + .execute_transaction(place_mark_call.tx_bytes, signature_base64, pub_key) + .await?; + + // Print any execution error. + let status = response.to_effect_response()?.effects.status; + if status.is_err() { + eprintln!("{:?}", status); + } + // Proceed to next turn. + self.next_turn( + my_identity, + self.fetch_game_state(*game_state.id.object_id()).await?, + ) + .await?; + } else { + println!("Waiting for opponent..."); + // Sleep until my turn. + while !self + .fetch_game_state(*game_state.id.object_id()) + .await? + .is_my_turn(my_identity) + { + thread::sleep(Duration::from_secs(1)); + } + self.next_turn( + my_identity, + self.fetch_game_state(*game_state.id.object_id()).await?, + ) + .await?; + }; + Ok(()) + } + + // Retrieve the latest game state from the server. + async fn fetch_game_state(&self, game_id: ObjectID) -> Result { + // Get the raw BCS serialised move object data + let current_game = self.client.get_raw_object(game_id).await?; + let current_game_bytes = current_game + .object()? + .data + .try_as_move() + .map(|m| &m.bcs_bytes) + .unwrap(); + // Deserialize the data bytes into TicTacToeState struct + Ok(bcs::from_bytes(current_game_bytes)?) + } +} + +// Helper function for getting console input +fn get_row_col_input(is_row: bool) -> u8 { + let r_c = if is_row { "row" } else { "column" }; + print!("Enter {} number (1-3) : ", r_c); + let _ = stdout().flush(); + let mut s = String::new(); + stdin() + .read_line(&mut s) + .expect("Did not enter a correct string"); + + if let Ok(number) = s.trim().parse() { + if number > 0 && number < 4 { + return number; + } + } + get_row_col_input(is_row) +} + +// Clap command line args parser +#[derive(Parser)] +#[clap( + name = "tic-tac-toe", + about = "A Byzantine fault tolerant Tic-Tac-Toe with low-latency finality and high throughput", + rename_all = "kebab-case" +)] +struct TicTacToeOpts { + #[clap(long)] + game_package_id: ObjectID, + #[clap(long)] + keystore_path: Option, + #[clap(long, default_value = "https://gateway.devnet.sui.io:443")] + rpc_server_url: String, + #[clap(subcommand)] + subcommand: TicTacToeCommand, +} + +fn default_keystore_path() -> PathBuf { + match dirs::home_dir() { + Some(v) => v.join(".sui").join("sui_config").join("sui.keystore"), + None => panic!("Cannot obtain home directory path"), + } +} + +#[derive(Subcommand)] +#[clap(rename_all = "kebab-case")] +enum TicTacToeCommand { + NewGame { + #[clap(long)] + player_x: Option, + #[clap(long)] + player_o: Option, + }, + JoinGame { + #[clap(long)] + my_identity: SuiAddress, + #[clap(long)] + game_id: ObjectID, + }, +} + +// Data structure mirroring move object `games::shared_tic_tac_toe::TicTacToe` for deserialization. +#[derive(Deserialize, Debug)] +struct TicTacToeState { + id: VersionedID, + gameboard: Vec>, + cur_turn: u8, + game_status: u8, + x_address: SuiAddress, + o_address: SuiAddress, +} + +impl TicTacToeState { + fn print_game_board(&self) { + println!(" 1 2 3"); + print!(" ┌-----┬-----┬-----┐"); + let mut row_num = 1; + for row in &self.gameboard { + println!(); + print!("{} ", row_num); + for cell in row { + let mark = match cell { + 0 => "X", + 1 => "O", + _ => " ", + }; + print!("| {} ", mark) + } + println!("|"); + print!(" ├-----┼-----┼-----┤"); + row_num += 1; + } + print!("\r"); + println!(" └-----┴-----┴-----┘"); + } + + fn is_my_turn(&self, my_identity: SuiAddress) -> bool { + let current_player = if self.cur_turn % 2 == 0 { + self.x_address + } else { + self.o_address + }; + current_player == my_identity + } +} diff --git a/crates/sui-sdk/examples/transfer_coins.rs b/crates/sui-sdk/examples/transfer_coins.rs new file mode 100644 index 0000000000000..383f9d9856631 --- /dev/null +++ b/crates/sui-sdk/examples/transfer_coins.rs @@ -0,0 +1,44 @@ +// Copyright (c) 2022, Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +use std::str::FromStr; +use sui_sdk::crypto::{Keystore, SuiKeystore}; +use sui_sdk::types::base_types::{ObjectID, SuiAddress}; +use sui_sdk::types::sui_serde::Base64; +use sui_sdk::SuiClient; + +#[tokio::main] +async fn main() -> Result<(), anyhow::Error> { + let sui = SuiClient::new_http_client("https://gateway.devnet.sui.io:443")?; + // Load keystore from ~/.sui/sui_config/sui.keystore + let keystore_path = match dirs::home_dir() { + Some(v) => v.join(".sui").join("sui_config").join("sui.keystore"), + None => panic!("Cannot obtain home directory path"), + }; + let keystore = SuiKeystore::load_or_create(&keystore_path)?; + + let my_address = SuiAddress::from_str("0x47722589dc23d63e82862f7814070002ffaaa465")?; + let gas_object_id = ObjectID::from_str("0x273b2a83f1af1fda3ddbc02ad31367fcb146a814")?; + let recipient = SuiAddress::from_str("0xbd42a850e81ebb8f80283266951d4f4f5722e301")?; + + // Create a sui transfer transaction + let transfer_tx = sui + .transfer_sui(my_address, gas_object_id, 1000, recipient, Some(1000)) + .await?; + + // Sign the transaction + let signature = keystore.sign(&my_address, &transfer_tx.tx_bytes.to_vec()?)?; + + // Execute the transaction + let transaction_response = sui + .execute_transaction( + transfer_tx.tx_bytes, + Base64::from_bytes(signature.signature_bytes()), + Base64::from_bytes(signature.public_key_bytes()), + ) + .await?; + + println!("{:?}", transaction_response); + + Ok(()) +} diff --git a/crates/sui-json-rpc-api/src/keystore.rs b/crates/sui-sdk/src/crypto.rs similarity index 100% rename from crates/sui-json-rpc-api/src/keystore.rs rename to crates/sui-sdk/src/crypto.rs diff --git a/crates/sui-sdk/src/lib.rs b/crates/sui-sdk/src/lib.rs new file mode 100644 index 0000000000000..9e8a8f613246e --- /dev/null +++ b/crates/sui-sdk/src/lib.rs @@ -0,0 +1,362 @@ +// Copyright (c) 2022, Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +use anyhow::anyhow; +use futures::StreamExt; +use futures_core::Stream; +use jsonrpsee::core::client::Subscription; +use jsonrpsee::http_client::{HttpClient, HttpClientBuilder}; +use jsonrpsee::ws_client::{WsClient, WsClientBuilder}; +use sui_json::SuiJsonValue; +use sui_json_rpc::api::EventStreamingApiClient; +use sui_json_rpc::api::RpcBcsApiClient; +use sui_json_rpc::api::RpcFullNodeReadApiClient; +use sui_json_rpc::api::RpcGatewayApiClient; +use sui_json_rpc::api::RpcReadApiClient; +use sui_json_rpc::api::RpcTransactionBuilderClient; +use sui_json_rpc::api::WalletSyncApiClient; +use sui_json_rpc_types::{ + GatewayTxSeqNumber, GetObjectDataResponse, GetRawObjectDataResponse, + RPCTransactionRequestParams, SuiEventEnvelope, SuiEventFilter, SuiObjectInfo, SuiTypeTag, + TransactionBytes, TransactionEffectsResponse, TransactionResponse, +}; +use sui_types::base_types::{ObjectID, SuiAddress, TransactionDigest}; +use sui_types::sui_serde::Base64; +pub mod crypto; + +// re-export essential sui crates +pub use sui_json as json; +pub use sui_json_rpc_types as rpc_types; +pub use sui_types as types; + +pub struct SuiClient { + client: Client, +} + +impl SuiClient { + pub fn new_http_client(server_url: &str) -> Result { + let client = HttpClientBuilder::default().build(server_url)?; + Ok(Self { + client: Client::Http(client), + }) + } + + pub async fn new_ws_client(server_url: &str) -> Result { + let client = WsClientBuilder::default().build(server_url).await?; + Ok(Self { + client: Client::Ws(client), + }) + } +} + +impl SuiClient { + pub async fn get_objects_owned_by_address( + &self, + address: SuiAddress, + ) -> anyhow::Result> { + Ok(match &self.client { + Client::Http(c) => c.get_objects_owned_by_address(address), + Client::Ws(c) => c.get_objects_owned_by_address(address), + } + .await?) + } + + pub async fn get_objects_owned_by_object( + &self, + object_id: ObjectID, + ) -> anyhow::Result> { + Ok(match &self.client { + Client::Http(c) => c.get_objects_owned_by_object(object_id), + Client::Ws(c) => c.get_objects_owned_by_object(object_id), + } + .await?) + } + + pub async fn get_total_transaction_number(&self) -> anyhow::Result { + Ok(match &self.client { + Client::Http(c) => c.get_total_transaction_number(), + Client::Ws(c) => c.get_total_transaction_number(), + } + .await?) + } + + pub async fn get_transactions_in_range( + &self, + start: GatewayTxSeqNumber, + end: GatewayTxSeqNumber, + ) -> anyhow::Result> { + Ok(match &self.client { + Client::Http(c) => c.get_transactions_in_range(start, end), + Client::Ws(c) => c.get_transactions_in_range(start, end), + } + .await?) + } + + pub async fn get_recent_transactions( + &self, + count: u64, + ) -> anyhow::Result> { + Ok(match &self.client { + Client::Http(c) => c.get_recent_transactions(count), + Client::Ws(c) => c.get_recent_transactions(count), + } + .await?) + } + + pub async fn get_transaction( + &self, + digest: TransactionDigest, + ) -> anyhow::Result { + Ok(match &self.client { + Client::Http(c) => c.get_transaction(digest), + Client::Ws(c) => c.get_transaction(digest), + } + .await?) + } + + pub async fn get_object(&self, object_id: ObjectID) -> anyhow::Result { + Ok(match &self.client { + Client::Http(c) => c.get_object(object_id), + Client::Ws(c) => c.get_object(object_id), + } + .await?) + } + + pub async fn get_raw_object( + &self, + object_id: ObjectID, + ) -> anyhow::Result { + Ok(match &self.client { + Client::Http(c) => c.get_raw_object(object_id), + Client::Ws(c) => c.get_raw_object(object_id), + } + .await?) + } + + pub async fn get_transactions_by_input_object( + &self, + object: ObjectID, + ) -> anyhow::Result> { + Ok(match &self.client { + Client::Http(c) => c.get_transactions_by_input_object(object), + Client::Ws(c) => c.get_transactions_by_input_object(object), + } + .await?) + } + + pub async fn get_transactions_by_mutated_object( + &self, + object: ObjectID, + ) -> anyhow::Result> { + Ok(match &self.client { + Client::Http(c) => c.get_transactions_by_mutated_object(object), + Client::Ws(c) => c.get_transactions_by_mutated_object(object), + } + .await?) + } + + pub async fn get_transactions_by_move_function( + &self, + package: ObjectID, + module: Option, + function: Option, + ) -> anyhow::Result> { + Ok(match &self.client { + Client::Http(c) => c.get_transactions_by_move_function(package, module, function), + Client::Ws(c) => c.get_transactions_by_move_function(package, module, function), + } + .await?) + } + + pub async fn get_transactions_from_addr( + &self, + addr: SuiAddress, + ) -> anyhow::Result> { + Ok(match &self.client { + Client::Http(c) => c.get_transactions_from_addr(addr), + Client::Ws(c) => c.get_transactions_from_addr(addr), + } + .await?) + } + + pub async fn get_transactions_to_addr( + &self, + addr: SuiAddress, + ) -> anyhow::Result> { + Ok(match &self.client { + Client::Http(c) => c.get_transactions_to_addr(addr), + Client::Ws(c) => c.get_transactions_to_addr(addr), + } + .await?) + } + + pub async fn execute_transaction( + &self, + tx_bytes: Base64, + signature: Base64, + pub_key: Base64, + ) -> anyhow::Result { + Ok(match &self.client { + Client::Http(c) => c.execute_transaction(tx_bytes, signature, pub_key), + Client::Ws(c) => c.execute_transaction(tx_bytes, signature, pub_key), + } + .await?) + } + + pub async fn transfer_object( + &self, + signer: SuiAddress, + object_id: ObjectID, + gas: Option, + gas_budget: u64, + recipient: SuiAddress, + ) -> anyhow::Result { + Ok(match &self.client { + Client::Http(c) => c.transfer_object(signer, object_id, gas, gas_budget, recipient), + Client::Ws(c) => c.transfer_object(signer, object_id, gas, gas_budget, recipient), + } + .await?) + } + + pub async fn transfer_sui( + &self, + signer: SuiAddress, + sui_object_id: ObjectID, + gas_budget: u64, + recipient: SuiAddress, + amount: Option, + ) -> anyhow::Result { + Ok(match &self.client { + Client::Http(c) => c.transfer_sui(signer, sui_object_id, gas_budget, recipient, amount), + Client::Ws(c) => c.transfer_sui(signer, sui_object_id, gas_budget, recipient, amount), + } + .await?) + } + + pub async fn move_call( + &self, + signer: SuiAddress, + package_object_id: ObjectID, + module: String, + function: String, + type_arguments: Vec, + arguments: Vec, + gas: Option, + gas_budget: u64, + ) -> anyhow::Result { + Ok(match &self.client { + Client::Http(c) => c.move_call( + signer, + package_object_id, + module, + function, + type_arguments, + arguments, + gas, + gas_budget, + ), + Client::Ws(c) => c.move_call( + signer, + package_object_id, + module, + function, + type_arguments, + arguments, + gas, + gas_budget, + ), + } + .await?) + } + + pub async fn publish( + &self, + sender: SuiAddress, + compiled_modules: Vec, + gas: Option, + gas_budget: u64, + ) -> anyhow::Result { + Ok(match &self.client { + Client::Http(c) => c.publish(sender, compiled_modules, gas, gas_budget), + Client::Ws(c) => c.publish(sender, compiled_modules, gas, gas_budget), + } + .await?) + } + + pub async fn split_coin( + &self, + signer: SuiAddress, + coin_object_id: ObjectID, + split_amounts: Vec, + gas: Option, + gas_budget: u64, + ) -> anyhow::Result { + Ok(match &self.client { + Client::Http(c) => c.split_coin(signer, coin_object_id, split_amounts, gas, gas_budget), + Client::Ws(c) => c.split_coin(signer, coin_object_id, split_amounts, gas, gas_budget), + } + .await?) + } + + pub async fn merge_coin( + &self, + signer: SuiAddress, + primary_coin: ObjectID, + coin_to_merge: ObjectID, + gas: Option, + gas_budget: u64, + ) -> anyhow::Result { + Ok(match &self.client { + Client::Http(c) => c.merge_coin(signer, primary_coin, coin_to_merge, gas, gas_budget), + Client::Ws(c) => c.merge_coin(signer, primary_coin, coin_to_merge, gas, gas_budget), + } + .await?) + } + + pub async fn batch_transaction( + &self, + signer: SuiAddress, + single_transaction_params: Vec, + gas: Option, + gas_budget: u64, + ) -> anyhow::Result { + Ok(match &self.client { + Client::Http(c) => { + c.batch_transaction(signer, single_transaction_params, gas, gas_budget) + } + Client::Ws(c) => { + c.batch_transaction(signer, single_transaction_params, gas, gas_budget) + } + } + .await?) + } + + pub async fn sync_account_state(&self, address: SuiAddress) -> anyhow::Result<()> { + Ok(match &self.client { + Client::Http(c) => c.sync_account_state(address), + Client::Ws(c) => c.sync_account_state(address), + } + .await?) + } + + pub async fn subscribe_event( + &self, + filter: SuiEventFilter, + ) -> anyhow::Result>> { + match &self.client { + Client::Ws(c) => { + let subscription: Subscription = + c.subscribe_event(filter).await?; + Ok(subscription.map(|item| Ok(item?))) + } + _ => Err(anyhow!( + "Subscription only supported with web socket client." + )), + } + } +} + +enum Client { + Http(HttpClient), + Ws(WsClient), +} diff --git a/crates/sui/Cargo.toml b/crates/sui/Cargo.toml index 15e83af633548..aee4d724c8e46 100644 --- a/crates/sui/Cargo.toml +++ b/crates/sui/Cargo.toml @@ -26,7 +26,8 @@ sui-types = { path = "../sui-types" } sui-json = { path = "../sui-json" } sui-gateway = { path = "../sui-gateway" } sui-swarm = { path = "../sui-swarm" } -sui-json-rpc-api = { path = "../sui-json-rpc-api" } +sui-json-rpc-types= { path = "../sui-json-rpc-types" } +sui-sdk = { path = "../sui-sdk" } rustyline = "9.1.2" rustyline-derive = "0.6.0" diff --git a/crates/sui/src/client_commands.rs b/crates/sui/src/client_commands.rs index 7900f1ae0f061..2dcd7da5d266e 100644 --- a/crates/sui/src/client_commands.rs +++ b/crates/sui/src/client_commands.rs @@ -16,7 +16,7 @@ use move_core_types::{language_storage::TypeTag, parser::parse_type_tag}; use move_package::BuildConfig; use serde::Serialize; use serde_json::json; -use sui_json_rpc_api::rpc_types::{ +use sui_json_rpc_types::{ GetObjectDataResponse, MergeCoinResponse, PublishResponse, SplitCoinResponse, SuiObjectInfo, SuiParsedObject, }; @@ -25,10 +25,8 @@ use tracing::info; use sui_core::gateway_state::GatewayClient; use sui_framework::build_move_package_to_bytes; use sui_json::SuiJsonValue; -use sui_json_rpc_api::keystore::Keystore; -use sui_json_rpc_api::rpc_types::{ - SuiCertifiedTransaction, SuiExecutionStatus, SuiTransactionEffects, -}; +use sui_json_rpc_types::{SuiCertifiedTransaction, SuiExecutionStatus, SuiTransactionEffects}; +use sui_sdk::crypto::Keystore; use sui_types::object::Owner; use sui_types::sui_serde::{Base64, Encoding}; use sui_types::{ diff --git a/crates/sui/src/config/mod.rs b/crates/sui/src/config/mod.rs index 563ddbba56c19..28dba1697d092 100644 --- a/crates/sui/src/config/mod.rs +++ b/crates/sui/src/config/mod.rs @@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize}; use serde_with::{hex::Hex, serde_as}; use std::fmt::{Display, Formatter, Write}; -use sui_json_rpc_api::keystore::KeystoreType; +use sui_sdk::crypto::KeystoreType; use sui_types::base_types::*; pub use sui_config::Config; diff --git a/crates/sui/src/keytool.rs b/crates/sui/src/keytool.rs index 5ba835a9d046d..a952c12b35dc9 100644 --- a/crates/sui/src/keytool.rs +++ b/crates/sui/src/keytool.rs @@ -5,7 +5,7 @@ use anyhow::anyhow; use clap::*; use std::fs; use std::path::Path; -use sui_json_rpc_api::keystore::{Keystore, SuiKeystore}; +use sui_sdk::crypto::{Keystore, SuiKeystore}; use sui_types::base_types::decode_bytes_hex; use sui_types::sui_serde::{Base64, Encoding}; use sui_types::{ diff --git a/crates/sui/src/sui_commands.rs b/crates/sui/src/sui_commands.rs index df86ca0feee61..040de3cf900b3 100644 --- a/crates/sui/src/sui_commands.rs +++ b/crates/sui/src/sui_commands.rs @@ -20,8 +20,8 @@ use sui_config::{ sui_config_dir, Config, PersistedConfig, SUI_CLIENT_CONFIG, SUI_FULLNODE_CONFIG, SUI_GATEWAY_CONFIG, SUI_NETWORK_CONFIG, }; -use sui_json_rpc_api::client::SuiRpcClient; -use sui_json_rpc_api::keystore::{KeystoreType, SuiKeystore}; +use sui_sdk::crypto::{KeystoreType, SuiKeystore}; +use sui_sdk::SuiClient; use sui_swarm::memory::Swarm; use sui_types::base_types::SuiAddress; use tracing::info; @@ -386,7 +386,7 @@ fn prompt_if_no_config(wallet_conf_path: &Path) -> Result<(), anyhow::Error> { }; // Check url is valid - SuiRpcClient::new(url)?; + SuiClient::new_http_client(url)?; let keystore_path = wallet_conf_path .parent() .unwrap_or(&sui_config_dir()?) diff --git a/crates/sui/src/unit_tests/cli_tests.rs b/crates/sui/src/unit_tests/cli_tests.rs index 4132fe69453ac..d2155a2935fd9 100644 --- a/crates/sui/src/unit_tests/cli_tests.rs +++ b/crates/sui/src/unit_tests/cli_tests.rs @@ -19,8 +19,8 @@ use sui_config::{ SUI_GATEWAY_CONFIG, SUI_GENESIS_FILENAME, SUI_KEYSTORE_FILENAME, SUI_NETWORK_CONFIG, }; use sui_json::SuiJsonValue; -use sui_json_rpc_api::keystore::KeystoreType; -use sui_json_rpc_api::rpc_types::{GetObjectDataResponse, SuiParsedObject, SuiTransactionEffects}; +use sui_json_rpc_types::{GetObjectDataResponse, SuiParsedObject, SuiTransactionEffects}; +use sui_sdk::crypto::KeystoreType; use sui_types::{base_types::ObjectID, crypto::get_key_pair, gas_coin::GasCoin}; use test_utils::network::{setup_network_and_wallet, start_test_network}; diff --git a/crates/sui/tests/full_node_tests.rs b/crates/sui/tests/full_node_tests.rs index 342a5d708df5d..bd3333a590931 100644 --- a/crates/sui/tests/full_node_tests.rs +++ b/crates/sui/tests/full_node_tests.rs @@ -21,11 +21,9 @@ use tracing::info; use sui::client_commands::{SuiClientCommandResult, SuiClientCommands, WalletContext}; use sui_core::authority::AuthorityState; use sui_json::SuiJsonValue; -use sui_json_rpc_api::rpc_types::{ - SplitCoinResponse, SuiEventEnvelope, SuiEventFilter, TransactionResponse, -}; -use sui_json_rpc_api::rpc_types::{ - SuiEvent, SuiMoveStruct, SuiMoveValue, SuiObjectInfo, SuiObjectRead, +use sui_json_rpc_types::{ + SplitCoinResponse, SuiEvent, SuiEventEnvelope, SuiEventFilter, SuiMoveStruct, SuiMoveValue, + SuiObjectInfo, SuiObjectRead, TransactionResponse, }; use sui_node::SuiNode; use sui_swarm::memory::Swarm; diff --git a/crates/test-utils/Cargo.toml b/crates/test-utils/Cargo.toml index 9d3e53ae9e92e..6ebfc80e75c43 100644 --- a/crates/test-utils/Cargo.toml +++ b/crates/test-utils/Cargo.toml @@ -23,10 +23,11 @@ sui-core = { path = "../sui-core" } sui-framework = { path = "../sui-framework" } sui-gateway = { path = "../sui-gateway" } sui-json-rpc = { path = "../sui-json-rpc" } -sui-json-rpc-api = { path = "../sui-json-rpc-api" } +sui-json-rpc-types= { path = "../sui-json-rpc-types" } sui-node = { path = "../sui-node" } sui-swarm = { path = "../sui-swarm" } sui-types = { path = "../sui-types" } +sui-sdk = { path = "../sui-sdk" } move-package = { git = "https://github.com/move-language/move", rev = "7733658048a8bc80e9ba415b8c99aed9234eaa5f" } move-core-types = { git = "https://github.com/move-language/move", rev = "7733658048a8bc80e9ba415b8c99aed9234eaa5f", features = ["address20"] } diff --git a/crates/test-utils/src/network.rs b/crates/test-utils/src/network.rs index c1163bffe0a93..8e0c3ea934ea8 100644 --- a/crates/test-utils/src/network.rs +++ b/crates/test-utils/src/network.rs @@ -14,14 +14,14 @@ use sui_config::genesis_config::GenesisConfig; use sui_config::{Config, SUI_CLIENT_CONFIG, SUI_GATEWAY_CONFIG, SUI_NETWORK_CONFIG}; use sui_config::{PersistedConfig, SUI_KEYSTORE_FILENAME}; use sui_gateway::create_client; +use sui_json_rpc::api::RpcGatewayApiServer; +use sui_json_rpc::api::RpcReadApiServer; +use sui_json_rpc::api::RpcTransactionBuilderServer; +use sui_json_rpc::api::WalletSyncApiServer; use sui_json_rpc::gateway_api::{ GatewayReadApiImpl, GatewayWalletSyncApiImpl, RpcGatewayImpl, TransactionBuilderImpl, }; -use sui_json_rpc_api::keystore::{KeystoreType, SuiKeystore}; -use sui_json_rpc_api::RpcGatewayApiServer; -use sui_json_rpc_api::RpcReadApiServer; -use sui_json_rpc_api::RpcTransactionBuilderServer; -use sui_json_rpc_api::WalletSyncApiServer; +use sui_sdk::crypto::{KeystoreType, SuiKeystore}; use sui_swarm::memory::Swarm; use sui_types::base_types::SuiAddress; const NUM_VALIDAOTR: usize = 4; diff --git a/doc/src/build/rust-sdk.md b/doc/src/build/rust-sdk.md new file mode 100644 index 0000000000000..527a27dc3b788 --- /dev/null +++ b/doc/src/build/rust-sdk.md @@ -0,0 +1,106 @@ +--- +title: Sui Rust SDK +--- + +## Overview +The Sui SDK is a collection of rust JSON-RPC wrapper and crypto utilities that you can use to interact with the Sui Gateway and Sui Full Node. +The `SuiClient` can be used to create a http(`SuiClient::new_http_client`) or a websocket client(`SuiClient::new_ws_client`). +See [JSON-RPC doc](json-rpc.md#sui-json-rpc-methods) for list of available methods. + +> Note: As of v0.6.0, the web socket client is for subscription only, please use http client for other api methods. + +## Examples +Add the sui-sdk crate in your Cargo.toml: +```toml +[dependencies] +sui-sdk = { git = "https://github.com/MystenLabs/sui" } +``` +Use the devnet branch if you are connecting to the devnet. +```toml +[dependencies] +sui-sdk = { git = "https://github.com/MystenLabs/sui", branch = "devnet" } +``` + +### Example 1 - Get all objects owned by an address +```rust +use std::str::FromStr; +use sui_sdk::types::base_types::SuiAddress; +use sui_sdk::SuiClient; + +#[tokio::main] +async fn main() -> Result<(), anyhow::Error> { + let sui = SuiClient::new_http_client("https://gateway.devnet.sui.io:443")?; + let address = SuiAddress::from_str("0xec11cad080d0496a53bafcea629fcbcfff2a9866")?; + let objects = sui.get_objects_owned_by_address(address).await?; + println!("{:?}", objects); + Ok(()) +} +``` +This will print a list of object summaries owned by the address "0xec11cad080d0496a53bafcea629fcbcfff2a9866". +You can verify the result with the [Sui explorer](https://explorer.devnet.sui.io/) if you are using the Sui devnet. + +### Example 2 - Create and execute transaction +```rust +use std::str::FromStr; +use sui_sdk::crypto::{Keystore, SuiKeystore}; +use sui_sdk::types::base_types::{ObjectID, SuiAddress}; +use sui_sdk::types::sui_serde::Base64; +use sui_sdk::SuiClient; + +#[tokio::main] +async fn main() -> Result<(), anyhow::Error> { + let sui = SuiClient::new_http_client("https://gateway.devnet.sui.io:443")?; + // Load keystore from ~/.sui/sui_config/sui.keystore + let keystore_path = match dirs::home_dir() { + Some(v) => v.join(".sui").join("sui_config").join("sui.keystore"), + None => panic!("Cannot obtain home directory path"), + }; + let keystore = SuiKeystore::load_or_create(&keystore_path)?; + + let my_address = SuiAddress::from_str("0x47722589dc23d63e82862f7814070002ffaaa465")?; + let gas_object_id = ObjectID::from_str("0x273b2a83f1af1fda3ddbc02ad31367fcb146a814")?; + let recipient = SuiAddress::from_str("0xbd42a850e81ebb8f80283266951d4f4f5722e301")?; + + // Create a sui transfer transaction + let transfer_tx = sui + .transfer_sui(my_address, gas_object_id, 1000, recipient, Some(1000)) + .await?; + + // Sign the transaction + let signature = keystore.sign(&my_address, &transfer_tx.tx_bytes.to_vec()?)?; + + // Execute the transaction + let transaction_response = sui + .execute_transaction( + transfer_tx.tx_bytes, + Base64::from_bytes(signature.signature_bytes()), + Base64::from_bytes(signature.public_key_bytes()), + ) + .await?; + + println!("{:?}", transaction_response); + + Ok(()) +} +``` + +### Example 3 - Event subscription +```rust +use futures::StreamExt; +use sui_sdk::rpc_types::SuiEventFilter; +use sui_sdk::SuiClient; + +#[tokio::main] +async fn main() -> Result<(), anyhow::Error> { + let sui = SuiClient::new_ws_client("ws://127.0.0.1:9001").await?; + let mut subscribe_all = sui.subscribe_event(SuiEventFilter::All(vec![])).await?; + loop { + println!("{:?}", subscribe_all.next().await); + } +} +``` +> Note: You will need to connect to a fullnode for the Event subscription service, see [Fullnode setup](fullnode.md#fullnode-setup) if you want to run a fullnode. + + +## Larger Examples +[Tic Tac Toe](../../../crates/sui-sdk/README.md) \ No newline at end of file