┌──────────────────────────┐
│ Web Client │
│ Next.js 14 App Router │
└─────────────┬────────────┘
│ Freighter / SDK signing
▼
┌──────────────────────────┐
│ Stellar JS Integration │
│ (@stellar/stellar-sdk) │
└─────────────┬────────────┘
│ invokeHostFunction / simulateTransaction
▼
┌──────────────────────────┐
│ Soroban RPC (Testnet) │
│ soroban-testnet.stellar │
└─────────────┬────────────┘
│
▼
┌──────────────────────────┐
│ stellar-give Contract │
│ (Rust + soroban-sdk) │
└─────────────┬────────────┘
│ emits events / stores state
▼
┌──────────────────────────┐
│ Frontend Polling Loop │
│ query campaign + events │
└──────────────────────────┘
| Contract Function | Purpose | Frontend Caller | Output Mapping |
|---|---|---|---|
create_campaign(creator, beneficiary, title, target_amount, deadline, accepted_token) |
Create campaign | Campaign create form | Returns campaign_id shown in dashboard |
donate(donor, campaign_id, amount) |
Donate funds | Donation flow on campaign detail | Tx hash + event feed update |
claim_funds(caller, campaign_id) |
Beneficiary claim | Beneficiary action panel | Claimed amount + claimed status |
get_campaign(campaign_id) |
Read campaign state | Campaign list/detail loaders | Maps to UI card/detail model |
Expected key spaces:
CampaignById(campaign_id)→ campaign struct (core campaign state)CampaignCounter→ monotonic counter for campaign IDsReentrancyLock(temporary storage) → one-call execution lock
Expected campaign struct fields:
id,creator,beneficiarytitletarget_amountraised_amountdeadlineaccepted_tokenclaimed
For full contract method and error reference documentation, see docs/CONTRACT_API.md.
Authoritative event reference: EVENT_SCHEMA.md.
Summary of the events this contract publishes:
| Topics | Data | Consumer |
|---|---|---|
("created",) |
CreatedEvent { id, creator, target_amount } |
Campaign feed |
("goal_reached",) |
GoalReachedEvent { campaign_id, total_raised } |
Instant milestone notifications |
("donation", "received") |
(campaign_id, donor, amount, raised_amount, accepted_token) |
Donation timeline / analytics |
("funds", "claimed") |
(campaign_id, caller, beneficiary, amount, accepted_token) |
Settlement and payout history |
See EVENT_SCHEMA.md for ScVal types, JSON/XDR
payload examples, and per-event indexing notes.
- Query campaign list on page load, then poll every 15-30s for active campaigns.
- Re-fetch immediately after a confirmed transaction (optimistic UI + authoritative refresh).
- Poll contract events in descending ledger order and deduplicate by
(ledger, topic, data-hash). - Backoff on RPC failures (e.g., 1s → 2s → 4s, cap at 30s) to avoid provider overload.
- For critical balances/claim status, always confirm via latest ledger read before showing success state.
- Use simulation before submission to catch auth/footprint failures early.
- Never trust client-side campaign deadline checks alone; enforce in contract.
- Keep RPC URL + network passphrase aligned to prevent wrong-network signing.
Stellar tokens use 7 decimal places. All amounts in the contract are in stroops (1 stroop = 0.0000001 tokens).
- Never use floating-point math for amounts. Use
i128withchecked_add,checked_mulin Rust, andBigIntin TypeScript. - Always convert at boundaries: frontend displays tokens; contract stores stroops.
- Round consistently: prefer rounding toward platform on fee splits to avoid deficit.
| Token Amount | Stroops |
|---|---|
| 1.0 XLM | 10,000,000 |
| 0.0000001 XLM | 1 |
| 1,000,000.5 XLM | 10,000,000,500,000 |
- Frontend:
src/lib/soroban.tsprovidestoStroops(amount: string | number): bigintandfromStroops(stroops: bigint | string | number): string.src/utils/format.tsprovidesformatStroop(stroop: bigint): string.
- Contract: All function arguments/returns use
i128for stroop amounts.
❌ let amount = 0.1 * 10_000_000; // 999999.9999999 due to float error
✅ let amount = 1_000_000n; // 0.1 XLM as bigint stroops