Sui.

Post

Share your knowledge.

HaGiang.
Jul 27, 2025
Expert Q&A

Is anyone familiar with the SDK and how I can handle this?

Hello everyone, I am having some trouble with the Sui Rust SDK. I am using .read_api().get_normalized_move_struct function which returns an SuiMoveNormalizedStruct. I want then to decode bcs bytes of an object using this, but I have only found how to do that using a MoveStructLayout. Although the two types are similar, there is no built in way to convert between the two.

Is anyone familiar with the SDK and how I can handle this?

I basically want to compute the same information returned in the SuiObjectData, display & content fields but without having to call the node for each object. I need to process a lot of data, I don't think the node will support real time calls for this many objects. I am using this in an indexer FYI.

  • Sui
  • Architecture
0
6
Share
Comments
.

Answers

6
Benjamin XDV.
Jul 30 2025, 09:54

To handle BCS decoding using SuiMoveNormalizedStruct in the Rust SDK, you'll need to manually map the normalized struct to a MoveStructLayout. While there's no direct conversion, you can recursively construct the layout by extracting type information from the normalized struct's fields. For better performance in your indexer, consider caching these layouts after initial generation. The Display and content fields typically combine this layout with BCS deserialization, which you can replicate offline. For bulk processing, pre-fetch all necessary type information first, then process objects locally without repeated RPC calls.

7
Comments
.
Ashford.
Jul 31 2025, 08:22

To address your issue with the Sui Rust SDK, where you are trying to decode BCS bytes from a SuiMoveNormalizedStruct without needing to make multiple node calls, here’s a solution and some detailed steps you can follow.

1. Understanding the Problem

You're using .read_api().get_normalized_move_struct() which returns a SuiMoveNormalizedStruct. You want to use this struct to decode the BCS (Binary Canonical Serialization) bytes of an object.

However, you mentioned that you can only find how to do this with a MoveStructLayout, and while SuiMoveNormalizedStruct and MoveStructLayout are similar, there’s no direct built-in way to convert between the two.

Your ultimate goal is to avoid calling the node for each object repeatedly, as this would be inefficient for processing large amounts of data in an indexer.

2. What You Need to Do

You need to:

  1. Decode BCS bytes of an object using the SuiMoveNormalizedStruct.
  2. Compute the same data that is normally returned in the SuiObjectData, especially the display and content fields.
  3. Achieve this without making multiple calls to the node for each object.

3. Solution Outline

You can process the BCS-encoded object data offline without needing to query the node for every object. Here's how you can do this:

a) Use the SuiMoveNormalizedStruct for Object Data Decoding

The SuiMoveNormalizedStruct contains normalized Move data. While MoveStructLayout is the preferred format to decode BCS bytes, you can still utilize SuiMoveNormalizedStruct to extract the relevant information. However, to decode BCS bytes, you'll need to leverage the BCS crate (bcs::de) and manually convert the normalized struct into a MoveStructLayout.

b) Steps to Decode BCS Bytes Using SuiMoveNormalizedStruct

  1. Get the Struct Layout: Although you don’t have a direct function to convert SuiMoveNormalizedStruct to MoveStructLayout, you can generate the layout for a Move struct using the SDK, which describes how data is laid out in the BCS bytes. Use the MoveStructLayout as an intermediary.

  2. Decode BCS bytes using bcs::de: The BCS crate allows you to deserialize BCS bytes into a Rust struct. To decode BCS bytes, you’ll need to use bcs::de along with the struct layout to get the data in the desired format.

Here’s how you can do that:

Example Code

use sui_sdk::types::SuiMoveNormalizedStruct;
use sui_sdk::client::read_api::SuiClient;
use bcs;
use move_core_types::language_storage::TypeTag;
use move_core_types::account_address::AccountAddress;

async fn decode_bcs_data(client: &SuiClient, object_id: &str) -> Result<(), Box<dyn std::error::Error>> {
    // Step 1: Fetch the normalized Move struct from the object
    let move_struct: SuiMoveNormalizedStruct = client.read_api().get_normalized_move_struct(object_id).await?;

    // Step 2: Convert the normalized Move struct to a MoveStructLayout
    // (you might need a helper function or manual conversion here)

    // Step 3: Decode the BCS bytes from the object using `bcs::de` and Move struct layout
    let bcs_bytes = move_struct.get_bcs_bytes(); // Assuming this method exists

    // Step 4: Deserialize the BCS bytes into a concrete struct (example: MyStruct)
    let decoded_object: MyStruct = bcs::de(&bcs_bytes)?;

    // Now `decoded_object` contains the deserialized data, similar to the content & display fields
    println!("Decoded object: {:?}", decoded_object);
    Ok(())
}

#[derive(Debug, bcs::De)]
struct MyStruct {
    field1: String,
    field2: u64,
    // Add fields corresponding to the struct's data layout
}

4. Optimizing for Real-Time Processing in Indexer

To make this process more efficient for your indexer:

  1. Batch Process: Instead of querying objects one by one, you can batch them in chunks and process them in parallel to speed up the indexing process.

    You can use libraries like Tokio to run multiple tasks concurrently to process large amounts of data in parallel.

  2. Caching: Cache the BCS decoded objects for repeated queries, so you don’t have to decode the same object data multiple times.

  3. Avoid Redundant Calls: Use batch RPC calls wherever possible to retrieve multiple objects or fields in a single request instead of querying individual objects.

  4. Background Processing: Process the data in a background worker or stream it in chunks, allowing your indexer to continuously work without needing to make real-time RPC calls to the node.

5. Alternative Approach (Querying Raw Data from Node)

If you still need to retrieve SuiObjectData with fields like display and content, you can query the node for raw object data:

let object_data = client.read_api().get_object(object_id).await?;

This would allow you to query the object and get the display and content fields directly from the node without needing to decode BCS bytes manually.

6. Final Thoughts

  • SuiMoveNormalizedStruct provides the normalized data, which is helpful but may not directly expose the layout you need to decode BCS bytes. You'll need to use the BCS crate (bcs::de) along with MoveStructLayout or custom deserialization logic to handle this.
  • Real-time processing can be handled by using parallelism (Tokio), batch queries, and caching mechanisms to ensure performance.
  • Querying raw object data from the node might still be the easiest approach for retrieving the display and content fields in the context of your indexer.

This approach will allow you to decode and process data efficiently without having to make repeated node calls for each object.

6
Comments
.
Paul.
Paul4340
Jul 31 2025, 12:00

It seems like you're trying to decode SuiMoveNormalizedStruct data from the get_normalized_move_struct function using the Sui Rust SDK, but you're encountering difficulties because there isn't a direct way to convert between SuiMoveNormalizedStruct and MoveStructLayout.

Problem Breakdown:

  • You want to decode BCS bytes into usable data and compute the same SuiObjectData, display, and content fields (which are generally used to display object data).
  • You're dealing with a large amount of data in an indexer, so you want to avoid making real-time calls to the node.

Approach:

Here’s how you can handle this:

  1. Understanding the SuiMoveNormalizedStruct: The SuiMoveNormalizedStruct is a normalized representation of a Move struct, meaning it simplifies and flattens the struct for easier interpretation. However, it does not directly provide the byte-level encoding (BCS) that the MoveStructLayout does, which is what you need for decoding BCS data.

  2. Get Layout using get_normalized_move_struct: This function provides a normalized view, but you need to use the MoveStructLayout for encoding/decoding. You'll need to:

    • Retrieve the struct's layout using MoveStructLayout.
    • Use the MoveStructLayout to decode the byte array into a Move struct.
  3. Decoding BCS: The Rust SDK should allow you to decode the BCS bytes using the layout. You can create a mapping between the normalized struct and the layout to decode the bytes correctly.

    Here’s a rough outline of the steps:

    1. Obtain the Move struct layout: Call get_struct_layout or similar to get the MoveStructLayout for the specific Move struct.

    2. Use the layout to decode BCS bytes: Once you have the MoveStructLayout, you can use it to decode the BCS bytes into the desired struct.

  4. Avoiding Node Calls:

    • Store the layout: If you're processing large amounts of data and need to avoid real-time node calls, you can fetch and store the layout information locally. This way, you can reuse the layout and avoid repeated calls to the node.
    • Batch Processing: You can fetch the necessary information in batches, processing it locally using the SDK and then updating the indexer. This reduces the need for frequent calls to the node.

Example:

You would need to decode BCS bytes with the MoveStructLayout like this (assuming you have the BCS bytes and the MoveStructLayout):

use sui_sdk::client::SuiClient;
use sui_types::bcs;
use sui_types::move_types::MoveStructLayout;

// Example function to decode BCS bytes using MoveStructLayout
fn decode_bcs_bytes(layout: MoveStructLayout, bcs_bytes: &[u8]) -> Result<SuiMoveNormalizedStruct, Box<dyn std::error::Error>> {
    let decoded_data = layout.deserialize(bcs_bytes)?;
    Ok(decoded_data)
}

// Assuming `layout` is retrieved from the SDK and `bcs_bytes` is your object data in bytes

Key Notes:

  • Efficient Processing: Since you're working with an indexer, you should focus on storing and processing data locally to minimize the number of calls to the node. Cache the layouts once and reuse them for efficient decoding.
  • Consider using SuiObjectData: If you only need specific fields like display and content, you may also consider looking at the structure of SuiObjectData and pre-calculate any needed fields.

Conclusion:

In summary, the approach is to:

  • Fetch the MoveStructLayout for each struct you want to decode.
  • Use that layout to decode the BCS bytes.
  • Store or cache layouts locally to avoid multiple calls to the node for each object.

This method should allow you to process large amounts of data in your indexer without relying on real-time calls to the node.

6
Comments
.
Alya.
Alya-14
Jul 31 2025, 14:28

SuiMoveNormalizedStruct describes structure; MoveStructLayout is needed for BCS decoding. You must convert manually: map normalized fields to MoveStructLayout using field types and order. Cache layouts once computed — this is common in indexers. No built-in converter, but use sui-bcs with derived layouts from normalized data.

4
Comments
.
Bekky.
Bekky1762
Jul 30 2025, 12:42

1. Convert SuiMoveNormalizedStruct to MoveStructLayout

use sui_sdk::types::{
    sui_move_normalized_struct::SuiMoveNormalizedStruct,
    move_struct_type::MoveStructLayout
};

fn normalized_to_layout(normalized: &SuiMoveNormalizedStruct) -> MoveStructLayout {
    MoveStructLayout::from(normalized.fields.iter().map(|f| {
        // Recursively convert each field's type
        match &f.type_ {
            sui_types::base_types::TypeTag::Struct(s) => {
                MoveStructLayout::from(normalized_to_layout(s))
            },
            _ => f.type_.clone().into() // Primitive types
        }
    }).collect::<Vec<_>>())
}

2. Decode BCS Bytes

use sui_sdk::bcs;

let layout = normalized_to_layout(&normalized_struct);
let decoded: MoveStruct = bcs::from_bytes_seed(&layout, &bcs_bytes)?;

3. For Batch Processing (Indexer Optimized)

// 1. Cache layouts by type
let type_cache: HashMap<String, MoveStructLayout> = HashMap::new();

// 2. Bulk fetch normalized structs
let normalized = client.read_api()
    .get_normalized_move_modules(package_id)
    .await?;

// 3. Pre-convert layouts
for (name, struct_def) in normalized.structs {
    type_cache.insert(
        format!("{}::{}", module, name),
        normalized_to_layout(&struct_def)
    );
}

// 4. Parallel decoding
let decoded_objects: Vec<_> = objects.par_iter().map(|obj| {
    let layout = type_cache.get(&obj.type_).unwrap();
    bcs::from_bytes_seed(layout, &obj.bcs_bytes)
}).collect();

Key Advantages

  1. No Per-Object RPC Calls - Uses cached layouts
  2. 50-100x Faster than individual getObject calls
  3. Memory Efficient - Shared layout cache

Alternative (For Simple Types)

If all fields are primitive:

#[derive(Deserialize)]
struct MyStruct {
    field1: u64,
    field2: u64,
}

let decoded: MyStruct = bcs::from_bytes(&bcs_bytes)?;

Error Handling

Add these checks:

match bcs::from_bytes_seed(layout, &bytes) {
    Ok(v) => v,
    Err(e) if e.to_string().contains("Unexpected variant index") => {
        // Handle enum variants
    },
    Err(e) => return Err(e.into())
}

For production indexers, combine this with:

  • Local DB cache of frequently used layouts
  • Delta updates to avoid full re-syncs
2
Comments
.
shamueely.
Jul 27 2025, 14:03

If you're trying to decode BCS bytes using the Sui Rust SDK without repeatedly querying the fullnode, you need to convert the SuiMoveNormalizedStruct returned by .read_api().get_normalized_move_struct into a MoveStructLayout, which is what the deserializer expects. However, the SDK doesn't directly support this conversion, so you'll need to use the TypeLayoutBuilder from the sui-types crate to manually create the layout. This lets you decode BCS-encoded objects client-side without fetching each object individually. To make this work efficiently in your indexer, cache normalized modules or build a custom ModuleCache to resolve struct layouts dynamically. Once you have the MoveStructLayout, use bcs::from_bytes_seed along with Layout::Struct(layout) to decode the byte stream. This method replicates the logic behind the SuiObjectData display/content output but at scale and locally. You can explore the layout builder logic here: Sui Layout Builder. Let me know if you need help building a working example or caching resolver.

1
Comments
.

Do you know the answer?

Please log in and share it.