본문으로 건너뛰기

대체 불가능 토큰 만들기

Sui에서는 모든 object가 고유하며 owner가 있다. 이는 Ethereum 같은 다른 블록체인이 ERC-721 같은 전용 standard를 필요로 하는 속성이다.

Sui와 object 기반이 아닌 다른 블록체인에서 Rare Reef Shark NFT 컬렉션을 만든다고 가정해 보자. 다른 블록체인에서 shark의 이름 같은 속성을 얻으려면 NFT ID를 사용해 해당 NFT를 만든 smart contract와 상호작용해야 한다. 일반적으로 이 정보는 offchain storage에서 가져온다. Sui에서는 name 속성이 NFT 자체를 정의하는 object의 field이다. 이 구조는 정보를 필요로 하는 smart contract가 object에서 name을 직접 반환할 수 있으므로 NFT metadata에 더 직접적으로 접근할 수 있게 한다.

사용 사례

Sui의 NFT는 다양한 애플리케이션에서 유용하다.

  • 디지털 수집품: 고유한 attribute와 provenance를 가진 art, trading card, 인게임 character 같은 고유 item. SuiFrens는 Sui의 collectible NFT collection 예시이다.
  • 게임 자산: weapon, skin, land parcel 같은 인게임 item. 각 asset은 고유한 속성을 가지며 호환되는 game 전반에서 소유, 거래, 사용할 수 있다.
  • 이벤트 티켓과 access pass: holder에게 event, community, gated experience에 대한 접근 권한을 부여하는 ticket 또는 credential. 고유성과 ownership은 온체인에서 검증할 수 있다.
  • Identity와 credential: certification, membership, reputation score 같은 전송 불가능한 credential을 나타내는 soulbound NFT.
  • NFT rental: Kiosk extension을 사용해 정의된 기간 동안 NFT를 다른 user에게 대여할 수 있다. 이를 통해 holder가 asset을 판매하지 않고 monetization하려는 use case를 지원한다.
  • 암호화된 미디어: NFT는 암호화된 콘텐츠를 래핑하여 owner만 underlying asset에 접근할 수 있게 할 수 있으며, 이는 freemium 및 try-before-you-buy model을 가능하게 한다.

NFT와 토큰화된 자산

Sui에서 NFT와 tokenized asset은 모두 object이다. 프로토콜은 둘을 구분하지 않는다. 차이는 smart contract를 설계하는 방식에 있다.

NFT는 일반적으로 supply가 1이고 name, description, image URL처럼 해당 instance에 특화된 metadata를 가진 object이다. tokenized asset은 supply가 1보다 크고 모든 fraction에서 metadata를 공유하는 fractional ownership용으로 설계된다. tokenized asset은 split 또는 merge할 수도 있지만 NFT는 이를 위해 설계되지 않는다.

다음 표는 핵심 설계 차이를 요약한다.

속성NFT토큰화된 자산
Supply11보다 큼
Metadatatoken마다 고유모든 fraction에서 공유
Divisibility분할 불가분할 가능(split 또는 merge)
Use case단일 item의 고유 ownershipasset의 fractional ownership
예시고유한 digital art 작품real estate의 fractional share

예시

다음 예시는 Sui에서 기본 NFT를 만든다. TestnetNFT struct는 id, name, description, url field를 사용해 NFT를 정의한다.

public struct TestnetNFT has key, store {
id: UID,
name: string::String,
description: string::String,
url: Url,
}

이 예시에서는 누구나 mint_to_sender 함수를 호출하여 NFT를 mint할 수 있다. 이 함수는 새 TestnetNFT를 만들고 호출을 수행한 address로 전송한다.

#[allow(lint(self_transfer))]
public fun mint_to_sender(
name: vector<u8>,
description: vector<u8>,
url: vector<u8>,
ctx: &mut TxContext,
) {
let sender = ctx.sender();
let nft = TestnetNFT {
id: object::new(ctx),
name: string::utf8(name),
description: string::utf8(description),
url: url::new_unsafe_from_bytes(url),
};

event::emit(NFTMinted {
object_id: object::id(&nft),
creator: sender,
name: nft.name,
});

transfer::public_transfer(nft, sender);
}

module에는 NFT metadata를 반환하는 함수가 포함되어 있다. name 함수를 호출해 해당 값을 가져올 수 있다. 이 함수는 NFT object에서 name field 값을 직접 반환한다.

public fun name(nft: &TestnetNFT): &string::String {
&nft.name
}
Click to open

testnet_nft.move

module examples::testnet_nft;

use std::string;
use sui::event;
use sui::url::{Self, Url};

/// An example NFT that can be minted by anybody
public struct TestnetNFT has key, store {
id: UID,
/// Name for the token
name: string::String,
/// Description of the token
description: string::String,
/// URL for the token
url: Url,
// TODO: allow custom attributes
}

// ===== Events =====

public struct NFTMinted has copy, drop {
// The Object ID of the NFT
object_id: ID,
// The creator of the NFT
creator: address,
// The name of the NFT
name: string::String,
}

// ===== Public view functions =====

/// Get the NFT's `name`
public fun name(nft: &TestnetNFT): &string::String {
&nft.name
}

/// Get the NFT's `description`
public fun description(nft: &TestnetNFT): &string::String {
&nft.description
}

/// Get the NFT's `url`
public fun url(nft: &TestnetNFT): &Url {
&nft.url
}

// ===== Entrypoints =====

#[allow(lint(self_transfer))]
/// Create a new devnet_nft
public fun mint_to_sender(
name: vector<u8>,
description: vector<u8>,
url: vector<u8>,
ctx: &mut TxContext,
) {
let sender = ctx.sender();
let nft = TestnetNFT {
id: object::new(ctx),
name: string::utf8(name),
description: string::utf8(description),
url: url::new_unsafe_from_bytes(url),
};

event::emit(NFTMinted {
object_id: object::id(&nft),
creator: sender,
name: nft.name,
});

transfer::public_transfer(nft, sender);
}

/// Transfer `nft` to `recipient`
public fun transfer(nft: TestnetNFT, recipient: address, _: &mut TxContext) {
transfer::public_transfer(nft, recipient)
}

/// Update the `description` of `nft` to `new_description`
public fun update_description(
nft: &mut TestnetNFT,
new_description: vector<u8>,
_: &mut TxContext,
) {
nft.description = string::utf8(new_description)
}

/// Permanently delete `nft`
public fun burn(nft: TestnetNFT, _: &mut TxContext) {
let TestnetNFT { id, name: _, description: _, url: _ } = nft;
id.delete()
}