본문으로 건너뛰기

코인 표준

코인 표준과 Currency Standard는 모두 대체 가능 토큰을 생성하는 데 사용된다. 하지만, 이들은 서로 다른 생성 방법을 사용하며 메타데이터를 서로 다른 유형의 object에 저장한다.

Coin은 coin::create_currency를 사용하여 토큰을 생성하는 반면 Currency는 sui::coin_registry를 사용한다.

Coin은 CoinMetadata를 사용하는 반면 Currency는 Currency를 사용한다.

코인 표준 또는 Currency Standard를 사용하여 Sui에서 생성된 대체 가능 토큰은 _coins_로 지칭된다.

Sui에서 Closed-Loop Token standard를 사용하여 생성된 대체 가능 토큰에 대해서는, 문서에서 _tokens_라는 용어를 사용한다. 실제로는, 이 두 object에 대한 용어는 종종 서로 바꿔서 사용된다.

코인 표준은 Sui에서 화폐를 생성하기 위해 Move 스마트 계약이 사용하는 기술 표준이다. sui::coin 모듈은 이 표준을 정의하는 로직을 제공한다.

Sui에서 화폐 생성이 표준화되었다는 것은 지갑, 거래소, 그리고 다른 스마트 계약이 추가적인 처리 로직 없이 SUI를 관리하는 것과 동일하게 Sui에서 생성된 화폐를 관리할 수 있음을 의미한다.

SUI 네이티브 화폐와 네트워크에서의 사용에 대해 더 알아보려면 Sui 토크노믹스를 참조한다.

Fungible tokens

Coin<T> 타입은 개방형 대체 가능 토큰을 나타낸다(폐쇄형 토큰에 대해서는 Token<T>를 참조한다). 코인은 타입 매개변수 T로 명명되며, TCoin<T>의 모든 인스턴스에 적용되는 메타데이터(이름, 심볼, 소수점 정밀도 등)와도 연관된다. sui::coin 모듈은 Coin<T>를 대체 가능한 것으로 취급하는 Coin<T>에 대한 인터페이스를 노출하며, 이는 전통적인 법정 화폐가 작동하는 방식과 유사하게 Coin<T>의 한 인스턴스에 보관된 T 단위가 다른 어떤 T 단위와도 교환 가능함을 의미한다.

Coin creation

coin::create_currency 함수를 사용해 코인을 생성하면, 코인을 생성하는 스마트 계약의 게시자는 TreasuryCap object를 받는다.

Supply states

레지스트리는 세 가지 다른 공급 관리 모델을 지원한다:

  • 고정 공급: 총 공급량이 영구적으로 잠겨 변경할 수 없다.
  • 소각 전용 공급: 코인은 Currency object를 통해 자유롭게 소각할 수 있다.
  • 비제어 공급: TreasuryCap 보유자가 민팅과 소각을 제어한다.

Regulatory states

화폐는 다른 규제 상태를 가질 수 있다:

  • 규제됨: 화폐에는 address가 이를 사용하는 것을 제한할 수 있는 DenyCapV2가 연결되어 있다.
  • 규제되지 않음: 화폐가 deny list capability 없이 생성되었다.
  • 알 수 없음: 규제 상태가 결정되지 않았으며, 일반적으로 기존 마이그레이션에서 발생한다.

Treasury capability

위험

TreasuryCap은 자유롭게 전송할 수 있지만, TreasuryCap을 동결하거나 공유하지 않는다. 그렇게 하면 악의적인 행위자가 화폐 소유자로서 함수를 호출할 수 있게 될 수 있으며, 후자는 누구나 새 코인을 민트하고 화폐의 소유자로 동작할 수 있게 한다.

TreasuryCap object는 새 코인을 민트하거나 현재 코인을 소각하는 데 필요하다. 따라서 이 object에 액세스할 수 있는 address만 Sui 네트워크에서 코인 공급량을 유지할 수 있다.

TreasuryCap object는 전송 가능하므로, TreasuryCap을 전송하면 제3자가 사용자가 생성한 코인의 관리를 인수할 수 있다. 하지만 capability를 전송한 후에는 더 이상 직접 토큰을 민트하거나 소각할 수 없다.

Regulated coins

Coin standard에는 규제된 코인을 생성하는 기능이 포함된다. 규제된 코인을 생성하려면 coin::create_regulated_currency_v2 함수를 사용하며(이 함수는 coin::create_currency 함수를 자체적으로 사용한다), 이 함수는 DenyCap capability도 반환한다. DenyCap capability는 토큰을 사용할 수 없는 address 목록을 bearer가 유지할 수 있게 한다.

regulated-coin-sample repository는 규제된 코인 생성의 예시를 제공한다.

DenyList object

특정 규제된 코인을 사용할 수 없는 address 목록은 시스템이 생성한 DenyList shared object 안에 보관된다. DenyCap에 액세스할 수 있으면 coin::deny_list_v2_addcoin::deny_list_v2_remove 함수를 사용해 address를 추가하고 제거할 수 있다.

Global pause switch

규제된 코인 object에는 allow_global_pause Boolean 필드가 포함된다. 이 값이 true로 설정되면, 해당 코인 타입에 대한 DenyCapV2 object의 bearer는 coin::deny_list_v2_enable_global_pause 함수를 사용해 코인 활동을 무기한으로 일시 중지할 수 있다. bearer가 일시 중지를 시작하자마자 네트워크는 모든 transaction에서 해당 코인 타입을 입력으로 사용하는 것을 허용하지 않는다. 다음 epoch 시작 시(epoch는 약 24시간 동안 지속된다), 네트워크는 추가로 모든 address가 해당 코인 타입을 수신하는 것도 허용하지 않는다.

해당 코인 타입에 대한 DenyCapV2 object의 bearer가 coin::deny_list_v2_disable_global_pause를 사용해 일시 중지를 해제하면, 코인은 즉시 transaction 입력으로 다시 사용할 수 있다. 하지만 address는 그 다음 epoch까지 해당 코인 타입을 수신할 수 없다.

글로벌 일시 중지 기능은 코인의 deny list에 영향을 주지 않는다. 코인의 일시 중지를 해제한 후에도 deny list에 포함된 모든 address는 여전히 코인과 상호작용할 수 없다.

Coin metadata

사용자가 생성하는 각 코인에는 이를 설명하는 메타데이터가 포함된다. 일반적으로 스마트 계약은 코인에 대한 메타데이터가 거의 변경되어서는 안 되기 때문에 transfer::public_freeze_object 함수를 사용해 생성 시 이 object를 동결한다. 규제된 코인은 생성하는 메타데이터를 자동으로 동결한다.

Coin standard를 사용하는 일반 코인에는 CoinMetadata object가 포함된다. 앞서 언급했듯이, 규제된 코인은 일반 코인을 생성하는 동일한 절차 위에 구축되므로 deny list 정보가 포함된 RegulatedCoinMetadata object에 더해 동일한 메타데이터 object를 받는다.

Metadata fields

메타데이터 object의 필드는 다음을 포함한다:

CoinMetadata

NameDescription
id토큰 메타데이터의 object ID이다.
decimals토큰이 사용하는 소수점 자리수이다. 이 필드를 3으로 설정하면, 값이 1000인 토큰은 1.000으로 표시된다.
name코인의 이름이다.
symbol코인의 심볼이다. 이는 name과 같을 수 있지만, 일반적으로 5자 미만의 모두 대문자이다. 예를 들어, SUI는 Sui의 네이티브 코인에 대한 symbol이지만 nameSUI이다.
description토큰을 설명하기 위한 짧은 설명이다.
icon_url지갑, 익스플로러, 그리고 다른 앱에서 표시하는 데 사용되는 토큰 아이콘의 URL이다.

RegulatedCoinMetadata

NameDescription
id규제된 토큰 메타데이터 object의 ID이다.
coin_metadata_object규제된 토큰에 대한 기본 메타데이터 object(CoinMetadata)의 ID이다.
deny_cap_object토큰의 DenyCapV2 object ID이며, 이는 토큰을 사용할 수 있는지 여부를 제어하는 deny list 항목을 유지하는 데 필요하다.

Minting and burning coins

coin 모듈은(연관된 TreasuryCap을 소유하고 있는 한) Sui 네트워크에서 코인을 생성하고 파기하기 위한 로직을 제공한다. 이 함수들은 모든 코인에 대해 동일하며 각 함수는 입력으로 TreasuryCap을 요구한다.

Mint

새 토큰을 생성하려면 coin::mint 함수를 사용한다.

public fun mint<T>(cap: &mut TreasuryCap<T>, value: u64, ctx: &mut TxContext): Coin<T> {
Coin {
id: object::new(ctx),
balance: cap.total_supply.increase_supply(value),
}
}

서명은 TreasuryCap, 생성된 코인의 값, 그리고 transaction 컨텍스트로 함수를 호출하면 Coin<T>가 생성됨을 보여준다. 이 함수는 TreasuryCap의 총 공급량을 자동으로 업데이트한다. 표시될 때, 코인 value는 메타데이터의 decimals 값을 따른다. 따라서 decimal 값이 6인 코인의 value로 1000000을 제공하면, 코인의 값은 1.000000으로 표시된다.

Burn

현재 토큰을 파기하려면 coin::burn 함수를 사용한다.

#[allow(lint(public_entry))]
public entry fun burn<T>(cap: &mut TreasuryCap<T>, c: Coin<T>): u64 {
let Coin { id, balance } = c;
id.delete();
cap.total_supply.decrease_supply(balance)
}

서명은 TreasuryCap과 소각하려는 코인 object만이 필요한 입력임을 보여주며, 공급이 감소된 양(코인의 값)을 반환한다. 이 함수는 공급량보다 더 많은 코인을 소각하도록 허용하지 않는다.

Adding and removing addresses to and from the deny list

deny list는 규제된 코인에만 적용된다. 앞서 언급했듯이, 규제된 코인을 생성하면 bearer에게 시스템이 생성한 DenyList object에서 address를 추가하고 제거하도록 권한을 부여하는 DenyCapV2를 받는다. 목록에 있는 어떤 address든 추가되는 즉시 코인을 transaction의 입력으로 사용할 수 없다. deny list에 address가 추가된 다음 epoch에, 해당 address는 추가로 코인 타입을 수신할 수도 없다. 즉, 코인 타입에 대한 deny list에 추가된 address는 즉시 코인을 보낼 수 없게 된다. 그 다음 epoch의 시작 시점에, 해당 address는 여전히 코인을 보낼 수 없을 뿐만 아니라 받을 수도 없다. 그 시점부터 DenyCapV2 bearer가 deny list에서 명시적으로 제거할 때까지 해당 address는 코인과 상호작용할 수 없다.

Add address to deny list

Use the coin::deny_list_v2_add function to add the provided address to the deny list for your coin. The signature for the function is:

public fun deny_list_v2_add<T>(
deny_list: &mut DenyList,
_deny_cap: &mut DenyCapV2<T>,
addr: address,
ctx: &mut TxContext,
) {
let ty = type_name::with_original_ids<T>().into_string().into_bytes();
deny_list.v2_add(DENY_LIST_COIN_INDEX, ty, addr, ctx)
}

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_v2_remove function to remove addresses from the deny list for your coin.

public fun deny_list_v2_remove<T>(
deny_list: &mut DenyList,
_deny_cap: &mut DenyCapV2<T>,
addr: address,
ctx: &mut TxContext,
) {
let ty = type_name::with_original_ids<T>().into_string().into_bytes();
deny_list.v2_remove(DENY_LIST_COIN_INDEX, ty, addr, ctx)
}

When using this function, you provide the DenyList object (0x403), the DenyCapV2 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. The following examples are based on the regulated coin sample.

const tx = new Transaction();

tx.moveCall({
target: `0x2::coin::deny_list_v2_add`,
arguments: [
tx.object(<SUI-DENY-LIST-OBJECT-ID>),
tx.object(<DENY-CAP-ID>),
tx.pure.address(options.address),
],
typeArguments: [<COIN-TYPE>],
});
  • <SUI-DENY-LIST-OBJECT-ID> is "0x403".
  • <DENY-CAP-ID> is the object of type DenyCapV2<REGULATED_COIN> you receive from publishing the contract.
  • options.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.

Globally pausing and unpausing regulated coin activity

Globally pausing coin activity is only applicable to regulated coin types.

Pause coin activity

To pause activity across the network for a regulated coin type with the allow_global_pause field set to true, use coin::deny_list_v2_enable_global_pause. You must provide the DenyCapV2 object for the coin type to initiate the pause. Transaction activity is paused immediately, and no addresses can receive the coin in the epoch that follows the call to pause.

#[allow(unused_mut_parameter)]
public fun deny_list_v2_enable_global_pause<T>(
deny_list: &mut DenyList,
deny_cap: &mut DenyCapV2<T>,
ctx: &mut TxContext,
)

Unpause coin activity

To restart network activity for a paused regulated coin, use the coin::deny_list_v2_disable_global_pause function. As with pausing, you must provide the DenyCapV2 object for the coin type. Transaction activity resumes immediately, and addresses can begin receiving the coin in the epoch that follows the call to remove the pause.

#[allow(unused_mut_parameter)]
public fun deny_list_v2_disable_global_pause<T>(
deny_list: &mut DenyList,
deny_cap: &mut DenyCapV2<T>,
ctx: &mut TxContext,
)

Reading coin data

Metadata

위험

CoinMetadata는 폐기될 예정으로 계획되어 있다. Coin standard 자체는 그렇지 않다.

코인에서 데이터를 검색하기 위해 다음 함수를 사용할 수 있다.

코인에 대한 메타데이터 object의 각 필드에 해당하는 값을 가져오려면 다음 함수를 사용한다.

FunctionSignature
get_decimalspublic fun get_decimals<T>(metadata: &coin::CoinMetadata<T>): u8
get_namepublic fun get_name<T>(metadata: &coin::CoinMetadata<T>): string::String
get_symbolpublic fun get_symbol<T>(metadata: &coin::CoinMetadata<T>): ascii::String
get_descriptionpublic fun get_description<T>(metadata: &coin::CoinMetadata<T>): string::String
get_icon_urlpublic fun get_icon_url<T>(metadata: &coin::CoinMetadata<T>): option::Option<url::Url>

Total supply

주어진 코인의 현재 공급량을 가져오려면 coin::supply 함수를 사용한다.

Update coin metadata

CoinMetadata object가 생성 시 동결되지 않았다면, 다음 함수를 사용해 값을 업데이트할 수 있다.

각 함수의 서명은 유사하다. 표에 정의된 값을 사용해 <FUNCTION-NAME><ATTRIBUTE-TYPE>을 바꾸면 각 함수의 서명을 얻을 수 있다:

public entry fun <FUNCTION-NAME><T>(
_treasury: &coin::TreasuryCap<T>,
metadata: &mut coin::CoinMetadata<T>,
<ATTRIBUTE-TYPE>
)
<FUNCTION-NAME><ATTRIBUTE-TYPE>
update_namename: string::String
update_symbolsymbol: ascii::String
update_descriptiondescription: string::String
update_icon_urlurl: ascii::String
정보

RegulatedCoinMetadata는 생성 시 동결되므로, 데이터를 업데이트하는 함수가 없다.

Migration from Coin to Currency Standard

Sui provides a migration path from the CoinMetadata<T> system while maintaining backward compatibility.

The migration system is designed with specific constraints to maintain data integrity and preserve existing functionality. Migration can only occur permissionlessly when done by reference, meaning the original CoinMetadata object remains intact while its data is copied to create a new Currency entry in the registry. This approach allows for safe registration of new currency data and updates to existing currency data, but only as long as the MetadataCap has not yet been claimed.

The system cannot allow permissionless migration by value, however, where the original CoinMetadata object would be consumed or destroyed during migration. This restriction exists because some coins have governance mechanisms that control CoinMetadata updates. Allowing value-based migration would irreversibly break those existing governance workflows by destroying the metadata objects that governance systems expect to manage.

The destruction of legacy CoinMetadata objects is only permitted after the corresponding MetadataCap has been claimed, serving as proof that the currency's owner has taken control through the new registry system. This ensures that legacy metadata cannot be accidentally destroyed while governance systems still depend on it, and provides a clear transition path where owners must explicitly claim control before legacy objects can be cleaned up.

This design preserves backward compatibility while enabling a smooth transition to the centralized registry system, protecting existing governance mechanisms until owners are ready to migrate fully to the new system.

Some of the benefits to migrate to the Coin Registry include:

  • Centralized management: Single source of truth for all coin information.
  • Enhanced features: Access to advanced supply models and regulatory tracking.
  • Ecosystem integration: Better support for wallets, exchanges, and apps.
  • Future-proofing: Access to ongoing registry enhancements.

Migration process

  1. Metadata migration: Use migrate_legacy_metadata<T>() to create a new Currency<T> entry based on existing CoinMetadata<T> information.

    public fun migrate_legacy_metadata<T>(
    registry: &mut CoinRegistry,
    legacy: &CoinMetadata<T>,
    _ctx: &mut TxContext,
    )
  2. Regulatory migration: For coins with deny list capabilities, use:

    • migrate_regulated_state_by_metadata<T>(): Migrate based on existing metadata.

      public fun migrate_regulated_state_by_metadata<T>(
      currency: &mut Currency<T>,
      metadata: &RegulatedCoinMetadata<T>,
      )
    • migrate_regulated_state_by_cap<T>(): Migrate based on deny capability.

      public fun migrate_regulated_state_by_cap<T>(currency: &mut Currency<T>, cap: &DenyCapV2<T>)

Migration function mappings

Update smart contract logic that relies on the coin module to use the coin_registry module instead:

  • coin::create_currency -> coin_registry::new_currency_with_otw
  • coin::create_regulated_currency_v2 -> coin_registry::new_currency_with_otw
public fun new_currency_with_otw<T: drop>(
otw: T,
decimals: u8,
symbol: String,
name: String,
description: String,
icon_url: String,
ctx: &mut TxContext,
): (CurrencyInitializer<T>, TreasuryCap<T>)