James Bachini

Building a SEP41 Token on Stellar Soroban with OpenZeppelin

OpenZeppelin Stellar Soroban

OpenZeppelin has just launched the first few contract libraries for the Soroban ecosystem. Let’s take a look and build out a token using their fungible token module.

James On YouTube

Deploying a Fungible Token on Soroban

Prerequisites

Ensure you have the following installed:

  • Rust (latest stable version)
  • Cargo (Rust package manager)
  • Stellar CLI
  • Soroban SDK

You’ll also need some testnet tokens which you can get from here. Just add your address on the end: https://friendbot.stellar.org/?addr=

There’s an introductory guide to building rust smart contracts on Soroban here if you need it: https://jamesbachini.com/building-rust-smart-contracts-on-stellar-soroban/


OpenZeppelin Contract Wizard

Update: 28th February 2025

You can now use the Open Zeppelin contract wizard to create soroban smart contracts.

Check out the UI here: https://wizard.openzeppelin.com/stellar

OpenZeppelin Contract Wizard Stellar Soroban

OpenZeppelin Soroban SEP41 Token Code

The code repository for the OpenZeppelin Stellar Soroban libraries is here: https://github.com/OpenZeppelin/stellar-contracts/

Note that this is a preliminary release and at time of writing the code is described as not production ready.

OpenZeppelin Warning

Despite this I couldn’t wait to dive in and get to know my way around their new Rust smart contract libraries.

Below is a Soroban SEP41 Fungible Token using OpenZeppelin libraries.

All the code for this is open source and available on Github: https://github.com/jamesbachini/Soroban-OpenZeppelin-Token

/*
    Example Soroban SEP41 Fungible Token
       Using OpenZeppelin Libraries

        .------------------------.
    }=>/  __------------------__  \ 
      /       Soroban SEP41        \
     }=>  Stellar ♥️ OpenZeppelin    )
      \   __     v.0.0.1      __   /
    }=>\    ------------------    / 
        '------------------------' 
                  `-----'   
*/

use openzeppelin_fungible_token::{
    self as fungible, burnable::FungibleBurnable, mintable::FungibleMintable, FungibleToken,
};

use soroban_sdk::{
    contract, contracterror, contractimpl, panic_with_error, symbol_short, Address, Env, String,
    Symbol,
};

pub const OWNER: Symbol = symbol_short!("OWNER");
pub const CAP: Symbol = symbol_short!("CAP");

#[contract]
pub struct MyCoinContract;

#[contracterror]
pub enum MyCoinContractError {
    MaxSupplyExceeded = 1,
}

#[contractimpl]
impl MyCoinContract {
    pub fn __constructor(e: &Env, owner: Address, cap: i128) {
        fungible::metadata::set_metadata(
            e,
            18,
            String::from_str(e, "My Coin"),
            String::from_str(e, "MC"),
        );
        e.storage().instance().set(&OWNER, &owner);
        e.storage().instance().set(&CAP, &cap);
    }
}

#[contractimpl]
impl FungibleToken for MyCoinContract {
    fn total_supply(e: &Env) -> i128 {
        fungible::total_supply(e)
    }

    fn balance(e: &Env, account: Address) -> i128 {
        fungible::balance(e, &account)
    }

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

    fn transfer(e: &Env, from: Address, to: Address, amount: i128) {
        fungible::transfer(e, &from, &to, amount);
    }

    fn transfer_from(e: &Env, spender: Address, from: Address, to: Address, amount: i128) {
        fungible::transfer_from(e, &spender, &from, &to, amount);
    }

    fn approve(e: &Env, owner: Address, spender: Address, amount: i128, live_until_ledger: u32) {
        fungible::approve(e, &owner, &spender, amount, live_until_ledger);
    }

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

    fn name(e: &Env) -> String {
        fungible::metadata::name(e)
    }

    fn symbol(e: &Env) -> String {
        fungible::metadata::symbol(e)
    }
}

#[contractimpl]
impl FungibleBurnable for MyCoinContract {
    fn burn(e: &Env, from: Address, amount: i128) {
        fungible::burnable::burn(e, &from, amount)
    }

    fn burn_from(e: &Env, spender: Address, from: Address, amount: i128) {
        fungible::burnable::burn_from(e, &spender, &from, amount)
    }
}

#[contractimpl]
impl FungibleMintable for MyCoinContract {
    fn mint(e: &Env, account: Address, amount: i128) {
        let owner: Address = e.storage().instance().get(&OWNER).expect("owner should be set");
        owner.require_auth();
        let cap: i128 = e.storage().instance().get(&CAP).expect("cap should be set");
        let current_total_supply = fungible::total_supply(e);
        if current_total_supply + amount > cap {
            panic_with_error!(e, MyCoinContractError::MaxSupplyExceeded);
        }
        fungible::mintable::mint(e, &account, amount);
    }
}

To deploy we will use the stellar cli:

cargo build

cargo test

cargo install --locked stellar-cli --features opt

stellar keys address james

cargo build --target wasm32-unknown-unknown --release

stellar contract deploy  --wasm target/wasm32-unknown-unknown/release/mycoin.wasm --source james --network testnet -- --owner GD6ERVU2XC35LUZQ57JKTRF6DMCNF2JI5TFL7COH5FSQ4TZ2IBA3H55C --cap 1000000

stellar contract invoke --id CBCRKOQKOYDDKGZEDHC6CGTHOTU2W3DL426QLS4ZYTZNDZDTTWAP4DYU --source james --network testnet -- mint --account GD6ERVU2XC35LUZQ57JKTRF6DMCNF2JI5TFL7COH5FSQ4TZ2IBA3H55C --amount 1000

And you should get some tokens come through to your address.

Stellar Cli Deploying Token

This collaboration will bring OpenZeppelin’s expertise in smart contract standards and security to Soroban, Stellar’s smart contract platform.

OpenZeppelin has been instrumental in setting security frameworks in the Ethereum ecosystem. Now, they are bringing the same level of expertise to Stellar by developing the Stellar Library, which will provide foundational smart contracts, advanced token standards, and cryptographic utilities.

The partnership spans January 2025 through December 2026 and includes:

  • Security Audits 40 Auditor Weeks dedicated over two years.
  • Bug Bounty Program OpenZeppelin will operate a security bug bounty for the Stellar Library.
  • Developer Tools Open source tools for contract inspection, relayers, monitors, and more.
  • Smart Contract Libraries Support for tokens, utilities and cryptographic functions.

This initiative ensures Stellar developers have access to audited, secure, and standardized contracts, allowing them to focus on building innovative decentralized applications.


Get The Blockchain Sector Newsletter, binge the YouTube channel and connect with me on Twitter

The Blockchain Sector newsletter goes out a few times a month when there is breaking news or interesting developments to discuss. All the content I produce is free, if you’d like to help please share this content on social media.

Thank you.

James Bachini

Disclaimer: Not a financial advisor, not financial advice. The content I create is to document my journey and for educational and entertainment purposes only. It is not under any circumstances investment advice. I am not an investment or trading professional and am learning myself while still making plenty of mistakes along the way. Any code published is experimental and not production ready to be used for financial transactions. Do your own research and do not play with funds you do not want to lose.