인게임 통화
Sui Closed-Loop Token standard를 사용하면, 플레이어의 행동에 대해 지급하거나 구매할 수 있게 만들 수 있는 in-game currency(모바일 게임의 gem이나 diamond와 같은)를 생성할 수 있다. 이 token은 Sui에서 mint하지만, 플레이어는 그 token을 게임 자체의 경제 안에서만 사용할 수 있다. 이러한 type의 token은 보통 transferable하지 않으며, 일반적으로 희소성과 게임 밸런스를 유지하기 위해 미리 정한 수량으로 mint한다.
다음 예시는 일정량의 SUI를 나타내는 GEM이라는 in-game currency를 만든다. 이 예시에서 사용자는 SUI를 사용해 대체 가능한 GEM을 구매할 수 있으며, 이후 이를 게임 내 currency로 사용할 수 있다. 예시의 로직은 code comment를 따라가면 된다.
Example
Sui repo에는 in-game currency를 생성하는 기본 예시가 있다.
예시의 경제를 만드는 Move module은 gems.move source file 안에 있다.
Module examples::sword
examples::sword module은 in-game value를 가진 object 중 하나인 sword를 만든다.
이 module은 sword에 GEM 단위의 value를 할당한다(다른 가치 있는 in-game item).
이 module은 또한 sword를 받기 위해 GEM을 거래하는 로직도 제공한다.
module examples::sword {
use examples::gem::GEM;
use sui::token::{Self, Token, ActionRequest};
const EWrongAmount: u64 = 0;
const SWORD_PRICE: u64 = 10;
public struct Sword has key, store { id: UID }
public fun buy_sword(gems: Token<GEM>, ctx: &mut TxContext): (Sword, ActionRequest<GEM>) {
assert!(SWORD_PRICE == token::value(&gems), EWrongAmount);
(Sword { id: object::new(ctx) }, token::spend(gems, ctx))
}
}
Module examples::gem
examples::gem module은 in-game currency인 GEM을 만든다.
사용자는 SUI를 사용해 GEM을 구매하고, 그다음 이를 sword와 거래할 수 있다.
이 module은 서로 다른 in-game value를 나타내는 3개의 GEM 그룹(small, medium, large)을 정의한다.
constant는 각 package의 value와 각 그룹이 실제로 포함하는 GEM 수를 모두 보관한다.
module의 init 함수는 coin::create_currency를 사용해 GEM을 만든다.
module이 게시될 때만 실행되는 init 함수는 in-game currency에 대한 policy를 설정하고, coin의 metadata를 freeze하며, policy capability를 package publisher에게 transfer하기도 한다.
#[allow(deprecated_usage)]
fun init(otw: GEM, ctx: &mut TxContext) {
let (treasury_cap, coin_metadata) = coin::create_currency(
otw,
0,
b"GEM",
b"Capy Gems", // otw, decimal, symbol, name
b"In-game currency for Capy Miners",
none(), // description, url
ctx,
);
let (mut policy, cap) = token::new_policy(&treasury_cap, ctx);
token::allow(&mut policy, &cap, buy_action(), ctx);
token::allow(&mut policy, &cap, token::spend_action(), ctx);
transfer::share_object(GemStore {
id: object::new(ctx),
gem_treasury: treasury_cap,
profits: balance::zero(),
});
transfer::public_freeze_object(coin_metadata);
transfer::public_transfer(cap, ctx.sender());
token::share_policy(policy);
}
module은 buy_gems 함수로 GEM 구매를 처리한다.
public fun buy_gems(
self: &mut GemStore,
payment: Coin<SUI>,
ctx: &mut TxContext,
): (Token<GEM>, ActionRequest<GEM>) {
let amount = coin::value(&payment);
let purchased = if (amount == SMALL_BUNDLE) {
SMALL_AMOUNT
} else if (amount == MEDIUM_BUNDLE) {
MEDIUM_AMOUNT
} else if (amount == LARGE_BUNDLE) {
LARGE_AMOUNT
} else {
abort EUnknownAmount
};
coin::put(&mut self.profits, payment);
let gems = token::mint(&mut self.gem_treasury, purchased, ctx);
let req = token::new_request(buy_action(), purchased, none(), none(), ctx);
(gems, req)
}
complete module의 표시를 제어하려면 다음 toggle을 사용한다.
examples::gem module in gems.move
examples::gem module in gems.movemodule examples::gem {
use std::option::none;
use std::string::{Self, String};
use sui::balance::{Self, Balance};
use sui::coin::{Self, Coin, TreasuryCap};
use sui::sui::SUI;
use sui::token::{Self, Token, ActionRequest};
use sui::tx_context::sender;
const EUnknownAmount: u64 = 0;
const SMALL_BUNDLE: u64 = 10_000_000_000;
const SMALL_AMOUNT: u64 = 100;
const MEDIUM_BUNDLE: u64 = 100_000_000_000;
const MEDIUM_AMOUNT: u64 = 5_000;
const LARGE_BUNDLE: u64 = 1_000_000_000_000;
const LARGE_AMOUNT: u64 = 100_000;
public struct GemStore has key {
id: UID,
profits: Balance<SUI>,
gem_treasury: TreasuryCap<GEM>,
}
public struct GEM has drop {}
#[allow(deprecated_usage)]
fun init(otw: GEM, ctx: &mut TxContext) {
let (treasury_cap, coin_metadata) = coin::create_currency(
otw,
0,
b"GEM",
b"Capy Gems", // otw, decimal, symbol, name
b"In-game currency for Capy Miners",
none(), // description, url
ctx,
);
let (mut policy, cap) = token::new_policy(&treasury_cap, ctx);
token::allow(&mut policy, &cap, buy_action(), ctx);
token::allow(&mut policy, &cap, token::spend_action(), ctx);
transfer::share_object(GemStore {
id: object::new(ctx),
gem_treasury: treasury_cap,
profits: balance::zero(),
});
transfer::public_freeze_object(coin_metadata);
transfer::public_transfer(cap, ctx.sender());
token::share_policy(policy);
}
public fun buy_gems(
self: &mut GemStore,
payment: Coin<SUI>,
ctx: &mut TxContext,
): (Token<GEM>, ActionRequest<GEM>) {
let amount = coin::value(&payment);
let purchased = if (amount == SMALL_BUNDLE) {
SMALL_AMOUNT
} else if (amount == MEDIUM_BUNDLE) {
MEDIUM_AMOUNT
} else if (amount == LARGE_BUNDLE) {
LARGE_AMOUNT
} else {
abort EUnknownAmount
};
coin::put(&mut self.profits, payment);
let gems = token::mint(&mut self.gem_treasury, purchased, ctx);
let req = token::new_request(buy_action(), purchased, none(), none(), ctx);
(gems, req)
}
public fun buy_action(): String { string::utf8(b"buy") }
}
Complete code
comment를 포함한 이 예시의 complete source를 표시하려면 toggle을 사용하거나, GitHub에서 project source를 보려면 Related links 섹션의 링크를 사용한다.
gems.move
gems.move/// This is a simple example of a permissionless module for an imaginary game
/// that sells swords for Gems. Gems are an in-game currency that can be bought
/// with SUI.
module examples::sword {
use examples::gem::GEM;
use sui::token::{Self, Token, ActionRequest};
/// Trying to purchase a sword with an incorrect amount.
const EWrongAmount: u64 = 0;
/// The price of a sword in Gems.
const SWORD_PRICE: u64 = 10;
/// A game item that can be purchased with Gems.
public struct Sword has key, store { id: UID }
/// Purchase a sword with Gems.
public fun buy_sword(gems: Token<GEM>, ctx: &mut TxContext): (Sword, ActionRequest<GEM>) {
assert!(SWORD_PRICE == token::value(&gems), EWrongAmount);
(Sword { id: object::new(ctx) }, token::spend(gems, ctx))
}
}
/// Module that defines the in-game currency: GEMs which can be purchased with
/// SUI and used to buy swords (in the `sword` module).
module examples::gem {
use std::option::none;
use std::string::{Self, String};
use sui::balance::{Self, Balance};
use sui::coin::{Self, Coin, TreasuryCap};
use sui::sui::SUI;
use sui::token::{Self, Token, ActionRequest};
use sui::tx_context::sender;
/// Trying to purchase Gems with an unexpected amount.
const EUnknownAmount: u64 = 0;
/// 10 SUI is the price of a small bundle of Gems.
const SMALL_BUNDLE: u64 = 10_000_000_000;
const SMALL_AMOUNT: u64 = 100;
/// 100 SUI is the price of a medium bundle of Gems.
const MEDIUM_BUNDLE: u64 = 100_000_000_000;
const MEDIUM_AMOUNT: u64 = 5_000;
/// 1000 SUI is the price of a large bundle of Gems.
/// This is the best deal.
const LARGE_BUNDLE: u64 = 1_000_000_000_000;
const LARGE_AMOUNT: u64 = 100_000;
/// Gems can be purchased through the `Store`.
public struct GemStore has key {
id: UID,
/// Profits from selling Gems.
profits: Balance<SUI>,
/// The Treasury Cap for the in-game currency.
gem_treasury: TreasuryCap<GEM>,
}
/// The OTW to create the in-game currency.
public struct GEM has drop {}
// In the module initializer we create the in-game currency and define the
// rules for different types of actions.
#[allow(deprecated_usage)]
fun init(otw: GEM, ctx: &mut TxContext) {
let (treasury_cap, coin_metadata) = coin::create_currency(
otw,
0,
b"GEM",
b"Capy Gems", // otw, decimal, symbol, name
b"In-game currency for Capy Miners",
none(), // description, url
ctx,
);
// create a `TokenPolicy` for GEMs
let (mut policy, cap) = token::new_policy(&treasury_cap, ctx);
token::allow(&mut policy, &cap, buy_action(), ctx);
token::allow(&mut policy, &cap, token::spend_action(), ctx);
// create and share the GemStore
transfer::share_object(GemStore {
id: object::new(ctx),
gem_treasury: treasury_cap,
profits: balance::zero(),
});
// deal with `TokenPolicy`, `CoinMetadata` and `TokenPolicyCap`
transfer::public_freeze_object(coin_metadata);
transfer::public_transfer(cap, ctx.sender());
token::share_policy(policy);
}
/// Purchase Gems from the GemStore. Very silly value matching against module
/// constants...
public fun buy_gems(
self: &mut GemStore,
payment: Coin<SUI>,
ctx: &mut TxContext,
): (Token<GEM>, ActionRequest<GEM>) {
let amount = coin::value(&payment);
let purchased = if (amount == SMALL_BUNDLE) {
SMALL_AMOUNT
} else if (amount == MEDIUM_BUNDLE) {
MEDIUM_AMOUNT
} else if (amount == LARGE_BUNDLE) {
LARGE_AMOUNT
} else {
abort EUnknownAmount
};
coin::put(&mut self.profits, payment);
// create custom request and mint some Gems
let gems = token::mint(&mut self.gem_treasury, purchased, ctx);
let req = token::new_request(buy_action(), purchased, none(), none(), ctx);
(gems, req)
}
/// The name of the `buy` action in the `GemStore`.
public fun buy_action(): String { string::utf8(b"buy") }
}