Two payout rails exist. Pick per network:
| Rail | settleOnchain |
Token | How funds move | Use |
|---|---|---|---|---|
| On-chain native | true (default) |
HSK | ProtectionPool.disburse → HSP router |
Local + testnet demo (MockHSPRouter) |
| HSP off-chain | false |
USDC | Payer wallet USDC.transfer, HSP adapter observes + signs receipt |
Mainnet (real HSP) |
Why two: HSP is zero-custody — no contract holds or routes funds. The payer's own wallet does a plain USDC.transfer; HSP is a verification/attestation layer on top. verifyingContract 0x…0001 is the EIP-712 signature domain, not a payable router. So on the HSP rail the on-chain trigger only authorizes (emits PayoutExecuted); the monitor's relay settles the beneficiary in USDC.
cd contracts
npx hardhat run scripts/deploy-testnet.ts --network hashkeyTestnet # needs testnet HSK in DEPLOYERLeaves HSP_ROUTER_ADDRESS_TESTNET blank → deploys MockHSPRouter. The script prints a VITE_* + monitor address block and writes deployments.testnet.env. Paste those into root .env.
Verify the source:
npx hardhat run scripts/verify.ts --network hashkeyTestnetnpm run dev # dashboard → "Deposit Premium" for policy 0
cd monitor && npm run dev # monitor polls, submits verdict, pool pays native HSKHSP_SETTLEMENT_ENABLED=false here → on-chain native settlement.
- Fund the payer wallet (
HSP_PAYER_PRIVATE_KEY, defaults toAI_SIGNER) with test USDC from${HSP_COORDINATOR_URL}/faucet/. - Confirm
.env:USDC_ADDRESS= testnet USDC0x8FE3…53c6,HSP_CHAIN_NAME=hashkey-testnet,PAYOUT_USDC_BASE_UNITSset, validHSP_API_KEY. - Dry-run one settlement directly:
This builds + EIP-712-signs a Mandate,
cd monitor npx tsx src/settleOnce.ts 0xBeneficiaryAddress 0POST /payments, does theUSDC.transfer,POST /payments/:id/observe, and prints the adapter receipt. - Full loop: set
HSP_SETTLEMENT_ENABLED=trueand runnpm run dev— the relay settles USDC wheneverPayoutExecutedfires.
Set in .env: SETTLE_ONCHAIN=false, USDC_ADDRESS=0x054ed45810DbBAb8B27668922D110669c9D88D0a
(USDC.e), HSP_CHAIN_NAME=hashkey, HSP_CONFIRMATIONS=5, VITE_CHAIN_ID=177.
HSP_ROUTER_ADDRESS_MAINNET is the real adapter 0x467A…5012 (has code → passes the deploy
code-check; never called while settleOnchain=false).
cd contracts
npx hardhat run scripts/deploy-mainnet.ts --network hashkeyMainnet # needs real HSK for gas
npx hardhat run scripts/verify.ts --network hashkeyMainnetDeploy sets settleOnchain=false, prints the mainnet VITE_* block, writes deployments.mainnet.env.
Paste into .env, then run the monitor with HSP_SETTLEMENT_ENABLED=true and a USDC-funded payer.
The dashboard is a TanStack Start SSR app, so it deploys as a serverless function — not a
static dist/ upload. Push to main and Vercel builds automatically using the mainnet VITE_*
vars. See CLOUD_DEPLOYMENT.md for the exact Vercel + GitHub Actions setup.
npm run build # local production build check (uses the mainnet VITE_* vars)
git push # Vercel auto-deploys the SSR handler- Never put
0x…0001in anHSP_ROUTER_ADDRESS_*field — it's the EIP-712 verifyingContract, not a router. The mainnet deploy script rejects any router address with no contract code. - The
@hsp/sdkpackage is a hackathon GitHub repo (github.com/project-hsp/hsp), not on npm; this project hand-rolls the flow inmonitor/src/hspMandate.ts+hspRelay.tsagainst the Coordinator REST API, matching the SDK'sclient.tswire format. POST /paymentsbody:{ chain, mandate, attestations: [] }wheremandate = { body, signerProof, requiredCapabilities: [] }. Inbody:signer.profileId = keccak256("eip712-eoa.v1"),signer.payload/recipient.payloadare ABI-encoded addresses, refs/binding/requiredCapabilitiesHash= zero.signerProofis the raw 65-byte EIP-712 signature over the Mandate digest. Verified against the repo's mandate-hash test vectors.