Skip to content

EVM support, MetaMask wallet, supernodes, loyalty quests, and platform hardening - #92

Open
akobrin1 wants to merge 333 commits into
mainfrom
develop
Open

EVM support, MetaMask wallet, supernodes, loyalty quests, and platform hardening#92
akobrin1 wants to merge 333 commits into
mainfrom
develop

Conversation

@akobrin1

@akobrin1 akobrin1 commented Aug 21, 2026

Copy link
Copy Markdown
Collaborator

Summary

Brings the full develop line to main (332 commits): EVM network support with MetaMask, the supernode and loyalty/referral platforms, explorer features, and the hardening and dependency work on top.

EVM support & wallets

  • Selectable network profiles (devnet / testnet / mainnet) with per-profile chain, RPC, SDK, SNScope, and portal settings plus env overrides
  • EVM wallet support on EVM-enabled profiles: MetaMask connect/disconnect, network add/switch and verification against the profile RPC, EVM transfers, and both Bech32 + ETH hex address formats surfaced throughout
  • Portal-style wallet chooser with selectable MetaMask and Keplr, connected-wallet account menu, and MetaMask onboarding
  • Wallet-state correctness: stale-async guards on wallet flows, MetaMask ↔ Keplr switching with the teardown suppression scoped to the in-flight connect attempt (non-persisted), post-connect verification with fresh account reads and rollback, and wallet errors surfaced in place
  • Wallet-connect tracking hardened: atomic first-connect detection (no double referral bonuses across processes), lenient referral/acquisition validation so tracking is never lost, per-address session markers (no retry loops or double counts)

Explorer & dashboard

  • Global search and the account inspector (rich account page on a tested data layer, with independent degradation per data slice)
  • Blocks page with backfill during polling; transaction history direction classification and action icons
  • Governance: current votes, Bech32-keyed vote queries, active-proposal voting countdown
  • Staking: overview cache with deferred refresh, auto-refresh fixes, EVM-profile staking gating and warnings
  • Dashboard versions block; Portal link keyed by network profile

Supernodes & Cascade

  • Supernodes page and details page with favorites; hardware/metrics table
  • Cascade map geolocation via a server route with bounded parallel lookups, a checked-in cold-start seed, and browser fallback that supports hostname endpoints
  • Cascade upload/download flows with balance checks and upload tracking

Loyalty quests & referrals

  • /loyalty quest pages with server-side verification (SuperNode uptime, storage requests, Whale tier, first-100 uploads, invite quests, active-stake maintenance) behind Google reCAPTCHA
  • Referral pages, referral stats, and claim rewards from the referral list; admin user list / create-user pages with manual review

Infrastructure & quality

Test plan

  • 305 web unit tests pass (vitest), tsc --noEmit clean, next lint clean on touched files
  • Full production build succeeds on next 15.5.21 (compiles packages/ui)
  • Review threads on this PR addressed and resolved
  • Manual smoke test of MetaMask ↔ Keplr switching and Cascade upload on the testnet profile

🤖 Generated with Claude Code

ngvtuan added 30 commits January 2, 2026 16:23
Hide the Sense page
Add zoom limit for the 3D Earth map
fixed UI issues
Improve layout for the cascade page. - wallet connected
Remove drag & drop for the Supernodes chart on mobile
Fix the bug on the Cascade page where the UI disappears after loading finishes and then reappears.
Add a warning icon when APIs return  errors
Cascade - Update download button colour
Cascade - View Public files
Cascade - UI not adjusting properly
Cascade - Update download button colour
Cascade - View Public files
Cascade - UI not adjusting properly
Added more space to the left menu.
update tracking
add UI for Stats
add tracking for cascade download
update UI for tracking page
akobrin1 and others added 26 commits August 17, 2026 13:42
The hub's deployed revision (39aedbc) sits on develop, which is not an
ancestor of main. main has not moved since 2025-12-09 while develop
accumulated 264 commits and serves both production hubs. evm-support was
branched from the stale main, so it lacks that feature surface.

Records the decision to move the smaller tested change onto the live line
rather than the reverse, the file-by-file reconciliation plan for the 40
overlapping files, the wallet consolidation to Keplr + MetaMask, and a
route-parity gate to stop a wrong HUB_VERSION re-pin from silently
dropping routes from the public hub.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Seventeen tasks implementing docs/design/2026-08-17-develop-evm-integration.md:
cut evm-on-develop, untrack the generated tamagui artifacts, prove the
route-parity gate in both directions, then take evm-support in with one
--no-ff merge resolved in dependency order.

Records the measured per-file resolution rules, the wallet consolidation to
Keplr + MetaMask, and a 2026-08-27 stop-and-reassess trigger ahead of the
Cosmostation shutdown.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Corrects the ours/theirs orientation (on evm-on-develop, develop is ours and
the EVM side is theirs, so the original "take ours" wording would have
discarded the EVM implementation), records the measured 31-path conflict
inventory from an actual merge rehearsal, and adds the utils/wallet-connect.ts
transport deletion that the earlier draft missed.

Also gates the MetaMask onboarding card on IS_EVM_NETWORK so the non-EVM
mainnet profile does not advertise an unsupported wallet, and notes that the
account data layer must be extended rather than swapped wholesale.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…alisation

Findings from review round 1:
- routes_from_ref's failure inside a process substitution was swallowed by
  set -e, so a bad/mistyped baseline ref silently compared as zero routes
  and the gate reported OK. Add an explicit git rev-parse --verify guard
  that exits 3 with a clear stderr message before any comparison runs.
- the bracket-collapse regex left a dangling ] on optional catch-all
  segments ([[...slug]] -> [*]]). Collapse [[...x]] to [*] before the
  plain [x] rule runs.
evm-support was branched from main, which has not moved since 2025-12-09.
The revision actually deployed to both hubs lives on develop (39aedbc), which
is not an ancestor of main and carries 264 commits of feature surface that
main never received: /blocks, /supernodes, /admin, /loyalty/*, /referral,
/wasm and a much richer account page.

Merging develop-first rather than the reverse means the result is based on
what is actually serving traffic, so it can become the HUB_VERSION pin.
31 conflicting paths were resolved in dependency order: generated artifacts,
manifests and lockfile, network config, providers, connect UI, transaction
hooks, staking, governance and home screens, the account layer, the app shell.

Wallets are reduced to Keplr and MetaMask. Leap was sunset by its vendor,
Cosmostation shuts down 2026-09-01, and the WalletConnect/Reown transport is
dropped as a product decision (its only consumer path was mobile QR/deep-link).

Two pre-existing develop bugs are fixed incidentally, because the correct
value sat on the other side of a conflict being resolved: the Cascade upload
gas price had lost its decimal point ('025ulume' -> '0.025ulume', a ~1000x
overcharge on every upload), and a transaction-history block link pointed at
a nonexistent /block/ route instead of /blocks/.

Verified: tsc --noEmit clean, 149/149 tests across 20 files, and
scripts/route-parity.sh confirms all 49 develop route shapes survive.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Extract the account page's remaining untested data fetching into
utils/account-activity.ts with the API client injected, and recompose
useAccount from parseAccountAddress, fetchBaseAccount, fetchAccountInfo,
fetchAccountActivity and fetchConnectedStaking. The hook's 26-field public
shape is unchanged, so AccountScreen needs no edit; per-section loading
flags are preserved through onSlice callbacks so a slow Cascade lookup
still cannot hold back the transaction tables.

Each activity source degrades to an empty collection on failure instead of
rejecting, so one upstream outage can no longer blank the whole page.

Also resolve the [address] route segment through parseAccountAddress, so
/account/0x… typed directly into the address bar resolves the same account
as its bech32 form instead of rendering empty. Cosmos REST rejects hex, so
this previously only worked via search, which converts before routing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Leap was sunset by its vendor and Cosmostation shuts down 2026-09-01;
neither belongs in onboarding anymore. Add a MetaMask card gated on
IS_EVM_NETWORK, since mainnet has no EVM RPC endpoint or chain ID and
genuinely cannot support it yet.
fetchAccountInfo and fetchEvmAccountInfo wrapped their four sources in a
bare Promise.all, so one rejection emptied every slice: a rewards 500 also
zeroed the displayed balance, delegations and unbonding, where develop lost
only the rewards section. Both now settle each source independently through
a shared helper, keeping AccountInfoData's shape and the per-endpoint
console.error trace. A failed EVM balance query yields no balance rather
than a confident zero, and the evmBalanceToMicroLume conversion is
unchanged.

Six tests cover one-endpoint-fails per slice for both functions. Also
correct the ConnectedStaking.availableBalance comment (it is the total bank
balance, not the spendable amount) and split the onSlice test so the
progressive-render claim is actually asserted against the aggregate
promise instead of only checking that callbacks ran.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…zero

Round 1 made the four account slices degrade independently, which stopped
one failing endpoint from blanking all four — but it also removed the only
signal the Wallet screen had: nothing rejects, so account.error stayed null
and a failed balances query rendered as "Available: 0 LUME", the number that
bounds the Send and Delegate inputs.

fetchAccountInfo and fetchEvmAccountInfo now record the slices whose source
failed in an additive optional AccountInfoData.unavailable, and the wallet
page composes describeAccountInfoGaps() into the error string WalletScreen
already renders under the balance total, so no packages/ui change is needed
and partial data still renders. A list of slice names rather than a boolean
lets the notice name the missing figure.

The account page keeps its current behaviour: an endpoint failure has always
rendered as zero there, so this is not a regression, and no number on that
read-only page bounds a user action.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The SDK constructor throws when it cannot find an API key, and Next.js
imports every API route module during its "collect page data" build phase.
Constructing at module scope therefore failed `next build` on any machine
without Snag credentials, including CI and the hub-docker image build, whose
Dockerfile deliberately passes no build secrets.

Exporting SNAG_API_KEY in the shell did not help either: turbo.json's build
task declares a strict env allowlist (NEXT_PUBLIC_* plus NEXT_OUTPUT) with no
passThroughEnv, so turbo stripped it before Next ran. Only a file-based
apps/web/.env.local worked, which a container build has no way to supply.

Construction now happens on first use behind a Proxy, so the 31 route modules
keep importing the default export unchanged. Methods are bound to the genuine
instance because the SDK keeps private state in WeakMaps keyed on the object
the constructor saw (_APIClient_baseURLOverridden, read by buildURL through
__classPrivateFieldGet) and brands instances in a WeakSet — calling such a
method with a Proxy as `this` throws a private-member TypeError.

Only a successful construction is cached, so a misconfigured deployment keeps
raising the SDK's own missing-credential error per request rather than caching
a broken client or degrading into a silent no-op.

Verified: `make testnet-build` succeeds with SNAG_API_KEY absent from both the
environment and .env.local; 187/187 tests across 22 files; tsc --noEmit clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…rrors in place

- resequence balance and transaction queries so a slower previous wallet
  cannot overwrite the active wallet's data after switching accounts
- validate the transaction hash MetaMask returns before reporting success
- show MetaMask errors inside the wallet modal and account menu instead of
  a header alert, and report global API failures as toasts
- require a complete profile (name, RPC, chain id) before advertising EVM
  support so a partial private override cannot enable MetaMask flows
- normalize SNSCOPE_URL (no trailing slash) and unify every call site to
  `${SNSCOPE_URL}/v1/...` so the path-proxied devnet deployment and
  slash-less overrides both resolve correctly
- memoize getClient/getOfflineSigner and cover connect, profile
  verification failure, disconnect revocation fallback and EIP-6963
  discovery with tests; add hook tests for send, transactions and wallet
  connect

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…upernode map

Wallet-connect tracking: normalize bad optional fields in hubUserSchema
instead of rejecting the whole request, decide isNewHub from the insert
itself so concurrent processes cannot double-award referral bonuses, track
every reported address per session (no retry loops on permanent 4xx, no
double counts on account switches).

Wallet state: connect/disconnect now supersede in-flight passive syncs in
the EVM provider, the header explains wallet errors that cleared the
address, and the global API error toast surfaces empty and overlapping
error messages instead of swallowing them.

Supernode map: geolocate unknown nodes through a bounded worker pool with
O(1) cache matching, restore the checked-in location seed as a cold-start
fallback, and let hostname-addressed nodes use the browser ipwho.is
fallback again.

Consolidation: shared useLatestRequest stale guard, openConnectView entry
point (fixes the dead Cascade upload connect prompt on EVM), shared ipwho
mapper and micro-LUME sum helper, PORTAL_URL keyed by network profile with
an env override.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The wallet-switch guard was derived from persisted modal UI state
(isModalOpen + preferredWalletName), which failed in both directions: a
reload mid-switch rehydrated the flags and latched the suppression for
the whole session, while a connect started from the auto-preselected
option never armed it at all — so the synchronizer tore the fresh Keplr
session down mid-approval on one path and never cleaned stale state on
the other.

The guard now reads connectingWalletName from a new walletFlow slice
that is deliberately kept out of the redux-persist whitelist. The wallet
chooser sets it at the start of every connect attempt (however the
wallet was selected) and clears it in finally, after the selection
dispatches, so the window is bounded to the attempt and there is no
render where the guard is down but the old wallet is still selected.
The persisted-storage Keplr scrub pauses together with the in-memory
teardown.

Also: post-connect verification moved into a shared, tested
getKeplrConnectionIssue predicate and now requires a fresh getAccount
read from the extension (the store may hold a rehydrated account from a
previous session), rolls a failed connect back with disconnect(), and
maps the NotExist startup race to a recoverable message. setModalOpen
no longer clobbers a stored wallet target when re-opened without one,
the staking page routes Switch wallet through openConnectView (fixing
the dead non-EVM path), wallet-option clicks are local again instead of
dispatching app-wide, the synchronizer uses narrow selectors, and the
Switch-wallet menu item is present on the menu's first paint.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…harts, @babel/core

Applies the version bumps from Dependabot PRs #83, #86, #87, #88, #89 and
#90 directly on this branch (their lockfiles were generated against main
and do not apply here). next is the important one: 15.5.21 fixes four
high- and five moderate-severity advisories (SSRF in rewrites and Server
Actions, middleware bypass, cache confusion, DoS). eslint-config-next
moves in lockstep with next. The caret ranges resolve slightly above
Dependabot's targets (axios 1.19.0, postcss 8.5.26, @babel/core 7.29.7).

Verified on the bumped tree: 305 web tests pass, tsc clean, next lint
clean, and a full production build succeeds (which also compiles
packages/ui with echarts 6.1.0). The echarts-for-react peer-range
warning predates this change (it never declared 6.x support).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comment thread packages/ui/src/screens/SupernodesScreen.tsx Fixed
The supernodes table guarded the hardware cell with the same CPUCores
check repeated three times (flagged by code review on PR #92), so a
report with cores but no memory/storage metrics would crash the render
on MemoryTotalGb.toFixed(). Each of the three rendered metrics now
guards itself, and the cell falls back to an em dash unless all three
are present.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@akobrin1 akobrin1 changed the title EVM wallet hardening, review fixes, and dependency security bumps EVM support, MetaMask wallet, supernodes, loyalty quests, and platform hardening Aug 21, 2026
@akobrin1
akobrin1 requested a review from a-ok123 August 21, 2026 17:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants