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.

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 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.

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.

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.