System Architecture & Soroban Smart Contracts
CredChain is built natively on the Stellar Soroban WASM execution environment. Below is the visual component flow illustrating how client applications, wallet providers, Soroban RPC nodes, and smart contract ledgers interact seamlessly.
Next.js UI & WalletKit
Connects user wallets (Freighter, Albedo, xBull) to construct XDR transaction payloads and handle user signatures.
Stellar Soroban RPC Node
Dispatches JSON-RPC requests (sendTransaction, getLatestLedger) and polls on-chain events.
Rust Soroban WASM Contract
Executes state checks, updates persistent storage with extend_ttl, and emits immutable ledger event logs.
The smart contract is written in Rust using the official soroban-sdk. It implements explicit state isolation and persistent storage extensions:
- Storage TTL Extension: Soroban ledger entries are archived if left untouched. Every write invokes
extend_ttl(5000, 10000)on the entry it touched, so active records stay live. - Checks-Effects-Interactions Pattern: Contract methods perform input validation and signature authentication before mutating storage or emitting events.
- Checked Arithmetic: Protects against integer overflows and underflows during sequence or total count updates.
- Admin Authority Transfer: Includes
transfer_adminto allow smooth governance handovers without locking out institutions.
Web3 transactions often fail with cryptic XDR codes. CredChain integrates a centralized error decoder (error-decoder.ts):
- Translates Soroban revert codes (
1=NotRegistered through6=InvalidInput) and Stellar Horizon errors into human-readable titles, codes, and remedies. The mapping is pinned by unit tests against the contract enum so the two cannot drift. - Expands transaction validity windows to 300 seconds to prevent
tx_too_lateexpirations when users sign via hardware or extension wallets. - Provides global React Error Boundaries (
error.tsx) and component-level diagnostic alerts (Web3ErrorAlert.tsx).
The contract stores only id, issuer, recipient, metadata_uri, issued_at, and revoked. Human-readable detail lives inside metadata_uri as a base64 data URI:
Verification therefore performs no external fetch — the whole credential is on the ledger. An HTTPS or IPFS pointer would instead make every verifier trust a host that could change or lose the content.
The issuer's name is deliberately absent from the payload. It is read from get_institution(issuer) on-chain, so whoever wrote the metadata cannot forge it. Credentials carrying a plain-string URI still verify; the page falls back to displaying the raw value.
The contract exposes no certificate list and no total counter, and adding one would require a redeploy — which mints a new address and abandons all existing state. The registry derives both instead, from two invariants:
- Certificate ids are sequential from 1, since
NextCertIdonly ever increments and nothing deletes. revoke_certificateflips a flag but never decrementscert_count.
Together those make the sum of every institution's cert_count exactly the highest id in existence, so the registry can fetch ids 1..total with no probing. Reads are chunked to bound RPC fan-out.
The activity feed backfills the full RPC retention window on load, paging by cursor from the floor that getHealth reports. Because RPC retains roughly seven days of events, older account history is read from Horizon, which keeps far more.