본문으로 건너뛰기

대체 불가능 토큰 만들기

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

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

사용 사례

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

  • 디지털 수집품: 고유한 attribute와 provenance를 가진 art, trading card, 인게임 character 같은 고유 item. SuiFrens는 Sui의 collectible NFT collection 예시이다.
  • 게임 자산: weapon, skin, land parcel 같은 인게임 item. 각 자산은 고유한 속성을 가지며 호환되는 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가 자산을 판매하지 않고 monetization하려는 사용 사례를 지원한다.
  • 암호화된 미디어: NFT는 암호화된 콘텐츠를 래핑하여 소유자만 underlying 자산에 접근할 수 있게 할 수 있으며, 이는 freemium 및 try-before-you-buy 모델을 가능하게 한다.

NFT와 토큰화된 자산

Sui에서 NFT와 토큰화된 자산은 모두 객체이다. 프로토콜은 둘을 구분하지 않는다. 차이는 스마트 계약을 설계하는 방식에 있다.

NFT는 일반적으로 supply가 1이고 name, description, image URL처럼 해당 인스턴스에 특화된 메타데이터를 가진 객체이다. 토큰화된 자산은 supply가 1보다 크고 모든 fraction에서 메타데이터를 공유하는 fractional ownership용으로 설계된다. 토큰화된 자산은 split 또는 병합할 수도 있지만 NFT는 이를 위해 설계되지 않는다.

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

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

예시

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

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

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

#[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);
}

모듈에는 NFT 메타데이터를 반환하는 함수가 포함되어 있다. name 함수를 호출해 해당 값을 가져올 수 있다. 이 함수는 NFT 객체에서 name 필드 값을 직접 반환한다.

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()
}