From 814420e19ee251207bc0e7372050433ef22513cb Mon Sep 17 00:00:00 2001 From: devchenyan Date: Tue, 14 Jan 2025 22:02:00 +0800 Subject: [PATCH] update --- .../src/components/DepositDialog/hooks.ts | 90 +++++-- .../src/components/DepositDialog/index.tsx | 25 +- .../src/components/MultisigAddress/index.tsx | 35 ++- .../MultisigAddressNervosDAODialog/hooks.ts | 86 +++---- .../MultisigAddressNervosDAODialog/index.tsx | 54 +++-- ...multisigAddressNervosDAODialog.module.scss | 1 + .../src/components/NervosDAO/index.tsx | 2 +- .../src/components/NervosDAORecord/index.tsx | 27 ++- .../src/components/PasswordRequest/hooks.ts | 3 +- .../src/components/PasswordRequest/index.tsx | 3 +- packages/neuron-ui/src/locales/ar.json | 1 + packages/neuron-ui/src/locales/en.json | 1 + packages/neuron-ui/src/locales/es.json | 1 + packages/neuron-ui/src/locales/fr.json | 1 + packages/neuron-ui/src/locales/zh-tw.json | 1 + packages/neuron-ui/src/locales/zh.json | 1 + .../neuron-ui/src/services/remote/multisig.ts | 39 +++ .../src/services/remote/remoteApiWrapper.ts | 5 + packages/neuron-ui/src/types/App/index.d.ts | 1 + packages/neuron-wallet/src/controllers/api.ts | 67 ++++++ packages/neuron-wallet/src/controllers/dao.ts | 107 +++++++++ packages/neuron-wallet/src/services/cells.ts | 70 ++++++ .../src/services/transaction-sender.ts | 226 ++++++++++++++++++ .../src/services/tx/transaction-generator.ts | 98 +++++--- 24 files changed, 786 insertions(+), 159 deletions(-) diff --git a/packages/neuron-ui/src/components/DepositDialog/hooks.ts b/packages/neuron-ui/src/components/DepositDialog/hooks.ts index 56117708e0..1fa9d90873 100644 --- a/packages/neuron-ui/src/components/DepositDialog/hooks.ts +++ b/packages/neuron-ui/src/components/DepositDialog/hooks.ts @@ -3,8 +3,11 @@ import { TFunction } from 'i18next' import { Dispatch, SetStateAction, useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import { + MultisigConfig, generateDaoDepositAllTx as generateDaoDepositAllTxAPI, generateDaoDepositTx as generateDaoDepositTxAPI, + generateMultisigDaoDepositTx as generateMultisigDaoDepositTxAPI, + generateMultisigDaoDepositAllTx as generateMultisigDaoDepositAllTxAPI, } from 'services/remote' import { AppActions, useDispatch } from 'states' import { @@ -45,17 +48,26 @@ function generateDaoDepositTx({ capacity, suggestFeeRate, t, + multisigConfig, }: { walletID: string capacity: string suggestFeeRate: number t: TFunction + multisigConfig?: MultisigConfig }): Promise { - return generateDaoDepositTxAPI({ - feeRate: `${suggestFeeRate}`, - capacity, - walletID, - }).then(res => { + const generateCall = multisigConfig + ? generateMultisigDaoDepositTxAPI({ + feeRate: `${suggestFeeRate}`, + capacity, + multisigConfig, + }) + : generateDaoDepositTxAPI({ + walletID, + feeRate: `${suggestFeeRate}`, + capacity, + }) + return generateCall.then(res => { if (isSuccessResponse(res)) { return res.result } @@ -73,16 +85,25 @@ function generateDaoDepositAllTx({ suggestFeeRate, isBalanceReserved, walletID, + multisigConfig, }: { suggestFeeRate: number isBalanceReserved: boolean walletID: string + multisigConfig?: MultisigConfig }): Promise { - return generateDaoDepositAllTxAPI({ - walletID, - feeRate: `${suggestFeeRate}`, - isBalanceReserved, - }).then(res => { + const generateAllCall = multisigConfig + ? generateMultisigDaoDepositAllTxAPI({ + feeRate: `${suggestFeeRate}`, + isBalanceReserved, + multisigConfig, + }) + : generateDaoDepositAllTxAPI({ + walletID, + feeRate: `${suggestFeeRate}`, + isBalanceReserved, + }) + return generateAllCall.then(res => { if (isSuccessResponse(res)) { return res.result } @@ -97,6 +118,7 @@ export const useGenerateDaoDepositTx = ({ suggestFeeRate, showDepositDialog, slidePercent, + multisigConfig, }: { walletID: string isBalanceReserved: boolean @@ -104,6 +126,7 @@ export const useGenerateDaoDepositTx = ({ suggestFeeRate: number showDepositDialog: boolean slidePercent: number + multisigConfig?: MultisigConfig }) => { const timer = useRef>() const [errorMessage, setErrorMessage] = useState('') @@ -127,8 +150,14 @@ export const useGenerateDaoDepositTx = ({ } const generateDaoDepositResult: Promise = isDepositAll - ? generateDaoDepositAllTx({ walletID, isBalanceReserved, suggestFeeRate }) - : generateDaoDepositTx({ walletID, capacity: CKBToShannonFormatter(depositValue), suggestFeeRate, t }) + ? generateDaoDepositAllTx({ walletID, isBalanceReserved, suggestFeeRate, multisigConfig }) + : generateDaoDepositTx({ + walletID, + capacity: CKBToShannonFormatter(depositValue), + suggestFeeRate, + t, + multisigConfig, + }) generateDaoDepositResult .then(res => { dispatch({ @@ -239,22 +268,37 @@ export const useBalanceReserved = () => { export const useOnDepositDialogSubmit = ({ onDepositSuccess, - walletID, + wallet, + multisigConfig, }: { onDepositSuccess: () => void - walletID: string + wallet: State.Wallet + multisigConfig?: MultisigConfig }) => { const dispatch = useDispatch() return useCallback(() => { - dispatch({ - type: AppActions.RequestPassword, - payload: { - walletID, - actionType: 'send', - onSuccess: onDepositSuccess, - }, - }) - }, [dispatch, walletID, onDepositSuccess]) + if (multisigConfig) { + dispatch({ + type: AppActions.RequestPassword, + payload: { + walletID: wallet.id, + actionType: multisigConfig.m === 1 ? 'send-from-multisig-need-one' : 'send-from-multisig', + multisigConfig, + onSuccess: onDepositSuccess, + title: 'password-request.verify-password', + }, + }) + } else { + dispatch({ + type: AppActions.RequestPassword, + payload: { + walletID: wallet.id, + actionType: 'send', + onSuccess: onDepositSuccess, + }, + }) + } + }, [dispatch, wallet.id, onDepositSuccess, multisigConfig]) } export const useOnDepositDialogCancel = ({ diff --git a/packages/neuron-ui/src/components/DepositDialog/index.tsx b/packages/neuron-ui/src/components/DepositDialog/index.tsx index 057fa087b6..7c08111d68 100644 --- a/packages/neuron-ui/src/components/DepositDialog/index.tsx +++ b/packages/neuron-ui/src/components/DepositDialog/index.tsx @@ -1,10 +1,11 @@ -import React, { useCallback } from 'react' +import React, { useCallback, useMemo } from 'react' import { Slider } from 'office-ui-fabric-react' import { Trans, useTranslation } from 'react-i18next' import TextField from 'widgets/TextField' import Spinner, { SpinnerSize } from 'widgets/Spinner' -import { openExternal } from 'services/remote' +import { openExternal, MultisigConfig } from 'services/remote' import { localNumberFormatter, shannonToCKBFormatter } from 'utils' +import getMultisigSignStatus from 'utils/getMultisigSignStatus' import { Attention, Success } from 'widgets/Icons/icon' import Dialog from 'widgets/Dialog' import Tooltip from 'widgets/Tooltip' @@ -30,9 +31,10 @@ interface DepositDialogProps { isDepositing: boolean isTxGenerated: boolean suggestFeeRate: number - walletID: string + wallet: State.Wallet globalAPC: number onDepositSuccess: () => void + multisigConfig?: MultisigConfig } const RfcLink = React.memo(() => ( @@ -50,7 +52,7 @@ const RfcLink = React.memo(() => ( )) const DepositDialog = ({ - walletID, + wallet, balance, show, fee, @@ -60,6 +62,7 @@ const DepositDialog = ({ suggestFeeRate, globalAPC, onDepositSuccess, + multisigConfig, }: DepositDialogProps) => { const [t, { language }] = useTranslation() const disabled = !isTxGenerated @@ -69,14 +72,22 @@ const DepositDialog = ({ show ) const { errorMessage, maxDepositValue } = useGenerateDaoDepositTx({ - walletID, + walletID: wallet.id, isBalanceReserved, depositValue, suggestFeeRate, showDepositDialog: show, slidePercent, + multisigConfig, }) - const onConfirm = useOnDepositDialogSubmit({ onDepositSuccess, walletID }) + + const canSign = useMemo(() => { + if (!multisigConfig) return true + const multisigSignStatus = getMultisigSignStatus({ multisigConfig, addresses: wallet.addresses }) + return multisigSignStatus.canSign + }, [multisigConfig, wallet.addresses]) + + const onConfirm = useOnDepositDialogSubmit({ onDepositSuccess, wallet, multisigConfig }) const onCancel = useOnDepositDialogCancel({ onCloseDepositDialog, resetDepositValue, setIsBalanceReserved }) const onSubmit = useCallback( (e: React.FormEvent) => { @@ -105,7 +116,7 @@ const DepositDialog = ({ onCancel={onCancel} onConfirm={onConfirm} cancelText={t('nervos-dao.cancel')} - confirmText={t('nervos-dao.proceed')} + confirmText={canSign ? t('nervos-dao.proceed') : t('nervos-dao-detail.export')} className={styles.container} > {isDepositing ? ( diff --git a/packages/neuron-ui/src/components/MultisigAddress/index.tsx b/packages/neuron-ui/src/components/MultisigAddress/index.tsx index 45b7524236..8ef71c9bde 100644 --- a/packages/neuron-ui/src/components/MultisigAddress/index.tsx +++ b/packages/neuron-ui/src/components/MultisigAddress/index.tsx @@ -116,7 +116,7 @@ const MultisigAddress = () => { }, settings: { networks = [] }, } = useGlobalState() - const { id: walletId, addresses, balance } = wallet + const { id: walletId, addresses } = wallet const isMainnet = isMainnetUtil(networks, networkID) const isLightClient = useMemo( () => networks.find(n => n.id === networkID)?.type === NetworkType.Light, @@ -273,23 +273,6 @@ const MultisigAddress = () => { showPageNotice('nervos-dao.deposit-submitted')(dispatch) }, [dispatch, daoDepositAction.closeDialog]) - const MemoizedDepositDialog = useMemo(() => { - return ( - - ) - }, [balance, walletId, fee, sending, send.generatedTx, suggestFeeRate, globalAPC, daoDepositAction]) - return (
{ /> ) : null} - {MemoizedDepositDialog} + {daoDepositAction.depositFromMultisig && daoDepositAction.isDialogOpen ? ( + + ) : null} {daoWithdrawAction.withdrawFromMultisig && daoWithdrawAction.isDialogOpen ? ( { return depositOutPoint ? `${depositOutPoint.txHash}-${depositOutPoint.index}` : `${outPoint.txHash}-${outPoint.index}` } -export const useInitData = ({ - clearGeneratedTx, - dispatch, - wallet, - setGenesisBlockTimestamp, - genesisBlockHash, -}: { - clearGeneratedTx: () => void - dispatch: React.Dispatch - wallet: State.Wallet - setGenesisBlockTimestamp: React.Dispatch> - genesisBlockHash?: string -}) => - useEffect(() => { - updateNervosDaoData({ walletID: wallet.id })(dispatch) - const intervalId = setInterval(() => { - updateNervosDaoData({ walletID: wallet.id })(dispatch) - }, 10000) - if (genesisBlockHash) { - getHeader(genesisBlockHash) - .then(header => setGenesisBlockTimestamp(+header.timestamp)) - .catch(err => console.error(err)) - } - return () => { - clearInterval(intervalId) - clearNervosDaoData()(dispatch) - clearGeneratedTx() - } - // eslint-disable-next-line - }, []) - export const useOnWithdrawDialogDismiss = (setActiveRecord: React.Dispatch) => useCallback(() => { setActiveRecord(null) @@ -59,6 +32,7 @@ export const useOnWithdrawDialogSubmit = ({ walletID, dispatch, suggestFeeRate, + multisigConfig, }: { activeRecord: State.NervosDAORecord | null setActiveRecord: React.Dispatch @@ -66,13 +40,14 @@ export const useOnWithdrawDialogSubmit = ({ walletID: string dispatch: React.Dispatch suggestFeeRate: number | string + multisigConfig: MultisigConfig }) => useCallback(() => { if (activeRecord) { - generateDaoWithdrawTx({ - walletID, + generateMultisigDaoWithdrawTx({ outPoint: activeRecord.outPoint, feeRate: `${suggestFeeRate}`, + multisigConfig, }) .then(res => { if (isSuccessResponse(res)) { @@ -84,8 +59,10 @@ export const useOnWithdrawDialogSubmit = ({ type: AppActions.RequestPassword, payload: { walletID, - actionType: 'send', + actionType: multisigConfig.m === 1 ? 'send-from-multisig-need-one' : 'send-from-multisig', + multisigConfig, onSuccess: () => {}, + title: 'password-request.verify-password', }, }) } else { @@ -110,14 +87,16 @@ export const useOnActionClick = ({ dispatch, walletID, setActiveRecord, - navigate, + isMainnet, + multisigConfig, }: { records: Readonly clearGeneratedTx: () => void dispatch: React.Dispatch walletID: string setActiveRecord: React.Dispatch - navigate: NavigateFunction + isMainnet: boolean + multisigConfig: MultisigConfig }) => useCallback( (e: any) => { @@ -129,13 +108,13 @@ export const useOnActionClick = ({ const record = records.find(r => r.outPoint.txHash === outPoint.txHash && r.outPoint.index === outPoint.index) if (record) { if (record.status === 'sent') { - navigate(`${RoutePath.History}/${record.depositInfo?.txHash}`) + openExternal(`${getExplorerUrl(isMainnet)}/transaction/${record?.depositInfo?.txHash}`) } else if (record.depositOutPoint) { - generateDaoClaimTx({ - walletID, + generateMultisigDaoClaimTx({ withdrawingOutPoint: record.outPoint, depositOutPoint: record.depositOutPoint, feeRate: `${MEDIUM_FEE_RATE}`, + multisigConfig, }) .then(res => { if (isSuccessResponse(res)) { @@ -147,8 +126,10 @@ export const useOnActionClick = ({ type: AppActions.RequestPassword, payload: { walletID, - actionType: 'send', + actionType: multisigConfig.m === 1 ? 'send-from-multisig-need-one' : 'send-from-multisig', + multisigConfig, onSuccess: () => {}, + title: 'password-request.verify-password', }, }) } else { @@ -317,23 +298,10 @@ export const useUpdateDepositEpochList = ({ } }, [records, setDepositEpochList, connectionStatus]) -export const useCanSign = ({ - addresses, - multisigConfig, -}: { - addresses: State.Address[] - multisigConfig: MultisigConfig -}) => { - const addressList = useMemo(() => addresses.map(v => v.address), [addresses]) - return useMemo(() => multisigConfig.addresses.some(v => addressList.includes(v)), [multisigConfig, addressList]) -} - export default { - useInitData, useOnWithdrawDialogDismiss, useOnWithdrawDialogSubmit, useOnActionClick, useUpdateWithdrawList, useUpdateDepositEpochList, - useCanSign, } diff --git a/packages/neuron-ui/src/components/MultisigAddressNervosDAODialog/index.tsx b/packages/neuron-ui/src/components/MultisigAddressNervosDAODialog/index.tsx index 1815d26a54..b1e7112ba0 100644 --- a/packages/neuron-ui/src/components/MultisigAddressNervosDAODialog/index.tsx +++ b/packages/neuron-ui/src/components/MultisigAddressNervosDAODialog/index.tsx @@ -1,15 +1,16 @@ -import React, { useState, useMemo } from 'react' +import React, { useState, useMemo, useEffect } from 'react' import { useState as useGlobalState, useDispatch } from 'states' import { useTranslation } from 'react-i18next' -import { useNavigate } from 'react-router-dom' -import { MultisigConfig } from 'services/remote' -import { clsx, useClearGeneratedTx } from 'utils' +import { MultisigConfig, getMultisigDaoData } from 'services/remote' +import { clsx, useClearGeneratedTx, isSuccessResponse, isMainnet as isMainnetUtil } from 'utils' import Dialog from 'widgets/Dialog' import WithdrawDialog from 'components/WithdrawDialog' import DAORecord, { DAORecordProps } from 'components/NervosDAORecord' import TableNoData from 'widgets/Icons/TableNoData.png' +import { getHeader } from 'services/chain' import useGetCountDownAndFeeRateStats from 'utils/hooks/useGetCountDownAndFeeRateStats' +import getMultisigSignStatus from 'utils/getMultisigSignStatus' import hooks from './hooks' import styles from './multisigAddressNervosDAODialog.module.scss' @@ -24,32 +25,25 @@ const MultisigAddressNervosDAODialog = ({ const { app: { tipDao, tipBlockTimestamp, epoch }, wallet, - nervosDAO: { records }, chain: { connectionStatus, networkID }, settings: { networks }, } = useGlobalState() const dispatch = useDispatch() - const navigate = useNavigate() const [t] = useTranslation() const { suggestFeeRate } = useGetCountDownAndFeeRateStats() + const [records, setRecords] = useState([]) const [activeRecord, setActiveRecord] = useState(null) const [withdrawList, setWithdrawList] = useState>(new Map()) const [genesisBlockTimestamp, setGenesisBlockTimestamp] = useState(undefined) const [depositEpochList, setDepositEpochList] = useState>(new Map()) const clearGeneratedTx = useClearGeneratedTx() - const canSign = hooks.useCanSign({ addresses: wallet.addresses, multisigConfig }) + const { canSign } = getMultisigSignStatus({ multisigConfig, addresses: wallet.addresses }) const onWithdrawDialogDismiss = hooks.useOnWithdrawDialogDismiss(setActiveRecord) const genesisBlockHash = useMemo(() => networks.find(v => v.id === networkID)?.genesisHash, [networkID, networks]) - hooks.useInitData({ - clearGeneratedTx, - dispatch, - wallet, - setGenesisBlockTimestamp, - genesisBlockHash, - }) + const onWithdrawDialogSubmit = hooks.useOnWithdrawDialogSubmit({ activeRecord, setActiveRecord, @@ -57,19 +51,47 @@ const MultisigAddressNervosDAODialog = ({ walletID: wallet.id, dispatch, suggestFeeRate, + multisigConfig, }) + const isMainnet = isMainnetUtil(networks, networkID) + const onActionClick = hooks.useOnActionClick({ records, clearGeneratedTx, dispatch, walletID: wallet.id, setActiveRecord, - navigate, + isMainnet, + multisigConfig, }) hooks.useUpdateDepositEpochList({ records, setDepositEpochList, connectionStatus }) + useEffect(() => { + getMultisigDaoData({ multisigConfig }).then(res => { + if (isSuccessResponse(res)) { + setRecords(res.result) + } + }) + const intervalId = setInterval(() => { + getMultisigDaoData({ multisigConfig }).then(res => { + if (isSuccessResponse(res)) { + setRecords(res.result) + } + }) + }, 10000) + if (genesisBlockHash) { + getHeader(genesisBlockHash) + .then(header => setGenesisBlockTimestamp(+header.timestamp)) + .catch(err => console.error(err)) + } + return () => { + clearInterval(intervalId) + clearGeneratedTx() + } + }, [multisigConfig]) + hooks.useUpdateWithdrawList({ records, tipDao, @@ -148,6 +170,8 @@ const MultisigAddressNervosDAODialog = ({ genesisBlockTimestamp, connectionStatus, hasCkbBalance: +wallet.balance > 0, + showDetailInExplorer: true, + isMainnet, } return (
diff --git a/packages/neuron-ui/src/components/MultisigAddressNervosDAODialog/multisigAddressNervosDAODialog.module.scss b/packages/neuron-ui/src/components/MultisigAddressNervosDAODialog/multisigAddressNervosDAODialog.module.scss index de81f40ca9..2574d8458f 100644 --- a/packages/neuron-ui/src/components/MultisigAddressNervosDAODialog/multisigAddressNervosDAODialog.module.scss +++ b/packages/neuron-ui/src/components/MultisigAddressNervosDAODialog/multisigAddressNervosDAODialog.module.scss @@ -2,6 +2,7 @@ .container { width: 900px; + min-height: 50vh; } .tabContainer { diff --git a/packages/neuron-ui/src/components/NervosDAO/index.tsx b/packages/neuron-ui/src/components/NervosDAO/index.tsx index 93d0a76200..e969fcfd1d 100644 --- a/packages/neuron-ui/src/components/NervosDAO/index.tsx +++ b/packages/neuron-ui/src/components/NervosDAO/index.tsx @@ -256,7 +256,7 @@ const NervosDAO = () => { return ( { const [t] = useTranslation() const [withdrawEpoch, setWithdrawEpoch] = useState('') @@ -207,9 +213,24 @@ export const DAORecord = ({ )}
- -
) diff --git a/packages/neuron-ui/src/components/PasswordRequest/hooks.ts b/packages/neuron-ui/src/components/PasswordRequest/hooks.ts index dbb4073155..b55fbaca59 100644 --- a/packages/neuron-ui/src/components/PasswordRequest/hooks.ts +++ b/packages/neuron-ui/src/components/PasswordRequest/hooks.ts @@ -45,7 +45,7 @@ export default ({ const [password, setPassword] = useState('') const [error, setError] = useState('') - const { walletID = '', actionType = null, amendHash, multisigConfig, onSuccess } = passwordRequest + const { walletID = '', actionType = null, title, amendHash, multisigConfig, onSuccess } = passwordRequest useEffect(() => { setPassword('') @@ -362,6 +362,7 @@ export default ({ isLoading, signType, actionType, + title, disabled, password, onSubmit, diff --git a/packages/neuron-ui/src/components/PasswordRequest/index.tsx b/packages/neuron-ui/src/components/PasswordRequest/index.tsx index 1310ccc45f..ba13ba2b19 100644 --- a/packages/neuron-ui/src/components/PasswordRequest/index.tsx +++ b/packages/neuron-ui/src/components/PasswordRequest/index.tsx @@ -36,6 +36,7 @@ const PasswordRequest = () => { isLoading, signType, actionType, + title, disabled, password, onSubmit, @@ -62,7 +63,7 @@ const PasswordRequest = () => { return ( ('change-multisig-sync-status') + +export const getMultisigDaoData = remoteApi<{ multisigConfig: MultisigConfig }>('get-multisig-dao-data') + +export const generateMultisigDaoDepositTx = remoteApi< + { + capacity: string + feeRate: string + multisigConfig: MultisigConfig + }, + State.GeneratedTx +>('generate-multisig-dao-deposit-tx') +export const generateMultisigDaoDepositAllTx = remoteApi< + { + isBalanceReserved: boolean + feeRate: string + multisigConfig: MultisigConfig + }, + State.GeneratedTx +>('generate-multisig-dao-deposit-all-tx') +export const generateMultisigDaoWithdrawTx = remoteApi<{ + outPoint: { + txHash: string + index: string + } + feeRate: string + multisigConfig: MultisigConfig +}>('start-withdraw-from-multisig-dao') +export const generateMultisigDaoClaimTx = remoteApi<{ + depositOutPoint: { + txHash: string + index: string + } + withdrawingOutPoint: { + txHash: string + index: string + } + feeRate: string + multisigConfig: MultisigConfig +}>('withdraw-from-multisig-dao') diff --git a/packages/neuron-ui/src/services/remote/remoteApiWrapper.ts b/packages/neuron-ui/src/services/remote/remoteApiWrapper.ts index ba8703f318..69bc7c1711 100644 --- a/packages/neuron-ui/src/services/remote/remoteApiWrapper.ts +++ b/packages/neuron-ui/src/services/remote/remoteApiWrapper.ts @@ -86,10 +86,15 @@ type Action = | 'export-transactions' // Dao | 'get-dao-data' + | 'get-multisig-dao-data' | 'generate-dao-deposit-tx' + | 'generate-multisig-dao-deposit-tx' | 'generate-dao-deposit-all-tx' + | 'generate-multisig-dao-deposit-all-tx' | 'start-withdraw-from-dao' + | 'start-withdraw-from-multisig-dao' | 'withdraw-from-dao' + | 'withdraw-from-multisig-dao' | 'calculate-unlock-dao-maximum-withdraw' // Special Assets | 'get-customized-asset-cells' diff --git a/packages/neuron-ui/src/types/App/index.d.ts b/packages/neuron-ui/src/types/App/index.d.ts index 649cf62493..c0223f71db 100644 --- a/packages/neuron-ui/src/types/App/index.d.ts +++ b/packages/neuron-ui/src/types/App/index.d.ts @@ -130,6 +130,7 @@ declare namespace State { onSuccess?: () => void showType?: 'Global' | '' amendHash?: string + title?: string } enum UDTType { diff --git a/packages/neuron-wallet/src/controllers/api.ts b/packages/neuron-wallet/src/controllers/api.ts index f8cf5e4e50..4486ef3da1 100644 --- a/packages/neuron-wallet/src/controllers/api.ts +++ b/packages/neuron-wallet/src/controllers/api.ts @@ -572,6 +572,12 @@ export default class ApiController { return this.#daoController.getDaoCells(params) }) + handle('get-multisig-dao-data', async (_, params: { multisigConfig: MultisigConfigModel }) => { + return this.#daoController.getMultisigDaoCells({ + multisigConfig: MultisigConfigModel.fromObject(params.multisigConfig), + }) + }) + handle( 'generate-dao-deposit-tx', async (_, params: { walletID: string; capacity: string; fee: string; feeRate: string }) => { @@ -579,6 +585,18 @@ export default class ApiController { } ) + handle( + 'generate-multisig-dao-deposit-tx', + async (_, params: { capacity: string; fee: string; feeRate: string; multisigConfig: MultisigConfigModel }) => { + return this.#daoController.generateMultisigDepositTx({ + capacity: params.capacity, + fee: params.fee, + feeRate: params.feeRate, + multisigConfig: MultisigConfigModel.fromObject(params.multisigConfig), + }) + } + ) + handle( 'generate-dao-deposit-all-tx', async (_, params: { walletID: string; isBalanceReserved: boolean; fee: string; feeRate: string }) => { @@ -586,6 +604,21 @@ export default class ApiController { } ) + handle( + 'generate-multisig-dao-deposit-all-tx', + async ( + _, + params: { isBalanceReserved: boolean; fee: string; feeRate: string; multisigConfig: MultisigConfigModel } + ) => { + return this.#daoController.generateMultisigDepositAllTx({ + isBalanceReserved: params.isBalanceReserved, + fee: params.fee, + feeRate: params.feeRate, + multisigConfig: MultisigConfigModel.fromObject(params.multisigConfig), + }) + } + ) + handle( 'start-withdraw-from-dao', async (_, params: { walletID: string; outPoint: OutPoint; fee: string; feeRate: string }) => { @@ -593,6 +626,18 @@ export default class ApiController { } ) + handle( + 'start-withdraw-from-multisig-dao', + async (_, params: { outPoint: OutPoint; fee: string; feeRate: string; multisigConfig: MultisigConfigModel }) => { + return this.#daoController.startWithdrawFromMultisigDao({ + outPoint: params.outPoint, + fee: params.fee, + feeRate: params.feeRate, + multisigConfig: MultisigConfigModel.fromObject(params.multisigConfig), + }) + } + ) + handle( 'withdraw-from-dao', async ( @@ -609,6 +654,28 @@ export default class ApiController { } ) + handle( + 'withdraw-from-multisig-dao', + async ( + _, + params: { + depositOutPoint: OutPoint + withdrawingOutPoint: OutPoint + fee: string + feeRate: string + multisigConfig: MultisigConfigModel + } + ) => { + return this.#daoController.withdrawFromMultisigDao({ + depositOutPoint: params.depositOutPoint, + withdrawingOutPoint: params.withdrawingOutPoint, + fee: params.fee, + feeRate: params.feeRate, + multisigConfig: MultisigConfigModel.fromObject(params.multisigConfig), + }) + } + ) + handle('calculate-unlock-dao-maximum-withdraw', async (_, unlockHash: string) => { return this.#daoController.calculateUnlockDaoMaximumWithdraw(unlockHash) }) diff --git a/packages/neuron-wallet/src/controllers/dao.ts b/packages/neuron-wallet/src/controllers/dao.ts index 37e30447ad..b644ab7d1e 100644 --- a/packages/neuron-wallet/src/controllers/dao.ts +++ b/packages/neuron-wallet/src/controllers/dao.ts @@ -6,6 +6,7 @@ import TransactionSender from '../services/transaction-sender' import OutPoint from '../models/chain/out-point' import Cell from '../models/chain/output' import Transaction from '../models/chain/transaction' +import MultisigConfigModel from '../models/multisig-config' export default class DaoController { public async getDaoCells(params: Controller.Params.GetDaoCellsParams): Promise> { @@ -22,6 +23,22 @@ export default class DaoController { } } + public async getMultisigDaoCells(params: { + multisigConfig: MultisigConfigModel + }): Promise> { + const { multisigConfig } = params + const cells = await CellsService.getMultisigDaoCells(multisigConfig) + + if (!cells) { + throw new ServiceHasNoResponse('DaoCells') + } + + return { + status: ResponseCode.Success, + result: cells, + } + } + public async generateDepositTx(params: { walletID: string capacity: string @@ -44,6 +61,28 @@ export default class DaoController { } } + public async generateMultisigDepositTx(params: { + capacity: string + fee: string + feeRate: string + multisigConfig: MultisigConfigModel + }): Promise> { + if (!params) { + throw new IsRequired('Parameters') + } + + const tx = await new TransactionSender().generateMultisigDepositTx( + params.capacity, + params.fee, + params.feeRate, + params.multisigConfig + ) + return { + status: ResponseCode.Success, + result: tx, + } + } + public async generateDepositAllTx(params: { walletID: string isBalanceReserved: boolean @@ -66,6 +105,28 @@ export default class DaoController { } } + public async generateMultisigDepositAllTx(params: { + isBalanceReserved: boolean + fee: string + feeRate: string + multisigConfig: MultisigConfigModel + }): Promise> { + if (!params) { + throw new IsRequired('Parameters') + } + + const tx = await new TransactionSender().generateMultisigDepositAllTx( + params.isBalanceReserved, + params.fee, + params.feeRate, + params.multisigConfig + ) + return { + status: ResponseCode.Success, + result: tx, + } + } + public async startWithdrawFromDao(params: { walletID: string outPoint: OutPointSDK @@ -88,6 +149,28 @@ export default class DaoController { } } + public async startWithdrawFromMultisigDao(params: { + outPoint: OutPointSDK + fee: string + feeRate: string + multisigConfig: MultisigConfigModel + }): Promise> { + if (!params) { + throw new IsRequired('Parameters') + } + + const tx = await new TransactionSender().startWithdrawFromMultisigDao( + OutPoint.fromSDK(params.outPoint), + params.fee, + params.feeRate, + params.multisigConfig + ) + return { + status: ResponseCode.Success, + result: tx, + } + } + public async withdrawFromDao(params: { walletID: string depositOutPoint: OutPointSDK @@ -112,6 +195,30 @@ export default class DaoController { } } + public async withdrawFromMultisigDao(params: { + depositOutPoint: OutPointSDK + withdrawingOutPoint: OutPointSDK + fee: string + feeRate: string + multisigConfig: MultisigConfigModel + }): Promise> { + if (!params) { + throw new IsRequired('Parameters') + } + + const tx = await new TransactionSender().withdrawFromMultisigDao( + OutPoint.fromSDK(params.depositOutPoint), + OutPoint.fromSDK(params.withdrawingOutPoint), + params.fee, + params.feeRate, + params.multisigConfig + ) + return { + status: ResponseCode.Success, + result: tx, + } + } + public async calculateUnlockDaoMaximumWithdraw(unlockHash: string): Promise> { const depositAndWithdrawInfo = await CellsService.getDaoWithdrawAndDeposit(unlockHash) let total = BigInt(0) diff --git a/packages/neuron-wallet/src/services/cells.ts b/packages/neuron-wallet/src/services/cells.ts index 04012b9d39..b51c438aa4 100644 --- a/packages/neuron-wallet/src/services/cells.ts +++ b/packages/neuron-wallet/src/services/cells.ts @@ -40,6 +40,7 @@ import HdPublicKeyInfo from '../database/chain/entities/hd-public-key-info' import CellLocalInfoService from './cell-local-info' import CellLocalInfo from '../database/chain/entities/cell-local-info' import { helpers } from '@ckb-lumos/lumos' +import logger from '../utils/logger' export interface PaginationResult { totalCount: number @@ -285,6 +286,75 @@ export default class CellsService { return cells } + public static async getMultisigDaoCells(multisigConfig: MultisigConfigModel): Promise { + const script = Multisig.getMultisigScript( + multisigConfig.blake160s, + multisigConfig.r, + multisigConfig.m, + multisigConfig.n + ) + + const multiSignBlake160 = script.args.slice(0, 42) + + logger.info('getMultisigDaoCells---multiSignBlake160----', multiSignBlake160) + + const outputs: OutputEntity[] = await getConnection() + .getRepository(OutputEntity) + .createQueryBuilder('output') + .leftJoinAndSelect('output.transaction', 'tx') + .where( + ` + output.daoData IS NOT NULL AND + output.lockArgs = :multiSignBlake160 AND + ( + output.status = :liveStatus OR + output.status = :sentStatus OR + tx.status = :failedStatus OR + ( + ( + output.status = :deadStatus OR + output.status = :pendingStatus + ) AND + output.depositTxHash is not null + ) + )`, + { + multiSignBlake160, + liveStatus: OutputStatus.Live, + sentStatus: OutputStatus.Sent, + failedStatus: TransactionStatus.Failed, + deadStatus: OutputStatus.Dead, + pendingStatus: OutputStatus.Pending, + } + ) + .orderBy(`CASE output.daoData WHEN '0x0000000000000000' THEN 1 ELSE 0 END`, 'ASC') + .addOrderBy('tx.timestamp', 'ASC') + .getMany() + + const cells: Cell[] = outputs.map(output => { + const cell = output.toModel() + if (!output.depositTxHash) { + // if deposit cell, set depositInfo + cell.setDepositInfo({ + txHash: output.transaction!.hash, + timestamp: output.transaction!.timestamp!, + }) + } else { + // if not deposit cell, set withdrawInfo + const withdrawTx = output.transaction + cell.setWithdrawInfo({ + txHash: withdrawTx!.hash, + timestamp: withdrawTx!.timestamp!, + }) + } + return cell + }) + + await Promise.all([CellsService.addDepositInfo(cells), CellsService.addUnlockInfo(cells)]) + + return cells + } + public static async getCustomizedAssetCells( blake160s: string[], pageNo: number, diff --git a/packages/neuron-wallet/src/services/transaction-sender.ts b/packages/neuron-wallet/src/services/transaction-sender.ts index ebe753e0c1..e2d482f474 100644 --- a/packages/neuron-wallet/src/services/transaction-sender.ts +++ b/packages/neuron-wallet/src/services/transaction-sender.ts @@ -666,6 +666,38 @@ export default class TransactionSender { return tx } + public generateMultisigDepositTx = async ( + capacity: string, + fee: string = '0', + feeRate: string = '0', + multisigConfig: MultisigConfigModel + ): Promise => { + const lockScript = Multisig.getMultisigScript( + multisigConfig.blake160s, + multisigConfig.r, + multisigConfig.m, + multisigConfig.n + ) + const multisigAddresses = scriptToAddress(lockScript, NetworksService.getInstance().isMainnet()) + + const tx = await TransactionGenerator.generateDepositTx( + '', + capacity, + multisigAddresses, + multisigAddresses, + fee, + feeRate, + { + lockArgs: [lockScript.args], + codeHash: SystemScriptInfo.MULTI_SIGN_CODE_HASH, + hashType: SystemScriptInfo.MULTI_SIGN_HASH_TYPE, + }, + multisigConfig + ) + + return tx + } + public startWithdrawFromDao = async ( walletID: string, outPoint: OutPoint, @@ -704,6 +736,53 @@ export default class TransactionSender { return tx } + public startWithdrawFromMultisigDao = async ( + outPoint: OutPoint, + fee: string = '0', + feeRate: string = '0', + multisigConfig: MultisigConfigModel + ): Promise => { + const lockScript = Multisig.getMultisigScript( + multisigConfig.blake160s, + multisigConfig.r, + multisigConfig.m, + multisigConfig.n + ) + const multisigAddresses = scriptToAddress(lockScript, NetworksService.getInstance().isMainnet()) + + const currentNetwork = NetworksService.getInstance().getCurrent() + const rpcService = new RpcService(currentNetwork.remote, currentNetwork.type) + const depositOutput = await CellsService.getLiveCell(outPoint) + if (!depositOutput) { + throw new CellIsNotYetLive() + } + const prevTx = await rpcService.getTransaction(outPoint.txHash) + if (!prevTx || !prevTx.txStatus.isCommitted()) { + throw new TransactionIsNotCommittedYet() + } + + const depositBlockHeader = await rpcService.getHeader(prevTx.txStatus.blockHash!) + + const tx: Transaction = await TransactionGenerator.startWithdrawFromDao( + '', + outPoint, + depositOutput, + depositBlockHeader!.number, + depositBlockHeader!.hash, + multisigAddresses, + fee, + feeRate, + { + lockArgs: [lockScript.args], + codeHash: SystemScriptInfo.MULTI_SIGN_CODE_HASH, + hashType: SystemScriptInfo.MULTI_SIGN_HASH_TYPE, + }, + multisigConfig + ) + + return tx + } + public withdrawFromDao = async ( walletID: string, depositOutPoint: OutPoint, @@ -815,6 +894,121 @@ export default class TransactionSender { return tx } + public withdrawFromMultisigDao = async ( + depositOutPoint: OutPoint, + withdrawingOutPoint: OutPoint, + fee: string = '0', + feeRate: string = '0', + multisigConfig: MultisigConfigModel + ): Promise => { + const DAO_LOCK_PERIOD_EPOCHS = BigInt(180) + + const feeInt = BigInt(fee) + const feeRateInt = BigInt(feeRate) + const mode = new FeeMode(feeRateInt) + + const currentNetwork = NetworksService.getInstance().getCurrent() + const rpcService = new RpcService(currentNetwork.remote, currentNetwork.type) + + const withdrawOutput = await CellsService.getLiveCell(withdrawingOutPoint) + if (!withdrawOutput) { + throw new CellIsNotYetLive() + } + const prevTx = (await rpcService.getTransaction(withdrawingOutPoint.txHash))! + if (!prevTx.txStatus.isCommitted()) { + throw new TransactionIsNotCommittedYet() + } + + const cellDep = await SystemScriptInfo.getInstance().getMultiSignCellDep() + const daoCellDep = await SystemScriptInfo.getInstance().getDaoCellDep() + + const content = withdrawOutput.daoData + if (!content) { + throw new Error(`Withdraw output cell is not a dao cell, ${withdrawOutput.outPoint?.txHash}`) + } + if (!withdrawOutput.depositOutPoint) { + throw new Error('DAO has not finish step first withdraw') + } + const depositTx = await rpcService.getTransaction(withdrawOutput.depositOutPoint.txHash) + if (!depositTx?.txStatus.blockHash) { + throw new Error(`Get deposit block hash failed with tx hash ${withdrawOutput.depositOutPoint.txHash}`) + } + const depositBlockHeader = await rpcService.getHeader(depositTx.txStatus.blockHash) + if (!depositBlockHeader) { + throw new Error(`Get Header failed with blockHash ${depositTx.txStatus.blockHash}`) + } + const depositEpoch = this.parseEpoch(BigInt(depositBlockHeader.epoch)) + const depositCapacity: bigint = BigInt(withdrawOutput.capacity) + + const withdrawBlockHeader = (await rpcService.getHeader(prevTx.txStatus.blockHash!))! + const withdrawEpoch = this.parseEpoch(BigInt(withdrawBlockHeader.epoch)) + + const withdrawFraction = withdrawEpoch.index * depositEpoch.length + const depositFraction = depositEpoch.index * withdrawEpoch.length + let depositedEpochs = withdrawEpoch.number - depositEpoch.number + if (withdrawFraction > depositFraction) { + depositedEpochs += BigInt(1) + } + const lockEpochs = + ((depositedEpochs + (DAO_LOCK_PERIOD_EPOCHS - BigInt(1))) / DAO_LOCK_PERIOD_EPOCHS) * DAO_LOCK_PERIOD_EPOCHS + const minimalSinceEpochNumber = depositEpoch.number + lockEpochs + const minimalSinceEpochIndex = depositEpoch.index + const minimalSinceEpochLength = depositEpoch.length + + const minimalSince = this.epochSince(minimalSinceEpochLength, minimalSinceEpochIndex, minimalSinceEpochNumber) + + const outputCapacity: bigint = await this.calculateDaoMaximumWithdraw(depositOutPoint, withdrawBlockHeader.hash) + + const lockScript = Multisig.getMultisigScript( + multisigConfig.blake160s, + multisigConfig.r, + multisigConfig.m, + multisigConfig.n + ) + const multisigAddresses = scriptToAddress(lockScript, NetworksService.getInstance().isMainnet()) + + const output: Output = new Output( + outputCapacity.toString(), + AddressParser.parse(multisigAddresses), + undefined, + '0x' + ) + + const outputs: Output[] = [output] + + const input: Input = new Input( + withdrawingOutPoint, + minimalSince.toString(), + withdrawOutput.capacity, + withdrawOutput.lock + ) + + const withdrawWitnessArgs: WitnessArgs = new WitnessArgs(WitnessArgs.EMPTY_LOCK, '0x0000000000000000') + const tx: Transaction = Transaction.fromObject({ + version: '0', + cellDeps: [cellDep, daoCellDep], + headerDeps: [depositBlockHeader.hash, withdrawBlockHeader.hash], + inputs: [input], + outputs, + outputsData: outputs.map(o => o.data || '0x'), + witnesses: [withdrawWitnessArgs], + interest: (BigInt(outputCapacity) - depositCapacity).toString(), + }) + if (mode.isFeeRateMode()) { + const txSize: number = TransactionSize.tx(tx) + const txFee: bigint = TransactionFee.fee(txSize, BigInt(feeRate)) + tx.fee = txFee.toString() + tx.outputs[0].capacity = (outputCapacity - txFee).toString() + } else { + tx.fee = fee + tx.outputs[0].capacity = (outputCapacity - feeInt).toString() + } + + logger.debug('withdrawFromDao fee:', tx.fee) + + return tx + } + public generateDepositAllTx = async ( walletID: string = '', isBalanceReserved = true, @@ -837,6 +1031,38 @@ export default class TransactionSender { return tx } + public generateMultisigDepositAllTx = async ( + isBalanceReserved = true, + fee: string = '0', + feeRate: string = '0', + multisigConfig: MultisigConfigModel + ): Promise => { + const lockScript = Multisig.getMultisigScript( + multisigConfig.blake160s, + multisigConfig.r, + multisigConfig.m, + multisigConfig.n + ) + const multisigAddresses = scriptToAddress(lockScript, NetworksService.getInstance().isMainnet()) + + const tx = await TransactionGenerator.generateDepositAllTx( + '', + multisigAddresses, + multisigAddresses, + isBalanceReserved, + fee, + feeRate, + { + lockArgs: [lockScript.args], + codeHash: SystemScriptInfo.MULTI_SIGN_CODE_HASH, + hashType: SystemScriptInfo.MULTI_SIGN_HASH_TYPE, + }, + multisigConfig + ) + + return tx + } + public async generateWithdrawMultiSignTx( walletID: string, outPoint: OutPoint, diff --git a/packages/neuron-wallet/src/services/tx/transaction-generator.ts b/packages/neuron-wallet/src/services/tx/transaction-generator.ts index ab8079ce94..f2609c2a30 100644 --- a/packages/neuron-wallet/src/services/tx/transaction-generator.ts +++ b/packages/neuron-wallet/src/services/tx/transaction-generator.ts @@ -408,26 +408,34 @@ export class TransactionGenerator { receiveAddress: string, changeAddress: string, fee: string = '0', - feeRate: string = '0' + feeRate: string = '0', + lockClass = { + lockArgs: [''], + codeHash: SystemScriptInfo.SECP_CODE_HASH, + hashType: ScriptHashType.Type, + }, + multisigConfig?: MultisigConfigModel ): Promise => { - const secpCellDep = await SystemScriptInfo.getInstance().getSecpCellDep() + let cellDep: CellDep + if (lockClass.codeHash === SystemScriptInfo.MULTI_SIGN_CODE_HASH) { + cellDep = await SystemScriptInfo.getInstance().getMultiSignCellDep() + } else { + cellDep = await SystemScriptInfo.getInstance().getSecpCellDep() + } const daoCellDep = await SystemScriptInfo.getInstance().getDaoCellDep() - const blake160: string = AddressParser.toBlake160(receiveAddress) + + const lockScript = AddressParser.parse(receiveAddress) const capacityInt: bigint = BigInt(capacity) - const output: Output = new Output( - capacity, - SystemScriptInfo.generateSecpScript(blake160), - SystemScriptInfo.generateDaoScript('0x') - ) + const output: Output = new Output(capacity, lockScript, SystemScriptInfo.generateDaoScript('0x')) output.setDaoData('0x0000000000000000') const outputs: Output[] = [output] const tx = Transaction.fromObject({ version: '0', - cellDeps: [secpCellDep, daoCellDep], + cellDeps: [cellDep, daoCellDep], headerDeps: [], inputs: [], outputs, @@ -444,18 +452,21 @@ export class TransactionGenerator { feeRate, baseSize, TransactionGenerator.CHANGE_OUTPUT_SIZE, - TransactionGenerator.CHANGE_OUTPUT_DATA_SIZE + TransactionGenerator.CHANGE_OUTPUT_DATA_SIZE, + undefined, + lockClass, + multisigConfig ? [multisigConfig] : [] ) const finalFeeInt = BigInt(finalFee) tx.inputs = inputs // change if (hasChangeOutput) { - const changeBlake160: string = AddressParser.toBlake160(changeAddress) + const lockScript = AddressParser.parse(changeAddress) const changeCapacity = BigInt(capacities) - capacityInt - finalFeeInt - const changeOutput = new Output(changeCapacity.toString(), SystemScriptInfo.generateSecpScript(changeBlake160)) + const changeOutput = new Output(changeCapacity.toString(), lockScript) tx.addOutput(changeOutput) } @@ -471,16 +482,34 @@ export class TransactionGenerator { changeAddress: string, isBalanceReserved = true, fee: string = '0', - feeRate: string = '0' + feeRate: string = '0', + lockClass = { + lockArgs: [''], + codeHash: SystemScriptInfo.SECP_CODE_HASH, + hashType: ScriptHashType.Type, + }, + multisigConfig?: MultisigConfigModel ): Promise => { - const secpCellDep = await SystemScriptInfo.getInstance().getSecpCellDep() + let cellDep: CellDep + if (lockClass.codeHash === SystemScriptInfo.MULTI_SIGN_CODE_HASH) { + cellDep = await SystemScriptInfo.getInstance().getMultiSignCellDep() + } else { + cellDep = await SystemScriptInfo.getInstance().getSecpCellDep() + } const daoCellDep = await SystemScriptInfo.getInstance().getDaoCellDep() const feeInt = BigInt(fee) const feeRateInt = BigInt(feeRate) const mode = new FeeMode(feeRateInt) - const allInputs: Input[] = await CellsService.gatherAllInputs(walletId) + const allInputs: Input[] = await CellsService.gatherAllInputs( + walletId, + multisigConfig + ? Script.fromSDK( + Multisig.getMultisigScript(multisigConfig.blake160s, multisigConfig.r, multisigConfig.m, multisigConfig.n) + ) + : undefined + ) if (allInputs.length === 0) { throw new CapacityNotEnough() } @@ -490,24 +519,20 @@ export class TransactionGenerator { const totalCapacity: bigint = allInputs.map(input => BigInt(input.capacity || 0)).reduce((result, c) => result + c, BigInt(0)) - reservedBalance - const receiveBlake160: string = AddressParser.toBlake160(receiveAddress) - const output = new Output( - totalCapacity.toString(), - SystemScriptInfo.generateSecpScript(receiveBlake160), - SystemScriptInfo.generateDaoScript('0x') - ) + const lockScript = AddressParser.parse(receiveAddress) + const output = new Output(totalCapacity.toString(), lockScript, SystemScriptInfo.generateDaoScript('0x')) output.setDaoData('0x0000000000000000') const outputs: Output[] = [output] if (isBalanceReserved) { - const changeBlake160 = AddressParser.toBlake160(changeAddress) - outputs.push(new Output(reservedBalance.toString(), SystemScriptInfo.generateSecpScript(changeBlake160))) + const lockScript = AddressParser.parse(changeAddress) + outputs.push(new Output(reservedBalance.toString(), lockScript)) } const tx = Transaction.fromObject({ version: '0', - cellDeps: [secpCellDep, daoCellDep], + cellDeps: [cellDep, daoCellDep], headerDeps: [], inputs: allInputs, outputs, @@ -541,9 +566,20 @@ export class TransactionGenerator { depositBlockHash: string, changeAddress: string, fee: string = '0', - feeRate: string = '0' + feeRate: string = '0', + lockClass = { + lockArgs: [''], + codeHash: SystemScriptInfo.SECP_CODE_HASH, + hashType: ScriptHashType.Type, + }, + multisigConfig?: MultisigConfigModel ): Promise => { - const secpCellDep = await SystemScriptInfo.getInstance().getSecpCellDep() + let cellDep: CellDep + if (lockClass.codeHash === SystemScriptInfo.MULTI_SIGN_CODE_HASH) { + cellDep = await SystemScriptInfo.getInstance().getMultiSignCellDep() + } else { + cellDep = await SystemScriptInfo.getInstance().getSecpCellDep() + } const daoCellDep = await SystemScriptInfo.getInstance().getDaoCellDep() const output = prevOutput @@ -556,7 +592,7 @@ export class TransactionGenerator { const tx = Transaction.fromObject({ version: '0', - cellDeps: [secpCellDep, daoCellDep], + cellDeps: [cellDep, daoCellDep], headerDeps: [depositBlockHash], inputs: [], outputs, @@ -581,7 +617,9 @@ export class TransactionGenerator { baseSize, TransactionGenerator.CHANGE_OUTPUT_SIZE, TransactionGenerator.CHANGE_OUTPUT_DATA_SIZE, - append + append, + lockClass, + multisigConfig ? [multisigConfig] : [] ) const finalFeeInt = BigInt(finalFee) @@ -594,10 +632,10 @@ export class TransactionGenerator { // change if (hasChangeOutput) { - const changeBlake160: string = AddressParser.toBlake160(changeAddress) + const lockScript = AddressParser.parse(changeAddress) const changeCapacity = BigInt(capacities) - finalFeeInt - const changeOutput = new Output(changeCapacity.toString(), SystemScriptInfo.generateSecpScript(changeBlake160)) + const changeOutput = new Output(changeCapacity.toString(), lockScript) tx.addOutput(changeOutput) }