Sui.

Post

Share your knowledge.

Owen.
Owen1185
Jun 11, 2025
Expert Q&A

How to update a merchant's key in ObjectTable when it changes in the struct?

Hi everyone, I'm just getting started with writing smart contracts and I'm working on my very first project. I'd love some help with an issue I'm stuck on.

So far, I’ve created a Merchant struct that looks like this:

  • id: a unique identifier (UID)
  • owner: the address of the merchant
  • key: a String used as a unique key
  • balance: a u64 representing their balance

I also made a MerchantRegistry struct to manage all merchants:

  • id: another UID
  • merchant_to_address: an ObjectTable<address, Merchant> mapping addresses to merchants
  • merchant_to_key: an ObjectTable<String, Merchant> mapping keys to merchants

I want to be able to look up a merchant either by their address or by their key.

When a user updates their key inside the Merchant struct, the change doesn’t automatically update the key in the merchant_to_key table. That means the old key still points to the merchant, which breaks things.

I tried removing the entry from the table and inserting it back with the new key, but I keep running into errors like:

"Cannot ignore values without drop ability"

I'm pretty sure this is a beginner mistake, but I haven't been able to find a clear explanation or solution anywhere. Is there a proper way to handle updating the key in both the struct and the lookup table?

  • Sui
  • Move
5
6
Share
Comments
.

Answers

6
HaGiang.
Jul 23 2025, 15:44

In Sui Move, objects are "resources" by default, meaning they cannot be duplicated or discarded accidentally. This is enforced through a type system where structs either have the drop ability (meaning they can be discarded if they go out of scope) or they don't.

Your Merchant struct likely does not have the drop ability because it contains a UID. UIDs are unique identifiers for objects on Sui, and they are designed to prevent accidental deletion or duplication of those objects. If an object contains a UID, it typically cannot be dropped.

When you try to remove an entry from an ObjectTable, the remove function returns the value (the Merchant object in this case). If you don't assign this returned value to a variable or pass it to another function that consumes it, Move assumes you're trying to "ignore" or "drop" it. Since your Merchant struct doesn't have the drop ability, this action is prohibited, leading to the error "Cannot ignore values without drop ability."

The core issue is that changing a field inside an object (like merchant.key = new_key;) does not automatically update the keys in your ObjectTable. The ObjectTable stores the object based on the key it was added with. So, if the internal key changes, the table still only knows about the old key.

module your_address::your_module_name {

    use sui::object::{Self, UID};
    use sui::tx_context::{Self, TxContext};
    use sui::string::{String, utf8};
    use sui::object_table::{Self, ObjectTable}; // Import ObjectTable

    // Define your Merchant struct
    public struct Merchant has key, store {
        id: UID,
        owner: address,
        key: String, // This is the key you want to update
        balance: u64,
    }

    // Define your MerchantRegistry struct
    public struct MerchantRegistry has key, store {
        id: UID,
        merchant_to_address: ObjectTable<address, Merchant>, // Mapping addresses to merchants
        merchant_to_key: ObjectTable<String, Merchant>,     // Mapping keys to merchants
    }

    // You'll likely have an `init` function to create the registry
    fun init(ctx: &mut TxContext) {
        let registry = MerchantRegistry {
            id: object::new(ctx),
            merchant_to_address: object_table::new(ctx),
            merchant_to_key: object_table::new(ctx),
        };
        // Transfer ownership of the registry, or make it shared if applicable
        sui::transfer::transfer(registry, tx_context::sender(ctx));
    }

    // Function to add a new merchant (for context)
    public entry fun add_merchant(
        registry: &mut MerchantRegistry,
        merchant_owner: address,
        merchant_key: vector<u8>, // Using vector<u8> for input, convert to String
        ctx: &mut TxContext
    ) {
        let merchant = Merchant {
            id: object::new(ctx),
            owner: merchant_owner,
            key: utf8(merchant_key),
            balance: 0,
        };

        // Add to both tables
        object_table::add(&mut registry.merchant_to_address, merchant_owner, object::share_object(merchant));     
        // You'll need to decide on a primary storage and potentially store references or IDs in other tables.
        // For simplicity, let's assume `merchant_to_key` is the primary and `merchant_to_address` stores the object ID.
        // If `Merchant` is a shared object, you can store it in multiple tables.
        // Let's adjust the `Merchant` struct for clarity in this example.

        // For simplicity and to avoid the "same object in multiple places" issue,
        // it's often better to have one "primary" table that owns the object,
        // and other tables store only the object's ID or a reference.
        // If you truly need the Merchant object directly in both tables, the Merchant itself needs to be a SHARED object.
        // Let's revise the example to make Merchant a shared object.

        // REVISED MERCHANT STRUCT (if you want it in both tables directly):
        // public struct Merchant has key { // No 'store' ability if it's shared directly
        //     id: UID,
        //     owner: address,
        //     key: String,
        //     balance: u64,
        // }
        // public entry fun add_merchant(...) {
        //     let merchant = Merchant { ... };
        //     object::share_object(merchant); // Make it shared
        //     object_table::add(&mut registry.merchant_to_address, merchant_owner, merchant);
        //     object_table::add(&mut registry.merchant_to_key, utf8(merchant_key), merchant);
        // }
        // This makes `Merchant` a shared object, which has different implications for ownership and mutability.

        // *************** Let's stick to the original assumption that Merchant is not shared,
        // *************** and therefore only one table directly owns the `Merchant` object.
        // *************** If you want to look up by address AND key, and only one table can own the object,
        // *************** then one table (e.g., merchant_to_address) stores the `Merchant` object,
        // *************** and the other (merchant_to_key) stores the `ID` of the Merchant.

        // For this scenario, let's assume `merchant_to_address` owns the `Merchant` objects,
        // and `merchant_to_key` will map a String key to the `ID` of the Merchant.

        // New Merchant struct:
        // public struct Merchant has key, store {
        //     id: UID,
        //     owner: address,
        //     key: String, // Stored inside the object
        //     balance: u64,
        // }

        // Updated MerchantRegistry:
        // public struct MerchantRegistry has key, store {
        //     id: UID,
        //     merchant_to_address: ObjectTable<address, Merchant>, // Owners the Merchant object
        //     merchant_to_key: ObjectTable<String, object::ID>,    // Maps key to Merchant's ID
        // }

        // **************** Let's assume for the sake of the user's original question *****************
        // **************** that `ObjectTable<String, Merchant>` is intended to hold ****************
        // **************** the actual Merchant object, which implies it's the primary ****************
        // **************** owner or the `Merchant` is shared.
        // **************** If `Merchant` is not shared, only ONE `ObjectTable` can hold it.
        // **************** The error "Cannot ignore values without drop ability" confirms
        // **************** that the `Merchant` object itself is being handled.

        // Assuming merchant_to_key is the primary table where the Merchant object resides:
        object_table::add(&mut registry.merchant_to_key, utf8(merchant_key), merchant);
        // If you still need lookup by address, you'd store the ID in merchant_to_address:
        // object_table::add(&mut registry.merchant_to_address, merchant_owner, object::id(&merchant));
        // This creates a circular dependency in a single transaction if you try to get ID *after* adding.
        // Best practice is one table owning the object, others holding IDs.

        // For the sake of directly answering the user's original question about `merchant_to_key`:
    }


    /// Entry function to update a merchant's key in the ObjectTable.
    public entry fun update_merchant_key(
        registry: &mut MerchantRegistry,
        old_key: vector<u8>, // Input as vector<u8> for String conversion
        new_key: vector<u8>, // Input as vector<u8> for String conversion
        ctx: &mut TxContext // TxContext is often needed for object::new or other operations
    ) {
        // Convert input vectors to Sui Strings
        let old_key_str = utf8(old_key);
        let new_key_str = utf8(new_key);

        // 1. Remove the merchant using the old key.
        // This returns the 'Merchant' object. Since Merchant does not have 'drop',
        // we *must* bind it to a variable.
        let mut merchant = object_table::remove(&mut registry.merchant_to_key, old_key_str);

        // 2. Update the 'key' field inside the retrieved Merchant struct.
        merchant.key = new_key_str;

        // 3. Re-insert the updated Merchant object into the table with the new key.
        // This will create a new entry in the ObjectTable.
        object_table::add(&mut registry.merchant_to_key, new_key_str, merchant);

        // If you also had a `merchant_to_address` table mapping to `object::ID`,
        // and the `owner` of the merchant might also change, you'd need to update that as well.
        // For example:
        // let old_address = merchant.owner; // Assuming merchant.owner is still the original owner
        // if (old_address != new_owner_address) { // If owner changes
        //     let merchant_id = object_table::remove(&mut registry.merchant_to_address, old_address);
        //     // merchant.owner = new_owner_address; // Update owner in the struct if needed
        //     object_table::add(&mut registry.merchant_to_address, new_owner_address, merchant_id);
        // }
    }

    // You might also want a function to get a merchant by key for testing
    public fun get_merchant_by_key(
        registry: &MerchantRegistry,
        key: vector<u8>
    ): &Merchant {
        object_table::borrow(&registry.merchant_to_key, utf8(key))
    }
}
4
Comments
.
MoonBags.
Jul 23 2025, 15:30

In Sui Move, you can't simply change an object's key within a table or directly delete/replace objects without proper handling. When you modify the key field inside your Merchant struct, it doesn't automatically update the merchant_to_key table. This leaves your old key still pointing to the object, while the new key has no association at all.

The error "Cannot ignore values without drop ability" arises because your Merchant struct likely doesn't have the drop ability (which is common for structs containing a UID). This means you cannot simply remove the object from the table and discard it; you must explicitly store and reuse the returned object.

3
Comments
.
0xduckmove.
Jul 23 2025, 15:32

To correctly update a merchant's key, you need to manually remove the old entry, update the Merchant struct with the new key, and then reinsert it into the table. Here's how you can do it within a transaction:

public entry fun update_merchant_key(
    registry: &mut MerchantRegistry,
    old_key: String,
    new_key: String
) {
    // 1. Remove the merchant using the old key and store the returned object.
    let merchant = object_table::remove(&mut registry.merchant_to_key, old_key);

    // 2. Update the key field inside the struct.
    merchant.key = new_key;

    // 3. Re-insert the merchant into the table with the updated (new) key.
    object_table::add(&mut registry.merchant_to_key, new_key, merchant);
}```

This approach ensures you safely extract the Merchant object, modify its key, and then insert it back into the ObjectTable with the new key. 
This method respects Move's ownership rules and prevents accidental loss or "leaking" of resources, which is crucial when dealing with types that lack the drop ability.

3
Comments
.
harry phan.
Jul 23 2025, 15:45

To update a merchant's key in ObjectTable, you must manually:

remove the Merchant object using the old_key.

Update the key field inside the Merchant struct you just retrieved and add the modified Merchant object back into the ObjectTable using the new_key.

public entry fun update_merchant_key(
    registry: &mut MerchantRegistry,
    old_key: String,
    new_key: String
) {
    let mut merchant = object_table::remove(&mut registry.merchant_to_key, old_key); // 1. Remove and capture
    merchant.key = new_key; // 2. Update the struct
    object_table::add(&mut registry.merchant_to_key, new_key, merchant); // 3. Re-insert
}
2
Comments
.
Meaning.Sui.
Jul 23 2025, 15:52

If you’re trying to update a merchant’s key in a ObjectTable<String, Merchant> and running into errors like “Cannot ignore values without drop ability,” the issue is that in Sui Move, you can’t just delete or replace objects in storage without explicitly handling ownership and types that lack the drop ability. When you change the key inside your Merchant struct, that doesn't automatically update the merchant_to_key table—so you end up with the old key still pointing to the object, while the new key isn't linked at all.

2
Comments
.
24p30p.
24p30p1865
Jul 9 2025, 05:43

If you’re trying to update a merchant’s key in a ObjectTable<String, Merchant> and running into errors like “Cannot ignore values without drop ability,” the issue is that in Sui Move, you can’t just delete or replace objects in storage without explicitly handling ownership and types that lack the drop ability. When you change the key inside your Merchant struct, that doesn't automatically update the merchant_to_key table—so you end up with the old key still pointing to the object, while the new key isn't linked at all.

To fix this, you need to manually remove the old entry, update the struct, and then reinsert it using the new key. But because the Merchant struct doesn't have the drop ability, you can't just remove it and ignore the result—you have to store and reuse the returned object. Here's how you can do it properly inside a transaction:

public entry fun update_merchant_key(
    registry: &mut MerchantRegistry,
    old_key: String,
    new_key: String
) {
    // Remove the merchant using the old key and store the returned object
    let merchant = object_table::remove(&mut registry.merchant_to_key, old_key);

    // Update the key field inside the struct
    merchant.key = new_key;

    // Re-insert into the table with the updated key
    object_table::add(&mut registry.merchant_to_key, new_key, merchant);
}

This way, you safely extract the merchant, modify it, and insert it back using the new key—all while respecting Move's ownership rules.

Remember, this is necessary because Move is designed to prevent accidentally dropping or leaking resources. If your Merchant struct doesn't have the drop ability (which is common when it contains a UID), you must always handle it explicitly.

To get a clearer idea of how ObjectTable works and how to handle ownership safely, check the official docs here: https://docs.sui.io/build/move/object-tables.

0
Comments
.

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.

609Posts1280Answers
Sui.X.Peera.

Earn Your Share of 1000 Sui

Gain Reputation Points & Get Rewards for Helping the Sui Community Grow.

Reward CampaignJuly