AI proposes. The chain disposes.
SentryPay is an autonomous verdict engine that watches real-world risk signals and — without any human approval step — executes an on-chain payout through HSP, with every decision's reasoning permanently logged for audit.
Submitted to the HashKey Chain Horizon Hackathon (AI Track) · July 2026
A policy defines a monitored metric, a trigger threshold, and a payout amount. Premiums are deposited into an on-chain pool. An off-chain AI monitor continuously polls the metric, runs it through a deterministic weighted-factor confidence engine, and submits a signed verdict on-chain. A smart contract independently re-checks the threshold — if both the AI's verdict and the contract's own calculation agree the condition is met, the contract calls the HSP router to settle the payout. No human is in the loop, and every verdict (triggered or not) is permanently logged with its contributing factors.
| Contract | Address |
|---|---|
| PolicyRegistry | 0xe68A4F963B294a3B0f44C3deC41759918CA8702c |
| ProtectionPool | 0x619B9465d6dD6EBAc4Dff383E9d7fe881284cD5a |
| PayoutTrigger | 0x7FEb9FDcd1C4596FD3fb912d79c75a5B2Cf06DCA |
| HSP Router | 0xA8E6fDa725895D40d56215077Ace2949eB2caf83 |
RPC:
https://testnet.hsk.xyz· Explorer: https://testnet-explorer.hsk.xyz Deploy block (event-scan start):30257900
| Component | Host | Notes |
|---|---|---|
| Dashboard (React SSR) | Vercel | Reads verdicts/payouts directly from the chain in the browser. |
| AI Monitor | GitHub Actions (cron, every 5 min) | Runs one poll cycle per invocation (RUN_ONCE), submits a signed verdict on-chain. |
| Shared state | HashKey Chain (testnet) | The two services never talk directly — the chain is the single source of truth. Monitor writes verdicts; dashboard reads them. |
See docs/CLOUD_DEPLOYMENT.md for the full free-hosting setup.
AI Monitor (GitHub Actions cron) HashKey Chain (testnet) Dashboard (Vercel)
──────────────────────────────── ─────────────────────── ──────────────────
fetchMetric.ts ┐ PolicyRegistry.sol
confidenceEngine.ts ├─ submitVerdict ─►ProtectionPool.sol ┌────► chain-reader.ts
runner.ts (RUN_ONCE) ┘ PayoutTrigger.sol │ (ethers getLogs)
│ │
▼ │
HSP Router → Beneficiary │
│ │
(events emitted) ─────────┘
The monitor and dashboard never call each other. They agree only because both point at the
same contract addresses on the same chain — the monitor writes VerdictLogged / PayoutExecuted
events, and the dashboard reads them via eth_getLogs on every refresh.
-
Deterministic confidence engine, not an LLM. The trigger decision uses a rule-based weighted-factor scorer. Same inputs → same output, always. The weights and factors are visible in source code and hashed on-chain. This is a deliberate choice for money-moving decisions — reproducibility and auditability over opaque AI calls.
-
Chain re-validates independently.
PayoutTrigger.soldoes not trust the AI'soffchainTriggeredflag alone. It re-checks the metric value against the stored threshold itself. The contract — not the script — is the final arbiter. -
Always log, fail closed. Every poll cycle produces a logged
VerdictLoggedevent, whether triggered or not. If the data source is unavailable, the cycle is skipped entirely — no verdict submitted, no false negatives dressed up as considered "no" decisions. -
No double-pay. Once a policy is
settled, any further verdict submissions for it simply log without executing another payout.
SentryPay/
├── contracts/ Solidity contracts + Hardhat setup
│ ├── src/
│ │ ├── PolicyRegistry.sol Policy definitions
│ │ ├── ProtectionPool.sol Premium custody + HSP disburse
│ │ ├── PayoutTrigger.sol On-chain decision gate
│ │ └── MockHSPRouter.sol Local dev only — NEVER deployed to mainnet
│ ├── interfaces/
│ │ └── IHSPRouter.sol
│ ├── test/ Hardhat/Mocha contract tests
│ └── scripts/
│ ├── deploy-local.ts Deploy to a local Hardhat node
│ ├── deploy-testnet.ts Deploy to HashKey testnet (Chain 133)
│ ├── deploy-mainnet.ts Deploy to HashKey mainnet (Chain 177)
│ ├── seed-demo-policy.ts Create a demo policy for end-to-end testing
│ └── verify.ts Blockscout source verification
│
├── monitor/ AI monitor service (Node.js + TypeScript)
│ ├── src/
│ │ ├── types.ts Shared type definitions
│ │ ├── fetchMetric.ts Data source polling
│ │ ├── confidenceEngine.ts Weighted-factor verdict engine
│ │ ├── submitVerdict.ts On-chain verdict submission (native rail)
│ │ ├── runner.ts Poll loop (persistent, or single-pass via RUN_ONCE)
│ │ ├── hspMandate.ts EIP-712 HSP Mandate build + sign (USDC rail)
│ │ ├── hspRelay.ts Watches PayoutExecuted, settles USDC via HSP
│ │ ├── settleOnce.ts One-shot HSP USDC settlement (dry-run helper)
│ │ ├── demoTrigger.ts CLI staged-trigger helper (BELOW policies)
│ │ └── checkBalances.ts Wallet gas / USDC balance preflight
│ └── test/
│ ├── confidenceEngine.test.ts
│ └── hspMandate.test.ts HSP mandate-hash vectors
│
├── .github/workflows/
│ └── monitor.yml Free cron host — runs the monitor once every 5 min
│
├── api/
│ └── index.mjs Vercel serverless entry — bridges the SSR handler to Node
├── vercel.json Vercel build + routing config (SSR)
│
├── src/ React frontend (TanStack Start / Router)
│ ├── lib/
│ │ ├── mock-data.ts Demo data (fallback when chain not connected)
│ │ ├── chain-config.ts Network config + contract addresses
│ │ ├── chain-reader.ts Ethers.js read-only event / state reader
│ │ ├── chain-writer.ts Signed writes: createPolicy / deactivate / deposit
│ │ ├── wallet.ts EIP-1193 injected-wallet plumbing
│ │ ├── demo-trigger.ts Server-function wrapper for the staged demo trigger
│ │ └── demo-trigger.server.ts SERVER-ONLY — signs the demo verdict (AI signer key)
│ ├── hooks/
│ │ ├── use-chain-data.ts TanStack Query hooks for live chain data
│ │ ├── use-wallet.tsx Wallet connection state (React context)
│ │ ├── use-is-owner.ts On-chain owner gating for the Admin area
│ │ ├── use-create-policy.ts / use-deactivate-policy.ts / use-deposit-premium.ts
│ │ ├── use-demo-trigger.ts Fires the staged demo trigger
│ │ └── use-owner-state.ts Local "simulate owner" toggle (mock mode only)
│ ├── components/ TopNav, Footer, and the ui-sentry design kit
│ └── routes/ Page components (Overview, Policies, Verdicts, Payouts, Admin)
│
├── docs/
│ ├── DEPLOYMENT.md Native + HSP USDC settlement runbook
│ └── CLOUD_DEPLOYMENT.md Free Vercel + GitHub Actions hosting setup
├── .env.example Environment variable template
└── README.md
# Frontend
npm install
# Contracts
cd contracts && npm install
# Monitor
cd monitor && npm installcp .env.example .env
# Fill in DEPLOYER_PRIVATE_KEY, AI_SIGNER_PRIVATE_KEY, RPC_URL, etc.cd contracts
npx hardhat test# Terminal 1 — start local node
cd contracts && npx hardhat node
# Terminal 2 — deploy
cd contracts && npx hardhat run scripts/deploy-local.ts --network localhostcd monitor
METRIC_SOURCE_TYPE=MOCK MOCK_SCENARIO=gradual_decline npx tsx src/runner.tsnpm run devFollow this step-by-step sequence to deploy, verify, and run SentryPay on HashKey Chain Testnet:
- Fund your wallets: Go to the HSP Sandbox Faucet and paste the public addresses for both your Deployer and AI Signer wallets to request testnet HSK and test USDC.
- Deploy the contracts:
Note: If
cd contracts npx hardhat run scripts/deploy-testnet.ts --network hashkeyTestnetHSP_ROUTER_ADDRESS_TESTNETis left blank in your.env, this script will automatically deploy and wire aMockHSPRoutercontract so you can test for free. - Configure environment:
The deploy script generates a
deployments.testnet.envfile. Copy the printed variables directly into your root.envfile. - Verify the contracts on Blockscout:
npx hardhat run scripts/verify.ts --network hashkeyTestnet
- Test the End-to-End flow:
- Start the frontend:
npm run devand deposit some test HSK premium. - Start the monitor:
cd ../monitor npm run dev - Watch the monitor evaluate the mock scenario, log the verdict on-chain, and execute the payout via the Mock HSP Router.
- Start the frontend:
Once testnet testing is successful, deploy to HashKey Chain Mainnet for judge review:
- Fund your Deployer wallet: Ensure your deployer wallet is funded with real HSK on mainnet to pay for contract deployment gas.
- Set the HSP Router Address:
Verify the mainnet adapter/router address from hsp-hackathon.hashkeymerchant.com/docs (default:
0x467AaF53D68A1aF0E480119330a91176251b5c40) and set it asHSP_ROUTER_ADDRESS_MAINNETin your.env. - Deploy the contracts:
The script has a 5-second safety countdown abort window and strictly checks that it is executing on Chain ID 177.
cd contracts npx hardhat run scripts/deploy-mainnet.ts --network hashkeyMainnet - Verify the contracts:
npx hardhat run scripts/verify.ts --network hashkeyMainnet
- Update environment variables:
Copy the variables from the generated
deployments.mainnet.envfile into your root.envto switch the frontend and monitor to mainnet. - Run the live monitor:
Start the monitor service. It will poll and log real
VerdictLoggedevents to the blockchain, building on-chain credibility for the judges. - Publish the dashboard:
The frontend is a TanStack Start SSR app (not a static bundle), so it deploys to Vercel
as a serverless function, not a
dist/upload. See docs/CLOUD_DEPLOYMENT.md for the exact Vercel + GitHub Actions setup. Push tomainand Vercel builds automatically:cd .. git push # Vercel auto-deploys; the monitor runs on GitHub Actions cron
SentryPay supports two payout rails, selected per network via the settleOnchain flag. The full runbook for both is in docs/DEPLOYMENT.md.
| Rail | settleOnchain |
Token | How funds move | Used for |
|---|---|---|---|---|
| On-chain native | true (default) |
HSK | ProtectionPool.disburse() → IHSPRouter.settle() → beneficiary |
Local + testnet demo |
| HSP off-chain | false |
USDC | Payer wallet USDC.transfer; HSP adapter observes + signs a receipt |
Mainnet (real HSP) |
On-chain native rail. ProtectionPool.disburse() calls IHSPRouter.settle() to move native HSK to the beneficiary. If the HSP call reverts for any reason, disburse() falls back to a direct native transfer — the payout is never stuck. For local/testnet development, MockHSPRouter.sol is a drop-in replacement that simply forwards the native token; it is never deployed to mainnet (the mainnet deploy script has a hard code-check).
HSP off-chain (USDC) rail. HSP is zero-custody — no contract holds or routes funds. Here the on-chain trigger only authorizes the payout (emits PayoutExecuted); the monitor's relay (monitor/src/hspRelay.ts) then EIP-712-signs an HSP Mandate, does the actual USDC.transfer from the payer wallet, and posts an observation to the HSP Coordinator. The EIP-712 verifyingContract (0x…0001) is a signature domain, not a payable router — never place it in an HSP_ROUTER_ADDRESS_* field.
The Policy detail page exposes a "Stage demo trigger" button. Clicking it submits a verdict whose metric value is chosen to satisfy the policy's stored trigger direction (ABOVE → comfortably over the threshold, BELOW → 0), so the demo works regardless of how the policy was configured. It uses the exact same contract path (PayoutTrigger.submitVerdict → executePayout → ProtectionPool.disburse → HSPRouter.settle) as an automatic trigger. Nothing is mocked or bypassed.
Because submitVerdict is onlyAiSigner, the verdict is signed server-side by src/lib/demo-trigger.server.ts using the AI signer key — that key never reaches the browser. The staged verdict is tagged demoTriggered: true in its off-chain factors JSON.
This is disclosed here and labeled in the UI specifically so judges can distinguish the staged path from the live autonomous one. The live monitor has been running against mainnet since deployment and its verdict log is visible on the Verdicts page.
For the HSK Pool Liquidity Drain metric (BELOW direction: triggers when drain ≥ threshold):
| Factor | Weight | What it measures |
|---|---|---|
| Magnitude | 0.55 | How far the current drain is relative to the threshold × 2 |
| Persistence | 0.30 | How many of the last 3 polls also showed the condition met |
| Velocity | 0.15 | Whether the drain rate is accelerating (2nd derivative > 0) |
The weighted sum produces confidenceScore (0–1). The policy's triggerConfidence field (0–100) is the minimum required to submit a triggering verdict. The on-chain factorsHash links to the off-chain JSON log for full factor inspection.
ProtectionPool.disburse()isnonReentrantand enforces checks-effects-interactions ordering.PayoutTrigger.submitVerdict()is restricted toaiSigneronly.- The AI signer wallet holds only enough HSK for gas — minimal blast radius.
- No
tx.originused anywhere. - Owner and AI signer are separate keys (no shared key).
- Integer thresholds are unit-tested to catch off-by-a-power-of-ten errors.
- Policy creation is owner-only. A production version would have permissionless policy creation with underwriting logic. Stated in
PolicyRegistry.solcomments. - No gas optimization. Correctness over efficiency for a 3-day build.
- Single metric per policy. Multi-metric generalization is a v2 roadmap item.
- No formal governance. Admin-only for the hackathon.
- Permissionless policy creation with on-chain underwriting rules
- Multi-metric, multi-factor policy definitions
- DAO governance for registry and pool parameters
- IPFS pinning of the factors JSON for permanent off-chain audit trail
- Chainlink/Pyth oracle integration as a corroborating data source
- Gas-optimized contracts (Yul/assembly for hot paths)