Skip to content

Commit

Permalink
feat: improved error handling on ecash hooks (#104)
Browse files Browse the repository at this point in the history
  • Loading branch information
alexlwn123 authored Dec 9, 2024
1 parent e98a1b8 commit 57036b0
Show file tree
Hide file tree
Showing 10 changed files with 147 additions and 84 deletions.
5 changes: 5 additions & 0 deletions .changeset/orange-rice-rest.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@fedimint/react': patch
---

Fixed behavior of useOpenWallet hook for concurrent usages.
22 changes: 14 additions & 8 deletions packages/core-web/src/services/MintService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,25 @@ import type {
Duration,
JSONObject,
JSONValue,
MintSpendNotesResponse,
MSats,
ReissueExternalNotesState,
SpendNotesState,
} from '../types'

export class MintService {
constructor(private client: WorkerClient) {}

/** https://web.fedimint.org/core/FedimintWallet/MintService/redeemEcash */
async redeemEcash(notes: string) {
await this.client.rpcSingle('mint', 'reissue_external_notes', {
oob_notes: notes, // "out of band notes"
extra_meta: null,
})
return await this.client.rpcSingle<string>(
'mint',
'reissue_external_notes',
{
oob_notes: notes, // "out of band notes"
extra_meta: null,
},
)
}

async reissueExternalNotes(oobNotes: string, extraMeta: JSONObject = {}) {
Expand All @@ -31,7 +37,7 @@ export class MintService {

subscribeReissueExternalNotes(
operationId: string,
onSuccess: (state: JSONValue) => void = () => {},
onSuccess: (state: ReissueExternalNotesState) => void = () => {},
onError: (error: string) => void = () => {},
) {
const unsubscribe = this.client.rpcStream<ReissueExternalNotesState>(
Expand Down Expand Up @@ -60,7 +66,7 @@ export class MintService {
? { nanos: 0, secs: tryCancelAfter }
: tryCancelAfter

const res = await this.client.rpcSingle<Array<string>>(
const res = await this.client.rpcSingle<MintSpendNotesResponse>(
'mint',
'spend_notes',
{
Expand Down Expand Up @@ -94,10 +100,10 @@ export class MintService {

subscribeSpendNotes(
operationId: string,
onSuccess: (state: JSONValue) => void = () => {},
onSuccess: (state: SpendNotesState) => void = () => {},
onError: (error: string) => void = () => {},
) {
return this.client.rpcStream(
return this.client.rpcStream<SpendNotesState>(
'mint',
'subscribe_spend_notes',
{ operation_id: operationId },
Expand Down
19 changes: 12 additions & 7 deletions packages/core-web/src/types/wallet.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { type MintService } from '../services'
import { MSats, Duration, JSONValue } from './utils'

const MODULE_KINDS = ['', 'ln', 'mint'] as const
Expand Down Expand Up @@ -81,13 +80,18 @@ type StreamResult<T extends JSONValue> =

type CancelFunction = () => void

type ReissueExternalNotesState =
| 'Created'
| 'Issuing'
| 'Done'
| { Failed: { error: string } }
type ReissueExternalNotesState = 'Created' | 'Issuing' | 'Done'
// | { Failed: { error: string } }

type MintSpendNotesResponse = Array<string>

type MintSpendNotesResponse = ReturnType<MintService['spendNotes']>
type SpendNotesState =
| 'Created'
| 'UserCanceledProcessing'
| 'UserCanceledSuccess'
| 'UserCanceledFailure'
| 'Success'
| 'Refunded'

export {
LightningGateway,
Expand All @@ -106,4 +110,5 @@ export {
CancelFunction,
ReissueExternalNotesState,
MintSpendNotesResponse,
SpendNotesState,
}
9 changes: 4 additions & 5 deletions packages/core-web/src/worker/WorkerClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -209,11 +209,10 @@ export class WorkerClient {
})
}

rpcSingle<Response extends JSONValue = JSONValue>(
module: ModuleKind,
method: string,
body: JSONValue,
) {
rpcSingle<
Response extends JSONValue = JSONValue,
Error extends string = string,
>(module: ModuleKind, method: string, body: JSONValue) {
logger.debug('WorkerClient - rpcSingle', module, method, body)
return new Promise<Response>((resolve, reject) => {
this.rpcStream<Response>(module, method, body, resolve, reject)
Expand Down
39 changes: 35 additions & 4 deletions packages/react/lib/contexts/FedimintWalletContext.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { FedimintWallet } from '@fedimint/core-web'
import { createContext, createElement } from 'react'
import {
createContext,
createElement,
useEffect,
useMemo,
useState,
} from 'react'

let wallet: FedimintWallet

Expand All @@ -8,6 +14,8 @@ type FedimintWalletConfig = {
debug?: boolean
}

export type WalletStatus = 'open' | 'closed' | 'opening'

export const setupFedimintWallet = (config: FedimintWalletConfig) => {
wallet = new FedimintWallet(!!config.lazy)
if (config.debug) {
Expand All @@ -16,22 +24,45 @@ export const setupFedimintWallet = (config: FedimintWalletConfig) => {
}

export const FedimintWalletContext = createContext<
{ wallet: FedimintWallet } | undefined
| {
wallet: FedimintWallet
walletStatus: WalletStatus
setWalletStatus: (status: WalletStatus) => void
}
| undefined
>(undefined)

export type FedimintWalletProviderProps = {}

export const FedimintWalletProvider = (
parameters: React.PropsWithChildren<FedimintWalletProviderProps>,
) => {
const [walletStatus, setWalletStatus] = useState<WalletStatus>('closed')
const { children } = parameters

if (!wallet)
throw new Error(
'You must call setupFedimintWallet() first. See the getting started guide.',
)

const props = { value: { wallet } }
const contextValue = useMemo(
() => ({
wallet,
walletStatus,
setWalletStatus,
}),
[walletStatus],
)

useEffect(() => {
wallet.waitForOpen().then(() => {
setWalletStatus('open')
})
}, [wallet])

return createElement(FedimintWalletContext.Provider, props, children)
return createElement(
FedimintWalletContext.Provider,
{ value: contextValue },
children,
)
}
32 changes: 14 additions & 18 deletions packages/react/lib/hooks/useOpenWallet.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import { useCallback, useEffect, useState } from 'react'
import { useFedimintWallet } from '.'

type WalletStatus = 'open' | 'closed' | 'opening'
import { useCallback, useContext } from 'react'
import { FedimintWalletContext } from '../contexts/FedimintWalletContext'

export const useOpenWallet = () => {
const wallet = useFedimintWallet()
const [walletStatus, setWalletStatus] = useState<WalletStatus>()
const value = useContext(FedimintWalletContext)

if (!value) {
throw new Error(
'useOpenWallet must be used within a FedimintWalletProvider',
)
}

const { wallet, walletStatus, setWalletStatus } = value

const openWallet = useCallback(() => {
if (walletStatus === 'open') return
Expand All @@ -22,21 +27,12 @@ export const useOpenWallet = () => {

setWalletStatus('opening')

const res = await wallet.joinFederation(invite)
setWalletStatus(res ? 'open' : 'closed')
await wallet.joinFederation(invite).then((res) => {
setWalletStatus(res ? 'open' : 'closed')
})
},
[wallet],
)

useEffect(() => {
wallet.waitForOpen().then(() => {
setWalletStatus('open')
})

return () => {
setWalletStatus('closed')
}
}, [wallet])

return { walletStatus, openWallet, joinFederation }
}
27 changes: 16 additions & 11 deletions packages/react/lib/hooks/useReceiveEcash.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,25 @@
import { useCallback, useEffect, useState } from 'react'
import { useFedimintWallet, useOpenWallet } from '.'
import { ReissueExternalNotesState } from '@fedimint/core-web'

export const useReceiveEcash = () => {
const wallet = useFedimintWallet()
const { walletStatus } = useOpenWallet()

const [operationId, setOperationId] = useState<string>()
const [notes, setNotes] = useState<string>()
const [state, setState] = useState<any>()
const [state, setState] = useState<ReissueExternalNotesState | 'Error'>()
const [error, setError] = useState<string>()

useEffect(() => {
if (!operationId) return

const unsubscribe = wallet.mint.subscribeReissueExternalNotes(
operationId,
(_state) => (_state ? setState(_state) : setState(undefined)),
(_state) => {
setState(_state)
},
(error) => {
console.error('ECASH SPEND STATE ERROR', error)
setError(error)
},
)

Expand All @@ -28,18 +31,20 @@ export const useReceiveEcash = () => {
const redeemEcash = useCallback(
async (notes: string) => {
if (walletStatus !== 'open') throw new Error('Wallet is not open')
const response = await wallet.mint.redeemEcash(notes)
console.error('REEDEEEM', response)
// setOperationId(response.operation_id)
// setNotes(response.notes)
return response
try {
const response = await wallet.mint.redeemEcash(notes)
setOperationId(response)
} catch (e) {
setState('Error')
setError(e as string)
}
},
[wallet],
[wallet, walletStatus],
)

return {
redeemEcash,
notes,
state,
error,
}
}
15 changes: 11 additions & 4 deletions packages/react/lib/hooks/useSpendEcash.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,28 @@
import { useCallback, useEffect, useState } from 'react'
import { useFedimintWallet, useOpenWallet } from '.'
import { type SpendNotesState } from '@fedimint/core-web'

export const useSpendEcash = () => {
const wallet = useFedimintWallet()
const { walletStatus } = useOpenWallet()

const [operationId, setOperationId] = useState<string>()
const [notes, setNotes] = useState<string>()
const [state, setState] = useState<any>()

const [state, setState] = useState<SpendNotesState | 'Error'>()
const [error, setError] = useState<string>()

useEffect(() => {
if (!operationId) return

const unsubscribe = wallet.mint.subscribeSpendNotes(
operationId,
(_state) => (_state ? setState(_state) : setState(undefined)),
(_state) => {
setState(_state)
},
(error) => {
console.error('ECASH SPEND STATE ERROR', error)
setState('Error')
setError(error)
},
)

Expand All @@ -36,12 +42,13 @@ export const useSpendEcash = () => {
setNotes(response.notes)
return response.notes
},
[wallet],
[wallet, walletStatus],
)

return {
spendEcash,
notes,
state,
error,
}
}
2 changes: 2 additions & 0 deletions packages/react/src/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,6 @@
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
user-select: all;
max-width: 200px;
}
Loading

0 comments on commit 57036b0

Please sign in to comment.