diff --git a/CHANGELOG.md b/CHANGELOG.md index 02eac30843..69d0dca1c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,18 @@ +# 0.100.2 (2021-10-15) + +[CKB v0.100.0](https://github.com/nervosnetwork/ckb/releases/tag/v0.100.0) was released on Sep. 22nd, 2021. This version of CKB node is now bundled and preconfigured in Neuron. + +**This version is compatible with v0.100.0 and above.** + +### New feature +* Add feature of destroy CKB Asset Account. + +### Bug fixes +* Kill ckb/indexer before Neuron quit. +* Fix a cosmetic bug of address truncation in addresses book under higher resolution. +* Remove useless token id prompt when asset account type is `CKB Account`. + + # 0.100.1 (2021-09-29) [CKB v0.100.0](https://github.com/nervosnetwork/ckb/releases/tag/v0.100.0) was released on Sep. 22nd, 2021. This version of CKB node is now bundled and preconfigured in Neuron. diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 9fabf8c3da..f805c8415d 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -169,7 +169,7 @@ stages: name: winSiginingCertificate displayName: "Download Windows Signing Certificate" inputs: - secureFile: Neuron_win.p12 + secureFile: neuron_win_2021.pfx - pwsh: | bash ./scripts/download-ckb.sh win yarn build diff --git a/lerna.json b/lerna.json index 6ce79cdb26..b6b25452a7 100644 --- a/lerna.json +++ b/lerna.json @@ -2,7 +2,7 @@ "packages": [ "packages/*" ], - "version": "0.100.1", + "version": "0.100.2", "npmClient": "yarn", "useWorkspaces": true } diff --git a/package.json b/package.json index 9f1330f759..149af70f76 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "neuron", "productName": "Neuron", "description": "CKB Neuron Wallet", - "version": "0.100.1", + "version": "0.100.2", "private": true, "author": { "name": "Nervos Core Dev", diff --git a/packages/neuron-ui/package.json b/packages/neuron-ui/package.json index 21a0a00f2e..22cd5fd51b 100644 --- a/packages/neuron-ui/package.json +++ b/packages/neuron-ui/package.json @@ -1,6 +1,6 @@ { "name": "neuron-ui", - "version": "0.100.1", + "version": "0.100.2", "private": true, "author": { "name": "Nervos Core Dev", diff --git a/packages/neuron-ui/src/components/Addresses/addresses.module.scss b/packages/neuron-ui/src/components/Addresses/addresses.module.scss index a714f24f93..8f76cb8c06 100644 --- a/packages/neuron-ui/src/components/Addresses/addresses.module.scss +++ b/packages/neuron-ui/src/components/Addresses/addresses.module.scss @@ -116,14 +116,12 @@ $change-color: #6666cc; display: none; } - @media screen and (max-width: 1365px) { + @media screen and (max-width: 1680px) { + width: 20vw; + .ellipsis { display: inline; } - } - - @media screen and (max-width: 1680px) { - width: 20vw; &::after { position: absolute; diff --git a/packages/neuron-ui/src/components/HardwareSign/index.tsx b/packages/neuron-ui/src/components/HardwareSign/index.tsx index 8847ce9552..acf75cebf3 100644 --- a/packages/neuron-ui/src/components/HardwareSign/index.tsx +++ b/packages/neuron-ui/src/components/HardwareSign/index.tsx @@ -231,6 +231,7 @@ const HardwareSign = ({ switch (type) { case 'send': case 'send-nft': + case 'destroy-ckb-account': case 'claim-cheque': { if (isSending) { break diff --git a/packages/neuron-ui/src/components/PasswordRequest/index.tsx b/packages/neuron-ui/src/components/PasswordRequest/index.tsx index f4e91d0009..f66b867b5e 100644 --- a/packages/neuron-ui/src/components/PasswordRequest/index.tsx +++ b/packages/neuron-ui/src/components/PasswordRequest/index.tsx @@ -185,6 +185,7 @@ const PasswordRequest = () => { await sendSUDTTransaction(params)(dispatch).then(handleSendTxRes) break } + case 'destroy-ckb-account': case 'send-nft': case 'send-cheque': { if (isSending) { diff --git a/packages/neuron-ui/src/components/SUDTCreateDialog/index.tsx b/packages/neuron-ui/src/components/SUDTCreateDialog/index.tsx index 168039c74e..e1d4158982 100644 --- a/packages/neuron-ui/src/components/SUDTCreateDialog/index.tsx +++ b/packages/neuron-ui/src/components/SUDTCreateDialog/index.tsx @@ -289,7 +289,7 @@ const SUDTCreateDialog = ({ error={tokenErrors[field.key]} className={accountType === AccountType.CKB ? styles.ckbField : undefined} hint={ - !tokenErrors[field.key] && field.key === 'tokenId' + !tokenErrors[field.key] && field.key === 'tokenId' && accountType === AccountType.SUDT ? t(`s-udt.create-dialog.placeholder.${field.label}`) : undefined } diff --git a/packages/neuron-ui/src/components/SUDTSend/index.tsx b/packages/neuron-ui/src/components/SUDTSend/index.tsx index f03c92f2e9..fb3d216c1a 100644 --- a/packages/neuron-ui/src/components/SUDTSend/index.tsx +++ b/packages/neuron-ui/src/components/SUDTSend/index.tsx @@ -16,6 +16,7 @@ import { generateSendAllSUDTTransaction, getAnyoneCanPayScript, generateChequeTransaction, + destoryCKBAssetAccount, } from 'services/remote' import { ckbCore } from 'services/chain' import { useState as useGlobalState, useDispatch, AppActions } from 'states' @@ -330,6 +331,37 @@ const SUDTSend = () => { [isSubmittable, globalDispatch, walletId, accountType, isSecp256k1ShortAddress] ) + const [isDestroying, setIsDestroying] = useState(false) + const onDestroy = useCallback(() => { + setIsDestroying(true) + destoryCKBAssetAccount({ walletID: walletId, id: accountId }) + .then(res => { + if (isSuccessResponse(res)) { + const tx = res.result + globalDispatch({ type: AppActions.UpdateExperimentalParams, payload: { tx } }) + globalDispatch({ + type: AppActions.RequestPassword, + payload: { + walletID: walletId, + actionType: 'destroy-ckb-account', + }, + }) + } else { + globalDispatch({ + type: AppActions.AddNotification, + payload: { + type: 'alert', + timestamp: +new Date(), + content: typeof res.message === 'string' ? res.message : res.message.content!, + }, + }) + } + }) + .finally(() => { + setIsDestroying(false) + }) + }, [globalDispatch, walletId, accountId]) + if (!isLoaded) { return (
@@ -412,7 +444,15 @@ const SUDTSend = () => {
-
+
+ {accountType === AccountType.CKB ? ( +
+ + {t('s-udt.send.destroy-desc')} +
+ ) : null}
diff --git a/packages/neuron-ui/src/components/SUDTSend/sUDTSend.module.scss b/packages/neuron-ui/src/components/SUDTSend/sUDTSend.module.scss index 1f8e650655..072c77ffac 100644 --- a/packages/neuron-ui/src/components/SUDTSend/sUDTSend.module.scss +++ b/packages/neuron-ui/src/components/SUDTSend/sUDTSend.module.scss @@ -136,6 +136,54 @@ justify-content: flex-end; } +.ckb-footer { + display: flex; + justify-content: space-between; +} + +.tooltip { + position: relative; + display: inline-block; + &:hover { + .tooltiptext { + visibility: visible; + opacity: 1; + } + } +} + +.tooltiptext { + visibility: hidden; + position: absolute; + background-color: #555; + color: #fff; + text-align: center; + border-radius: 6px; + z-index: 1; + opacity: 0; + transition: opacity .6s; + word-break: keep-all; + padding: 5px 10px; + left: 110%; + font-size: 12px; + margin-top: 2px; + white-space: nowrap; + &:hover { + visibility: visible; + opacity: 1; + } + &:after { + content: ""; + position: absolute; + top: 50%; + right: 100%; + margin-top: -5px; + border-width: 5px; + border-style: solid; + border-color: transparent #555 transparent transparent; + } +} + .modal { @include overlay; position: absolute; diff --git a/packages/neuron-ui/src/locales/en.json b/packages/neuron-ui/src/locales/en.json index 9e8c4c5c28..a426665463 100644 --- a/packages/neuron-ui/src/locales/en.json +++ b/packages/neuron-ui/src/locales/en.json @@ -405,6 +405,9 @@ }, "create-account-to-claim-cheque": { "title": "Claim Cheque Asset with New Account" + }, + "destroy-ckb-account": { + "title": "Destroy CKB Asset Account" } }, "qrcode": { @@ -718,7 +721,9 @@ "description": "Description", "submit": "Submit", "click-to-edit": "Click to edit", - "cheque-address-hint": "162 CKBytes capacity is going to be temporarily locked to initialize the token transfer, and it will be automatically unlocked after the token being claimed by the receiver." + "cheque-address-hint": "162 CKBytes capacity is going to be temporarily locked to initialize the token transfer, and it will be automatically unlocked after the token being claimed by the receiver.", + "destroy": "Destroy", + "destroy-desc": "Will returns all CKB of this CKB Asset account to your change address." }, "receive": { "notation": "Accept {{symbol}} only" diff --git a/packages/neuron-ui/src/locales/zh-tw.json b/packages/neuron-ui/src/locales/zh-tw.json index 62e1fe0f9d..c1a968ba00 100644 --- a/packages/neuron-ui/src/locales/zh-tw.json +++ b/packages/neuron-ui/src/locales/zh-tw.json @@ -398,6 +398,9 @@ }, "create-account-to-claim-cheque": { "title": "創建賬戶並領取 Cheque 資產" + }, + "destroy-ckb-account": { + "title": "銷毀 CKB 資產賬戶" } }, "qrcode": { @@ -711,7 +714,9 @@ "description": "備註", "submit": "提交", "click-to-edit": "點擊編輯", - "cheque-address-hint": "向該地址轉賬需要臨時鎖定 162 CKB,對方領取後自動解鎖。" + "cheque-address-hint": "向該地址轉賬需要臨時鎖定 162 CKB,對方領取後自動解鎖。", + "destroy": "銷毀", + "destroy-desc": "將會返還所有該 CKB 資產賬戶的 CKB 到你的找零地址中" }, "receive": { "notation": "只接收 {{symbol}} 轉賬" diff --git a/packages/neuron-ui/src/locales/zh.json b/packages/neuron-ui/src/locales/zh.json index 6172c1be50..f013fbce22 100644 --- a/packages/neuron-ui/src/locales/zh.json +++ b/packages/neuron-ui/src/locales/zh.json @@ -398,6 +398,9 @@ }, "create-account-to-claim-cheque": { "title": "创建账户并领取 Cheque 资产" + }, + "destroy-ckb-account": { + "title": "销毁 CKB 资产账户" } }, "qrcode": { @@ -711,7 +714,9 @@ "description": "备注", "submit": "提交", "click-to-edit": "点击编辑", - "cheque-address-hint": "向该地址转账需要临时锁定 162 CKB,对方领取后自动解锁。" + "cheque-address-hint": "向该地址转账需要临时锁定 162 CKB,对方领取后自动解锁。", + "destroy": "销毁", + "destroy-desc": "将会返还所有该 CKB 资产账户的 CKB 到你的找零地址中" }, "receive": { "notation": "只接收 {{symbol}} 转账" diff --git a/packages/neuron-ui/src/services/remote/remoteApiWrapper.ts b/packages/neuron-ui/src/services/remote/remoteApiWrapper.ts index 825a124d01..9d1ffd7dc3 100644 --- a/packages/neuron-ui/src/services/remote/remoteApiWrapper.ts +++ b/packages/neuron-ui/src/services/remote/remoteApiWrapper.ts @@ -103,6 +103,7 @@ type Action = | 'migrate-acp' | 'check-migrate-acp' | 'get-sudt-token-info' + | 'generate-destroy-ckb-account-tx' // Cheque | 'generate-create-cheque-tx' | 'generate-withdraw-cheque-tx' diff --git a/packages/neuron-ui/src/services/remote/specialAssets.ts b/packages/neuron-ui/src/services/remote/specialAssets.ts index 3eaa3eeeb5..6919869ef4 100644 --- a/packages/neuron-ui/src/services/remote/specialAssets.ts +++ b/packages/neuron-ui/src/services/remote/specialAssets.ts @@ -2,3 +2,4 @@ import { remoteApi } from './remoteApiWrapper' export const getSpecialAssets = remoteApi('get-customized-asset-cells') export const unlockSpecialAsset = remoteApi('generate-withdraw-customized-cell-tx') +export const destoryCKBAssetAccount = remoteApi('generate-destroy-ckb-account-tx') diff --git a/packages/neuron-ui/src/types/App/index.d.ts b/packages/neuron-ui/src/types/App/index.d.ts index 52902e00ae..672c39bd57 100644 --- a/packages/neuron-ui/src/types/App/index.d.ts +++ b/packages/neuron-ui/src/types/App/index.d.ts @@ -86,6 +86,7 @@ declare namespace State { | 'withdraw-cheque' | 'claim-cheque' | 'create-account-to-claim-cheque' + | 'destroy-ckb-account' | 'migrate-acp' | 'send-nft' | null diff --git a/packages/neuron-wallet/package.json b/packages/neuron-wallet/package.json index 9cbf65b31c..9a5aef1632 100644 --- a/packages/neuron-wallet/package.json +++ b/packages/neuron-wallet/package.json @@ -3,7 +3,7 @@ "productName": "Neuron", "description": "CKB Neuron Wallet", "homepage": "https://www.nervos.org/", - "version": "0.100.1", + "version": "0.100.2", "private": true, "author": { "name": "Nervos Core Dev", @@ -86,7 +86,7 @@ "electron-notarize": "0.2.1", "jest-when": "2.7.2", "lint-staged": "9.2.5", - "neuron-ui": "0.100.1", + "neuron-ui": "0.100.2", "rimraf": "3.0.0", "ttypescript": "1.5.10" } diff --git a/packages/neuron-wallet/src/controllers/api.ts b/packages/neuron-wallet/src/controllers/api.ts index cbd76de8c5..ef5709beaf 100644 --- a/packages/neuron-wallet/src/controllers/api.ts +++ b/packages/neuron-wallet/src/controllers/api.ts @@ -475,6 +475,10 @@ export default class ApiController { return this.sudtController.getSUDTTokenInfo(params) }) + handle('generate-destroy-ckb-account-tx', async (_, params: { walletID: string, id: number })=>{ + return this.assetAccountController.destoryCKBAssetAccount(params) + }) + // Hardware wallet handle('connect-device', async (_, deviceInfo: DeviceInfo) => { await this.hardwareController.connectDevice(deviceInfo) diff --git a/packages/neuron-wallet/src/controllers/app/index.ts b/packages/neuron-wallet/src/controllers/app/index.ts index c989139ea5..9e33b8f48c 100644 --- a/packages/neuron-wallet/src/controllers/app/index.ts +++ b/packages/neuron-wallet/src/controllers/app/index.ts @@ -41,8 +41,10 @@ export default class AppController { public end = async () => { if (!env.isTestMode) { - await new NodeController().stopNode() - await IndexerService.getInstance().stop() + await Promise.all([ + new NodeController().stopNode(), + IndexerService.getInstance().stop(), + ]) } } diff --git a/packages/neuron-wallet/src/controllers/asset-account.ts b/packages/neuron-wallet/src/controllers/asset-account.ts index 41a39e7588..b43b293e43 100644 --- a/packages/neuron-wallet/src/controllers/asset-account.ts +++ b/packages/neuron-wallet/src/controllers/asset-account.ts @@ -97,6 +97,23 @@ export default class AssetAccountController { } } + public async destoryCKBAssetAccount( + params: { walletID: string, id: number } + ): Promise> { + const account = await AssetAccountService.getAccount(params) + + if (!account) { + throw new ServiceHasNoResponse('AssetAccount') + } + + const { tx } = await AssetAccountService.destoryCKBAssetAccount(params.walletID, account) + + return { + status: ResponseCode.Success, + result: tx, + } + } + public async getAccount(params: { walletID: string, id: number }): Promise> { const account = await AssetAccountService.getAccount(params) diff --git a/packages/neuron-wallet/src/services/asset-account-service.ts b/packages/neuron-wallet/src/services/asset-account-service.ts index 2594f698df..612f251368 100644 --- a/packages/neuron-wallet/src/services/asset-account-service.ts +++ b/packages/neuron-wallet/src/services/asset-account-service.ts @@ -14,6 +14,7 @@ import { TransactionGenerator } from "./tx" import WalletService from "./wallets" import OutPoint from "models/chain/out-point" import SystemScriptInfo from "models/system-script-info" +import Input from "models/chain/input" export default class AssetAccountService { @@ -74,6 +75,30 @@ export default class AssetAccountService { return totalBalance } + public static async destoryCKBAssetAccount(walletID: string, assetAccount: AssetAccount) { + const cells = await AssetAccountService.getACPCells(assetAccount?.blake160, 'CKBytes') + const inputs = cells.map(cell => { + return Input.fromObject({ + previousOutput: cell.outPoint(), + capacity: cell.capacity, + lock: cell.lockScript(), + lockHash: cell.lockHash, + since: '0', + }) + }) + // 1. find next unused address + const wallet = WalletService.getInstance().get(walletID) + + const address = await wallet.getNextChangeAddress() + + const tx = await TransactionGenerator.generateDestoryCKBAssetAccountTx(walletID, inputs, address!.blake160) + + return { + assetAccount, + tx, + } + } + public static async getAll(walletId: string): Promise { const determineTokenID = (account: AssetAccountEntity) => account.tokenID.startsWith('0x') ? account.tokenID : 'CKBytes' @@ -302,15 +327,39 @@ export default class AssetAccountService { [assetAccount.tokenID, assetAccount.blake160] ) + const wallet = WalletService.getInstance().get(walletID) + const entity = AssetAccountEntity.fromModel(assetAccount) if (exists[0].exist === 1) { - throw new Error(`Asset account already exists!`) + // 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. + if (wallet.isHardware() && assetAccount.tokenID === 'CKBytes') { + const address = await wallet.getNextAddress() + if (address) { + const acpCells = await AssetAccountService.getACPCells(address.blake160, 'CKBytes') + if (acpCells.length) { + throw new Error(`Asset account already exists!`) + } else { + await getConnection() + .createQueryBuilder() + .delete() + .from(AssetAccountEntity) + .where("tokenID = :tokenID AND blake160 = :blake160", { + tokenID: 'CKBytes', + blake160: assetAccount.blake160, + }) + .execute() + } + } + } else { + throw new Error(`Asset account already exists!`) + } } // 2. send tx const txHash = await new TransactionSender().sendTx(walletID, tx, password, 0, skipSign) // 3. save asset account - const entity = AssetAccountEntity.fromModel(assetAccount) await connection.manager.save([entity.sudtTokenInfo, entity]) return txHash diff --git a/packages/neuron-wallet/src/services/live-cell-service.ts b/packages/neuron-wallet/src/services/live-cell-service.ts index c00137e768..1b9b02a87c 100644 --- a/packages/neuron-wallet/src/services/live-cell-service.ts +++ b/packages/neuron-wallet/src/services/live-cell-service.ts @@ -40,7 +40,10 @@ export default class LiveCellService { return item } } else { - return item + // if type is falsy, should return cell that type is empty + if (!item.type()) { + return item + } } } diff --git a/packages/neuron-wallet/src/services/tx/transaction-generator.ts b/packages/neuron-wallet/src/services/tx/transaction-generator.ts index 61c09917bc..8c0a12907f 100644 --- a/packages/neuron-wallet/src/services/tx/transaction-generator.ts +++ b/packages/neuron-wallet/src/services/tx/transaction-generator.ts @@ -717,6 +717,51 @@ export class TransactionGenerator { return tx } + public static async generateDestoryCKBAssetAccountTx(walletId: string, asssetAccountInputs: Input[], changeBlake160: string) { + const secpCellDep = await SystemScriptInfo.getInstance().getSecpCellDep() + const assetAccountInfo = new AssetAccountInfo() + const anyoneCanPayDep = assetAccountInfo.anyoneCanPayCellDep + + const cellDeps = [secpCellDep, anyoneCanPayDep] + + const { + inputs: changeInputs, + } = await CellsService.gatherInputs('0', walletId) + + if (changeInputs.length === 0) { + throw new CapacityNotEnough() + } + + const [changeInput] = changeInputs + const allInputs = [...asssetAccountInputs, changeInput] + const allCapacities = allInputs.reduce((a, b) => { + return a + BigInt(b.capacity as string) + }, BigInt(0)) + + + const output = Output.fromObject({ + capacity: '0', + lock: SystemScriptInfo.generateSecpScript(changeBlake160) + }) + + const tx = Transaction.fromObject({ + version: '0', + headerDeps: [], + cellDeps, + inputs: allInputs, + outputs: [output], + outputsData: [output.data], + witnesses: [] + }) + + const txSize = TransactionSize.tx(tx) + tx.fee = TransactionFee.fee(txSize, BigInt(1e4)).toString() + const outputCapacity = allCapacities - BigInt(tx.fee) + tx.outputs[0].capacity = outputCapacity.toString() + + return tx + } + // anyone-can-pay lock, CKB public static async generateAnyoneCanPayToCKBTx( walletId: string, diff --git a/yarn.lock b/yarn.lock index ac0f2ff80b..b5e936285e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -19869,9 +19869,9 @@ url-parse-lax@^3.0.0: prepend-http "^2.0.0" url-parse@^1.4.3: - version "1.5.1" - resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.1.tgz#d5fa9890af8a5e1f274a2c98376510f6425f6e3b" - integrity sha512-HOfCOUJt7iSYzEx/UqgtwKRMC6EU91NFhsCHMv9oM03VJcVo2Qrp8T8kI9D7amFf1cu+/3CEhgb3rF9zL7k85Q== + version "1.5.3" + resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.3.tgz#71c1303d38fb6639ade183c2992c8cc0686df862" + integrity sha512-IIORyIQD9rvj0A4CLWsHkBBJuNqWpFQe224b6j9t/ABmquIS0qDU2pY6kl6AuOrL5OkCXHMCFNe1jBcuAggjvQ== dependencies: querystringify "^2.1.1" requires-port "^1.0.0"