@algorandfoundation/wallet-provider
v1.0.0
Published
Abstract interface for compatibility between wallets
Keywords
Readme
💳 Wallet Provider
A modular, extensible wallet provider abstraction for wallet providers.
Inspired by the architecture of OctoKit and TxnLab's use-wallet, this package provides a base for building wallet integrations that can be dynamically extended with additional functionality.
🏗️ Provider
The Provider is the base class that represents a wallet's identity and core configuration. It manages:
- Configuration: Shared options for the provider and its extensions.
- Composition: Orchestrating the application of extensions.
🧩 Extensions
Extensions are modular functions that augment the Provider with specific capabilities.
- Responsibility: Extensions handle domain-specific logic such as account management, transaction signing, or specific API integrations (e.g., Liquid, OIDC).
- Flexibility: They can be independent packages, provided by third parties, or baseline defaults (like KeyStore + BIP39).
- Prefer Composition: Use
withExtensionsto create specialized Provider classes with a fixed set of extensions.
🚀 Usage
The following walk-through assembles a provider from several extensions at your application's composition root and wires it to your stores. It sticks to the steps; the reasoning behind each one is in the TUTORIAL, and how data then flows between the stores is in ARCHITECTURE.
1. Pick your extensions
An extension is a plain, synchronous function (provider, options) => api exported as
With<DomainPlural>. Install the packages for the domains you need, for example:
@algorandfoundation/keystore[published canary]@algorandfoundation/accounts[not published, future meta package]@algorandfoundation/accounts-keystore-extension[published canary, reference example, not for production]@algorandfoundation/provider-migrations[published canary]
(see For extension authors).
The examples below use WithKeyStore, WithAccounts, and WithAccountsKeystore.
[!NOTE]
WithAccountsKeystoreappears in this walkthrough because it is the smallest published bridge and shows the pattern end to end. It is a reference implementation only: it has no chain address scheme and keys each account by the raw (base64) public key. For a real Algorand wallet substitute the chain-specific bridge,WithAlgorandAccounts(@algorandfoundation/algorand-accounts-extension[not published]), which owns Algorand and Falcon-1024 address derivation.
2. Create one store per domain
You own the stores. Create one TanStack Store atom per domain and keep them at your composition root:
import { Store } from "@tanstack/store";
import type { KeyStoreState } from "@algorandfoundation/keystore";
import type { AccountStoreState } from "@algorandfoundation/accounts";
const keysStore = new Store<KeyStoreState>({ keys: [], status: "idle" });
const accountsStore = new Store<AccountStoreState>({
wallets: {},
activeWallet: null,
});Because you create the stores, you can also persist them, hydrate them on a server, inspect them in devtools, or swap them wholesale in tests. The provider operates on them; it does not own them (why).
3. Compose and instantiate
Provider.withExtensions([...]) composes a class on the fly. Its constructor is
(config: ProviderOptions, options?: ExtensionOptions): each extension's configuration,
including its store, goes in under that domain's namespace, options.<plural>:
import { Provider, type ProviderOptions } from "@algorandfoundation/wallet-provider";
import { WithKeyStore } from "@algorandfoundation/keystore";
import { WithAccounts } from "@algorandfoundation/accounts";
import { WithAccountsKeystore } from "@algorandfoundation/accounts-keystore-extension";
const MyWallet = Provider.withExtensions([WithKeyStore, WithAccounts, WithAccountsKeystore]);
const config: ProviderOptions = {
id: "my-wallet",
name: "My Wallet",
icon: "https://example.com/icon.png",
};
const wallet = new MyWallet(config, {
keystore: { store: keysStore },
accounts: { store: accountsStore },
});When you want a named, importable class, extend the composed one:
export class MyWalletProvider extends Provider.withExtensions([
WithKeyStore,
WithAccounts,
WithAccountsKeystore,
]) {}Either way, TypeScript infers the combined surface: everything each extension contributes
shows up fully typed on the instance, and options is the single ExtensionOptions
registry, so an unregistered namespace or a misspelled field is a compile error. (The plain
class MyWallet extends Provider { … } form deliberately keeps options loose so a
concrete wallet can own its option shape; see DISCOVERY.)
4. Mind the ordering
Extensions apply in order, and each one receives the provider as it exists so far. An
extension that depends on another's API (a bridge that reads from the keystore, say) must
come after it (bridges are named With<Domain><Dependency>):
// Correct: the bridge can see everything WithKeyStore and WithAccounts contributed.
// (Swap WithAccountsKeystore for WithAlgorandAccounts in a real Algorand wallet.)
Provider.withExtensions([WithKeyStore, WithAccounts, WithAccountsKeystore]);
// Wrong: the bridge applies first and finds no keys API on the provider.
Provider.withExtensions([WithAccountsKeystore, WithKeyStore, WithAccounts]);Extensions that only collaborate through shared stores have no ordering constraint.
5. Use the composed provider
Invoke commands on the provider's API namespace (provider.<singular>.store); read state
through the reactive getters (provider.<plural>) or subscribe to the stores you created:
// Commands go through the provider.
await wallet.account.store.addAccount({ ... });
// Reads can go through convenience getters...
console.log(wallet.accounts);
// ...or reactively through the store you created.
accountsStore.subscribe(() => {
console.log("accounts changed:", accountsStore.state.wallets);
});In React, bind a store slice with a selector so components re-render only when their slice changes: components read from stores and use the provider only to invoke commands (example).
📚 Where the docs live
This repository is the single home for the concepts and how-tos of the provider / extension model:
- README.md: this composition-root walk-through.
- TUTORIAL.md: the building blocks, the provider ↔ state contract, and the app-developer and extension-author perspectives.
- ARCHITECTURE.md: how data flows through a provider: standalone vs composed, the flow rules, bridges, and the whole wallet as one document.
- DISCOVERY.md: the options registry, namespace resolution, and the rules that keep extensions composable.
- Architectural Decision Records: why the design is the way it is.
- API Reference: the generated markdown API reference, browsable on
GitHub (
pnpm docs).
The wallet-provider-extensions
repository documents only its per-package flows (keystore, migrations, logs, …).
Consult the CONTRIBUTING guide for information on development, testing, and pull requests.
🤝 Acknowledgments
We would like to acknowledge the following individuals and entities for their contributions and inspiration to this project and the broader Algorand ecosystem:
- Architectural Vision: Algorand Foundation and Bruno Martins (@bmartins) for his role as an Architect.
- use-wallet: TxnLab and Doug Richar (@drichar), along with Gabriel Kuettel (@gabrielkuettel) (currently at Algorand Foundation), for their role in building the
use-wallethook. - Ecosystem Support: The Engineering Teams at Algorand Foundation ranging from AlgoKit, Engineering, and Devrel for their role in providing ecosystem libraries and support.
- Wallets:
- Pera and Will Beaumount (@mjbeau) for their role in the ecosystem as a wallet and the large refactor to React Native.
- Akita for their role in ARC58 adoption. With special thanks to Algorand Foundation engineering to Kyle(@kylebee) and Joe Polny(@joe-p) for their contributions to the ARC58 plugin standards.
- Lute and Andrew Funk (@acfunk) for their contributions to web wallets, readily adopting the latest features.
- Kibis-is and Kieran Roneill (@kieranroneill) for their work as an extension-based wallet and contributions to ARC standards such as ARC27.
- Defly and Kevin Wellenzohn (@k13n) for pioneering wallet features and deep engagement with the Algorand ecosystem and ARC standards.
