From 58ebfbf75de09e7a05624bbcfd8123b0fbb0e8f5 Mon Sep 17 00:00:00 2001 From: Norman Wilde Date: Thu, 20 Apr 2023 13:45:44 +0200 Subject: [PATCH 1/4] feat: auth with Ledger stacks account --- .../screens/authenticationRequest/index.tsx | 131 +++++++++++++++++- src/locales/en.json | 27 +++- 2 files changed, 153 insertions(+), 5 deletions(-) diff --git a/src/app/screens/authenticationRequest/index.tsx b/src/app/screens/authenticationRequest/index.tsx index 7e21723d4..52bd3890b 100644 --- a/src/app/screens/authenticationRequest/index.tsx +++ b/src/app/screens/authenticationRequest/index.tsx @@ -3,13 +3,25 @@ import { useLocation } from 'react-router-dom'; import ConfirmScreen from '@components/confirmScreen'; import { decodeToken } from 'jsontokens'; import { useTranslation } from 'react-i18next'; -import { createAuthResponse } from '@secretkeylabs/xverse-core'; +import { + createAuthResponse, + handleLedgerStxJWTAuth, + makeLedgerCompatibleUnsignedAuthResponsePayload, + signStxJWTAuth, +} from '@secretkeylabs/xverse-core'; import { MESSAGE_SOURCE } from '@common/types/message-types'; import { useState } from 'react'; import useWalletSelector from '@hooks/useWalletSelector'; import DappPlaceholderIcon from '@assets/img/webInteractions/authPlaceholder.svg'; import validUrl from 'valid-url'; import AccountHeaderComponent from '@components/accountHeader'; +import BottomModal from '@components/bottomModal'; +import LedgerConnectDefault from '@assets/img/ledger/ledger_connect_default.svg'; +import LedgerConnectionView from '@components/ledger/connectLedgerView'; +import ActionButton from '@components/button'; +import Transport from '@ledgerhq/hw-transport-webusb'; +import { ledgerDelay } from '@common/utils/ledger'; +import { publicKeyToAddress } from '@stacks/transactions'; const MainContainer = styled.div({ display: 'flex', @@ -22,6 +34,16 @@ const MainContainer = styled.div({ overflow: 'hidden', }); +const SuccessActionsContainer = styled.div((props) => ({ + width: '100%', + display: 'flex', + flexDirection: 'column', + gap: '12px', + paddingLeft: props.theme.spacing(8), + paddingRight: props.theme.spacing(8), + marginBottom: props.theme.spacing(30), +})); + const TopImage = styled.img({ aspectRatio: 1, height: 88, @@ -43,7 +65,15 @@ const DappTitle = styled.h2((props) => ({ function AuthenticationRequest() { const [loading, setLoading] = useState(false); + 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 [isTxApproved, setIsTxApproved] = useState(false); + const [isTxRejected, setIsTxRejected] = useState(false); const { t } = useTranslation('translation', { keyPrefix: 'AUTH_REQUEST_SCREEN' }); + const { search } = useLocation(); const params = new URLSearchParams(search); const authRequestToken = params.get('authRequest') ?? ''; @@ -53,10 +83,15 @@ function AuthenticationRequest() { const confirmCallback = async () => { setLoading(true); try { + if (selectedAccount?.isLedgerAccount) { + setIsModalVisible(true); + return; + } + const authResponse = await createAuthResponse( seedPhrase, selectedAccount?.id ?? 0, - authRequest, + authRequest ); chrome.tabs.sendMessage(+(params.get('tabId') ?? '0'), { source: MESSAGE_SOURCE, @@ -86,7 +121,66 @@ function AuthenticationRequest() { window.close(); }; - const getDappLogo = () => (validUrl.isWebUri(authRequest?.payload?.appDetails?.icon) ? authRequest?.payload?.appDetails?.icon : DappPlaceholderIcon); + const getDappLogo = () => + validUrl.isWebUri(authRequest?.payload?.appDetails?.icon) + ? authRequest?.payload?.appDetails?.icon + : DappPlaceholderIcon; + + const handleConnectAndConfirm = async () => { + if (!selectedAccount) { + console.error('No account selected'); + return; + } + setIsButtonDisabled(true); + + const transport = await Transport.create(); + + if (!transport) { + setIsConnectSuccess(false); + setIsConnectFailed(true); + setIsButtonDisabled(false); + return; + } + + setIsConnectSuccess(true); + await ledgerDelay(1500); + setCurrentStepIndex(1); + + const profile = { + stxAddress: { + mainnet: selectedAccount.stxAddress, + testnet: publicKeyToAddress(26, { + data: Buffer.from(selectedAccount.stxPublicKey, 'hex'), + type: 6, // 6 = Public Key + }), + }, + }; + + try { + const authResponse = await handleLedgerStxJWTAuth(transport, selectedAccount.id, profile); + setIsTxApproved(true); + await ledgerDelay(1500); + chrome.tabs.sendMessage(+(params.get('tabId') ?? '0'), { + source: MESSAGE_SOURCE, + payload: { + authenticationRequest: authRequestToken, + authenticationResponse: authResponse, + }, + method: 'authenticationResponse', + }); + window.close(); + } catch (e) { + console.error(e); + setIsTxRejected(true); + setIsButtonDisabled(false); + } + }; + + const handleRetry = async () => { + setIsTxRejected(false); + setIsConnectSuccess(false); + setCurrentStepIndex(0); + }; return ( {t('TITLE')} {`${t('REQUEST_TOOLTIP')} ${authRequest.payload.appDetails?.name}`} + setIsModalVisible(false)}> + {currentStepIndex === 0 ? ( + + ) : currentStepIndex === 1 ? ( + + ) : null} + + + + ); } diff --git a/src/locales/en.json b/src/locales/en.json index 6f5c3e2bc..558478be2 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -285,7 +285,30 @@ "TITLE": "Connection", "REQUEST_TOOLTIP": "Requested by", "CONNECT_BUTTON": "Connect", - "CANCEL_BUTTON": "Cancel" + "CANCEL_BUTTON": "Cancel", + "LEDGER": { + "CONNECT": { + "TITLE": "Connect your hardware wallet", + "SUBTITLE": "To continue, plug and unlock your device.", + "ERROR_TITLE": "Connection error", + "ERROR_SUBTITLE": "We haven’t been able to connect to your device." + }, + "CONFIRM": { + "TITLE": "Confirm the signature", + "SUBTITLE": "Authorise the signature on your device to continue.", + "ERROR_TITLE": "Signature denied", + "ERROR_SUBTITLE": "The signatures has been rejected. You can try again or close this tab." + }, + "SUCCESS": { + "TITLE": "Signature confirmed", + "SUBTITLE": "This transaction will be broadcasted by the sponsor." + }, + "CONFIRM_BUTTON": "Confirm", + "CONNECT_BUTTON": "Connect", + "CLOSE_BUTTON": "Close", + "CANCEL_BUTTON": "Cancel", + "RETRY_BUTTON": "Try again" + } }, "LOGIN_SCREEN": { "WELCOME_MESSAGE_FIRST_LOGIN": "Welcome!", @@ -572,4 +595,4 @@ "NETWORK_MISMATCH_ERROR_TITLE": "Mismatched Network", "NETWORK_MISMATCH_ERROR_DESCRIPTION": "The app is requesting your wallet address for a different network. You may have to switch your active network in wallet settings." } -} +} \ No newline at end of file From 194472ce544c99ec559c398faddfc1915c59036e Mon Sep 17 00:00:00 2001 From: Norman Wilde Date: Thu, 20 Apr 2023 14:20:37 +0200 Subject: [PATCH 2/4] feat: style ledger connect modal content --- src/app/components/bottomModal/index.tsx | 9 +++------ src/app/screens/authenticationRequest/index.tsx | 6 ++++-- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/app/components/bottomModal/index.tsx b/src/app/components/bottomModal/index.tsx index e57dae151..a1b6d383f 100644 --- a/src/app/components/bottomModal/index.tsx +++ b/src/app/components/bottomModal/index.tsx @@ -35,9 +35,7 @@ const CustomisedModal = styled(Modal)` position: absolute; `; -function BottomModal({ - header, children, visible, onClose, -}: Props) { +function BottomModal({ header, children, visible, onClose }: Props) { const theme = useTheme(); const isGalleryOpen: boolean = document.documentElement.clientWidth > 360; const customStyles = { @@ -67,7 +65,7 @@ function BottomModal({ return ( (document.getElementById('app') as HTMLElement)} + parentSelector={() => document.getElementById('app') as HTMLElement} ariaHideApp={false} style={customStyles} contentLabel="Example Modal" @@ -78,8 +76,7 @@ function BottomModal({ cross - - + {header && } {children} ); diff --git a/src/app/screens/authenticationRequest/index.tsx b/src/app/screens/authenticationRequest/index.tsx index 52bd3890b..69f2ad61c 100644 --- a/src/app/screens/authenticationRequest/index.tsx +++ b/src/app/screens/authenticationRequest/index.tsx @@ -41,7 +41,8 @@ const SuccessActionsContainer = styled.div((props) => ({ gap: '12px', paddingLeft: props.theme.spacing(8), paddingRight: props.theme.spacing(8), - marginBottom: props.theme.spacing(30), + marginBottom: props.theme.spacing(20), + marginTop: props.theme.spacing(20), })); const TopImage = styled.img({ @@ -221,10 +222,11 @@ function AuthenticationRequest() { + From 2ab2a7d1c8211218b0bd6ec5e224067e86962869 Mon Sep 17 00:00:00 2001 From: Norman Wilde Date: Thu, 20 Apr 2023 15:13:51 +0200 Subject: [PATCH 3/4] feat: sign message with Ledger stacks --- src/app/screens/signatureRequest/index.tsx | 134 +++++++++++++++++++-- src/locales/en.json | 25 +++- 2 files changed, 146 insertions(+), 13 deletions(-) diff --git a/src/app/screens/signatureRequest/index.tsx b/src/app/screens/signatureRequest/index.tsx index bf07e41ed..abf75546b 100644 --- a/src/app/screens/signatureRequest/index.tsx +++ b/src/app/screens/signatureRequest/index.tsx @@ -19,10 +19,17 @@ import useWalletReducer from '@hooks/useWalletReducer'; import { getNetworkType } from '@utils/helper'; import { useNavigate } from 'react-router-dom'; import InfoContainer from '@components/infoContainer'; -import { hashMessage } from '@secretkeylabs/xverse-core'; +import { hashMessage, signStxMessage } from '@secretkeylabs/xverse-core'; import SignatureRequestMessage from './signatureRequestMessage'; import SignatureRequestStructuredData from './signatureRequestStructuredData'; import { finalizeMessageSignature } from './utils'; +import BottomModal from '@components/bottomModal'; +import LedgerConnectionView from '@components/ledger/connectLedgerView'; +import LedgerConnectDefault from '@assets/img/ledger/ledger_connect_default.svg'; +import ActionButton from '@components/button'; +import Transport from '@ledgerhq/hw-transport-webusb'; +import { ledgerDelay } from '@common/utils/ledger'; +import { signatureVrsToRsv } from '@stacks/common'; const MainContainer = styled.div((props) => ({ display: 'flex', @@ -104,15 +111,31 @@ const ActionDisclaimer = styled.p((props) => ({ marginBottom: props.theme.spacing(8), })); +const SuccessActionsContainer = styled.div((props) => ({ + width: '100%', + display: 'flex', + flexDirection: 'column', + gap: '12px', + paddingLeft: props.theme.spacing(8), + paddingRight: props.theme.spacing(8), + marginBottom: props.theme.spacing(20), + marginTop: props.theme.spacing(20), +})); + function SignatureRequest(): JSX.Element { const { t } = useTranslation('translation'); const [isSigning, setIsSigning] = useState(false); const [showHash, setShowHash] = useState(false); + 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 [isTxApproved, setIsTxApproved] = useState(false); + const [isTxRejected, setIsTxRejected] = useState(false); const { selectedAccount, accountsList, network } = useWalletSelector(); const { switchAccount } = useWalletReducer(); - const { - messageType, request, payload, tabId, domain, - } = useSignatureRequest(); + const { messageType, request, payload, tabId, domain } = useSignatureRequest(); const navigate = useNavigate(); const switchAccountBasedOnRequest = () => { if (getNetworkType(payload.network) !== network.type) { @@ -163,6 +186,10 @@ function SignatureRequest(): JSX.Element { const confirmCallback = async () => { try { setIsSigning(true); + if (selectedAccount?.isLedgerAccount) { + setIsModalVisible(true); + return; + } const signature = await handleMessageSigning({ message: payload.message, domain: domain || undefined, @@ -177,6 +204,49 @@ function SignatureRequest(): JSX.Element { } }; + const handleConnectAndConfirm = async () => { + if (!selectedAccount) { + console.error('No account selected'); + return; + } + setIsButtonDisabled(true); + + const transport = await Transport.create(); + + if (!transport) { + setIsConnectSuccess(false); + setIsConnectFailed(true); + setIsButtonDisabled(false); + return; + } + + setIsConnectSuccess(true); + await ledgerDelay(1500); + setCurrentStepIndex(1); + + try { + const signature = await signStxMessage(transport, payload.message, selectedAccount.id); + const rsvSignature = signatureVrsToRsv(signature.signatureVRS.toString('hex')); + const data = { + signature: rsvSignature, + publicKey: selectedAccount.stxPublicKey, + }; + if (signature) { + finalizeMessageSignature({ requestPayload: request, tabId: +tabId, data }); + } + } catch (e) { + console.error(e); + setIsTxRejected(true); + setIsButtonDisabled(false); + } + }; + + const handleRetry = async () => { + setIsTxRejected(false); + setIsConnectSuccess(false); + setCurrentStepIndex(0); + }; + return ( {t('SIGNATURE_REQUEST.TITLE')} - {`${t('SIGNATURE_REQUEST.DAPP_NAME_PREFIX')} ${payload.appDetails?.name}`} + {`${t('SIGNATURE_REQUEST.DAPP_NAME_PREFIX')} ${ + payload.appDetails?.name + }`} {isUtf8Message(messageType) && ( - + )} {isStructuredMessage(messageType) && ( - + )} - {showHash ? t('SIGNATURE_REQUEST.HIDE_HASH_BUTTON') : t('SIGNATURE_REQUEST.SHOW_HASH_BUTTON')} + {showHash + ? t('SIGNATURE_REQUEST.HIDE_HASH_BUTTON') + : t('SIGNATURE_REQUEST.SHOW_HASH_BUTTON')} Show @@ -217,6 +287,46 @@ function SignatureRequest(): JSX.Element { {t('SIGNATURE_REQUEST.ACTION_DISCLAIMER')} + setIsModalVisible(false)}> + {currentStepIndex === 0 ? ( + + ) : currentStepIndex === 1 ? ( + + ) : null} + + + + + ); } diff --git a/src/locales/en.json b/src/locales/en.json index 558478be2..549a8653d 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -565,7 +565,30 @@ "ACTION_DISCLAIMER": "No transactions will be broadcasted and no fees will be applied.", "SIGNING_WARNING": "By signing this message, you prove that you are the owner of an account without broadcasting any on chain transactions. This signature cannot be used to send transactions on your behalf. You should only sign messages you trust.", "SIGN_BUTTON": "Sign", - "CANCEL_BUTTON": "Cancel" + "CANCEL_BUTTON": "Cancel", + "LEDGER": { + "CONNECT": { + "TITLE": "Connect your hardware wallet", + "SUBTITLE": "To continue, plug and unlock your device.", + "ERROR_TITLE": "Connection error", + "ERROR_SUBTITLE": "We haven’t been able to connect to your device." + }, + "CONFIRM": { + "TITLE": "Confirm the signature", + "SUBTITLE": "Authorise the signature on your device to continue.", + "ERROR_TITLE": "Signature denied", + "ERROR_SUBTITLE": "The signatures has been rejected. You can try again or close this tab." + }, + "SUCCESS": { + "TITLE": "Signature confirmed", + "SUBTITLE": "This transaction will be broadcasted by the sponsor." + }, + "CONFIRM_BUTTON": "Confirm", + "CONNECT_BUTTON": "Connect", + "CLOSE_BUTTON": "Close", + "CANCEL_BUTTON": "Cancel", + "RETRY_BUTTON": "Try again" + } }, "ERROR_SCREEN": { "TITLE": "Xverse crashed unexpectedly", From ac912eebfbe6379b2933a2bb15d46c5ae28925eb Mon Sep 17 00:00:00 2001 From: Norman Wilde Date: Fri, 21 Apr 2023 14:04:04 +0200 Subject: [PATCH 4/4] feat: tx request signing with Ledger --- .../confirmStxTransactionComponent/index.tsx | 159 +++++++++++++++--- src/app/screens/transactionRequest/index.tsx | 15 +- src/locales/en.json | 26 ++- 3 files changed, 164 insertions(+), 36 deletions(-) diff --git a/src/app/components/confirmStxTransactionComponent/index.tsx b/src/app/components/confirmStxTransactionComponent/index.tsx index fa7e24867..6bce6a93c 100644 --- a/src/app/components/confirmStxTransactionComponent/index.tsx +++ b/src/app/components/confirmStxTransactionComponent/index.tsx @@ -1,8 +1,6 @@ import { useTranslation } from 'react-i18next'; import styled from 'styled-components'; -import { - ReactNode, useEffect, useState, -} from 'react'; +import { ReactNode, useEffect, useState } from 'react'; import BigNumber from 'bignumber.js'; import ActionButton from '@components/button'; import SettingIcon from '@assets/img/dashboard/faders_horizontal.svg'; @@ -11,10 +9,20 @@ import { microstacksToStx, stxToMicrostacks } from '@secretkeylabs/xverse-core/c import { StacksTransaction } from '@secretkeylabs/xverse-core/types'; import TransferFeeView from '@components/transferFeeView'; import { - setFee, setNonce, getNonce, signMultiStxTransactions, signTransaction, + setFee, + setNonce, + getNonce, + signMultiStxTransactions, + signTransaction, + signStxTransaction, } from '@secretkeylabs/xverse-core'; import useWalletSelector from '@hooks/useWalletSelector'; import useNetworkSelector from '@hooks/useNetwork'; +import Transport from '@ledgerhq/hw-transport-webusb'; +import BottomModal from '@components/bottomModal'; +import LedgerConnectionView from '@components/ledger/connectLedgerView'; +import LedgerConnectDefault from '@assets/img/ledger/ledger_connect_default.svg'; +import { ledgerDelay } from '@common/utils/ledger'; const Container = styled.div` display: flex; @@ -75,6 +83,17 @@ const SponsoredInfoText = styled.h1((props) => ({ color: props.theme.colors.white['400'], })); +const SuccessActionsContainer = styled.div((props) => ({ + width: '100%', + display: 'flex', + flexDirection: 'column', + gap: '12px', + paddingLeft: props.theme.spacing(8), + paddingRight: props.theme.spacing(8), + marginBottom: props.theme.spacing(20), + marginTop: props.theme.spacing(20), +})); + interface Props { initialStxTransactions: StacksTransaction[]; loading: boolean; @@ -94,25 +113,30 @@ function ConfirmStxTransationComponent({ }: Props) { const { t } = useTranslation('translation', { keyPrefix: 'CONFIRM_TRANSACTION' }); const selectedNetwork = useNetworkSelector(); - const { - selectedAccount, - seedPhrase, - } = useWalletSelector(); + const { selectedAccount, seedPhrase, isLedgerAccount } = useWalletSelector(); const [openTransactionSettingModal, setOpenTransactionSettingModal] = useState(false); const [buttonLoading, setButtonLoading] = useState(loading); + 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 [isTxApproved, setIsTxApproved] = useState(false); + const [isTxRejected, setIsTxRejected] = useState(false); useEffect(() => { setButtonLoading(loading); }, [loading]); - const getFee = () => (isSponsored - ? new BigNumber(0) - : new BigNumber( - initialStxTransactions - .map((tx) => tx?.auth?.spendingCondition?.fee ?? BigInt(0)) - .reduce((prev, curr) => prev + curr, BigInt(0)) - .toString(10), - )); + const getFee = () => + isSponsored + ? new BigNumber(0) + : new BigNumber( + initialStxTransactions + .map((tx) => tx?.auth?.spendingCondition?.fee ?? BigInt(0)) + .reduce((prev, curr) => prev + curr, BigInt(0)) + .toString(10) + ); const getTxNonce = (): string => { const nonce = getNonce(initialStxTransactions[0]); @@ -128,13 +152,18 @@ function ConfirmStxTransationComponent({ }; const onConfirmButtonClick = async () => { + if (selectedAccount?.isLedgerAccount) { + setIsModalVisible(true); + return; + } + let signedTxs: StacksTransaction[] = []; if (initialStxTransactions.length === 1) { const signedContractCall = await signTransaction( initialStxTransactions[0], seedPhrase, selectedAccount?.id ?? 0, - selectedNetwork, + selectedNetwork ); signedTxs.push(signedContractCall); } else if (initialStxTransactions.length === 2) { @@ -142,7 +171,7 @@ function ConfirmStxTransationComponent({ initialStxTransactions, selectedAccount?.id ?? 0, selectedNetwork, - seedPhrase, + seedPhrase ); } onConfirmClick(signedTxs); @@ -157,24 +186,64 @@ function ConfirmStxTransationComponent({ setOpenTransactionSettingModal(false); }; + const handleConnectAndConfirm = async () => { + if (!selectedAccount) { + console.error('No account selected'); + return; + } + setIsButtonDisabled(true); + + const transport = await Transport.create(); + + if (!transport) { + setIsConnectSuccess(false); + setIsConnectFailed(true); + setIsButtonDisabled(false); + return; + } + + setIsConnectSuccess(true); + await ledgerDelay(1500); + setCurrentStepIndex(1); + try { + const signedTxs = await signStxTransaction( + transport, + initialStxTransactions[0], + selectedAccount.id + ); + setIsTxApproved(true); + await ledgerDelay(1500); + onConfirmClick([signedTxs]); + } catch (e) { + console.error(e); + setIsTxRejected(true); + setIsButtonDisabled(false); + } finally { + transport.close(); + } + }; + + const handleRetry = async () => { + setIsTxRejected(false); + setIsConnectSuccess(false); + setCurrentStepIndex(0); + }; + return ( <> {children} - + {!isSponsored && ( - + )} {isSponsored && {t('SPONSORED_TX_INFO')}} + setIsModalVisible(false)}> + {currentStepIndex === 0 ? ( + + ) : currentStepIndex === 1 ? ( + + ) : null} + + + + + ); } diff --git a/src/app/screens/transactionRequest/index.tsx b/src/app/screens/transactionRequest/index.tsx index 317b27467..04db2e22c 100644 --- a/src/app/screens/transactionRequest/index.tsx +++ b/src/app/screens/transactionRequest/index.tsx @@ -33,11 +33,10 @@ function TransactionRequest() { feeMultipliers, accountsList, selectedAccount, + isLedgerAccount, } = useWalletSelector(); const selectedNetwork = useNetworkSelector(); - const { - switchAccount, - } = useWalletReducer(); + const { switchAccount } = useWalletReducer(); const [unsignedTx, setUnsignedTx] = useState(); const [funcMetaData, setFuncMetaData] = useState(undefined); const [coinsMetaData, setCoinsMetaData] = useState(null); @@ -54,7 +53,7 @@ function TransactionRequest() { stxPublicKey, feeMultipliers!, selectedNetwork, - stxPendingTxData, + stxPendingTxData ); setUnsignedTx(unsignedSendStxTx); navigate('/confirm-stx-tx', { @@ -76,7 +75,9 @@ function TransactionRequest() { } = await getContractCallPromises(payload, stxAddress, selectedNetwork, stxPublicKey); setUnsignedTx(unSignedContractCall); setCoinsMetaData(coinMeta); - const invokedFuncMetaData: ContractFunction | undefined = contractInterface?.functions?.find((func) => func.name === payload.functionName); + const invokedFuncMetaData: ContractFunction | undefined = contractInterface?.functions?.find( + (func) => func.name === payload.functionName + ); if (invokedFuncMetaData) { setFuncMetaData(invokedFuncMetaData); } @@ -88,7 +89,7 @@ function TransactionRequest() { selectedNetwork, stxPublicKey, feeMultipliers!, - stxAddress, + stxAddress ); setUnsignedTx(response.contractDeployTx); setCodeBody(response.codeBody); @@ -108,7 +109,7 @@ function TransactionRequest() { }); return; } - if (payload.stxAddress !== selectedAccount?.stxAddress) { + if (payload.stxAddress !== selectedAccount?.stxAddress && !isLedgerAccount) { const account = accountsList.find((acc) => acc.stxAddress === payload.stxAddress); if (account) { switchAccount(account); diff --git a/src/locales/en.json b/src/locales/en.json index 549a8653d..e3a54f3d9 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -195,7 +195,31 @@ "PSBT_CANT_PARSE_ERROR_DESCRIPTION": "The requested transaction is invalid and cannot be processed please contact the developer of the requesting app for support", "PSBT_CANT_SIGN_ERROR_TITLE": "Failed to sign transaction", "YOU_WILL_TRANSFER": "You will transfer", - "YOU_WILL_RECEIVE": "You will receive" + "YOU_WILL_RECEIVE": "You will receive", + "LEDGER": { + "CONNECT": { + "TITLE": "Connect your hardware wallet", + "SUBTITLE": "To continue, plug and unlock your device.", + "ERROR_TITLE": "Connection error", + "ERROR_SUBTITLE": "We haven’t been able to connect to your device." + }, + "CONFIRM": { + "TITLE": "Confirm the transaction", + "SUBTITLE": "Authorise the transaction on your device to continue.", + "ERROR_TITLE": "Transaction denied", + "ERROR_SUBTITLE": "The transaction have been rejected. You can try again or close this tab." + }, + "SUCCESS": { + "TITLE": "Transaction confirmed", + "SUBTITLE": "Your transaction have been successfully broadcasted. You can close this tab." + }, + "CONFIRM_BUTTON": "Confirm", + "CONNECT_BUTTON": "Connect", + "CLOSE_BUTTON": "Close", + "CANCEL_BUTTON": "Cancel", + "SEE_TRANSACTION_BUTTON": "See transaction", + "RETRY_BUTTON": "Try again" + } }, "TX_ERRORS": { "INSUFFICIENT_BALANCE": "Insufficient balance",