Applications
Three frontends, one SDK, same seamless experience.
Rayos ships three user-facing applications. All of them are powered by @rayos/wallet-sdk and connect to the same relay backend and smart contracts.
Web Dashboardβ
Repository: web-dashboard
Tech: Next.js 16 Β· React 19 Β· shadcn/ui Β· Tailwind CSS v4 Β· TanStack Query
The primary user interface for Rayos. A full-featured wallet management app:
graph TD
subgraph WebApp["web-dashboard"]
LAND["Landing Page"]
CREATE["Create Wallet\n(3-step passkey flow)"]
LOGIN["Login\n(passkey assertion)"]
WALLET["Wallet Tab\nBalance Β· Send Β· History"]
POLICIES["Policies Tab\nSpend Limits Β· Sessions Β· Allow-list"]
GUARDIANS["Guardians Tab\nAdd Guardians Β· Recovery Status"]
end
LAND --> CREATE
LAND --> LOGIN
CREATE --> WALLET
LOGIN --> WALLET
WALLET --> POLICIES
WALLET --> GUARDIANS
How It's Structuredβ
| Layer | What It Is | Rule |
|---|---|---|
app/ | Next.js App Router pages | Pages are thin β they just render components |
components/ | UI components | Grouped by domain: wallet/, policies/, guardians/ |
hooks/ | React Query data hooks | All data fetching lives here, never in components |
lib/ | SDK client, auth, proxy | All backend calls go through /api/ proxy |
app/api/ | Next.js Route Handlers | Thin proxy layer to relay-backend (prevents CORS) |
Key Rule: Components never call the backend directly. They call hooks. Hooks call the SDK or lib/proxy.ts. The browser never knows the relay URL.
Sessions β How Login Worksβ
After WebAuthn assertion succeeds:
- A short-lived JWT (2 hours) is stored in an
httpOnlycookie - Subsequent page loads read the cookie β no passkey prompt on every navigation
- Expiry redirects to
/loginautomatically
Mobile Appβ
Repository: mobile-app
Tech: React Native 0.86 Β· Expo SDK 57 Β· expo-router Β· react-native-passkeys
The native sibling of the web dashboard β same features, but on iOS and Android with truly native biometrics.
graph TD
subgraph OnboardingGroup["(onboarding) group β No auth required"]
WELCOME["Welcome"]
CREATE["Create Wallet"]
LOGIN["Login"]
RECOVER["Recover"]
end
subgraph DashboardGroup["(dashboard) group β Auth required"]
WALLET_TAB["Wallet"]
POLICIES_TAB["Policies"]
GUARDIANS_TAB["Guardians"]
SETTINGS_TAB["Settings"]
end
WELCOME --> CREATE
WELCOME --> LOGIN
CREATE --> WALLET_TAB
LOGIN --> WALLET_TAB
What Makes Mobile Different?β
The mobile app uses the same wallet-sdk as the web app. The only platform-specific code is in native/passkey-adapter.ts:
// native/passkey-adapter.ts β the ONLY platform seam
export const walletSdk = new WalletSdk({
...config,
storage: secureStorageAdapter, // expo-secure-store
passkeyProvider: { // react-native-passkeys
createCredential,
signTransaction,
},
});
Everything else β hooks, screens, business logic β is identical to what runs on web.
Native-Specific Setupβ
For passkeys to work on real devices, the relay backend must serve domain association files:
| Platform | File | What It Does |
|---|---|---|
| iOS | /.well-known/apple-app-site-association | Links the app to a domain for Universal Links |
| Android | /.well-known/assetlinks.json | Verifies the app fingerprint for App Links |
Without these, passkeys silently fail on real hardware (they work in simulators/emulators without them).
Deep Linkingβ
Guardian recovery emails contain links like rayos://recovery/<proposalId>. When a guardian taps the link:
- The OS opens the app to
app/recovery/[proposalId].tsx - The screen prompts biometric login (even if not signed in)
- Guardian sees the proposal and approves with one tap
Demo Appβ
Repository: demo-app
Tech: Next.js 16 Β· Tailwind CSS v4 Β· Framer Motion Β· Playwright E2E
A polished, single-vertical showcase application built entirely on @rayos/wallet-sdk. It demonstrates a gasless, passkey-signed merchant checkout on Stellar Testnet.
This is what gets shown in grant application videos, live demos, and SCF reviewer calls.
flowchart LR
A(["User visits /shop"]) --> B["Browse mock catalog"]
B --> C["Click Buy Now"]
C --> D["Checkout page"]
D --> E{"Tap Pay with Passkey"}
E --> F["Biometric prompt"]
F --> G["wallet-sdk signs XDR locally"]
G --> H["relay-backend submits + sponsors gas"]
H --> I["Receipt page"]
I --> J["Link to Stellar Expert explorer"]
Design Prioritiesβ
| Priority | What It Means in Practice |
|---|---|
| Reliability | This app is recorded live. Every flow must work first try, every time. |
| On-chain proof | Every receipt links to Stellar Expert β makes "non-custodial" verifiable, not just claimed. |
| Testnet labeling | A persistent amber banner is required. SCF reviewers specifically check that demos can't be mistaken for live money apps. |
| Env-var driven | All backend URLs are env vars β no hard-coded production URLs in source. |
Live Demoβ
π rayos-demo-app.vercel.app
How All Three Apps Connectβ
graph LR
WEB["web-dashboard"] --> SDK["@rayos/wallet-sdk"]
MOB["mobile-app"] --> SDK
DEMO["demo-app"] --> SDK
SDK --> RELAY["relay-backend"]
RELAY --> CONTRACTS["wallet-contracts"]
CONTRACTS --> STELLAR["Stellar"]
All three apps are just different views on the same underlying platform. They share:
- The same SDK package
- The same relay backend
- The same smart contracts
- The same design philosophy (no mock data in production paths)