Skip to content

Commit

Permalink
fix(useWallet): ensure consistent wallet interface across adapters (#346
Browse files Browse the repository at this point in the history
)

* fix(*): align active wallet handling across framework adapters

Refactor active wallet handling in React and Vue adapters to consistently
return `Wallet` interface while preserving `BaseWallet` for transaction signing.
Extract `transformToWallet` helper to reduce code duplication.

Fix `Wallet` interface to use `WalletId` type instead of string for `id`.

* refactor(solid): remove unused Wallet interface

Remove unused `Wallet` interface from Solid adapter since it follows a more
idiomatic approach of returning individual reactive properties rather than
grouped `Wallet` objects like React and Vue adapters.

* fix(solid): update tests to mock BaseWallet array

Update test mocks to use `BaseWallet` instances instead of `Wallet` objects,
matching the actual implementation which returns `BaseWallet` instances in
the `wallets` array rather than the framework-specific `Wallet` interface.
  • Loading branch information
drichar authored Feb 13, 2025
1 parent 2fb6012 commit 276df84
Show file tree
Hide file tree
Showing 4 changed files with 61 additions and 81 deletions.
36 changes: 22 additions & 14 deletions packages/use-wallet-react/src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { useStore } from '@tanstack/react-store'
import {
NetworkId,
WalletId,
WalletManager,
type AlgodConfig,
type BaseWallet,
type WalletAccount,
type WalletMetadata
} from '@txnlab/use-wallet'
Expand Down Expand Up @@ -119,7 +121,7 @@ export const useNetwork = () => {
}

export interface Wallet {
id: string
id: WalletId
metadata: WalletMetadata
accounts: WalletAccount[]
activeAccount: WalletAccount | null
Expand All @@ -146,10 +148,9 @@ export const useWallet = () => {
const walletStateMap = useStore(manager.store, (state) => state.wallets)
const activeWalletId = useStore(manager.store, (state) => state.activeWallet)

const wallets = React.useMemo(() => {
return [...manager.wallets.values()].map((wallet): Wallet => {
const transformToWallet = React.useCallback(
(wallet: BaseWallet): Wallet => {
const walletState = walletStateMap[wallet.id]

return {
id: wallet.id,
metadata: wallet.metadata,
Expand All @@ -162,35 +163,42 @@ export const useWallet = () => {
setActive: () => wallet.setActive(),
setActiveAccount: (addr) => wallet.setActiveAccount(addr)
}
})
}, [manager, walletStateMap, activeWalletId])
},
[walletStateMap, activeWalletId]
)

const wallets = React.useMemo(() => {
return [...manager.wallets.values()].map(transformToWallet)
}, [manager, transformToWallet])

const activeWallet = activeWalletId ? manager.getWallet(activeWalletId) || null : null
const activeWalletState = activeWalletId ? walletStateMap[activeWalletId] || null : null
const activeBaseWallet = activeWalletId ? manager.getWallet(activeWalletId) || null : null
const activeWallet = React.useMemo(() => {
return activeBaseWallet ? transformToWallet(activeBaseWallet) : null
}, [activeBaseWallet, transformToWallet])

const activeWalletAccounts = activeWalletState?.accounts ?? null
const activeWalletAccounts = activeWallet?.accounts ?? null
const activeWalletAddresses = activeWalletAccounts?.map((account) => account.address) ?? null
const activeAccount = activeWalletState?.activeAccount ?? null
const activeAccount = activeWallet?.activeAccount ?? null
const activeAddress = activeAccount?.address ?? null

const signTransactions = <T extends algosdk.Transaction[] | Uint8Array[]>(
txnGroup: T | T[],
indexesToSign?: number[]
): Promise<(Uint8Array | null)[]> => {
if (!activeWallet) {
if (!activeBaseWallet) {
throw new Error('No active wallet')
}
return activeWallet.signTransactions(txnGroup, indexesToSign)
return activeBaseWallet.signTransactions(txnGroup, indexesToSign)
}

const transactionSigner = (
txnGroup: algosdk.Transaction[],
indexesToSign: number[]
): Promise<Uint8Array[]> => {
if (!activeWallet) {
if (!activeBaseWallet) {
throw new Error('No active wallet')
}
return activeWallet.transactionSigner(txnGroup, indexesToSign)
return activeBaseWallet.transactionSigner(txnGroup, indexesToSign)
}

return {
Expand Down
35 changes: 5 additions & 30 deletions packages/use-wallet-solid/src/__tests__/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
} from '@txnlab/use-wallet'
import algosdk from 'algosdk'
import { For, Show, createEffect, createSignal } from 'solid-js'
import { Wallet, WalletProvider, useWallet, useWalletManager, useNetwork } from '../index'
import { WalletProvider, useWallet, useWalletManager, useNetwork } from '../index'

// Create mock store with initial state
const mockStore = new Store<State>({
Expand Down Expand Up @@ -606,7 +606,7 @@ describe('useWallet', () => {
let mockWalletManager: WalletManager
let mockDeflyWallet: DeflyWallet
let mockMagicAuth: MagicAuth
let mockWallets: Wallet[]
let mockWallets: BaseWallet[]
// eslint-disable-next-line @typescript-eslint/no-unused-vars
let mockSetAlgodClient: (client: algosdk.Algodv2) => void
// eslint-disable-next-line @typescript-eslint/no-unused-vars
Expand Down Expand Up @@ -650,32 +650,7 @@ describe('useWallet', () => {
])
mockWalletManager.store = mockStore

mockWallets = [
{
id: () => mockDeflyWallet.id,
metadata: () => mockDeflyWallet.metadata,
accounts: () => [],
activeAccount: () => null,
isConnected: () => false,
isActive: () => false,
connect: expect.any(Function),
disconnect: expect.any(Function),
setActive: expect.any(Function),
setActiveAccount: expect.any(Function)
},
{
id: () => mockMagicAuth.id,
metadata: () => mockMagicAuth.metadata,
accounts: () => [],
activeAccount: () => null,
isConnected: () => false,
isActive: () => false,
connect: expect.any(Function),
disconnect: expect.any(Function),
setActive: expect.any(Function),
setActiveAccount: expect.any(Function)
}
]
mockWallets = [mockDeflyWallet, mockMagicAuth]

mockAlgodClient = new algosdk.Algodv2('token', 'https://server', '')

Expand All @@ -691,7 +666,7 @@ describe('useWallet', () => {
</WalletProvider>
))
expect(screen.getByTestId('wallets')).toHaveTextContent(
mockWallets.map((wallet) => wallet.id()).join(', ')
mockWallets.map((wallet) => wallet.id).join(', ')
)
expect(screen.getAllByTestId('wallet')).toHaveLength(2)
expect(screen.getByTestId('active-wallet')).toHaveTextContent('null')
Expand Down Expand Up @@ -832,7 +807,7 @@ describe('useWallet', () => {
expect(screen.getByTestId('active-wallet-state')).toHaveTextContent('null')
expect(screen.getByTestId('wallet-store')).toHaveTextContent('{}')
expect(screen.getByTestId('wallets')).toHaveTextContent(
mockWallets.map((wallet) => wallet.id()).join(', ')
mockWallets.map((wallet) => wallet.id).join(', ')
)
expect(screen.getAllByTestId('wallet')).toHaveLength(2)
expect(screen.getByTestId('wallet-name-defly')).toHaveTextContent('Defly')
Expand Down
15 changes: 0 additions & 15 deletions packages/use-wallet-solid/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,8 @@ import { JSX, createContext, createMemo, onMount, useContext } from 'solid-js'
import type {
AlgodConfig,
NetworkId,
WalletAccount,
WalletId,
WalletManager,
WalletMetadata,
WalletState
} from '@txnlab/use-wallet'

Expand Down Expand Up @@ -120,19 +118,6 @@ export const useNetwork = () => {
}
}

export interface Wallet {
id: () => string
metadata: () => WalletMetadata
accounts: () => WalletAccount[]
activeAccount: () => WalletAccount | null
isConnected: () => boolean
isActive: () => boolean
connect: (args?: Record<string, any>) => Promise<WalletAccount[]>
disconnect: () => Promise<void>
setActive: () => void
setActiveAccount: (address: string) => void
}

export const useWallet = () => {
const manager = createMemo(() => useWalletManager())

Expand Down
56 changes: 34 additions & 22 deletions packages/use-wallet-vue/src/useWallet.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
import { useStore } from '@tanstack/vue-store'
import { WalletManager, type WalletAccount, type WalletMetadata } from '@txnlab/use-wallet'
import {
BaseWallet,
WalletManager,
type WalletAccount,
type WalletMetadata,
type WalletId
} from '@txnlab/use-wallet'
import algosdk from 'algosdk'
import { computed, inject, ref } from 'vue'

export interface Wallet {
id: string
id: WalletId
metadata: WalletMetadata
accounts: WalletAccount[]
activeAccount: WalletAccount | null
Expand Down Expand Up @@ -35,26 +41,32 @@ export function useWallet() {
const walletStateMap = useStore(manager.store, (state) => state.wallets)
const activeWalletId = useStore(manager.store, (state) => state.activeWallet)

const transformToWallet = (wallet: BaseWallet): Wallet => {
const walletState = walletStateMap.value[wallet.id]
return {
id: wallet.id,
metadata: wallet.metadata,
accounts: walletState?.accounts ?? [],
activeAccount: walletState?.activeAccount ?? null,
isConnected: !!walletState,
isActive: wallet.id === activeWalletId.value,
connect: (args) => wallet.connect(args),
disconnect: () => wallet.disconnect(),
setActive: () => wallet.setActive(),
setActiveAccount: (addr) => wallet.setActiveAccount(addr)
}
}

const wallets = computed(() => {
return [...manager.wallets.values()].map((wallet): Wallet => {
const walletState = walletStateMap.value[wallet.id]

return {
id: wallet.id,
metadata: wallet.metadata,
accounts: walletState?.accounts ?? [],
activeAccount: walletState?.activeAccount ?? null,
isConnected: !!walletState,
isActive: wallet.id === activeWalletId.value,
connect: (args) => wallet.connect(args),
disconnect: () => wallet.disconnect(),
setActive: () => wallet.setActive(),
setActiveAccount: (addr) => wallet.setActiveAccount(addr)
}
})
return [...manager.wallets.values()].map(transformToWallet)
})

const activeWallet = computed(() => {
const wallet = activeWalletId.value ? manager.getWallet(activeWalletId.value) || null : null
return wallet ? transformToWallet(wallet) : null
})

const activeBaseWallet = computed(() => {
return activeWalletId.value ? manager.getWallet(activeWalletId.value) || null : null
})

Expand Down Expand Up @@ -83,20 +95,20 @@ export function useWallet() {
txnGroup: T | T[],
indexesToSign?: number[]
): Promise<(Uint8Array | null)[]> => {
if (!activeWallet.value) {
if (!activeBaseWallet.value) {
throw new Error('No active wallet')
}
return activeWallet.value.signTransactions(txnGroup, indexesToSign)
return activeBaseWallet.value.signTransactions(txnGroup, indexesToSign)
}

const transactionSigner = (
txnGroup: algosdk.Transaction[],
indexesToSign: number[]
): Promise<Uint8Array[]> => {
if (!activeWallet.value) {
if (!activeBaseWallet.value) {
throw new Error('No active wallet')
}
return activeWallet.value.transactionSigner(txnGroup, indexesToSign)
return activeBaseWallet.value.transactionSigner(txnGroup, indexesToSign)
}

return {
Expand Down

0 comments on commit 276df84

Please sign in to comment.