diff --git a/CHANGELOG.md b/CHANGELOG.md index 540d15156..3e0d64f64 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,17 @@ All notable changes to this project will be documented in this file. *Released* +## [v0.2.3] - 2021-06-17 + +### Additions +- Implemented search for the wallet list view +- Added support for croeseid testnet version 3 +- Fix governance votes tally numbers +- Add a new NFT UI tab and NFT section on the home screen +- Load, persist in the DB, and show to the UI all current accounts NFTs +- Add support to send and receive NFTs: Sign and broadcast NFT transactions +- Add capability to load the previous account NFT related transaction history: Send, Receive, Mint, Issue, etc, ... + ## [v0.2.2] - 2021-05-24 ### Additions diff --git a/electron/IpcMain.ts b/electron/IpcMain.ts index f5b9c2c8f..a67f4c05e 100644 --- a/electron/IpcMain.ts +++ b/electron/IpcMain.ts @@ -1,4 +1,5 @@ -import { Bytes } from '@crypto-com/chain-jslib/lib/dist/utils/bytes/bytes'; +import {Bytes} from "@crypto-org-chain/chain-jslib/lib/dist/utils/bytes/bytes"; + const { ipcMain } = require('electron'); import { LedgerSignerNative } from './LedgerSignerNative'; export class IpcMain { diff --git a/package.json b/package.json index 89068070b..f5bb85b89 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "chain-desktop-wallet", - "version": "0.2.2", + "version": "0.2.3", "description": "Crypto.com Chain Desktop Wallet App", "repository": "github:crypto-com/chain-desktop-wallet", "author": "Crypto.org ", @@ -13,7 +13,7 @@ "@babel/helper-builder-react-jsx": "^7.10.4", "@babel/helper-builder-react-jsx-experimental": "^7.12.11", "@cosmjs/stargate": "^0.24.0-alpha.22", - "@crypto-com/chain-jslib": "0.0.15", + "@crypto-org-chain/chain-jslib": "0.0.22", "@ledgerhq/hw-transport-node-hid": "^5.41.0", "@ledgerhq/hw-transport-webhid": "^5.38.0", "@ledgerhq/hw-transport-webusb": "^5.38.0", @@ -49,6 +49,7 @@ "react": "^17.0.1", "react-copy-to-clipboard": "^5.0.2", "react-dom": "^17.0.1", + "react-player": "^2.9.0", "react-refresh": "^0.8.3", "react-router-dom": "^5.2.0", "react-script": "^2.0.5", @@ -57,7 +58,6 @@ "scrypt-js": "^3.0.1", "stylelint-config-css-modules": "^2.2.0", "stylelint-config-prettier": "^8.0.2", - "stylelint-config-standard": "^21.0.0", "stylelint-declaration-block-no-ignored-properties": "^2.3.0", "stylelint-order": "^4.1.0", "ts-pnp": "1.2.0", @@ -168,6 +168,7 @@ "@typescript-eslint/eslint-plugin": "4.5.0", "@typescript-eslint/parser": "4.5.0", "@umijs/fabric": "2.3.1", + "autoprefixer": "10.2.5", "babel-eslint": "10.1.0", "babel-jest": "26.6.0", "babel-loader": "8.1.0", @@ -197,7 +198,6 @@ "eslint-webpack-plugin": "^2.1.0", "file-loader": "6.1.1", "fs-extra": "9.0.1", - "autoprefixer": "10.2.5", "html-webpack-plugin": "4.5.0", "husky": "4.3.0", "identity-obj-proxy": "3.0.0", @@ -228,7 +228,8 @@ "sass-loader": "8.0.2", "semver": "7.3.2", "style-loader": "1.3.0", - "stylelint": "13.7.2", + "stylelint": "13.13.1", + "stylelint-config-standard": "^22.0.0", "terser-webpack-plugin": "4.2.3", "wait-on": "5.2.1", "webpack": "4.44.2", @@ -300,6 +301,10 @@ "ssri": "8.0.1", "hosted-git-info": "3.0.8", "underscore": "1.12.1", - "trim": "0.0.3" + "trim": "0.0.3", + "dns-packet": "5.2.2", + "css-what": "5.0.1", + "trim-newlines": "3.0.1", + "normalize-url": "5.3.1" } } diff --git a/src/assets/nft-thumbnail.png b/src/assets/nft-thumbnail.png new file mode 100644 index 000000000..fdd851515 Binary files /dev/null and b/src/assets/nft-thumbnail.png differ diff --git a/src/config/StaticConfig.ts b/src/config/StaticConfig.ts index 6fa63c5b2..71459db62 100644 --- a/src/config/StaticConfig.ts +++ b/src/config/StaticConfig.ts @@ -1,8 +1,9 @@ -import { CroNetwork } from '@crypto-com/chain-jslib/lib/dist/core/cro'; +import { CroNetwork } from '@crypto-org-chain/chain-jslib/lib/dist/core/cro'; import { getRandomId } from '../crypto/RandomGen'; export const APP_DB_NAMESPACE = 'data-store'; export const MARKET_API_BASE_URL = 'https://crypto.org/api'; +export const NV_GRAPHQL_API_ENDPOINT = 'https://crypto.com/nft-api/graphql'; export const DEFAULT_CLIENT_MEMO = 'client:chain-desktop-app'; export const NodePorts = { @@ -51,6 +52,23 @@ const TestNetConfig: WalletConfig = { }, }; +const TestNetCroeseid3: WalletConfig = { + enabled: true, + name: 'TESTNET CROESEID 3', + derivationPath: "m/44'/1'/0'/0/0", + explorerUrl: 'https://crypto.org/explorer/croeseid3', + indexingUrl: 'https://crypto.org/explorer/croeseid3/api/v1/', + nodeUrl: CroNetwork.TestnetCroeseid3.defaultNodeUrl, + network: CroNetwork.TestnetCroeseid3, + disableDefaultClientMemo: false, + enableGeneralSettings: false, + analyticsDisabled: false, + fee: { + gasLimit: FIXED_DEFAULT_GAS_LIMIT, + networkFee: FIXED_DEFAULT_FEE, + }, +}; + // This constant value is used when actual values are not known yet // For instance : export const NOT_KNOWN_YET_VALUE = 'TO_BE_DECIDED'; @@ -104,6 +122,7 @@ export const DefaultWalletConfigs = { TestNetConfig, MainNetConfig, CustomDevNet, + TestNetCroeseid3, }; // Every created wallet get initialized with a new CRO asset @@ -134,4 +153,5 @@ export type Network = { validatorPubKeyPrefix: string; validatorAddressPrefix: string; coin: { baseDenom: string; croDenom: string }; + rpcUrl?: string; }; diff --git a/src/crypto/Cryptographer.ts b/src/crypto/Cryptographer.ts index 4f94808e9..356b4293b 100644 --- a/src/crypto/Cryptographer.ts +++ b/src/crypto/Cryptographer.ts @@ -1,5 +1,5 @@ import scrypt from 'scrypt-js'; -import { utils } from '@crypto-com/chain-jslib'; +import { utils } from '@crypto-org-chain/chain-jslib'; import { AES, enc, lib, mode, pad } from 'crypto-js'; import { EncryptionResult, HashResult, InitialVector } from '../models/SecretStorage'; diff --git a/src/layouts/home/home.tsx b/src/layouts/home/home.tsx index b00d93a6f..3afe48ffd 100644 --- a/src/layouts/home/home.tsx +++ b/src/layouts/home/home.tsx @@ -21,6 +21,7 @@ import { marketState, validatorListState, fetchingDBState, + nftListState, } from '../../recoil/atom'; import { trimString } from '../../utils/utils'; import WalletIcon from '../../assets/icon-wallet-grey.svg'; @@ -28,8 +29,10 @@ import IconHome from '../../svg/IconHome'; import IconSend from '../../svg/IconSend'; import IconReceive from '../../svg/IconReceive'; import IconStaking from '../../svg/IconStaking'; +import IconNft from '../../svg/IconNft'; import IconWallet from '../../svg/IconWallet'; import ModalPopup from '../../components/ModalPopup/ModalPopup'; +import SuccessModalPopup from '../../components/SuccessModalPopup/SuccessModalPopup'; import { walletService } from '../../service/WalletService'; import { Session } from '../../models/Session'; import packageJson from '../../../package.json'; @@ -46,38 +49,51 @@ const { Sider } = Layout; function HomeLayout(props: HomeLayoutProps) { const history = useHistory(); const [confirmDeleteForm] = Form.useForm(); + const [deleteWalletAddress, setDeleteWalletAddress] = useState(''); const [hasWallet, setHasWallet] = useState(true); // Default as true. useEffect will only re-render if result of hasWalletBeenCreated === false const [session, setSession] = useRecoilState(sessionState); const [userAsset, setUserAsset] = useRecoilState(walletAssetState); const [walletList, setWalletList] = useRecoilState(walletListState); const [marketData, setMarketData] = useRecoilState(marketState); const [validatorList, setValidatorList] = useRecoilState(validatorListState); + const [nftList, setNftList] = useRecoilState(nftListState); const [fetchingDB, setFetchingDB] = useRecoilState(fetchingDBState); const [isButtonDisabled, setIsButtonDisabled] = useState(true); const [isConfirmDeleteVisible, setIsConfirmDeleteVisible] = useState(false); const [isConfirmationModalVisible, setIsConfirmationModalVisible] = useState(false); + const [isSuccessDeleteModalVisible, setIsSuccessDeleteModalVisible] = useState(false); + const [isAnnouncementVisible, setIsAnnouncementVisible] = useState(false); const [isButtonLoading, setIsButtonLoading] = useState(false); const didMountRef = useRef(false); - async function fetchAndSetNewValidators() { + async function fetchAndSetNewValidators(currentSession) { try { - await walletService.fetchAndSaveValidators(session); + await walletService.fetchAndSaveValidators(currentSession); } catch (e) { // eslint-disable-next-line no-console console.log('Failed loading new wallet validators list', e); } } - async function fetchAndSetNewProposals() { + async function fetchAndSetNewProposals(currentSession) { try { - await walletService.fetchAndSaveProposals(session); + await walletService.fetchAndSaveProposals(currentSession); } catch (e) { // eslint-disable-next-line no-console console.log('Failed loading new wallet proposals', e); } } + async function fetchAndSetNFTs(currentSession) { + try { + await walletService.fetchAndSaveNFTs(currentSession); + } catch (e) { + // eslint-disable-next-line no-console + console.log('Failed loading new wallet NFTs', e); + } + } + const onWalletDeleteFinish = async () => { setIsButtonLoading(true); setFetchingDB(true); @@ -99,6 +115,7 @@ function HomeLayout(props: HomeLayoutProps) { setIsButtonLoading(false); setIsConfirmationModalVisible(false); + setIsSuccessDeleteModalVisible(true); setFetchingDB(false); setIsButtonDisabled(true); setIsConfirmDeleteVisible(false); @@ -106,11 +123,13 @@ function HomeLayout(props: HomeLayoutProps) { }; const handleCancel = () => { - setIsConfirmationModalVisible(false); - setIsConfirmDeleteVisible(false); - setIsButtonDisabled(true); - setIsConfirmDeleteVisible(false); - confirmDeleteForm.resetFields(); + if (!isButtonLoading) { + setIsConfirmationModalVisible(false); + setIsConfirmDeleteVisible(false); + setIsButtonDisabled(true); + setIsConfirmDeleteVisible(false); + confirmDeleteForm.resetFields(); + } }; const showPasswordModal = () => { @@ -121,27 +140,35 @@ function HomeLayout(props: HomeLayoutProps) { const fetchDB = async () => { setFetchingDB(true); const hasWalletBeenCreated = await walletService.hasWalletBeenCreated(); - const sessionData = await walletService.retrieveCurrentSession(); - const currentAsset = await walletService.retrieveDefaultWalletAsset(sessionData); + const currentSession = await walletService.retrieveCurrentSession(); + const currentAsset = await walletService.retrieveDefaultWalletAsset(currentSession); const allWalletsData = await walletService.retrieveAllWallets(); const currentMarketData = await walletService.retrieveAssetPrice( currentAsset.mainnetSymbol, 'usd', ); - const currentValidatorList = await walletService.retrieveTopValidators( - sessionData.wallet.config.network.chainId, - ); const announcementShown = await generalConfigService.checkIfHasShownAnalyticsPopup(); - setHasWallet(hasWalletBeenCreated); - setSession(sessionData); + setSession(currentSession); setUserAsset(currentAsset); setWalletList(allWalletsData); setMarketData(currentMarketData); + + await Promise.all([ + await fetchAndSetNewValidators(currentSession), + await fetchAndSetNewProposals(currentSession), + await fetchAndSetNFTs(currentSession), + ]); + + const currentValidatorList = await walletService.retrieveTopValidators( + currentSession.wallet.config.network.chainId, + ); + const currentNftList = await walletService.retrieveNFTs(currentSession.wallet.identifier); + setValidatorList(currentValidatorList); - await fetchAndSetNewValidators(); - await fetchAndSetNewProposals(); + setNftList(currentNftList); + setFetchingDB(false); // Timeout for loading @@ -169,11 +196,22 @@ function HomeLayout(props: HomeLayoutProps) { setMarketData, validatorList, setValidatorList, + nftList, + setNftList, ]); const HomeMenu = () => { const locationPath = useLocation().pathname; - const paths = ['/home', '/staking', '/send', '/receive', '/settings', '/governance', '/wallet']; + const paths = [ + '/home', + '/staking', + '/send', + '/receive', + '/settings', + '/governance', + '/nft', + '/wallet', + ]; let menuSelectedKey = locationPath; if (!paths.includes(menuSelectedKey)) { @@ -197,6 +235,9 @@ function HomeLayout(props: HomeLayoutProps) { }> Governance + }> + My NFT + }> Settings @@ -229,7 +270,10 @@ function HomeLayout(props: HomeLayoutProps) { <> setIsConfirmationModalVisible(true)} + onClick={() => { + setDeleteWalletAddress(session.wallet.address); + setIsConfirmationModalVisible(true); + }} > Delete Wallet @@ -317,7 +361,8 @@ function HomeLayout(props: HomeLayoutProps) {
Please review the below information.
Delete Wallet Address
-
{`${session.wallet.address}`}
+ {/*
{`${session.wallet.address}`}
*/} +
{`${deleteWalletAddress}`}
{!isConfirmDeleteVisible ? ( <> @@ -361,7 +406,7 @@ function HomeLayout(props: HomeLayoutProps) { required: true, }, { - pattern: /DELETE/, + pattern: /^DELETE$/, message: 'Please enter DELETE', }, ]} @@ -373,6 +418,38 @@ function HomeLayout(props: HomeLayoutProps) { )} + { + setIsSuccessDeleteModalVisible(false); + }} + handleOk={() => { + setIsSuccessDeleteModalVisible(false); + }} + title="Success!" + button={null} + footer={[ + , + ]} + > + <> +
+ Wallet Address +
+ {deleteWalletAddress} +
+ has been deleted. +
+ +
{ diff --git a/src/models/Transaction.ts b/src/models/Transaction.ts index f2f8d6c5c..cc7fda590 100644 --- a/src/models/Transaction.ts +++ b/src/models/Transaction.ts @@ -1,4 +1,9 @@ import { Proposal, ValidatorPubKey } from '../service/rpc/NodeRpcModels'; +import { + NftAccountTransactionResponse, + NftResponse, + NftTransactionResponse, +} from '../service/rpc/ChainIndexingModels'; export enum TransactionStatus { SUCCESS = 'SUCCESS', @@ -32,6 +37,27 @@ export interface StakingTransactionData extends TransactionData { stakedAmount: string; } +export interface NftTransactionData { + transactionHash: string; + data: { + denomId: string; + tokenId: string; + }; + receiverAddress: string; + blockTime: string; + status: boolean; +} + +export enum NftTransactionType { + ISSUE_DENOM = 'MsgIssueDenom', + MINT_NFT = 'MsgMintNFT', + EDIT_NFT = 'MsgEditNFT', + BURN_NFT = 'MsgBurnNFT', + TRANSFER_NFT = 'MsgTransferNFT', +} + +export interface NftAccountTransactionData extends NftAccountTransactionResponse {} + export interface StakingTransactionList { transactions: Array; totalBalance: string; @@ -48,6 +74,22 @@ export interface TransferTransactionList { walletId: string; } +export interface NftList { + nfts: Array; + walletId: string; +} + +export interface NftQueryParams { + tokenId: string; + denomId: string; +} + +export interface NftTransactionHistory { + transfers: Array; + walletId: string; + nftQuery: NftQueryParams; +} + export interface ValidatorList { validators: Array; chainId: string; @@ -86,6 +128,31 @@ export interface ValidatorModel { export interface ProposalModel extends Proposal {} +export interface NftTokenData { + drop?: string; + description?: string; + image?: string; + mimeType?: string; + animation_url?: string; +} + +export interface NftModel extends NftResponse { + isMintedByCDC: boolean; + marketplaceLink: string; +} + +export interface NftProcessedModel extends Omit { + tokenData: NftTokenData; +} + +export interface NftTransferModel extends NftTransactionResponse {} +// export interface NFTAccountTransactionModel extends NFTAccountTransactionResponse {} + +export interface NftAccountTransactionList { + transactions: Array; + walletId: string; +} + export const ProposalStatuses = { PROPOSAL_STATUS_UNSPECIFIED: 'PROPOSAL_STATUS_UNSPECIFIED', PROPOSAL_STATUS_DEPOSIT_PERIOD: 'PROPOSAL_STATUS_DEPOSIT_PERIOD', diff --git a/src/pages/governance/governance.tsx b/src/pages/governance/governance.tsx index 2d5a93aa1..6ae9182e9 100644 --- a/src/pages/governance/governance.tsx +++ b/src/pages/governance/governance.tsx @@ -487,8 +487,9 @@ const GovernancePage = () => { { { @@ -118,6 +156,46 @@ function convertTransfers( }); } +function convertNftTransfers(allTransfers: NftAccountTransactionData[]) { + function getStatus(transfer: NftAccountTransactionData) { + if (transfer.success) { + return TransactionStatus.SUCCESS; + } + return TransactionStatus.FAILED; + } + function getType(transfer: NftAccountTransactionData) { + if (transfer.messageType === NftTransactionType.ISSUE_DENOM) { + return NftTransactionType.ISSUE_DENOM; + // eslint-disable-next-line no-else-return + } else if (transfer.messageType === NftTransactionType.MINT_NFT) { + return NftTransactionType.MINT_NFT; + } else if (transfer.messageType === NftTransactionType.EDIT_NFT) { + return NftTransactionType.EDIT_NFT; + } else if (transfer.messageType === NftTransactionType.BURN_NFT) { + return NftTransactionType.BURN_NFT; + } + return NftTransactionType.TRANSFER_NFT; + } + + return allTransfers.map(transfer => { + const data: NftTransferTabularData = { + key: + transfer.transactionHash + + transfer.data.recipient + + transfer.data.denomId + + transfer.data.tokenId, + transactionHash: transfer.transactionHash, + messageType: getType(transfer), + denomId: transfer.data.denomId, + tokenId: transfer.data.tokenId, + recipientAddress: transfer.data.recipient, + time: new Date(transfer.blockTime).toLocaleString(), + status: getStatus(transfer), + }; + return data; + }); +} + const isWalletNotLive = (config: WalletConfig) => { return config.nodeUrl === NOT_KNOWN_YET_VALUE && config.indexingUrl === NOT_KNOWN_YET_VALUE; }; @@ -126,12 +204,16 @@ function HomePage() { const currentSession = useRecoilValue(sessionState); const [delegations, setDelegations] = useState([]); const [transfers, setTransfers] = useState([]); + const [nftTransfers, setNftTransfers] = useState([]); const [userAsset, setUserAsset] = useRecoilState(walletAssetState); + const setNFTList = useSetRecoilState(nftListState); const marketData = useRecoilValue(marketState); const [ledgerIsExpertMode, setLedgerIsExpertMode] = useRecoilState(ledgerIsExpertModeState); const [fetchingDB, setFetchingDB] = useRecoilState(fetchingDBState); const didMountRef = useRef(false); + const [processedNftList, setProcessedNftList] = useState([]); + // Undelegate action related states changes const [form] = Form.useForm(); @@ -167,74 +249,6 @@ function HomePage() { const analyticsService = new AnalyticsService(currentSession); - const showWalletStateNotification = (config: WalletConfig) => { - setTimeout(async () => { - if (isWalletNotLive(config) && !hasShownNotLiveWallet) { - notification.warning({ - message: `Wallet Info`, - description: `The wallet created will be limited only to display address because its ${config.name} configuration is not live yet`, - placement: 'topRight', - duration: 0, - }); - } - }, 200); - }; - - const onSyncBtnCall = async () => { - setFetchingDB(true); - - await walletService.syncAll(); - const sessionData = await walletService.retrieveCurrentSession(); - const currentAsset = await walletService.retrieveDefaultWalletAsset(sessionData); - const allDelegations: StakingTransactionData[] = await walletService.retrieveAllDelegations( - sessionData.wallet.identifier, - ); - const allTransfers: TransferTransactionData[] = await walletService.retrieveAllTransfers( - sessionData.wallet.identifier, - ); - - const stakingTabularData = convertDelegations(allDelegations, currentAsset); - const transferTabularData = convertTransfers(allTransfers, currentAsset, sessionData); - - showWalletStateNotification(currentSession.wallet.config); - - setDelegations(stakingTabularData); - setTransfers(transferTabularData); - setUserAsset(currentAsset); - setHasShownNotLiveWallet(true); - - setFetchingDB(false); - }; - - useEffect(() => { - const syncAssetData = async () => { - const sessionData = await walletService.retrieveCurrentSession(); - const currentAsset = await walletService.retrieveDefaultWalletAsset(sessionData); - const allDelegations: StakingTransactionData[] = await walletService.retrieveAllDelegations( - sessionData.wallet.identifier, - ); - const allTransfers: TransferTransactionData[] = await walletService.retrieveAllTransfers( - sessionData.wallet.identifier, - ); - - const stakingTabularData = convertDelegations(allDelegations, currentAsset); - const transferTabularData = convertTransfers(allTransfers, currentAsset, sessionData); - - showWalletStateNotification(currentSession.wallet.config); - setDelegations(stakingTabularData); - setTransfers(transferTabularData); - setUserAsset(currentAsset); - setHasShownNotLiveWallet(true); - }; - - syncAssetData(); - - if (!didMountRef.current) { - didMountRef.current = true; - analyticsService.logPage('Home'); - } - }, [fetchingDB]); - const TransactionColumns = [ { title: 'Transaction Hash', @@ -271,7 +285,16 @@ function HomePage() { title: 'Recipient', dataIndex: 'recipientAddress', key: 'recipientAddress', - render: text =>
{middleEllipsis(text, 12)}
, + render: text => ( + + {middleEllipsis(text, 12)} + + ), }, { title: 'Time', @@ -303,6 +326,251 @@ function HomePage() { }, ]; + const NftTransactionColumns = [ + { + title: 'Transaction Hash', + dataIndex: 'transactionHash', + key: 'transactionHash', + render: text => ( + + {middleEllipsis(text, 6)} + + ), + }, + { + title: 'Type', + dataIndex: 'messageType', + key: 'messageType', + render: (text, record: NftTransferTabularData) => { + let statusColor; + if (!record.status) { + statusColor = 'error'; + } else if (record.messageType === NftTransactionType.MINT_NFT) { + statusColor = 'success'; + } else if (record.messageType === NftTransactionType.TRANSFER_NFT) { + statusColor = + record.recipientAddress === currentSession.wallet.address ? 'processing' : 'error'; + } else { + statusColor = 'default'; + } + + if (record.status) { + if (record.messageType === NftTransactionType.MINT_NFT) { + return ( + + Minted NFT + + ); + // eslint-disable-next-line no-else-return + } else if (record.messageType === NftTransactionType.TRANSFER_NFT) { + return ( + + {record.recipientAddress === currentSession.wallet.address + ? 'Received NFT' + : 'Sent NFT'} + + ); + } else if (record.messageType === NftTransactionType.ISSUE_DENOM) { + return ( + + Issued Denom + + ); + } + return ( + + {record.messageType} + + ); + // eslint-disable-next-line no-else-return + } else { + if (record.messageType === NftTransactionType.MINT_NFT) { + return ( + + Failed Mint + + ); + // eslint-disable-next-line no-else-return + } else if (record.messageType === NftTransactionType.TRANSFER_NFT) { + return ( + + Failed Transfer + + ); + } else if (record.messageType === NftTransactionType.ISSUE_DENOM) { + return ( + + Failed Issue + + ); + } + return ( + + Failed {record.messageType} + + ); + } + }, + }, + { + title: 'NFT Name', + dataIndex: 'denomId', + key: 'denomId', + render: text =>
{text ? ellipsis(text, 12) : 'n.a.'}
, + }, + { + title: 'NFT ID', + dataIndex: 'tokenId', + key: 'tokenId', + render: text =>
{text ? ellipsis(text, 12) : 'n.a.'}
, + }, + { + title: 'Recipient', + dataIndex: 'recipientAddress', + key: 'recipientAddress', + render: text => { + return text ? ( + + {middleEllipsis(text, 12)} + + ) : ( +
n.a.
+ ); + }, + }, + { + title: 'Time', + dataIndex: 'time', + key: 'time', + }, + ]; + + const renderPreview = (_nft: NftProcessedModel) => { + return ( + {_nft?.denomName} { + (e.target as HTMLImageElement).src = nftThumbnail; + }} + /> + ); + }; + + const showWalletStateNotification = (config: WalletConfig) => { + setTimeout(async () => { + if (isWalletNotLive(config) && !hasShownNotLiveWallet) { + notification.warning({ + message: `Wallet Info`, + description: `The wallet created will be limited only to display address because its ${config.name} configuration is not live yet`, + placement: 'topRight', + duration: 0, + }); + } + }, 200); + }; + + const onSyncAndRefreshBtnCall = async () => { + setFetchingDB(true); + + await walletService.syncAll(); + const sessionData = await walletService.retrieveCurrentSession(); + const currentAsset = await walletService.retrieveDefaultWalletAsset(sessionData); + const allDelegations: StakingTransactionData[] = await walletService.retrieveAllDelegations( + sessionData.wallet.identifier, + ); + const allTransfers: TransferTransactionData[] = await walletService.retrieveAllTransfers( + sessionData.wallet.identifier, + ); + + const allNftTransfer: NftAccountTransactionData[] = await walletService.getAllNFTAccountTxs( + sessionData, + ); + + const stakingTabularData = convertDelegations(allDelegations, currentAsset); + const transferTabularData = convertTransfers(allTransfers, currentAsset, sessionData); + const nftTransferTabularData = convertNftTransfers(allNftTransfer); + + showWalletStateNotification(currentSession.wallet.config); + + setDelegations(stakingTabularData); + setTransfers(transferTabularData); + setNftTransfers(nftTransferTabularData); + setUserAsset(currentAsset); + setHasShownNotLiveWallet(true); + + await walletService.fetchAndSaveNFTs(sessionData); + setFetchingDB(false); + }; + + const processNftList = (currentList: NftModel[] | undefined) => { + if (currentList) { + return currentList.slice(0, maxNftPreview).map(item => { + const denomSchema = isJson(item.denomSchema) + ? JSON.parse(item.denomSchema) + : item.denomSchema; + const tokenData = isJson(item.tokenData) ? JSON.parse(item.tokenData) : item.tokenData; + const nftModel: NftProcessedModel = { + ...item, + denomSchema, + tokenData, + }; + return nftModel; + }); + } + return []; + }; + + useEffect(() => { + const syncAssetData = async () => { + const sessionData = await walletService.retrieveCurrentSession(); + const currentAsset = await walletService.retrieveDefaultWalletAsset(sessionData); + const allDelegations: StakingTransactionData[] = await walletService.retrieveAllDelegations( + sessionData.wallet.identifier, + ); + const allTransfers: TransferTransactionData[] = await walletService.retrieveAllTransfers( + sessionData.wallet.identifier, + ); + + const allNftTransfer: NftAccountTransactionData[] = await walletService.getAllNFTAccountTxs( + sessionData, + ); + + const allNFTs: NftModel[] = await walletService.retrieveNFTs(sessionData.wallet.identifier); + const currentNftList = processNftList(allNFTs); + setProcessedNftList(currentNftList); + setNFTList(allNFTs); + + const stakingTabularData = convertDelegations(allDelegations, currentAsset); + const transferTabularData = convertTransfers(allTransfers, currentAsset, sessionData); + const nftTransferTabularData = convertNftTransfers(allNftTransfer); + + showWalletStateNotification(currentSession.wallet.config); + setDelegations(stakingTabularData); + setTransfers(transferTabularData); + setNftTransfers(nftTransferTabularData); + setUserAsset(currentAsset); + setHasShownNotLiveWallet(true); + }; + + syncAssetData(); + + if (!didMountRef.current) { + didMountRef.current = true; + analyticsService.logPage('Home'); + } + }, [fetchingDB]); + const showConfirmationModal = () => { setInputPasswordVisible(false); setIsVisibleConfirmationModal(true); @@ -468,7 +736,16 @@ function HomePage() { title: 'Delegator Address', dataIndex: 'delegatorAddress', key: 'delegatorAddress', - render: text => {middleEllipsis(text, 8)}, + render: text => ( + + {middleEllipsis(text, 8)} + + ), }, { title: 'Undelegate', @@ -510,7 +787,7 @@ function HomePage() { Welcome Back! { - onSyncBtnCall(); + onSyncAndRefreshBtnCall(); }} style={{ position: 'absolute', right: '36px', marginTop: '6px' }} spin={fetchingDB} @@ -545,25 +822,68 @@ function HomePage() { + + +
+ ( + + + + + {middleEllipsis(item?.tokenMinter, 6)}{' '} + {item?.isMintedByCDC ? : ''} + + } + /> + + + )} + pagination={false} + /> + + See all + +
+
+
-
{ - // return { - // onClick: () => { - // setUndelegateFormValues({ - // validatorAddress: record.validatorAddress, - // undelegateAmount: record.stakedAmount, - // }); - // }, - // }; - // }} - /> +
+ + +
diff --git a/src/pages/nft/nft.less b/src/pages/nft/nft.less new file mode 100644 index 000000000..838875b72 --- /dev/null +++ b/src/pages/nft/nft.less @@ -0,0 +1,183 @@ +.nft-content { + position: relative; + padding: 24px; + .container { + .description { + margin-bottom: 20px; + color: @font-color; + } + .ant-btn { + width: 100%; + margin: 24px 0 -24px 0; + } + } + .view-selection { + text-align: right; + .ant-radio-button-wrapper { + border: none; + box-shadow: none; + transition: none; + } + .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled) { + box-shadow: none; + } + .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled)::before { + background-color: transparent; + } + .ant-radio-button-wrapper:not(:first-child)::before { + background-color: transparent; + } + } + .nft { + border: none; + box-shadow: none !important; + .ant-card-cover { + display: flex; + align-items: center; + justify-content: center; + width: 200px; + height: 200px; + margin: auto; + background: #f8f8f8; + img { + width: auto; + max-width: 200px; + height: auto; + max-height: 200px; + border-radius: 4px; + } + span.anticon { + position: absolute; + font-size: 48px; + } + } + .ant-card-body { + padding-right: 0; + padding-left: 0; + .ant-avatar { + width: 23px; + height: 23px; + // padding: 0 5px 5px 0; + margin-right: 5px; + } + } + } + .grid-container { + display: flex; + flex-wrap: wrap; + justify-content: space-around; + min-width: 800px; + .ant-card { + margin-bottom: 12px; + } + } + .back-button { + margin: 12px 0; + } +} +.nft-modal { + width: 1000px !important; + .nft-detail { + background: #fff; + .ant-card-meta a { + color: @font-color; + } + .ant-layout-content { + display: flex; + align-items: center; + justify-content: center; + width: 100%; + } + .nft-image { + display: flex; + align-items: center; + justify-content: center; + width: 100%; + margin: auto; + background: #f8f8f8; + img { + width: auto; + max-width: 100%; + height: auto; + max-height: 600px; + border-radius: 4px; + } + } + .title { + margin-top: 16px; + font-size: 24px; + } + .ant-card-meta { + margin-top: 30px; + .ant-avatar { + width: 25px; + height: 25px; + margin-right: 8px; + } + } + + .description { + max-width: 800px; + color: @font-color; + } + .item { + margin: 30px 0; + .subtitle { + margin: 10px 0; + } + .label { + color: @font-color; + } + .address { + padding: 10px 8px; + background: rgba(196, 196, 196, 0.1); + border-radius: 4px; + } + .ant-btn { + width: 100%; + margin: 0; + } + } + .table-row { + display: grid; + grid-template-columns: 50% 50%; + margin-bottom: 10px; + :last-child:not(a) { + color: @font-color; + } + } + .goto-marketplace { + text-align: center; + } + .ant-layout-sider { + padding-left: 32px; + background: #fff; + } + } +} +.nft-transfer-modal { + .nft-image { + display: flex; + align-items: center; + justify-content: center; + width: 200px; + height: 200px; + margin: auto; + background: #f8f8f8; + img { + width: auto; + max-width: 200px; + height: auto; + max-height: 200px; + } + } + .item { + .ant-layout, + .ant-layout-sider { + background: #fff !important; + } + &.notice { + color: @font-color; + } + } +} diff --git a/src/pages/nft/nft.tsx b/src/pages/nft/nft.tsx new file mode 100644 index 000000000..e57ff5c2c --- /dev/null +++ b/src/pages/nft/nft.tsx @@ -0,0 +1,764 @@ +import React, { useState, useEffect } from 'react'; +import './nft.less'; +import 'antd/dist/antd.css'; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +import { Layout, Card, Tabs, List, Avatar, Radio, Table, Button, Form, Input } from 'antd'; +import Icon, { MenuOutlined, AppstoreOutlined, ExclamationCircleOutlined } from '@ant-design/icons'; +import { useRecoilValue, useRecoilState } from 'recoil'; +import ReactPlayer from 'react-player'; +import { AddressType } from '@crypto-org-chain/chain-jslib/lib/dist/utils/address'; + +import { + sessionState, + nftListState, + fetchingDBState, + walletAssetState, + ledgerIsExpertModeState, +} from '../../recoil/atom'; +import { ellipsis, middleEllipsis, isJson } from '../../utils/utils'; +import { getUINormalScaleAmount } from '../../utils/NumberUtils'; +import { NftModel, NftProcessedModel, BroadCastResult } from '../../models/Transaction'; +import { TransactionUtils } from '../../utils/TransactionUtils'; +import { FIXED_DEFAULT_FEE } from '../../config/StaticConfig'; + +import { walletService } from '../../service/WalletService'; +import { secretStoreService } from '../../storage/SecretStoreService'; +import { detectConditionsError, LEDGER_WALLET_TYPE } from '../../service/LedgerService'; +import { + AnalyticsActions, + AnalyticsCategory, + AnalyticsService, + AnalyticsTxType, +} from '../../service/analytics/AnalyticsService'; + +import ModalPopup from '../../components/ModalPopup/ModalPopup'; +import SuccessModalPopup from '../../components/SuccessModalPopup/SuccessModalPopup'; +import ErrorModalPopup from '../../components/ErrorModalPopup/ErrorModalPopup'; +import PasswordFormModal from '../../components/PasswordForm/PasswordFormModal'; + +import IconTick from '../../svg/IconTick'; +import IconPlayer from '../../svg/IconPlayer'; +import nftThumbnail from '../../assets/nft-thumbnail.png'; + +const { Header, Content, Footer, Sider } = Layout; +const { TabPane } = Tabs; +const { Meta } = Card; +const layout = {}; + +const NftPage = () => { + const [form] = Form.useForm(); + const [formValues, setFormValues] = useState({ + tokenId: '', + denomId: '', + senderAddress: '', + recipientAddress: '', + amount: '', + memo: '', + }); + const currentSession = useRecoilValue(sessionState); + const [walletAsset, setWalletAsset] = useRecoilState(walletAssetState); + const [ledgerIsExpertMode, setLedgerIsExpertMode] = useRecoilState(ledgerIsExpertModeState); + const [nftList, setNftList] = useRecoilState(nftListState); + const fetchingDB = useRecoilValue(fetchingDBState); + const [confirmLoading, setConfirmLoading] = useState(false); + const [broadcastResult, setBroadcastResult] = useState({}); + const [errorMessages, setErrorMessages] = useState([]); + const [decryptedPhrase, setDecryptedPhrase] = useState(''); + + const [inputPasswordVisible, setInputPasswordVisible] = useState(false); + const [isSuccessModalVisible, setIsSuccessModalVisible] = useState(false); + const [isErrorModalVisible, setIsErrorModalVisible] = useState(false); + const [isNftModalVisible, setIsNftModalVisible] = useState(false); + const [isNftTransferModalVisible, setIsNftTransferModalVisible] = useState(false); + const [isNftTransferConfirmVisible, setIsNftTransferConfirmVisible] = useState(false); + + const [nft, setNft] = useState(); + const [nftView, setNftView] = useState('grid'); + const [processedNftList, setProcessedNftList] = useState([]); + const [isVideoPlaying, setIsVideoPlaying] = useState(false); + const [videoUrl, setVideoUrl] = useState(''); + + const analyticsService = new AnalyticsService(currentSession); + + const nftViewOptions = [ + { label: , value: 'list' }, + { label: , value: 'grid' }, + ]; + + const networkFee = + currentSession.wallet.config.fee !== undefined && + currentSession.wallet.config.fee.networkFee !== undefined + ? currentSession.wallet.config.fee.networkFee + : FIXED_DEFAULT_FEE; + + const closeSuccessModal = () => { + setIsSuccessModalVisible(false); + setIsNftModalVisible(false); + setIsNftTransferConfirmVisible(false); + }; + + const closeErrorModal = () => { + setIsErrorModalVisible(false); + }; + + const processNftList = (currentList: NftModel[] | undefined) => { + if (currentList) { + return currentList.map(item => { + const denomSchema = isJson(item.denomSchema) + ? JSON.parse(item.denomSchema) + : item.denomSchema; + const tokenData = isJson(item.tokenData) ? JSON.parse(item.tokenData) : item.tokenData; + const nftModel: NftProcessedModel = { + ...item, + denomSchema, + tokenData, + }; + return nftModel; + }); + } + return []; + }; + + const customAddressValidator = TransactionUtils.addressValidator( + currentSession, + walletAsset, + AddressType.USER, + ); + + const supportedVideo = (mimeType: string | undefined) => { + switch (mimeType) { + case 'video/mp4': + case 'video/webm': + case 'video/ogg': + case 'audio/ogg': + case 'audio/mpeg': + return true; + default: + return false; + } + }; + + const renderPreview = (_nft: NftProcessedModel | undefined, showThumbnail: boolean = true) => { + if (!showThumbnail && supportedVideo(_nft?.tokenData.mimeType)) { + return ( + + ); + } + return ( + {_nft?.denomName} { + (e.target as HTMLImageElement).src = nftThumbnail; + }} + /> + ); + }; + + const showConfirmationModal = () => { + setInputPasswordVisible(false); + setIsNftTransferConfirmVisible(true); + setIsNftTransferModalVisible(true); + setFormValues({ + ...form.getFieldsValue(true), + // Replace scientific notation to plain string values + denomId: nft?.denomId, + tokenId: nft?.tokenId, + senderAddress: currentSession.wallet.address, + }); + }; + + const showPasswordInput = () => { + if (decryptedPhrase || currentSession.wallet.walletType === LEDGER_WALLET_TYPE) { + showConfirmationModal(); + } else { + setInputPasswordVisible(true); + setIsNftTransferModalVisible(false); + } + }; + + const onWalletDecryptFinish = async (password: string) => { + const phraseDecrypted = await secretStoreService.decryptPhrase( + password, + currentSession.wallet.identifier, + ); + setDecryptedPhrase(phraseDecrypted); + showConfirmationModal(); + }; + + const onConfirmTransfer = async () => { + const { walletType } = currentSession.wallet; + const memo = formValues.memo !== null && formValues.memo !== undefined ? formValues.memo : ''; + if (!decryptedPhrase && walletType !== LEDGER_WALLET_TYPE) { + return; + } + try { + setConfirmLoading(true); + const sendResult = await walletService.sendNFT({ + tokenId: formValues.tokenId, + denomId: formValues.denomId, + sender: formValues.senderAddress, + recipient: formValues.recipientAddress, + memo, + decryptedPhrase, + asset: walletAsset, + walletType, + }); + + analyticsService.logTransactionEvent( + broadcastResult.transactionHash as string, + formValues.amount, + AnalyticsTxType.TransferTransaction, + AnalyticsActions.FundsTransfer, + AnalyticsCategory.Transfer, + ); + + const latestLoadedNFTs = await walletService.retrieveNFTs(currentSession.wallet.identifier); + setNftList(latestLoadedNFTs); + const processedNFTsLists = processNftList(latestLoadedNFTs); + setProcessedNftList(processedNFTsLists); + + setBroadcastResult(sendResult); + + setIsNftModalVisible(false); + setIsNftTransferModalVisible(false); + setIsNftTransferConfirmVisible(false); + setConfirmLoading(false); + + setIsSuccessModalVisible(true); + setInputPasswordVisible(false); + + const currentWalletAsset = await walletService.retrieveDefaultWalletAsset(currentSession); + setWalletAsset(currentWalletAsset); + + form.resetFields(); + } catch (e) { + if (walletType === LEDGER_WALLET_TYPE) { + setLedgerIsExpertMode(detectConditionsError(e.toString())); + } + + setErrorMessages(e.message.split(': ')); + setIsNftModalVisible(false); + setConfirmLoading(false); + setInputPasswordVisible(false); + setIsErrorModalVisible(true); + // eslint-disable-next-line no-console + console.log('Error occurred while transfer', e); + } + }; + + useEffect(() => { + const fetchNftList = async () => { + const currentNftList = processNftList(nftList); + setProcessedNftList(currentNftList); + }; + fetchNftList(); + }, [fetchingDB]); + + const NftColumns = [ + { + title: 'Drop Name', + key: 'name', + render: record => { + return record.tokenData.drop ? record.tokenData.drop : 'n.a.'; + }, + }, + { + title: 'Denom Name', + key: 'denomId', + render: record => { + return record.denomId; + }, + }, + { + title: 'Token ID', + key: 'tokenId', + render: record => { + return record.tokenId; + }, + }, + { + title: 'Creator', + key: 'creator', + render: record => { + return ( + + {middleEllipsis(record.tokenMinter, 8)} + + ); + }, + }, + { + title: 'Action', + key: 'viewAction', + render: record => { + return ( + { + setNft(record); + setVideoUrl(record?.tokenData.animation_url); + setIsVideoPlaying(true); + setIsNftModalVisible(true); + }} + > + View + + ); + }, + }, + ]; + + return ( + +
My NFT
+
+ An overview of your NFT Collection on Crypto.org Chain. +
+ + + +
+
+ { + setNftView(e.target.value); + }} + optionType="button" + /> +
+ {nftView === 'grid' ? ( + ( + + + {renderPreview(item)} + {supportedVideo(item?.tokenData.mimeType) ? ( + + ) : ( + '' + )} + + } + hoverable + onClick={() => { + setNft(item); + setVideoUrl(item?.tokenData.animation_url); + setIsVideoPlaying(true); + setIsNftModalVisible(true); + }} + className="nft" + > + + + {middleEllipsis(item?.tokenMinter, 6)}{' '} + {item?.isMintedByCDC ? : ''} + + } + /> + + + )} + pagination={{ + pageSize: 6, + }} + loading={fetchingDB} + /> + ) : ( +
+ )} + + + + + { + // Stop the video when closing + setIsVideoPlaying(false); + setVideoUrl(undefined); + setTimeout(() => { + setIsNftModalVisible(false); + }, 10); + }} + handleOk={() => {}} + footer={[]} + okText="Confirm" + className="nft-modal" + > + + +
{renderPreview(nft, false)}
+
+ + <> +
+ {nft?.tokenData.drop ? nft?.tokenData.drop : `${nft?.denomId} - #${nft?.tokenId}`} +
+
+ + + + {nft?.tokenMinter} + + {nft?.isMintedByCDC ? : ''} + + } + /> +
+
+
About the Drop
+
+ {nft?.tokenData.description ? nft?.tokenData.description : 'n.a.'} +
+
+
+
+
Denom Name
+
{nft?.denomName}
+
+
+
Token ID
+
{nft?.tokenId}
+
+ {nft?.tokenData.mimeType ? ( + + ) : ( + '' + )} +
+
+ +
+
+ {nft?.marketplaceLink !== '' ? ( + + View on Crypto.com NFT + + ) : ( + '' + )} +
+ +
+
+
+ { + setIsNftTransferModalVisible(false); + setIsNftTransferConfirmVisible(false); + setIsNftModalVisible(true); + form.resetFields(); + }} + handleOk={() => {}} + footer={[ + isNftTransferConfirmVisible ? ( + + ) : ( + + ), + , + ]} + okText="Confirm" + className="nft-transfer-modal" + > + <> + {isNftTransferConfirmVisible ? ( + <> +
Confirm Transfer
+
Please review the information below.
+
+
{renderPreview(nft)}
+
+
+
To
+
{`${form.getFieldValue('recipientAddress')}`}
+
+
+ + + + + + This NFT is on the Crypto.org Chain. Transferring the NFT to a recipient address + that is not compatible with the Crypto.org Chain NFT token standard will result + in the permanent loss of your asset. + + +
+
+
Denom Name
+
{`${formValues.denomId}`}
+
+
+
Token ID
+
{`${formValues.tokenId}`}
+
+
+
Transaction Fee
+
+ {getUINormalScaleAmount(networkFee, walletAsset.decimals)} {walletAsset.symbol} +
+
+ + ) : ( + <> +
Transfer NFT
+
Fill in the information below to transfer your NFT.
+
+
{renderPreview(nft)}
+
+
+
Sending
+
+ {nft?.tokenData.drop ? nft?.tokenData.drop : `${nft?.denomId} - #${nft?.tokenId}`} +
+
+
+ + + + + {networkFee > walletAsset.balance ? ( +
+ + + + + + Insufficient balance. Please ensure you have at least{' '} + {getUINormalScaleAmount(networkFee, walletAsset.decimals)}{' '} + {walletAsset.symbol} for network fee. + + +
+ ) : ( + '' + )} +
+ + + + + + This NFT is on the Crypto.org Chain. Transferring the NFT to a recipient address + that is not compatible with the Crypto.org Chain NFT token standard will result + in the permanent loss of your asset. + + +
+ + )} + +
+
+ { + setInputPasswordVisible(false); + setIsNftTransferModalVisible(true); + }} + onSuccess={onWalletDecryptFinish} + onValidatePassword={async (password: string) => { + const isValid = await secretStoreService.checkIfPasswordIsValid(password); + return { + valid: isValid, + errMsg: !isValid ? 'The password provided is incorrect, Please try again' : '', + }; + }} + successText="Wallet decrypted successfully !" + title="Provide app password" + visible={inputPasswordVisible} + successButtonText="Continue" + confirmPassword={false} + /> + + Ok + , + ]} + > + <> + {broadcastResult?.code !== undefined && + broadcastResult?.code !== null && + broadcastResult.code === walletService.BROADCAST_TIMEOUT_CODE ? ( +
+ The transaction timed out but it will be included in the subsequent blocks +
+ ) : ( +
Your NFT transaction was broadcasted successfully!
+ )} + +
+ + <> +
+ The NFT transaction failed. Please try again later. +
+ {errorMessages + .filter((item, idx) => { + return errorMessages.indexOf(item) === idx; + }) + .map((err, idx) => ( +
- {err}
+ ))} + {ledgerIsExpertMode ? ( +
Please ensure that your have enabled Expert mode on your ledger device.
+ ) : ( + '' + )} +
+ +
+ + ); +}; + +export default NftPage; diff --git a/src/pages/route.tsx b/src/pages/route.tsx index 55775744c..a05076f38 100644 --- a/src/pages/route.tsx +++ b/src/pages/route.tsx @@ -17,6 +17,7 @@ import ReceivePage from './receive/receive'; import WalletPage from './wallet/wallet'; import StakingPage from './staking/staking'; import GovernancePage from './governance/governance'; +import NftPage from './nft/nft'; import SettingsPage from './settings/settings'; import SignUpPage from './signup/signup'; import HomeLayout from '../layouts/home/home'; @@ -107,6 +108,12 @@ function RouteHub() { path: '/governance', component: , }, + { + name: 'Nft Page', + key: 'nft', + path: '/nft', + component: , + }, { name: 'Settings Page', key: 'settings', diff --git a/src/pages/send/send.tsx b/src/pages/send/send.tsx index 249446257..30c5e3828 100644 --- a/src/pages/send/send.tsx +++ b/src/pages/send/send.tsx @@ -3,7 +3,7 @@ import './send.less'; import 'antd/dist/antd.css'; import { Button, Form, Input, InputNumber, Layout } from 'antd'; import { useRecoilState, useRecoilValue } from 'recoil'; -import { AddressType } from '@crypto-com/chain-jslib/lib/dist/utils/address'; +import { AddressType } from '@crypto-org-chain/chain-jslib/lib/dist/utils/address'; // eslint-disable-next-line import/no-extraneous-dependencies // import {remote} from 'electron'; import ModalPopup from '../../components/ModalPopup/ModalPopup'; @@ -62,7 +62,7 @@ const FormSend = () => { const showConfirmationModal = () => { setInputPasswordVisible(false); - const transferInoputAmount = adjustedTransactionAmount( + const transferInputAmount = adjustedTransactionAmount( form.getFieldValue('amount'), walletAsset, currentSession.wallet.config.fee !== undefined && @@ -73,7 +73,7 @@ const FormSend = () => { setFormValues({ ...form.getFieldsValue(), // Replace scientific notation to plain string values - amount: fromScientificNotation(transferInoputAmount), + amount: fromScientificNotation(transferInputAmount), }); setIsVisibleConfirmationModal(true); }; diff --git a/src/pages/settings/settings.tsx b/src/pages/settings/settings.tsx index 32c1515c0..d6af1d10d 100644 --- a/src/pages/settings/settings.tsx +++ b/src/pages/settings/settings.tsx @@ -392,12 +392,14 @@ const FormSettings = () => { }; const handleCancelConfirmationModal = () => { - setIsConfirmationModalVisible(false); - setIsConfirmClearVisible(false); + if (!isButtonLoading) { + setIsConfirmationModalVisible(false); + setIsConfirmClearVisible(false); + } }; const onConfirmClear = () => { - setIsConfirmationModalVisible(false); + // setIsConfirmationModalVisible(false); setIsButtonLoading(true); indexedDB.deleteDatabase('NeDB'); setTimeout(() => { @@ -456,6 +458,7 @@ const FormSettings = () => { handleCancel={handleCancelConfirmationModal} handleOk={onConfirmClear} confirmationLoading={isButtonLoading} + closable={!isButtonLoading} footer={[
({ default: null, }); +const nftListState = atom({ + key: 'nftList', + default: undefined, +}); + const hasShownWarningOnWalletTypeState = atom({ key: 'hasShownWarningOnWalletTypeState', default: false, @@ -95,6 +100,7 @@ export { walletListState, walletTempBackupState, validatorListState, + nftListState, hasShownWarningOnWalletTypeState, ledgerIsExpertModeState, fetchingDBState, diff --git a/src/service/TransactionRequestModels.ts b/src/service/TransactionRequestModels.ts index 205f576a9..4b3c904c9 100644 --- a/src/service/TransactionRequestModels.ts +++ b/src/service/TransactionRequestModels.ts @@ -19,6 +19,18 @@ export interface VoteRequest { walletType: string; // normal, ledger } +export interface NFTTransferRequest { + tokenId: string; + denomId: string; + sender: string; + recipient: string; + + memo: string; + decryptedPhrase: string; + asset: UserAsset; + walletType: string; // normal, ledger +} + export interface DelegationRequest { validatorAddress: string; amount: string; diff --git a/src/service/WalletCreator.spec.ts b/src/service/WalletCreator.spec.ts index 7aaf3281a..2725e428b 100644 --- a/src/service/WalletCreator.spec.ts +++ b/src/service/WalletCreator.spec.ts @@ -8,6 +8,7 @@ describe('Testing Wallet Creation', () => { const testNetConfig = DefaultWalletConfigs.TestNetConfig; const createOptions: WalletCreateOptions = { + addressIndex: 0, walletType: 'normal', config: testNetConfig, walletName: 'My-TestNet-Wallet', @@ -27,6 +28,7 @@ describe('Testing Wallet Creation', () => { const mainNetConfig = DefaultWalletConfigs.MainNetConfig; const createOptions: WalletCreateOptions = { + addressIndex: 0, walletType: 'normal', config: mainNetConfig, walletName: 'My-MainNet-Wallet', @@ -49,6 +51,13 @@ describe('Testing Wallet Creation', () => { enabled: false, derivationPath: "44'/245'/0'/0/0", name: 'Pystaport-Custom-Network', + enableGeneralSettings: false, + disableDefaultClientMemo: false, + analyticsDisabled: false, + fee: { + gasLimit: '', + networkFee: '', + }, network: { defaultNodeUrl: '', chainId: 'pystaportnet', @@ -63,11 +72,13 @@ describe('Testing Wallet Creation', () => { coinType: 1, account: 0, }, + rpcUrl: '', }, nodeUrl: '123.18.45.12:3400', }; const createOptions: WalletCreateOptions = { + addressIndex: 0, walletType: 'normal', config: customConfig, walletName: 'My-Custom-Config-Wallet', diff --git a/src/service/WalletCreator.ts b/src/service/WalletCreator.ts index c9b2e2ec9..bcd12915b 100644 --- a/src/service/WalletCreator.ts +++ b/src/service/WalletCreator.ts @@ -1,4 +1,4 @@ -import sdk from '@crypto-com/chain-jslib'; +import sdk from '@crypto-org-chain/chain-jslib'; import { Wallet } from '../models/Wallet'; import { WalletConfig } from '../config/StaticConfig'; import { HDKey, Secp256k1KeyPair } from '../utils/ChainJsLib'; diff --git a/src/service/WalletImporter.spec.ts b/src/service/WalletImporter.spec.ts index 7526a58ea..e4298be06 100644 --- a/src/service/WalletImporter.spec.ts +++ b/src/service/WalletImporter.spec.ts @@ -8,6 +8,8 @@ describe('Testing WalletImporter', () => { const testNetConfig = DefaultWalletConfigs.TestNetConfig; const importOptions: WalletImportOptions = { + addressIndex: 0, + walletType: 'normal', config: testNetConfig, phrase: 'ramp sock spice enrich exhibit skate empower process kit pudding olive mesh friend camp labor coconut devote shell argue system pig then provide nose', @@ -24,6 +26,8 @@ describe('Testing WalletImporter', () => { const mainNetConfig = DefaultWalletConfigs.MainNetConfig; const importOptions: WalletImportOptions = { + addressIndex: 0, + walletType: 'normal', config: mainNetConfig, phrase: 'team school reopen cave banner pass autumn march immune album hockey region baby critic insect armor pigeon owner number velvet romance flight blame tone', @@ -43,6 +47,9 @@ describe('Testing WalletImporter', () => { enabled: true, derivationPath: "44'/245'/0'/0/0", name: 'Pystaport-Custom-Network', + enableGeneralSettings: false, + disableDefaultClientMemo: false, + analyticsDisabled: false, network: { defaultNodeUrl: '', chainId: 'chainmaind', @@ -57,11 +64,14 @@ describe('Testing WalletImporter', () => { coinType: 1, account: 0, }, + rpcUrl: '', }, nodeUrl: '123.18.45.12:3400', }; const importOptions: WalletImportOptions = { + addressIndex: 0, + walletType: 'normal', phrase: 'team school reopen cave banner pass autumn march immune album hockey region baby critic insect armor pigeon owner number velvet romance flight blame tone', config: customConfig, diff --git a/src/service/WalletImporter.ts b/src/service/WalletImporter.ts index 5572e899b..3e1e99723 100644 --- a/src/service/WalletImporter.ts +++ b/src/service/WalletImporter.ts @@ -1,4 +1,4 @@ -import sdk from '@crypto-com/chain-jslib'; +import sdk from '@crypto-org-chain/chain-jslib'; import { Wallet } from '../models/Wallet'; import { WalletConfig } from '../config/StaticConfig'; import { HDKey, Secp256k1KeyPair } from '../utils/ChainJsLib'; diff --git a/src/service/WalletService.ts b/src/service/WalletService.ts index 5f3e920b0..6298733fd 100644 --- a/src/service/WalletService.ts +++ b/src/service/WalletService.ts @@ -24,6 +24,7 @@ import { LedgerTransactionSigner } from './signers/LedgerTransactionSigner'; import { Session } from '../models/Session'; import { DelegateTransactionUnsigned, + NFTTransferUnsigned, RedelegateTransactionUnsigned, TransferTransactionUnsigned, UndelegateTransactionUnsigned, @@ -36,6 +37,10 @@ import { AssetMarketPrice, UserAsset } from '../models/UserAsset'; import { croMarketPriceApi } from './rpc/MarketApi'; import { BroadCastResult, + NftAccountTransactionData, + NftModel, + NftQueryParams, + NftTransferModel, ProposalModel, ProposalStatuses, RewardTransaction, @@ -52,6 +57,7 @@ import { createLedgerDevice, LEDGER_WALLET_TYPE } from './LedgerService'; import { ISignerProvider } from './signers/SignerProvider'; import { DelegationRequest, + NFTTransferRequest, RedelegationRequest, TransferRequest, UndelegationRequest, @@ -59,6 +65,7 @@ import { WithdrawStakingRewardRequest, } from './TransactionRequestModels'; import { FinalTallyResult } from './rpc/NodeRpcModels'; +import { sleep } from '../utils/utils'; class WalletService { private readonly storageService: StorageService; @@ -102,7 +109,12 @@ class WalletService { } const broadCastResult = await nodeRpc.broadcastTransaction(signedTxHex); - await this.syncAll(currentSession); + + await Promise.all([ + await this.fetchAndSaveTransfers(currentSession), + await this.fetchAndUpdateBalances(currentSession), + ]); + return broadCastResult; } @@ -151,7 +163,11 @@ class WalletService { } const broadCastResult = await nodeRpc.broadcastTransaction(signedTxHex); - await this.syncAll(currentSession); + await Promise.all([ + await this.fetchAndUpdateBalances(currentSession), + await this.fetchAndSaveDelegations(nodeRpc, currentSession), + ]); + return broadCastResult; } @@ -200,7 +216,11 @@ class WalletService { } const broadCastResult = await nodeRpc.broadcastTransaction(signedTxHex); - await this.syncAll(currentSession); + await Promise.all([ + await this.fetchAndUpdateBalances(currentSession), + await this.fetchAndSaveDelegations(nodeRpc, currentSession), + ]); + return broadCastResult; } @@ -249,7 +269,10 @@ class WalletService { } const broadCastResult = await nodeRpc.broadcastTransaction(signedTxHex); - await this.syncAll(currentSession); + await Promise.all([ + await this.fetchAndUpdateBalances(currentSession), + await this.fetchAndSaveDelegations(nodeRpc, currentSession), + ]); return broadCastResult; } @@ -291,6 +314,54 @@ class WalletService { return broadCastResult; } + public async sendNFT(nftTransferRequest: NFTTransferRequest): Promise { + const { + nodeRpc, + accountNumber, + accountSequence, + currentSession, + transactionSigner, + ledgerTransactionSigner, + } = await this.prepareTransaction(); + + const memo = !nftTransferRequest.memo ? DEFAULT_CLIENT_MEMO : nftTransferRequest.memo; + + const nftTransferUnsigned: NFTTransferUnsigned = { + tokenId: nftTransferRequest.tokenId, + denomId: nftTransferRequest.denomId, + sender: nftTransferRequest.sender, + recipient: nftTransferRequest.recipient, + + memo, + accountNumber, + accountSequence, + }; + + let signedTxHex: string = ''; + + if (nftTransferRequest.walletType === LEDGER_WALLET_TYPE) { + signedTxHex = await ledgerTransactionSigner.signNFTTransfer( + nftTransferUnsigned, + nftTransferRequest.decryptedPhrase, + ); + } else { + signedTxHex = await transactionSigner.signNFTTransfer( + nftTransferUnsigned, + nftTransferRequest.decryptedPhrase, + ); + } + + const broadCastResult = await nodeRpc.broadcastTransaction(signedTxHex); + + // It takes a few seconds for the indexing service to sync latest NFT state + await sleep(7_000); + await Promise.all([ + this.fetchAndSaveNFTs(currentSession), + this.fetchAndSaveNFTAccountTxs(currentSession), + ]); + return broadCastResult; + } + public async syncAll(session: Session | null = null) { const currentSession = session == null ? await this.storageService.retrieveCurrentSession() : session; @@ -306,6 +377,7 @@ class WalletService { await Promise.all([ this.syncBalancesData(currentSession), this.syncTransactionsData(currentSession), + this.fetchAndSaveNFTs(currentSession), ]); } @@ -344,7 +416,10 @@ class WalletService { } const broadCastResult = await nodeRpc.broadcastTransaction(signedTxHex); - await this.syncAll(currentSession); + await Promise.all([ + await this.fetchAndSaveRewards(nodeRpc, currentSession), + await this.fetchAndUpdateBalances(currentSession), + ]); return broadCastResult; } @@ -384,6 +459,7 @@ class WalletService { return [ DefaultWalletConfigs.MainNetConfig, DefaultWalletConfigs.TestNetConfig, + DefaultWalletConfigs.TestNetCroeseid3, DefaultWalletConfigs.CustomDevNet, ]; } @@ -530,7 +606,7 @@ class WalletService { this.fetchAndSaveRewards(nodeRpc, currentSession), this.fetchAndSaveTransfers(currentSession), this.fetchAndSaveValidators(currentSession), - this.fetchAndSaveProposals(currentSession), + this.fetchAndSaveNFTAccountTxs(currentSession), ]); } @@ -552,6 +628,35 @@ class WalletService { } } + public async fetchAndSaveNFTAccountTxs(currentSession: Session) { + try { + const chainIndexAPI = ChainIndexingAPI.init(currentSession.wallet.config.indexingUrl); + const nftAccountTransactionList = await chainIndexAPI.fetchAllAccountNFTsTransactions( + currentSession.wallet.address, + ); + + await this.storageService.saveNFTAccountTransactions({ + transactions: nftAccountTransactionList.result, + walletId: currentSession.wallet.identifier, + }); + } catch (e) { + // eslint-disable-next-line no-console + console.error('FAILED_TO_LOAD_SAVE_NFT_ACCOUNT_TXs', e); + } + } + + public async getAllNFTAccountTxs( + currentSession: Session, + ): Promise> { + const nftAccountTxs = await this.storageService.retrieveAllNFTAccountTransactions( + currentSession.wallet.identifier, + ); + if (!nftAccountTxs) { + return []; + } + return nftAccountTxs.transactions; + } + public async fetchAndSaveRewards(nodeRpc: NodeRpcService, currentSession: Session) { try { const rewards = await nodeRpc.fetchStakingRewards( @@ -611,6 +716,23 @@ class WalletService { } } + public async fetchAndSaveNFTs(currentSession: Session) { + try { + const nfts = await this.loadAllCurrentAccountNFTs(); + if (nfts === null) { + return; + } + + await this.storageService.saveNFTs({ + walletId: currentSession.wallet.identifier, + nfts, + }); + } catch (e) { + // eslint-disable-next-line no-console + console.error('FAILED_TO_LOAD_SAVE_NFTS', e); + } + } + public async retrieveCurrentWalletAssets(currentSession: Session): Promise { const assets = await this.storageService.retrieveAssetsByWallet( currentSession.wallet.identifier, @@ -820,6 +942,14 @@ class WalletService { return proposalSet.proposals; } + public async retrieveNFTs(walletID: string): Promise { + const nftSet = await this.storageService.retrieveAllNfts(walletID); + if (!nftSet) { + return []; + } + return nftSet.nfts; + } + private async getLatestTopValidators(): Promise { try { const currentSession = await this.storageService.retrieveCurrentSession(); @@ -855,6 +985,53 @@ class WalletService { } } + private async loadAllCurrentAccountNFTs(): Promise { + try { + const currentSession = await this.storageService.retrieveCurrentSession(); + if (currentSession?.wallet.config.nodeUrl === NOT_KNOWN_YET_VALUE) { + return Promise.resolve([]); + } + const chainIndexAPI = ChainIndexingAPI.init(currentSession.wallet.config.indexingUrl); + const nftList = await chainIndexAPI.getAccountNFTList(currentSession.wallet.address); + return await chainIndexAPI.getNftListMarketplaceData(nftList); + } catch (e) { + // eslint-disable-next-line no-console + console.log('FAILED_LOADING NFTs', e); + return null; + } + } + + public async loadNFTTransferHistory(nftQuery: NftQueryParams): Promise { + const currentSession = await this.storageService.retrieveCurrentSession(); + if (currentSession?.wallet.config.nodeUrl === NOT_KNOWN_YET_VALUE) { + return Promise.resolve([]); + } + + try { + const chainIndexAPI = ChainIndexingAPI.init(currentSession.wallet.config.indexingUrl); + const nftTransferTransactions = await chainIndexAPI.getNFTTransferHistory(nftQuery); + + await this.storageService.saveNFTTransferHistory({ + transfers: nftTransferTransactions, + walletId: currentSession.wallet.identifier, + nftQuery, + }); + + return nftTransferTransactions; + } catch (e) { + // eslint-disable-next-line no-console + console.log('FAILED_LOADING NFT Transfer history, returning DB data', e); + const localTransferHistory = await this.storageService.retrieveNFTTransferHistory( + currentSession.wallet.identifier, + nftQuery, + ); + if (!localTransferHistory) { + return []; + } + return localTransferHistory.transfers; + } + } + public async loadLatestProposalTally(proposalID: string): Promise { const currentSession = await this.storageService.retrieveCurrentSession(); if (currentSession?.wallet.config.nodeUrl === NOT_KNOWN_YET_VALUE) { diff --git a/src/service/rpc/ChainIndexingAPI.ts b/src/service/rpc/ChainIndexingAPI.ts index 344a6c6ff..b5741c72c 100644 --- a/src/service/rpc/ChainIndexingAPI.ts +++ b/src/service/rpc/ChainIndexingAPI.ts @@ -1,7 +1,23 @@ import axios, { AxiosInstance } from 'axios'; -import { TransferDataAmount, TransferListResponse, TransferResult } from './ChainIndexingModels'; -import { TransactionStatus, TransferTransactionData } from '../../models/Transaction'; +import { + NftAccountTransactionListResponse, + NftListResponse, + NftResponse, + NftTransactionListResponse, + NftTransactionResponse, + TransferDataAmount, + TransferListResponse, + TransferResult, +} from './ChainIndexingModels'; +import { + NftQueryParams, + TransactionStatus, + TransferTransactionData, + NftModel, +} from '../../models/Transaction'; import { DefaultWalletConfigs } from '../../config/StaticConfig'; +import { croNftApi, MintByCDCRequest } from './NftApi'; +import { splitToChunks } from '../../utils/utils'; export interface IChainIndexingAPI { fetchAllTransferTransactions( @@ -26,6 +42,98 @@ export class ChainIndexingAPI implements IChainIndexingAPI { return new ChainIndexingAPI(axiosClient); } + public async getAccountNFTList(account: string): Promise { + let paginationPage = 1; + const nftsListRequest = await this.axiosClient.get( + `/nfts/accounts/${account}/tokens?page=${paginationPage}`, + ); + const nftsListResponse: NftListResponse = nftsListRequest.data; + + let { pagination } = nftsListResponse; + + const nftLists = nftsListResponse.result; + + while (pagination.total_page > pagination.current_page) { + paginationPage += 1; + // eslint-disable-next-line no-await-in-loop + const pageNftsListRequest = await this.axiosClient.get( + `/nfts/accounts/${account}/tokens?page=${paginationPage}`, + ); + const pageNftsListResponse: NftListResponse = pageNftsListRequest.data; + + pagination = pageNftsListResponse.pagination; + nftLists.push(...pageNftsListResponse.result); + } + + return nftLists; + } + + // eslint-disable-next-line class-methods-use-this + public async getNftListMarketplaceData(nftLists: NftResponse[]): Promise { + const nftListMap: NftModel[] = []; + const payload: MintByCDCRequest[] = nftLists.map(item => { + nftListMap[`${item.denomId}-${item.tokenId}`] = { + ...item, + isMintedByCDC: false, + marketplaceLink: '', + }; + return { + denomId: item.denomId, + tokenIds: [item.tokenId], + }; + }); + + const payloadChunks = splitToChunks(payload, 10); + + try { + const results = await Promise.all( + payloadChunks.map(async chunks => { + return await croNftApi.getNftListMarketplaceData(chunks); + }), + ); + + results.forEach(result => { + result.forEach(item => { + nftListMap[`${item.denomId}-${item.tokenId}`] = { + ...nftListMap[`${item.denomId}-${item.tokenId}`], + isMintedByCDC: item.isMinted, + marketplaceLink: item.link ? item.link : '', + }; + }); + }); + return Object.values(nftListMap); + } catch (e) { + return Object.values(nftListMap); + } + } + + public async getNFTTransferHistory(nftQuery: NftQueryParams): Promise { + let paginationPage = 1; + const { denomId, tokenId } = nftQuery; + const nftsTxListRequest = await this.axiosClient.get( + `/nfts/denoms/${denomId}/tokens/${tokenId}/transfers`, + ); + + const nftTxListResponse: NftTransactionListResponse = nftsTxListRequest.data; + let { pagination } = nftTxListResponse; + + const nftTxLists = nftTxListResponse.result; + + while (pagination.total_page > pagination.current_page) { + paginationPage += 1; + // eslint-disable-next-line no-await-in-loop + const pageNftTxListRequest = await this.axiosClient.get( + `/nfts/denoms/${denomId}/tokens/${tokenId}/transfers?page=${paginationPage}`, + ); + const pageNftTxListResponse: NftTransactionListResponse = pageNftTxListRequest.data; + pagination = pageNftTxListResponse.pagination; + + nftTxLists.push(...pageNftTxListResponse.result); + } + + return nftTxLists; + } + public async fetchAllTransferTransactions( baseAssetSymbol: string, address: string, @@ -74,4 +182,21 @@ export class ChainIndexingAPI implements IChainIndexingAPI { return []; } } + + public async fetchAllAccountNFTsTransactions( + address: string, + ): Promise { + try { + const nftTxsListResponse = await this.axiosClient.get( + `accounts/${address}/messages?order=height.desc&filter.msgType=MsgTransferNFT,MsgMintNFT,MsgIssueDenom`, + ); + return nftTxsListResponse.data; + } catch (e) { + // eslint-disable-next-line no-console + console.log('FAILED_LOADING_NFT_ACCOUNT_TXs', e); + return { + result: [], + }; + } + } } diff --git a/src/service/rpc/ChainIndexingModels.ts b/src/service/rpc/ChainIndexingModels.ts index bf4be8b20..08375ed08 100644 --- a/src/service/rpc/ChainIndexingModels.ts +++ b/src/service/rpc/ChainIndexingModels.ts @@ -31,3 +31,100 @@ export interface TransferResult { export interface TransferListResponse { result: TransferResult[]; } + +/// Nft models + +export interface NftResponse { + denomId: string; + tokenId: string; + drop: string; + tokenBurned: boolean; + tokenName: string; + tokenURI: string; + tokenData: string; + tokenMinter: string; + tokenOwner: string; + tokenMintedAt: string; + tokenLastEditedAt: string; + denomName: string; + denomSchema: string; +} + +export interface NftListResponse { + result: NftResponse[]; + pagination: NftPagination; +} + +export interface NftPagination { + total_record: number; + total_page: number; + current_page: number; + limit: number; +} + +/// NFT transfer history + +export interface NftTransactionData { + uuid: string; + height: number; + msgName: string; + version: number; + msgIndex: number; + recipient: string; + name: string; + txHash: string; + denomId: string; + tokenId: string; + sender: string; +} + +export interface NftTransactionResponse { + denomId: string; + tokenId: string; + drop?: any; + blockHeight: number; + blockHash: string; + blockTime: string; + transactionHash: string; + success: boolean; + messageIndex: number; + messageType: string; + data: NftTransactionData; +} + +export interface NftTransactionListResponse { + result: NftTransactionResponse[]; + pagination: NftPagination; +} + +/// NFT account transactions data + +export interface NftAccountTransactionData { + msgIndex: number; + recipient: string; + txHash: string; + uuid: string; + height: number; + sender: string; + denomId: string; + msgName: string; + tokenId: string; + version: number; + name: string; +} + +export interface NftAccountTransactionResponse { + account: string; + blockHeight: number; + blockHash: string; + blockTime: string; + transactionHash: string; + success: boolean; + messageIndex: number; + messageType: string; + data: NftAccountTransactionData; +} + +export interface NftAccountTransactionListResponse { + result: NftAccountTransactionResponse[]; +} diff --git a/src/service/rpc/NftApi.ts b/src/service/rpc/NftApi.ts new file mode 100644 index 000000000..bad57734c --- /dev/null +++ b/src/service/rpc/NftApi.ts @@ -0,0 +1,49 @@ +import axios, { AxiosInstance } from 'axios'; +import { NV_GRAPHQL_API_ENDPOINT } from '../../config/StaticConfig'; + +export interface MintByCDCRequest { + denomId: String; + tokenIds: [String]; +} + +export interface INftApi { + getNftListMarketplaceData(payload: MintByCDCRequest[]): Promise; +} + +export class NftApi implements INftApi { + private readonly axiosClient: AxiosInstance; + + constructor() { + this.axiosClient = axios.create({ + baseURL: NV_GRAPHQL_API_ENDPOINT, + }); + } + + public async getNftListMarketplaceData(payload: MintByCDCRequest[]): Promise { + const result = await this.axiosClient.post('', { + operationName: null, + variables: { + payload, + }, + query: ` query ($payload: [MintByCDCRequest!]!) { + isMintedByCDC( + payload: $payload + ) { + link + isMinted + denomId + tokenId + } + } + `, + }); + + if (result.status !== 200 || result.data.errors) { + return []; + } + + return result.data.data.isMintedByCDC; + } +} + +export const croNftApi = new NftApi(); diff --git a/src/service/signers/IpcRender.ts b/src/service/signers/IpcRender.ts index 55e9fa07b..a0ab305be 100644 --- a/src/service/signers/IpcRender.ts +++ b/src/service/signers/IpcRender.ts @@ -1,4 +1,4 @@ -import { Bytes } from '@crypto-com/chain-jslib/lib/dist/utils/bytes/bytes'; +import { Bytes } from '@crypto-org-chain/chain-jslib/lib/dist/utils/bytes/bytes'; import { ISignerProvider } from './SignerProvider'; let electron: any; diff --git a/src/service/signers/LedgerSigner.ts b/src/service/signers/LedgerSigner.ts index e66f6c071..7feb5e037 100644 --- a/src/service/signers/LedgerSigner.ts +++ b/src/service/signers/LedgerSigner.ts @@ -1,4 +1,4 @@ -import { Bytes } from '@crypto-com/chain-jslib/lib/dist/utils/bytes/bytes'; +import { Bytes } from '@crypto-org-chain/chain-jslib/lib/dist/utils/bytes/bytes'; import bech32 from 'bech32'; export class LedgerSigner { diff --git a/src/service/signers/LedgerTransactionSigner.ts b/src/service/signers/LedgerTransactionSigner.ts index 516d3df38..ca51a660f 100644 --- a/src/service/signers/LedgerTransactionSigner.ts +++ b/src/service/signers/LedgerTransactionSigner.ts @@ -1,5 +1,5 @@ -import { Bytes } from '@crypto-com/chain-jslib/lib/dist/utils/bytes/bytes'; -import sdk from '@crypto-com/chain-jslib'; +import { Bytes } from '@crypto-org-chain/chain-jslib/lib/dist/utils/bytes/bytes'; +import sdk from '@crypto-org-chain/chain-jslib'; import { Big, Units } from '../../utils/ChainJsLib'; import { FIXED_DEFAULT_FEE, @@ -261,4 +261,37 @@ export class LedgerTransactionSigner implements ITransactionSigner { .toSigned() .getHexEncoded(); } + + async signNFTTransfer(transaction: any, decryptedPhrase: string) { + const { cro, rawTx } = this.getTransactionInfo(decryptedPhrase, transaction); + + const msgTransferNFT = new cro.nft.MsgTransferNFT({ + id: transaction.tokenId, + sender: transaction.sender, + denomId: transaction.denomId, + recipient: transaction.recipient, + }); + + const pubkeyoriginal = ( + await this.signerProvider.getPubKey(this.addressIndex, false) + ).toUint8Array(); + const pubkey = Bytes.fromUint8Array(pubkeyoriginal.slice(1)); + const signableTx = rawTx + .appendMessage(msgTransferNFT) + .addSigner({ + publicKey: pubkey, + accountNumber: new Big(transaction.accountNumber), + accountSequence: new Big(transaction.accountSequence), + signMode: 0, // LEGACY_AMINO_JSON = 0, DIRECT = 1, + }) + .toSignable(); + + const bytesMessage: Bytes = signableTx.toSignDocument(0); + const signature = await this.signerProvider.sign(bytesMessage); + + return signableTx + .setSignature(0, signature) + .toSigned() + .getHexEncoded(); + } } diff --git a/src/service/signers/LedgerWalletSignerProviderNative.ts b/src/service/signers/LedgerWalletSignerProviderNative.ts index 35184d4c7..7dad0fdcd 100644 --- a/src/service/signers/LedgerWalletSignerProviderNative.ts +++ b/src/service/signers/LedgerWalletSignerProviderNative.ts @@ -1,4 +1,4 @@ -import { Bytes } from '@crypto-com/chain-jslib/lib/dist/utils/bytes/bytes'; +import { Bytes } from '@crypto-org-chain/chain-jslib/lib/dist/utils/bytes/bytes'; import { IpcRender } from './IpcRender'; import { ISignerProvider } from './SignerProvider'; diff --git a/src/service/signers/LedgerWalletSignerProviderWebusb.ts b/src/service/signers/LedgerWalletSignerProviderWebusb.ts index 78dcdb828..295d8008d 100644 --- a/src/service/signers/LedgerWalletSignerProviderWebusb.ts +++ b/src/service/signers/LedgerWalletSignerProviderWebusb.ts @@ -1,4 +1,4 @@ -import { Bytes } from '@crypto-com/chain-jslib/lib/dist/utils/bytes/bytes'; +import { Bytes } from '@crypto-org-chain/chain-jslib/lib/dist/utils/bytes/bytes'; import { ISignerProvider } from './SignerProvider'; import { LedgerSignerWebusb } from './LedgerSignerWebusb'; @@ -9,12 +9,16 @@ export class LedgerWalletSignerProviderWebusb implements ISignerProvider { this.provider = new LedgerSignerWebusb(); } - public async getPubKey(index: number,showLedgerDisplay: boolean): Promise { - const result = await this.provider.enable(index, 'cro',showLedgerDisplay); // dummy value + public async getPubKey(index: number, showLedgerDisplay: boolean): Promise { + const result = await this.provider.enable(index, 'cro', showLedgerDisplay); // dummy value return result[1]; } - public async getAddress(index: number, addressPrefix: string, showLedgerDisplay: boolean): Promise { + public async getAddress( + index: number, + addressPrefix: string, + showLedgerDisplay: boolean, + ): Promise { const result = await this.provider.enable(index, addressPrefix, showLedgerDisplay); return result[0]; } diff --git a/src/service/signers/SignerProvider.ts b/src/service/signers/SignerProvider.ts index d138a05fe..78258d74d 100644 --- a/src/service/signers/SignerProvider.ts +++ b/src/service/signers/SignerProvider.ts @@ -1,4 +1,4 @@ -import { Bytes } from '@crypto-com/chain-jslib/lib/dist/utils/bytes/bytes'; +import { Bytes } from '@crypto-org-chain/chain-jslib/lib/dist/utils/bytes/bytes'; export interface ISignerProvider { getPubKey(index: number, showLedgerDisplay: boolean): Promise; diff --git a/src/service/signers/TransactionSigner.ts b/src/service/signers/TransactionSigner.ts index 45d250a58..34d7e8a2e 100644 --- a/src/service/signers/TransactionSigner.ts +++ b/src/service/signers/TransactionSigner.ts @@ -1,4 +1,4 @@ -import sdk from '@crypto-com/chain-jslib'; +import sdk from '@crypto-org-chain/chain-jslib'; import { Big, HDKey, Secp256k1KeyPair, Units } from '../../utils/ChainJsLib'; import { FIXED_DEFAULT_FEE, @@ -13,6 +13,7 @@ import { UndelegateTransactionUnsigned, RedelegateTransactionUnsigned, VoteTransactionUnsigned, + NFTTransferUnsigned, } from './TransactionSupported'; export interface ITransactionSigner { @@ -110,6 +111,31 @@ export class TransactionSigner implements ITransactionSigner { .getHexEncoded(); } + public async signNFTTransfer(transaction: NFTTransferUnsigned, phrase: string): Promise { + const { cro, keyPair, rawTx } = this.getTransactionInfo(phrase, transaction); + + const msgTransferNFT = new cro.nft.MsgTransferNFT({ + id: transaction.tokenId, + sender: transaction.sender, + denomId: transaction.denomId, + recipient: transaction.recipient, + }); + + const signableTx = rawTx + .appendMessage(msgTransferNFT) + .addSigner({ + publicKey: keyPair.getPubKey(), + accountNumber: new Big(transaction.accountNumber), + accountSequence: new Big(transaction.accountSequence), + }) + .toSignable(); + + return signableTx + .setSignature(0, keyPair.sign(signableTx.toSignDoc(0))) + .toSigned() + .getHexEncoded(); + } + public async signDelegateTx( transaction: DelegateTransactionUnsigned, phrase: string, diff --git a/src/service/signers/TransactionSupported.ts b/src/service/signers/TransactionSupported.ts index a6bc0cf4b..2e9acb8d4 100644 --- a/src/service/signers/TransactionSupported.ts +++ b/src/service/signers/TransactionSupported.ts @@ -18,6 +18,13 @@ export interface VoteTransactionUnsigned extends TransactionUnsigned { proposalID: string; } +export interface NFTTransferUnsigned extends TransactionUnsigned { + tokenId: string; + denomId: string; + sender: string; + recipient: string; +} + export interface DelegateTransactionUnsigned extends TransactionUnsigned { delegatorAddress: string; validatorAddress: string; diff --git a/src/storage/DatabaseManager.ts b/src/storage/DatabaseManager.ts index 7dc912ad8..d889cad2d 100644 --- a/src/storage/DatabaseManager.ts +++ b/src/storage/DatabaseManager.ts @@ -27,6 +27,12 @@ export class DatabaseManager { public readonly proposalStore: Datastore; + public readonly nftStore: Datastore; + + public readonly nftTransferHistoryStore: Datastore; + + public readonly nftAccountTxStore: Datastore; + // This is for configuration that span across all wallets public readonly generalConfigStore: Datastore; @@ -42,6 +48,9 @@ export class DatabaseManager { this.seedStore = getStore(namespace, 'seeds'); this.validatorStore = getStore(namespace, 'validators'); this.proposalStore = getStore(namespace, 'proposals'); + this.nftStore = getStore(namespace, 'nftStore'); this.generalConfigStore = getStore(namespace, 'generalConfigStore'); + this.nftTransferHistoryStore = getStore(namespace, 'nftTransferHistoryStore'); + this.nftAccountTxStore = getStore(namespace, 'nftAccountTxStore'); } } diff --git a/src/storage/StorageService.ts b/src/storage/StorageService.ts index 3bcff708a..95f48f324 100644 --- a/src/storage/StorageService.ts +++ b/src/storage/StorageService.ts @@ -13,6 +13,10 @@ import { UserAsset, } from '../models/UserAsset'; import { + NftAccountTransactionList, + NftList, + NftQueryParams, + NftTransactionHistory, ProposalList, RewardTransactionList, StakingTransactionData, @@ -286,6 +290,23 @@ export class StorageService { return this.db.transferStore.findOne({ walletId }); } + public async saveNFTAccountTransactions(nftAccountTransactionList: NftAccountTransactionList) { + if (nftAccountTransactionList.transactions.length === 0) { + return Promise.resolve(); + } + await this.db.nftAccountTxStore.remove( + { walletId: nftAccountTransactionList.walletId }, + { multi: true }, + ); + return this.db.nftAccountTxStore.insert(nftAccountTransactionList); + } + + public async retrieveAllNFTAccountTransactions( + walletId: string, + ): Promise { + return this.db.nftAccountTxStore.findOne({ walletId }); + } + public async saveValidators(validatorList: ValidatorList) { if (validatorList.validators.length === 0) { return Promise.resolve(); @@ -309,4 +330,35 @@ export class StorageService { public async retrieveAllProposals(chainId: string) { return this.db.proposalStore.findOne({ chainId }); } + + public async saveNFTs(nftList: NftList) { + if (!nftList) { + return Promise.resolve(); + } + await this.db.nftStore.remove({ walletId: nftList.walletId }, { multi: true }); + return this.db.nftStore.insert(nftList); + } + + public async retrieveAllNfts(walletId: string) { + return this.db.nftStore.findOne({ walletId }); + } + + public async saveNFTTransferHistory(nftTransactionHistory: NftTransactionHistory) { + if (!nftTransactionHistory || nftTransactionHistory.transfers.length === 0) { + return Promise.resolve(); + } + await this.db.nftTransferHistoryStore.remove( + { + walletId: nftTransactionHistory.walletId, + nftQuery: nftTransactionHistory.nftQuery, + }, + { multi: true }, + ); + + return this.db.nftTransferHistoryStore.insert(nftTransactionHistory); + } + + public async retrieveNFTTransferHistory(walletId: string, nftQuery: NftQueryParams) { + return this.db.nftTransferHistoryStore.findOne({ walletId, nftQuery }); + } } diff --git a/src/svg/IconNft.tsx b/src/svg/IconNft.tsx new file mode 100644 index 000000000..74687388d --- /dev/null +++ b/src/svg/IconNft.tsx @@ -0,0 +1,23 @@ +import * as React from 'react'; + +function IconNft(props) { + return ( + + + + + ); +} + +export default IconNft; diff --git a/src/svg/IconPlayer.tsx b/src/svg/IconPlayer.tsx new file mode 100644 index 000000000..34c123052 --- /dev/null +++ b/src/svg/IconPlayer.tsx @@ -0,0 +1,24 @@ +import * as React from 'react'; + +function IconPlayer(props) { + return ( + + + + ); +} + +export default IconPlayer; diff --git a/src/svg/IconTick.tsx b/src/svg/IconTick.tsx index c7954a63e..02749dd68 100644 --- a/src/svg/IconTick.tsx +++ b/src/svg/IconTick.tsx @@ -2,14 +2,12 @@ import * as React from 'react'; function IconTick(props) { return ( - - + ); diff --git a/src/utils/ChainJsLib.ts b/src/utils/ChainJsLib.ts index c3cfd3e43..cf944d055 100644 --- a/src/utils/ChainJsLib.ts +++ b/src/utils/ChainJsLib.ts @@ -1,4 +1,4 @@ -import chainjs from '@crypto-com/chain-jslib'; +import chainjs from '@crypto-org-chain/chain-jslib'; export const { HDKey, Units, Secp256k1KeyPair } = chainjs; export const { Bytes, Big } = chainjs.utils; diff --git a/src/utils/NumberUtils.ts b/src/utils/NumberUtils.ts index e4164e33e..d9750d501 100644 --- a/src/utils/NumberUtils.ts +++ b/src/utils/NumberUtils.ts @@ -47,46 +47,22 @@ export function getUIDynamicAmount(amount: string, currentAsset: UserAsset) { return finalAmount; } +export const formatLargeNumber = (n): string => { + if (n < 1e3) return `${n}`; + if (n >= 1e3 && n < 1e6) return `${+(n / 1e3).toFixed(1)}K`; + if (n >= 1e6 && n < 1e9) return `${+(n / 1e6).toFixed(1)}M`; + if (n >= 1e9 && n < 1e12) return `${+(n / 1e9).toFixed(1)}B`; + if (n >= 1e12) return `${+(n / 1e12).toFixed(1)}T`; + return ``; +}; + export function getUIVoteAmount(amount: string, asset: UserAsset) { const exp = Big(10).pow(asset.decimals); const voteAmount = Big(amount) .div(exp) - .toFixed(); - let returnVoteAmount = ''; - if ( - Big(voteAmount) - .div(Big(10).pow(9)) - .gte(Big(1)) - ) { - returnVoteAmount = `${Big(voteAmount) - .div(Big(10).pow(9)) - .toFixed(3) - .toString()}B`; - } else if ( - Big(voteAmount) - .div(Big(10).pow(6)) - .gte(Big(1)) - ) { - returnVoteAmount = `${Big(voteAmount) - .div(Big(10).pow(6)) - .toFixed(3) - .toString()}M`; - } else if ( - Big(voteAmount) - .div(Big(10).pow(3)) - .gte(Big(1)) - ) { - returnVoteAmount = `${Big(voteAmount) - .div(Big(10).pow(3)) - .toFixed(3) - .toString()}K`; - } else { - returnVoteAmount = `${Big(voteAmount) - .div(Big(10)) - .toFixed(3) - .toString()}`; - } - return returnVoteAmount; + .toNumber(); + + return formatLargeNumber(voteAmount); } export function getCurrentMinAssetAmount(userAsset: UserAsset) { diff --git a/src/utils/TransactionUtils.ts b/src/utils/TransactionUtils.ts index 8e4c7b6ad..55228ea41 100644 --- a/src/utils/TransactionUtils.ts +++ b/src/utils/TransactionUtils.ts @@ -1,5 +1,8 @@ import Big from 'big.js'; -import { AddressType, AddressValidator } from '@crypto-com/chain-jslib/lib/dist/utils/address'; +import { + AddressType, + AddressValidator, +} from '@crypto-org-chain/chain-jslib/lib/dist/utils/address'; import { Session } from '../models/Session'; import { UserAsset } from '../models/UserAsset'; diff --git a/src/utils/utils.tsx b/src/utils/utils.tsx index 1c5401b26..3484f9c35 100644 --- a/src/utils/utils.tsx +++ b/src/utils/utils.tsx @@ -36,3 +36,25 @@ export function middleEllipsis(str: string, len: number) { export function ellipsis(str: string, len: number) { return str.length <= len ? `${str}` : `${str.substr(0, len)}...`; } + +export function isJson(val: string) { + try { + JSON.parse(val); + } catch (e) { + return false; + } + return true; +} + +export function splitToChunks(arr: any[], len: number) { + const arrays: any[] = []; + // const result = + for (let i = 0, j = arr.length; i < j; i += len) { + arrays.push(arr.slice(i, i + len)); + } + return arrays; +} + +export function sleep(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); +} diff --git a/tests/src/test.ts b/tests/src/test.ts index 5a236c460..f420ebcc6 100644 --- a/tests/src/test.ts +++ b/tests/src/test.ts @@ -5,7 +5,7 @@ import { LedgerSigner } from '../../src/service/signers/LedgerSigner'; import { ISignerProvider } from '../../src/service/signers/SignerProvider'; import { LedgerTransactionSigner } from '../../src/service/signers/LedgerTransactionSigner'; import {CustomDevNet} from '../../src/config/StaticConfig'; -import { Bytes } from '@crypto-com/chain-jslib/lib/dist/utils/bytes/bytes'; +import { Bytes } from '@crypto-org-chain/chain-jslib/lib/dist/utils/bytes/bytes'; import {NodeRpcService} from "../../src/service/rpc/NodeRpcService"; const { exec } = require("child_process"); import chai from "chai"; diff --git a/yarn.lock b/yarn.lock index 074d220b6..b1fcc472a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1253,6 +1253,26 @@ ripemd160 "^2.0.2" sha.js "^2.4.11" +"@cosmjs/amino@0.25.0-alpha.2": + version "0.25.0-alpha.2" + resolved "https://registry.yarnpkg.com/@cosmjs/amino/-/amino-0.25.0-alpha.2.tgz#1b2d4e8cf7bfcdfd49f58d35681452607757f0c3" + integrity sha512-q8T4ORWB185SJ60bd/wXCEYG+65X3rMwzMZKpcbw43wBEPIYx104yqV62dR/BtlC+5IKDXPcP6kw/nTHo2QFWw== + dependencies: + "@cosmjs/crypto" "^0.25.0-alpha.2" + "@cosmjs/encoding" "^0.25.0-alpha.2" + "@cosmjs/math" "^0.25.0-alpha.2" + "@cosmjs/utils" "^0.25.0-alpha.2" + +"@cosmjs/amino@^0.25.0-alpha.2", "@cosmjs/amino@^0.25.4": + version "0.25.4" + resolved "https://registry.yarnpkg.com/@cosmjs/amino/-/amino-0.25.4.tgz#478da0e5933b50d22e412e17dcf6784eeeb7d937" + integrity sha512-S22PlzC/VoJirv5UpDYe4XIVtOHKHxGLYgpgBkv10P4vpEhD872R0G7dRfiZZ35lMbu0+vvJxn3e/pEOEVGcuA== + dependencies: + "@cosmjs/crypto" "^0.25.4" + "@cosmjs/encoding" "^0.25.4" + "@cosmjs/math" "^0.25.4" + "@cosmjs/utils" "^0.25.4" + "@cosmjs/crypto@^0.24.1": version "0.24.1" resolved "https://registry.yarnpkg.com/@cosmjs/crypto/-/crypto-0.24.1.tgz#62da59c32b26344f26b10dd31a02b93655586d04" @@ -1271,6 +1291,22 @@ sha.js "^2.4.11" unorm "^1.5.0" +"@cosmjs/crypto@^0.25.0-alpha.2", "@cosmjs/crypto@^0.25.4": + version "0.25.4" + resolved "https://registry.yarnpkg.com/@cosmjs/crypto/-/crypto-0.25.4.tgz#0e526a8939773b43799bd013252642e08e389f75" + integrity sha512-xm7o2xMQIERFjG+hBa/5f+l1CNdXrXzGqhICo3VJuKsuFRtOtEw3w0dbV+3DOp2oxaUQvLBkLqdYXNBL+lmHlQ== + dependencies: + "@cosmjs/encoding" "^0.25.4" + "@cosmjs/math" "^0.25.4" + "@cosmjs/utils" "^0.25.4" + bip39 "^3.0.2" + bn.js "^4.11.8" + elliptic "^6.5.3" + js-sha3 "^0.8.0" + libsodium-wrappers "^0.7.6" + ripemd160 "^2.0.2" + sha.js "^2.4.11" + "@cosmjs/encoding@0.23.1": version "0.23.1" resolved "https://registry.yarnpkg.com/@cosmjs/encoding/-/encoding-0.23.1.tgz#b51cd2813499cfdeeb0f9cc7d050a45eb8b27bf4" @@ -1289,6 +1325,15 @@ bech32 "^1.1.4" readonly-date "^1.0.0" +"@cosmjs/encoding@^0.25.0-alpha.2", "@cosmjs/encoding@^0.25.4": + version "0.25.4" + resolved "https://registry.yarnpkg.com/@cosmjs/encoding/-/encoding-0.25.4.tgz#7589f350410e91728f8377fef782117382873da6" + integrity sha512-wYwYYbCGwDyhaROX6EyZBaiMqpTME8oo7KaRChS6O/6w5hZcfWAyo0NVaHCv8atxt/h0lYroazEXxOVKY+uo6A== + dependencies: + base64-js "^1.3.0" + bech32 "^1.1.4" + readonly-date "^1.0.0" + "@cosmjs/json-rpc@^0.24.1": version "0.24.1" resolved "https://registry.yarnpkg.com/@cosmjs/json-rpc/-/json-rpc-0.24.1.tgz#5de8dde2b732639199785e4ff449039d92726e12" @@ -1297,6 +1342,14 @@ "@cosmjs/stream" "^0.24.1" xstream "^11.14.0" +"@cosmjs/json-rpc@^0.25.0-alpha.2", "@cosmjs/json-rpc@^0.25.4": + version "0.25.4" + resolved "https://registry.yarnpkg.com/@cosmjs/json-rpc/-/json-rpc-0.25.4.tgz#4109ebe5f6ab21561767c25e13ad7734dee94526" + integrity sha512-X3BzvzUpThD2o9+Ak2+icAqm8AAdWhCGB6Hl229DvKG1NUnXEKdwSxlI/VNw0IKT7ljy47Jv56syQiK5nFdXRQ== + dependencies: + "@cosmjs/stream" "^0.25.4" + xstream "^11.14.0" + "@cosmjs/launchpad@^0.24.1": version "0.24.1" resolved "https://registry.yarnpkg.com/@cosmjs/launchpad/-/launchpad-0.24.1.tgz#fe7e80734dfd60ea093429a646d7a38634c70134" @@ -1323,7 +1376,14 @@ dependencies: bn.js "^4.11.8" -"@cosmjs/proto-signing@^0.24.1": +"@cosmjs/math@^0.25.0-alpha.2", "@cosmjs/math@^0.25.4": + version "0.25.4" + resolved "https://registry.yarnpkg.com/@cosmjs/math/-/math-0.25.4.tgz#1a70b26c18f990654dfd195a1a2b544b48269572" + integrity sha512-mnf5TgDObjx1yt1Vxkr3k/vncTL4FPRu3eSHjYM+EyQeNmy/Dld0fHFWxELeGqlQ09kVuqFY1jkKG2R96YRHww== + dependencies: + bn.js "^4.11.8" + +"@cosmjs/proto-signing@0.24.1", "@cosmjs/proto-signing@^0.24.1": version "0.24.1" resolved "https://registry.yarnpkg.com/@cosmjs/proto-signing/-/proto-signing-0.24.1.tgz#4ee38d4e0d29c626344fb832235fda8e8d645c28" integrity sha512-/rnyNx+FlG6b6O+igsb42eMN1/RXY+pTrNnAE8/YZaRloP9A6MXiTMO5JdYSTcjaD0mEVhejiy96bcyflKYXBg== @@ -1332,6 +1392,15 @@ long "^4.0.0" protobufjs "~6.10.2" +"@cosmjs/proto-signing@^0.25.0-alpha.2": + version "0.25.4" + resolved "https://registry.yarnpkg.com/@cosmjs/proto-signing/-/proto-signing-0.25.4.tgz#82a2bdfc105f66c8d9cdf2cfbb86e6da960ab49a" + integrity sha512-W9qJTH0LkOY/WsCe/V2hv6px7gZZxwh8TpLEKSSh4XOUjtqKHcr2jhbdXfi1CQm5lgbvEsovdzVv8VgbtgJxDQ== + dependencies: + "@cosmjs/amino" "^0.25.4" + long "^4.0.0" + protobufjs "~6.10.2" + "@cosmjs/socket@^0.24.1": version "0.24.1" resolved "https://registry.yarnpkg.com/@cosmjs/socket/-/socket-0.24.1.tgz#02ae2024890e71d3fc9389193a512d60a8074b99" @@ -1342,6 +1411,32 @@ ws "^6.2.0" xstream "^11.14.0" +"@cosmjs/socket@^0.25.0-alpha.2", "@cosmjs/socket@^0.25.4": + version "0.25.4" + resolved "https://registry.yarnpkg.com/@cosmjs/socket/-/socket-0.25.4.tgz#f32bc7bb347ac26ee1be0c0a57614712bbb6c28c" + integrity sha512-hcL+2kISZ1qqgviNB8OFSzMyYGdiKsBp+j582WYJa+5h9rpZrNWJSm2BFe8hah5AvfYsVCZX1kn7jRu8dZpUnA== + dependencies: + "@cosmjs/stream" "^0.25.4" + isomorphic-ws "^4.0.1" + ws "^7" + xstream "^11.14.0" + +"@cosmjs/stargate@0.25.0-alpha.2": + version "0.25.0-alpha.2" + resolved "https://registry.yarnpkg.com/@cosmjs/stargate/-/stargate-0.25.0-alpha.2.tgz#db6fa0002e96f62875e5b72378e24bd19ef9478f" + integrity sha512-r6VT720EuF6yPwS1WGPPUAPUOfD5aVIRlVJNJHkePWGg4l+ztJtoUbr7QN1CoPrxvG3b+WflNug1EQ7dG44UsA== + dependencies: + "@confio/ics23" "^0.6.3" + "@cosmjs/amino" "^0.25.0-alpha.2" + "@cosmjs/encoding" "^0.25.0-alpha.2" + "@cosmjs/math" "^0.25.0-alpha.2" + "@cosmjs/proto-signing" "^0.25.0-alpha.2" + "@cosmjs/stream" "^0.25.0-alpha.2" + "@cosmjs/tendermint-rpc" "^0.25.0-alpha.2" + "@cosmjs/utils" "^0.25.0-alpha.2" + long "^4.0.0" + protobufjs "~6.10.2" + "@cosmjs/stargate@^0.24.0-alpha.22": version "0.24.1" resolved "https://registry.yarnpkg.com/@cosmjs/stargate/-/stargate-0.24.1.tgz#cae252cab9499120c2483e4ae9ecb8a14b618205" @@ -1365,6 +1460,28 @@ dependencies: xstream "^11.14.0" +"@cosmjs/stream@^0.25.0-alpha.2", "@cosmjs/stream@^0.25.4": + version "0.25.4" + resolved "https://registry.yarnpkg.com/@cosmjs/stream/-/stream-0.25.4.tgz#9b978ee27355d6d4268ebed7be6d6d701e6ccd50" + integrity sha512-Z/if46lnNyiGojzQgSi4ztaqDCJ4gljlmGw6hX/7MrPn5dtmaSqWjLep5CMh7moiR9ZaAeqRPTdUsb99CjiKMQ== + dependencies: + xstream "^11.14.0" + +"@cosmjs/tendermint-rpc@0.25.0-alpha.2": + version "0.25.0-alpha.2" + resolved "https://registry.yarnpkg.com/@cosmjs/tendermint-rpc/-/tendermint-rpc-0.25.0-alpha.2.tgz#15e31d8a9385085740ec71ea0029b9ebb9dd757e" + integrity sha512-1xK8mPwFWiWnyGafZhAdwYfcYmXl1l7UxQRR3yI2Q3kDk7CQhT87mgeAd56jw9JOaZvLYKKTgCRZkLNiKjXNew== + dependencies: + "@cosmjs/crypto" "^0.25.0-alpha.2" + "@cosmjs/encoding" "^0.25.0-alpha.2" + "@cosmjs/json-rpc" "^0.25.0-alpha.2" + "@cosmjs/math" "^0.25.0-alpha.2" + "@cosmjs/socket" "^0.25.0-alpha.2" + "@cosmjs/stream" "^0.25.0-alpha.2" + axios "^0.21.1" + readonly-date "^1.0.0" + xstream "^11.14.0" + "@cosmjs/tendermint-rpc@^0.24.1": version "0.24.1" resolved "https://registry.yarnpkg.com/@cosmjs/tendermint-rpc/-/tendermint-rpc-0.24.1.tgz#625b650c774cb507f48b582b95727d0d26436db9" @@ -1380,18 +1497,42 @@ readonly-date "^1.0.0" xstream "^11.14.0" +"@cosmjs/tendermint-rpc@^0.25.0-alpha.2": + version "0.25.4" + resolved "https://registry.yarnpkg.com/@cosmjs/tendermint-rpc/-/tendermint-rpc-0.25.4.tgz#8fcf8850ecbc4851a2763303bda8962779ee6d2b" + integrity sha512-F8qiSTtDcS7ZkfvX4RfodpoMG7C7kwLJ8P7umSeWe0wkLaO6NYoKcqxBGjc6E7XVy+XtJDyfF3gqNDJz5/Jtpg== + dependencies: + "@cosmjs/crypto" "^0.25.4" + "@cosmjs/encoding" "^0.25.4" + "@cosmjs/json-rpc" "^0.25.4" + "@cosmjs/math" "^0.25.4" + "@cosmjs/socket" "^0.25.4" + "@cosmjs/stream" "^0.25.4" + axios "^0.21.1" + readonly-date "^1.0.0" + xstream "^11.14.0" + "@cosmjs/utils@^0.24.1": version "0.24.1" resolved "https://registry.yarnpkg.com/@cosmjs/utils/-/utils-0.24.1.tgz#0adfefe63b7f17222bc2bc12f71296f35e7ad378" integrity sha512-VA3WFx1lMFb7esp9BqHWkDgMvHoA3D9w+uDRvWhVRpUpDc7RYHxMbWExASjz+gNblTCg556WJGzF64tXnf9tdQ== -"@crypto-com/chain-jslib@0.0.15": - version "0.0.15" - resolved "https://registry.yarnpkg.com/@crypto-com/chain-jslib/-/chain-jslib-0.0.15.tgz#fd898f823aec915cbf576689602d0d2a0b978c48" - integrity sha512-uWmGj7PCUcHloFb/zjj8QQA8gz3V0UaaMx2ihavs1Tu7s4TiR+uNIF2dvD6L9Do8WR9RdXrdmudlejLSonBXqA== +"@cosmjs/utils@^0.25.0-alpha.2", "@cosmjs/utils@^0.25.4": + version "0.25.4" + resolved "https://registry.yarnpkg.com/@cosmjs/utils/-/utils-0.25.4.tgz#6e617543476e9e1f72bfcb6e263b273e01e4ba93" + integrity sha512-SRkE+Nc0hwuWdsUCQCF3HNWcxhm8UtTg2fIo8CJpecusYfKSGKzkeL1O/Ja/+xDpuTAXW2s2mfVyaAW5b5pHVQ== + +"@crypto-org-chain/chain-jslib@0.0.22": + version "0.0.22" + resolved "https://registry.yarnpkg.com/@crypto-org-chain/chain-jslib/-/chain-jslib-0.0.22.tgz#5de61eb1b7c249f810b719c3801cd80c1a51b106" + integrity sha512-EZI2Rv6vOHBnzNjBrlu/sF0vn3DQlNfCp+Sae9UFdThL7uGs8dZf16/r3Gyrk4NkC/7TXDMZvBwoiv5pkOfxMQ== dependencies: + "@cosmjs/amino" "0.25.0-alpha.2" "@cosmjs/encoding" "0.23.1" "@cosmjs/math" "0.23.1" + "@cosmjs/proto-signing" "0.24.1" + "@cosmjs/stargate" "0.25.0-alpha.2" + "@cosmjs/tendermint-rpc" "0.25.0-alpha.2" axios "0.21.1" bech32 "1.1.4" big.js "6.0.0" @@ -1405,6 +1546,7 @@ protobufjs "6.10.1" randombytes "2.1.0" secp256k1 "4.0.2" + snakecase-keys "3.2.1" "@csstools/convert-colors@^1.4.0": version "1.4.0" @@ -1955,7 +2097,7 @@ dependencies: "@babel/core" ">=7.9.0" -"@stylelint/postcss-markdown@^0.36.1", "@stylelint/postcss-markdown@^0.36.2": +"@stylelint/postcss-markdown@^0.36.2": version "0.36.2" resolved "https://registry.yarnpkg.com/@stylelint/postcss-markdown/-/postcss-markdown-0.36.2.tgz#0a540c4692f8dcdfc13c8e352c17e7bfee2bb391" integrity sha512-2kGbqUVJUGE8dM+bMzXG/PYUWKkjLIkRLWNh39OaADkiabDRdw8ATFCgbMz5xdIcvwspPAluSL7uY+ZiTWdWmQ== @@ -3735,6 +3877,11 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= +balanced-match@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-2.0.0.tgz#dc70f920d78db8b858535795867bf48f820633d9" + integrity sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA== + base-x@^3.0.2: version "3.0.8" resolved "https://registry.yarnpkg.com/base-x/-/base-x-3.0.8.tgz#1e1106c2537f0162e8b52474a557ebb09000018d" @@ -4411,6 +4558,14 @@ chalk@^4.0.0, chalk@^4.1.0: ansi-styles "^4.1.0" supports-color "^7.1.0" +chalk@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.1.tgz#c80b3fab28bf6371e6863325eee67e618b77e6ad" + integrity sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + char-regex@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" @@ -5200,10 +5355,10 @@ css-tree@^1.1.2: mdn-data "2.0.14" source-map "^0.6.1" -css-what@^3.2.1: - version "3.4.2" - resolved "https://registry.yarnpkg.com/css-what/-/css-what-3.4.2.tgz#ea7026fcb01777edbde52124e21f327e7ae950e4" - integrity sha512-ACUm3L0/jiZTqfzRM3Hi9Q8eZqd6IK37mMWPLz9PJxkLWllYeRf+EHUSHYEtFop2Eqytaq1FizFVh7XfBnXCDQ== +css-what@5.0.1, css-what@^3.2.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/css-what/-/css-what-5.0.1.tgz#3efa820131f4669a8ac2408f9c32e7c7de9f4cad" + integrity sha512-FYDTSHb/7KXsWICVsxdmiExPjCfRC4qRFBdVwv7Ax9hMnvMmEjP9RfxTEZ3qPZGmADDn2vAKSo9UcN1jKVYscg== css@^2.0.0: version "2.2.4" @@ -5472,7 +5627,7 @@ deep-is@^0.1.3, deep-is@~0.1.3: resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= -deepmerge@^4.2.2: +deepmerge@^4.0.0, deepmerge@^4.2.2: version "4.2.2" resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955" integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg== @@ -5653,13 +5808,12 @@ dns-equal@^1.0.0: resolved "https://registry.yarnpkg.com/dns-equal/-/dns-equal-1.0.0.tgz#b39e7f1da6eb0a75ba9c17324b34753c47e0654d" integrity sha1-s55/HabrCnW6nBcySzR1PEfgZU0= -dns-packet@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-1.3.1.tgz#12aa426981075be500b910eedcd0b47dd7deda5a" - integrity sha512-0UxfQkMhYAUaZI+xrNZOz/as5KgDU0M/fQ9b6SpkyLbk3GEswDi6PADJVaYJradtRVsRIlF1zLyOodbcTCDzUg== +dns-packet@5.2.2, dns-packet@^1.3.1: + version "5.2.2" + resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-5.2.2.tgz#e4c7d12974cc320b0c0d4b9bbbf68ac151cfe81e" + integrity sha512-sQN+vLwC3PvOXiCH/oHcdzML2opFeIdVh8gjjMZrM45n4dR80QF6o3AzInQy6F9Eoc0VJYog4JpQTilt4RFLYQ== dependencies: - ip "^1.1.0" - safe-buffer "^5.0.1" + ip "^1.1.5" dns-txt@^2.0.2: version "2.0.2" @@ -6786,7 +6940,7 @@ fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== -fast-glob@^3.1.1, fast-glob@^3.2.4, fast-glob@^3.2.5: +fast-glob@^3.1.1, fast-glob@^3.2.5: version "3.2.5" resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.5.tgz#7939af2a656de79a4f1901903ee8adcaa7cb9661" integrity sha512-2DtFcgT68wiTTiwZ2hNdJfcHNke9XOfnwmBRWXhmeKM8rF0TGwmC/Qto3S7RoZKp5cilZbxzO5iTNTQsJ+EeDg== @@ -6860,13 +7014,6 @@ figures@^3.2.0: dependencies: escape-string-regexp "^1.0.5" -file-entry-cache@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-5.0.1.tgz#ca0f6efa6dd3d561333fb14515065c2fafdf439c" - integrity sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g== - dependencies: - flat-cache "^2.0.1" - file-entry-cache@^6.0.0, file-entry-cache@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" @@ -6984,15 +7131,6 @@ find-versions@^3.2.0: dependencies: semver-regex "^2.0.0" -flat-cache@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-2.0.1.tgz#5d296d6f04bda44a4630a301413bdbc2ec085ec0" - integrity sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA== - dependencies: - flatted "^2.0.0" - rimraf "2.6.3" - write "1.0.3" - flat-cache@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11" @@ -7008,11 +7146,6 @@ flat@^4.1.0: dependencies: is-buffer "~2.0.3" -flatted@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.2.tgz#4575b21e2bcee7434aa9be662f4b7b5f9c2b5138" - integrity sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA== - flatted@^3.1.0: version "3.1.1" resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.1.1.tgz#c4b489e80096d9df1dfc97c79871aea7c617c469" @@ -7391,7 +7524,7 @@ globby@11.0.1: merge2 "^1.3.0" slash "^3.0.0" -globby@^11.0.1, globby@^11.0.2: +globby@^11.0.1, globby@^11.0.2, globby@^11.0.3: version "11.0.3" resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.3.tgz#9b1f0cb523e171dd1ad8c7b2a9fb4b644b9593cb" integrity sha512-ffdmosjA807y7+lA1NM0jELARVmYul/715xiILEjo3hBLPTcirgQNnXECn5g3mtR8TOLCVbkfua1Hpen25/Xcg== @@ -8029,7 +8162,7 @@ ip-regex@^2.1.0: resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9" integrity sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk= -ip@^1.1.0, ip@^1.1.5: +ip@^1.1.5: version "1.1.5" resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a" integrity sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo= @@ -8354,7 +8487,7 @@ is-path-inside@^3.0.2: resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== -is-plain-obj@^1.0.0, is-plain-obj@^1.1.0: +is-plain-obj@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" integrity sha1-caUMhCnfync8kqOQpKA7OfzVHT4= @@ -9277,11 +9410,6 @@ klona@^2.0.4: resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.4.tgz#7bb1e3affb0cb8624547ef7e8f6708ea2e39dfc0" integrity sha512-ZRbnvdg/NxqzC7L9Uyqzf4psi1OM4Cuc+sJAkQPjO6XkQIJTNbfK2Rsmbw8fx1p2mkZdp2FZYo2+LwXYY/uwIA== -known-css-properties@^0.19.0: - version "0.19.0" - resolved "https://registry.yarnpkg.com/known-css-properties/-/known-css-properties-0.19.0.tgz#5d92b7fa16c72d971bda9b7fe295bdf61836ee5b" - integrity sha512-eYboRV94Vco725nKMlpkn3nV2+96p9c3gKXRsYqAJSswSENvBhN7n5L+uDhY58xQa0UukWsDMTGELzmD8Q+wTA== - known-css-properties@^0.20.0: version "0.20.0" resolved "https://registry.yarnpkg.com/known-css-properties/-/known-css-properties-0.20.0.tgz#0570831661b47dd835293218381166090ff60e96" @@ -9462,6 +9590,11 @@ load-json-file@^2.0.0: pify "^2.0.0" strip-bom "^3.0.0" +load-script@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/load-script/-/load-script-1.0.0.tgz#0491939e0bee5643ee494a7e3da3d2bac70c6ca4" + integrity sha1-BJGTngvuVkPuSUp+PaPSuscMbKQ= + loader-runner@^2.4.0: version "2.4.0" resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.4.0.tgz#ed47066bfe534d7e84c4c7b9998c2a75607d9357" @@ -9603,7 +9736,7 @@ log-symbols@4.0.0: dependencies: chalk "^4.0.0" -log-symbols@^4.0.0: +log-symbols@^4.0.0, log-symbols@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== @@ -9718,6 +9851,11 @@ map-obj@^4.0.0: resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-4.2.0.tgz#0e8bc823e2aaca8a0942567d12ed14f389eec153" integrity sha512-NAq0fCmZYGz9UFEQyndp7sisrow4GroyGeKluyKC/chuITZsPyOyC1UJZPJlVFImhXdROIP5xqouRLThT3BbpQ== +map-obj@^4.1.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-4.2.1.tgz#e4ea399dbc979ae735c83c863dd31bdf364277b7" + integrity sha512-+WA2/1sPmDj1dlvvJmB5G6JKfY9dpn7EVBUL06+y6PoljPkh+6V1QihwxNkbcGxCRjt2b0F9K0taiCuo7MbdFQ== + map-visit@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f" @@ -9794,6 +9932,11 @@ media-typer@0.3.0: resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= +memoize-one@^5.1.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.2.1.tgz#8337aa3c4335581839ec01c3d594090cebe8f00e" + integrity sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q== + memory-fs@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.4.1.tgz#3a9a20b8462523e447cfbc7e8bb80ed667bfc552" @@ -9810,23 +9953,6 @@ memory-fs@^0.5.0: errno "^0.1.3" readable-stream "^2.0.1" -meow@^7.1.1: - version "7.1.1" - resolved "https://registry.yarnpkg.com/meow/-/meow-7.1.1.tgz#7c01595e3d337fcb0ec4e8eed1666ea95903d306" - integrity sha512-GWHvA5QOcS412WCo8vwKDlTelGLsCGBVevQB5Kva961rmNfun0PCbv5+xta2kUMFJyR8/oWnn7ddeKdosbAPbA== - dependencies: - "@types/minimist" "^1.2.0" - camelcase-keys "^6.2.2" - decamelize-keys "^1.1.0" - hard-rejection "^2.1.0" - minimist-options "4.1.0" - normalize-package-data "^2.5.0" - read-pkg-up "^7.0.1" - redent "^3.0.0" - trim-newlines "^3.0.0" - type-fest "^0.13.1" - yargs-parser "^18.1.3" - meow@^9.0.0: version "9.0.0" resolved "https://registry.yarnpkg.com/meow/-/meow-9.0.0.tgz#cd9510bc5cac9dee7d03c73ee1f9ad959f4ea364" @@ -9905,6 +10031,14 @@ micromatch@^4.0.2: braces "^3.0.1" picomatch "^2.0.5" +micromatch@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.4.tgz#896d519dfe9db25fce94ceb7a500919bf881ebf9" + integrity sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg== + dependencies: + braces "^3.0.1" + picomatch "^2.2.3" + miller-rabin@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.1.tgz#f080351c865b0dc562a8462966daa53543c78a4d" @@ -10444,25 +10578,10 @@ normalize-selector@^0.2.0: resolved "https://registry.yarnpkg.com/normalize-selector/-/normalize-selector-0.2.0.tgz#d0b145eb691189c63a78d201dc4fdb1293ef0c03" integrity sha1-0LFF62kRicY6eNIB3E/bEpPvDAM= -normalize-url@1.9.1: - version "1.9.1" - resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-1.9.1.tgz#2cc0d66b31ea23036458436e3620d85954c66c3c" - integrity sha1-LMDWazHqIwNkWENuNiDYWVTGbDw= - dependencies: - object-assign "^4.0.1" - prepend-http "^1.0.0" - query-string "^4.1.0" - sort-keys "^1.0.0" - -normalize-url@^3.0.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-3.3.0.tgz#b2e1c4dc4f7c6d57743df733a4f5978d18650559" - integrity sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg== - -normalize-url@^4.1.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.0.tgz#453354087e6ca96957bd8f5baf753f5982142129" - integrity sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ== +normalize-url@1.9.1, normalize-url@5.3.1, normalize-url@^3.0.0, normalize-url@^4.1.0: + version "5.3.1" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-5.3.1.tgz#c8485c0f5ba2f9c17a6d2907b56117ae5967f882" + integrity sha512-K1c7+vaAP+Yh5bOGmA10PGPpp+6h7WZrl7GwqKhUflBc9flU9pzG27DDeB9+iuhZkE3BJZOcgN1P/2sS5pqrWw== npm-conf@^1.1.3: version "1.1.3" @@ -11050,6 +11169,11 @@ picomatch@^2.0.4, picomatch@^2.0.5, picomatch@^2.2.1, picomatch@^2.2.2: resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg== +picomatch@^2.2.3: + version "2.3.0" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972" + integrity sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw== + pify@^2.0.0: version "2.3.0" resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" @@ -11803,7 +11927,7 @@ postcss-selector-parser@^5.0.0-rc.3, postcss-selector-parser@^5.0.0-rc.4: indexes-of "^1.0.1" uniq "^1.0.1" -postcss-selector-parser@^6.0.0: +postcss-selector-parser@^6.0.0, postcss-selector-parser@^6.0.5: version "6.0.6" resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.6.tgz#2c5bba8174ac2f6981ab631a42ab0ee54af332ea" integrity sha512-9LXrvaaX3+mcv5xkg5kFwqSzSH1JIObIx51PrndZwlmznwXRfxMddDvo9gve3gVR8ZTKgoFDdWkbRFmEhT4PMg== @@ -11958,11 +12082,6 @@ prelude-ls@~1.1.2: resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= -prepend-http@^1.0.0: - version "1.0.4" - resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc" - integrity sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw= - prepend-http@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897" @@ -12208,14 +12327,6 @@ qs@~6.5.2: resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== -query-string@^4.1.0: - version "4.3.4" - resolved "https://registry.yarnpkg.com/query-string/-/query-string-4.3.4.tgz#bbb693b9ca915c232515b228b1a02b609043dbeb" - integrity sha1-u7aTucqRXCMlFbIosaArYJBD2+s= - dependencies: - object-assign "^4.1.0" - strict-uri-encode "^1.0.0" - querystring-es3@^0.2.0: version "0.2.1" resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73" @@ -12717,6 +12828,11 @@ react-error-overlay@^6.0.9: resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.9.tgz#3c743010c9359608c375ecd6bc76f35d93995b0a" integrity sha512-nQTTcUu+ATDbrSD1BZHr5kgSD4oF8OFjxun8uAaL8RwPBacGBNPf/yAuVVdx17N8XNzRDMrZ9XcKZHCjPW+9ew== +react-fast-compare@^3.0.1: + version "3.2.0" + resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.0.tgz#641a9da81b6a6320f270e89724fb45a0b39e43bb" + integrity sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA== + react-is@^16.12.0, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" @@ -12727,6 +12843,17 @@ react-is@^17.0.1: resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== +react-player@^2.9.0: + version "2.9.0" + resolved "https://registry.yarnpkg.com/react-player/-/react-player-2.9.0.tgz#ef7fe7073434087565f00ff219824e1e02c4b046" + integrity sha512-jNUkTfMmUhwPPAktAdIqiBcVUKsFKrVGH6Ocutj6535CNfM91yrvWxHg6fvIX8Y/fjYUPoejddwh7qboNV9vGA== + dependencies: + deepmerge "^4.0.0" + load-script "^1.0.0" + memoize-one "^5.1.1" + prop-types "^15.7.2" + react-fast-compare "^3.0.1" + react-refresh@^0.8.3: version "0.8.3" resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.8.3.tgz#721d4657672d400c5e3c75d063c4a85fb2d5d68f" @@ -13297,13 +13424,6 @@ rgba-regex@^1.0.0: resolved "https://registry.yarnpkg.com/rgba-regex/-/rgba-regex-1.0.0.tgz#43374e2e2ca0968b0ef1523460b7d730ff22eeb3" integrity sha1-QzdOLiyglosO8VI0YLfXMP8i7rM= -rimraf@2.6.3: - version "2.6.3" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" - integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA== - dependencies: - glob "^7.1.3" - rimraf@3.0.2, rimraf@^3.0.0, rimraf@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" @@ -13822,6 +13942,14 @@ smart-buffer@^4.0.2: resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.1.0.tgz#91605c25d91652f4661ea69ccf45f1b331ca21ba" integrity sha512-iVICrxOzCynf/SNaBQCw34eM9jROU/s5rzIhpOvzhzuYHfJR/DhZfDkXiZSgKXfgv26HT3Yni3AV/DGw0cGnnw== +snakecase-keys@3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/snakecase-keys/-/snakecase-keys-3.2.1.tgz#ce5d1a2de8a93c939d7992f76f2743aa59f3d5ad" + integrity sha512-CjU5pyRfwOtaOITYv5C8DzpZ8XA/ieRsDpr93HI2r6e3YInC6moZpSQbmUtg8cTk58tq2x3jcG2gv+p1IZGmMA== + dependencies: + map-obj "^4.1.0" + to-snake-case "^1.0.0" + snapdragon-node@^2.0.1: version "2.1.1" resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" @@ -13873,13 +14001,6 @@ sockjs@0.3.20: uuid "^3.4.0" websocket-driver "0.6.5" -sort-keys@^1.0.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-1.1.2.tgz#441b6d4d346798f1b4e49e8920adfba0e543f9ad" - integrity sha1-RBttTTRnmPG05J6JIK37oOVD+a0= - dependencies: - is-plain-obj "^1.0.0" - source-list-map@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34" @@ -14104,11 +14225,6 @@ stream-shift@^1.0.0: resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.1.tgz#d7088281559ab2778424279b0877da3c392d5a3d" integrity sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ== -strict-uri-encode@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713" - integrity sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM= - string-argv@0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.1.tgz#95e2fbec0427ae19184935f816d74aaa4c5c19da" @@ -14347,10 +14463,10 @@ stylelint-config-recommended@^3.0.0: resolved "https://registry.yarnpkg.com/stylelint-config-recommended/-/stylelint-config-recommended-3.0.0.tgz#e0e547434016c5539fe2650afd58049a2fd1d657" integrity sha512-F6yTRuc06xr1h5Qw/ykb2LuFynJ2IxkKfCMf+1xqPffkxh0S09Zc902XCffcsw/XMFq/OzQ1w54fLIDtmRNHnQ== -stylelint-config-recommended@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/stylelint-config-recommended/-/stylelint-config-recommended-4.0.0.tgz#665a0034065e6704d5032ba51bf4efa37d328ef9" - integrity sha512-sgna89Ng+25Hr9kmmaIxpGWt2LStVm1xf1807PdcWasiPDaOTkOHRL61sINw0twky7QMzafCGToGDnHT/kTHtQ== +stylelint-config-recommended@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/stylelint-config-recommended/-/stylelint-config-recommended-5.0.0.tgz#fb5653f495a60b4938f2ad3e77712d9e1039ae78" + integrity sha512-c8aubuARSu5A3vEHLBeOSJt1udOdS+1iue7BmJDTSXoCBmfEQmmWX+59vYIj3NQdJBY6a/QRv1ozVFpaB9jaqA== stylelint-config-standard@^20.0.0: version "20.0.0" @@ -14359,12 +14475,12 @@ stylelint-config-standard@^20.0.0: dependencies: stylelint-config-recommended "^3.0.0" -stylelint-config-standard@^21.0.0: - version "21.0.0" - resolved "https://registry.yarnpkg.com/stylelint-config-standard/-/stylelint-config-standard-21.0.0.tgz#4942cfa27301eb6702fa8fc46a44da35d1a5cfd7" - integrity sha512-Yf6mx5oYEbQQJxWuW7X3t1gcxqbUx52qC9SMS3saC2ruOVYEyqmr5zSW6k3wXflDjjFrPhar3kp68ugRopmlzg== +stylelint-config-standard@^22.0.0: + version "22.0.0" + resolved "https://registry.yarnpkg.com/stylelint-config-standard/-/stylelint-config-standard-22.0.0.tgz#c860be9a13ebbc1b084456fa10527bf13a44addf" + integrity sha512-uQVNi87SHjqTm8+4NIP5NMAyY/arXrBgimaaT7skvRfE9u3JKXRK9KBkbr4pVmeciuCcs64kAdjlxfq6Rur7Hw== dependencies: - stylelint-config-recommended "^4.0.0" + stylelint-config-recommended "^5.0.0" stylelint-declaration-block-no-ignored-properties@^2.1.0, stylelint-declaration-block-no-ignored-properties@^2.3.0: version "2.3.0" @@ -14391,38 +14507,38 @@ stylelint-order@^4.0.0, stylelint-order@^4.1.0: postcss "^7.0.31" postcss-sorting "^5.0.1" -stylelint@13.7.2: - version "13.7.2" - resolved "https://registry.yarnpkg.com/stylelint/-/stylelint-13.7.2.tgz#6f3c58eea4077680ed0ceb0d064b22b100970486" - integrity sha512-mmieorkfmO+ZA6CNDu1ic9qpt4tFvH2QUB7vqXgrMVHe5ENU69q7YDq0YUg/UHLuCsZOWhUAvcMcLzLDIERzSg== +stylelint@13.13.1: + version "13.13.1" + resolved "https://registry.yarnpkg.com/stylelint/-/stylelint-13.13.1.tgz#fca9c9f5de7990ab26a00f167b8978f083a18f3c" + integrity sha512-Mv+BQr5XTUrKqAXmpqm6Ddli6Ief+AiPZkRsIrAoUKFuq/ElkUh9ZMYxXD0iQNZ5ADghZKLOWz1h7hTClB7zgQ== dependencies: "@stylelint/postcss-css-in-js" "^0.37.2" - "@stylelint/postcss-markdown" "^0.36.1" + "@stylelint/postcss-markdown" "^0.36.2" autoprefixer "^9.8.6" - balanced-match "^1.0.0" - chalk "^4.1.0" + balanced-match "^2.0.0" + chalk "^4.1.1" cosmiconfig "^7.0.0" - debug "^4.1.1" + debug "^4.3.1" execall "^2.0.0" - fast-glob "^3.2.4" + fast-glob "^3.2.5" fastest-levenshtein "^1.0.12" - file-entry-cache "^5.0.1" + file-entry-cache "^6.0.1" get-stdin "^8.0.0" global-modules "^2.0.0" - globby "^11.0.1" + globby "^11.0.3" globjoin "^0.1.4" html-tags "^3.1.0" ignore "^5.1.8" import-lazy "^4.0.0" imurmurhash "^0.1.4" - known-css-properties "^0.19.0" - lodash "^4.17.20" - log-symbols "^4.0.0" + known-css-properties "^0.21.0" + lodash "^4.17.21" + log-symbols "^4.1.0" mathml-tag-names "^2.1.3" - meow "^7.1.1" - micromatch "^4.0.2" + meow "^9.0.0" + micromatch "^4.0.4" normalize-selector "^0.2.0" - postcss "^7.0.32" + postcss "^7.0.35" postcss-html "^0.36.0" postcss-less "^3.1.4" postcss-media-query-parser "^0.2.3" @@ -14430,19 +14546,19 @@ stylelint@13.7.2: postcss-safe-parser "^4.0.2" postcss-sass "^0.4.4" postcss-scss "^2.1.1" - postcss-selector-parser "^6.0.2" + postcss-selector-parser "^6.0.5" postcss-syntax "^0.36.2" postcss-value-parser "^4.1.0" resolve-from "^5.0.0" slash "^3.0.0" specificity "^0.4.1" - string-width "^4.2.0" + string-width "^4.2.2" strip-ansi "^6.0.0" style-search "^0.1.0" sugarss "^2.0.0" svg-tags "^1.0.0" - table "^6.0.1" - v8-compile-cache "^2.1.1" + table "^6.6.0" + v8-compile-cache "^2.3.0" write-file-atomic "^3.0.3" stylelint@13.9.0, stylelint@^9.10.1: @@ -14642,18 +14758,6 @@ symbol-tree@^3.2.4: resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== -table@^6.0.1: - version "6.7.1" - resolved "https://registry.yarnpkg.com/table/-/table-6.7.1.tgz#ee05592b7143831a8c94f3cee6aae4c1ccef33e2" - integrity sha512-ZGum47Yi6KOOFDE8m223td53ath2enHcYLgOCjGr5ngu8bdIARQk6mN/wRMv4yMRcHnCSnHbCEha4sobQx5yWg== - dependencies: - ajv "^8.0.1" - lodash.clonedeep "^4.5.0" - lodash.truncate "^4.4.2" - slice-ansi "^4.0.0" - string-width "^4.2.0" - strip-ansi "^6.0.0" - table@^6.0.4, table@^6.0.7: version "6.0.9" resolved "https://registry.yarnpkg.com/table/-/table-6.0.9.tgz#790a12bf1e09b87b30e60419bafd6a1fd85536fb" @@ -14669,6 +14773,18 @@ table@^6.0.4, table@^6.0.7: slice-ansi "^4.0.0" string-width "^4.2.0" +table@^6.6.0: + version "6.7.1" + resolved "https://registry.yarnpkg.com/table/-/table-6.7.1.tgz#ee05592b7143831a8c94f3cee6aae4c1ccef33e2" + integrity sha512-ZGum47Yi6KOOFDE8m223td53ath2enHcYLgOCjGr5ngu8bdIARQk6mN/wRMv4yMRcHnCSnHbCEha4sobQx5yWg== + dependencies: + ajv "^8.0.1" + lodash.clonedeep "^4.5.0" + lodash.truncate "^4.4.2" + slice-ansi "^4.0.0" + string-width "^4.2.0" + strip-ansi "^6.0.0" + tapable@^1.0.0, tapable@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2" @@ -14875,6 +14991,11 @@ to-fast-properties@^2.0.0: resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4= +to-no-case@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/to-no-case/-/to-no-case-1.0.2.tgz#c722907164ef6b178132c8e69930212d1b4aa16a" + integrity sha1-xyKQcWTvaxeBMsjmmTAhLRtKoWo= + to-object-path@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af" @@ -14912,6 +15033,20 @@ to-regex@^3.0.1, to-regex@^3.0.2: regex-not "^1.0.2" safe-regex "^1.1.0" +to-snake-case@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/to-snake-case/-/to-snake-case-1.0.0.tgz#ce746913897946019a87e62edfaeaea4c608ab8c" + integrity sha1-znRpE4l5RgGah+Yu366upMYIq4w= + dependencies: + to-space-case "^1.0.0" + +to-space-case@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/to-space-case/-/to-space-case-1.0.0.tgz#b052daafb1b2b29dc770cea0163e5ec0ebc9fc17" + integrity sha1-sFLar7Gysp3HcM6gFj5ewOvJ/Bc= + dependencies: + to-no-case "^1.0.0" + toggle-selection@^1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/toggle-selection/-/toggle-selection-1.0.6.tgz#6e45b1263f2017fa0acc7d89d78b15b8bf77da32" @@ -14951,10 +15086,10 @@ tree-kill@^1.2.2: resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc" integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A== -trim-newlines@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-3.0.0.tgz#79726304a6a898aa8373427298d54c2ee8b1cb30" - integrity sha512-C4+gOpvmxaSMKuEf9Qc134F1ZuOHVXKRbtEflf4NTtuuJDEIJ9p5PXsalL8SkeRw+qit1Mo+yuvMPAKwWg/1hA== +trim-newlines@3.0.1, trim-newlines@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-3.0.1.tgz#260a5d962d8b752425b32f3a7db0dcacd176c144" + integrity sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw== trim-trailing-lines@^1.0.0: version "1.1.4" @@ -15515,7 +15650,7 @@ uuid@^8.3.0, uuid@^8.3.2: resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== -v8-compile-cache@^2.0.3, v8-compile-cache@^2.1.1, v8-compile-cache@^2.2.0: +v8-compile-cache@^2.0.3, v8-compile-cache@^2.2.0, v8-compile-cache@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA== @@ -16128,20 +16263,18 @@ write-file-atomic@^3.0.0, write-file-atomic@^3.0.3: signal-exit "^3.0.2" typedarray-to-buffer "^3.1.5" -write@1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/write/-/write-1.0.3.tgz#0800e14523b923a387e415123c865616aae0f5c3" - integrity sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig== - dependencies: - mkdirp "^0.5.1" - ws@^6.2.0, ws@^6.2.1: - version "6.2.1" - resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.1.tgz#442fdf0a47ed64f59b6a5d8ff130f4748ed524fb" - integrity sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA== + version "6.2.2" + resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.2.tgz#dd5cdbd57a9979916097652d78f1cc5faea0c32e" + integrity sha512-zmhltoSR8u1cnDsD43TX59mzoMZsLKqUweyYBAIvTngR3shc0W6aOZylZmq/7hqyVxPdi+5Ud2QInblgyE72fw== dependencies: async-limiter "~1.0.0" +ws@^7: + version "7.4.6" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.6.tgz#5654ca8ecdeee47c33a9a4bf6d28e2be2980377c" + integrity sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A== + ws@^7.4.4: version "7.4.4" resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.4.tgz#383bc9742cb202292c9077ceab6f6047b17f2d59" @@ -16246,7 +16379,7 @@ yargs-parser@^15.0.1: camelcase "^5.0.0" decamelize "^1.2.0" -yargs-parser@^18.1.2, yargs-parser@^18.1.3: +yargs-parser@^18.1.2: version "18.1.3" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0" integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==