-
Notifications
You must be signed in to change notification settings - Fork 54
/
Fees.ts
136 lines (117 loc) · 4.48 KB
/
Fees.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
import { useCallback, useEffect, useState, useMemo } from 'react'
import { useRefreshConfigValues } from '../context/ServiceConfigContext'
import { AmountSats } from '../libs/JmWalletApi'
import { isValidNumber } from '../utils'
export type TxFeeValueUnit = 'blocks' | 'sats/kilo-vbyte'
export type TxFeeValue = number
export type TxFee = {
value?: TxFeeValue
unit?: TxFeeValueUnit
}
export const toTxFeeValueUnit = (val?: TxFeeValue): TxFeeValueUnit | undefined => {
if (val === undefined || !Number.isInteger(val) || val < 1) return undefined
return val <= 1_000 ? 'blocks' : 'sats/kilo-vbyte'
}
export const FEE_CONFIG_KEYS = {
tx_fees: { section: 'POLICY', field: 'tx_fees' },
tx_fees_factor: { section: 'POLICY', field: 'tx_fees_factor' },
max_cj_fee_abs: { section: 'POLICY', field: 'max_cj_fee_abs' },
max_cj_fee_rel: { section: 'POLICY', field: 'max_cj_fee_rel' },
max_sweep_fee_change: { section: 'POLICY', field: 'max_sweep_fee_change' },
}
export interface FeeValues {
tx_fees?: TxFee
tx_fees_factor?: number
max_cj_fee_abs?: number
max_cj_fee_rel?: number
max_sweep_fee_change?: number
}
export const useLoadFeeConfigValues = () => {
const refreshConfigValues = useRefreshConfigValues()
return useCallback(
async (signal?: AbortSignal) => {
const serviceConfig = await refreshConfigValues({
signal,
keys: Object.values(FEE_CONFIG_KEYS),
})
const policy = serviceConfig['POLICY'] || {}
const parsedTxFees = parseInt(policy.tx_fees || '', 10)
const parsedTxFeesFactor = parseFloat(policy.tx_fees_factor || '')
const parsedMaxFeeAbs = parseInt(policy.max_cj_fee_abs || '', 10)
const parsedMaxFeeRel = parseFloat(policy.max_cj_fee_rel || '')
const parsedMaxSweepFeeChange = parseFloat(policy.max_sweep_fee_change || '')
const feeValues: FeeValues = {
tx_fees: isValidNumber(parsedTxFees)
? {
value: parsedTxFees,
unit: toTxFeeValueUnit(parsedTxFees),
}
: undefined,
tx_fees_factor: isValidNumber(parsedTxFeesFactor) ? parsedTxFeesFactor : undefined,
max_cj_fee_abs: isValidNumber(parsedMaxFeeAbs) ? parsedMaxFeeAbs : undefined,
max_cj_fee_rel: isValidNumber(parsedMaxFeeRel) ? parsedMaxFeeRel : undefined,
max_sweep_fee_change: isValidNumber(parsedMaxSweepFeeChange) ? parsedMaxSweepFeeChange : undefined,
}
return feeValues
},
[refreshConfigValues],
)
}
export const useFeeConfigValues = (): [FeeValues | undefined, () => void] => {
const loadFeeConfigValues = useLoadFeeConfigValues()
const [values, setValues] = useState<FeeValues>()
const [reloadCounter, setReloadCounter] = useState(0)
useEffect(() => {
const abortCtrl = new AbortController()
loadFeeConfigValues(abortCtrl.signal)
.then((val) => setValues(val))
.catch((e) => {
if (abortCtrl.signal.aborted) return
console.log('Unable lo load fee config: ', e)
setValues(undefined)
})
return () => {
abortCtrl.abort()
}
}, [loadFeeConfigValues, reloadCounter])
return [values, () => setReloadCounter((val) => val + 1)]
}
interface EstimatMaxCollaboratorFeeProps {
amount: AmountSats
collaborators: number
maxFeeAbs: AmountSats
maxFeeRel: number // e.g. 0.001 for 0.1%
}
export const estimateMaxCollaboratorFee = ({
amount,
collaborators,
maxFeeAbs,
maxFeeRel,
}: EstimatMaxCollaboratorFeeProps): AmountSats => {
const maxFeePerCollaborator = Math.max(Math.ceil(amount * maxFeeRel), maxFeeAbs)
return collaborators > 0 ? Math.min(maxFeePerCollaborator * collaborators, amount) : 0
}
interface EstimatedMaxCollaboratorFeeArgs {
isCoinjoin: boolean
amount: AmountSats | null
numCollaborators: number | null
feeConfigValues?: FeeValues
}
export const useEstimatedMaxCollaboratorFee = ({
isCoinjoin,
amount,
numCollaborators,
feeConfigValues,
}: EstimatedMaxCollaboratorFeeArgs): AmountSats | null => {
return useMemo(() => {
if (!isCoinjoin || !feeConfigValues || !amount) return null
if (!isValidNumber(amount) || !isValidNumber(numCollaborators ?? undefined)) return null
if (!isValidNumber(feeConfigValues.max_cj_fee_abs) || !isValidNumber(feeConfigValues.max_cj_fee_rel)) return null
return estimateMaxCollaboratorFee({
amount,
collaborators: numCollaborators!,
maxFeeAbs: feeConfigValues.max_cj_fee_abs!,
maxFeeRel: feeConfigValues.max_cj_fee_rel!,
})
}, [amount, isCoinjoin, numCollaborators, feeConfigValues])
}