Skip to content

Commit 88dedbd

Browse files
pbennettdrichar
andauthored
feat(wallets): add Web3Auth provider with secure key handling (#413)
* feat(*): add Web3Auth wallet support introduce Web3Auth wallet integration with secure key handling. includes `SecureKeyContainer`, automatic memory zeroing, and enhanced cryptographic features. added relevant utility tests and optional peer dependencies. * feat(use-wallet): add tests for deriveAlgorandAccountFromEd25519 introduce comprehensive test coverage for `deriveAlgorandAccountFromEd25519`, including Web3Auth-style key handling, key validation, transaction signing, and integration with `SecureKeyContainer`. added utility functions to enhance test clarity and realism. * feat(*): enhance Web3Auth integration and update dependencies improve Web3Auth integration with support for `CommonPrivateKeyProvider`, updated chain configuration for Algorand, and conditional inclusion based on `VITE_WEB3AUTH_CLIENT_ID`. refine error handling and logging for missing dependencies. update packages and add optional peer dependencies for Web3Auth. adjust test suite to reflect changes. * feat(*): add support for Web3Auth custom authentication flows extend Web3Auth wallet to support custom JWT-based authentication, such as Firebase integration. implement `Single Factor Auth` (SFA) SDK with dynamic imports and conditional usage. update the `Connect` component to enable custom auth configuration in the UI. enhance test suite to cover new functionality. update dependencies to include `@web3auth/single-factor-auth`. * feat(Connect): add Firebase authentication support integrate Firebase authentication into the `Connect` component, enabling sign-in with Google and Firebase UID-based authentication for Web3Auth wallet connections. include dynamic handling of Firebase state changes, conditional authentication configuration in the UI, and enhanced error management. update dependencies with Firebase packages and related utilities. * feat(FirebaseAuth): add reusable Firebase authentication component introduce `FirebaseAuth` component for streamlined Firebase authentication functionality, supporting Google sign-in and email/password authentication flows. improve state management and error handling with user-friendly messages. update `Connect` component to integrate `FirebaseAuth`, removing redundant logic. include added styles for the new component and update dependencies with `firebaseui`. * style: format codebase with Prettier for CI compliance Run Prettier across the codebase to ensure all files meet formatting standards and CI checks pass. - Update .prettierignore to exclude CLAUDE.md and .claude/ directory - Format imports in firebase.ts for better readability - Adjust comment formatting in web3auth.test.ts - Add missing newline at end of secure-key.ts * chore(deps): add tweetnacl for ed25519 key derivation Add `tweetnacl` as a devDependency to resolve TypeScript compilation errors in secure-key.ts. The library is used for ed25519 keypair derivation in the Web3Auth wallet implementation, specifically for converting 32-byte ed25519 seeds into Algorand-compatible accounts. * chore: release v4.4.0-beta.0 * feat(Web3Auth): implement lazy authentication and automatic session refresh introduce lazy authentication mechanism that defers Web3Auth connection until `signTransactions()` is called. add automatic session refresh for Single Factor Auth (SFA) via `getAuthCredentials` callback. implement separate reconnection flows for SFA and modal authentication. enhance security by verifying address matches after re-authentication and disconnecting wallet if user changes. update session resume to restore cached address without initializing Web3Auth. extend test suite with lazy authentication scenarios covering session expiry, re-authentication flows, and modal cancellation. * chore: release v4.4.0-beta.1 * refactor(react-ts): separate Web3Auth modal and Firebase SFA flows Restructure Web3Auth authentication in the example to clearly distinguish between the two connection methods: 1. Standard Modal SDK - Uses Web3Auth's built-in UI for social logins 2. Firebase SFA - Uses Firebase JWT for single-factor authentication UI changes: - Replace checkbox toggle with separate "or connect with Firebase" section - Add section divider styling for visual separation - Fix button text wrapping with `white-space: nowrap` - Add `input[type='password']` to global input styles for consistency - Remove redundant custom styles for auth form inputs and buttons This separation makes the two authentication flows explicit and easier to understand for developers reviewing the example. * fix(Web3Auth): log actual error when package loading fails Include the caught error in the log output to help diagnose dynamic import failures (e.g., missing dependencies or bundler polyfill issues). * feat(examples): add Web3Auth and Firebase authentication Add Web3Auth wallet provider with Firebase Single Factor Authentication (SFA) support to all remaining example applications. ## Changes per example - nextjs: Add firebase.ts, FirebaseAuth.tsx, update Connect.tsx and providers.tsx - vue-ts: Add firebase.ts, FirebaseAuth.vue, update Connect.vue and main.ts - nuxt: Add firebase.ts, FirebaseAuth.vue, polyfills.client.ts for Web3Auth compatibility - solid-ts: Add firebase.ts, FirebaseAuth.tsx, update Connect.tsx and App.tsx - svelte-ts: Add firebase.ts, FirebaseAuth.svelte, update connect.svelte and +layout.svelte - vanilla-ts: Add firebase.ts, FirebaseAuthComponent.ts, update WalletComponent.ts and main.ts ## Additional fixes - Add password input styling to vue-ts and solid-ts global CSS - Add .env.example files with Web3Auth and Firebase configuration templates - Nuxt requires manual polyfills plugin due to vite-plugin-node-polyfills incompatibility * refactor(examples): simplify Web3Auth demos by removing custom auth complexity The Firebase authentication integrations add significant complexity to what should be simple, focused wallet integration demos. This simplifies all example applications to use only the Web3Auth modal for social login, providing a cleaner developer experience. Changes: - Remove Firebase SDK dependency from all 7 example apps - Remove Firebase auth components and utility files - Remove Firebase-related CSS styles - Simplify .env.example files to only include Web3Auth client ID - Update Connect components to remove Firebase SFA flow * docs: add Web3Auth wallet provider documentation Document the Web3Auth provider in the getting-started guides, including: - Installation instructions for required dependencies: @web3auth/modal, @web3auth/base, and @web3auth/base-provider - Configuration options for the modal-based authentication flow - Optional parameters: web3AuthNetwork, loginProvider, uiConfig - Custom Authentication section explaining Single Factor Auth (SFA) approach with note about additional @web3auth/single-factor-auth dependency - Links to Web3Auth dashboard and documentation * style(examples): fix Prettier formatting Apply Prettier formatting fixes to resolve CI failures. - Remove trailing blank lines from CSS files - Collapse Vue template attributes to single line where appropriate * chore(webpack): add Web3Auth packages to webpack fallback Add missing Web3Auth peer dependencies to the webpack fallback configuration to prevent build errors when these optional packages are not installed. - `@web3auth/base` - `@web3auth/base-provider` - `@web3auth/modal` - `@web3auth/single-factor-auth` --------- Co-authored-by: Doug Richar <drichar@gmail.com>
1 parent 3306f97 commit 88dedbd

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+3977
-230
lines changed

.prettierignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,7 @@ docs/**
1111

1212
# Generated Svelte files
1313
**/.svelte-kit
14+
15+
# Project files
16+
CLAUDE.md
17+
.claude/**

CLAUDE.md

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
# CLAUDE.md
2+
3+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4+
5+
**Important**: Keep this document updated when making significant changes to the codebase, such as adding new commands, changing architecture patterns, introducing new directories, or modifying the build/test setup.
6+
7+
## Project Overview
8+
9+
@txnlab/use-wallet is a framework-agnostic Algorand wallet integration library with reactive framework adapters for React, Vue, SolidJS, and Svelte. It enables dApps to connect to various Algorand wallets, sign transactions, and manage wallet sessions.
10+
11+
## Commands
12+
13+
### Development
14+
```bash
15+
pnpm install # Install dependencies
16+
pnpm dev # Watch mode for all packages
17+
pnpm build:packages # Build all packages
18+
pnpm build # Build packages and examples
19+
```
20+
21+
### Testing
22+
```bash
23+
pnpm test # Run all tests
24+
pnpm --filter @txnlab/use-wallet test # Run core package tests
25+
pnpm --filter @txnlab/use-wallet test:watch # Watch mode for core tests
26+
```
27+
28+
### Linting & Type Checking
29+
```bash
30+
pnpm lint # Lint all packages
31+
pnpm typecheck # Type check all packages
32+
pnpm prettier # Check formatting
33+
```
34+
35+
### Running Examples
36+
```bash
37+
pnpm example:react # React example
38+
pnpm example:vue # Vue example
39+
pnpm example:solid # SolidJS example
40+
pnpm example:svelte # Svelte example
41+
pnpm example:nextjs # Next.js example
42+
pnpm example:nuxt # Nuxt example
43+
pnpm example:ts # Vanilla TypeScript example
44+
```
45+
46+
## Architecture
47+
48+
### Package Structure (pnpm monorepo)
49+
50+
- **packages/use-wallet** (`@txnlab/use-wallet`) - Core framework-agnostic library
51+
- **packages/use-wallet-react** (`@txnlab/use-wallet-react`) - React adapter
52+
- **packages/use-wallet-vue** (`@txnlab/use-wallet-vue`) - Vue adapter
53+
- **packages/use-wallet-solid** (`@txnlab/use-wallet-solid`) - SolidJS adapter
54+
- **packages/use-wallet-svelte** (`@txnlab/use-wallet-svelte`) - Svelte adapter
55+
56+
Framework adapters depend on the core package via `workspace:*` and re-export all core exports.
57+
58+
### Core Package Architecture
59+
60+
**State Management**: Uses `@tanstack/store` for reactive state. State includes:
61+
- `wallets`: Map of wallet IDs to their connection state (accounts, active account)
62+
- `activeWallet`: Currently active wallet ID
63+
- `activeNetwork`: Current network (mainnet, testnet, etc.)
64+
- `algodClient`: Algorand SDK client instance
65+
66+
**Key Classes**:
67+
- `WalletManager` (`src/manager.ts`): Orchestrates wallet initialization, network configuration, and state persistence. Entry point for configuring the library.
68+
- `BaseWallet` (`src/wallets/base.ts`): Abstract base class all wallet implementations extend. Defines the wallet interface: `connect()`, `disconnect()`, `resumeSession()`, `signTransactions()`.
69+
70+
**Wallet Implementations** (`src/wallets/`): Each wallet provider (Pera, Defly, Exodus, WalletConnect, KMD, Web3Auth, etc.) has its own implementation extending `BaseWallet`. Wallet SDKs are peer dependencies, allowing users to install only what they need.
71+
72+
**Secure Key Utilities** (`src/secure-key.ts`): For wallets that handle raw private keys (Web3Auth, Mnemonic), provides `SecureKeyContainer` for safe key handling with automatic memory zeroing. Keys are never persisted and are cleared immediately after use.
73+
74+
**State Persistence**: Wallet state is persisted to localStorage under key `@txnlab/use-wallet:v4`.
75+
76+
### Framework Adapter Pattern
77+
78+
Each adapter provides:
79+
1. A context provider component (e.g., `WalletProvider` in React)
80+
2. Hooks/composables that subscribe to store state and provide reactive wallet data
81+
3. Re-exports of all core types and classes
82+
83+
Example: React's `useWallet()` hook uses `@tanstack/react-store` to subscribe to state changes and returns reactive wallet data plus signing methods.
84+
85+
## Key Types
86+
87+
- `WalletId`: Enum of supported wallet IDs (PERA, DEFLY, EXODUS, WALLETCONNECT, KMD, WEB3AUTH, MAGIC, etc.)
88+
- `WalletAccount`: `{ name: string, address: string }`
89+
- `SupportedWallet`: Either a `WalletId` string or a config object `{ id, options?, metadata? }`
90+
91+
## ESLint Configuration
92+
93+
Uses TypeScript ESLint with `@typescript-eslint/no-explicit-any` disabled. Unused variables prefixed with `_` are allowed.

docs/getting-started/installation.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,34 @@ bun add magic-sdk @magic-ext/algorand
367367
{% endtab %}
368368
{% endtabs %}
369369

370+
#### Web3Auth
371+
372+
{% tabs %}
373+
{% tab title="npm" %}
374+
```bash
375+
npm install @web3auth/modal @web3auth/base @web3auth/base-provider
376+
```
377+
{% endtab %}
378+
379+
{% tab title="yarn" %}
380+
```bash
381+
yarn add @web3auth/modal @web3auth/base @web3auth/base-provider
382+
```
383+
{% endtab %}
384+
385+
{% tab title="pnpm" %}
386+
```bash
387+
pnpm add @web3auth/modal @web3auth/base @web3auth/base-provider
388+
```
389+
{% endtab %}
390+
391+
{% tab title="bun" %}
392+
```bash
393+
bun add @web3auth/modal @web3auth/base @web3auth/base-provider
394+
```
395+
{% endtab %}
396+
{% endtabs %}
397+
370398
### Webpack Configuration
371399

372400
When using this library in a project that uses Webpack as its build tool, you may encounter "module not found" errors for optional dependencies of wallets you choose not to support. To resolve this, you can use the `webpackFallback` export.

docs/getting-started/supported-wallets.md

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,58 @@ import { WalletId } from '@txnlab/use-wallet'
186186
* [Magic Website](https://magic.link)
187187
* [Magic Algorand Documentation](https://magic.link/docs/blockchains/other-chains/other/algorand)
188188

189+
#### Web3Auth
190+
191+
Social login authentication provider supporting Google, Facebook, Twitter, Discord, and other OAuth providers. Web3Auth enables users to sign in with familiar social accounts and derive an Algorand wallet from their authentication credentials. [Installation instructions](installation.md#web3auth).
192+
193+
```typescript
194+
import { WalletId } from '@txnlab/use-wallet'
195+
196+
// Basic usage with modal (shows social login options)
197+
{
198+
id: WalletId.WEB3AUTH,
199+
options: {
200+
clientId: string // Required: from dashboard.web3auth.io
201+
}
202+
}
203+
204+
// With optional configuration
205+
{
206+
id: WalletId.WEB3AUTH,
207+
options: {
208+
clientId: string // Required: from dashboard.web3auth.io
209+
web3AuthNetwork?: string // Optional: 'sapphire_mainnet' (default) or 'sapphire_devnet'
210+
loginProvider?: string // Optional: skip modal and use specific provider (e.g., 'google', 'facebook')
211+
uiConfig?: { // Optional: customize modal appearance
212+
appName?: string
213+
logoLight?: string
214+
logoDark?: string
215+
mode?: 'light' | 'dark' | 'auto'
216+
}
217+
}
218+
}
219+
```
220+
221+
**Custom Authentication**
222+
223+
Web3Auth also supports custom authentication flows using Single Factor Auth (SFA), allowing integration with existing authentication systems such as Firebase, Auth0, or custom JWT providers. This approach requires an additional dependency:
224+
225+
```bash
226+
npm install @web3auth/single-factor-auth
227+
```
228+
229+
This advanced use case requires:
230+
231+
- Setting up a custom verifier in the [Web3Auth Dashboard](https://dashboard.web3auth.io)
232+
- Configuring the `verifier` option and `getAuthCredentials` callback
233+
- Managing authentication tokens and passing credentials to the `connect()` method
234+
235+
For implementation details, refer to the [Web3Auth Single Factor Auth documentation](https://web3auth.io/docs/sdk/core-kit/sfa-web).
236+
237+
* [Web3Auth Website](https://web3auth.io)
238+
* [Web3Auth Dashboard](https://dashboard.web3auth.io)
239+
* [Web3Auth Documentation](https://web3auth.io/docs)
240+
189241
#### Biatec
190242

191243
Open-source mobile wallet with community focus and WalletConnect support. [Installation instructions](installation.md#walletconnect).

examples/nextjs/.env.example

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Web3Auth Client ID - get yours from https://dashboard.web3auth.io
2+
NEXT_PUBLIC_WEB3AUTH_CLIENT_ID=your_web3auth_client_id_here

examples/nextjs/package.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,17 @@
1515
"@txnlab/use-wallet-react": "workspace:*",
1616
"@walletconnect/modal": "^2.7.0",
1717
"@walletconnect/sign-client": "^2.21.8",
18+
"@web3auth/base": "^9.6.0",
19+
"@web3auth/base-provider": "^9.6.0",
20+
"@web3auth/modal": "^9.6.0",
21+
"@web3auth/single-factor-auth": "^9.4.0",
1822
"algosdk": "3.4.0",
1923
"canonify": "2.1.1",
2024
"lute-connect": "^1.6.2",
2125
"next": "14.2.31",
2226
"react": "18.3.1",
23-
"react-dom": "18.3.1"
27+
"react-dom": "18.3.1",
28+
"tweetnacl": "^1.0.3"
2429
},
2530
"devDependencies": {
2631
"@types/node": "20.11.30",

examples/nextjs/src/app/Connect.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,12 @@ export function Connect() {
3636
return false
3737
}
3838

39-
const getConnectArgs = (wallet: Wallet) => {
39+
const handleConnect = async (wallet: Wallet) => {
4040
if (isMagicLink(wallet)) {
41-
return { email: magicEmail }
41+
await wallet.connect({ email: magicEmail })
42+
} else {
43+
await wallet.connect()
4244
}
43-
return undefined
4445
}
4546

4647
const setActiveAccount = (event: React.ChangeEvent<HTMLSelectElement>, wallet: Wallet) => {
@@ -158,7 +159,7 @@ export function Connect() {
158159
<div className={styles.walletButtons}>
159160
<button
160161
type="button"
161-
onClick={() => wallet.connect(getConnectArgs(wallet))}
162+
onClick={() => handleConnect(wallet)}
162163
disabled={isConnectDisabled(wallet)}
163164
>
164165
Connect

examples/nextjs/src/app/globals.css

Lines changed: 126 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,8 @@ button:disabled {
122122
}
123123

124124
input[type='text'],
125-
input[type='email'] {
125+
input[type='email'],
126+
input[type='password'] {
126127
border-radius: 8px;
127128
border: 1px solid #1a1a1a;
128129
padding: 0.6em 0.9em;
@@ -172,9 +173,133 @@ select {
172173
}
173174
input[type='text'],
174175
input[type='email'],
176+
input[type='password'],
175177
select {
176178
background-color: #f9f9f9;
177179
color: #1a1a1a;
178180
border-color: #cacaca;
179181
}
180182
}
183+
184+
/* Firebase Auth Styles */
185+
.firebase-auth-container {
186+
display: flex;
187+
flex-direction: column;
188+
gap: 1em;
189+
padding: 1em;
190+
max-width: 300px;
191+
margin: 0 auto;
192+
}
193+
194+
.auth-tabs {
195+
display: flex;
196+
gap: 0.5em;
197+
}
198+
199+
.auth-tabs button {
200+
flex: 1;
201+
}
202+
203+
.auth-tabs button.active {
204+
border-color: #0071ff;
205+
}
206+
207+
.auth-form {
208+
display: flex;
209+
flex-direction: column;
210+
gap: 0.75em;
211+
}
212+
213+
.auth-divider {
214+
display: flex;
215+
align-items: center;
216+
gap: 1em;
217+
color: rgba(255, 255, 255, 0.5);
218+
}
219+
220+
.auth-divider::before,
221+
.auth-divider::after {
222+
content: '';
223+
flex: 1;
224+
height: 1px;
225+
background: rgba(255, 255, 255, 0.2);
226+
}
227+
228+
.google-signin-btn {
229+
padding: 0.6em 1em;
230+
}
231+
232+
.auth-error {
233+
padding: 0.5em;
234+
color: #ff6b6b;
235+
background: rgba(255, 107, 107, 0.1);
236+
border-radius: 4px;
237+
font-size: 0.9em;
238+
}
239+
240+
.firebase-user {
241+
display: flex;
242+
flex-direction: column;
243+
align-items: center;
244+
gap: 0.6em;
245+
}
246+
247+
.firebase-user-buttons {
248+
display: flex;
249+
gap: 0.5em;
250+
}
251+
252+
.firebase-user-buttons button {
253+
white-space: nowrap;
254+
}
255+
256+
.firebase-user span {
257+
font-size: 0.9em;
258+
opacity: 0.8;
259+
}
260+
261+
.firebase-auth {
262+
margin-top: 0.5em;
263+
}
264+
265+
.firebase-sfa-section {
266+
width: 100%;
267+
max-width: 300px;
268+
}
269+
270+
.section-divider {
271+
display: flex;
272+
align-items: center;
273+
gap: 1em;
274+
margin: 1em 0;
275+
color: rgba(255, 255, 255, 0.5);
276+
font-size: 0.85em;
277+
}
278+
279+
.section-divider::before,
280+
.section-divider::after {
281+
content: '';
282+
flex: 1;
283+
height: 1px;
284+
background: rgba(255, 255, 255, 0.2);
285+
}
286+
287+
@media (prefers-color-scheme: light) {
288+
.auth-divider {
289+
color: rgba(0, 0, 0, 0.5);
290+
}
291+
292+
.auth-divider::before,
293+
.auth-divider::after {
294+
background: rgba(0, 0, 0, 0.2);
295+
}
296+
297+
.section-divider {
298+
color: rgba(0, 0, 0, 0.5);
299+
}
300+
301+
.section-divider::before,
302+
.section-divider::after {
303+
background: rgba(0, 0, 0, 0.2);
304+
}
305+
}

0 commit comments

Comments
 (0)