On-chain inheritance, in your browser
Live app: sorowill.vercel.app
SoroWill is a trustless, on-chain inheritance protocol on Stellar Soroban. This app is the dashboard for it: create a will, check in to prove you're active, review beneficiaries and guardians, and — for anyone named as a beneficiary — verify and claim an inheritance once a will's grace period has elapsed.
- Next.js 14 (App Router)
- TypeScript (strict mode)
- Tailwind CSS 3
- @sorowill/sdk for all contract interaction and Freighter wallet handling
Wallet support: This app currently only supports the Freighter browser extension. Other Stellar wallets (Albedo, xBull, Rabet, …) are not yet supported — you will not be able to connect them. Multi-wallet support is tracked in the SDK's wallet-adapter work; see the
@sorowill/sdkpackage for progress.
git clone https://github.com/SoroWill/sorowill-app.git
cd sorowill-app
# The .nvmrc file pins Node 20 (matching CI). If you use nvm/fnm/volta, run:
# nvm use (nvm)
# fnm use (fnm)
# volta install (volta)
npm install
cp .env.example .env.local
# fill in NEXT_PUBLIC_CONTRACT_ID with your deployed SoroWill contract address
npm run devThis app depends on
@sorowill/sdk, published to npm under thesorowillorg.
| Variable | Description |
|---|---|
NEXT_PUBLIC_STELLAR_NETWORK |
Stellar network to connect to: testnet or mainnet |
NEXT_PUBLIC_CONTRACT_ID |
Address of the deployed SoroWill contract |
NEXT_PUBLIC_RPC_URL |
Soroban RPC endpoint (defaults to the public testnet RPC) |
RESEND_API_KEY |
API key for reminder emails (optional; leave unset to skip sending) |
RESEND_FROM_EMAIL |
Verified Resend sender address used for reminder emails |
KV_REST_API_URL |
Vercel KV / Upstash Redis REST URL used to persist reminder subscriptions (required for reminders; the serverless filesystem is ephemeral) |
KV_REST_API_TOKEN |
REST token for the KV store above |
REMINDER_STORE_KV_KEY |
Optional key used to store the reminder blob in the KV store (defaults to sorowill:reminder-store) |
CRON_SECRET |
Bearer token for authenticating automated reminder dispatch requests from GitHub Actions (required if using the automated GitHub Actions trigger; see Reminder delivery) |
NEXT_PUBLIC_APP_URL |
Optional public base URL used to build the unsubscribe link in reminder emails (defaults to VERCEL_URL or http://localhost:3000) |
| Route | Description |
|---|---|
/ |
Landing page explaining SoroWill |
/dashboard |
Your wills (owned and inherited), with quick check-in |
/will/new |
Multi-step form to create a new will |
/will/[id] |
Full will detail: check in, top up, update beneficiaries, cancel, trigger, release |
/inherit/[id] |
Beneficiary view — see your entitled share and claim once ready |
/verify/[id] |
Public, wallet-free verification of a will's on-chain state |
This app uses next-intl for translation strings and automatic locale detection.
en— English (default)es— Español (Spanish)
The app automatically detects the user's preferred locale based on their browser's Accept-Language header:
- If the header requests Spanish (or any variant like
es-MX,es-AR), the app displays Spanish content - If the header requests English or any unsupported locale, the app defaults to English
- Users can manually switch locales using the Language Selector button in the header (top right, displays "EN" / "ES")
- The user's language preference is stored in
localStoragefor persistence across sessions
Translation namespaces and strings are stored in src/messages/, organized by feature:
src/messages/en.json— English translationssrc/messages/es.json— Spanish translations
Both files use the same structure with these namespaces:
common— reusable UI strings (buttons, labels, form text)landing— landing page (/)dashboard— dashboard page (/dashboard)willDetail— will detail view (/will/[id])status— will status labels and descriptionsinherit— beneficiary inheritance view (/inherit/[id])
To use translations in a component:
-
Server components:
import { getTranslations } from 'next-intl/server'; const t = await getTranslations('namespace'); // Use t('key') in JSX
-
Client components:
import { useTranslations } from 'next-intl'; const t = useTranslations('namespace'); // Use t('key') in JSX
To add support for a new language:
- Create a new file
src/messages/[locale].json(e.g.,src/messages/fr.jsonfor French) - Copy the structure from
src/messages/en.jsonand translate all strings - Add the locale code to the
supportedLocalesarray insrc/i18n/request.ts - Add the locale to the
localesarray inmiddleware.ts - Test locale detection by setting your browser's language preference
middleware.ts— Handles locale detection from Accept-Language header; does not require URL prefix (e.g.,/en/dashboard— just/dashboard)src/i18n/request.ts— Loads the appropriate message file based on detected locale and provides a fallback to English
Reminders are delivered by a server-side route that can be triggered on a schedule. The app ships a lightweight JSON store for subscriptions and dispatch history, so a daily cron job can call the dispatch endpoint without exposing any secrets.
The dispatch route (/api/reminders/dispatch) computes reminder windows from each will's lastCheckin and checkinPeriodDays, sending a well-before reminder once and an imminent reminder once for each active will that still has time left. The route is protected by a CRON_SECRET bearer token when configured.
The application supports the following automated trigger mechanisms to schedule daily reminder dispatch:
File: .github/workflows/reminder-cron.yml
The primary automated trigger runs via GitHub Actions, scheduled daily at 08:00 UTC (cron: 0 8 * * *). The workflow:
- Runs on a fixed schedule every day
- Supports manual re-runs via the GitHub UI (
workflow_dispatch) - Keeps per-run logs for debugging and monitoring
- Fails visibly if the dispatch endpoint returns a non-2xx response
Required GitHub Actions Secrets:
To enable reminder delivery via GitHub Actions, set the following repository secrets (not to be confused with environment variables):
-
REMINDER_DISPATCH_URL— The full URL to your/api/reminders/dispatchendpoint- Example:
https://sorowill.vercel.app/api/reminders/dispatch - For local/private deployments, use your deployment's public URL
- Example:
-
CRON_SECRET— A secure bearer token that authenticates cron requests to the dispatch endpoint- This token is passed in the
Authorization: Bearerheader - Can be any arbitrary string; treat it as a password
- The API route validates this against the
CRON_SECRETenvironment variable
- This token is passed in the
Configuration Steps:
- Go to your GitHub repository settings → Secrets and variables → Actions
- Create a New repository secret:
- Name:
REMINDER_DISPATCH_URL - Value: Your app's dispatch endpoint URL (e.g.,
https://your-deployment.vercel.app/api/reminders/dispatch)
- Name:
- Create another New repository secret:
- Name:
CRON_SECRET - Value: A random string to serve as the bearer token (e.g., generate with
openssl rand -hex 32)
- Name:
- Ensure your deployment has the corresponding
CRON_SECRETenvironment variable set (see Environment Variables)
For testing or one-off dispatch, you can manually call the endpoint with curl:
curl --fail -X POST https://your-app.example.com/api/reminders/dispatch \
-H "Authorization: Bearer YOUR_CRON_SECRET" \
-H "Content-Type: application/json"- Single Scheduler:
.github/workflows/reminder-cron.ymlis the only scheduled trigger. A previous Vercel Cron entry that fired on the same schedule has been removed to avoid duplicate dispatch runs. - No Duplicate Triggers: Do not add a second independent scheduler unless absolutely necessary; doing so causes the dispatch route to run multiple times in quick succession. If a backup scheduler is needed, give it a distinctly different schedule (e.g., a different hour) and document the intent here.
- Race Condition Mitigation: The dispatch route records
wellBeforeSentAtandimminentSentAttimestamps per will in the KV store immediately after each send. Subscriptions with a recorded timestamp of that kind are skipped. This prevents duplicate reminders even if two dispatch runs overlap, though the lack of locking means a true race condition is still theoretically possible—yet another reason to keep a single scheduler. - Error Visibility: GitHub Actions fails the run when the dispatch endpoint returns a non-2xx response, making broken dispatch immediately visible in the Actions UI.
This repo participates in the Stellar Wave Program on Drips. Maintainer-tagged issues carry Point values, and contributors who resolve them during an active Wave earn a proportional share of that Wave's reward pool. See CONTRIBUTING.md for the contribution workflow, and https://drips.network/wave for how Wave itself works.
