Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add btc consolidation logic and new ui components to psbt and batch psbt screens #726

Merged
merged 26 commits into from
Jan 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
18d7a7e
Add skeleton for PSBT signing
victorkirov Dec 12, 2023
9d5e22a
Update core
victorkirov Dec 12, 2023
125cfad
Remove old file
victorkirov Dec 12, 2023
5fdde0a
Add ledger support
victorkirov Dec 12, 2023
d184460
update core
victorkirov Dec 12, 2023
f9619f0
Update core
victorkirov Dec 12, 2023
7d8144c
feat: add new components for btc tx and use them in psbt and batch ps…
fedeerbes Dec 21, 2023
692146b
chore: pull origin
fedeerbes Dec 21, 2023
d33d617
chore: merge develop into vic/add-consolidation-logic-skeleton
fedeerbes Dec 21, 2023
4efec77
fix: double scroll in popup windows
fedeerbes Dec 26, 2023
5cbd101
fix: rare sats sort and scientific notation in amount component
fedeerbes Dec 26, 2023
2318124
chore: clean up code
fedeerbes Dec 26, 2023
9df0e22
chore: add anchor option to callout and use it in the warning of asse…
fedeerbes Dec 27, 2023
0c07ae4
feat: unconfirmed callout (#728)
abdulhaseeb4239 Dec 28, 2023
b839b7b
chore: add review suggestions
fedeerbes Dec 28, 2023
32d0d5a
chore: small improvements
fedeerbes Dec 28, 2023
fb30da8
chore: merge develop into vic/add-consolidation-logic-skeleton
fedeerbes Dec 28, 2023
3132c2f
fix: scientific notation in inputs output value
fedeerbes Dec 28, 2023
204f00a
fix: ui issue when content type exceeds container size and icon is pl…
fedeerbes Dec 29, 2023
b9daa4a
Fix PSBT signing with Core update
victorkirov Jan 3, 2024
7a46efb
chore: add showContentTypeThumbnail prop to ordinal image component a…
fedeerbes Jan 8, 2024
85a299e
Merge branch 'develop' into vic/add-consolidation-logic-skeleton
fedeerbes Jan 9, 2024
969a08a
chore: update core version to dummy utxo fixes branch
fedeerbes Jan 9, 2024
f18d3ce
chore: merge develop into vic/add-consolidation-logic-skeleton
fedeerbes Jan 9, 2024
3e980bb
chore: merge develop into vic/add-consolidation-logic-skeleton
fedeerbes Jan 10, 2024
9bdae4a
chore: update core version to fix ledger input issue
fedeerbes Jan 10, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
366 changes: 301 additions & 65 deletions package-lock.json

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
"@ledgerhq/hw-transport-webusb": "^6.27.13",
"@phosphor-icons/react": "^2.0.10",
"@react-spring/web": "^9.6.1",
"@secretkeylabs/xverse-core": "8.0.0",
"@scure/btc-signer": "^1.1.1",
"@secretkeylabs/xverse-core": "8.0.1",
"@stacks/connect": "7.4.1",
"@stacks/stacks-blockchain-api-types": "6.1.1",
"@stacks/transactions": "6.9.0",
Expand Down
229 changes: 229 additions & 0 deletions src/app/components/confirmBtcTransaction/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
import ledgerConnectDefaultIcon from '@assets/img/ledger/ledger_connect_default.svg';
import ledgerConnectBtcIcon from '@assets/img/ledger/ledger_import_connect_btc.svg';
import { delay } from '@common/utils/ledger';
import BottomModal from '@components/bottomModal';
import ActionButton from '@components/button';
import LedgerConnectionView from '@components/ledger/connectLedgerView';
import useWalletSelector from '@hooks/useWalletSelector';
import TransportFactory from '@ledgerhq/hw-transport-webusb';
import { btcTransaction, Transport } from '@secretkeylabs/xverse-core';
import Callout from '@ui-library/callout';
import { StickyHorizontalSplitButtonContainer, StyledP } from '@ui-library/common.styled';
import { isLedgerAccount } from '@utils/helper';
import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { MoonLoader } from 'react-spinners';
import styled from 'styled-components';
import SendLayout from '../../layouts/sendLayout';
import TransactionSummary from './transactionSummary';

const LoaderContainer = styled.div(() => ({
display: 'flex',
flex: 1,
justifyContent: 'center',
alignItems: 'center',
}));

const ReviewTransactionText = styled(StyledP)`
text-align: left;
margin-bottom: ${(props) => props.theme.space.l};
`;

const BroadcastCallout = styled(Callout)`
margin-bottom: ${(props) => props.theme.space.m};
`;

const SuccessActionsContainer = styled.div((props) => ({
width: '100%',
display: 'flex',
flexDirection: 'column',
gap: props.theme.space.s,
paddingLeft: props.theme.space.m,
paddingRight: props.theme.space.m,
marginBottom: props.theme.space.xxl,
marginTop: props.theme.space.xxl,
}));

type Props = {
inputs: btcTransaction.EnhancedInput[];
outputs: btcTransaction.EnhancedOutput[];
feeOutput?: btcTransaction.TransactionFeeOutput;
isLoading: boolean;
isSubmitting: boolean;
isBroadcast?: boolean;
isError?: boolean;
showAccountHeader?: boolean;
hideBottomBar?: boolean;
cancelText: string;
confirmText: string;
onConfirm: (ledgerTransport?: Transport) => void;
onCancel: () => void;
onBackClick?: () => void;
confirmDisabled?: boolean;
getFeeForFeeRate?: (feeRate: number, useEffectiveFeeRate?: boolean) => Promise<number>;
onFeeRateSet?: (feeRate: number) => void;
};

function ConfirmBtcTransaction({
inputs,
outputs,
feeOutput,
isLoading,
isSubmitting,
isBroadcast,
isError = false,
cancelText,
confirmText,
onConfirm,
onCancel,
onBackClick,
showAccountHeader,
hideBottomBar,
confirmDisabled = false,
getFeeForFeeRate,
onFeeRateSet,
}: Props) {
const [isModalVisible, setIsModalVisible] = useState(false);
const [currentStepIndex, setCurrentStepIndex] = useState(0);
const [isButtonDisabled, setIsButtonDisabled] = useState(false);
const [isConnectSuccess, setIsConnectSuccess] = useState(false);
const [isConnectFailed, setIsConnectFailed] = useState(false);
const [isTxRejected, setIsTxRejected] = useState(false);

const { t } = useTranslation('translation', { keyPrefix: 'CONFIRM_TRANSACTION' });
const { t: signatureRequestTranslate } = useTranslation('translation', {
keyPrefix: 'SIGNATURE_REQUEST',
});
const { selectedAccount } = useWalletSelector();

const hideBackButton = !onBackClick;

const onConfirmPress = async () => {
if (!isLedgerAccount(selectedAccount)) {
return onConfirm();
}

// show ledger connection screens
setIsModalVisible(true);
};

const handleConnectAndConfirm = async () => {
if (!selectedAccount) {
console.error('No account selected');

Check warning on line 112 in src/app/components/confirmBtcTransaction/index.tsx

View workflow job for this annotation

GitHub Actions / build

Unexpected console statement
return;
}
setIsButtonDisabled(true);

const transport = await TransportFactory.create();

if (!transport) {
setIsConnectSuccess(false);
setIsConnectFailed(true);
setIsButtonDisabled(false);
return;
}

setIsConnectSuccess(true);
await delay(1500);
setCurrentStepIndex(1);

try {
onConfirm(transport);
} catch (err) {
console.error(err);

Check warning on line 133 in src/app/components/confirmBtcTransaction/index.tsx

View workflow job for this annotation

GitHub Actions / build

Unexpected console statement
setIsTxRejected(true);
}
};

const handleRetry = async () => {
setIsTxRejected(false);
setIsConnectSuccess(false);
setCurrentStepIndex(0);
};

// TODO: this is a bit naive, but should be correct. We may want to look at the sig hash types of the inputs instead
const isPartialTransaction = !feeOutput;

return isLoading ? (
<LoaderContainer>
<MoonLoader color="white" size={50} />
</LoaderContainer>
) : (
<>
<SendLayout
selectedBottomTab="dashboard"
onClickBack={onBackClick}
hideBackButton={hideBackButton}
showAccountHeader={showAccountHeader}
hideBottomBar={hideBottomBar}
>
<ReviewTransactionText typography="headline_s">
{t('REVIEW_TRANSACTION')}
</ReviewTransactionText>
{!isBroadcast && <BroadcastCallout bodyText={t('PSBT_NO_BROADCAST_DISCLAIMER')} />}
<TransactionSummary
inputs={inputs}
outputs={outputs}
feeOutput={feeOutput}
isPartialTransaction={isPartialTransaction}
getFeeForFeeRate={getFeeForFeeRate}
onFeeRateSet={onFeeRateSet}
isSubmitting={isSubmitting}
/>
m-aboelenein marked this conversation as resolved.
Show resolved Hide resolved
{!isLoading && (
<StickyHorizontalSplitButtonContainer>
<ActionButton onPress={onCancel} text={cancelText} transparent />
<ActionButton
onPress={onConfirmPress}
disabled={confirmDisabled}
processing={isSubmitting}
text={confirmText}
warning={isError}
/>
</StickyHorizontalSplitButtonContainer>
)}
</SendLayout>
<BottomModal header="" visible={isModalVisible} onClose={() => setIsModalVisible(false)}>
{currentStepIndex === 0 && (
<LedgerConnectionView
title={signatureRequestTranslate('LEDGER.CONNECT.TITLE')}
text={signatureRequestTranslate('LEDGER.CONNECT.SUBTITLE', { name: 'Bitcoin' })}
titleFailed={signatureRequestTranslate('LEDGER.CONNECT.ERROR_TITLE')}
textFailed={signatureRequestTranslate('LEDGER.CONNECT.ERROR_SUBTITLE')}
imageDefault={ledgerConnectBtcIcon}
isConnectSuccess={isConnectSuccess}
isConnectFailed={isConnectFailed}
/>
)}
{currentStepIndex === 1 && (
<LedgerConnectionView
title={signatureRequestTranslate('LEDGER.CONFIRM.TITLE')}
text={signatureRequestTranslate('LEDGER.CONFIRM.SUBTITLE')}
titleFailed={signatureRequestTranslate('LEDGER.CONFIRM.ERROR_TITLE')}
textFailed={signatureRequestTranslate('LEDGER.CONFIRM.ERROR_SUBTITLE')}
imageDefault={ledgerConnectDefaultIcon}
isConnectSuccess={false}
isConnectFailed={isTxRejected}
/>
)}
<SuccessActionsContainer>
<ActionButton
onPress={isTxRejected || isConnectFailed ? handleRetry : handleConnectAndConfirm}
text={signatureRequestTranslate(
isTxRejected || isConnectFailed ? 'LEDGER.RETRY_BUTTON' : 'LEDGER.CONNECT_BUTTON',
)}
disabled={isButtonDisabled}
processing={isButtonDisabled}
/>
<ActionButton
onPress={onCancel}
text={signatureRequestTranslate('LEDGER.CANCEL_BUTTON')}
transparent
/>
</SuccessActionsContainer>
</BottomModal>
</>
);
}

export default ConfirmBtcTransaction;
86 changes: 86 additions & 0 deletions src/app/components/confirmBtcTransaction/itemRow/amount.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import TokenImage from '@components/tokenImage';
import useWalletSelector from '@hooks/useWalletSelector';
import { currencySymbolMap, getBtcFiatEquivalent, satsToBtc } from '@secretkeylabs/xverse-core';
import Avatar from '@ui-library/avatar';
import { StyledP } from '@ui-library/common.styled';
import BigNumber from 'bignumber.js';
import { useTranslation } from 'react-i18next';
import { NumericFormat } from 'react-number-format';
import styled from 'styled-components';

type Props = {
amount: number;
};

const RowCenter = styled.div<{ spaceBetween?: boolean }>((props) => ({
display: 'flex',
flex: 1,
flexDirection: 'row',
alignItems: 'center',
justifyContent: props.spaceBetween ? 'space-between' : 'initial',
}));

const NumberTypeContainer = styled.div`
text-align: right;
`;

const AvatarContainer = styled.div`
margin-right: ${(props) => props.theme.space.xs};
`;

export default function Amount({ amount }: Props) {
const { btcFiatRate, fiatCurrency } = useWalletSelector();
const { t } = useTranslation('translation');

const getFiatAmountString = (amountParam: number, btcFiatRateParam: string) => {
const fiatAmount = getBtcFiatEquivalent(
new BigNumber(amountParam),
BigNumber(btcFiatRateParam),
);
if (!fiatAmount) {
return '';
}

if (fiatAmount.isLessThan(0.01)) {
return `<${currencySymbolMap[fiatCurrency]}0.01 ${fiatCurrency}`;
}

return (
<NumericFormat
value={fiatAmount.toFixed(2).toString()}
displayType="text"
thousandSeparator
prefix={`${currencySymbolMap[fiatCurrency]} `}
suffix={` ${fiatCurrency}`}
renderText={(value: string) => `~ ${value}`}
/>
);
};

return (
<RowCenter>
<AvatarContainer>
<Avatar src={<TokenImage token="BTC" loading={false} size={32} />} />
</AvatarContainer>
<RowCenter spaceBetween>
<div>
<StyledP typography="body_medium_m" color="white_200">
{t('CONFIRM_TRANSACTION.AMOUNT')}
</StyledP>
</div>
<NumberTypeContainer>
<NumericFormat
value={satsToBtc(new BigNumber(amount)).toFixed()}
displayType="text"
thousandSeparator
suffix=" BTC"
renderText={(value: string) => <StyledP typography="body_medium_m">{value}</StyledP>}
/>
<StyledP typography="body_medium_s" color="white_400">
{getFiatAmountString(amount, btcFiatRate)}
</StyledP>
</NumberTypeContainer>
</RowCenter>
</RowCenter>
);
}
Loading
Loading