Poverty simulator
- macOS Tahoe 26+ with Xcode 26+
- iOS 17+ deployment target
- Swift 6
- XcodeGen (
brew install xcodegen)
- Clone the repo
git clone <repo-url> && cd bulkie- Configure secrets
cp Secrets.xcconfig.example Secrets.xcconfigEdit Secrets.xcconfig with your Bulk Exchange API endpoints and Privy credentials.
- Generate the Xcode project
xcodegen generate- Open in Xcode
open bulkie.xcodeproj- Build & Run on a simulator or physical device.
The app follows MVVM with a clean separation between the reusable SDK layer and the SwiftUI app.
bulkie/
├── BulkSDK/ # Local Swift Package (reusable)
│ └── Sources/BulkSDK/
│ ├── Base58.swift # Base58 encoder/decoder
│ ├── BulkAPI.swift # HTTP client for REST endpoints
│ ├── BulkModels.swift # Shared types, config, JSON parsing
│ ├── BulkSocket.swift # WebSocket client (ticker, account, orders)
│ ├── MessageSigner.swift # Protocol for Ed25519 message signing
│ ├── RingBuffer.swift # Generic fixed-capacity circular buffer
│ └── WincodeSerializer.swift # Binary serialization for Bulk exchange
│
├── bulkie/ # iOS App
│ ├── BulkieApp.swift # App entry point
│ ├── Info.plist # Font registration & runtime config keys
│ │
│ ├── Design/
│ │ └── DesignSystem.swift # Colors, typography (Satoshi), button styles, glassmorphism
│ │
│ ├── Models/
│ │ └── Models.swift # MarketType, RunConfig, RunResult, RiskLevel
│ │
│ ├── Services/
│ │ ├── AccountStore.swift # Account balance & positions (ObservableObject)
│ │ ├── BulkPriceService.swift # PriceServiceProtocol backed by live WS data
│ │ ├── GameEngine.swift # Multiplier calculation, crash detection
│ │ ├── HapticService.swift # UIKit haptic feedback
│ │ ├── MarketDataStore.swift # Ticker cache & price streams
│ │ ├── PrivyManager.swift # Privy auth (email OTP, passkeys), Solana wallet
│ │ ├── SigningService.swift # Transaction signing via Privy
│ │ └── TradingService.swift # High-level open/close position API
│ │
│ ├── ViewModels/
│ │ ├── AppState.swift # Navigation state
│ │ ├── LiveRunViewModel.swift # Live game state, chart points, PnL slogans
│ │ └── LobbyViewModel.swift # Game configuration
│ │
│ ├── Views/
│ │ ├── AuthGateView.swift # Auth flow gate
│ │ ├── HomeView.swift # Home screen with game cards
│ │ ├── LobbyView.swift # Market picker, bet config, launch
│ │ ├── LiveRunView.swift # Live game (chart, HUD, eject)
│ │ ├── ResultView.swift # Game result screen
│ │ ├── HistoryView.swift # Past runs
│ │ ├── HowItWorksView.swift # Game explainer
│ │ ├── LoginView.swift # Email OTP & Passkey login
│ │ └── Components/
│ │ └── LiveChartView.swift # Canvas-based real-time price chart
│ │
│ └── Resources/
│ ├── Assets.xcassets # App icon, images (moonordoom, perps)
│ └── Fonts/ # Satoshi-Light.otf, Satoshi-Regular.otf
│
├── project.yml # XcodeGen project definition
├── Secrets.xcconfig # Build-time secrets (git-ignored)
└── Secrets.xcconfig.example # Template for secrets
| Dependency | Purpose |
|---|---|
| Privy iOS SDK (v2.8.0) | Authentication (email OTP, passkeys) and embedded Solana wallet |
| BulkSDK (local package) | Bulk Exchange HTTP/WebSocket clients, binary serialization, Base58 |
- Pick a market (BTC-USD, ETH-USD, or SOL-USD)
- Set your bet amount
- A real 50x leveraged long position is opened on the Bulk Exchange
- Watch the live price chart -- your PnL updates in real time
- Eject to cash out, or get crashed if price drops 1%+
All positions are real, backed by the Bulk Exchange API with live market data streamed over WebSocket.
Secrets are injected via .xcconfig files and read from Bundle.main.infoDictionary at runtime:
| Key | Description |
|---|---|
BULK_HTTP_BASE_URL |
Bulk Exchange REST API base URL |
BULK_WS_URL |
Bulk Exchange WebSocket URL |
BULK_PASSKEY_RP |
Passkey relying party domain |
PRIVY_APP_ID |
Privy application ID |
PRIVY_CLIENT_ID |
Privy client ID |
- Chart points capped at 300 with automatic downsampling of older data
- Canvas rendering at 30fps (animation timeline)
- UI updates throttled to 60fps max
- Cached
NumberFormatterinstances - Timer updates at 250ms intervals
- Session persistence avoids redundant bootstrap on app reopen