In this tutorial I will be creating a SEP41 wrapped stablecoin using a Stellar smart contract.

I’ll be using the soropg.com online IDE along with the latest libraries from OpenZeppelin.
Note that all code in this tutorial is open source on github here: https://github.com/jamesbachini/Soroban-Stablecoin/
Let’s dive into the code:
// SPDX-License-Identifier: MIT
#![no_std]
use openzeppelin_fungible_token::{
self as fungible, burnable::FungibleBurnable, FungibleToken, mintable::FungibleMintable
};
use soroban_sdk::{Address, contract, contractimpl, Env, String, Symbol, symbol_short};
use sep_41_token::TokenClient;
const USDC: Symbol = symbol_short!("USDC");
#[contract]
pub struct JUSD;
#[contractimpl]
impl JUSD {
pub fn __constructor(e: &Env, usdc: Address) {
fungible::metadata::set_metadata(e, 18, String::from_str(e, "JUSD"), String::from_str(e, "JUSD"));
e.storage().instance().set(&USDC, &usdc);
}
}
#[contractimpl]
impl FungibleToken for JUSD {
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 FungibleMintable for JUSD {
fn mint(e: &Env, account: Address, amount: i128) {
account.require_auth();
let usdc: Address = e.storage().instance().get(&USDC).expect("usdc should be set");
TokenClient::new(&e, &usdc).transfer_from(
&e.current_contract_address(),
&account,
&e.current_contract_address(),
&amount,
);
fungible::mintable::mint(e, &account, amount);
}
}
#[contractimpl]
impl FungibleBurnable for JUSD {
fn burn(e: &Env, account: Address, amount: i128) {
account.require_auth();
let usdc: Address = e.storage().instance().get(&USDC).expect("usdc should be set");
fungible::burnable::burn(e, &account, amount);
TokenClient::new(&e, &usdc).transfer(
&e.current_contract_address(),
&account,
&amount,
);
}
}
The token is a wrapper, built around USDC with the following key features:
- 1:1 Backing – Each JUSD token is backed by exactly 1 USDC token held in the contract
- Minting Mechanism – Users deposit USDC to mint JUSD
- Burning Mechanism – Users can burn JUSD to withdraw the underlying USDC
- Standard Compliant – Implements the SEP41 token standard for compatibility with the Stellar ecosystem
The key functions are mint and burn which transfer USDC in and out of the contract. When doing this we call account.require_auth() which verifies that the account requesting minting has authorized this action. The USDC contract address is collected from instance storage after being set in the constructor argument.
We then use this alongside Script3’s sep-41-token library to do a transfer_from for minting which would require the user to preapprove the contract to spend funds. When burning we just do a transfer from the contract to the user.
This token contract demonstrates how to create a wrapped stablecoin on Soroban that maintains a 1:1 backing with another token (USDC). The implementation follows the SEP-41 standard, making it compatible with the broader Stellar ecosystem while adding functionality for minting and burning to convert between USDC and JUSD.
The design pattern shown here can be adapted for other wrapped tokens or more complex financial instruments built on Soroban.