Turn your ledger data into social proof. A shareable, monthly summary of your impact on the Stellar network.
Stellar Wrap is a "Spotify Wrapped"-style experience built specifically for the Stellar community.
Block explorers are great for data, but terrible for stories. Stellar Wrap takes your raw, complex on-chain historyโtransactions, smart contract deployments, NFT buysโand transforms it into a beautiful, personalized visual story that anyone can understand and share.
By simply connecting your wallet, you get a dynamic snapshot of your month on Stellar, highlighting your achievements and assigning you a unique on-chain persona based on your activity.
Itโs more than just stats; itโs a tool for builders to prove their contributions and for users to flex their participation in the Stellar ecosystem.
In Web3, your on-chain history is your resume, your identity, and your reputation. But right now, that reputation is hidden behind confusing transaction hashes.
Stellar Wrap solves the visibility gap:
- For Builders & Developers: It's hard to showcase the immense value of deploying open-source Soroban contracts. Stellar Wrap makes their code contributions visible and shareable to non-technical users.
- For the Community: We lack easy, viral loops to share excitement about whatโs happening on Stellar. This tool gives everyone a reason to post about their on-chain life on social media.
- For Users: It turns isolated transactions into a sense of progress and belonging within the ecosystem.
- Connect: Connect your Stellar wallet (e.g., Freighter, xBull) to our web app.
- Analyze: Our backend crunches your on-chain history for the month, pulling data on payments, DEX trades, Soroban interactions, and NFTs.
- Visualize: The frontend presents this data as a slick, animated story, highlighting your key stats.
- Persona: Based on your specific behavior, you get assigned a fun archetype (e.g., "The Soroban Architect," "The DeFi Patron," "The Diamond Hand").
- Share: Generate a beautiful, branded image card ready for one-click sharing to X (Twitter), Farcaster, etc.
This diagram shows the complete data flow from wallet connection through to the share card generation, highlighting where Horizon API and Soroban contract interactions occur.
flowchart TD
Wallet[("๐ Wallet<br/>(Freighter/Albedo/WalletConnect)")]
subgraph Connect ["๐ฑ Connect Phase"]
ConnectPage["/connect<br/>Wallet Connection"]
WalletConnect["WalletConnect<br/>Protocol"]
end
subgraph Indexing ["โ๏ธ Indexing Phase"]
IndexerService["indexerService<br/>/api/wrapped"]
HorizonAPI["๐ Horizon API<br/>(Stellar RPC)"]
IndexedDBCache["๐พ IndexedDB Cache"]
end
subgraph StateManagement ["๐๏ธ State Management"]
WrapStore[("useWrapperStore<br/>address, period, network,<br/>status, error, result")]
RateLimitStore[("useRateLimitStore<br/>isRateLimited, resetTime")]
TransactionStore[("useTransactionStore<br/>transactionHash, status")]
end
subgraph PersonaGeneration ["๐ญ Persona Generation"]
PersonaAction["generatePersonaDescription<br/>(AI Server Action)"]
OpenAI["๐ค OpenAI GPT-4o-mini"]
end
subgraph SorobanContracts ["๐ Soroban Contracts"]
ContractBridge["contractBridge.ts<br/>getContractInstance()"]
SorobanRPC["๐ Soroban RPC<br/>(Smart Contract Calls)"]
end
subgraph UIScreens ["๐ฅ๏ธ UI Screens"]
LoadingPage["/loading<br/>Indexing Progress"]
TopDapps["/top-daps<br/>DApp Interactions"]
Transactions["/transactions-of-fury<br/>Transaction History"]
VibeCheck["/vibe-check<br/>Vibe Analysis"]
PersonaPage["/persona<br/>Persona Reveal"]
SharePage["/share<br/>Share Card Generation"]
end
Wallet -->|"public key"| ConnectPage
ConnectPage -->|"session"| WalletConnect
WalletConnect -->|"address, network"| IndexerService
IndexerService -->|"fetch account data"| HorizonAPI
HorizonAPI -->|"transactions, payments"| IndexerService
IndexerService -->|"cache results"| IndexedDBCache
IndexedDBCache -->|"cached data"| IndexerService
IndexerService -->|"wrapped data"| WrapStore
IndexerService -->|"429 errors"| RateLimitStore
WrapStore -->|"read data"| LoadingPage
LoadingPage -->|"display progress"| TopDapps
TopDapps -->|"read dapps"| WrapStore
TopDapps -->|"navigate"| Transactions
Transactions -->|"read transactions"| WrapStore
Transactions -->|"navigate"| VibeCheck
VibeCheck -->|"read vibes"| WrapStore
VibeCheck -->|"navigate"| PersonaPage
PersonaPage -->|"read metrics"| WrapStore
PersonaPage -->|"stream persona"| PersonaAction
PersonaAction -->|"generate description"| OpenAI
OpenAI -->|"persona text"| PersonaAction
PersonaPage -->|"navigate"| SharePage
SharePage -->|"read address, network"| WrapStore
SharePage -->|"mint action"| ContractBridge
ContractBridge -->|"contract call"| SorobanRPC
SorobanRPC -->|"transaction hash"| TransactionStore
TransactionStore -->|"mint status"| SharePage
classDef wallet fill:#dbeafe,stroke:#2563eb,color:#0f172a
classDef connect fill:#e0e7ff,stroke:#4f46e5,color:#0f172a
classDef indexing fill:#fef3c7,stroke:#d97706,color:#0f172a
classDef state fill:#f8fafc,stroke:#64748b,color:#0f172a
classDef persona fill:#fce7f3,stroke:#db2777,color:#0f172a
classDef soroban fill:#ede9fe,stroke:#7c3aed,color:#0f172a
classDef ui fill:#dcfce7,stroke:#16a34a,color:#0f172a
class Wallet wallet
class ConnectPage,WalletConnect connect
class IndexerService,HorizonAPI,IndexedDBCache indexing
class WrapStore,RateLimitStore,TransactionStore state
class PersonaAction,OpenAI persona
class ContractBridge,SorobanRPC soroban
class LoadingPage,TopDapps,Transactions,VibeCheck,PersonaPage,SharePage ui
| Component | Location | Purpose |
|---|---|---|
| Horizon API | src/services/horizonIndexer.ts |
Fetches account data, payments, and transactions from Stellar Horizon |
| Indexer Service | app/services/indexerService.ts |
Orchestrates indexing with caching and rate limiting |
| Zustand Stores | app/store/, src/store/ |
Manages application state (wrap data, transactions, rate limits) |
| Contract Bridge | app/utils/contractBridge.ts |
Interfaces with Soroban smart contracts for minting |
| Persona Generator | app/actions/generate-persona.ts |
AI-powered persona description generation |
These Mermaid diagrams are intentionally GitHub-compatible so new contributors can preview the full app journey directly in the README.
flowchart TD
Landing["Landing<br/>/"]
Connect["Connect<br/>/connect"]
Manual["Manual address entry"]
Freighter["Freighter wallet"]
Albedo["Albedo wallet"]
WalletConnect["WalletConnect wallet"]
InvalidAddress["Invalid address error"]
WalletDisconnected["Wallet disconnect or rejected connection"]
Demo["Demo mode shortcut<br/>mock address + mock data"]
Loading["Loading / Indexing<br/>/loading"]
RateLimited["Horizon rate limiting<br/>retry / wait state"]
IndexingFailure["Indexing failure<br/>fallback or recovery"]
TopDapps["Top Dapps<br/>/top-daps"]
Transactions["Transactions of Fury<br/>/transactions-of-fury"]
VibeCheck["Vibe Check<br/>/vibe-check"]
Persona["Persona Reveal<br/>/persona"]
Share["Share<br/>/share"]
Mint["Mint wrapped card<br/>wallet signing"]
Landing --> Connect
Connect --> Manual
Connect --> Freighter
Connect --> Albedo
Connect --> WalletConnect
Connect --> Demo
Manual -->|valid Stellar address| Loading
Manual -->|invalid format or failed validation| InvalidAddress
Freighter -->|public key granted| Loading
Freighter -->|rejected / unavailable| WalletDisconnected
Albedo -->|public key granted| Loading
Albedo -->|rejected / unavailable| WalletDisconnected
WalletConnect -->|session approved| Loading
WalletConnect -->|disconnect / rejection| WalletDisconnected
Demo --> Loading
Loading -->|7 indexing steps complete| TopDapps
Loading -->|HTTP 429 from Horizon| RateLimited
RateLimited -->|retry after reset| Loading
Loading -->|step error| IndexingFailure
IndexingFailure -->|retry / fallback mock data| Loading
TopDapps --> Transactions
Transactions --> VibeCheck
VibeCheck --> Persona
Persona --> Share
Share --> Mint
classDef connect fill:#dbeafe,stroke:#2563eb,color:#0f172a
classDef loading fill:#fef3c7,stroke:#d97706,color:#0f172a
classDef stats fill:#dcfce7,stroke:#16a34a,color:#0f172a
classDef share fill:#ede9fe,stroke:#7c3aed,color:#0f172a
classDef error fill:#fee2e2,stroke:#dc2626,color:#0f172a
class Connect,Manual,Freighter,Albedo,WalletConnect,Demo connect
class Loading,RateLimited,IndexingFailure loading
class TopDapps,Transactions,VibeCheck,Persona stats
class Share,Mint share
class InvalidAddress,WalletDisconnected error
flowchart LR
ConnectPage["Connect page<br/>/connect"]
LoadingPage["Loading page<br/>/loading"]
StatsPages["Stats pages<br/>/top-daps, /transactions-of-fury, /vibe-check"]
PersonaPage["Persona page<br/>/persona"]
SharePage["Share page<br/>/share"]
MintAction["Mint action<br/>wallet signing"]
WrapStore[("useWrapStore<br/>address, period, network, status, error, result, cacheMeta, indexing progress")]
RateLimitStore[("useRateLimitStore<br/>isRateLimited, resetTime, retryAttempt, message")]
TransactionStore[("useTransactionStore<br/>transactionState, transactionHash, transactionError")]
MockData[("mockData / GOLDEN_USER<br/>demo and fallback data")]
Indexer["indexAccount + IndexerEventEmitter<br/>Horizon indexing events"]
PersonaAction["generatePersonaDescription<br/>streamed persona copy"]
ConnectPage -->|writes address, status, error| WrapStore
ConnectPage -->|demo shortcut reads| MockData
LoadingPage -->|reads address, period, network| WrapStore
LoadingPage -->|writes status, result, cacheMeta, indexing progress, indexingError| WrapStore
LoadingPage -->|calls| Indexer
Indexer -->|step progress and completion| WrapStore
Indexer -->|429 metadata| RateLimitStore
LoadingPage -->|fallback / demo result| MockData
StatsPages -->|read result.dapps, transactions, vibes| WrapStore
PersonaPage -->|reads result persona metrics| WrapStore
PersonaPage -->|streams description from| PersonaAction
SharePage -->|reads wallet address and network| WrapStore
SharePage -->|reads display fallbacks| MockData
MintAction -->|reads address and network| WrapStore
MintAction -->|writes transaction lifecycle| TransactionStore
SharePage -->|renders mint status| TransactionStore
classDef connect fill:#dbeafe,stroke:#2563eb,color:#0f172a
classDef loading fill:#fef3c7,stroke:#d97706,color:#0f172a
classDef stats fill:#dcfce7,stroke:#16a34a,color:#0f172a
classDef share fill:#ede9fe,stroke:#7c3aed,color:#0f172a
classDef store fill:#f8fafc,stroke:#64748b,color:#0f172a
class ConnectPage connect
class LoadingPage,Indexer,RateLimitStore loading
class StatsPages,PersonaPage,PersonaAction stats
class SharePage,MintAction,TransactionStore share
class WrapStore,MockData store
stateDiagram-v2
[*] --> Idle
Idle --> Initializing: startIndexing()
Initializing --> FetchingTransactions: complete initializing
FetchingTransactions --> FilteringTimeframes: complete fetching-transactions
FilteringTimeframes --> CalculatingVolume: complete filtering-timeframes
CalculatingVolume --> IdentifyingAssets: complete calculating-volume
IdentifyingAssets --> CountingContracts: complete identifying-assets
CountingContracts --> Finalizing: complete counting-contracts
Finalizing --> Ready: complete finalizing
Ready --> [*]
Initializing --> IndexingError: step error
FetchingTransactions --> RateLimited: Horizon 429
FetchingTransactions --> IndexingError: step error
FilteringTimeframes --> IndexingError: step error
CalculatingVolume --> IndexingError: step error
IdentifyingAssets --> IndexingError: step error
CountingContracts --> IndexingError: step error
Finalizing --> IndexingError: step error
RateLimited --> FetchingTransactions: retry after reset
IndexingError --> Initializing: retry from start
IndexingError --> Ready: fallback mock data
Initializing --> Cancelled: cancelIndexing()
FetchingTransactions --> Cancelled: cancelIndexing()
FilteringTimeframes --> Cancelled: cancelIndexing()
CalculatingVolume --> Cancelled: cancelIndexing()
IdentifyingAssets --> Cancelled: cancelIndexing()
CountingContracts --> Cancelled: cancelIndexing()
Finalizing --> Cancelled: cancelIndexing()
Cancelled --> Idle: resetIndexing()
We look beyond simple payments to capture the full spectrum of Stellar's vibrant ecosystem:
- ๐งโโ๏ธ Soroban Builder Stats: Contracts deployed and unique user interactions. (Critical for developer reputation!).
- ๐ค dApp Interactions: Which ecosystem projects did you support the most?
- ๐จ NFT Activity: New mints collected and top creators supported.
- ๐ธ Network Volume: A summary of your general transaction activity.
- ๐ Your Monthly Persona: A gamified badge that reflects your unique contribution style.
This project is designed to support the growth of the Stellar network by:
- Incentivizing Building: Publicly celebrating developers who ship code creates positive reinforcement. A "Soroban Architect" badge is a social flex that encourages more building.
- Driving Viral Activity: Every shared Stellar Wrap card is organic marketing for the blockchain, showing the world that Stellar is active and being used.
- Increasing Retention: Giving users a personalized summary fosters a sense of ownership and encourages them to come back next month to beat their stats.
- Frontend: Next.js, React, TailwindCSS
- Animations: Framer Motion
- Wallet Connection: Stellar SDK, Freighter integration
- Image Generation:
satori/html2canvasfor creating shareable social cards.
- Node.js >= 18.0.0
- pnpm >= 9.0.0
npm install -g pnpm@9Copy .env.example to .env.local and set:
| Variable | Description |
|---|---|
NEXT_PUBLIC_CONTRACT_ADDRESS_MAINNET |
Soroban contract address on mainnet (56-char, C...). |
NEXT_PUBLIC_CONTRACT_ADDRESS_TESTNET |
Soroban contract address on testnet (56-char, C...). |
NEXT_PUBLIC_CONTRACT_ADDRESS |
(Optional) Legacy: used for both networks if the two above are not set. |
NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID |
WalletConnect project ID (optional). |
NEXT_PUBLIC_PLAUSIBLE_DOMAIN |
Plausible Analytics domain for privacy-friendly page-view tracking (optional). |
Contract addresses are loaded per network; the app uses the selected network (mainnet/testnet) to choose the contract. When you switch networks in the UI, the contract instance is re-loaded for the new network.
### Running tests
**Unit & Integration Tests:**
```bash
pnpm install
pnpm testIf you see Cannot find module 'ansi-styles' when running pnpm test, run a clean install:
rm -rf node_modules && pnpm install
pnpm testEnd-to-End (E2E) Tests with Playwright:
Run the full user journey (landing โ connect โ loading โ persona โ share):
# Run e2e tests headlessly
pnpm e2e
# Run with interactive UI (recommended for development)
pnpm e2e:uiTests mock the Horizon API and validate:
- Manual wallet address entry
- Wallet connection (Freighter/Albedo)
- Loading/indexing progress
- Persona reveal animation
- Share card download
E2E tests run automatically in CI on pull requests and push to main.
See TESTING.md for full Lighthouse CI documentation, score thresholds, and troubleshooting.
The application exposes two HTTP API routes. A machine-readable OpenAPI 3.1 spec is also available.
TypeScript request/response types live in src/types/api.ts.
Returns aggregated on-chain statistics for a Stellar address.
Query parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
accountId |
string |
โ Yes | โ | Stellar public key (56 chars, starts with G) |
network |
string |
No | mainnet |
mainnet or testnet |
period |
string |
No | monthly |
weekly, monthly, or yearly |
Cache behaviour: Results are cached in IndexedDB for 60 minutes. Subsequent requests within that window return cached: true and may trigger a background re-index (refreshingInBackground: true).
Example request
curl "https://stellar-wrap.vercel.app/api/wrapped?accountId=GAAZI4TCR3TY5OJHCTJC2A4QSY6CJWJH5IAJTGKIN2ER7LBNVKOCCWN&network=mainnet&period=monthly"Example response (200)
{
"username": "alice.stellar",
"address": "GAAZI4TCR3TY5OJHCTJC2A4QSY6CJWJH5IAJTGKIN2ER7LBNVKOCCWN",
"totalTransactions": 142,
"totalVolume": 58432.5,
"percentile": 87,
"persona": "The DeFi Patron",
"personaDescription": "You move capital with purpose across Stellar's DEX.",
"dapps": [
{ "name": "Stellar DEX", "transactions": 80, "color": "#6366f1", "gradient": "linear-gradient(135deg,#6366f1,#8b5cf6)" }
],
"vibes": [
{ "type": "Power User", "percentage": 72, "color": "#f59e0b", "label": "Power User" }
],
"cached": false,
"cacheTimestamp": null,
"refreshingInBackground": false
}Error responses
| Status | Meaning |
|---|---|
400 |
Missing or invalid accountId, network, or period |
404 |
Account not found on the specified network |
429 |
Horizon rate limit exceeded โ retry after a short delay |
500 |
Unexpected server or Horizon error |
Returns a 1200 ร 1200 PNG share card image, rendered on Vercel Edge Runtime.
Query parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
username |
string |
No | StellarUser |
Username shown on the card |
transactions |
string |
No | 0 |
Total transaction count |
persona |
string |
No | Network Pioneer |
Archetype label |
topVibe |
string |
No | Steady |
Top vibe label |
vibePercentage |
string |
No | 0 |
Top vibe percentage (0โ100) |
archetypeImage |
string |
No | (derived) | Path to archetype image under /public |
Cache: Cache-Control: public, s-maxage=86400, stale-while-revalidate=604800 โ CDN-cached for 24 h, stale-while-revalidate for 7 days.
Example request
curl -o share-card.png \
"https://stellar-wrap.vercel.app/api/og?username=alice&transactions=142&persona=The+DeFi+Patron&topVibe=Power+User&vibePercentage=72"Response: Binary PNG (Content-Type: image/png).
Our immediate focus is on delivering a polished MVP for the community:
- โ Seamless wallet integration (Freighter/Albedo).
- โ Core data fetching and aggregation logic for a 30-day period.
- โ Developing the persona assignment algorithm.
- โ Building the dynamic social media card generator.
- โ Live public release for community testing.
Use a Conventional Commitโstyle format for all change lists (CLs) in this repo:
<type>(<scope>): <short summary in present tense>
[optional body]
[optional footer(s)]
- feat: new user-facing feature (UI, flow, interaction)
- fix: bug fix (visual, logic, or integration)
- refactor: code refactor that doesnโt change behavior
- style: purely visual changes (spacing, colors, typography) with no behavior change
- chore: tooling, configs, dependency bumps, project plumbing
- docs: documentation only (README, comments)
- test: adding or updating tests only
Use a small, descriptive scope in parentheses to indicate the area you touched. Examples for this project:
- landing: landing hero, CTA (
LandingPage,page.tsx) - connect:
/connectpage and wallet flow - loading:
/loadingpage and wrap animation - vibe-check:
/vibe-checkpage, vibes visualization - persona:
/personaarchetype reveal - share:
/sharepage, share card and menus - store: Zustand stores (
wrapStore, etc.) - theme:
globals.css, Tailwind theme tokens - layout:
app/layout.tsx, root shell and providers - utils: helpers like
walletConnect.ts
If a scope doesnโt fit, you can omit it: feat: add keyboard shortcuts.
- Single-file feature
feat(landing): add weekly/monthly/yearly period selector
- Cross-page flow change
feat(flow): wire connect -> loading -> persona with wrap store
- Bug fix
fix(connect): show error when Freighter is missing instead of hanging
- Visual tweak
style(persona): align oracle heading with progress indicator
- Tooling / config
chore(store): introduce canonical wrap store for frontend data
Use the body to add context when needed:
feat(share): use canonical wrap data in share card
- read wrap data from useWrapStore
- keep mockData only in loading for now
For breaking changes or references:
feat(store): consolidate state into wrapStore
BREAKING CHANGE: legacy store exports removed; update imports to useWrapStore.
