Skip to content

Commit

Permalink
fix(transfer): remove 80% onchain cap, misc fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
pwltr committed Dec 17, 2024
1 parent 3913667 commit 7b51fee
Show file tree
Hide file tree
Showing 10 changed files with 131 additions and 146 deletions.
2 changes: 1 addition & 1 deletion e2e/channels.e2e.js
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ d('Transfer', () => {
// Receiving Capacity
// can continue with min amount
await element(by.id('SpendingAdvancedMin')).tap();
await expect(element(by.text('2 000'))).toBeVisible();
await expect(element(by.text('2 500'))).toBeVisible();
await element(by.id('SpendingAdvancedContinue')).tap();
await element(by.id('SpendingConfirmDefault')).tap();
await element(by.id('SpendingConfirmAdvanced')).tap();
Expand Down
72 changes: 54 additions & 18 deletions src/hooks/transfer.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { useEffect, useState } from 'react';

import { useAppSelector } from './redux';
import { onChainBalanceSelector } from '../store/reselect/wallet';
import { estimateOrderFee } from '../utils/blocktank';
import { fiatToBitcoinUnit } from '../utils/conversion';
import { blocktankInfoSelector } from '../store/reselect/blocktank';
import { blocktankChannelsSizeSelector } from '../store/reselect/lightning';
import { fiatToBitcoinUnit } from '../utils/conversion';

type TTransferValues = {
maxClientBalance: number;
Expand Down Expand Up @@ -36,25 +38,18 @@ const getMinLspBalance = (
clientBalance: number,
minChannelSize: number,
): number => {
// LSP balance must be at least 2% of the channel size for LDK to accept (reserve balance)
const ldkMinimum = Math.round(clientBalance * 0.02);
// LSP balance must be at least 2.5% of the channel size for LDK to accept (reserve balance)
const ldkMinimum = Math.round(clientBalance * 0.025);
// Channel size must be at least minChannelSize
const lspMinimum = Math.max(minChannelSize - clientBalance, 0);

return Math.max(ldkMinimum, lspMinimum);
};

const getMaxClientBalance = (
onchainBalance: number,
maxChannelSize: number,
): number => {
// Remote balance must be at least 2% of the channel size for LDK to accept (reserve balance)
const minRemoteBalance = Math.round(maxChannelSize * 0.02);
// Cap client balance to 80% to leave buffer for fees
const feeMaximum = Math.round(onchainBalance * 0.8);
const ldkMaximum = maxChannelSize - minRemoteBalance;

return Math.min(feeMaximum, ldkMaximum);
const getMaxClientBalance = (maxChannelSize: number): number => {
// Remote balance must be at least 2.5% of the channel size for LDK to accept (reserve balance)
const minRemoteBalance = Math.round(maxChannelSize * 0.025);
return maxChannelSize - minRemoteBalance;
};

/**
Expand All @@ -64,7 +59,6 @@ const getMaxClientBalance = (
*/
export const useTransfer = (clientBalance: number): TTransferValues => {
const blocktankInfo = useAppSelector(blocktankInfoSelector);
const onchainBalance = useAppSelector(onChainBalanceSelector);
const channelsSize = useAppSelector(blocktankChannelsSizeSelector);

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

const minLspBalance = getMinLspBalance(clientBalance, minChannelSizeSat);
const maxLspBalance = maxChannelSize - clientBalance;
const maxLspBalance = Math.max(maxChannelSize - clientBalance, 0);
const defaultLspBalance = getDefaultLspBalance(clientBalance, maxLspBalance);
const maxClientBalance = getMaxClientBalance(onchainBalance, maxChannelSize);
const maxClientBalance = getMaxClientBalance(maxChannelSize);

return {
defaultLspBalance,
Expand All @@ -88,3 +82,45 @@ export const useTransfer = (clientBalance: number): TTransferValues => {
maxClientBalance,
};
};

/**
* Returns limits and default values for channel orders with the LSP
* @param {number} lspBalance
* @param {number} clientBalance
* @returns {{ fee: number, loading: boolean, error: string | null }}
*/
export const useTransferFee = (
lspBalance: number,
clientBalance: number,
): { fee: number; loading: boolean; error: string | null } => {
const [{ fee, loading, error }, setState] = useState<{
fee: number;
loading: boolean;
error: string | null;
}>({
fee: 0,
loading: true,
error: null,
});

useEffect(() => {
const getFeeEstimation = async (): Promise<void> => {
setState((prevState) => ({ ...prevState, loading: true }));
try {
const result = await estimateOrderFee({ lspBalance, clientBalance });
if (result.isOk()) {
const { feeSat } = result.value;
setState({ fee: feeSat, loading: false, error: null });
} else {
setState({ fee: 0, loading: false, error: result.error.message });
}
} catch (err) {
setState({ fee: 0, loading: false, error: err });
}
};

getFeeEstimation();
}, [lspBalance, clientBalance]);

return { fee, loading, error };
};
8 changes: 1 addition & 7 deletions src/screens/Transfer/SpendingAdvanced.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,13 +65,7 @@ const SpendingAdvanced = ({
return;
}

const result = await estimateOrderFee({
lspBalance,
options: {
clientBalanceSat: clientBalance,
turboChannel: false,
},
});
const result = await estimateOrderFee({ lspBalance, clientBalance });
if (result.isErr()) {
return;
}
Expand Down
66 changes: 39 additions & 27 deletions src/screens/Transfer/SpendingAmount.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,13 @@ import Button from '../../components/buttons/Button';
import UnitButton from '../Wallets/UnitButton';
import TransferNumberPad from './TransferNumberPad';
import type { TransferScreenProps } from '../../navigation/types';
import { useTransfer } from '../../hooks/transfer';
import { useTransfer, useTransferFee } from '../../hooks/transfer';
import { useAppSelector } from '../../hooks/redux';
import { useBalance, useSwitchUnit } from '../../hooks/wallet';
import { convertToSats } from '../../utils/conversion';
import { showToast } from '../../utils/notifications';
import { getNumberPadText } from '../../utils/numberpad';
import { getDisplayValues } from '../../utils/displayValues';
import { getMaxSendAmount } from '../../utils/wallet/transactions';
import { transactionSelector } from '../../store/reselect/wallet';
import {
resetSendTransaction,
Expand All @@ -45,6 +44,7 @@ import {
conversionUnitSelector,
denominationSelector,
} from '../../store/reselect/settings';
import { onChainFeesSelector } from '../../store/reselect/fees';

const SpendingAmount = ({
navigation,
Expand All @@ -57,37 +57,49 @@ const SpendingAmount = ({
const nextUnit = useAppSelector(nextUnitSelector);
const conversionUnit = useAppSelector(conversionUnitSelector);
const denomination = useAppSelector(denominationSelector);
const fees = useAppSelector(onChainFeesSelector);

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

const clientBalance = useMemo((): number => {
return convertToSats(textFieldValue, conversionUnit);
}, [textFieldValue, conversionUnit]);

const transferValues = useTransfer(clientBalance);
const { defaultLspBalance, maxClientBalance } = transferValues;

// Calculate the maximum amount that can be transferred
const availableAmount = onchainBalance - transaction.fee;
const { defaultLspBalance: maxLspBalance } = useTransfer(availableAmount);
const { fee: maxLspFee } = useTransferFee(maxLspBalance, availableAmount);
const feeMaximum = Math.floor(availableAmount - maxLspFee);
const maximum = Math.min(maxClientBalance, feeMaximum);

useFocusEffect(
useCallback(() => {
const setupTransfer = async (): Promise<void> => {
// In case of the low fee market, we bump fee by 5 sats
// details: https://github.com/synonymdev/bitkit/issues/2139
const getSatsPerByte = (fee: number): number => {
const MIN_FEE = 10;
const BUMP_FEE = 5;
return fee <= MIN_FEE ? fee + BUMP_FEE : fee;
};

const satsPerByte = getSatsPerByte(fees.fast);

await resetSendTransaction();
await setupOnChainTransaction({ rbf: false });
await setupOnChainTransaction({ satsPerByte, rbf: false });
refreshBlocktankInfo().then();
};
setupTransfer();

// onMount
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []),
);

const clientBalance = useMemo((): number => {
return convertToSats(textFieldValue, conversionUnit);
}, [textFieldValue, conversionUnit]);

const transferValues = useTransfer(clientBalance);
const { defaultLspBalance, maxClientBalance } = transferValues;

const availableAmount = useMemo(() => {
const maxAmountResponse = getMaxSendAmount();
if (maxAmountResponse.isOk()) {
return maxAmountResponse.value.amount;
}
return 0;
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [transaction.outputs, transaction.satsPerByte]);

const onChangeUnit = (): void => {
const result = getNumberPadText(clientBalance, denomination, nextUnit);
setTextFieldValue(result);
Expand All @@ -96,18 +108,18 @@ const SpendingAmount = ({

const onQuarter = (): void => {
const quarter = Math.round(onchainBalance / 4);
const amount = Math.min(quarter, maxClientBalance);
const amount = Math.min(quarter, maximum);
const result = getNumberPadText(amount, denomination, unit);
setTextFieldValue(result);
};

const onMaxAmount = (): void => {
const result = getNumberPadText(maxClientBalance, denomination, unit);
const result = getNumberPadText(maximum, denomination, unit);
setTextFieldValue(result);
};

const onNumberPadError = (): void => {
const dv = getDisplayValues({ satoshis: maxClientBalance });
const dv = getDisplayValues({ satoshis: maximum });
showToast({
type: 'warning',
title: t('spending_amount.error_max.title'),
Expand All @@ -121,12 +133,12 @@ const SpendingAmount = ({
setLoading(true);

const lspBalance = defaultLspBalance;
const response = await startChannelPurchase({ clientBalance, lspBalance });
const result = await startChannelPurchase({ clientBalance, lspBalance });

setLoading(false);

if (response.isErr()) {
const { message } = response.error;
if (result.isErr()) {
const { message } = result.error;
const nodeCapped = message.includes('channel size check');
const title = nodeCapped
? t('spending_amount.error_max.title')
Expand All @@ -143,7 +155,7 @@ const SpendingAmount = ({
return;
}

navigation.navigate('SpendingConfirm', { order: response.value });
navigation.navigate('SpendingConfirm', { order: result.value });
};

return (
Expand Down Expand Up @@ -222,7 +234,7 @@ const SpendingAmount = ({
<TransferNumberPad
style={styles.numberpad}
value={textFieldValue}
maxAmount={maxClientBalance}
maxAmount={maximum}
onChange={setTextFieldValue}
onError={onNumberPadError}
/>
Expand Down
6 changes: 4 additions & 2 deletions src/screens/Wallets/Receive/ReceiveAmount.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ const ReceiveAmount = ({
const switchUnit = useSwitchUnit();
const [minimumAmount, setMinimumAmount] = useState(0);

const { defaultLspBalance: lspBalance } = useTransfer(0);
const { defaultLspBalance: lspBalance, maxClientBalance } = useTransfer(0);

useFocusEffect(
useCallback(() => {
Expand Down Expand Up @@ -95,7 +95,9 @@ const ReceiveAmount = ({
};

const continueDisabled =
minimumAmount === 0 || invoice.amount < minimumAmount;
minimumAmount === 0 ||
invoice.amount < minimumAmount ||
invoice.amount > maxClientBalance;

return (
<GradientView style={styles.container}>
Expand Down
7 changes: 5 additions & 2 deletions src/screens/Wallets/Receive/ReceiveConnect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,10 @@ const ReceiveConnect = ({
useEffect(() => {
const getFeeEstimation = async (): Promise<void> => {
setIsLoading(true);
const feeResult = await estimateOrderFee({ lspBalance });
const feeResult = await estimateOrderFee({
lspBalance,
clientBalance: amount,
});
if (feeResult.isOk()) {
const fees = feeResult.value;
setFeeEstimate(fees);
Expand All @@ -163,7 +166,7 @@ const ReceiveConnect = ({
};

getFeeEstimation();
}, [t, lspBalance]);
}, [t, lspBalance, amount]);

return (
<GradientView style={styles.container}>
Expand Down
2 changes: 1 addition & 1 deletion src/screens/Wallets/Receive/ReceiveDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ const ReceiveDetails = ({
};

getFeeEstimation();
}, [lspBalance, t]);
}, [lspBalance]);

useEffect(() => {
if (invoice.tags.length > 0) {
Expand Down
6 changes: 2 additions & 4 deletions src/store/reselect/wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,10 +194,8 @@ export const transactionFeeSelector = createSelector(
[walletState],
(wallet) => {
const { selectedWallet, selectedNetwork } = wallet;
return (
wallet.wallets[selectedWallet]?.transaction[selectedNetwork].fee ||
defaultSendTransaction.fee
);
const { transaction } = wallet.wallets[selectedWallet];
return transaction[selectedNetwork].fee;
},
);

Expand Down
Loading

0 comments on commit 7b51fee

Please sign in to comment.