본문으로 건너뛰기

대체 불가능 토큰 만들기

Sui에서는 모든 것이 object이다. 또한 object는 고유하고 대체 불가능하며 owned이므로 모든 것은 대체 불가능 토큰 (NFT)이다.

Sui에서 NFT를 생성하는 방식은 object 기반이 아닌 다른 블록체인과는 다르다. 해당 블록체인들은 스마트 계약과 토큰 ID 간의 매핑을 기반으로 하기 때문에 NFT를 정의하는 속성을 처리하기 위해 전용 표준이 필요하다. 예를 들어 Ethereum의 ERC-721 표준은 네트워크에서 고유한 토큰 인스턴스를 생성하기 위해 전역적으로 고유한 ID를 해당 스마트 계약 address와 연결해야 했다.

Sui에서는 모든 object가 이미 고유한 ID를 가지므로 코인과 같은 백만 개의 대체 가능한 토큰을 다루든 SuiFrens처럼 개별 특성을 가진 수천 개의 NFT를 다루든 Sui의 스마트 계약은 항상 개별 object와 상호작용한다.

Sui와 object 기반이 아닌 다른 블록체인에서 Excitable Chimp NFT 컬렉션을 생성한다고 가정해 보자. 다른 블록체인에서 Chimp의 이름 같은 속성을 얻으려면 NFT ID를 사용해 해당 정보를 얻기 위해 NFT를 생성한 스마트 계약과 상호작용해야 한다. 일반적으로 이 정보는 오프체인 storage에서 온다. Sui에서는 이름 속성이 NFT를 정의하는 object 자체의 필드가 될 수 있다. 이 구조는 정보를 원하는 스마트 계약이 object 자체에서 이름을 반환하면 되므로 NFT 메타데이터에 접근하는 과정을 훨씬 더 단순하게 만든다.

Example

다음 예제는 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를 생성하고 이를 호출을 수행한 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);
}

이 모듈에는 NFT 메타데이터를 반환하는 함수도 포함되어 있다. 앞서 사용한 가상의 예를 이어서 보면 해당 값을 얻기 위해 name 함수를 호출할 수 있다. 확인할 수 있듯이 해당 함수는 NFT 자체의 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()
}