chore: add jm config store

This commit is contained in:
theborakompanioni 2025-09-18 21:29:50 +02:00 committed by Thebora Kompanioni
parent f5318171bb
commit a93c313ff7
5 changed files with 177 additions and 39 deletions

View file

@ -27,10 +27,11 @@ import { useApiClient } from '@/hooks/useApiClient'
import { token } from '@/lib/jm-api/generated/client'
import { sessionOptions } from '@/lib/jm-api/generated/client/@tanstack/react-query.gen'
import { queryClient } from '@/lib/queryClient'
import { setIntervalDebounced } from '@/lib/utils'
import { setIntervalDebounced, type WalletFileName } from '@/lib/utils'
import { authStore } from '@/store/authStore'
import { jmSessionStore } from '@/store/jmSessionStore'
import { isDebugFeatureEnabled } from './constants/debugFeatures'
import { useFeeConfigValidation } from './hooks/useFeeConfigValidation'
import { jamSettingsStore } from './store/jamSettingsStore'
const DevSetupPage = lazy(() => import('@/components/dev/DevSetupPage'))
@ -53,6 +54,7 @@ function App() {
<QueryClientProvider client={queryClient}>
<RefreshApiToken />
<RefreshJmSession />
{walletFileName && <LoadFeeConfigData walletFileName={walletFileName} />}
<Router>
<Routes>
<Route
@ -305,4 +307,19 @@ function RefreshJmSession() {
return <></>
}
function LoadFeeConfigData({ walletFileName }: { walletFileName: WalletFileName }) {
const { refetchAll } = useFeeConfigValidation({ walletFileName })
useEffect(() => {
refetchAll().catch(() => {
const isDevMode = jamSettingsStore.getState().state.developerMode
if (isDevMode) {
toast.error(`[DEV] Error while loading fee config data.`)
}
})
}, [refetchAll])
return <></>
}
export default App

View file

@ -9,13 +9,18 @@ type RelOfferType = 'sw0reloffer'
type AbsOfferType = 'sw0absoffer'
export type OfferType = RelOfferType | AbsOfferType | string
type SectionKey = string
export type SectionKey = string
interface ConfigKey {
export interface ConfigKey {
section: SectionKey
field: string
}
export interface ConfigValue {
key: ConfigKey
value: string | null
}
export const FEE_CONFIG_KEYS: Record<string, ConfigKey> = {
tx_fees: { section: 'POLICY', field: 'tx_fees' },
tx_fees_factor: { section: 'POLICY', field: 'tx_fees_factor' },

View file

@ -1,4 +1,4 @@
import { useMemo } from 'react'
import { useCallback, useMemo } from 'react'
import { useMutation } from '@tanstack/react-query'
import { FEE_CONFIG_KEYS } from '@/constants/jm'
import { useApiClient } from '@/hooks/useApiClient'
@ -28,28 +28,20 @@ export const useFeeConfigValidation = ({ walletFileName }: UseFeeConfigValidatio
const createConfigQuery = (configKey: keyof typeof FEE_CONFIG_KEYS) => ({
...configgetMutation({
client,
path: { walletname: walletFileName || '' },
path: { walletname: walletFileName },
body: FEE_CONFIG_KEYS[configKey],
}),
})
const maxCjFeeAbsQuery = useMutation(createConfigQuery('max_cj_fee_abs'))
const maxCjFeeRelQuery = useMutation(createConfigQuery('max_cj_fee_rel'))
const txFeesQuery = useMutation(createConfigQuery('tx_fees'))
const txFeesFactorQuery = useMutation(createConfigQuery('tx_fees_factor'))
const maxSweepFeeChangeQuery = useMutation(createConfigQuery('max_sweep_fee_change'))
const { mutateAsync: fetchMaxCjFeeAbs, ...maxCjFeeAbsQuery } = useMutation(createConfigQuery('max_cj_fee_abs'))
const { mutateAsync: fetchMaxCjFeeRel, ...maxCjFeeRelQuery } = useMutation(createConfigQuery('max_cj_fee_rel'))
const { mutateAsync: fetchTxFees, ...txFeesQuery } = useMutation(createConfigQuery('tx_fees'))
const { mutateAsync: fetchTxFeesFactor, ...txFeesFactorQuery } = useMutation(createConfigQuery('tx_fees_factor'))
const { mutateAsync: fetchMaxSweepFeeChange, ...maxSweepFeeChangeQuery } = useMutation(
createConfigQuery('max_sweep_fee_change'),
)
const feeConfigValues = useMemo<FeeConfigValues | undefined>(() => {
if (
!maxCjFeeAbsQuery.data &&
!maxCjFeeRelQuery.data &&
!txFeesQuery.data &&
!txFeesFactorQuery.data &&
!maxSweepFeeChangeQuery.data
) {
return undefined
}
return {
max_cj_fee_abs: maxCjFeeAbsQuery.data?.configvalue,
max_cj_fee_rel: maxCjFeeRelQuery.data?.configvalue,
@ -65,17 +57,6 @@ export const useFeeConfigValidation = ({ walletFileName }: UseFeeConfigValidatio
maxSweepFeeChangeQuery.data,
])
const maxFeesConfigMissing = useMemo(() => {
// Debug: Force the error for testing
if (forceFeeConfigMissing) {
return true
}
return (
feeConfigValues && (feeConfigValues.max_cj_fee_abs === undefined || feeConfigValues.max_cj_fee_rel === undefined)
)
}, [feeConfigValues, forceFeeConfigMissing])
const isLoading = useMemo(() => {
return (
maxCjFeeAbsQuery.isPending ||
@ -108,18 +89,29 @@ export const useFeeConfigValidation = ({ walletFileName }: UseFeeConfigValidatio
maxSweepFeeChangeQuery.error,
])
const refetchAll = async () => {
const refetchAll = useCallback(async () => {
const args = {
path: { walletname: walletFileName },
}
await Promise.all([
maxCjFeeAbsQuery.mutateAsync(args),
maxCjFeeRelQuery.mutateAsync(args),
txFeesQuery.mutateAsync(args),
txFeesFactorQuery.mutateAsync(args),
maxSweepFeeChangeQuery.mutateAsync(args),
return await Promise.all([
fetchMaxCjFeeAbs(args),
fetchMaxCjFeeRel(args),
fetchTxFees(args),
fetchTxFeesFactor(args),
fetchMaxSweepFeeChange(args),
])
}
}, [walletFileName, fetchMaxCjFeeAbs, fetchMaxCjFeeRel, fetchTxFees, fetchTxFeesFactor, fetchMaxSweepFeeChange])
const maxFeesConfigMissing = useMemo(() => {
// Debug: Force the error for testing
if (forceFeeConfigMissing) {
return true
}
return (
feeConfigValues && (feeConfigValues.max_cj_fee_abs === undefined || feeConfigValues.max_cj_fee_rel === undefined)
)
}, [feeConfigValues, forceFeeConfigMissing])
return {
feeConfigValues,

65
src/hooks/useJmConfig.ts Normal file
View file

@ -0,0 +1,65 @@
import { useCallback } from 'react'
import { useMutation } from '@tanstack/react-query'
import { useStore } from 'zustand'
import type { ConfigKey, ConfigValue } from '@/constants/jm'
import { useApiClient } from '@/hooks/useApiClient'
import { configgetMutation } from '@/lib/jm-api/generated/client/@tanstack/react-query.gen'
import type { WalletFileName } from '@/lib/utils'
import { jmConfigStore } from '@/store/jmConfigStore'
interface UseJmConfigProps {
walletFileName: WalletFileName
}
export const useJmConfig = ({ walletFileName }: UseJmConfigProps) => {
const client = useApiClient()
const jmConfigStoreState = useStore(jmConfigStore)
const { mutateAsync: fetchConfigAsync } = useMutation({
...configgetMutation({
client,
path: { walletname: walletFileName },
}),
retry: 3,
})
const get = useCallback(
(key: ConfigKey): ConfigValue | null => {
return jmConfigStoreState.get(key)
},
[jmConfigStoreState],
)
const refetch = useCallback(
async (key: ConfigKey): Promise<ConfigValue> => {
const { configvalue } = await fetchConfigAsync({
path: { walletname: walletFileName },
body: {
section: key.section,
field: key.field,
},
})
const result: ConfigValue = {
key,
value: configvalue ?? null,
}
jmConfigStoreState.set(result)
return result
},
[walletFileName, jmConfigStoreState, fetchConfigAsync],
)
const fetchIfMissing = useCallback(
async (key: ConfigKey): Promise<ConfigValue> => {
const value = get(key)
return value !== null ? value : refetch(key)
},
[get, refetch],
)
return {
get,
refetch,
fetchIfMissing,
}
}

View file

@ -0,0 +1,59 @@
import { createStore } from 'zustand'
import { persist, createJSONStorage } from 'zustand/middleware'
import type { ConfigKey, ConfigValue } from '@/constants/jm'
export type JmConfigs = {
[key: ConfigKey['section']]: Record<ConfigKey['field'], ConfigValue['value']>
}
interface JmConfigStoreState {
state: JmConfigs
get: (val: ConfigKey) => ConfigValue | null
getAll: () => ConfigValue[]
set: (val: ConfigValue) => void
clear: () => void
}
const initial: JmConfigs = {}
export const jmConfigStore = createStore<JmConfigStoreState>()(
persist(
(set, get) => ({
state: initial,
get: (key) => {
const fields = get().state[key.section]
if (fields === undefined) return null
const value = fields[key.field]
if (value === undefined) return null
return {
key,
value: value || null,
}
},
getAll: () => {
return Object.entries(get().state).flatMap(([section, entries]) => {
return Object.entries(entries).map(([field, value]) => ({
key: {
section,
field,
},
value,
}))
})
},
set: (val) =>
set((state) => {
const copy = { state: { ...(state.state || {}) } }
copy.state[val.key.section][val.key.field] = val.value
return copy
}),
clear: () => set({ state: initial }),
}),
{
name: 'jm-configs',
storage: createJSONStorage(() => sessionStorage),
},
),
)