- {newVersion} ({uniformTimeFormatter(new Date(releaseDate)).split(' ')[0]})
+ {newVersion} {publishedAt ? `(${uniformTimeFormatter(new Date(publishedAt)).split(' ')[0]})` : null}
@@ -217,7 +228,6 @@ const GeneralSetting = ({ updater }: GeneralSettingProps) => {
progress={updater.downloadProgress}
progressInfo={updater.progressInfo}
newVersion={updater.version}
- releaseDate={updater.releaseDate}
releaseNotes={updater.releaseNotes}
/>
diff --git a/packages/neuron-ui/src/components/History/history.module.scss b/packages/neuron-ui/src/components/History/history.module.scss
index 6f88e8e24d..c389de4ac8 100644
--- a/packages/neuron-ui/src/components/History/history.module.scss
+++ b/packages/neuron-ui/src/components/History/history.module.scss
@@ -37,6 +37,13 @@ body {
font-weight: 500;
padding-right: 10px;
}
+ .searchBoxSuffix {
+ font-family: inherit;
+ display: flex;
+ align-items: center;
+ padding-left: 10px;
+ cursor: pointer;
+ }
.exportButton {
color: var(--main-text-color);
border-color: var(--history-button-border);
diff --git a/packages/neuron-ui/src/components/History/index.tsx b/packages/neuron-ui/src/components/History/index.tsx
index 1329d5281b..38c96c5429 100644
--- a/packages/neuron-ui/src/components/History/index.tsx
+++ b/packages/neuron-ui/src/components/History/index.tsx
@@ -6,7 +6,7 @@ import SUDTAvatar from 'widgets/SUDTAvatar'
import Button from 'widgets/Button'
import Table, { TableProps } from 'widgets/Table'
import TextField from 'widgets/TextField'
-import { Download, Search, ArrowNext } from 'widgets/Icons/icon'
+import { Download, Search, ArrowNext, Clean } from 'widgets/Icons/icon'
import PageContainer from 'components/PageContainer'
import TransactionStatusWrap from 'components/TransactionStatusWrap'
@@ -47,6 +47,7 @@ const History = () => {
const { keywords, onKeywordsChange } = useSearch(search, id, dispatch)
const onSearch = useCallback(() => navigate(`${RoutePath.History}?keywords=${keywords}`), [navigate, keywords])
+ const onClean = useCallback(() => onKeywordsChange(undefined, ''), [onKeywordsChange])
const onExport = useCallback(() => {
setIsExporting(true)
const timer = setTimeout(() => {
@@ -183,6 +184,13 @@ const History = () => {
}
+ suffix={
+ keywords ? (
+
+
+
+ ) : null
+ }
placeholder={t('history.search.placeholder')}
className={styles.tableHeaderInput}
stack={false}
diff --git a/packages/neuron-ui/src/components/NetworkSetting/index.tsx b/packages/neuron-ui/src/components/NetworkSetting/index.tsx
index e82447e235..7af39881f5 100644
--- a/packages/neuron-ui/src/components/NetworkSetting/index.tsx
+++ b/packages/neuron-ui/src/components/NetworkSetting/index.tsx
@@ -101,7 +101,11 @@ const NetworkSetting = ({ chain = chainState, settings: { networks = [] } }: Sta
value: network.id,
label: (
-
{`${network.name} (${network.remote})`}
+
{`${network.name} (${
+ network.remote.length > 40
+ ? `${network.remote.slice(0, 20)}...${network.remote.slice(-20)}`
+ : network.remote
+ })`}
{t(getNetworkLabelI18nkey(network.chain))}
),
diff --git a/packages/neuron-ui/src/components/Send/index.tsx b/packages/neuron-ui/src/components/Send/index.tsx
index 97240daadb..05b2cb61ca 100644
--- a/packages/neuron-ui/src/components/Send/index.tsx
+++ b/packages/neuron-ui/src/components/Send/index.tsx
@@ -169,7 +169,7 @@ const Send = () => {
!send.generatedTx ||
outputs.some(v => !v.address)
- const outputErrors = useOutputErrors(outputs, isMainnet)
+ const outputErrors = useOutputErrors(outputs, isMainnet, isSendMax)
const isMaxBtnDisabled = (() => {
try {
diff --git a/packages/neuron-ui/src/components/SendFromMultisigDialog/hooks.ts b/packages/neuron-ui/src/components/SendFromMultisigDialog/hooks.ts
index e183db9ff0..e2463e3c31 100644
--- a/packages/neuron-ui/src/components/SendFromMultisigDialog/hooks.ts
+++ b/packages/neuron-ui/src/components/SendFromMultisigDialog/hooks.ts
@@ -133,7 +133,8 @@ export const useSendInfo = ({
},
[setSendInfoList]
)
- const outputErrors = useOutputErrors(sendInfoList, isMainnet)
+ const [isSendMax, setIsSendMax] = useState(false)
+ const outputErrors = useOutputErrors(sendInfoList, isMainnet, isSendMax)
const totalAmount = useMemo(
() => outputsToTotalAmount(sendInfoList.filter((v, idx) => !!v.amount && !outputErrors[idx].amountError)),
[sendInfoList, outputErrors]
@@ -172,7 +173,6 @@ export const useSendInfo = ({
}
}, 300)
}, [sendInfoList, setErrorMessage, multisigConfig, dispatch, t, isMainnet, outputErrors])
- const [isSendMax, setIsSendMax] = useState(false)
const onSendMaxClick = useCallback(() => {
if (!isSendMax) {
setIsSendMax(true)
diff --git a/packages/neuron-ui/src/components/TransactionStatusWrap/transactionStatusWrap.module.scss b/packages/neuron-ui/src/components/TransactionStatusWrap/transactionStatusWrap.module.scss
index 7e87b9f483..c19f1df9b9 100644
--- a/packages/neuron-ui/src/components/TransactionStatusWrap/transactionStatusWrap.module.scss
+++ b/packages/neuron-ui/src/components/TransactionStatusWrap/transactionStatusWrap.module.scss
@@ -20,5 +20,6 @@
color: #999;
font-size: 12px;
margin-left: 4px;
+ white-space: nowrap;
}
}
diff --git a/packages/neuron-ui/src/containers/Main/hooks.ts b/packages/neuron-ui/src/containers/Main/hooks.ts
index b332e2ca9d..3111c1ca43 100644
--- a/packages/neuron-ui/src/containers/Main/hooks.ts
+++ b/packages/neuron-ui/src/containers/Main/hooks.ts
@@ -198,6 +198,7 @@ export const useSubscription = ({
})
CONNECTING_DEADLINE = Date.now() + CONNECTING_BUFFER
currentNetworkIDCache.save(currentNetworkID)
+ updateAddressListAndBalance(walletID)(dispatch)
})
const connectionStatusSubscription = ConnectionStatusSubject.subscribe(status => {
if (isCurrentUrl(status.url)) {
diff --git a/packages/neuron-ui/src/exceptions/amount.ts b/packages/neuron-ui/src/exceptions/amount.ts
index f24fd01d78..f148c17714 100644
--- a/packages/neuron-ui/src/exceptions/amount.ts
+++ b/packages/neuron-ui/src/exceptions/amount.ts
@@ -56,3 +56,15 @@ export class AmountNegativeException extends RangeError {
this.i18n.fieldValue = value
}
}
+
+export class CapacityTooSmallException extends Error {
+ public code = ErrorCode.CapacityTooSmall
+ public i18n: {
+ bytes: string
+ }
+
+ constructor(bytes: string) {
+ super(`${I18N_PATH}${ErrorCode.CapacityTooSmall}`)
+ this.i18n = { bytes }
+ }
+}
diff --git a/packages/neuron-ui/src/locales/en.json b/packages/neuron-ui/src/locales/en.json
index 0720469ccc..08c1ba3add 100644
--- a/packages/neuron-ui/src/locales/en.json
+++ b/packages/neuron-ui/src/locales/en.json
@@ -678,7 +678,7 @@
"cancel": "Cancel",
"proceed": "Proceed",
"compensation": "Compensation",
- "notice-wait-time": "Notice: You are initiating withdrawn and the final withdrawn will need to wait for {{epochs}} epochs {{blocks}} blocks (about {{days} days).",
+ "notice-wait-time": "Notice: You are initiating withdrawn and the final withdrawn will need to wait for {{epochs}} epochs {{blocks}} blocks (about {{days}} days).",
"deposit-dialog-title": "Deposit",
"deposit-terms": "Nervos DAO needs 102 CKBytes for deposit cell storage, which is not compensation-bearing.
Please visit the <0>Nervos DAO RFC0> for more information about Nervos DAO",
"minimal-fee-required": "The minimum deposit capacity is {{minimal}} CKBytes",
@@ -728,7 +728,7 @@
"nervos-dao-detail": {
"tx-detail": "Transaction Detail",
"deposited": "Deposited",
- "withdrawn": "Withdrawn",
+ "withdrawn": "Withdraw",
"unlocked": "Unlocked",
"basic-information": "Basic Information",
"transaction-hash": "Transaction Hash",
diff --git a/packages/neuron-ui/src/tests/validators/capacity/fixtures.ts b/packages/neuron-ui/src/tests/validators/capacity/fixtures.ts
new file mode 100644
index 0000000000..002fcad53b
--- /dev/null
+++ b/packages/neuron-ui/src/tests/validators/capacity/fixtures.ts
@@ -0,0 +1,20 @@
+import { ErrorCode } from 'utils/enums'
+
+export default {
+ 'Should throw an error when address is not a string': {
+ params: {
+ address: 'ckb1qrgqep8saj8agswr30pls73hra28ry8jlnlc3ejzh3dl2ju7xxpjxqgqqy28n5m69va267nv0wvsudngtxz8t9jwss3xkhpk',
+ amount: '62',
+ unit: 'ckb',
+ },
+ exception: ErrorCode.CapacityTooSmall,
+ },
+ 'Should throw an error when address is required but not provided': {
+ params: {
+ address: 'ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsq2n0egquval9ljqm2ga6t9509kwcex5ejq3hy6dk',
+ amount: '62',
+ unit: 'ckb',
+ },
+ exception: null,
+ },
+}
diff --git a/packages/neuron-ui/src/tests/validators/capacity/index.test.ts b/packages/neuron-ui/src/tests/validators/capacity/index.test.ts
new file mode 100644
index 0000000000..d750f0b52f
--- /dev/null
+++ b/packages/neuron-ui/src/tests/validators/capacity/index.test.ts
@@ -0,0 +1,25 @@
+import { describe, test, expect } from '@jest/globals'
+import { isErrorWithI18n } from 'exceptions'
+import { validateCapacity } from 'utils/validators'
+import fixtures from './fixtures'
+
+const fixtureTable: Fixture.Validator
[] = Object.entries(fixtures).map(
+ ([title, { params, exception }]) => [title, [params], exception]
+)
+
+describe(`Test capacity validator`, () => {
+ test.each(fixtureTable)(`%s`, (_title, [params], exception) => {
+ if (exception) {
+ expect.assertions(2)
+ try {
+ validateCapacity(params)
+ } catch (err) {
+ expect(isErrorWithI18n(err)).toBeTruthy()
+ expect((err as { code: number }).code).toBe(exception)
+ }
+ } else {
+ expect.assertions(1)
+ expect(validateCapacity(params)).toBeTruthy()
+ }
+ })
+})
diff --git a/packages/neuron-ui/src/utils/hooks/index.ts b/packages/neuron-ui/src/utils/hooks/index.ts
index 0d2d63f131..9496c06422 100644
--- a/packages/neuron-ui/src/utils/hooks/index.ts
+++ b/packages/neuron-ui/src/utils/hooks/index.ts
@@ -23,6 +23,7 @@ import {
validateAmount,
validateAddress,
validateAmountRange,
+ validateCapacity,
} from 'utils/validators'
import { MenuItemConstructorOptions, shell } from 'electron'
import { ErrorWithI18n, isErrorWithI18n } from 'exceptions'
@@ -499,18 +500,22 @@ export const useForceUpdate = void>(cb: T) => {
}
export const useOutputErrors = (
- outputs: Partial>[],
- isMainnet: boolean
+ outputs: Partial>[],
+ isMainnet: boolean,
+ isSendMax?: boolean
) => {
return useMemo(
() =>
- outputs.map(({ address, amount, date }) => {
+ outputs.map(({ address, amount, date, unit }, index) => {
let amountError: ErrorWithI18n | undefined
if (amount !== undefined) {
try {
const extraSize = date ? CONSTANTS.SINCE_FIELD_SIZE : 0
validateAmount(amount)
validateAmountRange(amount, extraSize)
+ if (!(isSendMax && index === outputs.length - 1)) {
+ validateCapacity({ address, amount, unit })
+ }
} catch (err) {
if (isErrorWithI18n(err)) {
amountError = err
diff --git a/packages/neuron-ui/src/utils/validators/capacity.ts b/packages/neuron-ui/src/utils/validators/capacity.ts
new file mode 100644
index 0000000000..2f3a53f0c6
--- /dev/null
+++ b/packages/neuron-ui/src/utils/validators/capacity.ts
@@ -0,0 +1,19 @@
+import { CKBToShannonFormatter } from 'utils/formatters'
+import { CapacityTooSmallException } from 'exceptions'
+import { bytes as byteUtils } from '@ckb-lumos/codec'
+import { ckbCore } from 'services/chain'
+
+export const validateCapacity = (item: State.Output) => {
+ const { amount, unit, address } = item
+ const capacity = CKBToShannonFormatter(amount, unit)
+ const script = ckbCore.utils.addressToScript(address as string)
+ const size = 1 + byteUtils.concat(script.args, script.codeHash).byteLength
+ const outputSize = 8 + byteUtils.bytify('0x').byteLength + size
+
+ if (BigInt(capacity) < BigInt(outputSize) * BigInt(10 ** 8)) {
+ throw new CapacityTooSmallException(outputSize.toString())
+ }
+
+ return true
+}
+export default validateCapacity
diff --git a/packages/neuron-ui/src/utils/validators/index.ts b/packages/neuron-ui/src/utils/validators/index.ts
index f17bd58683..9a2e61c4e6 100644
--- a/packages/neuron-ui/src/utils/validators/index.ts
+++ b/packages/neuron-ui/src/utils/validators/index.ts
@@ -13,3 +13,4 @@ export * from './tokenId'
export * from './tokenName'
export * from './symbol'
export * from './decimal'
+export * from './capacity'
diff --git a/packages/neuron-ui/src/widgets/Icons/Clean.svg b/packages/neuron-ui/src/widgets/Icons/Clean.svg
new file mode 100644
index 0000000000..9edcc0df86
--- /dev/null
+++ b/packages/neuron-ui/src/widgets/Icons/Clean.svg
@@ -0,0 +1,3 @@
+
diff --git a/packages/neuron-ui/src/widgets/Icons/icon.tsx b/packages/neuron-ui/src/widgets/Icons/icon.tsx
index 464dc6e7d1..e58d9aa183 100644
--- a/packages/neuron-ui/src/widgets/Icons/icon.tsx
+++ b/packages/neuron-ui/src/widgets/Icons/icon.tsx
@@ -46,6 +46,7 @@ import { ReactComponent as AddressbookSvg } from './Addressbook.svg'
import { ReactComponent as AddSvg } from './Add.svg'
import { ReactComponent as AddSimpleSvg } from './AddSimple.svg'
import { ReactComponent as SwitchSvg } from './Switch.svg'
+import { ReactComponent as CleanSvg } from './Clean.svg'
import { ReactComponent as CellManageSvg } from './CellManage.svg'
import { ReactComponent as LockSvg } from './Lock.svg'
import { ReactComponent as LockCellSvg } from './LockCell.svg'
@@ -113,6 +114,7 @@ export const Addressbook = WrapSvg(AddressbookSvg, styles.withTheme)
export const Add = WrapSvg(AddSvg)
export const Switch = WrapSvg(SwitchSvg)
export const AddSimple = WrapSvg(AddSimpleSvg)
+export const Clean = WrapSvg(CleanSvg)
export const CellManage = WrapSvg(CellManageSvg, styles.withTheme)
export const Lock = WrapSvg(LockSvg, styles.withTheme)
export const LockCell = WrapSvg(LockCellSvg)