Skip to content

feat: generic accounts - #431

Open
PhearZero wants to merge 1 commit into
TxnLab:mainfrom
algorandfoundation:feat/account-types
Open

PhearZero wants to merge 1 commit into
TxnLab:mainfrom
algorandfoundation:feat/account-types

Conversation

@PhearZero

@PhearZero PhearZero commented Mar 9, 2026

Copy link
Copy Markdown
Contributor

feat: generic account types

Makes the account type generic across the stack: wallet state, store, manager, and the framework
hooks. Wallet adapters are untouched; every generic defaults to WalletAccount, so existing apps
compile and behave exactly as before. New capabilities are opt-in.

What

  • Generic wallet state (packages/core/src/wallets/base.ts, types.ts): WalletState<T> and
    BaseWallet<TOptions, TType> carry the account type, with WalletAccount as the default.
    Adapters can adopt a custom account type later via the second type parameter.

  • Generic store (packages/core/src/store.ts): State<T>, PersistedState<T>, and all
    mutations (addWallet, setAccounts, ...) are generic over the account type. Mutations and the
    WalletStateMap/PersistedState types are exported from the package root.

  • Injectable store (packages/core/src/manager.ts): new WalletManager({ options: { store } })
    adopts an externally created TanStack Store instead of creating a private one. Each writer owns
    its wallet keys: external entries survive hydration and stale-wallet cleanup, live entries win
    over stale persisted ones, and construction order does not matter.

  • Typed manager: WalletManager<TAccount> carries the union of its adapters' account types.
    WalletManager.create({ wallets }) infers that union from the adapter configs (direct
    construction cannot; TypeScript fixes the class parameter on the first array element), and
    classic new WalletManager<PQAccount | ClassicAccount>({ ... }) declares it explicitly.

  • Register (wagmi-style): each framework package (React, Vue, Solid, Svelte) exposes a
    Register interface, the same pattern wagmi uses to link its config type to its hooks. An app
    declares its manager type once, next to where the manager is created, and every bare
    useWallet() infers the account union with no generics or casts at the call site:

    const manager = WalletManager.create({ wallets: [pqWallet(), classicWallet()] })
    // or declare it when constructing:
    // const manager = new WalletManager<PQAccount | ClassicAccount>({ wallets: [...] })
    
    declare module '@txnlab/use-wallet-react' {
      interface Register {
        manager: typeof manager
      }
    }
    
    // in a component, imports unchanged:
    import { useWallet } from '@txnlab/use-wallet-react'
    const { activeAccount } = useWallet()
    // activeAccount: PQAccount | ClassicAccount | null

    Apps that don't register keep the base WalletAccount default. The hooks take no type
    parameter; the declaration is the single source of truth. The Vue plugin also accepts an
    existing manager instance (app.use(WalletManagerPlugin, manager)) so typeof manager works
    there too.

Why

  • Wallets can hold a heterogeneous mix of account types (e.g. post-quantum accounts alongside
    classic ones). Consumers need the full union statically visible so each account narrows through
    a type guard instead of a cast.
  • External systems can share one store instance with the manager and contribute their own wallet
    entries without being overwritten.

@drichar

drichar commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator

Hey @PhearZero! Heads up: v5.0.0 stable is shipping shortly. Since this exploration predates the adapter-package split and the signData rework (#439), it would need a rebase/rethink against the current v5 API.

I know a lot has changed since this work was done. Leaving it open as a draft for post-5.0 discussion.

@PhearZero

Copy link
Copy Markdown
Contributor Author

Sounds good @drichar, I will take a look at the latest and get this back up and running 🕺. It should be largely non-breaking even though the surface touches a bit of everything.

@drichar
drichar changed the base branch from v5 to main August 11, 2026 18:51
@PhearZero PhearZero closed this Aug 13, 2026
@PhearZero PhearZero reopened this Aug 14, 2026
@PhearZero
PhearZero marked this pull request as ready for review August 14, 2026 18:30
@PhearZero

Copy link
Copy Markdown
Contributor Author

I thought I was going to create a new PR but decided to push back to this thread since the changes were not as drastic as I had thought. This PR simplifies it a bit more by not touching any adapters for now.

As adapters adopt new account shapes, they will be resolved "automagically" to the user once they opt-in to the inferred types or explicit generics during construction. It's the pattern I've run into for ages, same one we are using in our Provider abstraction. (Astro has some really good examples as well for the inferred types in different areas of the framework)

Amazing work on v5!! I am loving it!!

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