Ever Sovereign Wallet (B2B2C)
B2B2C sovereign wallet integration: migration + edge function + dashboard panel.
Status: Phase 0 shipped (migration + Edge Function + dashboard panel). Phase 1 pending first-customer config.
What it is: Integration between medOS-ultra (this repo) and the Ever Sovereign Wallet desktop/stick app deployed one-per-room in partner nursing homes. Residents speak “Hey Ever, call a nurse” and the request lands in the medOS nursing home dashboard for staff to acknowledge.
Regulatory framing: Supplementary voice channel, NOT a primary nurse-call system. Physical pull-cords / bedside buttons must remain in place. Wellness device, not a medical device.
1. Architecture
┌──────────────────────────────────────────────────────────┐
│ Nursing home room │
│ ┌────────────────────────────────────────────────────┐ │
│ │ Ever Sovereign Wallet (Mac/Win/Linux or Android) │ │
│ │ • Per-resident health wallet identity │ │
│ │ • "Hey Ever" voice assistant │ │
│ │ • call_nurse + request_help voice actions │ │
│ │ • FacilityAlertService (HMAC-signed webhook POST) │ │
│ └───────────────────────┬────────────────────────────┘ │
└──────────────────────────┼───────────────────────────────┘
│ HTTPS POST
│ X-Ever-Signature: sha256=<hmac>
▼
┌──────────────────────────────────────────────────────────┐
│ medOS-ultra (this repo) │
│ │
│ ┌─ Supabase Edge Function: ever-wallet-alerts ──────┐ │
│ │ 1. Lookup device by wallet_address │ │
│ │ 2. Verify HMAC-SHA256 against device secret │ │
│ │ 3. Upsert into ever_wallet_alerts (dedup) │ │
│ │ 4. Update device.last_seen_at │ │
│ └──────────────┬───────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─ Supabase tables ────────────────────────────────┐ │
│ │ ever_wallet_devices — paired sticks │ │
│ │ ever_wallet_alerts — alert rows + lifecycle │ │
│ │ ever_wallet_alerts_audit — append-only audit │ │
│ │ (RLS + realtime publication enabled) │ │
│ └──────────────┬───────────────────────────────────┘ │
│ │ realtime subscription │
│ ▼ │
│ ┌─ NursingHomeDashboard.tsx ───────────────────────┐ │
│ │ <EverAlertsPanel /> │ │
│ │ • Priority-sorted pending alerts │ │
│ │ • [I'm coming] / [Resolved] buttons │ │
│ │ • SLA countdown badges │ │
│ │ • Updates Supabase row via everAlertService │ │
│ └──────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────┘
What this integration intentionally does NOT do:
- No NestJS/Moleculer service changes
- No NATS event publishing
- No Kaigo billing engine changes
- No touching of the hospital admission workflow
- No modification of existing nursing_home_residents, nursing_home_rooms, or kaigo_facilities tables
The entire integration lives in 5 files: one migration, one Edge Function, one frontend service, one React panel, plus a one-line mount in NursingHomeDashboard.tsx.
2. Files
| File | Purpose | LOC |
|---|---|---|
web/supabase/migrations/20260416_ever_wallet_alerts.sql |
3 tables + RLS + triggers + realtime publication | ~290 |
web/supabase/functions/ever-wallet-alerts/index.ts |
HMAC-verified webhook receiver | ~210 |
web/src/services/ever-wallet/everAlertService.ts |
Frontend Supabase wrapper (list / ack / resolve / subscribe / pair) | ~260 |
web/src/containers/nursing-home/EverAlertsPanel.tsx |
Realtime React panel mounted in the nursing home dashboard | ~380 |
web/src/containers/nursing-home/NursingHomeDashboard.tsx |
One-line mount above the existing Care Alerts section | +3 |
3. Data Model
ever_wallet_devices
One row per paired stick. Persistent — represents the hardware bound to a room.
| Column | Type | Notes |
|---|---|---|
id |
UUID PK | |
wallet_address |
TEXT UNIQUE | Ever wallet EdDSA/Ethereum address |
device_label |
TEXT | Human label (“Room 204 stick”) |
facility_id |
UUID FK → kaigo_facilities |
Nullable (staging devices) |
resident_id |
UUID FK → nursing_home_residents |
Nullable |
room_id |
UUID FK → nursing_home_rooms |
Nullable |
hmac_secret |
TEXT | 32-byte hex shared secret, rotated at pairing |
ack_callback_url |
TEXT | Optional reverse webhook for “nurse coming” TTS |
status |
enum | active / paused / retired |
paired_at, paired_by |
timestamptz / UUID | Audit |
last_seen_at |
timestamptz | Updated on every alert |
metadata |
JSONB | { residentName, roomLabel, language, ... } |
FK constraints are added via DO $$ ... $$ blocks that check for target table existence — safe against partial deploys.
ever_wallet_alerts
One row per alert, full lifecycle.
| Column | Type | Notes |
|---|---|---|
id |
UUID PK | medOS-generated |
client_alert_id |
TEXT | Wallet-generated (alert_1744838400_a1b2c3d4) — used for dedup |
device_id |
UUID FK | |
facility_id, resident_id, room_id |
UUID | Denormalized for RLS + fast queries |
resident_name_cached, room_label_cached |
TEXT | Snapshot for display |
kind |
enum | call_nurse / request_help |
priority |
enum | emergency / urgent / routine / request |
subtype |
TEXT | For request_help: water/food/bathroom/comfort/entertainment/family_call/other |
reason |
TEXT | LLM intent summary of what the resident said |
raw_payload |
JSONB | Full original webhook body (audit) |
status |
enum | pending / ack / resolved / timeout / cancelled |
created_at, received_at |
timestamptz | Wallet creation vs. medOS ingestion time |
ack_at, ack_by, ack_by_name |
Who acknowledged | |
resolved_at, resolved_by, resolved_by_name |
Who resolved | |
sla_deadline_at |
timestamptz | Computed via trigger: emergency=60s, urgent=180s, routine=900s, request=900s |
reverse_ack_* |
Phase 1 — for sending “nurse coming” back to the wallet |
Natural key: UNIQUE (device_id, client_alert_id) — wallet retries become no-op upserts.
ever_wallet_alerts_audit
Append-only log written by the ever_wallet_write_audit trigger. Never manually edited. One row per lifecycle event.
4. Webhook Contract
POST {SUPABASE_URL}/functions/v1/ever-wallet-alerts
Headers
Content-Type: application/json
X-Ever-Alert-Id: <client alert id>
X-Ever-Alert-Priority: emergency|urgent|routine|request
X-Ever-Alert-Kind: call_nurse|request_help
X-Ever-Signature: sha256=<hex hmac-sha256 of body, keyed with device.hmac_secret>
Body
{
"alert": {
"id": "alert_1744838400_a1b2c3d4",
"kind": "call_nurse",
"priority": "urgent",
"subtype": null,
"reason": "pain in my hip",
"createdAt": "2026-04-16T14:30:00.000Z",
"residentId": "0xabc...",
"roomId": "room-204",
"status": "pending",
"deliveryAttempted": false
},
"facilityId": "sompo-tokyo-shinagawa",
"facilityName": "SOMPO Care Tokyo Shinagawa",
"residentName": "Tanaka",
"deliveredAt": "2026-04-16T14:30:00.200Z"
}
alert.residentId must match a wallet_address in ever_wallet_devices. The device’s hmac_secret is used to verify the signature.
Responses
| Status | Body | Meaning |
|---|---|---|
| 200 | { ok, alertId, status, slaDeadline } |
Alert stored, dashboard subscribers notified |
| 400 | { error } |
Malformed payload / missing fields |
| 401 | { error } |
Missing or invalid HMAC signature |
| 403 | { error } |
Device is paused/retired |
| 404 | { error } |
Device not paired (unknown wallet_address) |
| 405 | — | Non-POST method |
| 500 | { error } |
Internal error |
HMAC spec
signature = "sha256=" + hex(hmac_sha256(device.hmac_secret, request_body))
The body used for signing must be the exact bytes sent, before any parsing. The Edge Function reads req.text() first, then verifies, then parses.
5. Deploying Phase 0
5.1 Run the migration
# Against local dev stack
cd web
supabase db push
# Or directly:
psql "$SUPABASE_DB_URL" -f supabase/migrations/20260416_ever_wallet_alerts.sql
Verify:
SELECT table_name FROM information_schema.tables
WHERE table_name LIKE 'ever_wallet%';
-- Expect: ever_wallet_devices, ever_wallet_alerts, ever_wallet_alerts_audit
5.2 Deploy the Edge Function
cd web
supabase functions deploy ever-wallet-alerts
The function needs two env vars (already set in Supabase project config by default):
SUPABASE_URLSUPABASE_SERVICE_ROLE_KEY
5.3 Pair a test device
Option A — via frontend (Phase 1 UI, not yet built):
import { everAlertService } from '@services/ever-wallet/everAlertService';
const device = await everAlertService.pairDevice({
walletAddress: '0x1234abcd...', // from the stick
deviceLabel: 'Room 204',
facilityId: '<kaigo_facilities.id>',
residentId: '<nursing_home_residents.id>',
metadata: { residentName: 'Tanaka', roomLabel: '204', language: 'ja' },
});
console.log('HMAC secret — paste into stick:', device.hmac_secret);
Option B — via SQL (Phase 0 manual):
INSERT INTO ever_wallet_devices (
wallet_address, device_label, facility_id, resident_id, room_id,
hmac_secret, metadata
) VALUES (
'0x1234abcd...',
'Room 204',
'<facility-uuid>',
'<resident-uuid>',
'<room-uuid>',
encode(gen_random_bytes(32), 'hex'),
'{"residentName": "Tanaka", "roomLabel": "204", "language": "ja"}'::jsonb
)
RETURNING id, hmac_secret;
The returned hmac_secret is displayed once — save it; it goes into the stick’s facility config.
5.4 Configure the stick
On the Ever Sovereign Wallet desktop app (DevTools console):
await window.everWallet.facility.setConfig({
enabled: true,
webhookUrl: 'https://<project>.supabase.co/functions/v1/ever-wallet-alerts',
webhookSecret: '<hmac_secret from step 5.3>',
facilityId: '<kaigo_facilities.id>',
facilityName: 'Customer Facility Name',
residentId: '<wallet address matching ever_wallet_devices.wallet_address>',
residentName: 'Tanaka',
roomId: 'room-204',
});
5.5 End-to-end test
- On the stick, enable voice and say “Hey Ever, call a nurse, I’m in pain”
- Expect in DevTools:
[Facility] ALERT CREATEDlog + webhook delivery - Expect in medOS dashboard (http://localhost:5173/nursing-home/dashboard): new alert appears in the Voice Alerts panel within ~1 second via realtime subscription
- Click “I’m coming” on the nurse dashboard
- Alert status transitions
pending → ack - Click “Resolved” — alert fades out after 4s
6. Gaps (Phase 1)
-
[x] Netflix-style pairing flow (Phase 1 SHIPPED). Manual SQL + DevTools copy/paste is gone. Replaced with a short-TTL (90s) pairing-code handshake + Proton §5 fragment-key return path. See §9 below for full details.
-
[ ] Reverse ack channel. When nurse clicks “I’m coming”, medOS should POST to
device.ack_callback_urlso the stick TTS says “Tanaka-san, the nurse is on her way.” Implemented as a Postgres trigger + pg_net, or a second Edge Function invoked by the frontend after ack. -
[ ] Facility-scoped RLS. Current policies are permissive for
authenticated— tighten once we integrate with the medOS staff auth claim system and know the exact JWT shape. -
[ ] SLA timeout sweep. A cron job that flips
pendingalerts past theirsla_deadline_attostatus=timeoutand fires an escalation notification. -
[ ] Pairing UI. A Facility Setup screen for care managers to provision new sticks without SQL.
-
[ ] Audio capture attachment. 10s pre/post ring buffer from the wallet, uploaded to filestore on
call_nursealerts for liability + nurse tone-of-voice context. -
[ ] Worklist integration. Create a
medical-worklisttask per alert so acked alerts appear in the nurse’s main task queue (Phase 2). -
[ ] Push notification fanout. Hook into
services/messaging/notification/so LINE Works / FCM / SMS fire onemergencypriority. -
[ ] Family portal mirror. Family members see alert history (sanitized) in the family portal.
7. Operational Notes
- Audit log:
ever_wallet_alerts_auditis append-only. Never DELETE or UPDATE rows — they’re a legal record. Retention policy TBD with first customer (30/90/forever). - HMAC secret rotation: To rotate, generate a new secret, UPDATE
ever_wallet_devices.hmac_secret, then update the stick config. There’s a brief window during which in-flight alerts may fail signature verification — accept that, or add ahmac_secret_previouscolumn in Phase 1 for graceful rotation. - Multi-facility queries: The frontend service passes
facilityIdas a filter on every query. Phase 1 enforces this at the RLS layer via JWT claims. - Realtime channel naming:
ever_wallet_alerts:${facilityId}— one channel per facility keeps subscriber counts bounded. - Supabase cost: Each alert = one Edge Function invocation + one row insert + one realtime broadcast. At 100 alerts/day per facility, cost is negligible (well under $1/month per facility at current Supabase pricing).
9. Phase 1 — Netflix-style secure pairing (SHIPPED)
The Phase 0.5 manual SQL + DevTools flow is replaced by a full self-service pairing system with cryptographic separation from edh-central infrastructure.
Files shipped in this repo
| File | Purpose |
|---|---|
web/supabase/migrations/20260417_ever_wallet_pairing_codes.sql |
ever_wallet_pairing_codes table (90s TTL, single-use) + audit table + extends ever_wallet_devices with paired_via, device_ephemeral_pub, paired_with_code_id columns + cleanup function |
web/supabase/functions/ever-wallet-pair/index.ts |
Deno Edge Function: verifies code → generates HMAC → wraps with nacl.box → consumes code → returns wrapped secret + stick config |
web/src/containers/nursing-home/EverDevicePairingDialog.tsx |
MUI v7 admin dialog with 6-digit-code + quick-setup tabs, 90s countdown, auto-regeneration, realtime transition to “Paired successfully” |
Operator setup (one-time, per Supabase project)
# 1. Generate server static X25519 keypair (Deno one-liner)
deno run -A -r - <<'EOF'
import nacl from 'https://esm.sh/tweetnacl@1.0.3'
import { encodeBase64 } from 'https://esm.sh/tweetnacl-util@0.15.1'
const kp = nacl.box.keyPair()
console.log('EVER_PAIR_STATIC_PRIV=' + encodeBase64(kp.secretKey))
console.log('EVER_PAIR_STATIC_PUB=' + encodeBase64(kp.publicKey))
EOF
# 2. Set as Supabase secrets
supabase secrets set EVER_PAIR_STATIC_PRIV="<from step 1>"
supabase secrets set EVER_PAIR_STATIC_PUB="<from step 1>"
# 3. Apply the migration
supabase db push # or psql with 20260417_ever_wallet_pairing_codes.sql
# 4. Deploy the function
supabase functions deploy ever-wallet-pair
After this one-time setup, the admin dialog at `` can generate codes that pair sticks in ~5 seconds with no DevTools involvement.
Handshake
1. Admin clicks "Pair new device" → dialog calls:
INSERT INTO ever_wallet_pairing_codes { facility_id, resident_id, room_id, hints }
→ 6-digit code, expires_at = NOW() + 90s
2. Admin reads code to stick operator (or shares the "Quick setup" link)
3. Stick enters pairing screen, generates fresh ephemeral X25519 keypair:
const eph = nacl.box.keyPair()
const deviceId = "ever-pair-" + random(16)
POSTs to ever-wallet-pair:
{ code, devicePub: eph.publicKey, deviceIdentifier: deviceId, deviceLabel }
4. Edge Function:
a. Looks up active code (expires_at > NOW() AND consumed_at IS NULL)
b. Generates fresh HMAC secret (crypto.getRandomValues(32))
c. Inserts ever_wallet_devices row (wallet_address = deviceId, hmac_secret)
d. Wraps HMAC with nacl.box(hmacSecret, nonce, devicePub, STATIC_PRIV)
e. Consumes code atomically (race-guarded — on loss, rolls back device row)
f. Returns { deviceId, wrappedHmacSecret, serverPub, nonce, functionUrl, … }
5. Stick unwraps with nacl.box.open(..., eph.secretKey)
Persists to its OWN electron-store file (ever-facility-pair.json) encrypted
with its OWN KDF salt. Zeros + discards eph.secretKey.
Security properties
- Forward secrecy on HMAC delivery: TLS compromise does not leak the HMAC. Attacker needs either the ephemeral private key (stick RAM, <1s lifetime) or the server static private key (Supabase secret, never on the wire).
- Proof of possession: only the stick that sent the ephemeral pubkey can unwrap the response.
- 90-second TTL + single-use: leaked codes are useless after the window closes or after first consumption.
- Bound at generation: code is pre-scoped to
{facility_id, resident_id, room_id}so a leaked code can’t be redirected. - Race-guarded consumption: if two devices try to claim the same code simultaneously, only one wins; the loser’s orphan device row is rolled back.
- Revocable: flip
ever_wallet_devices.status = 'retired'in the admin UI and the next alert from that stick is rejected with 403.
Quick-setup flow
The “Quick setup” tab in the admin dialog renders the same 90s-TTL code inside an ever-pair://v1?u=<b64>&c=<code>&f=<b64>&r=<b64> URL. The stick’s pairing screen has a matching “Paste setup link” tab that parses the URL and pre-fills both the Supabase URL and the code — one paste, one click.
This is a UX accelerator, not a second security model. The code inside the URL has the same 90-second TTL and single-use semantics as a manually-entered code. A leaked URL is useless after the window closes, even though it “contains” all the pairing info.
Completely separate from edh-central
The wallet-side pairing module (src/main/facility-pairing.ts in ever-sovereign-wallet) is architecturally and cryptographically isolated from edh-central:
- Own electron-store file:
ever-facility-pair.json, not shared withever-sovereign-config.json - Own KDF salt:
ever-facility-pair:${machineId}, mathematically independent from edh-store’sever-sovereign:${machineId}SHA-256 - Own ephemeral keys: fresh
nacl.box.keyPair()per pairing, NOT the user’sethers.Walletkey - Own device identity: random
ever-pair-<16b>id, NOT the user’s Ethereum wallet address - Zero imports from
edh-store,sync,wallet,clinical, or any other B2C module - Own IPC namespace:
facility-pair:*separate fromfacility:*andsync:*
A compromise of edh-central master keys reveals nothing about the facility HMAC secret; a compromise of the facility HMAC reveals nothing about edh-central-encrypted personal health data. The two trust trees share only the OS machine ID as a seed, and apply independent KDF salts to it.
8. Cross-repo links
The wallet side lives in Ever-Healthcare-OMA/ever-sovereign-wallet. Relevant files:
src/main/facility.ts—FacilityAlertService(webhook sender)src/renderer/src/components/VoiceAssistant/types.ts—CallNurseAction,RequestHelpActionsrc/renderer/src/components/VoiceAssistant/useVoiceFunctionCaller.ts— localized executors (en/ja/zh/th)src/renderer/src/components/AlertBanner.tsx— on-stick toastdocs/B2B2C_FACILITY_ARCHITECTURE.md— wallet-side architecture doc