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: deployed and verified on BOT Chain mainnet (chainId 677). The hosted app runs on mainnet with real PAY/SLASH settlements on chain; all four PulseGrid contracts are verified on
scan.botchain.ai(see Mainnet deployment). Gasless blob-anchored telemetry, on-chain per-epoch pay/slash with value-layer data-quality SLAs, a permissionless keeper set, an autonomous Go agent, and a Next.js operator app wired to real chain and agent data. Every headline number 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
device-signed facts and the SLA. A buggy or malicious agent (or keeper) cannot overpay, wrongly slash,
pay an un-anchored or out-of-range epoch, or double-settle. This is proven by an adversarial
Foundry suite (test_Adversarial_*), part of 81 passing tests.
Two-layer guarantee. The device signs its reading as a first-class EIP-712 field (int256 value,
milliunits). With a value-layer SLA, an interval pays only when at least minAnchors readings were
both delivered and in range [minValue, maxValue]; a device that streams its full quota out of
range is slashed. The guarantee thus extends from liveness (did you deliver?) to data quality
(was the data good?), with the agent still unable to assert either. Proven live on mainnet.
Permissionless settlement. settleEpoch pays an equal tip to whoever calls it from a dedicated
keeper pool (never skimmed from operator pay or the slash, so amounts are unchanged), turning the
single Settlement Agent into an incentivized, permissionless keeper set.
| 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.
flowchart TD
subgraph OFF["Off-chain"]
DEV["Device (EOA, 0 gas)<br/>signs EIP-712 telemetry"]
REL["Sponsor Relayer<br/>MegaFuel-shaped · pays gas"]
AGENT["Settlement Agent (Go)<br/>autonomous · predicts, never decides"]
APP["Next.js App<br/>wagmi + viem"]
AI["Advisory AI (DeepSeek)<br/>non-authoritative"]
end
subgraph ON["On-chain · BOT Chain mainnet (677)"]
TA["TelemetryAnchor<br/>verifies sig · buckets anchorsInEpoch"]
PM["PolicyModule<br/>pure pay/slash verdict"]
SV["SettlementVault<br/>dual escrow"]
OPW(["Operator wallet"])
TRE(["Treasury"])
end
DEV -->|"signed telemetry"| REL
REL -->|"type-3 blob tx"| TA
AGENT -->|"settleEpoch(deviceId, epoch)"| SV
TA -.->|"anchorsInEpoch (the only settlement fact)"| SV
PM -.->|"re-derives verdict"| SV
SV -->|"PAY · payEscrow"| OPW
SV -->|"SLASH · bond"| TRE
APP -->|"viem reads"| TA
APP ==>|"wagmi wallet writes"| SV
AGENT -.-> AI -.-> APP
The Settlement Agent supplies only
(deviceId, epoch); the vault re-derives pay-or-slash on-chain from the device-signedanchorsInEpochand the epoch's SLA. The agent (and the advisory AI) never decide a payment.
- contracts/ Foundry:
DeviceRegistry,TelemetryAnchor(epoch buckets),ServiceContract(versioned SLA),PolicyModule(pure verdict library),SettlementVault(dual escrow), andMockUSD(local dev only; mainnet settles in canonical USDT). - 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: 81/81 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.botchain.ai), 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 677 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.botchain.ai. (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 BOT. 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, then start the hosted services (see Host it). A ~10 BOT 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
677with a little BOT for gas, on the live mainnet app. - Get USDT + approve. In
/app/treasury, use Get USDT (bridge) to bridge canonical USDT to your wallet, 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. 5 USDT). Its next PAY is now real USDT.
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 USDT, all with no local process. The autonomous agent settles it like
any other device; on-chain policy decides.
cp .env.example .env # then set PRIVATE_KEY to a funded key on your target BOT Chain RPC
# 1. Deploy the settlement stack (prints the 5 addresses + deploy block)
cd contracts && forge script script/DeploySettlement.s.sol:DeploySettlement \
--rpc-url $BOT_RPC --broadcast --legacy --with-gas-price 50000000000 \
--private-key $PRIVATE_KEY
# 2. Register a device fleet (operator key), then bond + fund escrow + set each SLA
# (fund the settlement token to operator + funder; setSLA takes an SLA active from a future epoch).
cd ../agent && 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
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.
The browser tab and devicesim fleet are on-ramps; real hardware runs cmd/device - one device,
its own key held in an encrypted keystore (not a shared plaintext fleet.json), signing each
reading and streaming it gaslessly. The signing is byte-identical to the contract, so a device built
on the importable SDK (github.com/Manuel-dev01/pulseGrid/agent/device) is a first-class node. On-chain key
rotation is supported (DeviceRegistry.rotateDeviceKey).
cd agent
# 1. Provision: encrypt a fresh or browser-exported key into a keystore file.
KEYPASS=hunter2 RAWKEY=0x<privkey> go run ./cmd/device -import-key \
-key-env RAWKEY -keystore-out ./keys -keystore-pass-env KEYPASS
# (register that device address on-chain once: DeviceRegistry.registerDevice)
# 2. Run the device (device wallet spends ZERO gas; the relayer sponsors).
KEYPASS=hunter2 go run ./cmd/device -id NODE-1 -anchor $ANCHOR -relayer $RELAYER_URL \
-rpc https://rpc.botchain.ai -keystore ./keys/UTC--... -keystore-pass-env KEYPASS \
-metric power_kw -unit kW -min 38 -max 46 -interval 10sThe device logs each anchored reading's tx hash; it never sends a transaction itself. Value-layer
SLAs (below) pay it only when its readings fall in the configured [min, max] band.
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; needsPRIVATE_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 BOT) and each settlement (~0.03 BOT) costs real BOT. 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 BOT 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.
Deployed and Blockscout-verified on BOT Chain mainnet. The hosted DApp https://pulsegrid-nine.vercel.app runs against these contracts.
| Contract | Address (verified) |
|---|---|
| DeviceRegistry | 0x3697113dEe694BeEA5FD354384D03ec9198DE7aF |
| TelemetryAnchor | 0x6377BC3B08d81e33Fa91927D71CC7f806Fc45662 |
| ServiceContract | 0x908302Eb8810ef6E40f02dC5Dd63E62bc1cC00Eb |
| SettlementVault | 0x6380ebC2EbF72fC6deA07028e500943fb876C546 |
| USDT (settlement token, 6dp) | 0xaBabc7Ddc03e501d190C676BF3d92ef0e6e87a3C |
Deploy block 18822148, 600s (cost-tuned) epochs. The four PulseGrid contracts read Verified on
https://scan.botchain.ai/address/<addr>; the settlement token is BOT Chain's canonical
bridge-backed USDT (6 decimals), so the network settles operators in real USDT, no mock asset.
Proven live on mainnet: a device streamed gaslessly and the autonomous agent settled real USDT,
PAY 0x6b70ff3cd3402ade72ef8701099c394fcdc7684a7484b806ced8c97ad22623a2 (escrow to operator) and SLASH
0xd3263742c27c83bec60f441199e7983e22ea077fd206ccd4cdc7003377f69c75 (bond to treasury), each viewable
on scan.botchain.ai.
Settlement asset. The production vault settles in canonical USDT (0xaBabc7...), acquired via the
free bridge at bridge.botchain.ai. Operators are paid and slashed in real USDT: a device streams
telemetry gaslessly (its wallet spends 0 BOT) and, once its escrow and bond are funded, the autonomous
agent settles real USDT PAY/SLASH per interval. The vault is asset-agnostic (the token is a
constructor argument).
- Mainnet (live deployment): chainId
677, RPChttps://rpc.botchain.ai, explorerhttps://scan.botchain.ai - Run the app against any BOT Chain endpoint via
NEXT_PUBLIC_CHAIN_IDplus the matchingNEXT_PUBLIC_*_ADDR/BOT_RPC; the shippedweb/.env.local.exampletargets the live mainnet instance above.