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

[ENG-2879] feat: Batch sign PSBT in extension #631

Merged
merged 39 commits into from
Nov 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
92ff7d7
[ENG-2879] feat: Batch sign PSBT in extension
dhriaznov Oct 27, 2023
9947ffa
Add transaction review modal
dhriaznov Oct 27, 2023
3bd376b
Merge branch 'develop' into denys/eng-2879-batch-sign-psbt-in-extension
dhriaznov Oct 30, 2023
a044808
Improve tx review logic
dhriaznov Oct 30, 2023
45ef77e
Improve the tx review UI and logic
dhriaznov Oct 30, 2023
e35258f
Improve the tx review UI
dhriaznov Oct 31, 2023
d665e52
Improve the displaying of total receiving / transferring inscriptions
dhriaznov Oct 31, 2023
6db3798
Uncomment and update the `checkIfMismatch` func for the array of psbts
dhriaznov Oct 31, 2023
f45e341
Merge branch 'develop' into denys/eng-2879-batch-sign-psbt-in-extension
dhriaznov Nov 1, 2023
cb498d4
Merge branch 'develop' into denys/eng-2879-batch-sign-psbt-in-extension
dhriaznov Nov 1, 2023
55fcc02
Add `circularProgress` component for the loading state, improve the b…
dhriaznov Nov 1, 2023
3fe7ea7
Update the `useSignBatchPsbtTx` hook
dhriaznov Nov 2, 2023
331b75b
Finalize the batch psbt signing logic
dhriaznov Nov 2, 2023
a696d51
Add a `todo` for the `broadcast` bool property
dhriaznov Nov 2, 2023
a09334d
Remove the redundant text on the tx success screen
dhriaznov Nov 6, 2023
fcd02d4
Update the `sats-connect` package version and `broadcast` property logic
dhriaznov Nov 6, 2023
39640e9
Separate the tx broadcasting logic for batch tx signing
dhriaznov Nov 7, 2023
f139d1e
Merge branch 'develop' into denys/eng-2879-batch-sign-psbt-in-extension
dhriaznov Nov 9, 2023
2751b4b
Make some code fixes according to code review comments
dhriaznov Nov 9, 2023
c1de9d8
Make some code fixes according to code review comments
dhriaznov Nov 9, 2023
51b07fd
Update the `useDetectOrdinalInSignPsbt` hook logic
dhriaznov Nov 9, 2023
28bf112
Upgrade the `sats-connect` package
dhriaznov Nov 10, 2023
6ebdf93
Update the copy
dhriaznov Nov 13, 2023
5d9c7a6
Fix `confirmSignPsbt` function response usage
dhriaznov Nov 14, 2023
50e35f9
Fix `useDetectOrdinalInSignPsbt` hook for batch tx signing
dhriaznov Nov 15, 2023
8a03e39
Update `sats-connect` version, make psbt signing delay shorter, and t…
dhriaznov Nov 16, 2023
d1f9426
Merge branch 'develop' into denys/eng-2879-batch-sign-psbt-in-extension
dhriaznov Nov 17, 2023
57b388b
Merge branch 'develop' of github.com:secretkeylabs/xverse-web-extensi…
dhriaznov Nov 20, 2023
4a784c5
Show an error when not all txs were broadcasted
dhriaznov Nov 20, 2023
9fcae32
Remove the broadcasting logic
dhriaznov Nov 21, 2023
4a1f114
Merge branch 'develop' of github.com:secretkeylabs/xverse-web-extensi…
dhriaznov Nov 21, 2023
c02c2bd
Update the `sats-connect` version
dhriaznov Nov 22, 2023
a23107d
Merge branch 'develop' into denys/eng-2879-batch-sign-psbt-in-extension
dhriaznov Nov 24, 2023
36c54e4
chore: bump sats-connect to v1.3.0
teebszet Nov 29, 2023
0d997c1
Merge remote-tracking branch 'origin/develop' into denys/eng-2879-bat…
teebszet Nov 29, 2023
bb0894f
chore: ignore sats-connect type error until capability is added
teebszet Nov 29, 2023
7f142e4
fix: fix type errors
teebszet Nov 29, 2023
26489d3
fix: more type errors
teebszet Nov 29, 2023
1721f8b
fix: typo
teebszet Nov 29, 2023
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
14 changes: 7 additions & 7 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@
"redux": "^4.0.5",
"redux-persist": "^6.0.0",
"redux-state-sync": "^3.1.4",
"sats-connect": "1.1.2",
"sats-connect": "1.3.0",
"stream-browserify": "^3.0.0",
"string-to-color": "^2.2.2",
"styled-components": "^5.3.5",
Expand Down
26 changes: 15 additions & 11 deletions src/app/components/bottomModal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,41 +8,44 @@ const BottomModalHeaderText = styled.h1((props) => ({
flex: 1,
}));

const RowContainer = styled.div({
const RowContainer = styled.div((props) => ({
display: 'flex',
flexDirection: 'row',
alignItems: 'space-between',
margin: '24px 24px 20px 24px',
});
margin: props.theme.spacing(12),
marginBottom: props.theme.spacing(10),
}));

const ButtonImage = styled.button({
backgroundColor: 'transparent',
});

const CustomisedModal = styled(Modal)`
overflow-y: auto;
&::-webkit-scrollbar {
display: none;
}
position: absolute;
`;

interface Props {
header: string;
visible: boolean;
children: React.ReactNode;
onClose: () => void;
overlayStylesOverriding?: {};
contentStylesOverriding?: {};
className?: string;
}

const CustomisedModal = styled(Modal)`
overflow-y: auto;
&::-webkit-scrollbar {
display: none;
}
position: absolute;
`;

function BottomModal({
header,
children,
visible,
onClose,
overlayStylesOverriding,
contentStylesOverriding,
className,
}: Props) {
const theme = useTheme();
const isGalleryOpen: boolean = document.documentElement.clientWidth > 360;
Expand Down Expand Up @@ -79,6 +82,7 @@ function BottomModal({
ariaHideApp={false}
style={customStyles}
contentLabel="Example Modal"
className={className}
>
<RowContainer>
<BottomModalHeaderText>{header}</BottomModalHeaderText>
Expand Down
19 changes: 11 additions & 8 deletions src/app/components/button/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const Button = styled.button<ButtonProps>((props) => ({
width: '100%',
height: 44,
transition: 'all 0.1s ease',
columnGap: props.theme.spacing(3),
':disabled': {
opacity: 0.4,
cursor: 'not-allowed',
Expand Down Expand Up @@ -63,23 +64,22 @@ const AnimatedButtonText = styled.div((props) => ({
textAlign: 'center',
}));

const ButtonImage = styled.img((props) => ({
marginRight: props.theme.spacing(3),
const ButtonImage = styled.img({
alignSelf: 'center',
transform: 'all',
}));
});

const ButtonIconContainer = styled.div((props) => ({
const ButtonIconContainer = styled.div({
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
marginRight: props.theme.spacing(3),
}));
});

interface Props {
className?: string;
src?: string;
icon?: JSX.Element;
iconPosition?: 'left' | 'right';
text: string;
onPress: () => void;
processing?: boolean;
Expand All @@ -93,6 +93,7 @@ function ActionButton({
className,
src,
icon,
iconPosition = 'left',
text,
onPress,
processing = false,
Expand Down Expand Up @@ -120,8 +121,9 @@ function ActionButton({
) : (
<>
{src && <ButtonImage src={src} />}
{icon && <ButtonIconContainer>{icon}</ButtonIconContainer>}
{icon && iconPosition === 'left' && <ButtonIconContainer>{icon}</ButtonIconContainer>}
<AnimatedButtonText>{text}</AnimatedButtonText>
{icon && iconPosition === 'right' && <ButtonIconContainer>{icon}</ButtonIconContainer>}
</>
)}
</TransparentButton>
Expand All @@ -140,8 +142,9 @@ function ActionButton({
) : (
<>
{src && <ButtonImage src={src} />}
{icon && <ButtonIconContainer>{icon}</ButtonIconContainer>}
{icon && iconPosition === 'left' && <ButtonIconContainer>{icon}</ButtonIconContainer>}
<ButtonText warning={warning}>{text}</ButtonText>
{icon && iconPosition === 'right' && <ButtonIconContainer>{icon}</ButtonIconContainer>}
m-aboelenein marked this conversation as resolved.
Show resolved Hide resolved
</>
)}
</Button>
Expand Down
10 changes: 6 additions & 4 deletions src/app/components/confirmStxTransactionComponent/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import SettingIcon from '@assets/img/dashboard/faders_horizontal.svg';
import ledgerConnectDefaultIcon from '@assets/img/ledger/ledger_connect_default.svg';
import ledgerConnectStxIcon from '@assets/img/ledger/ledger_import_connect_stx.svg';
import { ledgerDelay } from '@common/utils/ledger';
import { delay } from '@common/utils/ledger';
import BottomModal from '@components/bottomModal';
import ActionButton from '@components/button';
import InfoContainer from '@components/infoContainer';
Expand Down Expand Up @@ -174,7 +174,7 @@
} else if (showFeeWarning) {
setShowFeeWarning(false);
}
}, [initialStxTransactions, feeMultipliers]);

Check warning on line 177 in src/app/components/confirmStxTransactionComponent/index.tsx

View workflow job for this annotation

GitHub Actions / build

React Hook useEffect has a missing dependency: 'showFeeWarning'. Either include it or remove the dependency array

const getFee = () =>
isSponsored
Expand Down Expand Up @@ -275,7 +275,7 @@
}

setIsConnectSuccess(true);
await ledgerDelay(1500);
await delay(1500);
setCurrentStepIndex(1);
try {
const signedTxs = await signLedgerStxTransaction({
Expand All @@ -284,7 +284,7 @@
addressIndex: selectedAccount.deviceAccountIndex,
});
setIsTxApproved(true);
await ledgerDelay(1500);
await delay(1500);
onConfirmClick([signedTxs]);
} catch (e) {
console.error(e);
Expand Down Expand Up @@ -323,7 +323,9 @@
{(initialStxTransactions[0]?.payload as any)?.amount && (
<TransferFeeView
fee={microstacksToStx(
getFee().plus(new BigNumber((initialStxTransactions[0]?.payload as any).amount?.toString(10))),
getFee().plus(
new BigNumber((initialStxTransactions[0]?.payload as any).amount?.toString(10)),
),
)}
currency="STX"
title={t('TOTAL')}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,12 @@ const StyledSvg = styled.svg<{ status: ConfirmationStatus; loadingPercentage: nu
export function CircularSvgAnimation({
status,
loadingPercentage,
withLoadingBgCircle = false,
onRest,
}: {
status: ConfirmationStatus;
loadingPercentage: number;
withLoadingBgCircle?: boolean;
onRest?: () => void;
}) {
const [isCircleRested, setIsCircleRested] = useState(false);
Expand Down Expand Up @@ -94,6 +96,13 @@ export function CircularSvgAnimation({
fill="none"
>
<path className={checkClass} d="M59.125 35.75L38.9582 55L28.875 45.375" />
{withLoadingBgCircle && status === 'LOADING' && (
<path
stroke="rgba(255, 255, 255, 0.1)"
strokeWidth={2}
d="M44 77C62.2254 77 77 62.2254 77 44C77 25.7746 62.2254 11 44 11C25.7746 11 11 25.7746 11 44C11 62.2254 25.7746 77 44 77Z"
/>
)}
<animated.path
className="circle"
d="M44 77C62.2254 77 77 62.2254 77 44C77 25.7746 62.2254 11 44 11C25.7746 11 11 25.7746 11 44C11 62.2254 25.7746 77 44 77Z"
Expand Down
15 changes: 9 additions & 6 deletions src/app/components/loadingTransactionStatus/index.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import ActionButton from '@components/button';
import className from 'classnames';
import styled from 'styled-components';
import { StyledP, StyledHeading, VerticalStackButtonContainer } from '@ui-library/common.styled';
import { animated, easings, useSpring } from '@react-spring/web';
import { StyledHeading, StyledP, VerticalStackButtonContainer } from '@ui-library/common.styled';
import className from 'classnames';
import { useState } from 'react';
import styled from 'styled-components';
import { CircularSvgAnimation, ConfirmationStatus } from './circularSvgAnimation';

type Texts = {
Expand Down Expand Up @@ -84,7 +84,7 @@ const StatusLarge = styled.div`
`;

const Heading = styled(StyledHeading)`
margin-bottom: 6px;
margin-bottom: 12px;
`;

/**
Expand All @@ -103,13 +103,15 @@ export function LoadingTransactionStatus({
primaryAction,
secondaryAction,
loadingPercentage,
withLoadingBgCircle = false,
}: {
status: ConfirmationStatus;
resultTexts: Texts;
loadingTexts: Texts;
primaryAction: Action;
secondaryAction: Action;
secondaryAction?: Action;
loadingPercentage: number;
withLoadingBgCircle?: boolean;
}) {
const [hasCircleAnimationRested, setHasCircleAnimationRested] = useState(false);

Expand Down Expand Up @@ -147,6 +149,7 @@ export function LoadingTransactionStatus({
status={status}
loadingPercentage={loadingPercentage}
onRest={handleAnimationRest}
withLoadingBgCircle={withLoadingBgCircle}
/>
</StatusLarge>
<AnimatedBodyContainer>
Expand All @@ -167,7 +170,7 @@ export function LoadingTransactionStatus({
<BottomFixedContainer className={visibleClass}>
<VerticalStackButtonContainer>
<ActionButton text={primaryAction.text} onPress={primaryAction.onPress} />
{status === 'SUCCESS' && (
{secondaryAction && status === 'SUCCESS' && (
<ActionButton
text={secondaryAction.text}
onPress={secondaryAction.onPress}
Expand Down
71 changes: 30 additions & 41 deletions src/app/hooks/useDetectOrdinalInSignPsbt.ts
Original file line number Diff line number Diff line change
@@ -1,62 +1,51 @@
import { getUtxoOrdinalBundle, ParsedPSBT } from '@secretkeylabs/xverse-core';
import { BundleItem, mapRareSatsAPIResponseToRareSats } from '@utils/rareSats';
import { isAxiosError } from 'axios';
import { useEffect, useState } from 'react';
import useWalletSelector from './useWalletSelector';

const useDetectOrdinalInSignPsbt = (parsedPsbt: undefined | ParsedPSBT) => {
const [loading, setLoading] = useState(false);
const [userReceivesOrdinal, setUserReceivesOrdinal] = useState(false);
const [bundleItemsData, setBundleItemsData] = useState<BundleItem[]>([]);
const useDetectOrdinalInSignPsbt = () => {
const { ordinalsAddress, network } = useWalletSelector();

async function handleOrdinalAndOrdinalInfo() {
const handleOrdinalAndOrdinalInfo = async (parsedPsbt?: ParsedPSBT) => {
const bundleItems: BundleItem[] = [];
let userReceivesOrdinal = false;

if (parsedPsbt) {
setLoading(true);
await Promise.all(
parsedPsbt.inputs.map(async (input) => {
try {
const data = await getUtxoOrdinalBundle(network.type, input.txid, input.index);

const bundle = mapRareSatsAPIResponseToRareSats(data);
bundle.items.forEach((item) => {
// we don't show unknown items for now
if (item.type === 'unknown') {
return;
}
bundleItems.push(item);
});
} catch (e) {
// we get back a 404 if the UTXO is not found, so it is likely this is a UTXO from an unpublished txn
if (!isAxiosError(e) || e.response?.status !== 404) {
// rethrow error if response was not 404
throw e;
parsedPsbt.inputs.map(async (input) => {
Copy link
Contributor Author

@dhriaznov dhriaznov Nov 15, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed the Promise.all function here as it has been breaking the displaying of the batch PSBTs (when there are dozens of txs) throwing a 429 error (Too Many Requests):

Monosnap DevTools - chrome-extension:::fpdmgfoenhfmedphgpdfkkdfcnggphhn:popup html 2023-11-15 15-35-09

try {
const data = await getUtxoOrdinalBundle(network.type, input.txid, input.index);

const bundle = mapRareSatsAPIResponseToRareSats(data);
bundle.items.forEach((item) => {
// we don't show unknown items for now
if (item.type === 'unknown') {
return;
}
bundleItems.push(item);
});
} catch (e) {
// we get back a 404 if the UTXO is not found, so it is likely this is a UTXO from an unpublished txn
if (!isAxiosError(e) || e.response?.status !== 404) {
// rethrow error if response was not 404
throw e;
}
}),
);

setBundleItemsData(bundleItems);
setLoading(false);
}
});

parsedPsbt.outputs.forEach(async (output) => {
parsedPsbt.outputs.forEach((output) => {
if (output.address === ordinalsAddress) {
setUserReceivesOrdinal(true);
userReceivesOrdinal = true;
}
});
}
}

useEffect(() => {
handleOrdinalAndOrdinalInfo();
}, []);

return {
loading,
bundleItemsData,
userReceivesOrdinal,
return {
bundleItemsData: bundleItems,
userReceivesOrdinal,
};
};

return handleOrdinalAndOrdinalInfo;
};

export default useDetectOrdinalInSignPsbt;
Loading
Loading