diff --git a/packages/neuron-ui/src/components/Addresses/index.tsx b/packages/neuron-ui/src/components/Addresses/index.tsx
index 4302ba02b5..e37238c0f9 100644
--- a/packages/neuron-ui/src/components/Addresses/index.tsx
+++ b/packages/neuron-ui/src/components/Addresses/index.tsx
@@ -83,7 +83,7 @@ const Addresses = ({
maxWidth: 500,
onRender: (item?: State.Address, _index?: number, column?: IColumn) => {
if (item) {
- if (column && (column.calculatedWidth || 0) < 400) {
+ if (column && (column.calculatedWidth || 0) < 420) {
return (
(
-
- {item.lockHash || 'none'}
-
- ),
- },
- {
- key: 'outPointCell',
- name: 'OutPoint Cell',
- minWidth: 150,
- onRender: (item: any) => {
- const text = item.previousOutput ? `${item.previousOutput.txHash}[${item.previousOutput.index}]` : 'none'
- return (
-
- {text}
-
- )
- },
- },
- {
- key: 'capacity',
- name: 'Capacity',
- minWidth: 200,
- maxWidth: 250,
- },
-].map(
- (col): IColumn => ({
- ariaLabel: col.name,
- fieldName: col.key,
- ...col,
- })
-)
-const outputColumns: IColumn[] = [
- {
- key: 'index',
- name: 'Index',
- minWidth: 80,
- maxWidth: 150,
- onRender: (item?: any | State.DetailedOutput) => {
- if (item) {
- return item.outPoint.index
- }
- return null
- },
- },
- {
- key: 'lockHash',
- name: 'Lock Hash',
- minWidth: 150,
- },
- {
- key: 'capacity',
- name: 'Capacity',
- minWidth: 200,
- maxWidth: 250,
- onRender: (output?: State.DetailedOutput) => {
- if (output) {
- return `${shannonToCKBFormatter(output.capacity)} CKB`
- }
- return null
- },
- },
-].map(col => ({
- ariaLabel: col.name,
- fieldName: col.key,
- ...col,
-}))
-
-const basicInfoColumns: IColumn[] = [
- {
- key: 'label',
- name: 'Label',
- minWidth: 100,
- maxWidth: 150,
- },
- {
- key: 'value',
- name: 'value',
- minWidth: 450,
- },
-].map(
- (col): IColumn => ({
- minWidth: MIN_CELL_WIDTH,
- ariaLabel: col.name,
- fieldName: col.key,
- ...col,
- })
-)
const Transaction = () => {
const [t] = useTranslation()
const [transaction, setTransaction] = useState(transactionState)
+ const [addressPrefix, setAddressPrefix] = useState(ckbCore.utils.AddressPrefix.Mainnet)
const [error, setError] = useState({ code: '', message: '' })
+
+ const inputColumns: IColumn[] = useMemo(
+ () =>
+ [
+ {
+ key: 'index',
+ name: t('transaction.index'),
+ minWidth: 60,
+ maxWidth: 60,
+ onRender: (_item?: any, index?: number) => {
+ if (undefined !== index) {
+ return index
+ }
+ return null
+ },
+ },
+ {
+ key: 'outPointCell',
+ name: 'OutPoint Cell',
+ minWidth: 150,
+ maxWidth: 600,
+ onRender: (item: any) => {
+ const text = item.previousOutput ? `${item.previousOutput.txHash}[${item.previousOutput.index}]` : 'none'
+ return (
+
+ {text}
+
+ )
+ },
+ },
+ {
+ key: 'capacity',
+ name: t('transaction.amount'),
+ minWidth: 100,
+ maxWidth: 250,
+ onRender: (input?: State.DetailedOutput) => {
+ if (input) {
+ return `${shannonToCKBFormatter(input.capacity)} CKB`
+ }
+ return null
+ },
+ },
+ ].map(
+ (col): IColumn => ({
+ ariaLabel: col.name,
+ fieldName: col.key,
+ ...col,
+ })
+ ),
+ [t]
+ )
+
+ const outputColumns: IColumn[] = useMemo(
+ () =>
+ [
+ {
+ key: 'index',
+ name: t('transaction.index'),
+ minWidth: 60,
+ maxWidth: 60,
+ onRender: (item?: any | State.DetailedOutput) => {
+ if (item) {
+ return item.outPoint.index
+ }
+ return null
+ },
+ },
+ {
+ key: 'address',
+ name: t('transaction.address'),
+ minWidth: 200,
+ maxWidth: 500,
+ onRender: (output?: State.DetailedOutput, _index?: number, column?: IColumn) => {
+ if (!output) {
+ return null
+ }
+ try {
+ const address = ckbCore.utils.bech32Address(output.lock.args[0], {
+ prefix: addressPrefix,
+ type: ckbCore.utils.AddressType.HashIdx,
+ codeHashIndex: '0x00',
+ })
+ if (column && (column.calculatedWidth || 0) < 450) {
+ return (
+
+ {address.slice(0, -6)}
+ {address.slice(-6)}
+
+ )
+ }
+ return (
+
+ {address}
+
+ )
+ } catch {
+ return null
+ }
+ },
+ },
+ {
+ key: 'capacity',
+ name: t('transaction.amount'),
+ minWidth: 100,
+ maxWidth: 250,
+ onRender: (output?: State.DetailedOutput) => {
+ if (output) {
+ return `${shannonToCKBFormatter(output.capacity)} CKB`
+ }
+ return null
+ },
+ },
+ ].map(col => ({
+ ariaLabel: col.name,
+ fieldName: col.key,
+ ...col,
+ })),
+ [addressPrefix, t]
+ )
+
+ const basicInfoColumns: IColumn[] = useMemo(
+ () =>
+ [
+ {
+ key: 'label',
+ name: 'label',
+ minWidth: 100,
+ maxWidth: 120,
+ },
+ {
+ key: 'value',
+ name: 'value',
+ minWidth: 150,
+ },
+ ].map(
+ (col): IColumn => ({
+ minWidth: MIN_CELL_WIDTH,
+ ariaLabel: col.name,
+ fieldName: col.key,
+ ...col,
+ })
+ ),
+ []
+ )
+
useEffect(() => {
+ Promise.all([getAllNetworks(), getCurrentNetworkID()])
+ .then(([networksRes, idRes]) => {
+ if (networksRes.status === 1 && idRes.status === 1) {
+ const network = networksRes.result.find((n: any) => n.id === idRes.result)
+ if (!network) {
+ throw new Error('Cannot find current network in the network list')
+ }
+
+ setAddressPrefix(
+ network.chain === process.env.REACT_APP_MAINNET_TAG
+ ? ckbCore.utils.AddressPrefix.Mainnet
+ : ckbCore.utils.AddressPrefix.Testnet
+ )
+ }
+ })
+ .catch(err => console.warn(err))
+
const currentWallet = currentWalletCache.load()
if (currentWallet) {
const hash = window.location.href.split('/').pop()
@@ -142,21 +220,26 @@ const Transaction = () => {
})
}, [])
+ // TODO: add conditional branch on mainnet and testnet
+ const onExplorerBtnClick = useCallback(() => {
+ openExternal(`https://explorer.nervos.org/transaction/${transaction.hash}`)
+ }, [transaction.hash])
+
const basicInfoItems = useMemo(
() => [
- { label: t('history.transaction-hash'), value: transaction.hash || 'none' },
+ { label: t('transaction.transaction-hash'), value: transaction.hash || 'none' },
{
- label: t('history.date'),
+ label: t('transaction.block-number'),
+ value: localNumberFormatter(transaction.blockNumber) || 'none',
+ },
+ {
+ label: t('transaction.date'),
value: +(transaction.timestamp || transaction.createdAt)
? uniformTimeFormatter(+(transaction.timestamp || transaction.createdAt))
: 'none',
},
{
- label: t('history.blockNumber'),
- value: localNumberFormatter(transaction.blockNumber) || 'none',
- },
- {
- label: t('history.amount'),
+ label: t('transaction.income'),
value: `${shannonToCKBFormatter(transaction.value)} CKB`,
},
],
@@ -185,10 +268,12 @@ const Transaction = () => {
isHeaderVisible={false}
/>
-
+
- Inputs
+ {`${t('transaction.inputs')} (${transaction.inputs.length}/${localNumberFormatter(
+ transaction.inputsCount
+ )})`}
{
- Outputs
+ {`${t('transaction.outputs')} (${transaction.outputs.length}/${localNumberFormatter(
+ transaction.outputsCount
+ )})`}
{
/>
+
)
}
diff --git a/packages/neuron-ui/src/components/Transaction/style.module.scss b/packages/neuron-ui/src/components/Transaction/style.module.scss
new file mode 100644
index 0000000000..e99cb291dd
--- /dev/null
+++ b/packages/neuron-ui/src/components/Transaction/style.module.scss
@@ -0,0 +1,38 @@
+.explorerNavButton {
+ position: fixed;
+ right: 10px;
+ bottom: 10px;
+ height: 30px;
+ width: 30px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ overflow: hidden;
+ font-size: 12px;
+ color: #fff;
+ background: rgba(60, 198, 138, 0.8);
+ border: none;
+ border-radius: 15px;
+ transition: all 0.2s;
+ z-index: 1;
+
+ span {
+ display: none;
+ }
+
+ i {
+ display: flex;
+ }
+
+ &:hover {
+ width: 100px;
+
+ span {
+ display: flex;
+ }
+
+ i {
+ display: none;
+ }
+ }
+}
diff --git a/packages/neuron-ui/src/locales/en.json b/packages/neuron-ui/src/locales/en.json
index c64691cfe7..34c89e06a3 100644
--- a/packages/neuron-ui/src/locales/en.json
+++ b/packages/neuron-ui/src/locales/en.json
@@ -139,7 +139,18 @@
"confirming-with-count": "{{confirmations}} confirmations"
},
"transaction": {
- "goBack": "Go back"
+ "date": "Date",
+ "transaction-hash": "Transaction Hash",
+ "block-number": "Block Number",
+ "goBack": "Go back",
+ "index": "Index",
+ "address": "Address",
+ "income": "Income",
+ "amount": "Amount",
+ "inputs": "Inputs",
+ "outputs": "Outputs",
+ "view-in-explorer": "Explorer",
+ "view-in-explorer-button-title": "View on explorer"
},
"addresses": {
"addresses": "Addresses",
diff --git a/packages/neuron-ui/src/locales/zh.json b/packages/neuron-ui/src/locales/zh.json
index 9b8136feb8..5c18d9f008 100644
--- a/packages/neuron-ui/src/locales/zh.json
+++ b/packages/neuron-ui/src/locales/zh.json
@@ -139,7 +139,18 @@
"confirming-with-count": " 已确认 {{confirmations}} 次"
},
"transaction": {
- "goBack": "返回"
+ "date": "时间",
+ "transaction-hash": "交易哈希",
+ "block-number": "区块高度",
+ "goBack": "返回",
+ "index": "序号",
+ "address": "地址",
+ "income": "收入",
+ "amount": "数量",
+ "inputs": "输入",
+ "outputs": "输出",
+ "view-in-explorer": "浏览器",
+ "view-in-explorer-button-title": "浏览器中查看详情"
},
"addresses": {
"addresses": "地址",
diff --git a/packages/neuron-ui/src/services/remote/index.ts b/packages/neuron-ui/src/services/remote/index.ts
index 36802546db..c7e61bfd36 100644
--- a/packages/neuron-ui/src/services/remote/index.ts
+++ b/packages/neuron-ui/src/services/remote/index.ts
@@ -69,6 +69,14 @@ export const showOpenDialog = (opt: { title: string; message?: string; onUpload:
)
}
+export const openExternal = (url: string) => {
+ if (!window.remote) {
+ window.open(url)
+ } else {
+ window.remote.require('electron').shell.openExternal(url)
+ }
+}
+
export default {
getLocale,
validateMnemonic,
@@ -77,4 +85,5 @@ export default {
showErrorMessage,
showOpenDialog,
getWinID,
+ openExternal,
}
diff --git a/packages/neuron-ui/src/services/remote/networks.ts b/packages/neuron-ui/src/services/remote/networks.ts
index 5bcda628b9..5a11114318 100644
--- a/packages/neuron-ui/src/services/remote/networks.ts
+++ b/packages/neuron-ui/src/services/remote/networks.ts
@@ -18,8 +18,18 @@ export const updateNetwork = controllerMethodWrapper(CONTROLLER_NAME)(
}
)
+export const getAllNetworks = controllerMethodWrapper(CONTROLLER_NAME)(controller => () => {
+ return controller.getAll()
+})
+
+export const getCurrentNetworkID = controllerMethodWrapper(CONTROLLER_NAME)(controller => () => {
+ return controller.currentID()
+})
+
export default {
createNetwork,
updateNetwork,
setCurrentNetowrk,
+ getAllNetworks,
+ getCurrentNetworkID,
}
diff --git a/packages/neuron-ui/src/states/initStates/chain.ts b/packages/neuron-ui/src/states/initStates/chain.ts
index 742b0f6c47..8cd5609b48 100644
--- a/packages/neuron-ui/src/states/initStates/chain.ts
+++ b/packages/neuron-ui/src/states/initStates/chain.ts
@@ -11,7 +11,9 @@ export const transactionState: State.DetailedTransaction = {
description: '',
status: 'pending',
inputs: [],
+ inputsCount: '0',
outputs: [],
+ outputsCount: '0',
deps: [],
blockNumber: '',
blockHash: '',
diff --git a/packages/neuron-ui/src/styles/index.scss b/packages/neuron-ui/src/styles/index.scss
index f3ca316e1d..5f0770add3 100755
--- a/packages/neuron-ui/src/styles/index.scss
+++ b/packages/neuron-ui/src/styles/index.scss
@@ -109,13 +109,14 @@ navbar {
}
}
- .textOverflow {
- overflow: hidden;
- text-overflow: ellipsis;
- }
}
+.textOverflow {
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
// hack fabric ui experimental pagination style
.ms-Pagination-container {
button[aria-selected=false] {
diff --git a/packages/neuron-ui/src/theme.tsx b/packages/neuron-ui/src/theme.tsx
index 15220aa1da..ef4c2b8897 100644
--- a/packages/neuron-ui/src/theme.tsx
+++ b/packages/neuron-ui/src/theme.tsx
@@ -9,6 +9,7 @@ import {
Close as DismissIcon,
Close as FailIcon,
Copy as CopyIcon,
+ Domain as ExplorerIcon,
Down as ArrowDownIcon,
FormEdit as EditIcon,
FormClose as ClearIcon,
@@ -89,6 +90,7 @@ registerIcons({
Keystore:
,
Edit:
,
Settings:
,
+ Explorer:
,
},
})
diff --git a/packages/neuron-ui/src/types/App/index.d.ts b/packages/neuron-ui/src/types/App/index.d.ts
index b38d199b9a..a87047d0ba 100644
--- a/packages/neuron-ui/src/types/App/index.d.ts
+++ b/packages/neuron-ui/src/types/App/index.d.ts
@@ -38,7 +38,9 @@ declare namespace State {
} | null
}
}[]
+ inputsCount: string
outputs: DetailedOutput[]
+ outputsCount: string
witnesses: string[]
}
interface Output {
diff --git a/packages/neuron-wallet/src/controllers/transactions.ts b/packages/neuron-wallet/src/controllers/transactions.ts
index d2c0b72c12..f5331fb22c 100644
--- a/packages/neuron-wallet/src/controllers/transactions.ts
+++ b/packages/neuron-wallet/src/controllers/transactions.ts
@@ -9,6 +9,8 @@ import { ResponseCode } from 'utils/const'
import { TransactionNotFound, CurrentWalletNotSet, ServiceHasNoResponse } from 'exceptions'
import LockUtils from 'models/lock-utils'
+const CELL_COUNT_THRESHOLD = 10
+
/**
* @class TransactionsController
* @description handle messages from transactions channel
@@ -16,7 +18,7 @@ import LockUtils from 'models/lock-utils'
export default class TransactionsController {
@CatchControllerError
public static async getAll(
- params: TransactionsByLockHashesParam
+ params: TransactionsByLockHashesParam,
): Promise
>> {
const transactions = await TransactionsService.getAll(params)
@@ -32,7 +34,7 @@ export default class TransactionsController {
@CatchControllerError
public static async getAllByKeywords(
- params: Controller.Params.TransactionsByKeywords
+ params: Controller.Params.TransactionsByKeywords,
): Promise & Controller.Params.TransactionsByKeywords>> {
const { pageNo = 1, pageSize = 15, keywords = '', walletID = '' } = params
@@ -56,7 +58,7 @@ export default class TransactionsController {
@CatchControllerError
public static async getAllByAddresses(
- params: Controller.Params.TransactionsByAddresses
+ params: Controller.Params.TransactionsByAddresses,
): Promise & Controller.Params.TransactionsByAddresses>> {
const { pageNo, pageSize, addresses = '' } = params
@@ -85,7 +87,10 @@ export default class TransactionsController {
}
@CatchControllerError
- public static async get(walletID: string, hash: string): Promise> {
+ public static async get(
+ walletID: string,
+ hash: string,
+ ): Promise> {
const transaction = await TransactionsService.get(hash)
if (!transaction) {
@@ -114,15 +119,20 @@ export default class TransactionsController {
.reduce((result, c) => result + c, BigInt(0))
const value: bigint = outputCapacities - inputCapacities
transaction.value = value.toString()
+ const inputsCount = transaction.inputs ? transaction.inputs.length.toString() : '0'
+ if (transaction.inputs) {
+ transaction.inputs = transaction.inputs.slice(0, CELL_COUNT_THRESHOLD)
+ }
+ const outputsCount = transaction.outputs ? transaction.outputs.length.toString() : '0'
if (transaction.outputs) {
- transaction.outputs = transaction
- .outputs.sort((o1, o2) => +o1.outPoint!.index - +o2.outPoint!.index)
- .slice(0, 200)
+ transaction.outputs = transaction.outputs
+ .sort((o1, o2) => +o1.outPoint!.index - +o2.outPoint!.index)
+ .slice(0, CELL_COUNT_THRESHOLD)
}
return {
status: ResponseCode.Success,
- result: transaction,
+ result: { ...transaction, outputsCount, inputsCount },
}
}