Replace the ethPerToken metric (free balance / adjusted supply) with total
LM ETH (free + WETH + position-locked) using a forge script with exact
Uni V3 integer math. Collapses 4+ RPC calls and Python float approximation
into a single forge script call using LiquidityAmounts + TickMath.
Also updates red-team prompt, report format, memory extraction, and adds
roadmap items for #536-#538 (backtesting pipeline, Push3 evolution).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When feeDestination == address(this), _scrapePositions() now skips the
fee safeTransfer calls so collected WETH/KRK stays in the LM balance
and is redeployed as liquidity on the next _setPositions() call.
Also fixes _getOutstandingSupply(): kraiken.outstandingSupply() already
subtracts balanceOf(liquidityManager), so when feeDestination IS the LM
the old code double-subtracted LM-held KRK, causing an arithmetic
underflow once positions were scraped. The subtraction is now skipped
for the self-referencing case.
VWAP recording is refactored to a single unconditional block so it fires
regardless of fee destination.
New test testSelfFeeDestination_FeesAccrueAsLiquidity() demonstrates
that a two-recenter cycle with self-feeDestination completes without
underflow and without leaking WETH to any external address.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Move snapshot to after setRecenterAccess so agent reverts restore
recenterAccess for account 2 on every retry
- Read feeDestination() dynamically from LM (removes hardcoded constant)
and add || die guards on impersonation calls
- Add EXIT/INT/TERM cleanup trap that reverts to the baseline snapshot
- Fix agent floor-check snippet: add FEE_DEST/FEE_BAL reads so formula
matches compute_eth_per_token (adj=s-f-k, not adj=s-k)
- Use `timeout "$CLAUDE_TIMEOUT"` to enforce wall-clock process limit
- Correct taxRateIndex range: 0-29 (30-element TAX_RATES array)
- Fix outstandingSupply() description: excludes LM-held KRK, not all KRK
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds scripts/harb-evaluator/red-team.sh which:
- Verifies the Anvil stack is running and deployments exist
- Grants recenterAccess to account 2 (impersonating feeDestination)
- Takes an Anvil snapshot as the clean baseline
- Computes ethPerToken before the agent run (mirrors floor.ts logic)
- Builds a self-contained prompt with contract addresses, account keys,
protocol mechanics, copy-paste cast command patterns, snapshot/revert
instructions, and structured rules for the agent
- Spawns `claude -p --dangerously-skip-permissions` with a 2-hour timeout
- Captures output to tmp/red-team-report.txt
- Computes ethPerToken after the agent run and reports pass/fail
Exit code 0 = floor held, exit code 1 = floor broken, exit code 2 = infra error.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove dead krkAddress field from UnstakeRpcConfig (bug)
- Drop swap.js import to avoid transitive Playwright dependency; fix
header comment to accurately describe the module boundary (warning)
- Inline pollReceipt() returning TxReceipt so snatch receipt is reused
for log parsing without a second round-trip (warning)
- Use ZeroAddress from ethers instead of manual constant (info)
- Add comment on fromBlock '0x0' genesis-scan caveat (info)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Address AI review findings:
- Bug: restore 30 s periodic eviction via setInterval so queries that
are never repeated don't accumulate forever (add setInterval/
clearInterval to ESLint globals to allow it)
- Bug: fix .finally() race – use identity check before deleting the
in-flight key so a waiting request's replacement promise is never
evicted by the original promise's cleanup handler
- Warning: replace `new URL(c.req.url).search` with a string-split
approach that cannot throw on relative URLs
- Warning: add MAX_CACHE_ENTRIES (500) cap with LRU-oldest eviction to
bound memory growth from callers with many unique variable sets
- Warning: prefix cache key with c.req.path so /graphql and / can
never produce cross-route cache collisions
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace setInterval-based eviction with lazy eviction to avoid the
no-undef ESLint error (setInterval is not in the allowed globals list).
Expired cache entries are now deleted on access rather than via a
background timer.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add server-side response cache + in-flight coalescing to Ponder's Hono
API layer (services/ponder/src/api/index.ts).
Previously every polling client generated an independent DB query, giving
O(users × 1/poll_interval) load. With a 5 s in-process cache keyed on the
raw request body (POST) or query string (GET), the effective DB hit rate
is capped at O(1/5s) regardless of how many clients are polling.
In-flight coalescing ensures that N concurrent identical queries that
arrive before the first response is ready all share a single DB hit
instead of each issuing their own. Expired entries are evicted every 30 s
to keep memory use bounded.
The 5 s TTL deliberately matches the existing Caddy `Cache-Control:
public, max-age=5` header so that if a caching proxy/CDN is layered in
front later, both layers stay in sync.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Address AI reviewer warnings:
- Add separate unstakeError ref so unstake failures are not displayed
via the load-data error UI whose Retry calls loadActivePositionData
- Add exitSucceeded flag to distinguish pre-exit failures ("Failed to
unstake position.") from post-exit data-refresh failures ("Position
unstaked, but failed to refresh data."), preventing factually wrong
messages after a successful transaction
- Unstake error UI has no Retry button; the Unstake button in actions
remains available for retrying a failed transaction
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Wrap the entire unstakePosition() body in try/catch/finally so that
errors from exitPosition(), loadActivePositions(), and loadPositions()
are caught, displayed to the user via the existing error ref, and
loading state is always reset correctly.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace waitForLoadState('networkidle') in the post-login redirect with
waitForURL('**/app/stake**'). Persistent WebSocket connections prevent
networkidle from ever firing, mirroring the same fix applied to navigate.ts.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Include taxDue in taxCostPercent computation so the Tax% and Net%
shown in the header P&L line are consistent with the Tax Cost card
and Total row, which already use taxDue + taxPaid.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
taxPaidGes = taxDue + taxPaid, so the displayed value includes both
outstanding tax and historically paid tax. Rename the UI label from
'Tax Paid' to 'Tax Cost' to accurately reflect the combined amount.
Update the matching E2E test selector accordingly.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace waitForLoadState('networkidle') with a comment explaining that callers
must assert on a route-specific element. The SPA keeps persistent WebSocket
connections for blockchain event subscriptions, so networkidle never fires
within the 10 s window, causing spurious TimeoutErrors in 01-acquire-and-stake
and 02-max-stake-all-tax-rates.
Vue Router processes popstate synchronously inside page.evaluate(), so the
route transition has already started by the time evaluate() resolves. Callers'
toBeVisible() assertions (with their own timeouts) serve as the readiness gate.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Clarifies that the event-driven engineering principles apply to infrastructure (Docker, scripts, startup/teardown) and test/scenario execution — NOT frontend HTTP API polling.
Frontend polling (e.g. LiveStats → Ponder GraphQL every 30s) is fine. The scalability solution is caching at the proxy layer (`Cache-Control` headers via Caddy), not WebSocket subscriptions.
Relates to #447
Co-authored-by: openhands <openhands@all-hands.dev>
Reviewed-on: https://codeberg.org/johba/harb/pulls/470
Replace the try/catch on observing the transient 'Submitting…' button text with a
two-phase disabled→enabled check. The button gains the `disabled` attribute the
moment `swapping=true` and loses it when the swap finishes. By placing
`toBeEnabled({ timeout: 60_000 })` unconditionally after the try block, both
paths (fast RPC where disabled state cycles in <100 ms and slow RPC where it is
clearly observable) now wait for the actual ready state rather than falling
through to only a 2-second static guard.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace all 10 bare waitForTimeout() calls in tests/e2e/usertest/helpers.ts
with proper event-driven Playwright waits per AGENTS.md Engineering Principle #3
("replace when touched"):
connectWallet():
- Remove 500ms resize-event sleep: connectButton.isVisible({ timeout: 5_000 })
already waits for the layout to settle after the resize event
- Remove 2000ms "connector init" sleep: the subsequent isVisible check was
already event-driven
- Replace 2x 1000ms panel-animation sleeps with
injectedConnector.waitFor({ state: 'visible' }) — the .connectors-element
appearing is the exact observable DOM event
- Remove 2x 2000ms "handshake" sleeps: walletDisplay.waitFor() at the end of
the function is the correct gate for connection completion
attemptStake():
- Remove 3000ms post-goto sleep: tokenAmountSlider.waitFor() at the next line
is the correct page-load gate
- Remove 2x 500ms debounce sleeps after fill/select: stakeButton.waitFor()
downstream is the correct reactive-state gate
- Remove 3000ms post-transaction sleep: the button returning to "Stake" text
(waitFor at line 619) is already the correct transaction-completion gate
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add eslint-disable-next-line no-restricted-syntax comments with
justification to the 10 bare waitForTimeout() calls in
tests/e2e/usertest/helpers.ts.
All waitForTimeout calls in the spec files (01–06 and all usertest
specs) were already properly documented after PR #417. helpers.ts was
the only remaining file with bare calls:
- 6 in connectWallet(): wallet connector panel animation and
connection handshake have no observable DOM event to await
- 4 in attemptStake(): Ponder indexing lag, debounced form handlers
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>