release: promote paused v2.3 testnet suite - #39
Merged
Merged
Conversation
Mirrors the working manual deployment documented in docs/NODE-SETUP.md.
Changes not yet validated against a live host run.
- gqrl role: fetch testnetv2 config.yml + genesis.ssz with sha256 check,
pass --networkid 1337, drop broken --{mainnet,testnet} flags
- qrysm-beacon role: --genesis-state + --chain-config-file + --config-file,
two hardcoded bootstrap ENRs extracted from qrysm mainnet_config.go:40-41
- qrysm-validator role: same chain-config wiring, keystore import step
deferred to manual follow-up
- group_vars/all.yml: drop rewards_oracle/operator_registry vars, add
validator_manager_address; v2.2 addresses now authoritative
Terraform + ansible not wired to a live stack; PR covers the static diff
only. Next pass: re-apply terraform, re-run playbook, iterate on residual
failures.
Three fixes found running the playbook against new terraform-provisioned hosts: - beacon health probe: /eth/v1/node/health -> /qrl/v1/node/health (QRL fork renamed the REST prefix; old path 404s) - qrysm-validator: guard 'Create qrysm user' with getent check; previously usermod ran on every play and failed when qrysm-beacon was already using the user (process 47822) - drop ansible_date_time.iso8601 from the backup-node lock-file template; it's undefined when the play is resumed via --start-at-task (no facts gathered) and the timestamp added nothing actionable anyway Verified: playbook runs clean on both primary + backup from fresh VPS; gqrl + qrysm-beacon up on both; primary beacon at slot 3007 / backup 2111 climbing toward clock head 21307.
Found when scenario 2 ran end-to-end on a freshly-provisioned Hetzner stack (see docs/V2-DEPLOYMENT-STATUS.md validator #2 notes). - fee_recipient_address: empty string made qrysm-validator exit on boot ("default fileConfig fee recipient is not a valid qrl address"). Set default to the QuantaPool deployer in group_vars; comment explains why it cannot be empty. Override per-inventory for other operators. - qrysm user primary group: Ansible's default user-module behavior on Ubuntu 24 assigned `users` (GID 100) as the primary group instead of the intended `qrysm` group. Result: 0640 files under /etc/quantapool became unreadable by qrysm uid and the validator failed to open its wallet password. Pin `group: qrysm` explicitly and create the group before the user. - staking-deposit-cli was never built by the roles; operators had to `go build` it by hand on the new host. Added a build task in the qrysm-beacon role (qrysm source tree is already cloned there) and a path variable. Syntax-checked via ansible-playbook --syntax-check. Full re-run deferred to the next fresh provision (validator #2's host is already patched in place).
Backend PR DigitalGuards/myqrlwallet-backend#36 landed the cache whitelist fix: only network-invariant RPC methods (qrl_chainId, net_version) are cached now; state reads always hit upstream. Verified in-session with scripts/probe-cache-race.js — a 100 QRL deposit immediately shows in bufferedQRL() with no wait. Removes the 30-iteration getBlockNumber poll that tx() used to sit in after each sendTransaction. Status phase re-run end-to-end, unchanged.
…2 moderate) overrides block (no @theQRL change, no --force): tmp 0.0.33->0.2.7 (HIGH GHSA-52f5-9888-hmc6 + GHSA-ph9p-34f9-6g65, via solc@0.8.34), follow-redirects 1.15.11->1.16.0 (moderate GHSA-r4q5-vmmm-2653, via solc), ws 8.20.0->8.21.0 (moderate GHSA-58qx-3vcg-4xpx, via @theqrl/web3-providers-ws). Audit 16 -> 12. Gate: forge build (exit 0) + forge test (187 passing) + npm compile (byte-identical solc 0.8.34 output). @theqrl/web3 1.0 + wallet.js 6 DEFERRED: forge tests Solidity only; deploy/integration JS scripts need a live RPC + funded wallet to validate.
…, follow-redirects ^1.16.0, tmp ^0.2.7)
…ts-ws fix(deps): pin tmp/follow-redirects/ws via overrides (audit 16->12)
The real fundValidator() path forwards 40k QRL to the beacon deposit contract, dropping the pool balance by the stake. _syncRewards() compared balance - withdrawalReserve against totalPooledQRL, so funding a validator read as a 40k slashing event: the next (permissionless) syncRewards would collapse the exchange rate, after which a dust deposit could mint a near-unbounded share count and drain the pool when stake/rewards returned. - add stakedQRL accumulator, incremented by fundValidator() when principal leaves for the beacon contract - _syncRewards() now reconciles balance + stakedQRL - withdrawalReserve, so funding is balance-neutral (fundValidatorMVP keeps stakedQRL at zero) - add owner-only recordValidatorExit(amount) to settle returned principal so it is not double-counted as rewards on the next sync - emergencyWithdraw() recoverable calc excludes off-contract stakedQRL - fix stale 0x01 -> 0x00 withdrawal-credentials doc comment - 8 new regression tests (195 pass total); refresh docs + test counts Note: live v2.2 pool carries the pre-fix bytecode and has already run a real fundValidator() — do not call syncRewards() on it until redeployed as v2.3. https://claude.ai/code/session_017KpTnYNbCJgqAffCktJ6Wc
test_EmergencyWithdraw_ExcludesStakedFromProtocolFunds sent the recovered QRL to owner (the Test contract), which has no receive() so the transfer reverted with TransferFailed. The stakedQRL carve-out under test had already worked (the call was reached). Send to a fresh EOA instead. 195/195 pass. https://claude.ai/code/session_017KpTnYNbCJgqAffCktJ6Wc
Minimal Lido-style web app for the liquid staking protocol, built on the MyQRLWallet design system (Vite 7, React 19, TypeScript, MobX, Tailwind 4, Radix primitives, dark theme + QRL orange). - Stake page: deposit QRL -> stQRL with live preview, position card, FAQ - Withdrawals page: request (128-block delay), claim (FIFO), cancel - Stats page: pool, validator funding progress, rewards, contract links - How-it-works page: plain-language user documentation - MobX poolStore: read-only RPC + EIP-6963 QRL Wallet extension signing (qrl_requestAccounts / qrl_sendTransaction), receipt polling for 60s blocks - Testnet contract addresses default from config/testnet-hyperion.json
Correctness (from 7-angle review): - Show the exact snapshot payout (withdrawalRequests.qrlAmount) for pending withdrawals instead of the drifting current share value — claimWithdrawal pays the snapshot, not the current rate - Disable withdrawal requests while the contract is paused (requestWithdrawal is whenNotPaused; previously only deposits were gated) - Guard refreshAccount against resurrecting state after disconnect/account switch mid-fetch - Move parseUnits inside the tx pipeline so invalid amounts surface as a failed tx instead of an unhandled rejection - Serialize transactions at the store level (single pending tx slot) - Handle provider accountsChanged events (switch account or disconnect) Efficiency / cleanup: - Cache contract instances; cache immutable (claimed/cancelled) withdrawal requests so the 30s poll stops refetching history - Skip background refresh while the tab is hidden; receipt polling 10s - Guard init() against StrictMode double-invoke - Replace hardcoded #4aafff with the blue-accent theme token everywhere - stakeableBalance (gas reserve) moved from page into the store - dismissConnectError store action instead of component-side mutation Explorer integration: - Fetch QRL/USD from zondscan /api/overview (same endpoint as myqrlwallet) and show USD values for TVL and the user's position
Stakers can now pull up their staking history in-app: Deposited, WithdrawalRequested, WithdrawalClaimed and WithdrawalCancelled events (all indexed by user) are merged into a newest-first activity card on the Stake page, each entry linking to the transaction on zondscan, with a direct 'View on Zondscan' link for the address (also added to the header address chip). Degrades gracefully when the RPC proxy doesn't expose log queries.
Addresses the phantom-reward front-running window flagged in review of the stakedQRL change. Validator exit proceeds arrive via EIP-4895 and land in address(this).balance a block before the owner can settle them with recordValidatorExit(). In that window balance + stakedQRL double-counts the principal, so an unrestricted _syncRewards() would book it as a large phantom reward and spike the exchange rate. Because requestWithdrawal() snapshots the QRL value at request time, a front-runner could lock that inflated rate into a withdrawal and drain the pool when the rate corrects. Fix: reward sync is permissionless only while all principal is on-contract (stakedQRL == 0). Once principal is staked off-contract (stakedQRL > 0), syncRewards() and the implicit sync inside requestWithdrawal/claimWithdrawal are owner-only, so exit settlement and reward recognition are sequenced by the operator and cannot be front-run. The MVP path (fundValidatorMVP keeps QRL in-contract, stakedQRL == 0) is unaffected and stays fully permissionless. - add _permissionlessSyncAllowed() (stakedQRL == 0) with rationale - syncRewards() reverts NotOwner for non-owner callers while stakedQRL > 0 - requestWithdrawal/claimWithdrawal only inline-sync when permissionless is safe (payout already uses the request-time snapshot, so behavior is unchanged) - 5 regression tests (PHANTOM-REWARD FRONT-RUN PROTECTION block): permissionless when unstaked, owner-only while staked, front-run blocked during exit, permissionless resumes after settlement, owner still recognizes genuine rewards - regenerate hyperion mirrors; refresh docs + test counts (200 pass) Note: compiles clean with solc 0.8.34; forge binaries are not reachable in the dev sandbox so the full Foundry suite was not run here.
…unding - refreshAccount now fetches only the live withdrawal tail [nextIndex, total) instead of every historical request. nextIndex = total - pending equals the contract's nextWithdrawalIndex, and indices below it are claimed/cancelled and immutable, so the per-poll RPC fan-out is bounded by pending requests rather than a user's entire withdrawal history (avoids hundreds of concurrent calls / rate-limiting for active accounts). - track completedWithdrawalsCount (= nextIndex) on AccountState and use it for the 'N completed withdrawals' line, instead of filtering the (no-longer-fully- fetched) request array for claimed entries. - blocksToTime: round to whole minutes before splitting hours/minutes so it can no longer render '≈ 60m' or '1h 60m'.
…-review-t3g5wj fix(DepositPool-v2): track off-contract staked principal in reward sync
…design-4xh69j Add contract ABIs for QuantaPool frontend
Uncommitted drift from the scenario-2 Hetzner deploy (PR #19 covered the ansible side; this is the terraform half): - contract vars: drop rewards_oracle/operator_registry (retired in v2), add validator_manager; defaults now the live Q-prefixed v2.2 addresses from config/testnet-hyperion.json - SSH key: look up pre-existing project key by fingerprint instead of creating one - server types: cpx31/21/11 retired by Hetzner; move to cpx32/22 - monitoring node off by default (primary-IP quota: 2); reuse node #1 - per-module hcloud required_providers pin (~> 1.45); datacenter -> location on hcloud_server; terraform fmt across the tree - gitignore terraform state/tfvars, .env.*, and the generated ansible inventory.ini (real host IPs; template stays tracked)
Referenced by docs/V2-DEPLOYMENT-STATUS.md (scenario 2) and the 433953a commit message but never committed: - fanout-test-wallets.js: seed N fresh ML-DSA-87 wallets from a funder mnemonic into .env.scenario2 (gitignored) - scenario2-deposit.js: deposit each wallet's balance into DepositPoolV2 - probe-cache-race.js: verify the backend cache whitelist fix (deposit then immediate bufferedQRL read); funder mnemonic moved out of the source into .env.scenario2 as FUNDER_MNEMONIC
Replace with plain punctuation (colons, commas, periods, hyphens). Empty-value placeholders now render "-" instead of an em dash. Deployed to quantapool.com and quantapool.io.
- README: status now reflects live v2.2 testnet deploy + frontend at quantapool.com/.io; roadmap updated (v2.3 redeploy pending); frontend/ added to project structure - V2-DEPLOYMENT-STATUS: PR #20 merge noted under section 7 (redeploy still required); new Frontend section with hosting, deploy, and CORS coupling (qrlwallet backend ALLOWED_ORIGINS + zondscan CORS_ALLOW_ORIGINS); last-updated bumped - infrastructure/docs: ARCHITECTURE rewards flow updated to v2 trustless sync; validator-integration marked deprecated (v1 doc) - all tracked markdown swept free of em dashes
Sweeps .gitignore, ansible, monitoring (incl. grafana dashboard title), and scripts. Audited contracts/ sources excluded on purpose: comment changes there require Hyperion mirror regen + forge gate, not worth it without an explicit ask. YAML/JSON/JS all re-validated after the sweep.
Deposit/withdraw yo-yo cycles could force the operator to bridge liquidity or exit validators at no cost to the attacker. Fresh deposits now mature for minStakeBlocks (default 1536, ~1 day) before they can be transferred or queued for withdrawal. - two-bucket lazy maturity in stQRLv2 (immatureSharesOf / matureAtBlockOf), mirroring the _lockedShares pattern; top-ups fold remaining immature shares into a new bucket and reset its maturity - immature shares are non-transferable: closes the fresh-address bypass; transfers never touch the recipient bucket, so no dust grief - owner deposits exempt: operator bridge capital enters and exits without the wait - setMinStakeBlocks owner-only, capped at MAX_MIN_STAKE_BLOCKS (46500, ~30 days), 0 disables - requestWithdrawal subtracts immature shares from spendable balance - 16 new tests incl. locked+immature<=balance invariant fuzz; legacy setUps pin minStakeBlocks=0 to stay same-block. Suite: 216 pass. - Hyperion mirrors regenerated, hypc compile clean Activates on-chain with the v2.3 redeploy; live v2.2 is unchanged.
- ABI: immatureSharesOf, matureAtBlockOf, minStakeBlocks - poolStore: immature shares + maturity block in the account snapshot, each call individually guarded so the UI degrades gracefully against the live v2.2 contracts (missing views read as 0); tracks currentBlock for countdowns - Withdrawals page: available balance excludes immature shares; shows a maturing notice with approximate wall-time remaining - Stake FAQ + How It Works copy updated; stale 178-test claim fixed - gates: eslint zero warnings, vite build clean
Answers the recurring community questions: two-step request/claim flow with snapshot pricing and share burn, where claim QRL comes from (deposit buffer first, full validator exit if needed, no partial pulls), and the cap question (deposits uncapped, claims gated on reserve liquidity; min stake lock noted).
feat(footer): link the MyQRLWallet ecosystem site
Add a wallet selector so users can connect either the QRL browser extension or MyQRLWallet (mobile/desktop/web) over the connect relay, matching the reference dApp example. - poolStore constructs the @qrlwallet/connect SDK (which announces over EIP-6963), discovers QRL-capable wallets, and drives both connect paths through one provider.request() surface. Full relay lifecycle: getConnectionURI first-connect, newConnection reset, mobile deep link, stored-session auto-reconnect, and QR regen on a wallet-side drop. - runTx branches the tx shape by transport: the QRL extension needs an explicit gas limit (it does not estimate for a dApp send; verified against the extension source) sent under both gas and gasLimit; the relay wallet self-estimates so it gets the minimal shape. - New WalletPickerModal + QrPairModal, both mounted at app root. - The Connect button opens the picker; the 3 existing call sites are unchanged.
…ension-rdns Accept the MyQRLWallet Extension rdns
- newConnection on mobile still used the bare location.href redirect and could dead-end without the app; route it through attemptWalletRedirect with the same fallback as connectViaRelay - deeplink helper: qrlconnect: scheme guard, try/catch around the navigation (settle instead of leaking listeners on synchronous throws), timer declared before use, navigator guard in appStoreUrl - extension discovery: defensive optional access on announce payloads (any page script can dispatch eip6963:announceProvider)
Gemini review followups: deep-link fallback hardening
Replaces the hand-copied QrPairModal internals with a thin MobX observer wrapper around <qrl-pairing-modal> from @qrlwallet/connect-ui@0.1.0 (same self-gating on poolStore.pairingUri). Gains the shared UX: 'Open web wallet' fragment handoff, 'Open desktop app' deep link, copy-code fallback, dialog a11y. Drops the qrcode dependency (the modal was its only user). Also fixes the pre-existing prefer-const lint error in utils/deeplink.ts (timer assigned exactly once) that was failing lint on dev.
refactor(frontend): adopt @qrlwallet/connect-ui for the pairing modal
…wallet/connect 3.2.0 Drops the inlined utils/deeplink.ts copy now that the SDK ships the helper (announced in its 3.2.0 changelog); behavior identical.
chore(frontend): use the SDK deep-link helper from @qrlwallet/connect 3.2.0
…nects The SDK emits 'disconnect' both for wallet-initiated terminates and for its reconnect-probe timeout, which fires whenever the wallet app is merely backgrounded (routine on mobile). The store treated every live-session disconnect as terminal: regenerateRelayQr() rotated the channel (orphaning the wallet side's session), wiped account/withdrawals/activity, and popped an unsolicited QR. With @qrlwallet/connect >= 3.3.0 the rotation can also strand an approval already in flight on the old channel. Now a disconnect while hasStoredSession() is still true keeps the paired state: any request revives the channel (relay-buffered; deep-link wake on 3.3.0). A true terminate (stored session cleared) still falls through to the existing re-pair and reset paths. Safe on the deployed 3.2.0 too. Mirrors myqrlwallet-connect PR #32's example fix and QuantaSwap PR #25.
…ssion Keep the revivable relay session on non-terminate disconnects
…e links, late_response)
…obots, sitemap - index.html: canonical to https://quantapool.com/, full Open Graph + Twitter summary_large_image tags, Organization (DigitalGuards) + WebApplication JSON-LD, apple-touch-icon, sharper title/description (QRL 2.0, post-quantum blockchain wording) - new 1200x630 og-image.png and 180x180 apple-touch-icon.png rendered from the brand SVG - public/robots.txt (allow all + sitemap ref) and sitemap.xml covering the four routes - RouteSeo component keeps title/description/canonical/og:url in sync per route; canonical always points at quantapool.com so the .io mirror self-identifies as a duplicate - footer: add QuantaSwap ecosystem link
feat(seo): full meta/JSON-LD/robots/sitemap + per-route SEO + brand OG card
feat(seo): logo-forward OG card v2
feat(brand): omega droplet logo
…ystem Port the myqrlwallet-frontend design system: warm-ink token set (ember primary, blue-accent identity, new success token, radius 0.75rem), body atmosphere (grain + ember/cyan glows) adapted to the body-scroll model, self-hosted Sora/Instrument Sans/JetBrains Mono via fontsource imported from main.tsx, surface-glass cards with surface-ember hero forms, font-data on all amounts/addresses/block numbers, page-enter route staggers and glow-dot status indicators with reduced-motion guards. Header network dot now reflects rpcError state. Orange acts, blue identifies: all action buttons are ember, addresses and info chips blue. Gates: eslint zero warnings, tsc + vite build green.
feat(frontend): Obsidian & Ember restyle to match the wallet design system
…sclosure, provider imprint
…mprint) Legal notice page: testnet-only scope, no-services disclosure, provider imprint
* feat(frontend): display native amounts as Quanta Native coin amounts now show the Quanta unit instead of QRL, via a shared NATIVE_UNIT constant in config/networks.ts. The stQRL token symbol and all network/project references (QRL network, QRL 2.0, QRL validator, QRL beacon chain) are unchanged. * refactor(frontend): route Quanta labels through NATIVE_UNIT (review)
…ing-20260803 fix(protocol): harden pool accounting and deployment
…t-20260803 chore(deploy): record paused v2.3 testnet suite
…260803 chore(release): synchronize main into dev
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
devchanges, including QuantaPool v2.3 accounting hardening and deployment safety controlsValidation
Release risk