Endless
  • 🚀README
  • Discovery
    • 🚀Endless Web3 Genesis Cloud
    • 💎Business Model
    • 🎯Vision
    • ✈️Roadmap
    • 🪙Economics
    • 👤Team
      • Yu Xiong
      • Amit Kumar Jaiswal
      • Ned
      • 0xfun
      • Scott Trowbridge
      • Neeraj Sharma LLB
      • Amjad Suleman
      • Binu Paul
      • Eduard Romulus GOEAN
    • ❤️Developer Community
  • Endless Chain
    • Tech Docs
      • Account Address Format
      • Endless Account
      • Endless Coin(EDS)
      • Sponsored Transaction
      • On-Chain Multisig
      • Randomness
      • Safety Transaction
      • Token Locking & Distribution
    • Start
      • Learn about Endless
        • Accounts
        • Resources
        • Events
        • Transactions and States
        • Gas and Storage Fees
        • Computing Transaction Gas
        • Blocks
        • Staking
          • Delegated Staking
        • Governance
        • Endless Blockchain Deep Dive
          • Validator Nodes Overview
          • Fullnodes Overview
          • Node Networks and Synchronization
        • Move - A Web3 Language and Runtime
      • Explore Endless
      • Latest Endless Releases
      • Networks
    • Build
      • Tutorials
        • Your First Transaction
        • Your First Fungible Asset
        • Your First NFT
        • Your First Move Module
        • Your First Multisig
      • Learn the Move Language
        • The Move Book
          • Getting Started
            • Introduction
            • Modules and Scripts
          • Primitive Types
            • Move Tutorial
            • Integers
            • Bool
            • Address
            • Vector
            • Signer
            • References
            • Tuples and Unit
          • Basic Concepts
            • Local Variables and Scope
            • Equality
            • Abort and Assert
            • Conditionals
            • While, For, and Loop
            • Functions
            • Structs and Resources
            • Constants
            • Generics
            • Abilities
            • Uses and Aliases
            • Friends
            • Packages
            • Package Upgrades
            • Unit Tests
          • Global Storage
            • Global Storage - Structure
            • Global Storage - Operators
          • Reference
            • Libraries
            • Move Coding Conventions
        • Advanced Move Guides
          • Objects
            • Creating Objects
            • Configuring objects
            • Using objects
          • Move Scripts
            • Writing Move Scripts
            • Compiling Move Scripts
            • Running Move Scripts
            • Move Scripts Tutorial
          • Resource Accounts
          • Modules on Endless
          • Cryptography
          • Gas Profiling
          • Security
      • Endless Standards
        • Object
        • Endless Fungible Asset Standard
        • Endless Digital Asset Standard
        • Endless Wallet Standard
      • Endless APIs
        • Fullnode Rest API
        • Indexer Restful API
          • Indexer Installation
        • GRPC Transaction Stream
          • Running Locally
          • Custom Processors
            • End-to-End Tutorial
            • Parsing Transactions
          • Self-Hosted Transaction Stream Service
      • Endless SDKs
        • TypeScript SDK
          • Account
          • SDK Configuration
          • Fetch data from chain
          • Transaction Builder
          • HTTP Client
          • Move Types
          • Testing
          • Typescript
        • Rust SDK
        • Go SDK
      • Endless CLI
        • Install the Endless CLI
          • Install On Mac
          • Install On Alibaba Cloud
          • Install On Linux
          • Install On Windows
        • CLI Configuration
        • Use Endless CLI
          • Working With Move Contracts
            • Arguments in JSON Tutorial
          • Trying Things On-Chain
            • Look Up On-Chain Account Info
            • Create Test Accounts
          • Running A Local Network
            • Running a Public Network
          • Managing a Network Node
      • Integrate with Endless
        • Endless Token Overview
        • Application Integration Guide
      • Endless VSCode extension
      • Advanced Builder Guides
        • Develop Locally
          • Running a Local Network
          • Run a Localnet with Validator
    • Nodes
      • Learn about Nodes
      • Run a Validator and VFN
        • Node Requirements
        • Deploy Nodes
          • Using Docker
          • Using AWS
          • Using Azure
          • Using GCP
        • Connect Nodes
          • Connect to a Network
        • Verify Nodes
          • Node Health
          • Validator Leaderboard
      • Run a Public Fullnode
        • PFN Requirements
        • Deploy a PFN
          • Using Pre-compiled Binary
          • Using Docker
          • Using GCP 🚧 (under_construction)
        • Verify a PFN
        • Modify a PFN
          • Upgrade your PFN
          • Generate a PFN Identity
          • Customize PFN Networks
      • Bootstrap a Node
        • Bootstrap from a Snapshot
        • Bootstrap from a Backup
      • Configure a Node
        • State Synchronization
        • Data Pruning
        • Telemetry
        • Locating Node Files
          • Files For Mainnet
          • Files For Testnet
          • Files For Devnet
      • Monitor a Node
        • Node Inspection Service
        • Important Node Metrics
        • Node Health Checker
    • Reference
      • Endless Error Codes
      • Move Reference Documentation
      • Endless Glossary
    • FAQs
  • Endless Bridge
    • Intro to Endless Bridge
    • How to use bridge
    • Liquidity Management
    • Faucet
    • Developer Integration
      • Contract Integration
        • Message Contract
        • Execute Contract
      • Server-Side Integration
        • Message Sender
        • Example of Message Listener Service (Rust)
        • Example of Token Cross-Chain (JS)
  • Endless Wallet
    • User Guide
    • Basic Tutorial
    • FAQs
    • MultiAccount
    • SDK
      • Functions
      • Events
  • GameFi
    • Intro
    • GameFi & Endless
  • Endless Modules
    • Stacks
    • Storage
    • Module List
  • Endless Ecosystem
    • Intro
    • Show Cases
    • App Demo
  • Whitepaper
  • Endless SCAN
    • User Guide
  • MULTI-SIGNATURE
    • Multi-Signature User Guide
  • Regulations
    • Privacy Policy
    • Terms of Service
    • Funding Terms - Disclaimer
Powered by GitBook
On this page
  • Endless Fungible Asset Standard
  • Structures
  • Metadata Object
  • Fungible Asset and Fungible Store
  • References
  • Creators
  • Users
  • Primitives
  • Events
  • Primary and secondary FungibleStores
  • How to enable Primary FungibleStore?
  • Primitives
  • Get Primary FungibleStore
  • Manually Create Primary FungibleStore
  • Check Balance and Frozen Status
  • Withdraw
  • Deposit
  • Transfer
  • Secondary FungibleStore
  • Ownership of FungibleStore
Export as PDF
  1. Endless Chain
  2. Build
  3. Endless Standards

Endless Fungible Asset Standard

PreviousObjectNextEndless Digital Asset Standard

Last updated 4 months ago

Endless Fungible Asset Standard

The Endless Fungible Asset Standard (also known as Fungible Asset or FA) is a core framework component within Endless that enables the tokenization of various assets, including commodities, real estate, and financial instruments. This standard facilitates the creation of decentralized financial applications.

The tokenization of securities and commodities provides fractional ownership, making these markets more accessible to a broader range of investors. Fungible tokens can also represent real estate ownership, enabling fractional ownership and providing liquidity to a traditionally illiquid market. In-game assets such as virtual currencies and characters can be tokenized, enabling players to own and trade their assets and creating new revenue streams for game developers and players.

Besides the aforementioned features, Fungible Asset (FA) is a superset of cryptocurrency, as coin is just one type of Fungible Asset. The Fungible Asset framework could replace the coin module in Move.

The provides a standard, type-safe framework for defining FAs within the Endless Move ecosystem.

The standard is built upon Endless object model, so all the resources defined here are included in the object resource group and stored inside objects. There are two types of objects related to FA:

  • Object<Metadata>: include information about the FA, such as name, symbol, and decimals.

  • Object<FungibleStore>: store a specific amount of FA units. FAs are units that are interchangeable with others of the same metadata. They can be stored in objects that contain a FungibleStore resource. These store objects can be freely created, and FAs can be moved, split, and combined between them easily.

The standard also supports minting new units and burning existing units with appropriate controls.

The different objects involved - Object<Metadata> and Object<FungibleStore> objects, and their relationships to accounts are shown in the diagram below:

Structures

Metadata Object

Metadata objects with unique addresses define the type of the FAs. Even if Metadata structs of two Object<Metadata> are exactly the same, as long as their addresses are different, the FAs points to them would be different. In short, the address of the metadata object can be used as unique identifier of the FA type.

#[resource_group_member(group = endless_framework::object::ObjectGroup)]
struct Metadata has key {
    supply: Option<Supply>,
    /// Name of the fungible metadata, i.e., "USDT".
    name: String,
    /// Symbol of the fungible metadata, usually a shorter version of the name.
    /// For example, Singapore Dollar is SGD.
    symbol: String,
    /// Number of decimals used for display purposes.
    /// For example, if `decimals` equals `2`, a balance of `505` coins should
    /// be displayed to a user as `5.05` (`505 / 10 ** 2`).
    decimals: u8,
}

Fungible Asset and Fungible Store

FA allows typing by allocating an object reference that points to the metadata. Hence, a set of units of FA is represented as an amount and a reference to the metadata, as shown:

struct FungibleAsset {
    metadata: Object<Metadata>,
    amount: u128,
}

The FAs is a struct representing the type and the amount of units held. As the struct does not have either key or store abilities, it can only be passed from one function to another but must be consumed by the end of a transaction. Specifically, it must be deposited back into a fungible store at the end of the transaction, which is defined as:

#[resource_group_member(group = endless_framework::object::ObjectGroup)]
struct FungibleStore has key {
    /// The address of the base metadata object.
    metadata: Object<Metadata>,
    /// The balance of the fungible metadata.
    balance: Aggregator<u128>,
    /// FAs transferring is a common operation, this allows for freezing/unfreezing accounts.
    frozen: bool,
}

FAs are always stored in the top-level FungibleStore resource. This makes it much easier to find, analyze, and control.

The only extra field added here is frozen. if it is true, this object is frozen, i.e., deposit and withdraw are both disabled without using TransferRef in the next section.

References

Reference (ref) is the means to implement granular permission control across different standards in Endless. In different contexts, it may be called capabilities. The FA standard has three distinct refs for minting, transferring, and burning FA: MintRef, TransferRef, and BurnRef. Each ref contains a reference to the FA metadata:

struct MintRef has drop, store {
    metadata: Object<Metadata>
}

struct TransferRef has drop, store {
    metadata: Object<Metadata>
}

struct BurnRef has drop, store {
    metadata: Object<Metadata>
}

Ref owners can do the following operations depending on the refs they own:

  • MintRef offers the capability to mint new FA units.

  • TransferRef offers the capability to mutate the value of freeze in any FungibleStore of the same metadata or transfer FA by ignoring freeze.

  • BurnRef offers the capability to burn or delete FA units.

The three refs collectively act as the building blocks of various permission control systems as they have store so can be passed around and stored anywhere. Please refer to the source file for mint(), mint_to(), burn(), burn_from(), withdraw_with_ref(), deposit_with_ref(), and transfer_with_ref(): These functions are used to mint, burn, withdraw, deposit, and transfer FA using the MintRef, BurnRef, and TransferRef.

Note, these are framework functions and must be combined with business logic to produce a usable system. Developers who want to use these functions should familiarize themselves with the concepts of Endless object model and understand how the reference system enables extensible designs within Endless move.

Creators

A Fungible Asset creator can add fungibility to any non-deletable object at creation by taking &ConstructorRef with the required information to make that object a metadata of the associated FA. Then FA of this metadata can be minted and used. It is noted here that non-deletable means the can_delete field of &ConstructorRef has to be false.

public fun add_fungibility(
    constructor_ref: &ConstructorRef,
    maximum_supply: Option<u128>,
    name: String,
    symbol: String,
    decimals: u8,
    icon_uri: String,
    project_uri: String,
): Object<Metadata>

The creator has the opportunity to define a name, symbol, decimals, icon URI, project URI, and whether the total supply for the FA has a maximum. The following applies:

  • The first three of the above (name, symbol, decimals, icon_uri, project_uri) are purely metadata and have no impact for on-chain applications. Some applications may use decimals to equate a single Coin from a fractional coin.

  • Maximum supply (maximum_supply) helps check the total supply does not exceed a maximum value. However, due to the way the parallel executor works, setting the maximum supply will prevent any parallel execution of mint and burn.

Users

Users are FA holders, who can:

  • Merge two FAs of the same metadata object.

  • Extract FA partially from another.

  • Deposit to and withdraw from a FungibleStore and emit events as a result.

Primitives

At creation, the creator has the option to generate refs from the same &ConstructorRef to manage FA. These will need to be stored in global storage to be used later.

Mint

If the manager would like to mint FA, they must retrieve a reference to MintRef and call:

public fun mint(ref: &MintRef, amount: u128): FungibleAsset

This will produce a new FA of the metadata in the ref, containing a value as dictated by the amount. The supply will also be adjusted. Also, there is a mint_to function that deposits to a FungibleStore after minting as a helper.

Burn

The opposite operation of minting. Likewise, a reference to BurnRef is required and call:

public fun burn(ref: &BurnRef, fa: FungibleAsset)

This will reduce the passed-in fa to ashes and adjust the supply. There is also a burn_from function that forcibly withdraws FA from an account first and then burns the withdrawn FA as a helper.

Transfer and Freeze/Unfreeze

TransferRef has two functions:

  • Flip frozen in FungibleStore holding FA of the same metadata in the TransferRef. if it is false, the store is "frozen" that nobody can deposit to or withdraw from this store without using the ref.

  • Withdraw from or deposit to a store ignoring frozen field.

To change frozen, call:

public fun set_frozen_flag<T: key>(
    ref: &TransferRef,
    store: Object<T>,
    frozen: bool,
)

This function will emit a FrozenEvent.

To forcibly withdraw, call:

public fun withdraw_with_ref<T: key>(
    ref: &TransferRef,
    store: Object<T>,
    amount: u128
): FungibleAsset

This function will emit a WithdrawEvent.

To forcibly deposit, call:

public fun deposit_with_ref<T: key>(
    ref: &TransferRef,
    store: Object<T>,
    fa: FungibleAsset
)

This function will emit a DepositEvent.

There is a function named transfer_with_ref that combining withdraw_with_ref and deposit_with_ref together as a helper.

Merging Fungible Assets

Two FAs of the same type can be merged into a single struct that represents the accumulated value of the two independently by calling:

public fun merge(dst_fungible_asset: &mut FungibleAsset, src_fungible_asset: FungibleAsset)

After merging, dst_fungible_asset will have all the amounts.

Extracting Fungible Asset

A Fungible Asset can have amount deducted to create another FA by calling:

public fun extract(fungible_asset: &mut FungibleAsset, amount: u128): FungibleAsset

This function may produce FA with 0 amount, which is not usable. It is supposed to be merged with other FA or destroyed through destroy_zero() in the module.

Withdraw

The owner of a FungibleStore object that is not frozen can extract FA with a specified amount, by calling:

public fun withdraw<T: key>(owner: &signer, store: Object<T>, amount: u128): FungibleAsset

This function will emit a WithdrawEvent.

Deposit

Any entity can deposit FA into a FungibleStore object that is not frozen, by calling:

public fun deposit<T: key>(store: Object<T>, fa: FungibleAsset)

This function will emit a DepositEvent.

Transfer

The owner of a FungibleStore can directly transfer FA from that store to another if neither is frozen by calling:

public entry fun transfer<T: key>(sender: &signer, from: Object<T>, to: Object<T>, amount: u128)

This will emit both WithdrawEvent and DepositEvent on the respective Fungibletores.

Events

  • DepositEvent: Emitted when FAs are deposited into a store.

  • WithdrawEvent: Emitted when FAs are withdrawn from a store.

  • FrozenEvent: Emitted when the frozen status of a fungible store is updated.

struct DepositEvent has drop, store {
    amount: u128,
}
struct WithdrawEvent has drop, store {
    amount: u128,
}
struct FrozenEvent has drop, store {
    frozen: bool,
}

Primary and secondary FungibleStores

Each FungibleStore object has an owner. However, an owner may possess more than one store. When Alice sends FA to Bob, how does she determine the correct destination? Additionally, what happens if Bob doesn't have a store yet?

To address these questions, the standard has been expanded to define primary and secondary stores.

  • Each account owns only one non-deletable primary store for each type of FA, the address of which is derived in a deterministic manner from the account address and metadata object address. If primary store does not exist, it will be created if FA is going to be deposited by calling functions defined in primary_fungible_store.move

  • Secondary stores do not have deterministic addresses and are theoretically deletable. Users are able to create as many secondary stores as they want using the provided functions but there is a caveat that addressing secondary stores on-chain may need extra work.

The vast majority of users will have primary store as their only store for a specific type of FAs. It is expected that secondary stores would be useful in complicated defi or other asset management contracts that will be introduced in other tutorials using FA.

How to enable Primary FungibleStore?

To add primary store support, when creating a metadata object, instead of the aforementioned add_fungibility(), the creator has to call:

public fun create_primary_store_enabled_fungible_asset(
    constructor_ref: &ConstructorRef,
    maximum_supply: Option<u128>,
    name: String,
    symbol: String,
    decimals: u8,
    icon_uri: String,
    project_uri: String,
)

The parameters are the same as those of add_fungibility().

Primitives

Get Primary FungibleStore

To get the primary store object of a metadata object belonging to an account, call:

public fun primary_store<T: key>(owner: address, metadata: Object<T>): Object<FungibleStore>

There are other utility functions. primary_store_address returns the deterministic address the primary store, and primary_store_exists checks the existence, etc.

Manually Create Primary FungibleStore

If a primary store does not exist, any entity is able to create it by calling:

public fun create_primary_store<T: key>(owner_addr: address, metadata: Object<T>): Object<FungibleStore>

Check Balance and Frozen Status

To check the balance of a primary store, call:

public fun balance<T: key>(account: address, metadata: Object<T>): u128

To check whether the given account's primary store is frozen, call:

public fun is_frozen<T: key>(account: address, metadata: Object<T>): bool

Withdraw

An owner can withdraw FA from their primary store by calling:

public fun withdraw<T: key>(owner: &signer, metadata: Object<T>, amount: u128): FungibleAsset

Deposit

An owner can deposit FA to their primary store by calling:

public fun deposit(owner: address, fa: FungibleAsset)

Transfer

An owner can deposit FA from their primary store to that of another account by calling:

public entry fun transfer<T: key>(sender: &signer, metadata: Object<T>, recipient: address, amount: u128)

Secondary FungibleStore

Secondary stores are not commonly used by normal users but prevailing for smart contracts to manage assets owned by contracts. For example, an asset pool may have to manage multiple fungible stores for one or more types of FA. Those stores do not necessarily have to have deterministic addresses and a user may have multiple stores for a given kind of FA. So primary fungible store is not a good fit for the needs where secondary store plays a vital role.

The way to create secondary store is to create an object first and get its ConstructorRef. Then call:

public fun create_store<T: key>(
    constructor_ref: &ConstructorRef,
    metadata: Object<T>,
): Object<FungibleStore>

It will turn make the newly created object a FungibleStore. Sometimes an object can be reused as a store. For example, a metadata object can also be a store to hold some FA of its own type or a liquidity pool object can be a store of the issued liquidity pool's token/coin.

Ownership of FungibleStore

It is crucial to set correct owner of a FungibleStore object for managing the FA stored inside. By default, the owner of a newly created object is the creator whose signer is passed into the creation function. For FungibleStore objects managed by smart contract itself, usually they shouldn't have an owner out of the control of this contract. For those cases, those objects could make themselves as their owners and keep their object ExtendRef at the proper place to create signer as needed by the contract logic.

Fungible Asset module