Choose a starting point the team can adopt
With an existing wallet shifting from RN to Electron and changing chain scope, I started with the relatively independent Provider / RPC, tested it separately, and reconnected it gradually.
Jun 2025–Feb 2026 · Two months of incremental refactoring
Two months rebuilding cross-platform wallet requests, state, and flows
With an existing wallet shifting from RN to Electron and changing chain scope, I started with the relatively independent Provider / RPC, tested it separately, and reconnected it gradually.
I brought permission, dedupe, and lock rules into one pipeline, then connected Send and address validation. Seven working documents supported decisions across product, design, and engineering.
Main owns real state and chain-kit owns chain capability; platform, permission, and product boundaries became explicit.
Chapters
Across two months, I wrote and shared seven working documents covering the wallet mental model, architecture risks, DApp requests, Send behavior, address validation, permissions, frontend/backend Service separation, and chain-capability consolidation. They were not retrospective case-study material; they explained context, conflict, trade-offs, and implementation paths while decisions were still being made. Expand one to see why it appeared, what changed next, and its matching chapters.
Core question
Is a wallet an in-memory object that owns data and behavior, or a rendering of database facts at this moment?
Why I wrote it
Establish a shared frame for discussing state, persistence, and cross-host reuse before changing code.
What happened next
The audit separated memory, persistence, and account modeling into explicit lines of inquiry.
Core question
Long-lived plaintext, non-atomic writes, missing migration checks, and an account hierarchy that could not extend naturally.
Why I wrote it
Map an abstract architecture difference onto the MegaWallet code that actually existed.
What happened next
This established that the issue was system-wide and that the first cut should not begin in UI.
Core question
Permission, duplicate prompts, lock behavior, and new-chain support should not remain scattered across Provider, handlers, and Service.
Why I wrote it
Find a relatively independent request boundary that could be replaced first and adopted incrementally.
What happened next
This produced the Provider / Transport and rpc-engine backbone shown in chapters 4–6.
Core question
Help product and design see what is still missing between a prototype, a mockup, and real implementation.
Why I wrote it
Once host development recovered, Send became a real delivery task; the static screens did not define dynamic height, input responsibility, or failure states.
What happened next
The review kept contact search while typing and deferred full validation until input completion, leading into document 06.
Core question
A small validation toolkit for the Recipient address-input flow.
Why I wrote it
Turn the Send-review interaction rules into composable behavior reusable across pages.
What happened next
This created typing → settled → complete → validating → valid / invalid states and validators.
Core question
A product-and-engineering primer covering the standard EOA permission flow and difficult boundary scenarios.
Why I wrote it
Before building DApp connection and confirmation screens, align durable connection with per-action approval.
What happened next
This led to Connector, ApprovalQueue, and one Confirmation UI flow.
Core question
One change split Service into frontend and backend paths for Electron / Extension; another moved scattered chain capability into chain-kit.
Why I wrote it
One change set touched requests, processes, chains, and keys, so its ownership changes needed to be explicit.
What happened next
Frontend/backend Service separation established the host boundary; chain-kit independently owned chain capability, and redundant or false Services disappeared.
01
Git shows the repository starting on June 5, 2025. By my first core refactor on January 8, 2026, 217 days and 836 commits had accumulated. Expo / React Native arrived in June, then Electron in September; the first desktop build literally copied mobile/dist into the renderer. Solana keyrings, signing, RPC, transactions, and Wallet Standard grew through June–August, while the old Solana Send UI was removed on February 12. Platform and chain scope changed before their boundaries were settled, so earlier assumptions kept stacking.
02
The seven documents covered the Wallet-Core mental model, architecture risks, DApp requests, the Send design review, address validation, and wallet permissions. Document 08 recorded two independent changes: splitting Service into frontend and backend paths for Electron / Extension, and moving chain capability out of crypto, vault, and utils/rpc into chain-kit. They were written for product, design, and engineering before implementation decisions hardened, so each change had shared, reviewable context.
03
Provider and DApp RPC touched every host and chain, but remained relatively independent from UI, assets, and storage. That made them replaceable, testable in isolation, and easier to reconnect incrementally. The old Provider used four inheritance layers to combine EVM state, compatibility methods, events, and platform communication before ending at globalThis.$jsBridge. I replaced it with independent EthereumProvider and SolanaProvider implementations: each owns its state and events, while host communication enters only through Transport. Provider no longer knows the concrete RN, Electron, or Extension API.
04
The old path entered service/src/dapp/request.ts, called getHandler(base), and constructed an EVM or Solana Handler for every request. Permission, lock behavior, approval prompts, and RPC fallback remained inside individual methods. New permissions meant editing each method; duplicate prompts required pendingApprovals in handleRequest; a new chain changed Handler classes, maps, types, and base branches. rpc-engine now runs blacklist, logging, error boundaries, permission, CAIP scope, dedupe, lock, and executor in a fixed order, including five protections that did not exist before. Each request ends in a wallet handler, user approval, or RPC proxy.
05
The old architecture split Provider and JS Bridge across two packages. ProviderDelegate depended on globalThis.$jsBridge; the mobile Bridge hard-coded ReactNativeWebView.postMessage, the desktop Bridge hard-coded Electron IPC, and each host needed another buildInject assembly script. A new platform changed both packages, while tests mocked global host objects. Provider now has no host knowledge: React Native, Electron, and Extension each implement a Transport adapter. Supporting another platform means adding one Transport, and tests replace only the interface.
06
Provider and rpc-engine were complete and reconnected to Service by January 15. Moving them into RN, Electron, and web then exposed the missing host foundation: Electron had no React HMR, WebView DevTools interfered with reload, and cross-host runtime entry points were incomplete. I completed those paths because a refactor is not done when old code disappears; it is done when the new backbone can be developed, debugged, and run inside the real app.
07
After establishing Provider / RPC, I connected product flows incrementally. Send and useAddressValidation landed on January 28. Permission / DApp and the process-aware Service architecture were authored on February 10; chain-kit followed on February 12. Relatively independent modules could be delivered and reconnected early, making each change easier for the team to adopt. UI delivery continued alongside work on permission, state authority, and chain capability.
08
The project began with React Native, so Service was organized to run directly inside one frontend runtime. Electron later reused the mobile web output and startup model without first defining one main / renderer authority. Renderer is the frontend the user sees; main is the wallet backend that can access local storage, networks, and keys. Their memory is not shared. The old code therefore imported and started Service on both sides, producing two unrelated wallets with the same package name. I moved full Service, RPC Engine, SQLite WAL storage, and signing into main as the only real state, while renderer keeps a sanitized mirror. User actions cross IPC to main; state changes return as Immer patches, with a full snapshot when patch application fails.
09
chain-kit was not a directory rename. crypto, vault, and utils/rpc became one function-based chain package; Transfer merged into Tx; the Nonce Service, which never provided its implied concurrency safety, was removed in favor of pending nonce; actions and selectors became write and read boundaries; Zod schemas became runtime validation and type sources. Separately, Signer clears its own Uint8Array buffers after use. Temporary library strings remain an explicit JavaScript limitation; that security enhancement is not evidence of chain-capability consolidation.
010
When a site connects, it needs a durable pass recording the site, chain, and visible accounts. Signing, transfers, network changes, and asset requests need only a one-time decision. Connector stores the durable relationship; ApprovalQueue holds the current request and destroys it after approval, rejection, or timeout. Connect, Sign Message, Send Transaction, Switch / Add Network, Watch Asset, and Solana confirmation screens all use this single flow.
011
In Figma, the Recipient Picker list state was 378×406 and the complete-address state 378×210. The issue was not merely animation: should the input search contacts or validate an address first? I defined typing, settled, complete, validating, and valid / invalid states, then composed address, domain, self-transfer, contract, address-book, and recent-recipient validators. A fixed three-row list, 64px result area, 300ms debounce, and 600ms settled hint turned continuous jumping into one meaningful transition.
012
This was not a greenfield wallet built in two months. It was incremental replacement inside 217 days and 836 prior commits. The current source contains the independent Provider / Transport, configurable RPC pipeline, a cross-host request backbone reconnected to Service, DApp connection and confirmations, main-authoritative Service, chain-kit, Send, and address validation. The project did not reach market launch, but platform, chain, state, permission, secret, and UI ownership had become explainable.
Jan 8
Transport adapters, heterogeneous Providers, and the middleware pipeline entered the repository.
Jan 14–15
The independent Provider and rpc-engine entered the main project, exposing its host-infrastructure gaps.
Jan 16–22
Cross-host runtime entry points arrived alongside Electron React HMR and the WebView DevTools / reload fix.
Jan 28
Design conflicts became input states, validators, caching, and multi-chain rules.
Feb 10
Connector, ApprovalQueue, confirmations, and the main/renderer bridge took shape.
Feb 12–13
Chain capability moved from three packages into chain-kit; Transfer and Nonce closed down. Separately, signer buffers gained post-use cleanup.