Skip to main content

Regulated Coin and Deny List

The Sui Coin standard provides a create_regulated_currency function to create coins. This function is different than create_currency in that it generates a coin that you can block certain addresses from being able to use those coins in transactions. This ability is a requirement for assets like stablecoins.

Behind the scenes, create_regulated_currency uses the create_currency function to create the coin, but also produces a DenyCap object that allows its bearer to control access to the coin's deny list in a DenyList object. Consequently, the way to create a coin using create_regulated_currency is similar to the previous example, with the addition of a transfer of the DenyCap object to the module publisher.

examples/move/coin/sources/regcoin.move
module examples::regcoin {
use sui::coin::{Self, DenyCapV2};
use sui::deny_list::{DenyList};

public struct REGCOIN has drop {}

fun init(witness: REGCOIN, ctx: &mut TxContext) {
let (treasury, deny_cap, metadata) = coin::create_regulated_currency_v2(
witness,
6,
b"REGCOIN",
b"",
b"",
option::none(),
false,
ctx,
);
transfer::public_freeze_object(metadata);
transfer::public_transfer(treasury, ctx.sender());
transfer::public_transfer(deny_cap, ctx.sender())
}

}

When you deploy the previous module using sui client publish, the console responds with transaction effects, including the creation of the following objects:

...

Object Changes

Created Objects:

ObjectID: <OBJECT-ID>
Sender: <SENDER-ADDR>
Owner: Immutable
ObjectType: 0x2::coin::CoinMetadata<<PACKAGE-ID>::regcoin::REGCOIN>
Version: <VERSION-NUMBER>
Digest: <DIGEST-HASH>

ObjectID: <OBJECT-ID>
Sender: <SENDER-ADDR>
Owner: Account Address ( <PUBLISHER-ADDRESS )
ObjectType: 0x2::package::UpgradeCap
Version: <VERSION-NUMBER>
Digest: <DIGEST-HASH>

ObjectID: <OBJECT-ID>
Sender: <SENDER-ADDR>
Owner: Immutable
ObjectType: 0x2::coin::RegulatedCoinMetadata<<PACKAGE-ID>::regcoin::REGCOIN>
Version: <VERSION-NUMBER>
Digest: <DIGEST-HASH>

ObjectID: <OBJECT-ID>
Sender: <SENDER-ADDR>
Owner: Account Address ( <PUBLISHER-ADDRESS )
ObjectType: 0x2::coin::DenyCap<<PACKAGE-ID>::regcoin::REGCOIN>
Version: <VERSION-NUMBER>
Digest: <DIGEST-HASH>


ObjectID: <OBJECT-ID>
Sender: <SENDER-ADDR>
Owner: Account Address ( <PUBLISHER-ADDRESS )
ObjectType: 0x2::coin::TreasuryCap<PACKAGE-ID>::regcoin::REGCOIN>
Version: <VERSION-NUMBER>
Digest: <DIGEST-HASH>

...

As you might have noticed, the publish action creates a RegulatedCoinMetadata object along with the standard CoinMetadata object. You don't need to explicitly call the freeze_object on the RegulatedCoinMetadata object, however, because create_regulated_currency automatically performs this action.

The output also shows the three objects that the publisher now owns: UpgradeCap for package upgrades, TreasuryCap for minting or burning coins, and the DenyCap for adding or removing addresses to or from the deny list for this coin.

DenyList

The Sui framework provides a DenyList singleton, shared object that the bearer of a DenyCap can access to specify a list of addresses that are unable to use a Sui core type. The initial use case for DenyList, however, focuses on limiting access to coins of a specified type. This is useful, for example, when creating a regulated coin on Sui that requires the ability to block certain addresses from using it as inputs to transactions. Regulated coins on Sui satisfy any regulations that require the ability to prevent known bad actors from having access to those coins.

info

The DenyList object is a system object that has the address 0x403. You cannot create it yourself.

Manipulate deny list

For the ability to manipulate the addresses assigned to the deny list for your coin, you must add a few functions to the previous example.

examples/move/coin/sources/regcoin.move
public fun add_addr_from_deny_list(
denylist: &mut DenyList,
denycap: &mut DenyCapV2<REGCOIN>,
denyaddy: address,
ctx: &mut TxContext,
) {
coin::deny_list_v2_add(denylist, denycap, denyaddy, ctx);
}

public fun remove_addr_from_deny_list(
denylist: &mut DenyList,
denycap: &mut DenyCapV2<REGCOIN>,
denyaddy: address,
ctx: &mut TxContext,
) {
coin::deny_list_v2_remove(denylist, denycap, denyaddy, ctx);
}

To use these functions, you pass the DenyList object (0x403), your DenyCap object ID, and the address you want to either add or remove. Using the Sui CLI, you could use sui client call with the required information:

tip

Beginning with the Sui v1.24.1 release, the --gas-budget flag is no longer required for CLI commands.

sui client call --function add_addr_from_deny_list --module regcoin --package <PACKAGE-ID> --args <DENY-LIST> <DENY-CAP> <ADDRESS-TO-DENY> --gas-budget <GAS-AMOUNT>
Transaction Digest: <DIGEST-HASH>

The console displays the response from the network, where you can verify the DenyList object is mutated.

...

MutatedObjects:

ObjectID: 0x0...403
Sender: <SENDER-ADDRESS>
Owner: Shared
ObjectType: 0x2::deny_list::DenyList
Version: <VERSION-NUMBER>
Digest: <DIGEST-HASH>

...

For all Coin functions available, see the Sui framework coin module documentation.