Skip to main content

One-Time Witness

A one-time witness (OTW) is a special type that is guaranteed to have at most one instance. It is useful for limiting certain actions to only happen once (for example, creating a coin). In Move, a type is considered a OTW if:

  • Its name is the same as its module's names, all uppercased.
  • It has the drop ability and only the drop ability.
  • It has no fields, or a single bool field.

The only instance of this type is passed to its module's init function when the package containing it is published.

To check whether a type could be used as a OTW, pass an instance of it to sui::types::is_one_time_witness.

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
)
}
}