diff --git a/packages/neuron-ui/package.json b/packages/neuron-ui/package.json index 80ad01f168..c832d47fd1 100644 --- a/packages/neuron-ui/package.json +++ b/packages/neuron-ui/package.json @@ -50,10 +50,14 @@ "last 2 chrome versions" ], "dependencies": { - "@ckb-lumos/rpc": "0.23.0", - "@ckb-lumos/base": "0.23.0", - "@ckb-lumos/lumos": "0.23.0", - "@ckb-lumos/helpers": "0.23.0", + "@ckb-lumos/bi": "0.21.1", + "@ckb-lumos/rpc": "0.21.1", + "@ckb-lumos/base": "0.21.1", + "@ckb-lumos/codec": "0.21.1", + "@ckb-lumos/hd": "0.21.1", + "@ckb-lumos/helpers": "0.21.1", + "@ckb-lumos/config-manager": "0.21.1", + "@ckb-lumos/common-scripts": "0.21.1", "canvg": "2.0.0", "i18next": "23.7.11", "immer": "9.0.21", diff --git a/packages/neuron-ui/src/components/WalletWizard/hooks.ts b/packages/neuron-ui/src/components/WalletWizard/hooks.ts index c52c3867bd..c073cc8661 100644 --- a/packages/neuron-ui/src/components/WalletWizard/hooks.ts +++ b/packages/neuron-ui/src/components/WalletWizard/hooks.ts @@ -1,12 +1,34 @@ import { useState, useCallback } from 'react' +const MNEMONIC_SENTENCE_WORDS = 12 + export const useInputWords = () => { - const [inputsWords, setInputsWords] = useState(new Array(12).fill('')) + const [inputsWords, setInputsWords] = useState(new Array(MNEMONIC_SENTENCE_WORDS).fill('')) const onChangeInput = useCallback( - (e: React.ChangeEvent) => { + ( + e: + | React.ChangeEvent + | { + target: { + dataset: { idx: string } + value: string + } + } + ) => { const idx = Number(e.target.dataset.idx) if (Number.isNaN(idx)) return const { value } = e.target + if (Number(idx) === 0) { + const list = value + .trim() + .replace(/[^0-9a-z]+/g, ' ') + .split(' ') + if (list.length === MNEMONIC_SENTENCE_WORDS) { + setInputsWords(list) + return + } + } + setInputsWords(v => { const newWords = [...v] newWords[idx] = value diff --git a/packages/neuron-ui/src/components/WalletWizard/index.tsx b/packages/neuron-ui/src/components/WalletWizard/index.tsx index 384492854a..b4c7ab61b0 100644 --- a/packages/neuron-ui/src/components/WalletWizard/index.tsx +++ b/packages/neuron-ui/src/components/WalletWizard/index.tsx @@ -20,7 +20,7 @@ import i18n from 'utils/i18n' import MnemonicInput from 'widgets/MnemonicInput' import ReplaceDuplicateWalletDialog, { useReplaceDuplicateWallet } from 'components/ReplaceDuplicateWalletDialog' import Alert from 'widgets/Alert' -import { Loading } from 'widgets/Icons/icon' +import { Loading, SuccessInfo, Error as ErrorIcon } from 'widgets/Icons/icon' import TextField from 'widgets/TextField' import { showGlobalAlertDialog, useDispatch } from 'states' import { importedWalletDialogShown } from 'services/localCache' @@ -182,19 +182,17 @@ const Welcome = ({ rootPath = '/wizard/', wallets = [], dispatch }: WizardElemen Welcome.displayName = 'Welcome' -const typeHits: Record = { - [MnemonicAction.Create]: 'wizard.write-down-seed', - [MnemonicAction.Verify]: 'wizard.input-seed-verify', - [MnemonicAction.Import]: '', -} - const Mnemonic = ({ state = initState, rootPath = '/wizard/', dispatch }: WizardElementProps) => { const { generated, imported } = state const navigate = useNavigate() const { type = MnemonicAction.Create } = useParams<{ type: MnemonicAction }>() const [t] = useTranslation() const isCreate = type === MnemonicAction.Create - const message = isCreate ? 'wizard.your-wallet-seed-is' : 'wizard.input-your-seed' + const message = { + [MnemonicAction.Create]: 'wizard.your-wallet-seed-is', + [MnemonicAction.Verify]: 'wizard.replenish-your-seed', + [MnemonicAction.Import]: 'wizard.input-your-seed', + }[type] const { inputsWords, onChangeInput, setInputsWords } = useInputWords() const [searchParams] = useSearchParams() const disableNext = @@ -202,6 +200,8 @@ const Mnemonic = ({ state = initState, rootPath = '/wizard/', dispatch }: Wizard (type === MnemonicAction.Verify && generated !== inputsWords.join(' ')) const [step, changeStep] = useState(0) + const [blankIndexes, setBlankIndexes] = useState([]) + useEffect(() => { if (type === MnemonicAction.Create) { generateMnemonic().then(res => { @@ -210,7 +210,15 @@ const Mnemonic = ({ state = initState, rootPath = '/wizard/', dispatch }: Wizard type: 'generated', payload: res.result, }) - setInputsWords(new Array(12).fill('')) + const uniqueRandomArray = new Set() + while (uniqueRandomArray.size < 3) { + const randomInt = Math.floor(Math.random() * 12) + uniqueRandomArray.add(randomInt) + } + const nums = [...uniqueRandomArray] + const list = res.result.split(' ').map((item: string, index: number) => (nums.includes(index) ? '' : item)) + setBlankIndexes(nums) + setInputsWords(list) } }) } else { @@ -219,7 +227,7 @@ const Mnemonic = ({ state = initState, rootPath = '/wizard/', dispatch }: Wizard payload: '', }) } - }, [dispatch, type, navigate]) + }, [dispatch, type, navigate, setBlankIndexes]) const globalDispatch = useDispatch() @@ -285,17 +293,32 @@ const Mnemonic = ({ state = initState, rootPath = '/wizard/', dispatch }: Wizard )}
{t(message)}
- {type === MnemonicAction.Import ? ( - - ) : ( -
{t(typeHits[type])}
+ {type === MnemonicAction.Import && } + {type === MnemonicAction.Create && ( +
+
+ + {t('wizard.handwritten-recommended')} +
+
+ + {t('wizard.do-not-copy')} +
+
+ + {t('wizard.do-not-save-scrrenshots')} +
+
)} + {type === MnemonicAction.Verify &&
{t('wizard.input-seed-verify')}
} + {type === MnemonicAction.Import &&
{t('wizard.input-seed-first-empty-space')}
}
+ ))} +
+ ) : null} ))} diff --git a/packages/neuron-wallet/src/block-sync-renderer/sync/indexer-cache-service.ts b/packages/neuron-wallet/src/block-sync-renderer/sync/indexer-cache-service.ts index 185371d945..99f29b49e4 100644 --- a/packages/neuron-wallet/src/block-sync-renderer/sync/indexer-cache-service.ts +++ b/packages/neuron-wallet/src/block-sync-renderer/sync/indexer-cache-service.ts @@ -194,7 +194,7 @@ export default class IndexerCacheService { const txsWithStatus: TransactionWithStatus[] = [] const fetchBlockDetailsQueue = queue(async (hash: string) => { const txWithStatus = await this.rpcService.getTransaction(hash) - if (!txWithStatus) { + if (!txWithStatus?.transaction) { return } const blockHeader = await this.rpcService.getHeader(txWithStatus!.txStatus.blockHash!) diff --git a/packages/neuron-wallet/src/block-sync-renderer/tx-status-listener.ts b/packages/neuron-wallet/src/block-sync-renderer/tx-status-listener.ts index 1a2532559b..3d996b3cf7 100644 --- a/packages/neuron-wallet/src/block-sync-renderer/tx-status-listener.ts +++ b/packages/neuron-wallet/src/block-sync-renderer/tx-status-listener.ts @@ -7,6 +7,7 @@ import TransactionWithStatus from '../models/chain/transaction-with-status' import logger from '../utils/logger' import { getConnection } from '../database/chain/connection' import { interval } from 'rxjs' +import TxStatus from '../models/chain/tx-status' type TransactionDetail = { hash: string @@ -18,7 +19,8 @@ type TransactionDetail = { const getTransactionStatus = async (hash: string) => { const network = NetworksService.getInstance().getCurrent() const rpcService = new RpcService(network.remote, network.type) - const txWithStatus: TransactionWithStatus | undefined = await rpcService.getTransaction(hash) + const txWithStatus: TransactionWithStatus | undefined | { transaction: null; txStatus: TxStatus } = + await rpcService.getTransaction(hash) if (!txWithStatus) { return { tx: txWithStatus, @@ -33,6 +35,13 @@ const getTransactionStatus = async (hash: string) => { blockHash: txWithStatus.txStatus.blockHash, } } + if (txWithStatus.txStatus.isRejected()) { + return { + tx: null, + status: TransactionStatus.Rejected, + blockHash: null, + } + } return { tx: txWithStatus.transaction, status: TransactionStatus.Pending, @@ -41,16 +50,16 @@ const getTransactionStatus = async (hash: string) => { } const trackingStatus = async () => { - const pendingTransactions = await FailedTransaction.pendings() + const pendingOrFailedTransactions = await FailedTransaction.pendingOrFaileds() await FailedTransaction.processAmendFailedTxs() - if (!pendingTransactions.length) { + if (!pendingOrFailedTransactions.length) { return } - const pendingHashes = pendingTransactions.map(tx => tx.hash) + const pendingOrFailedHashes = pendingOrFailedTransactions.map(tx => tx.hash) const txs = await Promise.all( - pendingHashes.map(async hash => { + pendingOrFailedHashes.map(async hash => { try { const txWithStatus = await getTransactionStatus(hash) return { @@ -65,17 +74,18 @@ const trackingStatus = async () => { }) ) - const failedTxs = txs.filter( - (tx): tx is TransactionDetail & { status: TransactionStatus.Failed } => tx?.status === TransactionStatus.Failed - ) - const successTxs = txs.filter( - (tx): tx is TransactionDetail & { status: TransactionStatus.Success } => tx?.status === TransactionStatus.Success - ) + const failedTxs = txs.filter((tx): tx is TransactionDetail => tx?.status === TransactionStatus.Failed) + const successTxs = txs.filter((tx): tx is TransactionDetail => tx?.status === TransactionStatus.Success) + const rejectedTxs = txs.filter((tx): tx is TransactionDetail => tx?.status === TransactionStatus.Rejected) if (failedTxs.length) { await FailedTransaction.updateFailedTxs(failedTxs.map(tx => tx.hash)) } + if (rejectedTxs.length) { + await FailedTransaction.deleteFailedTxs(rejectedTxs.map(tx => tx.hash)) + } + if (successTxs.length > 0) { const network = NetworksService.getInstance().getCurrent() const rpcService = new RpcService(network.remote, network.type) diff --git a/packages/neuron-wallet/src/models/chain/transaction.ts b/packages/neuron-wallet/src/models/chain/transaction.ts index d618357e5c..762d4b5312 100644 --- a/packages/neuron-wallet/src/models/chain/transaction.ts +++ b/packages/neuron-wallet/src/models/chain/transaction.ts @@ -14,6 +14,7 @@ export enum TransactionStatus { Pending = 'pending', Success = 'success', Failed = 'failed', + Rejected = 'rejected', } export interface SudtTokenInfo { diff --git a/packages/neuron-wallet/src/models/chain/tx-status.ts b/packages/neuron-wallet/src/models/chain/tx-status.ts index a2c54c553a..ba2292e8d1 100644 --- a/packages/neuron-wallet/src/models/chain/tx-status.ts +++ b/packages/neuron-wallet/src/models/chain/tx-status.ts @@ -4,6 +4,7 @@ export enum TxStatusType { Pending = 'pending', Proposed = 'proposed', Committed = 'committed', + Rejected = 'rejected', } export default class TxStatus { @@ -28,6 +29,10 @@ export default class TxStatus { return this.status === TxStatusType.Committed } + public isRejected(): boolean { + return this.status === TxStatusType.Rejected + } + public toSDK() { return { blockHash: this.blockHash, diff --git a/packages/neuron-wallet/src/services/rpc-service.ts b/packages/neuron-wallet/src/services/rpc-service.ts index 8b59adf858..3f6aab645e 100644 --- a/packages/neuron-wallet/src/services/rpc-service.ts +++ b/packages/neuron-wallet/src/services/rpc-service.ts @@ -6,6 +6,7 @@ import TransactionWithStatus from '../models/chain/transaction-with-status' import logger from '../utils/logger' import { generateRPC } from '../utils/ckb-rpc' import { NetworkType } from '../models/network' +import TxStatus, { TxStatusType } from '../models/chain/tx-status' export default class RpcService { private retryTime: number @@ -29,20 +30,19 @@ export default class RpcService { return BlockHeader.fromSDK(result) } - /** - * TODO: rejected tx should be handled - * { - * transaction: null, - * txStatus: { blockHash: null, status: 'rejected' } - * } - */ - public async getTransaction(hash: string): Promise { + public async getTransaction( + hash: string + ): Promise { const result = await this.rpc.getTransaction(hash) if (result?.transaction) { return TransactionWithStatus.fromSDK(result) } - if ((result.txStatus as any) === 'rejected') { + if (result.txStatus.status === TxStatusType.Rejected) { logger.warn(`Transaction[${hash}] was rejected`) + return { + transaction: null, + txStatus: TxStatus.fromSDK(result.txStatus), + } } return undefined } diff --git a/packages/neuron-wallet/src/services/tx/failed-transaction.ts b/packages/neuron-wallet/src/services/tx/failed-transaction.ts index 996c53d336..96c8f754cf 100644 --- a/packages/neuron-wallet/src/services/tx/failed-transaction.ts +++ b/packages/neuron-wallet/src/services/tx/failed-transaction.ts @@ -9,16 +9,41 @@ import { TransactionStatus } from '../../models/chain/transaction' import AmendTransactionEntity from '../../database/chain/entities/amend-transaction' export class FailedTransaction { - public static pendings = async (): Promise => { - const pendingTransactions = await getConnection() + public static pendingOrFaileds = async (): Promise => { + const transactions = await getConnection() .getRepository(TransactionEntity) .createQueryBuilder('tx') .where({ - status: TransactionStatus.Pending, + status: In([TransactionStatus.Pending, TransactionStatus.Failed]), }) .getMany() - return pendingTransactions + return transactions + } + + public static deleteFailedTxs = async (hashes: string[]) => { + await getConnection().manager.transaction(async transactionalEntityManager => { + await transactionalEntityManager + .createQueryBuilder() + .delete() + .from(TransactionEntity) + .where({ hash: In(hashes) }) + .execute() + + await transactionalEntityManager + .createQueryBuilder() + .delete() + .from(OutputEntity) + .where({ outPointTxHash: In(hashes) }) + .execute() + + await transactionalEntityManager + .createQueryBuilder() + .delete() + .from(InputEntity) + .where({ outPointTxHash: In(hashes) }) + .execute() + }) } public static processAmendFailedTxs = async () => { @@ -56,28 +81,7 @@ export class FailedTransaction { } }) - await getConnection().manager.transaction(async transactionalEntityManager => { - await transactionalEntityManager - .createQueryBuilder() - .delete() - .from(TransactionEntity) - .where({ hash: In(removeTxs) }) - .execute() - - await transactionalEntityManager - .createQueryBuilder() - .delete() - .from(OutputEntity) - .where({ outPointTxHash: In(removeTxs) }) - .execute() - - await transactionalEntityManager - .createQueryBuilder() - .delete() - .from(InputEntity) - .where({ outPointTxHash: In(removeTxs) }) - .execute() - }) + await FailedTransaction.deleteFailedTxs(removeTxs) } // update tx status to TransactionStatus.Failed diff --git a/packages/neuron-wallet/src/services/tx/transaction-generator.ts b/packages/neuron-wallet/src/services/tx/transaction-generator.ts index fbaaeafbdb..2c0214f958 100644 --- a/packages/neuron-wallet/src/services/tx/transaction-generator.ts +++ b/packages/neuron-wallet/src/services/tx/transaction-generator.ts @@ -72,7 +72,7 @@ export class TransactionGenerator { if (!nftCell) return const nftTx = await this.getRpcService().getTransaction(outPoint.txHash) - const nftOriginalOutputData = nftTx?.transaction.outputsData[Number(outPoint.index)] + const nftOriginalOutputData = nftTx?.transaction?.outputsData[Number(outPoint.index)] if (!nftOriginalOutputData) return nftCell.data = nftOriginalOutputData