refactor: pure-frontend (zero-backend) migration — storage-class + foundation prep [WIP · PAUSED]#400
Draft
jhfnetboy wants to merge 8 commits into
Draft
refactor: pure-frontend (zero-backend) migration — storage-class + foundation prep [WIP · PAUSED]#400jhfnetboy wants to merge 8 commits into
jhfnetboy wants to merge 8 commits into
Conversation
f9d0b1a to
9c03d6c
Compare
Anchor doc for incrementally removing the NestJS backend so YAA becomes a pure client-side app (then packageable as Chrome MV3 / React Native / single-file widget). Captures the server-only-secret constraints and their zero-backend resolutions (KMS Origin-direct, public/sponsored bundler, passkey-rooted auth, IndexedDB storage), a module-by-module destination map, the low-risk-first extraction order, and open questions (bundler, KMS browser auth, auth model, operator surfaces).
…e store Replace the backend /address-book* endpoints + JSON store with a per-account localStorage store (lib/address-book-store.ts) mirroring the old server logic (sort by usage then recency, keep last 5 tx hashes, case-insensitive match). Transfer + the address-book page read/write the client store; the transfer flow records the recipient client-side when polling confirms the tx (replacing the server-side recordSuccessful- Transfer). addressBookAPI removed from lib/api.ts. Scope change: address book is now per-AA-account (was per-JWT-user), consistent with the default-paymaster preference. Backend address-book module left in place for now (removed in migration step 8). Also records the API-key model (bundler+KMS) in the plan. 11/11 store logic tests pass; tsc/lint/build green.
9c03d6c to
58fe81d
Compare
…(client-side) Replace the backend GET /tokens/balance/:address with a direct viem on-chain read (lib/token-balance.ts: erc20Abi balanceOf/decimals/symbol/name, lazy Sepolia public client), returning the same TokenBalance shape. transfer + DashboardContext now read balances client-side; getTokenBalance removed from lib/api.ts. Verified the read path live on Sepolia (aPNTs: symbol/name/decimals/balanceOf). getTokenBalance was the only tokenAPI method actually used. The saved-token *list* (userTokenAPI) is still backend — tracked as step 1c. tsc/lint/format/build green; dashboard + transfer smoke 200.
…side store Replace the backend /user-tokens* endpoints + DB with per-account localStorage (lib/user-token-store.ts). getUserTokens / addUserToken / initializeDefaultTokens are client-side; a custom token's metadata resolves on-chain (getTokenMetadata added to token-balance.ts), balances attach via step 1b, and the defaults mirror the old backend PRESET_TOKENS. TokenSelector + DashboardContext use the store; userTokenAPI removed. Only getUserTokens/addUserToken/initializeDefaultTokens were ever used (update/remove/ delete/search/reorder were dead). Scope: per-AA-account (was per-JWT-user). Preset tokens are Optimism contracts — a pre-existing Sepolia-app mismatch preserved verbatim. 8/8 store logic tests; tsc/lint/format/build green; dashboard + transfer smoke 200.
…lient-side Move the saved paymaster list to per-account localStorage and build the recommended presets from the SDK canonical table (lib/paymaster-store.ts). The list was only a frontend picker — paymasterAPI.sponsor was unused and the transfer backend sponsors from the passed paymasterAddress — so this touches no sponsorship logic (that moves client-side with the transfer flow, step 4). paymasterAPI removed. Self-review (Opus) caught + fixed an account-timing regression: the now account-scoped saved list was read once at mount, so a fresh load beating the async account fetch left it empty with no reload. Transfer now (re)loads it in an effect keyed on account.address; the Paymaster page's load effect gained the accountAddress dep. (TokenSelector already keyed loadTokens on accountAddress.) 8/8 store logic tests; tsc/lint/format/build green; paymaster + transfer + dashboard 200.
| function save(account: string | null | undefined, list: StoredPaymaster[]): void { | ||
| if (typeof window === "undefined") return; | ||
| try { | ||
| window.localStorage.setItem(storageKey(account), JSON.stringify(list)); |
…uth first) blsAPI was never called by the frontend (BLS runs in the backend transfer flow), so it's removed as dead code; the real BLS moves client-side with the transfer migration. Re-planning: the account operations (getAccount/create) are KMS/auth-rooted — the account address and signatures derive from the user's KMS key via the backend signer, so account/ transfer/guardian can't move to the browser until the KMS/auth foundation exists. The plan doc now inverts the order: KMS Origin-direct + API-key model + passkey-rooted auth come FIRST, then account/transfer/guardian build on top. That foundation depends on real KMS Origin-auth infra (not just a frontend edit).
…m + Settings Frontend-only groundwork for the zero-backend KMS/auth foundation, done ahead of the infra and deliberately NOT wired into any flow (non-breaking): - lib/api-key-store.ts: hold the user's AAStar API key + optional KMS/bundler endpoint overrides in localStorage (the key that will authorize direct browser->KMS/bundler). - lib/kms-client.ts: extend the existing KmsClient with a direct-KMS seam — kmsBaseUrl(), isDirectKmsReady(), directKmsClient() (wires KmsClient to the user's key), pingKms(). - app/settings/page.tsx (+ avatar-menu link): enter/save/clear the key + endpoint overrides, test KMS connectivity, show direct-ready status. pingKms CORS-fails until KMS allows the browser Origin — that failure is itself the readiness signal. Auth/transfer still use the /kms-api proxy; these switch over once the KMS Origin+API-key path is live. lib/auth.ts already centralizes the JWT, so no convergence needed there. tsc/lint/format/build green; /settings + /dashboard smoke 200.
| function set(key: string, value: string): void { | ||
| if (typeof window === "undefined") return; | ||
| try { | ||
| window.localStorage.setItem(key, value); |
Consolidate the migration record: overall ~25% (storage-class + foundation prep done; core auth/account/transfer/guardian blocked on infra), a Done/Left summary, and a new 'Infra requirements' section for the KMS/bundler teams — Origin trust for non-aastar.io shells, production per-user-API-key auth, CORS, and the same for the bundler. Branch is paused until that infra lands.
Member
Author
|
Paused (2026-07-01). Storage-class migrations + frontend foundation prep are done (~25% overall). The remaining core (auth / account / transfer / guardian) is KMS/auth-rooted and blocked on KMS/bundler infra that doesn't exist yet — see the Infra requirements section in |
This was referenced Jul 2, 2026
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.
WIP / draft — incremental zero-backend migration: remove the NestJS backend so YAA becomes a pure client-side app, then packageable as Chrome MV3 / React Native / single-file widget. Plan:
docs/PURE_FRONTEND_MIGRATION.md.Done — storage-class migrations (no infra needed)
lib/address-book-store.ts(localStorage, per-account); transfer records the recipient client-side on confirmation.lib/token-balance.ts(viemerc20Abion-chain read); verified live on Sepolia.lib/user-token-store.ts(localStorage; custom-token metadata resolved on-chain).lib/paymaster-store.ts(localStorage list + SDK-canonical presets).sponsorwas unused; sponsorship stays in the transfer flow (moves in step 4).blsAPI(frontend never called it; BLS runs in the transfer flow).lib/api.ts: addressBookAPI, userTokenAPI, paymasterAPI, blsAPI, tokenAPI.getTokenBalance. Each is per-account-scoped localStorage.Done — foundation prep (frontend-only, not yet wired)
lib/api-key-store.ts— holds the user's AAStar API key + KMS/bundler endpoint overrides.lib/kms-client.ts— direct-KMS seam (kmsBaseUrl/directKmsClient/pingKms) over the existingKmsClient.app/settings/page.tsx— enter/save/test the key (avatar-menu link).Re-plan (important)
Everything left — account / transfer / guardian / auth — is KMS/auth-rooted (account address + all signatures derive from the user's KMS key via the backend signer). So the order is inverted: the KMS Origin-direct + API-key model + passkey-rooted auth foundation must come first, then account/transfer/guardian move on top, then delete
aastar/+output:'export'. That foundation depends on KMS-side infra (Origin trust for the app + accepting per-user API keys from browsers in production + CORS), not just frontend edits.Quality
Every step: per-account localStorage or on-chain read, gates green (tsc/lint/format/build), store logic tests, live smoke 200. Self-reviewed (Opus); Opus review caught + fixed an account-timing regression in step 2.