web/docs/secret-system-spec.md

317 lines
8.7 KiB
Markdown

# Edut Secret System - Wallet-First Designation + Membership Spec
## Overview
The Edut onboarding flow is deterministic and wallet-first:
1. Visitor requests designation intent from `edut.ai`.
2. Server issues one-time typed-data payload (`nonce`, `deadline`, `price`, `currency`).
3. Visitor signs intent with wallet (identity proof, no value transfer).
4. Server verifies signature and creates/updates designation record.
5. Visitor mints paid EDUT membership on-chain (intent proof).
6. Server confirms transaction and marks membership active.
7. Visitor downloads the EDUT platform app from post-mint success links.
This flow is the pre-launch identity and commerce envelope. It is not a throwaway waitlist.
## User Experience Sequence (Continue Flow)
1. Initial page state: orb, identity text, footer links.
2. First click anywhere: globe spin intensifies, `continue` appears.
3. Click `continue`: wallet explainer appears.
4. User selects `I have a wallet` or `I need a wallet`.
5. `I need a wallet` shows install guidance and remains in-page.
6. `I have a wallet` triggers wallet connection request.
7. Page calls `POST /secret/wallet/intent`.
8. Wallet signs EIP-712 typed intent.
9. Page calls `POST /secret/wallet/verify`.
10. Page requests membership quote via `POST /secret/membership/quote`.
11. Wallet submits membership mint transaction on Base.
12. Page confirms via `POST /secret/membership/confirm` and/or status poll.
13. UI shows `acknowledged · {token}` when membership is active.
14. Post-mint success state presents `download your platform` links (Desktop/iOS/Android).
15. Member opens the app, signs in with the same wallet, and receives platform updates through app notifications.
Privacy and Terms links bypass flow and navigate normally.
## Architecture
```text
Landing page -> POST /secret/wallet/intent
Wallet signs typed intent (EIP-712)
Landing page -> POST /secret/wallet/verify
Landing page -> POST /secret/membership/quote
Wallet sends paid mint tx on Base
Landing page -> POST /secret/membership/confirm
Membership active -> acknowledged state
Post-mint success -> app download links (Desktop/iOS/Android)
```
## Core Commerce Rule
1. Membership is required to purchase marketplace offers.
2. Membership is not a product/module license.
3. Offer-specific licenses/entitlements are purchased separately.
4. Membership purchase delivers initial platform access (download entry point) immediately after activation.
## Infrastructure
| Service | Domain / Endpoint | Purpose |
|---------|-------------------|---------|
| Landing UI | `edut.ai`, `edut.dev` | Continue flow + wallet intent/signature + membership mint UX |
| API | `api.edut.ai/secret/wallet/intent` | Create one-time designation intent |
| API | `api.edut.ai/secret/wallet/verify` | Verify signature and bind wallet identity |
| API | `api.edut.ai/secret/membership/quote` | Return current payable membership quote |
| API | `api.edut.ai/secret/membership/confirm` | Confirm membership tx and activation state |
| Chain | Base | Membership mint settlement and evidence |
| Database | `/var/lib/edut/secrets.db` | Durable designation + membership state |
## Deterministic State Machine
`pending_signature` -> `signature_verified` -> `pending_membership_mint` -> `membership_active`
Additional side states:
- `intent_expired`
- `quote_expired`
- `tx_unconfirmed`
- `rejected` (invalid signature, wrong chain, mismatched wallet)
- `rate_limited`
Rules:
1. No backwards transition except administrative recovery event with audit entry.
2. `membership_active` requires successful on-chain payment confirmation.
3. Signature and quote nonces are single-use; replay attempts are rejected.
4. Acknowledged UI state requires `membership_active`.
## API Contracts
### 1) Wallet Intent
#### `POST /secret/wallet/intent`
Request JSON:
```json
{
"address": "0xabc123...",
"origin": "https://edut.ai",
"locale": "en",
"chain_id": 8453
}
```
Behavior:
1. Normalize and validate wallet address format.
2. Validate `origin` against allowlist.
3. Enforce rate limits (IP + address + rolling windows).
4. Generate designation `code`, `auth_token`, nonce, and intent TTL.
5. Persist `pending_signature` state.
6. Return typed-data envelope fields required for signing.
Response:
```json
{
"intent_id": "wi_...",
"designation_code": "0217073045482",
"display_token": "0217-0730-4548-2",
"nonce": "f2e9...",
"issued_at": "2026-02-17T07:30:45Z",
"expires_at": "2026-02-17T07:40:45Z",
"domain_name": "EDUT Designation",
"chain_id": 8453,
"verifying_contract": "0x0000000000000000000000000000000000000000"
}
```
### 2) Wallet Verify
#### `POST /secret/wallet/verify`
Request JSON:
```json
{
"intent_id": "wi_...",
"address": "0xabc123...",
"chain_id": 8453,
"signature": "0x..."
}
```
Behavior:
1. Load pending intent by `intent_id`.
2. Verify not expired and not consumed.
3. Reconstruct typed payload exactly as issued.
4. Recover signer and compare to declared address.
5. Validate chain allowlist and origin.
6. Transition to `signature_verified` and `pending_membership_mint`.
Response:
```json
{
"status": "signature_verified",
"designation_code": "0217073045482",
"display_token": "0217-0730-4548-2",
"verified_at": "2026-02-17T07:31:12Z"
}
```
### 3) Membership Quote
#### `POST /secret/membership/quote`
Request JSON:
```json
{
"designation_code": "0217073045482",
"address": "0xabc123...",
"chain_id": 8453
}
```
Behavior:
1. Verify `signature_verified` status for designation/address pair.
2. Fetch current policy (`currency`, `amount`, `deadline`, `contract`).
3. Return quote payload for client-side transaction submission.
Response:
```json
{
"quote_id": "mq_...",
"chain_id": 8453,
"currency": "USDC",
"amount": "5.00",
"amount_atomic": "5000000",
"deadline": "2026-02-17T07:36:12Z",
"contract_address": "0x...",
"method": "mintMembership",
"calldata": "0x..."
}
```
### 4) Membership Confirm
#### `POST /secret/membership/confirm`
Request JSON:
```json
{
"designation_code": "0217073045482",
"quote_id": "mq_...",
"tx_hash": "0x...",
"address": "0xabc123...",
"chain_id": 8453
}
```
Behavior:
1. Validate quote ownership and expiry.
2. Verify tx inclusion and success on allowed chain.
3. Validate minted membership recipient and amount policy.
4. Transition to `membership_active`.
5. Emit activation evidence receipt.
Response:
```json
{
"status": "membership_active",
"designation_code": "0217073045482",
"display_token": "0217-0730-4548-2",
"tx_hash": "0x...",
"activated_at": "2026-02-17T07:33:09Z"
}
```
## Security Controls
1. Intent TTL and one-time nonce consumption.
2. Quote TTL with explicit `deadline`.
3. Strict origin allowlist for intent and verify endpoints.
4. Chain allowlist enforcement.
5. Signature and quote replay prevention.
6. IP + address rate limits on all public endpoints.
7. Deterministic audit trail for signature and payment confirmation.
8. No private key handling server-side.
## Data Model (SQLite)
### File: `/var/lib/edut/secrets.db`
```sql
CREATE TABLE IF NOT EXISTS designations (
id INTEGER PRIMARY KEY AUTOINCREMENT,
code TEXT NOT NULL UNIQUE,
auth_token TEXT NOT NULL,
status TEXT NOT NULL DEFAULT 'pending_signature',
wallet_address TEXT,
chain_id INTEGER,
intent_id TEXT UNIQUE,
intent_nonce TEXT,
intent_issued_at DATETIME,
intent_expires_at DATETIME,
signature TEXT,
signature_verified_at DATETIME,
membership_quote_id TEXT,
membership_currency TEXT,
membership_amount_atomic TEXT,
membership_quote_expires_at DATETIME,
membership_tx_hash TEXT,
membership_activated_at DATETIME,
origin TEXT,
locale TEXT,
created_at DATETIME DEFAULT (datetime('now'))
);
CREATE INDEX idx_designations_code ON designations(code);
CREATE INDEX idx_designations_wallet ON designations(wallet_address);
CREATE INDEX idx_designations_status ON designations(status);
CREATE INDEX idx_designations_intent ON designations(intent_id);
CREATE INDEX idx_designations_quote ON designations(membership_quote_id);
CREATE INDEX idx_designations_created ON designations(created_at);
```
## Launch-Day Activation Messaging
At launch, `membership_active` records are eligible for access-level updates and marketplace onboarding.
## NGINX Routing (Example)
```nginx
location /secret/wallet/intent {
proxy_pass http://127.0.0.1:9091;
}
location /secret/wallet/verify {
proxy_pass http://127.0.0.1:9091;
}
location /secret/membership/quote {
proxy_pass http://127.0.0.1:9091;
}
location /secret/membership/confirm {
proxy_pass http://127.0.0.1:9091;
}
```
## Summary
The wallet-first designation plus paid membership flow creates a deterministic two-factor identity and commitment chain:
1. signature proves wallet control,
2. paid mint proves intent,
3. membership gates all future marketplace purchases.