Coin Standard
The Coin standard is the technical standard used for smart contracts on IOTA for creating coins on the IOTA blockchain. The standardization of coin creation on IOTA means that wallets, exchanges, and other smart contracts can manage coins created on IOTA the same as they manage IOTA, without any additional processing logic.
See IOTA Tokenomics to learn more about the IOTA native coin and its use on the IOTA network.
Although coins on IOTA follow the Coin standard, they can offer specialized abilities. For example, you can create a regulated token that allows its creator to add specific addresses to a deny list, so that the identified addresses cannot use the token as inputs to transactions.
See the coin module documentation for all available options when creating a coin-type token on IOTA.
Fungible tokens
In the IOTA blockchain ecosystem, the Coin<T>
type represents open-loop fungible tokens (see Token<T>
for closed-loop tokens). Coins are denominated by their type parameter, T
, which is also associated with metadata (like name, symbol, decimal precision, and so on) that applies to all instances of Coin<T>
. The iota::coin
module exposes an interface over Coin<T>
that treats it as fungible, meaning that a unit of T
held in one instance of Coin<T>
is interchangeable with any other unit of T
, much like how traditional fiat currencies operate.
The documentation refers to fungible tokens created on IOTA using the Coin standard as "coins". For fungible tokens created on IOTA using the Closed-Loop Token standard, the documentation uses the term "tokens". In practice, the terms for both these objects are often interchangeable.
Treasury capability
When you create a coin using the coin::create_currency
function, the publisher of the smart contract that creates the coin receives a TreasuryCap
object. The TreasuryCap
object is required to mint new coins or to burn current ones. Consequently, only addresses that have access to this object are able to maintain the coin supply on the IOTA network.
The TreasuryCap
object is transferable, so a third party can take over the management of a coin that you create if you transfer the TreasuryCap
. After transferring the capability, however, you are no longer able to mint and burn tokens yourself.
We highly recommend you use the Coin Manager functionality to manage your Coin
instead of just keeping the TreasuryCap.
This will provide more clarity to the end user and will open up more functionalities.
Regulated coins
The Coin standard includes the ability to create regulated coins. To create a regulated coin, you use the coin::create_regulated_currency
function (which uses the coin::create_currency
function itself), but which also returns a DenyCap
capability. The DenyCap
capability allows the bearer to maintain a list of addresses that aren't allowed to use the token.
The regulated-coin-example repository provides an example of regulated coin creation.
DenyList object
The list of addresses that aren't able to use a particular regulated coin is held within a system-created DenyList
shared object. If you have access to the DenyCap
, then you can use the coin::deny_list_add
and coin::deny_list_remove
functions to add and remove addresses. You can also use the coin::deny_list_contains
function to check if an address is already on the list.
Coin metadata
Each coin you create includes metadata that describes it. Typically, smart contracts freeze this object upon creation using the transfer::public_freeze_object
function because the metadata for coins should almost never change. Regulated coins freeze the metadata they create automatically.
Regular coins using the Coin standard include a CoinMetadata
object. As mentioned previously, regulated coins build on top of the same procedure that creates regular coins, so they receive the same metadata object in addition to a RegulatedCoinMetadata
object that includes deny list information.
The fields of the metadata objects include the following:
CoinMetadata
Name | Description |
---|---|
id | The object ID of the metadata for the token. |
decimals | The number of decimals the token uses. If you set this field to 3 , then a token of value 1000 would display as 1.000 . |
name | Name of the coin. |
symbol | Symbol for the coin. This might be the same as name , but is typically fewer than five all capital letters. For example, IOTA is the symbol for the native coin on IOTA but its name is also IOTA . |
description | A short description to describe the token. |
icon_url | The URL for the token's icon, used for display in wallets, explorers, and other apps. |
RegulatedCoinMetadata
Name | Description |
---|---|
id | The ID of the metadata object for the regulated token. |
coin_metadata_object | The ID of the underlying metadata object (CoinMetadata ) for the regulated token. |
deny_cap_object | The ID of the token's DenyCap object, which is necessary to maintain the deny list entries that controls who can and cannot use the token. |
Minting and burning coins
The coin
module provides the logic for creating and destroying coins on the IOTA network (as long as you own the associated TreasuryCap
). These functions are the same for all coins and each requires the TreasuryCap
as an input.
Mint
Use the coin::mint
function to create new tokens. The signature for the function is:
public fun mint<T>(
cap: &mut coin::TreasuryCap<T>,
value: u64,
ctx: &mut tx_context::TxContext
): coin::Coin<T>
The signature shows that a Coin<T>
results from calling the function with a TreasuryCap
, value for the coin created, and the transaction context. The function updates the total supply in TreasuryCap
automatically. Upon display, the coin value
respects the decimals
value in the metadata. So, if you supply 1000000 as the coin value
that has a decimal
value of 6
, the coin's value displays as 1.000000
.
Burn
Use the coin::burn
function to destroy current tokens. The signature for the function is:
public entry fun burn<T>(
cap: &mut coin::TreasuryCap<T>,
c: coin::Coin<T>
): u64
The signature shows that only the TreasuryCap
and coin object you want to burn are necessary inputs, returning the amount by which the supply was decreased (value of the coin). The function does not allow you to burn more coins than are available in the supply.
Adding and removing addresses to and from the deny list
The deny list is only applicable to regulated coins. As mentioned previously, when you create a regulated coin you receive a DenyCap
that authorizes the bearer to add and remove addresses from the system-created DenyList
object. Any address on the list for your coin is unable to use the coin as an input to transactions.
Add address to deny list
Use the coin::deny_list_add
function to add the provided address to the deny list for your coin. The signature for the function is:
public fun deny_list_add<T>(
deny_list: &mut deny_list::DenyList,
_deny_cap: &mut coin::DenyCap<T>,
addr: address,
_ctx: &mut tx_context::TxContext
)
When using this function, you provide the DenyList
object (0x403
), the DenyCap
you receive on coin creation, the address to add to the list, and the transaction context. After using this function, the address you provide is unable to use your coin by the next epoch.
Remove address from deny list
Use the coin::deny_list_remove
function to remove addresses from the deny list for your coin. The signature for the function is:
public fun deny_list_remove<T>(
deny_list: &mut deny_list::DenyList,
_deny_cap: &mut coin::DenyCap<T>,
addr: address,
_ctx: &mut tx_context::TxContext
)
When using this function, you provide the DenyList
object (0x403
), the DenyCap
you receive on coin creation, the address to remove from the list, and the transaction context. If you try to remove an address that isn't on the list, you receive an ENotFrozen
error and the function aborts. After calling this function, the address you provide is able to use your coin by the next epoch.
Using an SDK
You can use either the TypeScript or Rust SDK to manipulate the addresses held in the DenyList
for your coin.
- TypeScript
- Rust
const tx = new Transaction();
tx.moveCall({
target: `0x2::coin::deny_list_add`,
arguments: [
tx.object(<IOTA-DENY-LIST-OBJECT-ID>),
tx.object(<DENY-CAP-ID>),
tx.pure.address(options.address),
],
typeArguments: [<COIN-TYPE>],
});
<IOTA-DENY-LIST-OBJECT-ID>
is"0x403"
<DENY-CAP-ID>
is the object of typeDenyCap<REGULATED_COIN>
we received from publishing the contractoptions.address
is the address to ban<COIN-TYPE>
is${PACKAGE-ID}::${MODULE-NAME}::${COIN-NAME}
, which is${PACKAGE-ID}::regulated_coin::REGULATED_COIN
based on the example.
let mut ptb = ProgrammableTransactionBuilder::new();
let deny_list = ptb.obj(ObjectArg::SharedObject {
id: deny_list.0,
initial_shared_version: deny_list.1,
mutable: true,
})?;
let deny_cap = ptb.obj(ObjectArg::ImmOrOwnedObject(deny_cap))?;
let address_to_ban = IotaAddress::from_str("0x...")?;
let address_to_ban_arg = ptb.pure(address_to_ban)?;
ptb.programmable_move_call(
IOTA_FRAMEWORK_PACKAGE_ID,
Identifier::from(COIN_MODULE_NAME),
Identifier::from_str("deny_list_add")?,
vec![<otw-type>],
vec![deny_list, deny_cap, address_to_ban_arg],
);
let builder = ptb.finish();
deny_list
is of type(ObjectID, SequenceNumber)
.ObjectID
is0x403
.SequenceNumber
is theinitial_shared_version
of theDenyList
singleton.
deny_cap
is theObjectRef
((ObjectID, SequenceNumber, ObjectDigest)
) of theDenyCap<REGULATED_COIN>
the publisher has received.otw_type
is theTypeTag
created from<PACKAGE_ID>::regulated_coin::REGULATED_COIN
type.address_to_ban
is the address to ban as anIotaAddress
.
Query coin data
You can use the following functions to retrieve data from coins.
Metadata
Use the following functions to get the values for the respective fields on the metadata object for coins.
Function | Signature |
---|---|
get_decimals | public fun get_decimals<T>(metadata: &coin::CoinMetadata<T>): u8 |
get_name | public fun get_name<T>(metadata: &coin::CoinMetadata<T>): string::String |
get_symbol | public fun get_symbol<T>(metadata: &coin::CoinMetadata<T>): ascii::String |
get_description | public fun get_description<T>(metadata: &coin::CoinMetadata<T>): string::String |
get_icon_url | public fun get_icon_url<T>(metadata: &coin::CoinMetadata<T>): option::Option<url::Url> |
Supply
Use the coin::supply
function to get the current supply of a given coin.
Check for address on deny list
Use the coin::deny_list_contains
function to check if an address exists on the deny list for your coin. The signature of the function is:
public fun deny_list_contains<T>(
freezer: &deny_list::DenyList,
addr: address
): bool
The function returns true
if the address is found on the coin's list, otherwise it returns false
.
Update coin metadata
If the CoinMetadata
object was not frozen upon creation, you can use the following functions to update its values.
Each function signature is similar. Replace <FUNCTION-NAME>
and <ATTRIBUTE-TYPE>
with the values defined in the table to get the signature of each function:
public entry fun <FUNCTION-NAME><T>(
_treasury: &coin::TreasuryCap<T>,
metadata: &mut coin::CoinMetadata<T>,
<ATTRIBUTE-TYPE>
)
<FUNCTION-NAME> | <ATTRIBUTE-TYPE> |
---|---|
update_name | name: string::String |
update_symbol | symbol: ascii::String |
update_description | description: string::String |
update_icon_url | url: ascii::String |
RegulatedCoinMetadata
is frozen upon creation, so there are no functions to update its data.
Related links
Check out the following content for more information about coins and tokens on IOTA:
- Create a Coin: Guide for creating coins and regulated coins in your smart contracts.
- Closed-Loop Token Standard: Details the Token standard on IOTA.
coin
module documentation: Automated documentation output for the IOTA frameworkcoin
module.token
module documentation: Automated documentation output for the IOTA frameworktoken
module.- Tokenomics: Discover the IOTA ecosystem and where IOTA coins fit within it.