본문으로 건너뛰기

Object Display V1

Sui Object Display standard는 타입에 대한 오프체인 표현(display)의 온체인 관리를 가능하게 하는 template engine이다. 이를 통해 object의 데이터를 template string에 대입할 수 있다. 이 standard는 설정할 수 있는 field를 제한하지 않는다. {property} syntax를 사용하여 모든 object property에 액세스한 다음 template string의 일부로 삽입할 수 있다.

소유한 Publisher object를 사용하여 타입에 대한 sui::display를 설정한다. Publisher object에 대한 자세한 내용은 Sui Move by ExamplePublisher 주제를 참조한다.

Sui Move에서 Display<T>는 타입 T에 대한 named template 집합을 지정하는 object를 나타낸다. 예를 들어, 타입 0x2::capy::Capy의 경우 display syntax는: Display<0x2::capy::Capy>이다.

Sui 풀 노드는 Display 정의와 매칭하여 타입 T의 모든 object를 처리하고, 쿼리에서 { showDisplay: true } 설정으로 object를 쿼리할 때 처리된 결과를 반환한다.

Display properties

제안되는 기본 property 집합은 다음을 포함한다:

  • name: object의 name이다. 사용자가 object를 볼 때 name이 표시된다.
  • description: object에 대한 description이다. 사용자가 object를 볼 때 description이 표시된다.
  • link: 애플리케이션에서 사용할 object에 대한 link이다.
  • image_url: object에 대한 이미지가 있는 URL 또는 blob이다.
  • thumbnail_url: wallet, explorer, 그리고 다른 product에서 preview로 사용할 더 작은 이미지에 대한 URL이다.
  • project_url: object 또는 creator와 연관된 웹사이트에 대한 link이다.
  • creator: object creator를 나타내는 string이다.

Example: Sui Hero module

다음 code sample은 타입 Heroname, id, image_url property에 따라 예시 Hero 모듈의 Display가 어떻게 달라지는지 보여준다. 다음은 init 함수가 정의하는 template을 나타낸다:

{
"name": "{name}",
"link": "https://sui-heroes.io/hero/{id}",
"image_url": "https://sui-heroes.io/hero/{image_url}",
"description": "A true Hero of the Sui ecosystem!",
"project_url": "https://sui-heroes.io",
"creator": "Unknown Sui Fan"
}
/// 무제한 "Sui Hero" collection의 예시로, 누구나
/// 자신의 Hero를 mint할 수 있다. `Publisher`를 초기화하는 방법과
/// 이를 사용해 `Display<Hero>` object를 얻는 방법, 즉 ecosystem의
/// type을 설명하는 방식을 보여 준다.
module examples::my_hero;

use std::string::String;

// creator bundle: 이 두 package는 자주 함께 사용된다.
use sui::package;
use sui::display;

/// Hero - 뛰어난 디지털 아트 collection이다.
public struct Hero has key, store {
id: UID,
name: String,
image_url: String,
}

/// module용 One-Time-Witness이다.
public struct MY_HERO has drop {}

/// module initializer에서 `Publisher` object를 claim한 다음
/// `Display`를 생성한다. `Display`는 field 집합으로 초기화되고
/// (나중에 수정할 수 있지만) `update_version` 호출을 통해
/// 게시된다.
///
/// 키와 값은 initializer에서 설정하지만
/// `Publisher` object가 생성되었다면 게시 후에도 설정할 수 있다.
fun init(otw: MY_HERO, ctx: &mut TxContext) {
let keys = vector[
b"name".to_string(),
b"link".to_string(),
b"image_url".to_string(),
b"description".to_string(),
b"project_url".to_string(),
b"creator".to_string(),
];

let values = vector[
// `name`에는 `Hero.name` property를 사용할 수 있다
b"{name}".to_string(),
// `link`에는 `id` property를 사용해 URL을 구성할 수 있다
b"https://sui-heroes.io/hero/{id}".to_string(),
// `image_url`에는 IPFS template + `image_url` property를 사용한다.
b"ipfs://{image_url}".to_string(),
// description은 모든 `Hero` object에서 고정된다.
b"A true Hero of the Sui ecosystem!".to_string(),
// Project URL은 보통 고정된다
b"https://sui-heroes.io".to_string(),
// creator field는 어떤 값이든 될 수 있다
b"Unknown Sui Fan".to_string(),
];

// package에 대한 `Publisher`를 claim한다!
let publisher = package::claim(otw, ctx);

// `Hero` type에 대한 새 `Display` object를 가져온다.
let mut display = display::new_with_fields<Hero>(
&publisher, keys, values, ctx
);

// 변경 사항을 적용하려면 `Display`의 첫 번째 version을 commit한다.
display.update_version();

transfer::public_transfer(publisher, ctx.sender());
transfer::public_transfer(display, ctx.sender());
}

/// 누구나 자신의 `Hero`를 mint할 수 있다!
public fun mint(name: String, image_url: String, ctx: &mut TxContext): Hero {
Hero {
id: object::new(ctx),
name,
image_url
}
}

Work with Object Display

display::new<T> 호출은 사용자 정의 함수나 모듈 초기화 함수에서, 또는 programmable transaction의 일부로 Display를 생성한다. 다음 code sample은 Display를 생성하는 방법을 보여준다:

module sui::display;

/// `T`에 대한 새 Display object를 가져온다.
/// Publisher는 `T`의 publisher여야 하며, `from_package`
/// 검사를 수행한다.
public fun new<T>(pub: &Publisher): Display<T> { /* ... */ }

Display를 생성한 후에는 수정할 수 있다. 다음 code sample은 Display를 수정하는 방법을 보여준다:

module sui::display;

/// 여러 field를 한 번에 설정한다
public fun add_multiple(
self: &mut Display,
keys: vector<String>,
values: vector<String>
) { /* ... */ }

/// 단일 field를 편집한다
public fun edit(self: &mut Display, key: String, value: String) { /* ... */ }

/// Display에서 키를 제거한다
public fun remove(self: &mut Display, key: String ) { /* ... */ }

다음으로, update_version 호출은 변경 사항을 적용하고 event를 방출하여 T에 대한 Display를 설정한다. 풀 노드는 event를 수신하고 event의 데이터를 사용하여 타입에 대한 template을 검색한다.

다음 code sample은 update_version 호출을 사용하는 방법을 보여준다:

module sui::display;

/// Display의 version을 업데이트하고 event를 방출한다
public fun update_version(self: &mut Display) { /* ... */ }

Sui utility objects

Sui에서 utility object는 capability에 대한 권한 부여를 가능하게 한다. 거의 모든 모듈에는 필요한 capability로만 액세스할 수 있는 feature가 있다. generic 모듈은 marketplace와 같이 애플리케이션당 하나의 capability를 허용한다. 일부 capability는 온체인에서 shared object의 ownership을 표시하거나, 다른 account의 shared data에 액세스한다. capability를 사용하면 사용자 인터페이스 구현을 용이하게 하기 위해 object에 대한 의미 있는 description을 제공하는 것이 중요하다. 이는 object가 비슷할 때 잘못된 object를 실수로 전송하는 것을 방지하는 데 도움이 된다. 또한 사용자가 보는 item에 대한 user-friendly description을 제공한다.

다음 예시는 capy capability를 생성하는 방법을 보여준다:

module capy::utility;

/// Capy Manager에 새 genes를 추가하고
/// Capy Market을 관리할 권한을 부여하는 capability
public struct CapyManagerCap has key, store { id: UID }

Typical objects with data duplication

in-game item의 일반적인 경우는 어떤 기준에 따라 그룹화된 많은 수의 유사한 object를 갖는 것이다. 크기와 mint 및 update 비용을 최적화하는 것이 중요하다. 일반적으로, game은 group 또는 item criteria당 단일 source image 또는 URL을 사용한다. 모든 object 내부에 source image를 저장하는 것은 최적이 아니다. 어떤 경우에는 game이 허용할 때 또는 사용자가 in-game item을 구매할 때 사용자가 in-game item을 mint한다. 이를 가능하게 하려면, 일부 IPFS/Arweave metadata를 미리 생성하고 저장해야 한다. 이를 위해서는 일반적으로 item의 in-game property와 관련이 없는 additional logic이 필요하다.

다음 예시는 Capy를 생성하는 방법을 보여준다:

module capy::capy_items;

use std::string::String;

/// 착용 가능한 Capy item이다. 일부 item은
/// 무제한 공급될 수 있으며 이름이 같으면 동일하다.
public struct CapyItem has key, store {
id: UID,
name: String
}

Unique objects with dynamic representation

Sui Capy는 dynamic image generation을 사용한다. Capy가 태어나면, color 또는 pattern과 같은 attribute가 Capy의 appearance를 결정한다. 사용자가 Capy에 item을 장착하면, Capy의 appearance가 변경된다. 사용자가 Capy에 여러 item을 장착하면, item 조합에 대한 bonus 기회가 있다. 이를 구현하기 위해, Capys game API service는 user-initiated change에 대한 응답으로 image를 refresh한다. Capy의 URL은 capy.id가 있는 template이다. 그러나 diverse population으로 인해 Capy object의 다른 field뿐만 아니라 전체 URL을 저장하면 사용자에게 excess storage 및 increased gas fee 비용을 초래한다.

다음 예시는 dynamic image generation을 구현하는 방법을 보여준다:

module capy::capy;

/// Capy로, 다양한 gene 조합을 가진 object이다.
/// 동적으로 생성되며 images에는 dynamic SVG
/// generation을 사용한다.
public struct Capy has key, store {
id: UID,
genes: vector<u8>
}

Objects with unique static content

이것은 가장 간단한 시나리오이며 object가 모든 것을 자체적으로 나타낸다. 이러한 종류의 object에 metadata standard를 적용하는 것은 매우 쉬우며, 특히 object가 영원히 immutable로 유지되는 경우 더욱 그렇다. 그러나 metadata standard가 발전하고 일부 ecosystem project가 일부 property에 new feature를 추가하는 경우, 이 object는 항상 원래 형태로 유지되며 backward-compatible change가 필요할 수 있다.

module sui::devnet_nft;

use std::string::String;

/// 정적 data를 가진 Collectible이다. URL, name, description은
/// mint event에서 한 번만 설정된다
public struct DevNetNFT has key, store {
id: UID,
name: String,
description: String,
url: String,
}