Skip to content

Commit

Permalink
refactor(liquid): implement dynamic import for LiquidAuthClient (#299)
Browse files Browse the repository at this point in the history
* feat(liquid): add logging

- Integrate `logger` into `LiquidWallet` for improved debugging and monitoring
- Update tests to mock `logger`

* refactor(liquid): update signTransactions method

- Add debug logging for transaction signing process
- Implement try-catch block for error handling
- Remove underscore prefix from method parameters

* refactor(liquid): add createWalletWithStore to tests

* refactor(liquid): implement dynamic import for LiquidAuthClient

- Remove static import of `LiquidAuthClient`
- Add `initializeClient` method for lazy-loading
- Update `connect` and `signTransactions` to use lazy-loaded client
- Inline `ICON` constant to remove static dependency
- Improve error handling and logging
- Update tests to reflect new behavior

* chore: add LiquidAuthClient dependency to example apps

- Install `@algorandfoundation/liquid-auth-use-wallet-client` in example apps
- Add fallback for this optional dependency in Next.js config

* fix(liquid): convert public methods to arrow functions

Ensures correct 'this' context when methods are used as callbacks, addressing issues like undefined 'this.store' in React components. Aligns with the fix implemented in PR #152.

See: #152

* refactor(examples): simplify LiquidWallet configuration

Update example apps to use enum syntax for `LiquidWallet` when no
custom options are needed.
  • Loading branch information
drichar authored Oct 21, 2024
1 parent abf22d8 commit 7a97d94
Show file tree
Hide file tree
Showing 14 changed files with 167 additions and 55 deletions.
1 change: 1 addition & 0 deletions examples/nextjs/next.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const nextConfig = {
config.resolve.fallback = {
...config.resolve.fallback,
'@agoralabs-sh/avm-web-provider': false,
'@algorandfoundation/liquid-auth-use-wallet-client': false,
'@blockshake/defly-connect': false,
'@magic-ext/algorand': false,
'@perawallet/connect': false,
Expand Down
1 change: 1 addition & 0 deletions examples/nuxt/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"typecheck": "npx nuxi typecheck"
},
"dependencies": {
"@algorandfoundation/liquid-auth-use-wallet-client": "1.1.0",
"@blockshake/defly-connect": "^1.1.6",
"@perawallet/connect": "^1.3.4",
"@txnlab/use-wallet": "workspace:*",
Expand Down
4 changes: 1 addition & 3 deletions examples/nuxt/plugins/walletManager.client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,7 @@ export default defineNuxtPlugin((nuxtApp) => {
},
WalletId.KMD,
WalletId.KIBISIS,
{
id: WalletId.LIQUID
},
WalletId.LIQUID,
{
id: WalletId.LUTE,
options: { siteName: 'Example Site' }
Expand Down
1 change: 1 addition & 0 deletions examples/react-ts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@algorandfoundation/liquid-auth-use-wallet-client": "1.1.0",
"@blockshake/defly-connect": "^1.1.6",
"@perawallet/connect": "^1.3.4",
"@txnlab/use-wallet-react": "workspace:*",
Expand Down
4 changes: 1 addition & 3 deletions examples/react-ts/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,7 @@ const walletManager = new WalletManager({
},
WalletId.KMD,
WalletId.KIBISIS,
{
id: WalletId.LIQUID
},
WalletId.LIQUID,
{
id: WalletId.LUTE,
options: { siteName: 'Example Site' }
Expand Down
1 change: 1 addition & 0 deletions examples/solid-ts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@algorandfoundation/liquid-auth-use-wallet-client": "1.1.0",
"@blockshake/defly-connect": "^1.1.6",
"@perawallet/connect": "^1.3.4",
"@txnlab/use-wallet-solid": "workspace:*",
Expand Down
4 changes: 1 addition & 3 deletions examples/solid-ts/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,7 @@ const walletManager = new WalletManager({
},
WalletId.KMD,
WalletId.KIBISIS,
{
id: WalletId.LIQUID
},
WalletId.LIQUID,
{
id: WalletId.LUTE,
options: { siteName: 'Example Site' }
Expand Down
1 change: 1 addition & 0 deletions examples/vanilla-ts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"vite": "5.4.8"
},
"dependencies": {
"@algorandfoundation/liquid-auth-use-wallet-client": "1.1.0",
"@blockshake/defly-connect": "^1.1.6",
"@perawallet/connect": "^1.3.4",
"@txnlab/use-wallet": "workspace:*",
Expand Down
4 changes: 1 addition & 3 deletions examples/vanilla-ts/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,7 @@ const walletManager = new WalletManager({
},
WalletId.KMD,
WalletId.KIBISIS,
{
id: WalletId.LIQUID
},
WalletId.LIQUID,
{
id: WalletId.LUTE,
options: { siteName: 'Example Site' }
Expand Down
1 change: 1 addition & 0 deletions examples/vue-ts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"typecheck": "vue-tsc --noEmit"
},
"dependencies": {
"@algorandfoundation/liquid-auth-use-wallet-client": "1.1.0",
"@blockshake/defly-connect": "^1.1.6",
"@perawallet/connect": "^1.3.4",
"@txnlab/use-wallet-vue": "workspace:*",
Expand Down
4 changes: 1 addition & 3 deletions examples/vue-ts/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,7 @@ app.use(WalletManagerPlugin, {
},
WalletId.KMD,
WalletId.KIBISIS,
{
id: WalletId.LIQUID
},
WalletId.LIQUID,
{
id: WalletId.LUTE,
options: { siteName: 'Example Site' }
Expand Down
81 changes: 67 additions & 14 deletions packages/use-wallet/src/__tests__/wallets/liquid.test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,24 @@
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
import { describe, it, expect, vi, beforeEach, afterEach, Mock } from 'vitest'
import { Store } from '@tanstack/store'
import { Transaction } from 'algosdk'
import { logger } from 'src/logger'
import { StorageAdapter } from 'src/storage'
import { LOCAL_STORAGE_KEY, State, defaultState } from 'src/store'
import { LOCAL_STORAGE_KEY, State, WalletState, defaultState } from 'src/store'
import { LiquidWallet } from 'src/wallets/liquid'
import { WalletId } from 'src/wallets/types'

// Mock logger
vi.mock('src/logger', () => ({
logger: {
createScopedLogger: vi.fn().mockReturnValue({
debug: vi.fn(),
info: vi.fn(),
warn: vi.fn(),
error: vi.fn()
})
}
}))

// Mock storage adapter
vi.mock('src/storage', () => ({
StorageAdapter: {
Expand Down Expand Up @@ -33,10 +46,30 @@ vi.mock('@algorandfoundation/liquid-auth-use-wallet-client', () => ({
ICON: 'mockIcon'
}))

function createWalletWithStore(store: Store<State>): LiquidWallet {
return new LiquidWallet({
id: WalletId.LIQUID,
options: {
RTC_config_username: 'username',
RTC_config_credential: 'credential'
},
metadata: {},
getAlgodClient: () => ({}) as any,
store,
subscribe: vi.fn()
})
}

describe('LiquidWallet', () => {
let wallet: LiquidWallet
let store: Store<State>
let mockInitialState: State | null = null
let mockLogger: {
debug: Mock
info: Mock
warn: Mock
error: Mock
}

const account1 = {
name: 'Liquid Account 1',
Expand All @@ -59,15 +92,16 @@ describe('LiquidWallet', () => {
}
})

mockLogger = {
debug: vi.fn(),
info: vi.fn(),
warn: vi.fn(),
error: vi.fn()
}
vi.mocked(logger.createScopedLogger).mockReturnValue(mockLogger)

store = new Store<State>(defaultState)
wallet = new LiquidWallet({
id: WalletId.LIQUID,
metadata: { name: 'Liquid' }, // Ensure metadata is correctly initialized
getAlgodClient: {} as any,
store,
subscribe: vi.fn(),
options: { RTC_config_username: 'username', RTC_config_credential: 'credential' }
})
wallet = createWalletWithStore(store)
})

afterEach(async () => {
Expand Down Expand Up @@ -130,19 +164,38 @@ describe('LiquidWallet', () => {
it('disconnect: should throw an error if no auth client is found', async () => {
wallet.authClient = null

await expect(wallet.disconnect()).rejects.toThrowError(
'No auth client found to disconnect from'
)
await expect(wallet.disconnect()).rejects.toThrowError('No auth client to disconnect')
})
})

describe('resumeSession', () => {
it('resumeSession: should call disconnect', async () => {
it('resumeSession: should call disconnect if wallet state exists', async () => {
const walletState: WalletState = {
accounts: [account1],
activeAccount: account1
}

store = new Store<State>({
...defaultState,
wallets: {
[WalletId.LIQUID]: walletState
}
})

wallet = createWalletWithStore(store)

const disconnectSpy = vi.spyOn(wallet, 'disconnect')
await wallet.resumeSession()

expect(disconnectSpy).toHaveBeenCalled()
})

it('resumeSession: should not call disconnect if no wallet state exists', async () => {
const disconnectSpy = vi.spyOn(wallet, 'disconnect')
await wallet.resumeSession()

expect(disconnectSpy).not.toHaveBeenCalled()
})
})

describe('signTransactions', () => {
Expand Down
96 changes: 72 additions & 24 deletions packages/use-wallet/src/wallets/liquid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,27 @@ import { WalletAccount, WalletConstructor, WalletId } from './types'
import { Store } from '@tanstack/store'
import { addWallet, State, WalletState } from 'src/store'
import { Transaction } from 'algosdk'
import { LiquidAuthClient, ICON } from '@algorandfoundation/liquid-auth-use-wallet-client'
import type { LiquidOptions } from '@algorandfoundation/liquid-auth-use-wallet-client'
import type {
LiquidAuthClient,
LiquidOptions
} from '@algorandfoundation/liquid-auth-use-wallet-client'

export { LiquidOptions }

const ICON = `data:image/svg+xml;base64,${btoa(`
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="249" height="249" viewBox="0 0 249 249" xml:space="preserve">
<g transform="matrix(2.52 0 0 2.52 124.74 124.74)">
<circle style="stroke: rgb(0,0,0); stroke-width: 19; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(0,0,0); fill-rule: nonzero; opacity: 1;" cx="0" cy="0" r="40"/>
</g>
<g transform="matrix(-1.16 -0.01 0.01 -0.97 125.63 187.7)">
<path style="stroke: rgb(0,0,0); stroke-width: 0; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(170,0,255); fill-rule: nonzero; opacity: 1;" transform=" translate(-57.95, -28.98)" d="m 0 57.952755 l 0 0 c 0 -32.006424 25.946333 -57.952755 57.952755 -57.952755 c 32.006428 0 57.952755 25.946333 57.952755 57.952755 l -28.97638 0 c 0 -16.003212 -12.97316 -28.976377 -28.976376 -28.976377 c -16.003212 0 -28.976377 12.9731655 -28.976377 28.976377 z" stroke-linecap="round"/>
</g>
<g transform="matrix(1.16 0 0 2.21 126.06 96.74)">
<path style="stroke: rgb(255,4,233); stroke-width: 0; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(170,0,255); fill-rule: nonzero; opacity: 1;" transform=" translate(-57.95, -28.98)" d="m 0 57.952755 l 0 0 c 0 -32.006424 25.946333 -57.952755 57.952755 -57.952755 c 32.006428 0 57.952755 25.946333 57.952755 57.952755 l -28.97638 0 c 0 -16.003212 -12.97316 -28.976377 -28.976376 -28.976377 c -16.003212 0 -28.976377 12.9731655 -28.976377 28.976377 z" stroke-linecap="round"/>
</g>
</svg>
`)}`

export class LiquidWallet extends BaseWallet {
protected store: Store<State>
public authClient: LiquidAuthClient | undefined | null
Expand All @@ -28,25 +44,34 @@ export class LiquidWallet extends BaseWallet {
RTC_config_username: 'username',
RTC_config_credential: 'credential'
}
this.authClient = new LiquidAuthClient(this.options)
}

static defaultMetadata = {
name: 'Liquid',
icon: ICON
}

public async connect(_args?: Record<string, any>): Promise<WalletAccount[]> {
if (!this.authClient) {
this.authClient = new LiquidAuthClient(this.options)
}
private async initializeClient(): Promise<LiquidAuthClient> {
this.logger.info('Initializing client...')
const { LiquidAuthClient } = await import('@algorandfoundation/liquid-auth-use-wallet-client')

const client = new LiquidAuthClient(this.options)
this.authClient = client
this.logger.info('Client initialized')
return client
}

public connect = async (_args?: Record<string, any>): Promise<WalletAccount[]> => {
this.logger.info('Connecting...')
const authClient = this.authClient || (await this.initializeClient())

await this.authClient.connect()
await authClient.connect()

const sessionData = await this.authClient.checkSession()
const sessionData = await authClient.checkSession()
const account = sessionData?.user?.wallet

if (!account) {
this.logger.error('No accounts found!')
throw new Error('No accounts found!')
}

Expand All @@ -67,34 +92,57 @@ export class LiquidWallet extends BaseWallet {
wallet: walletState
})

console.info(`[${this.metadata.name}] ✅ Connected.`, walletState)
this.authClient.hideModal()
this.logger.info('Connected successfully', walletState)
authClient.hideModal()
return Promise.resolve(walletAccounts)
}

public async disconnect(): Promise<void> {
public disconnect = async (): Promise<void> => {
this.logger.info('Disconnecting...')
if (!this.authClient) {
throw new Error('No auth client found to disconnect from')
this.logger.error('No auth client to disconnect')
throw new Error('No auth client to disconnect')
}

await this.authClient.disconnect()
this.onDisconnect()
console.info(`[${this.metadata.name}] ✅ Disconnected.`)
this.logger.info('Disconnected.')
this.authClient = null
}

public resumeSession(): Promise<void> {
return this.disconnect()
}
public resumeSession = async (): Promise<void> => {
try {
const state = this.store.state
const walletState = state.wallets[this.id]

public async signTransactions<T extends Transaction[] | Uint8Array[]>(
_txnGroup: T | T[],
_indexesToSign?: number[]
): Promise<(Uint8Array | null)[]> {
if (!this.activeAddress) {
throw new Error('No active account')
// No session to resume
if (!walletState) {
this.logger.info('No session to resume')
return
}
this.disconnect()
} catch (error) {
this.logger.error('Error resuming session', error)
this.onDisconnect()
throw error
}
}

return this.authClient!.signTransactions(_txnGroup, this.activeAddress, _indexesToSign)
public signTransactions = async <T extends Transaction[] | Uint8Array[]>(
txnGroup: T | T[],
indexesToSign?: number[]
): Promise<(Uint8Array | null)[]> => {
try {
if (!this.activeAddress) {
throw new Error('No active account')
}
this.logger.debug('Signing transactions...', { txnGroup, indexesToSign })

const authClient = this.authClient || (await this.initializeClient())
return authClient.signTransactions(txnGroup, this.activeAddress, indexesToSign)
} catch (error) {
this.logger.error('Error signing transactions', error)
throw error
}
}
}
Loading

0 comments on commit 7a97d94

Please sign in to comment.