From a469894d0c8cc0c867f2ae4e1e082367d29baf23 Mon Sep 17 00:00:00 2001 From: Den <36603049+dhriaznov@users.noreply.github.com> Date: Wed, 29 Nov 2023 15:35:56 +0100 Subject: [PATCH 01/28] [ENG-2879] feat: Batch sign PSBT in extension (#631) * [ENG-2879] feat: Batch sign PSBT in extension * Add transaction review modal * Improve tx review logic * Improve the tx review UI and logic * Improve the tx review UI * Improve the displaying of total receiving / transferring inscriptions * Uncomment and update the `checkIfMismatch` func for the array of psbts * Add `circularProgress` component for the loading state, improve the batch tx signing logic * Update the `useSignBatchPsbtTx` hook * Finalize the batch psbt signing logic * Add a `todo` for the `broadcast` bool property * Remove the redundant text on the tx success screen * Update the `sats-connect` package version and `broadcast` property logic * Separate the tx broadcasting logic for batch tx signing * Make some code fixes according to code review comments * Make some code fixes according to code review comments * Update the `useDetectOrdinalInSignPsbt` hook logic * Upgrade the `sats-connect` package * Update the copy * Fix `confirmSignPsbt` function response usage * Fix `useDetectOrdinalInSignPsbt` hook for batch tx signing * Update `sats-connect` version, make psbt signing delay shorter, and try-catch for broadcasting * Show an error when not all txs were broadcasted * Remove the broadcasting logic * Update the `sats-connect` version * chore: bump sats-connect to v1.3.0 * chore: ignore sats-connect type error until capability is added * fix: fix type errors * fix: more type errors * fix: typo --------- Co-authored-by: Tim Man --- package-lock.json | 14 +- package.json | 2 +- src/app/components/bottomModal/index.tsx | 26 +- src/app/components/button/index.tsx | 19 +- .../confirmStxTransactionComponent/index.tsx | 10 +- .../circularSvgAnimation.tsx | 9 + .../loadingTransactionStatus/index.tsx | 15 +- src/app/hooks/useDetectOrdinalInSignPsbt.ts | 71 ++- src/app/hooks/useSignBatchPsbtTx.ts | 65 +++ src/app/hooks/useSignPsbtTx.ts | 2 +- src/app/routes/index.tsx | 9 + .../screens/authenticationRequest/index.tsx | 6 +- .../screens/confirmNftTransaction/index.tsx | 4 +- .../screens/ledger/addStxAddress/index.tsx | 6 +- .../ledger/confirmLedgerTransaction/index.tsx | 10 +- .../ledger/importLedgerAccount/index.tsx | 14 +- .../importLedgerAccount/stepControls.tsx | 2 +- .../verifyLedgerAccountAddress/index.tsx | 50 +- .../bundleItemsComponent.tsx | 233 +++++++++ .../screens/signBatchPsbtRequest/index.tsx | 494 ++++++++++++++++++ src/app/screens/signPsbtRequest/index.tsx | 41 +- src/app/screens/signatureRequest/index.tsx | 4 +- .../signatureRequestStructuredData.tsx | 2 +- src/common/types/inpage-types.ts | 7 + src/common/types/message-types.ts | 15 + src/common/utils/ledger.ts | 2 +- .../utils/legacy-external-message-handler.ts | 21 + src/common/utils/route-urls.ts | 1 + src/content-scripts/content-script.ts | 15 +- src/inpage/sats.inpage.ts | 30 ++ src/locales/en.json | 23 +- 31 files changed, 1083 insertions(+), 139 deletions(-) create mode 100644 src/app/hooks/useSignBatchPsbtTx.ts create mode 100644 src/app/screens/signBatchPsbtRequest/bundleItemsComponent.tsx create mode 100644 src/app/screens/signBatchPsbtRequest/index.tsx diff --git a/package-lock.json b/package-lock.json index 1a4544634..1127f1f8d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -62,7 +62,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", @@ -12513,9 +12513,9 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "node_modules/sats-connect": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/sats-connect/-/sats-connect-1.1.2.tgz", - "integrity": "sha512-nJmCV69WgMNGH9H2cTTH/HsO9wEYS7g0AvnCNNgY5fezEJmA6jNpRMP4Xidv/c0xWL1uGOUHPLJEEOSG4NoneQ==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/sats-connect/-/sats-connect-1.3.0.tgz", + "integrity": "sha512-qKWfqmvJDyPaDCawMa4wi7L+S1t8AfgzDn7835VAuqNlhb3RC+uME5kRuqItwyfHgFY6O8kJinQjWLhmV9PsmA==", "dependencies": { "jsontokens": "^4.0.1", "process": "^0.11.10", @@ -24292,9 +24292,9 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "sats-connect": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/sats-connect/-/sats-connect-1.1.2.tgz", - "integrity": "sha512-nJmCV69WgMNGH9H2cTTH/HsO9wEYS7g0AvnCNNgY5fezEJmA6jNpRMP4Xidv/c0xWL1uGOUHPLJEEOSG4NoneQ==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/sats-connect/-/sats-connect-1.3.0.tgz", + "integrity": "sha512-qKWfqmvJDyPaDCawMa4wi7L+S1t8AfgzDn7835VAuqNlhb3RC+uME5kRuqItwyfHgFY6O8kJinQjWLhmV9PsmA==", "requires": { "jsontokens": "^4.0.1", "process": "^0.11.10", diff --git a/package.json b/package.json index c15f2a14e..89110eee9 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/app/components/bottomModal/index.tsx b/src/app/components/bottomModal/index.tsx index f26cd9bf7..24a431f4b 100644 --- a/src/app/components/bottomModal/index.tsx +++ b/src/app/components/bottomModal/index.tsx @@ -8,17 +8,26 @@ 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; @@ -26,16 +35,9 @@ interface Props { onClose: () => void; overlayStylesOverriding?: {}; contentStylesOverriding?: {}; + className?: string; } -const CustomisedModal = styled(Modal)` - overflow-y: auto; - &::-webkit-scrollbar { - display: none; - } - position: absolute; -`; - function BottomModal({ header, children, @@ -43,6 +45,7 @@ function BottomModal({ onClose, overlayStylesOverriding, contentStylesOverriding, + className, }: Props) { const theme = useTheme(); const isGalleryOpen: boolean = document.documentElement.clientWidth > 360; @@ -79,6 +82,7 @@ function BottomModal({ ariaHideApp={false} style={customStyles} contentLabel="Example Modal" + className={className} > {header} diff --git a/src/app/components/button/index.tsx b/src/app/components/button/index.tsx index 2aa6735b4..c33a6f5d5 100644 --- a/src/app/components/button/index.tsx +++ b/src/app/components/button/index.tsx @@ -18,6 +18,7 @@ const Button = styled.button((props) => ({ width: '100%', height: 44, transition: 'all 0.1s ease', + columnGap: props.theme.spacing(3), ':disabled': { opacity: 0.4, cursor: 'not-allowed', @@ -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; @@ -93,6 +93,7 @@ function ActionButton({ className, src, icon, + iconPosition = 'left', text, onPress, processing = false, @@ -120,8 +121,9 @@ function ActionButton({ ) : ( <> {src && } - {icon && {icon}} + {icon && iconPosition === 'left' && {icon}} {text} + {icon && iconPosition === 'right' && {icon}} )} @@ -140,8 +142,9 @@ function ActionButton({ ) : ( <> {src && } - {icon && {icon}} + {icon && iconPosition === 'left' && {icon}} {text} + {icon && iconPosition === 'right' && {icon}} )} diff --git a/src/app/components/confirmStxTransactionComponent/index.tsx b/src/app/components/confirmStxTransactionComponent/index.tsx index 8eba65def..de189144e 100644 --- a/src/app/components/confirmStxTransactionComponent/index.tsx +++ b/src/app/components/confirmStxTransactionComponent/index.tsx @@ -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'; @@ -275,7 +275,7 @@ function ConfirmStxTransationComponent({ } setIsConnectSuccess(true); - await ledgerDelay(1500); + await delay(1500); setCurrentStepIndex(1); try { const signedTxs = await signLedgerStxTransaction({ @@ -284,7 +284,7 @@ function ConfirmStxTransationComponent({ addressIndex: selectedAccount.deviceAccountIndex, }); setIsTxApproved(true); - await ledgerDelay(1500); + await delay(1500); onConfirmClick([signedTxs]); } catch (e) { console.error(e); @@ -323,7 +323,9 @@ function ConfirmStxTransationComponent({ {(initialStxTransactions[0]?.payload as any)?.amount && ( void; }) { const [isCircleRested, setIsCircleRested] = useState(false); @@ -94,6 +96,13 @@ export function CircularSvgAnimation({ fill="none" > + {withLoadingBgCircle && status === 'LOADING' && ( + + )} @@ -167,7 +170,7 @@ export function LoadingTransactionStatus({ - {status === 'SUCCESS' && ( + {secondaryAction && status === 'SUCCESS' && ( { - const [loading, setLoading] = useState(false); - const [userReceivesOrdinal, setUserReceivesOrdinal] = useState(false); - const [bundleItemsData, setBundleItemsData] = useState([]); +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) => { + 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; diff --git a/src/app/hooks/useSignBatchPsbtTx.ts b/src/app/hooks/useSignBatchPsbtTx.ts new file mode 100644 index 000000000..343c54fa4 --- /dev/null +++ b/src/app/hooks/useSignBatchPsbtTx.ts @@ -0,0 +1,65 @@ +import { ExternalSatsMethods, MESSAGE_SOURCE } from '@common/types/message-types'; +import useWalletSelector from '@hooks/useWalletSelector'; +import { InputToSign, signPsbt } from '@secretkeylabs/xverse-core'; +import { decodeToken } from 'jsontokens'; +import { useLocation } from 'react-router-dom'; +import { SignMultiplePsbtPayload, SignMultipleTransactionOptions } from 'sats-connect'; +import useSeedVault from './useSeedVault'; + +const useSignBatchPsbtTx = () => { + const { accountsList, network } = useWalletSelector(); + const { search } = useLocation(); + const { getSeed } = useSeedVault(); + const params = new URLSearchParams(search); + const requestToken = params.get('signBatchPsbtRequest') ?? ''; + const request = decodeToken(requestToken) as any as SignMultipleTransactionOptions; + const tabId = params.get('tabId') ?? '0'; + + const confirmSignPsbt = async (psbt: SignMultiplePsbtPayload) => { + const txId = ''; + const seedPhrase = await getSeed(); + const signingResponse = await signPsbt( + seedPhrase, + accountsList, + psbt.inputsToSign, + psbt.psbtBase64, + false, + network.type, + ); + + return { + txId, + signingResponse, + }; + }; + + const cancelSignPsbt = () => { + const signingMessage = { + source: MESSAGE_SOURCE, + method: ExternalSatsMethods.signBatchPsbtResponse, + payload: { signBatchPsbtRequest: requestToken, signBatchPsbtResponse: 'cancel' }, + }; + chrome.tabs.sendMessage(+tabId, signingMessage); + }; + + const getSigningAddresses = (inputsToSign: InputToSign[]) => { + const signingAddresses: Array = []; + inputsToSign.forEach((inputToSign) => { + inputToSign.signingIndexes.forEach((signingIndex) => { + signingAddresses[signingIndex] = inputToSign.address; + }); + }); + return signingAddresses; + }; + + return { + payload: request.payload, + tabId, + requestToken, + getSigningAddresses, + confirmSignPsbt, + cancelSignPsbt, + }; +}; + +export default useSignBatchPsbtTx; diff --git a/src/app/hooks/useSignPsbtTx.ts b/src/app/hooks/useSignPsbtTx.ts index 4f8a17ee2..30cf0d0bf 100644 --- a/src/app/hooks/useSignPsbtTx.ts +++ b/src/app/hooks/useSignPsbtTx.ts @@ -60,7 +60,7 @@ const useSignPsbtTx = () => { chrome.tabs.sendMessage(+tabId, signingMessage); }; - const getSigningAddresses = (inputsToSign: Array) => { + const getSigningAddresses = (inputsToSign: InputToSign[]) => { const signingAddresses: Array = []; inputsToSign.forEach((inputToSign) => { inputToSign.signingIndexes.forEach((signingIndex) => { diff --git a/src/app/routes/index.tsx b/src/app/routes/index.tsx index 60dc5417b..6e939cfa5 100644 --- a/src/app/routes/index.tsx +++ b/src/app/routes/index.tsx @@ -62,6 +62,7 @@ import FiatCurrencyScreen from '@screens/settings/fiatCurrency'; import LockCountdown from '@screens/settings/lockCountdown'; import PrivacyPreferencesScreen from '@screens/settings/privacyPreferences'; import SignatureRequest from '@screens/signatureRequest'; +import SignBatchPsbtRequest from '@screens/signBatchPsbtRequest'; import SignPsbtRequest from '@screens/signPsbtRequest'; import Stacking from '@screens/stacking'; import SwapScreen from '@screens/swap'; @@ -249,6 +250,14 @@ const router = createHashRouter([ ), }, + { + path: 'batch-psbt-signing-request', + element: ( + + + + ), + }, { path: 'btc-send-request', element: ( diff --git a/src/app/screens/authenticationRequest/index.tsx b/src/app/screens/authenticationRequest/index.tsx index fbcf5a76a..0727a19f3 100644 --- a/src/app/screens/authenticationRequest/index.tsx +++ b/src/app/screens/authenticationRequest/index.tsx @@ -2,7 +2,7 @@ import ledgerConnectDefaultIcon from '@assets/img/ledger/ledger_connect_default. import ledgerConnectStxIcon from '@assets/img/ledger/ledger_import_connect_stx.svg'; import DappPlaceholderIcon from '@assets/img/webInteractions/authPlaceholder.svg'; import { MESSAGE_SOURCE } from '@common/types/message-types'; -import { ledgerDelay } from '@common/utils/ledger'; +import { delay } from '@common/utils/ledger'; import AccountHeaderComponent from '@components/accountHeader'; import BottomModal from '@components/bottomModal'; import ActionButton from '@components/button'; @@ -160,7 +160,7 @@ function AuthenticationRequest() { } setIsConnectSuccess(true); - await ledgerDelay(1500); + await delay(1500); setCurrentStepIndex(1); const profile = { @@ -180,7 +180,7 @@ function AuthenticationRequest() { profile, }); setIsTxApproved(true); - await ledgerDelay(1500); + await delay(1500); chrome.tabs.sendMessage(+(params.get('tabId') ?? '0'), { source: MESSAGE_SOURCE, payload: { diff --git a/src/app/screens/confirmNftTransaction/index.tsx b/src/app/screens/confirmNftTransaction/index.tsx index b4265d7fa..d93103bfb 100644 --- a/src/app/screens/confirmNftTransaction/index.tsx +++ b/src/app/screens/confirmNftTransaction/index.tsx @@ -6,10 +6,10 @@ import RecipientComponent from '@components/recipientComponent'; import BottomBar from '@components/tabBar'; import TopRow from '@components/topRow'; import TransactionDetailComponent from '@components/transactionDetailComponent'; +import useNftDetail from '@hooks/queries/useNftDetail'; import useStxWalletData from '@hooks/queries/useStxWalletData'; import useNetworkSelector from '@hooks/useNetwork'; import { useResetUserFlow } from '@hooks/useResetUserFlow'; -import useNftDetail from '@hooks/queries/useNftDetail'; import useWalletSelector from '@hooks/useWalletSelector'; import NftImage from '@screens/nftDashboard/nftImage'; import { broadcastSignedTransaction, StacksTransaction } from '@secretkeylabs/xverse-core'; @@ -71,7 +71,7 @@ function ConfirmNftTransaction() { const { id } = useParams(); const nftDetailQuery = useNftDetail(id!); - const nft = nftDetailQuery.data?.data + const nft = nftDetailQuery.data?.data; const { unsignedTx: unsignedTxHex, recipientAddress } = location.state; const unsignedTx = deserializeTransaction(unsignedTxHex); diff --git a/src/app/screens/ledger/addStxAddress/index.tsx b/src/app/screens/ledger/addStxAddress/index.tsx index 61d9d9460..04a2620d5 100644 --- a/src/app/screens/ledger/addStxAddress/index.tsx +++ b/src/app/screens/ledger/addStxAddress/index.tsx @@ -1,7 +1,7 @@ import checkCircleIcon from '@assets/img/ledger/check_circle.svg'; import ledgerConnectStxIcon from '@assets/img/ledger/ledger_import_connect_stx.svg'; import stxIcon from '@assets/img/ledger/stx_icon.svg'; -import { ledgerDelay } from '@common/utils/ledger'; +import { delay } from '@common/utils/ledger'; import ActionButton from '@components/button'; import LedgerConnectionView from '@components/ledger/connectLedgerView'; import LedgerFailView from '@components/ledger/failLedgerView'; @@ -74,7 +74,7 @@ function AddStxAddress(): JSX.Element { stxPublicKey: stacksCreds?.publicKey || '', }; await updateLedgerAccounts(ledgerAccount); - await ledgerDelay(1000); + await delay(1000); setCurrentStep(Steps.AddressAdded); setIsButtonDisabled(false); }; @@ -123,7 +123,7 @@ function AddStxAddress(): JSX.Element { } setIsConnectSuccess(true); - await ledgerDelay(1500); + await delay(1500); handleClickNext(); const stacksCreds = await importStxAccounts(true); diff --git a/src/app/screens/ledger/confirmLedgerTransaction/index.tsx b/src/app/screens/ledger/confirmLedgerTransaction/index.tsx index 0ee34da7e..0208186d5 100644 --- a/src/app/screens/ledger/confirmLedgerTransaction/index.tsx +++ b/src/app/screens/ledger/confirmLedgerTransaction/index.tsx @@ -5,7 +5,7 @@ import ledgerConnectDefaultIcon from '@assets/img/ledger/ledger_connect_default. import ledgerConnectStxIcon from '@assets/img/ledger/ledger_import_connect_stx.svg'; import ledgerConfirmOrdinalsIcon from '@assets/img/ledger/ordinals_icon_big.svg'; import { LedgerTransactionType } from '@common/types/ledger'; -import { ledgerDelay } from '@common/utils/ledger'; +import { delay } from '@common/utils/ledger'; import ActionButton from '@components/button'; import InfoContainer from '@components/infoContainer'; import LedgerConnectionView, { @@ -121,7 +121,7 @@ function ConfirmLedgerTransaction(): JSX.Element { const { value: txHex } = await result.next(); setIsFinalTxApproved(true); - await ledgerDelay(1500); + await delay(1500); const transactionId = await btcClient.sendRawTransaction(txHex || taprootSignedValue); setTxId(transactionId.tx.hash); setCurrentStep(Steps.TransactionConfirmed); @@ -144,7 +144,7 @@ function ConfirmLedgerTransaction(): JSX.Element { feeRate: feeRateInput?.toString(), }); setIsFinalTxApproved(true); - await ledgerDelay(1500); + await delay(1500); const transactionId = await btcClient.sendRawTransaction(result); setTxId(transactionId.tx.hash); setCurrentStep(Steps.TransactionConfirmed); @@ -165,7 +165,7 @@ function ConfirmLedgerTransaction(): JSX.Element { addressIndex, }); setIsFinalTxApproved(true); - await ledgerDelay(1500); + await delay(1500); const transactionHash = await broadcastSignedTransaction(result, selectedNetwork); setTxId(transactionHash); setCurrentStep(Steps.TransactionConfirmed); @@ -207,7 +207,7 @@ function ConfirmLedgerTransaction(): JSX.Element { } setIsConnectSuccess(true); - await ledgerDelay(1500); + await delay(1500); if ( type === 'ORDINALS' && diff --git a/src/app/screens/ledger/importLedgerAccount/index.tsx b/src/app/screens/ledger/importLedgerAccount/index.tsx index 78f912c87..9a7e5d83b 100644 --- a/src/app/screens/ledger/importLedgerAccount/index.tsx +++ b/src/app/screens/ledger/importLedgerAccount/index.tsx @@ -1,4 +1,4 @@ -import { getDeviceNewAccountIndex, getNewAccountId, ledgerDelay } from '@common/utils/ledger'; +import { delay, getDeviceNewAccountIndex, getNewAccountId } from '@common/utils/ledger'; import FullScreenHeader from '@components/ledger/fullScreenHeader'; import useWalletReducer from '@hooks/useWalletReducer'; import useWalletSelector from '@hooks/useWalletSelector'; @@ -210,7 +210,7 @@ function ImportLedger(): JSX.Element { ), }; await addLedgerAccount(ledgerAccount); - await ledgerDelay(1000); + await delay(1000); setCurrentStep(ImportLedgerSteps.ADDRESS_ADDED); setIsButtonDisabled(false); return; @@ -225,7 +225,7 @@ function ImportLedger(): JSX.Element { ordinalsPublicKey: ordinalsCreds?.publicKey || '', }; await updateLedgerAccounts(ledgerAccount); - await ledgerDelay(1000); + await delay(1000); setCurrentStep(ImportLedgerSteps.ADDRESS_ADDED); setIsButtonDisabled(false); return; @@ -238,12 +238,12 @@ function ImportLedger(): JSX.Element { stxPublicKey: stacksCreds?.publicKey || '', }; await updateLedgerAccounts(ledgerAccount); - await ledgerDelay(1000); + await delay(1000); setCurrentStep(ImportLedgerSteps.ADDRESS_ADDED); setIsButtonDisabled(false); } - await ledgerDelay(500); + await delay(500); setIsButtonDisabled(false); } catch (err) { console.error(err); @@ -291,7 +291,7 @@ function ImportLedger(): JSX.Element { await importStxAccounts(false); } setIsConnectSuccess(true); - await ledgerDelay(1500); + await delay(1500); if ( isBitcoinSelected && ledgerAccountsList?.find((account) => account.masterPubKey === masterFingerPrint) @@ -343,7 +343,7 @@ function ImportLedger(): JSX.Element { } const updatedAccount: Account = { ...accountToUpdate, accountName }; await updateLedgerAccounts(updatedAccount); - await ledgerDelay(1000); + await delay(1000); setIsButtonDisabled(false); handleClickNext(); } catch (err) { diff --git a/src/app/screens/ledger/importLedgerAccount/stepControls.tsx b/src/app/screens/ledger/importLedgerAccount/stepControls.tsx index df3b700a0..3ff9ae7c3 100644 --- a/src/app/screens/ledger/importLedgerAccount/stepControls.tsx +++ b/src/app/screens/ledger/importLedgerAccount/stepControls.tsx @@ -1,6 +1,6 @@ -import styled from 'styled-components'; import ActionButton from '@components/button'; import { useTranslation } from 'react-i18next'; +import styled from 'styled-components'; import { ImportLedgerSteps } from './types'; const ButtonContainer = styled.div((props) => ({ diff --git a/src/app/screens/ledger/verifyLedgerAccountAddress/index.tsx b/src/app/screens/ledger/verifyLedgerAccountAddress/index.tsx index 4049f628a..257161fdc 100644 --- a/src/app/screens/ledger/verifyLedgerAccountAddress/index.tsx +++ b/src/app/screens/ledger/verifyLedgerAccountAddress/index.tsx @@ -1,48 +1,48 @@ -import { useState } from 'react'; -import { useTranslation } from 'react-i18next'; -import { useTransition } from '@react-spring/web'; -import Transport from '@ledgerhq/hw-transport-webusb'; +import { delay } from '@common/utils/ledger'; import ActionButton from '@components/button'; +import InfoContainer from '@components/infoContainer'; +import FullScreenHeader from '@components/ledger/fullScreenHeader'; +import LedgerAddressComponent from '@components/ledger/ledgerAddressComponent'; +import useWalletSelector from '@hooks/useWalletSelector'; +import Transport from '@ledgerhq/hw-transport-webusb'; +import { useTransition } from '@react-spring/web'; import { importNativeSegwitAccountFromLedger, importStacksAccountFromLedger, importTaprootAccountFromLedger, } from '@secretkeylabs/xverse-core'; -import { ledgerDelay } from '@common/utils/ledger'; -import LedgerAddressComponent from '@components/ledger/ledgerAddressComponent'; -import useWalletSelector from '@hooks/useWalletSelector'; -import FullScreenHeader from '@components/ledger/fullScreenHeader'; -import { useLocation } from 'react-router-dom'; +import { useState } from 'react'; +import { useTranslation } from 'react-i18next'; import QRCode from 'react-qr-code'; -import InfoContainer from '@components/infoContainer'; +import { useLocation } from 'react-router-dom'; import btcIcon from '@assets/img/ledger/btc_icon.svg'; -import ordinalsIcon from '@assets/img/ledger/ordinals_icon.svg'; -import stxIcon from '@assets/img/ledger/stx_icon.svg'; +import checkCircleIcon from '@assets/img/ledger/check_circle.svg'; import ledgerConnectBtcIcon from '@assets/img/ledger/ledger_import_connect_btc.svg'; import ledgerConnectStxIcon from '@assets/img/ledger/ledger_import_connect_stx.svg'; -import checkCircleIcon from '@assets/img/ledger/check_circle.svg'; +import ordinalsIcon from '@assets/img/ledger/ordinals_icon.svg'; +import stxIcon from '@assets/img/ledger/stx_icon.svg'; import LedgerFailView from '@components/ledger/failLedgerView'; -import { DEFAULT_TRANSITION_OPTIONS } from '@utils/constants'; import useResetUserFlow from '@hooks/useResetUserFlow'; +import { DEFAULT_TRANSITION_OPTIONS } from '@utils/constants'; import LedgerConnectionView from '../../../components/ledger/connectLedgerView'; import { - Container, - OnBoardingContentContainer, - ActionButtonsContainer, ActionButtonContainer, - AddAddressHeaderContainer, - SelectAssetTitle, + ActionButtonsContainer, AddAddressDetailsContainer, - SelectAssetText, - QRCodeContainer, + AddAddressHeaderContainer, + AddressAddedContainer, + Container, CopyContainer, InfoAlertContainer, - AddressAddedContainer, - OnBoardingActionsContainer, - LedgerFailViewContainer, LedgerFailButtonsContainer, + LedgerFailViewContainer, + OnBoardingActionsContainer, + OnBoardingContentContainer, + QRCodeContainer, + SelectAssetText, + SelectAssetTitle, } from './index.styled'; enum Steps { @@ -186,7 +186,7 @@ function VerifyLedger(): JSX.Element { } setIsConnectSuccess(true); - await ledgerDelay(1500); + await delay(1500); handleClickNext(); if (isBitcoinSelected || isOrdinalSelected) { await importBtcAccounts(true); diff --git a/src/app/screens/signBatchPsbtRequest/bundleItemsComponent.tsx b/src/app/screens/signBatchPsbtRequest/bundleItemsComponent.tsx new file mode 100644 index 000000000..9e1f4850c --- /dev/null +++ b/src/app/screens/signBatchPsbtRequest/bundleItemsComponent.tsx @@ -0,0 +1,233 @@ +import Eye from '@assets/img/createPassword/Eye.svg'; +import Cross from '@assets/img/dashboard/X.svg'; +import IconOrdinal from '@assets/img/transactions/ordinal.svg'; +import RareSatAsset from '@components/rareSatAsset/rareSatAsset'; +import { animated, useSpring } from '@react-spring/web'; +import { getTruncatedAddress } from '@utils/helper'; +import { BundleItem, getBundleItemSubText } from '@utils/rareSats'; +import { useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import styled from 'styled-components'; + +const Container = styled.div((props) => ({ + display: 'flex', + flexDirection: 'column', + background: props.theme.colors.elevation1, + borderRadius: 12, + padding: '16px 16px', + justifyContent: 'center', + marginBottom: 12, +})); + +const RecipientTitleText = styled.h1((props) => ({ + ...props.theme.body_medium_m, + color: props.theme.colors.white_200, + marginBottom: 10, +})); + +const RowContainer = styled.div({ + display: 'flex', + flexDirection: 'row', + alignItems: 'center', +}); + +const TransparentButton = styled.button({ + background: 'transparent', + display: 'flex', + alignItems: 'center', + marginLeft: 10, +}); + +const Icon = styled.img((props) => ({ + marginRight: props.theme.spacing(4), + width: 32, + height: 32, + borderRadius: 30, +})); + +const TitleText = styled.h1((props) => ({ + ...props.theme.body_medium_m, + color: props.theme.colors.white_200, +})); + +const ValueText = styled.h1((props) => ({ + ...props.theme.body_medium_m, + color: props.theme.colors.white_0, +})); + +const SubValueText = styled.h1((props) => ({ + ...props.theme.body_m, + fontSize: 12, + color: props.theme.colors.white_400, +})); + +const InscriptionText = styled.h1((props) => ({ + ...props.theme.body_medium_m, + fontSize: 21, + marginTop: 24, + textAlign: 'center', + color: props.theme.colors.white[0], + overflowWrap: 'break-word', + wordWrap: 'break-word', + wordBreak: 'break-word', +})); + +const ColumnContainer = styled.div((props) => ({ + display: 'flex', + flexDirection: 'column', + flex: 1, + justifyContent: 'flex-end', + alignItems: 'flex-end', + marginTop: props.theme.spacing(8), +})); + +const CrossContainer = styled.div({ + display: 'flex', + marginTop: 10, + justifyContent: 'flex-end', + alignItems: 'flex-end', +}); + +const OrdinalOuterImageContainer = styled.div({ + justifyContent: 'center', + alignItems: 'center', + borderRadius: 2, + display: 'flex', + flexDirection: 'column', + flex: 1, +}); + +const OrdinalImageContainer = styled.div({ + width: '50%', +}); + +const OrdinalBackgroundContainer = styled(animated.div)({ + width: '100%', + height: '100%', + top: 0, + left: 0, + bottom: 0, + right: 0, + position: 'fixed', + zIndex: 10, + background: 'rgba(18, 21, 30, 0.8)', + backdropFilter: 'blur(16px)', + padding: 16, + display: 'flex', + flexDirection: 'column', +}); + +const EyeIcon = styled.img({ + width: 20, + height: 20, +}); + +interface Props { + items: BundleItem[]; + userReceivesOrdinal?: boolean; +} +function BundleItemsComponent({ items, userReceivesOrdinal = false }: Props) { + const { t } = useTranslation('translation'); + const [showOrdinal, setShowOrdinal] = useState(false); + const [chosenOrdinal, setChosenOrdinal] = useState(0); + const styles = useSpring({ + from: { + opacity: 0, + y: 24, + }, + to: { + y: 0, + opacity: 1, + }, + delay: 100, + }); + + const onCloseClick = () => { + setShowOrdinal(false); + }; + + const getItemId = (item) => { + if (item.type === 'inscription') { + return item.inscription.id; + } + if (item.type === 'inscribed-sat' || item.type === 'rare-sat') { + return item.number; + } + return ''; + }; + + const getDetail = (item) => { + if (item.type === 'inscription' || item.type === 'inscribed-sat') { + return item.inscription.content_type; + } + + return getBundleItemSubText({ + satType: item.type, + rareSatsType: item.rarity_ranking, + }); + }; + + const getTitle = (item) => { + if (item.type === 'inscription') { + return t('COMMON.INSCRIPTION'); + } + if (item.type === 'inscribed-sat') { + return t('RARE_SATS.INSCRIBED_SAT'); + } + return t('RARE_SATS.RARE_SAT'); + }; + + return ( + <> + + + {userReceivesOrdinal + ? t('CONFIRM_TRANSACTION.YOU_WILL_RECEIVE_IN_TOTAL') + : t('CONFIRM_TRANSACTION.YOU_WILL_TRANSFER_IN_TOTAL')} + + + {items.map((item, index) => ( + // eslint-disable-next-line react/no-array-index-key + + + {getTitle(item)} + + + {getTruncatedAddress(String(getItemId(item)))} + { + setChosenOrdinal(index); + setShowOrdinal(true); + }} + > + + + + {getDetail(item)} + + + ))} + + + {showOrdinal && ( + + + + cross + + + + + + + {`${getTitle(items[chosenOrdinal])} ${getItemId( + items[chosenOrdinal], + )} `} + + + )} + + ); +} + +export default BundleItemsComponent; diff --git a/src/app/screens/signBatchPsbtRequest/index.tsx b/src/app/screens/signBatchPsbtRequest/index.tsx new file mode 100644 index 000000000..5108e0cd2 --- /dev/null +++ b/src/app/screens/signBatchPsbtRequest/index.tsx @@ -0,0 +1,494 @@ +import { ExternalSatsMethods, MESSAGE_SOURCE } from '@common/types/message-types'; +import { delay } from '@common/utils/ledger'; +import AccountHeaderComponent from '@components/accountHeader'; +import BottomModal from '@components/bottomModal'; +import ActionButton from '@components/button'; +import InputOutputComponent from '@components/confirmBtcTransactionComponent/inputOutputComponent'; +import InfoContainer from '@components/infoContainer'; +import LoadingTransactionStatus from '@components/loadingTransactionStatus'; +import { ConfirmationStatus } from '@components/loadingTransactionStatus/circularSvgAnimation'; +import RecipientComponent from '@components/recipientComponent'; +import TransactionDetailComponent from '@components/transactionDetailComponent'; +import useDetectOrdinalInSignPsbt from '@hooks/useDetectOrdinalInSignPsbt'; +import useSignBatchPsbtTx from '@hooks/useSignBatchPsbtTx'; +import useWalletSelector from '@hooks/useWalletSelector'; +import { ArrowLeft, ArrowRight } from '@phosphor-icons/react'; +import { parsePsbt, satsToBtc } from '@secretkeylabs/xverse-core'; +import { isLedgerAccount } from '@utils/helper'; +import { BundleItem } from '@utils/rareSats'; +import BigNumber from 'bignumber.js'; +import { useCallback, useEffect, useMemo, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useLocation, useNavigate } from 'react-router-dom'; +import { MoonLoader } from 'react-spinners'; +import { SignMultiplePsbtPayload } from 'sats-connect'; +import styled from 'styled-components'; +import BundleItemComponent from '../signPsbtRequest/bundleItemsComponent'; +import BundleItemsComponent from './bundleItemsComponent'; + +const OuterContainer = styled.div` + display: flex; + flex: 1; + flex-direction: column; + overflow-y: auto; + &::-webkit-scrollbar { + display: none; + } +`; + +const Container = styled.div((props) => ({ + display: 'flex', + flexDirection: 'column', + flex: 1, + marginTop: props.theme.spacing(11), + marginLeft: props.theme.spacing(8), + marginRight: props.theme.spacing(8), +})); + +const LoaderContainer = styled.div((props) => ({ + display: 'flex', + flex: 1, + justifyContent: 'center', + alignItems: 'center', + marginTop: props.theme.spacing(12), +})); + +const ButtonContainer = styled.div((props) => ({ + display: 'flex', + flexDirection: 'row', + padding: props.theme.spacing(8), + paddingTop: props.theme.spacing(12), + paddingBottom: props.theme.spacing(20), +})); + +const TransparentButtonContainer = styled.div((props) => ({ + marginRight: props.theme.spacing(6), + width: '100%', +})); + +const ReviewTransactionText = styled.h1((props) => ({ + ...props.theme.headline_s, + color: props.theme.colors.white_0, + marginBottom: props.theme.spacing(12), + textAlign: 'left', +})); + +const BundleLinkContainer = styled.button((props) => ({ + display: 'flex', + alignItems: 'center', + backgroundColor: 'transparent', + color: props.theme.colors.tangerine, + transition: 'color 0.2s ease', + marginBottom: props.theme.spacing(6), + ':hover': { + color: props.theme.colors.tangerine_light, + }, +})); + +const BundleLinkText = styled.div((props) => ({ + ...props.theme.body_medium_m, + marginRight: props.theme.spacing(2), +})); + +const CustomizedModal = styled(BottomModal)` + display: flex; + flex-direction: column; + height: 100%; + max-height: 100% !important; + background-color: #181818 !important; +`; + +const CustomizedModalContainer = styled(Container)` + margin-top: 0; +`; + +const TxReviewModalControls = styled.div((props) => ({ + display: 'flex', + columnGap: props.theme.spacing(6), + padding: props.theme.spacing(8), + paddingTop: props.theme.spacing(12), + paddingBottom: props.theme.spacing(20), +})); + +interface TxResponse { + txId: string; + psbtBase64: string; +} + +function SignBatchPsbtRequest() { + const { btcAddress, ordinalsAddress, selectedAccount, network } = useWalletSelector(); + const navigate = useNavigate(); + const { t } = useTranslation('translation', { keyPrefix: 'CONFIRM_TRANSACTION' }); + const [expandInputOutputView, setExpandInputOutputView] = useState(false); + const { payload, confirmSignPsbt, cancelSignPsbt, getSigningAddresses, requestToken } = + useSignBatchPsbtTx(); + const [isSigning, setIsSigning] = useState(false); + const [isSigningComplete, setIsSigningComplete] = useState(false); + const [signingPsbtIndex, setSigningPsbtIndex] = useState(1); + const [hasOutputScript, setHasOutputScript] = useState(false); + const [currentPsbtIndex, setCurrentPsbtIndex] = useState(0); + const [reviewTransaction, setReviewTransaction] = useState(false); + const { search } = useLocation(); + const params = new URLSearchParams(search); + const tabId = params.get('tabId') ?? '0'; + const handleOrdinalAndOrdinalInfo = useDetectOrdinalInSignPsbt(); + const [userReceivesOrdinalArr, setUserReceivesOrdinalArr] = useState< + { bundleItemsData: BundleItem[]; userReceivesOrdinal: boolean }[] + >([]); + const [isLoading, setIsLoading] = useState(true); + + const handlePsbtParsing = useCallback( + (psbt: SignMultiplePsbtPayload, index: number) => { + try { + return parsePsbt(selectedAccount!, psbt.inputsToSign, psbt.psbtBase64, network.type); + } catch (err) { + navigate('/tx-status', { + state: { + txid: '', + currency: 'BTC', + errorTitle: t('PSBT_CANT_PARSE_ERROR_TITLE'), + error: t('PSBT_INDEX_CANT_PARSE_ERROR_DESCRIPTION', { index }), + browserTx: true, + }, + }); + return undefined; + } + }, + [selectedAccount, network.type], + ); + + const parsedPsbts = useMemo( + () => payload.psbts.map(handlePsbtParsing), + [handlePsbtParsing, payload.psbts], + ); + + const checkAddressMismatch = (input) => { + if (input.address !== btcAddress && input.address !== ordinalsAddress) { + navigate('/tx-status', { + state: { + txid: '', + currency: 'BTC', + error: t('ADDRESS_MISMATCH'), + browserTx: true, + }, + }); + } + }; + + const checkIfMismatch = () => { + if (payload.network.type !== network.type) { + navigate('/tx-status', { + state: { + txid: '', + currency: 'BTC', + error: t('NETWORK_MISMATCH'), + browserTx: true, + }, + }); + } + + payload.psbts.forEach((psbt) => psbt.inputsToSign.forEach(checkAddressMismatch)); + }; + + const checkIfUserReceivesOrdinals = async () => { + try { + const results = await Promise.all(parsedPsbts.map(handleOrdinalAndOrdinalInfo)); + setUserReceivesOrdinalArr(results); + } catch { + navigate('/tx-status', { + state: { + txid: '', + currency: 'BTC', + errorTitle: t('PSBT_CANT_PARSE_ERROR_TITLE'), + error: t('PSBT_CANT_PARSE_ERROR_DESCRIPTION'), + browserTx: true, + }, + }); + } finally { + setIsLoading(false); + } + }; + + useEffect(() => { + checkIfUserReceivesOrdinals(); + }, []); + + useEffect(() => { + checkIfMismatch(); + }, []); + + useEffect(() => { + if (parsedPsbts) { + let outputScriptDetected = false; + + parsedPsbts.forEach((psbt) => { + if (!psbt) { + return; + } + + if (psbt.outputs.some((output) => !!output.outputScript)) { + outputScriptDetected = true; + } + }); + + setHasOutputScript(outputScriptDetected); + } + }, [parsedPsbts]); + + const onSignPsbtConfirmed = async () => { + try { + if (isLedgerAccount(selectedAccount)) { + // setIsModalVisible(true); + return; + } + setIsSigning(true); + + const signedPsbts: TxResponse[] = []; + // eslint-disable-next-line no-restricted-syntax + for (const psbt of payload.psbts) { + // eslint-disable-next-line no-await-in-loop + await delay(100); + + // eslint-disable-next-line no-await-in-loop + const signedPsbt = await confirmSignPsbt(psbt); + signedPsbts.push({ + txId: signedPsbt.txId, + psbtBase64: signedPsbt.signingResponse, + }); + + if (payload.psbts.findIndex((item) => item === psbt) !== payload.psbts.length - 1) { + setSigningPsbtIndex((prevIndex) => prevIndex + 1); + } + } + + setIsSigningComplete(true); + setIsSigning(false); + + const signingMessage = { + source: MESSAGE_SOURCE, + method: ExternalSatsMethods.signBatchPsbtResponse, + payload: { + signBatchPsbtRequest: requestToken, + signBatchPsbtResponse: signedPsbts, + }, + }; + + chrome.tabs.sendMessage(+tabId, signingMessage); + } catch (err) { + setIsSigning(false); + setIsSigningComplete(false); + + if (err instanceof Error) { + navigate('/tx-status', { + state: { + txid: '', + currency: 'BTC', + errorTitle: t('PSBT_CANT_SIGN_ERROR_TITLE'), + error: err.message, + browserTx: true, + }, + }); + } + } + }; + + const onCancelClick = async () => { + cancelSignPsbt(); + window.close(); + }; + + const expandInputOutputSection = () => { + setExpandInputOutputView(!expandInputOutputView); + }; + + const closeCallback = () => { + window.close(); + }; + + const totalNetAmount = parsedPsbts.reduce( + (sum, psbt) => (psbt ? sum.plus(new BigNumber(psbt.netAmount.toString())) : sum), + new BigNumber(0), + ); + + const userReceivesOrdinals = userReceivesOrdinalArr + .filter((item) => item.userReceivesOrdinal) + .map((item) => item.bundleItemsData) + .flat(); + + const userTransfersOrdinals = userReceivesOrdinalArr + .filter((item) => !item.userReceivesOrdinal) + .map((item) => item.bundleItemsData) + .flat(); + + const signingStatus: ConfirmationStatus = isSigningComplete ? 'SUCCESS' : 'LOADING'; + + if (isSigning || isSigningComplete) { + return ( + + ); + } + + return ( + <> + + {isLoading ? ( + + + + ) : ( + <> + + {isLedgerAccount(selectedAccount) ? ( + + + + ) : ( + + + {t('SIGN_TRANSACTIONS', { count: parsedPsbts.length })} + + + setReviewTransaction(true)}> + {t('REVIEW_ALL')} + + + + {userTransfersOrdinals.length > 0 && ( + + )} + + {userReceivesOrdinals.length > 0 && ( + + )} + + + + + + {hasOutputScript && } + + )} + + + + + + + + + )} + { + setReviewTransaction(false); + setCurrentPsbtIndex(0); + }} + > + + + + {t('TRANSACTION')} {currentPsbtIndex + 1}/{parsedPsbts.length} + + + {userReceivesOrdinalArr[currentPsbtIndex]?.bundleItemsData?.map((bundleItem, index) => ( + + ))} + + + + + + {hasOutputScript && } + + + + {currentPsbtIndex > 0 && ( + { + setCurrentPsbtIndex((prevIndex) => prevIndex - 1); + }} + icon={} + /> + )} + {currentPsbtIndex < parsedPsbts.length - 1 && ( + { + setCurrentPsbtIndex((prevIndex) => prevIndex + 1); + }} + icon={} + iconPosition="right" + /> + )} + {currentPsbtIndex === parsedPsbts.length - 1 && ( + { + setReviewTransaction(false); + setCurrentPsbtIndex(0); + }} + /> + )} + + + + ); +} + +export default SignBatchPsbtRequest; diff --git a/src/app/screens/signPsbtRequest/index.tsx b/src/app/screens/signPsbtRequest/index.tsx index 880585f60..ed52dbe5e 100644 --- a/src/app/screens/signPsbtRequest/index.tsx +++ b/src/app/screens/signPsbtRequest/index.tsx @@ -1,7 +1,7 @@ import ledgerConnectDefaultIcon from '@assets/img/ledger/ledger_connect_default.svg'; import ledgerConnectBtcIcon from '@assets/img/ledger/ledger_import_connect_btc.svg'; import { ExternalSatsMethods, MESSAGE_SOURCE } from '@common/types/message-types'; -import { ledgerDelay } from '@common/utils/ledger'; +import { delay } from '@common/utils/ledger'; import AccountHeaderComponent from '@components/accountHeader'; import BottomModal from '@components/bottomModal'; import ActionButton from '@components/button'; @@ -24,6 +24,7 @@ import { Transport as TransportType, } from '@secretkeylabs/xverse-core'; import { isLedgerAccount } from '@utils/helper'; +import { BundleItem } from '@utils/rareSats'; import BigNumber from 'bignumber.js'; import { decodeToken } from 'jsontokens'; import { useEffect, useMemo, useState } from 'react'; @@ -72,8 +73,7 @@ const ButtonContainer = styled.div((props) => ({ })); const TransparentButtonContainer = styled.div((props) => ({ - marginLeft: props.theme.spacing(2), - marginRight: props.theme.spacing(2), + marginRight: props.theme.spacing(6), width: '100%', })); @@ -136,7 +136,10 @@ function SignPsbtRequest() { } }, [selectedAccount, payload.inputsToSign, payload.psbtBase64, network.type]); - const { loading, bundleItemsData, userReceivesOrdinal } = useDetectOrdinalInSignPsbt(parsedPsbt); + const handleOrdinalAndOrdinalInfo = useDetectOrdinalInSignPsbt(); + const [isLoading, setIsLoading] = useState(true); + const [userReceivesOrdinal, setUserReceivesOrdinal] = useState(false); + const [bundleItemsData, setBundleItemsData] = useState([]); const signingAddresses = useMemo( () => getSigningAddresses(payload.inputsToSign), [payload.inputsToSign], @@ -170,7 +173,7 @@ function SignPsbtRequest() { navigate('/tx-status', { state: { txid: '', - currency: 'STX', + currency: 'BTC', error: t('ADDRESS_MISMATCH'), browserTx: true, }, @@ -180,6 +183,30 @@ function SignPsbtRequest() { } }; + const checkIfUserReceivesOrdinal = async () => { + try { + const result = await handleOrdinalAndOrdinalInfo(parsedPsbt); + setBundleItemsData(result.bundleItemsData); + setUserReceivesOrdinal(result.userReceivesOrdinal); + } catch { + navigate('/tx-status', { + state: { + txid: '', + currency: 'BTC', + errorTitle: t('PSBT_CANT_PARSE_ERROR_TITLE'), + error: t('PSBT_CANT_PARSE_ERROR_DESCRIPTION'), + browserTx: true, + }, + }); + } finally { + setIsLoading(false); + } + }; + + useEffect(() => { + checkIfUserReceivesOrdinal(); + }, []); + useEffect(() => { checkIfMismatch(); }, []); @@ -298,7 +325,7 @@ function SignPsbtRequest() { } setIsConnectSuccess(true); - await ledgerDelay(1500); + await delay(1500); setCurrentStepIndex(1); try { @@ -347,7 +374,7 @@ function SignPsbtRequest() { return ( <> - {loading ? ( + {isLoading ? ( diff --git a/src/app/screens/signatureRequest/index.tsx b/src/app/screens/signatureRequest/index.tsx index b7505b25f..d5a6d64cb 100644 --- a/src/app/screens/signatureRequest/index.tsx +++ b/src/app/screens/signatureRequest/index.tsx @@ -2,7 +2,7 @@ import ledgerConnectDefaultIcon from '@assets/img/ledger/ledger_connect_default. import ledgerConnectBtcIcon from '@assets/img/ledger/ledger_import_connect_btc.svg'; import ledgerConnectStxIcon from '@assets/img/ledger/ledger_import_connect_stx.svg'; import { ExternalSatsMethods, MESSAGE_SOURCE } from '@common/types/message-types'; -import { ledgerDelay } from '@common/utils/ledger'; +import { delay } from '@common/utils/ledger'; import AccountHeaderComponent from '@components/accountHeader'; import BottomModal from '@components/bottomModal'; import ActionButton from '@components/button'; @@ -264,7 +264,7 @@ function SignatureRequest(): JSX.Element { } setIsConnectSuccess(true); - await ledgerDelay(1500); + await delay(1500); setCurrentStepIndex(1); try { diff --git a/src/app/screens/signatureRequest/signatureRequestStructuredData.tsx b/src/app/screens/signatureRequest/signatureRequestStructuredData.tsx index 317ca1c43..43277c646 100644 --- a/src/app/screens/signatureRequest/signatureRequestStructuredData.tsx +++ b/src/app/screens/signatureRequest/signatureRequestStructuredData.tsx @@ -14,7 +14,7 @@ export default function SignatureRequestStructuredData(props: SignatureRequestSt return ( diff --git a/src/common/types/inpage-types.ts b/src/common/types/inpage-types.ts index f7cf2c058..fff450ce5 100644 --- a/src/common/types/inpage-types.ts +++ b/src/common/types/inpage-types.ts @@ -8,6 +8,7 @@ export enum DomEventName { transactionRequest = 'stacksTransactionRequest', getAddressRequest = 'SatsAddressRequest', signPsbtRequest = 'SatsPsbtRequest', + signBatchPsbtRequest = 'SatsBatchPsbtRequest', signMessageRequest = 'SatsSignMessage', sendBtcRequest = 'SatsSendBtcRequest', createInscriptionRequest = 'SatsCreateInscriptionRequest', @@ -43,6 +44,12 @@ export interface SignPsbtRequestEventDetails { export type SignPsbtRequestEvent = CustomEvent; +export interface SignBatchPsbtRequestEventDetails { + signBatchPsbtRequest: string; +} + +export type SignBatchPsbtRequestEvent = CustomEvent; + export interface SignMessageRequestEventDetails { signMessageRequest: string; } diff --git a/src/common/types/message-types.ts b/src/common/types/message-types.ts index 3d7fd0a46..d2e9fc1e6 100644 --- a/src/common/types/message-types.ts +++ b/src/common/types/message-types.ts @@ -2,6 +2,7 @@ import { FinishedTxPayload, SignatureData, SponsoredFinishedTxPayload } from '@s import { CreateInscriptionResponse, GetAddressResponse, + SignMultipleTransactionsResponse, SignTransactionResponse, } from 'sats-connect'; @@ -104,7 +105,9 @@ export enum ExternalSatsMethods { getAddressRequest = 'getAddressRequest', getAddressResponse = 'getAddressResponse', signPsbtRequest = 'signPsbtRequest', + signBatchPsbtRequest = 'signBatchPsbtRequest', signPsbtResponse = 'signPsbtResponse', + signBatchPsbtResponse = 'signBatchPsbtResponse', signMessageRequest = 'signMessageRequest', signMessageResponse = 'signMessageResponse', sendBtcRequest = 'sendBtcRequest', @@ -125,6 +128,8 @@ export type GetAddressResponseMessage = Message< type SignPsbtRequestMessage = Message; +type SignBatchPsbtRequestMessage = Message; + export type SignPsbtResponseMessage = Message< ExternalSatsMethods.signPsbtResponse, { @@ -133,6 +138,14 @@ export type SignPsbtResponseMessage = Message< } >; +export type SignBatchPsbtResponseMessage = Message< + ExternalSatsMethods.signBatchPsbtResponse, + { + signBatchPsbtRequest: string; + signBatchPsbtResponse: SignMultipleTransactionsResponse | string; + } +>; + type SignMessageRequestMessage = Message; export type SignMessageResponseMessage = Message< @@ -169,6 +182,7 @@ export type CreateInscriptionResponseMessage = Message< export type SatsConnectMessageFromContentScript = | GetAddressRequestMessage | SignPsbtRequestMessage + | SignBatchPsbtRequestMessage | SignMessageRequestMessage | SendBtcRequestMessage | CreateInscriptionRequestMessage; @@ -176,6 +190,7 @@ export type SatsConnectMessageFromContentScript = export type SatsConnectMessageToContentScript = | GetAddressResponseMessage | SignPsbtResponseMessage + | SignBatchPsbtResponseMessage | SignMessageResponseMessage | SendBtcResponseMessage | CreateInscriptionResponseMessage; diff --git a/src/common/utils/ledger.ts b/src/common/utils/ledger.ts index 9a79c00ea..3095b9ce3 100644 --- a/src/common/utils/ledger.ts +++ b/src/common/utils/ledger.ts @@ -1,6 +1,6 @@ import { Account } from '@secretkeylabs/xverse-core'; -export const ledgerDelay = (ms: number) => +export const delay = (ms: number) => new Promise((res) => { setTimeout(res, ms); }); diff --git a/src/common/utils/legacy-external-message-handler.ts b/src/common/utils/legacy-external-message-handler.ts index 11e8d6f50..6736f89d0 100644 --- a/src/common/utils/legacy-external-message-handler.ts +++ b/src/common/utils/legacy-external-message-handler.ts @@ -209,6 +209,27 @@ export async function handleLegacyExternalMethodFormat( listenForOriginTabClose({ tabId }); break; } + case ExternalSatsMethods.signBatchPsbtRequest: { + const { urlParams, tabId } = makeSearchParamsWithDefaults(port, [ + ['signBatchPsbtRequest', payload], + ]); + + const { id } = await triggerRequestWindowOpen(RequestsRoutes.SignBatchBtcTx, urlParams); + listenForPopupClose({ + id, + tabId, + response: { + source: MESSAGE_SOURCE, + payload: { + signBatchPsbtRequest: payload, + signBatchPsbtResponse: 'cancel', + }, + method: ExternalSatsMethods.signBatchPsbtResponse, + }, + }); + listenForOriginTabClose({ tabId }); + break; + } case ExternalSatsMethods.signMessageRequest: { const { urlParams, tabId } = makeSearchParamsWithDefaults(port, [ ['signMessageRequest', payload], diff --git a/src/common/utils/route-urls.ts b/src/common/utils/route-urls.ts index 08cb067ae..efcb1ab47 100644 --- a/src/common/utils/route-urls.ts +++ b/src/common/utils/route-urls.ts @@ -5,6 +5,7 @@ enum RequestsRoutes { SignatureRequest = '/signature-request', AddressRequest = '/btc-select-address-request', SignBtcTx = '/psbt-signing-request', + SignBatchBtcTx = '/batch-psbt-signing-request', SendBtcTx = '/btc-send-request', CreateInscription = '/create-inscription', } diff --git a/src/content-scripts/content-script.ts b/src/content-scripts/content-script.ts index 049864e1a..c829c7110 100644 --- a/src/content-scripts/content-script.ts +++ b/src/content-scripts/content-script.ts @@ -4,9 +4,10 @@ import { DomEventName, GetAddressRequestEvent, SendBtcRequestEvent, + SignatureRequestEvent, + SignBatchPsbtRequestEvent, SignMessageRequestEvent, SignPsbtRequestEvent, - SignatureRequestEvent, TransactionRequestEvent, } from '@common/types/inpage-types'; import { @@ -145,6 +146,18 @@ document.addEventListener(DomEventName.signPsbtRequest, ((event: SignPsbtRequest }); }) as EventListener); +// Listen for a CustomEvent (Batch PSBT Signing request) coming from the web app +document.addEventListener(DomEventName.signBatchPsbtRequest, (( + event: SignBatchPsbtRequestEvent, +) => { + forwardDomEventToBackground({ + path: RequestsRoutes.SignBatchBtcTx, + payload: event.detail.signBatchPsbtRequest, + urlParam: 'signBatchPsbtRequest', + method: ExternalSatsMethods.signBatchPsbtRequest, + }); +}) as EventListener); + // Listen for a CustomEvent (Message Signing request) coming from the web app document.addEventListener(DomEventName.signMessageRequest, ((event: SignMessageRequestEvent) => { forwardDomEventToBackground({ diff --git a/src/inpage/sats.inpage.ts b/src/inpage/sats.inpage.ts index 9b270beef..4e14d1a21 100644 --- a/src/inpage/sats.inpage.ts +++ b/src/inpage/sats.inpage.ts @@ -3,6 +3,7 @@ import { DomEventName, GetAddressRequestEventDetails, SendBtcRequestEventDetails, + SignBatchPsbtRequestEventDetails, SignMessageRequestEventDetails, SignPsbtRequestEventDetails, } from '@common/types/inpage-types'; @@ -13,6 +14,7 @@ import { MESSAGE_SOURCE, SatsConnectMessageToContentScript, SendBtcResponseMessage, + SignBatchPsbtResponseMessage, SignMessageResponseMessage, SignPsbtResponseMessage, } from '@common/types/message-types'; @@ -20,6 +22,7 @@ import { BitcoinProvider, CreateInscriptionResponse, GetAddressResponse, + SignMultipleTransactionsResponse, SignTransactionResponse, } from 'sats-connect'; @@ -30,6 +33,7 @@ const isValidEvent = (event: MessageEvent, method: SatsConnectMessageToContentSc return correctSource && correctMethod && !!data.payload; }; +// @ts-ignore const SatsMethodsProvider: BitcoinProvider = { connect: async (btcAddressRequest): Promise => { const event = new CustomEvent(DomEventName.getAddressRequest, { @@ -73,6 +77,32 @@ const SatsMethodsProvider: BitcoinProvider = { window.addEventListener('message', handleMessage); }); }, + signMultipleTransactions: async ( + signBatchPsbtRequest: string, + ): Promise => { + const event = new CustomEvent( + DomEventName.signBatchPsbtRequest, + { + detail: { signBatchPsbtRequest }, + }, + ); + document.dispatchEvent(event); + return new Promise((resolve, reject) => { + const handleMessage = (eventMessage: MessageEvent) => { + if (!isValidEvent(eventMessage, ExternalSatsMethods.signBatchPsbtResponse)) return; + if (eventMessage.data.payload?.signBatchPsbtRequest !== signBatchPsbtRequest) return; + window.removeEventListener('message', handleMessage); + if (eventMessage.data.payload.signBatchPsbtResponse === 'cancel') { + reject(eventMessage.data.payload.signBatchPsbtResponse); + return; + } + if (typeof eventMessage.data.payload.signBatchPsbtResponse !== 'string') { + resolve(eventMessage.data.payload.signBatchPsbtResponse); + } + }; + window.addEventListener('message', handleMessage); + }); + }, signMessage: async (signMessageRequest: string): Promise => { const event = new CustomEvent(DomEventName.signMessageRequest, { detail: { signMessageRequest }, diff --git a/src/locales/en.json b/src/locales/en.json index 68a3871dc..806715d52 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -291,9 +291,14 @@ "MEMO": "Attached memo", "NETWORK": "Network", "FEES": "Fees", + "TRANSACTION": "Transaction", + "PREVIOUS": "Previous", + "NEXT": "Next", + "DONE": "Done", "ADVANCED_SETTING": "Advanced Settings", "EDIT_FEES": "Edit Fees", "CONFIRM": "Confirm", + "CONFIRM_ALL": "Confirm all", "CANCEL": "Cancel", "SATS": "sats", "CONFIRM_TX": "Confirm Transaction", @@ -302,23 +307,37 @@ "TOTAL": "Total", "AMOUNT_PLUS_FEES": "Amount + fees", "REVIEW_TRANSACTION": "Review transaction", + "SIGN_TRANSACTIONS": "Sign {{count}} transactions", "AMOUNT": "Amount", "YOUR_ADDRESS": "Your address", "FROM": "From", "INPUT": "Inputs", "INPUT_AND_OUTPUT": "Inputs & Outputs", - "OUTPUT": "Output", + "OUTPUT": "Outputs", "RECIPIENT": "Recipient", "ASSET": "Asset", + "CLOSE": "Close", + "SIGNING_TRANSACTIONS": "Signing transactions", + "BROADCASTING_TRANSACTIONS": "Broadcasting transactions", + "THIS_MAY_TAKE_A_FEW_MINUTES": "This may take a few minutes, please keep this window open.", + "TRANSACTIONS_BROADCASTED": "Transactions broadcasted", + "TRANSACTIONS_FAILED_TO_BROADCAST": "{{failed}}/{{total}} transactions failed to broadcast", + "CHECK_THE_APP_YOU_USE": "Please check the app you are using.", + "TRANSACTIONS_SIGNED": "Transactions signed", "NETWORK_MISMATCH": "There’s a mismatch between your active network and the network you’re logged with.", "ADDRESS_MISMATCH": "There’s a mismatch between your signing address and the address you’re logged with.", "PSBT_NO_BROADCAST_DISCLAIMER": "This transaction will not be broadcasted from your wallet. It may be broadcasted later by a third party.", + "PSBTS_NO_BROADCAST_DISCLAIMER": "These transactions will not be broadcasted from your wallet. They may be broadcasted later by a third party.", "PSBT_CANT_PARSE_ERROR_TITLE": "Transaction Error", - "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_PARSE_ERROR_DESCRIPTION": "The requested transaction is invalid and cannot be processed. Please contact the developer of the requesting app for support.", + "PSBT_INDEX_CANT_PARSE_ERROR_DESCRIPTION": "The requested transaction at {{index}} index 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", "To": "To", "YOU_WILL_TRANSFER": "You will transfer", "YOU_WILL_RECEIVE": "You will receive", + "YOU_WILL_TRANSFER_IN_TOTAL": "You will transfer in total", + "YOU_WILL_RECEIVE_IN_TOTAL": "You will receive in total", + "REVIEW_ALL": "Review all", "SCRIPT_OUTPUT": "Script output", "SCRIPT_OUTPUT_TX": "This transaction contains a script output. Be sure you trust the source of this transaction before confirming it.", "ORDINAL": "Ordinal", From fdc6b67b3a14cf5cf3c41f124710802d339ca7f7 Mon Sep 17 00:00:00 2001 From: Tim Man Date: Wed, 29 Nov 2023 22:37:32 +0800 Subject: [PATCH 02/28] release: v0.24.0 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1127f1f8d..b89008b49 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "xverse-web-extension", - "version": "0.23.2", + "version": "0.24.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "xverse-web-extension", - "version": "0.23.2", + "version": "0.24.0", "dependencies": { "@ledgerhq/hw-transport-webusb": "^6.27.13", "@phosphor-icons/react": "^2.0.10", diff --git a/package.json b/package.json index 89110eee9..76ccb152d 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "xverse-web-extension", "description": "A Bitcoin wallet for Web3", - "version": "0.23.2", + "version": "0.24.0", "private": true, "engines": { "node": "^18.18.2" From 6893ea3ccaae8f6a452f4cda2bb6c028026080ab Mon Sep 17 00:00:00 2001 From: Tim Man Date: Thu, 30 Nov 2023 16:15:31 +0800 Subject: [PATCH 03/28] feat: exotics v1 (#689) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: merge from upstream develop (#26) * release: v0.19.0 (#607) * release: v0.19.0 * update package-lock * bump xverse core version to fix fee issue --------- Co-authored-by: Yukan * Build analytics tracking for web-extension (#590) * Build analytics tracking for web-extension * Add `/privacy-preferences` screen * Build analytics tracking for web-extension * Update mixpanel tracking logic * Remove unused wallet action name * Add authorize data collection toggler and popup * Update tracking logic * Add translation keys * Update mixpanel tracking logic * Remove unused imports * Make some code changes after PR review, upgrade `xverse-core` package version * Upgrade `xverse-core` package version * Resolve git conflicts * Handling fees with thresholds (#601) * Handling fees with thresholds * Add margin under the high fees warning * Change the high fees warning position for Ordinals & Brc-20 txs * Show the warning if the initial fee from transaction is greater than the threshold * Add high fee warning for one-step brc20 transfer * update copy --------- Co-authored-by: Yukan * Refactor ledger-related logic (#586) * Add STX support for Ledger accounts * Update the copy and ledger account import logic * Update the ledger account import logic for STX support * Add `/add-stx-address-ledger` route for adding stx account * Remove the old case handling when there is no `ordinalsAddress` * Add STX address verification with ledger device * Remove the `/send-stx-ledger` path and update `/send-stx` to handle ledger * Handle regular STX transactions * Remove unused `/review-ledger-stx-tx` path, add STX NFT handling * Remove `/review-ledger-ft-tx` and `/send-ft-ledger` routes, update `/send-ft` to support ledger * Enable STX auth requests for ledger accounts that have an STX address * Add link to the auth popup to add the STX Ledger account * Update screen UI for STX NFT sending * Update copy for STX incoming tx signing * Update STX message signing logic * Update STX-related logic for ledger accounts * Update error handling for STX message signing with ledger * Get rid of `findLedgerAccountId` ledger util, move more copy to locales * Fix cropped button container for tx signing popup * Update address index definition for adding stx address * Update address index definition for address verification and stx tx confirmation * Update address index definition for stx jwt auth * Refactor ledger-related logic * Refactor ledger account import * Fix ledger account import when both BTC and STX options are selected * Refactor ledger address verification screen * Refactor ledger tx confirmation screen * Refactor Add stx address screen * Fix `unsignedTx` type * Add `StacksRecipient` type and make some small code fixes * Add `icon` prop for the `ActionButton` component * Change the ledger steps in a callback * Add types for ledger tx state objects * Fix account index for stx account import, update ledger tx types and utils * Change the import path for `StacksRecipient` * Change the steps numeration for ledger account import * Get rid of unused step changing logic for ledger account import * Add more error handling * Update xverse-core version for testing purposes * Remove caret symbol in xverse-core package * Fix CI build * Make a couple of code fixes according to PR review comments * Add more transaltions * Add the `DEFAULT_TRANSITION_OPTIONS` constant * Make separate components for Steps and StepControls for the Ledger account import flow * Fix style imports * Upgrade the `xverse-core` package version * Upgrade `xverse-core` package version * Disable PSBT tx signing for ledger accounts * update xverse-core * package-lock --------- Co-authored-by: Yukan --------- Co-authored-by: Yukan Co-authored-by: Den <36603049+dhriaznov@users.noreply.github.com> * chore: allow ci build version to pass env vars to build (#27) * feat/eng 2904 implement frontend on web extension (#5) * Tim/eng 2933 implement dashboard display of rare sats bundles (#1) * chore: create a hook for rare sats with placeholder data * chore: add some inscription placeholder with image * feat/eng 2933 implement dashboard rare sats tab (#4) * chore: create a collectiblesTabs component with react-tabs * refactor: move nft dashboard logic into hook * feat: move rare sats into separate tab * fix: add tab state styling and move to common.styled * feat: style collectibles header * fix: restyle all grid item colors and add total items * fix: supply total nfts and total rare sats number and type the collectiblesTabs props * style: comments * feat/eng 2934 implement sats bundle UI screen (#6) * Abdulhaseeb/eng 2930 implement settings screen updates (#7) * feat: enable rare sats from settings * fix: disabled UI * fix: typos * Abdulhaseeb/eng 2931 implement info dialogs (#8) * feat: enable rare sats from settings * feat: notice alert and rarities screen * feat: new feature dialog * fix: styles * feat: add UI for rare sats bundle in collectible item details (#2) * fix: ordinal thumbnails were broken (#10) * Add no collectibles and error screens (#11) * feat: integrate rare sats form core (#9) * feat: add rare sats item detail screen (#3) * feat: add UI for rare sats bundle in collectible item details * feat: implement rare sats details screen --------- Co-authored-by: Tim Man * feat: implement rare sats send screen (#12) * chore: use getUtxoOrdinalBundle to know if a inscription belongs to a bundle (#14) * Abdulhaseeb/eng 2938 implement rare sats confirm screen (#15) * feat: confirm rare sat tx * feat: added warning callout * feat: implement UI for rare sats thumbnails (#16) * feat: add UI for rare sats bundle in collectible item details * feat: implement rare sats details screen * feat: add rare sats thumbnathumbnails * chore: add missing mock data for testing inscriptions belonging to a bundle * Tim/eng 2959 set up test mocks (#17) * fix: react console errors * fix: send rare sat heading and sub text * fix: eslint errors * chore: add mock test cases in hook * fix: back button sometimes has no history * fix: revert merge change * fix: should be no commoners in mock tests * feat: added thumbnail in confirm screen (#18) * fix: react console errors * fix: send rare sat heading and sub text * fix: eslint errors * chore: add mock test cases in hook * fix: back button sometimes has no history * fix: revert merge change * fix: should be no commoners in mock tests * feat: added thumbnail in confirm screen * fix: ui --------- Co-authored-by: Tim Man * fix: rarities screen UI (#19) * chore: fix some todos and issues in rare sats feature (#20) * chore: Unify assets and rarity label in rarityTile and rareSatIcon components * chore: create bundle asset and use it in RareSatsTabGridItem and confirm send screen * chore: add bundle asset component in send screen * chore: make glow optional and remove it from inscribed rare sats asset * chore: add assets to bundle gallery view * chore: remove unsupported media callout. add margin bottom and fix cannot send rare sat individually for gallery view * fix: styling on gallery view for dashboard and rare sats bundle grids (#22) * fix: styling on gallery view for dashboard and rare sats bundle grids * fix: add column layout for rare sats bundle gallery view * fix: rare sats bundle back should always fo to gallery * fix: header spacing rare sats bundle and load more button * feat: add isLoading state for rare sats tab * fix: remove is owned by active account check the user flow resets whenever account changes, so this check is no longer necessary * feat: save collectibles tab index in query params * fix: restore ledger open in new tab behaviour * fix: show info panel regardless of empty state (#23) * chore: fix UI issues (#24) * chore: fix issues with dont see your rare sat banner * chore: fix ui issues in rare sats details screen * Update src/app/screens/nftDashboard/notice.tsx * refactor: use library components and minor styling fixes * fix: rare sat bundle send button width * fix: add separator in gallery view rare sats bundle and styling fixes * fix: minor style fix --------- Co-authored-by: Tim Man * fix: branch merge errors * chore: remove mock data (#28) * chore: remove mock data * chore: add extenral links to scan tool and rare sats post * chore: move external link urls to constants file --------- Co-authored-by: Abdul Haseeb Co-authored-by: fede erbes Co-authored-by: Victor Kirov * release v0.20.0 (#29) * release: v0.19.0 (#607) * release: v0.19.0 * update package-lock * bump xverse core version to fix fee issue --------- Co-authored-by: Yukan * Build analytics tracking for web-extension (#590) * Build analytics tracking for web-extension * Add `/privacy-preferences` screen * Build analytics tracking for web-extension * Update mixpanel tracking logic * Remove unused wallet action name * Add authorize data collection toggler and popup * Update tracking logic * Add translation keys * Update mixpanel tracking logic * Remove unused imports * Make some code changes after PR review, upgrade `xverse-core` package version * Upgrade `xverse-core` package version * Resolve git conflicts * Handling fees with thresholds (#601) * Handling fees with thresholds * Add margin under the high fees warning * Change the high fees warning position for Ordinals & Brc-20 txs * Show the warning if the initial fee from transaction is greater than the threshold * Add high fee warning for one-step brc20 transfer * update copy --------- Co-authored-by: Yukan * Refactor ledger-related logic (#586) * Add STX support for Ledger accounts * Update the copy and ledger account import logic * Update the ledger account import logic for STX support * Add `/add-stx-address-ledger` route for adding stx account * Remove the old case handling when there is no `ordinalsAddress` * Add STX address verification with ledger device * Remove the `/send-stx-ledger` path and update `/send-stx` to handle ledger * Handle regular STX transactions * Remove unused `/review-ledger-stx-tx` path, add STX NFT handling * Remove `/review-ledger-ft-tx` and `/send-ft-ledger` routes, update `/send-ft` to support ledger * Enable STX auth requests for ledger accounts that have an STX address * Add link to the auth popup to add the STX Ledger account * Update screen UI for STX NFT sending * Update copy for STX incoming tx signing * Update STX message signing logic * Update STX-related logic for ledger accounts * Update error handling for STX message signing with ledger * Get rid of `findLedgerAccountId` ledger util, move more copy to locales * Fix cropped button container for tx signing popup * Update address index definition for adding stx address * Update address index definition for address verification and stx tx confirmation * Update address index definition for stx jwt auth * Refactor ledger-related logic * Refactor ledger account import * Fix ledger account import when both BTC and STX options are selected * Refactor ledger address verification screen * Refactor ledger tx confirmation screen * Refactor Add stx address screen * Fix `unsignedTx` type * Add `StacksRecipient` type and make some small code fixes * Add `icon` prop for the `ActionButton` component * Change the ledger steps in a callback * Add types for ledger tx state objects * Fix account index for stx account import, update ledger tx types and utils * Change the import path for `StacksRecipient` * Change the steps numeration for ledger account import * Get rid of unused step changing logic for ledger account import * Add more error handling * Update xverse-core version for testing purposes * Remove caret symbol in xverse-core package * Fix CI build * Make a couple of code fixes according to PR review comments * Add more transaltions * Add the `DEFAULT_TRANSITION_OPTIONS` constant * Make separate components for Steps and StepControls for the Ledger account import flow * Fix style imports * Upgrade the `xverse-core` package version * Upgrade `xverse-core` package version * Disable PSBT tx signing for ledger accounts * update xverse-core * package-lock --------- Co-authored-by: Yukan * release v0.20.0 * chore: bump to xverse-core 1.8.2 for bip322 signing fix * fix: collectibles dashboard should ignore invalid params errors (#33) * fix: put in a quick fix for more location.state serialization bugs (#34) * chore: add support for rare sats in tx confirmation screen (#35) * chore: add support for rare sats in tx confirmation screen * chore: remove logs and fix typo * chore: change unknown icon --------- Co-authored-by: Tim Man Co-authored-by: Den <36603049+dhriaznov@users.noreply.github.com> Co-authored-by: Tim Man Co-authored-by: fede erbes * chore: tidy up zips of each build run and the release uploads (#31) * feat: collectibles ui frontend (#30) * [ENG-2836] feat: Ordinals and BRC-20 collectibles UI * Improve ordinals & brc-20 collectibles fetching logic and UI * Collectible ordinal detail page (#32) * Tim/eng 2933 implement dashboard display of rare sats bundles (#1) * chore: create a hook for rare sats with placeholder data * chore: add some inscription placeholder with image * feat/eng 2933 implement dashboard rare sats tab (#4) * chore: create a collectiblesTabs component with react-tabs * refactor: move nft dashboard logic into hook * feat: move rare sats into separate tab * fix: add tab state styling and move to common.styled * feat: style collectibles header * fix: restyle all grid item colors and add total items * fix: supply total nfts and total rare sats number and type the collectiblesTabs props * style: comments * feat/eng 2934 implement sats bundle UI screen (#6) * Abdulhaseeb/eng 2930 implement settings screen updates (#7) * feat: enable rare sats from settings * fix: disabled UI * fix: typos * Abdulhaseeb/eng 2931 implement info dialogs (#8) * feat: enable rare sats from settings * feat: notice alert and rarities screen * feat: new feature dialog * fix: styles * feat: add UI for rare sats bundle in collectible item details (#2) * fix: ordinal thumbnails were broken (#10) * Add no collectibles and error screens (#11) * feat: integrate rare sats form core (#9) * feat: add rare sats item detail screen (#3) * feat: add UI for rare sats bundle in collectible item details * feat: implement rare sats details screen --------- Co-authored-by: Tim Man * feat: implement rare sats send screen (#12) * chore: use getUtxoOrdinalBundle to know if a inscription belongs to a bundle (#14) * Abdulhaseeb/eng 2938 implement rare sats confirm screen (#15) * feat: confirm rare sat tx * feat: added warning callout * feat: implement UI for rare sats thumbnails (#16) * feat: add UI for rare sats bundle in collectible item details * feat: implement rare sats details screen * feat: add rare sats thumbnathumbnails * chore: add missing mock data for testing inscriptions belonging to a bundle * Tim/eng 2959 set up test mocks (#17) * fix: react console errors * fix: send rare sat heading and sub text * fix: eslint errors * chore: add mock test cases in hook * fix: back button sometimes has no history * fix: revert merge change * fix: should be no commoners in mock tests * feat: added thumbnail in confirm screen (#18) * fix: react console errors * fix: send rare sat heading and sub text * fix: eslint errors * chore: add mock test cases in hook * fix: back button sometimes has no history * fix: revert merge change * fix: should be no commoners in mock tests * feat: added thumbnail in confirm screen * fix: ui --------- Co-authored-by: Tim Man * fix: rarities screen UI (#19) * chore: fix some todos and issues in rare sats feature (#20) * chore: Unify assets and rarity label in rarityTile and rareSatIcon components * chore: create bundle asset and use it in RareSatsTabGridItem and confirm send screen * chore: add bundle asset component in send screen * chore: make glow optional and remove it from inscribed rare sats asset * chore: add assets to bundle gallery view * chore: remove unsupported media callout. add margin bottom and fix cannot send rare sat individually for gallery view * fix: styling on gallery view for dashboard and rare sats bundle grids (#22) * fix: styling on gallery view for dashboard and rare sats bundle grids * fix: add column layout for rare sats bundle gallery view * fix: rare sats bundle back should always fo to gallery * fix: header spacing rare sats bundle and load more button * feat: add isLoading state for rare sats tab * fix: remove is owned by active account check the user flow resets whenever account changes, so this check is no longer necessary * feat: save collectibles tab index in query params * fix: restore ledger open in new tab behaviour * fix: show info panel regardless of empty state (#23) * Add ordinal attributes in detail screen * chore: fix UI issues (#24) * chore: fix issues with dont see your rare sat banner * chore: fix ui issues in rare sats details screen * Update src/app/screens/nftDashboard/notice.tsx * refactor: use library components and minor styling fixes * fix: rare sat bundle send button width * fix: add separator in gallery view rare sats bundle and styling fixes * fix: minor style fix --------- Co-authored-by: Tim Man * fix: branch merge errors * chore: remove mock data (#28) * chore: remove mock data * chore: add extenral links to scan tool and rare sats post * chore: move external link urls to constants file * Add scrollbar to ordinal detail screen * Adjust text alignment * Update Navigation Bar * Add Share button in extension ordinal detail screen * Include useInscription hook --------- Co-authored-by: Tim Man Co-authored-by: Abdul Haseeb Co-authored-by: fede erbes Co-authored-by: Victor Kirov * Fix `getNextPageParam` param in the `useAddressInscriptionCollections` hook * feat: inscriptions collection page UI components (#36) * chore: use xverse-core with api functions * feat: add ordinals collection screen and route * chore: add prettier organize imports plugin * chore: remove unused eslint disable line * fix: fix tab query params * feat: add ordinals collection route and screen * fix: revert scrollbar change * Tim/eng 2813 inscriptions collection page data fetching pagination (#37) * Make some small code tweaks * Complete Remaining Todos for Ordinal Detail Screen (#38) * Update ordinal detail Ui accodrding to MVP * Get ordinal details from api * Have placeholder incase market data does not exist * Fix navigation and address comments * feat: collection grid items (#39) * Add content skeleton loader for Collectibles tab * Fix border radius for Collectibles skeleton loader * Fix the loader condition in Collectibles tab * Update BRC20 token collection thumbnail in main collectible page * Tim/eng 2833 inscriptions full screen responsive main page collection (#41) * feat: style the send screen including responsive layout * style: use theme radius Co-authored-by: Den <36603049+dhriaznov@users.noreply.github.com> --------- Co-authored-by: Den <36603049+dhriaznov@users.noreply.github.com> * Display collection market data (#40) * Get collection amrket data * Fix alignment on collection page * chore: revert the large package-lock.json diff * Update receive screen and bottom modal (#42) * Update recieve screen * Add updated QR code in recieve screen * Update receive modal * Fix getNextPageParam for const useAddressInscriptionCollections = () => { * Remove unused package * fix: use theme radius Co-authored-by: Den <36603049+dhriaznov@users.noreply.github.com> * fix: use theme radius Co-authored-by: Den <36603049+dhriaznov@users.noreply.github.com> * Address comments * chore: remove empty unreferenced component * Remove unused background color * fix: layout and spacing on updated bottom modal, receive nft --------- Co-authored-by: Tim Man Co-authored-by: Den <36603049+dhriaznov@users.noreply.github.com> * fix color for custom switch and code typo (#43) * unstaged package-lock.json * fix: update all custom switch colors --------- Co-authored-by: Tim Man * fix: inscriptions grid fixes (#44) * chore: bump core version to 1.9.1 * Update function to recognise and parse brc20 tokens (#46) * Update skeleton loader logic for Collectibles UI (#45) * Update skeleton loader logic for Collectibles UI * Add a separate `TilesSkeletonLoader` component, add skeleton loader on the Collection page * Add ordinal detail page skeleton loader for the extension view * Add skeleton loader for the ordinal image component * fix: collectibles fixes from review (#47) * Fix minor ui bugs in collectible screen (#48) --------- Co-authored-by: Imamah-Zafar <88320460+Imamah-Zafar@users.noreply.github.com> Co-authored-by: Tim Man Co-authored-by: Abdul Haseeb Co-authored-by: fede erbes Co-authored-by: Victor Kirov Co-authored-by: Duska.T <55587184+DuskaT021@users.noreply.github.com> * Release/0.21.0 (#49) * chore: merge upstream develop (#53) * release: v0.19.0 (#607) * release: v0.19.0 * update package-lock * bump xverse core version to fix fee issue --------- Co-authored-by: Yukan * Build analytics tracking for web-extension (#590) * Build analytics tracking for web-extension * Add `/privacy-preferences` screen * Build analytics tracking for web-extension * Update mixpanel tracking logic * Remove unused wallet action name * Add authorize data collection toggler and popup * Update tracking logic * Add translation keys * Update mixpanel tracking logic * Remove unused imports * Make some code changes after PR review, upgrade `xverse-core` package version * Upgrade `xverse-core` package version * Resolve git conflicts * Handling fees with thresholds (#601) * Handling fees with thresholds * Add margin under the high fees warning * Change the high fees warning position for Ordinals & Brc-20 txs * Show the warning if the initial fee from transaction is greater than the threshold * Add high fee warning for one-step brc20 transfer * update copy --------- Co-authored-by: Yukan * Refactor ledger-related logic (#586) * Add STX support for Ledger accounts * Update the copy and ledger account import logic * Update the ledger account import logic for STX support * Add `/add-stx-address-ledger` route for adding stx account * Remove the old case handling when there is no `ordinalsAddress` * Add STX address verification with ledger device * Remove the `/send-stx-ledger` path and update `/send-stx` to handle ledger * Handle regular STX transactions * Remove unused `/review-ledger-stx-tx` path, add STX NFT handling * Remove `/review-ledger-ft-tx` and `/send-ft-ledger` routes, update `/send-ft` to support ledger * Enable STX auth requests for ledger accounts that have an STX address * Add link to the auth popup to add the STX Ledger account * Update screen UI for STX NFT sending * Update copy for STX incoming tx signing * Update STX message signing logic * Update STX-related logic for ledger accounts * Update error handling for STX message signing with ledger * Get rid of `findLedgerAccountId` ledger util, move more copy to locales * Fix cropped button container for tx signing popup * Update address index definition for adding stx address * Update address index definition for address verification and stx tx confirmation * Update address index definition for stx jwt auth * Refactor ledger-related logic * Refactor ledger account import * Fix ledger account import when both BTC and STX options are selected * Refactor ledger address verification screen * Refactor ledger tx confirmation screen * Refactor Add stx address screen * Fix `unsignedTx` type * Add `StacksRecipient` type and make some small code fixes * Add `icon` prop for the `ActionButton` component * Change the ledger steps in a callback * Add types for ledger tx state objects * Fix account index for stx account import, update ledger tx types and utils * Change the import path for `StacksRecipient` * Change the steps numeration for ledger account import * Get rid of unused step changing logic for ledger account import * Add more error handling * Update xverse-core version for testing purposes * Remove caret symbol in xverse-core package * Fix CI build * Make a couple of code fixes according to PR review comments * Add more transaltions * Add the `DEFAULT_TRANSITION_OPTIONS` constant * Make separate components for Steps and StepControls for the Ledger account import flow * Fix style imports * Upgrade the `xverse-core` package version * Upgrade `xverse-core` package version * Disable PSBT tx signing for ledger accounts * update xverse-core * package-lock --------- Co-authored-by: Yukan * Release/v0.20.0 (#611) * chore: merge from upstream develop (#26) * release: v0.19.0 (#607) * release: v0.19.0 * update package-lock * bump xverse core version to fix fee issue --------- Co-authored-by: Yukan * Build analytics tracking for web-extension (#590) * Build analytics tracking for web-extension * Add `/privacy-preferences` screen * Build analytics tracking for web-extension * Update mixpanel tracking logic * Remove unused wallet action name * Add authorize data collection toggler and popup * Update tracking logic * Add translation keys * Update mixpanel tracking logic * Remove unused imports * Make some code changes after PR review, upgrade `xverse-core` package version * Upgrade `xverse-core` package version * Resolve git conflicts * Handling fees with thresholds (#601) * Handling fees with thresholds * Add margin under the high fees warning * Change the high fees warning position for Ordinals & Brc-20 txs * Show the warning if the initial fee from transaction is greater than the threshold * Add high fee warning for one-step brc20 transfer * update copy --------- Co-authored-by: Yukan * Refactor ledger-related logic (#586) * Add STX support for Ledger accounts * Update the copy and ledger account import logic * Update the ledger account import logic for STX support * Add `/add-stx-address-ledger` route for adding stx account * Remove the old case handling when there is no `ordinalsAddress` * Add STX address verification with ledger device * Remove the `/send-stx-ledger` path and update `/send-stx` to handle ledger * Handle regular STX transactions * Remove unused `/review-ledger-stx-tx` path, add STX NFT handling * Remove `/review-ledger-ft-tx` and `/send-ft-ledger` routes, update `/send-ft` to support ledger * Enable STX auth requests for ledger accounts that have an STX address * Add link to the auth popup to add the STX Ledger account * Update screen UI for STX NFT sending * Update copy for STX incoming tx signing * Update STX message signing logic * Update STX-related logic for ledger accounts * Update error handling for STX message signing with ledger * Get rid of `findLedgerAccountId` ledger util, move more copy to locales * Fix cropped button container for tx signing popup * Update address index definition for adding stx address * Update address index definition for address verification and stx tx confirmation * Update address index definition for stx jwt auth * Refactor ledger-related logic * Refactor ledger account import * Fix ledger account import when both BTC and STX options are selected * Refactor ledger address verification screen * Refactor ledger tx confirmation screen * Refactor Add stx address screen * Fix `unsignedTx` type * Add `StacksRecipient` type and make some small code fixes * Add `icon` prop for the `ActionButton` component * Change the ledger steps in a callback * Add types for ledger tx state objects * Fix account index for stx account import, update ledger tx types and utils * Change the import path for `StacksRecipient` * Change the steps numeration for ledger account import * Get rid of unused step changing logic for ledger account import * Add more error handling * Update xverse-core version for testing purposes * Remove caret symbol in xverse-core package * Fix CI build * Make a couple of code fixes according to PR review comments * Add more transaltions * Add the `DEFAULT_TRANSITION_OPTIONS` constant * Make separate components for Steps and StepControls for the Ledger account import flow * Fix style imports * Upgrade the `xverse-core` package version * Upgrade `xverse-core` package version * Disable PSBT tx signing for ledger accounts * update xverse-core * package-lock --------- Co-authored-by: Yukan --------- Co-authored-by: Yukan Co-authored-by: Den <36603049+dhriaznov@users.noreply.github.com> * chore: allow ci build version to pass env vars to build (#27) * feat/eng 2904 implement frontend on web extension (#5) * Tim/eng 2933 implement dashboard display of rare sats bundles (#1) * chore: create a hook for rare sats with placeholder data * chore: add some inscription placeholder with image * feat/eng 2933 implement dashboard rare sats tab (#4) * chore: create a collectiblesTabs component with react-tabs * refactor: move nft dashboard logic into hook * feat: move rare sats into separate tab * fix: add tab state styling and move to common.styled * feat: style collectibles header * fix: restyle all grid item colors and add total items * fix: supply total nfts and total rare sats number and type the collectiblesTabs props * style: comments * feat/eng 2934 implement sats bundle UI screen (#6) * Abdulhaseeb/eng 2930 implement settings screen updates (#7) * feat: enable rare sats from settings * fix: disabled UI * fix: typos * Abdulhaseeb/eng 2931 implement info dialogs (#8) * feat: enable rare sats from settings * feat: notice alert and rarities screen * feat: new feature dialog * fix: styles * feat: add UI for rare sats bundle in collectible item details (#2) * fix: ordinal thumbnails were broken (#10) * Add no collectibles and error screens (#11) * feat: integrate rare sats form core (#9) * feat: add rare sats item detail screen (#3) * feat: add UI for rare sats bundle in collectible item details * feat: implement rare sats details screen --------- Co-authored-by: Tim Man * feat: implement rare sats send screen (#12) * chore: use getUtxoOrdinalBundle to know if a inscription belongs to a bundle (#14) * Abdulhaseeb/eng 2938 implement rare sats confirm screen (#15) * feat: confirm rare sat tx * feat: added warning callout * feat: implement UI for rare sats thumbnails (#16) * feat: add UI for rare sats bundle in collectible item details * feat: implement rare sats details screen * feat: add rare sats thumbnathumbnails * chore: add missing mock data for testing inscriptions belonging to a bundle * Tim/eng 2959 set up test mocks (#17) * fix: react console errors * fix: send rare sat heading and sub text * fix: eslint errors * chore: add mock test cases in hook * fix: back button sometimes has no history * fix: revert merge change * fix: should be no commoners in mock tests * feat: added thumbnail in confirm screen (#18) * fix: react console errors * fix: send rare sat heading and sub text * fix: eslint errors * chore: add mock test cases in hook * fix: back button sometimes has no history * fix: revert merge change * fix: should be no commoners in mock tests * feat: added thumbnail in confirm screen * fix: ui --------- Co-authored-by: Tim Man * fix: rarities screen UI (#19) * chore: fix some todos and issues in rare sats feature (#20) * chore: Unify assets and rarity label in rarityTile and rareSatIcon components * chore: create bundle asset and use it in RareSatsTabGridItem and confirm send screen * chore: add bundle asset component in send screen * chore: make glow optional and remove it from inscribed rare sats asset * chore: add assets to bundle gallery view * chore: remove unsupported media callout. add margin bottom and fix cannot send rare sat individually for gallery view * fix: styling on gallery view for dashboard and rare sats bundle grids (#22) * fix: styling on gallery view for dashboard and rare sats bundle grids * fix: add column layout for rare sats bundle gallery view * fix: rare sats bundle back should always fo to gallery * fix: header spacing rare sats bundle and load more button * feat: add isLoading state for rare sats tab * fix: remove is owned by active account check the user flow resets whenever account changes, so this check is no longer necessary * feat: save collectibles tab index in query params * fix: restore ledger open in new tab behaviour * fix: show info panel regardless of empty state (#23) * chore: fix UI issues (#24) * chore: fix issues with dont see your rare sat banner * chore: fix ui issues in rare sats details screen * Update src/app/screens/nftDashboard/notice.tsx * refactor: use library components and minor styling fixes * fix: rare sat bundle send button width * fix: add separator in gallery view rare sats bundle and styling fixes * fix: minor style fix --------- Co-authored-by: Tim Man * fix: branch merge errors * chore: remove mock data (#28) * chore: remove mock data * chore: add extenral links to scan tool and rare sats post * chore: move external link urls to constants file --------- Co-authored-by: Abdul Haseeb Co-authored-by: fede erbes Co-authored-by: Victor Kirov * release v0.20.0 * chore: bump to xverse-core 1.8.2 for bip322 signing fix * fix: collectibles dashboard should ignore invalid params errors (#33) * fix: put in a quick fix for more location.state serialization bugs (#34) * chore: add support for rare sats in tx confirmation screen (#35) * chore: add support for rare sats in tx confirmation screen * chore: remove logs and fix typo * chore: change unknown icon --------- Co-authored-by: Yukan Co-authored-by: Den <36603049+dhriaznov@users.noreply.github.com> Co-authored-by: Abdul Haseeb Co-authored-by: fede erbes Co-authored-by: Victor Kirov * Show BRC20 token transaction history (#523) * Show BRC20 token transaction history * Handle pending state * Use updated brc20 transaction history response * Update package.lock.json file * fix loading state * Update brc20 history tx title * Update core version to latest --------- Co-authored-by: Denys Hriaznov <36603049+dhriaznov@users.noreply.github.com> * fix rare sats settings copy (#612) * Release/0.21.0 (#613) * chore: merge from upstream develop (#26) * release: v0.19.0 (#607) * release: v0.19.0 * update package-lock * bump xverse core version to fix fee issue --------- Co-authored-by: Yukan * Build analytics tracking for web-extension (#590) * Build analytics tracking for web-extension * Add `/privacy-preferences` screen * Build analytics tracking for web-extension * Update mixpanel tracking logic * Remove unused wallet action name * Add authorize data collection toggler and popup * Update tracking logic * Add translation keys * Update mixpanel tracking logic * Remove unused imports * Make some code changes after PR review, upgrade `xverse-core` package version * Upgrade `xverse-core` package version * Resolve git conflicts * Handling fees with thresholds (#601) * Handling fees with thresholds * Add margin under the high fees warning * Change the high fees warning position for Ordinals & Brc-20 txs * Show the warning if the initial fee from transaction is greater than the threshold * Add high fee warning for one-step brc20 transfer * update copy --------- Co-authored-by: Yukan * Refactor ledger-related logic (#586) * Add STX support for Ledger accounts * Update the copy and ledger account import logic * Update the ledger account import logic for STX support * Add `/add-stx-address-ledger` route for adding stx account * Remove the old case handling when there is no `ordinalsAddress` * Add STX address verification with ledger device * Remove the `/send-stx-ledger` path and update `/send-stx` to handle ledger * Handle regular STX transactions * Remove unused `/review-ledger-stx-tx` path, add STX NFT handling * Remove `/review-ledger-ft-tx` and `/send-ft-ledger` routes, update `/send-ft` to support ledger * Enable STX auth requests for ledger accounts that have an STX address * Add link to the auth popup to add the STX Ledger account * Update screen UI for STX NFT sending * Update copy for STX incoming tx signing * Update STX message signing logic * Update STX-related logic for ledger accounts * Update error handling for STX message signing with ledger * Get rid of `findLedgerAccountId` ledger util, move more copy to locales * Fix cropped button container for tx signing popup * Update address index definition for adding stx address * Update address index definition for address verification and stx tx confirmation * Update address index definition for stx jwt auth * Refactor ledger-related logic * Refactor ledger account import * Fix ledger account import when both BTC and STX options are selected * Refactor ledger address verification screen * Refactor ledger tx confirmation screen * Refactor Add stx address screen * Fix `unsignedTx` type * Add `StacksRecipient` type and make some small code fixes * Add `icon` prop for the `ActionButton` component * Change the ledger steps in a callback * Add types for ledger tx state objects * Fix account index for stx account import, update ledger tx types and utils * Change the import path for `StacksRecipient` * Change the steps numeration for ledger account import * Get rid of unused step changing logic for ledger account import * Add more error handling * Update xverse-core version for testing purposes * Remove caret symbol in xverse-core package * Fix CI build * Make a couple of code fixes according to PR review comments * Add more transaltions * Add the `DEFAULT_TRANSITION_OPTIONS` constant * Make separate components for Steps and StepControls for the Ledger account import flow * Fix style imports * Upgrade the `xverse-core` package version * Upgrade `xverse-core` package version * Disable PSBT tx signing for ledger accounts * update xverse-core * package-lock --------- Co-authored-by: Yukan --------- Co-authored-by: Yukan Co-authored-by: Den <36603049+dhriaznov@users.noreply.github.com> * chore: allow ci build version to pass env vars to build (#27) * feat/eng 2904 implement frontend on web extension (#5) * Tim/eng 2933 implement dashboard display of rare sats bundles (#1) * chore: create a hook for rare sats with placeholder data * chore: add some inscription placeholder with image * feat/eng 2933 implement dashboard rare sats tab (#4) * chore: create a collectiblesTabs component with react-tabs * refactor: move nft dashboard logic into hook * feat: move rare sats into separate tab * fix: add tab state styling and move to common.styled * feat: style collectibles header * fix: restyle all grid item colors and add total items * fix: supply total nfts and total rare sats number and type the collectiblesTabs props * style: comments * feat/eng 2934 implement sats bundle UI screen (#6) * Abdulhaseeb/eng 2930 implement settings screen updates (#7) * feat: enable rare sats from settings * fix: disabled UI * fix: typos * Abdulhaseeb/eng 2931 implement info dialogs (#8) * feat: enable rare sats from settings * feat: notice alert and rarities screen * feat: new feature dialog * fix: styles * feat: add UI for rare sats bundle in collectible item details (#2) * fix: ordinal thumbnails were broken (#10) * Add no collectibles and error screens (#11) * feat: integrate rare sats form core (#9) * feat: add rare sats item detail screen (#3) * feat: add UI for rare sats bundle in collectible item details * feat: implement rare sats details screen --------- Co-authored-by: Tim Man * feat: implement rare sats send screen (#12) * chore: use getUtxoOrdinalBundle to know if a inscription belongs to a bundle (#14) * Abdulhaseeb/eng 2938 implement rare sats confirm screen (#15) * feat: confirm rare sat tx * feat: added warning callout * feat: implement UI for rare sats thumbnails (#16) * feat: add UI for rare sats bundle in collectible item details * feat: implement rare sats details screen * feat: add rare sats thumbnathumbnails * chore: add missing mock data for testing inscriptions belonging to a bundle * Tim/eng 2959 set up test mocks (#17) * fix: react console errors * fix: send rare sat heading and sub text * fix: eslint errors * chore: add mock test cases in hook * fix: back button sometimes has no history * fix: revert merge change * fix: should be no commoners in mock tests * feat: added thumbnail in confirm screen (#18) * fix: react console errors * fix: send rare sat heading and sub text * fix: eslint errors * chore: add mock test cases in hook * fix: back button sometimes has no history * fix: revert merge change * fix: should be no commoners in mock tests * feat: added thumbnail in confirm screen * fix: ui --------- Co-authored-by: Tim Man * fix: rarities screen UI (#19) * chore: fix some todos and issues in rare sats feature (#20) * chore: Unify assets and rarity label in rarityTile and rareSatIcon components * chore: create bundle asset and use it in RareSatsTabGridItem and confirm send screen * chore: add bundle asset component in send screen * chore: make glow optional and remove it from inscribed rare sats asset * chore: add assets to bundle gallery view * chore: remove unsupported media callout. add margin bottom and fix cannot send rare sat individually for gallery view * fix: styling on gallery view for dashboard and rare sats bundle grids (#22) * fix: styling on gallery view for dashboard and rare sats bundle grids * fix: add column layout for rare sats bundle gallery view * fix: rare sats bundle back should always fo to gallery * fix: header spacing rare sats bundle and load more button * feat: add isLoading state for rare sats tab * fix: remove is owned by active account check the user flow resets whenever account changes, so this check is no longer necessary * feat: save collectibles tab index in query params * fix: restore ledger open in new tab behaviour * fix: show info panel regardless of empty state (#23) * chore: fix UI issues (#24) * chore: fix issues with dont see your rare sat banner * chore: fix ui issues in rare sats details screen * Update src/app/screens/nftDashboard/notice.tsx * refactor: use library components and minor styling fixes * fix: rare sat bundle send button width * fix: add separator in gallery view rare sats bundle and styling fixes * fix: minor style fix --------- Co-authored-by: Tim Man * fix: branch merge errors * chore: remove mock data (#28) * chore: remove mock data * chore: add extenral links to scan tool and rare sats post * chore: move external link urls to constants file --------- Co-authored-by: Abdul Haseeb Co-authored-by: fede erbes Co-authored-by: Victor Kirov * release v0.20.0 (#29) * release: v0.19.0 (#607) * release: v0.19.0 * update package-lock * bump xverse core version to fix fee issue --------- Co-authored-by: Yukan * Build analytics tracking for web-extension (#590) * Build analytics tracking for web-extension * Add `/privacy-preferences` screen * Build analytics tracking for web-extension * Update mixpanel tracking logic * Remove unused wallet action name * Add authorize data collection toggler and popup * Update tracking logic * Add translation keys * Update mixpanel tracking logic * Remove unused imports * Make some code changes after PR review, upgrade `xverse-core` package version * Upgrade `xverse-core` package version * Resolve git conflicts * Handling fees with thresholds (#601) * Handling fees with thresholds * Add margin under the high fees warning * Change the high fees warning position for Ordinals & Brc-20 txs * Show the warning if the initial fee from transaction is greater than the threshold * Add high fee warning for one-step brc20 transfer * update copy --------- Co-authored-by: Yukan * Refactor ledger-related logic (#586) * Add STX support for Ledger accounts * Update the copy and ledger account import logic * Update the ledger account import logic for STX support * Add `/add-stx-address-ledger` route for adding stx account * Remove the old case handling when there is no `ordinalsAddress` * Add STX address verification with ledger device * Remove the `/send-stx-ledger` path and update `/send-stx` to handle ledger * Handle regular STX transactions * Remove unused `/review-ledger-stx-tx` path, add STX NFT handling * Remove `/review-ledger-ft-tx` and `/send-ft-ledger` routes, update `/send-ft` to support ledger * Enable STX auth requests for ledger accounts that have an STX address * Add link to the auth popup to add the STX Ledger account * Update screen UI for STX NFT sending * Update copy for STX incoming tx signing * Update STX message signing logic * Update STX-related logic for ledger accounts * Update error handling for STX message signing with ledger * Get rid of `findLedgerAccountId` ledger util, move more copy to locales * Fix cropped button container for tx signing popup * Update address index definition for adding stx address * Update address index definition for address verification and stx tx confirmation * Update address index definition for stx jwt auth * Refactor ledger-related logic * Refactor ledger account import * Fix ledger account import when both BTC and STX options are selected * Refactor ledger address verification screen * Refactor ledger tx confirmation screen * Refactor Add stx address screen * Fix `unsignedTx` type * Add `StacksRecipient` type and make some small code fixes * Add `icon` prop for the `ActionButton` component * Change the ledger steps in a callback * Add types for ledger tx state objects * Fix account index for stx account import, update ledger tx types and utils * Change the import path for `StacksRecipient` * Change the steps numeration for ledger account import * Get rid of unused step changing logic for ledger account import * Add more error handling * Update xverse-core version for testing purposes * Remove caret symbol in xverse-core package * Fix CI build * Make a couple of code fixes according to PR review comments * Add more transaltions * Add the `DEFAULT_TRANSITION_OPTIONS` constant * Make separate components for Steps and StepControls for the Ledger account import flow * Fix style imports * Upgrade the `xverse-core` package version * Upgrade `xverse-core` package version * Disable PSBT tx signing for ledger accounts * update xverse-core * package-lock --------- Co-authored-by: Yukan * release v0.20.0 * chore: bump to xverse-core 1.8.2 for bip322 signing fix * fix: collectibles dashboard should ignore invalid params errors (#33) * fix: put in a quick fix for more location.state serialization bugs (#34) * chore: add support for rare sats in tx confirmation screen (#35) * chore: add support for rare sats in tx confirmation screen * chore: remove logs and fix typo * chore: change unknown icon --------- Co-authored-by: Tim Man Co-authored-by: Den <36603049+dhriaznov@users.noreply.github.com> Co-authored-by: Tim Man Co-authored-by: fede erbes * chore: tidy up zips of each build run and the release uploads (#31) * feat: collectibles ui frontend (#30) * [ENG-2836] feat: Ordinals and BRC-20 collectibles UI * Improve ordinals & brc-20 collectibles fetching logic and UI * Collectible ordinal detail page (#32) * Tim/eng 2933 implement dashboard display of rare sats bundles (#1) * chore: create a hook for rare sats with placeholder data * chore: add some inscription placeholder with image * feat/eng 2933 implement dashboard rare sats tab (#4) * chore: create a collectiblesTabs component with react-tabs * refactor: move nft dashboard logic into hook * feat: move rare sats into separate tab * fix: add tab state styling and move to common.styled * feat: style collectibles header * fix: restyle all grid item colors and add total items * fix: supply total nfts and total rare sats number and type the collectiblesTabs props * style: comments * feat/eng 2934 implement sats bundle UI screen (#6) * Abdulhaseeb/eng 2930 implement settings screen updates (#7) * feat: enable rare sats from settings * fix: disabled UI * fix: typos * Abdulhaseeb/eng 2931 implement info dialogs (#8) * feat: enable rare sats from settings * feat: notice alert and rarities screen * feat: new feature dialog * fix: styles * feat: add UI for rare sats bundle in collectible item details (#2) * fix: ordinal thumbnails were broken (#10) * Add no collectibles and error screens (#11) * feat: integrate rare sats form core (#9) * feat: add rare sats item detail screen (#3) * feat: add UI for rare sats bundle in collectible item details * feat: implement rare sats details screen --------- Co-authored-by: Tim Man * feat: implement rare sats send screen (#12) * chore: use getUtxoOrdinalBundle to know if a inscription belongs to a bundle (#14) * Abdulhaseeb/eng 2938 implement rare sats confirm screen (#15) * feat: confirm rare sat tx * feat: added warning callout * feat: implement UI for rare sats thumbnails (#16) * feat: add UI for rare sats bundle in collectible item details * feat: implement rare sats details screen * feat: add rare sats thumbnathumbnails * chore: add missing mock data for testing inscriptions belonging to a bundle * Tim/eng 2959 set up test mocks (#17) * fix: react console errors * fix: send rare sat heading and sub text * fix: eslint errors * chore: add mock test cases in hook * fix: back button sometimes has no history * fix: revert merge change * fix: should be no commoners in mock tests * feat: added thumbnail in confirm screen (#18) * fix: react console errors * fix: send rare sat heading and sub text * fix: eslint errors * chore: add mock test cases in hook * fix: back button sometimes has no history * fix: revert merge change * fix: should be no commoners in mock tests * feat: added thumbnail in confirm screen * fix: ui --------- Co-authored-by: Tim Man * fix: rarities screen UI (#19) * chore: fix some todos and issues in rare sats feature (#20) * chore: Unify assets and rarity label in rarityTile and rareSatIcon components * chore: create bundle asset and use it in RareSatsTabGridItem and confirm send screen * chore: add bundle asset component in send screen * chore: make glow optional and remove it from inscribed rare sats asset * chore: add assets to bundle gallery view * chore: remove unsupported media callout. add margin bottom and fix cannot send rare sat individually for gallery view * fix: styling on gallery view for dashboard and rare sats bundle grids (#22) * fix: styling on gallery view for dashboard and rare sats bundle grids * fix: add column layout for rare sats bundle gallery view * fix: rare sats bundle back should always fo to gallery * fix: header spacing rare sats bundle and load more button * feat: add isLoading state for rare sats tab * fix: remove is owned by active account check the user flow resets whenever account changes, so this check is no longer necessary * feat: save collectibles tab index in query params * fix: restore ledger open in new tab behaviour * fix: show info panel regardless of empty state (#23) * Add ordinal attributes in detail screen * chore: fix UI issues (#24) * chore: fix issues with dont see your rare sat banner * chore: fix ui issues in rare sats details screen * Update src/app/screens/nftDashboard/notice.tsx * refactor: use library components and minor styling fixes * fix: rare sat bundle send button width * fix: add separator in gallery view rare sats bundle and styling fixes * fix: minor style fix --------- Co-authored-by: Tim Man * fix: branch merge errors * chore: remove mock data (#28) * chore: remove mock data * chore: add extenral links to scan tool and rare sats post * chore: move external link urls to constants file * Add scrollbar to ordinal detail screen * Adjust text alignment * Update Navigation Bar * Add Share button in extension ordinal detail screen * Include useInscription hook --------- Co-authored-by: Tim Man Co-authored-by: Abdul Haseeb Co-authored-by: fede erbes Co-authored-by: Victor Kirov * Fix `getNextPageParam` param in the `useAddressInscriptionCollections` hook * feat: inscriptions collection page UI components (#36) * chore: use xverse-core with api functions * feat: add ordinals collection screen and route * chore: add prettier organize imports plugin * chore: remove unused eslint disable line * fix: fix tab query params * feat: add ordinals collection route and screen * fix: revert scrollbar change * Tim/eng 2813 inscriptions collection page data fetching pagination (#37) * Make some small code tweaks * Complete Remaining Todos for Ordinal Detail Screen (#38) * Update ordinal detail Ui accodrding to MVP * Get ordinal details from api * Have placeholder incase market data does not exist * Fix navigation and address comments * feat: collection grid items (#39) * Add content skeleton loader for Collectibles tab * Fix border radius for Collectibles skeleton loader * Fix the loader condition in Collectibles tab * Update BRC20 token collection thumbnail in main collectible page * Tim/eng 2833 inscriptions full screen responsive main page collection (#41) * feat: style the send screen including responsive layout * style: use theme radius Co-authored-by: Den <36603049+dhriaznov@users.noreply.github.com> --------- Co-authored-by: Den <36603049+dhriaznov@users.noreply.github.com> * Display collection market data (#40) * Get collection amrket data * Fix alignment on collection page * chore: revert the large package-lock.json diff * Update receive screen and bottom modal (#42) * Update recieve screen * Add updated QR code in recieve screen * Update receive modal * Fix getNextPageParam for const useAddressInscriptionCollections = () => { * Remove unused package * fix: use theme radius Co-authored-by: Den <36603049+dhriaznov@users.noreply.github.com> * fix: use theme radius Co-authored-by: Den <36603049+dhriaznov@users.noreply.github.com> * Address comments * chore: remove empty unreferenced component * Remove unused background color * fix: layout and spacing on updated bottom modal, receive nft --------- Co-authored-by: Tim Man Co-authored-by: Den <36603049+dhriaznov@users.noreply.github.com> * fix color for custom switch and code typo (#43) * unstaged package-lock.json * fix: update all custom switch colors --------- Co-authored-by: Tim Man * fix: inscriptions grid fixes (#44) * chore: bump core version to 1.9.1 * Update function to recognise and parse brc20 tokens (#46) * Update skeleton loader logic for Collectibles UI (#45) * Update skeleton loader logic for Collectibles UI * Add a separate `TilesSkeletonLoader` component, add skeleton loader on the Collection page * Add ordinal detail page skeleton loader for the extension view * Add skeleton loader for the ordinal image component * fix: collectibles fixes from review (#47) * Fix minor ui bugs in collectible screen (#48) --------- Co-authored-by: Imamah-Zafar <88320460+Imamah-Zafar@users.noreply.github.com> Co-authored-by: Tim Man Co-authored-by: Abdul Haseeb Co-authored-by: fede erbes Co-authored-by: Victor Kirov Co-authored-by: Duska.T <55587184+DuskaT021@users.noreply.github.com> * release: v0.21.0 * Small fixes for the Collectibles UI loading state (#50) * Small fixes for the Collectibles UI * Fix loader appearance in the Collectibles tabs * Fix skeleton loader on the main Collectibles tab gallery view * Fix skeleton loader on the Collection page gallery view * Add skeleton loader for the ordinal detail page * fix: container padding on ordinals collection page * chore: remove debug lines --------- Co-authored-by: Tim Man * fix: more collectible UI fixes (#52) * chore: remove testAddress and TODOs * fix: brc20 status in ordinal details * fix: back button from nft details goes to nfts tab and back button from ordinal detail says back to collection or gallery * Minor ui fixes (#51) * sort brc20 inscription array * Fix styling * Remove unused funciton --------- Co-authored-by: Yukan Co-authored-by: Den <36603049+dhriaznov@users.noreply.github.com> Co-authored-by: Abdul Haseeb Co-authored-by: fede erbes Co-authored-by: Victor Kirov Co-authored-by: Imamah-Zafar <88320460+Imamah-Zafar@users.noreply.github.com> Co-authored-by: Duska.T <55587184+DuskaT021@users.noreply.github.com> * feat: revamp all colors (#588) * Tim/eng 2317 consistent dashboard buttons in extension (#618) * fix: use revamped swap svg * fix: hide swap button on testnet * Ledger send btc screen crash when going back and forward (#615) * Make `Authorize data collection` property turned on by default (#614) * Make `Authorize data collection` property turned on by default * Change the html tag and text color constant * [ENG-2961] Fix eslint errors for web extension (#617) * [ENG-2961] Fix eslint errors for web extension * [ENG-2961] Fix eslint errors for web extension * Update src/app/hooks/queries/ordinals/useInscriptionDetails.ts Co-authored-by: Victor Kirov * Update src/app/hooks/queries/useAppConfig.ts Co-authored-by: Victor Kirov --------- Co-authored-by: Victor Kirov * [ENG-3059] fix: Remove ordinal callout from send rare sats (#619) * [ENG-3059] fix: Remove ordinal callout from send rare sats * Update `amount` field validation error styling * chore: turn on tsc --noEmit precommit hook (#622) * chore: add tsc-files dev dep and run on lint staged pre commit also ran npm audit fix for a critical vuln and updated direct svg import typings * chore: use string for svg typescript declarations * chore: run eslint on repo before passing the workflow checks * feat: allow unpublished UTXOs as inputs to PSBTs (#621) * feat: allow unpublished UTXOs as inputs to PSBTs * Use correct status from response --------- Co-authored-by: Tim Man * [ENG-3039] fix: Skeleton loader for BRC-20 icons in home dashboard (#620) it can be merged, tested on ledger as well * Mahmoud/eng 2284 seedphrase vault (#510) * refactor wallet flow to use seed vault * code cleanup * update auth flow and seedphrase usage * lock dependencies and user xverse-core beta * migrate onboarding to use seed vault * use account switch method from wallet reducer hook * use seed vault for stx swaps * removed seedPhrase from wallet store * rebase fixes * small fixes * clear home screen errors * revert hashing algo change * fix swap hook * Revert "clear home screen errors" This reverts commit 55da10e60ead4c0798c40d61701b904085d6e603. * apply core changes * clear encrypted seed after successfully migrating * type fixes * update core version * onboarding guard refactor * update seed vault hook * use chromeStorage driver for session storage * updated core-lib version * refactor onboarding guard to use hasSeed * update core-lib * small fixes * update auth guard and update core version * updated csp * deprecate internal message handler * update wallet exists guard * overwrite any existing seed on restore wallet * update flow to explicitly clear vault for onboarding * update image csp * reset navigation if seed is not pre-set * update sendRareSat screen to use seed vault * add rich media csp policy * added authguard to settings screen * updated lock file * minor fixes * fresh npm install using v18 * lock with develop * adde new package updates * use fixed version of core * update to core lts version --------- Co-authored-by: Victor Kirov Co-authored-by: Tim Man * [ENG-3115] fix: UI issue in the legal screen (#624) * feat: add vout to the end of the tx id for unknown rarities (#623) * feat: add vout to the end of the tx id for unknown rarities * chore: sed replace forward slash in workflow file should replace all occurrences --------- Co-authored-by: Tim Man * Fix minor UI issues (#625) * Have thousandths separators for STX balance * Add scrollbar in message signing screen * Fix spacing in ordinal detail screen * Add loader for recover ordinal screen * fix: disabling ordinals user should not see them in the (#627) * fix: only display tab buttons when more than one tab (#629) * chore: turn on eslint rule and fix up some core internal imports (#630) * chore: turn on eslint rule and fix up some core internal imports but not all, as some are not exported yet * chore: escape . in eslint rule * chore: use a different eslint rule for this * release: v0.22.0 (#626) * fix: unable to change the network to testnet on the latest (#636) * chore: investigate npm dependabot warnings on extension (#635) * Hotfix/fix (#646) * release: v0.22.0-rc.4 to main (#642) * fix: refactor select currency logic to use reducer and unit test * chore: commit package-lock * fix: disable button when insufficient balance * fix: add token name to min received * fix: advanced settings modal on swaps * fix: use 4dp rounding on exchange rate * fix: toggle button should also toggle amounts * fix: function font style should be body_medium_m * feat: make swap confirmation buttons sticky bottom * fix: standardise button hover, active, disabled css ref: https://zeroheight.com/0683c9fa7/p/5270b4-buttons/b/32e1a2 * feat: add error state to slippage input * fix: use intended params for swap advance settings on apply handler * fix: padding on swap confirm buttons * fix: no exponents in fees block of swap confirmation * feat: add sponsor swap transaction UI and add a hook with placeholder * feat: bump xverse-core dep version and use sponsor2 url * fix: bump xverse-core version and handle sponsor transaction error * feat(Dashboard): update new action button style and add swap button * feat(Swap): add swap screen placeholder * feat(swap): add swap basic UI * feat(swap): add more UI components * feat(swap): add slippage * feat(swap): make BitCoin and Stacks option in the coin select modal * feat(swap): add token selection logic * feat: add swap data connection * feat(swap): add runSwap * chore(swap): bump alex-sdk version * fix(swap): slippage setting * feat: swap confirm layout * feat: add data bindings * fix: function name and copy address * feat: add advanced settings to the confirmation page * feat: bump alex-sdk to latest and fix the issue where from is the same as to * feat: add math.floor before converting to bigInt * fix: skip action when input is invalid * chore: update package.lock * feat(swap): update to support amm v1_1 pools * chore: fix lint warnings * fix: update swap svg stroke and add missing translation * fix: add key to fix react warnings * chore: fix lint warnings * fix: add error 700 border to swap token card * fix: update swap error message and restrict input to number * chore: prettier * style: add a not-allowed cursor to disabled buttons * fix: update translations * fix: use background elevation_1 * style: update swap details spacing * style: use unicode arrow right for swap route details * feat: implement toggle tokens button in swaps * feat: add reset slippage button to swaps * fix: refactor select currency logic to use reducer and unit test * chore: commit package-lock * fix: disable button when insufficient balance * fix: add token name to min received * fix: advanced settings modal on swaps * fix: use 4dp rounding on exchange rate * fix: toggle button should also toggle amounts * fix: function font style should be body_medium_m * feat: make swap confirmation buttons sticky bottom * fix: standardise button hover, active, disabled css ref: https://zeroheight.com/0683c9fa7/p/5270b4-buttons/b/32e1a2 * feat: add error state to slippage input * fix: use intended params for swap advance settings on apply handler * fix: padding on swap confirm buttons * fix: no exponents in fees block of swap confirmation * style: prettier * chore: update package-lock with npm i * chore: fix package-lock.json for correct xverse-core dep * chore: replace workflow dep with github api calls * chore: remove erroneous ` * Fix Edit fees popup error * Fix Edit fees popup error * Add receiving address verification for ledger accounts * Fix warning display condition on the ledger verification screen * Dont close popup on checkbox click * fix: fix for JSON serialisation of location.state * chore: use the test instance URL * chore: upgrade to alex-sdk 0.1.14 * Show wrong ledger device error * Add custom ledger account name validation * fix: turn react deps warning back on and fix stale callback * fix: move sponsor url to constants and add sponsor to swap unsigned tx * fix: display the transaction explorer link on successful sponsored tx * fix: set swap transaction fee to 0 if being sponsored * The transaction history not showing a pending send transaction that was just sent from the address * release v0.14.0 * chore: update xverse-core to published release 1.4.0 * chore: update swaps xverse sponsor 2 host * fix: remove btc from swaps coin modal. change was removed during merge * fix: remove static text describing swap route * Add Before Getting Started screen for Ledger onboarding * update copy * add links and update copy * chore: add husky and pre commit prettier to staged files * chore: run prettier --write ./src * fix: show inscription service fee fiat amount * Fix ledger account name UI issue * Show loader instead of previous transaction history data * comment out swaps button * Add localization for onboarding flow Ledger Live warning screen * chore: automatically do husky install on npm install * chore: prefer .husky for hooks * chore: run prettier again * fix: fix unsigned tx serialisation in advance settings for swaps * fix: use getNewNonce to ensure swaps transactions have correct nonce * docs: comment * bump xverse-core to v1.4.2 * fix: only show swaps button if not on a ledger account * fix: post condition info should display to/from depending on the swap * Fix regression issues v0.14.0-rc.1 * fix: ensure we are sending the correct ordinal * fix: ensure we are sending the correct ordinal * 0.14.1 * fix: remove unnecessary nested BalanceAmountText * fix: use semantic html instead of all h1 * fix: add a filter list to not display coins for swaps * Fix screen crash when changing account on /send-brc20 screen * fix: button should always be disabled when processing and show loading state in swap with await, and fix typing * Update /send-brc20 screen logic * use toString to convert value to BigNumber * chore: bump xverse-core to 1.5.0 * fix: not all tickers are all uppercase e.g. xBTC * fix: decouple alex swap from/to token list and visible token list * Fix the case when address verification screen flashes when using wrong device * Add an override switch for sponsoring transaction in swap screen * Update function param name * chore: bump core version to 1.6.0 (#560) * Allow send ordinal to self and display warning instead of error (#525) * feat: allow send ordinal to self and display warning instead of error * feat: remove send btc send to self error and use warning also adjust send ordinal warning if sending brc20 ordinal to self * Release v0.15.0 (#544) * bump version to v0.15.0 * Switch to alex sdk & url for sponsoring service Inspect error code and show sponsoring service info block feat: move alex sponsored transaction hook to new file revert: changes to useSponsoredTransaction hook for future use fix: sponsor transaction switch should toggle userOverrideSponsorValue feat: add a try again button on failed swaps due to sponsor error * feat: add warning texts to explain why a swap cannot be sponsored (#553) * fix: enforce width on swap number input (#554) * Fix swap screen fee update issue (#555) * Use correct value for fee card component * Fix fee state management * Dont show fee block when sponsored * chore: bump xverse-core to 1.5.1 to include fetchStxPendingTxData fix * Fix ui glitch on the login screen (#556) * Remove css transition code * Add hover and active state * fix: use :disabled css pseudo class instead of props on buttons --------- Co-authored-by: Tim Man * Fix swaps tx history (#557) * Use ft transfer array to show token specific history * Add filter to only show specific token history * Refactor txTransfers file to reuse code * Use token ticker * Update variable name * Use correct decimal places for fungible token (#559) * Use ftDecimals function for fungible token amount * fix undefined check --------- Co-authored-by: Tim Man Co-authored-by: Imamah-Zafar Co-authored-by: Tim Man Co-authored-by: Imamah-Zafar <88320460+Imamah-Zafar@users.noreply.github.com> * chore: bump xverse-core to 1.6.1 (#561) * fix: Don't use ordinals as inputs when recovering ordinals (#558) * Use updated type from sats-connect (#546) * Use updated type from sats-connect * chore: bump sats-connect to 1.0.0 * Update package-lock.json --------- Co-authored-by: Tim Man Co-authored-by: Tim Man * feat: remove binance onramp from buy screen (#567) * Release 0.16.0 (#565) * 0.16.0 * feat: change restore and backup wallet inputs (#566) * chore: fix eslint errors with typing and use of fragments * feat: move restore wallet route to larger screen size * feat: replace restore wallet seed phrase text area with input boxes * fix: ensure only one input can be visible at a time * feat: implement verify seed quiz * feat: disable copy of seed phrase view * fix: set error message properly * refactor: clean up to use one effect * fix: styling and input labels on backup/restore wallet * fix: handle edge case where new mnemonic already contains the answer * feat: seedPhraseInput will focus on next input on space and do not allow some common special characters * fix: decouple copy from restore wallet and change password screens * fix: bump xverse-core to 1.6.2 (#574) * Fix incorrect swap transaction fee ticker (#562) * Use STX as fee currency in swap screen * Fix typo * Fix variable name * Fix fiat rate state calculation * Show correct ticker and currency * Remove isSponsored check for fee amount * Add ability to remove connected Ledger account (#524) * Add ability to remove connected Ledger account * Improve the ledger account removal logic * Select the first account after ledger account removal * Create a reusable `optionsDialog` component * Fix account list logic, rename `seperator` component to `separator` * Fix option dialog indents * Add `deviceAccountIndex` value for ledger accounts that are missing it * Fix ledger account address verification * Move the `deviceAccountIndex` field migration to `loadActiveAccounts` func * Update copy --------- Co-authored-by: Tim Man * Support BTC message signing with ledger accounts (#549) * Support BTC message signing with ledger accounts * Remove console.logs * Change ledger signing modal displaying * Add segwit tx signing functionality * Use `signSimpleBip322Message` method for bip 322 message signing * Update package-lock.json * Update PSBT signing logic for ledger accounts * Add `findLedgerAccountId` util, update usage for `signIncomingSingleSigPSBT` function * Update `isAccountSelected` func logic for external popup account select window * Pass an array of inputs to sign to `signIncomingSingleSigPSBT` func * Move `handleBip322LedgerMessageSigning` func to utils, make some small code fixes * Disable the PSBT signing for BTC ledger accounts * Remove the `@stacks/common` module * Add error warning regarding ledger Bitcoin app version * Update error handling for signature request popup * Update copy and make some small UI tweaks * Update the copy for message signing screen * release: v0.17.0 (#576) * release: v0.17.0 * chore: bump to 1.6.3-rc.1 (#575) * chore: bump to 1.6.3-rc.1 * bump core version --------- Co-authored-by: Yukan * update package-lock --------- Co-authored-by: Yukan * Filter out dead pending transactions (#568) * Filter out dead pending transactions * Get account current nonce from confirmed and pending transactions * Refactor filterng out dead pending transaction logic * Address comments * Update variable name * fix: account select navigation (#578) * Display pending fungible token transactions in token dashboard (#572) * Update filterTx function to show pending fungible transactions * Fix transaction title display * Show pending ordinal transactions (#563) * Show pending ordinal transactions * Slight refactor of getBtcTokenTransferTitle * Fix duplicate tx in groupBtcTxsByDate * chore: bump to xverse-core v1.6.4-rc.2 * Remove unused fallback value --------- Co-authored-by: Den <36603049+dhriaznov@users.noreply.github.com> Co-authored-by: Tim Man * feat: add Xverse namespaced providers (#581) * add Xverse namespaced providers * fix comments * Enable PSBT signing for ledger BTC accounts (#570) * Support BTC message signing with ledger accounts * Remove console.logs * Change ledger signing modal displaying * Add segwit tx signing functionality * Use `signSimpleBip322Message` method for bip 322 message signing * Update package-lock.json * Update PSBT signing logic for ledger accounts * Add `findLedgerAccountId` util, update usage for `signIncomingSingleSigPSBT` function * Update `isAccountSelected` func logic for external popup account select window * Pass an array of inputs to sign to `signIncomingSingleSigPSBT` func * Move `handleBip322LedgerMessageSigning` func to utils, make some small code fixes * Disable the PSBT signing for BTC ledger accounts * Enable PSBT signing for ledger BTC accounts * Remove the `@stacks/common` module * Add error warning regarding ledger Bitcoin app version * Update error handling for signature request popup * Update copy and make some small UI tweaks * Update the copy for message signing screen * Update PSBT signing screen copy * fix: allow display of brc-20 if application/json type (#587) * fix: allow display of brc-20 if application/json type * make bg colours for a BRC-20 coin consistent * fix: full screen and popup styling (#577) * fix: full screen and popup styling * fix screen container * fix app load styling * fix full screen popup pages --------- Co-authored-by: Tim Man * feat: add inscription confirmation for sats connect (#571) * feat: add inscription confirmation for sats connect * implement inscribing and improve event naming * Added complete inscription screen * Add edit fees * Add HTML and SVG preview functionality * change content to content type and allow more previews * implement error designs on main screen * add error modal * fix: completion page not displaying * fix: styling issues and add inscription developer fees * add custom initial fee rate * fix screen height styling * set app height to 600 again * revert screen container size * improve screen container height * moved feerate get one level up * fix: full screen and popup styling * fix screen container * fix app load styling * Make top address selector sticky. Also fixes it for sign psbt and sign message requests * Update todos * fix full screen popup pages * fix account row styling * add json validaiton * add brc-20 and sats names views * add preview menu and fix styling * add preview markdown * switch to single inscribe event * fix preview of file type text and html * Ensure we only use non ordinal UTXOs for inscriptions * update sats-connect arg names and update the dom event name * fix type name * Show correct fiat currency * Use phosphor-icons * update beta core * fix: fee rate and styling issues * clean up icons * Clean up more icons * Update Core * Update sats connect version * Update sats connect * Add STX support for Ledger accounts (#564) * Add STX support for Ledger accounts * Update the copy and ledger account import logic * Update the ledger account import logic for STX support * Add `/add-stx-address-ledger` route for adding stx account * Remove the old case handling when there is no `ordinalsAddress` * Add STX address verification with ledger device * Remove the `/send-stx-ledger` path and update `/send-stx` to handle ledger * Handle regular STX transactions * Remove unused `/review-ledger-stx-tx` path, add STX NFT handling * Remove `/review-ledger-ft-tx` and `/send-ft-ledger` routes, update `/send-ft` to support ledger * Enable STX auth requests for ledger accounts that have an STX address * Add link to the auth popup to add the STX Ledger account * Update screen UI for STX NFT sending * Update copy for STX incoming tx signing * Update STX message signing logic * Update STX-related logic for ledger accounts * Update error handling for STX message signing with ledger * Get rid of `findLedgerAccountId` ledger util, move more copy to locales * Fix cropped button container for tx signing popup * Update address index definition for adding stx address * Update address index definition for address verification and stx tx confirmation * Update address index definition for stx jwt auth * Fix ledger account import when both BTC and STX options are selected * Fix `unsignedTx` type * Add `StacksRecipient` type and make some small code fixes * Add `icon` prop for the `ActionButton` component * Change the ledger steps in a callback * Add types for ledger tx state objects * Fix account index for stx account import, update ledger tx types and utils * Change the import path for `StacksRecipient` * Add error handling when user rejects stx address * Add a guard from account changing for the `/add-stx-address-ledger` route * Remove the back button on the ledger account import flow * Throw an error when `stacksCreds` were not fetched * Upgrade `@secretkeylabs/xverse-core` version to `^1.6.5-3bb284c` * Update package files * Fix add stx address error handling * Fix signing with 1st ledger account and bottom modal position * Skip the `No errors` value of the `errorMessage` for STX ledger signature * Remove the caret symbol in `sats-connect` package version * chore: bump xverse-core version 1.7.2 --------- Co-authored-by: Tim Man * fix: brc-20 balance number check (#592) * fix: don't show fees until we have UTXOs on inscription screen (#594) * fix: improve full screen popup styling (#593) * fix: improve full screen popup styling * fix: set height to 600 on options window * Release/v0.18.0 (#591) * release: v0.18.0 * feat: add inscription confirmation for sats connect (#571) * feat: add inscription confirmation for sats connect * implement inscribing and improve event naming * Added complete inscription screen * Add edit fees * Add HTML and SVG preview functionality * change content to content type and allow more previews * implement error designs on main screen * add error modal * fix: completion page not displaying * fix: styling issues and add inscription developer fees * add custom initial fee rate * fix screen height styling * set app height to 600 again * revert screen container size * improve screen container height * moved feerate get one level up * fix: full screen and popup styling * fix screen container * fix app load styling * Make top address selector sticky. Also fixes it for sign psbt and sign message requests * Update todos * fix full screen popup pages * fix account row styling * add json validaiton * add brc-20 and sats names views * add preview menu and fix styling * add preview markdown * switch to single inscribe event * fix preview of file type text and html * Ensure we only use non ordinal UTXOs for inscriptions * update sats-connect arg names and update the dom event name * fix type name * Show correct fiat currency * Use phosphor-icons * update beta core * fix: fee rate and styling issues * clean up icons * Clean up more icons * Update Core * Update sats connect version * Update sats connect * Use `ticker` field instead of `name` for ft and brc-20 token sending (#595) * update package-lock.json --------- Co-authored-by: Victor Kirov Co-authored-by: Yukan Co-authored-by: Den <36603049+dhriaznov@users.noreply.github.com> * Fix Tx history screen reload & send form navigation issues (#584) * Donot show spinner when refetching transaction history data * Fix STX navigation issue * Fix input fields clearance on naviagtion * Update dependency array for useEffect * Add comment * Fix memo error * Support script outputs in psbts (#534) * Support script outputs * Update copy * Revert package.json change * Add info message for output script * Use Array.some * Remove unused style * Update core versio and address comments * Remove pretext from output script ui * Refactor render script output logic * Donot show spinner when refetching transaction history data * Fix STX navigation issue * Fix input fields clearance on naviagtion * chore: turn on eslint for staged files on precommit and turn off no-plusplus rule * Fix eslint issues * Update dependency array for useEffect * Add comment * Fix script count * fix: eslint fix --------- Co-authored-by: Tim Man Co-authored-by: Tim Man * Mismatch when verifying the addresses from the ledger device (#580) * Handle min fee rate returned by the API (#583) * Handle min fee rate returned by the API * Add a check for the STX tx min fee * Fix a couple of tslint errors * fix: don't show loader on balance refetch on home screen ENG-2757 (#589) * Add refetch loader * resize loading spinner * Release/v0.18.2 (#600) * release: v0.18.0 * feat: add inscription confirmation for sats connect (#571) * feat: add inscription confirmation for sats connect * implement inscribing and improve event naming * Added complete inscription screen * Add edit fee… * feat: add new sattributes to rarity scale screen (#54) * feat: add new sattributes to rarity scale screen * chore: remove PALINCEPTION and PURE_PALINCEPTION * chore: replace icons and color for epic, rare and uncommon * chore: add mock data and local storage values for testing and mapRareSatsAPIResponseToRareSatsV2 method to map the response for v2 api (#56) * chore: add mock data and local storage values for testing and mapRareSatsAPIResponseToRareSatsV2 method to map the response for v2 api * chore: add localstorage value to use prod api * feat: rare sats tab UI (#57) * feat: rare sats tab UI * fix: gallery view UI * fix: icons * fix: thousand separator --------- Co-authored-by: Abdul Haseeb * feat: tweak rare sats bundle screen to support new satributes (#58) * feat: tweak rare sats bundle screen to support new satributes * chore: move ExoticSatsRow to component folder * Chore merge ours to downstream develop (#62) * release: v0.19.0 (#607) * release: v0.19.0 * update package-lock * bump xverse core version to fix fee issue --------- Co-authored-by: Yukan * Build analytics tracking for web-extension (#590) * Build analytics tracking for web-extension * Add `/privacy-preferences` screen * Build analytics tracking for web-extension * Update mixpanel tracking logic * Remove unused wallet action name * Add authorize data collection toggler and popup * Update tracking logic * Add translation keys * Update mixpanel tracking logic * Remove unused imports * Make some code changes after PR review, upgrade `xverse-core` package version * Upgrade `xverse-core` package version * Resolve git conflicts * Handling fees with thresholds (#601) * Handling fees with thresholds * Add margin under the high fees warning * Change the high fees warning position for Ordinals & Brc-20 txs * Show the warning if the initial fee from transaction is greater than the threshold * Add high fee warning for one-step brc20 transfer * update copy --------- Co-authored-by: Yukan * Refactor ledger-related logic (#586) * Add STX support for Ledger accounts * Update the copy and ledger account import logic * Update the ledger account import logic for STX support * Add `/add-stx-address-ledger` route for adding stx account * Remove the old case handling when there is no `ordinalsAddress` * Add STX address verification with ledger device * Remove the `/send-stx-ledger` path and update `/send-stx` to handle ledger * Handle regular STX transactions * Remove unused `/review-ledger-stx-tx` path, add STX NFT handling * Remove `/review-ledger-ft-tx` and `/send-ft-ledger` routes, update `/send-ft` to support ledger * Enable STX auth requests for ledger accounts that have an STX address * Add link to the auth popup to add the STX Ledger account * Update screen UI for STX NFT sending * Update copy for STX incoming tx signing * Update STX message signing logic * Update STX-related logic for ledger accounts * Update error handling for STX message signing with ledger * Get rid of `findLedgerAccountId` ledger util, move more copy to locales * Fix cropped button container for tx signing popup * Update address index definition for adding stx address * Update address index definition for address verification and stx tx confirmation * Update address index definition for stx jwt auth * Refactor ledger-related logic * Refactor ledger account import * Fix ledger account import when both BTC and STX options are selected * Refactor ledger address verification screen * Refactor ledger tx confirmation screen * Refactor Add stx address screen * Fix `unsignedTx` type * Add `StacksRecipient` type and make some small code fixes * Add `icon` prop for the `ActionButton` component * Change the ledger steps in a callback * Add types for ledger tx state objects * Fix account index for stx account import, update ledger tx types and utils * Change the import path for `StacksRecipient` * Change the steps numeration for ledger account import * Get rid of unused step changing logic for ledger account import * Add more error handling * Update xverse-core version for testing purposes * Remove caret symbol in xverse-core package * Fix CI build * Make a couple of code fixes according to PR review comments * Add more transaltions * Add the `DEFAULT_TRANSITION_OPTIONS` constant * Make separate components for Steps and StepControls for the Ledger account import flow * Fix style imports * Upgrade the `xverse-core` package version * Upgrade `xverse-core` package version * Disable PSBT tx signing for ledger accounts * update xverse-core * package-lock --------- Co-authored-by: Yukan * Release/v0.20.0 (#611) * chore: merge from upstream develop (#26) * release: v0.19.0 (#607) * release: v0.19.0 * update package-lock * bump xverse core version to fix fee issue --------- Co-authored-by: Yukan * Build analytics tracking for web-extension (#590) * Build analytics tracking for web-extension * Add `/privacy-preferences` screen * Build analytics tracking for web-extension * Update mixpanel tracking logic * Remove unused wallet action name * Add authorize data collection toggler and popup * Update tracking logic * Add translation keys * Update mixpanel tracking logic * Remove unused imports * Make some code changes after PR review, upgrade `xverse-core` package version * Upgrade `xverse-core` package version * Resolve git conflicts * Handling fees with thresholds (#601) * Handling fees with thresholds * Add margin under the high fees warning * Change the high fees warning position for Ordinals & Brc-20 txs * Show the warning if the initial fee from transaction is greater than the threshold * Add high fee warning for one-step brc20 transfer * update copy --------- Co-authored-by: Yukan * Refactor ledger-related logic (#586) * Add STX support for Ledger accounts * Update the copy and ledger account import logic * Update the ledger account import logic for STX support * Add `/add-stx-address-ledger` route for adding stx account * Remove the old case handling when there is no `ordinalsAddress` * Add STX address verification with ledger device * Remove the `/send-stx-ledger` path and update `/send-stx` to handle ledger * Handle regular STX transactions * Remove unused `/review-ledger-stx-tx` path, add STX NFT handling * Remove `/review-ledger-ft-tx` and `/send-ft-ledger` routes, update `/send-ft` to support ledger * Enable STX auth requests for ledger accounts that have an STX address * Add link to the auth popup to add the STX Ledger account * Update screen UI for STX NFT sending * Update copy for STX incoming tx signing * Update STX message signing logic * Update STX-related logic for ledger accounts * Update error handling for STX message signing with ledger * Get rid of `findLedgerAccountId` ledger util, move more copy to locales * Fix cropped button container for tx signing popup * Update address index definition for adding stx address * Update address index definition for address verification and stx tx confirmation * Update address index definition for stx jwt auth * Refactor ledger-related logic * Refactor ledger account import * Fix ledger account import when both BTC and STX options are selected * Refactor ledger address verification screen * Refactor ledger tx confirmation screen * Refactor Add stx address screen * Fix `unsignedTx` type * Add `StacksRecipient` type and make some small code fixes * Add `icon` prop for the `ActionButton` component * Change the ledger steps in a callback * Add types for ledger tx state objects * Fix account index for stx account import, update ledger tx types and utils * Change the import path for `StacksRecipient` * Change the steps numeration for ledger account import * Get rid of unused step changing logic for ledger account import * Add more error handling * Update xverse-core version for testing purposes * Remove caret symbol in xverse-core package * Fix CI build * Make a couple of code fixes according to PR review comments * Add more transaltions * Add the `DEFAULT_TRANSITION_OPTIONS` constant * Make separate components for Steps and StepControls for the Ledger account import flow * Fix style imports * Upgrade the `xverse-core` package version * Upgrade `xverse-core` package version * Disable PSBT tx signing for ledger accounts * update xverse-core * package-lock --------- Co-authored-by: Yukan --------- Co-authored-by: Yukan Co-authored-by: Den <36603049+dhriaznov@users.noreply.github.com> * chore: allow ci build version to pass env vars to build (#27) * feat/eng 2904 implement frontend on web extension (#5) * Tim/eng 2933 implement dashboard display of rare sats bundles (#1) * chore: create a hook for rare sats with placeholder data * chore: add some inscription placeholder with image * feat/eng 2933 implement dashboard rare sats tab (#4) * chore: create a collectiblesTabs component with react-tabs * refactor: move nft dashboard logic into hook * feat: move rare sats into separate tab * fix: add tab state styling and move to common.styled * feat: style collectibles header * fix: restyle all grid item colors and add total items * fix: supply total nfts and total rare sats number and type the collectiblesTabs props * style: comments * feat/eng 2934 implement sats bundle UI screen (#6) * Abdulhaseeb/eng 2930 implement settings screen updates (#7) * feat: enable rare sats from settings * fix: disabled UI * fix: typos * Abdulhaseeb/eng 2931 implement info dialogs (#8) * feat: enable rare sats from settings * feat: notice alert and rarities screen * feat: new feature dialog * fix: styles * feat: add UI for rare sats bundle in collectible item details (#2) * fix: ordinal thumbnails were broken (#10) * Add no collectibles and error screens (#11) * feat: integrate rare sats form core (#9) * feat: add rare sats item detail screen (#3) * feat: add UI for rare sats bundle in collectible item details * feat: implement rare sats details screen --------- Co-authored-by: Tim Man * feat: implement rare sats send screen (#12) * chore: use getUtxoOrdinalBundle to know if a inscription belongs to a bundle (#14) * Abdulhaseeb/eng 2938 implement rare sats confirm screen (#15) * feat: confirm rare sat tx * feat: added warning callout * feat: implement UI for rare sats thumbnails (#16) * feat: add UI for rare sats bundle in collectible item details * feat: implement rare sats details screen * feat: add rare sats thumbnathumbnails * chore: add missing mock data for testing inscriptions belonging to a bundle * Tim/eng 2959 set up test mocks (#17) * fix: react console errors * fix: send rare sat heading and sub text * fix: eslint errors * chore: add mock test cases in hook * fix: back button sometimes has no history * fix: revert merge change * fix: should be no commoners in mock tests * feat: added thumbnail in confirm screen (#18) * fix: react console errors * fix: send rare sat heading and sub text * fix: eslint errors * chore: add mock test cases in hook * fix: back button sometimes has no history * fix: revert merge change * fix: should be no commoners in mock tests * feat: added thumbnail in confirm screen * fix: ui --------- Co-authored-by: Tim Man * fix: rarities screen UI (#19) * chore: fix some todos and issues in rare sats feature (#20) * chore: Unify assets and rarity label in rarityTile and rareSatIcon components * chore: create bundle asset and use it in RareSatsTabGridItem and confirm send screen * chore: add bundle asset component in send screen * chore: make glow optional and remove it from inscribed rare sats asset * chore: add assets to bundle gallery view * chore: remove unsupported media callout. add margin bottom and fix cannot send rare sat individually for gallery view * fix: styling on gallery view for dashboard and rare sats bundle grids (#22) * fix: styling on gallery view for dashboard and rare sats bundle grids * fix: add column layout for rare sats bundle gallery view * fix: rare sats bundle back should always fo to gallery * fix: header spacing rare sats bundle and load more button * feat: add isLoading state for rare sats tab * fix: remove is owned by active account check the user flow resets whenever account changes, so this check is no longer necessary * feat: save collectibles tab index in query params * fix: restore ledger open in new tab behaviour * fix: show info panel regardless of empty state (#23) * chore: fix UI issues (#24) * chore: fix issues with dont see your rare sat banner * chore: fix ui issues in rare sats details screen * Update src/app/screens/nftDashboard/notice.tsx * refactor: use library components and minor styling fixes * fix: rare sat bundle send button width * fix: add separator in gallery view rare sats bundle and styling fixes * fix: minor style fix --------- Co-authored-by: Tim Man * fix: branch merge errors * chore: remove mock data (#28) * chore: remove mock data * chore: add extenral links to scan tool and rare sats post * chore: move external link urls to constants file --------- Co-authored-by: Abdul Haseeb Co-authored-by: fede erbes Co-authored-by: Victor Kirov * release v0.20.0 * chore: bump to xverse-core 1.8.2 for bip322 signing fix * fix: collectibles dashboard should ignore invalid params errors (#33) * fix: put in a quick fix for more location.state serialization bugs (#34) * chore: add support for rare sats in tx confirmation screen (#35) * chore: add support for rare sats in tx confirmation screen * chore: remove logs and fix typo * chore: change unknown icon --------- Co-authored-by: Yukan Co-authored-by: Den <36603049+dhriaznov@users.noreply.github.com> Co-authored-by: Abdul Haseeb Co-authored-by: fede erbes Co-authored-by: Victor Kirov * Show BRC20 token transaction history (#523) * Show BRC20 token transaction history * Handle pending state * Use updated brc20 transaction history response * Update package.lock.json file * fix loading state * Update brc20 history tx title * Update core version to latest --------- Co-authored-by: Denys Hriaznov <36603049+dhriaznov@users.noreply.github.com> * fix rare sats settings copy (#612) * Release/0.21.0 (#613) * chore: merge from upstream develop (#26) * release: v0.19.0 (#607) * release: v0.19.0 * update package-lock * bump xverse core version to fix fee issue --------- Co-authored-by: Yukan * Build analytics tracking for web-extension (#590) * Build analytics tracking for web-extension * Add `/privacy-preferences` screen * Build analytics tracking for web-extension * Update mixpanel tracking logic * Remove unused wallet action name * Add authorize data collection toggler and popup * Update tracking logic * Add translation keys * Update mixpanel tracking logic * Remove unused imports * Make some code changes after PR review, upgrade `xverse-core` package version * Upgrade `xverse-core` package version * Resolve git conflicts * Handling fees with thresholds (#601) * Handling fees with thresholds * Add margin under the high fees warning * Change the high fees warning position for Ordinals & Brc-20 txs * Show the warning if the initial fee from transaction is greater than the threshold * Add high fee warning for one-step brc20 transfer * update copy --------- Co-authored-by: Yukan * Refactor ledger-related logic (#586) * Add STX support for Ledger accounts * Update the copy and ledger account import logic * Update the ledger account import logic for STX support * Add `/add-stx-address-ledger` route for adding stx account * Remove the old case handling when there is no `ordinalsAddress` * Add STX address verification with ledger device * Remove the `/send-stx-ledger` path and update `/send-stx` to handle ledger * Handle regular STX transactions * Remove unused `/review-ledger-stx-tx` path, add STX NFT handling * Remove `/review-ledger-ft-tx` and `/send-ft-ledger` routes, update `/send-ft` to support ledger * Enable STX auth requests for ledger accounts that have an STX address * Add link to the auth popup to add the STX Ledger account * Update screen UI for STX NFT sending * Update copy for STX incoming tx signing * Update STX message signing logic * Update STX-related logic for ledger accounts * Update error handling for STX message signing with ledger * Get rid of `findLedgerAccountId` ledger util, move more copy to locales * Fix cropped button container for tx signing popup * Update address index definition for adding stx address * Update address index definition for address verification and stx tx confirmation * Update address index definition for stx jwt auth * Refactor ledger-related logic * Refactor ledger account import * Fix ledger account import when both BTC and STX options are selected * Refactor ledger address verification screen * Refactor ledger tx confirmation screen * Refactor Add stx address screen * Fix `unsignedTx` type * Add `StacksRecipient` type and make some small code fixes * Add `icon` prop for the `ActionButton` component * Change the ledger steps in a callback * Add types for ledger tx state objects * Fix account index for stx account import, update ledger tx types and utils * Change the import path for `StacksRecipient` * Change the steps numeration for ledger account import * Get rid of unused step changing logic for ledger account import * Add more error handling * Update xverse-core version for testing purposes * Remove caret symbol in xverse-core package * Fix CI build * Make a couple of code fixes according to PR review comments * Add more transaltions * Add the `DEFAULT_TRANSITION_OPTIONS` constant * Make separate components for Steps and StepControls for the Ledger account import flow * Fix style imports * Upgrade the `xverse-core` package version * Upgrade `xverse-core` package version * Disable PSBT tx signing for ledger accounts * update xverse-core * package-lock --------- Co-authored-by: Yukan --------- Co-authored-by: Yukan Co-authored-by: Den <36603049+dhriaznov@users.noreply.github.com> * chore: allow ci build version to pass env vars to build (#27) * feat/eng 2904 implement frontend on web extension (#5) * Tim/eng 2933 implement dashboard display of rare sats bundles (#1) * chore: create a hook for rare sats with placeholder data * chore: add some inscription placeholder with image * feat/eng 2933 implement dashboard rare sats tab (#4) * chore: create a collectiblesTabs component with react-tabs * refactor: move nft dashboard logic into hook * feat: move rare sats into separate tab * fix: add tab state styling and move to common.styled * feat: style collectibles header * fix: restyle all grid item colors and add total items * fix: supply total nfts and total rare sats number and type the collectiblesTabs props * style: comments * feat/eng 2934 implement sats bundle UI screen (#6) * Abdulhaseeb/eng 2930 implement settings screen updates (#7) * feat: enable rare sats from settings * fix: disabled UI * fix: typos * Abdulhaseeb/eng 2931 implement info dialogs (#8) * feat: enable rare sats from settings * feat: notice alert and rarities screen * feat: new feature dialog * fix: styles * feat: add UI for rare sats bundle in collectible item details (#2) * fix: ordinal thumbnails were broken (#10) * Add no collectibles and error screens (#11) * feat: integrate rare sats form core (#9) * feat: add rare sats item detail screen (#3) * feat: add UI for rare sats bundle in collectible item details * feat: implement rare sats details screen --------- Co-authored-by: Tim Man * feat: implement rare sats send screen (#12) * chore: use getUtxoOrdinalBundle to know if a inscription belongs to a bundle (#14) * Abdulhaseeb/eng 2938 implement rare sats confirm screen (#15) * feat: confirm rare sat tx * feat: added warning callout * feat: implement UI for rare sats thumbnails (#16) * feat: add UI for rare sats bundle in collectible item details * feat: implement rare sats details screen * feat: add rare sats thumbnathumbnails * chore: add missing mock data for testing inscriptions belonging to a bundle * Tim/eng 2959 set up test mocks (#17) * fix: react console errors * fix: send rare sat heading and sub text * fix: eslint errors * chore: add mock test cases in hook * fix: back button sometimes has no history * fix: revert merge change * fix: should be no commoners in mock tests * feat: added thumbnail in confirm screen (#18) * fix: react console errors * fix: send rare sat heading and sub text * fix: eslint errors * chore: add mock test cases in hook * fix: back button sometimes has no history * fix: revert merge change * fix: should be no commoners in mock tests * feat: added thumbnail in confirm screen * fix: ui --------- Co-authored-by: Tim Man * fix: rarities screen UI (#19) * chore: fix some todos and issues in rare sats feature (#20) * chore: Unify assets and rarity label in rarityTile and rareSatIcon components * chore: create bundle asset and use it in RareSatsTabGridItem and confirm send screen * chore: add bundle asset component in send screen * chore: make glow optional and remove it from inscribed rare sats asset * chore: add assets to bundle gallery view * chore: remove unsupported media callout. add margin bottom and fix cannot send rare sat individually for gallery view * fix: styling on gallery view for dashboard and rare sats bundle grids (#22) * fix: styling on gallery view for dashboard and rare sats bundle grids * fix: add column layout for rare sats bundle gallery view * fix: rare sats bundle back should always fo to gallery * fix: header spacing rare sats bundle and load more button * feat: add isLoading state for rare sats tab * fix: remove is owned by active account check the user flow resets whenever account changes, so this check is no longer necessary * feat: save collectibles tab index in query params * fix: restore ledger open in new tab behaviour * fix: show info panel regardless of empty state (#23) * chore: fix UI issues (#24) * chore: fix issues with dont see your rare sat banner * chore: fix ui issues in rare sats details screen * Update src/app/screens/nftDashboard/notice.tsx * refactor: use library components and minor styling fixes * fix: rare sat bundle send button width * fix: add separator in gallery view rare sats bundle and styling fixes * fix: minor style fix --------- Co-authored-by: Tim Man * fix: branch merge errors * chore: remove mock data (#28) * chore: remove mock data * chore: add extenral links to scan tool and rare sats post * chore: move external link urls to constants file --------- Co-authored-by: Abdul Haseeb Co-authored-by: fede erbes Co-authored-by: Victor Kirov * release v0.20.0 (#29) * release: v0.19.0 (#607) * release: v0.19.0 * update package-lock * bump xverse core version to fix fee issue --------- Co-authored-by: Yukan * Build analytics tracking for web-extension (#590) * Build analytics tracking for web-extension * Add `/privacy-preferences` screen * Build analytics tracking for web-extension * Update mixpanel tracking logic * Remove unused wallet action name * Add authorize data collection toggler and popup * Update tracking logic * Add translation keys * Update mixpanel tracking logic * Remove unused imports * Make some code changes after PR review, upgrade `xverse-core` package version * Upgrade `xverse-core` package version * Resolve git conflicts * Handling fees with thresholds (#601) * Handling fees with thresholds * Add margin under the high fees warning * Change the high fees warning position for Ordinals & Brc-20 txs * Show the warning if the initial fee from transaction is greater than the threshold * Add high fee warning for one-step brc20 transfer * update copy --------- Co-authored-by: Yukan * Refactor ledger-related logic (#586) * Add STX support for Ledger accounts * Update the copy and ledger account import logic * Update the ledger account import logic for STX support * Add `/add-stx-address-ledger` route for adding stx account * Remove the old case handling when there is no `ordinalsAddress` * Add STX address verification with ledger device * Remove the `/send-stx-ledger` path and update `/send-stx` to handle ledger * Handle regular STX transactions * Remove unused `/review-ledger-stx-tx` path, add STX NFT handling * Remove `/review-ledger-ft-tx` and `/send-ft-ledger` routes, update `/send-ft` to support ledger * Enable STX auth requests for ledger accounts that have an STX address * Add link to the auth popup to add the STX Ledger account * Update screen UI for STX NFT sending * Update copy for STX incoming tx signing * Update STX message signing logic * Update STX-related logic for ledger accounts * Update error handling for STX message signing with ledger * Get rid of `findLedgerAccountId` ledger util, move more copy to locales * Fix cropped button container for tx signing popup * Update address index definition for adding stx address * Update address index definition for address verification and stx tx confirmation * Update address index definition for stx jwt auth * Refactor ledger-related logic * Refactor ledger account import * Fix ledger account import when both BTC and STX options are selected * Refactor ledger address verification screen * Refactor ledger tx confirmation screen * Refactor Add stx address screen * Fix `unsignedTx` type * Add `StacksRecipient` type and make some small code fixes * Add `icon` prop for the `ActionButton` component * Change the ledger steps in a callback * Add types for ledger tx state objects * Fix account index for stx account import, update ledger tx types and utils * Change the import path for `StacksRecipient` * Change the steps numeration for ledger account import * Get rid of unused step changing logic for ledger account import * Add more error handling * Update xverse-core version for testing purposes * Remove caret symbol in xverse-core package * Fix CI build * Make a couple of code fixes according to PR review comments * Add more transaltions * Add the `DEFAULT_TRANSITION_OPTIONS` constant * Make separate components for Steps and StepControls for the Ledger account import flow * Fix style imports * Upgrade the `xverse-core` package version * Upgrade `xverse-core` package version * Disable PSBT tx signing for ledger accounts * update xverse-core * package-lock --------- Co-authored-by: Yukan * release v0.20.0 * chore: bump to xverse-core 1.8.2 for bip322 signing fix * fix: collectibles dashboard should ignore invalid params errors (#33) * fix: put in a quick fix for more location.state serialization bugs (#34) * chore: add support for rare sats in tx confirmation screen (#35) * chore: add support for rare sats in tx confirmation screen * chore: remove logs and fix typo * chore: change unknown icon --------- Co-authored-by: Tim Man Co-authored-by: Den <36603049+dhriaznov@users.noreply.github.com> Co-authored-by: Tim Man Co-authored-by: fede erbes * chore: tidy up zips of each build run and the release uploads (#31) * feat: collectibles ui frontend (#30) * [ENG-2836] feat: Ordinals and BRC-20 collectibles UI * Improve ordinals & brc-20 collectibles fetching logic and UI * Collectible ordinal detail page (#32) * Tim/eng 2933 implement dashboard display of rare sats bundles (#1) * chore: create a hook for rare sats with placeholder data * chore: add some inscription placeholder with image * feat/eng 2933 implement dashboard rare sats tab (#4) * chore: create a collectiblesTabs component with react-tabs * refactor: move nft dashboard logic into hook * feat: move rare sats into separate tab * fix: add tab state styling and move to common.styled * feat: style collectibles header * fix: restyle all grid item colors and add total items * fix: supply total nfts and total rare sats number and type the collectiblesTabs props * style: comments * feat/eng 2934 implement sats bundle UI screen (#6) * Abdulhaseeb/eng 2930 implement settings screen updates (#7) * feat: enable rare sats from settings * fix: disabled UI * fix: typos * Abdulhaseeb/eng 2931 implement info dialogs (#8) * feat: enable rare sats from settings * feat: notice alert and rarities screen * feat: new feature dialog * fix: styles * feat: add UI for rare sats bundle in collectible item details (#2) * fix: ordinal thumbnails were broken (#10) * Add no collectibles and error screens (#11) * feat: integrate rare sats form core (#9) * feat: add rare sats item detail screen (#3) * feat: add UI for rare sats bundle in collectible item details * feat: implement rare sats details screen --------- Co-authored-by: Tim Man * feat: implement rare sats send screen (#12) * chore: use getUtxoOrdinalBundle to know if a inscription belongs to a bundle (#14) * Abdulhaseeb/eng 2938 implement rare sats confirm screen (#15) * feat: confirm rare sat tx * feat: added warning callout * feat: implement UI for rare sats thumbnails (#16) * feat: add UI for rare sats bundle in collectible item details * feat: implement rare sats details screen * feat: add rare sats thumbnathumbnails * chore: add missing mock data for testing inscriptions belonging to a bundle * Tim/eng 2959 set up test mocks (#17) * fix: react console errors * fix: send rare sat heading and sub text * fix: eslint errors * chore: add mock test cases in hook * fix: back button sometimes has no history * fix: revert merge change * fix: should be no commoners in mock tests * feat: added thumbnail in confirm screen (#18) * fix: react console errors * fix: send rare sat heading and sub text * fix: eslint errors * chore: add mock test cases in hook * fix: back button sometimes has no history * fix: revert merge change * fix: should be no commoners in mock tests * feat: added thumbnail in confirm screen * fix: ui --------- Co-authored-by: Tim Man * fix: rarities screen UI (#19) * chore: fix some todos and issues in rare sats feature (#20) * chore: Unify assets and rarity label in rarityTile and rareSatIcon components * chore: create bundle asset and use it in RareSatsTabGridItem and confirm send screen * chore: add bundle asset component in send screen * chore: make glow optional and remove it from inscribed rare sats asset * chore: add assets to bundle gallery view * chore: remove unsupported media callout. add margin bottom and fix cannot send rare sat individually for gallery view * fix: styling on gallery view for dashboard and rare sats bundle grids (#22) * fix: styling on gallery view for dashboard and rare sats bundle grids * fix: add column layout for rare sats bundle gallery view * fix: rare sats bundle back should always fo to gallery * fix: header spacing rare sats bundle and load more button * feat: add isLoading state for rare sats tab * fix: remove is owned by active account check the user flow resets whenever account changes, so this check is no longer necessary * feat: save collectibles tab index in query params * fix: restore ledger open in new tab behaviour * fix: show info panel regardless of empty state (#23) * Add ordinal attributes in detail screen * chore: fix UI issues (#24) * chore: fix issues with dont see your rare sat banner * chore: fix ui issues in rare sats details screen * Update src/app/screens/nftDashboard/notice.tsx * refactor: use library components and minor styling fixes * fix: rare sat bundle send button width * fix: add separator in gallery view rare sats bundle and styling fixes * fix: minor style fix --------- Co-authored-by: Tim Man * fix: branch merge errors * chore: remove mock data (#28) * chore: remove mock data * chore: add extenral links to scan tool and rare sats post * chore: move external link urls to constants file * Add scrollbar to ordinal detail screen * Adjust text alignment * Update Navigation Bar * Add Share button in extension ordinal detail screen * Include useInscription hook --------- Co-authored-by: Tim Man Co-authored-by: Abdul Haseeb Co-authored-by: fede erbes Co-authored-by: Victor Kirov * Fix `getNextPageParam` param in the `useAddressInscriptionCollections` hook * feat: inscriptions collection page UI components (#36) * chore: use xverse-core with api functions * feat: add ordinals collection screen and route * chore: add prettier organize imports plugin * chore: remove unused eslint disable line * fix: fix tab query params * feat: add ordinals collection route and screen * fix: revert scrollbar change * Tim/eng 2813 inscriptions collection page data fetching pagination (#37) * Make some small code tweaks * Complete Remaining Todos for Ordinal Detail Screen (#38) * Update ordinal detail Ui accodrding to MVP * Get ordinal details from api * Have placeholder incase market data does not exist * Fix navigation and address comments * feat: collection grid items (#39) * Add content skeleton loader for Collectibles tab * Fix border radius for Collectibles skeleton loader * Fix the loader condition in Collectibles tab * Update BRC20 token collection thumbnail in main collectible page * Tim/eng 2833 inscriptions full screen responsive main page collection (#41) * feat: style the send screen including responsive layout * style: use theme radius Co-authored-by: Den <36603049+dhriaznov@users.noreply.github.com> --------- Co-authored-by: Den <36603049+dhriaznov@users.noreply.github.com> * Display collection market data (#40) * Get collection amrket data * Fix alignment on collection page * chore: revert the large package-lock.json diff * Update receive screen and bottom modal (#42) * Update recieve screen * Add updated QR code in recieve screen * Update receive modal * Fix getNextPageParam for const useAddressInscriptionCollections = () => { * Remove unused package * fix: use theme radius Co-authored-by: Den <36603049+dhriaznov@users.noreply.github.com> * fix: use theme radius Co-authored-by: Den <36603049+dhriaznov@users.noreply.github.com> * Address comments * chore: remove empty unreferenced component * Remove unused background color * fix: layout and spacing on updated bottom modal, receive nft --------- Co-authored-by: Tim Man Co-authored-by: Den <36603049+dhriaznov@users.noreply.github.com> * fix color for custom switch and code typo (#43) * unstaged package-lock.json * fix: update all custom switch colors --------- Co-authored-by: Tim Man * fix: inscriptions grid fixes (#44) * chore: bump core version to 1.9.1 * Update function to recognise and parse brc20 tokens (#46) * Update skeleton loader logic for Collectibles UI (#45) * Update skeleton loader logic for Collectibles UI * Add a separate `TilesSkeletonLoader` component, add skeleton loader on the Collection page * Add ordinal detail page skeleton loader for the extension view * Add skeleton loader for the ordinal image component * fix: collectibles fixes from review (#47) * Fix minor ui bugs in collectible screen (#48) --------- Co-authored-by: Imamah-Zafar <88320460+Imamah-Zafar@users.noreply.github.com> Co-authored-by: Tim Man Co-authored-by: Abdul Haseeb Co-authored-by: fede erbes Co-authored-by: Victor Kirov Co-authored-by: Duska.T <55587184+DuskaT021@users.noreply.github.com> * release: v0.21.0 * Small fixes for the Collectibles UI loading state (#50) * Small fixes for the Collectibles UI * Fix loader appearance in the Collectibles tabs * Fix skeleton loader on the main Collectibles tab gallery view * Fix skeleton loader on the Collection page gallery view * Add skeleton loader for the ordinal detail page * fix: container padding on ordinals collection page * chore: remove debug lines --------- Co-authored-by: Tim Man * fix: more collectible UI fixes (#52) * chore: remove testAddress and TODOs * fix: brc20 status in ordinal details * fix: back button from nft details goes to nfts tab and back button from ordinal detail says back to collection or gallery * Minor ui fixes (#51) * sort brc20 inscription array * Fix styling * Remove unused funciton --------- Co-authored-by: Yukan Co-authored-by: Den <36603049+dhriaznov@users.noreply.github.com> Co-authored-by: Abdul Haseeb Co-authored-by: fede erbes Co-authored-by: Victor Kirov Co-authored-by: Imamah-Zafar <88320460+Imamah-Zafar@users.noreply.github.com> Co-authored-by: Duska.T <55587184+DuskaT021@users.noreply.github.com> * feat: revamp all colors (#588) * Tim/eng 2317 consistent dashboard buttons in extension (#618) * fix: use revamped swap svg * fix: hide swap button on testnet * Ledger send btc screen crash when going back and forward (#615) * Make `Authorize data collection` property turned on by default (#614) * Make `Authorize data collection` property turned on by default * Change the html tag and text color constant * [ENG-2961] Fix eslint errors for web extension (#617) * [ENG-2961] Fix eslint errors for web extension * [ENG-2961] Fix eslint errors for web extension * Update src/app/hooks/queries/ordinals/useInscriptionDetails.ts Co-authored-by: Victor Kirov * Update src/app/hooks/queries/useAppConfig.ts Co-authored-by: Victor Kirov --------- Co-authored-by: Victor Kirov * [ENG-3059] fix: Remove ordinal callout from send rare sats (#619) * [ENG-3059] fix: Remove ordinal callout from send rare sats * Update `amount` field validation error styling * chore: turn on tsc --noEmit precommit hook (#622) * chore: add tsc-files dev dep and run on lint staged pre commit also ran npm audit fix for a critical vuln and updated direct svg import typings * chore: use string for svg typescript declarations * chore: run eslint on repo before passing the workflow checks * feat: allow unpublished UTXOs as inputs to PSBTs (#621) * feat: allow unpublished UTXOs as inputs to PSBTs * Use correct status from response --------- Co-authored-by: Tim Man * [ENG-3039] fix: Skeleton loader for BRC-20 icons in home dashboard (#620) it can be merged, tested on ledger as well * Mahmoud/eng 2284 seedphrase vault (#510) * refactor wallet flow to use seed vault * code cleanup * update auth flow and seedphrase usage * lock dependencies and user xverse-core beta * migrate onboarding to use seed vault * use account switch method from wallet reducer hook * use seed vault for stx swaps * removed seedPhrase from wallet store * rebase fixes * small fixes * clear home screen errors * revert hashing algo change * fix swap hook * Revert "clear home screen errors" This reverts commit 55da10e60ead4c0798c40d61701b904085d6e603. * apply core changes * clear encrypted seed after successfully migrating * type fixes * update core version * onboarding guard refactor * update seed vault hook * use chromeStorage driver for session storage * updated core-lib version * refactor onboarding guard to use hasSeed * update core-lib * small fixes * update auth guard and update core version * updated csp * deprecate internal message handler * update wallet exists guard * overwrite any existing seed on restore wallet * update flow to explicitly clear vault for onboarding * update image csp * reset navigation if seed is not pre-set * update sendRareSat screen to use seed vault * add rich media csp policy * added authguard to settings screen * updated lock file * minor fixes * fresh npm install using v18 * lock with develop * adde new package updates * use fixed version of core * update to core lts version --------- Co-authored-by: Victor Kirov Co-authored-by: Tim Man * [ENG-3115] fix: UI issue in the legal screen (#624) * feat: add vout to the end of the tx id for unknown rarities (#623) * feat: add vout to the end of the tx id for unknown rarities * chore: sed replace forward slash in workflow file should replace all occurrences --------- Co-authored-by: Tim Man * Fix minor UI issues (#625) * Have thousandths separators for STX balance * Add scrollbar in message signing screen * Fix spacing in ordinal detail screen * Add loader for recover ordinal screen * fix: disabling ordinals user should not see them in the (#627) * fix: only display tab buttons when more than one tab (#629) * chore: turn on eslint rule and fix up some core internal imports (#630) * chore: turn on eslint rule and fix up some core internal imports but not all, as some are not exported yet * chore: escape . in eslint rule * chore: use a different eslint rule for this * release: v0.22.0 (#626) * fix: unable to change the network to testnet on the latest (#636) * chore: investigate npm dependabot warnings on extension (#635) * Hotfix/fix (#646) * release: v0.22.0-rc.4 to main (#642) * fix: refactor select currency logic to use reducer and unit test * chore: commit package-lock * fix: disable button when insufficient balance * fix: add token name to min received * fix: advanced settings modal on swaps * fix: use 4dp rounding on exchange rate * fix: toggle button should also toggle amounts * fix: function font style should be body_medium_m * feat: make swap confirmation buttons sticky bottom * fix: standardise button hover, active, disabled css ref: https://zeroheight.com/0683c9fa7/p/5270b4-buttons/b/32e1a2 * feat: add error state to slippage input * fix: use intended params for swap advance settings on apply handler * fix: padding on swap confirm buttons * fix: no exponents in fees block of swap confirmation * feat: add sponsor swap transaction UI and add a hook with placeholder * feat: bump xverse-core dep version and use sponsor2 url * fix: bump xverse-core version and handle sponsor transaction error * feat(Dashboard): update new action button style and add swap button * feat(Swap): add swap screen placeholder * feat(swap): add swap basic UI * feat(swap): add more UI components * feat(swap): add slippage * feat(swap): make BitCoin and Stacks option in the coin select modal * feat(swap): add token selection logic * feat: add swap data connection * feat(swap): add runSwap * chore(swap): bump alex-sdk version * fix(swap): slippage setting * feat: swap confirm layout * feat: add data bindings * fix: function name and copy address * feat: add advanced settings to the confirmation page * feat: bump alex-sdk to latest and fix the issue where from is the same as to * feat: add math.floor before converting to bigInt * fix: skip action when input is invalid * chore: update package.lock * feat(swap): update to support amm v1_1 pools * chore: fix lint warnings * fix: update swap svg stroke and add missing translation * fix: add key to fix react warnings * chore: fix lint warnings * fix: add error 700 border to swap token card * fix: update swap error message and restrict input to number * chore: prettier * style: add a not-allowed cursor to disabled buttons * fix: update translations * fix: use background elevation_1 * style: update swap details spacing * style: use unicode arrow right for swap route details * feat: implement toggle tokens button in swaps * feat: add reset slippage button to swaps * fix: refactor select currency logic to use reducer and unit test * chore: commit package-lock * fix: disable button when insufficient balance * fix: add token name to min received * fix: advanced settings modal on swaps * fix: use 4dp rounding on exchange rate * fix: toggle button should also toggle amounts * fix: function font style should be body_medium_m * feat: make swap confirmation buttons sticky bottom * fix: standardise button hover, active, disabled css ref: https://zeroheight.com/0683c9fa7/p/5270b4-buttons/b/32e1a2 * feat: add error state to slippage input * fix: use intended params for swap advance settings on apply handler * fix: padding on swap confirm buttons * fix: no exponents in fees block of swap confirmation * style: prettier * chore: update package-lock with npm i * chore: fix package-lock.json for correct xverse-core dep * chore: replace workflow dep with github api calls * chore: remove erroneous ` * Fix Edit fees popup error * Fix Edit fees popup error * Add receiving address verification for ledger accounts * Fix warning display condition on the ledger verification screen * Dont close popup on checkbox click * fix: fix for JSON serialisation of location.state * chore: use the test instance URL * chore: upgrade to alex-sdk 0.1.14 * Show wrong ledger device error * Add custom ledger account name validation * fix: turn react deps warning back on and fix stale callback * fix: move sponsor url to constants and add sponsor to swap unsigned tx * fix: display the transaction explorer link on successful sponsored tx * fix: set swap transaction fee to 0 if being sponsored * The transaction history not showing a pending send transaction that was just sent from the address * release v0.14.0 * chore: update xverse-core to published release 1.4.0 * chore: update swaps xverse sponsor 2 host * fix: remove btc from swaps coin modal. change was removed during merge * fix: remove static text describing swap route * Add Before Getting Started screen for Ledger onboarding * update copy * add links and update copy * chore: add husky and pre commit prettier to staged files * chore: run prettier --write ./src * fix: show inscription service fee fiat amount * Fix ledger account name UI issue * Show loader instead of previous transaction history data * comment out swaps button * Add localization for onboarding flow Ledger Live warning screen * chore: automatically do husky install on npm install * chore: prefer .husky for hooks * chore: run prettier again * fix: fix unsigned tx serialisation in advance settings for swaps * fix: use getNewNonce to ensure swaps transactions have correct nonce * docs: comment * bump xverse-core to v1.4.2 * fix: only show swaps button if not on a ledger account * fix: post condition info should display to/from depending on the swap * Fix regression issues v0.14.0-rc.1 * fix: ensure we are sending the correct ordinal * fix: ensure we are sending the correct ordinal * 0.14.1 * fix: remove unnecessary nested BalanceAmountText * fix: use semantic html instead of all h1 * fix: add a filter list to not display coins for swaps * Fix screen crash when changing account on /send-brc20 screen * fix: button should always be disabled when processing and show loading state in swap with await, and fix typing * Update /send-brc20 screen logic * use toString to convert value to BigNumber * chore: bump xverse-core to 1.5.0 * fix: not all tickers are all uppercase e.g. xBTC * fix: decouple alex swap from/to token list and visible token list * Fix the case when address verification screen flashes when using wrong device * Add an override switch for sponsoring transaction in swap screen * Update function param name * chore: bump core version to 1.6.0 (#560) * Allow send ordinal to self and display warning instead of error (#525) * feat: allow send ordinal to self and display warning instead of error * feat: remove send btc send to self error and use warning also adjust send ordinal warning if sending brc20 ordinal to self * Release v0.15.0 (#544) * bump version to v0.15.0 * Switch to alex sdk & url for sponsoring service Inspect error code and show sponsoring service info block feat: move alex sponsored transaction hook to new file revert: changes to useSponsoredTransaction hook for future use fix: sponsor transaction switch should toggle userOverrideSponsorValue feat: add a try again button on failed swaps due to sponsor error * feat: add warning texts to explain why a swap cannot be sponsored (#553) * fix: enforce width on swap number input (#554) * Fix swap screen fee update issue (#555) * Use correct value for fee card component * Fix fee state management * Dont show fee block when sponsored * chore: bump xverse-core to 1.5.1 to include fetchStxPendingTxData fix * Fix ui glitch on the login screen (#556) * Remove css transition code * Add hover and active state * fix: use :disabled css pseudo class instead of props on buttons --------- Co-authored-by: Tim Man * Fix swaps tx history (#557) * Use ft transfer array to show token specific history * Add filter to only show specific token history * Refactor txTransfers file to reuse code * Use token ticker * Update variable name * Use correct decimal places for fungible token (#559) * Use ftDecimals function for fungible token amount * fix undefined check --------- Co-authored-by: Tim Man Co-authored-by: Imamah-Zafar Co-authored-by: Tim Man Co-authored-by: Imamah-Zafar <88320460+Imamah-Zafar@users.noreply.github.com> * chore: bump xverse-core to 1.6.1 (#561) * fix: Don't use ordinals as inputs when recovering ordinals (#558) * Use updated type from sats-connect (#546) * Use updated type from sats-connect * chore: bump sats-connect to 1.0.0 * Update package-lock.json --------- Co-authored-by: Tim Man Co-authored-by: Tim Man * feat: remove binance onramp from buy screen (#567) * Release 0.16.0 (#565) * 0.16.0 * feat: change restore and backup wallet inputs (#566) * chore: fix eslint errors with typing and use of fragments * feat: move restore wallet route to larger screen size * feat: replace restore wallet seed phrase text area with input boxes * fix: ensure only one input can be visible at a time * feat: implement verify seed quiz * feat: disable copy of seed phrase view * fix: set error message properly * refactor: clean up to use one effect * fix: styling and input labels on backup/restore wallet * fix: handle edge case where new mnemonic already contains the answer * feat: seedPhraseInput will focus on next input on space and do not allow some common special characters * fix: decouple copy from restore wallet and change password screens * fix: bump xverse-core to 1.6.2 (#574) * Fix incorrect swap transaction fee ticker (#562) * Use STX as fee currency in swap screen * Fix typo * Fix variable name * Fix fiat rate state calculation * Show correct ticker and currency * Remove isSponsored check for fee amount * Add ability to remove connected Ledger account (#524) * Add ability to remove connected Ledger account * Improve the ledger account removal logic * Select the first account after ledger account removal * Create a reusable `optionsDialog` component * Fix account list logic, rename `seperator` component to `separator` * Fix option dialog indents * Add `deviceAccountIndex` value for ledger accounts that are missing it * Fix ledger account address verification * Move the `deviceAccountIndex` field migration to `loadActiveAccounts` func * Update copy --------- Co-authored-by: Tim Man * Support BTC message signing with ledger accounts (#549) * Support BTC message signing with ledger accounts * Remove console.logs * Change ledger signing modal displaying * Add segwit tx signing functionality * Use `signSimpleBip322Message` method for bip 322 message signing * Update package-lock.json * Update PSBT signing logic for ledger accounts * Add `findLedgerAccountId` util, update usage for `signIncomingSingleSigPSBT` function * Update `isAccountSelected` func logic for external popup account select window * Pass an array of inputs to sign to `signIncomingSingleSigPSBT` func * Move `handleBip322LedgerMessageSigning` func to utils, make some small code fixes * Disable the PSBT signing for BTC ledger accounts * Remove the `@stacks/common` module * Add error warning regarding ledger Bitcoin app version * Update error handling for signature request popup * Update copy and make some small UI tweaks * Update the copy for message signing screen * release: v0.17.0 (#576) * release: v0.17.0 * chore: bump to 1.6.3-rc.1 (#575) * chore: bump to 1.6.3-rc.1 * bump core version --------- Co-authored-by: Yukan * update package-lock --------- Co-authored-by: Yukan * Filter out dead pending transactions (#568) * Filter out dead pending transactions * Get account current nonce from confirmed and pending transactions * Refactor filterng out dead pending transaction logic * Address comments * Update variable name * fix: account select navigation (#578) * Display pending fungible token transactions in token dashboard (#572) * Update filterTx function to show pending fungible transactions * Fix transaction title display * Show pending ordinal transactions (#563) * Show pending ordinal transactions * Slight refactor of getBtcTokenTransferTitle * Fix duplicate tx in groupBtcTxsByDate * chore: bump to xverse-core v1.6.4-rc.2 * Remove unused fallback value --------- Co-authored-by: Den <36603049+dhriaznov@users.noreply.github.com> Co-authored-by: Tim Man * feat: add Xverse namespaced providers (#581) * add Xverse namespaced providers * fix comments * Enable PSBT signing for ledger BTC accounts (#570) * Support BTC message signing with ledger accounts * Remove console.logs * Change ledger signing modal displaying * Add segwit tx signing functionality * Use `signSimpleBip322Message` method for bip 322 message signing * Update package-lock.json * Update PSBT signing logic for ledger accounts * Add `findLedgerAccountId` util, update usage for `signIncomingSingleSigPSBT` function * Update `isAccountSelected` func logic for external popup account select window * Pass an array of inputs to sign to `signIncomingSingleSigPSBT` func * Move `handleBip322LedgerMessageSigning` func to utils, make some small code fixes * Disable the PSBT signing for BTC ledger accounts * Enable PSBT signing for ledger BTC accounts * Remove the `@stacks/common` module * Add error warning regarding ledger Bitcoin app version * Update error handling for signature request popup * Update copy and make some small UI tweaks * Update the copy for message signing screen * Update PSBT signing screen copy * fix: allow display of brc-20 if application/json type (#587) * fix: allow display of brc-20 if application/json type * make bg colours for a BRC-20 coin consistent * fix: full screen and popup styling (#577) * fix: full screen and popup styling * fix screen container * fix app load styling * fix full screen popup pages --------- Co-authored-by: Tim Man * feat: add inscription confirmation for sats connect (#571) * feat: add inscription confirmation for sats connect * implement inscribing and improve event naming * Added complete inscription screen * Add edit fees * Add HTML and SVG preview functionality * change content to content type and allow more previews * implement error designs on main screen * add error modal * fix: completion page not displaying * fix: styling issues and add inscription developer fees * add custom initial fee rate * fix screen height styling * set app height to 600 again * revert screen container size * improve screen container height * moved feerate get one level up * fix: full screen and popup styling * fix screen container * fix app load styling * Make top address selector sticky. Also fixes it for sign psbt and sign message requests * Update todos * fix full screen popup pages * fix account row styling * add json validaiton * add brc-20 and sats names views * add preview menu and fix styling * add preview markdown * switch to single inscribe event * fix preview of file type text and html * Ensure we only use non ordinal UTXOs for inscriptions * update sats-connect arg names and update the dom event name * fix type name * Show correct fiat currency * Use phosphor-icons * update beta core * fix: fee rate and styling issues * clean up icons * Clean up more icons * Update Core * Update sats connect version * Update sats connect * Add STX support for Ledger accounts (#564) * Add STX support for Ledger accounts * Update the copy and ledger account import logic * Update the ledger account import logic for STX support * Add `/add-stx-address-ledger` route for adding stx account * Remove the old case handling when there is no `ordinalsAddress` * Add STX address verification with ledger device * Remove the `/send-stx-ledger` path and update `/send-stx` to handle ledger * Handle regular STX transactions * Remove unused `/review-ledger-stx-tx` path, add STX NFT handling * Remove `/review-ledger-ft-tx` and `/send-ft-ledger` routes, update `/send-ft` to support ledger * Enable STX auth requests for ledger accounts that have an STX address * Add link to the auth popup to add the STX Ledger account * Update screen UI for STX NFT sending * Update copy for STX incoming tx signing * Update STX message signing logic * Update STX-related logic for ledger accounts * Update error handling for STX message signing with ledger * Get rid of `findLedgerAccountId` ledger util, move more copy to locales * Fix cropped button container for tx signing popup * Update address index definition for adding stx address * Update address index definition for address verification and stx tx confirmation * Update address index definition for stx jwt auth * Fix ledger account import when both BTC and STX options are selected * Fix `unsignedTx` type * Add `StacksRecipient` type and make some small code fixes * Add `icon` prop for the `ActionButton` component * Change the ledger steps in a callback * Add types for ledger tx state objects * Fix account index for stx account import, update ledger tx types and utils * Change the import path for `StacksRecipient` * Add error handling when user rejects stx address * Add a guard from account changing for the `/add-stx-address-ledger` route * Remove the back button on the ledger account import flow * Throw an error when `stacksCreds` were not fetched * Upgrade `@secretkeylabs/xverse-core` version to `^1.6.5-3bb284c` * Update package files * Fix add stx address error handling * Fix signing with 1st ledger account and bottom modal position * Skip the `No errors` value of the `errorMessage` for STX ledger signature * Remove the caret symbol in `sats-connect` package version * chore: bump xverse-core version 1.7.2 --------- Co-authored-by: Tim Man * fix: brc-20 balance number check (#592) * fix: don't show fees until we have UTXOs on inscription screen (#594) * fix: improve full screen popup styling (#593) * fix: improve full screen popup styling * fix: set height to 600 on options window * Release/v0.18.0 (#591) * release: v0.18.0 * feat: add inscription confirmation for sats connect (#571) * feat: add inscription confirmation for sats connect * implement inscribing and improve event naming * Added complete inscription screen * Add edit fees * Add HTML and SVG preview functionality * change content to content type and allow more previews * implement error designs on main screen * add error modal * fix: completion page not displaying * fix: styling issues and add inscription developer fees * add custom initial fee rate * fix screen height styling * set app height to 600 again * revert screen container size * improve screen container height * moved feerate get one level up * fix: full screen and popup styling * fix screen container * fix app load styling * Make top address selector sticky. Also fixes it for sign psbt and sign message requests * Update todos * fix full screen popup pages * fix account row styling * add json validaiton * add brc-20 and sats names views * add preview menu and fix styling * add preview markdown * switch to single inscribe event * fix preview of file type text and html * Ensure we only use non ordinal UTXOs for inscriptions * update sats-connect arg names and update the dom event name * fix type name * Show correct fiat currency * Use phosphor-icons * update beta core * fix: fee rate and styling issues * clean up icons * Clean up more icons * Update Core * Update sats connect version * Update sats connect * Use `ticker` field instead of `name` for ft and brc-20 token sending (#595) * update package-lock.json --------- Co-authored-by: Victor Kirov Co-authored-by: Yukan Co-authored-by: Den <36603049+dhriaznov@users.noreply.github.com> * Fix Tx history screen reload & send form navigation issues (#584) * Donot show spinner when refetching transaction history data * Fix STX navigation issue * Fix input fields clearance on naviagtion * Update dependency array for useEffect * Add comment * Fix memo error * Support script outputs in psbts (#534) * Support script outputs * Update copy * Revert package.json change * Add info message for output script * Use Array.some * Remove unused style * Update core versio and address comments * Remove pretext from output script ui * Refactor render script output logic * Donot show spinner when refetching transaction history data * Fix STX navigation issue * Fix input fields clearance on naviagtion * chore: turn on eslint for staged files on precommit and turn off no-plusplus rule * Fix eslint issues * Update dependency array for useEffect * Add comment * Fix script count * fix: eslint fix --------- Co-authored-by: Tim Man Co-authored-by: Tim Man * Mismatch when verifying the addresses from the ledger device (#580) * Handle min fee rate returned by the API (#583) * Handle min fee rate returned by the API * Add a check for the STX tx min fee * Fix a couple of tslint errors * fix: don't show loader on balance refetch on home screen ENG-2757 (#589) * Add refetch loader * resize loading spinner * Release/v0.18.2 (#600) * release: v0.18.0 * feat: add inscription confirmation for sats connect (#571) * feat: add inscription confirmation for sats connect * implement inscribing and improve event naming * Added complete inscription screen * Add… * feat: send flow (#63) * feat: tweak rare sats bundle screen to support new satributes * feat: update send rare sats screen * feat: update confirm screen * fix: UI * fix: resolve comments --------- Co-authored-by: fede erbes * feat: add v2 of useGetUtxoOrdinalBundle and use it in ordinal detail screen (#59) * feat: tweak rare sats bundle screen to support new satributes * feat: add v2 of useGetUtxoOrdinalBundle and use it in ordinal detail screen * feat: add sattributes to ordinal details screen (#60) * chore: remove glow from RareSatIcon component * feat: add sattributes to ordinal details screen --------- Co-authored-by: Abdul Haseeb --------- Co-authored-by: Abdul Haseeb * feat: send inscriptions that belong to a rare sat (#64) * feat: tweak rare sats bundle screen to support new satributes * feat: add v2 of useGetUtxoOrdinalBundle and use it in ordinal detail screen * feat: ordinal bundle in confirm screen * fix: add missing ordinal number --------- Co-authored-by: fede erbes * feat: add sat bundle layout to sign psbt screen (#65) * chore: move bundle from ConfirmBtcTransactionComponent to it own component * chore: use getSatLabel from utils in RareSatsBundleGridItem * feat: tweak useDetectOrdinalInSignPsbt hook to use V2 of rare sats api and implement sats bundle component in SignPsbtRequest screen * feat: show callout if ordinal holds a rare sat (#66) * chore: move methods and utils to core, remove v2 suffix and clean up code (#67) * chore: remove unknown type and unify it with common * fix: go back navigation when navigating from inscription to bundle screen * chore: move methods and utils to core, remove v2 suffix and clean up code * feat: confirm ordinal screen gallery view (#68) * refactor: send rare sats screen, update gallery UI * feat: confirm screen gallery UI update * chore: remove console log, used space object --------- Co-authored-by: fede erbes * fix: testing issues (#69) * fix: spacing issues * added scrollbar in rare sat detail * fix: scrollbar in confirm screen * fix: scrollbar in rarity screen * fix: send errors * removed console log * fix: rare sat bundle navigation * fix: rare detail text * fix: confirm UI * fix: rare sats bundle navigation * fix: logic and ui in RareSatsTabGridItem component (#692) * fix: typo * chore: use colors from theme * fix: padding * chore: remove mock data and code * fix: restore cache key * fix: UI * fix: import sort --------- Co-authored-by: Yukan Co-authored-by: Den <36603049+dhriaznov@users.noreply.github.com> Co-authored-by: Abdul Haseeb Co-authored-by: fede erbes Co-authored-by: Victor Kirov Co-authored-by: Imamah-Zafar <88320460+Imamah-Zafar@users.noreply.github.com> Co-authored-by: Duska.T <55587184+DuskaT021@users.noreply.github.com> Co-authored-by: Mahmoud Aboelenein Co-authored-by: Kyle Fang Co-authored-by: Denys Hriaznov Co-authored-by: Imamah-Zafar Co-authored-by: Jordan K <65149726+jordankzf@users.noreply.github.com> --- package-lock.json | 14 +- package.json | 2 +- src/app/components/assetModal/index.tsx | 88 ++++ .../components/bundleAsset/bundleAsset.tsx | 42 -- .../collectibleCollage/collectibleCollage.tsx | 16 +- .../confirmBtcTransactionComponent/bundle.tsx | 109 +++++ .../bundleItem.tsx | 128 +++++ .../confirmBtcTransactionComponent/index.tsx | 48 +- .../exoticSatsRow/exoticSatsRow.tsx | 120 +++++ .../components/rareSatAsset/rareSatAsset.tsx | 86 ---- .../components/rareSatIcon/rareSatIcon.tsx | 103 ++-- .../components/recipientComponent/index.tsx | 55 ++- .../queries/ordinals/useAddressRareSats.ts | 27 +- src/app/hooks/useDetectOrdinalInSignPsbt.ts | 52 ++- src/app/layouts/sendLayout.tsx | 14 +- src/app/routes/index.tsx | 8 + .../screens/confirmBtcTransaction/index.tsx | 11 +- .../confirmOrdinalTransaction/index.tsx | 115 ++--- src/app/screens/login/index.tsx | 4 +- .../screens/nftDashboard/collectiblesTabs.tsx | 27 +- .../nftDashboard/inscriptionsTabGridItem.tsx | 10 +- .../nftDashboard/rareSatsTabGridItem.tsx | 149 ++++-- .../nftDashboard/supportedRarities/index.tsx | 26 +- .../supportedRarities/rarityTile.tsx | 43 +- src/app/screens/ordinalDetail/index.tsx | 156 ++++--- .../screens/ordinalDetail/useOrdinalDetail.ts | 11 +- src/app/screens/rareSatsBundle/index.tsx | 158 +++++-- .../rareSatsBundle/rareSatsBundleGridItem.tsx | 107 ++--- .../screens/rareSatsDetail/rareSatsDetail.tsx | 440 +----------------- src/app/screens/sendOrdinal/index.tsx | 2 +- src/app/screens/sendRareSat/index.tsx | 311 ++++++------- .../bundleItemsComponent.tsx | 233 ---------- .../screens/signBatchPsbtRequest/index.tsx | 42 +- .../signPsbtRequest/bundleItemsComponent.tsx | 219 --------- src/app/screens/signPsbtRequest/index.tsx | 34 +- src/app/stores/nftData/actions/types.ts | 3 +- src/app/utils/constants.ts | 2 - src/app/utils/inscriptions.ts | 10 - src/app/utils/rareSats.test.ts | 187 -------- src/app/utils/rareSats.ts | 249 ++-------- .../img/nftDashboard/rareSats/1d_pali.svg | 9 + .../img/nftDashboard/rareSats/2d_pali.svg | 9 + .../img/nftDashboard/rareSats/3d_pali.svg | 9 + .../img/nftDashboard/rareSats/alpha.svg | 9 + .../img/nftDashboard/rareSats/black_epic.svg | 9 + .../nftDashboard/rareSats/black_legendary.svg | 10 + .../img/nftDashboard/rareSats/black_rare.svg | 7 + .../nftDashboard/rareSats/black_uncommon.svg | 8 + .../img/nftDashboard/rareSats/block_78.svg | 9 + .../img/nftDashboard/rareSats/block_9.svg | 9 + .../img/nftDashboard/rareSats/block_pali.svg | 9 + src/assets/img/nftDashboard/rareSats/epic.svg | 16 +- .../rareSats/fibonacci_sequence.svg | 9 + .../rareSats/first_transaction_silkroad.svg | 9 + .../img/nftDashboard/rareSats/hitman.svg | 9 + src/assets/img/nftDashboard/rareSats/jpeg.svg | 9 + .../img/nftDashboard/rareSats/nakamoto.svg | 6 + .../img/nftDashboard/rareSats/omega.svg | 4 + .../nftDashboard/rareSats/palinception.svg | 9 + .../img/nftDashboard/rareSats/palindrome.svg | 17 + .../img/nftDashboard/rareSats/pizza.svg | 20 + src/assets/img/nftDashboard/rareSats/rare.svg | 12 +- .../nftDashboard/rareSats/sequence_pali.svg | 9 + .../img/nftDashboard/rareSats/uncommon.svg | 14 +- .../img/nftDashboard/rareSats/vintage.svg | 22 + src/assets/img/rareSats/ic_ordinal_small.svg | 6 + src/assets/img/rareSats/satBundle.svg | 5 + src/locales/en.json | 91 +++- 68 files changed, 1644 insertions(+), 2181 deletions(-) create mode 100644 src/app/components/assetModal/index.tsx delete mode 100644 src/app/components/bundleAsset/bundleAsset.tsx create mode 100644 src/app/components/confirmBtcTransactionComponent/bundle.tsx create mode 100644 src/app/components/confirmBtcTransactionComponent/bundleItem.tsx create mode 100644 src/app/components/exoticSatsRow/exoticSatsRow.tsx delete mode 100644 src/app/components/rareSatAsset/rareSatAsset.tsx delete mode 100644 src/app/screens/signBatchPsbtRequest/bundleItemsComponent.tsx delete mode 100644 src/app/screens/signPsbtRequest/bundleItemsComponent.tsx delete mode 100644 src/app/utils/rareSats.test.ts create mode 100644 src/assets/img/nftDashboard/rareSats/1d_pali.svg create mode 100644 src/assets/img/nftDashboard/rareSats/2d_pali.svg create mode 100644 src/assets/img/nftDashboard/rareSats/3d_pali.svg create mode 100644 src/assets/img/nftDashboard/rareSats/alpha.svg create mode 100644 src/assets/img/nftDashboard/rareSats/black_epic.svg create mode 100644 src/assets/img/nftDashboard/rareSats/black_legendary.svg create mode 100644 src/assets/img/nftDashboard/rareSats/black_rare.svg create mode 100644 src/assets/img/nftDashboard/rareSats/black_uncommon.svg create mode 100644 src/assets/img/nftDashboard/rareSats/block_78.svg create mode 100644 src/assets/img/nftDashboard/rareSats/block_9.svg create mode 100644 src/assets/img/nftDashboard/rareSats/block_pali.svg create mode 100644 src/assets/img/nftDashboard/rareSats/fibonacci_sequence.svg create mode 100644 src/assets/img/nftDashboard/rareSats/first_transaction_silkroad.svg create mode 100644 src/assets/img/nftDashboard/rareSats/hitman.svg create mode 100644 src/assets/img/nftDashboard/rareSats/jpeg.svg create mode 100644 src/assets/img/nftDashboard/rareSats/nakamoto.svg create mode 100644 src/assets/img/nftDashboard/rareSats/omega.svg create mode 100644 src/assets/img/nftDashboard/rareSats/palinception.svg create mode 100644 src/assets/img/nftDashboard/rareSats/palindrome.svg create mode 100644 src/assets/img/nftDashboard/rareSats/pizza.svg create mode 100644 src/assets/img/nftDashboard/rareSats/sequence_pali.svg create mode 100644 src/assets/img/nftDashboard/rareSats/vintage.svg create mode 100644 src/assets/img/rareSats/ic_ordinal_small.svg create mode 100644 src/assets/img/rareSats/satBundle.svg diff --git a/package-lock.json b/package-lock.json index 1127f1f8d..ac35a50f0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,7 @@ "@ledgerhq/hw-transport-webusb": "^6.27.13", "@phosphor-icons/react": "^2.0.10", "@react-spring/web": "^9.6.1", - "@secretkeylabs/xverse-core": "3.1.1", + "@secretkeylabs/xverse-core": "4.0.0", "@stacks/connect": "^6.10.2", "@stacks/encryption": "4.3.5", "@stacks/stacks-blockchain-api-types": "6.1.1", @@ -1727,9 +1727,9 @@ } }, "node_modules/@secretkeylabs/xverse-core": { - "version": "3.1.1", - "resolved": "https://npm.pkg.github.com/download/@secretkeylabs/xverse-core/3.1.1/c21f1fb2f3efb5938adb8ce211c1fc1e5521de33", - "integrity": "sha512-HyRLLFW1zE69xJfZhupcVoqi6f4k38cJtATMtSsIh3c0gC5ROWDH/W/FIdnVZV0vQ5o8bZj2GQmmww6leYAtsQ==", + "version": "4.0.0", + "resolved": "https://npm.pkg.github.com/download/@secretkeylabs/xverse-core/4.0.0/b2776f6bd4a6eb065b31eafd6ab198c486af7b8d", + "integrity": "sha512-u4XBHz8VYeQGFtpjxfOIMMcx6OAusQx02j/fAanwURHAbgeX8P90esY4s8rtAFcOV16dGRqA/HHEIusnt3d6+Q==", "license": "ISC", "dependencies": { "@bitcoinerlab/secp256k1": "^1.0.2", @@ -16046,9 +16046,9 @@ } }, "@secretkeylabs/xverse-core": { - "version": "3.1.1", - "resolved": "https://npm.pkg.github.com/download/@secretkeylabs/xverse-core/3.1.1/c21f1fb2f3efb5938adb8ce211c1fc1e5521de33", - "integrity": "sha512-HyRLLFW1zE69xJfZhupcVoqi6f4k38cJtATMtSsIh3c0gC5ROWDH/W/FIdnVZV0vQ5o8bZj2GQmmww6leYAtsQ==", + "version": "4.0.0", + "resolved": "https://npm.pkg.github.com/download/@secretkeylabs/xverse-core/4.0.0/b2776f6bd4a6eb065b31eafd6ab198c486af7b8d", + "integrity": "sha512-u4XBHz8VYeQGFtpjxfOIMMcx6OAusQx02j/fAanwURHAbgeX8P90esY4s8rtAFcOV16dGRqA/HHEIusnt3d6+Q==", "requires": { "@bitcoinerlab/secp256k1": "^1.0.2", "@noble/secp256k1": "^1.7.1", diff --git a/package.json b/package.json index 89110eee9..83a967996 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "@ledgerhq/hw-transport-webusb": "^6.27.13", "@phosphor-icons/react": "^2.0.10", "@react-spring/web": "^9.6.1", - "@secretkeylabs/xverse-core": "3.1.1", + "@secretkeylabs/xverse-core": "4.0.0", "@stacks/connect": "^6.10.2", "@stacks/encryption": "4.3.5", "@stacks/stacks-blockchain-api-types": "6.1.1", diff --git a/src/app/components/assetModal/index.tsx b/src/app/components/assetModal/index.tsx new file mode 100644 index 000000000..a0274acc9 --- /dev/null +++ b/src/app/components/assetModal/index.tsx @@ -0,0 +1,88 @@ +import Cross from '@assets/img/dashboard/X.svg'; +import { animated, useSpring } from '@react-spring/web'; +import OrdinalImage from '@screens/ordinals/ordinalImage'; +import { CondensedInscription, SatRangeInscription } from '@secretkeylabs/xverse-core'; +import styled from 'styled-components'; + +const TransparentButton = styled.button({ + background: 'transparent', + display: 'flex', + alignItems: 'center', + marginLeft: 10, +}); + +const CrossContainer = styled.div({ + display: 'flex', + marginTop: 10, + justifyContent: 'flex-end', + alignItems: 'flex-end', +}); + +const OrdinalOuterImageContainer = styled.div({ + justifyContent: 'center', + alignItems: 'center', + borderRadius: 2, + display: 'flex', + flexDirection: 'column', + flex: 1, +}); + +const OrdinalImageContainer = styled.div({ + width: '50%', +}); + +const OrdinalBackgroundContainer = styled(animated.div)({ + width: '100%', + height: '100%', + top: 0, + left: 0, + bottom: 0, + right: 0, + position: 'fixed', + zIndex: 10, + background: 'rgba(18, 21, 30, 0.8)', + backdropFilter: 'blur(16px)', + padding: 16, + display: 'flex', + flexDirection: 'column', +}); + +interface Props { + inscription: SatRangeInscription; + onClose: () => void; +} + +function AssetModal({ inscription, onClose }: Props) { + const consdensedInscription: CondensedInscription = { + ...inscription, + number: inscription.inscription_number, + }; + const styles = useSpring({ + from: { + opacity: 0, + y: 24, + }, + to: { + y: 0, + opacity: 1, + }, + delay: 100, + }); + + return ( + + + + cross + + + + + + + + + ); +} + +export default AssetModal; diff --git a/src/app/components/bundleAsset/bundleAsset.tsx b/src/app/components/bundleAsset/bundleAsset.tsx deleted file mode 100644 index 284534cba..000000000 --- a/src/app/components/bundleAsset/bundleAsset.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import RareSatAsset from '@components/rareSatAsset/rareSatAsset'; -import { Bundle } from '@utils/rareSats'; -import styled from 'styled-components'; - -import CollectibleCollage from '../collectibleCollage/collectibleCollage'; - -const ImageContainer = styled.div` - display: flex; - justify-content: center; - align-items: center; - width: 100%; - aspect-ratio: 1; - overflow: hidden; - border-radius: ${(props) => props.theme.radius(1)}px; -`; - -const IndividualAssetContainer = styled.div` - display: flex; - width: 100%; - background: ${(props) => props.theme.colors.elevation1}; -`; - -interface Props { - bundle: Bundle; -} - -function BundleAsset({ bundle }: Props) { - const isMoreThanOneItem = bundle.items?.length > 1; - return ( - - {isMoreThanOneItem ? ( - - ) : ( - - - - )} - - ); -} - -export default BundleAsset; diff --git a/src/app/components/collectibleCollage/collectibleCollage.tsx b/src/app/components/collectibleCollage/collectibleCollage.tsx index e15ee56c5..c875cda75 100644 --- a/src/app/components/collectibleCollage/collectibleCollage.tsx +++ b/src/app/components/collectibleCollage/collectibleCollage.tsx @@ -1,7 +1,6 @@ -import RareSatAsset from '@components/rareSatAsset/rareSatAsset'; import Nft from '@screens/nftDashboard/nft'; -import { NonFungibleToken } from '@secretkeylabs/xverse-core'; -import { BundleItem } from '@utils/rareSats'; +import OrdinalImage from '@screens/ordinals/ordinalImage'; +import { CondensedInscription, NonFungibleToken } from '@secretkeylabs/xverse-core'; import styled from 'styled-components'; const CollageContainer = styled.div` @@ -39,10 +38,11 @@ const RemainingAmountOfAssets = styled.div((props) => ({ }, })); -function CollectibleCollage({ items }: { items: Array }) { +function CollectibleCollage({ items }: { items: Array }) { const moreThanFourItems = items.length > 4; - const isBundleItem = (item: any): boolean => (item as BundleItem).rarity_ranking !== undefined; + const isStacksNft = (item: CondensedInscription | NonFungibleToken): boolean => + 'asset_identifier' in item; return ( {items.slice(0, 4).map((item, index) => ( @@ -53,10 +53,10 @@ function CollectibleCollage({ items }: { items: Array+{items.length - 4}

) : // Conditionally render RareSatAsset if item is a BundleItem otherwise render Nft - isBundleItem(item) ? ( - - ) : ( + isStacksNft(item) ? ( + ) : ( + )} ))} diff --git a/src/app/components/confirmBtcTransactionComponent/bundle.tsx b/src/app/components/confirmBtcTransactionComponent/bundle.tsx new file mode 100644 index 000000000..84b728b71 --- /dev/null +++ b/src/app/components/confirmBtcTransactionComponent/bundle.tsx @@ -0,0 +1,109 @@ +import BundleIcon from '@assets/img/rareSats/satBundle.svg'; +import AssetModal from '@components/assetModal'; +import { CaretDown } from '@phosphor-icons/react'; +import { Bundle, BundleSatRange, SatRangeInscription } from '@secretkeylabs/xverse-core'; +import { StyledP } from '@ui-library/common.styled'; +import { useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import styled from 'styled-components'; +import Theme from 'theme'; +import BundleItem from './bundleItem'; + +interface BundleItemContainerProps { + addMargin: boolean; +} + +const BundleItemsContainer = styled.div` + margin-top: ${(props) => (props.addMargin ? props.theme.space.m : 0)}; +`; + +const SatsBundleContainer = styled.div` + display: flex; + flex-direction: column; + margin-bottom: ${(props) => props.theme.space.s}; + border-radius: ${(props) => props.theme.space.s}; + padding: ${(props) => props.theme.space.m}; + background-color: ${(props) => props.theme.colors.elevation1}; +`; + +const SatsBundleButton = styled.button` + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + background-color: ${(props) => props.theme.colors.elevation1}; +`; + +const Row = styled.div` + display: flex; + flex-direction: row; + align-items: center; +`; + +const BundleTitle = styled(StyledP)` + margin-left: ${(props) => props.theme.space.s}; +`; + +const BundleValue = styled(StyledP)` + margin-right: ${(props) => props.theme.space.xs}; +`; + +const Title = styled(StyledP)((props) => ({ + marginBottom: props.theme.space.xs, +})); + +function SatsBundle({ bundle, title }: { bundle: Bundle; title?: string }) { + const [showBundleDetail, setShowBundleDetail] = useState(false); + const [inscriptionToShow, setInscriptionToShow] = useState( + undefined, + ); + + const { t } = useTranslation('translation'); + + return ( + <> + {inscriptionToShow && ( + setInscriptionToShow(undefined)} + inscription={inscriptionToShow} + /> + )} + + {title && {title}} + setShowBundleDetail((prevState) => !prevState)} + > + + bundle + + {t('RARE_SATS.SATS_BUNDLE')} + + + + {`${bundle.totalExoticSats} ${t( + 'NFT_DASHBOARD_SCREEN.RARE_SATS', + )}`} + + + + + {showBundleDetail && + bundle.satRanges.map((item: BundleSatRange, index: number) => ( + + { + // show ordinal modal to show asset + setInscriptionToShow(inscription); + }} + showDivider={index !== bundle.satRanges.length - 1} + /> + + ))} + + + ); +} + +export default SatsBundle; diff --git a/src/app/components/confirmBtcTransactionComponent/bundleItem.tsx b/src/app/components/confirmBtcTransactionComponent/bundleItem.tsx new file mode 100644 index 000000000..009220d0b --- /dev/null +++ b/src/app/components/confirmBtcTransactionComponent/bundleItem.tsx @@ -0,0 +1,128 @@ +import OrdinalIcon from '@assets/img/rareSats/ic_ordinal_small.svg'; +import RareSatIcon from '@components/rareSatIcon/rareSatIcon'; +import { DotsThree, Eye } from '@phosphor-icons/react'; +import { BundleSatRange, SatRangeInscription } from '@secretkeylabs/xverse-core'; +import { StyledP } from '@ui-library/common.styled'; +import { getSatLabel } from '@utils/rareSats'; +import { NumericFormat } from 'react-number-format'; +import styled from 'styled-components'; +import Theme from 'theme'; + +const RangeContainer = styled.div``; + +const Range = styled.div` + display: flex; + border-radius: 6px; + border: 1px solid ${(props) => props.theme.colors.white_800}; + padding: 1px; + flex-wrap: wrap; + flex-direction: row; + align-items: center; +`; + +interface ComponentWithDividerProps { + showDivider: boolean; +} + +const Container = styled.div` + padding-top:${(props) => props.theme.space.s}; + padding-bottom:${(props) => props.theme.space.s}; + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + border-bottom: ${(props) => + props.showDivider ? `1px solid ${props.theme.colors.white_900}` : 'transparent'}; + width: 100%; +}`; + +const Column = styled.div` + display: flex; + flex: 1; + flex-direction: column; + align-items: flex-end; +`; + +const SatsText = styled(StyledP)` + width: 100%; + text-align: right; +`; +const InscriptionRow = styled.button` + display: flex; + flex-direction: row; + align-items: center; + background-color: transparent; +`; +const InscriptionText = styled(StyledP)` + text-wrap: nowrap; + overflow: hidden; + text-overflow: ellipsis; + width: 100%; + margin-left: 4px; +`; + +const BundleText = styled(StyledP)` + text-align: right; + width: 100%; +`; + +function BundleItem({ + item, + ordinalEyePressed, + showDivider, +}: { + item: BundleSatRange; + ordinalEyePressed: (inscription: SatRangeInscription) => void; + showDivider?: boolean; +}) { + const renderedIcons = () => ( + + + {item.satributes.map((satribute, index) => { + if (index > 4) return null; + if (index === 4) { + return ; + } + return ; + })} + + + ); + + return ( + + {renderedIcons()} + + {getSatLabel(item.satributes)} + 1 ? ' Sats' : ' Sat'} + thousandSeparator + renderText={(value: string) => ( + + {value} + + )} + /> + {item.inscriptions.map((inscription) => ( + { + ordinalEyePressed(inscription); + }} + > + ordinal + + {inscription.inscription_number} + + + + ))} + + + ); +} + +export default BundleItem; diff --git a/src/app/components/confirmBtcTransactionComponent/index.tsx b/src/app/components/confirmBtcTransactionComponent/index.tsx index 27d998118..6cf5e32e4 100644 --- a/src/app/components/confirmBtcTransactionComponent/index.tsx +++ b/src/app/components/confirmBtcTransactionComponent/index.tsx @@ -3,13 +3,14 @@ import AssetIcon from '@assets/img/transactions/Assets.svg'; import ActionButton from '@components/button'; import InfoContainer from '@components/infoContainer'; import RecipientComponent from '@components/recipientComponent'; -import TopRow from '@components/topRow'; import TransactionSettingAlert from '@components/transactionSetting'; import TransferFeeView from '@components/transferFeeView'; +import useNftDataSelector from '@hooks/stores/useNftDataSelector'; import useOrdinalsByAddress from '@hooks/useOrdinalsByAddress'; import useSeedVault from '@hooks/useSeedVault'; import useWalletSelector from '@hooks/useWalletSelector'; import { + Bundle, ErrorCodes, getBtcFiatEquivalent, Recipient, @@ -30,14 +31,18 @@ import { useTranslation } from 'react-i18next'; import { NumericFormat } from 'react-number-format'; import styled from 'styled-components'; import TransactionDetailComponent from '../transactionDetailComponent'; +import SatsBundle from './bundle'; -const OuterContainer = styled.div` +interface MainContainerProps { + isGalleryOpen: boolean; +} + +const OuterContainer = styled.div` display: flex; flex-direction: column; - overflow-y: auto; - &::-webkit-scrollbar { - display: none; - } + flex: 1; + flex-grow: 1; + ...${(props) => (props.isGalleryOpen ? props.theme.scrollbar : {})}; `; const Container = styled.div((props) => ({ @@ -45,8 +50,6 @@ const Container = styled.div((props) => ({ flexDirection: 'column', flex: 1, marginTop: props.theme.spacing(11), - marginLeft: props.theme.spacing(8), - marginRight: props.theme.spacing(8), })); interface ButtonProps { @@ -57,8 +60,6 @@ const ButtonContainer = styled.div((props) => ({ display: 'flex', flexDirection: 'row', position: 'relative', - marginLeft: props.theme.spacing(8), - marginRight: props.theme.spacing(8), marginBottom: props.isBtcSendBrowserTx ? props.theme.spacing(20) : props.theme.spacing(5), })); @@ -102,13 +103,13 @@ const ErrorText = styled.h1((props) => ({ })); interface ReviewTransactionTitleProps { - isOridnalTx: boolean; + centerAligned: boolean; } const ReviewTransactionText = styled.h1((props) => ({ ...props.theme.typography.headline_s, color: props.theme.colors.white_0, marginBottom: props.theme.spacing(16), - textAlign: props.isOridnalTx ? 'center' : 'left', + textAlign: props.centerAligned ? 'center' : 'left', })); const CalloutContainer = styled.div((props) => ({ @@ -131,6 +132,8 @@ interface Props { isBtcSendBrowserTx?: boolean; currencyType?: CurrencyTypes; isPartOfBundle?: boolean; + ordinalBundle?: Bundle; + holdsRareSats?: boolean; currentFeeRate: BigNumber; setCurrentFee: (feeRate: BigNumber) => void; setCurrentFeeRate: (feeRate: BigNumber) => void; @@ -154,6 +157,8 @@ function ConfirmBtcTransactionComponent({ isBtcSendBrowserTx, isPartOfBundle, currencyType, + ordinalBundle, + holdsRareSats, currentFeeRate, setCurrentFee, setCurrentFeeRate, @@ -165,12 +170,15 @@ function ConfirmBtcTransactionComponent({ const isGalleryOpen: boolean = document.documentElement.clientWidth > 360; const [loading, setLoading] = useState(false); const { btcAddress, selectedAccount, network, btcFiatRate, feeMultipliers } = useWalletSelector(); + const { selectedSatBundle } = useNftDataSelector(); const { getSeed } = useSeedVault(); const [showFeeSettings, setShowFeeSettings] = useState(false); const [error, setError] = useState(''); const [signedTx, setSignedTx] = useState(signedTxHex); const [total, setTotal] = useState(new BigNumber(0)); const [showFeeWarning, setShowFeeWarning] = useState(false); + + const bundle = selectedSatBundle ?? ordinalBundle ?? undefined; const { isLoading, data, @@ -373,10 +381,7 @@ function ConfirmBtcTransactionComponent({ return ( <> - - {!isBtcSendBrowserTx && !isGalleryOpen && ( - - )} + {showFeeWarning && ( + {t('CONFIRM_TRANSACTION.REVIEW_TRANSACTION')} @@ -398,11 +403,18 @@ function ConfirmBtcTransactionComponent({ /> )} + {holdsRareSats && ( + + + + )} + + {bundle && } {ordinalTxUtxo ? ( props.theme.space.m}; +`; + +const IconsContainer = styled.div` + flex: 1; + display: flex; + flex-direction: row; + justify-content: flex-end; +`; + +const StyledBundleId = styled(StyledP)` + text-wrap: nowrap; + overflow: hidden; + text-overflow: ellipsis; + width: 100%; + text-align: left; +`; + +const InscriptionText = styled(StyledP)` + text-wrap: nowrap; + overflow: hidden; + text-overflow: ellipsis; + width: 100%; + margin-left: 4px; + text-align: left; +`; + +const StyledBundleSub = styled(StyledP)` + text-align: left; + width: 100%; +`; + +const ItemContainer = styled.div` + display: flex; + align-items: center; + flex-direction: row; + flex: 1; + padding: ${(props) => props.theme.space.m}; + border-radius: ${(props) => props.theme.space.xs}; + background-color: ${(props) => props.theme.colors.elevation1}; + justify-content: space-between; +`; + +const Row = styled.div` + display: flex; + flex-direction: row; + align-items: center; + flex-direction: row; +`; + +function ExoticSatsRow({ + title, + satAmount, + inscriptions, + icons, + showNumberOfInscriptions = false, +}: { + title: string; + satAmount: number; + inscriptions: SatRangeInscription[]; + showNumberOfInscriptions?: boolean; + icons: ReactNode; +}) { + const { t } = useTranslation('translation', { keyPrefix: 'COMMON' }); + return ( + + + + {title} + + ( + + {value} + + )} + /> + {showNumberOfInscriptions && inscriptions.length ? ( + + ordinal + + {inscriptions.length > 1 + ? `+${inscriptions.length}` + : inscriptions[0].inscription_number} + + + ) : ( + inscriptions.map((inscription) => ( + + ordinal + + {inscription.inscription_number} + + + )) + )} + + {icons} + + ); +} + +export default ExoticSatsRow; diff --git a/src/app/components/rareSatAsset/rareSatAsset.tsx b/src/app/components/rareSatAsset/rareSatAsset.tsx deleted file mode 100644 index 2915738ae..000000000 --- a/src/app/components/rareSatAsset/rareSatAsset.tsx +++ /dev/null @@ -1,86 +0,0 @@ -import RareSatIcon from '@components/rareSatIcon/rareSatIcon'; -import OrdinalImage from '@screens/ordinals/ordinalImage'; -import { Inscription } from '@secretkeylabs/xverse-core'; -import { BundleItem } from '@utils/rareSats'; -import styled from 'styled-components'; - -const Container = styled.div` - width: 100%; - height: 100%; -`; - -const InscriptionContainer = styled.div` - width: 100%; - height: 100%; - position: relative; - border-radius: 8px; - overflow: hidden; -`; - -const RareSatIconContainer = styled.div<{ isGallery: boolean }>((props) => ({ - display: 'flex', - position: 'absolute', - zIndex: 1, - left: props.isGallery ? 20 : 8, - top: props.isGallery ? 20 : 8, -})); - -const RareSatsContainer = styled.div((props) => ({ - display: 'flex', - justifyContent: 'center', - alignItems: 'center', - width: '100%', - aspectRatio: '1', - overflow: 'hidden', - position: 'relative', - backgroundColor: props.theme.colors.elevation1, - borderRadius: 8, -})); - -const DynamicSizeContainer = styled.div<{ isCollage: boolean }>((props) => ({ - width: props.isCollage ? '40%' : '50%', - height: props.isCollage ? '40%' : '50%', -})); - -interface Props { - item: BundleItem; - isCollage?: boolean; -} - -function RareSatAsset({ item, isCollage = false }: Props) { - const isGallery: boolean = document.documentElement.clientWidth > 360; - const isInscription = item.type === 'inscription' || item.type === 'inscribed-sat'; - - return ( - - {isInscription ? ( - - {!isCollage && !!item.rarity_ranking && item.rarity_ranking !== 'common' && ( - - - - )} - - - ) : ( - - - - - - )} - - ); -} - -export default RareSatAsset; diff --git a/src/app/components/rareSatIcon/rareSatIcon.tsx b/src/app/components/rareSatIcon/rareSatIcon.tsx index def1d41f5..39715b138 100644 --- a/src/app/components/rareSatIcon/rareSatIcon.tsx +++ b/src/app/components/rareSatIcon/rareSatIcon.tsx @@ -1,12 +1,33 @@ +import OneDPali from '@assets/img/nftDashboard/rareSats/1d_pali.svg'; +import TwoDPali from '@assets/img/nftDashboard/rareSats/2d_pali.svg'; +import ThreeDPali from '@assets/img/nftDashboard/rareSats/3d_pali.svg'; +import Alpha from '@assets/img/nftDashboard/rareSats/alpha.svg'; +import BlackEpic from '@assets/img/nftDashboard/rareSats/black_epic.svg'; +import BlackLegendary from '@assets/img/nftDashboard/rareSats/black_legendary.svg'; +import BlackRare from '@assets/img/nftDashboard/rareSats/black_rare.svg'; +import BlackUncommon from '@assets/img/nftDashboard/rareSats/black_uncommon.svg'; +import Block78 from '@assets/img/nftDashboard/rareSats/block_78.svg'; +import Block9 from '@assets/img/nftDashboard/rareSats/block_9.svg'; +import BlockPali from '@assets/img/nftDashboard/rareSats/block_pali.svg'; import Epic from '@assets/img/nftDashboard/rareSats/epic.svg'; +import FibonacciSequence from '@assets/img/nftDashboard/rareSats/fibonacci_sequence.svg'; +import FirstTransactionSilkroad from '@assets/img/nftDashboard/rareSats/first_transaction_silkroad.svg'; +import Hitman from '@assets/img/nftDashboard/rareSats/hitman.svg'; +import Jpeg from '@assets/img/nftDashboard/rareSats/jpeg.svg'; import Legendary from '@assets/img/nftDashboard/rareSats/legendary.svg'; import Mythic from '@assets/img/nftDashboard/rareSats/mythic.svg'; +import Nakamoto from '@assets/img/nftDashboard/rareSats/nakamoto.svg'; +import Omega from '@assets/img/nftDashboard/rareSats/omega.svg'; +import Palinception from '@assets/img/nftDashboard/rareSats/palinception.svg'; +import Palindrome from '@assets/img/nftDashboard/rareSats/palindrome.svg'; +import Pizza from '@assets/img/nftDashboard/rareSats/pizza.svg'; import Rare from '@assets/img/nftDashboard/rareSats/rare.svg'; +import SequencePali from '@assets/img/nftDashboard/rareSats/sequence_pali.svg'; import Uncommon from '@assets/img/nftDashboard/rareSats/uncommon.svg'; import Unknown from '@assets/img/nftDashboard/rareSats/unknown.svg'; -import { getRareSatsColorsByRareSatsType, RareSatsType } from '@utils/rareSats'; +import Vintage from '@assets/img/nftDashboard/rareSats/vintage.svg'; +import { RareSatsType } from '@secretkeylabs/xverse-core'; import styled from 'styled-components'; - import Theme from '../../../theme'; const Container = styled.div<{ bgColor: string; padding: number }>((props) => ({ @@ -27,28 +48,6 @@ const Image = styled.img` height: 100%; zindex: 2; `; -type GlowProps = { - color: string; - outerColor: string; - isCollage: boolean; - isGallery: boolean; -}; -const Glow = styled.div((props) => { - const boxShadow = { - 'extension-collage': `0 0 calc(5vw) calc(1.5vw) ${props.color}`, - 'extension-one-item': `0 0 calc(12vw) calc(2vw) ${props.color}`, - 'gallery-collage': `0 0 calc(3.5vw) calc(0.8vw) ${props.color}`, - 'gallery-one-item': `0 0 calc(7vw) calc(1.5vw) ${props.color}`, - }[`${props.isGallery ? 'gallery' : 'extension'}-${props.isCollage ? 'collage' : 'one-item'}`]; - return { - position: 'absolute', - zIndex: 1, - width: '10%', - height: '10%', - borderRadius: '100%', - boxShadow, - }; -}); interface Props { type: RareSatsType; @@ -56,36 +55,48 @@ interface Props { bgColor?: keyof (typeof Theme)['colors']['background']; padding?: number; isDynamicSize?: boolean; - isCollage?: boolean; - glow?: boolean; } -function RareSatIcon({ - type, - size = 24, - bgColor, - padding = 0, - isDynamicSize = false, - isCollage = false, - glow = true, -}: Props) { - const isGallery: boolean = document.documentElement.clientWidth > 360; +function RareSatIcon({ type, size = 24, bgColor, padding = 0, isDynamicSize = false }: Props) { const src = { - epic: Epic, - legendary: Legendary, - mythic: Mythic, - rare: Rare, - uncommon: Uncommon, - unknown: Unknown, + EPIC: Epic, + LEGENDARY: Legendary, + MYTHIC: Mythic, + RARE: Rare, + UNCOMMON: Uncommon, + COMMON: Unknown, + BLACK_LEGENDARY: BlackLegendary, + BLACK_EPIC: BlackEpic, + BLACK_RARE: BlackRare, + BLACK_UNCOMMON: BlackUncommon, + FIBONACCI: FibonacciSequence, + '1D_PALINDROME': OneDPali, + '2D_PALINDROME': TwoDPali, + '3D_PALINDROME': ThreeDPali, + SEQUENCE_PALINDROME: SequencePali, + PERFECT_PALINCEPTION: Palinception, + PALIBLOCK_PALINDROME: BlockPali, + PALINDROME: Palindrome, + NAME_PALINDROME: Palindrome, + ALPHA: Alpha, + OMEGA: Omega, + FIRST_TRANSACTION: FirstTransactionSilkroad, + BLOCK9: Block9, + BLOCK78: Block78, + NAKAMOTO: Nakamoto, + VINTAGE: Vintage, + PIZZA: Pizza, + JPEG: Jpeg, + HITMAN: Hitman, + SILK_ROAD: FirstTransactionSilkroad, }[type]; + if (!src) { + return null; + } const backgroundColor = bgColor ? Theme.colors.background[bgColor] : 'transparent'; - const { color, backgroundColor: outerColor } = getRareSatsColorsByRareSatsType(type); return ( - {glow && type !== 'unknown' && ( - - )} {type} diff --git a/src/app/components/recipientComponent/index.tsx b/src/app/components/recipientComponent/index.tsx index 483d6d118..7b4ef8585 100644 --- a/src/app/components/recipientComponent/index.tsx +++ b/src/app/components/recipientComponent/index.tsx @@ -27,7 +27,7 @@ const Container = styled.div((props) => ({ const RecipientTitleText = styled.p((props) => ({ ...props.theme.body_medium_m, color: props.theme.colors.white_200, - marginBottom: 16, + marginBottom: props.theme.space.xs, })); const RowContainer = styled.div({ @@ -35,10 +35,7 @@ const RowContainer = styled.div({ flexDirection: 'row', width: '100%', alignItems: 'flex-start', -}); - -const AddressContainer = styled.div({ - marginTop: 12, + marginBottom: 12, }); const Icon = styled.img((props) => ({ @@ -209,29 +206,31 @@ function RecipientComponent({ )} {heading && {heading}} - - {renderIcon()} - {title} - {currencyType === 'NFT' || currencyType === 'Ordinal' || currencyType === 'RareSat' ? ( - - {value} - {valueDetail && {valueDetail}} - - ) : ( - - {amount}} - /> - {getFiatAmountString(new BigNumber(fiatAmount!))} - - )} - + {value && ( + + {renderIcon()} + {title} + {currencyType === 'NFT' || currencyType === 'Ordinal' || currencyType === 'RareSat' ? ( + + {value} + {valueDetail && {valueDetail}} + + ) : ( + + {amount}} + /> + {getFiatAmountString(new BigNumber(fiatAmount!))} + + )} + + )} {address && ( - +
{showSenderAddress ? ( @@ -241,7 +240,7 @@ function RecipientComponent({ ) : ( )} - +
)}
); diff --git a/src/app/hooks/queries/ordinals/useAddressRareSats.ts b/src/app/hooks/queries/ordinals/useAddressRareSats.ts index 0329325dc..3e21ff647 100644 --- a/src/app/hooks/queries/ordinals/useAddressRareSats.ts +++ b/src/app/hooks/queries/ordinals/useAddressRareSats.ts @@ -1,8 +1,11 @@ import useWalletSelector from '@hooks/useWalletSelector'; -import { getAddressUtxoOrdinalBundles, getUtxoOrdinalBundle } from '@secretkeylabs/xverse-core'; +import { + getAddressUtxoOrdinalBundles, + getUtxoOrdinalBundle, + mapRareSatsAPIResponseToBundle, +} from '@secretkeylabs/xverse-core'; import { useInfiniteQuery, useQuery } from '@tanstack/react-query'; import { handleRetries, InvalidParamsError } from '@utils/query'; -import { mapRareSatsAPIResponseToRareSats } from '@utils/rareSats'; const PAGE_SIZE = 30; @@ -39,7 +42,11 @@ export const useAddressRareSats = () => { }); }; -export const useGetUtxoOrdinalBundle = (output?: string, shouldMakeTheCall?: boolean) => { +export const useGetUtxoOrdinalBundle = ( + output?: string, + shouldMakeTheCall?: boolean, + ordinalNumber?: number, +) => { const { network } = useWalletSelector(); const getUtxoOrdinalBundleByOutput = async () => { if (!output) { @@ -58,11 +65,21 @@ export const useGetUtxoOrdinalBundle = (output?: string, shouldMakeTheCall?: boo retry: handleRetries, staleTime: 1 * 60 * 1000, // 1 min }); - const bundle = data?.txid ? mapRareSatsAPIResponseToRareSats(data) : undefined; + + const bundle = data?.txid ? mapRareSatsAPIResponseToBundle(data) : undefined; + const inscriptionRange = bundle?.satRanges.find((range) => + range.inscriptions.some((inscription) => inscription.inscription_number === ordinalNumber), + ); + const ordinalSatributes = + inscriptionRange?.satributes.filter((satribute) => satribute !== 'COMMON') ?? []; + const exoticRangesCount = (bundle?.satributes.filter((range) => !range.includes('COMMON')) ?? []) + .length; + const isPartOfABundle = exoticRangesCount > ordinalSatributes.length; return { bundle, - isPartOfABundle: (bundle?.items ?? []).length > 1, + isPartOfABundle, + ordinalSatributes, isLoading, }; }; diff --git a/src/app/hooks/useDetectOrdinalInSignPsbt.ts b/src/app/hooks/useDetectOrdinalInSignPsbt.ts index 66fe5cdeb..b570729cf 100644 --- a/src/app/hooks/useDetectOrdinalInSignPsbt.ts +++ b/src/app/hooks/useDetectOrdinalInSignPsbt.ts @@ -1,35 +1,33 @@ -import { getUtxoOrdinalBundle, ParsedPSBT } from '@secretkeylabs/xverse-core'; -import { BundleItem, mapRareSatsAPIResponseToRareSats } from '@utils/rareSats'; -import { isAxiosError } from 'axios'; +import { + Bundle, + BundleSatRange, + getUtxoOrdinalBundle, + mapRareSatsAPIResponseToBundle, + ParsedPSBT, +} from '@secretkeylabs/xverse-core'; import useWalletSelector from './useWalletSelector'; +export type InputsBundle = Pick; + const useDetectOrdinalInSignPsbt = () => { const { ordinalsAddress, network } = useWalletSelector(); const handleOrdinalAndOrdinalInfo = async (parsedPsbt?: ParsedPSBT) => { - const bundleItems: BundleItem[] = []; + const satRanges: BundleSatRange[] = []; + let value = 0; + let totalExoticSats = 0; let userReceivesOrdinal = false; if (parsedPsbt) { - 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; - } - } + const inputsRequest = parsedPsbt.inputs.map((input) => + getUtxoOrdinalBundle(network.type, input.txid, input.index), + ); + const inputsResponse = await Promise.all(inputsRequest); + inputsResponse.forEach((inputResponse) => { + const bundle = mapRareSatsAPIResponseToBundle(inputResponse); + value += bundle.value; + totalExoticSats += bundle.totalExoticSats; + satRanges.push(...bundle.satRanges); }); parsedPsbt.outputs.forEach((output) => { @@ -39,8 +37,14 @@ const useDetectOrdinalInSignPsbt = () => { }); } + const bundleItemsData = { + value, + satRanges, + totalExoticSats, + }; + return { - bundleItemsData: bundleItems, + bundleItemsData, userReceivesOrdinal, }; }; diff --git a/src/app/layouts/sendLayout.tsx b/src/app/layouts/sendLayout.tsx index 8a077ce8b..dc5ca3537 100644 --- a/src/app/layouts/sendLayout.tsx +++ b/src/app/layouts/sendLayout.tsx @@ -8,6 +8,10 @@ import { useTranslation } from 'react-i18next'; import styled from 'styled-components'; import { breakpoints, devices } from 'theme'; +interface ContainerProps { + isGallery?: boolean; +} + const ScrollContainer = styled.div((props) => ({ display: 'flex', flex: 1, @@ -15,7 +19,7 @@ const ScrollContainer = styled.div((props) => ({ ...props.theme.scrollbar, })); -const Container = styled.div` +const Container = styled.div` display: flex; flex-direction: column; margin: auto; @@ -31,7 +35,9 @@ const Container = styled.div` max-width: 588px; border: 1px solid ${(props) => props.theme.colors.elevation3}; border-radius: ${(props) => props.theme.space.s}; - padding: ${(props) => props.theme.space.l} ${(props) => props.theme.space.m}; + padding-top: ${(props) => props.theme.space.l}; + padding-left: ${(props) => (props.isGallery ? props.theme.space.m : 0)}; + padding-right: ${(props) => (props.isGallery ? props.theme.space.m : 0)}; padding-bottom: ${(props) => props.theme.space.xxl}; margin-top: ${(props) => props.theme.space.xxxxl}; } @@ -51,6 +57,7 @@ const Button = styled.button` display: flex; background-color: transparent; margin-bottom: ${(props) => props.theme.space.l}; + margin-left: ${(props) => props.theme.space.s}; `; function SendLayout({ @@ -66,6 +73,7 @@ function SendLayout({ const { t } = useTranslation('translation', { keyPrefix: 'SEND' }); const isScreenLargerThanXs = document.documentElement.clientWidth > Number(breakpoints.xs); const year = new Date().getFullYear(); + const isGalleryOpen: boolean = document.documentElement.clientWidth > 360; return ( <> @@ -75,7 +83,7 @@ function SendLayout({ )} - + {isScreenLargerThanXs && !hideBackButton && onClickBack && ( - */} - - )} - - + + + {selectedOrdinal && ( - {selectedSatBundle && isRareSat ? ( - - ) : ( - - )} + - - - {!isGalleryOpen && ( - - - )} - - + + ); } export default ConfirmOrdinalTransaction; diff --git a/src/app/screens/login/index.tsx b/src/app/screens/login/index.tsx index 9370dded4..d97e46dd0 100644 --- a/src/app/screens/login/index.tsx +++ b/src/app/screens/login/index.tsx @@ -3,10 +3,10 @@ import EyeSlash from '@assets/img/createPassword/EyeSlash.svg'; import logo from '@assets/img/xverse_logo.svg'; import ActionButton from '@components/button'; import useWalletReducer from '@hooks/useWalletReducer'; -import { animated,useSpring } from '@react-spring/web'; +import { animated, useSpring } from '@react-spring/web'; import MigrationConfirmation from '@screens/migrationConfirmation'; import { addMinutes } from 'date-fns'; -import { useEffect,useState } from 'react'; +import { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useNavigate } from 'react-router-dom'; import styled from 'styled-components'; diff --git a/src/app/screens/nftDashboard/collectiblesTabs.tsx b/src/app/screens/nftDashboard/collectiblesTabs.tsx index e2182443c..c68db4df1 100644 --- a/src/app/screens/nftDashboard/collectiblesTabs.tsx +++ b/src/app/screens/nftDashboard/collectiblesTabs.tsx @@ -1,7 +1,11 @@ import ActionButton from '@components/button'; import WrenchErrorMessage from '@components/wrenchErrorMessage'; +import { + Bundle, + mapRareSatsAPIResponseToBundle, + UtxoOrdinalBundle, +} from '@secretkeylabs/xverse-core'; import { StyledP, StyledTab, StyledTabList } from '@ui-library/common.styled'; -import { ApiBundle, Bundle, mapRareSatsAPIResponseToRareSats } from '@utils/rareSats'; import { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useNavigate, useSearchParams } from 'react-router-dom'; @@ -12,6 +16,9 @@ import Notice from './notice'; import RareSatsTabGridItem from './rareSatsTabGridItem'; import type { NftDashboardState } from './useNftDashboard'; +const MAX_SATS_ITEMS_EXTENSION = 5; +const MAX_SATS_ITEMS_GALLERY = 20; + export const GridContainer = styled.div<{ isGalleryOpen: boolean; }>((props) => ({ @@ -24,6 +31,12 @@ export const GridContainer = styled.div<{ : 'repeat(auto-fill,minmax(150px,1fr))', })); +export const RareSatsTabContainer = styled.div<{ + isGalleryOpen: boolean; +}>((props) => ({ + marginTop: props.theme.space.l, +})); + const StickyStyledTabList = styled(StyledTabList)` position: sticky; background: ${(props) => props.theme.colors.elevation0}; @@ -227,17 +240,21 @@ export default function CollectiblesTabs({ {rareSatsQuery.isInitialLoading ? ( ) : ( - + {!rareSatsQuery.error && !rareSatsQuery.isLoading && rareSatsQuery.data?.pages ?.map((page) => page?.results) .flat() - .map((utxo: ApiBundle) => mapRareSatsAPIResponseToRareSats(utxo)) + .map((utxo: UtxoOrdinalBundle) => mapRareSatsAPIResponseToBundle(utxo)) .map((bundle: Bundle) => ( - + ))} - + )} {rareSatsQuery.hasNextPage && ( diff --git a/src/app/screens/nftDashboard/inscriptionsTabGridItem.tsx b/src/app/screens/nftDashboard/inscriptionsTabGridItem.tsx index 518c43b26..3610ebb3d 100644 --- a/src/app/screens/nftDashboard/inscriptionsTabGridItem.tsx +++ b/src/app/screens/nftDashboard/inscriptionsTabGridItem.tsx @@ -1,5 +1,4 @@ import CollectibleCollage from '@components/collectibleCollage/collectibleCollage'; -import RareSatAsset from '@components/rareSatAsset/rareSatAsset'; import OrdinalImage from '@screens/ordinals/ordinalImage'; import { InscriptionCollectionsData } from '@secretkeylabs/xverse-core'; import { StyledP } from '@ui-library/common.styled'; @@ -8,7 +7,6 @@ import { getInscriptionsTabGridItemId, getInscriptionsTabGridItemSubText, isCollection, - mapCondensedInscriptionToBundleItem, } from '@utils/inscriptions'; import { useNavigate } from 'react-router-dom'; import styled from 'styled-components'; @@ -76,15 +74,11 @@ export function InscriptionsTabGridItem({ {!collection.thumbnail_inscriptions ? ( // eslint-disable-line no-nested-ternary ) : !isCollection(collection) || collection.thumbnail_inscriptions.length === 1 ? ( // eslint-disable-line no-nested-ternary - + ) : collection.category === 'brc-20' ? ( ) : ( - + )} diff --git a/src/app/screens/nftDashboard/rareSatsTabGridItem.tsx b/src/app/screens/nftDashboard/rareSatsTabGridItem.tsx index eb8228b36..68d2e0601 100644 --- a/src/app/screens/nftDashboard/rareSatsTabGridItem.tsx +++ b/src/app/screens/nftDashboard/rareSatsTabGridItem.tsx @@ -1,70 +1,127 @@ -import BundleAsset from '@components/bundleAsset/bundleAsset'; +import ExoticSatsRow from '@components/exoticSatsRow/exoticSatsRow'; +import RareSatIcon from '@components/rareSatIcon/rareSatIcon'; import useSatBundleDataReducer from '@hooks/stores/useSatBundleReducer'; +import { DotsThree } from '@phosphor-icons/react'; +import { Bundle, RareSatsType } from '@secretkeylabs/xverse-core'; import { StyledP } from '@ui-library/common.styled'; -import { Bundle, getBundleId, getBundleSubText } from '@utils/rareSats'; +import { getFormattedTxIdVoutFromBundle } from '@utils/rareSats'; import { useNavigate } from 'react-router-dom'; import styled from 'styled-components'; +import Theme from 'theme'; -const InfoContainer = styled.div` +const Range = styled.div` display: flex; - flex-direction: column; - align-items: flex-start; - width: 100%; + flex-direction: row; + border-radius: 6px; + border: 1px solid ${(props) => props.theme.colors.white_800}; + margin-left: 2px; + align-items: center; + padding: 1px; `; -const StyledBundleId = styled(StyledP)` - text-align: left; - text-wrap: nowrap; - overflow: hidden; - width: 100%; +const TileText = styled(StyledP)` + text-align: center; + color: ${(props) => props.theme.colors.white_200}; + padding: 2px 3px; `; -const StyledBundleSub = styled(StyledP)` - text-align: left; - text-overflow: ellipsis; - text-wrap: nowrap; - overflow: hidden; - width: 100%; -`; - -const GridItemContainer = styled.button` - display: flex; - flex-direction: column; - background: transparent; - gap: ${(props) => props.theme.space.s}; - width: 100%; -`; +const Pressable = styled.button((props) => ({ + background: 'transparent', + width: '100%', + marginBottom: props.theme.space.s, +})); -function RareSatsTabGridItem({ bundle }: { bundle: Bundle }) { +function RareSatsTabGridItem({ bundle, maxItems }: { bundle: Bundle; maxItems: number }) { const navigate = useNavigate(); - const { setSelectedSatBundleDetails, setSelectedSatBundleItemIndex } = useSatBundleDataReducer(); - const isMoreThanOneItem = bundle.items?.length > 1; + const { setSelectedSatBundleDetails } = useSatBundleDataReducer(); const handleOnClick = () => { + // exotics v1 wont show range details only bundle details setSelectedSatBundleDetails(bundle); - if (isMoreThanOneItem) { - return navigate('/nft-dashboard/rare-sats-bundle'); + navigate('/nft-dashboard/rare-sats-bundle', { state: { source: 'RareSatsTab' } }); + }; + + const renderedIcons = () => { + let totalIconsDisplayed = 0; + let totalTilesDisplayed = 0; + + const icons: (RareSatsType | 'ellipsis' | '+X')[][] = []; + let overLimitSatsIndex: number | null = null; + let totalSats = 0; + let totalTiles = 0; + bundle.satributes + .filter((satributes) => !(satributes.includes('COMMON') && bundle.satributes.length > 1)) + .forEach((sats, index) => { + totalSats += sats.length; + totalTiles += 1; + + const isOverLimit = + totalIconsDisplayed + sats.length > maxItems - (sats.length > 1 ? 2 : 1); + // we add ranges till we reach the limit and we store the index of the range that is over the limit + if (isOverLimit || overLimitSatsIndex !== null) { + overLimitSatsIndex = overLimitSatsIndex !== null ? overLimitSatsIndex : index; + return; + } + totalTilesDisplayed += 1; + totalIconsDisplayed += sats.length; + icons.push(sats); + }); + + // if we have more than 1 range and we have reached the limit we add ellipsis and +X + if (overLimitSatsIndex !== null) { + const sats = bundle.satributes[overLimitSatsIndex]; + const satsToDisplay = maxItems - totalIconsDisplayed - 2; + const firstSats = sats.slice(0, satsToDisplay); + // we add ellipsis only if we have more than 1 slot left counting the +X + if (firstSats.length > 0) { + totalTilesDisplayed += 1; + icons.push([...firstSats, 'ellipsis']); + } + + if (totalSats > maxItems) { + icons.push(['+X']); + } } - setSelectedSatBundleItemIndex(0); - navigate('/nft-dashboard/rare-sats-detail'); + return icons.map((sats, index) => ( + + {sats.map((sattribute, indexSatributes) => { + if (sattribute === 'ellipsis') { + return ( + + ); + } + + if (sattribute === '+X') { + return ( + + +{totalTiles - totalTilesDisplayed} + + ); + } + // eslint-disable-next-line react/no-array-index-key + return ; + })} + + )); }; - const bundleId = getBundleId(bundle); - const bundleSubText = getBundleSubText(bundle); + const bundleId = getFormattedTxIdVoutFromBundle(bundle); return ( - - - - - {bundleId} - - - {bundleSubText} - - - + + + ); } export default RareSatsTabGridItem; diff --git a/src/app/screens/nftDashboard/supportedRarities/index.tsx b/src/app/screens/nftDashboard/supportedRarities/index.tsx index 43cd55e69..ce91f4cda 100644 --- a/src/app/screens/nftDashboard/supportedRarities/index.tsx +++ b/src/app/screens/nftDashboard/supportedRarities/index.tsx @@ -1,12 +1,11 @@ -import { useTranslation } from 'react-i18next'; -import { ArrowUpRight } from '@phosphor-icons/react'; -import styled from 'styled-components'; import TopRow from '@components/topRow'; -import { useNavigate } from 'react-router-dom'; +import { ArrowUpRight } from '@phosphor-icons/react'; +import { RodarmorRareSats, Satributes } from '@secretkeylabs/xverse-core'; import { StyledP } from '@ui-library/common.styled'; -import { RareSats } from '@utils/rareSats'; -import { useMemo } from 'react'; import { BLOG_LINK } from '@utils/constants'; +import { useTranslation } from 'react-i18next'; +import { useNavigate } from 'react-router-dom'; +import styled from 'styled-components'; import Theme from 'theme'; import RarityTile from './rarityTile'; @@ -46,21 +45,22 @@ const Container = styled.div((props) => ({ width: props.isGallery ? 580 : '100%', })); -const MainContainer = styled.div({ +const MainContainer = styled.div((props) => ({ + ...props.theme.scrollbar, display: 'flex', flexDirection: 'column', alignItems: 'center', width: '100%', - height: '100%', -}); + backgroundColor: Theme.colors.elevation0, +})); -const rarityTypes = RareSats.filter((rareSat) => rareSat !== 'common'); +const rarityTypes = [...RodarmorRareSats, ...Satributes]; function SupportedRarities() { const navigate = useNavigate(); - const { t } = useTranslation('translation', { keyPrefix: 'NFT_DASHBOARD_SCREEN' }); + const { t } = useTranslation('translation', { keyPrefix: 'RARE_SATS' }); - const isGalleryOpen: boolean = useMemo(() => document.documentElement.clientWidth > 360, []); + const isGalleryOpen: boolean = document.documentElement.clientWidth > 360; const openLearnMoreLink = () => window.open(`${BLOG_LINK}/rare-satoshis`, '_blank', 'noopener,noreferrer'); @@ -80,7 +80,7 @@ function SupportedRarities() { {t('RARITY_DETAIL.LEARN_MORE')} - +
diff --git a/src/app/screens/nftDashboard/supportedRarities/rarityTile.tsx b/src/app/screens/nftDashboard/supportedRarities/rarityTile.tsx index dd5acb1a2..b54809b8a 100644 --- a/src/app/screens/nftDashboard/supportedRarities/rarityTile.tsx +++ b/src/app/screens/nftDashboard/supportedRarities/rarityTile.tsx @@ -1,11 +1,8 @@ +import RareSatIcon from '@components/rareSatIcon/rareSatIcon'; +import { RareSatsType } from '@secretkeylabs/xverse-core'; +import { getRareSatsLabelByType } from '@utils/rareSats'; import { useTranslation } from 'react-i18next'; -import { ArrowUpRight } from '@phosphor-icons/react'; import styled from 'styled-components'; -import { RareSatsType } from '@utils/rareSats'; -import RareSatIcon from '@components/rareSatIcon/rareSatIcon'; -import useWalletSelector from '@hooks/useWalletSelector'; -import { MAGISAT_IO_RARITY_SCAN_URL } from '@utils/constants'; -import Theme from 'theme'; const Container = styled.div((props) => ({ display: 'flex', @@ -25,27 +22,13 @@ const TextsColumn = styled.div((props) => ({ const RarityText = styled.p((props) => ({ ...props.theme.typography.body_bold_m, - color: props.theme.colors.white[0], + color: props.theme.colors.white_0, textTransform: 'capitalize', })); const RarityDetailText = styled.p((props) => ({ ...props.theme.typography.body_medium_m, - color: props.theme.colors.white[200], -})); - -const ButtonText = styled.p((props) => ({ - ...props.theme.typography.body_medium_m, - color: props.theme.colors.orange_main, - marginRight: props.theme.spacing(2), -})); - -const ButtonImage = styled.button((props) => ({ - backgroundColor: 'transparent', - display: 'flex', - flexDirection: 'row', - alignItems: 'center', - marginTop: props.theme.spacing(2), + color: props.theme.colors.white_200, })); interface Props { @@ -53,24 +36,14 @@ interface Props { } function RarityTile({ type }: Props) { - const { ordinalsAddress } = useWalletSelector(); - const { t } = useTranslation('translation', { keyPrefix: 'NFT_DASHBOARD_SCREEN' }); - - const openScanLink = () => - window.open(`${MAGISAT_IO_RARITY_SCAN_URL}${ordinalsAddress}`, '_blank', 'noopener,noreferrer'); + const { t } = useTranslation('translation', { keyPrefix: 'RARE_SATS' }); return ( - + - {type} + {getRareSatsLabelByType(type)} {t(`RARITY_DETAIL.${type.toUpperCase()}`)} - {type === 'unknown' && ( - - {t('RARITY_DETAIL.SCAN')} - - - )} ); diff --git a/src/app/screens/ordinalDetail/index.tsx b/src/app/screens/ordinalDetail/index.tsx index 53a5279f8..13d056b27 100644 --- a/src/app/screens/ordinalDetail/index.tsx +++ b/src/app/screens/ordinalDetail/index.tsx @@ -5,14 +5,18 @@ import AlertMessage from '@components/alertMessage'; import { BetterBarLoader } from '@components/barLoader'; import ActionButton from '@components/button'; import CollectibleDetailTile from '@components/collectibleDetailTile'; +import RareSatIcon from '@components/rareSatIcon/rareSatIcon'; import Separator from '@components/separator'; import SquareButton from '@components/squareButton'; import BottomTabBar from '@components/tabBar'; import TopRow from '@components/topRow'; import WebGalleryButton from '@components/webGalleryButton'; import { useResetUserFlow } from '@hooks/useResetUserFlow'; -import { ArrowRight, ArrowUp, CubeTransparent, Share } from '@phosphor-icons/react'; +import { ArrowUp, Share } from '@phosphor-icons/react'; import OrdinalImage from '@screens/ordinals/ordinalImage'; +import Callout from '@ui-library/callout'; +import { StyledP } from '@ui-library/common.styled'; +import { getRareSatsColorsByRareSatsType, getRareSatsLabelByType } from '@utils/rareSats'; import { useTranslation } from 'react-i18next'; import { Tooltip } from 'react-tooltip'; import styled from 'styled-components'; @@ -89,8 +93,8 @@ const ExtensionOrdinalsContainer = styled.div((props) => ({ justifyContent: 'center', alignItems: 'center', borderRadius: props.theme.radius(1), - marginBottom: props.theme.spacing(12), marginTop: props.theme.spacing(12), + marginBottom: props.theme.space.m, })); const OrdinalTitleText = styled.h1((props) => ({ @@ -109,7 +113,6 @@ const DescriptionText = styled.h1((props) => ({ ...props.theme.typography.headline_l, color: props.theme.colors.white_0, fontSize: 24, - marginBottom: props.theme.spacing(8), })); const NftOwnedByText = styled.h1((props) => ({ @@ -164,7 +167,7 @@ const MintLimitContainer = styled.div((props) => ({ marginLeft: props.theme.spacing(30), })); -const DescriptionContainer = styled.h1((props) => ({ +const DescriptionContainer = styled.div((props) => ({ display: 'flex', flexDirection: 'column', marginBottom: props.theme.spacing(30), @@ -270,40 +273,6 @@ const Text = styled.h1((props) => ({ marginLeft: props.theme.spacing(2), })); -const RareSatsBundleContainer = styled.div((props) => ({ - display: 'flex', - flex: 1, - flexDirection: 'row', - padding: props.theme.spacing(8), - marginBottom: props.theme.spacing(8), - border: `1px solid ${props.theme.colors.white_800}`, - borderRadius: '12px', -})); -const CubeTransparentIcon = styled(CubeTransparent)((props) => ({ - color: props.theme.colors.white_200, - marginRight: props.theme.spacing(8), -})); -const RareSatsBundleTextDescription = styled.div((props) => ({ - ...props.theme.typography.body_m, - color: props.theme.colors.white_200, -})); -const BundleLinkContainer = styled.button((props) => ({ - display: 'inline-flex', - flexDirection: 'row', - alignItems: 'center', - marginTop: props.theme.spacing(4), - backgroundColor: 'transparent', - color: props.theme.colors.white_0, - transition: 'background-color 0.2s ease, opacity 0.2s ease', - ':hover': { - color: props.theme.colors.white_200, - }, -})); -const BundleLinkText = styled.div((props) => ({ - ...props.theme.typography.body_medium_m, - marginRight: props.theme.spacing(1), -})); - const GalleryButtonContainer = styled.div` width: 190px; border-radius: 12px; @@ -314,9 +283,12 @@ const RowButtonContainer = styled.div((props) => ({ flexDirection: 'row', justifyContent: 'center', columnGap: props.theme.spacing(11), - paddingBottom: props.theme.spacing(16), - marginBottom: props.theme.spacing(4), - marginTop: props.theme.spacing(4), + marginBottom: props.theme.space.l, + marginTop: props.theme.space.m, + width: '100%', +})); + +const Divider = styled.div((props) => ({ width: '100%', borderBottom: `1px solid ${props.theme.colors.elevation3}`, })); @@ -379,8 +351,49 @@ const InfoContainer = styled.div((props) => ({ padding: `0 ${props.theme.spacing(8)}px`, })); +const RareSatsBundleCallout = styled(Callout)((props) => ({ + width: props.isGallery ? 400 : '100%', + marginBottom: props.isGallery ? 0 : props.theme.space.l, + marginTop: props.isGallery ? props.theme.space.xs : 0, +})); + +const SatributesIconsContainer = styled.div((props) => ({ + display: 'inline-flex', + flexDirection: 'row', + marginTop: props.isGallery ? props.theme.space.m : 0, +})); + +const SatributesBadgeContainer = styled.div((props) => ({ + marginTop: props.isGallery ? 0 : props.theme.space.m, +})); +const SatributesBadges = styled.div((props) => ({ + display: 'inline-flex', + flexDirection: 'row', + flexWrap: 'wrap', + maxWidth: props.isGallery ? 400 : '100%', + marginTop: props.theme.space.s, +})); +const Badge = styled.div<{ backgroundColor?: string; isLastItem: boolean }>((props) => ({ + display: 'inline-flex', + flexDirection: 'row', + alignItems: 'center', + backgroundColor: props.backgroundColor, + padding: `${props.theme.space.s} ${props.theme.space.s}`, + borderRadius: props.theme.radius(2), + border: `1px solid ${props.theme.colors.elevation3}`, + marginRight: props.isLastItem ? 0 : props.theme.space.xs, + marginBottom: props.theme.space.xs, +})); +const SatributeBadgeLabel = styled(StyledP)` + margin-left: ${(props) => props.theme.space.xs}; +`; +const DataItemsContainer = styled.div` + margin-top: ${(props) => props.theme.space.l}; +`; + function OrdinalDetailScreen() { const { t } = useTranslation('translation', { keyPrefix: 'NFT_DETAIL_SCREEN' }); + const { t: commonT } = useTranslation('translation', { keyPrefix: 'COMMON' }); const ordinalDetails = useOrdinalDetail(); const { ordinal, @@ -390,6 +403,7 @@ function OrdinalDetailScreen() { showSendOridnalsAlert, isBrc20Ordinal, isPartOfABundle, + ordinalSatributes, isGalleryOpen, brc20InscriptionStatus, brc20InscriptionStatusColor, @@ -573,18 +587,45 @@ function OrdinalDetailScreen() { }; const rareSats = isPartOfABundle && ( - - -
- - {t('RARE_SATS_BUNDLE_DESCRIPTION')} - - - {t('RARE_SATS_BUNDLE_LINK')} - - -
-
+ + ); + + const showSatributes = ordinalSatributes.length > 0; + const satributesIcons = showSatributes && ( + + {ordinalSatributes.map((satribute) => ( + + ))} + + ); + const stributesBadges = showSatributes && ( + + + {commonT('SATTRIBUTES')} + + + {ordinalSatributes.map((satribute, index) => { + const backgroundColor = getRareSatsColorsByRareSatsType(satribute) ?? 'transparent'; + return ( + = ordinalSatributes.length} + > + + + {getRareSatsLabelByType(satribute)} + + + ); + })} + + ); const extensionView = isLoading ? ( @@ -627,6 +668,7 @@ function OrdinalDetailScreen() { + {satributesIcons} } @@ -649,6 +691,8 @@ function OrdinalDetailScreen() { /> {rareSats} + + {stributesBadges} {isBrc20Ordinal ? showBrc20OrdinalDetail(false) : ordinalDetailAttributes} {t('VIEW_IN')} @@ -714,6 +758,7 @@ function OrdinalDetailScreen() { : ordinal?.collection_name || t('INSCRIPTION')} {ordinal?.number} + {satributesIcons} {t('OWNED_BY')} {`${ordinalsAddress.substring( @@ -752,7 +797,10 @@ function OrdinalDetailScreen() { {t('DATA')} {rareSats} - {isBrc20Ordinal ? showBrc20OrdinalDetail(true) : ordinalDetailAttributes} + + {stributesBadges} + {isBrc20Ordinal ? showBrc20OrdinalDetail(true) : ordinalDetailAttributes} + {t('VIEW_IN')} {t('ORDINAL_VIEWER')} diff --git a/src/app/screens/ordinalDetail/useOrdinalDetail.ts b/src/app/screens/ordinalDetail/useOrdinalDetail.ts index 7704e5b78..9ba917938 100644 --- a/src/app/screens/ordinalDetail/useOrdinalDetail.ts +++ b/src/app/screens/ordinalDetail/useOrdinalDetail.ts @@ -33,9 +33,10 @@ export default function useOrdinalDetail() { const { isPending, pendingTxHash } = usePendingOrdinalTxs(ordinalData?.tx_id); const textContent = useTextOrdinalContent(ordinalData!); const { setSelectedSatBundleDetails } = useSatBundleDataReducer(); - const { bundle, isPartOfABundle } = useGetUtxoOrdinalBundle( + const { bundle, isPartOfABundle, ordinalSatributes } = useGetUtxoOrdinalBundle( ordinalData?.output, hasActivatedRareSatsKey, + ordinalData?.number, ); const theme = useTheme(); const { t } = useTranslation('translation', { keyPrefix: 'NFT_DETAIL_SCREEN' }); @@ -104,11 +105,12 @@ export default function useOrdinalDetail() { }; const handleNavigationToRareSatsBundle = () => { - if (!bundle) { + if (!bundle || !ordinalData) { return; } + setSelectedOrdinalDetails(ordinalData); setSelectedSatBundleDetails(bundle); - navigate('/nft-dashboard/rare-sats-bundle'); + navigate('/nft-dashboard/rare-sats-bundle', { state: { source: 'OrdinalDetail' } }); }; const onCopyClick = () => { @@ -128,7 +130,8 @@ export default function useOrdinalDetail() { ordinalsAddress, showSendOridnalsAlert, isBrc20Ordinal, - isPartOfABundle, + isPartOfABundle: isPartOfABundle && hasActivatedRareSatsKey, + ordinalSatributes: hasActivatedRareSatsKey ? ordinalSatributes : [], isGalleryOpen, brc20InscriptionStatus, brc20InscriptionStatusColor, diff --git a/src/app/screens/rareSatsBundle/index.tsx b/src/app/screens/rareSatsBundle/index.tsx index 985bf887b..c6beccec5 100644 --- a/src/app/screens/rareSatsBundle/index.tsx +++ b/src/app/screens/rareSatsBundle/index.tsx @@ -12,13 +12,17 @@ import useSatBundleDataReducer from '@hooks/stores/useSatBundleReducer'; import { useResetUserFlow } from '@hooks/useResetUserFlow'; import useWalletSelector from '@hooks/useWalletSelector'; import { ArrowRight, ArrowUp } from '@phosphor-icons/react'; -import { GridContainer } from '@screens/nftDashboard/collectiblesTabs'; +import { BundleSatRange } from '@secretkeylabs/xverse-core'; import { StyledHeading, StyledP } from '@ui-library/common.styled'; -import { getBtcTxStatusUrl, isInOptions, isLedgerAccount } from '@utils/helper'; -import { BundleItem } from '@utils/rareSats'; +import { + getBtcTxStatusUrl, + getTruncatedAddress, + isInOptions, + isLedgerAccount, +} from '@utils/helper'; import { useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { useNavigate } from 'react-router-dom'; +import { useLocation, useNavigate } from 'react-router-dom'; import styled from 'styled-components'; import OrdinalAttributeComponent from '../ordinalDetail/ordinalAttributeComponent'; import { RareSatsBundleGridItem } from './rareSatsBundleGridItem'; @@ -29,11 +33,13 @@ interface DetailSectionProps { /* layout */ const Container = styled.div` + ...${(props) => props.theme.scrollbar}; overflow-y: auto; + padding-bottom: ${(props) => props.theme.space.l}; `; const PageHeader = styled.div` - padding: ${(props) => props.theme.space.m}; + padding: ${(props) => (props.isGalleryOpen ? props.theme.space.m : 0)}; padding-top: 0; max-width: 1224px; margin-top: ${(props) => (props.isGalleryOpen ? props.theme.space.xxl : props.theme.space.l)}; @@ -47,27 +53,17 @@ const PageHeaderContent = styled.div` display: flex; flex-direction: ${(props) => (props.isGalleryOpen ? 'row' : 'column')}; justify-content: ${(props) => (props.isGalleryOpen ? 'space-between' : 'initial')}; - row-gap: ${(props) => props.theme.space.xl}; `; -const AttributesContainer = styled.div` - max-width: 285px; -`; +const AttributesContainer = styled.div((props) => ({ + maxWidth: props.isGalleryOpen ? '285px' : '100%', + padding: props.isGalleryOpen ? 0 : `0 ${props.theme.space.m}`, +})); const StyledSeparator = styled(Separator)` margin-bottom: ${(props) => props.theme.space.xxl}; `; -const StyledGridContainer = styled(GridContainer)` - margin-top: ${(props) => props.theme.spacing(8)}px; - padding: 0 ${(props) => props.theme.space.m}; - padding-bottom: ${(props) => props.theme.space.xl}; - max-width: 1224px; - margin-left: auto; - margin-right: auto; - width: 100%; -`; - /* components */ const StyledWebGalleryButton = styled(WebGalleryButton)` @@ -138,11 +134,39 @@ const NoCollectiblesText = styled.p((props) => ({ textAlign: 'center', })); +const Header = styled.div<{ isGalleryOpen: boolean }>((props) => ({ + display: props.isGalleryOpen ? 'block' : 'flex', + flexDirection: props.isGalleryOpen ? 'row' : 'column', + alignItems: props.isGalleryOpen ? 'flex-start' : 'center', +})); + +const SatRangeContainer = styled.div((props) => ({ + marginTop: props.isGalleryOpen ? 0 : props.theme.space.xl, + maxWidth: '1224px', + marginLeft: 'auto', + marginRight: 'auto', + width: '100%', +})); + +const DetailSection = styled.div((props) => ({ + display: 'flex', + flexDirection: props.isGalleryOpen ? 'column' : 'row', + justifyContent: 'space-between', + columnGap: props.theme.space.m, + width: '100%', +})); + +const SeeRarityContainer = styled.div` + padding: ${(props) => props.theme.space.l} ${(props) => props.theme.space.m}; +`; + function RareSatsBundle() { const { t } = useTranslation('translation'); const navigate = useNavigate(); - const { network, selectedAccount } = useWalletSelector(); - const { selectedSatBundle: bundle } = useNftDataSelector(); + const location = useLocation(); + const { source } = location.state || {}; + const { network, selectedAccount, ordinalsAddress } = useWalletSelector(); + const { selectedSatBundle: bundle, selectedOrdinal } = useNftDataSelector(); const { isPending, pendingTxHash } = usePendingOrdinalTxs(bundle?.txid); const [showSendOrdinalsAlert, setShowSendOrdinalsAlert] = useState(false); const { setSelectedSatBundleDetails } = useSatBundleDataReducer(); @@ -152,7 +176,11 @@ function RareSatsBundle() { useResetUserFlow('/rare-sats-bundle'); const handleBackButtonClick = () => { - navigate('/nft-dashboard?tab=rareSats'); + if (source === 'OrdinalDetail') { + navigate(-1); + } else { + navigate('/nft-dashboard?tab=rareSats'); + } setSelectedSatBundleDetails(null); }; @@ -191,7 +219,11 @@ function RareSatsBundle() { navigate('/nft-dashboard/supported-rarity-scale'); }; - const isEmpty = !bundle?.items?.length; + const isEmpty = !bundle?.satRanges?.length; + + const goBackText = selectedOrdinal?.id + ? t('SEND.MOVE_TO_ASSET_DETAIL') + : t('NFT_DETAIL_SCREEN.MOVE_TO_ASSET_DETAIL'); return ( <> @@ -207,20 +239,18 @@ function RareSatsBundle() { )} -
+
- {t('RARE_SATS.RARE_SATS_BUNDLE')} + {t('NFT_DASHBOARD_SCREEN.RARE_SATS')} - {t('NFT_DASHBOARD_SCREEN.TOTAL_ITEMS', { total: bundle?.items?.length })} + {bundle?.totalExoticSats} {!isGalleryOpen && } @@ -230,19 +260,47 @@ function RareSatsBundle() { onPress={handleSendOrdinal} /> - - - {t('RARE_SATS.RARITY_LINK_TEXT')} - - - -
- - + {isGalleryOpen && ( + + + {t('RARE_SATS.RARITY_LINK_TEXT')} + + + + )} + + {isEmpty && ( + {t('NFT_DASHBOARD_SCREEN.NO_COLLECTIBLES')} + )} + {!isGalleryOpen && ( + + {bundle?.satRanges.map((item: BundleSatRange) => ( + + ))} + + )} + {!isGalleryOpen && ( + + + + )} + + + + + {t('NFT_DASHBOARD_SCREEN.NO_COLLECTIBLES')} )} - - {bundle?.items?.map((item: BundleItem, index) => ( - - ))} - + {isGalleryOpen && ( + + {bundle?.satRanges.map((item: BundleSatRange) => ( + + ))} + + )} {showSendOrdinalsAlert && ( props.theme.radius(3)}px; - background: ${(props) => props.theme.colors.elevation1}; + border-radius: 6px; + border: 1px solid var(--white-800, rgba(255, 255, 255, 0.2)); + padding: 1px; + flex-wrap: wrap; `; -const GridItemContainer = styled.button` - display: flex; - flex-direction: column; - background: transparent; - gap: ${(props) => props.theme.space.s}; -`; - -export function RareSatsBundleGridItem({ - item, - itemIndex, -}: { - item: BundleItem; - itemIndex: number; -}) { - const navigate = useNavigate(); - const { setSelectedSatBundleItemIndex } = useSatBundleDataReducer(); - const { selectedSatBundle } = useNftDataSelector(); - - const handleOnClick = () => { - setSelectedSatBundleItemIndex(itemIndex); - navigate('/nft-dashboard/rare-sats-detail'); - }; - - const itemId = getBundleItemId(selectedSatBundle!, itemIndex); - const itemSubText = getBundleItemSubText({ - satType: item.type, - rareSatsType: item.rarity_ranking, - }); +const Container = styled.div((props) => ({ + marginBottom: props.theme.space.s, + padding: `0 ${props.theme.space.m}`, +})); +export function RareSatsBundleGridItem({ item }: { item: BundleSatRange }) { return ( - - - - - - - {itemId} - - - {itemSubText} - - - + + + + {item.satributes.map((satribute) => ( + + ))} + + + } + /> + ); } export default RareSatsBundleGridItem; diff --git a/src/app/screens/rareSatsDetail/rareSatsDetail.tsx b/src/app/screens/rareSatsDetail/rareSatsDetail.tsx index b5bb2b9a0..c3ee17fec 100644 --- a/src/app/screens/rareSatsDetail/rareSatsDetail.tsx +++ b/src/app/screens/rareSatsDetail/rareSatsDetail.tsx @@ -1,42 +1,12 @@ -import ArrowLeft from '@assets/img/dashboard/arrow_left.svg'; import AccountHeaderComponent from '@components/accountHeader'; -import AlertMessage from '@components/alertMessage'; -import ActionButton from '@components/button'; -import RareSatAsset from '@components/rareSatAsset/rareSatAsset'; import BottomTabBar from '@components/tabBar'; import TopRow from '@components/topRow'; -import WebGalleryButton from '@components/webGalleryButton'; -import usePendingOrdinalTxs from '@hooks/queries/usePendingOrdinalTx'; -import useNftDataSelector from '@hooks/stores/useNftDataSelector'; import useSatBundleDataReducer from '@hooks/stores/useSatBundleReducer'; import { useResetUserFlow } from '@hooks/useResetUserFlow'; -import useWalletSelector from '@hooks/useWalletSelector'; -import { ArrowRight, ArrowUp, Circle } from '@phosphor-icons/react'; -import Callout from '@ui-library/callout'; -import { XVERSE_ORDIVIEW_URL } from '@utils/constants'; -import { - getBtcTxStatusUrl, - getTruncatedAddress, - isInOptions, - isLedgerAccount, -} from '@utils/helper'; -import { - BundleItem, - getBundleItemId, - getBundleItemSubText, - getRareSatsColorsByRareSatsType, - getRareSatsLabelByType, - getRarityLabelByRareSatsType, -} from '@utils/rareSats'; -import { useMemo, useState } from 'react'; -import { useTranslation } from 'react-i18next'; +import { StyledP } from '@ui-library/common.styled'; +import { useMemo } from 'react'; import { useLocation, useNavigate } from 'react-router-dom'; import styled from 'styled-components'; -import OrdinalAttributeComponent from '../ordinalDetail/ordinalAttributeComponent'; - -interface DetailSectionProps { - isGalleryOpen?: boolean; -} const Container = styled.div` display: flex; @@ -50,223 +20,19 @@ const Container = styled.div` } `; -const SendButtonContainer = styled.div` - width: ${(props) => (props.isGalleryOpen ? '222px' : '155px')}; -`; - -const BackButtonContainer = styled.div((props) => ({ - display: 'flex', - flexDirection: 'row', - marginTop: props.theme.spacing(40), -})); - -const ExtensionContainer = styled.div({ - display: 'flex', - flexDirection: 'column', - marginTop: 8, - marginBottom: 40, - alignItems: 'center', - flex: 1, -}); - -const RareSatsContainer = styled.div((props) => ({ - maxWidth: 450, - width: '60%', - display: 'flex', - aspectRatio: '1', - flexDirection: 'column', - justifyContent: 'flex-start', - alignItems: 'flex-start', - borderRadius: 8, - marginBottom: props.theme.spacing(12), -})); - -const ExtensionRareSatsContainer = styled.div<{ isInscription?: boolean }>((props) => ({ - maxHeight: props.isInscription ? 148 : 64, - width: props.isInscription ? 148 : 64, - display: 'flex', - aspectRatio: '1', - justifyContent: 'center', - alignItems: 'center', - borderRadius: 8, - marginBottom: props.theme.spacing(12), - marginTop: props.theme.spacing(12), -})); - -const RareSatsTitleText = styled.h1((props) => ({ - ...props.theme.headline_m, - color: props.theme.colors.white['0'], - textAlign: 'center', -})); - -const RareSatsGalleryTitleText = styled.p((props) => ({ - ...props.theme.headline_l, - color: props.theme.colors.white['0'], - marginBottom: props.theme.spacing(12), -})); - -const DescriptionText = styled.p((props) => ({ - ...props.theme.headline_l, - color: props.theme.colors.white['0'], - fontSize: 24, - marginBottom: props.theme.spacing(16), -})); - const BottomBarContainer = styled.div({ marginTop: 'auto', }); -const RowContainer = styled.div((props) => ({ - display: 'flex', - alignItems: 'flex-start', - marginTop: props.theme.spacing(6), - flexDirection: 'row', -})); - -const ColumnContainer = styled.div({ - display: 'flex', - alignItems: 'flex-start', - flexDirection: 'column', - width: '100%', -}); - -const DescriptionContainer = styled.div((props) => ({ - display: 'flex', - flex: 1, - marginLeft: props.theme.spacing(20), - flexDirection: 'column', - marginBottom: props.theme.spacing(30), -})); - -const StyledWebGalleryButton = styled(WebGalleryButton)` - color: ${(props) => props.theme.colors.white_200}; - margin-top: ${(props) => props.theme.space.xs}; -`; - -const ButtonImage = styled.img((props) => ({ - marginRight: props.theme.spacing(3), - alignSelf: 'center', - transform: 'all', -})); - -const Button = styled.button((props) => ({ - display: 'flex', - flexDirection: 'row', - justifyContent: 'flex-start', - alignItems: 'center', - background: 'transparent', - marginBottom: props.theme.spacing(12), -})); - -const AssetDetailButtonText = styled.div((props) => ({ - ...props.theme.body_xs, - fontWeight: 400, - fontSize: 14, - color: props.theme.colors.white['0'], - textAlign: 'center', -})); - -const SatTypeText = styled.p((props) => ({ - ...props.theme[props.isGalleryOpen ? 'body_bold_l' : 'body_bold_m'], - color: props.theme.colors.white['400'], - textAlign: props.isGalleryOpen ? 'left' : 'center', - textTransform: 'capitalize', -})); - -const DetailSection = styled.div((props) => ({ - display: 'flex', - flexDirection: !props.isGalleryOpen ? 'row' : 'column', - justifyContent: 'space-between', - width: '100%', -})); - -const RareSatRankingBadge = styled.div<{ bgColor: string }>((props) => ({ - display: 'flex', - flexDirection: 'row', - alignItems: 'center', - borderRadius: '30px', - backgroundColor: props.bgColor, - marginTop: props.theme.spacing(1), - padding: '5px 10px', -})); - -const RareSatRankingBadgeText = styled.div((props) => ({ - ...props.theme.body_medium_s, - marginLeft: props.theme.spacing(4), -})); - -const StyledCallout = styled(Callout)((props) => ({ - marginBottom: props.theme.space.l, -})); - -const BundleRarityLinkContainer = styled.button((props) => ({ - marginTop: props.isGalleryOpen ? props.theme.space.l : props.theme.space.m, - display: 'inline-flex', - alignSelf: props.isGalleryOpen ? 'flex-start' : 'center', - flexDirection: 'row', - marginBottom: props.isGalleryOpen ? props.theme.spacing(14) : 0, - alignItems: 'center', - backgroundColor: 'transparent', - color: props.theme.colors.white_0, - transition: 'background-color 0.2s ease, opacity 0.2s ease', - ':hover': { - color: props.theme.colors.action.classicLight, - opacity: 0.6, - }, -})); -const BundleRarityTextLink = styled.p((props) => ({ - ...props.theme.body_medium_m, - color: props.theme.colors.white_200, - marginRight: props.theme.spacing(1), -})); -const ArrowRightIcon = styled(ArrowRight)((props) => ({ - color: props.theme.colors.white_200, -})); -const Divider = styled.div((props) => ({ - width: '100%', - height: '1px', - backgroundColor: props.theme.colors.white_900, - marginTop: props.theme.spacing(20), - marginBottom: props.theme.spacing(4), -})); -const Flex1 = styled.div(() => ({ - flex: 1, - width: '100%', -})); -const ViewInExplorerButton = styled.button((props) => ({ - display: 'flex', - flexDirection: 'row', - justifyContent: 'center', - alignItems: 'center', - backgroundColor: 'transparent', - marginTop: props.theme.space.xxl, - width: '100%', -})); - +// TODO: this screen will be re-implemented in future iteration of exotics sats function RareSatsDetailScreen() { - const { t } = useTranslation('translation'); const navigate = useNavigate(); const location = useLocation(); - const { ordinalsAddress, network, selectedAccount } = useWalletSelector(); - const { selectedSatBundle, selectedSatBundleItemIndex } = useNftDataSelector(); - const [showSendOridnalsAlert, setshowSendOridnalsAlert] = useState(false); + const { setSelectedSatBundleItemIndex } = useSatBundleDataReducer(); useResetUserFlow('/rare-sats-detail'); - const isGalleryOpen: boolean = useMemo(() => document.documentElement.clientWidth > 360, []); - - const bundle = selectedSatBundle!; - const { isPending, pendingTxHash } = usePendingOrdinalTxs(bundle.txid); - const itemIndex = selectedSatBundleItemIndex!; - const item = bundle.items[itemIndex] as BundleItem | undefined; - // when going back, selectedSatBundleItemIndex is set tu null and we don't want to render anything - if (!item) { - return null; - } - - const isBundle = bundle.items.length < 2; - const isUnknown = item?.type === 'unknown'; - const isInscription = item?.type === 'inscription' || item?.type === 'inscribed-sat'; + const isGalleryOpen: boolean = useMemo(() => document.documentElement.clientWidth > 360, []); const handleBackButtonClick = () => { // only go back if there is history @@ -278,191 +44,6 @@ function RareSatsDetailScreen() { setSelectedSatBundleItemIndex(null); }; - const openInGalleryView = async () => { - await chrome.tabs.create({ - url: chrome.runtime.getURL('options.html#/nft-dashboard/rare-sats-detail'), - }); - }; - - const showAlert = () => { - setshowSendOridnalsAlert(true); - }; - - const onCloseAlert = () => { - setshowSendOridnalsAlert(false); - }; - - const handleSendRareSats = async () => { - if (isPending) { - return showAlert(); - } - - if (isLedgerAccount(selectedAccount) && !isInOptions()) { - await chrome.tabs.create({ - url: chrome.runtime.getURL('options.html#/nft-dashboard/send-rare-sat'), - }); - return; - } - - navigate('/nft-dashboard/send-rare-sat'); - }; - - const handleRedirectToTx = () => { - if (pendingTxHash) { - window.open(getBtcTxStatusUrl(pendingTxHash, network), '_blank', 'noopener,noreferrer'); - } - }; - - const handleRarityScale = () => { - navigate('/nft-dashboard/supported-rarity-scale'); - }; - - const openInOrdinalsExplorer = () => { - if (!isInscription) { - return; - } - window.open(`${XVERSE_ORDIVIEW_URL(network.type)}/inscription/${item.inscription.id}`); - }; - - const { color, backgroundColor } = getRareSatsColorsByRareSatsType(item.rarity_ranking); - - const satsRanking = ( - - - - {getRareSatsLabelByType(item.rarity_ranking)} - - - } - /> - ); - const satsValue = ( - - ); - const satsRarity = ( - - ); - const ownedBy = ( - - ); - const id = ( - - ); - const title = getBundleItemId(bundle, itemIndex); - const sendActionSection = isBundle ? ( - <> - - } - text={t('COMMON.SEND')} - onPress={handleSendRareSats} - /> - - - {t('RARE_SATS.RARITY_LINK_TEXT')} - - - - ) : ( - - ); - - const extensionView = ( - - - {getBundleItemSubText({ satType: item.type, rareSatsType: item.rarity_ranking })} - - {title} - - - - - {sendActionSection} - - - - {!isUnknown && {satsRanking}} - {isBundle ? satsValue : satsRarity} - - - {!isUnknown && isBundle && {satsRarity}} - {isUnknown ? id : ownedBy} - - - {isInscription && ( - - - - )} - - ); - - const galleryView = ( - - - - - - {getBundleItemSubText({ satType: item.type, rareSatsType: item.rarity_ranking })} - - {title} - {sendActionSection} - - - - - - {t('NFT_DETAIL_SCREEN.DESCRIPTION')} - - - {!isUnknown && {satsRanking}} - {isBundle ? satsValue : satsRarity} - - - {!isUnknown && isBundle && {satsRarity}} - {isUnknown ? id : ownedBy} - - - {isInscription && ( - - - - )} - - - - ); - return ( <> {isGalleryOpen ? ( @@ -471,16 +52,7 @@ function RareSatsDetailScreen() { )} - {showSendOridnalsAlert && ( - - )} - {isGalleryOpen && selectedSatBundle !== null ? galleryView : extensionView} + TODO {!isGalleryOpen && ( diff --git a/src/app/screens/sendOrdinal/index.tsx b/src/app/screens/sendOrdinal/index.tsx index db42c9ca4..1fceae6c6 100644 --- a/src/app/screens/sendOrdinal/index.tsx +++ b/src/app/screens/sendOrdinal/index.tsx @@ -155,7 +155,7 @@ function SendOrdinal() { useEffect(() => { if (data) { - navigate(`/confirm-ordinal-tx/${selectedOrdinal?.id}`, { + navigate(`/nft-dashboard/confirm-ordinal-tx/${selectedOrdinal?.id}`, { state: { signedTxHex: data.signedTx, recipientAddress, diff --git a/src/app/screens/sendRareSat/index.tsx b/src/app/screens/sendRareSat/index.tsx index 471cab01d..c8c20d76d 100644 --- a/src/app/screens/sendRareSat/index.tsx +++ b/src/app/screens/sendRareSat/index.tsx @@ -1,9 +1,4 @@ -import ArrowLeft from '@assets/img/dashboard/arrow_left.svg'; -import AccountHeaderComponent from '@components/accountHeader'; -import BundleAsset from '@components/bundleAsset/bundleAsset'; -import SendForm from '@components/sendForm'; -import BottomBar from '@components/tabBar'; -import TopRow from '@components/topRow'; +import ActionButton from '@components/button'; import useNftDataSelector from '@hooks/stores/useNftDataSelector'; import useBtcClient from '@hooks/useBtcClient'; import { useResetUserFlow } from '@hooks/useResetUserFlow'; @@ -19,85 +14,91 @@ import { validateBtcAddress, } from '@secretkeylabs/xverse-core'; import { useMutation } from '@tanstack/react-query'; -import { StyledHeading, StyledP } from '@ui-library/common.styled'; -import { isLedgerAccount } from '@utils/helper'; -import { getBundleId, getBundleSubText } from '@utils/rareSats'; +import Callout from '@ui-library/callout'; +import { StyledHeading } from '@ui-library/common.styled'; +import InputFeedback, { InputFeedbackProps, isDangerFeedback } from '@ui-library/inputFeedback'; import BigNumber from 'bignumber.js'; -import { useEffect, useMemo, useState } from 'react'; +import { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useLocation, useNavigate } from 'react-router-dom'; import styled from 'styled-components'; +import SendLayout from '../../layouts/sendLayout'; -const ScrollContainer = styled.div` +const Container = styled.div` display: flex; - flex: 1; flex-direction: column; - overflow-y: auto; - &::-webkit-scrollbar { - display: none; - } - width: 360px; - margin: auto; + justify-content: space-between; + flex-grow: 1; `; -const Container = styled.div({ - display: 'flex', - flexDirection: 'column', - justifyContent: 'center', - alignItems: 'center', - flex: 1, -}); +const StyledSendTo = styled(StyledHeading)` + margin-bottom: ${(props) => props.theme.space.l}; +`; -const BottomBarContainer = styled.div({ - marginTop: 'auto', -}); +const NextButtonContainer = styled.div((props) => ({ + position: 'sticky', + bottom: 0, + paddingBottom: props.theme.space.s, + paddingTop: props.theme.space.s, + backgroundColor: props.theme.colors.elevation0, +})); + +const InputGroup = styled.div` + margin-top: ${(props) => props.theme.spacing(8)}px; +`; -const ButtonContainer = styled.div((props) => ({ +const Label = styled.label((props) => ({ + ...props.theme.typography.body_medium_m, + color: props.theme.colors.white_200, display: 'flex', - flexDirection: 'row', - marginLeft: '15%', - marginTop: props.theme.spacing(40), + flex: 1, })); -const Button = styled.button((props) => ({ +const AmountInputContainer = styled.div<{ error: boolean }>((props) => ({ display: 'flex', flexDirection: 'row', - justifyContent: 'flex-end', alignItems: 'center', + marginTop: props.theme.space.xs, + marginBottom: props.theme.space.xs, + border: props.error + ? `1px solid ${props.theme.colors.danger_dark_200}` + : `1px solid ${props.theme.colors.white_800}`, + backgroundColor: props.theme.colors.elevation_n1, borderRadius: props.theme.radius(1), - backgroundColor: 'transparent', - opacity: 0.8, - marginTop: props.theme.spacing(5), + paddingLeft: props.theme.space.s, + paddingRight: props.theme.space.s, + height: 44, })); -const ButtonText = styled.div((props) => ({ - ...props.theme.body_xs, - fontWeight: 400, - fontSize: 14, - color: props.theme.colors.white['0'], - textAlign: 'center', +const InputFieldContainer = styled.div(() => ({ + flex: 1, })); -const ButtonImage = styled.img((props) => ({ - marginRight: props.theme.spacing(3), - alignSelf: 'center', - transform: 'all', +const InputField = styled.input((props) => ({ + ...props.theme.typography.body_m, + backgroundColor: 'transparent', + color: props.theme.colors.white_0, + width: '100%', + border: 'transparent', })); -const BundleAssetContainer = styled.div((props) => ({ - maxHeight: 148, - width: 148, +const ErrorContainer = styled.div((props) => ({ + marginTop: props.theme.space.xs, + marginBottom: props.theme.space.l, +})); + +const RowContainer = styled.div({ display: 'flex', - aspectRatio: 1, - justifyContent: 'center', + flexDirection: 'row', alignItems: 'center', - borderRadius: 8, - marginTop: props.theme.spacing(8), - marginBottom: props.theme.spacing(6), -})); +}); + +const StyledCallout = styled(Callout)` + margin-bottom: ${(props) => props.theme.spacing(14)}px; +`; function SendOrdinal() { - const { t } = useTranslation('translation'); + const { t } = useTranslation('translation', { keyPrefix: 'SEND' }); const navigate = useNavigate(); const { selectedSatBundle } = useNftDataSelector(); const btcClient = useBtcClient(); @@ -106,52 +107,42 @@ function SendOrdinal() { useWalletSelector(); const { getSeed } = useSeedVault(); const [ordinalUtxo, setOrdinalUtxo] = useState(undefined); - const [error, setError] = useState(''); const [recipientAddress, setRecipientAddress] = useState(''); - const [warning, setWarning] = useState(''); + const [recipientError, setRecipientError] = useState(null); useResetUserFlow('/send-rare-sat'); - const address: string | undefined = useMemo( - () => (location.state ? location.state.recipientAddress : undefined), - [location.state], - ); - const isGalleryOpen: boolean = useMemo(() => document.documentElement.clientWidth > 360, []); - - const signTransaction = async (recipient: string) => { - const addressUtxos = await btcClient.getUnspentUtxos(ordinalsAddress); - const ordUtxo = addressUtxos.find( - (utxo) => - `${utxo.txid}:${utxo.vout}` === `${selectedSatBundle?.txid}:${selectedSatBundle?.vout}`, - ); - setOrdinalUtxo(ordUtxo); - if (ordUtxo) { - const seedPhrase = await getSeed(); - const signedTx = await signOrdinalSendTransaction( - recipient, - ordUtxo, - btcAddress, - Number(selectedAccount?.id), - seedPhrase, - network.type, - [ordUtxo], - ); - - return signedTx; - } - }; - const { isLoading, data, error: txError, mutate, } = useMutation({ - mutationFn: signTransaction, + mutationFn: async (recipient) => { + const addressUtxos = await btcClient.getUnspentUtxos(ordinalsAddress); + const ordUtxo = addressUtxos.find( + (utxo) => + `${utxo.txid}:${utxo.vout}` === `${selectedSatBundle?.txid}:${selectedSatBundle?.vout}`, + ); + setOrdinalUtxo(ordUtxo); + if (ordUtxo) { + const seedPhrase = await getSeed(); + const signedTx = await signOrdinalSendTransaction( + recipient, + ordUtxo, + btcAddress, + Number(selectedAccount?.id), + seedPhrase, + network.type, + [ordUtxo], + ); + return signedTx; + } + }, }); useEffect(() => { if (data) { - navigate(`/confirm-ordinal-tx/${selectedSatBundle?.txid}`, { + navigate(`/nft-dashboard/confirm-ordinal-tx/${selectedSatBundle?.txid}`, { state: { signedTxHex: data.signedTx, recipientAddress, @@ -170,10 +161,13 @@ function SendOrdinal() { useEffect(() => { if (txError) { if (Number(txError) === ErrorCodes.InSufficientBalance) { - setError(t('SEND.ERRORS.INSUFFICIENT_BALANCE')); + setRecipientError({ variant: 'danger', message: t('ERRORS.INSUFFICIENT_BALANCE') }); } else if (Number(txError) === ErrorCodes.InSufficientBalanceWithTxFee) { - setError(t('SEND.ERRORS.INSUFFICIENT_BALANCE_FEES')); - } else setError(txError.toString()); + setRecipientError({ + variant: 'danger', + message: t('ERRORS.INSUFFICIENT_BALANCE_FEES'), + }); + } else setRecipientError({ variant: 'danger', message: txError.toString() }); } }, [txError]); // eslint-disable-line react-hooks/exhaustive-deps @@ -181,85 +175,84 @@ function SendOrdinal() { navigate(-1); }; - function validateFields(associatedAddress: string): boolean { - if (!associatedAddress) { - setError(t('SEND.ERRORS.ADDRESS_REQUIRED')); + const validateRecipientAddress = (address: string): boolean => { + if (!address) { + setRecipientError({ variant: 'danger', message: t('ERRORS.ADDRESS_REQUIRED') }); return false; } - - if (!validateBtcAddress({ btcAddress: associatedAddress, network: network.type })) { - setError(t('SEND.ERRORS.ADDRESS_INVALID')); + if ( + !validateBtcAddress({ + btcAddress: address, + network: network.type, + }) + ) { + setRecipientError({ variant: 'danger', message: t('ERRORS.ADDRESS_INVALID') }); return false; } - + if (address === ordinalsAddress || address === btcAddress) { + setRecipientError({ variant: 'info', message: t('YOU_ARE_TRANSFERRING_TO_YOURSELF') }); + return true; + } + setRecipientError(null); return true; - } + }; - const onPressNext = async (associatedAddress: string) => { - setRecipientAddress(associatedAddress); - if (validateFields(associatedAddress)) { - mutate(associatedAddress); + const onPressNext = async () => { + if (validateRecipientAddress(recipientAddress)) { + mutate(recipientAddress); } }; - const handleInputChange = (inputAddress: string) => { - if (inputAddress === ordinalsAddress) { - return setWarning(t('SEND.YOU_ARE_TRANSFERRING_TO_YOURSELF')); - } - setWarning(''); + const handleAddressChange = (e: React.ChangeEvent) => { + validateRecipientAddress(e.target.value); + setRecipientAddress(e.target.value); }; - const heading = selectedSatBundle ? getBundleSubText(selectedSatBundle) : ''; - const subText = selectedSatBundle ? getBundleId(selectedSatBundle) : ''; + const isNextEnabled = !isDangerFeedback(recipientError) && !!recipientAddress; + + // hide back button if there is no history + const hideBackButton = location.key === 'default'; return ( - <> - {isGalleryOpen && ( - <> - - {!isLedgerAccount(selectedAccount) && ( - - - - )} - - )} - - {!isGalleryOpen && } - - - - - - - {heading} - - - {subText} - - - - {!isGalleryOpen && } - - + + +
+ + {t('SEND_TO')} + + + + + + + + + + + + {recipientError && } + + + +
+ + + +
+
); } diff --git a/src/app/screens/signBatchPsbtRequest/bundleItemsComponent.tsx b/src/app/screens/signBatchPsbtRequest/bundleItemsComponent.tsx deleted file mode 100644 index 9e1f4850c..000000000 --- a/src/app/screens/signBatchPsbtRequest/bundleItemsComponent.tsx +++ /dev/null @@ -1,233 +0,0 @@ -import Eye from '@assets/img/createPassword/Eye.svg'; -import Cross from '@assets/img/dashboard/X.svg'; -import IconOrdinal from '@assets/img/transactions/ordinal.svg'; -import RareSatAsset from '@components/rareSatAsset/rareSatAsset'; -import { animated, useSpring } from '@react-spring/web'; -import { getTruncatedAddress } from '@utils/helper'; -import { BundleItem, getBundleItemSubText } from '@utils/rareSats'; -import { useState } from 'react'; -import { useTranslation } from 'react-i18next'; -import styled from 'styled-components'; - -const Container = styled.div((props) => ({ - display: 'flex', - flexDirection: 'column', - background: props.theme.colors.elevation1, - borderRadius: 12, - padding: '16px 16px', - justifyContent: 'center', - marginBottom: 12, -})); - -const RecipientTitleText = styled.h1((props) => ({ - ...props.theme.body_medium_m, - color: props.theme.colors.white_200, - marginBottom: 10, -})); - -const RowContainer = styled.div({ - display: 'flex', - flexDirection: 'row', - alignItems: 'center', -}); - -const TransparentButton = styled.button({ - background: 'transparent', - display: 'flex', - alignItems: 'center', - marginLeft: 10, -}); - -const Icon = styled.img((props) => ({ - marginRight: props.theme.spacing(4), - width: 32, - height: 32, - borderRadius: 30, -})); - -const TitleText = styled.h1((props) => ({ - ...props.theme.body_medium_m, - color: props.theme.colors.white_200, -})); - -const ValueText = styled.h1((props) => ({ - ...props.theme.body_medium_m, - color: props.theme.colors.white_0, -})); - -const SubValueText = styled.h1((props) => ({ - ...props.theme.body_m, - fontSize: 12, - color: props.theme.colors.white_400, -})); - -const InscriptionText = styled.h1((props) => ({ - ...props.theme.body_medium_m, - fontSize: 21, - marginTop: 24, - textAlign: 'center', - color: props.theme.colors.white[0], - overflowWrap: 'break-word', - wordWrap: 'break-word', - wordBreak: 'break-word', -})); - -const ColumnContainer = styled.div((props) => ({ - display: 'flex', - flexDirection: 'column', - flex: 1, - justifyContent: 'flex-end', - alignItems: 'flex-end', - marginTop: props.theme.spacing(8), -})); - -const CrossContainer = styled.div({ - display: 'flex', - marginTop: 10, - justifyContent: 'flex-end', - alignItems: 'flex-end', -}); - -const OrdinalOuterImageContainer = styled.div({ - justifyContent: 'center', - alignItems: 'center', - borderRadius: 2, - display: 'flex', - flexDirection: 'column', - flex: 1, -}); - -const OrdinalImageContainer = styled.div({ - width: '50%', -}); - -const OrdinalBackgroundContainer = styled(animated.div)({ - width: '100%', - height: '100%', - top: 0, - left: 0, - bottom: 0, - right: 0, - position: 'fixed', - zIndex: 10, - background: 'rgba(18, 21, 30, 0.8)', - backdropFilter: 'blur(16px)', - padding: 16, - display: 'flex', - flexDirection: 'column', -}); - -const EyeIcon = styled.img({ - width: 20, - height: 20, -}); - -interface Props { - items: BundleItem[]; - userReceivesOrdinal?: boolean; -} -function BundleItemsComponent({ items, userReceivesOrdinal = false }: Props) { - const { t } = useTranslation('translation'); - const [showOrdinal, setShowOrdinal] = useState(false); - const [chosenOrdinal, setChosenOrdinal] = useState(0); - const styles = useSpring({ - from: { - opacity: 0, - y: 24, - }, - to: { - y: 0, - opacity: 1, - }, - delay: 100, - }); - - const onCloseClick = () => { - setShowOrdinal(false); - }; - - const getItemId = (item) => { - if (item.type === 'inscription') { - return item.inscription.id; - } - if (item.type === 'inscribed-sat' || item.type === 'rare-sat') { - return item.number; - } - return ''; - }; - - const getDetail = (item) => { - if (item.type === 'inscription' || item.type === 'inscribed-sat') { - return item.inscription.content_type; - } - - return getBundleItemSubText({ - satType: item.type, - rareSatsType: item.rarity_ranking, - }); - }; - - const getTitle = (item) => { - if (item.type === 'inscription') { - return t('COMMON.INSCRIPTION'); - } - if (item.type === 'inscribed-sat') { - return t('RARE_SATS.INSCRIBED_SAT'); - } - return t('RARE_SATS.RARE_SAT'); - }; - - return ( - <> - - - {userReceivesOrdinal - ? t('CONFIRM_TRANSACTION.YOU_WILL_RECEIVE_IN_TOTAL') - : t('CONFIRM_TRANSACTION.YOU_WILL_TRANSFER_IN_TOTAL')} - - - {items.map((item, index) => ( - // eslint-disable-next-line react/no-array-index-key - - - {getTitle(item)} - - - {getTruncatedAddress(String(getItemId(item)))} - { - setChosenOrdinal(index); - setShowOrdinal(true); - }} - > - - - - {getDetail(item)} - - - ))} - - - {showOrdinal && ( - - - - cross - - - - - - - {`${getTitle(items[chosenOrdinal])} ${getItemId( - items[chosenOrdinal], - )} `} - - - )} - - ); -} - -export default BundleItemsComponent; diff --git a/src/app/screens/signBatchPsbtRequest/index.tsx b/src/app/screens/signBatchPsbtRequest/index.tsx index 5108e0cd2..e546b7fe6 100644 --- a/src/app/screens/signBatchPsbtRequest/index.tsx +++ b/src/app/screens/signBatchPsbtRequest/index.tsx @@ -3,19 +3,19 @@ import { delay } from '@common/utils/ledger'; import AccountHeaderComponent from '@components/accountHeader'; import BottomModal from '@components/bottomModal'; import ActionButton from '@components/button'; +import SatsBundle from '@components/confirmBtcTransactionComponent/bundle'; import InputOutputComponent from '@components/confirmBtcTransactionComponent/inputOutputComponent'; import InfoContainer from '@components/infoContainer'; import LoadingTransactionStatus from '@components/loadingTransactionStatus'; import { ConfirmationStatus } from '@components/loadingTransactionStatus/circularSvgAnimation'; import RecipientComponent from '@components/recipientComponent'; import TransactionDetailComponent from '@components/transactionDetailComponent'; -import useDetectOrdinalInSignPsbt from '@hooks/useDetectOrdinalInSignPsbt'; +import useDetectOrdinalInSignPsbt, { InputsBundle } from '@hooks/useDetectOrdinalInSignPsbt'; import useSignBatchPsbtTx from '@hooks/useSignBatchPsbtTx'; import useWalletSelector from '@hooks/useWalletSelector'; import { ArrowLeft, ArrowRight } from '@phosphor-icons/react'; -import { parsePsbt, satsToBtc } from '@secretkeylabs/xverse-core'; +import { Bundle, parsePsbt, satsToBtc } from '@secretkeylabs/xverse-core'; import { isLedgerAccount } from '@utils/helper'; -import { BundleItem } from '@utils/rareSats'; import BigNumber from 'bignumber.js'; import { useCallback, useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; @@ -23,8 +23,6 @@ import { useLocation, useNavigate } from 'react-router-dom'; import { MoonLoader } from 'react-spinners'; import { SignMultiplePsbtPayload } from 'sats-connect'; import styled from 'styled-components'; -import BundleItemComponent from '../signPsbtRequest/bundleItemsComponent'; -import BundleItemsComponent from './bundleItemsComponent'; const OuterContainer = styled.div` display: flex; @@ -133,7 +131,7 @@ function SignBatchPsbtRequest() { const tabId = params.get('tabId') ?? '0'; const handleOrdinalAndOrdinalInfo = useDetectOrdinalInSignPsbt(); const [userReceivesOrdinalArr, setUserReceivesOrdinalArr] = useState< - { bundleItemsData: BundleItem[]; userReceivesOrdinal: boolean }[] + { bundleItemsData: InputsBundle; userReceivesOrdinal: boolean }[] >([]); const [isLoading, setIsLoading] = useState(true); @@ -366,13 +364,17 @@ function SignBatchPsbtRequest() { - {userTransfersOrdinals.length > 0 && ( - - )} + {userTransfersOrdinals.length > 0 && + userTransfersOrdinals.map((item, index) => ( + // eslint-disable-next-line react/no-array-index-key + + ))} - {userReceivesOrdinals.length > 0 && ( - - )} + {userReceivesOrdinals.length > 0 && + userTransfersOrdinals.map((item, index) => ( + // eslint-disable-next-line react/no-array-index-key + + ))} - {userReceivesOrdinalArr[currentPsbtIndex]?.bundleItemsData?.map((bundleItem, index) => ( - - ))} + )} ({ - display: 'flex', - flexDirection: 'column', - background: props.theme.colors.elevation1, - borderRadius: 12, - padding: '16px 16px', - justifyContent: 'center', - marginBottom: 12, -})); - -const RecipientTitleText = styled.h1((props) => ({ - ...props.theme.body_medium_m, - color: props.theme.colors.white_200, - marginBottom: 10, -})); - -const RowContainer = styled.div({ - display: 'flex', - flexDirection: 'row', - alignItems: 'center', -}); - -const TransparentButton = styled.button({ - background: 'transparent', - display: 'flex', - alignItems: 'center', - marginLeft: 10, -}); - -const Icon = styled.img((props) => ({ - marginRight: props.theme.spacing(4), - width: 32, - height: 32, - borderRadius: 30, -})); - -const TitleText = styled.h1((props) => ({ - ...props.theme.body_medium_m, - color: props.theme.colors.white_200, -})); - -const ValueText = styled.h1((props) => ({ - ...props.theme.body_medium_m, - color: props.theme.colors.white_0, -})); - -const SubValueText = styled.h1((props) => ({ - ...props.theme.body_m, - fontSize: 12, - color: props.theme.colors.white_400, -})); - -const InscriptionText = styled.h1((props) => ({ - ...props.theme.body_medium_m, - fontSize: 21, - marginTop: 24, - textAlign: 'center', - color: props.theme.colors.white[0], - overflowWrap: 'break-word', - wordWrap: 'break-word', - wordBreak: 'break-word', -})); - -const ColumnContainer = styled.div({ - display: 'flex', - flexDirection: 'column', - flex: 1, - justifyContent: 'flex-end', - alignItems: 'flex-end', - marginTop: 12, -}); - -const CrossContainer = styled.div({ - display: 'flex', - marginTop: 10, - justifyContent: 'flex-end', - alignItems: 'flex-end', -}); - -const OrdinalOuterImageContainer = styled.div({ - justifyContent: 'center', - alignItems: 'center', - borderRadius: 2, - display: 'flex', - flexDirection: 'column', - flex: 1, -}); - -const OrdinalImageContainer = styled.div({ - width: '50%', -}); - -const OrdinalBackgroundContainer = styled(animated.div)({ - width: '100%', - height: '100%', - top: 0, - left: 0, - bottom: 0, - right: 0, - position: 'fixed', - zIndex: 10, - background: 'rgba(18, 21, 30, 0.8)', - backdropFilter: 'blur(16px)', - padding: 16, - display: 'flex', - flexDirection: 'column', -}); - -const EyeIcon = styled.img({ - width: 20, - height: 20, -}); - -interface Props { - item: BundleItem; - userReceivesOrdinal: boolean; -} -function BundleItemsComponent({ item, userReceivesOrdinal }: Props) { - const { t } = useTranslation('translation'); - const [showOrdinal, setShowOrdinal] = useState(false); - const styles = useSpring({ - from: { - opacity: 0, - y: 24, - }, - to: { - y: 0, - opacity: 1, - }, - delay: 100, - }); - const onButtonClick = () => { - setShowOrdinal(true); - }; - - const onCrossClick = () => { - setShowOrdinal(false); - }; - const getItemId = () => { - if (item.type === 'inscription') { - return item.inscription.id; - } - if (item.type === 'inscribed-sat' || item.type === 'rare-sat') { - return item.number; - } - return ''; - }; - const itemSubText = getBundleItemSubText({ - satType: item.type, - rareSatsType: item.rarity_ranking, - }); - const getDetail = () => { - if (item.type === 'inscription' || item.type === 'inscribed-sat') { - return item.inscription.content_type; - } - return itemSubText; - }; - const getTitle = () => { - if (item.type === 'inscription') { - return t('COMMON.INSCRIPTION'); - } - if (item.type === 'inscribed-sat') { - return t('RARE_SATS.INSCRIBED_SAT'); - } - return t('RARE_SATS.RARE_SAT'); - }; - return ( - <> - {showOrdinal && ( - - - - cross - - - - - - - {`${getTitle()} ${getItemId()} `} - - - )} - - - {userReceivesOrdinal - ? t('CONFIRM_TRANSACTION.YOU_WILL_RECEIVE') - : t('CONFIRM_TRANSACTION.YOU_WILL_TRANSFER')} - - - - {getTitle()} - - - {getTruncatedAddress(String(getItemId()))} - - - - - {getDetail()} - - - - - ); -} - -export default BundleItemsComponent; diff --git a/src/app/screens/signPsbtRequest/index.tsx b/src/app/screens/signPsbtRequest/index.tsx index ed52dbe5e..0e87e1542 100644 --- a/src/app/screens/signPsbtRequest/index.tsx +++ b/src/app/screens/signPsbtRequest/index.tsx @@ -5,17 +5,19 @@ import { delay } from '@common/utils/ledger'; import AccountHeaderComponent from '@components/accountHeader'; import BottomModal from '@components/bottomModal'; import ActionButton from '@components/button'; +import SatsBundle from '@components/confirmBtcTransactionComponent/bundle'; import InputOutputComponent from '@components/confirmBtcTransactionComponent/inputOutputComponent'; import InfoContainer from '@components/infoContainer'; import LedgerConnectionView from '@components/ledger/connectLedgerView'; import RecipientComponent from '@components/recipientComponent'; import TransactionDetailComponent from '@components/transactionDetailComponent'; import useBtcClient from '@hooks/useBtcClient'; -import useDetectOrdinalInSignPsbt from '@hooks/useDetectOrdinalInSignPsbt'; +import useDetectOrdinalInSignPsbt, { InputsBundle } from '@hooks/useDetectOrdinalInSignPsbt'; import useSignPsbtTx from '@hooks/useSignPsbtTx'; import useWalletSelector from '@hooks/useWalletSelector'; import Transport from '@ledgerhq/hw-transport-webusb'; import { + Bundle, getBtcFiatEquivalent, parsePsbt, psbtBase64ToHex, @@ -24,7 +26,6 @@ import { Transport as TransportType, } from '@secretkeylabs/xverse-core'; import { isLedgerAccount } from '@utils/helper'; -import { BundleItem } from '@utils/rareSats'; import BigNumber from 'bignumber.js'; import { decodeToken } from 'jsontokens'; import { useEffect, useMemo, useState } from 'react'; @@ -34,7 +35,6 @@ import { useLocation, useNavigate } from 'react-router-dom'; import { MoonLoader } from 'react-spinners'; import { SignTransactionOptions } from 'sats-connect'; import styled from 'styled-components'; -import BundleItemsComponent from './bundleItemsComponent'; const OuterContainer = styled.div` display: flex; @@ -110,6 +110,9 @@ function SignPsbtRequest() { const { t: signatureRequestTranslate } = useTranslation('translation', { keyPrefix: 'SIGNATURE_REQUEST', }); + const { t: rareSatsTranslate } = useTranslation('translation', { + keyPrefix: 'RARE_SATS', + }); const [expandInputOutputView, setExpandInputOutputView] = useState(false); const { payload, confirmSignPsbt, cancelSignPsbt, getSigningAddresses } = useSignPsbtTx(); const [isSigning, setIsSigning] = useState(false); @@ -139,7 +142,7 @@ function SignPsbtRequest() { const handleOrdinalAndOrdinalInfo = useDetectOrdinalInSignPsbt(); const [isLoading, setIsLoading] = useState(true); const [userReceivesOrdinal, setUserReceivesOrdinal] = useState(false); - const [bundleItemsData, setBundleItemsData] = useState([]); + const [bundleItemsData, setBundleItemsData] = useState(); const signingAddresses = useMemo( () => getSigningAddresses(payload.inputsToSign), [payload.inputsToSign], @@ -384,15 +387,12 @@ function SignPsbtRequest() { {t('REVIEW_TRANSACTION')} {!payload.broadcast && } - {bundleItemsData && - bundleItemsData.map((bundleItem, index) => ( - - ))} + {bundleItemsData && ( + + )} + {payload.broadcast ? ( `https://ord${network === 'Mainnet' ? '' : '-testnet'}.xverse.app`; -export const MAGISAT_IO_RARITY_SCAN_URL = 'https://magisat.io/wallet?walletAddress='; - export const TRANSAC_URL = 'https://global.transak.com'; export const TRANSAC_API_KEY = process.env.TRANSAC_API_KEY; export const MOON_PAY_URL = 'https://buy.moonpay.com'; diff --git a/src/app/utils/inscriptions.ts b/src/app/utils/inscriptions.ts index 2afc4b87c..4d3424186 100644 --- a/src/app/utils/inscriptions.ts +++ b/src/app/utils/inscriptions.ts @@ -1,11 +1,9 @@ import { - CondensedInscription, Inscription, InscriptionCollectionsData, isBrcTransferValid, } from '@secretkeylabs/xverse-core'; import type { Color } from 'theme'; -import { BundleItem } from './rareSats'; export type Brc20Status = 'valid' | 'used'; @@ -45,11 +43,3 @@ export const getInscriptionsTabGridItemSubText = (collection: InscriptionCollect } return collection.total_inscriptions > 1 ? `${collection.total_inscriptions} Items` : '1 Item'; }; - -export const mapCondensedInscriptionToBundleItem = ( - inscription: CondensedInscription, -): BundleItem => ({ - inscription, - type: 'inscription', - rarity_ranking: 'common', // TODO eventually want to fetch this rarity and display it -}); diff --git a/src/app/utils/rareSats.test.ts b/src/app/utils/rareSats.test.ts deleted file mode 100644 index 304a814ae..000000000 --- a/src/app/utils/rareSats.test.ts +++ /dev/null @@ -1,187 +0,0 @@ -import { describe, expect, test } from 'vitest'; -import { ApiBundle, Bundle, mapRareSatsAPIResponseToRareSats } from './rareSats'; - -describe('rareSats', () => { - describe('mapRareSatsAPIResponseToRareSats', () => { - const testCases: Array<{ name: string; input: ApiBundle; expected: Bundle }> = [ - { - name: 'mixed (sats, inscriptions)', - input: { - txid: '10f78695a5f83dc2c508fffceb479e49423cf5d538c680864e56c0020c7f262e', - vout: 0, - block_height: 803440, - value: 600, - sats: [ - { - number: '32234503563456', - offset: 0, - rarity_ranking: 'epic', - }, - { - number: '0', - offset: 100, - rarity_ranking: 'mythic', - }, - ], - inscriptions: [ - { - id: '10f78695a5f83dc2c508fffceb479e49423cf5d538c680864e56c0020c7f262ei0', - offset: 0, - content_type: 'image/jpeg', - }, - { - id: '10f78695a5f83dc2c508fffceb479e49423cf5d538c680864e56c0020c7f262ei1', - offset: 500, - content_type: 'text/html', - }, - ], - }, - expected: { - txid: '10f78695a5f83dc2c508fffceb479e49423cf5d538c680864e56c0020c7f262e', - vout: 0, - block_height: 803440, - value: 600, - items: [ - { - inscription: { - content_type: 'text/html', - id: '10f78695a5f83dc2c508fffceb479e49423cf5d538c680864e56c0020c7f262ei1', - }, - rarity_ranking: 'common', - type: 'inscription', - }, - { - inscription: { - content_type: 'image/jpeg', - id: '10f78695a5f83dc2c508fffceb479e49423cf5d538c680864e56c0020c7f262ei0', - }, - rarity_ranking: 'epic', - type: 'inscribed-sat', - number: '32234503563456', - }, - { - number: '0', - rarity_ranking: 'mythic', - type: 'rare-sat', - }, - ], - }, - }, - { - name: 'only rare sats', - input: { - txid: '10f78695a5f83dc2c508fffceb479e49423cf5d538c680864e56c0020c7f262e', - vout: 0, - block_height: 803440, - value: 600, - sats: [ - { - number: '32234503563456', - offset: 0, - rarity_ranking: 'epic', - }, - { - number: '0', - offset: 100, - rarity_ranking: 'mythic', - }, - ], - inscriptions: [], - }, - expected: { - txid: '10f78695a5f83dc2c508fffceb479e49423cf5d538c680864e56c0020c7f262e', - vout: 0, - block_height: 803440, - value: 600, - items: [ - { - number: '32234503563456', - rarity_ranking: 'epic', - type: 'rare-sat', - }, - { - number: '0', - rarity_ranking: 'mythic', - type: 'rare-sat', - }, - ], - }, - }, - { - name: 'only inscriptions', - input: { - txid: '10f78695a5f83dc2c508fffceb479e49423cf5d538c680864e56c0020c7f262e', - vout: 0, - block_height: 803440, - value: 600, - sats: [], - inscriptions: [ - { - id: '10f78695a5f83dc2c508fffceb479e49423cf5d538c680864e56c0020c7f262ei0', - offset: 0, - content_type: 'image/jpeg', - }, - { - id: '10f78695a5f83dc2c508fffceb479e49423cf5d538c680864e56c0020c7f262ei1', - offset: 500, - content_type: 'text/html', - }, - ], - }, - expected: { - txid: '10f78695a5f83dc2c508fffceb479e49423cf5d538c680864e56c0020c7f262e', - vout: 0, - block_height: 803440, - value: 600, - items: [ - { - inscription: { - content_type: 'image/jpeg', - id: '10f78695a5f83dc2c508fffceb479e49423cf5d538c680864e56c0020c7f262ei0', - }, - rarity_ranking: 'common', - type: 'inscription', - }, - { - inscription: { - content_type: 'text/html', - id: '10f78695a5f83dc2c508fffceb479e49423cf5d538c680864e56c0020c7f262ei1', - }, - rarity_ranking: 'common', - type: 'inscription', - }, - ], - }, - }, - { - name: 'unknown', - input: { - txid: '10f78695a5f83dc2c508fffceb479e49423cf5d538c680864e56c0020c7f262e', - vout: 0, - block_height: 803440, - value: 600, - sats: [], - inscriptions: [], - }, - expected: { - txid: '10f78695a5f83dc2c508fffceb479e49423cf5d538c680864e56c0020c7f262e', - vout: 0, - block_height: 803440, - value: 600, - items: [ - { - type: 'unknown', - rarity_ranking: 'unknown', - }, - ], - }, - }, - ]; - - testCases.forEach(({ name, input, expected }) => { - test(name, () => { - expect(mapRareSatsAPIResponseToRareSats(input)).toEqual(expected); - }); - }); - }); -}); diff --git a/src/app/utils/rareSats.ts b/src/app/utils/rareSats.ts index d7217e96c..1506977ed 100644 --- a/src/app/utils/rareSats.ts +++ b/src/app/utils/rareSats.ts @@ -1,234 +1,43 @@ +import { + Bundle, + RareSatsType, + RodarmorRareSats, + RodarmorRareSatsType, +} from '@secretkeylabs/xverse-core'; import { t } from 'i18next'; import { getTruncatedAddress } from './helper'; -const RoadArmorRareSats = ['uncommon', 'rare', 'epic', 'legendary', 'mythic', 'common'] as const; -export type RoadArmorRareSatsType = (typeof RoadArmorRareSats)[number]; +export const getRareSatsLabelByType = (type: RareSatsType) => t(`RARE_SATS.RARITY_LABEL.${type}`); -export const RareSats = ['unknown', ...RoadArmorRareSats] as const; -export type RareSatsType = (typeof RareSats)[number]; - -export const getRareSatsLabelByType = (type: RareSatsType) => - t(`RARE_SATS.RARE_TYPES.${type.toUpperCase()}`); - -export type SatType = 'inscription' | 'rare-sat' | 'inscribed-sat' | 'unknown'; - -export const getBundleItemSubText = ({ - satType, - rareSatsType, -}: { - satType: SatType; - rareSatsType?: RareSatsType; -}) => - ({ - inscription: t('COMMON.INSCRIPTION'), - 'rare-sat': t('RARE_SATS.SAT_TYPES.RARE_SAT', { - type: getRareSatsLabelByType(rareSatsType ?? 'unknown'), - }), - 'inscribed-sat': t('RARE_SATS.SAT_TYPES.INSCRIBED_RARE_SAT', { - type: getRareSatsLabelByType(rareSatsType ?? 'unknown'), - }), - unknown: t('RARE_SATS.SAT_TYPES.UNKNOWN_RARE_SAT'), - }[satType]); - -// TODO: make number separator dynamic by locale, extension only supports en-US for now so this is not a priority -export const getRarityLabelByRareSatsType = (rareSatsType: RareSatsType) => - ({ - mythic: t('RARE_SATS.RARITY_RANKING_POSITION', { position: '1 / 2.1' }), - legendary: t('RARE_SATS.RARITY_RANKING_POSITION', { position: '5 / 2.1' }), - epic: t('RARE_SATS.RARITY_RANKING_POSITION', { position: '32 / 2.1' }), - rare: t('RARE_SATS.RARITY_RANKING_POSITION', { position: '3,437 / 2.1' }), - uncommon: t('RARE_SATS.RARITY_RANKING_POSITION', { position: '6,929,999 / 2.1' }), - common: '--', - }[rareSatsType]); - -export const getRareSatsColorsByRareSatsType = (rareSatsType: RareSatsType) => - ({ - unknown: { - color: 'rgb(175,186,189)', - backgroundColor: 'rgba(175,186,189,0.15)', - }, - uncommon: { - color: 'rgb(0,218,182)', - backgroundColor: 'rgba(0,218,182,0.15)', - }, - rare: { - color: 'rgb(100,196,246)', - backgroundColor: 'rgba(100,196,246,0.15)', - }, - epic: { - color: 'rgb(182,105,254)', - backgroundColor: 'rgba(182,105,254,0.15)', - }, - legendary: { - color: 'rgb(255,205,120)', - backgroundColor: 'rgba(255,205,120,0.15)', - }, - mythic: { - color: 'rgb(255,244,203)', - backgroundColor: 'rgba(255,244,203, 0.15)', - }, - common: { - color: 'rgb(216,216,216)', - backgroundColor: 'rgba(216,216,216,0.15)', - }, - }[rareSatsType ?? 'common']); - -type SatInscription = { - id: string; - offset: number; - content_type: string; -}; - -type Sat = { number: string; offset: number; rarity_ranking: RoadArmorRareSatsType }; - -export type ApiBundle = { - txid: string; - vout: number; - block_height: number; - value: number; - sats: Array; - inscriptions: Array; -}; - -export type BundleItem = - | { - type: 'rare-sat'; - rarity_ranking: RoadArmorRareSatsType; - number: string; - } - | { - type: 'inscribed-sat'; - rarity_ranking: RoadArmorRareSatsType; - number: string; - inscription: { - id: string; - content_type: string; - }; - } - | { - type: 'inscription'; - rarity_ranking: RoadArmorRareSatsType; - inscription: { - id: string; - content_type: string; - }; - } - | { - type: 'unknown'; - rarity_ranking: 'unknown'; - }; - -export type Bundle = Omit & { - items: Array; -}; - -export const mapRareSatsAPIResponseToRareSats = (apiBundles: ApiBundle): Bundle => { - const generalBundleInfo = { - txid: apiBundles.txid, - vout: apiBundles.vout, - block_height: apiBundles.block_height, - value: apiBundles.value, - }; - - // unknown - if (!apiBundles.sats.length && !apiBundles.inscriptions.length) { - return { ...generalBundleInfo, items: [{ type: 'unknown', rarity_ranking: 'unknown' }] }; - } - - // only rare sats - if (!apiBundles.inscriptions.length) { - return { - ...generalBundleInfo, - items: apiBundles.sats.map((sat) => ({ - type: 'rare-sat', - rarity_ranking: sat.rarity_ranking, - number: sat.number, - })), - }; - } - - // can be mixed - const satsObject = apiBundles.sats.reduce((acc, sat) => { - acc[sat.offset] = sat; - return acc; - }, {} as Record); - - const inscriptionsObject: Record = {}; - const items: Array = []; - - apiBundles.inscriptions.forEach((inscription) => { - inscriptionsObject[inscription.offset] = inscription; - - if (satsObject[inscription.offset]) { - return; - } - items.push({ - type: 'inscription', - rarity_ranking: 'common', - inscription: { - id: inscription.id, - content_type: inscription.content_type, - }, - }); - }); - - apiBundles.sats.forEach((sat) => { - const inscription = inscriptionsObject[sat.offset]; - if (!inscription) { - return items.push({ - type: 'rare-sat', - rarity_ranking: sat.rarity_ranking, - number: sat.number, - }); - } - items.push({ - type: 'inscribed-sat', - rarity_ranking: sat.rarity_ranking, - number: sat.number, - inscription: { - id: inscription.id, - content_type: inscription.content_type, - }, - }); - }); - - return { - ...generalBundleInfo, - items, +export const getRareSatsColorsByRareSatsType = (rareSatsType: RareSatsType) => { + const colors: Partial> = { + UNCOMMON: 'rgba(215, 105, 254, 0.20)', + RARE: 'rgba(131, 113, 242, 0.20)', + EPIC: 'rgba(145, 226, 96, 0.20)', + LEGENDARY: 'rgba(255,205,120,0.20)', + MYTHIC: 'rgba(255,244,203, 0.20)', + COMMON: 'rgba(175,186,189,0.20)', }; + return colors[rareSatsType]; }; -const getFormattedTxIdVoutFromBundle = (bundle: Bundle) => +export const getFormattedTxIdVoutFromBundle = (bundle: Bundle) => `${getTruncatedAddress(bundle.txid, 6)}:${bundle.vout}`; -export const getBundleId = (bundle: Bundle): string => { - if ( - bundle.items.length === 1 && - bundle.items[0].type !== 'unknown' && - bundle.items[0].type !== 'inscription' - ) { - return bundle.items[0].number; +export const getSatLabel = (satributes: RareSatsType[]): string => { + const isLengthGrateThanTwo = satributes.length > 2; + if (satributes.length === 1) { + return `${getRareSatsLabelByType(satributes[0])}`; } - return getFormattedTxIdVoutFromBundle(bundle); -}; - -export const getBundleSubText = (bundle: Bundle): string => { - if (bundle.items.length > 1) { - return t('RARE_SATS.RARE_SATS_BUNDLE'); + // we expect to roadarmor sats be in the first position + if (RodarmorRareSats.includes(satributes[0] as RodarmorRareSatsType)) { + return `${getRareSatsLabelByType(satributes[0])} ${t( + isLengthGrateThanTwo ? 'COMMON.COMBO' : `RARE_SATS.RARITY_LABEL.${satributes[1]}`, + )}`; } - const item = bundle.items[0]; - return getBundleItemSubText({ satType: item.type, rareSatsType: item.rarity_ranking }); -}; - -export const getBundleItemId = (bundle: Bundle, index: number): string => { - const item = bundle.items[index]; - if (item.type === 'unknown') { - return getFormattedTxIdVoutFromBundle(bundle); - } - if (item.type === 'inscription' || item.type === 'inscribed-sat') { - return getTruncatedAddress(item.inscription.id, 6); - } - return item.number; + return isLengthGrateThanTwo + ? `${t('COMMON.COMBO')}` + : `${getRareSatsLabelByType(satributes[0])} ${getRareSatsLabelByType(satributes[1])}`; }; diff --git a/src/assets/img/nftDashboard/rareSats/1d_pali.svg b/src/assets/img/nftDashboard/rareSats/1d_pali.svg new file mode 100644 index 000000000..d8b6cceec --- /dev/null +++ b/src/assets/img/nftDashboard/rareSats/1d_pali.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/assets/img/nftDashboard/rareSats/2d_pali.svg b/src/assets/img/nftDashboard/rareSats/2d_pali.svg new file mode 100644 index 000000000..29117d3f1 --- /dev/null +++ b/src/assets/img/nftDashboard/rareSats/2d_pali.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/assets/img/nftDashboard/rareSats/3d_pali.svg b/src/assets/img/nftDashboard/rareSats/3d_pali.svg new file mode 100644 index 000000000..122851258 --- /dev/null +++ b/src/assets/img/nftDashboard/rareSats/3d_pali.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/assets/img/nftDashboard/rareSats/alpha.svg b/src/assets/img/nftDashboard/rareSats/alpha.svg new file mode 100644 index 000000000..612fb5d2b --- /dev/null +++ b/src/assets/img/nftDashboard/rareSats/alpha.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/assets/img/nftDashboard/rareSats/black_epic.svg b/src/assets/img/nftDashboard/rareSats/black_epic.svg new file mode 100644 index 000000000..fa2dc2102 --- /dev/null +++ b/src/assets/img/nftDashboard/rareSats/black_epic.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/assets/img/nftDashboard/rareSats/black_legendary.svg b/src/assets/img/nftDashboard/rareSats/black_legendary.svg new file mode 100644 index 000000000..f5f6ba4ef --- /dev/null +++ b/src/assets/img/nftDashboard/rareSats/black_legendary.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/assets/img/nftDashboard/rareSats/black_rare.svg b/src/assets/img/nftDashboard/rareSats/black_rare.svg new file mode 100644 index 000000000..292974198 --- /dev/null +++ b/src/assets/img/nftDashboard/rareSats/black_rare.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/assets/img/nftDashboard/rareSats/black_uncommon.svg b/src/assets/img/nftDashboard/rareSats/black_uncommon.svg new file mode 100644 index 000000000..016063156 --- /dev/null +++ b/src/assets/img/nftDashboard/rareSats/black_uncommon.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/assets/img/nftDashboard/rareSats/block_78.svg b/src/assets/img/nftDashboard/rareSats/block_78.svg new file mode 100644 index 000000000..df22e9a77 --- /dev/null +++ b/src/assets/img/nftDashboard/rareSats/block_78.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/assets/img/nftDashboard/rareSats/block_9.svg b/src/assets/img/nftDashboard/rareSats/block_9.svg new file mode 100644 index 000000000..1ca053006 --- /dev/null +++ b/src/assets/img/nftDashboard/rareSats/block_9.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/assets/img/nftDashboard/rareSats/block_pali.svg b/src/assets/img/nftDashboard/rareSats/block_pali.svg new file mode 100644 index 000000000..453c76441 --- /dev/null +++ b/src/assets/img/nftDashboard/rareSats/block_pali.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/assets/img/nftDashboard/rareSats/epic.svg b/src/assets/img/nftDashboard/rareSats/epic.svg index 5efdf652e..b5267895a 100644 --- a/src/assets/img/nftDashboard/rareSats/epic.svg +++ b/src/assets/img/nftDashboard/rareSats/epic.svg @@ -1,9 +1,9 @@ - - - - - - - - + + + + + + + + diff --git a/src/assets/img/nftDashboard/rareSats/fibonacci_sequence.svg b/src/assets/img/nftDashboard/rareSats/fibonacci_sequence.svg new file mode 100644 index 000000000..20d232759 --- /dev/null +++ b/src/assets/img/nftDashboard/rareSats/fibonacci_sequence.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/assets/img/nftDashboard/rareSats/first_transaction_silkroad.svg b/src/assets/img/nftDashboard/rareSats/first_transaction_silkroad.svg new file mode 100644 index 000000000..da992764c --- /dev/null +++ b/src/assets/img/nftDashboard/rareSats/first_transaction_silkroad.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/assets/img/nftDashboard/rareSats/hitman.svg b/src/assets/img/nftDashboard/rareSats/hitman.svg new file mode 100644 index 000000000..544415779 --- /dev/null +++ b/src/assets/img/nftDashboard/rareSats/hitman.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/assets/img/nftDashboard/rareSats/jpeg.svg b/src/assets/img/nftDashboard/rareSats/jpeg.svg new file mode 100644 index 000000000..537c084dc --- /dev/null +++ b/src/assets/img/nftDashboard/rareSats/jpeg.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/assets/img/nftDashboard/rareSats/nakamoto.svg b/src/assets/img/nftDashboard/rareSats/nakamoto.svg new file mode 100644 index 000000000..82cce4dc7 --- /dev/null +++ b/src/assets/img/nftDashboard/rareSats/nakamoto.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/assets/img/nftDashboard/rareSats/omega.svg b/src/assets/img/nftDashboard/rareSats/omega.svg new file mode 100644 index 000000000..b356cc15b --- /dev/null +++ b/src/assets/img/nftDashboard/rareSats/omega.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/img/nftDashboard/rareSats/palinception.svg b/src/assets/img/nftDashboard/rareSats/palinception.svg new file mode 100644 index 000000000..c51b51469 --- /dev/null +++ b/src/assets/img/nftDashboard/rareSats/palinception.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/assets/img/nftDashboard/rareSats/palindrome.svg b/src/assets/img/nftDashboard/rareSats/palindrome.svg new file mode 100644 index 000000000..ac568fc3c --- /dev/null +++ b/src/assets/img/nftDashboard/rareSats/palindrome.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/src/assets/img/nftDashboard/rareSats/pizza.svg b/src/assets/img/nftDashboard/rareSats/pizza.svg new file mode 100644 index 000000000..122cc7204 --- /dev/null +++ b/src/assets/img/nftDashboard/rareSats/pizza.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/img/nftDashboard/rareSats/rare.svg b/src/assets/img/nftDashboard/rareSats/rare.svg index 452b9a55c..4b00d56d9 100644 --- a/src/assets/img/nftDashboard/rareSats/rare.svg +++ b/src/assets/img/nftDashboard/rareSats/rare.svg @@ -1,7 +1,7 @@ - - - - - - + + + + + + diff --git a/src/assets/img/nftDashboard/rareSats/sequence_pali.svg b/src/assets/img/nftDashboard/rareSats/sequence_pali.svg new file mode 100644 index 000000000..e5168f27d --- /dev/null +++ b/src/assets/img/nftDashboard/rareSats/sequence_pali.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/assets/img/nftDashboard/rareSats/uncommon.svg b/src/assets/img/nftDashboard/rareSats/uncommon.svg index 257ee10ba..9582059b4 100644 --- a/src/assets/img/nftDashboard/rareSats/uncommon.svg +++ b/src/assets/img/nftDashboard/rareSats/uncommon.svg @@ -1,8 +1,8 @@ - - - - - - - + + + + + + + diff --git a/src/assets/img/nftDashboard/rareSats/vintage.svg b/src/assets/img/nftDashboard/rareSats/vintage.svg new file mode 100644 index 000000000..51ad3ade9 --- /dev/null +++ b/src/assets/img/nftDashboard/rareSats/vintage.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/img/rareSats/ic_ordinal_small.svg b/src/assets/img/rareSats/ic_ordinal_small.svg new file mode 100644 index 000000000..cb9e95ff4 --- /dev/null +++ b/src/assets/img/rareSats/ic_ordinal_small.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/assets/img/rareSats/satBundle.svg b/src/assets/img/rareSats/satBundle.svg new file mode 100644 index 000000000..e9941a7cb --- /dev/null +++ b/src/assets/img/rareSats/satBundle.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/locales/en.json b/src/locales/en.json index 806715d52..0835bbf82 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -1,7 +1,10 @@ { "COMMON": { "INSCRIPTION": "Inscription", - "SEND": "Send" + "SEND": "Send", + "COMBO": "Combo", + "SATS": "Sats", + "SATTRIBUTES": "Sattributes" }, "LANDING_SCREEN": { "SCREEN_TITLE": "Wallet for Stacks & Bitcoin", @@ -585,19 +588,6 @@ "VERIFY_ADDRESS_ON_LEDGER": "Verify address on Ledger", "VIEW_ADDRESS": "View address", "ADD_STACKS_ADDRESS": "Add a Stacks address", - "RARITY_DETAIL": { - "TOP_TEXT": "Currently, only the default rarity scale is supported. Your sats could have other attributes.", - "LEARN_MORE": "Learn more", - "UNKNOWN": "A sat of unknown rarity.", - "UNCOMMON": "The first sat of each block.", - "RARE": "The first sat of each difficulty adjustment period.", - "EPIC": "The first sat of each halving epoch.", - "LEGENDARY": "The first sat of each cycle.", - "MYTHIC": "The first sat of the genesis block.", - "SCAN": "Scan for custom attributes", - "SUPPORTED_RARITIES": "Supported rarities", - "RARITY_INFO": "Currently, only the default rarity scale is supported. Your sats could have other attributes." - }, "NFTS": "NFTs", "RARE_SATS": "Rare Sats", "NEW_FEATURE": "New Feature, Rare Sats", @@ -608,7 +598,8 @@ "RARE_SATS_NOTICE_TITLE": "Don't see your rare sat?", "RARE_SATS_NOTICE_DETAIL": "Currently, Xverse only supports the Rodarmor rarity index. Your sats may have other attributes.", "SEE_SUPPORTED": "See supported rarity scale", - "FROM_RARE_SAT_BUNDLE": "This inscription belongs to the same bundle as other assets. Transferring it will involve transferring the full bundle." + "FROM_RARE_SAT_BUNDLE": "This inscription belongs to the same bundle as other assets. Transferring it will involve transferring the full bundle.", + "HOLDS_RARE_SAT": "This inscription holds a rare sat." }, "RESTORE_FUND_SCREEN": { "TITLE": "Restore assets", @@ -1101,14 +1092,73 @@ "LEARN_MORE": "Learn more" }, "RARE_SATS": { - "RARE_TYPES": { - "UNKNOWN": "Unknown", + "RARITY_LABEL": { "UNCOMMON": "Uncommon", - "COMMON": "Common", + "COMMON": "Common/Unknown", "RARE": "Rare", "EPIC": "Epic", "LEGENDARY": "Legendary", - "MYTHIC": "Mythic" + "MYTHIC": "Mythic", + "BLACK_LEGENDARY": "Black Legendary", + "BLACK_EPIC": "Black Epic", + "BLACK_RARE": "Black Rare", + "BLACK_UNCOMMON": "Black Uncommon", + "FIBONACCI": "Fibonacci Sequence", + "1D_PALINDROME": "1D Pali", + "2D_PALINDROME": "2D Pali", + "3D_PALINDROME": "3D Pali", + "SEQUENCE_PALINDROME": "Sequence Pali", + "PERFECT_PALINCEPTION": "Perfect Paliception", + "PALIBLOCK_PALINDROME": "Block Pali", + "PALINDROME": "Palindrome", + "NAME_PALINDROME": "Name Palindrome", + "ALPHA": "Alpha", + "OMEGA": "Omega", + "FIRST_TRANSACTION": "First transaction", + "BLOCK9": "Block 9", + "BLOCK78": "Block 78", + "NAKAMOTO": "Nakamoto", + "VINTAGE": "Vintage", + "PIZZA": "Pizza", + "JPEG": "Jpeg", + "HITMAN": "Hitman", + "SILK_ROAD": "Silkroad" + }, + "RARITY_DETAIL": { + "TOP_TEXT": "Currently, only the default rarity scale is supported. Your sats could have other attributes.", + "LEARN_MORE": "Learn more", + "RARITY_INFO": "Currently, only the default rarity scale is supported. Your sats could have other attributes.", + "SUPPORTED_RARITIES": "Supported rarities", + "MYTHIC": "The first sat of the genesis block.", + "LEGENDARY": "The first sat of each cycle.", + "EPIC": "The first sat of each halving epoch.", + "RARE": "The first sat of each difficulty adjustment period.", + "UNCOMMON": "The first sat of each block.", + "COMMON": "A sat of unknown rarity.", + "BLACK_LEGENDARY": "The last sat of each cycle.", + "BLACK_EPIC": "The last sat of each halving epoch.", + "BLACK_RARE": "The last sat of each difficulty adjustment period.", + "BLACK_UNCOMMON": "The last sat of each block.", + "FIBONACCI": "Sats with IDs that follow the Fibonacci Sequence.", + "1D_PALINDROME": "Sats with number composed of only 1 digit (ex: 888888888888).", + "2D_PALINDROME": "Sats with palindromic number, composed of only 2 digit (ex: 8888822288888).", + "3D_PALINDROME": "Sats with palindromic number, composed of only 3 digit (ex: 8885522255888).", + "SEQUENCE_PALINDROME": "Sats with palindromic number, and a sequence of at least 3 consecutive identical digits (ex: 3275433345723).", + "PERFECT_PALINCEPTION": "Sats with palindromic number made of a subsequence which is also a palindrome of at least 2 distinct digits.", + "PALIBLOCK_PALINDROME": "Sats with palindromic number, in a block with a palindromic number.", + "PALINDROME": "Sats with palindromic number (ex: 3275431345723).", + "NAME_PALINDROME": "Sats with palindromic names (ex: abcba).", + "ALPHA": "The first sats in each bitcoin. They always end in at least 8 zeros.", + "OMEGA": "The last sats in each bitcoin. They always end in at least 8 nines.", + "FIRST_TRANSACTION": "Sats from the 10 bitcoins Satoshi Nakamoto sent Hal Finney in the first bitcoin transaction.", + "BLOCK9": "Sats mined in Block 9 (the first block with sats circulating today).", + "BLOCK78": "Sats mined by Hal Finney in Block 78 (the first block mined by someone other than Satoshi).", + "NAKAMOTO": "Sats mined by Satoshi Nakamoto himself.", + "VINTAGE": "Sats mined in the first 1000 bitcoin blocks.", + "PIZZA": "Sats involved in the famous pizza transaction from 2010.", + "JPEG": "Sats involved in the possible first bitcoin trade for an image on February 24, 2010.", + "HITMAN": "Sats involved in the transaction made by Ross Ulbricht to hire a hitman.", + "SILK_ROAD": "Sats seized from Silk Road and auctioned off on June 27, 2014 by US Marshals." }, "SAT_TYPES": { "RARE_SAT": "{{type}} Sat", @@ -1125,7 +1175,8 @@ "RARE_SATS_BUNDLE": "Rare Sats Bundle", "BUNDLE_PENDING_SEND_DESCRIPTION": "This bundle is already in a pending transfer.", "RARE_SAT": "Rare Sat", - "INSCRIBED_SAT": "Inscribed Sat" + "INSCRIBED_SAT": "Inscribed Sat", + "BUNDLE_SIZE": "Bundle size" }, "COLLECTIBLE_COLLECTION_SCREEN": { "BACK_TO_GALLERY": "Back to gallery", From 3515fbdfcd92a7f585509819ae3d337726f7d394 Mon Sep 17 00:00:00 2001 From: Tim Man Date: Thu, 30 Nov 2023 23:03:32 +0800 Subject: [PATCH 04/28] [ENG-3314] feat: support repeat minter (#687) --- package-lock.json | 14 +- package.json | 2 +- src/app/routes/index.tsx | 8 + .../screens/confirmBtcTransaction/index.tsx | 2 +- .../createInscription/ContentLabel/index.tsx | 28 +- src/app/screens/createInscription/index.tsx | 431 +++++++++++------- src/app/utils/helper.ts | 4 + src/common/types/inpage-types.ts | 7 + src/common/types/message-types.ts | 22 +- .../utils/legacy-external-message-handler.ts | 21 + src/common/utils/route-urls.ts | 1 + src/content-scripts/content-script.ts | 13 + src/inpage/sats.inpage.ts | 30 ++ src/locales/en.json | 17 +- 14 files changed, 405 insertions(+), 195 deletions(-) diff --git a/package-lock.json b/package-lock.json index ac35a50f0..7a65036e3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,7 @@ "@ledgerhq/hw-transport-webusb": "^6.27.13", "@phosphor-icons/react": "^2.0.10", "@react-spring/web": "^9.6.1", - "@secretkeylabs/xverse-core": "4.0.0", + "@secretkeylabs/xverse-core": "4.1.0", "@stacks/connect": "^6.10.2", "@stacks/encryption": "4.3.5", "@stacks/stacks-blockchain-api-types": "6.1.1", @@ -1727,9 +1727,9 @@ } }, "node_modules/@secretkeylabs/xverse-core": { - "version": "4.0.0", - "resolved": "https://npm.pkg.github.com/download/@secretkeylabs/xverse-core/4.0.0/b2776f6bd4a6eb065b31eafd6ab198c486af7b8d", - "integrity": "sha512-u4XBHz8VYeQGFtpjxfOIMMcx6OAusQx02j/fAanwURHAbgeX8P90esY4s8rtAFcOV16dGRqA/HHEIusnt3d6+Q==", + "version": "4.1.0", + "resolved": "https://npm.pkg.github.com/download/@secretkeylabs/xverse-core/4.1.0/b8eaf1ac953cbc06959ae2f5e59544dd6b9d053c", + "integrity": "sha512-nkKyXZMAWCEohfrpQCsmgEQrz/Tdhl8KOoF1ptOIDRzKx75I7m2b8UOAC4eKgwzShAZSg0izxRxWoLuMSv/EiA==", "license": "ISC", "dependencies": { "@bitcoinerlab/secp256k1": "^1.0.2", @@ -16046,9 +16046,9 @@ } }, "@secretkeylabs/xverse-core": { - "version": "4.0.0", - "resolved": "https://npm.pkg.github.com/download/@secretkeylabs/xverse-core/4.0.0/b2776f6bd4a6eb065b31eafd6ab198c486af7b8d", - "integrity": "sha512-u4XBHz8VYeQGFtpjxfOIMMcx6OAusQx02j/fAanwURHAbgeX8P90esY4s8rtAFcOV16dGRqA/HHEIusnt3d6+Q==", + "version": "4.1.0", + "resolved": "https://npm.pkg.github.com/download/@secretkeylabs/xverse-core/4.1.0/b8eaf1ac953cbc06959ae2f5e59544dd6b9d053c", + "integrity": "sha512-nkKyXZMAWCEohfrpQCsmgEQrz/Tdhl8KOoF1ptOIDRzKx75I7m2b8UOAC4eKgwzShAZSg0izxRxWoLuMSv/EiA==", "requires": { "@bitcoinerlab/secp256k1": "^1.0.2", "@noble/secp256k1": "^1.7.1", diff --git a/package.json b/package.json index 83a967996..58007a7a5 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "@ledgerhq/hw-transport-webusb": "^6.27.13", "@phosphor-icons/react": "^2.0.10", "@react-spring/web": "^9.6.1", - "@secretkeylabs/xverse-core": "4.0.0", + "@secretkeylabs/xverse-core": "4.1.0", "@stacks/connect": "^6.10.2", "@stacks/encryption": "4.3.5", "@stacks/stacks-blockchain-api-types": "6.1.1", diff --git a/src/app/routes/index.tsx b/src/app/routes/index.tsx index 6bde691de..3df5e66d7 100644 --- a/src/app/routes/index.tsx +++ b/src/app/routes/index.tsx @@ -274,6 +274,14 @@ const router = createHashRouter([ ), }, + { + path: 'create-repeat-inscriptions', + element: ( + + + + ), + }, { path: 'login', element: , diff --git a/src/app/screens/confirmBtcTransaction/index.tsx b/src/app/screens/confirmBtcTransaction/index.tsx index d96bf5958..4739b583e 100644 --- a/src/app/screens/confirmBtcTransaction/index.tsx +++ b/src/app/screens/confirmBtcTransaction/index.tsx @@ -18,7 +18,7 @@ import { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useLocation, useNavigate } from 'react-router-dom'; import styled from 'styled-components'; -import SendLayout from 'app/layouts/sendLayout'; +import SendLayout from '../../layouts/sendLayout'; const BottomBarContainer = styled.h1((props) => ({ marginTop: props.theme.spacing(5), diff --git a/src/app/screens/createInscription/ContentLabel/index.tsx b/src/app/screens/createInscription/ContentLabel/index.tsx index 8131b14da..7b0552a8b 100644 --- a/src/app/screens/createInscription/ContentLabel/index.tsx +++ b/src/app/screens/createInscription/ContentLabel/index.tsx @@ -1,13 +1,13 @@ +import useWalletSelector from '@hooks/useWalletSelector'; +import { DotsThreeVertical, Eye, Share } from '@phosphor-icons/react'; +import { getBrc20Details } from '@utils/brc20'; +import { XVERSE_ORDIVIEW_URL } from '@utils/constants'; +import { formatNumber } from '@utils/helper'; import axios from 'axios'; +import BigNumber from 'bignumber.js'; import { MouseEvent as ReactMouseEvent, useEffect, useMemo, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import styled from 'styled-components'; - -import { DotsThreeVertical, Eye, Share } from '@phosphor-icons/react'; -import { XVERSE_ORDIVIEW_URL } from '@utils/constants'; - -import useWalletSelector from '@hooks/useWalletSelector'; -import { getBrc20Details } from '@utils/brc20'; import { ContentType } from './common'; import Preview from './preview'; import getSatsDetails from './utils'; @@ -41,11 +41,13 @@ const getContentType = (inputContentType: string) => { const isPreviewable = (contentType: ContentType) => previewableContentTypes.has(contentType); const isOrdiPreviewable = (contentType: ContentType) => ordiViewTypes.has(contentType); -const SuffixContainer = styled.div({ +const SuffixContainer = styled.div((props) => ({ + ...props.theme.typography.body_medium_m, + color: props.theme.colors.white_0, display: 'flex', flexDirection: 'column', alignItems: 'flex-end', -}); +})); const Container = styled.div({ display: 'flex', @@ -62,8 +64,8 @@ const ButtonIcon = styled.div((props) => ({ })); const Suffix = styled.div((props) => ({ - ...props.theme.body_xs, - color: props.theme.colors.white[400], + ...props.theme.typography.body_medium_s, + color: props.theme.colors.white_400, })); const MenuContainer = styled.div({ @@ -98,9 +100,10 @@ type Props = { type: 'BASE_64' | 'PLAIN_TEXT'; contentType: string; content: string; + repeat?: number; }; -function ContentIcon({ type, content, contentType: inputContentType }: Props) { +function ContentIcon({ type, content, contentType: inputContentType, repeat = 1 }: Props) { const { t } = useTranslation('translation', { keyPrefix: 'INSCRIPTION_REQUEST.PREVIEW' }); const [showPreview, setShowPreview] = useState(false); const [showMenu, setShowMenu] = useState(false); @@ -151,7 +154,8 @@ function ContentIcon({ type, content, contentType: inputContentType }: Props) { {t('BRC20.TITLE')} {t(`BRC20.${brc20Details.op.toUpperCase()}`)} - {brc20Details.tick} - {brc20Details.value} + {formatNumber(new BigNumber(brc20Details.value).multipliedBy(repeat).toString())}{' '} + {brc20Details.tick} ); diff --git a/src/app/screens/createInscription/index.tsx b/src/app/screens/createInscription/index.tsx index a4ef2b964..1159ec276 100644 --- a/src/app/screens/createInscription/index.tsx +++ b/src/app/screens/createInscription/index.tsx @@ -1,4 +1,4 @@ -import { Wallet } from '@phosphor-icons/react'; +import { ArrowDown } from '@phosphor-icons/react'; import BigNumber from 'bignumber.js'; import { decodeToken } from 'jsontokens'; import { useEffect, useMemo, useState } from 'react'; @@ -17,7 +17,7 @@ import { useInscriptionFees, UTXO, } from '@secretkeylabs/xverse-core'; -import { CreateInscriptionPayload } from 'sats-connect'; +import { CreateInscriptionPayload, CreateRepeatInscriptionsPayload } from 'sats-connect'; import SettingIcon from '@assets/img/dashboard/faders_horizontal.svg'; import OrdinalsIcon from '@assets/img/nftDashboard/white_ordinals_icon.svg'; @@ -28,11 +28,105 @@ import useWalletSelector from '@hooks/useWalletSelector'; import { getShortTruncatedAddress } from '@utils/helper'; import useSeedVault from '@hooks/useSeedVault'; +import Callout from '@ui-library/callout'; +import { StyledP } from '@ui-library/common.styled'; import CompleteScreen from './CompleteScreen'; import ContentLabel from './ContentLabel'; import EditFee from './EditFee'; import ErrorModal from './ErrorModal'; +const SATS_PER_BTC = 100e6; + +type CardRowProps = { + topMargin?: boolean; + center?: boolean; +}; +const CardRow = styled.div((props) => ({ + display: 'flex', + flexDirection: 'row', + alignItems: props.center ? 'center' : 'flex-start', + justifyContent: 'space-between', + marginTop: props.topMargin ? props.theme.spacing(8) : 0, +})); + +const NumberWithSuffixContainer = styled.div((props) => ({ + display: 'flex', + flexDirection: 'column', + justifyContent: 'space-between', + alignItems: 'flex-end', + color: props.theme.colors.white_0, +})); + +const NumberSuffix = styled.div((props) => ({ + ...props.theme.typography.body_s, + color: props.theme.colors.white_400, +})); + +const StyledPillLabel = styled.p` + display: flex; + align-items: center; + gap: ${(props) => props.theme.space.s}; +`; + +const Pill = styled.span` + ${(props) => props.theme.typography.body_bold_s} + color: ${(props) => props.theme.colors.elevation0}; + background-color: ${(props) => props.theme.colors.white_0}; + padding: 3px 6px; + border-radius: 40px; +`; + +function FeeRow({ + label, + subLabel, + value = 0, + fiatCurrency, + fiatRate, + repeat, +}: { + label: string; + subLabel?: string; + value?: number | string | null; + fiatCurrency: string; + fiatRate: string; + repeat?: number; +}) { + if (!value) { + return null; + } + const fiatValue = new BigNumber(value || 0) + .dividedBy(SATS_PER_BTC) + .multipliedBy(fiatRate) + .toFixed(2); + + return ( + +
+ + {label} + {repeat && {`x${repeat}`}} + + {!!subLabel && {subLabel}} +
+ + + {val}} + /> + + + ); +} + +const YourAddress = styled.div` + text-align: right; +`; + const OuterContainer = styled.div({ display: 'flex', flexDirection: 'column', @@ -52,24 +146,31 @@ const MainContainer = styled.div((props) => ({ })); const Title = styled.h1((props) => ({ - ...props.theme.headline_s, + ...props.theme.typography.headline_s, marginTop: props.theme.spacing(11), - color: props.theme.colors.white[0], + color: props.theme.colors.white_0, textAlign: 'left', })); const SubTitle = styled.h1((props) => ({ - ...props.theme.headline_category_s, + ...props.theme.typography.body_medium_m, + color: props.theme.colors.white_400, marginTop: props.theme.spacing(4), - color: props.theme.colors.white[400], textAlign: 'left', marginBottom: props.theme.spacing(12), })); +const StyledCallout = styled(Callout)` + margin-bottom: ${(props) => props.theme.space.m}; +`; + const CardContainer = styled.div<{ bottomPadding?: boolean }>((props) => ({ + ...props.theme.typography.body_medium_m, + color: props.theme.colors.white_200, display: 'flex', flexDirection: 'column', - background: props.theme.colors.background.elevation1, + gap: props.theme.space.m, + background: props.theme.colors.elevation1, borderRadius: 12, padding: props.theme.spacing(8), paddingBottom: props.bottomPadding ? props.theme.spacing(12) : props.theme.spacing(8), @@ -78,24 +179,14 @@ const CardContainer = styled.div<{ bottomPadding?: boolean }>((props) => ({ fontSize: 14, })); -type CardRowProps = { - topMargin?: boolean; - center?: boolean; -}; -const CardRow = styled.div((props) => ({ - display: 'flex', - flexDirection: 'row', - alignItems: props.center ? 'center' : 'flex-start', - justifyContent: 'space-between', - marginTop: props.topMargin ? props.theme.spacing(8) : 0, -})); - -const IconLabel = styled.div({ +const IconLabel = styled.div((props) => ({ + ...props.theme.typography.body_medium_m, + color: props.theme.colors.white_200, display: 'flex', flexDirection: 'row', alignItems: 'center', justifyContent: 'center', -}); +})); const ButtonIcon = styled.img((props) => ({ width: 32, @@ -104,20 +195,17 @@ const ButtonIcon = styled.img((props) => ({ })); const InfoIconContainer = styled.div((props) => ({ - background: props.theme.colors.background.elevation3, + background: props.theme.colors.white_0, + color: props.theme.colors.elevation0, width: 32, height: 32, display: 'flex', justifyContent: 'center', alignItems: 'center', - borderRadius: 16, + borderRadius: '50%', marginRight: props.theme.spacing(5), })); -const MutedLabel = styled.div((props) => ({ - color: props.theme.colors.white[400], -})); - const Button = styled.button((props) => ({ display: 'flex', flexDirection: 'row', @@ -129,8 +217,8 @@ const Button = styled.button((props) => ({ })); const ButtonText = styled.div((props) => ({ - ...props.theme.body_medium_m, - color: props.theme.colors.white['0'], + ...props.theme.typography.body_medium_m, + color: props.theme.colors.white_0, textAlign: 'center', })); @@ -140,32 +228,27 @@ const ButtonImage = styled.img((props) => ({ transform: 'all', })); -const NumberWithSuffixContainer = styled.div((props) => ({ - display: 'flex', - flexDirection: 'column', - justifyContent: 'space-between', - alignItems: 'flex-end', - color: props.theme.colors.white[0], -})); - -const NumberSuffix = styled.div((props) => ({ - ...props.theme.body_xs, - color: props.theme.colors.white[400], -})); - const DEFAULT_FEE_RATE = 8; function CreateInscription() { const { t } = useTranslation('translation', { keyPrefix: 'INSCRIPTION_REQUEST' }); const { search } = useLocation(); - const [payload, requestToken, tabId] = useMemo(() => { + const [payload, requestToken, tabId, origin] = useMemo(() => { const params = new URLSearchParams(search); - const requestEncoded = params.get('createInscription'); + const requestEncoded = + params.get('createInscription') ?? params.get('createRepeatInscriptions'); const requestBody = decodeToken(requestEncoded as string); - return [requestBody.payload as unknown, requestEncoded, Number(params.get('tabId'))]; + return [ + requestBody.payload as unknown, + requestEncoded, + Number(params.get('tabId')), + params.get('origin'), + ]; }, [search]); + const appName = new URL(origin || '')?.host; + const { contentType, content, @@ -174,7 +257,10 @@ function CreateInscription() { appFeeAddress, appFee, suggestedMinerFeeRate, - } = payload as CreateInscriptionPayload; + } = payload as CreateInscriptionPayload | CreateRepeatInscriptionsPayload; + + const { repeat } = payload as CreateRepeatInscriptionsPayload; + const showOver24RepeatsError = !Number.isNaN(repeat) && repeat > 24; const [utxos, setUtxos] = useState(); const [showFeeSettings, setShowFeeSettings] = useState(false); @@ -221,7 +307,6 @@ function CreateInscription() { }, [contentType, content, payloadType]); const { - commitValue, commitValueBreakdown, errorCode: feeErrorCode, isLoading: inscriptionFeesLoading, @@ -234,6 +319,7 @@ function CreateInscription() { serviceFee: appFee, serviceFeeAddress: appFeeAddress, network: network.type, + repetitions: repeat, }); const { @@ -255,13 +341,20 @@ function CreateInscription() { contentString: payloadType === 'PLAIN_TEXT' ? content : undefined, serviceFee: appFee, serviceFeeAddress: appFeeAddress, + repetitions: repeat, }); const cancelCallback = () => { const response = { source: MESSAGE_SOURCE, - method: ExternalSatsMethods.createInscriptionResponse, - payload: { createInscriptionRequest: requestToken, createInscriptionResponse: 'cancel' }, + method: repeat + ? ExternalSatsMethods.createRepeatInscriptionsResponse + : ExternalSatsMethods.createInscriptionResponse, + payload: { + createInscriptionRequest: requestToken, + createInscriptionResponse: 'cancel', + ...(repeat ? { repeat } : null), + }, }; chrome.tabs.sendMessage(tabId, response); @@ -277,30 +370,46 @@ function CreateInscription() { setShowFeeSettings(false); }; - const revealServiceFee = commitValueBreakdown?.revealServiceFee; - const externalServiceFee = commitValueBreakdown?.externalServiceFee; - const chainFee = - (commitValueBreakdown?.revealChainFee ?? 0) + (commitValueBreakdown?.commitChainFee ?? 0); + const { + revealServiceFee, + externalServiceFee, + revealChainFee, + commitChainFee, + totalInscriptionValue, + inscriptionValue, + } = commitValueBreakdown ?? {}; + + const chainFee = (revealChainFee ?? 0) + (commitChainFee ?? 0); const totalFee = (revealServiceFee ?? 0) + (externalServiceFee ?? 0) + chainFee; + const showTotalFee = totalFee !== chainFee; - const fiatFees = new BigNumber(totalFee).dividedBy(100e6).multipliedBy(btcFiatRate).toFixed(2); + const toFiat = (value: number | string = 0) => + new BigNumber(value).dividedBy(SATS_PER_BTC).multipliedBy(btcFiatRate).toFixed(2); - const fiatValue = new BigNumber(commitValue ?? 0) - .dividedBy(100e6) - .multipliedBy(btcFiatRate) - .toFixed(2); + const bundlePlusFees = new BigNumber(totalFee ?? 0) + .plus(new BigNumber(totalInscriptionValue ?? 0)) + .toString(); if (complete && revealTransactionId) { const onClose = () => { const response = { source: MESSAGE_SOURCE, - method: ExternalSatsMethods.createInscriptionResponse, - payload: { - createInscriptionRequest: requestToken, - createInscriptionResponse: { - txId: revealTransactionId, - }, - }, + method: repeat + ? ExternalSatsMethods.createRepeatInscriptionsResponse + : ExternalSatsMethods.createInscriptionResponse, + payload: repeat + ? { + createRepeatInscriptionsRequest: requestToken, + createRepeatInscriptionsResponse: { + txId: revealTransactionId, + }, + } + : { + createInscriptionRequest: requestToken, + createInscriptionResponse: { + txId: revealTransactionId, + }, + }, }; chrome.tabs.sendMessage(tabId, response); window.close(); @@ -320,141 +429,133 @@ function CreateInscription() { cancelText={t('CANCEL_BUTTON')} confirmText={!errorCode ? t('CONFIRM_BUTTON') : t(`ERRORS.SHORT.${errorCode}`)} loading={isExecuting || isLoading} - disabled={!!errorCode || isExecuting} - isError={!!errorCode} + disabled={!!errorCode || isExecuting || showOver24RepeatsError} + isError={!!errorCode || showOver24RepeatsError} > {t('TITLE')} - {t('SUBTITLE')} + {t('SUBTITLE', { name: appName ?? '' })} + {showOver24RepeatsError && ( + + )} -
{t('SUMMARY.TITLE')}
+ + {t('SUMMARY.TITLE')} + {repeat && {`x${repeat}`}} +
- +
{t('SUMMARY.ORDINAL')}
- +
- - {t('SUMMARY.TO')} - - + - + - {t('SUMMARY.YOUR_ADDRESS')} + {t('SUMMARY.TO')} -
{getShortTruncatedAddress(ordinalsAddress)}
+ + + {getShortTruncatedAddress(ordinalsAddress)} + + + {t('SUMMARY.YOUR_ADDRESS')} + +
{t('NETWORK')}
-
{network.type}
+ + {network.type} +
- -
{t('VALUE')}
-
- {isLoading && } - {!isLoading && ( - - - {value}} - /> - - )} -
-
+
{t('FEES.TITLE')}
{isLoading && }
- {!isLoading && ( - <> - -
{t('FEES.INSCRIPTION')}
- -
- {externalServiceFee && ( - -
{t('FEES.DEVELOPER')}
- -
- )} - -
{t('FEES.TRANSACTION')}
- - - {value}} - /> - -
- -
{t('FEES.TOTAL')}
-
- - - {value}} - /> - -
-
- + + + +
{t('FEES.TRANSACTION')}
+ + + {value}} + /> + {value}} + /> + +
+ {showTotalFee && ( + )}
+ + + - -
- - {error} - + )} + + + {!!error && ( + + {error} + + )} - - - - + + - + ); } diff --git a/src/app/layouts/sendLayout.tsx b/src/app/layouts/sendLayout.tsx index dc5ca3537..630eea0cf 100644 --- a/src/app/layouts/sendLayout.tsx +++ b/src/app/layouts/sendLayout.tsx @@ -8,23 +8,24 @@ import { useTranslation } from 'react-i18next'; import styled from 'styled-components'; import { breakpoints, devices } from 'theme'; -interface ContainerProps { - isGallery?: boolean; -} - -const ScrollContainer = styled.div((props) => ({ - display: 'flex', - flex: 1, - flexDirection: 'column', - ...props.theme.scrollbar, -})); +const ScrollContainer = styled.div` + display: flex; + flex: 1; + flex-direction: column; + justify-content: space-between; + ${(props) => props.theme.scrollbar} +`; -const Container = styled.div` +const Container = styled.div` display: flex; flex-direction: column; margin: auto; - margin-top: ${(props) => props.theme.space.xxs}; - padding: 0 ${(props) => props.theme.space.s}; + margin-top: 0; + margin-bottom: ${(props) => props.theme.space.xxs}; + padding-top: 0; + padding-left: ${(props) => props.theme.space.xs}; + padding-right: ${(props) => props.theme.space.xs}; + padding-bottom: 0; width: 100%; height: 100%; max-width: ${breakpoints.xs}px; @@ -33,11 +34,13 @@ const Container = styled.div` @media only screen and ${devices.min.s} { flex: initial; max-width: 588px; + max-height: unset; + height: auto; border: 1px solid ${(props) => props.theme.colors.elevation3}; border-radius: ${(props) => props.theme.space.s}; padding-top: ${(props) => props.theme.space.l}; - padding-left: ${(props) => (props.isGallery ? props.theme.space.m : 0)}; - padding-right: ${(props) => (props.isGallery ? props.theme.space.m : 0)}; + padding-left: ${(props) => props.theme.space.m}; + padding-right: ${(props) => props.theme.space.m}; padding-bottom: ${(props) => props.theme.space.xxl}; margin-top: ${(props) => props.theme.space.xxxxl}; } @@ -57,7 +60,6 @@ const Button = styled.button` display: flex; background-color: transparent; margin-bottom: ${(props) => props.theme.space.l}; - margin-left: ${(props) => props.theme.space.s}; `; function SendLayout({ @@ -73,7 +75,6 @@ function SendLayout({ const { t } = useTranslation('translation', { keyPrefix: 'SEND' }); const isScreenLargerThanXs = document.documentElement.clientWidth > Number(breakpoints.xs); const year = new Date().getFullYear(); - const isGalleryOpen: boolean = document.documentElement.clientWidth > 360; return ( <> @@ -83,7 +84,7 @@ function SendLayout({ )} - + {isScreenLargerThanXs && !hideBackButton && onClickBack && (