A modular Soroban smart contract platform for token minting on the Stellar blockchain, with a TypeScript SDK for seamless integration.
Built for open-source collaboration via drips.network.
- SEP-41 Compliant Token — Full
TokenInterfaceimplementation (balance, transfer, approve, burn) - Admin-Controlled Minting — Only the contract admin can mint new tokens
- Pausable Lifecycle — Emergency pause/unpause to halt all operations
- Ownership Transfer — Securely hand over admin rights
- Total Supply Tracking — Accurate supply updated on every mint/burn
- TypeScript SDK — High-level client for all contract interactions
- Modular Architecture — Separate crates for admin, lifecycle, and token logic
- Reentrancy Protection — Comprehensive reentrancy guards for all state-modifying functions
- Rate Limiting — Configurable global and per-address rate limits for mint and transfer operations
- Batch Payout — Per-recipient failure isolation in batch payments; failed transfers recorded for retry
- Property-Based Fuzz Testing — Enhanced proptest framework for invariant verification
- End-to-End Integration Tests — Complete lifecycle testing on Stellar testnet
- Automatic Storage TTL Management — Shared helper module extends Soroban contract and persistent storage TTL across calls
bc-forge/
├── contracts/ # Soroban smart contracts (Rust)
│ ├── admin/ # Admin access control module
│ │ ├── Cargo.toml
│ │ └── src/lib.rs
│ ├── lifecycle/ # Pause/unpause lifecycle module
│ │ ├── Cargo.toml
│ │ └── src/lib.rs
│ ├── rate-limit/ # Rate limiting module
│ ├── split/ # Batch payout with per-recipient failure isolation
│ │ ├── Cargo.toml
│ │ └── src/
│ │ ├── lib.rs # Split contract implementation
│ │ ├── events.rs # Structured event emissions
│ │ └── test.rs # Unit tests
│ ├── ttl/ # Shared storage TTL helpers
│ │ ├── Cargo.toml
│ │ └── src/lib.rs
│ └── token/ # Core SEP-41 token contract
│ ├── Cargo.toml
│ └── src/
│ ├── lib.rs # Token contract implementation
│ ├── events.rs # Structured event emissions
│ ├── proptest.rs # Property-based fuzz testing
│ ├── reentrancy_guard.rs # Reentrancy protection
│ ├── rate_limit.rs # Rate limit integration
│ └── test.rs # Unit tests
├── e2e/ # End-to-end integration tests
│ ├── Cargo.toml # E2E test dependencies
│ ├── integration_test.rs # Integration test suite
│ └── README.md # E2E test documentation
├── sdk/ # TypeScript SDK
│ ├── src/
│ │ ├── index.ts # Entry point
│ │ ├── client.ts # bcForgeClient class
│ │ └── utils.ts # Transaction helpers
│ ├── package.json
│ └── tsconfig.json
├── .github/
│ ├── ISSUE_TEMPLATE/ # Bug, Feature, Contract Improvement
│ ├── PULL_REQUEST_TEMPLATE.md
│ └── workflows/ci.yml # CI pipeline
├── Cargo.toml # Workspace manifest
├── CONTRIBUTING.md # Contributor guide (drips.network)
├── LICENSE # MIT
└── README.md # This file
To keep Soroban contract state active, bc-forge now includes shared TTL logic that:
- extends the contract instance TTL on every public token, admin, and lifecycle call
- refreshes persistent storage TTL for balances, allowances, lockups, roles, and proposals
- treats expired balances and allowances as zero instead of panicking
This makes the system more resilient to Soroban storage expiry while preserving on-chain security semantics.
| Tool | Version | Install |
|---|---|---|
| Rust | 1.74+ | rustup.rs |
| Wasm target | — | rustup target add wasm32-unknown-unknown |
| Stellar CLI | 22.0+ | cargo install stellar-cli --locked |
| Node.js | 18+ | nodejs.org |
git clone https://github.com/p3ris0n/bc-forge.git
cd bc-forge# Install Rust (if not already installed)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# Add the WebAssembly target
rustup target add wasm32-unknown-unknown
# Install Stellar CLI (includes Soroban)
cargo install stellar-cli --locked# Build all contracts (debug)
cargo build
# Build optimized WASM for deployment
cargo build --target wasm32-unknown-unknown --release
# Or use Stellar CLI
stellar contract buildcargo test --testsExpected output:
running 5 tests (admin) ... ok
running 5 tests (lifecycle) ... ok
running 16 tests (token) ... ok
cd sdk
npm install
npm run buildThe CLI reads .bc-forge.json from the current working directory. Start with the ready-to-use config.example.json:
cp config.example.json .bc-forge.jsonYou can also generate a minimal file with bc-forge config init. The example contains these fields:
| Field | Required | Description |
|---|---|---|
version |
No | Configuration schema version. Defaults to 1.0.0. |
name |
Yes | Token or project name. |
symbol |
Yes | Token symbol. |
decimals |
No | Token decimal precision, from 0 to 18. Defaults to 7. |
admin |
No | Stellar public G... address that administers the token. Required when initializing a contract. |
superAdmin |
No | Stellar public G... address assigned the initial SuperAdmin role during RBAC initialization. Defaults to admin when omitted. |
network |
No | Deployment environment: mainnet, testnet, futurenet, standalone, or custom. Defaults to testnet. |
rpcUrl |
No | Soroban RPC endpoint URL. |
networkPassphrase |
No | Stellar network passphrase. |
secretKey |
No | Stellar S... secret key used to sign transactions. Prefer SECRET_KEY or another secret manager. |
contracts |
No | Map of deployed contract metadata keyed by contract name. |
contracts.<name>.contractId |
No | Deployed Soroban contract ID. |
contracts.<name>.wasmHash |
No | Hash of the deployed contract WASM. |
contracts.<name>.deployer |
No | Stellar public address that deployed the contract. |
Environment variables take precedence over file and local-store values for RPC_URL, NETWORK_PASSPHRASE, CONTRACT_ID, and SECRET_KEY. Replace every placeholder before deploying, and do not commit real secret keys.
stellar keys generate --global deployer --network testnetstellar keys fund deployer --network testnetstellar contract deploy \
--wasm target/wasm32-unknown-unknown/release/bc_forge_token.wasm \
--source deployer \
--network testnetSave the returned Contract ID (e.g., CABC...XYZ).
stellar contract invoke \
--id <CONTRACT_ID> \
--source deployer \
--network testnet \
-- \
initialize \
--admin <YOUR_PUBLIC_KEY> \
--decimal 7 \
--name "bc-forge Token" \
--symbol "SFG"After initialization, run the init_rbac step to bootstrap role-based access
control and assign the initial SuperAdmin role:
# Bootstrap the SuperAdmin mapping from the configured admin (idempotent)
stellar contract invoke \
--id <CONTRACT_ID> \
--source deployer \
--network testnet \
-- \
migrate_admin
# Assign the initial SuperAdmin role (the contract admin can perform this grant)
stellar contract invoke \
--id <CONTRACT_ID> \
--source deployer \
--network testnet \
-- \
grant_role \
--caller <YOUR_PUBLIC_KEY> \
--role SuperAdmin \
--address <SUPER_ADMIN_PUBLIC_KEY>
# Verify the assignment
stellar contract invoke \
--id <CONTRACT_ID> \
--network testnet \
-- \
has_role \
--role SuperAdmin \
--address <SUPER_ADMIN_PUBLIC_KEY>stellar contract invoke \
--id <CONTRACT_ID> \
--source deployer \
--network testnet \
-- \
mint \
--to <RECIPIENT_ADDRESS> \
--amount 10000000000stellar contract invoke \
--id <CONTRACT_ID> \
--network testnet \
-- \
balance \
--id <ADDRESS>If you want to build and test against a local Soroban network, run the Stellar Quickstart container instead of using public testnet services.
docker run -d \
-p "8000:8000" \
--name stellar \
stellar/quickstart \
--localThis starts a local Stellar network with RPC, Horizon, and Friendbot on your machine.
Register the local network once, then switch the CLI to it:
stellar network add local \
--rpc-url http://localhost:8000/rpc \
--network-passphrase "Test SDF Network ; September 2015"
stellar network use localCreate a local identity and fund it from the local Friendbot instance:
stellar keys generate deployer
stellar keys fund deployerYou can use stellar keys public-key deployer to print the address, then use that keypair as the source account for contract deploy and invoke commands on the local network.
When using bcForgeClient, point rpcUrl at the local Quickstart instance:
import { bcForgeClient } from '@bc-forge/sdk';
const client = new bcForgeClient({
rpcUrl: 'http://localhost:8000',
networkPassphrase: 'Test SDF Network ; September 2015',
contractId: 'CABC...XYZ',
});If your local Quickstart setup exposes RPC on a different path, keep the same host and update the URL to match your container configuration.
import { bcForgeClient } from '@bc-forge/sdk';
import { Keypair } from '@stellar/stellar-sdk';
const client = new bcForgeClient({
rpcUrl: 'https://soroban-testnet.stellar.org',
networkPassphrase: 'Test SDF Network ; September 2015',
contractId: 'CABC...XYZ',
});
// Query balance
const balance = await client.getBalance('GABC...DEF');
console.log('Balance:', balance.toString());
// Mint tokens (admin only)
const admin = Keypair.fromSecret('SXXX...');
await client.mint('GABC...DEF', BigInt(1000_0000000), admin);
// Transfer tokens
const sender = Keypair.fromSecret('SYYY...');
await client.transfer(
sender.publicKey(),
'GXYZ...ABC',
BigInt(100_0000000),
sender
);See sdk/README.md for the full API reference.
See the access-control diagrams for the current role hierarchy, authorization sequence, protected operations, and governance flow. See the Vault Integration Guide for details on yield-bearing fee vaults, APY calculations, and frontend dApp integration.
┌─────────────────────────────────────────────────┐
│ BcForgeToken │
│ ┌───────────┐ ┌──────────────┐ ┌───────────┐│
│ │ Admin │ │ Lifecycle │ │ SEP-41 ││
│ │ Module │ │ Module │ │ Interface ││
│ │ │ │ │ │ ││
│ │ set_admin │ │ pause() │ │ balance() ││
│ │ get_admin │ │ unpause() │ │ transfer()││
│ │ require_ │ │ is_paused() │ │ approve() ││
│ │ admin() │ │ require_not_ │ │ burn() ││
│ │ │ │ paused() │ │ mint() ││
│ └───────────┘ └──────────────┘ └───────────┘│
│ ┌──────────────────────────────────────────┐ │
│ │ Split Module │ │
│ │ ┌────────────────────────────────────┐ │ │
│ │ │ release_payment(invoice_id) │ │ │
│ │ │ └─ try_transfer(recipient, amt) │ │ │
│ │ │ retry_failed_payout(invoice_id, │ │ │
│ │ │ recipient) │ │ │
│ │ │ get_failed_payout / get_invoice │ │ │
│ │ └────────────────────────────────────┘ │ │
│ └──────────────────────────────────────────┘ │
└─────────────────────────────────────────────────┘
We welcome contributions! bc-forge is maintained on drips.network — contributors can earn rewards by resolving posted issues.
- Browse open issues — Look for issues labeled
good-first-issue,smart-contract, orsdk - Fork & branch — Create a branch:
feature/<issue-number>-<short-description> - Implement & test — Write code, add/update tests, ensure
cargo testandnpm run buildpass - Submit a PR — Use the PR template; reference the issue number
See CONTRIBUTING.md for the full guide.
feature/<issue-number>-<description> # New features
fix/<issue-number>-<description> # Bug fixes
docs/<issue-number>-<description> # Documentation
test/<issue-number>-<description> # Test improvements
Security is our top priority. If you discover a security vulnerability in bc-forge, please report it responsibly following our Security Policy.
Important: Do not report security vulnerabilities through GitHub issues, discussions, or other public channels. All security reports must be made privately to [email protected].
For more details about our vulnerability disclosure process, supported versions, scope, and response timeline, please review the SECURITY.md file.
MIT — Free for personal and commercial use.