James Bachini

Soroban Token Deployment + React dApp Tutorial (Stellar SDK)

Soroban Tokens

In this tutorial, we’ll walk through the process of deploying a fungible token on the Stellar network using Soroban smart contracts, and then build a decentralized application to interact with it. We’ll cover everything from setting up your development environment to creating a user interface for token transfers.

James On YouTube

Prerequisites

Before we begin, you’ll need:

  • Basic familiarity with Rust and React
  • A code editor such as vscode
  • Command line interface/terminal
  • Node.js and npm installed
  • Rust and cargo installed

Soroban Tokens

A fungible token is a digital asset where each unit is identical and interchangeable with another unit of the same token. This differs from non-fungible tokens, where each token is unique. Our implementation will follow standard token patterns similar to ERC20 on Ethereum, including functions like:

  • mint Enables the owner to mint tokens to any wallet
  • transfer Send tokens directly between wallets
  • approve Allow a contract to spend tokens on your behalf
  • transfer_from Let an approved contract move tokens

Note that Soroban/Stellar tokens have 7 decimals as standard and there is a max of 18 decimals set at the contract level. The balances are held within an i128 integer which would make the max possible value:
170,141,183,460,469,231,731,687,303,715,884,105,727


1. Setting Up the Development Environment

Add WebAssembly target support:

rustup target add wasm32-unknown-unknown

Install the Stellar CLI:

cargo install --locked stellar-cli --features opt
2. Building and Deploying the Token Contract

Test the contract:

cargo test

Build the WebAssembly binary:

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

Create a wallet for deployment:

stellar keys generate --global james --network testnet

Get your wallet address:

stellar keys address james

Deploy the contract:

stellar contract deploy --wasm target/wasm32-unknown-unknown/release/soroban_token_contract.wasm --source james --network testnet

Save the contract ID returned from this command, you’ll need it later.

3. Initializing the Token

Initialize the token with basic parameters:

stellar contract invoke --id <CONTRACT_ID> --source-account james --network testnet -- initialize --admin <YOUR_ADDRESS> --decimal 7 --name '' --symbol '""'

Mint some tokens:

stellar contract invoke --id <CONTRACT_ID> --source-account james --network testnet -- mint --to <YOUR_ADDRESS> --amount <AMOUNT>

Note: Remember to consider decimals when setting amounts. For 7 decimals, multiply your desired amount by 10^7 or add seven zeros at the end.

4. Testing Token Transfers

Create another test wallet:

stellar keys generate --global alice --network testnet

Transfer tokens:

stellar contract invoke --id <CONTRACT_ID> --source-account james --network testnet -- transfer --from <YOUR_ADDRESS> --to <RECIPIENT_ADDRESS> --amount <AMOUNT>

Check balances:

stellar contract invoke --id <CONTRACT_ID> --source-account james --network testnet -- balance --id <ADDRESS>

Building the dApp

Now let’s create a user interface to interact with our token.

Soroban Token dApp

We’ll use React and the Freighter wallet.

Set up your React project and install dependencies:

npm install stellar-sdk @stellar/freighter-api

Create the main App component (App.js):

The full code for this is available at:

https://github.com/jamesbachini/Token-dApp/blob/main/dapp/src/App.js

import './App.css';
import React, { useState, useEffect } from 'react';
import * as StellarSdk from 'stellar-sdk';
import freighterApi from "@stellar/freighter-api";

const App = () => {
  const [publicKey, setPublicKey] = useState('');
  const [balance, setBalance] = useState('');
  const [to, setTo] = useState('');
  const [value, setValue] = useState('');
  const [error, setError] = useState('');

  const rpc = new StellarSdk.SorobanRpc.Server('https://soroban-testnet.stellar.org');
  const contractId = 'CCOCB24RH7R2TKF4QVS4J6GBLZ5IZK4FXWQWMQ6GYRGHNUFPW53VJOFU';
  const contract = new StellarSdk.Contract(contractId);

  const handleSend = async (e) => {
    e.preventDefault();
    try {
      const inputFrom = StellarSdk.nativeToScVal(publicKey, { type: "address" });
      const inputTo = StellarSdk.nativeToScVal(to, { type: "address" });
      const inputValue = StellarSdk.nativeToScVal(value, { type: "i128" });
      const account = await rpc.getAccount(publicKey);
      const network = await freighterApi.getNetwork()
      const tx = new StellarSdk.TransactionBuilder(account, {
          fee: StellarSdk.BASE_FEE,
          networkPassphrase: StellarSdk.Networks.TESTNET,
        })
        .addOperation(contract.call("transfer", inputFrom, inputTo, inputValue))
        .setTimeout(30)
        .build();
      console.log('tx.toXDR()',tx.toXDR());
      //const preparedTx = await rpc.prepareTransaction(tx);
      const signedTX = await freighterApi.signTransaction(tx.toXDR(), network);
      console.log('signedTransaction', signedTX);
      const preparedTx = StellarSdk.TransactionBuilder.fromXDR(signedTX, StellarSdk.Networks.TESTNET);
      console.log('preparedTx',preparedTx);
      const txResult = await rpc.sendTransaction(preparedTx);
      console.log('txResult', txResult);
      setTo('');
      setValue('');
    } catch (err) {
      setError('Failed to set value: ' + err.message);
    }
  };

  const connectWallet = async () => {
    const isAppConnected = await freighterApi.isConnected();
    if (!isAppConnected.isConnected) {
      alert("Please install the Freighter wallet");
      window.open('https://freighter.app');
    }
    const accessObj = await freighterApi.requestAccess();
    if (accessObj.error) {
      alert('Error connecting freighter wallet');
    } else {
      setPublicKey(accessObj.address);
    }
  }

  const getBalance = async () => {
    if (!publicKey) {
      console.log('No public key to fetch balance');
      return;
    }
    //console.log('publicKey', publicKey);
    try {
      const inputAddressID = StellarSdk.nativeToScVal(publicKey, { type: "address" });
      //console.log('inputAddressID',inputAddressID);
      const account = await rpc.getAccount(publicKey);
      //console.log('account', account)
      const tx = new StellarSdk.TransactionBuilder(account, {
          fee: StellarSdk.BASE_FEE,
          networkPassphrase: StellarSdk.Networks.TESTNET,
        })
        .addOperation(contract.call("balance", inputAddressID))
        .setTimeout(30)
        .build();
      //console.log('tx', tx);
      rpc.simulateTransaction(tx).then((sim) => {
        const decoded = StellarSdk.scValToNative(sim.result?.retval);
        //console.log('decoded', decoded);
        setBalance(decoded.toString());
      });
    } catch (err) {
      setError('Failed to get value: ' + err.message);
    }
  }

  useEffect(() => {
    connectWallet();
  }, []);

  getBalance();


  return (
    <div className="app">
      <h1 className="app-title">Soroban Token dApp</h1>

      <div className="wallet">Wallet: <span className="public-key">{publicKey}</span></div>
      <div className="balance">Balance: <span className="balance-amount">{balance}</span></div>

      {error && (
        <div className="error-message">
          <strong>Error: </strong>
          <span>{error}</span>
        </div>
      )}

      <div className="section">
        <h2 className="section-title">Send Tokens</h2>
        <form onSubmit={handleSend} className="form">
          <input
            type="text"
            value={to}
            onChange={(e) => setTo(e.target.value)}
            placeholder="To Address"
            className="input"
          />
          <input
            type="text"
            value={value}
            onChange={(e) => setValue(e.target.value)}
            placeholder="Value"
            className="input"
          />
          <button type="submit" className="button">Send</button>
        </form>
      </div>
    </div>
  );
};

export default App;

We can build this for an external host by running npm run build


6. Using the Token in Freighter Wallet
  1. Open Freighter wallet
  2. Go to “Manage Assets”
  3. Add a new asset manually using your contract ID
  4. The token will appear under unverified assets
  5. Refresh to see your balance after transfers
7. Testing the Complete System

Start your React development server:

npm start
Freighter Stellar Connect Web3
  • Connect your Freighter wallet
  • Try sending tokens to another address
  • Verify the transaction in Stellar Expert

You can also see the token interface within Stellar.expert which should look familiar if you’ve worked with digital assets in the past.

Stellar Expert Token Interface

You now have a fully functional token on the Stellar network with a dApp interface. This foundation can be extended with additional features like:

  • Token burning
  • Allowance management
  • Enhanced transaction history
  • Advanced error handling

Remember to keep your contract ID and deployment details safe, and always follow security best practices when handling real assets. Code is for experimental purposes only, has not been security audited and is not fit for production use.


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.