One-Time Witness
A one-time witness (OTW) is a special instance of a type created in the module initializer and guaranteed to be unique with only one instance. It's important for cases where you need to make sure that a witness-authorized action was performed only once (for example, creating a new coin). In Move, a type is considered a OTW if it meets the following criteria:
- Named after the module
- All uppercased
- Only has the drop ability
- A type defined in the module
- Is empty
To check whether an instance is an OTW, use sui::types::is_one_time_witness(witness).
To get an instance of this type, you need to add it as the first argument to the init() function. Sui runtime supplies both initializer arguments automatically.
module examples::mycoin {
/// Name matches the module name
struct MYCOIN has drop {}
/// The instance is received as the first argument
fun init(witness: MYCOIN, ctx: &mut TxContext) {
/* ... */
}
}
The following example illustrates how you could use a OTW:
/// This example illustrates how One Time Witness works.
///
/// One Time Witness (OTW) is an instance of a type which is guaranteed to
/// be unique across the system. It has the following properties:
///
/// - created only in module initializer
/// - named after the module (uppercased)
/// - cannot be packed manually
/// - has a `drop` ability
module examples::one_time_witness_registry {
use sui::tx_context::TxContext;
use sui::object::{Self, UID};
use std::string::String;
use sui::transfer;
// This dependency allows us to check whether type
// is a one-time witness (OTW)
use sui::types;
/// For when someone tries to send a non OTW struct
const ENotOneTimeWitness: u64 = 0;
/// An object of this type will mark that there's a type,
/// and there can be only one record per type.
struct UniqueTypeRecord<phantom T> has key {
id: UID,
name: String
}
/// Expose a public function to allow registering new types with
/// custom names. With a `is_one_time_witness` call we make sure
/// that for a single `T` this function can be called only once.
public fun add_record<T: drop>(
witness: T,
name: String,
ctx: &mut TxContext
) {
// This call allows us to check whether type is an OTW;
assert!(types::is_one_time_witness(&witness), ENotOneTimeWitness);
// Share the record for the world to see. :)
transfer::share_object(UniqueTypeRecord<T> {
id: object::new(ctx),
name
});
}
}
/// Example of spawning an OTW.
module examples::my_otw {
use std::string;
use sui::tx_context::TxContext;
use examples::one_time_witness_registry as registry;
/// Type is named after the module but uppercased
struct MY_OTW has drop {}
/// To get it, use the first argument of the module initializer.
/// It is a full instance and not a reference type.
fun init(witness: MY_OTW, ctx: &mut TxContext) {
registry::add_record(
witness, // here it goes
string::utf8(b"My awesome record"),
ctx
)
}
}