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 (
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+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)}
+ >
+
+
+
+ {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);
+ }}
+ >
+
+
+ {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 ? (
+
+
+
+ {inscriptions.length > 1
+ ? `+${inscriptions.length}`
+ : inscriptions[0].inscription_number}
+
+
+ ) : (
+ inscriptions.map((inscription) => (
+
+
+
+ {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' && (
-
- )}
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 && (