Skip to content

Commit a34400c

Browse files
authored
fix(transfer): remove 80% onchain cap, misc fixes (#2406)
1 parent 0bcd35b commit a34400c

File tree

11 files changed

+144
-150
lines changed

11 files changed

+144
-150
lines changed

e2e/channels.e2e.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ d('Transfer', () => {
160160
// Receiving Capacity
161161
// can continue with min amount
162162
await element(by.id('SpendingAdvancedMin')).tap();
163-
await expect(element(by.text('2 000'))).toBeVisible();
163+
await expect(element(by.text('2 500'))).toBeVisible();
164164
await element(by.id('SpendingAdvancedContinue')).tap();
165165
await element(by.id('SpendingConfirmDefault')).tap();
166166
await element(by.id('SpendingConfirmAdvanced')).tap();

src/hooks/transfer.ts

Lines changed: 54 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1+
import { useEffect, useState } from 'react';
2+
13
import { useAppSelector } from './redux';
2-
import { onChainBalanceSelector } from '../store/reselect/wallet';
4+
import { estimateOrderFee } from '../utils/blocktank';
5+
import { fiatToBitcoinUnit } from '../utils/conversion';
36
import { blocktankInfoSelector } from '../store/reselect/blocktank';
47
import { blocktankChannelsSizeSelector } from '../store/reselect/lightning';
5-
import { fiatToBitcoinUnit } from '../utils/conversion';
68

79
type TTransferValues = {
810
maxClientBalance: number;
@@ -36,25 +38,18 @@ const getMinLspBalance = (
3638
clientBalance: number,
3739
minChannelSize: number,
3840
): number => {
39-
// LSP balance must be at least 2% of the channel size for LDK to accept (reserve balance)
40-
const ldkMinimum = Math.round(clientBalance * 0.02);
41+
// LSP balance must be at least 2.5% of the channel size for LDK to accept (reserve balance)
42+
const ldkMinimum = Math.round(clientBalance * 0.025);
4143
// Channel size must be at least minChannelSize
4244
const lspMinimum = Math.max(minChannelSize - clientBalance, 0);
4345

4446
return Math.max(ldkMinimum, lspMinimum);
4547
};
4648

47-
const getMaxClientBalance = (
48-
onchainBalance: number,
49-
maxChannelSize: number,
50-
): number => {
51-
// Remote balance must be at least 2% of the channel size for LDK to accept (reserve balance)
52-
const minRemoteBalance = Math.round(maxChannelSize * 0.02);
53-
// Cap client balance to 80% to leave buffer for fees
54-
const feeMaximum = Math.round(onchainBalance * 0.8);
55-
const ldkMaximum = maxChannelSize - minRemoteBalance;
56-
57-
return Math.min(feeMaximum, ldkMaximum);
49+
const getMaxClientBalance = (maxChannelSize: number): number => {
50+
// Remote balance must be at least 2.5% of the channel size for LDK to accept (reserve balance)
51+
const minRemoteBalance = Math.round(maxChannelSize * 0.025);
52+
return maxChannelSize - minRemoteBalance;
5853
};
5954

6055
/**
@@ -64,7 +59,6 @@ const getMaxClientBalance = (
6459
*/
6560
export const useTransfer = (clientBalance: number): TTransferValues => {
6661
const blocktankInfo = useAppSelector(blocktankInfoSelector);
67-
const onchainBalance = useAppSelector(onChainBalanceSelector);
6862
const channelsSize = useAppSelector(blocktankChannelsSizeSelector);
6963

7064
const { minChannelSizeSat, maxChannelSizeSat } = blocktankInfo.options;
@@ -77,9 +71,9 @@ export const useTransfer = (clientBalance: number): TTransferValues => {
7771
const maxChannelSize = Math.min(maxChannelSize1, maxChannelSize2);
7872

7973
const minLspBalance = getMinLspBalance(clientBalance, minChannelSizeSat);
80-
const maxLspBalance = maxChannelSize - clientBalance;
74+
const maxLspBalance = Math.max(maxChannelSize - clientBalance, 0);
8175
const defaultLspBalance = getDefaultLspBalance(clientBalance, maxLspBalance);
82-
const maxClientBalance = getMaxClientBalance(onchainBalance, maxChannelSize);
76+
const maxClientBalance = getMaxClientBalance(maxChannelSize);
8377

8478
return {
8579
defaultLspBalance,
@@ -88,3 +82,45 @@ export const useTransfer = (clientBalance: number): TTransferValues => {
8882
maxClientBalance,
8983
};
9084
};
85+
86+
/**
87+
* Returns limits and default values for channel orders with the LSP
88+
* @param {number} lspBalance
89+
* @param {number} clientBalance
90+
* @returns {{ fee: number, loading: boolean, error: string | null }}
91+
*/
92+
export const useTransferFee = (
93+
lspBalance: number,
94+
clientBalance: number,
95+
): { fee: number; loading: boolean; error: string | null } => {
96+
const [{ fee, loading, error }, setState] = useState<{
97+
fee: number;
98+
loading: boolean;
99+
error: string | null;
100+
}>({
101+
fee: 0,
102+
loading: true,
103+
error: null,
104+
});
105+
106+
useEffect(() => {
107+
const getFeeEstimation = async (): Promise<void> => {
108+
setState((prevState) => ({ ...prevState, loading: true }));
109+
try {
110+
const result = await estimateOrderFee({ lspBalance, clientBalance });
111+
if (result.isOk()) {
112+
const { feeSat } = result.value;
113+
setState({ fee: feeSat, loading: false, error: null });
114+
} else {
115+
setState({ fee: 0, loading: false, error: result.error.message });
116+
}
117+
} catch (err) {
118+
setState({ fee: 0, loading: false, error: err });
119+
}
120+
};
121+
122+
getFeeEstimation();
123+
}, [lspBalance, clientBalance]);
124+
125+
return { fee, loading, error };
126+
};

src/screens/Transfer/SpendingAdvanced.tsx

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -65,13 +65,7 @@ const SpendingAdvanced = ({
6565
return;
6666
}
6767

68-
const result = await estimateOrderFee({
69-
lspBalance,
70-
options: {
71-
clientBalanceSat: clientBalance,
72-
turboChannel: false,
73-
},
74-
});
68+
const result = await estimateOrderFee({ lspBalance, clientBalance });
7569
if (result.isErr()) {
7670
return;
7771
}

src/screens/Transfer/SpendingAmount.tsx

Lines changed: 49 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,13 @@ import Button from '../../components/buttons/Button';
2222
import UnitButton from '../Wallets/UnitButton';
2323
import TransferNumberPad from './TransferNumberPad';
2424
import type { TransferScreenProps } from '../../navigation/types';
25-
import { useTransfer } from '../../hooks/transfer';
25+
import { useTransfer, useTransferFee } from '../../hooks/transfer';
2626
import { useAppSelector } from '../../hooks/redux';
2727
import { useBalance, useSwitchUnit } from '../../hooks/wallet';
2828
import { convertToSats } from '../../utils/conversion';
2929
import { showToast } from '../../utils/notifications';
3030
import { getNumberPadText } from '../../utils/numberpad';
3131
import { getDisplayValues } from '../../utils/displayValues';
32-
import { getMaxSendAmount } from '../../utils/wallet/transactions';
3332
import { transactionSelector } from '../../store/reselect/wallet';
3433
import {
3534
resetSendTransaction,
@@ -45,6 +44,7 @@ import {
4544
conversionUnitSelector,
4645
denominationSelector,
4746
} from '../../store/reselect/settings';
47+
import { onChainFeesSelector } from '../../store/reselect/fees';
4848

4949
const SpendingAmount = ({
5050
navigation,
@@ -57,37 +57,49 @@ const SpendingAmount = ({
5757
const nextUnit = useAppSelector(nextUnitSelector);
5858
const conversionUnit = useAppSelector(conversionUnitSelector);
5959
const denomination = useAppSelector(denominationSelector);
60+
const fees = useAppSelector(onChainFeesSelector);
6061

6162
const [textFieldValue, setTextFieldValue] = useState('');
6263
const [loading, setLoading] = useState(false);
6364

65+
const clientBalance = useMemo((): number => {
66+
return convertToSats(textFieldValue, conversionUnit);
67+
}, [textFieldValue, conversionUnit]);
68+
69+
const transferValues = useTransfer(clientBalance);
70+
const { minLspBalance, defaultLspBalance, maxClientBalance } = transferValues;
71+
72+
// Calculate the maximum amount that can be transferred
73+
const availableAmount = onchainBalance - transaction.fee;
74+
const { defaultLspBalance: maxLspBalance } = useTransfer(availableAmount);
75+
const { fee: maxLspFee } = useTransferFee(maxLspBalance, availableAmount);
76+
const feeMaximum = Math.floor(availableAmount - maxLspFee);
77+
const maximum = Math.min(maxClientBalance, feeMaximum);
78+
6479
useFocusEffect(
6580
useCallback(() => {
6681
const setupTransfer = async (): Promise<void> => {
82+
// In case of the low fee market, we bump fee by 5 sats
83+
// details: https://github.com/synonymdev/bitkit/issues/2139
84+
const getSatsPerByte = (fee: number): number => {
85+
const MIN_FEE = 10;
86+
const BUMP_FEE = 5;
87+
return fee <= MIN_FEE ? fee + BUMP_FEE : fee;
88+
};
89+
90+
const satsPerByte = getSatsPerByte(fees.fast);
91+
6792
await resetSendTransaction();
68-
await setupOnChainTransaction({ rbf: false });
93+
await setupOnChainTransaction({ satsPerByte, rbf: false });
6994
refreshBlocktankInfo().then();
7095
};
7196
setupTransfer();
97+
98+
// onMount
99+
// eslint-disable-next-line react-hooks/exhaustive-deps
72100
}, []),
73101
);
74102

75-
const clientBalance = useMemo((): number => {
76-
return convertToSats(textFieldValue, conversionUnit);
77-
}, [textFieldValue, conversionUnit]);
78-
79-
const transferValues = useTransfer(clientBalance);
80-
const { defaultLspBalance, maxClientBalance } = transferValues;
81-
82-
const availableAmount = useMemo(() => {
83-
const maxAmountResponse = getMaxSendAmount();
84-
if (maxAmountResponse.isOk()) {
85-
return maxAmountResponse.value.amount;
86-
}
87-
return 0;
88-
// eslint-disable-next-line react-hooks/exhaustive-deps
89-
}, [transaction.outputs, transaction.satsPerByte]);
90-
91103
const onChangeUnit = (): void => {
92104
const result = getNumberPadText(clientBalance, denomination, nextUnit);
93105
setTextFieldValue(result);
@@ -96,37 +108,43 @@ const SpendingAmount = ({
96108

97109
const onQuarter = (): void => {
98110
const quarter = Math.round(onchainBalance / 4);
99-
const amount = Math.min(quarter, maxClientBalance);
111+
const amount = Math.min(quarter, maximum);
100112
const result = getNumberPadText(amount, denomination, unit);
101113
setTextFieldValue(result);
102114
};
103115

104116
const onMaxAmount = (): void => {
105-
const result = getNumberPadText(maxClientBalance, denomination, unit);
117+
const result = getNumberPadText(maximum, denomination, unit);
106118
setTextFieldValue(result);
107119
};
108120

109121
const onNumberPadError = (): void => {
110-
const dv = getDisplayValues({ satoshis: maxClientBalance });
122+
const dv = getDisplayValues({ satoshis: maximum });
123+
let description = t('spending_amount.error_max.description', {
124+
amount: dv.bitcoinFormatted,
125+
});
126+
127+
if (maximum === 0) {
128+
description = t('spending_amount.error_max.description_zero');
129+
}
130+
111131
showToast({
112132
type: 'warning',
113133
title: t('spending_amount.error_max.title'),
114-
description: t('spending_amount.error_max.description', {
115-
amount: dv.bitcoinFormatted,
116-
}),
134+
description,
117135
});
118136
};
119137

120138
const onContinue = async (): Promise<void> => {
121139
setLoading(true);
122140

123-
const lspBalance = defaultLspBalance;
124-
const response = await startChannelPurchase({ clientBalance, lspBalance });
141+
const lspBalance = Math.max(defaultLspBalance, minLspBalance);
142+
const result = await startChannelPurchase({ clientBalance, lspBalance });
125143

126144
setLoading(false);
127145

128-
if (response.isErr()) {
129-
const { message } = response.error;
146+
if (result.isErr()) {
147+
const { message } = result.error;
130148
const nodeCapped = message.includes('channel size check');
131149
const title = nodeCapped
132150
? t('spending_amount.error_max.title')
@@ -143,7 +161,7 @@ const SpendingAmount = ({
143161
return;
144162
}
145163

146-
navigation.navigate('SpendingConfirm', { order: response.value });
164+
navigation.navigate('SpendingConfirm', { order: result.value });
147165
};
148166

149167
return (
@@ -222,7 +240,7 @@ const SpendingAmount = ({
222240
<TransferNumberPad
223241
style={styles.numberpad}
224242
value={textFieldValue}
225-
maxAmount={maxClientBalance}
243+
maxAmount={maximum}
226244
onChange={setTextFieldValue}
227245
onError={onNumberPadError}
228246
/>

src/screens/Wallets/Receive/ReceiveAmount.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ const ReceiveAmount = ({
4646
const switchUnit = useSwitchUnit();
4747
const [minimumAmount, setMinimumAmount] = useState(0);
4848

49-
const { defaultLspBalance: lspBalance } = useTransfer(0);
49+
const { defaultLspBalance: lspBalance, maxClientBalance } = useTransfer(0);
5050

5151
useFocusEffect(
5252
useCallback(() => {
@@ -95,7 +95,9 @@ const ReceiveAmount = ({
9595
};
9696

9797
const continueDisabled =
98-
minimumAmount === 0 || invoice.amount < minimumAmount;
98+
minimumAmount === 0 ||
99+
invoice.amount < minimumAmount ||
100+
invoice.amount > maxClientBalance;
99101

100102
return (
101103
<GradientView style={styles.container}>

src/screens/Wallets/Receive/ReceiveConnect.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,10 @@ const ReceiveConnect = ({
148148
useEffect(() => {
149149
const getFeeEstimation = async (): Promise<void> => {
150150
setIsLoading(true);
151-
const feeResult = await estimateOrderFee({ lspBalance });
151+
const feeResult = await estimateOrderFee({
152+
lspBalance,
153+
clientBalance: amount,
154+
});
152155
if (feeResult.isOk()) {
153156
const fees = feeResult.value;
154157
setFeeEstimate(fees);
@@ -163,7 +166,7 @@ const ReceiveConnect = ({
163166
};
164167

165168
getFeeEstimation();
166-
}, [t, lspBalance]);
169+
}, [t, lspBalance, amount]);
167170

168171
return (
169172
<GradientView style={styles.container}>

src/screens/Wallets/Receive/ReceiveDetails.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ const ReceiveDetails = ({
7070
};
7171

7272
getFeeEstimation();
73-
}, [lspBalance, t]);
73+
}, [lspBalance]);
7474

7575
useEffect(() => {
7676
if (invoice.tags.length > 0) {

src/store/reselect/wallet.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -194,10 +194,8 @@ export const transactionFeeSelector = createSelector(
194194
[walletState],
195195
(wallet) => {
196196
const { selectedWallet, selectedNetwork } = wallet;
197-
return (
198-
wallet.wallets[selectedWallet]?.transaction[selectedNetwork].fee ||
199-
defaultSendTransaction.fee
200-
);
197+
const { transaction } = wallet.wallets[selectedWallet];
198+
return transaction[selectedNetwork].fee;
201199
},
202200
);
203201

0 commit comments

Comments
 (0)