soulprint
README.md
# ๐ Soulprint
**Decentralized KYC identity protocol for AI agents.**
Soulprint lets any AI bot prove there's a verified human behind it โ without revealing who that human is. No companies, no servers, no paid APIs. Just cryptographic proof.
[](LICENSE)
[[](https://npmjs.com/package/soulprint)
[](https://npmjs.com/package/soulprint-mcp)
()
[](https://npmjs.com/package/soulprint-network)[]()
---
## The Problem
AI agents are acting on behalf of humans: booking flights, calling APIs, making decisions. But no service can know if a bot is legitimate or malicious. There's no accountability.
**Soulprint solves this** by linking every bot to a verified human identity โ cryptographically, privately, and without any central authority.
---
## How It Works
```
1. User runs: npx soulprint verify-me --selfie me.jpg --document cedula.jpg
โ
2. LOCAL (on-device, nothing leaves your machine):
โข Tesseract OCR reads the cedula (Colombian ID)
โข InsightFace matches your face to the document photo
โข Poseidon hash derives a unique nullifier from (cedula + birthdate + face_key)
โข ZK proof generated: "I verified my identity" without revealing any data
โข Photos deleted from memory
โ
3. ZK proof + SPT broadcast to validator node (verifies in 25ms, offline)
โ
4. Soulprint Token (SPT) stored in ~/.soulprint/token.spt โ valid 24h
โ
5. Any MCP server or API verifies in <50ms, offline, for free
```
**What the verifier knows:** โ
Real human, verified Colombian ID, trust score
**What the verifier doesn't know:** ๐ Name, cedula number, face, birthdate
---
## Quick Start
### 1. Install Python deps (face recognition)
```bash
npx soulprint install-deps
```
### 2. Verify your identity
```bash
npx soulprint verify-me \
--selfie path/to/selfie.jpg \
--document path/to/cedula.jpg
```
Output:
```
๐ Soulprint โ Verificaciรณn de identidad
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
Validaciรณn de imรกgenes
โ
OCR del documento
โ
Coincidencia facial
โ
Derivaciรณn de nullifier
โ
Generaciรณn de ZK proof
โ
Emisiรณn del token SPT
DID: did:key:z6Mk...
Trust Score: 45/100
ZK Proof: โ
incluido
Tiempo: 3.2s
```
### 3. Show your token
```bash
npx soulprint show
```
### 4. Renew (no re-verify needed)
```bash
npx soulprint renew
```
### 5. Run a validator node
```bash
npx soulprint node --port 4888
```
---
## Protect Any MCP Server (3 lines)
```typescript
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { soulprint } from "soulprint-mcp";
const server = new McpServer({ name: "my-server", version: "1.0" });
server.use(soulprint({ minScore: 60 })); // require KYC-verified humans
```
The client must include the SPT in capabilities:
```json
{
"capabilities": {
"identity": { "soulprint": "<token>" }
}
}
```
Or in the HTTP header: `X-Soulprint: <token>`
### With DPoP โ prevent token theft (v0.3.8)
```typescript
// โโ Server side โ strict mode โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
server.use(soulprint({ minScore: 60, requireDPoP: true }));
// โ 401 { error: "dpop_required" } if no proof header
// โโ Client side โ sign every request โโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
import { signDPoP, serializeDPoP } from "soulprint-core";
// Load your keypair (never transmit the private key)
const { privateKey, did } = loadKeypair();
const myToken = "<your-SPT>";
// Before each tool call:
const proof = signDPoP(privateKey, did, "POST", toolUrl, myToken);
headers["X-Soulprint"] = myToken;
headers["X-Soulprint-Proof"] = serializeDPoP(proof);
```
A stolen SPT is **useless** without the private key. The proof is:
- Unique per request (random nonce)
- URL + method bound (no MITM)
- Expires in 5 minutes
- Hash-bound to the specific token
---
## Protect Any REST API
```typescript
import express from "express";
import { soulprint } from "soulprint-express";
const app = express();
// Protect entire API
app.use(soulprint({ minScore: 40 }));
// Strict: require DPoP proof (prevent token theft)
app.use(soulprint({ minScore: 65, requireDPoP: true }));
// Or specific routes
app.post("/sensitive", soulprint({ require: ["DocumentVerified", "FaceMatch"] }), handler);
// Access the verified identity + check if token was auto-renewed
app.get("/me", soulprint({ minScore: 20 }), (req, res) => {
const renewedToken = res.getHeader("X-Soulprint-Token-Renewed");
res.json({
nullifier: req.soulprint!.nullifier, // unique per human, no PII
score: req.soulprint!.score,
...(renewedToken ? { token_renewed: renewedToken } : {}),
});
});
```
### Fastify
```typescript
import { soulprintFastify } from "soulprint-express";
await fastify.register(soulprintFastify, { minScore: 60 });
fastify.get("/me", async (request) => ({
nullifier: request.soulprint?.nullifier,
}));
```
## Run a Validator Node
Anyone can run a validator node. Each node runs **two stacks simultaneously**: HTTP (port 4888) + libp2p P2P (port 6888).
```bash
# Arranque simple โ mDNS descubre nodos en la misma LAN automรกticamente
npx soulprint node
# Con bootstrap nodes para conectar a la red global
SOULPRINT_BOOTSTRAP=/ip4/x.x.x.x/tcp/6888/p2p/12D3KooW... \
npx soulprint node
```
Output esperado:
```
๐ Soulprint Validator Node v0.2.2
Node DID: did:key:z6Mk...
Listening: http://0.0.0.0:4888
๐ P2P activo
Peer ID: 12D3KooW...
Multiaddrs: /ip4/x.x.x.x/tcp/6888/p2p/12D3KooW...
Gossip: HTTP fallback + GossipSub P2P
Discovery: mDNS (+ DHT si hay bootstraps)
```
Node API:
```
GET /info โ node info + p2p stats (peer_id, peers, multiaddrs)
POST /verify โ verify ZK proof + co-sign SPT
POST /reputation/attest โ issue +1/-1 attestation (propagado via GossipSub)
GET /reputation/:did โ get bot reputation
GET /nullifier/:hash โ check anti-Sybil registry
```
---
## Architecture
```
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Layer 4 โ SDKs (soulprint-mcp, express) โ
Done โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ Layer 3 โ Validator Nodes (HTTP + anti-Sybil) โ
Done โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ Layer 2 โ ZK Proofs (Circom + snarkjs) โ
Done โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ Layer 1 โ Local Verification (Face + OCR) โ
Done โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
```
### On-Demand ML Models
AI models are **never running persistently**:
```
Idle state: ~8MB RAM (only the CLI)
During verify: ~200MB RAM (InsightFace subprocess spawned)
After verify: ~8MB RAM (subprocess exits โ memory freed)
```
---
## Packages
| Package | Version | Description | Install |
|---|---|---|---|
| [`soulprint-core`](packages/core) | `0.1.6` | DID, SPT tokens, Poseidon nullifier, PROTOCOL constants, anti-farming | `npm i soulprint-core` |
| [`soulprint-verify`](packages/verify-local) | `0.1.4` | OCR + face match (on-demand), biometric thresholds from PROTOCOL | `npm i soulprint-verify` |
| [`soulprint-zkp`](packages/zkp) | `0.1.5` | Circom circuit + snarkjs prover, face_key via PROTOCOL.FACE_KEY_DIMS | `npm i soulprint-zkp` |
| [`soulprint-network`](packages/network) | `0.4.1` | Validator node: HTTP + P2P + credential validators + anti-farming | `npm i soulprint-network` |
| [`soulprint-mcp`](packages/mcp) | `0.1.5` | MCP middleware (3 lines) | `npm i soulprint-mcp` |
| [`soulprint-express`](packages/express) | `0.1.3` | Express/Fastify middleware | `npm i soulprint-express` |
| [`soulprint`](packages/cli) | `0.1.3` | `npx soulprint` CLI | `npm i -g soulprint` |
---
## ZK Circuit
The heart of Soulprint is a [Circom](https://circom.io) circuit that proves:
> *"I know a cedula number + birthdate + face key such that*
> *`Poseidon(cedula, birthdate, face_key) == nullifier`*
> *AND the cedula is within valid Registradurรญa ranges"*
Without revealing any of the private inputs.
**Circuit stats:**
- 844 non-linear constraints
- 4 private inputs (cedula, birthdate, face_key, salt)
- 2 public inputs (nullifier, context_tag)
- Proof generation: ~600ms on a laptop
- Proof verification: ~25ms offline
---
## Soulprint Token (SPT)
A base64url-encoded signed JWT. **Contains no PII.**
```json
{
"sip": "1",
"did": "did:key:z6MkhaXgBZ...",
"score": 45,
"level": "KYCFull",
"country": "CO",
"credentials": ["DocumentVerified", "FaceMatch"],
"nullifier": "0x7090787188...",
"zkp": "eyJwIjp7InBpX2EiOlsi...",
"issued": 1740000000,
"expires": 1740086400,
"sig": "ed25519_signature"
}
```
---
## Trust Scoring
```
Credential | Score
--------------------|-------
EmailVerified | +10
PhoneVerified | +15
GitHubLinked | +20
DocumentVerified | +25
FaceMatch | +20
BiometricBound | +10
|
KYCFull (doc+face) | 45/100
```
Services choose their own threshold:
```typescript
soulprint({ minScore: 20 }) // email verified is enough
soulprint({ minScore: 45 }) // require doc + face KYC
soulprint({ minScore: 80 }) // require full biometric + extra
```
---
## Anti-Sybil Protection
The nullifier is derived from **biometric + document data**:
```
nullifier = Poseidon(cedula_number, birthdate, face_key)
face_key = Poseidon(quantized_face_embedding[0..31])
```
- Same person, different device โ **same nullifier**
- Different person, same cedula โ **different nullifier** (face doesn't match)
- Person registers twice โ nullifier already exists โ **rejected by validator**
---
## Supported Countries
| Country | Document | Status |
|---|---|---|
| ๐จ๐ด Colombia | Cรฉdula de Ciudadanรญa (MRZ + OCR) | โ
Supported |
| ๐ Others | Passport (ICAO TD3 MRZ) | ๐ง Planned |
---
## Development Setup
```bash
git clone https://github.com/manuelariasfz/soulprint
cd soulprint
pnpm install
pnpm build
```
### Run integration tests
```bash
# ZK proof tests (no circuit compilation needed)
cd packages/zkp && node dist/prover.test.js
# Full integration tests
node -e "require('./packages/core/dist/index.js')"
```
### Compile ZK circuit (first time only)
```bash
pnpm --filter soulprint-zkp build:circuits
```
### Python dependencies
```bash
pip3 install insightface opencv-python-headless onnxruntime
```
---
## Trust Score โ 0 to 100
```
Total Score (0-100) = Identity (0-80) + Bot Reputation (0-20)
```
**Identity credentials (max 80 pts):**
| Credential | Points | How |
|---|---|---|
| EmailVerified | +8 | Email confirmation |
| PhoneVerified | +12 | SMS OTP |
| GitHubLinked | +16 | OAuth |
| DocumentVerified | +20 | OCR + MRZ (ICAO 9303) |
| FaceMatch | +16 | InsightFace biometric |
| BiometricBound | +8 | Device binding |
**Access levels:**
| Score | Level | Access |
|---|---|---|
| 0โ17 | Anonymous | Basic tools |
| 18โ59 | Partial KYC | Standard features |
| 60โ94 | KYCFull | Advanced features |
| **95โ100** | **KYCFull + reputation** | **Premium endpoints** |
---
## Bot Reputation (v0.1.3)
The reputation layer (0โ20 pts) builds over time from behavioral **attestations** issued by verified services.
```
Reputation starts at: 10 (neutral)
Verified service issues +1 โ goes up (max 20)
Verified service issues -1 โ goes down (min 0)
```
**Attestation format (Ed25519 signed):**
```typescript
interface BotAttestation {
issuer_did: string; // service DID (requires score >= 60 to issue)
target_did: string; // bot being rated
value: 1 | -1;
context: string; // "spam-detected", "normal-usage", "payment-completed"
timestamp: number;
sig: string; // Ed25519 โ bound to issuer_did
}
```
**Only services with score โฅ 60 can issue attestations.** This prevents low-quality services from gaming the network.
Attestations propagate **P2P across all validator nodes** via libp2p GossipSub (with HTTP fallback for legacy nodes).
---
## Anti-Farming Protection (v0.3.5)
The reputation system is protected against point farming. **Detected farming โ automatic -1 penalty** (not just rejection).
Rules enforced by all validator nodes (`FARMING_RULES` โ `Object.freeze`):
| Rule | Limit |
|---|---|
| Daily gain cap | Max **+1 point/day** per DID |
| Weekly gain cap | Max **+2 points/week** per DID |
| New DID probation | DIDs < 7 days need **2+ existing attestations** before earning |
| Same-issuer cooldown | Max 1 reward/day from the same service |
| Session duration | Min **30 seconds** |
| Tool entropy | Min **4 distinct tools** used |
| Robotic pattern | Call interval stddev < 10% of mean โ detected as bot |
```typescript
// Example: attacker trying to farm +1 every 60s
// Result: +1 โ converted to -1 (automatic penalty)
POST /reputation/attest
{ did, value: 1, context: "normal-usage", session: { duration: 8000, tools: ["search","search","search"] } }
// โ { value: -1, farming_detected: true, reason: "robotic-pattern" }
```
---
## Credential Validators (v0.3.5)
Every validator node ships with **3 open-source credential verifiers** โ no API keys required:
### ๐ง Email OTP (nodemailer)
```bash
POST /credentials/email/start { did, email }
# โ OTP sent to email (dev: Ethereal preview, prod: any SMTP)
POST /credentials/email/verify { sessionId, otp }
# โ issues credential:EmailVerified attestation, gossiped P2P
```
### ๐ฑ Phone TOTP (RFC 6238 โ no SMS, no API key)
```bash
POST /credentials/phone/start { did, phone }
# โ returns totpUri โ scan with Google Authenticator / Authy / Aegis
POST /credentials/phone/verify { sessionId, code }
# โ issues credential:PhoneVerified attestation
```
### ๐ GitHub OAuth (native fetch)
```bash
GET /credentials/github/start?did=...
# โ redirects to github.com OAuth
GET /credentials/github/callback
# โ issues credential:GitHubLinked attestation with github.login
```
Config: `GITHUB_CLIENT_ID` + `GITHUB_CLIENT_SECRET` + `SOULPRINT_BASE_URL`
---
## Protocol Constants (v0.3.5)
All critical values are **immutable at runtime** via `Object.freeze()` in `soulprint-core`. Changing them requires a new SIP (Soulprint Improvement Proposal) and a protocol version bump.
```typescript
import { PROTOCOL } from 'soulprint-core';
PROTOCOL.FACE_SIM_DOC_SELFIE // 0.35 โ min similarity document vs selfie
PROTOCOL.FACE_SIM_SELFIE_SELFIE // 0.65 โ min similarity selfie vs selfie (liveness)
PROTOCOL.FACE_KEY_DIMS // 32 โ embedding dimensions for face_key
PROTOCOL.FACE_KEY_PRECISION // 1 โ decimal precision (absorbs ยฑ0.01 noise)
PROTOCOL.SCORE_FLOOR // 65 โ minimum score any service can require
PROTOCOL.VERIFIED_SCORE_FLOOR // 52 โ floor for DocumentVerified identities
PROTOCOL.MIN_ATTESTER_SCORE // 65 โ minimum score to issue attestations
PROTOCOL.VERIFY_RETRY_MAX // 3 โ max retries for remote verification
```
> These constants are **write-protected** โ `PROTOCOL.FACE_SIM_DOC_SELFIE = 0.1` throws at runtime.
---
## Live Ecosystem โ mcp-colombia-hub
[mcp-colombia-hub](https://github.com/manuelariasfz/mcp-colombia) is the **first verified service** in the Soulprint ecosystem:
- **Service score:** 80 (DocumentVerified + FaceMatch + GitHubLinked + BiometricBound)
- **Auto-issues -1** when a bot spams (>5 req/60s)
- **Auto-issues +1** when a bot completes 3+ tools normally
- **Premium endpoint `trabajo_aplicar`** requires score โฅ 40
```bash
npx -y mcp-colombia-hub
```
---
## Security Model
| Threat | Defense |
|---|---|
| Someone learns your DID | DID is public โ harmless without private key |
| Private key theft | Key lives in `~/.soulprint/` (mode 0600) |
| Fake cedula image | Face match required |
| Register twice | Nullifier uniqueness on validator network |
| Replay attack | Token expires in 24h + context_tag per service |
| Sybil attack | Biometric nullifier โ same face = same nullifier |
| DID substitution attack | Ed25519 signature bound to DID keypair |
---
## Roadmap
```
โ
Phase 1 โ Local verification (cedula OCR + face match + nullifier)
โ
Phase 2 โ ZK proofs (Circom circuit + snarkjs prover/verifier)
โ
Phase 3 โ Validator nodes (HTTP + ZK verify + anti-Sybil registry)
โ
Phase 4 โ SDKs (soulprint-mcp, soulprint-express)
โ
Phase 5 โ P2P network (libp2p v2 ยท Kademlia DHT + GossipSub + mDNS ยท soulprint-network@0.2.2)
โ
v0.3.7 โ Challenge-Response peer integrity ยท snarkjs critical fix ยท SPT auto-renewal
โ
v0.3.5 โ Anti-farming engine ยท Credential validators (email/phone/GitHub) ยท Biometric PROTOCOL constants
๐ง Phase 6 โ Multi-country support (passport, DNI, CURP, RUT...)
๐ฎ Phase 7 โ On-chain nullifier registry (optional, EVM-compatible)
```
---
### Phase 5f โ Auto-Renewal of SPT (v0.3.6) โ
SPTs (Soulprint Protocol Tokens) now renew automatically โ no more downtime when a 24-hour token expires.
#### How it works
```
[Bot SDK] โโdetects near-expiryโโโบ POST /token/renew โโโบ [Validator Node]
โ current SPT โ fresh SPT (24h)
[Middleware] โโโโ X-Soulprint-Token-Renewed: <new_spt> โโโโโโโโโโ
```
**Renewal windows:**
| Scenario | Window | Action |
|---|---|---|
| Token valid, < 1h remaining | Pre-emptive | Auto-renew |
| Token expired < 7 days ago | Grace period | Auto-renew |
| Token expired > 7 days ago | Stale | Full re-verification required |
#### Validator endpoint
```bash
POST /token/renew
Body: { "spt": "<current_token>" }
Response 200: {
"spt": "<new_token>",
"expires_in": 86400,
"renewed": true,
"method": "preemptive" | "grace_window"
}
```
#### Express middleware (automatic)
```typescript
import { soulprint } from "soulprint-express";
app.use(soulprint({
minScore: 40,
nodeUrl: "https://validator.soulprint.digital", // enables auto-renew
}));
// New token arrives in response header if renewed:
// X-Soulprint-Token-Renewed: <new_spt>
// X-Soulprint-Expires-In: 86400
```
#### MCP middleware (automatic)
```typescript
import { requireSoulprint } from "soulprint-mcp";
server.use(requireSoulprint({
minScore: 65,
nodeUrl: "https://validator.soulprint.digital",
}));
// Renewed token propagated in context.meta["x-soulprint-token-renewed"]
```
#### Manual (any SDK)
```typescript
import { autoRenew, needsRenewal } from "soulprint-core";
const check = needsRenewal(currentSpt);
if (check.needsRenew) {
const { spt, renewed } = await autoRenew(currentSpt, { nodeUrl });
if (renewed) saveSpt(spt); // persist the new token
}
```
### Phase 5g โ Challenge-Response Peer Integrity + snarkjs Fix (v0.3.7) โ
#### Critical bug fix โ `soulprint-zkp@0.1.5`
`verifyProof()` was silently broken since v0.1.0. The snarkjs CJS module has `__esModule: true` but no `.default` property โ TypeScript's `__importDefault` returned the module as-is, then code accessed `.default.groth16` which was `undefined`. All ZK proof verifications crashed at runtime.
```typescript
// โ Before (broken):
import snarkjs from "snarkjs"; // compiles to snarkjs_1.default.groth16 โ undefined
// โ
After (fixed):
import * as snarkjs from "snarkjs"; // compiles to snarkjs.groth16 โ
```
#### Challenge-Response Protocol (`soulprint-network@0.3.7`)
Peers now cryptographically verify that remote nodes are running **unmodified ZK verification code** before accepting them into the network.
```
Challenger Peer
โ โ
โโโ POST /challenge โโโโโโโโโโโโโโบโ
โ {challenge_id, nonce, โ
โ valid_proof, โ verifyProof(valid_proof) โ true
โ invalid_proof} โ verifyProof(invalid_proof) โ false
โ โ sign(results, node_key)
โโโโ {result_valid: true, โโโโโโโโโ
โ result_invalid: false, โ
โ signature: Ed25519(...)} โ
โ โ
โ verify signature โ
โ
โ result_valid == true โ
โ
โ result_invalid == false โ
โ
โ โ
โ โ PEER ACCEPTED โ
```
**Attacks blocked:**
| Attack | Detection |
|--------|-----------|
| ZK always returns `true` (bypass) | `invalid_proof` must return `false` |
| ZK always returns `false` (broken) | `valid_proof` must return `true` |
| Pre-computed / cached response | Fresh random `nonce` makes `invalid_proof` unique per challenge |
| Node impersonation | Ed25519 signature tied to `node_did` |
| Replay attack | 30-second TTL on challenges |
**Invalid proof generation** โ the challenger mutates the valid proof with a random nonce:
```
invalid_proof.pi_a[0] = (valid_proof.pi_a[0] + nonce) mod p
```
This produces a cryptographically invalid proof that snarkjs will always reject โ but it's unpredictable without the nonce.
**Automatic peer verification** โ `POST /peers/register` now runs `verifyPeerBehavior()` before accepting any peer. A peer with modified ZK code is rejected with HTTP 403.
### Phase 5h โ DPoP: Demonstrating Proof of Possession (v0.3.8) โ
SPT tokens are bearer tokens โ stolen tokens could be used until expiry (24h). **DPoP** closes this window by requiring a fresh cryptographic proof with every request.
```
Without DPoP: stolen SPT โ attacker calls API โ SUCCESS โ
With DPoP: stolen SPT โ attacker has no private key โ 401 โ
```
**How it works:**
Every request carries `X-Soulprint-Proof` โ a payload signed with the user's Ed25519 private key:
```typescript
{
typ: "soulprint-dpop",
method: "POST", // HTTP method โ bound
url: "https://...", // exact URL โ bound
nonce: "a3f1b2...", // 16 random bytes โ unique per request
iat: 1740000000, // expires in 5 minutes
spt_hash: sha256(spt), // bound to THIS specific token
}
// Signed: Ed25519(sha256(JSON.stringify(payload)), privateKey)
```
**Attacks blocked (8):** token theft, replay, URL MITM, method MITM, DID mismatch, expired proof, malformed proof, foreign token reuse.
**API:**
```typescript
import { signDPoP, verifyDPoP, serializeDPoP, NonceStore } from "soulprint-core";
const proof = signDPoP(privateKey, did, "POST", url, spt);
const header = serializeDPoP(proof); // base64url string โ X-Soulprint-Proof
const result = verifyDPoP(header, spt, "POST", url, nonceStore, sptDid);
// result.valid โ bool | result.reason โ string
```
---
### Phase 5i โ MCPRegistry: Verified MCP Ecosystem (v0.3.9) โ
A public on-chain registry of verified MCP servers. Agents can check whether a server is legitimate before trusting it.
**Contract:** `MCPRegistry.sol` on Base Sepolia
**Address:** `0x59EA3c8f60ecbAe22B4c323A8dDc2b0BCd9D3C2a`
**Admin:** Soulprint Protocol (not any individual MCP)
```
Unverified MCP: agent connects โ no guarantee โ risk โ
Verified MCP: isVerified(0x...) โ true on-chain โ trusted โ
```
**Registration flow:**
```bash
# 1. Any dev registers their MCP (permissionless)
curl -X POST http://soulprint-node/admin/mcp/register \
-d '{ "ownerKey": "0x...", "address": "0x...",
"name": "My Finance MCP", "url": "https://...", "category": "finance" }'
# 2. Soulprint admin reviews and verifies
curl -X POST http://soulprint-node/admin/mcp/verify \
-H "Authorization: Bearer ADMIN_TOKEN" \
-d '{ "address": "0x..." }'
# โ on-chain tx โ MCPVerified event โ permanent record
# 3. Anyone checks
curl http://soulprint-node/mcps/verified
# โ [{ name: "My Finance MCP", badge: "โ
VERIFIED", verified_at: "..." }]
```
**Check from code:**
```typescript
import { isVerifiedOnChain, getMCPEntry } from "soulprint-network";
const trusted = await isVerifiedOnChain("0x..."); // โ true/false, on-chain
const entry = await getMCPEntry("0x...");
// โ { name, url, category, verified, verified_at, badge: "โ
VERIFIED by Soulprint" }
```
**Architectural separation:**
```
Soulprint validator = protocol authority โ admin endpoints (verify/revoke)
Individual MCPs = participants โ read-only (check status, list verified)
MCPRegistry.sol = source of truth โ on-chain, immutable, auditable
```
---
### Phase 5j โ ProtocolThresholds: Mutable On-Chain Governance (v0.4.1) โ
Protocol thresholds (SCORE_FLOOR, VERIFIED_SCORE_FLOOR, FACE_SIM_*, etc.) now live on-chain in `ProtocolThresholds.sol` instead of being hardcoded.
**Contract (Base Sepolia):** `0xD8f78d65b35806101672A49801b57F743f2D2ab1`
```solidity
// Anyone can read
getThreshold("SCORE_FLOOR") // โ 65
getThreshold("FACE_SIM_DOC_SELFIE") // โ 350 (= 0.35)
getAll() // โ all 9 thresholds
// Only superAdmin can write
setThreshold("SCORE_FLOOR", 70) // emits ThresholdUpdated event
// Admin transfer (2-step safety)
proposeSuperAdmin(addr) โ acceptSuperAdmin()
```
**Validator integration:**
- Node loads thresholds from blockchain at startup (non-blocking, fallback to local if RPC unreachable)
- Auto-refresh every 10 minutes
- New endpoint: `GET /protocol/thresholds`
```json
{
"source": "blockchain",
"contract": "0xD8f78d65b35806101672A49801b57F743f2D2ab1",
"thresholds": {
"SCORE_FLOOR": 65,
"VERIFIED_SCORE_FLOOR": 52,
"MIN_ATTESTER_SCORE": 65,
"FACE_SIM_DOC_SELFIE": 0.35,
"FACE_SIM_SELFIE_SELFIE": 0.65,
"DEFAULT_REPUTATION": 10,
"IDENTITY_MAX": 80,
"REPUTATION_MAX": 20
}
}
```
**Tests:** 17/17 real flow tests on Base Sepolia (`tests/protocol-thresholds-tests.mjs`)
---
## Protocol Spec
See [specs/SIP-v0.1.md](specs/SIP-v0.1.md) for the Soulprint Identity Protocol specification.
---
## Contributing
See [CONTRIBUTING.md](CONTRIBUTING.md). All countries welcome โ add your ID document format in `packages/verify-local/src/document/`.
---
## License
MIT โ free for personal and commercial use.
---
*Built for the age of AI agents. Every bot has a soul behind it.*
This server cannot be deployed
Maintenance
ActivityInactive
ResponsivenessNo issues