Skip to main content

Create Fungible Tokens: Currency Standard

Assets created using the Currency Standard are free-flowing, wrappable, freely transferable fungible assets that you can store in any app. The Currency Standard uses the sui::coin_registry module and the Currency object for metadata.

The Currency<T> type represents open-loop fungible tokens. Currencies are denominated by their type parameter T, which is also associated with metadata (like name, symbol, and decimal precision) that applies to all instances of Currency<T>. The sui::coin_registry module exposes an interface over Currency<T> that treats it as fungible, meaning that a unit of T held in one instance of Currency<T> is interchangeable with any other unit of T, similar to how traditional fiat currencies operate.

Coin Registry​

The Coin Registry system provides a centralized approach to currency management through the sui::coin_registry module. The registry is a shared object located at address 0xc that stores metadata, supply information, and regulatory status for all registered coin types.

Creation options​

The registry supports two different coin creation flows:

  • Standard creation (coin_registry::new_currency): Recommended for most cases. You can call this method any time after the coin type is published.
  • One-Time Witness creation (coin_registry::new_currency_with_otw): Uses a One-Time Witness for uniqueness proof. Requires a two-step publish and finalize process.

Both creation methods return a CurrencyInitializer<T> that allows for additional configuration:

  • Regulated tokens: Add deny list capabilities to make your token regulated.
  • Supply model: Choose between fixed, burn-only, or flexible supply.
  • Extensions: Include additional fields for custom functionality.
/// Hot potato wrapper to enforce registration after "new_currency" data creation.
/// Destroyed in the `finalize` call and either transferred to the `CoinRegistry`
/// (in case of an OTW registration) or shared directly (for dynamically created
/// currencies).
public struct CurrencyInitializer<phantom T> {
currency: Currency<T>,
extra_fields: Bag,
is_otw: bool,
}

Use the new_currency function at any time after the coin type is published. It creates a shared Currency<T> object, where type T must be a key-only type (public struct MyCoin has key { id: UID }):

#[allow(lint(self_transfer))]
public fun new_currency(registry: &mut CoinRegistry, ctx: &mut TxContext): Coin<MyCoin> {
let (mut currency, mut treasury_cap) = coin_registry::new_currency(
registry,
6, // Decimals
b"MyCoin".to_string(), // Symbol
b"My Coin".to_string(), // Name
b"Standard Unregulated Coin".to_string(), // Description
b"https://example.com/my_coin.png".to_string(), // Icon URL
ctx,
);

let total_supply = treasury_cap.mint(TOTAL_SUPPLY, ctx);
currency.make_supply_burn_only(treasury_cap);

let metadata_cap = currency.finalize(ctx);
transfer::public_transfer(metadata_cap, ctx.sender());

total_supply
}

Once you call the finalize function, currency creation is complete:

#[allow(lint(share_owned))]
/// Finalize the coin initialization, returning `MetadataCap`
public fun finalize<T>(builder: CurrencyInitializer<T>, ctx: &mut TxContext): MetadataCap<T>

One-Time Witness creation​

Proper creation and RPC support requires a second transaction to promote the currency to the registry.

One-Time Witness creation of a new coin is a two-step process. The initialization process begins with package publication:

fun init(witness: MY_COIN_NEW, ctx: &mut TxContext) {
let (builder, treasury_cap) = coin_registry::new_currency_with_otw(
witness,
6, // Decimals
b"MY_COIN".to_string(), // Symbol
b"My Coin".to_string(), // Name
b"Standard Unregulated Coin".to_string(), // Description
b"https://example.com/my_coin.png".to_string(), // Icon URL
ctx,
);

let metadata_cap = builder.finalize(ctx);

transfer::public_transfer(treasury_cap, ctx.sender());
transfer::public_transfer(metadata_cap, ctx.sender());
}

Then, call coin_registry::finalize_registration to place the coin into the registry:

# Requires the ID of the Currency object created during publishing.
# This step is only required for OTW-created currencies.
sui client ptb \
--assign @created_currency_object_id currency_to_promote \
--move-call 0x2::coin_registry::finalize_registration <CURRENCY_TYPE> @0xc currency_to_promote
/// The second step in the "otw" initialization of coin metadata, that takes in
/// the `Currency<T>` that was transferred from init, and transforms it in to a
/// "derived address" shared object.
///
/// Can be performed by anyone.
public fun finalize_registration<T>(
registry: &mut CoinRegistry,
currency: Receiving<Currency<T>>,
_ctx: &mut TxContext,
)