Real-time DePIN telemetry and verifiable settlement on BOT Chain.
Devices stream signed telemetry that is anchored on-chain every interval. An autonomous Settlement Agent then pays or slashes each operator per interval in stablecoin, automatically, continuously, and gaslessly for the devices. On-chain policy is the sole authority: an operator cannot be paid for data they did not deliver, and the agent cannot stiff an honest operator. Built for the BOT Chain Builder Challenge #1 (DePIN / Real World track, AI-Agent cross-eligible).
▶ Demo video: https://www.youtube.com/watch?v=zK4Gjhi_khY · Live app: https://pulsegrid-nine.vercel.app
Status: live on BOT Chain testnet (chainId 968). Gasless blob-anchored telemetry, on-chain per-epoch pay/slash, an autonomous Go agent, and a Next.js operator app wired to real chain and agent data. Every headline number below is a live chain read with an explorer link.
Seamless, no local setup. A tester connects a wallet, deploys a node, and clicks Go live to stream signed telemetry directly from the browser tab (gaslessly, the device key never leaves the client). The agent then pays it every satisfied interval and slashes it if the tab closes, all on-chain. The registry is permissionless, so anyone is an operator of the nodes they deploy. An optional DeepSeek layer adds advisory device-health narratives, kept strictly non-authoritative.
Reading the dashboard. It is always live against chain state, so it is never blank: the settlement ledger, its full settled history, and every proof transaction render immediately. BOT Chain has a gas floor, so the streaming fleet runs in short funded windows (for a recording or a judging session), not 24/7. When it is not streaming, devices honestly read "idle, not streaming" or "awaiting telemetry". That is truthful live state, not mock data. See What you'll see to bring it fully live.
Bilateral machine-trustlessness. SettlementVault.settleEpoch(deviceId, epoch) takes only
(deviceId, epoch). The pay/slash verdict and the amount are re-derived on-chain from the
device-signed anchor count and the SLA. A buggy or malicious agent cannot overpay, wrongly slash,
pay an un-anchored epoch, or double-settle. This is proven by an adversarial Foundry suite
(test_Adversarial_*), part of 68 passing tests.
| Native primitive | How PulseGrid uses it |
|---|---|
| Sub-second blocks (0.74s, measured) | per-interval real-time settlement driven by an autonomous agent |
| Near-zero fees (base fee 0, measured) | continuous micro-settlement is economically viable |
| EOA Paymaster / gasless | devices sign EIP-712 telemetry and spend zero gas (plain EOAs, no 4337) via a self-hosted MegaFuel-shaped sponsor relayer |
| Blob API (EIP-4844) | raw telemetry rides in type-3 blobs; a compact blobhash(0) anchor is committed to state |
| On-chain policy authority | the agent only drives; the vault re-derives every verdict |
Autonomous + advisory AI (AI-Agent track). The Settlement Agent is an autonomous Go service that
drives settlement each epoch; the contract decides. An optional DeepSeek layer reads the on-chain
facts and the statistical reputation to produce advisory device-health analysis for the dashboard. It
is display-only: there is no code path from any AI output to settleEpoch, so the guarantee is
untouched, and the app runs identically without a key.
See docs/ARCHITECTURE.md for the design and
docs/WRITEUP.md for the submission narrative.
device (EOA, 0 gas) --EIP-712 signed telemetry--> sponsor relayer --type-3 blob tx--> TelemetryAnchor
| (anchorsInEpoch)
autonomous Settlement Agent --settleEpoch(deviceId, epoch)--> SettlementVault <-- PolicyModule (re-derives)
(Go; predicts, never decides) dual escrow: funder payEscrow -> operator (PAY)
operator bond -> treasury (SLASH)
Next.js app --viem server routes (reads)--> contracts + reputation.json (agent intelligence layer)
--wagmi wallet txs (writes)---> contracts
- contracts/ Foundry:
DeviceRegistry,TelemetryAnchor(epoch buckets),ServiceContract(versioned SLA),PolicyModule(pure verdict library),SettlementVault(dual escrow),MockUSD. - agent/ Go:
settler(autonomous agent),paymaster(sponsor relayer),devicesim(gasless fleet),internal/{telemetry,chain,policy,reputation,ai}(ai= optional DeepSeek advisory layer). Container/host config inagent/Dockerfile+agent/railway.json. - web/ Next.js (App Router, wagmi/viem): landing plus the operator app, live-wired. A deployed node can stream from the browser tab (same EIP-712 signing) through the hosted relayer, no install.
Foundry (forge/cast), Go 1.25+, Node 20+.
The app defaults to the deployed demo instance, so it runs against real chain state out of the box.
git clone <this repo> && cd pulseGrid
make test # Foundry: 68/68 green
cp web/.env.local.example web/.env.local
make web-install
make web # http://localhost:3000 (landing) and /app (operator flow)The dashboard reads the live contracts: the fleet table, the settlement ledger (PAY green / SLASH
red, each deep-linked to scan.bohr.life), per-device detail, the on-chain SLA, treasury balances,
and the agent's reputation snapshot. No mock data anywhere on the app path.
Connect a wallet on chainId 968 and you can deploy your own node in /app/deploy, then click
Go live to stream it straight from the browser (gasless, no local process). It appears under
Operators and is settled automatically. /app/operators groups every device by operator wallet
with per-operator earnings, slashes, and reputation.
The dashboard is always live against chain state, so it is never blank: the settlement ledger,
the full settled history, dev-001/003's paid history, and dev-002's slash are permanent on-chain
facts and render immediately, each deep-linked to scan.bohr.life. (The ledger is cumulative and
grows with every demo window, so read current totals from the live app, not a fixed number here.)
BOT Chain has a gas floor, so gasless anchoring and per-epoch settlement cost real tBOT. We therefore do not run the streaming fleet 24/7; it runs in short funded windows. When it is not in a window, devices honestly read "idle, not streaming" or "awaiting telemetry", and recent intervals read "idle, no telemetry". That is truthful live state, not mock data and not a bug: a device that is not currently delivering is shown as such (and would be slashed for it).
To see it fully live (devices streaming, new PAY/SLASH rows appearing on camera):
- Fastest, one device: deploy a node in
/app/deployand click Go live to stream it from your browser. Needs the hosted relayer funded (below). - Full fleet: fund the relayer and agent keys from the faucet, then start the hosted services (see Host it). A ~10 tBOT budget covers roughly 25 to 45 minutes of live operation; stop the services when done. Every settlement is a real on-chain tx.
This is the seamless product flow: you become an operator and watch your own device get paid, entirely from the browser. It uses two tabs on purpose (explained below).
- Connect a wallet on chainId
968with a little tBOT for gas (faucet above), on the live app or your local one. - Mint + approve. In
/app/treasury, click Mint 1,000 mUSD, then Approve vault (a one-time allowance, no visible change is expected). - Deploy in
/app/deploy: give it a label, keep minAnchors 3, click 1 Register device, then 2 Bind policy. A device signing key is generated in this tab and never leaves it. - Go live (button appears after step 3). The tab now signs a reading every ~10s and the sponsor relayer anchors it on-chain, gaslessly. You will see anchored ✓ tx ticks within ~10 seconds. Keep this tab open: it is the device; closing it stops the stream and the next interval slashes.
- Fund escrow, in a second tab. Your node streams and settles immediately, but each PAY is
$0until its pay-escrow is funded. Open/app/treasuryin a new tab (leave the streaming tab running), find your node under Your devices, and Deposit escrow (e.g. 500). Its next PAY is now real mUSD.
Why two tabs? The in-browser device key lives only in the deploy tab's memory. Navigating that tab away (to Treasury, or via a redirect) regenerates the key, i.e. a different device that is no longer the one streaming. So you fund escrow in a separate tab and let the first keep streaming. All in-app links from the Go-live panel open in new tabs for exactly this reason.
Within one or two intervals your node shows online on /app, appears under your wallet on
/app/operators, and earns real mUSD, all with no local process. The autonomous agent settles it like
any other device; on-chain policy decides.
cp .env.example .env # then set TESTNET_PRIVATE_KEY to a faucet-funded testnet key
# faucet: https://faucet.botchain.ai/basic
# 1. Deploy the settlement stack (prints the 5 addresses + deploy block)
cd contracts && forge script script/DeploySettlement.s.sol:DeploySettlement \
--rpc-url bot_testnet --broadcast --legacy --with-gas-price 50000000000 \
--private-key $TESTNET_PRIVATE_KEY
# 2. Register a device fleet (operator key), then bond + fund escrow + set each SLA
# (MockUSD.mint is open; setSLA takes an SLA active from a future epoch).
cd ../agent && TESTNET_PRIVATE_KEY=$OPERATOR_KEY go run ./cmd/devicesim -register -n 3 -registry $REGISTRY
# 3. Run the three processes; point web at your addresses via NEXT_PUBLIC_* in web/.env.local
TESTNET_PRIVATE_KEY=$RELAYER go run ./cmd/paymaster -addr :8789 -anchor $ANCHOR # sponsor (devices pay 0 gas)
go run ./cmd/devicesim -anchor $ANCHOR -relayer http://localhost:8789 \
-interval 2s -drop dev-002 -drop-after 240s # gasless fleet; drop one to force a SLASH
AGENT_PRIVATE_KEY=$AGENT go run ./cmd/settler -registry $REGISTRY -anchor $ANCHOR \
-service $SERVICE -vault $VAULT -from-block $DEPLOY_BLOCK -tick 15s -rep-snapshot reputation.jsonThe agent logs, per epoch, its off-chain predict and then the on-chain settled result: the two
always match, because the contract is the authority. A live device is paid; the dropped device,
whose epochs anchor below the SLA threshold, is slashed and its bond flows to the treasury.
To keep the network live and let browser-deployed nodes stream, host the three Go services with the
provided agent/Dockerfile + agent/railway.json. It is
one image, the role selected by PG_SERVICE=relayer|settler|fleet:
- relayer (
paymaster) - public HTTPS, sponsors gas; needsTESTNET_PRIVATE_KEY,ANCHOR_ADDR. Point the app at it withNEXT_PUBLIC_RELAYER_URL(Vercel env orweb/.env.local). - settler - needs
AGENT_PRIVATE_KEY+ the contract addresses; auto-discovers every on-chain device. Serves its reputation at/reputation(setREPUTATION_URLon the app to surface it and the advisory AI). OptionalDEEPSEEK_KEYenables the narratives;BACKFILL=Nstops a freshly started agent from re-settling a long idle gap. - fleet (
devicesim) - streams the canonical devices; needs the device keys (FLEET_JSON).
Economics (why funded windows, not 24/7). BOT Chain has a ~50 gwei gas floor, so each gasless blob
anchor (~0.04 tBOT) and each settlement (~0.03 tBOT) costs real tBOT. Run the fleet and settler in
short funded windows: fund the relayer and agent keys, railway up the services, record or judge,
then railway down. A ~10 tBOT budget covers roughly 25 to 45 minutes of live operation. Browser
Go live streams a single device and needs only a funded relayer, so it is much cheaper. When
nothing is streaming, the app shows honest idle state; the settled ledger stays on-chain permanently.
| Contract | Address |
|---|---|
| DeviceRegistry | 0x8e55fac4f0bdc53662ef6f55cd38e897d6071ed0 |
| TelemetryAnchor | 0x2cc5946299f11be57f43933e780f6f39de490bcf |
| ServiceContract | 0x30cc2a56538dd04d4c4c1900f555c4b47aa0f95a |
| SettlementVault | 0x252cf0165dca8d04aae9da184b3836c12144cd40 |
| MockUSD (mUSD, 6dp) | 0x975e7990fae4e71f0b40d4a6b990e872d3046607 |
Deploy block 15371632, 60s settlement epochs (tuned for live-testnet relayer throughput). Look up
any address or tx on the explorer below.
- Testnet (build target): chainId
968, RPChttps://rpc.bohr.life, explorerhttps://scan.bohr.life - Faucet:
https://faucet.botchain.ai/basic(manual, Turnstile-gated) - Note the domain split: testnet infra is on
*.bohr.life; product and mainnet links use*.botchain.ai.