Post
Share your knowledge.
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 merchantkey
: a String used as a unique keybalance
: a u64 representing their balance
I also made a MerchantRegistry
struct to manage all merchants:
id
: another UIDmerchant_to_address
: anObjectTable<address, Merchant>
mapping addresses to merchantsmerchant_to_key
: anObjectTable<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
Answers
6In 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(®istry.merchant_to_key, utf8(key))
}
}
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.
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.
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
}
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.
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.
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.

- ... SUISuiLover+1211
- ... SUI0xduckmove+1207
- ... SUIThorfin+1204
- ... SUIharry phan+849
- ... SUIOwen+689
- ... SUIMeaning.Sui+675
- ... SUItheking+587
- Why does BCS require exact field order for deserialization when Move structs have named fields?53
- Multiple Source Verification Errors" in Sui Move Module Publications - Automated Error Resolution43
- Sui Transaction Failing: Objects Reserved for Another Transaction25
- How do ability constraints interact with dynamic fields in heterogeneous collections?05