From 83c0abcaf643cc7e17413863caf66480e6059ee1 Mon Sep 17 00:00:00 2001 From: yanguoyu <841185308@qq.com> Date: Tue, 9 Jul 2024 16:58:25 +0800 Subject: [PATCH] feat: Support XUDT --- packages/neuron-ui/package.json | 14 +- .../CellManagement/cellManagement.module.scss | 2 +- .../src/components/CellManagement/hooks.ts | 7 +- .../src/components/CellManagement/index.tsx | 4 +- .../src/components/NervosDAO/hooks.ts | 2 +- .../src/components/SUDTAccountList/index.tsx | 10 +- .../src/components/SUDTCreateDialog/index.tsx | 35 ++- .../components/SUDTMigrateDialog/index.tsx | 8 +- .../SUDTMigrateToExistAccountDialog/index.tsx | 13 +- .../SUDTMigrateToNewAccountDialog/index.tsx | 7 +- .../src/components/SUDTUpdateDialog/index.tsx | 10 +- .../src/components/SpecialAssetList/hooks.ts | 16 +- .../src/components/SpecialAssetList/index.tsx | 50 ++-- .../src/components/TransactionType/index.tsx | 2 +- packages/neuron-ui/src/locales/en.json | 25 +- packages/neuron-ui/src/locales/es.json | 25 +- packages/neuron-ui/src/locales/fr.json | 25 +- packages/neuron-ui/src/locales/zh-tw.json | 25 +- packages/neuron-ui/src/locales/zh.json | 25 +- .../neuron-ui/src/services/remote/sudt.ts | 5 - .../src/states/stateProvider/reducer.ts | 3 +- .../src/stories/SUDTCreateDialog.stories.tsx | 6 +- packages/neuron-ui/src/types/App/index.d.ts | 9 +- .../neuron-ui/src/types/Controller/index.d.ts | 15 +- packages/neuron-ui/src/utils/enums.ts | 7 + .../src/utils/hooks/createSUDTAccount.ts | 35 ++- packages/neuron-ui/src/utils/is.ts | 28 ++ packages/neuron-wallet/package.json | 18 +- .../sync/light-synchronizer.ts | 1 + .../src/block-sync-renderer/sync/queue.ts | 6 +- .../sync/tx-address-finder.ts | 15 + packages/neuron-wallet/src/controllers/api.ts | 4 - .../src/controllers/asset-account.ts | 14 +- .../neuron-wallet/src/controllers/sudt.ts | 10 - .../database/chain/entities/asset-account.ts | 14 +- .../chain/entities/sudt-token-info.ts | 10 +- .../migrations/1720089814860-AddUdtType.ts | 22 ++ .../src/database/chain/ormconfig.ts | 2 + .../src/models/asset-account-info.ts | 86 +++--- .../neuron-wallet/src/models/asset-account.ts | 11 +- .../src/models/chain/transaction.ts | 1 + .../src/services/anyone-can-pay.ts | 12 +- .../src/services/asset-account-service.ts | 144 ++++++--- packages/neuron-wallet/src/services/cells.ts | 25 +- .../src/services/sudt-token-info.ts | 6 +- .../src/services/tx/transaction-generator.ts | 125 +++++--- .../src/services/tx/transaction-service.ts | 283 +++++++++++------- packages/neuron-wallet/src/utils/ckb-rpc.ts | 2 +- packages/neuron-wallet/src/utils/const.ts | 5 + .../tests/models/asset-account-info.test.ts | 20 +- .../tests/services/anyone-can-pay.test.ts | 18 +- .../services/asset-account-service.test.ts | 59 +++- .../tests/services/cells.test.ts | 27 +- .../tests/services/multisig.test.ts | 16 +- .../tests/services/networks.test.ts | 37 ++- .../tests/services/sudt-token-info.test.ts | 7 +- .../services/tx/transaction-generator.test.ts | 82 +++-- .../setupAndTeardown/accounts.fixture.ts | 2 + yarn.lock | 129 +++++++- 59 files changed, 1108 insertions(+), 518 deletions(-) create mode 100644 packages/neuron-wallet/src/database/chain/migrations/1720089814860-AddUdtType.ts diff --git a/packages/neuron-ui/package.json b/packages/neuron-ui/package.json index 82ca18157c..8339f2b9f0 100644 --- a/packages/neuron-ui/package.json +++ b/packages/neuron-ui/package.json @@ -50,13 +50,13 @@ "last 2 chrome versions" ], "dependencies": { - "@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/helpers": "0.21.1", - "@ckb-lumos/config-manager": "0.21.1", - "@ckb-lumos/common-scripts": "0.21.1", + "@ckb-lumos/bi": "0.23.0", + "@ckb-lumos/rpc": "0.23.0", + "@ckb-lumos/base": "0.23.0", + "@ckb-lumos/codec": "0.23.0", + "@ckb-lumos/helpers": "0.23.0", + "@ckb-lumos/config-manager": "0.23.0", + "@ckb-lumos/common-scripts": "0.23.0", "canvg": "2.0.0", "i18next": "23.7.11", "immer": "9.0.21", diff --git a/packages/neuron-ui/src/components/CellManagement/cellManagement.module.scss b/packages/neuron-ui/src/components/CellManagement/cellManagement.module.scss index a142528c7e..a36bad6e1e 100644 --- a/packages/neuron-ui/src/components/CellManagement/cellManagement.module.scss +++ b/packages/neuron-ui/src/components/CellManagement/cellManagement.module.scss @@ -53,7 +53,7 @@ .actions { display: flex; gap: 16px; - & > svg { + & svg { cursor: pointer; &[data-disabled='true'] { cursor: not-allowed; diff --git a/packages/neuron-ui/src/components/CellManagement/hooks.ts b/packages/neuron-ui/src/components/CellManagement/hooks.ts index 1823cc98c0..4977cc6ced 100644 --- a/packages/neuron-ui/src/components/CellManagement/hooks.ts +++ b/packages/neuron-ui/src/components/CellManagement/hooks.ts @@ -18,7 +18,8 @@ import { ErrorCode, LockScriptCategory, RoutePath, TypeScriptCategory, isSuccess import { SortType } from 'widgets/Table' const cellTypeOrder: Record = { - [TypeScriptCategory.SUDT]: 1, + [TypeScriptCategory.SUDT]: 0, + [TypeScriptCategory.XUDT]: 1, [TypeScriptCategory.NFT]: 2, [TypeScriptCategory.Spore]: 3, [TypeScriptCategory.Unknown]: 4, @@ -47,6 +48,9 @@ const getLockStatusAndReason = (item: State.LiveCellWithLocalInfo) => { case TypeScriptCategory.DAO: lockedReason = { key: 'cell-manage.locked-reason.NFT-SUDT-DAO', params: { type: 'Nervos DAO' } } break + case TypeScriptCategory.XUDT: + lockedReason = { key: 'cell-manage.locked-reason.NFT-SUDT-DAO', params: { type: 'XUDT' } } + break case TypeScriptCategory.Unknown: lockedReason = { key: 'cell-manage.locked-reason.Unknown' } break @@ -82,6 +86,7 @@ const getCellType = (item: State.LiveCellWithLocalInfo) => { switch (item.typeScriptType) { case TypeScriptCategory.NFT: case TypeScriptCategory.SUDT: + case TypeScriptCategory.XUDT: case TypeScriptCategory.Spore: case TypeScriptCategory.Unknown: return item.typeScriptType diff --git a/packages/neuron-ui/src/components/CellManagement/index.tsx b/packages/neuron-ui/src/components/CellManagement/index.tsx index 186e884d6a..46b4e8fd58 100644 --- a/packages/neuron-ui/src/components/CellManagement/index.tsx +++ b/packages/neuron-ui/src/components/CellManagement/index.tsx @@ -193,7 +193,7 @@ const getColumns = ({ @@ -202,7 +202,7 @@ const getColumns = ({ diff --git a/packages/neuron-ui/src/components/NervosDAO/hooks.ts b/packages/neuron-ui/src/components/NervosDAO/hooks.ts index 93498cadf1..ff239c7742 100644 --- a/packages/neuron-ui/src/components/NervosDAO/hooks.ts +++ b/packages/neuron-ui/src/components/NervosDAO/hooks.ts @@ -337,7 +337,7 @@ export const useUpdateDepositEpochList = ({ useEffect(() => { if (connectionStatus === 'online') { getBlockHashes(records.map(v => v.depositOutPoint?.txHash).filter(v => !!v) as string[]).then( - (depositBlockHashes: { txHash: string; blockHash: string | null }[]) => { + (depositBlockHashes: { txHash: string; blockHash: string | undefined }[]) => { const recordKeyIdx: string[] = [] const batchParams: ['getHeader', string][] = [] records.forEach(record => { diff --git a/packages/neuron-ui/src/components/SUDTAccountList/index.tsx b/packages/neuron-ui/src/components/SUDTAccountList/index.tsx index fa4f6972ef..b961ca079d 100644 --- a/packages/neuron-ui/src/components/SUDTAccountList/index.tsx +++ b/packages/neuron-ui/src/components/SUDTAccountList/index.tsx @@ -24,6 +24,7 @@ import { isSuccessResponse, useIsInsufficientToCreateSUDTAccount, useOnGenerateNewAccountTransaction, + UDTType, } from 'utils' import { getSUDTAccountList, updateSUDTAccount } from 'services/remote' @@ -48,7 +49,11 @@ const SUDTAccountList = () => { const [keyword, setKeyword] = useState('') const [dialog, setDialog] = useState<{ id: string; action: 'create' | 'update' } | null>(null) const [isLoaded, setIsLoaded] = useState(false) - const [insufficient, setInsufficient] = useState({ [AccountType.CKB]: false, [AccountType.SUDT]: false }) + const [insufficient, setInsufficient] = useState({ + [AccountType.CKB]: false, + [AccountType.SUDT]: false, + [AccountType.XUDT]: false, + }) const isMainnet = isMainnetUtil(networks, networkID) const [receiveData, setReceiveData] = useState(null) @@ -178,6 +183,7 @@ const SUDTAccountList = () => { tokenName: accountToUpdate.tokenName || DEFAULT_SUDT_FIELDS.tokenName, symbol: accountToUpdate.symbol || DEFAULT_SUDT_FIELDS.symbol, isCKB: accountToUpdate.tokenId === DEFAULT_SUDT_FIELDS.CKBTokenId, + udtType: accountToUpdate.udtType, onSubmit: (info: Omit) => { const params: any = { id: accountToUpdate.accountId } Object.keys(info).forEach(key => { @@ -208,7 +214,7 @@ const SUDTAccountList = () => { : undefined const handleCreateAccount = useCallback( - (info: TokenInfo) => { + (info: TokenInfo & { udtType?: UDTType }) => { createAccount(info, () => { setNotice(t('s-udt.create-account-success')) }) diff --git a/packages/neuron-ui/src/components/SUDTCreateDialog/index.tsx b/packages/neuron-ui/src/components/SUDTCreateDialog/index.tsx index fa9c5a0b19..ed7ea75805 100644 --- a/packages/neuron-ui/src/components/SUDTCreateDialog/index.tsx +++ b/packages/neuron-ui/src/components/SUDTCreateDialog/index.tsx @@ -10,13 +10,15 @@ import { isSuccessResponse, useSUDTAccountInfoErrors, useFetchTokenInfoList, - useOpenSUDTTokenUrl, + useOpenUDTTokenUrl, + UDTType, } from 'utils' import { DEFAULT_SUDT_FIELDS } from 'utils/const' import styles from './sUDTCreateDialog.module.scss' export enum AccountType { SUDT = 'sudt', + XUDT = 'xudt', CKB = 'ckb', } @@ -33,10 +35,10 @@ export interface TokenInfo extends BasicInfo { export interface SUDTCreateDialogProps extends TokenInfo { isMainnet: boolean - onSubmit: (info: TokenInfo) => void + onSubmit: (info: TokenInfo & { udtType?: UDTType }) => void onCancel: () => void existingAccountNames?: string[] - insufficient?: { [AccountType.CKB]: boolean; [AccountType.SUDT]: boolean } + insufficient?: { [P in AccountType]: boolean } } enum DialogSection { @@ -49,6 +51,10 @@ const accountTypes: { key: AccountType; label: string }[] = [ key: AccountType.SUDT, label: 's-udt.create-dialog.sudt-account', }, + { + key: AccountType.XUDT, + label: 's-udt.create-dialog.xudt-account', + }, { key: AccountType.CKB, label: 's-udt.create-dialog.ckb-account', @@ -121,13 +127,18 @@ const SUDTCreateDialog = ({ onSubmit, onCancel, existingAccountNames = [], - insufficient = { [AccountType.CKB]: false, [AccountType.SUDT]: false }, + insufficient = { [AccountType.CKB]: false, [AccountType.SUDT]: false, [AccountType.XUDT]: false }, isMainnet, }: Partial> & Pick) => { const [t] = useTranslation() const [info, dispatch] = useReducer(reducer, { accountName, tokenId, tokenName, symbol, decimal }) - const [accountType, setAccountType] = useState([AccountType.SUDT, AccountType.CKB].find(at => !insufficient[at])) + const [accountType, setAccountType] = useState( + [AccountType.SUDT, AccountType.CKB, AccountType.XUDT].find(at => !insufficient[at]) + ) + const isUDT = accountType === AccountType.SUDT || accountType === AccountType.XUDT + // eslint-disable-next-line no-nested-ternary + const udtType = isUDT ? (accountType === AccountType.SUDT ? UDTType.SUDT : UDTType.XUDT) : undefined const [step, setStep] = useState(DialogSection.Account) const tokenInfoList = useFetchTokenInfoList() @@ -135,7 +146,7 @@ const SUDTCreateDialog = ({ const tokenErrors = useSUDTAccountInfoErrors({ info, - isCKB: AccountType.CKB === accountType, + isCKB: !isUDT, existingAccountNames, t, }) @@ -190,7 +201,7 @@ const SUDTCreateDialog = ({ } case DialogSection.Token: { if (isTokenReady) { - onSubmit({ ...info, accountName: info.accountName.trim(), tokenName: info.tokenName.trim() }) + onSubmit({ ...info, udtType, accountName: info.accountName.trim(), tokenName: info.tokenName.trim() }) } break } @@ -216,7 +227,7 @@ const SUDTCreateDialog = ({ } } } - const openSUDTTokenUrl = useOpenSUDTTokenUrl(info.tokenId, isMainnet) + const openSUDTTokenUrl = useOpenUDTTokenUrl(info.tokenId, udtType, isMainnet) return ( ))} - {accountType === AccountType.SUDT && !tokenErrors.tokenId && info.tokenId && ( + {isUDT && !tokenErrors.tokenId && info.tokenId && ( )} diff --git a/packages/neuron-ui/src/components/SpecialAssetList/hooks.ts b/packages/neuron-ui/src/components/SpecialAssetList/hooks.ts index 206ea8ceb1..fd26901107 100644 --- a/packages/neuron-ui/src/components/SpecialAssetList/hooks.ts +++ b/packages/neuron-ui/src/components/SpecialAssetList/hooks.ts @@ -180,13 +180,6 @@ export const useSpecialAssetColumnInfo = ({ } break } - case PresetScript.SUDT: { - status = 'user-defined-token' - const tokenInfo = tokenInfoList.find(info => info.tokenID === type?.args) - const amountInfo = getSUDTAmount({ tokenInfo, data }) - amount = amountInfo.amount - break - } default: { // ignore } @@ -220,6 +213,14 @@ export const useSpecialAssetColumnInfo = ({ amount = t('special-assets.unknown-asset') break } + case PresetScript.XUDT: + case PresetScript.SUDT: { + status = 'user-defined-token' + const tokenInfo = tokenInfoList.find(info => info.tokenID === type?.args) + const amountInfo = getSUDTAmount({ tokenInfo, data }) + amount = amountInfo.amount + break + } default: { break } @@ -235,6 +236,7 @@ export const useSpecialAssetColumnInfo = ({ epochsInfo, isSpore, sporeClusterInfo, + udtType: assetInfo.type, } }, [epoch, bestKnownBlockTimestamp, tokenInfoList, t] diff --git a/packages/neuron-ui/src/components/SpecialAssetList/index.tsx b/packages/neuron-ui/src/components/SpecialAssetList/index.tsx index 171aa745cb..7f4c5eb6b9 100644 --- a/packages/neuron-ui/src/components/SpecialAssetList/index.tsx +++ b/packages/neuron-ui/src/components/SpecialAssetList/index.tsx @@ -238,6 +238,7 @@ const SpecialAssetList = () => { symbol: (accountToClaim.account.symbol || foundTokenInfo?.symbol) ?? '', decimal: (accountToClaim.account.decimal || foundTokenInfo?.decimal) ?? '', isCKB: false, + udtType: accountToClaim.account.udtType, onSubmit: (info: Omit) => { const params: any = accountToClaim?.account || {} Object.keys(info).forEach(key => { @@ -371,6 +372,12 @@ const SpecialAssetList = () => { } break } + default: { + // ignore + } + } + switch (cell.customizedAssetInfo.type) { + case PresetScript.XUDT: case PresetScript.SUDT: { setMigrateCell(cell) const findTokenInfo = tokenInfoList.find(info => info.tokenID === cell.type?.args) @@ -379,9 +386,8 @@ const SpecialAssetList = () => { setIsMigrateDialogOpen(true) break } - default: { - // ignore - } + default: + break } }, [cells, id, dispatch, setAccountToClaim, navigate, setIsMigrateDialogOpen, tokenInfoList, handleActionSuccess] @@ -437,8 +443,15 @@ const SpecialAssetList = () => { customizedAssetInfo, } = item - const { status, targetTime, isLockedCheque, isNFTTransferable, isNFTClassOrIssuer, epochsInfo } = - handleGetSpecialAssetColumnInfo(item) + const { + status, + targetTime, + isLockedCheque, + isNFTTransferable, + isNFTClassOrIssuer, + epochsInfo, + udtType, + } = handleGetSpecialAssetColumnInfo(item) if (isNFTClassOrIssuer || (customizedAssetInfo.type === NFTType.NFT && !isNFTTransferable)) { return ( @@ -458,22 +471,15 @@ const SpecialAssetList = () => { ['user-defined-asset', 'locked-asset', 'user-defined-token'].includes(status) || isLockedCheque if (showTip) { - if (customizedAssetInfo.lock !== PresetScript.Cheque || isLockedCheque) { - tip = t(`special-assets.${status}-tooltip`, { - epochs: epochsInfo?.target.toFixed(2), - year: targetTime ? new Date(targetTime).getFullYear() : '', - month: targetTime ? new Date(targetTime).getMonth() + 1 : '', - day: targetTime ? new Date(targetTime).getDate() : '', - hour: targetTime ? new Date(targetTime).getHours() : '', - minute: targetTime ? new Date(targetTime).getMinutes() : '', - }) - } - if (status === 'user-defined-token') { - tip = t('special-assets.user-defined-asset-tooltip') - } - if (status === 'user-defined-token') { - tip = t('special-assets.user-defined-token-tooltip') - } + tip = t(`special-assets.${status}-tooltip`, { + udtType, + epochs: epochsInfo?.target.toFixed(2), + year: targetTime ? new Date(targetTime).getFullYear() : '', + month: targetTime ? new Date(targetTime).getMonth() + 1 : '', + day: targetTime ? new Date(targetTime).getDate() : '', + hour: targetTime ? new Date(targetTime).getHours() : '', + minute: targetTime ? new Date(targetTime).getMinutes() : '', + }) } const btnDisabled = @@ -490,7 +496,7 @@ const SpecialAssetList = () => { data-idx={index} data-status={status} type="primary" - label={t(`special-assets.${status}`)} + label={t(`special-assets.${status}`, { udtType })} className={styles.actionBtn} onClick={handleAction} disabled={!!btnDisabled} diff --git a/packages/neuron-ui/src/components/TransactionType/index.tsx b/packages/neuron-ui/src/components/TransactionType/index.tsx index 59ed2b91ab..068702b92f 100644 --- a/packages/neuron-ui/src/components/TransactionType/index.tsx +++ b/packages/neuron-ui/src/components/TransactionType/index.tsx @@ -74,7 +74,7 @@ const TransactionType = ({ i18nKey: `overview.${item.type}SUDT`, components: [ , diff --git a/packages/neuron-ui/src/locales/en.json b/packages/neuron-ui/src/locales/en.json index 65b3341a08..ddf3606e1e 100644 --- a/packages/neuron-ui/src/locales/en.json +++ b/packages/neuron-ui/src/locales/en.json @@ -512,10 +512,10 @@ "title": "Create Asset Account" }, "send-sudt": { - "title": "Send sUDT" + "title": "Send UDT" }, "transfer-to-sudt": { - "title": "Send to sUDT account" + "title": "Send to UDT account" }, "send-ckb-asset": { "title": "Send CKB" @@ -524,7 +524,7 @@ "title": "Send CKB" }, "send-acp-sudt-to-new-cell": { - "title": "Send sUDT" + "title": "Send UDT" }, "send-acp-ckb-to-new-cell": { "title": "Send CKB" @@ -956,7 +956,7 @@ "locked-asset": "Locked", "locked-asset-tooltip": "Lock parameter is {{epochs}} epochs, estimated release date is {{year}}-{{month}}-{{day}}(according to the actual running block height, there may be some time variances in locktime).", "withdraw-asset-tooltip": "Estimate release time is {{year}}-{{month}}-{{day}} {{hour}}:{{minute}}(according to the actual running block height).", - "user-defined-token-tooltip": "Migrate sUDT asset to a sUDT Asset Account", + "user-defined-token-tooltip": "Migrate {{udtType}} asset to a {{udtType}} Asset Account", "claim-asset": "Claim", "withdraw-asset": "Withdraw", "view-details": "View Details", @@ -967,14 +967,14 @@ "transfer-nft": "Send", "user-defined-token": "Migrate", "transfer-nft-success": "Transfer of assets successful", - "migrate-sudt-success": "Conversion to new sUDT asset account successful", - "send-sudt-success": "Transfer to sUDT account successful", + "migrate-sudt-success": "Conversion to new {{udtType}} asset account successful", + "send-sudt-success": "Transfer to {{udtType}} account successful", "unlock-success": "Asset claimed, go to transaction history for details", "withdraw-cheque-success": "Asset withdrawn, go to transaction history for details", "claim-cheque-success": "Asset claimed, go to transaction history for details" }, "migrate-sudt": { - "title": "Migrate to a sUDT Asset Account", + "title": "Migrate to a {{udtType}} Asset Account", "choose-title": "Migration mode", "next": "Next", "back": "Back", @@ -982,14 +982,14 @@ "input-symbol": "Please input symbol", "input-decimal": "Please input decimal", "turn-into-new-account": { - "title": "Turn into a new sUDT Asset Account", - "sub-title": "Turn the sUDT Asset into a new sUDT Account, occupies at least 142 CKBytes", + "title": "Turn into a new {{udtType}} Asset Account", + "sub-title": "Turn the {{udtType}} Asset into a new {{udtType}} Account, occupies at least 142 CKBytes", "cancel": "Cancel", "confirm": "Confirm" }, "transfer-to-exist-account": { - "title": "Transfer to a sUDT Asset Account", - "sub-title": "Transfer all sUDT balance to an existing sUDT Account, please make sure the target account is alive" + "title": "Transfer to a {{udtType}} Asset Account", + "sub-title": "Transfer all {{udtType}} balance to an existing {{udtType}} Account, please make sure the target account is alive" }, "cancel": "Cancel", "confirm": "Confirm", @@ -1015,6 +1015,7 @@ "select-account-type": "Select account type", "account-name": "Account Name", "sudt-account": "sUDT Account", + "xudt-account": "xUDT Account", "delete-failed": "Failed to delete the multisig config, reason for failure: {{reason}}", "ckb-account": "CKB Account", "set-token-info": "Set Token Info", @@ -1036,7 +1037,7 @@ "decimal": "Please input decimal" }, "placeholder": { - "token-id": "Token ID is the Args of the sUDT Type Script, which is the same as the lock hash of the token issuer." + "token-id": "Token ID is the Args of the {{udtType}} Type Script, which is the same as the lock hash of the token issuer." } }, "send": { diff --git a/packages/neuron-ui/src/locales/es.json b/packages/neuron-ui/src/locales/es.json index b8561c404f..28d879eef7 100644 --- a/packages/neuron-ui/src/locales/es.json +++ b/packages/neuron-ui/src/locales/es.json @@ -495,10 +495,10 @@ "title": "Crear Cuenta de Activos" }, "send-sudt": { - "title": "Enviar sUDT" + "title": "Enviar UDT" }, "transfer-to-sudt": { - "title": "Enviar a cuenta sUDT" + "title": "Enviar a cuenta UDT" }, "send-ckb-asset": { "title": "Enviar CKB" @@ -507,7 +507,7 @@ "title": "Enviar CKB" }, "send-acp-sudt-to-new-cell": { - "title": "Enviar sUDT" + "title": "Enviar UDT" }, "send-acp-ckb-to-new-cell": { "title": "Enviar CKB" @@ -939,7 +939,7 @@ "locked-asset": "Bloqueado", "locked-asset-tooltip": "El parámetro de bloqueo es de {{epochs}} épocas, la fecha de lanzamiento estimada es {{year}}-{{month}}-{{day}} (según la altura del bloque en ejecución real, pueden haber algunas variaciones de tiempo en el tiempo de bloqueo).", "withdraw-asset-tooltip": "Hora estimada de lanzamiento es {{year}}-{{month}}-{{day}} {{hour}}:{{minute}} (según la altura del bloque en ejecución real).", - "user-defined-token-tooltip": "Migre el activo sUDT a una cuenta de activos sUDT", + "user-defined-token-tooltip": "Migre el activo {{udtType}} a una cuenta de activos {{udtType}}", "claim-asset": "Reclamar", "withdraw-asset": "Retirar", "view-details": "Ver detalles", @@ -950,14 +950,14 @@ "transfer-nft": "Enviar", "user-defined-token": "Migrar", "transfer-nft-success": "Transferencia de activos exitosa", - "migrate-sudt-success": "Conversión a nueva cuenta de activo sUDT exitosa", - "send-sudt-success": "Transferencia exitosa a la cuenta de activo sUDT", + "migrate-sudt-success": "Conversión a nueva cuenta de activo {{udtType}} exitosa", + "send-sudt-success": "Transferencia exitosa a la cuenta de activo {{udtType}}", "unlock-success": "Activo reclamado. Vaya al historial de transacciones para obtener más detalles.", "withdraw-cheque-success": "El activo fue retirado. Vaya al historial de transacciones para obtener más detalles.", "claim-cheque-success": "Activo reclamado. Vaya al historial de transacciones para obtener más detalles." }, "migrate-sudt": { - "title": "Migrar a una cuenta de activo sUDT", + "title": "Migrar a una cuenta de activo {{udtType}}", "choose-title": "Modo de migración", "next": "Siguiente", "back": "Atrás", @@ -965,14 +965,14 @@ "input-symbol": "Por favor ingrese el símbolo", "input-decimal": "Por favor ingrese el decimal", "turn-into-new-account": { - "title": "Conviértalo en una nueva cuenta de activo sUDT", - "sub-title": "Convierta el activo sUDT en una nueva cuenta sUDT, ocupa al menos 142 CKbytes.", + "title": "Conviértalo en una nueva cuenta de activo {{udtType}}", + "sub-title": "Convierta el activo {{udtType}} en una nueva cuenta {{udtType}}, ocupa al menos 142 CKbytes.", "cancel": "Cancelar", "confirm": "Confirmar" }, "transfer-to-exist-account": { - "title": "Transferir a una cuenta de activo sUDT", - "sub-title": "Transfiera todo el saldo sUDT a una cuenta sUDT existente. Asegúrese de que la cuenta objetivo esté activa." + "title": "Transferir a una cuenta de activo {{udtType}}", + "sub-title": "Transfiera todo el saldo {{udtType}} a una cuenta {{udtType}} existente. Asegúrese de que la cuenta objetivo esté activa." }, "cancel": "Cancelar", "confirm": "Confirmar", @@ -998,6 +998,7 @@ "select-account-type": "Seleccione el tipo de cuenta", "account-name": "Nombre de la cuenta", "sudt-account": "Cuenta de activos sUDT", + "xudt-account": "Cuenta de activos xUDT", "delete-failed": "No se pudo eliminar la configuración multifirma, el motivo del fallo: {{reason}}", "ckb-account": "Cuenta CKB", "set-token-info": "Establecer información del token", @@ -1019,7 +1020,7 @@ "decimal": "Ingrese el decimal" }, "placeholder": { - "token-id": "El ID del token es el Args del sUDT Type Script, que es el mismo que el hash de bloqueo del emisor del token." + "token-id": "El ID del token es el Args del {{udtType}} Type Script, que es el mismo que el hash de bloqueo del emisor del token." } }, "send": { diff --git a/packages/neuron-ui/src/locales/fr.json b/packages/neuron-ui/src/locales/fr.json index 985df1b7c9..689d9679fb 100644 --- a/packages/neuron-ui/src/locales/fr.json +++ b/packages/neuron-ui/src/locales/fr.json @@ -502,10 +502,10 @@ "title": "Créer un compte d'actif" }, "send-sudt": { - "title": "Envoyer sUDT" + "title": "Envoyer UDT" }, "transfer-to-sudt": { - "title": "Envoyer vers le compte sUDT" + "title": "Envoyer vers le compte UDT" }, "send-ckb-asset": { "title": "Envoyer CKB" @@ -514,7 +514,7 @@ "title": "Envoyer CKB" }, "send-acp-sudt-to-new-cell": { - "title": "Envoyer sUDT" + "title": "Envoyer UDT" }, "send-acp-ckb-to-new-cell": { "title": "Envoyer CKB" @@ -946,7 +946,7 @@ "locked-asset": "Verrouillé", "locked-asset-tooltip": "Le paramètre de verrouillage est de {{epochs}} époques, la date de libération estimée est le {{year}}-{{month}}-{{day}} (selon la hauteur de bloc réelle en cours d'exécution, il peut y avoir des variances de temps dans le verrouillage).", "withdraw-asset-tooltip": "L'heure de libération estimée est le {{year}}-{{month}}-{{day}} à {{hour}}:{{minute}} (selon la hauteur de bloc réelle en cours d'exécution).", - "user-defined-token-tooltip": "Migrer l'actif sUDT vers un compte d'actif sUDT", + "user-defined-token-tooltip": "Migrer l'actif {{udtType}} vers un compte d'actif {{udtType}}", "claim-asset": "Réclamation", "withdraw-asset": "Retrait", "view-details": "Voir les détails", @@ -957,14 +957,14 @@ "transfer-nft": "Envoyer", "user-defined-token": "Migrer", "transfer-nft-success": "Transfert d'actifs réussi", - "migrate-sudt-success": "Conversion en nouveau compte d'actif sUDT réussie", - "send-sudt-success": "Transfert vers le compte sUDT réussi", + "migrate-sudt-success": "Conversion en nouveau compte d'actif {{udtType}} réussie", + "send-sudt-success": "Transfert vers le compte {{udtType}} réussi", "unlock-success": "Actif réclamé, consultez l'historique des transactions pour plus de détails", "withdraw-cheque-success": "Actif retiré, consultez l'historique des transactions pour plus de détails", "claim-cheque-success": "Actif réclamé, consultez l'historique des transactions pour plus de détails" }, "migrate-sudt": { - "title": "Migrer vers un compte d'actif sUDT", + "title": "Migrer vers un compte d'actif {{udtType}}", "choose-title": "Mode de migration", "next": "Suivant", "back": "Retour", @@ -972,14 +972,14 @@ "input-symbol": "Veuillez saisir le symbole", "input-decimal": "Veuillez saisir la décimale", "turn-into-new-account": { - "title": "Transformez en un nouveau compte d'actif sUDT", - "sub-title": "Transformez l'actif sUDT en un nouveau compte sUDT, occupe au moins 142 CKBytes", + "title": "Transformez en un nouveau compte d'actif {{udtType}}", + "sub-title": "Transformez l'actif {{udtType}} en un nouveau compte {{udtType}}, occupe au moins 142 CKBytes", "cancel": "Annuler", "confirm": "Confirmer" }, "transfer-to-exist-account": { - "title": "Transférer vers un compte d'actif sUDT", - "sub-title": "Transférez tout le solde sUDT vers un compte sUDT existant, assurez-vous que le compte cible est actif" + "title": "Transférer vers un compte d'actif {{udtType}}", + "sub-title": "Transférez tout le solde {{udtType}} vers un compte {{udtType}} existant, assurez-vous que le compte cible est actif" }, "cancel": "Annuler", "confirm": "Confirmer", @@ -1005,6 +1005,7 @@ "select-account-type": "Sélectionner le type de compte", "account-name": "Nom du compte", "sudt-account": "Compte sUDT", + "xudt-account": "Compte xUDT", "delete-failed": "Échec de la suppression de la configuration multisig, raison de l'échec : {{reason}}", "ckb-account": "Compte CKB", "set-token-info": "Définir les informations du token", @@ -1026,7 +1027,7 @@ "decimal": "Veuillez saisir la décimale" }, "placeholder": { - "token-id": "L'ID du token est l'argument du script de type sUDT, qui est identique au hachage de verrouillage de l'émetteur du token." + "token-id": "L'ID du token est l'argument du script de type {{udtType}}, qui est identique au hachage de verrouillage de l'émetteur du token." } }, "send": { diff --git a/packages/neuron-ui/src/locales/zh-tw.json b/packages/neuron-ui/src/locales/zh-tw.json index d12dd36228..0fe82beb76 100644 --- a/packages/neuron-ui/src/locales/zh-tw.json +++ b/packages/neuron-ui/src/locales/zh-tw.json @@ -506,10 +506,10 @@ "title": "創建資產賬戶" }, "send-sudt": { - "title": "發起 sUDT 交易" + "title": "發起 UDT 交易" }, "transfer-to-sudt": { - "title": "發起 sUDT 交易" + "title": "發起 UDT 交易" }, "send-ckb-asset": { "title": "發起 CKB 交易" @@ -518,7 +518,7 @@ "title": "發起 CKB 交易" }, "send-acp-sudt-to-new-cell": { - "title": "發起 sUDT 交易" + "title": "發起 UDT 交易" }, "send-acp-ckb-to-new-cell": { "title": "發起 CKB 交易" @@ -950,7 +950,7 @@ "locked-asset": "已鎖定", "locked-asset-tooltip": "鎖定參數為 {{epochs}} epochs, 預計解鎖時間為 {{year}}年{{month}}月{{day}}日(鎖定時間根據區塊鏈實際運行情況會有一定的誤差)。", "withdraw-asset-tooltip": "預計解鎖時間為 {{year}}年{{month}}月{{day}}日 {{hour}}時{{minute}}分(根據區塊鏈實際運行情況會有一定的誤差)。", - "user-defined-token-tooltip": "將 sUDT 資產轉移到 sUDT 賬戶", + "user-defined-token-tooltip": "將 {{udtType}} 資產轉移到 {{udtType}} 賬戶", "claim-asset": "領取", "withdraw-asset": "撤回", "view-details": "查看詳情", @@ -961,14 +961,14 @@ "transfer-nft": "轉讓", "user-defined-token": "遷移", "transfer-nft-success": "轉讓資產成功", - "migrate-sudt-success": "轉換成新的 sUDT 資產帳戶成功", - "send-sudt-success": "轉入 sUDT 帳戶成功", + "migrate-sudt-success": "轉換成新的 {{udtType}} 資產帳戶成功", + "send-sudt-success": "轉入 {{udtType}} 帳戶成功", "unlock-success": "資產已領取,可前往交易歷史查看詳情", "withdraw-cheque-success": "資產已撤回,可前往交易歷史查看詳情", "claim-cheque-success": "資產已領取,可前往交易歷史查看詳情" }, "migrate-sudt": { - "title": "遷移至 sUDT 資產賬戶", + "title": "遷移至 {{udtType}} 資產賬戶", "choose-title": "選擇遷移方式", "next": "下一步", "back": "上一步", @@ -976,14 +976,14 @@ "input-symbol": "請輸入簡稱", "input-decimal": "請輸入小數位", "turn-into-new-account": { - "title": "轉換成新的 sUDT 資產賬戶", - "sub-title": "將 sUDT 資產轉換成 sUDT 資產賬戶, 至少占用 142 CKBytes", + "title": "轉換成新的 {{udtType}} 資產賬戶", + "sub-title": "將 {{udtType}} 資產轉換成 {{udtType}} 資產賬戶, 至少占用 142 CKBytes", "cancel": "取消", "confirm": "確認" }, "transfer-to-exist-account": { - "title": "轉入 sUDT 賬戶", - "sub-title": "將全部 sUDT 余額轉入已存在的 sUDT 資產賬戶, 請確保目標賬戶存在" + "title": "轉入 {{udtType}} 賬戶", + "sub-title": "將全部 {{udtType}} 余額轉入已存在的 {{udtType}} 資產賬戶, 請確保目標賬戶存在" }, "cancel": "取消", "confirm": "確認", @@ -1009,6 +1009,7 @@ "select-account-type": "選擇帳戶類型", "account-name": "賬戶名稱", "sudt-account": "sUDT 賬戶", + "xudt-account": "xUDT 賬戶", "ckb-account": "CKB 賬戶", "set-token-info": "設置代幣信息", "token-id": "代幣ID", @@ -1029,7 +1030,7 @@ "decimal": "請輸入小數位" }, "placeholder": { - "token-id": "代幣 ID 即 sUDT Type Script 的 Args,等同於該 token 發行者的 lock hash。" + "token-id": "代幣 ID 即 {{udtType}} Type Script 的 Args,等同於該 token 發行者的 lock hash。" } }, "send": { diff --git a/packages/neuron-ui/src/locales/zh.json b/packages/neuron-ui/src/locales/zh.json index 0d5bba3a30..5cbb6f0cc0 100644 --- a/packages/neuron-ui/src/locales/zh.json +++ b/packages/neuron-ui/src/locales/zh.json @@ -505,10 +505,10 @@ "title": "创建资产账户" }, "send-sudt": { - "title": "发起 sUDT 交易" + "title": "发起 UDT 交易" }, "transfer-to-sudt": { - "title": "转入 sUDT 账户" + "title": "转入 UDT 账户" }, "send-ckb-asset": { "title": "发起 CKB 交易" @@ -517,7 +517,7 @@ "title": "发起 CKB 交易" }, "send-acp-sudt-to-new-cell": { - "title": "发起 sUDT 交易" + "title": "发起 UDT 交易" }, "send-acp-ckb-to-new-cell": { "title": "发起 CKB 交易" @@ -949,7 +949,7 @@ "locked-asset": "已锁定", "locked-asset-tooltip": "锁定参数为 {{epochs}} epochs, 预计解锁时间为 {{year}}年{{month}}月{{day}}日(锁定时间根据区块链实际运行情况会有一定的误差)。", "withdraw-asset-tooltip": "预计解锁时间为 {{year}}年{{month}}月{{day}}日 {{hour}}时{{minute}}分(根据区块链实际运行情况会有一定的误差)。", - "user-defined-token-tooltip": "将 sUDT 资产转移到 sUDT 账户", + "user-defined-token-tooltip": "将 {{udtType}} 资产转移到 {{udtType}} 账户", "claim-asset": "领取", "withdraw-asset": "撤回", "view-details": "查看详情", @@ -960,14 +960,14 @@ "transfer-nft": "转让", "user-defined-token": "迁移", "transfer-nft-success": "转让资产成功", - "migrate-sudt-success": "转换成新的 sUDT 资产账户成功", - "send-sudt-success": "转入 sUDT 账户成功", + "migrate-sudt-success": "转换成新的 {{udtType}} 资产账户成功", + "send-sudt-success": "转入 {{udtType}} 账户成功", "unlock-success": "资产已领取,可前往交易历史查看详情", "withdraw-cheque-success": "资产已撤回,可前往交易历史查看详情", "claim-cheque-success": "资产已领取,可前往交易历史查看详情" }, "migrate-sudt": { - "title": "迁移至 sUDT 资产账户", + "title": "迁移至 {{udtType}} 资产账户", "choose-title": "选择迁移方式", "next": "下一步", "back": "上一步", @@ -975,14 +975,14 @@ "input-symbol": "请输入简称", "input-decimal": "请输入小数位", "turn-into-new-account": { - "title": "转换成新的 sUDT 资产账户", - "sub-title": "将 sUDT 资产转换成 sUDT 资产账户, 至少占用 142 CKBytes", + "title": "转换成新的 {{udtType}} 资产账户", + "sub-title": "将 {{udtType}} 资产转换成 {{udtType}} 资产账户, 至少占用 142 CKBytes", "cancel": "取消", "confirm": "确认" }, "transfer-to-exist-account": { - "title": "转入 sUDT 账户", - "sub-title": "将全部 sUDT 余额转入已存在的 sUDT 资产账户, 请确保目标账户存在" + "title": "转入 {{udtType}} 账户", + "sub-title": "将全部 {{udtType}} 余额转入已存在的 {{udtType}} 资产账户, 请确保目标账户存在" }, "cancel": "取消", "confirm": "确认", @@ -1008,6 +1008,7 @@ "select-account-type": "选择账户类型", "account-name": "账户名称", "sudt-account": "sUDT 账户", + "xudt-account": "xUDT 账户", "ckb-account": "CKB 账户", "set-token-info": "设置代币信息", "token-id": "代币ID", @@ -1028,7 +1029,7 @@ "decimal": "请输入小数位" }, "placeholder": { - "token-id": "代币 ID 即 sUDT Type Script 的 Args,等同于该 token 发行者的 lock hash。" + "token-id": "代币 ID 即 {{udtType}} Type Script 的 Args,等同于该 token 发行者的 lock hash。" } }, "send": { diff --git a/packages/neuron-ui/src/services/remote/sudt.ts b/packages/neuron-ui/src/services/remote/sudt.ts index 0faf64622c..1f2c14b8a9 100644 --- a/packages/neuron-ui/src/services/remote/sudt.ts +++ b/packages/neuron-ui/src/services/remote/sudt.ts @@ -33,9 +33,4 @@ export const getSUDTTokenInfo = remoteApi('get-sudt-type-script-hash') - export const generateSudtMigrateAcpTx = remoteApi('generate-sudt-migrate-acp-tx') diff --git a/packages/neuron-ui/src/states/stateProvider/reducer.ts b/packages/neuron-ui/src/states/stateProvider/reducer.ts index 801a9fe30c..c561a9eb5f 100644 --- a/packages/neuron-ui/src/states/stateProvider/reducer.ts +++ b/packages/neuron-ui/src/states/stateProvider/reducer.ts @@ -234,7 +234,7 @@ export const reducer = produce((state: Draft, action: state.sUDTAccounts = action.payload .filter(account => account.id !== undefined) .sort(sortAccounts) - .map(({ id, accountName, tokenName, symbol, tokenID, balance: accountBalance, address, decimal }) => ({ + .map(({ id, accountName, tokenName, symbol, tokenID, balance: accountBalance, address, decimal, udtType }) => ({ accountId: id!.toString(), accountName, tokenName, @@ -243,6 +243,7 @@ export const reducer = produce((state: Draft, action: address, decimal, tokenId: tokenID, + udtType, })) break } diff --git a/packages/neuron-ui/src/stories/SUDTCreateDialog.stories.tsx b/packages/neuron-ui/src/stories/SUDTCreateDialog.stories.tsx index 744a73dc7c..887c65fc41 100644 --- a/packages/neuron-ui/src/stories/SUDTCreateDialog.stories.tsx +++ b/packages/neuron-ui/src/stories/SUDTCreateDialog.stories.tsx @@ -20,9 +20,9 @@ const baseProps = { } const propsList: { [name: string]: SUDTCreateDialogProps } = { Basic: baseProps, - InsufficientForSUDT: { ...baseProps, insufficient: { ckb: false, sudt: true } }, - InsufficientForCKB: { ...baseProps, insufficient: { ckb: true, sudt: false } }, - InsufficientForCKBAndSUDT: { ...baseProps, insufficient: { ckb: true, sudt: true } }, + InsufficientForSUDT: { ...baseProps, insufficient: { ckb: false, sudt: true, xudt: true } }, + InsufficientForCKB: { ...baseProps, insufficient: { ckb: true, sudt: false, xudt: false } }, + InsufficientForCKBAndSUDT: { ...baseProps, insufficient: { ckb: true, sudt: true, xudt: true } }, } const meta: Meta = { diff --git a/packages/neuron-ui/src/types/App/index.d.ts b/packages/neuron-ui/src/types/App/index.d.ts index 2a237caef5..649cf62493 100644 --- a/packages/neuron-ui/src/types/App/index.d.ts +++ b/packages/neuron-ui/src/types/App/index.d.ts @@ -132,6 +132,11 @@ declare namespace State { amendHash?: string } + enum UDTType { + SUDT = 'sUDT', + XUDT = 'xUDT', + } + interface SUDTAccount { accountId: string accountName?: string @@ -141,6 +146,7 @@ declare namespace State { tokenId: string address: string decimal: string + udtType?: UDTType } type GlobalAlertDialog = { @@ -364,6 +370,7 @@ declare namespace State { NFTClass = 'NFTClass', NFTIssuer = 'NFTIssuer', SUDT = 'SUDT', + XUDT = 'XUDT', Spore = 'Spore', Unknown = 'Unknown', } @@ -382,7 +389,7 @@ declare namespace State { } interface LiveCellWithLocalInfo extends LiveCellWithLocalInfoAPI { lockedReason?: { key: string; params?: Record } - cellType?: 'CKB' | 'SUDT' | 'NFT' | 'Spore' | 'Unknown' + cellType?: 'CKB' | 'SUDT' | 'XUDT' | 'NFT' | 'Spore' | 'Unknown' } interface UpdateLiveCellLocalInfo { diff --git a/packages/neuron-ui/src/types/Controller/index.d.ts b/packages/neuron-ui/src/types/Controller/index.d.ts index 83d7cab827..5737d4f79c 100644 --- a/packages/neuron-ui/src/types/Controller/index.d.ts +++ b/packages/neuron-ui/src/types/Controller/index.d.ts @@ -1,4 +1,9 @@ declare namespace Controller { + enum UDTType { + SUDT = 'sUDT', + XUDT = 'xUDT', + } + interface RequestOpenInExplorerParams { key: string type: 'transaction' @@ -217,6 +222,7 @@ declare namespace Controller { balance: string blake160: string address: string + udtType?: UDTType } namespace GetSUDTAccount { @@ -244,6 +250,7 @@ declare namespace Controller { symbol: string decimal: string feeRate: string + udtType?: UDTType } interface Response { assetAccount: any @@ -254,7 +261,7 @@ declare namespace Controller { namespace SendCreateSUDTAccountTransaction { interface Params { walletID: string - assetAccount: Pick + assetAccount: Pick tx: any password?: string } @@ -385,9 +392,9 @@ declare namespace Controller { } namespace GenerateClaimChequeTransaction { - type AssetAccount = Record< - 'accountName' | 'balance' | 'blake160' | 'decimal' | 'symbol' | 'tokenID' | 'tokenName', - string + type AssetAccount = Pick< + SUDTAccount, + 'accountName' | 'balance' | 'blake160' | 'decimal' | 'symbol' | 'tokenID' | 'tokenName' | 'udtType' > interface Params { diff --git a/packages/neuron-ui/src/utils/enums.ts b/packages/neuron-ui/src/utils/enums.ts index 23c0b96b6f..2ae3a13711 100644 --- a/packages/neuron-ui/src/utils/enums.ts +++ b/packages/neuron-ui/src/utils/enums.ts @@ -126,6 +126,7 @@ export enum PresetScript { Locktime = 'SingleMultiSign', Cheque = 'Cheque', SUDT = 'SUDT', + XUDT = 'XUDT', } export enum CompensationPeriod { @@ -241,6 +242,12 @@ export enum TypeScriptCategory { NFTClass = 'NFTClass', NFTIssuer = 'NFTIssuer', SUDT = 'SUDT', + XUDT = 'XUDT', Spore = 'Spore', Unknown = 'Unknown', } + +export enum UDTType { + SUDT = 'sUDT', + XUDT = 'xUDT', +} diff --git a/packages/neuron-ui/src/utils/hooks/createSUDTAccount.ts b/packages/neuron-ui/src/utils/hooks/createSUDTAccount.ts index 3eb7e94b15..525028d543 100644 --- a/packages/neuron-ui/src/utils/hooks/createSUDTAccount.ts +++ b/packages/neuron-ui/src/utils/hooks/createSUDTAccount.ts @@ -3,15 +3,12 @@ import { TFunction } from 'i18next' import { useEffect, useCallback } from 'react' import { AccountType, TokenInfo } from 'components/SUDTCreateDialog' import { AppActions, StateAction } from 'states' -import { - generateCreateSUDTAccountTransaction, - openExternal, - getSUDTTypeScriptHash, - invokeShowErrorMessage, -} from 'services/remote' +import { generateCreateSUDTAccountTransaction, openExternal, invokeShowErrorMessage } from 'services/remote' import { getExplorerUrl } from 'utils' +import { predefined } from '@ckb-lumos/config-manager' +import { utils } from '@ckb-lumos/base' import useGetCountDownAndFeeRateStats from './useGetCountDownAndFeeRateStats' -import { ErrorCode } from '../enums' +import { ErrorCode, UDTType } from '../enums' import { isSuccessResponse } from '../is' import { MIN_CKB_REQUIRED_BY_CKB_SUDT, @@ -77,6 +74,7 @@ export const useIsInsufficientToCreateSUDTAccount = ({ setInsufficient({ [AccountType.CKB]: insufficientToCreateCKBAccount, [AccountType.SUDT]: insufficientToCreateSUDTAccount, + [AccountType.XUDT]: insufficientToCreateSUDTAccount, }) }) }, [walletId, balance, setInsufficient, suggestFeeRate]) @@ -94,7 +92,10 @@ export const useOnGenerateNewAccountTransaction = ({ t: TFunction }) => useCallback( - ({ tokenId, tokenName, accountName, symbol, decimal }: TokenInfo, onSuccess?: () => void) => { + ( + { tokenId, tokenName, accountName, symbol, decimal, udtType }: TokenInfo & { udtType?: UDTType }, + onSuccess?: () => void + ) => { return generateCreateSUDTAccountTransaction({ walletID: walletId, tokenID: tokenId, @@ -103,6 +104,7 @@ export const useOnGenerateNewAccountTransaction = ({ symbol, decimal, feeRate: `${MEDIUM_FEE_RATE}`, + udtType, }) .then(res => { if (isSuccessResponse(res)) { @@ -131,15 +133,18 @@ export const useOnGenerateNewAccountTransaction = ({ [onGenerated, walletId, dispatch, t] ) -export const useOpenSUDTTokenUrl = (tokenID: string, isMainnet?: boolean) => +export const useOpenUDTTokenUrl = (tokenID: string, udtType?: UDTType, isMainnet?: boolean) => useCallback(() => { - if (tokenID) { - getSUDTTypeScriptHash({ tokenID }).then(res => { - if (isSuccessResponse(res) && res.result) { - openExternal(`${getExplorerUrl(isMainnet)}/sudt/${res.result}`) - } + if (tokenID && udtType) { + const { SUDT, XUDT } = isMainnet ? predefined.LINA.SCRIPTS : predefined.AGGRON4.SCRIPTS + const udtScript = udtType === UDTType.SUDT ? SUDT : XUDT + const scriptHash = utils.computeScriptHash({ + codeHash: udtScript.CODE_HASH, + hashType: udtScript.HASH_TYPE, + args: tokenID, }) + openExternal(`${getExplorerUrl(isMainnet)}/${udtType === UDTType.SUDT ? 'sudt' : 'xudt'}/${scriptHash}`) } }, [isMainnet, tokenID]) -export default { useIsInsufficientToCreateSUDTAccount, useOnGenerateNewAccountTransaction, useOpenSUDTTokenUrl } +export default { useIsInsufficientToCreateSUDTAccount, useOnGenerateNewAccountTransaction, useOpenUDTTokenUrl } diff --git a/packages/neuron-ui/src/utils/is.ts b/packages/neuron-ui/src/utils/is.ts index 5a82900882..1e53b008b2 100644 --- a/packages/neuron-ui/src/utils/is.ts +++ b/packages/neuron-ui/src/utils/is.ts @@ -7,7 +7,10 @@ import { AnyoneCanPayLockInfoOnLina, PwAcpLockInfoOnMainNet, PwAcpLockInfoOnTestNet, + UDTType, } from 'utils/enums' +import { type Script } from '@ckb-lumos/base' +import { predefined } from '@ckb-lumos/config-manager' import { MAINNET_CLIENT_LIST } from './const' export const isMainnet = (networks: Readonly, networkID: string) => { @@ -62,3 +65,28 @@ export const isAnyoneCanPayAddress = (address: string, isMainnetAddress: boolean return false } } + +// Add string type to recognize the return type of API from https://github.com/nervosnetwork/neuron/blob/v0.116.2/packages/neuron-wallet/src/models/chain/output.ts#L20 +// TODO, make the UDT code hash globally configurable to ensure it can test with the devnet +export const getUdtType = (type: Script | string | null) => { + if (type === null) return undefined + if (typeof type === 'string') { + switch (type) { + case UDTType.SUDT: + case UDTType.XUDT: + return type + default: + return undefined + } + } + switch (type.codeHash) { + case predefined.AGGRON4.SCRIPTS.SUDT.CODE_HASH: + case predefined.LINA.SCRIPTS.SUDT.CODE_HASH: + return UDTType.SUDT + case predefined.AGGRON4.SCRIPTS.XUDT.CODE_HASH: + case predefined.LINA.SCRIPTS.XUDT.CODE_HASH: + return UDTType.XUDT + default: + return undefined + } +} diff --git a/packages/neuron-wallet/package.json b/packages/neuron-wallet/package.json index f6af151e56..8e246f7b01 100644 --- a/packages/neuron-wallet/package.json +++ b/packages/neuron-wallet/package.json @@ -42,15 +42,15 @@ ] }, "dependencies": { - "@ckb-lumos/base": "0.21.1", - "@ckb-lumos/bi": "0.21.1", - "@ckb-lumos/ckb-indexer": "0.21.1", - "@ckb-lumos/codec": "0.21.1", - "@ckb-lumos/common-scripts": "0.21.1", - "@ckb-lumos/config-manager": "0.21.1", - "@ckb-lumos/hd": "0.21.1", - "@ckb-lumos/helpers": "0.21.1", - "@ckb-lumos/rpc": "0.21.1", + "@ckb-lumos/base": "0.23.0", + "@ckb-lumos/bi": "0.23.0", + "@ckb-lumos/ckb-indexer": "0.23.0", + "@ckb-lumos/codec": "0.23.0", + "@ckb-lumos/common-scripts": "0.23.0", + "@ckb-lumos/config-manager": "0.23.0", + "@ckb-lumos/hd": "0.23.0", + "@ckb-lumos/helpers": "0.23.0", + "@ckb-lumos/rpc": "0.23.0", "@iarna/toml": "2.2.5", "@ledgerhq/hw-transport-node-hid": "6.27.22", "@spore-sdk/core": "0.1.0", diff --git a/packages/neuron-wallet/src/block-sync-renderer/sync/light-synchronizer.ts b/packages/neuron-wallet/src/block-sync-renderer/sync/light-synchronizer.ts index fe89dabefb..abe7908df8 100644 --- a/packages/neuron-wallet/src/block-sync-renderer/sync/light-synchronizer.ts +++ b/packages/neuron-wallet/src/block-sync-renderer/sync/light-synchronizer.ts @@ -43,6 +43,7 @@ export default class LightSynchronizer extends Synchronizer { const fetchCellDeps = [ assetAccountInfo.anyoneCanPayCellDep, assetAccountInfo.sudtCellDep, + assetAccountInfo.xudtCellDep, assetAccountInfo.getNftClassInfo().cellDep, assetAccountInfo.getNftInfo().cellDep, assetAccountInfo.getNftIssuerInfo().cellDep, diff --git a/packages/neuron-wallet/src/block-sync-renderer/sync/queue.ts b/packages/neuron-wallet/src/block-sync-renderer/sync/queue.ts index 8eb2b923f3..acec8a126c 100644 --- a/packages/neuron-wallet/src/block-sync-renderer/sync/queue.ts +++ b/packages/neuron-wallet/src/block-sync-renderer/sync/queue.ts @@ -133,8 +133,8 @@ export default class Queue { for (let index = 0; index < txsWithStatus.length; index++) { if (txsWithStatus[index]?.transaction) { const tx = Transaction.fromSDK(txsWithStatus[index].transaction) - tx.blockHash = txsWithStatus[index].txStatus.blockHash! - blockHashes.push(tx.blockHash) + tx.blockHash = txsWithStatus[index].txStatus.blockHash + blockHashes.push(tx.blockHash!) txs.push(tx) } else { if ((txsWithStatus[index].txStatus as any) === 'rejected') { @@ -231,7 +231,7 @@ export default class Queue { } await TransactionPersistor.saveFetchTx(tx, this.#lockArgsSet) for (const info of anyoneCanPayInfos) { - await AssetAccountService.checkAndSaveAssetAccountWhenSync(info.tokenID, info.blake160) + await AssetAccountService.checkAndSaveAssetAccountWhenSync(info.tokenID, info.blake160, info.udtType) } await this.#checkAndGenerateAddressesByTx(tx) diff --git a/packages/neuron-wallet/src/block-sync-renderer/sync/tx-address-finder.ts b/packages/neuron-wallet/src/block-sync-renderer/sync/tx-address-finder.ts index 200aaeded3..8cb87fffd7 100644 --- a/packages/neuron-wallet/src/block-sync-renderer/sync/tx-address-finder.ts +++ b/packages/neuron-wallet/src/block-sync-renderer/sync/tx-address-finder.ts @@ -6,10 +6,13 @@ import OutPoint from '../../models/chain/out-point' import Transaction from '../../models/chain/transaction' import SystemScriptInfo from '../../models/system-script-info' import { getConnection } from '../../database/chain/connection' +import { UDTType } from '../../utils/const' +import AssetAccountInfo from '../../models/asset-account-info' export interface AnyoneCanPayInfo { tokenID: string blake160: string + udtType?: UDTType } // Search for all addresses related to a transaction. These addresses include: @@ -47,6 +50,7 @@ export default class TxAddressFinder { let shouldSync = false // const anyoneCanPayBlake160s: string[] = [] const anyoneCanPayInfos: AnyoneCanPayInfo[] = [] + const assetAccountInfo = new AssetAccountInfo() const outputs: Output[] = this.tx .outputs!.map((output, index) => { if (SystemScriptInfo.isMultiSignScript(output.lock)) { @@ -62,6 +66,11 @@ export default class TxAddressFinder { anyoneCanPayInfos.push({ blake160: output.lock.args, tokenID: output.type?.args || 'CKBytes', + udtType: output.type + ? assetAccountInfo.isSudtScript(output.type) + ? UDTType.SUDT + : UDTType.XUDT + : undefined, }) } if (this.lockHashes.has(output.lockHash!)) { @@ -86,6 +95,7 @@ export default class TxAddressFinder { const anyoneCanPayInfos: AnyoneCanPayInfo[] = [] const inputs = this.tx.inputs!.filter(i => i.previousOutput !== null) const isMainnet = NetworksService.getInstance().isMainnet() + const assetAccountInfo = new AssetAccountInfo() let shouldSync = false for (const input of inputs) { @@ -100,6 +110,11 @@ export default class TxAddressFinder { anyoneCanPayInfos.push({ blake160: output.lockArgs, tokenID: output.typeArgs || 'CKBytes', + udtType: output.typeScript() + ? assetAccountInfo.isSudtScript(output.typeScript()!) + ? UDTType.SUDT + : UDTType.XUDT + : undefined, }) } if (output && this.lockHashes.has(output.lockHash)) { diff --git a/packages/neuron-wallet/src/controllers/api.ts b/packages/neuron-wallet/src/controllers/api.ts index 7f5b8b6aed..8c4e8aff9b 100644 --- a/packages/neuron-wallet/src/controllers/api.ts +++ b/packages/neuron-wallet/src/controllers/api.ts @@ -817,10 +817,6 @@ export default class ApiController { return this.#sudtController.getSUDTTokenInfo(params) }) - handle('get-sudt-type-script-hash', async (_, params: { tokenID: string }) => { - return this.#sudtController.getSUDTTypeScriptHash(params) - }) - handle('generate-destroy-asset-account-tx', async (_, params: { walletID: string; id: number }) => { return this.#assetAccountController.destroyAssetAccount(params) }) diff --git a/packages/neuron-wallet/src/controllers/asset-account.ts b/packages/neuron-wallet/src/controllers/asset-account.ts index 1469996125..1fefd3e22e 100644 --- a/packages/neuron-wallet/src/controllers/asset-account.ts +++ b/packages/neuron-wallet/src/controllers/asset-account.ts @@ -3,7 +3,7 @@ import AssetAccount from '../models/asset-account' import Transaction from '../models/chain/transaction' import AssetAccountService from '../services/asset-account-service' import { ServiceHasNoResponse } from '../exceptions' -import { ResponseCode } from '../utils/const' +import { ResponseCode, UDTType } from '../utils/const' import NetworksService from '../services/networks' import AssetAccountInfo from '../models/asset-account-info' import TransactionSender from '../services/transaction-sender' @@ -22,6 +22,7 @@ export interface GenerateCreateAssetAccountTxParams { decimal: string feeRate: string fee: string + udtType?: UDTType } export interface SendCreateAssetAccountTxParams { @@ -137,16 +138,7 @@ export default class AssetAccountController { tx: Transaction }> > { - const result = await AssetAccountService.generateCreateTx( - params.walletID, - params.tokenID, - params.symbol, - params.accountName, - params.tokenName, - params.decimal, - params.feeRate, - params.fee - ) + const result = await AssetAccountService.generateCreateTx(params) if (!result) { throw new ServiceHasNoResponse('AssetAccount') diff --git a/packages/neuron-wallet/src/controllers/sudt.ts b/packages/neuron-wallet/src/controllers/sudt.ts index 7b9813c497..c643890635 100644 --- a/packages/neuron-wallet/src/controllers/sudt.ts +++ b/packages/neuron-wallet/src/controllers/sudt.ts @@ -1,4 +1,3 @@ -import { computeScriptHash as scriptToHash } from '@ckb-lumos/base/lib/utils' import LiveCellService from '../services/live-cell-service' import AssetAccountInfo from '../models/asset-account-info' import Script, { ScriptHashType } from '../models/chain/script' @@ -35,13 +34,4 @@ export default class SUDTController { result: { tokenID: params.tokenID, symbol: symbol, tokenName: name, decimal: decimal }, } } - - public getSUDTTypeScriptHash(params: { tokenID: string }): Controller.Response { - const assetAccount = new AssetAccountInfo() - const script = new Script(assetAccount.infos.sudt.codeHash, params.tokenID, assetAccount.infos.sudt.hashType) - return { - status: ResponseCode.Success, - result: scriptToHash(script.toSDK()), - } - } } diff --git a/packages/neuron-wallet/src/database/chain/entities/asset-account.ts b/packages/neuron-wallet/src/database/chain/entities/asset-account.ts index d59d67ecd4..009e30b8e3 100644 --- a/packages/neuron-wallet/src/database/chain/entities/asset-account.ts +++ b/packages/neuron-wallet/src/database/chain/entities/asset-account.ts @@ -1,9 +1,10 @@ import { Entity, Column, PrimaryGeneratedColumn, Index, ManyToOne, JoinColumn } from 'typeorm' import AssetAccountModel from '../../../models/asset-account' import SudtTokenInfo from './sudt-token-info' +import { UDTType } from '../../../utils/const' @Entity() -@Index(['tokenID', 'blake160'], { unique: true }) +@Index(['tokenID', 'blake160', 'udtType'], { unique: true }) export default class AssetAccount { @PrimaryGeneratedColumn() id!: number @@ -13,6 +14,12 @@ export default class AssetAccount { }) tokenID!: string + @Column({ + type: 'varchar', + nullable: true, + }) + udtType?: UDTType + @Column({ type: 'varchar', default: '', @@ -40,12 +47,14 @@ export default class AssetAccount { assetAccount.accountName = info.accountName assetAccount.balance = info.balance assetAccount.blake160 = info.blake160 + assetAccount.udtType = info.udtType const sudtTokenInfo = new SudtTokenInfo() sudtTokenInfo.tokenID = info.tokenID sudtTokenInfo.symbol = info.symbol sudtTokenInfo.tokenName = info.tokenName sudtTokenInfo.decimal = info.decimal + sudtTokenInfo.udtType = info.udtType assetAccount.sudtTokenInfo = sudtTokenInfo return assetAccount @@ -60,7 +69,8 @@ export default class AssetAccount { this.sudtTokenInfo.decimal, this.balance, this.blake160, - this.id + this.id, + this.udtType ) } } diff --git a/packages/neuron-wallet/src/database/chain/entities/sudt-token-info.ts b/packages/neuron-wallet/src/database/chain/entities/sudt-token-info.ts index ec0614395b..35c6512f3a 100644 --- a/packages/neuron-wallet/src/database/chain/entities/sudt-token-info.ts +++ b/packages/neuron-wallet/src/database/chain/entities/sudt-token-info.ts @@ -1,14 +1,21 @@ import { Entity, Column, Index, OneToMany, PrimaryColumn } from 'typeorm' import AssetAccount from './asset-account' +import { UDTType } from '../../../utils/const' @Entity() -@Index(['tokenID'], { unique: true }) +@Index(['tokenID', 'udtType'], { unique: true }) export default class SudtTokenInfo { @PrimaryColumn({ type: 'varchar', }) tokenID!: string + @Column({ + type: 'varchar', + nullable: true, + }) + udtType?: UDTType + @Column({ type: 'varchar', }) @@ -33,6 +40,7 @@ export default class SudtTokenInfo { tokenName: this.tokenName, symbol: this.symbol, decimal: this.decimal, + udtType: this.udtType, } } } diff --git a/packages/neuron-wallet/src/database/chain/migrations/1720089814860-AddUdtType.ts b/packages/neuron-wallet/src/database/chain/migrations/1720089814860-AddUdtType.ts new file mode 100644 index 0000000000..b30e1bf7f3 --- /dev/null +++ b/packages/neuron-wallet/src/database/chain/migrations/1720089814860-AddUdtType.ts @@ -0,0 +1,22 @@ +import {MigrationInterface, QueryRunner} from "typeorm"; + +export class AddUdtType1720089814860 implements MigrationInterface { + name = 'AddUdtType1720089814860' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "sudt_token_info" ADD COLUMN "udtType" varchar;`) + await queryRunner.query(`CREATE UNIQUE INDEX "IDX_37bb4f2a4a849bf0b1dadee2c7" ON "sudt_token_info" ("tokenID", "udtType") `); + await queryRunner.query(`UPDATE "sudt_token_info" set udtType="sUDT" where tokenID!="CKBytes"`) + await queryRunner.query(`ALTER TABLE "asset_account" ADD COLUMN "udtType" varchar;`) + await queryRunner.query(`CREATE UNIQUE INDEX "IDX_5139df6b311e63ecdd93cd17ed" ON "asset_account" ("tokenID", "blake160", "udtType") `); + await queryRunner.query(`UPDATE "asset_account" set udtType="sUDT" where tokenID!="CKBytes"`) + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`DROP INDEX "IDX_37bb4f2a4a849bf0b1dadee2c7"`); + await queryRunner.query(`ALTER TABLE "sudt_token_info" DROP COLUMN "udtType";`); + await queryRunner.query(`DROP INDEX "IDX_5139df6b311e63ecdd93cd17ed"`); + await queryRunner.query(`ALTER TABLE "asset_account" DROP COLUMN "udtType";`); + } + +} diff --git a/packages/neuron-wallet/src/database/chain/ormconfig.ts b/packages/neuron-wallet/src/database/chain/ormconfig.ts index c3458375cd..b8699466d2 100644 --- a/packages/neuron-wallet/src/database/chain/ormconfig.ts +++ b/packages/neuron-wallet/src/database/chain/ormconfig.ts @@ -69,6 +69,7 @@ import TxDescriptionSubscribe from './subscriber/tx-description-subscriber' import SudtTokenInfoSubscribe from './subscriber/sudt-token-info-subscriber' import AssetAccountSubscribe from './subscriber/asset-account-subscriber' import { AddStartBlockNumber1716539079505 } from './migrations/1716539079505-AddStartBlockNumber' +import { AddUdtType1720089814860 } from './migrations/1720089814860-AddUdtType' export const CONNECTION_NOT_FOUND_NAME = 'ConnectionNotFoundError' export type ConnectionName = 'light' | 'full' @@ -186,6 +187,7 @@ const getConnectionOptions = (genesisBlockHash: string, connectionName: Connecti RemoveAddressInIndexerCache1704357651876, AmendTransaction1709008125088, AddStartBlockNumber1716539079505, + AddUdtType1720089814860, ], subscribers: [ AddressSubscribe, diff --git a/packages/neuron-wallet/src/models/asset-account-info.ts b/packages/neuron-wallet/src/models/asset-account-info.ts index 285abb9cf6..bf7df5d57b 100644 --- a/packages/neuron-wallet/src/models/asset-account-info.ts +++ b/packages/neuron-wallet/src/models/asset-account-info.ts @@ -1,4 +1,5 @@ import { bytes, molecule } from '@ckb-lumos/codec' +import { predefined } from '@ckb-lumos/config-manager' import CellDep, { DepType } from './chain/cell-dep' import Script, { ScriptHashType } from './chain/script' import OutPoint from './chain/out-point' @@ -6,6 +7,7 @@ import NetworksService from '../services/networks' import Transaction from './chain/transaction' import SystemScriptInfo from './system-script-info' import { Address } from './address' +import { UDTType } from '../utils/const' import { createFixedHexBytesCodec } from '@ckb-lumos/codec/lib/blockchain' import { predefinedSporeConfigs, SporeConfig, SporeScript } from '@spore-sdk/core' @@ -25,6 +27,7 @@ export default class AssetAccountInfo { private nftIssuerInfo: ScriptCellInfo private nftClassInfo: ScriptCellInfo private nftInfo: ScriptCellInfo + private xudt: ScriptCellInfo private sporeInfos: ScriptCellInfo[] private sporeClusterInfos: ScriptCellInfo[] @@ -37,19 +40,32 @@ export default class AssetAccountInfo { sudt: this.sudt, sudtInfo: this.sudtInfo, anyoneCanPay: this.anyoneCanPayInfo, + xudt: this.xudt, } } constructor(genesisBlockHash: string = NetworksService.getInstance().getCurrent().genesisHash) { - if (genesisBlockHash === AssetAccountInfo.MAINNET_GENESIS_BLOCK_HASH) { - this.sudt = { - cellDep: new CellDep( - new OutPoint(process.env.MAINNET_SUDT_DEP_TXHASH!, process.env.MAINNET_SUDT_DEP_INDEX!), - process.env.MAINNET_SUDT_DEP_TYPE! as DepType - ), - codeHash: process.env.MAINNET_SUDT_SCRIPT_CODEHASH!, - hashType: process.env.MAINNET_SUDT_SCRIPT_HASHTYPE! as ScriptHashType, - } + const isMainnet = genesisBlockHash === AssetAccountInfo.MAINNET_GENESIS_BLOCK_HASH + const { XUDT, SUDT, ANYONE_CAN_PAY } = isMainnet ? predefined.LINA.SCRIPTS : predefined.AGGRON4.SCRIPTS + this.xudt = { + cellDep: new CellDep(new OutPoint(XUDT.TX_HASH, XUDT.INDEX), XUDT.DEP_TYPE as DepType), + codeHash: XUDT.CODE_HASH, + hashType: XUDT.HASH_TYPE as ScriptHashType, + } + this.sudt = { + cellDep: new CellDep(new OutPoint(SUDT.TX_HASH, SUDT.INDEX), SUDT.DEP_TYPE as DepType), + codeHash: SUDT.CODE_HASH, + hashType: SUDT.HASH_TYPE as ScriptHashType, + } + this.anyoneCanPayInfo = { + cellDep: new CellDep( + new OutPoint(ANYONE_CAN_PAY.TX_HASH, ANYONE_CAN_PAY.INDEX), + ANYONE_CAN_PAY.DEP_TYPE as DepType + ), + codeHash: ANYONE_CAN_PAY.CODE_HASH, + hashType: ANYONE_CAN_PAY.HASH_TYPE as ScriptHashType, + } + if (isMainnet) { this.sudtInfo = { cellDep: new CellDep( new OutPoint(process.env.MAINNET_SUDT_INFO_DEP_TXHASH!, process.env.MAINNET_SUDT_INFO_DEP_INDEX!), @@ -58,14 +74,6 @@ export default class AssetAccountInfo { codeHash: process.env.MAINNET_SUDT_INFO_SCRIPT_CODEHASH!, hashType: process.env.MAINNET_SUDT_INFO_SCRIPT_HASHTYPE! as ScriptHashType, } - this.anyoneCanPayInfo = { - cellDep: new CellDep( - new OutPoint(process.env.MAINNET_ACP_DEP_TXHASH!, process.env.MAINNET_ACP_DEP_INDEX!), - process.env.MAINNET_ACP_DEP_TYPE! as DepType - ), - codeHash: process.env.MAINNET_ACP_SCRIPT_CODEHASH!, - hashType: process.env.MAINNET_ACP_SCRIPT_HASHTYPE! as ScriptHashType, - } this.legacyAnyoneCanPayInfo = { cellDep: new CellDep( new OutPoint(process.env.LEGACY_MAINNET_ACP_DEP_TXHASH!, process.env.LEGACY_MAINNET_ACP_DEP_INDEX!), @@ -114,19 +122,10 @@ export default class AssetAccountInfo { codeHash: process.env.MAINNET_NFT_SCRIPT_CODEHASH!, hashType: process.env.MAINNET_NFT_SCRIPT_HASH_TYPE! as ScriptHashType, } - // TODO infos for mainnet this.sporeInfos = [] this.sporeClusterInfos = [] } else { - this.sudt = { - cellDep: new CellDep( - new OutPoint(process.env.TESTNET_SUDT_DEP_TXHASH!, process.env.TESTNET_SUDT_DEP_INDEX!), - process.env.TESTNET_SUDT_DEP_TYPE! as DepType - ), - codeHash: process.env.TESTNET_SUDT_SCRIPT_CODEHASH!, - hashType: process.env.TESTNET_SUDT_SCRIPT_HASHTYPE! as ScriptHashType, - } this.sudtInfo = { cellDep: new CellDep( new OutPoint(process.env.TESTNET_SUDT_INFO_DEP_TXHASH!, process.env.TESTNET_SUDT_INFO_DEP_INDEX!), @@ -135,14 +134,6 @@ export default class AssetAccountInfo { codeHash: process.env.TESTNET_SUDT_INFO_SCRIPT_CODEHASH!, hashType: process.env.TESTNET_SUDT_INFO_SCRIPT_HASHTYPE! as ScriptHashType, } - this.anyoneCanPayInfo = { - cellDep: new CellDep( - new OutPoint(process.env.TESTNET_ACP_DEP_TXHASH!, process.env.TESTNET_ACP_DEP_INDEX!), - process.env.TESTNET_ACP_DEP_TYPE! as DepType - ), - codeHash: process.env.TESTNET_ACP_SCRIPT_CODEHASH!, - hashType: process.env.TESTNET_ACP_SCRIPT_HASHTYPE! as ScriptHashType, - } this.legacyAnyoneCanPayInfo = { cellDep: new CellDep( new OutPoint(process.env.LEGACY_TESTNET_ACP_DEP_TXHASH!, process.env.LEGACY_TESTNET_ACP_DEP_INDEX!), @@ -211,6 +202,10 @@ export default class AssetAccountInfo { return this.anyoneCanPayInfo.cellDep } + public get xudtCellDep(): CellDep { + return this.xudt.cellDep + } + public get anyoneCanPayCodeHash(): string { return this.anyoneCanPayInfo.codeHash } @@ -243,10 +238,6 @@ export default class AssetAccountInfo { return this.sporeClusterInfos } - public getAcpCodeHash(): string { - return this.anyoneCanPayInfo.codeHash - } - public getSudtCodeHash(): string { return this.sudt.codeHash } @@ -271,10 +262,29 @@ export default class AssetAccountInfo { return new Script(info.codeHash, args, info.hashType) } + public generateXudtScript(args: string): Script { + return new Script(this.xudt.codeHash, args, this.xudt.hashType) + } + + public generateUdtScript(args: string, udtType?: UDTType): Script | undefined { + switch (udtType) { + case UDTType.SUDT: + return this.generateSudtScript(args) + case UDTType.XUDT: + return this.generateXudtScript(args) + default: + return undefined + } + } + public isSudtScript(script: Script): boolean { return script.codeHash === this.sudt.codeHash && script.hashType === this.sudt.hashType } + public isXudtScript(script: Script): boolean { + return script.codeHash === this.xudt.codeHash && script.hashType === this.xudt.hashType + } + public isAnyoneCanPayScript(script: Script): boolean { const acpScripts = [this.anyoneCanPayInfo, this.pwAnyoneCanPayInfo] const exist = acpScripts.find(acpScript => { diff --git a/packages/neuron-wallet/src/models/asset-account.ts b/packages/neuron-wallet/src/models/asset-account.ts index 38f295423b..06e3c09ea8 100644 --- a/packages/neuron-wallet/src/models/asset-account.ts +++ b/packages/neuron-wallet/src/models/asset-account.ts @@ -1,3 +1,5 @@ +import { UDTType } from '../utils/const' + export default class AssetAccount { public id?: number public tokenID: string @@ -7,6 +9,7 @@ export default class AssetAccount { public decimal: string public balance: string public blake160: string + public udtType?: UDTType constructor( tokenID: string, @@ -16,7 +19,8 @@ export default class AssetAccount { decimal: string, balance: string, blake160: string, - id?: number + id?: number, + udtType?: UDTType ) { this.tokenID = tokenID this.symbol = symbol @@ -26,6 +30,7 @@ export default class AssetAccount { this.balance = balance this.blake160 = blake160 this.id = id + this.udtType = udtType } public static fromObject(params: { @@ -37,6 +42,7 @@ export default class AssetAccount { balance: string blake160: string id?: number + udtType?: UDTType }): AssetAccount { return new AssetAccount( params.tokenID, @@ -46,7 +52,8 @@ export default class AssetAccount { params.decimal, params.balance, params.blake160, - params.id + params.id, + params.udtType ) } } diff --git a/packages/neuron-wallet/src/models/chain/transaction.ts b/packages/neuron-wallet/src/models/chain/transaction.ts index 613143dbb4..f6db68bab1 100644 --- a/packages/neuron-wallet/src/models/chain/transaction.ts +++ b/packages/neuron-wallet/src/models/chain/transaction.ts @@ -41,6 +41,7 @@ export interface NFTInfo { export enum AssetAccountType { CKB = 'CKB', SUDT = 'sUDT', + XUDT = 'xUDT', } export default class Transaction { diff --git a/packages/neuron-wallet/src/services/anyone-can-pay.ts b/packages/neuron-wallet/src/services/anyone-can-pay.ts index bfe9b8af33..d86e8792c2 100644 --- a/packages/neuron-wallet/src/services/anyone-can-pay.ts +++ b/packages/neuron-wallet/src/services/anyone-can-pay.ts @@ -18,7 +18,7 @@ import LiveCellService from './live-cell-service' import WalletService from './wallets' import SystemScriptInfo from '../models/system-script-info' import CellsService from './cells' -import { MIN_SUDT_CAPACITY } from '../utils/const' +import { MIN_SUDT_CAPACITY, UDTType } from '../utils/const' import NetworksService from './networks' import { NetworkType } from '../models/network' @@ -57,7 +57,7 @@ export default class AnyoneCanPayService { const targetOutput = isCKB ? await AnyoneCanPayService.getCKBTargetOutput(targetLockScript) - : await AnyoneCanPayService.getSUDTTargetOutput(targetLockScript, tokenID) + : await AnyoneCanPayService.getSUDTTargetOutput(targetLockScript, tokenID, assetAccount.udtType!) const wallet = WalletService.getInstance().get(walletID) const changeBlake160: string = (await wallet.getNextChangeAddress())!.blake160 @@ -112,18 +112,18 @@ export default class AnyoneCanPayService { throw new TargetLockError() } - private static async getSUDTTargetOutput(lockScript: Script, tokenID: string) { + private static async getSUDTTargetOutput(lockScript: Script, tokenID: string, udtType: UDTType) { if (SystemScriptInfo.isSecpScript(lockScript)) { return Output.fromObject({ capacity: BigInt(MIN_SUDT_CAPACITY).toString(), lock: lockScript, - type: new AssetAccountInfo().generateSudtScript(tokenID), + type: new AssetAccountInfo().generateUdtScript(tokenID, udtType), }) } const liveCellService = LiveCellService.getInstance() const targetOutputLiveCell: LiveCell | null = await liveCellService.getOneByLockScriptAndTypeScript( lockScript, - new AssetAccountInfo().generateSudtScript(tokenID) + new AssetAccountInfo().generateUdtScript(tokenID, udtType)! ) if (targetOutputLiveCell && new AssetAccountInfo().isAnyoneCanPayScript(lockScript)) { return Output.fromObject({ @@ -138,7 +138,7 @@ export default class AnyoneCanPayService { return Output.fromObject({ capacity: AnyoneCanPayService.getSUDTAddCapacity(lockScript.args), lock: lockScript, - type: new AssetAccountInfo().generateSudtScript(tokenID), + type: new AssetAccountInfo().generateUdtScript(tokenID, udtType), }) } diff --git a/packages/neuron-wallet/src/services/asset-account-service.ts b/packages/neuron-wallet/src/services/asset-account-service.ts index 991338a744..563fe68ae4 100644 --- a/packages/neuron-wallet/src/services/asset-account-service.ts +++ b/packages/neuron-wallet/src/services/asset-account-service.ts @@ -15,11 +15,11 @@ import WalletService from './wallets' import OutPoint from '../models/chain/out-point' import SystemScriptInfo from '../models/system-script-info' import Input from '../models/chain/input' -import { MIN_CELL_CAPACITY } from '../utils/const' +import { MIN_CELL_CAPACITY, UDTType } from '../utils/const' import SudtTokenInfoService from './sudt-token-info' export default class AssetAccountService { - private static async getACPCells(publicKeyHash: string, tokenId: string = 'CKBytes') { + private static async getACPCells(publicKeyHash: string, tokenId: string = 'CKBytes', udtType?: UDTType) { const assetAccountInfo = new AssetAccountInfo() const anyoneCanPayLockHash = assetAccountInfo.generateAnyoneCanPayScript(publicKeyHash).computeHash() const outputs = await getConnection() @@ -27,7 +27,10 @@ export default class AssetAccountService { .findBy({ status: In([OutputStatus.Live, OutputStatus.Sent]), lockHash: anyoneCanPayLockHash, - typeHash: tokenId !== 'CKBytes' ? assetAccountInfo.generateSudtScript(tokenId).computeHash() : IsNull(), + typeHash: + tokenId !== 'CKBytes' + ? assetAccountInfo.generateUdtScript(tokenId, udtType ?? UDTType.SUDT)!.computeHash() + : IsNull(), }) return outputs @@ -49,10 +52,13 @@ export default class AssetAccountService { return availableBalance >= 0 ? availableBalance.toString() : BigInt(0) } - private static async calculateUDTAccountBalance(publicKeyHash: string, tokenId: string) { + private static async calculateUDTAccountBalance(publicKeyHash: string, tokenId: string, udtType?: UDTType) { const assetAccountInfo = new AssetAccountInfo() const anyoneCanPayLockHash = assetAccountInfo.generateAnyoneCanPayScript(publicKeyHash).computeHash() - const typeHash = assetAccountInfo.generateSudtScript(tokenId).computeHash() + const typeHash = + udtType === UDTType.SUDT + ? assetAccountInfo.generateSudtScript(tokenId).computeHash() + : assetAccountInfo.generateXudtScript(tokenId).computeHash() const outputs = await getConnection() .getRepository(OutputEntity) .createQueryBuilder('output') @@ -71,7 +77,11 @@ export default class AssetAccountService { } public static async destroyAssetAccount(walletID: string, assetAccount: AssetAccount) { - const cells = await AssetAccountService.getACPCells(assetAccount?.blake160, assetAccount.tokenID) + const cells = await AssetAccountService.getACPCells( + assetAccount?.blake160, + assetAccount.tokenID, + assetAccount.udtType + ) const inputs = cells.map(cell => { return Input.fromObject({ previousOutput: cell.outPoint(), @@ -126,7 +136,7 @@ export default class AssetAccountService { const model = aa.toModel() const tokenID = determineTokenID(aa) - const cells = await this.getACPCells(aa.blake160, tokenID) + const cells = await this.getACPCells(aa.blake160, tokenID, aa.udtType) if (!cells.length) { return } @@ -135,7 +145,7 @@ export default class AssetAccountService { const bigIntAmount = await this.calculateAvailableCKBBalance(aa.blake160) model.balance = bigIntAmount.toString() } else { - const bigIntAmount = await this.calculateUDTAccountBalance(aa.blake160, aa.tokenID) + const bigIntAmount = await this.calculateUDTAccountBalance(aa.blake160, aa.tokenID, aa.udtType) model.balance = bigIntAmount.toString() } @@ -171,23 +181,38 @@ export default class AssetAccountService { const bitIntAmount = await this.calculateAvailableCKBBalance(assetAccount.blake160) assetAccount.balance = bitIntAmount.toString() } else { - const bigIntAmount = await this.calculateUDTAccountBalance(assetAccount.blake160, assetAccount.tokenID) + const bigIntAmount = await this.calculateUDTAccountBalance( + assetAccount.blake160, + assetAccount.tokenID, + assetAccount.udtType + ) assetAccount.balance = bigIntAmount.toString() } return assetAccount } - public static async generateCreateTx( - walletID: string, - tokenID: string, - symbol: string, - accountName: string, - tokenName: string, - decimal: string, - feeRate: string, + public static async generateCreateTx({ + walletID, + tokenID, + symbol, + accountName, + tokenName, + decimal, + feeRate, + fee, + udtType, + }: { + walletID: string + tokenID: string + symbol: string + accountName: string + tokenName: string + decimal: string + feeRate: string fee: string - ): Promise<{ + udtType?: UDTType + }): Promise<{ assetAccount: AssetAccount tx: Transaction }> { @@ -203,31 +228,42 @@ export default class AssetAccountService { const addrObj = !wallet.isHDWallet() ? addresses[0] : addresses.find(a => !usedBlake160s.has(a.blake160))! // 2. generate AssetAccount object - const assetAccount = new AssetAccount(tokenID, symbol, accountName, tokenName, decimal, '0', addrObj.blake160) + const assetAccount = AssetAccount.fromObject({ + tokenID, + symbol, + accountName, + tokenName, + decimal, + balance: '0', + blake160: addrObj.blake160, + udtType, + }) // 3. generate tx const changeAddrObj = await wallet.getNextChangeAddress() let tx: Transaction | undefined try { - tx = await TransactionGenerator.generateCreateAnyoneCanPayTx( + tx = await TransactionGenerator.generateCreateAnyoneCanPayTx({ tokenID, - walletID, - addrObj.blake160, - changeAddrObj!.blake160, + walletId: walletID, + blake160: addrObj.blake160, + changeBlake160: changeAddrObj!.blake160, feeRate, - fee - ) + fee, + udtType, + }) } catch (err) { if (!(err instanceof CapacityNotEnoughForChange)) { throw err } - tx = await TransactionGenerator.generateCreateAnyoneCanPayTxUseAllBalance( + tx = await TransactionGenerator.generateCreateAnyoneCanPayTxUseAllBalance({ tokenID, - walletID, - addrObj.blake160, + walletId: walletID, + blake160: addrObj.blake160, feeRate, - fee - ) + fee, + udtType, + }) } return { @@ -236,12 +272,12 @@ export default class AssetAccountService { } } - public static async checkAndSaveAssetAccountWhenSync(tokenID: string, blake160: string) { + public static async checkAndSaveAssetAccountWhenSync(tokenID: string, blake160: string, udtType?: UDTType) { const isCKB = tokenID === 'CKBytes' const decimal = isCKB ? '8' : '' const symbol = isCKB ? 'CKB' : '' const tokenName = isCKB ? 'CKBytes' : '' - const assetAccount = new AssetAccount(tokenID, symbol, '', tokenName, decimal, '0', blake160) + const assetAccount = new AssetAccount(tokenID, symbol, '', tokenName, decimal, '0', blake160, undefined, udtType) const assetAccountEntity = AssetAccountEntity.fromModel(assetAccount) await SudtTokenInfoService.insertSudtTokenInfo(assetAccountEntity.sudtTokenInfo) const existAccountAccount = await getConnection() @@ -322,6 +358,18 @@ export default class AssetAccountService { return assetAccounts.map(aa => aa.blake160) } + private static async getExistAssetAccount(assetAccount: AssetAccount) { + return getConnection() + .getRepository(AssetAccountEntity) + .createQueryBuilder() + .where({ + tokenID: assetAccount.tokenID, + blake160: assetAccount.blake160, + udtType: assetAccount.udtType ?? IsNull(), + }) + .getOne() + } + public static async sendTx( walletID: string, assetAccount: AssetAccount, @@ -331,12 +379,9 @@ export default class AssetAccountService { ): Promise { // 1. check AssetAccount exists const connection = getConnection() - const exists = await connection.manager.query( - `SELECT EXISTS (SELECT 1 FROM asset_account where tokenID = ? AND blake160 = ?) as exist`, - [assetAccount.tokenID, assetAccount.blake160] - ) + const exists = await AssetAccountService.getExistAssetAccount(assetAccount) - if (exists[0].exist === 1 && walletID) { + if (exists && walletID) { // For hardware wallet in ckb asset account: // 1. If a ckb account has been created, another one cannot be created; // 2. If a ckb account has been destroyed, ckb account can be created. @@ -344,7 +389,11 @@ export default class AssetAccountService { if (wallet.isHardware()) { const address = await wallet.getNextAddress() if (address) { - const acpCells = await AssetAccountService.getACPCells(address.blake160, assetAccount.tokenID) + const acpCells = await AssetAccountService.getACPCells( + address.blake160, + assetAccount.tokenID, + assetAccount.udtType + ) if (acpCells.length) { throw new Error(`Asset account already exists!`) } else { @@ -352,10 +401,14 @@ export default class AssetAccountService { .createQueryBuilder() .delete() .from(AssetAccountEntity) - .where('tokenID = :tokenID AND blake160 = :blake160', { - tokenID: assetAccount.tokenID, - blake160: assetAccount.blake160, - }) + .where( + `tokenID = :tokenID AND blake160 = :blake160 ${assetAccount.udtType ? 'AND udtType = :udtType' : ''}`, + { + tokenID: assetAccount.tokenID, + blake160: assetAccount.blake160, + udtType: assetAccount.udtType, + } + ) .execute() } } @@ -486,7 +539,12 @@ export default class AssetAccountService { } const tokenId = chequeLiveCell.type!.args - const assetAccount = new AssetAccount(tokenId, '', '', '', '', '0', receiverAcpScript.args) + const udtType = assetAccountInfo.isSudtScript(chequeLiveCell.type!) + ? UDTType.SUDT + : assetAccountInfo.isXudtScript(chequeLiveCell.type!) + ? UDTType.XUDT + : undefined + const assetAccount = new AssetAccount(tokenId, '', '', '', '', '0', receiverAcpScript.args, undefined, udtType) return { tx, assetAccount } } diff --git a/packages/neuron-wallet/src/services/cells.ts b/packages/neuron-wallet/src/services/cells.ts index 03e8cc0a1f..435cd6dd5c 100644 --- a/packages/neuron-wallet/src/services/cells.ts +++ b/packages/neuron-wallet/src/services/cells.ts @@ -57,6 +57,7 @@ export enum CustomizedType { NFTIssuer = 'NFTIssuer', SUDT = 'SUDT', + XUDT = 'XUDT', Spore = 'Spore', SporeCluster = 'SporeCluster', @@ -79,6 +80,7 @@ export enum TypeScriptCategory { NFTClass = CustomizedType.NFTClass, NFTIssuer = CustomizedType.NFTIssuer, SUDT = CustomizedType.SUDT, + XUDT = CustomizedType.XUDT, Spore = CustomizedType.Spore, Unknown = CustomizedType.Unknown, } @@ -296,6 +298,7 @@ export default class CellsService { const nftClassCodehash = assetAccountInfo.getNftClassInfo().codeHash const nftCodehash = assetAccountInfo.getNftInfo().codeHash const sudtCodehash = assetAccountInfo.getSudtCodeHash() + const xudtCodeHash = assetAccountInfo.infos.xudt.codeHash const sporeInfos = assetAccountInfo.getSporeInfos() const secp256k1LockHashes = [...blake160Hashes].map(blake160 => @@ -304,7 +307,7 @@ export default class CellsService { const skip = (pageNo - 1) * pageSize - const allMultiSignOutputs = await getConnection() + const allCustomizedOutputs = await getConnection() .getRepository(OutputEntity) .createQueryBuilder('output') .leftJoinAndSelect('output.transaction', 'tx') @@ -365,7 +368,7 @@ export default class CellsService { // to make the Spore NFT data available, // we need to fetch it from RPC instead of database const rpc = generateRPC(currentNetwork.remote, currentNetwork.type) - const sporeOutputs = allMultiSignOutputs.filter(item => + const sporeOutputs = allCustomizedOutputs.filter(item => sporeInfos.some(info => item.typeCodeHash && bytes.equal(info.codeHash, item.typeCodeHash)) ) @@ -393,7 +396,7 @@ export default class CellsService { }) ) - const matchedOutputs = allMultiSignOutputs.filter(o => { + const matchedOutputs = allCustomizedOutputs.filter(o => { if (o.multiSignBlake160) { return multiSignHashes.has(o.multiSignBlake160) } @@ -406,7 +409,11 @@ export default class CellsService { ) } - if (o.hasData && o.typeCodeHash === sudtCodehash && o.lockCodeHash === assetAccountInfo.anyoneCanPayCodeHash) { + if ( + o.hasData && + (o.typeCodeHash === sudtCodehash || o.typeCodeHash === xudtCodeHash) && + o.lockCodeHash === assetAccountInfo.anyoneCanPayCodeHash + ) { return false } @@ -466,10 +473,16 @@ export default class CellsService { }) } else if (o.typeCodeHash === sudtCodehash) { cell.setCustomizedAssetInfo({ - lock: CustomizedLock.SUDT, + lock: '', type: CustomizedType.SUDT, data: '', }) + } else if (o.typeCodeHash === xudtCodeHash) { + cell.setCustomizedAssetInfo({ + lock: '', + type: CustomizedType.XUDT, + data: '', + }) } else if (sporeInfos.some(info => o.typeCodeHash && bytes.equal(info.codeHash, o.typeCodeHash))) { const data = (() => { try { @@ -1365,6 +1378,8 @@ export default class CellsService { return TypeScriptCategory.NFTClass case assetAccountInfo.getSudtCodeHash(): return TypeScriptCategory.SUDT + case assetAccountInfo.infos.xudt.codeHash: + return TypeScriptCategory.XUDT case SystemScriptInfo.DAO_CODE_HASH: return TypeScriptCategory.DAO default: diff --git a/packages/neuron-wallet/src/services/sudt-token-info.ts b/packages/neuron-wallet/src/services/sudt-token-info.ts index 7195f2cdb2..f69f26c760 100644 --- a/packages/neuron-wallet/src/services/sudt-token-info.ts +++ b/packages/neuron-wallet/src/services/sudt-token-info.ts @@ -1,6 +1,7 @@ import { In, Not } from 'typeorm' import SudtTokenInfoEntity from '../database/chain/entities/sudt-token-info' import { getConnection } from '../database/chain/connection' +import { UDTType } from '../utils/const' export default class SudtTokenInfoService { static async findSudtTokenInfoByArgs(typeArgsList: string[]) { @@ -41,13 +42,14 @@ export default class SudtTokenInfoService { .execute() } - static getSudtTokenInfo(typeArgs: string): Promise { + static getSudtTokenInfo(typeArgs: string, udtType: UDTType): Promise { return getConnection() .getRepository(SudtTokenInfoEntity) .createQueryBuilder('info') .leftJoinAndSelect('info.assetAccounts', 'aa') - .where(`info.tokenID = :typeArgs`, { + .where(`info.tokenID = :typeArgs AND info.udtType = :udtType`, { typeArgs, + udtType, }) .getOne() } diff --git a/packages/neuron-wallet/src/services/tx/transaction-generator.ts b/packages/neuron-wallet/src/services/tx/transaction-generator.ts index 78746dc8b5..1471e515e2 100644 --- a/packages/neuron-wallet/src/services/tx/transaction-generator.ts +++ b/packages/neuron-wallet/src/services/tx/transaction-generator.ts @@ -31,7 +31,7 @@ import AddressService from '../../services/addresses' import { addressToScript } from '../../utils/scriptAndAddress' import MultisigConfigModel from '../../models/multisig-config' import WalletService from '../../services/wallets' -import { MIN_CELL_CAPACITY, MIN_SUDT_CAPACITY } from '../../utils/const' +import { MIN_CELL_CAPACITY, MIN_SUDT_CAPACITY, UDTType } from '../../utils/const' import AssetAccountService from '../../services/asset-account-service' import LiveCellService from '../../services/live-cell-service' import NetworksService from '../networks' @@ -651,31 +651,46 @@ export class TransactionGenerator { } // sUDT - public static async generateCreateAnyoneCanPayTx( - tokenID: string, - walletId: string, - blake160: string, - changeBlake160: string, - feeRate: string, + public static async generateCreateAnyoneCanPayTx({ + tokenID, + walletId, + blake160, + changeBlake160, + feeRate, + fee, + udtType, + }: { + tokenID: string + walletId: string + blake160: string + changeBlake160: string + feeRate: string fee: string - ): Promise { + udtType?: UDTType + }): Promise { // if tokenID === '' or 'CKBytes', create ckb cell const isCKB = tokenID === 'CKBytes' || tokenID === '' + if (!isCKB && !udtType) throw new Error('The udt token must has udt Type') const secpCellDep = await SystemScriptInfo.getInstance().getSecpCellDep() const assetAccountInfo = new AssetAccountInfo() const sudtCellDep = assetAccountInfo.sudtCellDep + const xudtCellDep = assetAccountInfo.xudtCellDep const needCapacities = isCKB ? BigInt(61 * 10 ** 8) : BigInt(142 * 10 ** 8) const output = Output.fromObject({ capacity: needCapacities.toString(), lock: assetAccountInfo.generateAnyoneCanPayScript(blake160), - type: isCKB ? null : assetAccountInfo.generateSudtScript(tokenID), + type: isCKB + ? null + : udtType === UDTType.SUDT + ? assetAccountInfo.generateSudtScript(tokenID) + : assetAccountInfo.generateXudtScript(tokenID), data: isCKB ? '0x' : BufferUtils.writeBigUInt128LE(BigInt(0)), }) const tx = Transaction.fromObject({ version: '0', headerDeps: [], - cellDeps: [secpCellDep, sudtCellDep], + cellDeps: [secpCellDep, sudtCellDep, xudtCellDep], inputs: [], outputs: [output], outputsData: [output.data], @@ -713,13 +728,21 @@ export class TransactionGenerator { return tx } - public static async generateCreateAnyoneCanPayTxUseAllBalance( - tokenID: string, - walletId: string, - blake160: string, - feeRate: string, + public static async generateCreateAnyoneCanPayTxUseAllBalance({ + tokenID, + walletId, + blake160, + feeRate, + fee, + udtType, + }: { + tokenID: string + walletId: string + blake160: string + feeRate: string fee: string - ): Promise { + udtType?: UDTType + }): Promise { // if tokenID === '' or 'CKBytes', create ckb cell const isCKB = tokenID === 'CKBytes' || tokenID === '' @@ -729,6 +752,7 @@ export class TransactionGenerator { const secpCellDep = await SystemScriptInfo.getInstance().getSecpCellDep() const assetAccountInfo = new AssetAccountInfo() const sudtCellDep = assetAccountInfo.sudtCellDep + const xudtCellDep = assetAccountInfo.xudtCellDep const allInputs: Input[] = await CellsService.gatherAllInputs(walletId) @@ -741,14 +765,18 @@ export class TransactionGenerator { const output = Output.fromObject({ capacity: totalCapacity.toString(), lock: assetAccountInfo.generateAnyoneCanPayScript(blake160), - type: isCKB ? null : assetAccountInfo.generateSudtScript(tokenID), + type: isCKB + ? null + : udtType === UDTType.SUDT + ? assetAccountInfo.generateSudtScript(tokenID) + : assetAccountInfo.generateXudtScript(tokenID), data: isCKB ? '0x' : BufferUtils.writeBigUInt128LE(BigInt(0)), }) const tx = Transaction.fromObject({ version: '0', headerDeps: [], - cellDeps: [secpCellDep, sudtCellDep], + cellDeps: [secpCellDep, sudtCellDep, xudtCellDep], inputs: allInputs, outputs: [output], outputsData: [output.data], @@ -782,7 +810,7 @@ export class TransactionGenerator { throw new SudtAcpHaveDataError() } if (!isCKBAccount) { - cellDeps.push(assetAccountInfo.sudtCellDep) + cellDeps.push(assetAccountInfo.sudtCellDep, assetAccountInfo.xudtCellDep) } const output = Output.fromObject({ @@ -921,7 +949,6 @@ export class TransactionGenerator { ) { const secpCellDep = await SystemScriptInfo.getInstance().getSecpCellDep() const assetAccountInfo = new AssetAccountInfo() - const sudtCellDep = assetAccountInfo.sudtCellDep const anyoneCanPayDep = assetAccountInfo.anyoneCanPayCellDep const targetAmount: bigint = amount === 'all' ? BigInt(0) : BufferUtils.parseAmountFromSUDTData(targetOutput.data) + BigInt(amount) @@ -940,10 +967,14 @@ export class TransactionGenerator { data: targetOutput.data, }) : undefined + const udtCellDep = + targetOutput.type && assetAccountInfo.isSudtScript(targetOutput.type) + ? assetAccountInfo.sudtCellDep + : assetAccountInfo.xudtCellDep const tx = Transaction.fromObject({ version: '0', headerDeps: [], - cellDeps: [secpCellDep, sudtCellDep, anyoneCanPayDep], + cellDeps: [secpCellDep, udtCellDep, anyoneCanPayDep], inputs: targetInput ? [targetInput] : [], outputs: [output], outputsData: [output.data], @@ -987,7 +1018,7 @@ export class TransactionGenerator { // amount assertion TransactionGenerator.checkTxCapacity(tx, 'generateAnyoneCanPayToSudtTx capacity not match!') - TransactionGenerator.checkTxSudtAmount(tx, 'generateAnyoneCanPayToSudtTx sUDT amount not match!', assetAccountInfo) + TransactionGenerator.checkTxUdtAmount(tx, 'generateAnyoneCanPayToSudtTx sUDT amount not match!', assetAccountInfo) return tx } @@ -1075,7 +1106,8 @@ export class TransactionGenerator { const assetAccountInfo = new AssetAccountInfo() const secpCellDep = await SystemScriptInfo.getInstance().getSecpCellDep() - const sudtCellDep = assetAccountInfo.sudtCellDep + const udtCellDep = + assetAccount.udtType === UDTType.SUDT ? assetAccountInfo.sudtCellDep : assetAccountInfo.xudtCellDep const anyoneCanPayDep = assetAccountInfo.anyoneCanPayCellDep const senderAcpScript = assetAccountInfo.generateAnyoneCanPayScript(assetAccount.blake160) @@ -1084,13 +1116,13 @@ export class TransactionGenerator { const chequeCellTmp = Output.fromObject({ capacity: BigInt(162 * 10 ** 8).toString(), lock: assetAccountInfo.generateChequeScript(bytes.hexify(Buffer.alloc(20)), bytes.hexify(Buffer.alloc(20))), - type: assetAccountInfo.generateSudtScript(assetAccount.tokenID), + type: assetAccountInfo.generateUdtScript(assetAccount.tokenID, assetAccount.udtType), }) const tx = Transaction.fromObject({ version: '0', headerDeps: [], - cellDeps: [secpCellDep, sudtCellDep, anyoneCanPayDep], + cellDeps: [secpCellDep, udtCellDep, anyoneCanPayDep], inputs: [], outputs: [], outputsData: [], @@ -1173,7 +1205,7 @@ export class TransactionGenerator { tx.anyoneCanPaySendAmount = tx.sudtInfo.amount TransactionGenerator.checkTxCapacity(tx, 'generateCreateChequeTx capacity not match!') - TransactionGenerator.checkTxSudtAmount(tx, 'generateCreateChequeTx sUDT amount not match!', assetAccountInfo) + TransactionGenerator.checkTxUdtAmount(tx, 'generateCreateChequeTx sUDT amount not match!', assetAccountInfo) return tx } @@ -1208,7 +1240,10 @@ export class TransactionGenerator { const assetAccountInfo = new AssetAccountInfo() const secpCellDep = await SystemScriptInfo.getInstance().getSecpCellDep() - const sudtCellDep = assetAccountInfo.sudtCellDep + const udtCellDep = + chequeCell.type && assetAccountInfo.isSudtScript(chequeCell.type) + ? assetAccountInfo.sudtCellDep + : assetAccountInfo.xudtCellDep const anyoneCanPayDep = assetAccountInfo.anyoneCanPayCellDep const chequeDep = assetAccountInfo.getChequeInfo().cellDep @@ -1229,7 +1264,7 @@ export class TransactionGenerator { const tx = Transaction.fromObject({ version: '0', - cellDeps: [secpCellDep, sudtCellDep, anyoneCanPayDep, chequeDep], + cellDeps: [secpCellDep, udtCellDep, anyoneCanPayDep, chequeDep], headerDeps: [], inputs: [chequeInput], outputs: [senderOutput], @@ -1306,7 +1341,7 @@ export class TransactionGenerator { } TransactionGenerator.checkTxCapacity(tx, 'generateClaimChequeTx capacity not match!') - TransactionGenerator.checkTxSudtAmount(tx, 'generateClaimChequeTx sUDT amount not match!', assetAccountInfo) + TransactionGenerator.checkTxUdtAmount(tx, 'generateClaimChequeTx sUDT amount not match!', assetAccountInfo) return tx } @@ -1378,12 +1413,13 @@ export class TransactionGenerator { const secpCellDep = await SystemScriptInfo.getInstance().getSecpCellDep() const sudtCellDep = assetAccountInfo.sudtCellDep + const xudtCellDep = assetAccountInfo.xudtCellDep const anyoneCanPayDep = assetAccountInfo.anyoneCanPayCellDep const chequeDep = assetAccountInfo.getChequeInfo().cellDep const tx = Transaction.fromObject({ version: '0', - cellDeps: [secpCellDep, sudtCellDep, anyoneCanPayDep, chequeDep], + cellDeps: [secpCellDep, sudtCellDep, xudtCellDep, anyoneCanPayDep, chequeDep], headerDeps: [], inputs: [chequeSenderAcpInput, chequeInput], outputs: [senderAcpOutput], @@ -1410,7 +1446,7 @@ export class TransactionGenerator { tx.outputsData.push('0x') TransactionGenerator.checkTxCapacity(tx, 'generateWithdrawChequeTx capacity not match!') - TransactionGenerator.checkTxSudtAmount(tx, 'generateWithdrawChequeTx sUDT amount not match!', assetAccountInfo) + TransactionGenerator.checkTxUdtAmount(tx, 'generateWithdrawChequeTx sUDT amount not match!', assetAccountInfo) return tx } @@ -1456,7 +1492,6 @@ export class TransactionGenerator { ] const secpCellDep = await SystemScriptInfo.getInstance().getSecpCellDep() - const sudtCellDep = assetAccountInfo.sudtCellDep let outputs: Output[] = [] let acpInputCell: Input | null = null const acpCodeHashes = new Set([sudtCell.lock.codeHash]) @@ -1511,7 +1546,12 @@ export class TransactionGenerator { const tx = Transaction.fromObject({ version: '0', headerDeps: [], - cellDeps: [secpCellDep, sudtCellDep, ...acpCellDeps.filter((v): v is CellDep => !!v)], + cellDeps: [ + secpCellDep, + assetAccountInfo.sudtCellDep, + assetAccountInfo.xudtCellDep, + ...acpCellDeps.filter((v): v is CellDep => !!v), + ], inputs: sudtMigrateAcpInputs, outputs: outputs, outputsData: outputs.map(v => v.data || '0x'), @@ -1577,18 +1617,29 @@ export class TransactionGenerator { ) } - private static checkTxSudtAmount(tx: Transaction, msg: string, assetAccountInfo: AssetAccountInfo) { - const inputAmount = tx.inputs + private static checkTxUdtAmount(tx: Transaction, msg: string, assetAccountInfo: AssetAccountInfo) { + const sudtInputAmount = tx.inputs .filter(i => i.type && assetAccountInfo.isSudtScript(i.type)) .map(i => BufferUtils.parseAmountFromSUDTData(i.data!)) .reduce((result, c) => result + c, BigInt(0)) - const outputAmount = tx.outputs + const sudtOutputAmount = tx.outputs .filter(o => o.type && assetAccountInfo.isSudtScript(o.type)) .map(o => BufferUtils.parseAmountFromSUDTData(o.data!)) .reduce((result, c) => result + c, BigInt(0)) - assert.equal(inputAmount.toString(), outputAmount.toString(), `${msg}: ${JSON.stringify(tx)}`) + assert.equal(sudtInputAmount.toString(), sudtOutputAmount.toString(), `${msg}: ${JSON.stringify(tx)}`) + + const xudtInputAmount = tx.inputs + .filter(i => i.type && assetAccountInfo.isXudtScript(i.type)) + .map(i => BufferUtils.parseAmountFromSUDTData(i.data!)) + .reduce((result, c) => result + c, BigInt(0)) + + const xudtOutputAmount = tx.outputs + .filter(o => o.type && assetAccountInfo.isXudtScript(o.type)) + .map(o => BufferUtils.parseAmountFromSUDTData(o.data!)) + .reduce((result, c) => result + c, BigInt(0)) + assert.equal(xudtInputAmount.toString(), xudtOutputAmount.toString(), `${msg}: ${JSON.stringify(tx)}`) } } diff --git a/packages/neuron-wallet/src/services/tx/transaction-service.ts b/packages/neuron-wallet/src/services/tx/transaction-service.ts index 8b43057ed9..ebe89d8e52 100644 --- a/packages/neuron-wallet/src/services/tx/transaction-service.ts +++ b/packages/neuron-wallet/src/services/tx/transaction-service.ts @@ -2,13 +2,7 @@ import { CKBRPC } from '@ckb-lumos/rpc' import TransactionEntity from '../../database/chain/entities/transaction' import OutputEntity from '../../database/chain/entities/output' import { getConnection } from '../../database/chain/connection' -import Transaction, { - TransactionStatus, - SudtInfo, - NFTType, - NFTInfo, - AssetAccountType, -} from '../../models/chain/transaction' +import Transaction, { TransactionStatus, NFTType, AssetAccountType } from '../../models/chain/transaction' import InputEntity from '../../database/chain/entities/input' import AddressParser from '../../models/address-parser' import AssetAccountInfo from '../../models/asset-account-info' @@ -22,6 +16,7 @@ import Input from '../../models/chain/input' import SudtTokenInfoService from '../sudt-token-info' import TransactionSize from '../../models/transaction-size' import Output from '../../models/chain/output' +import { UDTType } from '../../utils/const' export interface TransactionsByAddressesParam { pageNo: number @@ -93,6 +88,145 @@ export class TransactionsService { return 0 } + private static async getSudtInfo({ + txHash, + acpUdtInputs, + acpUdtOutputs, + udtType, + }: { + txHash: string + acpUdtInputs: InputEntity[] + acpUdtOutputs: OutputEntity[] + udtType: UDTType + }) { + const udtInput = acpUdtInputs.find(i => i.transactionHash === txHash) + const udtOutput = acpUdtOutputs.find(o => o.outPointTxHash === txHash) + const typeArgs: string | undefined | null = udtInput?.typeArgs ?? udtOutput?.typeArgs + const assetAccountType: AssetAccountType | undefined = + udtInput || udtOutput ? (udtType === UDTType.SUDT ? AssetAccountType.SUDT : AssetAccountType.XUDT) : undefined + if (!typeArgs) return undefined + const inputAmount = acpUdtInputs + .filter(i => i.transactionHash === txHash && i.typeArgs === typeArgs) + .map(i => BufferUtils.parseAmountFromSUDTData(i.data)) + .reduce((result, c) => result + c, BigInt(0)) + const outputAmount = acpUdtOutputs + .filter(o => o.outPointTxHash === txHash && o.typeArgs === typeArgs) + .map(o => BufferUtils.parseAmountFromSUDTData(o.data)) + .reduce((result, c) => result + c, BigInt(0)) + const amount = outputAmount - inputAmount + let txType = amount > 0 ? 'receive' : 'send' + if (udtInput && !udtOutput) { + txType = 'destroy' + } else if (!udtInput && udtOutput) { + txType = 'create' + } + + return { + sudtInfo: { + sUDT: (await SudtTokenInfoService.getSudtTokenInfo(typeArgs, udtType)) ?? undefined, + amount: amount.toString(), + }, + txType, + assetAccountType, + } + } + + private static async getAssetCKBInfo({ + txHash, + acpCKBInputs, + acpCKBOutputs, + }: { + txHash: string + acpCKBInputs: InputEntity[] + acpCKBOutputs: OutputEntity[] + }) { + const ckbAssetInput = acpCKBInputs.find(i => i.transactionHash === txHash) + const ckbAssetOutput = acpCKBOutputs.find(o => o.outPointTxHash === txHash) + if (!ckbAssetInput && !ckbAssetOutput) return + if (ckbAssetInput && !ckbAssetOutput) { + return { + txType: 'destroy', + assetAccountType: AssetAccountType.CKB, + } + } + if (!ckbAssetInput && ckbAssetOutput) { + return { + txType: 'create', + assetAccountType: AssetAccountType.CKB, + } + } + } + + private static getNtfInfo({ + txHash, + nftInputs, + nftOutputs, + }: { + txHash: string + nftInputs: InputEntity[] + nftOutputs: OutputEntity[] + }) { + const sendNFTCell = nftInputs.find(i => i.transactionHash === txHash) + const receiveNFTCell = nftOutputs.find(o => o.outPointTxHash === txHash) + + if (sendNFTCell) { + return { type: NFTType.Send, data: sendNFTCell.typeArgs! } + } + if (receiveNFTCell) { + return { type: NFTType.Receive, data: receiveNFTCell.typeArgs! } + } + } + + private static getDAOCapacity( + ckbChange: bigint, + txDaoInfo?: { + inputs: { + capacity: string + transactionHash: string + outPointTxHash: string + outPointIndex: string + data: string + }[] + outputs: { + capacity: string + transactionHash: string + daoData: string + }[] + } + ) { + if (!txDaoInfo) return + if (txDaoInfo.inputs.length && !txDaoInfo.outputs.length) { + return txDaoInfo.inputs.reduce((pre, cur) => BigInt(cur.capacity) + pre, ckbChange).toString() + } + if (!txDaoInfo.inputs.length && txDaoInfo.outputs.length) { + return `-${txDaoInfo.outputs.reduce((pre, cur) => BigInt(cur.capacity) + pre, BigInt(0)).toString()}` + } + } + + private static groupAssetCells(cells: T[]) { + const assetAccountInfo = new AssetAccountInfo() + const acpSudtCells: T[] = [] + const acpCKBCells: T[] = [] + const acpXudtCells: T[] = [] + cells.forEach(v => { + const typeScript = v.typeScript() + if (typeScript) { + if (assetAccountInfo.isSudtScript(typeScript)) { + acpSudtCells.push(v) + } else if (assetAccountInfo.isXudtScript(typeScript)) { + acpXudtCells.push(v) + } + } else { + acpCKBCells.push(v) + } + }) + return { + acpCKBCells, + acpSudtCells, + acpXudtCells, + } + } + public static async getAllByAddresses( params: TransactionsByAddressesParam, searchValue: string = '' @@ -359,14 +493,6 @@ export class TransactionsService { ) .getMany() - const anyoneCanPayInputs = assetAccountInputs.filter(i => i.typeScript()) - - const anyoneCanPayOutputs = assetAccountOutputs.filter(i => i.typeScript()) - - const ckbAssetInputs = assetAccountInputs.filter(i => !i.typeScript()) - - const ckbAssetOutputs = assetAccountOutputs.filter(i => !i.typeScript()) - const inputPreviousTxHashes: string[] = inputs.map(i => i.outPointTxHash).filter(h => !!h) as string[] const daoCellOutPoints: { txHash: string; index: string }[] = ( @@ -410,114 +536,53 @@ export class TransactionsService { } }) + const { + acpCKBCells: acpCKBInputs, + acpSudtCells: acpSudtInputs, + acpXudtCells: acpXudtInputs, + } = TransactionsService.groupAssetCells(assetAccountInputs) + const { + acpCKBCells: acpCKBOutputs, + acpSudtCells: acpSudtOutputs, + acpXudtCells: acpXudtOutputs, + } = TransactionsService.groupAssetCells(assetAccountOutputs) const txs = await Promise.all( transactions.map(async tx => { const value = sums.get(tx.hash!) || BigInt(0) - - let typeArgs: string | undefined | null - let txType: string = value > BigInt(0) ? 'receive' : 'send' - let assetAccountType: AssetAccountType | undefined - - const sudtInput = anyoneCanPayInputs.find( - i => i.transactionHash === tx.hash && assetAccountInfo.isSudtScript(i.typeScript()!) - ) - const sudtOutput = anyoneCanPayOutputs.find( - o => o.outPointTxHash === tx.hash && assetAccountInfo.isSudtScript(o.typeScript()!) - ) - if (sudtInput) { - typeArgs = sudtInput.typeArgs - if (!sudtOutput) { - txType = 'destroy' - assetAccountType = AssetAccountType.SUDT - } - } else { - if (sudtOutput) { - typeArgs = sudtOutput.typeArgs - txType = 'create' - assetAccountType = AssetAccountType.SUDT - } - } - - const ckbAssetInput = ckbAssetInputs.find(i => i.transactionHash === tx.hash) - const ckbAssetOutput = ckbAssetOutputs.find(o => o.outPointTxHash === tx.hash) - if (ckbAssetInput && !ckbAssetOutput) { - txType = 'destroy' - assetAccountType = AssetAccountType.CKB - } else if (!ckbAssetInput && ckbAssetOutput) { - txType = 'create' - assetAccountType = AssetAccountType.CKB - } - - let sudtInfo: SudtInfo | undefined - - if (typeArgs) { - // const typeArgs = sudtInput.typeArgs - const inputAmount = anyoneCanPayInputs - .filter( - i => - i.transactionHash === tx.hash && - assetAccountInfo.isSudtScript(i.typeScript()!) && - i.typeArgs === typeArgs - ) - .map(i => BufferUtils.parseAmountFromSUDTData(i.data)) - .reduce((result, c) => result + c, BigInt(0)) - const outputAmount = anyoneCanPayOutputs - .filter( - o => - o.outPointTxHash === tx.hash && - assetAccountInfo.isSudtScript(o.typeScript()!) && - o.typeArgs === typeArgs - ) - .map(o => BufferUtils.parseAmountFromSUDTData(o.data)) - .reduce((result, c) => result + c, BigInt(0)) - - const amount = outputAmount - inputAmount - const tokenInfo = await SudtTokenInfoService.getSudtTokenInfo(typeArgs) - - if (tokenInfo) { - sudtInfo = { - sUDT: tokenInfo, - amount: amount.toString(), - } - } - } - - const sendNFTCell = nftInputs.find(i => i.typeCodeHash === nftCodehash && i.transactionHash === tx.hash) - const receiveNFTCell = nftOutputs.find(o => o.typeCodeHash === nftCodehash && o.outPointTxHash === tx.hash) - - let nftInfo: NFTInfo | undefined - if (sendNFTCell) { - nftInfo = { type: NFTType.Send, data: sendNFTCell.typeArgs! } - } else if (receiveNFTCell) { - nftInfo = { type: NFTType.Receive, data: receiveNFTCell.typeArgs! } - } - - const txDaoInfo = daoInfo.get(tx.hash) - let daoCapacity: string | undefined - if (txDaoInfo) { - if (txDaoInfo.inputs.length && !txDaoInfo.outputs.length) { - daoCapacity = txDaoInfo.inputs.reduce((pre, cur) => BigInt(cur.capacity) + pre, BigInt(value)).toString() - } else if (!txDaoInfo.inputs.length && txDaoInfo.outputs.length) { - daoCapacity = `-${txDaoInfo.outputs.reduce((pre, cur) => BigInt(cur.capacity) + pre, BigInt(0)).toString()}` - } - } + const typeScriptInfo = + (await TransactionsService.getSudtInfo({ + txHash: tx.hash, + acpUdtInputs: acpSudtInputs, + acpUdtOutputs: acpSudtOutputs, + udtType: UDTType.SUDT, + })) || + (await TransactionsService.getSudtInfo({ + txHash: tx.hash, + acpUdtInputs: acpXudtInputs, + acpUdtOutputs: acpXudtOutputs, + udtType: UDTType.XUDT, + })) || + (await TransactionsService.getAssetCKBInfo({ + txHash: tx.hash, + acpCKBInputs, + acpCKBOutputs, + })) return Transaction.fromObject({ timestamp: tx.timestamp, value: value.toString(), hash: tx.hash, version: tx.version, - type: txType, - assetAccountType: assetAccountType, - nervosDao: !!txDaoInfo, status: tx.status, description: tx.description, createdAt: tx.createdAt, updatedAt: tx.updatedAt, blockNumber: tx.blockNumber, - sudtInfo: sudtInfo, - nftInfo: nftInfo, - daoCapacity, + ...typeScriptInfo, + type: typeScriptInfo?.txType ?? (value > BigInt(0) ? 'receive' : 'send'), + nftInfo: TransactionsService.getNtfInfo({ txHash: tx.hash, nftInputs, nftOutputs }), + nervosDao: !!daoInfo.get(tx.hash), + daoCapacity: TransactionsService.getDAOCapacity(value, daoInfo.get(tx.hash)), }) }) ) diff --git a/packages/neuron-wallet/src/utils/ckb-rpc.ts b/packages/neuron-wallet/src/utils/ckb-rpc.ts index fc33fbefe7..e590185f16 100644 --- a/packages/neuron-wallet/src/utils/ckb-rpc.ts +++ b/packages/neuron-wallet/src/utils/ckb-rpc.ts @@ -111,7 +111,7 @@ const lightRPCProperties: Record[0] paramsFormatters: [paramsFormatter.toHash], resultFormatters: (result: { status: 'fetched' | 'fetching' | 'added' | 'not_found' - data?: RPC.TransactionWithStatus + data?: Parameters[0] }) => { return { status: result.status, diff --git a/packages/neuron-wallet/src/utils/const.ts b/packages/neuron-wallet/src/utils/const.ts index d294db6b7f..9059715d05 100644 --- a/packages/neuron-wallet/src/utils/const.ts +++ b/packages/neuron-wallet/src/utils/const.ts @@ -24,6 +24,11 @@ export enum ResponseCode { Success, } +export enum UDTType { + SUDT = 'sUDT', + XUDT = 'xUDT', +} + export default { ResponseCode, } diff --git a/packages/neuron-wallet/tests/models/asset-account-info.test.ts b/packages/neuron-wallet/tests/models/asset-account-info.test.ts index cb7d34a570..1d22cb4a07 100644 --- a/packages/neuron-wallet/tests/models/asset-account-info.test.ts +++ b/packages/neuron-wallet/tests/models/asset-account-info.test.ts @@ -1,27 +1,25 @@ +import { predefined } from '@ckb-lumos/config-manager' import AssetAccountInfo from '../../src/models/asset-account-info' import CellDep, { DepType } from '../../src/models/chain/cell-dep' import OutPoint from '../../src/models/chain/out-point' -import { ScriptHashType } from '../../src/models/chain/script' import { AddressType } from '@ckb-lumos/hd' import AddressMeta from '../../src/database/address/meta' describe('AssetAccountInfo', () => { + const { SUDT, ANYONE_CAN_PAY } = predefined.AGGRON4.SCRIPTS const testnetSudtInfo = { - cellDep: new CellDep( - new OutPoint('0xc1b2ae129fad7465aaa9acc9785f842ba3e6e8b8051d899defa89f5508a77958', '0'), - DepType.Code - ), - codeHash: '0x48dbf59b4c7ee1547238021b4869bceedf4eea6b43772e5d66ef8865b6ae7212', - hashType: ScriptHashType.Data, + cellDep: new CellDep(new OutPoint(SUDT.TX_HASH, SUDT.INDEX), SUDT.DEP_TYPE as DepType), + codeHash: SUDT.CODE_HASH, + hashType: SUDT.HASH_TYPE, } const testnetAnyoneCanPayInfo = { cellDep: new CellDep( - new OutPoint('0x4f32b3e39bd1b6350d326fdfafdfe05e5221865c3098ae323096f0bfc69e0a8c', '0'), - DepType.DepGroup + new OutPoint(ANYONE_CAN_PAY.TX_HASH, ANYONE_CAN_PAY.INDEX), + ANYONE_CAN_PAY.DEP_TYPE as DepType ), - codeHash: process.env.TESTNET_ACP_SCRIPT_CODEHASH, - hashType: process.env.TESTNET_ACP_SCRIPT_HASHTYPE, + codeHash: ANYONE_CAN_PAY.CODE_HASH, + hashType: ANYONE_CAN_PAY.HASH_TYPE, } describe('testnet', () => { diff --git a/packages/neuron-wallet/tests/services/anyone-can-pay.test.ts b/packages/neuron-wallet/tests/services/anyone-can-pay.test.ts index 4f259b2a8e..c6e91c71fc 100644 --- a/packages/neuron-wallet/tests/services/anyone-can-pay.test.ts +++ b/packages/neuron-wallet/tests/services/anyone-can-pay.test.ts @@ -5,7 +5,7 @@ import { initConnection, closeConnection, getConnection } from '../setupAndTeard import { AcpSendSameAccountError, TargetLockError, TargetOutputNotFoundError } from '../../src/exceptions' import AssetAccountInfo from '../../src/models/asset-account-info' import SystemScriptInfo from '../../src/models/system-script-info' -import { MIN_SUDT_CAPACITY } from '../../src/utils/const' +import { MIN_SUDT_CAPACITY, UDTType } from '../../src/utils/const' const addressParseMock = jest.fn() jest.mock('../../src/models/address-parser', () => ({ @@ -78,7 +78,9 @@ describe('anyone-can-pay-service', () => { 'tokenName', '8', '0', - '0x62260b4dd406bee8a021185edaa60b7a77f7e99a' + '0x62260b4dd406bee8a021185edaa60b7a77f7e99a', + undefined, + UDTType.SUDT ) ) const ckbAssetAccount = AssetAccountEntity.fromModel( @@ -292,7 +294,8 @@ describe('anyone-can-pay-service', () => { //@ts-ignore await AnyoneCanPayService.getSUDTTargetOutput( SystemScriptInfo.generateSecpScript(assetAccount.blake160), - 'tokenID' + 'tokenID', + UDTType.SUDT ) expect(fromObjectMock).toHaveBeenCalledWith({ capacity: BigInt(MIN_SUDT_CAPACITY).toString(), @@ -305,7 +308,8 @@ describe('anyone-can-pay-service', () => { //@ts-ignore await AnyoneCanPayService.getSUDTTargetOutput( new AssetAccountInfo().generateAnyoneCanPayScript(assetAccount.blake160), - 'tokenID' + 'tokenID', + UDTType.SUDT ) expect(fromObjectMock).toHaveBeenCalledWith({ capacity: BigInt(MIN_SUDT_CAPACITY).toString(), @@ -345,7 +349,11 @@ describe('anyone-can-pay-service', () => { getOneByLockScriptAndTypeScriptMock.mockResolvedValue(undefined) const args = `0x${'0'.repeat(40)}` //@ts-ignore - await AnyoneCanPayService.getSUDTTargetOutput(new AssetAccountInfo().generateChequeScript(args, args), 'tokenID') + await AnyoneCanPayService.getSUDTTargetOutput( + new AssetAccountInfo().generateChequeScript(args, args), + 'tokenID', + UDTType.SUDT + ) expect(fromObjectMock).toHaveBeenCalledWith({ capacity: BigInt(162 * 10 ** 8).toString(), lock: new AssetAccountInfo().generateChequeScript(args, args), diff --git a/packages/neuron-wallet/tests/services/asset-account-service.test.ts b/packages/neuron-wallet/tests/services/asset-account-service.test.ts index 0e980b8a08..91c297eee9 100644 --- a/packages/neuron-wallet/tests/services/asset-account-service.test.ts +++ b/packages/neuron-wallet/tests/services/asset-account-service.test.ts @@ -18,6 +18,7 @@ import SystemScriptInfo from '../../src/models/system-script-info' import Script from '../../src/models/chain/script' import Input from '../../src/models/chain/input' import { keyInfos } from '../setupAndTeardown/public-key-info.fixture' +import { UDTType } from '../../src/utils/const' const stubbedWalletServiceGet = jest.fn() const stubbedGenerateClaimChequeTx = jest.fn() @@ -59,7 +60,8 @@ const generateOutput = ( tokenAmount = '100', customData: string | undefined = undefined, status: OutputStatus = OutputStatus.Live, - lock?: Script + lock?: Script, + udtType?: UDTType ) => { const outputEntity = new OutputEntity() outputEntity.outPointTxHash = randomHex() @@ -76,7 +78,7 @@ const generateOutput = ( outputEntity.data = customData || '0x' outputEntity.hasData = customData ? true : false if (tokenID !== 'CKBytes') { - const type = assetAccountInfo.generateSudtScript(tokenID) + const type = assetAccountInfo.generateUdtScript(tokenID, udtType ?? UDTType.SUDT)! outputEntity.typeCodeHash = type.codeHash outputEntity.typeArgs = type.args outputEntity.typeHashType = type.hashType @@ -251,6 +253,7 @@ describe('AssetAccountService', () => { describe('#getAll', () => { const tokenID = '0x' + '0'.repeat(64) + const xUdtTokenID = '0x' + '1'.repeat(64) describe('with both sUDT and CKB accounts', () => { describe('with live cells', () => { @@ -264,6 +267,17 @@ describe('AssetAccountService', () => { balance: '0', accountName: 'sUDT', blake160, + udtType: UDTType.SUDT, + }), + AssetAccount.fromObject({ + tokenID: xUdtTokenID, + symbol: 'xUDT', + tokenName: 'xUDT', + decimal: '0', + balance: '0', + accountName: 'xUDT', + blake160, + udtType: UDTType.XUDT, }), AssetAccount.fromObject({ tokenID: 'CKBytes', @@ -280,6 +294,7 @@ describe('AssetAccountService', () => { generateOutput(undefined, undefined, undefined, toShannon(1000)), generateOutput(tokenID), generateOutput(tokenID), + generateOutput(xUdtTokenID), ] await createAccounts(assetAccounts, outputs) }) @@ -302,6 +317,17 @@ describe('AssetAccountService', () => { balance: '0', accountName: 'sUDT', blake160, + udtType: UDTType.SUDT, + }), + AssetAccount.fromObject({ + tokenID: xUdtTokenID, + symbol: 'xUDT', + tokenName: 'xUDT', + decimal: '0', + balance: '0', + accountName: 'xUDT', + blake160, + udtType: UDTType.XUDT, }), AssetAccount.fromObject({ tokenID: 'CKBytes', @@ -318,13 +344,35 @@ describe('AssetAccountService', () => { generateOutput(undefined, undefined, undefined, toShannon(1000), undefined, undefined, OutputStatus.Sent), generateOutput(tokenID), generateOutput(tokenID, undefined, undefined, undefined, undefined, undefined, OutputStatus.Sent), + generateOutput( + xUdtTokenID, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + UDTType.XUDT + ), + generateOutput( + xUdtTokenID, + undefined, + undefined, + undefined, + undefined, + undefined, + OutputStatus.Sent, + undefined, + UDTType.XUDT + ), ] await createAccounts(assetAccounts, outputs) }) it('includes balance calculations for both sUDT and CKB accounts', async () => { const result = await AssetAccountService.getAll(walletId) - expect(result.length).toEqual(2) + expect(result.length).toEqual(3) expect(result.find((a: any) => a.tokenID === tokenID)?.balance).toEqual('200') expect(result.find((a: any) => a.tokenID === 'CKBytes')?.balance).toEqual(toShannon(2000 - 61).toString()) }) @@ -507,6 +555,7 @@ describe('AssetAccountService', () => { balance: '0', accountName: 'sUDT', blake160, + udtType: UDTType.SUDT, }), AssetAccount.fromObject({ tokenID: 'CKBytes', @@ -962,7 +1011,9 @@ describe('AssetAccountService', () => { '', '', '0', - '0x0000000000000000000000000000000000000000' + '0x0000000000000000000000000000000000000000', + undefined, + UDTType.SUDT ) const tx = {} beforeEach(async () => { diff --git a/packages/neuron-wallet/tests/services/cells.test.ts b/packages/neuron-wallet/tests/services/cells.test.ts index 813a373f69..6260b5df54 100644 --- a/packages/neuron-wallet/tests/services/cells.test.ts +++ b/packages/neuron-wallet/tests/services/cells.test.ts @@ -1270,6 +1270,10 @@ describe('CellsService', () => { generateCell('666', OutputStatus.Live, true, typeScript, { lockScript: bobDefaultLock }), // sudt asset generateCell('777', OutputStatus.Live, true, sudtType, { lockScript: bobDefaultLock }), + // xudt asset + generateCell('888', OutputStatus.Live, true, assetAccountInfo.generateXudtScript('0x'), { + lockScript: bobDefaultLock, + }), ] await getConnection().manager.save(cells) }) @@ -1280,10 +1284,10 @@ describe('CellsService', () => { result = await CellsService.getCustomizedAssetCells([publicKeyHash], 1, 10) }) it('returns all items', () => { - expect(result.totalCount).toEqual(7) - expect(result.items.length).toEqual(7) + expect(result.totalCount).toEqual(8) + expect(result.items.length).toEqual(8) const totalCapacity = result.items.reduce((total: number, cell: any) => total + parseInt(cell.capacity), 0) - expect(totalCapacity).toEqual(100 * 3 + 10000 * 2 + 666 + 777) + expect(totalCapacity).toEqual(100 * 3 + 10000 * 2 + 666 + 777 + 888) }) it('attaches setCustomizedAssetInfo to single multisign cells', async () => { const singleMultiSignCells = result.items.filter( @@ -1312,23 +1316,32 @@ describe('CellsService', () => { const cells = result.items.filter((item: any) => item.capacity === '777') expect(cells.length).toEqual(1) expect(cells[0].customizedAssetInfo).toEqual({ - lock: CustomizedLock.SUDT, + lock: '', type: CustomizedType.SUDT, data: '', }) }) + it('attaches setCustomizedAssetInfo to xudt cells', () => { + const cells = result.items.filter((item: any) => item.capacity === '888') + expect(cells.length).toEqual(1) + expect(cells[0].customizedAssetInfo).toEqual({ + lock: '', + type: CustomizedType.XUDT, + data: '', + }) + }) }) describe('within pagination scope', () => { it('returns first page result', async () => { const page = 1 const result = await CellsService.getCustomizedAssetCells([publicKeyHash], page, pageSize) - expect(result.totalCount).toEqual(7) + expect(result.totalCount).toEqual(8) expect(result.items.length).toEqual(pageSize) }) it('returns the remaining cells for the last page', async () => { const page = 2 const result = await CellsService.getCustomizedAssetCells([publicKeyHash], page, pageSize) - expect(result.totalCount).toEqual(7) + expect(result.totalCount).toEqual(8) expect(result.items.length).toEqual(2) }) }) @@ -1336,7 +1349,7 @@ describe('CellsService', () => { it('returns empty result', async () => { const page = 8 const result = await CellsService.getCustomizedAssetCells([publicKeyHash], page, pageSize) - expect(result.totalCount).toEqual(7) + expect(result.totalCount).toEqual(8) expect(result.items.length).toEqual(0) }) }) diff --git a/packages/neuron-wallet/tests/services/multisig.test.ts b/packages/neuron-wallet/tests/services/multisig.test.ts index 444c916b22..76c17f3bb8 100644 --- a/packages/neuron-wallet/tests/services/multisig.test.ts +++ b/packages/neuron-wallet/tests/services/multisig.test.ts @@ -1,3 +1,12 @@ +jest.doMock('../../src/models/subjects/multisig-config-db-changed-subject', () => ({ + getSubject() { + return { + next: jest.fn(), + subscribe: jest.fn(), + } + }, +})) + import MultisigConfig from '../../src/database/chain/entities/multisig-config' import MultisigConfigModel from '../../src/models/multisig-config' import MultisigService from '../../src/services/multisig' @@ -85,13 +94,14 @@ describe('multisig service', () => { multisigConfigModel.id = res?.id await getConnection().manager.save(multisigOutput) rpcBatchRequestMock.mockResolvedValue([]) - }) + }, 20_000) afterEach(async () => { const connection = getConnection() - await connection.synchronize(true) + await connection.getRepository(MultisigConfig).clear() + await connection.getRepository(MultisigOutput).clear() rpcBatchRequestMock.mockReset() multisigOutputChangedSubjectNextMock.mockReset() - }) + }, 20_000) describe('save multisig config', () => { it('has exist', async () => { diff --git a/packages/neuron-wallet/tests/services/networks.test.ts b/packages/neuron-wallet/tests/services/networks.test.ts index af3ad18a67..98b2b910ca 100644 --- a/packages/neuron-wallet/tests/services/networks.test.ts +++ b/packages/neuron-wallet/tests/services/networks.test.ts @@ -158,21 +158,27 @@ describe(`Unit tests of networks service`, () => { describe(`validation on parameters`, () => { describe(`validation on parameters`, () => { it(`service.create requires name, and remote`, async () => { - expect(service.create(undefined as any, undefined as any)).rejects.toThrowError(t(ERROR_MESSAGE.MISSING_ARG)) - expect(service.create('network name', undefined as any)).rejects.toThrowError(t(ERROR_MESSAGE.MISSING_ARG)) + await expect(service.create(undefined as any, undefined as any)).rejects.toThrowError( + t(ERROR_MESSAGE.MISSING_ARG) + ) + await expect(service.create('network name', undefined as any)).rejects.toThrowError( + t(ERROR_MESSAGE.MISSING_ARG) + ) }) - it(`service.update requires id, options`, () => { - expect(service.update(undefined as any, undefined as any)).rejects.toThrowError(t(ERROR_MESSAGE.MISSING_ARG)) - expect(service.update('', undefined as any)).rejects.toThrowError(t(ERROR_MESSAGE.MISSING_ARG)) + it(`service.update requires id, options`, async () => { + await expect(service.update(undefined as any, undefined as any)).rejects.toThrowError( + t(ERROR_MESSAGE.MISSING_ARG) + ) + await expect(service.update('', undefined as any)).rejects.toThrowError(t(ERROR_MESSAGE.MISSING_ARG)) }) - it(`service.delete requires id `, () => { - expect(service.delete(undefined as any)).rejects.toThrowError(t(ERROR_MESSAGE.MISSING_ARG)) + it(`service.delete requires id `, async () => { + await expect(service.delete(undefined as any)).rejects.toThrowError(t(ERROR_MESSAGE.MISSING_ARG)) }) - it(`service.activate requires id `, () => { - expect(service.activate(undefined as any)).rejects.toThrowError(t(ERROR_MESSAGE.MISSING_ARG)) + it(`service.activate requires id `, async () => { + await expect(service.activate(undefined as any)).rejects.toThrowError(t(ERROR_MESSAGE.MISSING_ARG)) }) }) }) @@ -182,18 +188,18 @@ describe(`Unit tests of networks service`, () => { await service.create('Default', 'http://127.0.0.1:8114') }) - it(`create network with existing name of Default`, () => { - expect(service.create('Default', 'http://127.0.0.1:8114')).rejects.toThrowError(t(ERROR_MESSAGE.NAME_USED)) + it(`create network with existing name of Default`, async () => { + await expect(service.create('Default', 'http://127.0.0.1:8114')).rejects.toThrowError(t(ERROR_MESSAGE.NAME_USED)) }) - it(`update network which is not existing`, () => { + it(`update network which is not existing`, async () => { const id = `not-existing-id` - expect(service.update(id, {})).rejects.toThrowError(t(ERROR_MESSAGE.NETWORK_ID_NOT_FOUND, { id })) + await expect(service.update(id, {})).rejects.toThrowError(t(ERROR_MESSAGE.NETWORK_ID_NOT_FOUND, { id })) }) - it(`activate network which is not existing`, () => { + it(`activate network which is not existing`, async () => { const id = `not-existing-id` - expect(service.activate(id)).rejects.toThrowError(t(ERROR_MESSAGE.NETWORK_ID_NOT_FOUND, { id })) + await expect(service.activate(id)).rejects.toThrowError(t(ERROR_MESSAGE.NETWORK_ID_NOT_FOUND, { id })) }) }) @@ -223,6 +229,7 @@ describe(`Unit tests of networks service`, () => { service.readSync = readSyncMock service.writeSync = writeSyncMock service.updateAll = updateAllMock + service.activate = jest.fn() }) afterEach(() => { readSyncMock.mockReset() diff --git a/packages/neuron-wallet/tests/services/sudt-token-info.test.ts b/packages/neuron-wallet/tests/services/sudt-token-info.test.ts index f89fd33088..9ce636567d 100644 --- a/packages/neuron-wallet/tests/services/sudt-token-info.test.ts +++ b/packages/neuron-wallet/tests/services/sudt-token-info.test.ts @@ -4,6 +4,7 @@ import { closeConnection, getConnection, initConnection } from '../setupAndTeard import HdPublicKeyInfo from '../../src/database/chain/entities/hd-public-key-info' import AssetAccountEntity from '../../src/database/chain/entities/asset-account' import accounts from '../setupAndTeardown/accounts.fixture' +import { UDTType } from '../../src/utils/const' const defaultTokenId = '0x' + '0'.repeat(64) @@ -162,19 +163,19 @@ describe('sudt token info service', () => { }) it('no token info', async () => { - await expect(SudtTokenInfoService.getSudtTokenInfo('0x')).resolves.toBeNull() + await expect(SudtTokenInfoService.getSudtTokenInfo('0x', UDTType.SUDT)).resolves.toBeNull() }) it('token info not match', async () => { const entity = AssetAccountEntity.fromModel(assetAccount) await getConnection().manager.save([entity.sudtTokenInfo, entity]) - await expect(SudtTokenInfoService.getSudtTokenInfo(`0x${'00'.repeat(20)}`)).resolves.toBeNull() + await expect(SudtTokenInfoService.getSudtTokenInfo(`0x${'00'.repeat(20)}`, UDTType.SUDT)).resolves.toBeNull() }) it('match token info', async () => { const entity = AssetAccountEntity.fromModel(assetAccount) await getConnection().manager.save([entity.sudtTokenInfo, entity]) - const result = await SudtTokenInfoService.getSudtTokenInfo(assetAccount.tokenID) + const result = await SudtTokenInfoService.getSudtTokenInfo(assetAccount.tokenID, UDTType.SUDT) expect(result).toBeDefined() expect(result?.assetAccounts).toHaveLength(1) }) diff --git a/packages/neuron-wallet/tests/services/tx/transaction-generator.test.ts b/packages/neuron-wallet/tests/services/tx/transaction-generator.test.ts index c8ba7d414a..322c6907d6 100644 --- a/packages/neuron-wallet/tests/services/tx/transaction-generator.test.ts +++ b/packages/neuron-wallet/tests/services/tx/transaction-generator.test.ts @@ -87,6 +87,7 @@ import AssetAccount from '../../../src/models/asset-account' import MultisigConfigModel from '../../../src/models/multisig-config' import MultisigOutput from '../../../src/database/chain/entities/multisig-output' import { closeConnection, getConnection, initConnection } from '../../setupAndTeardown' +import { UDTType } from '../../../src/utils/const' describe('TransactionGenerator', () => { beforeAll(async () => { @@ -2094,14 +2095,14 @@ describe('TransactionGenerator', () => { describe('generateCreateAnyoneCanPayTx', () => { const feeRate = '1000' it('create ckb', async () => { - const tx = await TransactionGenerator.generateCreateAnyoneCanPayTx( - 'CKBytes', - walletId1, - alice.lockScript.args, - bob.lockScript.args, + const tx = await TransactionGenerator.generateCreateAnyoneCanPayTx({ + tokenID: 'CKBytes', + walletId: walletId1, + blake160: alice.lockScript.args, + changeBlake160: bob.lockScript.args, feeRate, - '0' - ) + fee: '0', + }) // check fee const inputCapacities = tx.inputs.map(i => BigInt(i.capacity ?? 0)).reduce((result, c) => result + c, BigInt(0)) @@ -2129,14 +2130,15 @@ describe('TransactionGenerator', () => { it('create sudt', async () => { const tokenID = '0x' + '0'.repeat(64) - const tx = await TransactionGenerator.generateCreateAnyoneCanPayTx( + const tx = await TransactionGenerator.generateCreateAnyoneCanPayTx({ tokenID, - walletId1, - alice.lockScript.args, - bob.lockScript.args, + walletId: walletId1, + blake160: alice.lockScript.args, + changeBlake160: bob.lockScript.args, feeRate, - '0' - ) + fee: '0', + udtType: UDTType.SUDT, + }) // check fee const inputCapacities = tx.inputs.map(i => BigInt(i.capacity ?? 0)).reduce((result, c) => result + c, BigInt(0)) @@ -2193,7 +2195,7 @@ describe('TransactionGenerator', () => { type: senderAcpLiveCell.type(), data: BufferUtils.writeBigUInt128LE(BigInt(110)), }) - assetAccount = new AssetAccount(tokenID, '', '', '', '', '', alice.lockScript.args) + assetAccount = new AssetAccount(tokenID, '', '', '', '', '', alice.lockScript.args, undefined, UDTType.SUDT) }) describe('with numeric send amount', () => { beforeEach(async () => { @@ -2633,13 +2635,13 @@ describe('TransactionGenerator', () => { const cells: OutputEntity[] = [generateCell(toShannon('100'), OutputStatus.Live, false, null)] await getConnection().manager.save(cells) - const tx = await TransactionGenerator.generateCreateAnyoneCanPayTxUseAllBalance( - 'CKBytes', - walletId1, - alice.lockScript.args, + const tx = await TransactionGenerator.generateCreateAnyoneCanPayTxUseAllBalance({ + tokenID: 'CKBytes', + walletId: walletId1, + blake160: alice.lockScript.args, feeRate, - '0' - ) + fee: '0', + }) // check fee const inputCapacities = tx.inputs.map(i => BigInt(i.capacity ?? 0)).reduce((result, c) => result + c, BigInt(0)) @@ -2667,13 +2669,14 @@ describe('TransactionGenerator', () => { await getConnection().manager.save(cells) const tokenID = '0x' + '0'.repeat(64) - const tx = await TransactionGenerator.generateCreateAnyoneCanPayTxUseAllBalance( + const tx = await TransactionGenerator.generateCreateAnyoneCanPayTxUseAllBalance({ tokenID, - walletId1, - alice.lockScript.args, + walletId: walletId1, + blake160: alice.lockScript.args, feeRate, - '0' - ) + fee: '0', + udtType: UDTType.SUDT, + }) // check fee const inputCapacities = tx.inputs.map(i => BigInt(i.capacity ?? 0)).reduce((result, c) => result + c, BigInt(0)) @@ -2689,6 +2692,35 @@ describe('TransactionGenerator', () => { expect(assetAccountInfo.isAnyoneCanPayScript(output.lock)).toBe(true) expect(output.data).toEqual('0x' + '0'.repeat(32)) }) + + it('create xudt', async () => { + const cells: OutputEntity[] = [generateCell(toShannon('143'), OutputStatus.Live, false, null)] + await getConnection().manager.save(cells) + + const tokenID = '0x' + '0'.repeat(64) + const tx = await TransactionGenerator.generateCreateAnyoneCanPayTxUseAllBalance({ + tokenID, + walletId: walletId1, + blake160: alice.lockScript.args, + feeRate, + fee: '0', + udtType: UDTType.XUDT, + }) + + // check fee + const inputCapacities = tx.inputs.map(i => BigInt(i.capacity ?? 0)).reduce((result, c) => result + c, BigInt(0)) + const outputCapacities = tx.outputs.map(o => BigInt(o.capacity)).reduce((result, c) => result + c, BigInt(0)) + expect(tx.fee).toEqual((inputCapacities - outputCapacities).toString()) + + // check output + expect(tx.outputs.length).toEqual(1) + + const output = tx.outputs[0] + expect(output.capacity).toEqual((BigInt(143 * 10 ** 8) - BigInt(tx.fee ?? 0)).toString()) + expect(assetAccountInfo.isXudtScript(output.type!)).toBe(true) + expect(assetAccountInfo.isAnyoneCanPayScript(output.lock)).toBe(true) + expect(output.data).toEqual('0x' + '0'.repeat(32)) + }) }) describe('#generateMigrateLegacyACPTx', () => { diff --git a/packages/neuron-wallet/tests/setupAndTeardown/accounts.fixture.ts b/packages/neuron-wallet/tests/setupAndTeardown/accounts.fixture.ts index 21d4c06b97..86d25a65fc 100644 --- a/packages/neuron-wallet/tests/setupAndTeardown/accounts.fixture.ts +++ b/packages/neuron-wallet/tests/setupAndTeardown/accounts.fixture.ts @@ -1,4 +1,5 @@ import AssetAccount from '../../src/models/asset-account' +import { UDTType } from '../../src/utils/const' import { DEPLOY_KEY } from './keys' const ASSET_ACCOUNT = { @@ -9,6 +10,7 @@ const ASSET_ACCOUNT = { balance: '0', accountName: 'SUDT Account', blake160: DEPLOY_KEY.blake160, + udtType: UDTType.SUDT, } const CKB_ASSET_ACCOUNT = { diff --git a/yarn.lock b/yarn.lock index 94e8003987..6eef94b2a2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2131,6 +2131,20 @@ js-xxhash "^1.0.4" lodash.isequal "^4.5.0" +"@ckb-lumos/base@0.23.0": + version "0.23.0" + resolved "https://registry.yarnpkg.com/@ckb-lumos/base/-/base-0.23.0.tgz#507e2860dd49547268e9088e10aa2051507d4343" + integrity sha512-8aLFsUyWIK0rT7GQlYFuXyiG5lQ2bLRK2GvUsxv5G7I3nJ1UyxjwvVOdtlsR/cwhzOam3ujwqASqBIayBL6GLA== + dependencies: + "@ckb-lumos/bi" "0.23.0" + "@ckb-lumos/codec" "0.23.0" + "@ckb-lumos/toolkit" "0.23.0" + "@types/blake2b" "^2.1.0" + "@types/lodash.isequal" "^4.5.5" + blake2b "^2.1.3" + js-xxhash "^1.0.4" + lodash.isequal "^4.5.0" + "@ckb-lumos/bi@0.21.1", "@ckb-lumos/bi@^0.21.0-next.0": version "0.21.1" resolved "https://registry.yarnpkg.com/@ckb-lumos/bi/-/bi-0.21.1.tgz#dfaa0968a9ffd990c1c4fcfc0711f20f09eb0485" @@ -2138,6 +2152,13 @@ dependencies: jsbi "^4.1.0" +"@ckb-lumos/bi@0.23.0": + version "0.23.0" + resolved "https://registry.yarnpkg.com/@ckb-lumos/bi/-/bi-0.23.0.tgz#8439d712b823234b858bffff2636ffc21d98199f" + integrity sha512-KAy+lyVpL+Al4XD+c9tHrA9DSpHkMusyXtTS81aNZi5MyL6F9jrVmFcqLorhfyfl8Fsv2sEjMe5Neo2Y+w/RJQ== + dependencies: + jsbi "^4.1.0" + "@ckb-lumos/ckb-indexer@0.21.1": version "0.21.1" resolved "https://registry.yarnpkg.com/@ckb-lumos/ckb-indexer/-/ckb-indexer-0.21.1.tgz#a0e0ac3261c98181dc8c72e9e726030bb210779d" @@ -2151,6 +2172,19 @@ cross-fetch "^3.1.5" events "^3.3.0" +"@ckb-lumos/ckb-indexer@0.23.0": + version "0.23.0" + resolved "https://registry.yarnpkg.com/@ckb-lumos/ckb-indexer/-/ckb-indexer-0.23.0.tgz#67cbf4539904ea511517fda1e23c5ec4515b574d" + integrity sha512-yLODLJzvtz4M6W6OJR4iRbBTUGrKReV2dhVePPjbH/HRkRY6f6J6cEM9+qM2I3QABmaCXeAM3hWvnWU9hjalQQ== + dependencies: + "@ckb-lumos/base" "0.23.0" + "@ckb-lumos/bi" "0.23.0" + "@ckb-lumos/codec" "0.23.0" + "@ckb-lumos/rpc" "0.23.0" + "@ckb-lumos/toolkit" "0.23.0" + cross-fetch "^3.1.5" + events "^3.3.0" + "@ckb-lumos/codec@0.21.1", "@ckb-lumos/codec@^0.21.0-next.0": version "0.21.1" resolved "https://registry.yarnpkg.com/@ckb-lumos/codec/-/codec-0.21.1.tgz#160f21efa0cded6ea461eb6476f9af6010b682e0" @@ -2158,6 +2192,13 @@ dependencies: "@ckb-lumos/bi" "0.21.1" +"@ckb-lumos/codec@0.23.0": + version "0.23.0" + resolved "https://registry.yarnpkg.com/@ckb-lumos/codec/-/codec-0.23.0.tgz#abc8c2da95931859d3347608af4b461d27c5daff" + integrity sha512-FwYooXnsFDjlHHnlFnCTB1UbBzV72I0VjkRpeauFk5nQ4+/75xl28ywK3J14M+0aHTnYU9msXUTRDAGqC0CaNQ== + dependencies: + "@ckb-lumos/bi" "0.23.0" + "@ckb-lumos/common-scripts@0.21.1", "@ckb-lumos/common-scripts@^0.21.0-next.0": version "0.21.1" resolved "https://registry.yarnpkg.com/@ckb-lumos/common-scripts/-/common-scripts-0.21.1.tgz#64776b5ce55d8c66898a3ab592c7f77fe13d865e" @@ -2172,6 +2213,22 @@ "@ckb-lumos/toolkit" "0.21.1" immutable "^4.3.0" +"@ckb-lumos/common-scripts@0.23.0": + version "0.23.0" + resolved "https://registry.yarnpkg.com/@ckb-lumos/common-scripts/-/common-scripts-0.23.0.tgz#ccb04214ca7186829036ddcbb0dc1b5326127a90" + integrity sha512-Dwic0Al94afdGNu+TGAMmZiU5OVF/zvXbzhCvNmkFS25t8BxPdFjGEc0MlWBI4ZSEoGRrC0O+BOxjzfl5VxSYg== + dependencies: + "@ckb-lumos/base" "0.23.0" + "@ckb-lumos/bi" "0.23.0" + "@ckb-lumos/codec" "0.23.0" + "@ckb-lumos/config-manager" "0.23.0" + "@ckb-lumos/helpers" "0.23.0" + "@ckb-lumos/rpc" "0.23.0" + "@ckb-lumos/toolkit" "0.23.0" + bech32 "^2.0.0" + bs58 "^5.0.0" + immutable "^4.3.0" + "@ckb-lumos/config-manager@0.21.1", "@ckb-lumos/config-manager@^0.21.0-next.0": version "0.21.1" resolved "https://registry.yarnpkg.com/@ckb-lumos/config-manager/-/config-manager-0.21.1.tgz#857050ebf2ca38096d8fae26b6a8927164798ca3" @@ -2183,6 +2240,18 @@ "@types/deep-freeze-strict" "^1.1.0" deep-freeze-strict "^1.1.1" +"@ckb-lumos/config-manager@0.23.0": + version "0.23.0" + resolved "https://registry.yarnpkg.com/@ckb-lumos/config-manager/-/config-manager-0.23.0.tgz#93a607f7857c9a24fae86735e29f1ca40ee34fb5" + integrity sha512-MvNyzGIJTmIpEf5WJB3TkE4icZyZ2HZhFIfJB2SXDRAC84E02jxENPelCnqRbM1rlFHnxjh/5a/oCi5LcXefag== + dependencies: + "@ckb-lumos/base" "0.23.0" + "@ckb-lumos/bi" "0.23.0" + "@ckb-lumos/codec" "0.23.0" + "@ckb-lumos/rpc" "0.23.0" + "@types/deep-freeze-strict" "^1.1.0" + deep-freeze-strict "^1.1.1" + "@ckb-lumos/hd@0.21.1": version "0.21.1" resolved "https://registry.yarnpkg.com/@ckb-lumos/hd/-/hd-0.21.1.tgz#406026ac2b570f72b4407149cb941d09bb8a0ab6" @@ -2196,6 +2265,19 @@ sha3 "^2.1.3" uuid "^8.3.0" +"@ckb-lumos/hd@0.23.0": + version "0.23.0" + resolved "https://registry.yarnpkg.com/@ckb-lumos/hd/-/hd-0.23.0.tgz#97ddf6e291bdb44b7b8d61bcf437c3a1b17d0182" + integrity sha512-z7EsR/GeX54hq4ukqwW3nrqLCsYrTWIFAjZLR1Ao8xycqQp0IBjCWZLLjRrZY6krbUQpVOoiKo3NBLpPW36LXg== + dependencies: + "@ckb-lumos/base" "0.23.0" + "@ckb-lumos/bi" "0.23.0" + bn.js "^5.1.3" + elliptic "^6.5.4" + scrypt-js "^3.0.1" + sha3 "^2.1.3" + uuid "^8.3.0" + "@ckb-lumos/helpers@0.21.1": version "0.21.1" resolved "https://registry.yarnpkg.com/@ckb-lumos/helpers/-/helpers-0.21.1.tgz#fffd455071f51556b2cd2539a255b3759893da08" @@ -2209,6 +2291,19 @@ bech32 "^2.0.0" immutable "^4.3.0" +"@ckb-lumos/helpers@0.23.0": + version "0.23.0" + resolved "https://registry.yarnpkg.com/@ckb-lumos/helpers/-/helpers-0.23.0.tgz#095a6d4752d756287cfbee0360c614d27a5f71f3" + integrity sha512-yfD28vSn1BBk8BA+/ivL7pF3rMsx4OPQ+UUJjsQiR1zGdkNR3zhJOecgICeddJGYDTBBDVgwHcuyoekLxQzmGg== + dependencies: + "@ckb-lumos/base" "0.23.0" + "@ckb-lumos/bi" "0.23.0" + "@ckb-lumos/codec" "0.23.0" + "@ckb-lumos/config-manager" "0.23.0" + "@ckb-lumos/toolkit" "0.23.0" + bech32 "^2.0.0" + immutable "^4.3.0" + "@ckb-lumos/lumos@^0.21.0-next.0": version "0.21.1" resolved "https://registry.yarnpkg.com/@ckb-lumos/lumos/-/lumos-0.21.1.tgz#ff5a9a10859b305530026668bd14cdc12e5e7532" @@ -2234,6 +2329,16 @@ abort-controller "^3.0.0" cross-fetch "^3.1.5" +"@ckb-lumos/rpc@0.23.0": + version "0.23.0" + resolved "https://registry.yarnpkg.com/@ckb-lumos/rpc/-/rpc-0.23.0.tgz#e17bd281e62e17957db80459e6be25d0c64bbb59" + integrity sha512-NEY1Wb2cNMYdHwcZYtd8XZ3CP6WGPd25hcsudoDAFlAt9vjHsPlNiwSS7tcZCZfg1XiJy3taViVgG8pFemgpbA== + dependencies: + "@ckb-lumos/base" "0.23.0" + "@ckb-lumos/bi" "0.23.0" + abort-controller "^3.0.0" + cross-fetch "^3.1.5" + "@ckb-lumos/toolkit@0.21.1": version "0.21.1" resolved "https://registry.yarnpkg.com/@ckb-lumos/toolkit/-/toolkit-0.21.1.tgz#bcf4bf05615375087e3dd02fb087b797a8c67f37" @@ -2241,6 +2346,13 @@ dependencies: "@ckb-lumos/bi" "0.21.1" +"@ckb-lumos/toolkit@0.23.0": + version "0.23.0" + resolved "https://registry.yarnpkg.com/@ckb-lumos/toolkit/-/toolkit-0.23.0.tgz#4f5d25ea4292bd77dfc191506984ffc2c38078f7" + integrity sha512-7LTsUFfoNCBWJLgh+V/QFnemjGw+y4mmLeQvubwYuJqIPIhIpwKUuKRzkvVG8snA8xVQSfjSSQOs5m3mKp66Kg== + dependencies: + "@ckb-lumos/bi" "0.23.0" + "@cnakazawa/watch@^1.0.3": version "1.0.4" resolved "https://registry.yarnpkg.com/@cnakazawa/watch/-/watch-1.0.4.tgz#f864ae85004d0fcab6f50be9141c4da368d1656a" @@ -7386,6 +7498,11 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== +base-x@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/base-x/-/base-x-4.0.0.tgz#d0e3b7753450c73f8ad2389b5c018a4af7b2224a" + integrity sha512-FuwxlW4H5kh37X/oW59pwTzzTKRzfrrQwhmyspRM7swOEZcHtDZSCt45U6oKgtuFE+WYPblePMVIPR4RZrh/hw== + base64-js@^1.3.1, base64-js@^1.5.1: version "1.5.1" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" @@ -7661,6 +7778,13 @@ bs-logger@0.x: dependencies: fast-json-stable-stringify "2.x" +bs58@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/bs58/-/bs58-5.0.0.tgz#865575b4d13c09ea2a84622df6c8cbeb54ffc279" + integrity sha512-r+ihvQJvahgYT50JD05dyJNKlmmSlMoOGwn1lCcEzanPglg7TxYjioQUYehQ9mAR/+hOSd2jRc/Z2y5UxBymvQ== + dependencies: + base-x "^4.0.0" + bser@2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" @@ -19512,11 +19636,6 @@ tslib@^2.0.0, tslib@^2.0.1, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.3.0, tslib@^2.4 resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.0.tgz#b295854684dbda164e181d259a22cd779dcd7bc3" integrity sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA== -tslib@^2.5.0: - version "2.6.3" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.3.tgz#0438f810ad7a9edcde7a241c3d80db693c8cbfe0" - integrity sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ== - tsutils@^3.21.0: version "3.21.0" resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623"