Soulprint
๐ Decentralized KYC for AI agents โ ZK proof of human identity behind any bot. No servers, no companies, no PII stored.
Installation
npx soulprintAsk AI about Soulprint
Powered by Claude ยท Grounded in docs
I know everything about Soulprint. Ask me about installation, configuration, usage, or troubleshooting.
0/500
Reviews
Documentation
๐ 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.
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)
npx soulprint install-deps
2. Verify your identity
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
npx soulprint show
4. Renew (no re-verify needed)
npx soulprint renew
5. Run a validator node
npx soulprint node --port 4888
Protect Any MCP Server (3 lines)
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:
{
"capabilities": {
"identity": { "soulprint": "<token>" }
}
}
Or in the HTTP header: X-Soulprint: <token>
With DPoP โ prevent token theft (v0.3.8)
// โโ 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
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
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).
# 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 | 0.1.6 | DID, SPT tokens, Poseidon nullifier, PROTOCOL constants, anti-farming | npm i soulprint-core |
soulprint-verify | 0.1.4 | OCR + face match (on-demand), biometric thresholds from PROTOCOL | npm i soulprint-verify |
soulprint-zkp | 0.1.5 | Circom circuit + snarkjs prover, face_key via PROTOCOL.FACE_KEY_DIMS | npm i soulprint-zkp |
soulprint-network | 0.4.1 | Validator node: HTTP + P2P + credential validators + anti-farming | npm i soulprint-network |
soulprint-mcp | 0.1.5 | MCP middleware (3 lines) | npm i soulprint-mcp |
soulprint-express | 0.1.3 | Express/Fastify middleware | npm i soulprint-express |
soulprint | 0.1.3 | npx soulprint CLI | npm i -g soulprint |
ZK Circuit
The heart of Soulprint is a Circom 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.
{
"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:
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
git clone https://github.com/manuelariasfz/soulprint
cd soulprint
pnpm install
pnpm build
Run integration tests
# 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)
pnpm --filter soulprint-zkp build:circuits
Python dependencies
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):
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 |
// 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)
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)
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)
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.
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.1throws at runtime.
Live Ecosystem โ mcp-colombia-hub
mcp-colombia-hub 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_aplicarrequires score โฅ 40
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
POST /token/renew
Body: { "spt": "<current_token>" }
Response 200: {
"spt": "<new_token>",
"expires_in": 86400,
"renewed": true,
"method": "preemptive" | "grace_window"
}
Express middleware (automatic)
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)
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)
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.
// โ 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:
{
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:
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:
# 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:
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
// 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
{
"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 for the Soulprint Identity Protocol specification.
Contributing
See 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.
