fix(keeperhub): swap on-chain setText for webhook pulses (α — keep KeeperHub firing, zero gas)#13
Merged
fritzschoff merged 1 commit intomainfrom Apr 29, 2026
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Per-quote KeeperHub workflow runs were doing real Sepolia setText txs
signed by PRICEWATCH_PK — ~14k gas burned per quote just to write
last-seen-at and reputation-summary text records. Worse, this was the
KeeperHub demo surface for the hackathon: the workflows ARE the
sponsor story, deleting them is wrong.
Fix: keep the workflows firing on every paid x402 quote (sponsor story
intact) but replace their Web3 Write nodes with Webhook POST nodes
that hit our app instead.
/api/keeperhub/heartbeat-pulse
- validates KEEPERHUB_WEBHOOK_SECRET bearer
- writes timestamp to Redis: agent:1:last-seen +
ens:dynamic:1:last-seen-at (24h TTL)
- calls pushKeeperhubRun so the run is visible on /keeperhub
- returns 200 with the updated lastSeenAt ISO
/api/keeperhub/reputation-pulse
- same auth shape
- reads ReputationRegistry.feedbackCount (cheap on-chain view, no tx)
- writes summary to Redis: reputation:summary:1 +
ens:dynamic:1:reputation-summary (24h TTL)
- calls pushKeeperhubRun, returns 200
lib/ens.ts:resolveAgentEns now prefers the Redis-backed ens:dynamic:*
keys for last-seen-at + reputation-summary, falling back to the
on-chain text record only if Redis is empty. Dashboard heartbeat pill
stays live; on-chain text records freeze and become irrelevant.
Both KeeperHub workflows already updated via update_workflow MCP call
(see /tmp/swap-workflows-to-webhooks.ts).
W2's CCIP-Read gateway will expose these Redis values as proper ENS
text records readable from any client. This is W2-α reduced to a
single record pair — the W2 plan still ships the full version.
Net result:
- KeeperHub fires per quote ✓ (sponsor story)
- Workflow runs visible on /keeperhub ✓
- Dashboard heartbeat live ✓
- Zero gas burn ✓
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
b0fdd02 to
514c9eb
Compare
4 tasks
fritzschoff
added a commit
that referenced
this pull request
Apr 30, 2026
…'re now webhook-only post PR #13) Adds scripts/setup-keeperhub-workflows.ts that calls create_workflow via KeeperHub MCP for ENSPrimaryNameSetter, ENSAvatarSync, and GatewayCacheInvalidator. Supports --dry-run flag. Skips deletion of heartbeat/reputation-cache (both converted to webhook-only triggers in PR #13). Workflow IDs created: KEEPERHUB_WORKFLOW_ID_PRIMARY_NAME=x3x1yxn1i9fi6qs63v4lu KEEPERHUB_WORKFLOW_ID_AVATAR_SYNC=iosfz5m65htyd18be78sp KEEPERHUB_WORKFLOW_ID_GATEWAY_INVALIDATE=3tzmhfpvsnom1bnkeieoz Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
fritzschoff
added a commit
that referenced
this pull request
Apr 30, 2026
…15) * docs: W3 implementation plan (primary names + KeeperHub orchestration) 13 tasks across 7 milestones: M1 reverse names for locally-keyed wallets (AGENT_PK, PRICEWATCH_PK, VALIDATOR_PK) on Sepolia + Base Sepolia M2 KeeperHub workflow provisioning — 4 new (PrimaryNameSetter, AvatarSync, GatewayCacheInvalidator, OnboardAgent), 2 deleted (Heartbeat, ReputationCache) M3 webhook integrations + event-firehose cron M4 primary name for Turnkey wallet via PrimaryNameSetter workflow M5 OnboardAgent end-to-end (6 orchestrated steps) M6 e2e tests M7 PR + walkthrough Depends on W2 — gateway needs to handle nested wallet labels (agent-eoa.tradewise.agentlab.eth etc). The plan extends labelToAgent in lib/ens-gateway.ts in M1 Task 1. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(w3): gateway handles nested wallet labels (agent-eoa, pricewatch-deployer, validator) Add WALLET_LABELS map and addressOverride field to AgentInfo so that direct wallet subnames (agent-eoa.tradewise.agentlab.eth, pricewatch-deployer.agentlab.eth, validator.agentlab.eth, keeperhub.agentlab.eth) resolve to their respective addresses without hitting the agentId-based Edge Config lookup. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(w3): correct addr CCIP-Read encoding from bytes to address type addr(bytes32) return type is `address` not `bytes`. viem getEnsAddress decodes resolveWithProof result as abi.decode(result,(address)), so the gateway must encode as abi.encode(address) — not abi.encode(bytes). This fixes getEnsName forward-validation which was returning 0x20 (the bytes offset pointer) instead of the actual wallet address. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(w3): scripts/setup-primary-names — set reverse name per chain idempotently One-shot runner for AGENT_PK, PRICEWATCH_PK, VALIDATOR_PK. Checks balances upfront, verifies registrar bytecode (graceful degradation for missing L2 deployer), skips if reverse name already matches. Runs on Sepolia (ReverseRegistrar 0xA0a1...) and Base Sepolia (ENSIP-19 L2 reverse registrar 0x00000BeEF...) with per-chain ABI variants. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(w3): keeperhub-workflows — typed builders for 3 new workflow shapes (PrimaryName, AvatarSync, GatewayInvalidate) Adds lib/keeperhub-workflows.ts with WorkflowSpec type and three builders: - buildEnsPrimaryNameSetter: dual-chain Web3 Write setName on Sepolia + Base Sepolia - buildEnsAvatarSync: Web3 Write setText avatar on Sepolia PublicResolver - buildGatewayCacheInvalidator: webhook-to-webhook relay (zero gas) Extends KeeperHubKind in edge-config.ts with primary-name, avatar-sync, gateway-invalidate. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(w3): provision 3 new workflows (heartbeat + rep-cache stay; they're now webhook-only post PR #13) Adds scripts/setup-keeperhub-workflows.ts that calls create_workflow via KeeperHub MCP for ENSPrimaryNameSetter, ENSAvatarSync, and GatewayCacheInvalidator. Supports --dry-run flag. Skips deletion of heartbeat/reputation-cache (both converted to webhook-only triggers in PR #13). Workflow IDs created: KEEPERHUB_WORKFLOW_ID_PRIMARY_NAME=x3x1yxn1i9fi6qs63v4lu KEEPERHUB_WORKFLOW_ID_AVATAR_SYNC=iosfz5m65htyd18be78sp KEEPERHUB_WORKFLOW_ID_GATEWAY_INVALIDATE=3tzmhfpvsnom1bnkeieoz Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(w3): wire ENSAvatarSync + GatewayCacheInvalidator triggers from confirm-transfer Adds triggerKeeperHubByKind wrapper to lib/keeperhub.ts (validates kind string against KeeperHubKind, delegates to existing triggerKeeperHub). Fires both avatar-sync and gateway-invalidate as fire-and-forget .catch() triggers at the end of the confirm-transfer oracle route after Redis key rotation succeeds. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(w3): keeperhub.agentlab.eth maps to actual Turnkey address (was placeholder) W3 M4 confirmed the Turnkey-managed wallet's address from the on-chain setName tx logs: 0xB28cC07F397Af54c89b2Ff06b6c595F282856539. Forward addr lookup must match the reverse record for getEnsName to return the label — viem does the round-trip check per ENSIP-19. The earlier placeholder was 0x0000…0000 and was based on a typo in M2's report (extra 'c'). Tx 0xf07068…0d56e on Sepolia is the canonical setName that established the reverse record. * test(w3): e2e for primary names + manual walkthrough doc Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(w3): force-dynamic /inft + 8s timeout on ENS reads (build was timing out) PR #15's preview build kept failing because the /inft page does 5 parallel CCIP-Read calls during static generation, each calling the production gateway URL. Total exceeded the 60s build-step budget and Vercel marked the deploy Error. Two fixes: 1. /inft page: export const dynamic = "force-dynamic" — render at request time, not build time. Each viewer gets fresh data anyway since the W2 gateway returns live values. 2. lib/ens-records.ts: 8s timeout per getEnsText call. If the gateway is slow or unreachable, the page renders with null fallbacks instead of hanging. Both are defense in depth — even if one of the 5 reads times out, the page still renders and the user sees a "—" instead of an error. * fix(w3): force-dynamic on / too (resolveAgentEns goes through CCIP-Read) * docs: extend /docs deep-dive with W2 + W3 sections Adds 5 new sections (∇09–∇13) below the existing W1 architecture deep dive so judges see the full agent-identity package end-to-end: ∇09 W2 — CCIP-Read ENS gateway (architecture + trust posture) ∇10 Resolve flow — what happens when wagmi/viem queries an ENS record ∇11 Live records served by the gateway (full table) ∇12 W3 — ENSIP-19 multichain primary names (4-wallet table) ∇13 W3 — KeeperHub orchestration (6-workflow table + cross-link explanation) Also extends the contract-addresses ledger (∇14, was ∇08) with W2's OffchainResolver + INFT_GATEWAY signer + ENS Registry, plus the W3 wallet→name mappings. Three new sub-nav rows up top so the doc TOC reflects the structure. Helper components RecordRow / NameRow / WfRow added at the bottom. --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The two KeeperHub workflows triggered after every paid x402 quote —
Heartbeat (0zuje21a39euf7ow86f2s)andReputationCache (in0hbqxlivyp34dchqufb)— each did a real Sepolia `setText` signed by `PRICEWATCH_PK`. ~14k gas burned per quote. A heartbeat that costs gas is a contradiction.This PR keeps the workflows firing on every quote (the sponsor demo surface stays intact) but swaps their on-chain Web3 Write nodes for Webhook POST nodes that hit our app. The app updates Redis. The dashboard reads
last-seen-atandreputation-summaryfrom Redis, with the on-chain text record as a cold-cache fallback.What changed
Two new endpoints
POST /api/keeperhub/heartbeat-pulse— bearer-auth via `KEEPERHUB_WEBHOOK_SECRET`. Writes `agent:1:last-seen` and `ens:dynamic:1:last-seen-at` to Redis (24h TTL). Calls `pushKeeperhubRun` so the run shows on `/keeperhub`.POST /api/keeperhub/reputation-pulse— same auth. Reads `ReputationRegistry.feedbackCount(agentId)` (cheap on-chain view, no tx), writes `reputation:summary:1` and `ens:dynamic:1:reputation-summary`.Read path
lib/ens.ts:resolveAgentEnsnow prefers Redis-backed `ens:dynamic:*` keys for the two dynamic fields, falling back to the on-chain text record only if Redis is empty. Dashboard heartbeat pill stays live; on-chain text records freeze and become irrelevant.KeeperHub side
The two workflows have already been updated via `update_workflow` MCP call (script at `/tmp/swap-workflows-to-webhooks.ts`):
Net result
Forward path
W2 (issue #11, plan in PR #16) ships the CCIP-Read offchain resolver that exposes these Redis values as proper ENS text records readable from any wallet/etherscan/wagmi client. After W2 lands, the Redis pulse becomes the authoritative source and the on-chain text records can be deleted entirely (W3 plan #15).
Vercel env
Set
KEEPERHUB_WEBHOOK_SECRET(Production already done; matchesINFT_ORACLE_API_KEYfor now — different in production hardening if needed).Test plan
🤖 Generated with Claude Code