From 391caac934ece64b36cb86af30ceb2cced71ace4 Mon Sep 17 00:00:00 2001 From: slient-coder Date: Wed, 11 Dec 2024 09:52:10 +0800 Subject: [PATCH] feat: Add CAT721 fix: Missing "key" prop for element in iterator fix some type error. update Cat721CollectionScreen. motify Cat20 -> CAT20. fix: Reverse fix feat: Add the feature to purchase FB. fix Revert "feat: Add the feature to purchase FB." This reverts commit 2870ff939404c6ed01c3840d9bbfaf0edbf7415b. --- build/_raw/images/icons/fb.svg | 13 + src/background/controller/wallet.ts | 103 ++++++- src/background/service/openapi.ts | 75 +++++- src/shared/types.ts | 21 ++ src/ui/components/Button/index.tsx | 16 +- .../index.tsx | 0 .../components/CAT721CollectionCard/index.tsx | 108 ++++++++ src/ui/components/CAT721Preview/index.tsx | 87 ++++++ src/ui/components/Card/index.tsx | 5 +- src/ui/components/Content/index.tsx | 2 +- src/ui/components/Icon/index.tsx | 5 +- .../components/InscriptionPreview/index.tsx | 2 +- src/ui/components/Tabs/index.tsx | 67 +++++ src/ui/components/Text/index.less | 8 + src/ui/components/Text/index.tsx | 10 +- .../CAT20TokenScreen.tsx} | 0 .../MergeCAT20HistoryScreen.tsx | 0 .../MergeCAT20Screen/MergeProgressLayout.tsx | 0 .../MergeCAT20Screen/MergingItem.tsx | 0 .../MergeCAT20Screen/NoMergeLayout.tsx | 0 .../MergeCAT20Screen/index.tsx | 0 .../SendCAT20Screen.tsx} | 0 .../pages/CAT721/CAT721CollectionScreen.tsx | 170 ++++++++++++ src/ui/pages/CAT721/CAT721NFTScreen.tsx | 57 ++++ src/ui/pages/CAT721/SendCAT721Screen.tsx | 252 ++++++++++++++++++ .../{Cat20List.tsx => CAT20List.tsx} | 2 +- .../pages/Main/WalletTabScreen/CAT721List.tsx | 101 +++++++ src/ui/pages/Main/WalletTabScreen/CATTab.tsx | 51 ++++ src/ui/pages/Main/WalletTabScreen/index.tsx | 50 +++- src/ui/pages/MainRoute.tsx | 26 +- src/ui/state/settings/hooks.ts | 11 + src/ui/state/transactions/hooks.ts | 23 +- src/ui/state/ui/hooks.ts | 7 +- src/ui/state/ui/reducer.ts | 16 +- src/ui/theme/colors.ts | 5 +- src/ui/utils/WalletContext.tsx | 64 ++++- 36 files changed, 1301 insertions(+), 56 deletions(-) create mode 100644 build/_raw/images/icons/fb.svg rename src/ui/components/{Cat20BalanceCard => CAT20BalanceCard}/index.tsx (100%) create mode 100644 src/ui/components/CAT721CollectionCard/index.tsx create mode 100644 src/ui/components/CAT721Preview/index.tsx create mode 100644 src/ui/components/Tabs/index.tsx rename src/ui/pages/{Cat20/Cat20TokenScreen.tsx => CAT20/CAT20TokenScreen.tsx} (100%) rename src/ui/pages/{Cat20 => CAT20}/MergeCAT20HistoryScreen.tsx (100%) rename src/ui/pages/{Cat20 => CAT20}/MergeCAT20Screen/MergeProgressLayout.tsx (100%) rename src/ui/pages/{Cat20 => CAT20}/MergeCAT20Screen/MergingItem.tsx (100%) rename src/ui/pages/{Cat20 => CAT20}/MergeCAT20Screen/NoMergeLayout.tsx (100%) rename src/ui/pages/{Cat20 => CAT20}/MergeCAT20Screen/index.tsx (100%) rename src/ui/pages/{Cat20/SendCat20Screen.tsx => CAT20/SendCAT20Screen.tsx} (100%) create mode 100644 src/ui/pages/CAT721/CAT721CollectionScreen.tsx create mode 100644 src/ui/pages/CAT721/CAT721NFTScreen.tsx create mode 100644 src/ui/pages/CAT721/SendCAT721Screen.tsx rename src/ui/pages/Main/WalletTabScreen/{Cat20List.tsx => CAT20List.tsx} (98%) create mode 100644 src/ui/pages/Main/WalletTabScreen/CAT721List.tsx create mode 100644 src/ui/pages/Main/WalletTabScreen/CATTab.tsx diff --git a/build/_raw/images/icons/fb.svg b/build/_raw/images/icons/fb.svg new file mode 100644 index 00000000..7e67eefb --- /dev/null +++ b/build/_raw/images/icons/fb.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/background/controller/wallet.ts b/src/background/controller/wallet.ts index 42f52d16..c8851189 100644 --- a/src/background/controller/wallet.ts +++ b/src/background/controller/wallet.ts @@ -1,28 +1,64 @@ -import { contactBookService, keyringService, notificationService, openapiService, permissionService, preferenceService, sessionService } from '@/background/service'; +import { + contactBookService, + keyringService, + notificationService, + openapiService, + permissionService, + preferenceService, + sessionService +} from '@/background/service'; import i18n from '@/background/service/i18n'; import { DisplayedKeyring, Keyring } from '@/background/service/keyring'; -import { ADDRESS_TYPES, AddressFlagType, AUTO_LOCKTIMES, BRAND_ALIAN_TYPE_TEXT, CHAINS_ENUM, CHAINS_MAP, ChainType, COIN_NAME, COIN_SYMBOL, DEFAULT_LOCKTIME_ID, EVENTS, KEYRING_TYPE, KEYRING_TYPES, NETWORK_TYPES, UNCONFIRMED_HEIGHT } from '@/shared/constant'; +import { + ADDRESS_TYPES, + AddressFlagType, + AUTO_LOCKTIMES, + BRAND_ALIAN_TYPE_TEXT, + CHAINS_ENUM, + CHAINS_MAP, + ChainType, + COIN_NAME, + COIN_SYMBOL, + DEFAULT_LOCKTIME_ID, + EVENTS, + KEYRING_TYPE, + KEYRING_TYPES, + NETWORK_TYPES, + UNCONFIRMED_HEIGHT +} from '@/shared/constant'; import eventBus from '@/shared/eventBus'; import { runesUtils } from '@/shared/lib/runes-utils'; -import { Account, AddressType, AddressUserToSignInput, BitcoinBalance, NetworkType, PublicKeyUserToSignInput, SignPsbtOptions, ToSignInput, UTXO, WalletKeyring } from '@/shared/types'; +import { + Account, + AddressType, + AddressUserToSignInput, + BitcoinBalance, + NetworkType, + PublicKeyUserToSignInput, + SignPsbtOptions, + ToSignInput, + UTXO, + WalletKeyring +} from '@/shared/types'; import { checkAddressFlag, getChainInfo } from '@/shared/utils'; import { txHelpers, UnspentOutput, UTXO_DUST } from '@unisat/wallet-sdk'; import { isValidAddress, publicKeyToAddress, scriptPkToAddress } from '@unisat/wallet-sdk/lib/address'; import { bitcoin, ECPair } from '@unisat/wallet-sdk/lib/bitcoin-core'; import { KeystoneKeyring } from '@unisat/wallet-sdk/lib/keyring'; -import { genPsbtOfBIP322Simple, getSignatureFromPsbtOfBIP322Simple, signMessageOfBIP322Simple } from '@unisat/wallet-sdk/lib/message'; +import { + genPsbtOfBIP322Simple, + getSignatureFromPsbtOfBIP322Simple, + signMessageOfBIP322Simple +} from '@unisat/wallet-sdk/lib/message'; import { toPsbtNetwork } from '@unisat/wallet-sdk/lib/network'; import { getAddressUtxoDust } from '@unisat/wallet-sdk/lib/transaction'; import { toXOnly } from '@unisat/wallet-sdk/lib/utils'; - - import { ContactBookItem } from '../service/contactBook'; import { OpenApiService } from '../service/openapi'; import { ConnectedSite } from '../service/permission'; import BaseController from './base'; - const stashKeyrings: Record = {}; export type AccountAsset = { name: string; @@ -2090,6 +2126,59 @@ export class WalletController extends BaseController { getBlockActiveInfo = () => { return openapiService.getBlockActiveInfo(); }; + + getCAT721List = async (address: string, currentPage: number, pageSize: number) => { + const cursor = (currentPage - 1) * pageSize; + const size = pageSize; + const { total, list } = await openapiService.getCAT721CollectionList(address, cursor, size); + + return { + currentPage, + pageSize, + total, + list + }; + }; + + getAddressCAT721CollectionSummary = async (address: string, collectionId: string) => { + const collectionSummary = await openapiService.getAddressCAT721CollectionSummary(address, collectionId); + return collectionSummary; + }; + + transferCAT721Step1 = async (to: string, collectionId: string, localId: string, feeRate: number) => { + const currentAccount = await this.getCurrentAccount(); + if (!currentAccount) { + return; + } + + const _res = await openapiService.transferCAT721Step1( + currentAccount.address, + currentAccount.pubkey, + to, + collectionId, + localId, + feeRate + ); + return _res; + }; + + transferCAT721Step2 = async (transferId: string, commitTx: string, toSignInputs: ToSignInput[]) => { + const networkType = this.getNetworkType(); + const psbtNetwork = toPsbtNetwork(networkType); + const psbt = bitcoin.Psbt.fromBase64(commitTx, { network: psbtNetwork }); + await this.signPsbt(psbt, toSignInputs, true); + const _res = await openapiService.transferCAT721Step2(transferId, psbt.toBase64()); + return _res; + }; + + transferCAT721Step3 = async (transferId: string, revealTx: string, toSignInputs: ToSignInput[]) => { + const networkType = this.getNetworkType(); + const psbtNetwork = toPsbtNetwork(networkType); + const psbt = bitcoin.Psbt.fromBase64(revealTx, { network: psbtNetwork }); + await this.signPsbt(psbt, toSignInputs, false); + const _res = await openapiService.transferCAT721Step3(transferId, psbt.toBase64()); + return _res; + }; } export default new WalletController(); diff --git a/src/background/service/openapi.ts b/src/background/service/openapi.ts index 1c61078a..ea69bf43 100644 --- a/src/background/service/openapi.ts +++ b/src/background/service/openapi.ts @@ -1,16 +1,35 @@ import randomstring from 'randomstring'; - - import { createPersistStore } from '@/background/utils'; import { CHAINS_MAP, CHANNEL, VERSION } from '@/shared/constant'; -import { AddressRunesTokenSummary, AddressSummary, AddressTokenSummary, AppInfo, AppSummary, Arc20Balance, BitcoinBalance, CAT20Balance, CoinPrice, DecodedPsbt, FeeSummary, InscribeOrder, Inscription, InscriptionSummary, RuneBalance, TickPriceItem, TokenBalance, TokenTransfer, UTXO, UTXO_Detail, VersionDetail, WalletConfig } from '@/shared/types'; - - +import { + AddressRunesTokenSummary, + AddressSummary, + AddressTokenSummary, + AppInfo, + AppSummary, + Arc20Balance, + BitcoinBalance, + CAT20Balance, + CAT721CollectionInfo, + CoinPrice, + DecodedPsbt, + FeeSummary, + InscribeOrder, + Inscription, + InscriptionSummary, + RuneBalance, + TickPriceItem, + TokenBalance, + TokenTransfer, + UTXO, + UTXO_Detail, + VersionDetail, + WalletConfig +} from '@/shared/types'; import { preferenceService } from '.'; - interface OpenApiStore { deviceId: string; config?: WalletConfig; @@ -637,6 +656,50 @@ export class OpenApiService { id: mergeId }); } + + async getCAT721CollectionList( + address: string, + cursor: number, + size: number + ): Promise<{ list: CAT721CollectionInfo[]; total: number }> { + return this.httpGet('/v5/cat721/collection/list', { address, cursor, size }); + } + + async getAddressCAT721CollectionSummary(address: string, collectionId: string) { + return this.httpGet(`/v5/cat721/collection-summary?address=${address}&collectionId=${collectionId}`, {}); + } + + async transferCAT721Step1( + address: string, + pubkey: string, + to: string, + collectionId: string, + localId: string, + feeRate: number + ) { + return this.httpPost(`/v5/cat721/transfer-nft-step1`, { + address, + pubkey, + to, + collectionId, + localId, + feeRate + }); + } + + async transferCAT721Step2(transferId: string, signedPsbt: string) { + return this.httpPost(`/v5/cat721/transfer-nft-step2`, { + id: transferId, + psbt: signedPsbt + }); + } + + async transferCAT721Step3(transferId: string, signedPsbt: string) { + return this.httpPost(`/v5/cat721/transfer-nft-step3`, { + id: transferId, + psbt: signedPsbt + }); + } } export default new OpenApiService(); diff --git a/src/shared/types.ts b/src/shared/types.ts index f0da1c4f..bdffcff2 100644 --- a/src/shared/types.ts +++ b/src/shared/types.ts @@ -517,3 +517,24 @@ export interface WebsiteResult { warning: string; allowQuickMultiSign: boolean; } + +export interface CAT721Balance { + collectionId: string; + name: string; + count: number; + previewLocalIds: string[]; +} + +export interface CAT721CollectionInfo { + collectionId: string; + name: string; + symbol: string; + max: string; + premine: string; + description: string; +} + +export interface AddressCAT721CollectionSummary { + collectionInfo: CAT721CollectionInfo; + localIds: string[]; +} diff --git a/src/ui/components/Button/index.tsx b/src/ui/components/Button/index.tsx index 1f60994c..f2ed06bf 100644 --- a/src/ui/components/Button/index.tsx +++ b/src/ui/components/Button/index.tsx @@ -51,6 +51,10 @@ export interface ButtonProps { children?: React.ReactNode; onClick?: React.MouseEventHandler; icon?: IconTypes; + iconSize?: { + width: number; + height: number; + }; disabled?: boolean; full?: boolean; } @@ -210,6 +214,7 @@ export function Button(props: ButtonProps) { LeftAccessory, onClick, icon, + iconSize, disabled, full, ...rest @@ -262,7 +267,16 @@ export function Button(props: ButtonProps) { onClick={disabled ? undefined : onClick} onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)}> - {icon && } + {icon && ( + + )} {text && } ); diff --git a/src/ui/components/Cat20BalanceCard/index.tsx b/src/ui/components/CAT20BalanceCard/index.tsx similarity index 100% rename from src/ui/components/Cat20BalanceCard/index.tsx rename to src/ui/components/CAT20BalanceCard/index.tsx diff --git a/src/ui/components/CAT721CollectionCard/index.tsx b/src/ui/components/CAT721CollectionCard/index.tsx new file mode 100644 index 00000000..92ad04d9 --- /dev/null +++ b/src/ui/components/CAT721CollectionCard/index.tsx @@ -0,0 +1,108 @@ +import { CAT721Balance } from '@/shared/types'; +import { useCAT721NFTContentBaseUrl } from '@/ui/state/settings/hooks'; + +import { Column } from '../Column'; +import { Image } from '../Image'; +import { Row } from '../Row'; +import { Text } from '../Text'; + +export interface CAT721CollectionCardProps { + cat721Balance: CAT721Balance; + onClick?: () => void; +} + +function CardComponent(props: { children: React.ReactNode; onClick?: () => void }) { + return ( + { + props.onClick && props.onClick(); + }}> + {props.children} + + ); +} + +export function CAT721CollectionCard(props: CAT721CollectionCardProps) { + const { cat721Balance, onClick } = props; + + const previewLocalIds = cat721Balance.previewLocalIds.slice(0, 4); + if (previewLocalIds.length > 1) { + for (let i = 0; i < 4; i++) { + if (!previewLocalIds[i]) { + previewLocalIds[i] = 'PLACEHOLDER'; + } + } + } + + const contentBaseUrl = useCAT721NFTContentBaseUrl(); + return ( + + {cat721Balance.previewLocalIds.length > 1 ? ( + + {previewLocalIds.map((localId, index) => { + if (localId === 'PLACEHOLDER') { + return ( + + ); + } else { + return ( + + ); + } + })} + + ) : ( + + + + )} + + + + + + + + + + ); +} diff --git a/src/ui/components/CAT721Preview/index.tsx b/src/ui/components/CAT721Preview/index.tsx new file mode 100644 index 00000000..f66c7e77 --- /dev/null +++ b/src/ui/components/CAT721Preview/index.tsx @@ -0,0 +1,87 @@ +import { useCAT721NFTContentBaseUrl } from '@/ui/state/settings/hooks'; + +import { Column } from '../Column'; +import { Image } from '../Image'; +import { Row } from '../Row'; +import { Sizes, Text } from '../Text'; + +// import './index.less'; + +const $viewPresets = { + large: {}, + + medium: {}, + + small: {} +}; + +const $stylePresets: { + [key: string]: { + width: number; + height: number; + borderRadius: number; + textSize: Sizes; + }; +} = { + large: { + width: 300, + height: 300, + borderRadius: 20, + textSize: 'md' + }, + medium: { + width: 156, + height: 156, + borderRadius: 15, + textSize: 'sm' + }, + small: { + width: 80, + height: 80, + borderRadius: 10, + textSize: 'xxs' + } +}; + +type Presets = keyof typeof $viewPresets; + +export interface InscriptionProps { + collectionId: string; + localId: string; + onClick?: (data: any) => void; + preset: Presets; +} + +export default function CAT721Preview({ collectionId, localId, onClick, preset }: InscriptionProps) { + const style = $stylePresets[preset]; + + const contentBaseUrl = useCAT721NFTContentBaseUrl(); + return ( + + + + + + + + + + + ); +} diff --git a/src/ui/components/Card/index.tsx b/src/ui/components/Card/index.tsx index 5ae57e26..71300e61 100644 --- a/src/ui/components/Card/index.tsx +++ b/src/ui/components/Card/index.tsx @@ -1,4 +1,4 @@ -import React, { CSSProperties } from 'react'; +import { CSSProperties } from 'react'; import { colors } from '@/ui/theme/colors'; import { spacingGap } from '@/ui/theme/spacing'; @@ -24,8 +24,7 @@ const $viewPresets = { paddingTop: spacingGap.lg, paddingBottom: spacingGap.lg, paddingLeft: spacingGap.lg, - paddingRight: spacingGap.lg, - minHeight: 50 + paddingRight: spacingGap.lg } as CSSProperties) as CSSProperties, style1: Object.assign({}, $baseViewStyle, { height: '75px', diff --git a/src/ui/components/Content/index.tsx b/src/ui/components/Content/index.tsx index 433884aa..4d0c17a7 100644 --- a/src/ui/components/Content/index.tsx +++ b/src/ui/components/Content/index.tsx @@ -25,7 +25,7 @@ const $contentStyle = { const $viewPresets = { large: Object.assign({}, $contentStyle, { alignItems: 'stretch', - padding: spacing.large, + padding: spacing.medium, paddingTop: 0 }), middle: Object.assign({}, $contentStyle, { diff --git a/src/ui/components/Icon/index.tsx b/src/ui/components/Icon/index.tsx index 11a1b6ce..f776f1a5 100644 --- a/src/ui/components/Icon/index.tsx +++ b/src/ui/components/Icon/index.tsx @@ -5,7 +5,7 @@ import { fontSizes } from '@/ui/theme/font'; export const svgRegistry = { history: './images/icons/clock-solid.svg', - send: './images/icons/send.svg', + send: './images/icons/arrow-left-right.svg', receive: './images/icons/qrcode.svg', right: './images/icons/right.svg', @@ -55,7 +55,8 @@ export const svgRegistry = { paused: '/images/icons/paused.svg', unisat: './images/icons/unisat.svg', - gas: './images/icons/gas.svg' + gas: './images/icons/gas.svg', + fb: './images/icons/fb.svg' }; const iconImgList: Array = ['success', 'delete', 'btc']; diff --git a/src/ui/components/InscriptionPreview/index.tsx b/src/ui/components/InscriptionPreview/index.tsx index c3a853b9..8b0dd295 100644 --- a/src/ui/components/InscriptionPreview/index.tsx +++ b/src/ui/components/InscriptionPreview/index.tsx @@ -145,7 +145,7 @@ export default function InscriptionPreview({ data, onClick, preset, asLogo, hide ) : null} - + {isUnconfirmed == false && data.timestamp && ( )} diff --git a/src/ui/components/Tabs/index.tsx b/src/ui/components/Tabs/index.tsx new file mode 100644 index 00000000..53032c5d --- /dev/null +++ b/src/ui/components/Tabs/index.tsx @@ -0,0 +1,67 @@ +import { useEffect, useState } from 'react'; + +import { colors } from '@/ui/theme/colors'; + +import { Column } from '../Column'; +import { Row } from '../Row'; +import { Text } from '../Text'; + +interface TabItem { + key: string; + label: string; + children: React.ReactNode; +} + +interface TabsProps { + defaultActiveKey: string; + activeKey: string; + items: TabItem[]; + onTabClick: (key: string) => void; +} + +export function Tabs({ items, defaultActiveKey, activeKey, onTabClick }: TabsProps) { + const [activeTab, setActiveTab] = useState(defaultActiveKey); + + useEffect(() => { + setActiveTab(activeKey); + }, [activeKey]); + + return ( + + + + {items.map((item) => { + const isActiveItem = item.key === activeTab; + return ( + onTabClick(item.key)} mx="md"> + + + + + + ); + })} + + + + + {items.find((item) => item.key === activeTab)?.children} + + ); +} diff --git a/src/ui/components/Text/index.less b/src/ui/components/Text/index.less index 0142012f..9e9c0a6f 100644 --- a/src/ui/components/Text/index.less +++ b/src/ui/components/Text/index.less @@ -6,3 +6,11 @@ -webkit-line-clamp: 2; line-clamp: 2; } + +.span-max-lines-1 { + resize: both; + overflow: hidden; + white-space: nowrap; + font-size: calc(1em - 1px); + line-height: 1; +} diff --git a/src/ui/components/Text/index.tsx b/src/ui/components/Text/index.tsx index a95d0b21..8f652491 100644 --- a/src/ui/components/Text/index.tsx +++ b/src/ui/components/Text/index.tsx @@ -73,6 +73,7 @@ export interface TextProps extends BaseViewProps { disableTranslate?: boolean; digital?: boolean; ellipsis?: boolean; + max1Lines?: boolean; max2Lines?: boolean; } @@ -89,6 +90,7 @@ export function Text(props: TextProps) { disableTranslate, ellipsis, style: $styleOverride, + max1Lines, max2Lines, ...rest } = props; @@ -111,6 +113,12 @@ export function Text(props: TextProps) { ); const $style = Object.assign({}, $textStyle, $styleOverride); const textUse = props.digital ? showLongNumber(text) : text; + let textUseClassName = ''; + if (max1Lines) { + textUseClassName = 'span-max-lines-1'; + } else if (max2Lines) { + textUseClassName = 'span-max-lines-2'; + } return ( {disableTranslate ? ( @@ -118,7 +126,7 @@ export function Text(props: TextProps) { {textUse} ) : ( - {textUse} + {textUse} )} ); diff --git a/src/ui/pages/Cat20/Cat20TokenScreen.tsx b/src/ui/pages/CAT20/CAT20TokenScreen.tsx similarity index 100% rename from src/ui/pages/Cat20/Cat20TokenScreen.tsx rename to src/ui/pages/CAT20/CAT20TokenScreen.tsx diff --git a/src/ui/pages/Cat20/MergeCAT20HistoryScreen.tsx b/src/ui/pages/CAT20/MergeCAT20HistoryScreen.tsx similarity index 100% rename from src/ui/pages/Cat20/MergeCAT20HistoryScreen.tsx rename to src/ui/pages/CAT20/MergeCAT20HistoryScreen.tsx diff --git a/src/ui/pages/Cat20/MergeCAT20Screen/MergeProgressLayout.tsx b/src/ui/pages/CAT20/MergeCAT20Screen/MergeProgressLayout.tsx similarity index 100% rename from src/ui/pages/Cat20/MergeCAT20Screen/MergeProgressLayout.tsx rename to src/ui/pages/CAT20/MergeCAT20Screen/MergeProgressLayout.tsx diff --git a/src/ui/pages/Cat20/MergeCAT20Screen/MergingItem.tsx b/src/ui/pages/CAT20/MergeCAT20Screen/MergingItem.tsx similarity index 100% rename from src/ui/pages/Cat20/MergeCAT20Screen/MergingItem.tsx rename to src/ui/pages/CAT20/MergeCAT20Screen/MergingItem.tsx diff --git a/src/ui/pages/Cat20/MergeCAT20Screen/NoMergeLayout.tsx b/src/ui/pages/CAT20/MergeCAT20Screen/NoMergeLayout.tsx similarity index 100% rename from src/ui/pages/Cat20/MergeCAT20Screen/NoMergeLayout.tsx rename to src/ui/pages/CAT20/MergeCAT20Screen/NoMergeLayout.tsx diff --git a/src/ui/pages/Cat20/MergeCAT20Screen/index.tsx b/src/ui/pages/CAT20/MergeCAT20Screen/index.tsx similarity index 100% rename from src/ui/pages/Cat20/MergeCAT20Screen/index.tsx rename to src/ui/pages/CAT20/MergeCAT20Screen/index.tsx diff --git a/src/ui/pages/Cat20/SendCat20Screen.tsx b/src/ui/pages/CAT20/SendCAT20Screen.tsx similarity index 100% rename from src/ui/pages/Cat20/SendCat20Screen.tsx rename to src/ui/pages/CAT20/SendCAT20Screen.tsx diff --git a/src/ui/pages/CAT721/CAT721CollectionScreen.tsx b/src/ui/pages/CAT721/CAT721CollectionScreen.tsx new file mode 100644 index 00000000..2b85dbf0 --- /dev/null +++ b/src/ui/pages/CAT721/CAT721CollectionScreen.tsx @@ -0,0 +1,170 @@ +import { useEffect, useState } from 'react'; + +import { AddressCAT721CollectionSummary } from '@/shared/types'; +import { Card, Column, Content, Header, Icon, Layout, Row, Text } from '@/ui/components'; +import { useTools } from '@/ui/components/ActionComponent'; +import CAT721Preview from '@/ui/components/CAT721Preview'; +import { useCurrentAccount } from '@/ui/state/accounts/hooks'; +import { useCurrentKeyring } from '@/ui/state/keyrings/hooks'; +import { colors } from '@/ui/theme/colors'; +import { fontSizes } from '@/ui/theme/font'; +import { copyToClipboard, shortAddress, useLocationState, useWallet } from '@/ui/utils'; +import { CopyOutlined, LoadingOutlined } from '@ant-design/icons'; + +import { useNavigate } from '../MainRoute'; + +interface LocationState { + collectionId: string; +} + +export default function CAT721CollectionScreen() { + const { collectionId } = useLocationState(); + const [collectionSummary, setCollectionSummary] = useState({ + collectionInfo: { + collectionId: '', + name: '', + symbol: '', + description: '', + max: '0', + premine: '0' + }, + localIds: [] + }); + + const wallet = useWallet(); + + const account = useCurrentAccount(); + + const keyring = useCurrentKeyring(); + + const [loading, setLoading] = useState(true); + + useEffect(() => { + wallet.getAddressCAT721CollectionSummary(account.address, collectionId).then((collectionSummary) => { + setCollectionSummary(collectionSummary); + setLoading(false); + }); + }, []); + + const navigate = useNavigate(); + + if (loading) { + return ( + + + + + + + + ); + } + + if (!collectionSummary || !collectionSummary.collectionInfo) { + return ( + +
{ + window.history.go(-1); + }} + /> + + + + + ); + } + + return ( + +
{ + window.history.go(-1); + }} + /> + {collectionSummary && ( + + + + + + + +
+
+
+
+ {collectionSummary.collectionInfo.description ? ( + + ) : null} + {collectionSummary.collectionInfo.description ? ( + + + + ) : null} + + + + {collectionSummary.localIds.length > 0 && ( + + {collectionSummary.localIds.map((localId, index) => ( + { + navigate('CAT721NFTScreen', { + collectionInfo: collectionSummary.collectionInfo, + localId + }); + }} + /> + ))} + + )} + + )} + + ); +} + +export function Section({ + value, + title, + link, + showCopyIcon +}: { + value: string | number; + title: string; + link?: string; + showCopyIcon?: boolean; +}) { + const tools = useTools(); + let displayText = value.toString(); + if (value && typeof value === 'string' && value.length > 20) { + displayText = shortAddress(value, 10); + } + return ( + + + { + if (link) { + window.open(link); + } else { + copyToClipboard(value).then(() => { + tools.toastSuccess('Copied'); + }); + } + }}> + + {showCopyIcon && } + + + ); +} diff --git a/src/ui/pages/CAT721/CAT721NFTScreen.tsx b/src/ui/pages/CAT721/CAT721NFTScreen.tsx new file mode 100644 index 00000000..d512c50c --- /dev/null +++ b/src/ui/pages/CAT721/CAT721NFTScreen.tsx @@ -0,0 +1,57 @@ +import { useLocation } from 'react-router-dom'; + +import { CAT721CollectionInfo } from '@/shared/types'; +import { Button, Card, Column, Content, Header, Layout, Row, Text } from '@/ui/components'; +import CAT721Preview from '@/ui/components/CAT721Preview'; +import { useNavigate } from '@/ui/pages/MainRoute'; + +import { Section } from './CAT721CollectionScreen'; + +export default function CAT721NFTScreen() { + const { state } = useLocation(); + const props = state as { + collectionInfo: CAT721CollectionInfo; + localId: string; + }; + + const collectionInfo = props.collectionInfo; + const localId = props.localId; + + const navigate = useNavigate(); + + return ( + +
{ + window.history.go(-1); + }}> + + + + +
+ + + + + + + +
+
+ + + + + + ); +} diff --git a/src/ui/pages/CAT721/SendCAT721Screen.tsx b/src/ui/pages/CAT721/SendCAT721Screen.tsx new file mode 100644 index 00000000..d207a03a --- /dev/null +++ b/src/ui/pages/CAT721/SendCAT721Screen.tsx @@ -0,0 +1,252 @@ +import { useEffect, useRef, useState } from 'react'; +import { useLocation } from 'react-router-dom'; + +import { CAT721CollectionInfo, Inscription, TxType, UserToSignInput } from '@/shared/types'; +import { Button, Column, Content, Header, Input, Layout, Row, Text } from '@/ui/components'; +import { useTools } from '@/ui/components/ActionComponent'; +import { Loading } from '@/ui/components/ActionComponent/Loading'; +import CAT721Preview from '@/ui/components/CAT721Preview'; +import { FeeRateBar } from '@/ui/components/FeeRateBar'; +import { MergeBTCPopover } from '@/ui/components/MergeBTCPopover'; +import { useNavigate } from '@/ui/pages/MainRoute'; +import { useCurrentAccount } from '@/ui/state/accounts/hooks'; +import { useNetworkType } from '@/ui/state/settings/hooks'; +import { useRunesTx } from '@/ui/state/transactions/hooks'; +import { isValidAddress, useWallet } from '@/ui/utils'; +import { AddressType } from '@unisat/wallet-sdk'; +import { getAddressType } from '@unisat/wallet-sdk/lib/address'; + +import { SignPsbt } from '../Approval/components'; + +export default function SendCAT721Screen() { + const { state } = useLocation(); + const props = state as { + collectionInfo: CAT721CollectionInfo; + localId: string; + }; + + const collectionInfo = props.collectionInfo; + const localId = props.localId; + + const wallet = useWallet(); + + const navigate = useNavigate(); + const runesTx = useRunesTx(); + const [inputAmount, setInputAmount] = useState(''); + const [disabled, setDisabled] = useState(false); + const [toInfo, setToInfo] = useState<{ + address: string; + domain: string; + inscription?: Inscription; + }>({ + address: runesTx.toAddress, + domain: runesTx.toDomain, + inscription: undefined + }); + + const [error, setError] = useState(''); + + const account = useCurrentAccount(); + + const networkType = useNetworkType(); + + const [showMergeBTCUTXOPopover, setShowMergeBTCUTXOPopover] = useState(false); + const tools = useTools(); + + const [feeRate, setFeeRate] = useState(5); + + useEffect(() => { + setError(''); + setDisabled(true); + + if (!isValidAddress(toInfo.address)) { + return; + } + + const addressType = getAddressType(toInfo.address, networkType); + if (addressType !== AddressType.P2TR && addressType !== AddressType.P2WPKH) { + setError('The recipient must be P2TR or P2WPKH address type'); + return; + } + + setDisabled(false); + }, [toInfo, inputAmount]); + + const transferData = useRef<{ + id: string; + commitTx: string; + commitToSignInputs: UserToSignInput[]; + revealTx: string; + revealToSignInputs: UserToSignInput[]; + }>({ + id: '', + commitTx: '', + commitToSignInputs: [], + revealTx: '', + revealToSignInputs: [] + }); + const [step, setStep] = useState(0); + const onConfirm = async () => { + tools.showLoading(true); + try { + const step1 = await wallet.transferCAT721Step1(toInfo.address, collectionInfo.collectionId, localId, feeRate); + if (step1) { + transferData.current.id = step1.id; + transferData.current.commitTx = step1.commitTx; + transferData.current.commitToSignInputs = step1.toSignInputs; + setStep(1); + } + } catch (e) { + const msg = (e as any).message; + if (msg.includes('-307')) { + setShowMergeBTCUTXOPopover(true); + return; + } + setError((e as any).message); + } finally { + tools.showLoading(false); + } + }; + + if (step == 1) { + return ( + { + setStep(0); + }} + /> + params={{ + data: { + psbtHex: transferData.current.commitTx, + type: TxType.SIGN_TX, + options: { autoFinalized: false, toSignInputs: transferData.current.commitToSignInputs } + } + }} + handleCancel={() => { + setStep(0); + }} + handleConfirm={async () => { + try { + tools.showLoading(true); + const step2 = await wallet.transferCAT721Step2( + transferData.current.id, + transferData.current.commitTx, + transferData.current.commitToSignInputs + ); + + transferData.current.revealTx = step2.revealTx; + transferData.current.revealToSignInputs = step2.toSignInputs; + + setStep(1.5); + setTimeout(() => { + setStep(2); + }, 100); + } catch (e) { + console.log(e); + } finally { + tools.showLoading(false); + } + }} + /> + ); + } else if (step == 1.5) { + return ; + } else if (step == 2) { + return ( + { + setStep(0); + }} + /> + params={{ + data: { + psbtHex: transferData.current.revealTx, + type: TxType.SIGN_TX, + options: { autoFinalized: false, toSignInputs: transferData.current.revealToSignInputs } + } + }} + handleCancel={() => { + setStep(0); + }} + handleConfirm={async () => { + tools.showLoading(true); + try { + const step3 = await wallet.transferCAT721Step3( + transferData.current.id, + transferData.current.revealTx, + transferData.current.revealToSignInputs + ); + navigate('TxSuccessScreen', { txid: step3.txid }); + } catch (e) { + // tools.toastError((e as any).message); + navigate('TxFailScreen', { error: (e as any).message }); + } finally { + tools.showLoading(false); + } + }} + /> + ); + } + + return ( + +
{ + window.history.go(-1); + }} + title={`Send CAT721`} + /> + + + + + + + + + + { + setToInfo(val); + }} + autoFocus={true} + /> + + + + + + { + setFeeRate(val); + }} + /> + + + {error && } + + + + {showMergeBTCUTXOPopover && ( + { + setShowMergeBTCUTXOPopover(false); + }} + /> + )} + + + ); +} diff --git a/src/ui/pages/Main/WalletTabScreen/Cat20List.tsx b/src/ui/pages/Main/WalletTabScreen/CAT20List.tsx similarity index 98% rename from src/ui/pages/Main/WalletTabScreen/Cat20List.tsx rename to src/ui/pages/Main/WalletTabScreen/CAT20List.tsx index 75fab3f2..fdf4e591 100644 --- a/src/ui/pages/Main/WalletTabScreen/Cat20List.tsx +++ b/src/ui/pages/Main/WalletTabScreen/CAT20List.tsx @@ -32,7 +32,7 @@ export function CAT20List() { const [priceMap, setPriceMap] = useState<{ [key: string]: TickPriceItem }>(); useEffect(() => { const fetchData = async () => { - if (supportedAssets.assets.CAT20 == false) { + if (!supportedAssets.assets.CAT20) { setTokens([]); setTotal(0); return; diff --git a/src/ui/pages/Main/WalletTabScreen/CAT721List.tsx b/src/ui/pages/Main/WalletTabScreen/CAT721List.tsx new file mode 100644 index 00000000..92647cb4 --- /dev/null +++ b/src/ui/pages/Main/WalletTabScreen/CAT721List.tsx @@ -0,0 +1,101 @@ +import { useEffect, useState } from 'react'; + +import { CAT721Balance } from '@/shared/types'; +import { Column, Row } from '@/ui/components'; +import { useTools } from '@/ui/components/ActionComponent'; +import { CAT721CollectionCard } from '@/ui/components/CAT721CollectionCard'; +import { Empty } from '@/ui/components/Empty'; +import { Pagination } from '@/ui/components/Pagination'; +import { useCurrentAccount } from '@/ui/state/accounts/hooks'; +import { useChain, useChainType } from '@/ui/state/settings/hooks'; +import { useSupportedAssets } from '@/ui/state/ui/hooks'; +import { useWallet } from '@/ui/utils'; +import { LoadingOutlined } from '@ant-design/icons'; + +import { useNavigate } from '../../MainRoute'; + +export function CAT721List() { + const navigate = useNavigate(); + const wallet = useWallet(); + const currentAccount = useCurrentAccount(); + const chainType = useChainType(); + const chain = useChain(); + + const [collections, setCollections] = useState([]); + const [total, setTotal] = useState(-1); + const [pagination, setPagination] = useState({ currentPage: 1, pageSize: 100 }); + + const tools = useTools(); + + const supportedAssets = useSupportedAssets(); + + useEffect(() => { + const fetchData = async () => { + if (!supportedAssets.assets.CAT20) { + setCollections([]); + setTotal(0); + return; + } + try { + const { list, total } = await wallet.getCAT721List( + currentAccount.address, + pagination.currentPage, + pagination.pageSize + ); + setCollections(list); + setTotal(total); + } catch (e) { + setCollections([]); + tools.toastError((e as Error).message); + } finally { + // tools.showLoading(false); + } + }; + + fetchData(); + }, [pagination, currentAccount.address, chainType, supportedAssets.key]); + + if (total === -1) { + return ( + + + + ); + } + + if (total === 0) { + return ( + + + + ); + } + + return ( + + + {collections.map((data, index) => ( + { + navigate('CAT721CollectionScreen', { + collectionId: data.collectionId + }); + }} + /> + ))} + + + + { + setPagination(pagination); + }} + /> + + + ); +} diff --git a/src/ui/pages/Main/WalletTabScreen/CATTab.tsx b/src/ui/pages/Main/WalletTabScreen/CATTab.tsx new file mode 100644 index 00000000..49be3a8d --- /dev/null +++ b/src/ui/pages/Main/WalletTabScreen/CATTab.tsx @@ -0,0 +1,51 @@ +import { useMemo } from 'react'; + +import { Column, Row } from '@/ui/components'; +import { TabBar } from '@/ui/components/TabBar'; +import { useAppDispatch } from '@/ui/state/hooks'; +import { useCATAssetTabKey } from '@/ui/state/ui/hooks'; +import { CATAssetTabKey, uiActions } from '@/ui/state/ui/reducer'; + +import { CAT20List } from './CAT20List'; +import { CAT721List } from './CAT721List'; + +export function CATTab() { + const tabKey = useCATAssetTabKey(); + + const dispatch = useAppDispatch(); + + const tabItems = useMemo(() => { + const items = [ + { + key: CATAssetTabKey.CAT20, + label: `CAT20`, + children: + }, + { + key: CATAssetTabKey.CAT721, + label: `CAT721`, + children: + } + ]; + + return items; + }, []); + + return ( + + + { + dispatch(uiActions.updateAssetTabScreen({ catAssetTabKey: key })); + }} + /> + + + {tabItems[tabKey] ? tabItems[tabKey].children : null} + + ); +} diff --git a/src/ui/pages/Main/WalletTabScreen/index.tsx b/src/ui/pages/Main/WalletTabScreen/index.tsx index 2c1932cb..b765edf4 100644 --- a/src/ui/pages/Main/WalletTabScreen/index.tsx +++ b/src/ui/pages/Main/WalletTabScreen/index.tsx @@ -1,4 +1,4 @@ -import { Tabs, Tooltip } from 'antd'; +import { Tooltip } from 'antd'; import { CSSProperties, useEffect, useMemo, useRef, useState } from 'react'; import { AddressFlagType, ChainType } from '@/shared/constant'; @@ -12,6 +12,7 @@ import { FeeRateIcon } from '@/ui/components/FeeRateIcon'; import { NavTabBar } from '@/ui/components/NavTabBar'; import { NoticePopover } from '@/ui/components/NoticePopover'; import { SwitchNetworkBar } from '@/ui/components/SwitchNetworkBar'; +import { Tabs } from '@/ui/components/Tabs'; import { UpgradePopover } from '@/ui/components/UpgradePopover'; import { getCurrentTab } from '@/ui/features/browser/tabs'; import { BtcDisplay } from '@/ui/pages/Main/WalletTabScreen/components/BtcDisplay'; @@ -39,7 +40,7 @@ import { BuyBTCModal } from '../../BuyBTC/BuyBTCModal'; import { useNavigate } from '../../MainRoute'; import { SwitchChainModal } from '../../Settings/SwitchChainModal'; import { AtomicalsTab } from './AtomicalsTab'; -import { CAT20List } from './CAT20List'; +import { CATTab } from './CATTab'; import { OrdinalsTab } from './OrdinalsTab'; import { RunesList } from './RunesList'; @@ -160,9 +161,9 @@ export default function WalletTabScreen() { } if (supportedAssets.assets.CAT20) { items.push({ - key: AssetTabKey.CAT20, - label: 'CAT20', - children: + key: AssetTabKey.CAT, + label: 'CAT', + children: }); } return items; @@ -278,6 +279,7 @@ export default function WalletTabScreen() { + + {/* + + { + + + + } + */}