Skip to content

Commit

Permalink
[docs] Coin standard (new) and token standard (updates) (#16046)
Browse files Browse the repository at this point in the history
## Description 

Creates a Coin standard page. Includes edits for closed-loop token and
other content edits to better align terminology. Also includes plug-in
for Docusaurus.

## Test Plan 

How did you test the new or updated feature?

---
If your changes are not user-facing and do not break anything, you can
skip the following section. Otherwise, please briefly describe what has
changed under the Release Notes section.

### Type of Change (Check all that apply)

- [ ] protocol change
- [ ] user-visible impact
- [ ] breaking change for a client SDKs
- [ ] breaking change for FNs (FN binary must upgrade)
- [ ] breaking change for validators or node operators (must upgrade
binaries)
- [ ] breaking change for on-chain data layout
- [ ] necessitate either a data wipe or data migration

### Release notes
  • Loading branch information
ronny-mysten authored Feb 14, 2024
1 parent cbf3c9c commit 1f84041
Show file tree
Hide file tree
Showing 27 changed files with 905 additions and 337 deletions.
2 changes: 1 addition & 1 deletion crates/sui-framework/docs/sui-framework/coin.md
Original file line number Diff line number Diff line change
Expand Up @@ -882,7 +882,7 @@ with the coin as input objects.

## Function `mint`

Create a coin worth <code>value</code>. and increase the total supply
Create a coin worth <code>value</code> and increase the total supply
in <code>cap</code> accordingly.


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ module sui::coin {
(treasury_cap, deny_cap, metadata)
}

/// Create a coin worth `value`. and increase the total supply
/// Create a coin worth `value` and increase the total supply
/// in `cap` accordingly.
public fun mint<T>(
cap: &mut TreasuryCap<T>, value: u64, ctx: &mut TxContext,
Expand Down
2 changes: 1 addition & 1 deletion docs/content/concepts.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ Sui is different than other blockchains. The concepts explored in this section p
## Tokenomics

<Cards>
<Card title="SUI token" href="concepts/tokenomics/sui-token" />
<Card title="SUI token" href="concepts/tokenomics/sui-coin" />
<Card title="Gas in Sui" href="concepts/tokenomics/gas-in-sui" />
<Card title="Storage fund" href="concepts/tokenomics/storage-fund" />
</Cards>
2 changes: 1 addition & 1 deletion docs/content/concepts/components.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ There is a cost associated with everything, and blockchain transactions are no e
To learn more about the tokenomics of Sui, see the following topics:

- [Sui Tokenomics](./tokenomics.mdx)
- [Sui Token](./tokenomics/sui-token.mdx)
- [Sui Token](./tokenomics/sui-coin.mdx)
- [Gas in Sui](./tokenomics/gas-in-sui.mdx)
- [Sui Storage Fund](./tokenomics/storage-fund.mdx)

Expand Down
2 changes: 1 addition & 1 deletion docs/content/concepts/tokenomics.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ Three main types of participants characterize the Sui economy:

The Sui economy is composed of five core components:

- **[SUI](./tokenomics/sui-token.mdx):** The SUI token is the Sui platform native asset.
- **[SUI](./tokenomics/sui-coin.mdx):** The SUI token is the Sui platform native asset.
- **[Gas fees](./tokenomics/gas-in-sui.mdx):** Gas fees are charged on all network operations and used to reward participants of the proof-of-stake mechanism and prevent spam and denial-of-service attacks.
- **[Storage fund](./tokenomics/storage-fund.mdx):** The Sui storage fund is used to shift stake rewards across time and compensate future validators for storage costs of previously stored on-chain data.
- **[Proof-of-stake](./tokenomics/proof-of-stake.mdx):** The delegated proof-of-stake mechanism is used to select, incentivize, and reward honest behavior by Sui Validators and the SUI owners that stake with them.
Expand Down
17 changes: 17 additions & 0 deletions docs/content/concepts/tokenomics/sui-coin.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
title: SUI Coin
description: The native asset on Sui is called SUI. The coin uses the capitalized version of SUI to distinguish the coin from the Sui network.
---

The native asset on Sui is called SUI. The coin uses the capitalized version of SUI to distinguish the coin from the Sui network.

The total supply of SUI is capped at **10,000,000,000** (ten billion coins). A share of SUI total supply became liquid at Mainnet launch, with the remaining coins vesting over the coming years, or distributed as future stake reward subsidies.

The SUI coin serves four purposes on the Sui network:

- You can stake SUI to participate in the proof-of-stake mechanism.
- SUI is the asset denomination needed to pay the gas fees required to execute and store transactions or other operations on the Sui network.
- You can use SUI as a versatile and liquid asset for various applications, including the standard features of money - a unit of account, a medium of exchange, or a store of value - and more complex functionality smart contracts enable, interoperability, and composability across the Sui ecosystem.
- SUI coins play an important role in governance by acting as a right to participate in on-chain voting on issues such as protocol upgrades.

There is a finite supply of SUI. The balance must support all economic activities to scale as more and more people migrate to the Sui platform. In addition, the presence of a storage fund creates important monetary dynamics, in that higher on-chain data requirements translate into a larger storage fund, reducing the amount of SUI in circulation.
17 changes: 0 additions & 17 deletions docs/content/concepts/tokenomics/sui-token.mdx

This file was deleted.

120 changes: 15 additions & 105 deletions docs/content/guides/developer/sui-101/create-coin.mdx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
---
title: Create a Coin
title: Create Coins and Tokens
---

Coins and tokens on Sui are similar. In practice, the terms are used interchangeably, but there are some differences in their implementation. You can learn about these differences in the respective standard documentation, [Closed-Loop Token](../../../standards/closed-loop-token.mdx) and [Coin](../../../standards/coin.mdx).

Publishing a coin on Sui is nearly as straightforward as publishing a new type. The main difference is the requirement of a [one-time witness](/concepts/sui-move-concepts/one-time-witness.mdx) when creating a coin.

```move
Expand All @@ -26,7 +28,7 @@ module examples::mycoin {
}
```

The `Coin<T>` is a generic implementation of a coin on Sui. The owner of the `TreasuryCap` gets control over the minting and burning of coins. Further transactions can be sent directly to the `sui::coin::Coin` with `TreasuryCap` object as authorization.
The `Coin<T>` is a generic implementation of a coin on Sui. Access to the `TreasuryCap` provides control over the minting and burning of coins. Further transactions can be sent directly to the `sui::coin::Coin` with `TreasuryCap` object as authorization.

Extending the example further, add a `mint` function to the module. Use the `mint` function of the `Coin` module to create (mint) a coin and then transfer it to an address.

Expand Down Expand Up @@ -78,113 +80,21 @@ If you need the ability to deny specific addresses from having access to your co

Behind the scenes, `create_regulated_currency` uses the `create_currency` function to create the coin, but also produces a `DenyCap` object that allows its bearer to control access to the coin's deny list in a `DenyList` object. Consequently, the way to create a coin using `create_regulated_currency` is similar to the previous example, with the addition of a transfer of the `DenyCap` object to the module publisher.

```move title="regcoin.move"
module examples::regcoin {
use std::option;
use sui::coin;
use sui::transfer;
use sui::tx_context::{Self, TxContext};
struct REGCOIN has drop {}
fun init(witness: REGCOIN, ctx: &mut TxContext) {
let (treasury, deny_cap, metadata) = coin::create_regulated_currency(witness, 6, b"REGCOIN", b"", b"", option::none(), ctx);
transfer::public_freeze_object(metadata);
transfer::public_transfer(treasury, tx_context::sender(ctx));
transfer::public_transfer(deny_cap, tx_context::sender(ctx))
}
}
```

When you deploy the previous module using `sui client publish`, the console responds with transaction effects, including the creation of the following objects:

```shell
...

Object Changes

Created Objects:

ObjectID: <OBJECT-ID>
Sender: <SENDER-ADDR>
Owner: Immutable
ObjectType: 0x2::coin::CoinMetadata<<PACKAGE-ID>::regcoin::REGCOIN>
Version: <VERSION-NUMBER>
Digest: <DIGEST-HASH>
ObjectID: <OBJECT-ID>
Sender: <SENDER-ADDR>
Owner: Account Address ( <PUBLISHER-ADDRESS )
ObjectType: 0x2::package::UpgradeCap
Version: <VERSION-NUMBER>
Digest: <DIGEST-HASH>
ObjectID: <OBJECT-ID>
Sender: <SENDER-ADDR>
Owner: Immutable
ObjectType: 0x2::coin::RegulatedCoinMetadata<<PACKAGE-ID>::regcoin::REGCOIN>
Version: <VERSION-NUMBER>
Digest: <DIGEST-HASH>
ObjectID: <OBJECT-ID>
Sender: <SENDER-ADDR>
Owner: Account Address ( <PUBLISHER-ADDRESS )
ObjectType: 0x2::coin::DenyCap<<PACKAGE-ID>::regcoin::REGCOIN>
Version: <VERSION-NUMBER>
Digest: <DIGEST-HASH>
ObjectID: <OBJECT-ID>
Sender: <SENDER-ADDR>
Owner: Account Address ( <PUBLISHER-ADDRESS )
ObjectType: 0x2::coin::TreasuryCap<PACKAGE-ID>::regcoin::REGCOIN>
Version: <VERSION-NUMBER>
Digest: <DIGEST-HASH>
...
```
As you might have noticed, the publish action creates a `RegulatedCoinMetadata` object along with the standard `CoinMetadata` object. You don't need to explicitly call the `freeze_object` on the `RegulatedCoinMetadata` object, however, because `create_regulated_currency` automatically performs this action.
The output also shows the three objects that the publisher now owns: `UpgradeCap` for [package upgrades](../../../concepts/sui-move-concepts/packages/upgrade.mdx), `TreasuryCap` for minting or burning coins, and the `DenyCap` for adding or removing addresses to or from the deny list for this coin.
### Manipulate deny list
For the ability to manipulate the addresses assigned to the deny list for your coin, you must add a few functions to the previous example.
```move
public fun add_addr_from_deny_list(denylist: &mut DenyList, denycap: &mut DenyCap<REGCOIN>, denyaddy: address, ctx: &mut TxContext){
coin::deny_list_add(denylist, denycap, denyaddy, ctx );
}
public fun remove_addr_from_deny_list(denylist: &mut DenyList, denycap: &mut DenyCap<REGCOIN>, denyaddy: address, ctx: &mut TxContext){
coin::deny_list_remove(denylist, denycap, denyaddy, ctx );
}
```
To use these functions, you pass the `DenyList` object (`0x403`), your `DenyCap` object ID, and the address you want to either add or remove. Using the Sui CLI, you could use `sui client call` with the required information:
## Create tokens

```shell
sui client call --function add_addr_from_deny_list --module regcoin --package <PACKAGE-ID> --args <DENY-LIST> <DENY-CAP> <ADDRESS-TO-DENY> --gas-budget <GAS-AMOUNT>
Transaction Digest: <DIGEST-HASH>
```
Tokens reuse the `TreasuryCap` defined in the `sui::coin` module and therefore have the same initialization process. The `coin::create_currency` function guarantees the uniqueness of the `TreasuryCap` and forces the creation of a `CoinMetadata` object.

The console displays the response from the network, where you can verify the DenyList object is mutated.
Coin-like functions perform the minting and burning of tokens. Both require the `TreasuryCap`:

```shell
...
MutatedObjects:
- `token::mint` - mint a token
- `token::burn` - burn a token

ObjectID: 0x0...403
Sender: <SENDER-ADDRESS>
Owner: Shared
ObjectType: 0x2::deny_list::DenyList
Version: <VERSION-NUMBER>
Digest: <DIGEST-HASH>
See [Closed-Loop Token](../../../standards/closed-loop-token.mdx) standard for complete details of working with tokens.

...
## Examples

```
See the following topics for examples of some common use cases for coin and token creation.

For all `Coin` functions available, see the [Sui framework documentation](https://github.com/MystenLabs/sui/blob/main/crates/sui-framework/docs/sui-framework/coin.md).
- [Regulated Coin and Deny List](./create-coin/regulated.mdx): Create a regulated coin and add or remove names from the deny list.
- [Loyalty Token](./create-coin/loyalty.mdx): Create a token to reward user loyalty.
- [In-Game Token](./create-coin/in-game-token.mdx): Create tokens that can be used only within a mobile game.
140 changes: 140 additions & 0 deletions docs/content/guides/developer/sui-101/create-coin/in-game-token.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
---
title: In-Game Currency
---

Using the Sui [Closed-Loop Token](../../../../standards/closed-loop-token.mdx) standard, you can create in-game currency (such as gems or diamonds in mobile games) that you can grant to players for their actions or make available to purchase. You mint the tokens on Sui, but players can only use the tokens within the economy of the game itself. These types of tokens are usually not transferrable and you would typically mint them in predefined amounts to maintain scarcity and game balance.

The following example creates an in-game currency called a GEM, which represents a certain number of SUI. In the example, the user can buy fungible GEMs using SUI, which can then be used as currency within the game. Use the code comments to follow the logic of the example.

```move title="sui/examples/move/token/sources/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 sui::tx_context::TxContext;
use sui::object::{Self, UID};
use sui::token::{Self, Token, ActionRequest};
use examples::gem::GEM;
/// 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.
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::sui::SUI;
use sui::transfer;
use sui::object::{Self, UID};
use sui::balance::{Self, Balance};
use sui::tx_context::{sender, TxContext};
use sui::coin::{Self, Coin, TreasuryCap};
use sui::token::{Self, Token, ActionRequest};
/// 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;
#[allow(lint(coin_field))]
/// Gems can be purchased through the `Store`.
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.
struct GEM has drop {}
// In the module initializer we create the in-game currency and define the
// rules for different types of actions.
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 (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, sender(ctx));
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") }
}
```
Loading

0 comments on commit 1f84041

Please sign in to comment.