Skip to main content

Fungible Token

Source Code

Open in Codespaces

Fungible tokens represent assets where each unit is identical and interchangeable, such as currencies, commodities, or utility tokens. On Stellar, you can create fungible tokens where each token has the same value and properties, with balances and ownership tracked through Soroban smart contracts.

Overview

The fungible module provides three different Fungible Token variants that differ in how certain features like token transfers and approvals are handled:

The module provides several implementation options to suit different use cases:

  1. Base implementation (FungibleToken with Base contract type): Suitable for most standard token use cases.
  2. AllowList extension (FungibleToken with AllowList contract type): For tokens that require an allowlist mechanism to control who can transfer tokens.
  3. BlockList extension (FungibleToken with BlockList contract type): For tokens that need to block specific addresses from transferring tokens.

These implementations share core functionality and a common interface, exposing identical contract functions as entry points. However, the extensions provide specialized behavior by overriding certain functions to implement their specific requirements.

Run the Example

First go through the Setup process to get your development environment configured, then clone the OpenZeppelin Stellar Contracts repository:

git clone https://github.com/OpenZeppelin/stellar-contracts
cd stellar-contracts

Or, skip the development environment setup and open this example in GitHub Codespaces or Code Anywhere.

To run the tests for the example, navigate to the fungible-token-interface directory, and use cargo test.

cd examples/fungible-token-interface
cargo test

Code

note

This example demonstrates how to use the OpenZeppelin Stellar Contracts library to create a fungible token. The library provides pre-built, audited implementations that follow best practices.

examples/fungible-token-interface/src/contract.rs
use soroban_sdk::{contract, contractimpl, token::Interface as TokenInterface, Address, Env};
use stellar_access::ownable::{self as ownable, Ownable};
use stellar_macros::{default_impl, only_owner, when_not_paused};
use stellar_pausable::pausable::{self as pausable, Pausable};
use stellar_tokens::fungible::{Base, FungibleToken};

#[contract]
pub struct FungibleTokenContract;

#[contractimpl]
impl FungibleTokenContract {
pub fn __constructor(e: &Env, owner: Address) {
// Set token metadata
Base::set_metadata(
e,
18, // 18 decimals
"My Token".into(),
"TKN".into(),
);

// Set the contract owner
ownable::set_owner(e, &owner);
}

#[only_owner]
pub fn mint(e: &Env, to: Address, amount: i128) {
Base::mint(e, &to, amount);
}

#[only_owner]
pub fn pause(e: &Env) {
pausable::pause(e);
}

#[only_owner]
pub fn unpause(e: &Env) {
pausable::unpause(e);
}
}

#[default_impl]
#[contractimpl]
impl FungibleToken for FungibleTokenContract {
type ContractType = Base;
}

#[default_impl]
#[contractimpl]
impl TokenInterface for FungibleTokenContract {
#[when_not_paused]
fn transfer(e: Env, from: Address, to: Address, amount: i128) {
Base::transfer(&e, &from, &to, amount);
}

#[when_not_paused]
fn transfer_from(e: Env, spender: Address, from: Address, to: Address, amount: i128) {
Base::transfer_from(&e, &spender, &from, &to, amount);
}

#[when_not_paused]
fn burn(e: Env, from: Address, amount: i128) {
Base::burn(&e, &from, amount);
}

#[when_not_paused]
fn burn_from(e: Env, spender: Address, from: Address, amount: i128) {
Base::burn_from(&e, &spender, &from, amount);
}

#[when_not_paused]
fn approve(e: Env, from: Address, spender: Address, amount: i128, expiration_ledger: u32) {
Base::approve(&e, &from, &spender, amount, expiration_ledger);
}

fn allowance(e: Env, from: Address, spender: Address) -> i128 {
Base::allowance(&e, &from, &spender)
}

fn balance(e: Env, id: Address) -> i128 {
Base::balance(&e, &id)
}

fn decimals(e: Env) -> u32 {
Base::decimals(&e)
}

fn name(e: Env) -> soroban_sdk::String {
Base::name(&e)
}

fn symbol(e: Env) -> soroban_sdk::String {
Base::symbol(&e)
}
}

#[default_impl]
#[contractimpl]
impl Ownable for FungibleTokenContract {}

#[default_impl]
#[contractimpl]
impl Pausable for FungibleTokenContract {}

Ref: https://github.com/OpenZeppelin/stellar-contracts/tree/main/examples/fungible-token-interface

How it Works

This example demonstrates how to create a fungible token using the OpenZeppelin Stellar Contracts library. The library provides pre-built, audited implementations that follow industry best practices and security standards.

The contract implements several key features:

  1. Token Interface Compliance: Fully implements the standard Token Interface for maximum interoperability
  2. Ownership Control: Uses the Ownable pattern for administrative functions
  3. Pausable Functionality: Allows the owner to pause/unpause token operations in emergency situations
  4. Secure Minting: Only the contract owner can mint new tokens

By leveraging the OpenZeppelin library, developers can focus on their specific business logic while relying on battle-tested implementations for core token functionality.

Using OpenZeppelin Library Components

The OpenZeppelin Stellar Contracts library provides modular components that can be easily composed together:

  • Base: Core fungible token functionality with standard token operations
  • Ownable: Access control pattern for administrative functions
  • Pausable: Emergency stop mechanism for token operations
  • Macros: #[only_owner] and #[when_not_paused] for declarative access control

The #[default_impl] macro automatically generates the standard implementations, reducing boilerplate code while maintaining full compatibility with the Token Interface.

Enhanced Security Features

This example showcases several security features built into the OpenZeppelin library:

Access Control: The #[only_owner] macro ensures that only the designated owner can perform administrative actions like minting tokens or pausing the contract.

Pausable Operations: The #[when_not_paused] macro allows the contract to be paused in emergency situations, preventing transfers, burns, and approvals while still allowing balance queries.

Secure Defaults: The library implements secure defaults for all token operations, including proper authorization checks and event emissions.

tip

The OpenZeppelin Stellar Contracts library has been audited and follows industry best practices. Using these pre-built components reduces the risk of security vulnerabilities in your token contracts.

Usage

We'll create a simple token for a game's in-game currency. Players can earn tokens by completing tasks, and they can spend tokens on in-game items. The contract owner can mint new tokens as needed, and players can transfer tokens between accounts.

Here's what a basic fungible token contract might look like:

use soroban_sdk::{contract, contractimpl, Address, Env, String};
use stellar_tokens::fungible::{burnable::FungibleBurnable, Base, ContractOverrides, FungibleToken};
use stellar_access::ownable::{self as ownable, Ownable};
use stellar_macros::{default_impl, only_owner};

#[contract]
pub struct GameCurrency;

#[contractimpl]
impl GameCurrency {
pub fn __constructor(e: &Env, initial_owner: Address) {
// Set token metadata
Base::set_metadata(
e,
8, // 8 decimals
String::from_str(e, "Game Currency"),
String::from_str(e, "GCUR"),
);

// Set the contract owner
ownable::set_owner(e, &initial_owner);
}

#[only_owner]
pub fn mint_tokens(e: &Env, to: Address, amount: i128) {
// Mint tokens to the recipient
Base::mint(e, &to, amount);
}
}

#[default_impl]
#[contractimpl]
impl FungibleToken for GameCurrency {
type ContractType = Base;
}

#[default_impl]
#[contractimpl]
impl FungibleBurnable for GameCurrency {}

Tests

The OpenZeppelin example includes comprehensive tests that verify both standard token functionality and the additional security features.

Key test scenarios include:

  • Token Operations: Testing mint, transfer, burn, and approval functions
  • Access Control: Verifying only the owner can perform administrative actions
  • Pausable Functionality: Ensuring token operations are properly restricted when paused
  • Authorization: Confirming proper authentication requirements for each function
  • Edge Cases: Testing boundary conditions and error scenarios

To run the tests:

cd examples/fungible-token-interface
cargo test

The tests demonstrate how the OpenZeppelin library handles complex scenarios while maintaining security and proper access control.

Build the Contract

To build the contract, use the stellar contract build command.

stellar contract build

A .wasm file should be outputted in the target directory:

target/wasm32v1-none/release/fungible_token_interface.wasm