Post
Share your knowledge.
fetch shared object versions in the Rust SDK
Building backends and PTBs almost always requires passing shared objects as arguments to move calls. In order to be used as a PTB input, we need to provide three pieces of information about the shared object:
Its ID Whether it should be passed as mutable A u64 representing the shared version Fetching the shared version u64 is not ideal from a developer experience perspective, it requires making an RPC request and handling all possible errors. This results in extra lines of code.
The TypeScript Sui SDK performs this automatically, while still allowing the developer to manually provide the shared version to avoid the extra request.
- Sui
Answers
8Regarding BCS, there are multiple solutions for this
Solution 1: Modifying the ObjectArg enum, without its BCS impl We can add a new case to ObjectArg that we can call SharedObjectLatest (just an example name), without the field initial_shared_version. When preparing a Transaction struct from a TransactionData, we can retrieve the initial_shared_version and convert SharedObjectLatest to SharedObject
To make this secure, we would write a custom Serialize impl for ObjectArg such that we ensure SharedObjectLatest cannot be serialized, and other cases have the correct discriminant: 0 for ImmOrOwnedObject, 1 for SharedObject, and 2 for Receiving
Finally, methods such as Transaction::to_tx_bytes_and_signatures would return a Result, returning Err when bcs::to_bytes fails (currently there's an .unwrap() on it)
Solution 2: Adding a new function to ProgrammableTransactionBuilder We could add an async method shared_obj_latest on ProgrammableTransactionBuilder which would have the following signature:
pub async fn shared_obj_latest(&mut self, object_id: ObjectID, mutable: bool) -> anyhow::Result
Even better, to maximize developer experience, we can add initial_shared_version as an Option
pub async fn shared_obj(
&mut self,
object_id: ObjectID,
mutable: bool,
initial_shared_number: Option
Let me know what you think about this! Or if the things coming down the pipe make my proposed solutions irrelevant
To fetch shared object versions in the Rust SDK for Sui, you would indeed need to make an RPC call and handle potential errors, as there's no built-in automatic version-fetching like in the TypeScript SDK. However, you can simplify this process by encapsulating the logic in a helper function to minimize the boilerplate code.
Here’s an example of how you can fetch the version of a shared object in Rust and handle the response:
1. Creating the Helper Function:
First, you’ll need to create a function to retrieve the shared object version from the Sui RPC endpoint.
use sui_sdk::rpc_types::{SuiObjectData, ObjectID};
use sui_sdk::SuiClient;
use std::error::Error;
use tokio::runtime::Runtime;
async fn fetch_shared_object_version(client: &SuiClient, object_id: &ObjectID) -> Result<u64, Box<dyn Error>> {
// Fetch the object data from the Sui client
let object_data: SuiObjectData = client.read_api().get_object_data(object_id).await?;
// Extract the version of the shared object
if let Some(version) = object_data.version {
Ok(version)
} else {
Err("No version found for the shared object".into())
}
}
In this function:
- We use the
SuiClientto query the object’s data. - The function returns the version as a
u64if found, or an error if not.
2. Calling the Function:
Then, in your backend logic, you can call this function where you need the shared object version:
fn main() {
let rt = Runtime::new().unwrap();
let client = SuiClient::new("http://localhost:5001"); // replace with the correct URL for your Sui client
let object_id = ObjectID::from_str("your-object-id-here").unwrap();
// Calling the async function in a sync context (main function)
match rt.block_on(fetch_shared_object_version(&client, &object_id)) {
Ok(version) => {
println!("Shared object version: {}", version);
}
Err(e) => {
eprintln!("Error fetching version: {}", e);
}
}
}
3. Passing the Shared Object Information:
When you need to pass the shared object to a Move call, you can use the object ID, mutable flag, and version (either fetched or provided manually):
let object_id = ObjectID::from_str("your-object-id-here").unwrap();
let is_mutable = true; // or false based on your logic
let version = 1234567890; // Or the version fetched earlier
// Now you can pass these to your Move call
4. Avoiding Extra Requests:
To avoid making the version fetch on every call, you can cache the version in your backend if the shared object’s version is unlikely to change during your workflow, and only fetch it when necessary (e.g., when the object is mutated).
Key Points:
- RPC Request: The version fetch does require an RPC call to
get_object_data, which could be an extra overhead if done frequently. - Cache Versions: Cache the version where possible, especially if the object is static or doesn’t change frequently, to minimize the calls to the blockchain.
- Manual Version Input: If you already know the version of the object (e.g., from previous interactions), you can manually supply the version, avoiding the need for an additional RPC request.
While this adds an extra step in the Rust SDK, you can streamline it by wrapping the logic in helper functions or implementing caching mechanisms, reducing the developer's burden.
Rust SDK Solution
Use get_object to fetch the shared object’s version automatically:
use sui_sdk::SuiClient;
let client = SuiClient::new("https://fullnode.mainnet.sui.io", None).await?;
let shared_obj = client
.read_api()
.get_object_with_options(
"0xSHARED_OBJECT_ID",
sui_json_rpc_types::ObjectDataOptions::full_content(),
)
.await?;
let version = shared_obj.object.version();
PTB Integration
Pass it directly to your transaction:
let mut txb = TransactionBuilder::new();
txb.move_call(
package_id,
module,
function,
type_args,
vec![
Input::shared_object(shared_obj_id, version, /* mutable */ true),
],
);
Key Points
Automatic version fetch (like TypeScript SDK)
Optional manual override (pass version if already known)
Error handling built-in (via Result)
When working with shared objects in the Sui Rust SDK, developers must provide the object's ID, mutability flag, and its shared version (a u64). The shared version represents the object's initial version when it was first published as shared. This version is required when constructing programmable transaction blocks (PTBs) that interact with shared objects. Unfortunately, unlike the TypeScript SDK, the Rust SDK does not yet automatically fetch the shared version for you. Instead, developers must manually make an RPC call—typically using read_api().get_object()—to retrieve the object and extract the version from the response. This process introduces boilerplate code and increases complexity, especially when handling errors and parsing the results.
In contrast, the TypeScript SDK handles this internally unless a version is explicitly supplied, offering a smoother experience. The Rust SDK could benefit from a utility wrapper or helper method to replicate this functionality. Until then, developers are advised to implement their own abstraction layer to streamline repeated shared object handling. Improving this area would enhance developer productivity and reduce setup time for backend services and transaction builders.
In the Sui Rust SDK, when constructing programmable transaction blocks (PTBs) that involve shared objects, you must provide three critical components for each shared object: the object ID, its mutability (mutable or immutable), and the shared version represented as a u64. The shared version refers to the version of the object at the point it was marked as shared. This is not always readily known, so you are required to fetch it via an RPC request, typically using the get_object or multi_get_objects method. This creates a poor developer experience because it adds extra complexity to the backend code. You must handle potential RPC failures, missing object states, or deserialization issues.
In contrast, the Sui TypeScript SDK abstracts this away. If a developer does not provide the shared version explicitly, the TypeScript SDK performs the version-fetching logic automatically before building the transaction. This results in a much cleaner development flow. Developers using the Rust SDK have to manually build logic that first fetches the shared version, parses it, handles RPC errors, and only then includes it in the PTB.
This is especially tedious when working with multiple shared objects in batch or automated systems. To simplify things, you could write a wrapper function around the Rust SDK's object fetch API to always extract and return the shared version when needed. This keeps your main PTB construction logic cleaner.
Also, caching previously known shared versions locally can help reduce the number of RPC calls in performance-critical systems. Another workaround is maintaining a mapping of known shared object IDs and their versions if the application logic allows it. However, this requires careful synchronization when network upgrades or object changes occur.
Currently, there is no official support in the Rust SDK for auto-injecting shared versions like in the TypeScript SDK. That might change in future releases as developers demand more ergonomic APIs. Until then, developers building high-level services or tooling on top of the Rust SDK need to plan for this gap. It's a necessary step for accurate PTB execution involving shared objects.
Optimal Approach (Rust SDK)
use sui_sdk::{SuiClient, types::object::ObjectRead};
async fn get_shared_version(client: &SuiClient, object_id: &str) -> Result<u64, anyhow::Error> {
let obj = client
.read_api()
.get_object_with_options(
object_id,
sui_json_rpc_types::ObjectDataOptions::full_content(),
)
.await?;
Ok(obj.object.version())
}
// Usage in PTB:
let version = get_shared_version(&client, "0xshared_obj_id").await?;
txb.move_call(
package_id,
module,
function,
vec![Input::shared_object(object_id, version, true)], // true = mutable
);
Key Improvements Over Manual Fetching
- Automatic Version Detection - Like TS SDK
- Error Handling Included - Propagates
Result - Reusable Function - Call anywhere
For Better DX
Wrap this in a trait:
trait SharedObjectExt {
async fn as_shared_arg(&self, mutable: bool) -> Result<Input, Error>;
}
Why This Works
- Shared objects always expose their latest version via
get_object - Rust's type system ensures safety
- Matches TS SDK behavior while being explicit
Note: Still 1 RPC call per object (unavoidable for freshness). Cache versions if performance-critical.
In the Sui Rust SDK, using shared objects in PTBs requires you to provide the object ID, mutability, and its shared version (u64). The shared version is the initial version when the object became shared, and it must be fetched manually using an RPC call like get_object. Unlike the TypeScript SDK, which auto-fetches the shared version if not provided, the Rust SDK puts this responsibility on the developer. This adds extra code and error handling, making development less streamlined. To improve the experience, you can create a helper function to wrap the version-fetching logic.
Do you know the answer?
Please log in and share it.
Sui is a Layer 1 protocol blockchain designed as the first internet-scale programmable blockchain platform.
- How to Maximize Profit Holding SUI: Sui Staking vs Liquid Staking616
- Why does BCS require exact field order for deserialization when Move structs have named fields?65
- Multiple Source Verification Errors" in Sui Move Module Publications - Automated Error Resolution55
- Sui Move Error - Unable to process transaction No valid gas coins found for the transaction419
- Sui Transaction Failing: Objects Reserved for Another Transaction410