Skip to content

v2: wallet hardening, Microsoft Store build and dependency upgrades - #194

Merged
igorls merged 51 commits into
masterfrom
v2-tauri-rewrite
Oct 14, 2026
Merged

igorls merged 51 commits into
masterfrom
v2-tauri-rewrite

Conversation

@igorls

@igorls igorls commented Oct 14, 2026

Copy link
Copy Markdown
Member

Brings master up to date with v2-tauri-rewrite since #163. After this, building from master gives the current v2 desktop wallet (bun install && bun run tauri:build; see the README).

Security

  • Trusted signing confirmation. Signing and key export go through a backend-owned sign-confirm window that shows exactly the bytes that will be signed. The legacy signing and export commands are no longer registered, so the renderer cannot sign silently.
  • Key generation draws only from the OS CSPRNG through one rng module, and the backend was hardened against panics, key exposure and config gaps.
  • Quick-unlock PIN is session-only. The master key is held in memory under a key derived from the PIN with a random salt, five wrong PINs drop it, and nothing PIN-related is written to disk. pin.dat files from earlier builds are deleted.
  • Windows Hello unlock decrypts with a key derived from a Windows Hello signature, instead of a DPAPI secret behind a consent prompt that other programs running as the user could read. Old-format files are deleted.
  • Key store. The wallet always uses the encrypted file store. keyring 4 made the OS-keyring probe pass, which hid existing vaults. A known-answer test now pins the PBKDF2 / AES-GCM unlock chain across crate upgrades.
  • App Store builds enable the sign-confirm capability again.

Features

  • Ultra EVM bridge, Create Account screen, per-chain contacts, watch-only import and quick-pick in Send.
  • Update notifications in the UI, provider stale-node detection, and fixes for XPR balances and Vaulta action routing.
  • Fonts are bundled with the app, so it makes no font requests at runtime.

Distribution

  • Microsoft Store (MSIX) build: bun run tauri:build:win:store, documented in docs/windows-store.md. The NSIS installer and its updater are unchanged.
  • Windows and Linux packaging, and source builds without an updater signing key. A source build still verifies official updates.
  • The app version is declared once, in package.json.
  • PRIVACY.md describes what stays on the device and what the app sends over the network.

Dependencies

Tauri 3.0.0-alpha.4, Angular 22.2.2, reqwest 0.13, k256 0.14, sha2 0.11, ripemd 0.2, pbkdf2 0.13, aes-gcm 0.11, rand 0.10, base64 0.23, keyring 4, windows 0.62. The unused aes, cbc and hmac crates were removed, along with four unreachable RPC endpoints.

Testing

  • cargo test --lib: 157 passed, 3 ignored. This covers the session PIN (it never reaches disk, attempts are capped, and it is dropped where it would outlive its key) and the Windows Hello file format.
  • bun run test: 101 passed; bun run build succeeds
  • CI passes on the branch head
  • Manual checks on Windows 11:
    • an existing vault unlocks after the upgrade
    • Windows Hello: enable, unlock, and recovery from an old-format file
    • the MSIX package installs, reads the existing vault, and leaves it intact on uninstall

igorls added 30 commits May 29, 2026 19:38
Apply mechanical fixes from the pre-release security audit. No behavior
change for valid inputs; each change is tagged with its audit ID (SEC-NNN).

Panic/DoS hardening:
- New char-safe truncation helper (util.rs) replaces every attacker-
  reachable &s[..N]; Ledger frame parsing made saturating, bounds-checked
  and frame-capped; serialize_apdu rejects >255B; packed_tx varuint counts
  capped before allocation; PowerUp math on i128/checked.
  (SEC-010/011/012/013/019/036/042/043/044/056)

Network resource limits:
- Cap RPC/Hyperion/discovery response bodies (8 MiB / 1 MiB) and disable
  HTTP redirects on all clients. (SEC-017/060)

Key-material hygiene:
- Zeroize decrypted private keys; proactive 30s idle-lock task; reset_wallet,
  change_passphrase and export_backup now cover testnets, bls_* finalizer
  keys and OS-keyring index entries; drop the pubkey log loop.
  (SEC-026/027/028/029/041)

Config & dapp isolation:
- Drop the devtools feature from release builds; JSON-encode and validate
  the dapp bridge request_id/reason before eval; validate the dapp URL
  scheme on initial load; scope sensitive events to the main window.
  (SEC-008/045/048/050)

CI hardening:
- Least-privilege GITHUB_TOKEN, pin Bun to 1.3.11, cargo --locked, verify
  e2e .deb checksums. (SEC-062/064/065)

Verified: cargo check, cargo clippy and tsc --noEmit all pass.
Deferred: Windows owner-only ACL for pin.dat/biometric.dat/keystore files
left as a TODO (SEC-030), to land with the unlock redesign.
Replace the Unix-only scripts/with-supported-node.sh wrapper with
cross-platform JS helpers (scripts/with-supported-node.js, scripts/
tauri-dev.js) so start/build/watch/test and tauri:dev run on Windows,
macOS and Linux. Move the Angular dev server to port 42024 (devUrl +
ng serve). Update CLAUDE.md and README accordingly.
…tion window for signing

- Removed the LinkSessionService and associated sealed message handling due to security concerns.
- Introduced a new ConfirmWindowComponent for trusted transaction confirmations.
- Updated TauriIpcService to support new signing flow with backend confirmation.
- Modified TransactionService to route signing requests through the trusted confirmation window.
- Added confirmation policy settings in the SettingsComponent to require passphrase for every signature.
- Enhanced the MsigInboxComponent to bind approvals to exact proposal bytes for security.
…, echarts 6.1, prettier 3.9, vitest 4.1.10; cargo update 129 crates; drop unused @types/echarts
…ta action routing

Fixes surfaced while registering a Telos finalizer key and testing on Vaulta.

Provider reliability:
- Detect stale RPC nodes: health check now compares head_block_time to the
  local clock and flags a node lagging > MAX_HEAD_LAG_SECS (120s) as stale.
  Stale nodes are excluded from selection but never circuit-broken, so a
  wildly-wrong local clock degrades gracefully instead of locking up. This is
  the root cause of the mis-sent finalizer tx (a dead greymass node stuck days
  behind still answered 200 OK).
- Remove the two non-functional Telos endpoints (greymass = pre-Savanna/stale,
  eosnation = 404 on the chain API) from chain_config.

BP Keys:
- Render the real permission structure from get_account (owner/active/custom
  perms with their actual public keys, thresholds, account-auths, waits)
  instead of a hard-coded owner/active/claim placeholder.
- Gate the finalizer-key UI on Savanna support: check the chain's eosio ABI for
  regfinkey and show a clear message when the chain hasn't activated Savanna,
  instead of letting the user push a doomed transaction.

Vaulta rebrand:
- Add a per-chain system_contract config field ("core.vaulta" on Vaulta, "eosio"
  elsewhere) and a WalletStateService.systemAccount() helper. Route the core
  economic actions (claimrewards, voteproducer, buy/sellram, delegatebw, rex,
  powerup, ...) to core.vaulta on Vaulta; producer/consensus actions stay on
  eosio. Fixes the "EOS has been rebranded to Vaulta" assertion on claim.
- Keep these on the trusted local (Native) serializers by normalizing
  core.vaulta -> eosio in try_native_serialize and the high-risk-warning
  matcher (layouts are byte-identical), so signing stays WYSIWYS.

Tests: provider staleness + Vaulta serialization covered; full backend lib
suite passes (113).
New dashboard screen creating accounts via newaccount + buyrambytes + delegatebw, signed through the trusted confirmation window. Generates keys in the backend, validates on-chain name availability, and can import the created account into the wallet. Routed at /dashboard/create-account with a nav entry; checks off the ROADMAP account-creation items.
… module

Route secp256k1 keygen, BLS finalizer IKM, and AES-GCM nonces through a single crate::rng module backed by OsRng (getrandom) instead of thread_rng/rand::random, keeping the entropy trust base to the OS kernel. Add fail-closed guards (reject all-zero draws), a startup CSPRNG self-test, and zeroization of transient secret buffers.
- Added ContactsService to handle contact management, including loading, adding, updating, and removing contacts scoped to specific chains.
- Introduced validation and normalization functions for contact addresses, supporting account names, FIO handles, and FIO public keys.
- Implemented migration logic for legacy contact data to the new per-chain structure.
- Enhanced the Tauri IPC service to include exchange deposit account configurations.
- Created memo validation logic for exchange deposit accounts, ensuring correct formatting and length.
- Updated the SendComponent to integrate contact management and memo validation, providing user feedback for exchanges.
- Added unit tests for memo validation and exchange rules.
The titlebars relied on `-webkit-app-region: drag`, which is an
Electron/Chromium-shell feature that WebView2 and WKWebView ignore. In
Tauri, dragging comes solely from `data-tauri-drag-region`, and a bare
attribute only drags when that exact element is the click target.

The dashboard therefore had a single draggable element — the 120px
`.titlebar-drag-fill`. Empty space after the last account tab, the tab
strip's reserved 28px paddings, the chain-filter padding and the flex
gaps were all dead zones with no visible element to explain them. The
lockscreen and landing titlebars worked only because the attribute was
copied onto each child, and still missed the "EOS" span inside the
wordmark; the sign-confirm window's brand text was dead as well.

Switch every titlebar to `data-tauri-drag-region="deep"`, so any click
that does not land on an interactive element drags the window — Tauri's
handler still lets buttons, links and inputs block it, keeping account
tab clicks and tab reordering intact. The chain-filter dropdown opts
out with `data-tauri-drag-region="false"` so its padding does not move
the window while the menu is open.

Also drop the inert `-webkit-app-region` declarations so the mechanism
is not misleading later.

Bump the workspace to 2.0.0-alpha.2 (package.json, tauri.conf.json,
Cargo.toml/lock, about screen) so the signed macOS alpha can ship with
these changes.
The version was written out in five places — package.json,
tauri.conf.json, Cargo.toml, Cargo.lock and three templates — so a
release meant editing all of them and the footers had already drifted
(they showed a suffix-less v2.0.0-alpha while About said alpha.1).

package.json is now the single source of truth:

- tauri.conf.json takes `"version": "../package.json"`, which Tauri
  resolves at config-read time, so bundle metadata, the DMG name and
  the updater all follow the same number.
- Cargo.toml is pinned to 0.0.0 with a comment — the crate version is
  not the app version, and nothing reads CARGO_PKG_VERSION.
- The UI injects AppVersionService, which asks the running bundle via
  Tauri's getVersion() and falls back to `dev` in the browser-only dev
  server. The About screen, lockscreen and sidebar footers use it.
- scripts/macos-package.sh reads package.json for the DMG name.

tauri.appstore.conf.json still pins a plain x.y.z, since App Store
CFBundleShortVersionString rejects prerelease tags; that number cannot
be derived, so macos-appstore-package.sh now fails the build when it
drifts from package.json instead of shipping a stale version.
The scroll arrows were 24px circles positioned absolutely over the tab
strip, which meant they sat on top of the first and last tab and forced
28px of dead padding inside the scroller to keep from covering them.

Make them real flex siblings instead: [ ‹ ] [ viewport ] [ › ], full
titlebar height, flush and flat with a hairline separating them from
the tabs, so nothing overlaps and the arrows read as window chrome. The
edge fades move onto the new viewport element so they stop at the
arrows rather than washing over them.

Both arrows now render together as soon as the strip overflows and are
merely disabled at each end. Rendering them per-direction resized the
strip mid-scroll and slid the tabs under the pointer.

Tab balances are rounded for scanning — `18088.0334794 WAX` reads as
`18,088 WAX`, millions collapse to `1.23M`, and dust keeps enough
digits not to render as zero. The exact amount stays in the tooltip.
The formatter lives in core/format.ts so other dense views can share it.
The post-unlock screen opened with a hero that took most of the window
before showing anything actionable: a kicker, a 2rem title, a paragraph
of onboarding copy, three meta chips, three highlight cards and four
stat cards — cards nested inside cards, where most of the height came
from. It also repeated itself. The chips restated the account and chain
counts, "fast state mode" restated the priced count, and "producer
coverage" restated the chains card's own subtitle.

This is a screen people land on several times a day to answer three
questions: is anything on fire, what is it all worth, and where is the
account I need. So the hero becomes a summary bar — one panel, four
figures split by hairlines, with the estimated value given the only
extra emphasis. The description is gone; someone opening the wallet for
the first time reads the empty state, which already explains the
screen. The unpriced case now reads as a dash plus "No cached price
yet" instead of wrapping "No cached price" across three lines at 28px.

Everything below it is trimmed the same way:

- Resource health puts its title and threshold legend on one line and
  drops the instruction to open resources — each row has a Manage
  action already.
- Chain headers lose the "Network summary" kicker and drop from 20px to
  15px, with the counts inline.
- Account cards lose the RAM metric block, which showed the same number
  as the RAM meter directly beneath it, and the repeated "Open account"
  caption becomes a chevron that leans in on hover. RAM usage in bytes
  moves to the meter's tooltip.

Balances on this screen use the same rounding as the account tabs, with
the exact amounts kept in title attributes, and core/format.ts picks up
unit tests for the rounding edges (dust, millions, non-asset input).
…able

Everything under the updater was already wired — plugin registered and
gated off for App Store builds, capabilities granted, pubkey pinned,
createUpdaterArtifacts on, and a service that downloads with progress
and relaunches. None of it could reach a user.

Nothing read isUpdateAvailable and installUpdate() had no call sites,
so a found update was detected and discarded. The single call site
passes silent=true, which skips the one alert in the service, and check
failures went to console.error only. On top of that the endpoint 404s:
GitHub's releases/latest/ resolves to the newest non-prerelease, which
for this repo is v1.0.5 from 2020, and the alpha release has only the
DMG attached — no latest.json, no .sig.

UI:

- Settings gains an Updates card: running version, current state
  (checking / up to date / available / downloading with a progress
  bar), release notes, and Install and restart. Hidden entirely in App
  Store builds, where Apple ships updates.
- The sidebar footer swaps the version label for an "Update ready"
  indicator when there is something to install, deep-linking to that
  card, which scrolls into view and highlights. The startup check stays
  silent — the indicator is the notification, not a toast.
- UpdateService keeps the last error and check time so a persistently
  unreachable manifest is visible instead of failing forever in silence.

Release side:

- The endpoint moves to a fixed branch path, which is unaffected by
  prerelease flags: raw.githubusercontent.com/eosrio/simpleos/updater.
- scripts/updater-manifest.js builds latest.json from the signed
  artifacts under src-tauri/target, mapping each to the {os}-{arch}
  keys the plugin looks up. A universal macOS build is listed under
  both darwin-aarch64 and darwin-x86_64 — Tauri matches on the running
  arch and has no universal fallback.
- scripts/publish-updater.js uploads those artifacts to the release and
  writes the manifest to the branch, merging into what is already
  published so a Windows release adds its platform without dropping the
  macOS entries. It refuses to publish a manifest referencing an
  artifact that is neither built locally nor already on the release, so
  the endpoint cannot advertise a download that 404s.
- docs/releasing-updates.md documents the flow; bun run updater:manifest
  / updater:publish (and :dry) drive it.
Adds the packaging path for Windows and Linux, makes building from
source work for people who are not cutting a release, and adds a way to
exercise the update flow locally. Verified by building and running the
Windows bundle on Windows.

scripts/package-desktop.js is now the entry point for `bun run
tauri:build` on every OS (nsis / appimage+deb / app+dmg). It exists
mainly because of what happens without the updater signing key: the
config pins an updater public key, and Tauri refuses to bundle when it
finds a public key with no private key, so a clone of this repo was
simply not buildable. It now passes --no-sign in that case and the
build succeeds. Such a build still *receives* official updates —
verification uses the public key in the config — it just cannot be
published as one. `tauri:build:raw` keeps the unwrapped behaviour.

Three traps found by actually running the release path, each of which
cost a full compile before showing itself:

- `tauri build` only reads TAURI_SIGNING_PRIVATE_KEY. The _PATH variant
  that docs/macos-packaging.md tells releasers to export is ignored
  when bundling, and the build dies with "a public key has been found,
  but no private key" *after* everything is compiled. Both packaging
  scripts now read the file into the variable Tauri actually reads,
  which is likely why the alpha.1 release shipped without any .sig.
- With a key but no TAURI_SIGNING_PRIVATE_KEY_PASSWORD, Tauri prompts
  on stdin and hangs a non-interactive build at the same late stage.
  Both scripts warn, and the docs say to export it even when empty.
- The Windows updater artifact is the setup.exe itself, not a
  .nsis.zip. The manifest generator classifies it correctly.

scripts/updater-selftest.js rehearses discovery, download, signature
check and install against a throwaway key and a localhost endpoint, so
nothing production is involved. The generated override sets
dangerousInsecureTransportProtocol because the plugin panics at startup
on a non-HTTPS endpoint; it lives only in that generated file and never
in the shipped config. Note the single-instance plugin: a second launch
hands off and exits, which looks exactly like a broken updater.

Because a rehearsal leaves artifacts signed by the throwaway key in the
same target directory, the manifest generator now compares each
signature's minisign key ID against the pubkey in tauri.conf.json and
refuses to build a manifest from anything the shipped app cannot
verify.
The saturated chain brand colors read fine as a fill or a 1px border
but fail badly as small text — Telos at 2.6:1 on a dark card, WAX at
2.0:1 on a light one. Rather than desaturate the brand, add
--accent-text: the same hue lifted on dark and deepened on light until
it clears AA on every surface in the palette, per chain and per theme.
--accent stays the fill/border color; --accent-text is what `color:`
uses. Every `color: var(--accent)` in the app moves over.

The neutral text ramp is retuned on the same basis, with the measured
worst-case ratio across all five --bg-* surfaces recorded next to each
token. --text-muted carries real content at 10-13px, so it now clears
AA (4.5) even on --bg-active; --text-disabled is placeholder-only and
AA-exempt, but stays legible at 3:1. Several places that recessed
accent text with `opacity: 0.6` now use --text-muted instead — the
alpha was undoing the contrast the ramp exists to guarantee.

The light-theme chain blocks must stay after the chain accents: the
bare [data-theme="light"] default ties with :root on specificity and
wins only on source order, which is how chains without an explicit
light block (including vaulta) get a readable accent.
Add the action-permission screen and handle contract-wide links. Harden signing, vault recovery, preference access, updater verification, and CI. Improve RPC failover, history recovery, and account/network response ownership with regression tests and readiness evidence.
Update current-major frontend and Rust dependencies and refresh both lockfiles.
pako 3 ships its own types, so drop the deprecated @types/pako stub. Named deflateRaw and inflateRaw imports are unchanged.
The test environment now requires Node 22.22, 24.15, or 26. Unit tests pass on Node 26.
Angular 22 accepts Vitest 5. The unit suite still passes with clearMocks on by default.
PublicKey.from/equals and Name.value still match the two call sites. signing-request 3 still pulls antelope 1 until that package moves.
The ESR helpers keep the same calls. Identity fixtures still match, and the duplicate antelope 1 copy leaves the bundle.
The simple feature is gone, so enable the sha2 feature and use its Sha256. pbkdf2 0.13 requires Rust 1.85. Encrypt and decrypt round-trips still pass.
Nonce::from_slice is deprecated, so build the nonce with TryFrom. The keystore encrypt and decrypt tests still pass.
igorls added 21 commits October 1, 2026 21:03
keyring 3 was built without a platform feature, so it only had its mock
store. The startup probe always failed and every shipped build kept its
vault in FileKeyStore. keyring 4 enables the real OS store by default,
so the probe started passing and the app switched to an empty
OsKeyStore. Unlock then could not find the vault verification token and
reported a wrong passphrase, although the vault on disk was intact.

Select FileKeyStore unconditionally and drop the startup probe.
OsKeyStore also lacks the atomic store_batch and is_empty the vault
relies on, so moving to the OS keyring needs an explicit migration.
The Test Keyring diagnostic now reports the real backend and the probe
result separately.

Add a known-answer test that pins the unlock chain (PBKDF2 master key,
PBKDF2 storage key, AES-256-GCM verify token) to vectors from an
independent implementation. Round-trip tests cannot catch a crypto
crate upgrade that changes the output.
Nothing in the crate uses them since the anchor-link sealed-session
transport was removed. pbkdf2 and aes-gcm bring their own versions.
Matches the RustCrypto generation pbkdf2, aes-gcm and k256 already use,
so the build no longer carries two copies of sha2. No source changes.
No source changes. The BLS key encoding tests still pass.
rand 0.10 is on rand_core 0.10 like k256 0.14, so key generation uses
SysRng directly and the hand-written K256Rng adapter is gone.
secure_rng() wraps SysRng in UnwrapErr, which panics on any OS RNG
error, so generation still fails closed. Entropy still comes only from
getrandom, and the build now carries a single rand.
Semver-compatible updates only, including tokio 1.53.2, hyper 1.12,
serde_json 1.0.154 and zeroize 1.9.1.
jsdom 30.1.2, prettier 3.9.10 and vitest 5.0.4. TypeScript stays on 6.0
because Angular 22.2 supports >=6.0 <6.1 only.
fio.api.eosnation.io and libre-api.edenia.cloud no longer resolve,
testnet.wax.pink.gg serves a certificate for another name, and
api.eossweden.org fails the TLS handshake with both rustls and
schannel. Each chain keeps at least two working endpoints.
Rio Blocks is the name the company uses for its software, and the
Microsoft Store listing is published under it.
Listing capabilities in app.security.capabilities enables only the
listed ones, so both App Store configs dropped sign-confirm. The
trusted confirmation window then has no capability and cannot reach
approve_sign or reject_sign.
The Store re-signs MSIX packages, so this gives a signed Windows build
without a code signing certificate. Tauri only bundles NSIS and MSI, so
bun run tauri:build:win:store builds the binary with --no-bundle, stages
it with the manifest and visual assets, indexes them with makepri and
packs the MSIX with makeappx. The NSIS installer on GitHub is unchanged.

The Store build reuses the app-store feature and the appstore Angular
configuration, so the self-updater and its UI are left out.

MSIX redirects new AppData files to a per-package folder that Windows
deletes on uninstall, which would take vault keys with it. The manifest
excludes the app's Roaming and Local AppData folders from write
virtualization (Windows 11 per-folder form, Windows 10 fallback), so the
Store and NSIS builds share the same vault and it survives uninstall.

Versions map to MSIX form so prereleases sort below the release
(2.0.0-alpha.3 -> 2.0.3.0, 2.0.0 -> 2.0.999.0). Unplated target-size
icons keep Windows from drawing the taskbar icon on an accent plate.
PIN unlock stored the wallet passphrase in pin.dat, encrypted under a
key derived from the 4-6 digit PIN with a salt shared by every install.
A copy of the app data folder was enough to recover the passphrase by
trying every PIN offline, and wallet reset left the file behind.

The PIN now lasts only while the app runs. set_pin keeps the master key
in memory, encrypted under a key derived from the PIN with a random
salt, and unlock_with_pin re-opens the session after an auto-lock or
manual lock. Five wrong PINs, quitting, a passphrase change, reset or
sign-per-use mode drop it, and nothing PIN-related is written to disk.
Startup and reset delete any pin.dat left by earlier builds, and reset
also removes confirm_policy so a new wallet starts from the strict
confirmation policy.

The lockscreen shows how many attempts remain and falls back to the
passphrase once the PIN is gone. Settings explains that the PIN lasts
until the app quits and hides it in sign-per-use mode.
The stylesheet imported both from Google Fonts, so every launch fetched
them from fonts.gstatic.com, which delayed text rendering and sent the
user's IP address to Google. They now come from @fontsource and ship
inside the app.
Describes what stays on the device, what the app sends over the network
and to whom, and how to erase the vault. The Microsoft Store listing
links to it.
Windows Hello unlock stored the passphrase in biometric.dat with DPAPI
and asked UserConsentVerifier for a yes/no prompt first. The prompt
gated nothing cryptographically: any program running as the same user
could call CryptUnprotectData with the published entropy and read the
passphrase without a prompt.

Enabling now creates a Windows Hello key (KeyCredentialManager), has it
sign a random 32-byte challenge, and encrypts the passphrase with
AES-256-GCM under SHA-256 of a domain tag and that signature.
biometric.dat keeps only the challenge and the ciphertext. Unlock signs
the challenge again, which requires the Windows Hello prompt, to rebuild
the key. Enrollment verifies the signature as RSA PKCS#1 v1.5 against
the key's public half, so the scheme is only enabled where signing is
deterministic. The plaintext is length-prefixed and padded to 256-byte
blocks so the file does not reveal the passphrase length.

A watcher brings the Hello dialog to the foreground, since this API
version cannot parent it to the app window. When the key is gone, for
example after a Windows PIN reset, or the file no longer matches, the
shortcut is removed and the lockscreen falls back to the passphrase
with an explanation. Startup deletes any biometric.dat in the old
format, and reset and passphrase changes also delete the Hello key.
They were working notes for the September release review, not project
documentation.
The browser-only mock wallet and the design spec used real account
names. Store screenshots and demos are taken from this mode.
Copilot AI balanced review requested due to automatic review settings October 14, 2026 01:16

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@igorls
igorls merged commit ce959c6 into master Oct 14, 2026
6 of 7 checks passed
igorls added a commit that referenced this pull request Oct 14, 2026
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.

2 participants