Installation
Add the SDK to yourCargo.toml:
[dependencies]
sui-sdk = "1.18"
tokio = { version = "1", features = ["full"] }
anyhow = "1.0"
Creating a Client
Connect to different Sui networks:Connecting to Networks
Fromcrates/sui-sdk/examples/sui_client.rs:
use sui_sdk::SuiClientBuilder;
#[tokio::main]
async fn main() -> Result<(), anyhow::Error> {
// Local network
let sui = SuiClientBuilder::default()
.build("http://127.0.0.1:9000")
.await?;
println!("Sui local version: {}", sui.api_version());
// Or use dedicated functions
let sui_local = SuiClientBuilder::default()
.build_localnet()
.await?;
// Devnet
let sui_devnet = SuiClientBuilder::default()
.build_devnet()
.await?;
// Testnet
let sui_testnet = SuiClientBuilder::default()
.build_testnet()
.await?;
// Mainnet
let sui_mainnet = SuiClientBuilder::default()
.build_mainnet()
.await?;
// Check available methods
println!("RPC methods: {:?}", sui_testnet.available_rpc_methods());
println!("Subscriptions: {:?}", sui_testnet.available_subscriptions());
Ok(())
}
Reading Data
Fromcrates/sui-sdk/examples/read_api.rs:
use sui_sdk::rpc_types::{SuiObjectDataOptions, SuiTransactionBlockResponseOptions};
use sui_sdk::types::base_types::ObjectID;
#[tokio::main]
async fn main() -> Result<(), anyhow::Error> {
let sui = SuiClientBuilder::default().build_testnet().await?;
let address = /* your address */;
// Get owned objects
let owned_objects = sui
.read_api()
.get_owned_objects(
address,
None, // query
None, // cursor
Some(5), // limit
)
.await?;
println!("Owned objects: {:?}", owned_objects);
// Get specific object with options
let object_id = owned_objects.data.first().unwrap().object_id;
let obj_data_options = SuiObjectDataOptions {
show_type: true,
show_owner: true,
show_previous_transaction: true,
show_display: true,
show_content: true,
show_bcs: true,
show_storage_rebate: true,
};
let object = sui
.read_api()
.get_object_with_options(object_id, obj_data_options)
.await?;
println!("Object details: {:?}", object);
// Get chain identifier
let chain_id = sui.read_api().get_chain_identifier().await?;
println!("Chain ID: {:?}", chain_id);
// Get protocol config
let config = sui.read_api().get_protocol_config(None).await?;
println!("Protocol config: {:?}", config);
Ok(())
}
Working with Coins
Fromcrates/sui-sdk/examples/coin_read_api.rs:
use sui_sdk::SuiClientBuilder;
#[tokio::main]
async fn main() -> Result<(), anyhow::Error> {
let sui = SuiClientBuilder::default().build_testnet().await?;
let address = /* your address */;
// Get all coins
let coins = sui
.coin_read_api()
.get_coins(address, None, None, None)
.await?;
println!("Coins: {:?}", coins);
// Get balance for specific coin type
let balance = sui
.coin_read_api()
.get_balance(address, None) // None = SUI
.await?;
println!("SUI balance: {}", balance.total_balance);
// Get all balances
let all_balances = sui
.coin_read_api()
.get_all_balances(address)
.await?;
for balance in all_balances {
println!("Type: {}, Balance: {}", balance.coin_type, balance.total_balance);
}
Ok(())
}
Building Transactions
Fromcrates/sui-sdk/examples/programmable_transactions_api.rs:
use sui_sdk::{
rpc_types::SuiTransactionBlockResponseOptions,
types::{
programmable_transaction_builder::ProgrammableTransactionBuilder,
transaction::{Argument, Command, Transaction, TransactionData},
},
};
#[tokio::main]
async fn main() -> Result<(), anyhow::Error> {
let sui = SuiClientBuilder::default().build_testnet().await?;
let sender = /* your address */;
let recipient = /* recipient address */;
// Get a gas coin
let coins = sui
.coin_read_api()
.get_coins(sender, None, None, None)
.await?;
let coin = coins.data.into_iter().next().unwrap();
// Build programmable transaction
let mut ptb = ProgrammableTransactionBuilder::new();
// Split coin: create new coin with 1000 MIST
let split_amount = ptb.pure(1000u64)?;
ptb.command(Command::SplitCoins(
Argument::GasCoin,
vec![split_amount],
));
// Transfer the split coin
let recipient_arg = ptb.pure(recipient)?;
ptb.command(Command::TransferObjects(
vec![Argument::Result(0)],
recipient_arg,
));
let builder = ptb.finish();
// Create transaction data
let gas_budget = 5_000_000;
let gas_price = sui.read_api().get_reference_gas_price().await?;
let tx_data = TransactionData::new_programmable(
sender,
vec![coin.object_ref()],
builder,
gas_budget,
gas_price,
);
println!("Transaction built: {:?}", tx_data);
Ok(())
}
Calling Move Functions
Fromcrates/sui-sdk/examples/function_move_call.rs:
use sui_sdk::types::{
Identifier,
base_types::ObjectID,
programmable_transaction_builder::ProgrammableTransactionBuilder,
transaction::{Argument, CallArg, Command},
};
use anyhow::anyhow;
#[tokio::main]
async fn main() -> Result<(), anyhow::Error> {
let sui = SuiClientBuilder::default().build_testnet().await?;
let sender = /* your address */;
let mut ptb = ProgrammableTransactionBuilder::new();
// Add input argument
let input_value = 10u64;
let input_argument = CallArg::Pure(bcs::to_bytes(&input_value).unwrap());
ptb.input(input_argument)?;
// Call Move function
let package = ObjectID::from_hex_literal("0x...").map_err(|e| anyhow!(e))?;
let module = Identifier::new("module_name").map_err(|e| anyhow!(e))?;
let function = Identifier::new("function_name").map_err(|e| anyhow!(e))?;
ptb.command(Command::move_call(
package,
module,
function,
vec![], // type arguments
vec![Argument::Input(0)], // arguments
));
let builder = ptb.finish();
// Get gas and build transaction
let coins = sui.coin_read_api().get_coins(sender, None, None, None).await?;
let coin = coins.data.into_iter().next().unwrap();
let gas_budget = 10_000_000;
let gas_price = sui.read_api().get_reference_gas_price().await?;
let tx_data = TransactionData::new_programmable(
sender,
vec![coin.object_ref()],
builder,
gas_budget,
gas_price,
);
Ok(())
}
Signing and Executing
Fromcrates/sui-sdk/examples/sign_tx_guide.rs:
use fastcrypto::ed25519::Ed25519KeyPair;
use fastcrypto::traits::{KeyPair, EncodeDecodeBase64};
use rand::{SeedableRng, rngs::StdRng};
use shared_crypto::intent::{Intent, IntentMessage};
use sui_types::crypto::{SuiKeyPair, SuiSignature, Signer};
use sui_types::signature::GenericSignature;
#[tokio::main]
async fn main() -> Result<(), anyhow::Error> {
let sui = SuiClientBuilder::default().build_testnet().await?;
// Generate keypair (for testing - use secure key management in production)
let keypair = SuiKeyPair::Ed25519(
Ed25519KeyPair::generate(&mut StdRng::from_seed([0; 32]))
);
let sender = SuiAddress::from(&keypair.public());
// Build transaction (tx_data from previous examples)
let tx_data = /* ... */;
// Sign transaction
let intent_msg = IntentMessage::new(Intent::sui_transaction(), tx_data);
let raw_tx = bcs::to_bytes(&intent_msg).expect("bcs should not fail");
let mut hasher = sui_types::crypto::DefaultHash::default();
hasher.update(raw_tx.clone());
let digest = hasher.finalize().digest;
let signature = keypair.sign(&digest);
// Verify signature locally
let verification = signature.verify_secure(
&intent_msg,
sender,
sui_types::crypto::SignatureScheme::ED25519,
);
assert!(verification.is_ok());
// Execute transaction
let transaction_response = sui
.quorum_driver_api()
.execute_transaction_block(
sui_types::transaction::Transaction::from_generic_sig_data(
intent_msg.value,
vec![GenericSignature::Signature(signature)],
),
SuiTransactionBlockResponseOptions::default(),
None,
)
.await?;
println!("Transaction digest: {}", transaction_response.digest);
Ok(())
}
Querying Events
Fromcrates/sui-sdk/examples/event_api.rs:
use sui_sdk::rpc_types::EventFilter;
#[tokio::main]
async fn main() -> Result<(), anyhow::Error> {
let sui = SuiClientBuilder::default().build_testnet().await?;
// Query events by transaction
let events = sui
.event_api()
.query_events(
EventFilter::Transaction(digest),
None, // cursor
Some(10), // limit
false, // descending
)
.await?;
for event in events.data {
println!("Event: {:?}", event);
}
// Query by Move event type
let events = sui
.event_api()
.query_events(
EventFilter::MoveEventType(
"0x2::nft::NFTMinted".parse()?
),
None,
Some(50),
false,
)
.await?;
Ok(())
}
Working with Objects
use sui_sdk::rpc_types::SuiObjectDataOptions;
#[tokio::main]
async fn main() -> Result<(), anyhow::Error> {
let sui = SuiClientBuilder::default().build_testnet().await?;
// Get object
let object_id: ObjectID = "0x...".parse()?;
let object = sui
.read_api()
.get_object_with_options(
object_id,
SuiObjectDataOptions::full_content(),
)
.await?;
// Get dynamic fields
let dynamic_fields = sui
.read_api()
.get_dynamic_fields(object_id, None, None)
.await?;
for field in dynamic_fields.data {
println!("Dynamic field: {:?}", field);
}
Ok(())
}
Error Handling
use anyhow::{Context, Result};
async fn transfer_sui(
sui: &SuiClient,
sender: SuiAddress,
recipient: SuiAddress,
amount: u64,
) -> Result<TransactionDigest> {
// Get gas coin
let coins = sui
.coin_read_api()
.get_coins(sender, None, None, None)
.await
.context("Failed to get coins")?;
let coin = coins
.data
.into_iter()
.next()
.context("No coins found")?;
// Build transaction
let mut ptb = ProgrammableTransactionBuilder::new();
ptb.pay_sui(vec![recipient], vec![amount])
.context("Failed to build transaction")?;
// Execute
// ...
Ok(digest)
}
Best Practices
1. Reuse client instances
// Good: Share client
struct App {
sui_client: Arc<SuiClient>,
}
// Avoid: Create new client for each request
async fn bad_example() {
let client = SuiClientBuilder::default().build_testnet().await?;
// Use once and drop
}
2. Handle errors properly
use anyhow::{Context, Result};
async fn safe_operation() -> Result<()> {
let sui = SuiClientBuilder::default()
.build_testnet()
.await
.context("Failed to connect to testnet")?;
let objects = sui
.read_api()
.get_owned_objects(address, None, None, None)
.await
.context("Failed to fetch objects")?;
Ok(())
}
3. Use connection pooling
use std::sync::Arc;
#[derive(Clone)]
struct SuiService {
client: Arc<SuiClient>,
}
impl SuiService {
async fn new() -> Result<Self> {
let client = SuiClientBuilder::default()
.build_testnet()
.await?;
Ok(Self {
client: Arc::new(client),
})
}
}