Skip to main content

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​

LayerWhat It IsRule
app/Next.js App Router pagesPages are thin β€” they just render components
components/UI componentsGrouped by domain: wallet/, policies/, guardians/
hooks/React Query data hooksAll data fetching lives here, never in components
lib/SDK client, auth, proxyAll backend calls go through /api/ proxy
app/api/Next.js Route HandlersThin 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:

  1. A short-lived JWT (2 hours) is stored in an httpOnly cookie
  2. Subsequent page loads read the cookie β€” no passkey prompt on every navigation
  3. Expiry redirects to /login automatically

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:

PlatformFileWhat It Does
iOS/.well-known/apple-app-site-associationLinks the app to a domain for Universal Links
Android/.well-known/assetlinks.jsonVerifies 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:

  1. The OS opens the app to app/recovery/[proposalId].tsx
  2. The screen prompts biometric login (even if not signed in)
  3. 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​

PriorityWhat It Means in Practice
ReliabilityThis app is recorded live. Every flow must work first try, every time.
On-chain proofEvery receipt links to Stellar Expert β€” makes "non-custodial" verifiable, not just claimed.
Testnet labelingA persistent amber banner is required. SCF reviewers specifically check that demos can't be mistaken for live money apps.
Env-var drivenAll 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)