ICN Platform Layer Design
Date: 2025-01-15 Status: Implementation Phase (Phase 14) Strategic Shift: ICN as Cooperative Backend Platform
Vision
NOT: "People run ICN" BUT: "Co-ops build apps that use ICN as backend"
A food co-op builds their Shopper's Club app (React + Node), uses ICN under the hood for members/balances/contracts, and members just log in - never seeing icnd.
What This Platform IS and IS NOT
This IS:
- A developer-facing API layer (REST + WebSocket)
- TypeScript SDK for easy integration
- Reference apps as starting points
- Multi-tenant hosting for early pilots
This is NOT:
- An app runtime (like AWS Lambda, Cloudflare Workers)
- A place to deploy custom backend code
- A "cooperative cloud" (that's Phase 16+, conditional on pilot validation)
Why not a runtime now?
- We have zero pilots - don't know what co-ops need
- 6-12 month investment without user signal
- Gateway + SDK ships in 2-3 months, validates faster
- Runtime can be added later if pilots prove it's needed
The path:
- Phase 14: Build gateway + SDK + reference app
- Phase 15: Host reference app for 1-2 pilot co-ops
- Phase 16+: Let pilot feedback drive what's next (runtime, templates, self-hosting tools, etc.)
Layer Cake Architecture
┌─────────────────────────────────────────────────────────────┐
│ Apps (what co-ops build) │
│ - Food Co-op Shopper's Club │
│ - Housing Maintenance Portal │
│ - Timebank Scheduler │
│ - Community Tool Library │
└─────────────────────────────────────────────────────────────┘
↕
┌─────────────────────────────────────────────────────────────┐
│ Platform Layer (what we build now) │
│ - REST + WebSocket API (icn-gateway) │
│ - App auth & capability tokens │
│ - Coop namespaces & isolation │
│ - Event streaming │
│ - TypeScript/Python SDKs │
└─────────────────────────────────────────────────────────────┘
↕
┌─────────────────────────────────────────────────────────────┐
│ Substrate (what we have) │
│ - Identity, Trust, Ledger, Gossip, Storage, Contracts │
│ - icnd daemon, icnctl CLI │
└─────────────────────────────────────────────────────────────┘
What Co-ops Actually Need
1. Identity & Membership
Questions apps ask:
- "Who is this user?" → DID
- "Which co-op(s) are they part of?" → Coop namespace
- "What role are they in?" → member / worker / admin / treasurer
API Examples:
// TypeScript SDK
const user = await icn.auth.getCurrentUser();
// → { did: "did:icn:abc123", name: "Alice" }
const memberships = await icn.membership.getCoops(user.did);
// → [{ coopId: "food-coop-pdx", role: "member" }, ...]
const role = await icn.membership.getRole("food-coop-pdx", user.did);
// → "member" | "worker" | "admin" | "treasurer"
2. Auth from App's POV
App can say: "User X (DID) is logged in, here's a token that lets me:
- Read their balances in this co-op
- Initiate a payment on their behalf
- Read/write certain contract state"
Auth Flow (Capability Tokens):
User Device App Server icn-gateway
│ │ │
│ 1. Sign auth challenge │ │
├───────────────────────────>│ │
│ │ 2. Verify + request │
│ │ scoped token │
│ ├───────────────────────>│
│ │ │
│ │ 3. Issue capability │
│ │ token (JWT-ish) │
│ │<───────────────────────┤
│ │ │
│ 4. Token (to use in API) │ │
│<───────────────────────────┤ │
│ │ │
│ 5. API calls with token │ │
├────────────────────────────┼───────────────────────>│
│ │ │
Token Structure:
{
"sub": "did:icn:abc123",
"coop": "food-coop-pdx",
"app": "shoppers-club",
"capabilities": [
"read:balance",
"write:payment",
"read:members"
],
"exp": 1234567890,
"iat": 1234560000
}
3. Ledger & Payments
Simple API:
// Get balance
const balance = await icn.ledger.getBalance({
coopId: "food-coop-pdx",
did: user.did,
currency: "coop-credits"
});
// → { balance: 150, creditLimit: -50 }
// Send payment
const tx = await icn.ledger.sendPayment({
coopId: "food-coop-pdx",
from: user.did,
to: "did:icn:coop-till",
amount: 25,
currency: "coop-credits",
memo: "Purchased groceries"
});
// → { txId: "tx_123...", timestamp: 1234567890 }
// Get transaction history
const history = await icn.ledger.getHistory({
coopId: "food-coop-pdx",
did: user.did,
limit: 50
});
All the hard work (mutual credit, Merkle-DAG, signatures) stays inside ICN substrate.
4. Groups / Spaces (Coop Namespaces)
Each co-op = a namespace with:
- Members list
- Role bindings
- Trust rules
- Ledger domains (currencies)
- Contract set
Data Model:
pub struct CoopNamespace {
/// Unique co-op ID
pub id: CoopId,
/// Human-readable name
pub name: String,
/// Member DIDs
pub members: Vec<Did>,
/// Role assignments (did -> roles)
pub roles: HashMap<Did, Vec<String>>,
/// Ledger currencies for this co-op
pub currencies: Vec<String>,
/// Trust rules (optional, per-coop overrides)
pub trust_rules: Option<TrustConfig>,
/// Installed contracts
pub contracts: Vec<ContentHash>,
/// Apps authorized for this co-op
pub apps: Vec<AppRegistration>,
/// Created timestamp
pub created_at: u64,
}
pub struct AppRegistration {
/// Unique app ID
pub id: AppId,
/// App name
pub name: String,
/// Allowed capabilities
pub capabilities: Vec<String>,
/// App public key (for verifying requests)
pub public_key: Vec<u8>,
}
API Examples:
// Create a new co-op namespace
const coop = await icn.coops.create({
name: "Portland Food Co-op",
foundingMembers: [alice.did, bob.did, carol.did],
currencies: ["coop-credits"],
});
// → { coopId: "food-coop-pdx", ... }
// Add member
await icn.coops.addMember({
coopId: "food-coop-pdx",
member: dave.did,
role: "member"
});
// List co-op members
const members = await icn.coops.getMembers("food-coop-pdx");
// → [{ did: "did:icn:...", role: "member", joinedAt: 123... }, ...]
5. Events (Real-Time Subscriptions)
App can subscribe to:
- New transactions
- Member joined/left
- Contract state changes
- Governance proposals/votes
WebSocket API:
// Subscribe to co-op events
const stream = icn.events.subscribe({
coopId: "food-coop-pdx",
eventTypes: ["transaction", "member_joined", "proposal_created"]
});
stream.on("transaction", (event) => {
console.log(`New transaction: ${event.from} → ${event.to}: ${event.amount}`);
// Update UI live
});
stream.on("member_joined", (event) => {
console.log(`${event.member} joined as ${event.role}`);
});
Event Types:
type ICNEvent =
| { type: "transaction", coopId: string, from: DID, to: DID, amount: number, currency: string, timestamp: number }
| { type: "member_joined", coopId: string, member: DID, role: string, timestamp: number }
| { type: "member_left", coopId: string, member: DID, timestamp: number }
| { type: "role_changed", coopId: string, member: DID, oldRole: string, newRole: string, timestamp: number }
| { type: "proposal_created", coopId: string, proposalId: string, author: DID, subject: string, timestamp: number }
| { type: "proposal_voted", coopId: string, proposalId: string, voter: DID, vote: Vote, timestamp: number }
| { type: "contract_executed", coopId: string, contractHash: string, caller: DID, result: any, timestamp: number };
Platform Components
1. icn-gateway (New Crate)
Purpose: Expose stable, language-agnostic API for co-op apps.
Stack:
- Actix-web (Rust async HTTP server)
- REST endpoints (JSON)
- WebSocket for event streaming
- Talks to
icndvia internal RPC
Directory Structure:
icn/crates/icn-gateway/
├── src/
│ ├── lib.rs # Gateway server
│ ├── auth.rs # Capability token handling
│ ├── api/
│ │ ├── mod.rs
│ │ ├── coops.rs # /coops/* endpoints
│ │ ├── ledger.rs # /ledger/* endpoints
│ │ ├── members.rs # /members/* endpoints
│ │ ├── contracts.rs # /contracts/* endpoints
│ │ └── events.rs # WebSocket event streaming
│ ├── models.rs # API request/response types
│ └── error.rs # HTTP error responses
└── Cargo.toml
API Surface (detailed below)
2. Coop Namespaces (Extend icn-store)
Purpose: Isolate co-ops from each other. Each co-op = separate namespace for members, ledgers, contracts.
Storage Schema (in Sled):
Prefix: "coop:{coop_id}:*"
coop:{coop_id}:meta → CoopNamespace (bincode)
coop:{coop_id}:members → Vec<Did> (bincode)
coop:{coop_id}:roles:{did} → Vec<String> (roles)
coop:{coop_id}:apps → Vec<AppRegistration> (bincode)
# Ledger entries scoped per co-op
coop:{coop_id}:ledger:balance:{did}:{currency} → i64
coop:{coop_id}:ledger:tx:{tx_id} → JournalEntry
# Governance scoped per co-op
coop:{coop_id}:proposals:{prop_id} → Proposal
Implementation:
- Add
icn-store/src/coops.rsfor CRUD operations - Add namespace isolation to ledger operations
- Add per-coop governance storage (proposals, votes)
3. App Auth & Capability Tokens
Purpose: Apps get scoped tokens to act on behalf of users. Not OAuth (no third-party IdP), but similar flow.
Token Issuance Flow:
- User signs a challenge with their DID keypair
- App verifies signature, requests token from
icn-gateway - Gateway issues short-lived JWT-like token with capabilities
- App includes token in
Authorization: Bearer <token>header
Token Claims:
{
"sub": "did:icn:abc123", // User DID
"coop": "food-coop-pdx", // Coop namespace
"app": "shoppers-club", // App ID
"capabilities": [
"read:balance",
"write:payment",
"read:members",
"read:proposals"
],
"exp": 1234567890, // Expiration (1 hour default)
"iat": 1234560000, // Issued at
"jti": "token_unique_id" // Token ID (for revocation)
}
Capability Scopes:
read:balance- Read user's balance in co-opwrite:payment- Initiate payment on user's behalfread:members- List co-op memberswrite:members- Add/remove members (admin only)read:proposals- View governance proposalswrite:proposals- Create proposalsvote:proposals- Vote on proposalsexecute:contract:{hash}- Execute specific contract
Revocation:
- Gateway maintains revocation list (Redis or in-memory)
- On token verification, check if
jtiis revoked - User can revoke all tokens for an app
- Tokens auto-expire after 1 hour (refresh flow)
Implementation:
icn-gateway/src/auth.rs- Token issuance/verification- Use
jsonwebtokencrate for JWT handling - Ed25519 signing (same as DID keypairs)
4. TypeScript SDK (@icn/client)
Purpose: Make ICN trivial to use from JavaScript/TypeScript apps.
Package Structure:
sdk/typescript/
├── package.json
├── src/
│ ├── index.ts # Main exports
│ ├── client.ts # ICNClient class
│ ├── auth.ts # Authentication
│ ├── coops.ts # Coop management
│ ├── ledger.ts # Ledger operations
│ ├── members.ts # Membership
│ ├── events.ts # WebSocket event streaming
│ ├── types.ts # TypeScript types
│ └── utils.ts # Helpers
└── tsconfig.json
Usage Example:
import { ICNClient } from '@icn/client';
// Initialize client
const icn = new ICNClient({
gatewayUrl: 'http://localhost:8000',
});
// Authenticate (user signs challenge with wallet/device)
const user = await icn.auth.login({
did: 'did:icn:abc123',
signChallenge: async (challenge) => {
// Sign with device key
return await wallet.sign(challenge);
}
});
// Get token for co-op app
const token = await icn.auth.getToken({
coopId: 'food-coop-pdx',
appId: 'shoppers-club',
capabilities: ['read:balance', 'write:payment']
});
// Use token for API calls
icn.setToken(token);
// Get balance
const balance = await icn.ledger.getBalance({
coopId: 'food-coop-pdx',
did: user.did,
currency: 'coop-credits'
});
// Send payment
const tx = await icn.ledger.sendPayment({
coopId: 'food-coop-pdx',
from: user.did,
to: 'did:icn:coop-till',
amount: 25,
currency: 'coop-credits',
memo: 'Groceries'
});
// Subscribe to events
const stream = icn.events.subscribe({
coopId: 'food-coop-pdx',
eventTypes: ['transaction', 'member_joined']
});
stream.on('transaction', (event) => {
console.log('New transaction:', event);
});
icn-gateway API Reference
Base URL
http://localhost:8000/api/v1
Authentication
All endpoints except /auth/* require Authorization: Bearer <token> header.
POST /auth/challenge
Generate authentication challenge for DID.
Request:
{
"did": "did:icn:abc123"
}
Response:
{
"challenge": "random_base64_string",
"expiresAt": 1234567890
}
POST /auth/verify
Verify signed challenge and issue token.
Request:
{
"did": "did:icn:abc123",
"challenge": "random_base64_string",
"signature": "ed25519_signature_base64",
"coopId": "food-coop-pdx",
"appId": "shoppers-club",
"capabilities": ["read:balance", "write:payment"]
}
Response:
{
"token": "jwt_token_string",
"expiresAt": 1234567890
}
POST /auth/revoke
Revoke a token.
Request:
{
"tokenId": "token_unique_id"
}
Response:
{
"success": true
}
Co-ops
POST /coops
Create a new co-op namespace.
Request:
{
"name": "Portland Food Co-op",
"foundingMembers": ["did:icn:abc", "did:icn:def"],
"currencies": ["coop-credits"],
"roles": {
"did:icn:abc": ["admin", "member"],
"did:icn:def": ["member"]
}
}
Response:
{
"coopId": "food-coop-pdx",
"name": "Portland Food Co-op",
"createdAt": 1234567890
}
GET /coops/{coopId}
Get co-op details.
Response:
{
"id": "food-coop-pdx",
"name": "Portland Food Co-op",
"memberCount": 42,
"currencies": ["coop-credits"],
"createdAt": 1234567890
}
GET /coops/{coopId}/members
List co-op members.
Response:
{
"members": [
{
"did": "did:icn:abc123",
"roles": ["admin", "member"],
"joinedAt": 1234567890
},
...
]
}
POST /coops/{coopId}/members
Add a member to co-op.
Request:
{
"member": "did:icn:xyz789",
"role": "member"
}
Response:
{
"success": true,
"member": "did:icn:xyz789",
"role": "member"
}
Ledger
GET /ledger/{coopId}/balance/{did}
Get balance for a DID in a co-op.
Query Parameters:
currency(optional) - Filter by currency
Response:
{
"balances": [
{
"currency": "coop-credits",
"balance": 150,
"creditLimit": -50
}
]
}
POST /ledger/{coopId}/payment
Send a payment.
Request:
{
"from": "did:icn:abc123",
"to": "did:icn:coop-till",
"amount": 25,
"currency": "coop-credits",
"memo": "Purchased groceries"
}
Response:
{
"txId": "tx_1234567890_abc",
"from": "did:icn:abc123",
"to": "did:icn:coop-till",
"amount": 25,
"currency": "coop-credits",
"timestamp": 1234567890,
"newBalance": 125
}
GET /ledger/{coopId}/history/{did}
Get transaction history.
Query Parameters:
limit(optional, default 50)offset(optional, default 0)currency(optional)
Response:
{
"transactions": [
{
"txId": "tx_...",
"from": "did:icn:abc",
"to": "did:icn:def",
"amount": 25,
"currency": "coop-credits",
"memo": "Groceries",
"timestamp": 1234567890
},
...
],
"total": 234
}
Events (WebSocket)
WS /events/subscribe
Connect:
ws://localhost:8000/api/v1/events/subscribe?token=<jwt_token>
Subscribe Message:
{
"action": "subscribe",
"coopId": "food-coop-pdx",
"eventTypes": ["transaction", "member_joined", "proposal_created"]
}
Event Messages:
{
"type": "transaction",
"coopId": "food-coop-pdx",
"from": "did:icn:abc",
"to": "did:icn:def",
"amount": 25,
"currency": "coop-credits",
"timestamp": 1234567890
}
Food Co-op Example Walkthrough
Phase 1: Identity + Anchor
Co-op builds basic web app:
Member Onboarding:
- New member downloads ICN wallet app (or web extension)
- Generates DID keypair on device
- Admin adds them to co-op:
POST /coops/food-coop-pdx/members - Member can now log into Shopper's Club app
App Auth:
- User clicks "Sign In" in Shopper's Club
- App requests challenge:
POST /auth/challenge - Wallet signs challenge with DID key
- App verifies:
POST /auth/verify→ gets token - App uses token for all API calls
At this stage: ICN = identity + event anchor. App uses Postgres for inventory.
Phase 2: Loyalty Points (Mutual Credit)
Co-op adds "Shopper Points":
Earning Points:
// When member works a shift
await icn.ledger.sendPayment({
coopId: 'food-coop-pdx',
from: 'did:icn:coop-treasury',
to: member.did,
amount: 10,
currency: 'coop-credits',
memo: 'Worked Friday stocking shift'
});
Displaying Balance:
const balance = await icn.ledger.getBalance({
coopId: 'food-coop-pdx',
did: member.did,
currency: 'coop-credits'
});
// Show: "You have 150 co-op credits"
At this stage: ICN = identity + mutual credit ledger. App still uses Postgres for products.
Phase 3: Payment at Checkout
Checkout Flow:
// Show payment options
const balance = await icn.ledger.getBalance({ ... });
// Display: "Pay with: [Credit Card] [Co-op Credits (balance: 150)]"
// User chooses co-op credits
await icn.ledger.sendPayment({
coopId: 'food-coop-pdx',
from: member.did,
to: 'did:icn:coop-till',
amount: 25,
currency: 'coop-credits',
memo: 'Purchase #4567'
});
// → Transaction recorded, balance updated
Now: ICN is doing real value accounting, but user just sees "payment completed."
Implementation Plan
Milestone 1: Gateway + Namespaces (2 weeks)
- Create
icn-gatewaycrate with Actix-web - Implement
/auth/*endpoints (challenge, verify, revoke) - Extend
icn-storewith coop namespace storage - Implement
/coops/*endpoints (create, get, members) - Add capability token verification middleware
Milestone 2: Ledger API (1 week)
- Implement
/ledger/*endpoints (balance, payment, history) - Add ledger scoping per co-op namespace
- Test with manual curl/Postman requests
Milestone 3: Events (1 week)
- Implement WebSocket event streaming
- Hook ledger operations to emit events
- Hook membership operations to emit events
- Test real-time updates
Milestone 4: TypeScript SDK (2 weeks)
- Set up
sdk/typescriptpackage - Implement
ICNClientclass - Implement auth, coops, ledger, events modules
- Write comprehensive tests
- Publish to npm as
@icn/client
Milestone 5: Reference App (2 weeks)
- Build "Tiny Food Co-op Shopper's Club" (Next.js)
- Features:
- Member login (DID auth)
- View balance
- Send payment
- Transaction history
- Live event updates
- Deploy as demo
Total: ~8 weeks for complete platform layer
Open Questions
- Token Refresh: How long should tokens last? Refresh flow needed?
- Rate Limiting: Per-app? Per-user? Per-co-op?
- Multi-Coop Users: Can one user be in multiple co-ops? (Yes, but which token structure?)
- Contract Execution: Should gateway expose contract execution API, or only via SDK helpers?
- Event Filtering: Can apps subscribe to events for specific DIDs (privacy concern)?
- Governance Integration: Should governance be in v1 of gateway, or phase 2?
Success Criteria
For a co-op app developer:
- Can build a working app in < 1 day
- Doesn't need to understand QUIC, gossip, Merkle-DAG, or vector clocks
- SDK docs answer 90% of questions
- Reference app is copy-paste starting point
For co-op members:
- Sign in with wallet (or eventually phone biometric)
- See balance, send payment, view history
- Updates are instant (WebSocket events)
- Never hear the word "daemon" or "CLI"
For ICN:
- Platform layer is stable API boundary
- Substrate can evolve underneath without breaking apps
- Multiple co-ops can run on one ICN node (isolation via namespaces)
- Apps "just work" and don't feel blockchain-y or crypto-y
Next Steps:
- ✅ Design complete (this doc)
- ⏭️ Implement icn-gateway Milestone 1 (auth + namespaces)
- ⏭️ Implement TypeScript SDK
- ⏭️ Build reference app (Food Co-op Shopper's Club)
Last Updated: 2025-01-15 Contributors: This design is a living document. Propose changes via issues/PRs.