StellarForge is a user-friendly decentralized application (dApp) that enables creators, entrepreneurs, and businesses in emerging markets to deploy custom tokens on the Stellar blockchain without writing a single line of code.
- Token Factory Contract: Deploy custom tokens on Stellar using Soroban smart contracts
- Fee-Based System: Configurable fees for token creation, metadata setting, and minting
- IPFS Integration: Store token metadata (images, descriptions) on IPFS via Pinata
- Wallet Integration: Connect with Freighter wallet for seamless transactions
- Burn Functionality: Burn tokens to reduce supply
- Admin Controls: Update fees and manage the factory
- Network Switcher: Toggle between testnet and mainnet from the UI
- Transaction History: View on-chain contract events with pagination
- Testnet & Mainnet Support: Deploy on both testnet and mainnet
- Rust: Programming language for Soroban contracts
- Soroban SDK: Stellar's smart contract development framework
- Soroban Token SDK: For token operations
- React 19: UI framework
- TypeScript: Type-safe JavaScript
- Vite: Build tool and dev server
- Tailwind CSS: Utility-first CSS framework
- Vitest: Testing framework
- Freighter Wallet: Stellar wallet browser extension
- IPFS/Pinata: Decentralized file storage for metadata
- Stellar Horizon: Blockchain data API
- Soroban RPC: Smart contract interaction
- Rust: For building Soroban contracts
- Node.js (v18+): For frontend development
- Stellar CLI: For contract deployment and testing (see setup below)
- Freighter Wallet: Browser extension for Stellar transactions
You can set up StellarForge using either Docker (recommended for quick start) or local installation.
Prerequisites: Docker and Docker Compose
# Clone the repository
git clone <repository-url>
cd stellar-forge
# Start development environment
docker compose up -d
# Frontend available at: http://localhost:5173
# Access contract builder: docker compose exec contract-builder bashSee DOCKER_SETUP.md for detailed Docker instructions.
Prerequisites: Rust, Node.js (v18+), Stellar CLI, Freighter Wallet
git clone <repository-url>
cd stellar-forgeRun the setup script to install Rust, Stellar CLI, and configure testnet:
./scripts/setup-soroban.shNote: The Soroban CLI was renamed to
stellarin recent versions. All commands below usestellar. If you have the oldsorobanbinary installed, uninstall it and run the setup script again:cargo uninstall soroban-cli cargo install stellar-cli --features opt
cd frontend
npm installCopy the example env file and fill in your values: Copy the example file and fill in your values:
cp frontend/.env.example frontend/.envThen edit frontend/.env:
VITE_NETWORK=testnet
VITE_FACTORY_CONTRACT_ID=<deployed-contract-id>
VITE_IPFS_API_KEY=<pinata-api-key>
VITE_IPFS_API_SECRET=<pinata-api-secret>Note:
VITE_FACTORY_CONTRACT_ID,VITE_IPFS_API_KEY, andVITE_IPFS_API_SECRETare required. The app will display a misconfiguration screen if any of these are missing, rather than failing silently at runtime.
cd contracts
cargo build --target wasm32-unknown-unknown --releaseFor an optimized binary (requires binaryen — install via apt install binaryen or brew install binaryen):
cd contracts/token-factory
bash build.shThis produces target/wasm32-unknown-unknown/release/token_factory.optimized.wasm, which is significantly smaller and lowers on-chain deployment costs.
cd contracts/token-factory
cargo testFuzz testing with random inputs discovers edge cases and potential crashes:
cd contracts/token-factory/fuzz
cargo fuzz run fuzz_create_token -- -timeout=60 # Test token creation
cargo fuzz run fuzz_fee_arithmetic -- -timeout=60 # Test fee calculations
cargo fuzz run fuzz_burn -- -timeout=60 # Test burn operationsFor more details on fuzz testing setup and interpretation, see contracts/token-factory/fuzz/README.md.
cd frontend
npm run dev # Start dev server
npm run build # Build for production
npm run test # Run tests
npm run lint # Lint codeinitialize(admin, treasury, base_fee, metadata_fee): Set up the factory with admin controls and fees
create_token(creator, name, symbol, decimals, initial_supply, fee_payment): Deploy a new tokenmint_tokens(token_address, admin, to, amount, fee_payment): Mint additional tokensburn(token_address, from, amount): Burn tokens from supply
set_metadata(token_address, admin, metadata_uri, fee_payment): Set token metadata URI
update_fees(admin, base_fee?, metadata_fee?): Update factory feespause(admin)/unpause(admin): Pause or resume the factory
get_state(): Get factory stateget_base_fee(): Get token creation feeget_metadata_fee(): Get metadata setting feeget_token_info(index): Get token information by indexget_tokens_by_creator(creator): Get all token indices created by a given address
- Connect Wallet: Use Freighter wallet to connect to the dApp
- Create Token: Fill in token details (name, symbol, decimals, supply) and pay the creation fee
- Set Metadata: Upload token image and description to IPFS
- Mint Tokens: Mint additional tokens as needed
- Manage Supply: Burn tokens to reduce circulating supply
This guide walks you through deploying StellarForge to Stellar testnet from scratch.
- Stellar CLI installed (run
./scripts/setup-soroban.shif not) - Freighter wallet installed in your browser
- Basic understanding of command line
You need testnet XLM to pay for contract deployment and transactions.
-
Create a testnet account using Stellar CLI:
stellar keys generate deployer --network testnet
This creates a new keypair and saves it locally. The output shows your public key.
-
Fund your account using Friendbot:
stellar keys address deployer # Copy the address (starts with G...) # Fund with 10,000 testnet XLM curl "https://friendbot.stellar.org?addr=YOUR_ADDRESS_HERE"
-
Verify your balance:
stellar account balance deployer --network testnet
You should see 10,000 XLM.
cd contracts/token-factory
# Build the contract
cargo build --target wasm32-unknown-unknown --release
# Optimize the binary (reduces size and deployment costs)
stellar contract optimize \
--wasm ../../target/wasm32-unknown-unknown/release/token_factory.wasmThe optimized WASM will be at ../../target/wasm32-unknown-unknown/release/token_factory.optimized.wasm.
# Deploy to testnet
stellar contract deploy \
--wasm ../../target/wasm32-unknown-unknown/release/token_factory.optimized.wasm \
--source deployer \
--network testnet
# Save the contract ID (starts with C...)
# Example output: CXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXSave this contract ID - you'll need it for initialization and frontend configuration.
The factory needs the token contract WASM hash to deploy tokens.
# First, build the standard Stellar token contract
# (or use your custom token implementation)
stellar contract install \
--wasm path/to/soroban_token_contract.wasm \
--source deployer \
--network testnet
# Save the WASM hash (64 hex characters)
# Example: 1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdefIf you don't have a token WASM, you can use the Stellar Asset Contract:
# Download the official Stellar token contract
wget https://github.com/stellar/soroban-examples/raw/main/token/target/wasm32-unknown-unknown/release/soroban_token_contract.wasm
# Install it
stellar contract install \
--wasm soroban_token_contract.wasm \
--source deployer \
--network testnet# Get your admin address (same as deployer for simplicity)
ADMIN_ADDRESS=$(stellar keys address deployer)
# Initialize the contract
stellar contract invoke \
--id <FACTORY_CONTRACT_ID> \
--source deployer \
--network testnet \
-- \
initialize \
--admin $ADMIN_ADDRESS \
--treasury $ADMIN_ADDRESS \
--fee_token <NATIVE_XLM_CONTRACT_ADDRESS> \
--base_fee 100000000 \
--metadata_fee 50000000Parameters explained:
admin: Address that can update fees and pause the factorytreasury: Address that receives fees from token creationfee_token: Contract address for the fee token (use native XLM contract:CDLZFC3SYJYDZT7K67VZ75HPJVIEUVNIXF47ZG2FB2RMQQVU2HHGCYSC)base_fee: Fee for creating a token (in stroops, 1 XLM = 10,000,000 stroops)metadata_fee: Fee for setting token metadata
cd frontend
# Copy environment template
cp .env.example .env
# Edit .env with your values
nano .envUpdate these required variables:
VITE_NETWORK=testnet
VITE_FACTORY_CONTRACT_ID=<your-factory-contract-id>
VITE_TOKEN_WASM_HASH=<your-token-wasm-hash>
VITE_IPFS_API_KEY=<your-pinata-api-key>
VITE_IPFS_API_SECRET=<your-pinata-api-secret>Getting Pinata credentials:
- Sign up at https://app.pinata.cloud
- Go to API Keys → New Key
- Enable "pinFileToIPFS" permission
- Copy the API Key and API Secret
# Start the development server
npm run dev
# Open http://localhost:5173 in your browser- Connect your Freighter wallet (make sure it's on testnet)
- Try creating a test token
- Verify the transaction on Stellar Expert
# Build for production
npm run build
# Deploy the dist/ folder to your hosting serviceDeployment options:
- Vercel: See docs/deployment-vercel.md
- Netlify: Drag and drop the
dist/folder - GitHub Pages: Use
gh-pagespackage - Your own server: Serve the
dist/folder with nginx/apache
The process is identical to testnet, but:
- Use
--network mainnetinstead of--network testnet - Fund your account with real XLM (buy from an exchange)
- Set
VITE_NETWORK=mainnetin your.env - Review all parameters carefully before deployment
- Consider using a hardware wallet for the admin key
Your account doesn't exist on the network yet. Fund it with Friendbot (testnet) or send XLM from an exchange (mainnet).
# Testnet
curl "https://friendbot.stellar.org?addr=$(stellar keys address deployer)"You don't have enough XLM to pay for the transaction.
# Check balance
stellar account balance deployer --network testnet
# Get more testnet XLM
curl "https://friendbot.stellar.org?addr=$(stellar keys address deployer)"The contract has already been initialized. You can't initialize it again. If you need to change parameters, deploy a new contract.
The WASM hash you provided doesn't exist on the network. Make sure you ran stellar contract install first and used the correct hash.
Add the wasm32 target to Rust:
rustup target add wasm32-unknown-unknownOne or more required environment variables are missing. Check that your .env file has:
VITE_FACTORY_CONTRACT_IDVITE_TOKEN_WASM_HASHVITE_IPFS_API_KEYVITE_IPFS_API_SECRET
Restart the dev server after changing .env files.
stellar-forge/
├── contracts/ # Soroban smart contracts
│ ├── Cargo.toml # Workspace configuration
│ └── token-factory/ # Token factory contract
│ ├── Cargo.toml
│ └── src/
│ ├── lib.rs # Contract implementation
│ └── test.rs # Contract tests
├── frontend/ # React application
│ ├── src/
│ │ ├── components/ # UI components (NetworkSwitcher, TransactionHistory, ...)
│ │ ├── context/ # React contexts (Wallet, Toast, Network)
│ │ ├── services/ # API integrations (stellar, wallet, ipfs)
│ │ ├── hooks/ # React hooks
│ │ ├── config/ # Configuration files
│ │ ├── types/ # TypeScript type definitions
│ │ └── utils/ # Utility functions
│ ├── package.json
│ └── vite.config.ts
├── scripts/ # Setup scripts
│ └── setup-soroban.sh # Installs Rust + Stellar CLI + configures testnet
└── README.md
StellarForge consists of three main components that work together:
┌─────────────────────────────────────────────────────────────────┐
│ User's Browser │
│ │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ React Frontend │ │
│ │ ┌─────────────┐ ┌──────────────┐ ┌─────────────────┐ │ │
│ │ │ UI Layer │ │ Services │ │ Wallet SDK │ │ │
│ │ │ Components │→ │ stellar.ts │→ │ Freighter │ │ │
│ │ │ Forms │ │ ipfs.ts │ │ Integration │ │ │
│ │ └─────────────┘ └──────────────┘ └─────────────────┘ │ │
│ └───────────────────────────────────────────────────────────┘ │
│ ↓ │
└──────────────────────────────┼──────────────────────────────────┘
↓
┌────────────────┴────────────────┐
│ │
↓ ↓
┌─────────────────────────┐ ┌─────────────────────────┐
│ Stellar Network │ │ IPFS (Pinata) │
│ │ │ │
│ ┌──────────────────┐ │ │ ┌──────────────────┐ │
│ │ Factory Contract │ │ │ │ Token Metadata │ │
│ │ - create_token │ │ │ │ - Images │ │
│ │ - mint_tokens │ │ │ │ - Descriptions │ │
│ │ - burn │ │ │ │ - JSON files │ │
│ │ - set_metadata │ │ │ └──────────────────┘ │
│ └────────┬─────────┘ │ └─────────────────────────┘
│ │ │
│ ↓ │
│ ┌──────────────────┐ │
│ │ Token Contracts │ │
│ │ (deployed by │ │
│ │ factory) │ │
│ │ - transfer │ │
│ │ - balance │ │
│ │ - approve │ │
│ └──────────────────┘ │
└─────────────────────────┘
-
User → Frontend: User interacts with React UI to create tokens, set metadata, etc.
-
Frontend → Freighter: Frontend uses Freighter API to request transaction signatures
-
Frontend → Stellar Network: Signed transactions are submitted to Stellar via Soroban RPC
-
Factory Contract → Token Contracts: Factory deploys new token contracts using the token WASM hash
-
Frontend → IPFS: Token metadata (images, descriptions) are uploaded to IPFS via Pinata
-
Frontend → Stellar Network: Metadata URIs (ipfs://...) are stored on-chain via
set_metadata
1. User fills form → 2. Frontend validates → 3. Freighter signs tx
↓
4. Submit to Stellar
↓
5. Factory Contract
- Validates params
- Collects fee
- Deploys token
↓
6. New Token Contract
- Initialized
- Mints supply
↓
7. Event emitted
↓
8. Frontend updates UI
Symptoms: "Wallet not installed" error or connection button doesn't work
Solutions:
- Install Freighter wallet extension
- Refresh the page after installing
- Check that Freighter is enabled in your browser extensions
- Try a different browser (Chrome, Firefox, Brave, Edge supported)
Symptoms: Transactions fail with "account not found" or "contract not found"
Solutions:
- Check the network indicator in the top-right corner of the app
- Click the network switcher to toggle between testnet and mainnet
- In Freighter, ensure you're on the same network as the app
- Verify
VITE_NETWORKin your.envmatches your deployment
Symptoms: "insufficient balance" or "insufficient fee" errors
Solutions:
- Testnet: Get free XLM from Friendbot:
curl "https://friendbot.stellar.org?addr=YOUR_ADDRESS" - Mainnet: Buy XLM from an exchange and send to your wallet
- Check your balance in Freighter wallet
- Ensure you have at least 2-3 XLM for contract interactions
- Fee errors may indicate the factory's fee requirements have increased
Symptoms: Transaction pending for a long time, then fails
Solutions:
- Check Stellar network status at status.stellar.org
- Increase timeout in the code (default is 30 seconds)
- Try submitting the transaction again
- Check if Soroban RPC endpoint is responding:
curl https://soroban-testnet.stellar.org/health
Symptoms: "Failed to upload metadata" or IPFS errors
Solutions:
- Verify your Pinata API credentials in
.env - Check Pinata dashboard for API key status
- Ensure image file is under 10MB
- Try a different image format (PNG, JPG, GIF supported)
- Check Pinata service status at status.pinata.cloud
Symptoms: "AlreadyInitialized" error or initialization transaction fails
Solutions:
- Contract can only be initialized once
- If you need different parameters, deploy a new contract
- Check if contract is already initialized:
stellar contract invoke \ --id <contract-id> \ --network testnet \ -- get_state
Symptoms: "InvalidTokenParams" or creation transaction fails
Solutions:
- Ensure token name is 1-32 characters
- Ensure token symbol is 1-12 characters
- Decimals must be 0-18
- Initial supply must be non-negative
- Check that you have enough XLM to pay the creation fee
- Verify the factory is not paused:
stellar contract invoke \ --id <contract-id> \ --network testnet \ -- get_state
Symptoms: Token created but image/description doesn't show
Solutions:
- Check that metadata was set (look for
metadata_setevent) - Verify IPFS URI is accessible:
curl https://gateway.pinata.cloud/ipfs/<your-cid>
- Clear browser cache and reload
- Check browser console for CORS or loading errors
- Ensure metadata JSON follows the correct format:
{ "name": "Token Name", "description": "Token description", "image": "ipfs://..." }
Symptoms: cargo build or npm run build fails
Solutions:
- Rust build fails:
rustup update rustup target add wasm32-unknown-unknown cd contracts && cargo clean && cargo build
- Frontend build fails:
cd frontend rm -rf node_modules package-lock.json npm install npm run build - Check that you're using compatible versions (Node 18+, Rust stable)
Symptoms: Transaction history or token events don't display
Solutions:
- Check that Soroban RPC endpoint supports
getEvents - Verify contract ID is correct in
.env - Check browser console for API errors
- Try refreshing the page
- Ensure you're on the correct network (testnet/mainnet)
If you're still experiencing issues:
- Check the logs: Open browser DevTools (F12) and check the Console tab
- Search existing issues: GitHub Issues
- Ask for help: Create a new issue with:
- Description of the problem
- Steps to reproduce
- Error messages (from browser console and terminal)
- Your environment (OS, browser, Node version)
- Join the community: Stellar Discord or developer forums
We take security seriously. If you discover a security vulnerability, please review our Security Policy for responsible disclosure guidelines.
A strict CSP is defined as a <meta> tag in frontend/index.html:
default-src 'self';
connect-src 'self' https://*.stellar.org https://api.pinata.cloud;
img-src 'self' data: https://gateway.pinata.cloud;
script-src 'self'
For stronger enforcement, set the CSP as an HTTP response header on your hosting provider instead of (or in addition to) the meta tag — HTTP headers take precedence and support more directives like frame-ancestors.
Vercel — add to vercel.json:
{
"headers": [
{
"source": "/(.*)",
"headers": [
{
"key": "Content-Security-Policy",
"value": "default-src 'self'; connect-src 'self' https://*.stellar.org https://api.pinata.cloud; img-src 'self' data: https://gateway.pinata.cloud; script-src 'self'"
}
]
}
]
}Netlify — add to netlify.toml:
[[headers]]
for = "/*"
[headers.values]
Content-Security-Policy = "default-src 'self'; connect-src 'self' https://*.stellar.org https://api.pinata.cloud; img-src 'self' data: https://gateway.pinata.cloud; script-src 'self'"Nginx — add to your server block:
add_header Content-Security-Policy "default-src 'self'; connect-src 'self' https://*.stellar.org https://api.pinata.cloud; img-src 'self' data: https://gateway.pinata.cloud; script-src 'self'";For users deploying tokens, we strongly recommend:
- Always test on testnet first before mainnet deployment
- Review all parameters carefully using the mainnet deployment checklist
- Verify contract addresses and transaction details before signing
If a user's XLM balance is too low to cover the network base fee, their transaction will fail. Stellar's fee bump mechanism lets a third-party account (the fee source) pay the base fee on behalf of the original sender.
- The inner transaction's source account has near-zero XLM.
- You want to sponsor fees for users as part of your application UX.
- Resubmitting a stuck transaction with a higher fee without re-signing the inner envelope.
Two utilities are exported from frontend/src/services/stellar.ts:
// 1. Wrap a signed inner transaction in a fee bump envelope.
// The fee-source account (connected via Freighter) signs the bump.
const signedFeeBumpXdr = await buildFeeBumpTransaction(innerTxXdr, feeSourceAddress)
// 2. Submit the fee bump and wait for confirmation.
const txHash = await submitFeeBumpTransaction(signedFeeBumpXdr)The fee source must have enough XLM to cover the base fee. The inner transaction is not re-signed — only the fee bump envelope requires the fee source's signature.
See CONTRIBUTING.md for local development setup and contribution guidelines.
Key architectural decisions are documented in docs/adr/:
- ADR-001: Choice of Stellar / Soroban for smart contracts
- ADR-002: Freighter wallet integration
- ADR-003: Pinata for IPFS metadata storage
- ADR-004: React + Vite + TypeScript for frontend
The factory contract supports in-place WASM upgrades without redeploying or migrating state.
- Build and optimize the new contract WASM.
- Upload the new WASM to the network to obtain its hash:
stellar contract upload \ --wasm target/wasm32-unknown-unknown/release/token_factory.optimized.wasm \ --source <admin-secret-key> \ --network testnet # Outputs: <new-wasm-hash>
- Call
upgradeon the deployed contract:stellar contract invoke \ --id <contract-id> \ --source <admin-secret-key> \ --network testnet \ -- upgrade \ --admin <admin-address> \ --new_wasm_hash <new-wasm-hash>
- If the new version requires data layout changes, call
migrateimmediately after:stellar contract invoke \ --id <contract-id> \ --source <admin-secret-key> \ --network testnet \ -- migrate \ --admin <admin-address>
Only the admin address can call upgrade. Non-admin callers receive Error::Unauthorized. Contract state (tokens, fees, admin) is fully preserved across upgrades.
This project follows the Contributor Covenant Code of Conduct. By participating, you are expected to uphold this code.
This project is licensed under the MIT License - see the LICENSE file for details.
This software is for educational and development purposes. Always test thoroughly on testnet before mainnet deployment. The authors are not responsible for any financial losses incurred through the use of this software.