A Soroban smart contract for on-chain crowdfunding, with a non-custodial React dApp + Freighter wallet front-end on the Stellar Testnet
This project ships two cooperating Rust/Soroban contracts on the Stellar Testnet:
- StellarFund (
fund) — a fully on-chain crowdfunding campaign. Donors send the native XLM asset into the contract, which tracks the cumulative amount raised, the unique-donor count, and each donor's running total; the beneficiary (owner) can withdraw the collected funds. - DonorBadge (
badge) — a companion contract. On every donation,fundmakes a cross-contract call tobadge.award, assigning each donor a loyalty tier (Bronze / Silver / Gold) from their cumulative total. → see Inter-Contract Communication.
| fund | badge | |
|---|---|---|
| Contract ID | CCIYIE3WDF5EEC4DL25JR2O4SAV2G3USARIBMCLWPIFQVUOIVDEN5FWI |
deploy + paste ID |
| Source | contract/contracts/fund/src/lib.rs |
contract/contracts/badge/src/lib.rs |
| Network | Stellar Testnet | Stellar Testnet |
| Explorer | fund ↗ | — |
- Asset — native XLM (SAC
CDLZFC3SYJYDZT7K67VZ75HPJVIEUVNIXF47ZG2FB2RMQQVU2HHGCYSC) · Goal — 1,000 XLM (10000000000stroops)
| Action | Hash |
|---|---|
| Deploy contract | 97bb3a92…806ee41 |
donate() interaction |
5edecdcb…1937e4 |
The complete contract source lives in contract/contracts/fund/src/lib.rs under a standard Soroban workspace layout:
contract/
├── Cargo.toml # Soroban workspace (soroban-sdk 26)
└── contracts/
└── fund/
├── Cargo.toml # fund crate — crate-type ["lib", "cdylib"]
├── Makefile
└── src/
├── lib.rs # ← the StellarFund smart contract source
└── test.rs # unit tests (5 tests)
The contract uses Soroban's #[contract], #[contractimpl], #[contracttype], #[contracterror], and #[contractevent] macros, with require_auth() authorization and both instance and persistent storage.
| Method | Kind | Description |
|---|---|---|
__constructor(owner, token, goal) |
init | Sets the beneficiary, campaign asset (SAC), and fundraising goal at deploy time. |
donate(from, amount) |
write | Requires from auth, pulls amount of the token into the contract, updates the cumulative total + per-donor contribution, closes the campaign when the goal is met, emits a Donated event. Returns the new total raised. |
withdraw() |
write | Owner-only (require_auth). Transfers the full contract balance to the beneficiary, emits Withdrawn. |
goal() / raised() / donors() |
read | Campaign progress. |
is_closed() |
read | true once the goal has been reached. |
contribution(who) |
read | A given address's running total. |
owner() / token() |
read | Campaign configuration. |
Custom errors: ZeroAmount (1) · CampaignClosed (2) · NothingRaised (3)
Events: Donated { from, amount, total } · Withdrawn { owner, amount }
cd contract
cargo test # run the full suite — 11 tests (6 fund + 5 badge)
stellar contract build # build both optimized wasm filesFull step-by-step deployment of both contracts (including the set_badge
wiring for the cross-contract call) lives in contract/README.md.
StellarFund and DonorBadge demonstrate a real cross-contract call on Soroban:
donor ──donate()──▶ fund contract ──award()──▶ badge contract
(records gift) (assigns loyalty tier)
- A donor calls
fund.donate(from, amount). fundrecords the donation and updates the donor's running total.- If a badge contract is registered (
fund.set_badge(<BADGE_ID>)),fundinvokesBadgeClient::new(&env, &badge).award(&from, &donor_total). badge.awardauthorizes the caller viaadmin.require_auth()— whereadminis the fund contract's own address — so only the fund contract can mint badges. Both writes share one transaction, making the badge update atomic with the donation.
The typed client is generated with #[contractclient] from a trait in
fund/src/lib.rs, and the end-to-end path
is covered by the donation_awards_badge_cross_contract test.
| Suite | Command | Tests |
|---|---|---|
| Contracts (Rust) | cd contract && cargo test |
11 (6 fund + 5 badge) |
| Frontend (Jest) | CI=true npm test |
9 (src/lib/stellar.test.js) |
Frontend tests cover the pure conversion, validation, and tier-mapping helpers
in src/lib/stellar.js — stroop ↔ XLM conversion, Stellar
address format validation, and badge-tier naming.
Every push and pull request to main runs GitHub Actions with two parallel jobs:
| Job | Steps |
|---|---|
| Soroban contracts | install Rust → cargo test --workspace |
| Frontend | npm ci → npm test → npm run build |
┌─────────────────────────────────────────────────────────────┐
│ React dApp (CRA) │
│ Header → Crowdfund ──► Fund.js (Soroban RPC client) │
│ Freighter.js (wallet: sign/address) │
│ lib/stellar.js (pure, unit-tested) │
└───────────────┬─────────────────────────────────────────────┘
│ @stellar/stellar-sdk + @stellar/freighter-api
▼
Soroban RPC (Testnet)
│
┌────────────┴────────────┐
▼ ▼
fund contract ──award()──► badge contract
(crowdfunding) (DonorBadge tiers)
- Separation of concerns — pure helpers (
lib/stellar.js) are isolated from network/wallet code so they can be unit-tested without mocks. - Typed errors + loading states —
FundErrormaps contract failures to friendly UI copy;Crowdfund.jstracksidle → pending → success → error. - Real-time updates —
getRecentDonations()streams on-chainDonatedevents into a live activity feed. - Atomic cross-contract writes — donation + badge award commit in one transaction.
StellarFlow is the non-custodial React + Freighter front-end that pairs with the StellarFund contract. It connects a user's Freighter wallet,reads their address and live XLM balance, and signs/submits real Stellar Testnet transactions — no sign-up, no middleman, no custody of keys.
- ✅ Connect Wallet — Freighter integration in
src/components/Freighter.js:setAllowed()→ permission,requestAccess()→ address retrieval,signTransaction()→ transaction signing - ✅ Wallet Balance Checker — live XLM balance displayed on connection
- ✅ Send XLM — real Testnet payments with client-side address validation
- ✅ Transaction History Viewer — persistent activity panel via
localStorage
| Feature | Details |
|---|---|
| 🔗 Wallet Connection | Connects via Freighter browser extension (non-custodial) |
| 🪪 Address Retrieval | requestAccess() returns the connected public key |
| ✍️ Transaction Signing | signTransaction() signs XDR inside Freighter |
| 💸 Send XLM | Real Stellar Testnet transactions |
| 📊 Live Balance | Auto-refreshes after every transaction |
| 🧾 Transaction History | Persisted in localStorage — survives reconnects |
| ✅ Address Validation | Client-side Stellar address format check |
| 🌐 WebGL Landing Page | Interactive light-rays hero (OGL + custom GLSL shaders) |
| 📱 Responsive Design | Mobile, tablet, and desktop |
Mouse-interactive WebGL background, glassmorphism nav, hero CTA
Balance displayed, wallet address shown, Recent Activity panel ready
Recipient address input with Stellar format validation, amount field, Process Transfer button
Green "Transaction confirmed" badge with the on-chain transaction hash
| Layer | Technology |
|---|---|
| Smart Contract | Rust + Soroban SDK 26 (contract/contracts/fund/src/lib.rs) |
| Contract Network | Stellar Testnet (deployed) |
| Frontend | React 18 (Create React App) |
| Wallet | Freighter Browser Extension |
| Stellar SDK | @stellar/stellar-sdk |
| Freighter API | @stellar/freighter-api v6 |
| WebGL Rendering | ogl (minimal WebGL library) |
| Styling | Vanilla CSS — glassmorphism + dark theme |
| Persistence | Browser localStorage (transaction history) |
- Node.js v16+ — Download
- npm v8+ (comes with Node)
- Freighter Wallet extension — Install for Chrome
git clone https://github.com/amankoli09/Stellar-Connect-Wallet.git
cd Stellar-Connect-Wallet
npm installnpm startThe app opens at http://localhost:3000.
- Click the Freighter extension icon
- Go to Settings → Network → Testnet
- Return to
localhost:3000and click Connect
https://friendbot.stellar.org/?addr=YOUR_PUBLIC_KEY
Or use Stellar Laboratory.
1. Install & configure Freighter (switch to Testnet)
2. Open http://localhost:3000
3. Click "Connect" → approve in Freighter (setAllowed + requestAccess)
4. Your XLM balance loads automatically
5. Enter a recipient Stellar address (G... 56 chars) + amount
6. Click "Process Transfer" → sign in Freighter (signTransaction)
7. Watch the balance update and "Transaction confirmed" appear in green
8. Check Recent Activity — history persists even after disconnect
stellar-connect-wallet/
├── .github/workflows/ci.yml # ← CI/CD: contract tests + frontend test/build
├── contract/ # ← Soroban smart contracts (Rust workspace)
│ ├── Cargo.toml # workspace (members = contracts/*)
│ └── contracts/
│ ├── fund/src/
│ │ ├── lib.rs # StellarFund crowdfunding contract → calls badge
│ │ └── test.rs # 6 unit tests (incl. cross-contract)
│ └── badge/src/
│ ├── lib.rs # DonorBadge contract ← called by fund
│ └── test.rs # 5 unit tests
├── public/
│ └── index.html
├── src/
│ ├── components/
│ │ ├── Header.js # landing page + dashboard UI
│ │ ├── Crowdfund.js # campaign panel, loading/error states
│ │ ├── Fund.js # Soroban RPC client (read/donate/events/badge)
│ │ ├── Freighter.js # wallet connect, address, balance, signing
│ │ ├── LightRays.js # WebGL light-rays effect (OGL + GLSL)
│ │ └── LightRays.css
│ ├── lib/
│ │ ├── stellar.js # pure helpers (conversion/validation/tiers)
│ │ └── stellar.test.js # 9 frontend unit tests
│ ├── App.js # root component
│ ├── App.css # design system (glassmorphism dark theme)
│ └── index.css
├── package.json
└── README.md
- Non-custodial: StellarFlow never stores, transmits, or accesses private keys
- All transaction signing happens inside the Freighter extension
- The
withdrawcontract method is owner-gated viarequire_auth() - The front-end only calls the public Stellar Horizon Testnet API
Live app: https://lynxxpro.vercel.app/
Demo video : https://youtu.be/S3Znxelt8Ms
The dApp is fully responsive across mobile, tablet, and desktop.
MIT © 2026 — Built for the Stellar Developer Track submission.
LynxX is a fully deployed, production-ready dApp on the Stellar Testnet.
| Requirement | Status | Details |
|---|---|---|
| Production deployment | ✅ | https://lynxxpro.vercel.app/ |
| Mobile-responsive UI | ✅ | Glassmorphism dark theme — responsive at 320px–1920px |
| Loading states & error handling | ✅ | idle → pending → success → error states on all async flows |
| Stable smart contract architecture | ✅ | StellarFund + DonorBadge on Stellar Testnet |
| Monitoring & analytics integration | ✅ | Live on-chain analytics panel (Soroban RPC, auto-refreshes every 30s) |
| CI/CD pipeline | ✅ | GitHub Actions — contract tests + frontend test/build on every push |
We actively sought feedback from real testnet users to improve LynxX.
- Google Form - Feedback Collection (Contains Name, Email, Wallet Address, Network, Rating, and 3+ additional questions)
- Excel Sheet - Form Responses (Publicly accessible export of all collected data)
We successfully onboarded 10+ active testnet users who connected their wallets, sent XLM, and provided valuable feedback.
| User ID | Name | Wallet Address | Feedback Summary | |
|---|---|---|---|---|
| U-001 | Aman Koli | amanko***@gmail.com | GAJG7C...Y27ES |
The clean and intuitive UI, fast wallet creation. |
| U-002 | Raj Koli | kolira***@gmail.com | GDJW4E...3S6N |
Simple wallet connection and smooth user experience. |
| U-003 | Nandu Kumar | nandau***@gmail.com | GCMHLA...24YKF |
Fast and secure transactions with a simple layout. |
| U-004 | Avinash Gharat | aterus***@gmail.com | GBA7SL...RJTGI |
I like the ui it is good. |
| U-005 | Namrata | kolina***@gmail.com | GC3C7B...U2PVB |
I like the receipt feature. |
| U-006 | Olaleye Olajide | olal*******@gmail.com | ... |
The analytics is awesome |
| U-007 | Muhammad Dzakwan Najmi | repm*****@gmail.com | ... |
The non-custodial approach combined with crowdfunding is a strong combo — being able to do trustless transfers and pool funding on-chain without giving up custody of my assets feels like a genuine upgrade over typical centralized crowdfunding platforms. |
| U-008 | [Pending User] | Pending Response | ... |
Pending Response |
| U-009 | [Pending User] | Pending Response | ... |
Pending Response |
| U-010 | [Pending User] | Pending Response | ... |
Pending Response |
Based on the feedback collected via our Google Form, we made several improvements to the product. Below is the Improvement Summary mapped to the corresponding user feedback and Git commits.
| User ID | Name | Wallet Address | Feedback Summary | Improvement Made | Git Commit ID | |
|---|---|---|---|---|---|---|
| U-002 | Raj Koli | kolira***@gmail.com | GDJW4E...3S6N |
Address copying was clunky; needed clearer notification. | Implemented global toast notification (sonner) for better UX when copying wallet address. |
2d7bd5c |
| U-003 | Nandu Kumar | nandau***@gmail.com | GCMHLA...24YKF |
Wanted to see visitor tracking and analytics. | Integrated Vercel Analytics into the root layout to track active visitors and usage. | a27da37 |
| Feature | Component | Description |
|---|---|---|
| Live interaction tracker | src/components/UserInteractions.js |
Animated stats + filterable live feed of all wallet actions |
| Persistent feedback form | src/components/FeedbackForm.js |
In-app star rating + comment form; displays all submitted reviews |
| Interaction recording | src/lib/jsonbin.js |
Records connect/send/donate events to persistent storage |
| Feedback storage | src/lib/jsonbin.js |
Saves feedback with localStorage merge; survives page refreshes |
| Header wiring | src/components/Header.js |
Automatically records interactions on wallet connect, send, and donate |
| Section | URL |
|---|---|
| Landing page | lynxxpro.vercel.app/ |
| Live interactions | lynxxpro.vercel.app/#interactions |
| Feedback form | lynxxpro.vercel.app/#user-feedback |
| On-chain analytics | lynxxpro.vercel.app/#analytics |

