Building Pingsy: A Decentralized Uptime Monitor Powered by DePIN
How I replaced single-point-of-failure monitoring with a permissionless network of cryptographically authenticated validators and why Web3…
Building Pingsy: A Decentralized Uptime Monitor Powered by DePIN
How I replaced single-point-of-failure monitoring with a permissionless network of cryptographically authenticated validators and why Web3 primitives were the right tool for the job.
The Problem: Centralized Monitoring Has a Trust Problem
Every modern team uses uptime monitoring. Pingdom, UptimeRobot, BetterStack, they all work the same way: a handful of servers owned by one company ping your site on an interval and alert you if it goes down.
This model has three fundamental flaws:
- Single point of failure: If the monitoring provider itself has an outage, you’re flying blind, exactly when you need monitoring most.
- Regional blind spots: A provider with servers only in
us-east-1andeu-west-1cannot tell you if your site is unreachable from South Asia or South America. - Trust asymmetry: You are trusting one vendor to accurately report whether your infrastructure is working. There’s no way to audit their check methodology, verify their results, or hold them accountable for false negatives.
The third point is the most underappreciated. Monitoring is, at its core, an attestation problem: “Is this URL reachable right now?” The answer should come from multiple independent sources, not a single corporate entity.
The Solution: DePIN for Uptime
Pingsy is a Decentralized Physical Infrastructure Network (DePIN) for uptime monitoring. Instead of relying on a centralized fleet, it coordinates a permissionless network of independent validator nodes; anyone can run one to continuously health-check registered websites and report results.
The key insight: by borrowing three ideas from Web3 infrastructure, we solve all three problems above:
- Decentralized execution → No single point of failure. Validators are geographically distributed and independently operated.
- Cryptographic identity → Each validator authenticates via a Solana keypair. Every monitoring result is signed, making results tamper-evident and attributable.
- Economic incentives → Validators earn credits (
pendingPayout) for each valid check they perform, aligning their incentives with honest, reliable reporting.
Architecture Overview
Pingsy is a TypeScript monorepo managed by Turborepo, consisting of four applications and a shared database layer.

The Four Applications
**apps/web**Next.js + React user-facing dashboard. Google OAuth login, add/delete websites, view real-time uptime buckets and per-region latency.**apps/api**Express on BunREST API gateway. Handles authentication (JWT), CRUD for websites, and latency aggregation queries.**apps/hub**Bun native WebSocket coordinator. Distributes monitoring tasks to validators every 60 seconds, verifies signed results, records ticks, and credits validator payouts.**apps/validator**Bun CLI daemon worker node. Connects to the Hub via WebSocket, receives URLs to check, performs HTTP pings, measures latency, signs the result with its Solana keypair, and reports back.
Shared Packages
**packages/db**Prisma schema + generated client, shared byapiandhub. Uses@prisma/adapter-pgfor native PostgreSQL driver support.**packages/ui**Shared React component library.**packages/eslint-config**Unified ESLint configuration across all apps.**packages/typescript-config**Sharedtsconfigbase.
Data Model
The entire system is modeled with four Prisma entities:

The WebsiteTick is the atomic unit of truth in the system. Each tick records:
- Which website was checked (
websiteId) - Who checked it (
validatorId) traceable to a Solana public key - When it was checked (
time) - What the result was (
status:Good|Bad,latencyin milliseconds)
This makes every monitoring event fully auditable: you can trace any status claim back to a specific validator’s signed attestation.
The Coordination Protocol
The most interesting part of the system is the WebSocket-based coordination protocol between the Hub and the Validators. It’s a custom request-response pattern built on top of callback IDs.
Phase 1: Validator Signup
When a validator node starts, it must register with the Hub. The signup is authenticated via Solana’s Ed25519 signature scheme:

The key cryptographic step: the validator signs the message "Signup request for {callbackId} - {publicKey}" with its Solana private key. The Hub reconstructs this expected message, then verifies the signature against the claimed public key using tweetnacl. If it doesn't match, the signup is rejected.
This means you cannot impersonate a validator. Identity is tied to a Solana keypair.
Phase 2: Task Distribution (Every 60 Seconds)
The Hub runs a setInterval loop that fans out monitoring tasks:
setInterval(async () => {
const allWebsites = await prismaClient.website.findMany();
AVAILABLE_VALIDATORS.forEach((validator) => {
allWebsites.forEach((website) => {
const callbackId = crypto.randomUUID();
validator.ws.send(JSON.stringify({
type: "validate",
data: { callbackId, websiteUrl: website.url }
}));
// Register a callback for this specific task
CALLBACKS[callbackId] = async (response) => { /* ... */ };
});
});
}, 60_000);
Every registered website is sent to every connected validator. This is intentional, it produces N independent measurements for the same URL, enabling
- Consensus: if 4/5 validators say a site is up, it’s probably up (one validator might have network issues).
- Regional latency profiling: a validator in Chandigarh and one in Frankfurt will measure different latencies to the same URL.
Phase 3: Validation Response
When a validator receives a task, it performs the actual HTTP check and signs the result:
async function ValidateWebsiteHandler(websiteUrl, callbackId, ws, keyPair) {
const signedMessage = await SignMessageHandler(
`Replying to validation request ${callbackId}`,
keyPair
);
const startTime = Date.now();
const res = await fetch(websiteUrl);
const latency = Date.now() - startTime;
ws.send(JSON.stringify({
type: "validate",
data: {
latency,
status: res.status === 200 ? "Good" : "Bad",
signedMessage,
callbackId,
validatorId
}
}));
}
The Hub receives this, verifies the signature (ensuring the response came from the validator it was assigned to), then writes the result in a Prisma transaction:
await prismaClient.$transaction(async (tx) => {
// 1. Record the tick
await tx.websiteTick.create({
data: { latency, status, validatorId, time: new Date(), websiteId }
});
// 2. Credit the validator
await tx.validator.update({
where: { id: validatorId },
data: { pendingPayout: { increment: LAMPORTS_CREDITS_PER_VALIDATION } }
});
});
The transaction ensures atomicity: the tick and the payout credit either both succeed or both fail. No validator gets paid for work that isn’t recorded.
The Latency Aggregation Pipeline
When a user clicks into a website’s detail view, the API runs a multi-step aggregation:

This gives the user a real-time, per-region view: “Your site responds in 42ms from Chandigarh and 187ms from Frankfurt. It’s UP everywhere.”
The design choice of using minimum latency per region (rather than average) is deliberate, it represents the best achievable network path, filtering out transient outliers from individual validators that may have local congestion.
Authentication & Authorization
The system uses a clean separation between two identity domains:
- Users (dashboard): Google OAuth → JWT
Users authenticate via Google Sign-In. The API verifies the Google ID token, upserts the user in PostgreSQL, and issues a 7-day JWT containing { userId, email }. All subsequent API calls pass this JWT in the Authorization: Bearer header.
- Validators (network): Solana Ed25519 Keypair
Validators authenticate by cryptographically signing messages with their private key. The Hub verifies these signatures against the validator’s registered public key. No passwords, no tokens- identity is the keypair.
This dual-identity model is a natural fit: users care about convenience (Google login), while validators need tamper-proof identity (Solana keypairs).
The Frontend: Real-Time Status Buckets
The dashboard doesn’t just show “UP” or “DOWN”; it renders a 30-minute timeline of 10 status buckets, each representing a 3-minute window:

The bucketing algorithm maps ticks into time windows:
const THIRTY_MINUTES_MS = 30 * 60 * 1000;
const THREE_MINUTES_MS = 3 * 60 * 1000;
const TOTAL_BUCKETS = 10;
for (const tick of ticks) {
const rawBucketIndex = Math.floor(
(tickTime - rangeStart) / THREE_MINUTES_MS
);
const bucketIndex = Math.min(Math.max(rawBucketIndex, 0), TOTAL_BUCKETS - 1);
// Priority: bad > good > no-info
// A single "bad" tick in a window marks the whole bucket red
}
This “worst-status-wins” strategy is intentional — a single failure in a 3-minute window is worth surfacing, even if the site recovered immediately after.
Tech Stack Summary

Language: TypeScript (end-to-end)
Runtime: Bun (API, Hub, Validator), Node.js (Web/Next.js)
Frontend: Next.js 16, React 19, Tailwind CSS 4
API: Express 5, JWT, Google Auth Library
Real-time: Bun native WebSocket server
Crypto: tweetnacl (Ed25519), @solana/web3.js (keypair management)
Database: PostgreSQL via Prisma ORM (with @prisma/adapter-pg)
Build: Turborepo for monorepo orchestration
Auth: Google OAuth 2.0 (users), Solana Ed25519 (validators)
What Makes This Different
Most uptime monitors are SaaS products where you trust a vendor. Pingsy inverts this:
- Anyone can run a validator. The network is permissionless. You install it, point it at the Hub, and your Solana keypair is your identity.
- Every result is signed. You can independently verify that a specific validator made a specific claim about a specific URL at a specific time.
- Validators are economically incentivized. The
pendingPayoutsystem (currently at 10 lamport-credits per validation) ensures validators have skin in the game. - Regional truth, not regional assumptions. Instead of assuming your site is up because a US datacenter says so, you get per-region latency and status from actual machines in those locations.
Conclusion
Pingsy demonstrates that DePIN isn’t just for storage and compute. It’s a natural fit for any attestation workload where you need independent, verifiable, geographically distributed witnesses. Uptime monitoring is one of the simplest instances of this pattern, but the same architecture (WebSocket coordination + signed attestations + economic incentives) could extend to SSL certificate monitoring, DNS propagation checks, or even API contract testing.
The codebase is intentionally small ~600 lines of core logic across the four apps because the complexity lives in the protocol design, not the implementation. That’s usually a good sign.
Built with TypeScript, Bun, Solana, and a healthy distrust of centralized infrastructure.
메타데이터
- post_id
- afc73d2c3f71
- slug
- building-pingsy-a-decentralized-uptime-monitor-powered-by-depin-afc73d2c3f71
- url
- https://coinsbench.com/building-pingsy-a-decentralized-uptime-monitor-powered-by-depin-afc73d2c3f71
- canonical_url
- https://coinsbench.com/building-pingsy-a-decentralized-uptime-monitor-powered-by-depin-afc73d2c3f71
- author_url
- https://medium.com/@singhakem03
- status
- ok
- fetched_at
- 2026-07-09 13:13:48