mobile/ was moved out in April (18d41743), but the root package.json still
ran it: `npm run build`, `lint`, `test` and `dev` all called into a missing
directory and failed. The scripts now cover what is here (web, sign,
backend), concurrently is gone with the only command that needed it, and the
root lockfile shrinks to nothing.
The README described the mobile app and not the signing portal; it now lists
the real layout (web, sign, backend, shared, ops).
Node 20 is past its end of life. CI and both images move to Node 24; the
backend's sqlite3 native module builds and its tests pass on 24.
🏛️ KYC Council Backend
Backend simulation of pallet-collective voting for KYC approvals.
📋 Overview
Purpose: Decentralized KYC approval system without runtime changes
Architecture:
- Backend tracks council votes (in-memory)
- 60% threshold (e.g., 7/11 votes)
- Auto-executes approve_kyc when threshold reached
- Uses SUDO account to sign blockchain transactions
🔗 Chain Flow
User applies → Blockchain (PENDING) → Council votes → Backend auto-approves → Welati NFT minted
Why SUDO account?
identityKyc.approveKyc()requiresEnsureRootorigin- Backend signs transactions on behalf of council
- Alternative: Change runtime to accept council origin (not needed for MVP)
🚀 Installation
1. Install Dependencies
cd /home/mamostehp/pwap/backend
npm install
2. Configure Environment
cp .env.example .env
nano .env
Required variables:
WS_ENDPOINT=wss://ws.pezkuwichain.io
SUDO_SEED=your_sudo_seed_phrase_here
PORT=3001
⚠️ Security Warning: Keep SUDO_SEED secret! Use a dedicated account for KYC approvals only.
3. Start Server
Development (with hot reload):
npm run dev
Production:
npm start
📡 API Endpoints
Council Management
Add Council Member
POST /api/council/add-member
{
"address": "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY",
"signature": "0x..." // TODO: Implement signature verification
}
Remove Council Member
POST /api/council/remove-member
{
"address": "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY"
}
Get Council Members
GET /api/council/members
Response:
{
"members": ["5DFw...Dwd3", "5Grw...utQY"],
"totalMembers": 2,
"threshold": 0.6,
"votesRequired": 2
}
Sync with Noter Tiki Holders
POST /api/council/sync-notaries
Auto-fetches first 10 Noter tiki holders from blockchain and updates council.
KYC Voting
Propose KYC Approval
POST /api/kyc/propose
{
"userAddress": "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY",
"proposerAddress": "5DFwqK698vL4gXHEcanaewnAqhxJ2rjhAogpSTHw3iwGDwd3",
"signature": "0x..."
}
Logic:
- Proposer auto-votes AYE
- If threshold already met (e.g., 1/1 member), auto-executes immediately
Vote on Proposal
POST /api/kyc/vote
{
"userAddress": "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY",
"voterAddress": "5DFwqK698vL4gXHEcanaewnAqhxJ2rjhAogpSTHw3iwGDwd3",
"approve": true,
"signature": "0x..."
}
Approve: true = AYE, false = NAY
Auto-execute: If votes reach threshold, backend signs and submits approve_kyc transaction.
Get Pending Proposals
GET /api/kyc/pending
Response:
{
"pending": [
{
"userAddress": "5Grw...utQY",
"proposer": "5DFw...Dwd3",
"ayes": ["5DFw...Dwd3", "5HpG...vSKr"],
"nays": [],
"timestamp": 1700000000000,
"votesCount": 2,
"threshold": 7,
"status": "VOTING"
}
]
}
🔐 Council Membership Rules
Initial Setup
- 1 member: Founder delegate (hardcoded:
5DFwqK698vL4gXHEcanaewnAqhxJ2rjhAogpSTHw3iwGDwd3) - Threshold: 1/1 = 100% (single vote approves)
Growth Path
- Add Noter holders: Use
/api/council/sync-notariesto fetch first 10 Noter tiki holders - Council size: 11 members (1 founder + 10 notaries)
- Threshold: 7/11 = 63.6% (60% threshold met)
Automatic Updates
- When Noter loses tiki → Remove from council
- When new Noter available → Add to council (first 10 priority)
- If no Notaries available → Serok (president) can appoint manually
🧪 Testing
Test 1: Single Member (Founder Only)
# 1. Start backend
npm run dev
# 2. Get council members
curl http://localhost:3001/api/council/members
# Expected: 1 member (founder delegate)
# 3. User applies KYC (via frontend)
# 4. Propose approval
curl -X POST http://localhost:3001/api/kyc/propose \
-H "Content-Type: application/json" \
-d '{
"userAddress": "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY",
"proposerAddress": "5DFwqK698vL4gXHEcanaewnAqhxJ2rjhAogpSTHw3iwGDwd3"
}'
# Expected: Auto-execute (1/1 threshold reached)
Test 2: 3 Members
# 1. Add 2 notaries
curl -X POST http://localhost:3001/api/council/add-member \
-d '{"address": "5HpG...vSKr"}'
curl -X POST http://localhost:3001/api/council/add-member \
-d '{"address": "5FLe...dXRp"}'
# 2. Council: 3 members, threshold = 2 votes (60% of 3 = 1.8 → ceil = 2)
# 3. Propose
curl -X POST http://localhost:3001/api/kyc/propose \
-d '{
"userAddress": "5Grw...utQY",
"proposerAddress": "5DFw...Dwd3"
}'
# Status: 1/2 (proposer voted AYE)
# 4. Vote from member 2
curl -X POST http://localhost:3001/api/kyc/vote \
-d '{
"userAddress": "5Grw...utQY",
"voterAddress": "5HpG...vSKr",
"approve": true
}'
# Expected: Auto-execute (2/2 threshold reached) ✅
📊 Monitoring
Health Check
GET /health
Response:
{
"status": "ok",
"blockchain": "connected",
"sudoAccount": "5DFw...Dwd3",
"councilMembers": 11,
"pendingVotes": 3
}
Console Logs
🔗 Connecting to PezkuwiChain...
✅ Sudo account loaded: 5DFw...Dwd3
✅ Connected to blockchain
📊 Chain: PezkuwiChain
🏛️ Runtime version: 106
🚀 KYC Council Backend running on port 3001
📊 Council members: 1
🎯 Threshold: 60%
📝 KYC proposal created for 5Grw...utQY by 5DFw...Dwd3
📊 Votes: 1/1 (1 members, 60% threshold)
🎉 Threshold reached for 5Grw...utQY! Executing approve_kyc...
📡 Transaction status: Ready
📡 Transaction status: InBlock
✅ KYC APPROVED for 5Grw...utQY
🏛️ User will receive Welati NFT automatically
🔄 Integration with Frontend
Option A: Direct Backend API Calls
Frontend calls backend endpoints directly (simpler for MVP).
// Propose KYC approval
const response = await fetch('http://localhost:3001/api/kyc/propose', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
userAddress: application.address,
proposerAddress: selectedAccount.address,
signature: await signMessage(...)
})
});
Option B: Blockchain Events + Backend Sync
Backend listens to blockchain events and auto-tracks proposals (future enhancement).
🚨 Security Considerations
-
SUDO Account Protection:
- Use dedicated hardware wallet (Ledger recommended)
- Only use for KYC approvals, nothing else
- Consider multi-sig in production
-
Signature Verification:
- TODO: Implement Polkadot signature verification
- Prevent vote spam from non-members
-
Rate Limiting:
- Add rate limiting to API endpoints
- Prevent DoS attacks
-
Audit Trail:
- Log all votes to database (future enhancement)
- Track council member changes
📝 TODO
- Implement signature verification
- Add database persistence (replace in-memory Maps)
- Add rate limiting middleware
- Add automated council sync cron job
- Add multi-sig support for sudo account
- Add audit logging
- Add Prometheus metrics
- Add Docker support
📞 Support
Backend Developer: [Your contact]
Runtime Issues: Check /Pezkuwi-SDK/pezkuwi/pallets/identity-kyc
Frontend Integration: See /pwap/ADMIN_KYC_GUIDE.md
Version: 1.0 (Backend Council MVP) Last Updated: 17 Kasım 2025