diff --git a/packages/neuron-ui/src/components/MultisigAddress/index.tsx b/packages/neuron-ui/src/components/MultisigAddress/index.tsx index c586003db0..640b548392 100644 --- a/packages/neuron-ui/src/components/MultisigAddress/index.tsx +++ b/packages/neuron-ui/src/components/MultisigAddress/index.tsx @@ -204,6 +204,25 @@ const MultisigAddress = () => { [listActionOptions] ) + const daoDisabledMessage = useMemo(() => { + if (!wallet.device) return '' + + if ( + (daoDepositAction.depositFromMultisig && daoDepositAction.isDialogOpen) || + (daoWithdrawAction.withdrawFromMultisig && daoWithdrawAction.isDialogOpen) + ) { + const multisigConfig = daoDepositAction.depositFromMultisig || daoWithdrawAction.withdrawFromMultisig + const { canSign } = getMultisigSignStatus({ + multisigConfig: multisigConfig!, + addresses, + }) + + return canSign ? 'dao-ledger-notice' : 'dao-hardware-not-match' + } + + return '' + }, [daoDepositAction, daoWithdrawAction, wallet.device, addresses]) + const { keywords, onChange, onBlur } = useSearch(clearSelected, onFilterConfig) const sendTotalBalance = useMemo(() => { @@ -584,7 +603,7 @@ const MultisigAddress = () => { /> ) : null} - {daoDepositAction.depositFromMultisig && daoDepositAction.isDialogOpen ? ( + {!daoDisabledMessage && daoDepositAction.depositFromMultisig && daoDepositAction.isDialogOpen ? ( { /> ) : null} - {daoWithdrawAction.withdrawFromMultisig && daoWithdrawAction.isDialogOpen ? ( + {!daoDisabledMessage && daoWithdrawAction.withdrawFromMultisig && daoWithdrawAction.isDialogOpen ? ( ) : null} + + { + daoDepositAction.closeDialog() + daoWithdrawAction.closeDialog() + }} + /> ) } diff --git a/packages/neuron-ui/src/components/WithdrawDialog/index.tsx b/packages/neuron-ui/src/components/WithdrawDialog/index.tsx index db6c6035cd..d5d5545d4a 100644 --- a/packages/neuron-ui/src/components/WithdrawDialog/index.tsx +++ b/packages/neuron-ui/src/components/WithdrawDialog/index.tsx @@ -75,7 +75,7 @@ const WithdrawDialog = ({ (Number(currentEpochInfo.number) + Number(currentEpochInfo.index) / Number(currentEpochInfo.length)) ).toFixed(1) const message = - epochs >= 0 ? ( + epochs > 5 ? ( <> {t('nervos-dao.notice-wait-time', { diff --git a/packages/neuron-ui/src/components/WithdrawDialog/withdrawDialog.module.scss b/packages/neuron-ui/src/components/WithdrawDialog/withdrawDialog.module.scss index c406f379ae..c3dcb2feea 100644 --- a/packages/neuron-ui/src/components/WithdrawDialog/withdrawDialog.module.scss +++ b/packages/neuron-ui/src/components/WithdrawDialog/withdrawDialog.module.scss @@ -60,7 +60,7 @@ margin: 44px 0 0 0; border: 1px solid rgba(252, 136, 0, 0.2); padding: 8px 36px; - width: max-content; + max-width: 760px; border-radius: 4px; background: #fff6eb; color: #f68c2a; diff --git a/packages/neuron-ui/src/locales/ar.json b/packages/neuron-ui/src/locales/ar.json index 6e979ac5df..03433f7d16 100644 --- a/packages/neuron-ui/src/locales/ar.json +++ b/packages/neuron-ui/src/locales/ar.json @@ -1171,6 +1171,8 @@ "daoWithdraw": "سحب من DAO" } }, + "dao-ledger-notice": "محافظ Ledger لا تدعم حاليًا معاملات DAO لعناوين التوقيع المتعدد. يُرجى استخدام عنوان بتوقيع فردي أو الانتظار للتحديثات المستقبلية.", + "dao-hardware-not-match": "المحفظة الصلبة المتصلة حاليًا لا تتطابق مع المحفظة الحالية.", "import-dialog": { "actions": { "cancel": "إلغاء", diff --git a/packages/neuron-ui/src/locales/en.json b/packages/neuron-ui/src/locales/en.json index d943e24dcc..367b55d3e1 100644 --- a/packages/neuron-ui/src/locales/en.json +++ b/packages/neuron-ui/src/locales/en.json @@ -1171,6 +1171,8 @@ "daoWithdraw": "DAO Withdraw" } }, + "dao-ledger-notice": "Ledger wallets currently do not support DAO transactions for multisig addresses. Please use a single-signature address or await future updates.", + "dao-hardware-not-match": "The hardware wallet currently connected does not match the current wallet.", "import-dialog": { "actions": { "cancel": "Cancel", diff --git a/packages/neuron-ui/src/locales/es.json b/packages/neuron-ui/src/locales/es.json index 38179b3161..c960b4e222 100644 --- a/packages/neuron-ui/src/locales/es.json +++ b/packages/neuron-ui/src/locales/es.json @@ -1154,6 +1154,8 @@ "daoWithdraw": "Retirar de DAO" } }, + "dao-ledger-notice": "Las billeteras Ledger actualmente no admiten transacciones DAO para direcciones multifirma. Por favor, use una dirección de firma única o espere futuras actualizaciones.", + "dao-hardware-not-match": "La billetera de hardware conectada actualmente no coincide con la billetera actual.", "import-dialog": { "actions": { "cancel": "Cancelar", diff --git a/packages/neuron-ui/src/locales/fr.json b/packages/neuron-ui/src/locales/fr.json index 31e566bc62..43441d83a7 100644 --- a/packages/neuron-ui/src/locales/fr.json +++ b/packages/neuron-ui/src/locales/fr.json @@ -1161,6 +1161,8 @@ "daoWithdraw": "Retirer de DAO" } }, + "dao-ledger-notice": "Les portefeuilles Ledger ne prennent actuellement pas en charge les transactions DAO pour les adresses multisignatures. Veuillez utiliser une adresse à signature unique ou attendre les futures mises à jour.", + "dao-hardware-not-match": "Le portefeuille matériel actuellement connecté ne correspond pas au portefeuille actuel.", "import-dialog": { "actions": { "cancel": "Annuler", diff --git a/packages/neuron-ui/src/locales/zh-tw.json b/packages/neuron-ui/src/locales/zh-tw.json index 8018cb5f86..17c8bd7d3e 100644 --- a/packages/neuron-ui/src/locales/zh-tw.json +++ b/packages/neuron-ui/src/locales/zh-tw.json @@ -1164,6 +1164,8 @@ "daoWithdraw": "從 DAO 取出" } }, + "dao-ledger-notice": "Ledger 錢包目前不支援多重簽名地址的 DAO 交易。請使用單一簽名地址或等待未來的更新。", + "dao-hardware-not-match": "當前連接的硬體錢包與當前錢包不匹配。", "import-dialog": { "actions": { "cancel": "取消", diff --git a/packages/neuron-ui/src/locales/zh.json b/packages/neuron-ui/src/locales/zh.json index 9b958cde38..dd79782209 100644 --- a/packages/neuron-ui/src/locales/zh.json +++ b/packages/neuron-ui/src/locales/zh.json @@ -1163,6 +1163,8 @@ "daoWithdraw": "从 DAO 取出" } }, + "dao-ledger-notice": "Ledger 钱包目前不支持多签地址的 DAO 交易。请使用单签地址或等待未来的更新。", + "dao-hardware-not-match": "当前连接的硬件钱包与当前钱包不匹配。", "import-dialog": { "actions": { "cancel": "取消", diff --git a/packages/neuron-ui/src/styles/mixin.scss b/packages/neuron-ui/src/styles/mixin.scss index 9dfa2f6de3..4d36e5f6f9 100644 --- a/packages/neuron-ui/src/styles/mixin.scss +++ b/packages/neuron-ui/src/styles/mixin.scss @@ -196,23 +196,23 @@ height: 20px; padding-left: 30px; line-height: 20px; - background: url('../widgets/Icons/Checkbox.svg') no-repeat left top; + background: url('widgets/Icons/Checkbox.svg') no-repeat left top; user-select: none; } input[type='checkbox']:checked + span { - background: url('../widgets/Icons/CheckboxSelected.svg') no-repeat left top; + background: url('widgets/Icons/CheckboxSelected.svg') no-repeat left top; } input[type='checkbox']:disabled:checked + span { - background: url('../widgets/Icons/CheckboxSelected.svg') no-repeat left top; + background: url('widgets/Icons/CheckboxSelected.svg') no-repeat left top; opacity: 0.5; } input[type='checkbox']:disabled + span { cursor: not-allowed; - background: url('../widgets/Icons/CheckboxDisabled.svg') no-repeat left top; + background: url('widgets/Icons/CheckboxDisabled.svg') no-repeat left top; @media (prefers-color-scheme: dark) { - background: url('../widgets/Icons/CheckboxDisabledDark.svg') no-repeat left top; + background: url('widgets/Icons/CheckboxDisabledDark.svg') no-repeat left top; } } } diff --git a/packages/neuron-wallet/src/services/cells.ts b/packages/neuron-wallet/src/services/cells.ts index 235648ef46..fae732840e 100644 --- a/packages/neuron-wallet/src/services/cells.ts +++ b/packages/neuron-wallet/src/services/cells.ts @@ -1321,15 +1321,6 @@ export default class CellsService { return {} } const lockHashes = multisigAddresses.map(v => scriptToHash(addressToScript(v))) - - const outputs = await getConnection() - .getRepository(OutputEntity) - .createQueryBuilder('output') - .where('output.lockHash IN (:...lockHashes)', { lockHashes }) - .andWhere('output.hasData = :hasData', { hasData: true }) - .andWhere('output.typeHash IS NOT NULL') - .getMany() - const connection = await getConnection() const [sql, parameters] = connection.driver.escapeQueryWithParameters( ` @@ -1345,7 +1336,7 @@ export default class CellsService { `, { lockHashes, - statuses: [OutputStatus.Live, OutputStatus.Sent], + statuses: [OutputStatus.Live], }, {} ) @@ -1369,18 +1360,6 @@ export default class CellsService { ] = c.balance }) - outputs.forEach(item => { - const key = scriptToAddress( - { - args: item.lockArgs, - codeHash: SystemScriptInfo.MULTI_SIGN_CODE_HASH, - hashType: SystemScriptInfo.MULTI_SIGN_HASH_TYPE, - }, - isMainnet - ) - balances[key] = (BigInt(balances[key]) - BigInt(item.capacity)).toString() - }) - return balances } diff --git a/packages/neuron-wallet/src/services/multisig.ts b/packages/neuron-wallet/src/services/multisig.ts index a2baccd5ee..c90b995a37 100644 --- a/packages/neuron-wallet/src/services/multisig.ts +++ b/packages/neuron-wallet/src/services/multisig.ts @@ -13,6 +13,13 @@ import Multisig from '../models/multisig' import SyncProgress, { SyncAddressType } from '../database/chain/entities/sync-progress' import { NetworkType } from '../models/network' import logger from '../utils/logger' +import { TransactionPersistor } from './tx' +import { DAO_DATA } from '../utils/const' +import RpcService from '../services/rpc-service' +import TransactionWithStatus from '../models/chain/transaction-with-status' +import TxStatus from '../models/chain/tx-status' +import SystemScriptInfo from '../models/system-script-info' +import OutPoint from '../models/chain/out-point' const max64Int = '0x' + 'f'.repeat(16) export default class MultisigService { @@ -107,8 +114,8 @@ export default class MultisigService { }) } - static async getLiveCells(multisigConfigs: MultisigConfig[]) { - const liveCells: MultisigOutput[] = [] + static async getCells(multisigConfigs: MultisigConfig[]) { + const cells: RPC.IndexerCell[] = [] const addressCursorMap: Map = new Map() let currentMultisigConfigs = MultisigService.removeDulpicateConfig(multisigConfigs) const network = NetworksService.getInstance().getCurrent() @@ -144,17 +151,18 @@ export default class MultisigService { const config = currentMultisigConfigs[idx] const script = Multisig.getMultisigScript(config.blake160s, config.r, config.m, config.n) addressCursorMap.set(script.args, v?.result?.last_cursor) - liveCells.push( - ...v.result.objects - .filter((object: any) => !object?.output?.type) - .map((object: any) => MultisigOutput.fromIndexer(object)) - ) + cells.push(...v.result.objects) nextMultisigConfigs.push(currentMultisigConfigs[idx]) } }) currentMultisigConfigs = nextMultisigConfigs } - return liveCells + return cells + } + + static async getLiveCells(multisigConfigs: MultisigConfig[]) { + const cells = await MultisigService.getCells(multisigConfigs) + return cells.filter(object => !object?.output?.type).map(object => MultisigOutput.fromIndexer(object)) } static async saveLiveMultisigOutput() { @@ -166,6 +174,60 @@ export default class MultisigService { } } + static async saveMultisigDaoTx(multisigConfigs: MultisigConfig[]) { + const cells = await MultisigService.getCells(multisigConfigs) + if (cells.length) { + const daoTxHash = new Set() + cells.forEach(cell => { + if (cell.output?.type?.code_hash === SystemScriptInfo.DAO_CODE_HASH) { + daoTxHash.add(cell.out_point.tx_hash) + } + }) + + const network = NetworksService.getInstance().getCurrent() + const rpcService = new RpcService(network.remote, network.type) + + const getTx = async (txHash: string) => { + const txWithStatus: TransactionWithStatus | undefined | { transaction: null; txStatus: TxStatus } = + await rpcService.getTransaction(txHash) + if (txWithStatus?.transaction) { + const tx = Transaction.fromSDK(txWithStatus.transaction) + tx.blockHash = txWithStatus.txStatus.blockHash || undefined + if (tx.blockHash) { + const header = await rpcService.getHeader(tx.blockHash) + tx.timestamp = header?.timestamp + tx.blockNumber = header?.number + } + return tx + } + } + + if (daoTxHash.size > 0) { + for (const txHash of daoTxHash) { + const tx = await getTx(txHash) + if (tx) { + const previousTxHashes: string[] = [] + tx.outputs.forEach((output, index) => { + if (output.type?.codeHash === SystemScriptInfo.DAO_CODE_HASH) { + output.daoData = tx.outputsData[index] + if (tx.outputsData[index] !== DAO_DATA) { + const previousTxHash = tx.inputs[index].previousOutput!.txHash + previousTxHashes.push(previousTxHash) + output.setDepositOutPoint(new OutPoint(previousTxHash, tx.inputs[index].previousOutput!.index)) + } + } + }) + for (const previousTxHash of previousTxHashes) { + const previousTx = await getTx(previousTxHash) + if (previousTx) await TransactionPersistor.saveFetchTx(previousTx) + } + await TransactionPersistor.saveFetchTx(tx) + } + } + } + } + } + static async getMultisigTransactionHashList(multisigConfigs: MultisigConfig[]) { const multisigOutputTxHashList = new Set() const addressCursorMap: Map = new Map() @@ -302,6 +364,7 @@ export default class MultisigService { try { const multisigConfigs = await getConnection().getRepository(MultisigConfig).createQueryBuilder().getMany() await MultisigService.saveLiveMultisigOutput() + await MultisigService.saveMultisigDaoTx(multisigConfigs) await MultisigService.deleteDeadMultisigOutput(multisigConfigs) await MultisigService.saveMultisigSyncBlockNumber(multisigConfigs, lastestBlockNumber) MultisigOutputChangedSubject.getSubject().next('update') diff --git a/packages/neuron-wallet/src/services/transaction-sender.ts b/packages/neuron-wallet/src/services/transaction-sender.ts index 5ac0ec0ca6..89a6581957 100644 --- a/packages/neuron-wallet/src/services/transaction-sender.ts +++ b/packages/neuron-wallet/src/services/transaction-sender.ts @@ -896,8 +896,7 @@ export default class TransactionSender { multisigConfig.m, multisigConfig.n ) - const multisigAddresses = scriptToAddress(lockScript, NetworksService.getInstance().isMainnet()) - output = new Output(outputCapacity.toString(), AddressParser.parse(multisigAddresses), undefined, '0x') + output = new Output(outputCapacity.toString(), lockScript, undefined, '0x') } else { const wallet = WalletService.getInstance().get(walletID) const address = await wallet.getNextAddress() @@ -919,7 +918,10 @@ export default class TransactionSender { withdrawOutput.lock ) - const withdrawWitnessArgs: WitnessArgs = new WitnessArgs(WitnessArgs.EMPTY_LOCK, '0x0000000000000000') + const withdrawWitnessArgs: WitnessArgs = new WitnessArgs( + multisigConfig ? '' : WitnessArgs.EMPTY_LOCK, + '0x0000000000000000' + ) const tx: Transaction = Transaction.fromObject({ version: '0', cellDeps: [cellDep, daoCellDep], diff --git a/packages/neuron-wallet/src/utils/const.ts b/packages/neuron-wallet/src/utils/const.ts index 28ed3e21c6..25b9c5cd31 100644 --- a/packages/neuron-wallet/src/utils/const.ts +++ b/packages/neuron-wallet/src/utils/const.ts @@ -19,6 +19,7 @@ export const DEFAULT_ARGS_LENGTH = 42 export const LOCKTIME_ARGS_LENGTH = 58 export const CHEQUE_ARGS_LENGTH = 82 export const CKB_NODE_DATA_SIZE_BUFFER_RATIO = 1.2 +export const DAO_DATA = '0x0000000000000000' export enum ResponseCode { Fail,