415 lines
11 KiB
Markdown
415 lines
11 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. Download endpoints perform wallet membership status checks before channel authorization messaging.
|
|
16. 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.
|
|
5. Entitlement ownership wallet is the runtime authority; payment wallet may differ when authorized by ownership-wallet proof.
|
|
|
|
## 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/wallet/session/refresh` | Rotate wallet session token |
|
|
| API | `api.edut.ai/secret/wallet/session/revoke` | Revoke wallet session token |
|
|
| 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",
|
|
"session_token": "9f2c50f8a0f5d8d0b0efc4fa665e4032f31bb0c4c4f31b8c",
|
|
"session_expires_at": "2026-03-18T07:31:12Z"
|
|
}
|
|
```
|
|
|
|
### 3) Wallet Session Lifecycle
|
|
|
|
#### `POST /secret/wallet/session/refresh`
|
|
|
|
Request JSON:
|
|
|
|
```json
|
|
{
|
|
"wallet": "0xabc123..."
|
|
}
|
|
```
|
|
|
|
Requirements:
|
|
|
|
1. Valid wallet session token via `Authorization: Bearer <token>` or `X-Edut-Session`.
|
|
2. Session wallet must match request wallet.
|
|
|
|
Behavior:
|
|
|
|
1. Validate current token state (not expired/revoked, chain matches).
|
|
2. Revoke the presented token.
|
|
3. Issue a replacement token with fresh expiry.
|
|
|
|
Response:
|
|
|
|
```json
|
|
{
|
|
"status": "session_refreshed",
|
|
"wallet": "0xabc123...",
|
|
"session_token": "f9bc20f15ecf7fd53f1f4ba8ca774564a1098e6ed9db6f0f",
|
|
"session_expires_at": "2026-03-18T07:42:10Z"
|
|
}
|
|
```
|
|
|
|
#### `POST /secret/wallet/session/revoke`
|
|
|
|
Request JSON:
|
|
|
|
```json
|
|
{
|
|
"wallet": "0xabc123..."
|
|
}
|
|
```
|
|
|
|
Requirements:
|
|
|
|
1. Valid wallet session token via `Authorization: Bearer <token>` or `X-Edut-Session`.
|
|
2. Session wallet must match request wallet.
|
|
|
|
Behavior:
|
|
|
|
1. Validate current token state.
|
|
2. Mark session as revoked immediately.
|
|
|
|
Response:
|
|
|
|
```json
|
|
{
|
|
"status": "session_revoked",
|
|
"wallet": "0xabc123...",
|
|
"revoked_at": "2026-02-17T07:34:02Z"
|
|
}
|
|
```
|
|
|
|
### 4) 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,
|
|
"regulatory_profile_id": "us_general_2026",
|
|
"currency": "USDC",
|
|
"amount_atomic": "100000000",
|
|
"decimals": 6,
|
|
"cost_envelope": {
|
|
"version": "edut.quote_cost_envelope.v1",
|
|
"checkout_currency": "USDC",
|
|
"checkout_decimals": 6,
|
|
"checkout_total_atomic": "100000000",
|
|
"checkout_total": "100",
|
|
"provider_fee_policy": "edut_absorbed",
|
|
"provider_fee_included": true,
|
|
"provider_fee_estimate_status": "absorbed_by_edut",
|
|
"provider_fee_estimate_atomic": "0",
|
|
"network_fee_policy": "payer_wallet_pays_chain_gas",
|
|
"network_fee_currency": "ETH",
|
|
"network_fee_estimate_status": "wallet_estimate_required",
|
|
"network_fee_estimate_atomic": "0"
|
|
},
|
|
"deadline": "2026-02-17T07:36:12Z",
|
|
"contract_address": "0x...",
|
|
"method": "mintMembership(address)",
|
|
"calldata": "0x..."
|
|
}
|
|
```
|
|
|
|
### 5) 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",
|
|
"regulatory_profile_id": "us_general_2026",
|
|
"tx_hash": "0x...",
|
|
"activated_at": "2026-02-17T07:33:09Z"
|
|
}
|
|
```
|
|
|
|
`regulatory_profile_id` binds quote/activation responses to deployment policy posture (`us_general_2026` or `eu_ai_act_2026_baseline`).
|
|
|
|
## 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/wallet/session/refresh {
|
|
proxy_pass http://127.0.0.1:9091;
|
|
}
|
|
|
|
location /secret/wallet/session/revoke {
|
|
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. verify issues wallet session for fail-closed control-plane access,
|
|
3. paid mint proves intent,
|
|
4. membership gates all future marketplace purchases.
|