Conversation
…onciliation crashes
…ents Consolidate native and ERC20 balance fetchers onto a shared rpcCall helper with exponential backoff for HTTP 429, 5xx, network errors, and malformed gateway responses with missing result fields. The missing-result case was surfacing as BigInt(undefined) on the analytics page.
…ress validation Split rpcCall and helpers out of fetch-balances.ts into lib/wallet/rpc.ts so the retry logic is unit-testable in isolation. Add randomized jitter (up to 30%) to the backoff to avoid lockstep retries, and clamp the absolute maximum delay to 5s regardless of schedule. Validate EVM addresses before encoding balanceOf call data so an oversized input cannot silently produce wrong call data via padStart. Emit a Sentry breadcrumb on every retry attempt so the retry history is attached to any captured exception. Add lib/wallet/rpc.test.ts covering encodeBalanceOfCallData validation, hexWeiToBigInt edge cases, getRpcBackoffMs jitter and cap behavior, and all rpcCall retry/throw branches.
…fails Wire the chain-level default_fallback_rpc through ChainData so the wallet balance fetchers can fail over when the primary RPC is throttled or down. Add rpcCallWithFailover that walks the URL list in order with a reduced per-URL retry budget so the handoff is fast. Emit rpc.failover breadcrumbs for every hop. The user now sees an error only when every configured RPC is exhausted, not when the primary alone gets a 429.
Two nodes could be connected multiple times via the same source/target handle combination. Allow multiple connections between a node pair only when they use different handles (e.g. Condition true/false to the same target); reject exact duplicates. - Extract hasDuplicateEdge to lib/workflow/edge-helpers.ts - Reject duplicates in isValidConnection (drag-time) and onConnect (after sourceHandle auto-assignment)
The pane right-click menu exposed Add Step on the landing page because onPaneContextMenu was wired unconditionally while the canvas is shared between / and /workflows/[id] via PersistentCanvas. Other add-node entry points (toolbar, AddStepButton, onConnectEnd) already gate on isWorkflowRoute or require a real node to be present. Extend the existing isGenerating guard with !isWorkflowRoute so the handler resolves to undefined on the landing page; the canvas already derives isWorkflowRoute from the pathname.
The AI prompt path bypassed isValidConnection/onConnect by calling setEdges directly with workflow data. A hallucinated duplicate would slip through into the canvas and the DB. - Add dedupeEdges helper (O(n) via Set keyed on source/handle/target). - Apply it to the streaming setEdges and to finalEdges before workflow.create/update. - Cover with 5 additional unit tests.
- Move Billing from left nav to user menu dropdown (gated on isOwner + isBillingEnabled; routes to /billing) - Move Address Book from user menu to left nav (replaces Billing slot); opens overlay on click - Add Report an issue button to left nav bottom section (below Documentation); opens FeedbackOverlay - New user menu order: Wallet, Settings, Connections, API Keys, Billing, Projects and Tags - Combine separate Projects and Tags overlays into one tabbed ProjectsAndTagsOverlay (mirrors SettingsOverlay pattern; supports initialTab prop) - NavItem ACTION_ITEM_IDS allowlist so href=null items that open overlays are not marked "Coming Soon"
…olish
UI redesign (/billing)
- PricingTable mirrors landing /pricing: pill Monthly/Annual toggle with inline
"Save 20%" badge; centered 4-col grid at xl; HeroMetrics stat panel per card;
custom TierSelect dropdown with green highlight for selected tier
- Outline current plan with a thin keeperhub-green-dark border + "CURRENT" badge
instead of the old POPULAR highlight
- Enterprise card consistent with others ("Custom" price, "Talk to us" CTA,
mailto:human@keeperhub.com); no gradient background or special border
- Shared ComparisonTable below the grid: "Compare all features" toggle reveals
a 10-row striped matrix with Enterprise column accented in green
- CTA label normalized to "Change plan" for all paid plan changes
- Remove subheadings under price; remove redundant executions pill on Current
Plan card; move renewal message (e.g. "Your plan ends on ...") inline with
plan name + status pill
- Execution usage bar and gas credits bar restyled on keeperhub-green tokens
with subtle /15 track; overage tail in yellow
- FAQ link footer at bottom of /billing routes to https://keeperhub.com/pricing
Billing Details card
- New BillingDetails card rendered next to BillingHistory (lg:grid-cols-[2fr_1fr])
- Fetches GET /api/billing/billing-details; shows card brand + last4 + expiry
and invoice email; empty state when no card on file
- Edit pencil inline next to "Billing Details" title; opens Stripe portal
Auth gate
- billing-page.tsx mirrors analytics page pattern: useSession + local AuthGate,
early-return when anonymous (no more pricing table exposure to logged-out users)
Data plumbing
- Add BillingDetails type and getBillingDetails(customerId) to BillingProvider
- Stripe implementation cascades: customer.invoice_settings.default_payment_method
-> subscription.default_payment_method -> first customer payment method
(Stripe Checkout attaches card to subscription, not customer, by default)
- Add BILLING_DETAILS to BILLING_API constants
- New GET /api/billing/billing-details route (owner-auth)
- /billing bumps refreshKey 2s after ?checkout=success so BillingDetails
remounts once Stripe has attached the payment method
- BillingHistory view/PDF links recolored keeperhub green
- Confirm plan change dialog copy: remove leading "--" before the prorated
billing note
Tests
- billing-handle-event.test.ts mock provider stubs getBillingDetails
…onnections fix: KEEP-287 prevent duplicate edges between same nodes
The gate is subtle (the canvas is shared between / and /workflows/[id] via PersistentCanvas). A comment at the call site prevents a future maintainer from stripping !isWorkflowRoute as "cleanup to match siblings".
- New AppBanner client component mounted in app/layout.tsx: fixed 36px strip at the top of the app, keeperhub-green tint with border, centered info icon + body + "See plans" link, close (X) at right edge - Dismissal is permanent-per-browser via localStorage key kh-billing-announce-v1 so the banner never reappears for a user who closes it (version suffix lets us introduce a new banner later without wiping other prefs) - Banner height is exposed via --app-banner-height CSS var on <html> so fixed overlays shift down cleanly when visible and snap back on dismiss. Updated: - components/workflow/workflow-toolbar.tsx (persistent toolbar top) - components/navigation-sidebar.tsx (sidebar top-[60px] now includes banner) - components/flyout-panel.tsx (two fixed surfaces) - app/workflows/[workflowId]/page.tsx (side panel lg breakpoint) - components/billing/billing-page.tsx (pt-20 -> calc) - components/analytics/analytics-page.tsx (pt-20 -> calc) - components/earnings/earnings-page.tsx (pt-20 -> calc) - No hydration flash: component renders null until mounted to avoid SSR/client mismatch reading localStorage
The trigger node is the anchor of a workflow and cannot be deleted. Remove the non-functional delete affordance from the properties tab and suppress the node-level context menu on trigger nodes.
…ode-creation fix: KEEP-289 gate pane context menu to workflow routes
…elete-option fix: KEEP-290 hide delete option on trigger nodes
Add key={selectedNode.id} on the per-node config wrapper so the entire
subtree unmounts and remounts when the user selects a different node.
Without this, leaf components (AbiFunctionArgsField.localArgValues,
FieldGroup.isExpanded, etc.) retained useState from the previous node
because React preserves state for components at the same tree position
across prop changes. Result: field inputs from the old node persisted
in the panel after clicking a new node.
…sValid
The DOM-based hit-test (event.target.closest('.react-flow__node'|'.react-flow__handle'))
is unreliable on mouseup during an xyflow connection drag. event.target can be the
connection-line overlay rather than the target handle, so a successful handle-to-handle
drop fired both onConnect (correct edge) and onConnectEnd's fallback "create node on
pane drop" branch (spurious node + spurious edge).
Replace the DOM check with xyflow's own tri-state signal: FinalConnectionState.isValid.
Only null (pointer never entered a handle's connection radius) represents a true pane
drop. true means onConnect already handled it; false means an invalid handle drop.
…onnect fix: KEEP-288 guard onConnectEnd node creation with connectionState.isValid
…nel-on-node-select fix: KEEP-291 config panel retains previous node's field inputs
…ld-ecr fix: KEEP-293 tolerate missing ECR credentials for Dependabot PR builds
Bumps the npm_and_yarn group with 1 update in the /docs-site directory: [dompurify](https://github.com/cure53/DOMPurify). Updates `dompurify` from 3.3.3 to 3.4.0 - [Release notes](https://github.com/cure53/DOMPurify/releases) - [Commits](cure53/DOMPurify@3.3.3...3.4.0) --- updated-dependencies: - dependency-name: dompurify dependency-version: 3.4.0 dependency-type: indirect dependency-group: npm_and_yarn ... Signed-off-by: dependabot[bot] <support@github.com>
The CDP Bazaar (agentic.market) crawls paid x402 endpoints and looks for extensions.bazaar.discoverable:true plus category/tags before indexing the resource. Our prior work (KEEP-176) only emitted bazaar.schema for agentcash/x402scan/mppscan; CDP Bazaar requires the discovery fields separately. Also fixes the resource.url in the dual-402 response, which threaded request.url through and resolved to the internal pod bind (https://0.0.0.0:3000/...) in K8s. buildPaymentConfig already used NEXT_PUBLIC_APP_URL; buildDual402Response now does too. - lib/payments/router.ts: discoverable:true always, category/tags when present, resource.url from NEXT_PUBLIC_APP_URL - lib/x402/types.ts: project category, add tagName to CallRouteWorkflow - app/api/mcp/workflows/[slug]/call/route.ts: leftJoin tags for tagName - tests/unit/payment-router.test.ts: replace omit-extensions case, add category/tags case Closes KEEP-294. Related: KEEP-176, KEEP-264.
…ite/npm_and_yarn-2a73d1bbcf chore(deps): bump dompurify from 3.3.3 to 3.4.0 in /docs-site in the npm_and_yarn group across 1 directory
Canonical chain ID for Tempo mainnet is 4217 (already used throughout the app, payment router, seed chains, and lib/rpc/types.ts). Three stragglers still referenced the legacy 42420 at spec-comment-noted blind spots from KEEP-176: - keeperhub-events/event-tracker/lib/utils/chains.ts: dead constant (nothing in event-tracker imports AVAILABLE_CHAINS.TEMPO_MAINNET), update to 4217 for consistency. - .claude/agents/protocol-domain.md: stale doc-prompt line referencing the Tempo Blockscout explorer by legacy ID. Actual explorer config in scripts/seed/seed-chains.ts:523 is keyed by 4217. - lib/rpc/rpc-config.ts: remove the duplicate 42420 RPC entry. The canonical 4217 entry handles every in-app caller; nothing on our side resolves RPC by the legacy ID. Adds a CI grep guard in pr-checks.yml (lint job) so future changes can't reintroduce 42420 as a bare numeric literal in source. The guard excludes *.md, drizzle/meta/*, and the workflow file itself. Closes KEEP-261.
…revenue Two new pages close the creator-facing and caller-facing documentation gaps flagged in KEEP-259 after KEEP-176 shipped dual-protocol payments without explaining to either audience what to expect. - docs/workflows/paid-workflows.md -- creator-facing. Explains how x402 settles on Base USDC and MPP settles on Tempo USDC.e, so a creator with balances on both chains understands the split is a function of which agents paid them (not a bug). Lists discovery scanners, pricing guidance, and points at the mcp-test dogfood reference. - docs/ai-tools/agentcash-install.md -- caller-facing. Covers the one-liner `npx agentcash add https://app.keeperhub.com` that installs a KeeperHub skill into every supported AI agent's skill directory (17 at time of writing). Clarifies that agentcash handles per-call payment, no API key setup, and documents the two real meta-tools (search_workflows, call_workflow) that the skill exposes. Both pages wired into their section _meta.ts and index.md. This is the docs portion of KEEP-259. Wallet-overlay UI changes (per-chain breakdown, first-visit explainer, Earnings card tooltip) will ship in a follow-up PR on a separate branch. Related: KEEP-259, KEEP-176, KEEP-294.
Add ABI-driven protocol definition for Aave V4 Hub-and-Spoke lending, starting with the Lido Spoke on Ethereum mainnet. - protocols/abis/aave-v4.json: reduced ABI covering 8 ISpoke functions - protocols/aave-v4.ts: defineAbiProtocol with lidoSpoke contract - tests/unit/protocol-aave-v4.test.ts: 19 unit tests Actions mirror the V3 surface with V4 semantics: supply, withdraw, borrow, repay, set-collateral, get-user-supplied-assets, get-user-debt, and a new get-reserve-id utility needed to resolve the opaque uint256 reserveId V4 uses to identify reserves within a Spoke. Differences from V3 noted in the file: no referralCode on supply, no interestRateMode on borrow/repay, onBehalfOf added to setUsingAsCollateral. Write actions surface (shares, amount) return values as named outputs. Additional Spokes (EtherFi, Kelp, Ethena Correlated, Ethena Ecosystem, Lombard BTC) share the same ABI and can be added as further contract entries in follow-ups. Notes: - No Sepolia deployment exists yet; integration tests deferred. - getUserAccountData intentionally excluded this pass - it returns a Solidity struct and the template engine path for nested tuple field access needs verification first.
Makes the dual-chain revenue split visible to creators on the Earnings
page so balances on Base and Tempo are both obviously real, not a bug.
- lib/earnings/types.ts: add perChain { base, tempo } with grossRevenue
and invocationCount per chain.
- lib/earnings/queries.ts: new chain-grouped aggregation over
workflow_payments using workflow_payments.chain. Extracted
buildPerChainEarnings() for unit testing and to guarantee the UI
always receives a fixed { base, tempo } shape (missing chain defaults
to zero, unknown chains are ignored).
- components/earnings/earnings-kpi-cards.tsx: show the per-chain split
as subtext on the Total Revenue and Total Invocations cards
("Base $X -- Tempo $Y"). Adds a HelpCircle info link on the Total
Revenue and Earnings cards that opens the new paid-workflows docs
page in a new tab.
Part 2 of KEEP-259. Part 1 (PR #909) added the underlying docs pages.
Not in scope (deliberately parked): first-visit dismissible explainer
overlay on the wallet overlay. The inline help link + docs covers the
disambiguation need; a separate dismissible modal adds surface area
with unclear measurement of whether it's seen. Revisit if creator
support volume shows the current affordance is insufficient.
Related: KEEP-259, KEEP-176, KEEP-294.
…ions.type
Schema audit surfaced two slug-bearing surfaces the original 0051 missed:
* workflows.nodes[].data._eventProtocolSlug -- event trigger nodes store
the protocol slug here (separate from action nodes' actionType /
_protocolMeta). Modelled after the 0025 safe-wallet precedent using
jsonb_set via jsonb_agg, since _eventProtocolSlug is a native jsonb
key (unlike the stringified _protocolMeta).
* integrations.type -- $type<IntegrationType>. Protocol plugins set
requiresCredentials: false so no rows are expected in practice, but
adding the rename is idempotent and cheap insurance.
Not touched:
* _eventProtocolIconPath -- icon file itself is unchanged across the
rename (protocols/aave-v3.ts still declares "/protocols/aave.png"),
unlike the 0025 safe-wallet case which had to rewrite the icon path.
* Historical execution tables (workflow_executions, workflow_execution_logs,
direct_executions) -- rewriting them would falsify past-run history.
Migration header now lists the audited surfaces explicitly.
Previous pattern swallowed any error that didn't contain three specific
strings into a passing test:
try {
// positive assertions -- only run if RPC succeeds
} catch (error) {
expect(String(error)).not.toContain("INVALID_ARGUMENT");
expect(String(error)).not.toContain("could not decode");
expect(String(error)).not.toContain("invalid function");
}
An RPC timeout, a rate-limit, an unrelated revert reason, or a genuine
ABI mismatch whose error string happened to miss those three needles would
all pass green with no positive assertion actually running.
Rewrite:
- Read tests: remove try/catch. If provider.call fails or the return
can't be decoded, the test fails loudly (as it should).
- Write tests (supply, setUsingAsCollateral): switch from estimateGas
to provider.call with the zero-balance TEST_ADDRESS to trigger a
business-logic revert, and assert rejects.toMatchObject({ code:
"CALL_EXCEPTION" }) -- ethers v6's canonical code for contract-level
reverts. An ABI-level failure (INVALID_ARGUMENT, BAD_DATA) or an
unknown selector raises a different error class, so the matcher
distinguishes "calldata was understood by the contract" from
"calldata never got that far".
Test count unchanged (6). Integration tests remain gated on
INTEGRATION_TEST_MAINNET_RPC_URL, so this change doesn't affect the
default CI path.
Running the suite against mainnet revealed that setUsingAsCollateral
silently succeeds on reserveId=0 (the Spoke no-ops on nonexistent
reserves rather than reverting), returning "0x". My previous assertion
rejects.toMatchObject({ code: "CALL_EXCEPTION" }) was too narrow --
it assumed every write would revert for a zero-balance caller.
What the write tests are actually proving is "the deployed bytecode
understood our calldata". Both a clean return ("0x" for void functions)
and CALL_EXCEPTION are valid evidence of that. What we still reject:
INVALID_ARGUMENT, BAD_DATA, BUFFER_OVERRUN -- ABI-level errors that
would signal the protocol definition doesn't match the deployed contract.
Extracted the try/catch into expectCallAcceptedByBytecode() so supply
and setUsingAsCollateral share the assertion shape.
Verified: all 6 integration tests pass against eth-mainnet.
feat: add Aave V4 protocol (and update V3)
fix: guard wallet RPC calls against undefined result and retry transients
Read-workflow calls via /api/mcp/workflows/[slug]/call now block up to 25s
for execution to finish and return the mapped output inline. Long-running
reads still degrade gracefully to {executionId, status: "running"} so
callers can poll. Write workflows are unchanged.
…crash fix: disable auto-translation on workflow editor to prevent React reconciliation crashes
…gn-nav-reorg feat(billing): landing-style redesign, billing details, nav reorg
fix(bazaar): emit discoverable/category/tags + fix resource URL for CDP Bazaar indexing
…audit chore(chain-id): migrate legacy Tempo chain ID 42420 to 4217
docs: paid-workflows + agentcash-install guides for dual-chain revenue (KEEP-259 part 1)
…ings-ui feat(earnings): per-chain revenue breakdown + docs tooltip (KEEP-259 part 2)
…hould-wait-for-read-workflow-completion fix(mcp): make call_workflow wait for read completion (KEEP-265)
The main-content branch used a fixed pt-20, so when AppBanner was visible (fresh sessions without dismissal), the persistent WorkflowToolbar stacked below the banner covered the AnalyticsHeader and the time-range nav was unclickable. The hasNoData branch already used pt-[calc(5rem+var(--app-banner-height,0px))] -- applying the same pattern here. Fixes the failing analytics-gas e2e test on staging.
fix(analytics): account for app-banner height in page padding
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
Promote the following merged PRs from staging to prod:
Risk callouts
drizzle/0051_rename_aave_slug_to_v3.sql, snapshot + journal updates (Aave slug migration from feat: add Aave V4 protocol (and update V3) #846)package.json/pnpm-lock.yamlunchanged; dompurify bump in chore(deps): bump dompurify from 3.3.3 to 3.4.0 in /docs-site in the npm_and_yarn group across 1 directory #889 is scoped todocs-site/)Post-deploy verification
deploy-keeperhubworkflow finishes greencurl -fsS https://app.keeperhub.com/api/healthreturns 200