I wanted ride-sharing operations — request, offer, accept, pay, cancel — to be first-class on-chain transactions, not generic smart-contract calls wrapped in app logic. So I built Clutch Protocol: a custom non-EVM blockchain in Rust, a GraphQL bridge for apps, a JavaScript SDK for client-side signing, and a public stage testnet you can try without installing anything.
This post is the technical story: what I built, why I didn't use Ethereum, how a ride actually flows through the stack, and what's still alpha.
The problem I was solving
Traditional ride apps centralize trust: the platform owns matching, payments, and dispute resolution. Putting the ride state machine on-chain changes the contract between riders, drivers, and app builders:
- Every step is a signed, auditable transaction
- Private keys stay on the client (Bitcoin-style)
- App developers can earn on-chain referrer fees when users complete rides
- Drivers receive CLT directly via
RidePay, not through a platform ledger
The tradeoff is real: you lose EVM composability and must ship custom SDKs. For a domain-specific protocol, that tradeoff felt acceptable.
Architecture at a glance
Demo App / Your dApp
│
▼
clutch-hub-sdk-js (client-side signing, RLP, secp256k1)
│
▼
clutch-hub-api (GraphQL + WebSocket + faucet)
│
▼
clutch-node (Aura consensus, WebSocket JSON-RPC)
│
▼
clutch-explorer (indexer → Postgres → REST UI)
| Component | Role | Stack |
|---|---|---|
| clutch-node | Blockchain core | Rust, Aura, libp2p |
| clutch-hub-api | App bridge | Rust, async-graphql |
| clutch-hub-sdk-js | Client SDK | TypeScript, npm |
| clutch-hub-demo-app | Reference UI | React, Vite, Leaflet |
| clutch-deploy | Full stack | Docker Compose |
Docs: https://docs.clutchprotocol.io
Why a custom chain (and not Ethereum)
Clutch is non-EVM. Ride operations are native transaction types with RLP encoding:
| Tag | Type | Purpose |
|---|---|---|
| 1 | RideRequest |
Passenger requests a ride |
| 2 | RideOffer |
Driver offers to fulfill |
| 3 | RideAcceptance |
Passenger accepts; fare debited |
| 4 | RidePay |
Payment installment to driver + referrers |
| 5 | RideCancel |
Cancel trip; refund unpaid fare |
| 8 | RideRequestCancel |
Cancel pending request |
Apps don't deploy contracts. They call the Hub API for unsigned payloads, sign locally, and submit signed RLP hex. The node validates signatures, nonces, and applies the ride state machine.
What you gain: simpler app surface, predictable tx format, ride logic enforced in the node.
What you lose: DeFi composability, existing wallet/tooling, large validator ecosystem.
How a ride works (end to end)
RideRequest → RideOffer(s) → RideAcceptance → RidePay → completed
↓ ↓
RideRequestCancel RideCancel
1. Build unsigned transaction (server)
The Hub API constructs the payload and injects referrer addresses from config:
mutation {
createUnsignedRideRequest(
pickupLatitude: 35.7,
pickupLongitude: 51.4,
dropoffLatitude: 35.8,
dropoffLongitude: 51.5,
fare: 1000
)
}
Returns JSON like:
{
"from": "0x...",
"nonce": 3,
"data": {
"function_call_type": "RideRequest",
"arguments": { }
}
}
2. Sign client-side (never send private keys)
The SDK hashes and signs with secp256k1. Keys never touch the API:
import { ClutchHubSdk } from 'clutch-hub-sdk-js';
const sdk = new ClutchHubSdk('https://api-stage.clutchprotocol.io', publicKey);
await sdk.ensureAuth();
const unsigned = await sdk.createUnsignedRideRequest({
pickup: { latitude: 35.7, longitude: 51.4 },
dropoff: { latitude: 35.8, longitude: 51.5 },
fare: 1000,
});
const signed = await sdk.signTransaction(unsigned, privateKey);
3. Submit signed transaction
await sdk.submitTransaction(signed.rawTransaction);
The Hub forwards to the node over WebSocket JSON-RPC (send_raw_transaction). Validators include the tx in a block; state updates atomically.
4. Read state (GraphQL or subscriptions)
const requests = await sdk.listRideRequests();
await sdk.subscribeRideRequests((updated) => {
console.log('Open requests:', updated.length);
});
Subscriptions multiplex over a shared WebSocket to /graphql/ws. Under the hood the API polls the node (~0.5–1s) and pushes snapshots — honest alpha limitation.
CLT economics (driver-first)
Ride payments and validator rewards are separate:
| Layer | Mechanism | Default |
|---|---|---|
RidePay |
Referrer fees + driver remainder | 2% request + 2% offer |
| Blocks | Fixed reward to block author | 50 CLT per block |
Example: 10 CLT fare, one full RidePay, both referrers set:
- Request referrer: 1 CLT
- Offer referrer: 1 CLT
- Driver: 8 CLT
App builders: run your own Hub API, set your wallet as default_ride_request_referrer / default_ride_offer_referrer, and earn CLT when users complete rides on your deployment. No separate grants program — rewards come from real ride activity.
Details: https://docs.clutchprotocol.io/getting-started/app-developer-incentives
Security model
- Client-side signing only — API receives signed RLP hex, not private keys
-
Wallet JWT — identity is a public key (
generateToken), no passwords - Nonce anti-replay — per-account nonce enforced on-chain
-
Faucet — only server-side signer, testnet
Transferonly; disable in production
Try it in 3 minutes (no install)
- Open https://app-stage.clutchprotocol.io
- Choose Passenger or Driver → generate wallet → Request CLT (faucet)
- Passenger: request a ride on the map · Driver: submit an offer
Full tutorial: https://docs.clutchprotocol.io/getting-started/ride-lifecycle
Run the full stack locally
git clone https://github.com/clutchprotocol/clutch-deploy.git
cd clutch-deploy
cp .env.example .env
docker compose up -d
| Service | URL |
|---|---|
| Demo app | http://localhost:5173 |
| Hub API | http://localhost:3000/health |
| Explorer | http://localhost:5174 |
npm install clutch-hub-sdk-js
What's alpha (honest limitations)
- Testnet only — no mainnet, small validator set on stage
- DAO governance — roadmap, not shipped
-
ConfirmArrival/ComplainArrival— stubs in the node, not in Hub/SDK yet - Hub subscriptions poll the node; not push-from-chain yet
- APIs may change without notice
Open source
Eight public repos under https://github.com/clutchprotocol
- Star the org if this is interesting
- Discussions: https://github.com/orgs/clutchprotocol/discussions
- Issues and PRs welcome — conventional commits appreciated
Questions I'd love feedback on
- Domain-specific chain vs. smart contracts — worth it for ride-sharing, or would you always pick an L2?
- Referrer-fee model for app builders — sensible incentive or weird?
- What would you build on this stack?
Links
- Website: https://clutchprotocol.io
- Docs: https://docs.clutchprotocol.io
- Stage demo: https://app-stage.clutchprotocol.io
- npm SDK: https://www.npmjs.com/package/clutch-hub-sdk-js
- GitHub: https://github.com/clutchprotocol
Built by Mehran Mazhar (GitHub). Alpha software — use at your own risk.
Top comments (2)
The limitations section is what makes me trust the rest of the post, and it also points right at the hard part. The chain can prove a RidePay happened, but it can't know the ride itself did, and that gap is exactly where ConfirmArrival and ComplainArrival sit as stubs today. Once you wire those up, you're back to needing someone or something off-chain to vouch that the car actually showed up, which is the classic oracle problem in disguise. How are you thinking about closing that without a trusted middle party quietly becoming the thing the whole design was trying to avoid?
Thanks for reading — happy to answer questions in the comments.
Try it without installing anything: app-stage.clutchprotocol.io
(create wallet → Request CLT → request a ride or submit an offer)
Docs: docs.clutchprotocol.io
GitHub: github.com/clutchprotocol
Alpha testnet — APIs may still change.
Curious what you think: for ride-sharing, would you build on a domain-specific chain with native ride tx types, or always use smart contracts on an L2?