-
-
+
@@ -98,7 +101,7 @@ export function Profile() {
{state.data?.network}
-
+
diff --git a/apps/hostd/contexts/contracts/columns.tsx b/apps/hostd/contexts/contracts/columns.tsx
index bc019e590..94fe71bfa 100644
--- a/apps/hostd/contexts/contracts/columns.tsx
+++ b/apps/hostd/contexts/contracts/columns.tsx
@@ -26,6 +26,7 @@ type Context = {
startHeight: number
endHeight: number
}
+ siascanUrl: string
}
type ContractsTableColumn = TableColumn<
@@ -52,14 +53,15 @@ export const columns: ContractsTableColumn[] = (
id: 'contractId',
label: 'contract ID',
category: 'general',
- render: ({ data }) => {
+ render: ({ data, context }) => {
const { id, renewedFrom, isRenewedFrom, renewedTo, isRenewedTo } = data
return (
{isRenewedFrom && (
@@ -70,8 +72,9 @@ export const columns: ContractsTableColumn[] = (
@@ -86,7 +89,8 @@ export const columns: ContractsTableColumn[] = (
color="subtle"
size="10"
value={stripPrefix(renewedTo)}
- label="contract ID"
+ type="contract"
+ siascanUrl={context.siascanUrl}
/>
diff --git a/apps/hostd/contexts/contracts/index.tsx b/apps/hostd/contexts/contracts/index.tsx
index 4404ec7b6..1746afca5 100644
--- a/apps/hostd/contexts/contracts/index.tsx
+++ b/apps/hostd/contexts/contracts/index.tsx
@@ -21,6 +21,7 @@ import {
import { columns } from './columns'
import { useDataset } from './dataset'
import { useSyncStatus } from '../../hooks/useSyncStatus'
+import { useSiascanUrl } from '../../hooks/useSiascanUrl'
const defaultLimit = 50
@@ -92,14 +93,22 @@ function useContractsMain() {
[currentHeight, dataset]
)
+ const siascanUrl = useSiascanUrl()
+
+ const cellContext = useMemo(
+ () => ({
+ contractsTimeRange,
+ currentHeight,
+ siascanUrl,
+ }),
+ [contractsTimeRange, currentHeight, siascanUrl]
+ )
+
return {
dataState,
offset,
limit,
- cellContext: {
- contractsTimeRange,
- currentHeight,
- },
+ cellContext,
pageCount: dataset?.length || 0,
totalCount: response.data?.count,
columns: filteredTableColumns,
diff --git a/apps/hostd/contexts/transactions/index.tsx b/apps/hostd/contexts/transactions/index.tsx
index 80d60f80b..c09cb4276 100644
--- a/apps/hostd/contexts/transactions/index.tsx
+++ b/apps/hostd/contexts/transactions/index.tsx
@@ -1,5 +1,5 @@
import {
- EntityListItemProps,
+ TxType,
daysInMilliseconds,
getTransactionType,
secondsInMilliseconds,
@@ -14,10 +14,30 @@ import { createContext, useContext, useMemo } from 'react'
import { useDialog } from '../dialog'
import BigNumber from 'bignumber.js'
import { useRouter } from 'next/router'
+import { useSiascanUrl } from '../../hooks/useSiascanUrl'
const defaultLimit = 50
const filters = []
+export type TransactionDataPending = {
+ type: 'transaction'
+ txType: TxType
+ siascanUrl: string
+}
+
+export type TransactionDataConfirmed = {
+ type: 'transaction'
+ txType: TxType
+ hash: string
+ timestamp: number
+ onClick: () => void
+ unconfirmed: boolean
+ sc: BigNumber
+ siascanUrl: string
+}
+
+export type TransactionData = TransactionDataPending | TransactionDataConfirmed
+
function useTransactionsMain() {
const router = useRouter()
const limit = Number(router.query.limit || defaultLimit)
@@ -42,13 +62,15 @@ function useTransactionsMain() {
})
const { openDialog } = useDialog()
+ const siascanUrl = useSiascanUrl()
- const dataset: EntityListItemProps[] | null = useMemo(() => {
+ const dataset: TransactionData[] | null = useMemo(() => {
if (!pending.data || !transactions.data) {
return null
}
return [
- ...(pending.data || []).map((t): EntityListItemProps => {
+ ...(pending.data || []).map((t): TransactionData => {
+ const notRealTxn = t.source !== 'transaction'
return {
type: 'transaction',
txType: getTransactionType(t.transaction, t.source),
@@ -56,10 +78,12 @@ function useTransactionsMain() {
timestamp: new Date(t.timestamp).getTime(),
sc: new BigNumber(t.inflow).minus(t.outflow),
unconfirmed: true,
+ siascanUrl: notRealTxn ? undefined : siascanUrl,
}
}),
...(transactions.data || [])
- .map((t): EntityListItemProps => {
+ .map((t): TransactionData => {
+ const notRealTxn = t.source !== 'transaction'
return {
type: 'transaction',
txType: getTransactionType(t.transaction, t.source),
@@ -67,11 +91,12 @@ function useTransactionsMain() {
timestamp: new Date(t.timestamp).getTime(),
onClick: () => openDialog('transactionDetails', t.ID),
sc: new BigNumber(t.inflow).minus(t.outflow),
+ siascanUrl: notRealTxn ? undefined : siascanUrl,
}
})
- .sort((a, b) => (a.timestamp < b.timestamp ? 1 : -1)),
+ .sort((a, b) => (a['timestamp'] < b['timestamp'] ? 1 : -1)),
]
- }, [pending, transactions, openDialog])
+ }, [pending, transactions, openDialog, siascanUrl])
const dayPeriods = 30
const start = useMemo(() => {
diff --git a/apps/hostd/hooks/useSiascanUrl.ts b/apps/hostd/hooks/useSiascanUrl.ts
new file mode 100644
index 000000000..e90e3c150
--- /dev/null
+++ b/apps/hostd/hooks/useSiascanUrl.ts
@@ -0,0 +1,9 @@
+import { webLinks } from '@siafoundation/design-system'
+import { useStateHost } from '@siafoundation/react-hostd'
+
+export function useSiascanUrl() {
+ const state = useStateHost()
+ return state.data?.network === 'Zen Testnet'
+ ? webLinks.explore.testnetZen
+ : webLinks.explore.mainnet
+}
diff --git a/apps/renterd/components/Profile/index.tsx b/apps/renterd/components/Profile/index.tsx
index 6efb6b5fe..e048e5b4e 100644
--- a/apps/renterd/components/Profile/index.tsx
+++ b/apps/renterd/components/Profile/index.tsx
@@ -56,19 +56,20 @@ export function Profile() {
-
+
+
+
-
diff --git a/apps/renterd/contexts/contracts/columns.tsx b/apps/renterd/contexts/contracts/columns.tsx
index 8d11b51d9..64cdc4768 100644
--- a/apps/renterd/contexts/contracts/columns.tsx
+++ b/apps/renterd/contexts/contracts/columns.tsx
@@ -22,6 +22,7 @@ type Context = {
startHeight: number
endHeight: number
}
+ siascanUrl: string
}
type ContractsTableColumn = TableColumn<
@@ -47,14 +48,19 @@ export const columns: ContractsTableColumn[] = [
id: 'contractId',
label: 'contract ID',
category: 'general',
- render: ({ data: { id, isRenewed, renewedFrom } }) => {
+ render: ({
+ data: { id, isRenewed, renewedFrom },
+ context: { siascanUrl },
+ }) => {
// const { label, color } = getStatus(row)
return (
{isRenewed && (
@@ -65,7 +71,9 @@ export const columns: ContractsTableColumn[] = [
@@ -79,13 +87,13 @@ export const columns: ContractsTableColumn[] = [
id: 'hostIp',
label: 'host address',
category: 'general',
- render: ({ data: { hostIp } }) => {
+ render: ({ data: { hostIp }, context: { siascanUrl } }) => {
return (
)
},
@@ -94,8 +102,15 @@ export const columns: ContractsTableColumn[] = [
id: 'hostKey',
label: 'host public key',
category: 'general',
- render: ({ data: { hostKey } }) => {
- return
+ render: ({ data: { hostKey }, context: { siascanUrl } }) => {
+ return (
+
+ )
},
},
{
diff --git a/apps/renterd/contexts/contracts/index.tsx b/apps/renterd/contexts/contracts/index.tsx
index 6173fdbdc..e71f17a9b 100644
--- a/apps/renterd/contexts/contracts/index.tsx
+++ b/apps/renterd/contexts/contracts/index.tsx
@@ -20,6 +20,7 @@ import {
import { columns } from './columns'
import { useSiaCentralHosts } from '@siafoundation/react-sia-central'
import { useSyncStatus } from '../../hooks/useSyncStatus'
+import { useSiascanUrl } from '../../hooks/useSiascanUrl'
const defaultLimit = 50
@@ -139,6 +140,17 @@ function useContractsMain() {
filters
)
+ const siascanUrl = useSiascanUrl()
+
+ const cellContext = useMemo(
+ () => ({
+ currentHeight: syncStatus.estimatedBlockHeight,
+ contractsTimeRange,
+ siascanUrl,
+ }),
+ [syncStatus.estimatedBlockHeight, contractsTimeRange, siascanUrl]
+ )
+
return {
dataState,
limit,
@@ -150,11 +162,8 @@ function useContractsMain() {
datasetFilteredCount: datasetFiltered?.length || 0,
columns: filteredTableColumns,
dataset,
+ cellContext,
datasetPage,
- cellContext: {
- currentHeight: syncStatus.estimatedBlockHeight,
- contractsTimeRange,
- },
configurableColumns,
enabledColumns,
sortableColumns,
diff --git a/apps/renterd/contexts/hosts/columns.tsx b/apps/renterd/contexts/hosts/columns.tsx
index 0b8f440c0..4da6806fc 100644
--- a/apps/renterd/contexts/hosts/columns.tsx
+++ b/apps/renterd/contexts/hosts/columns.tsx
@@ -16,7 +16,7 @@ import {
CheckboxCheckedFilled16,
} from '@siafoundation/react-icons'
import { humanBytes, humanNumber } from '@siafoundation/sia-js'
-import { HostData, TableColumnId } from './types'
+import { HostContext, HostData, TableColumnId } from './types'
import { format, formatDistance, formatRelative } from 'date-fns'
import { HostContextMenu } from '../../components/Hosts/HostContextMenu'
import { useWorkflows } from '@siafoundation/react-core'
@@ -29,11 +29,7 @@ import {
import BigNumber from 'bignumber.js'
import React, { memo } from 'react'
-type HostsTableColumn = TableColumn<
- TableColumnId,
- HostData,
- { isAutopilotConfigured: boolean }
-> & {
+type HostsTableColumn = TableColumn
& {
fixed?: boolean
category: string
}
@@ -309,12 +305,12 @@ export const columns: HostsTableColumn[] = (
id: 'netAddress',
label: 'address',
category: 'general',
- render: ({ data }) => (
+ render: ({ data, context }) => (
),
},
@@ -322,11 +318,12 @@ export const columns: HostsTableColumn[] = (
id: 'publicKey',
label: 'public key',
category: 'general',
- render: ({ data }) => (
+ render: ({ data, context }) => (
),
},
diff --git a/apps/renterd/contexts/hosts/index.tsx b/apps/renterd/contexts/hosts/index.tsx
index bfc6116f7..bc4f0f67a 100644
--- a/apps/renterd/contexts/hosts/index.tsx
+++ b/apps/renterd/contexts/hosts/index.tsx
@@ -37,6 +37,7 @@ import { useApp } from '../app'
import { useAppSettings } from '@siafoundation/react-core'
import { Commands, emptyCommands } from '../../components/Hosts/HostMap/Globe'
import { useSiaCentralHosts } from '@siafoundation/react-sia-central'
+import { useSiascanUrl } from '../../hooks/useSiascanUrl'
const defaultLimit = 50
@@ -240,12 +241,14 @@ function useHostsMain() {
autopilot.status === 'on' ? autopilotResponse.error : regularResponse.error
const dataState = useDatasetEmptyState(dataset, isValidating, error, filters)
+ const siascanUrl = useSiascanUrl()
const isAutopilotConfigured = autopilot.state.data?.configured
const tableContext = useMemo(
() => ({
isAutopilotConfigured,
+ siascanUrl,
}),
- [isAutopilotConfigured]
+ [isAutopilotConfigured, siascanUrl]
)
const hostsWithLocation = useMemo(
diff --git a/apps/renterd/contexts/hosts/types.tsx b/apps/renterd/contexts/hosts/types.tsx
index 99471ef78..2c1e72e2c 100644
--- a/apps/renterd/contexts/hosts/types.tsx
+++ b/apps/renterd/contexts/hosts/types.tsx
@@ -2,6 +2,8 @@ import { AutopilotHost } from '@siafoundation/react-renterd'
import BigNumber from 'bignumber.js'
import { ContractData } from '../contracts/types'
+export type HostContext = { isAutopilotConfigured: boolean; siascanUrl: string }
+
export type HostData = {
id: string
isOnAllowlist: boolean
diff --git a/apps/renterd/contexts/transactions/index.tsx b/apps/renterd/contexts/transactions/index.tsx
index b25b3fa39..f69e51d57 100644
--- a/apps/renterd/contexts/transactions/index.tsx
+++ b/apps/renterd/contexts/transactions/index.tsx
@@ -1,8 +1,9 @@
import {
- EntityListItemProps,
+ TxType,
daysInMilliseconds,
getTransactionType,
secondsInMilliseconds,
+ stripPrefix,
useDatasetEmptyState,
} from '@siafoundation/design-system'
import {
@@ -14,10 +15,34 @@ import { createContext, useContext, useMemo } from 'react'
import { useDialog } from '../dialog'
import BigNumber from 'bignumber.js'
import { useRouter } from 'next/router'
+import { useSiascanUrl } from '../../hooks/useSiascanUrl'
+import { Transaction } from '@siafoundation/react-core'
const defaultLimit = 50
const filters = []
+export type TransactionDataPending = {
+ type: 'transaction'
+ txType: TxType
+ siascanUrl: string
+}
+
+export type TransactionDataConfirmed = {
+ type: 'transaction'
+ txType: TxType
+ hash: string
+ timestamp: number
+ onClick: () => void
+ raw: Transaction
+ inflow: string
+ outflow: string
+ unconfirmed: boolean
+ sc: BigNumber
+ siascanUrl: string
+}
+
+export type TransactionData = TransactionDataPending | TransactionDataConfirmed
+
function useTransactionsMain() {
const router = useRouter()
const limit = Number(router.query.limit || defaultLimit)
@@ -45,12 +70,14 @@ function useTransactionsMain() {
const { openDialog } = useDialog()
- const dataset: EntityListItemProps[] | null = useMemo(() => {
+ const siascanUrl = useSiascanUrl()
+
+ const dataset: TransactionData[] | null = useMemo(() => {
if (!pending.data || !transactions.data) {
return null
}
return [
- ...(pending.data || []).map((t): EntityListItemProps => {
+ ...(pending.data || []).map((t): TransactionData => {
return {
type: 'transaction',
txType: getTransactionType(t),
@@ -59,50 +86,28 @@ function useTransactionsMain() {
// onClick: () => openDialog('transactionDetails', t.ID),
// sc: totals.sc,
unconfirmed: true,
+ siascanUrl,
}
}),
...(transactions.data || [])
- .map((t): EntityListItemProps => {
+ .map((t): TransactionData => {
return {
type: 'transaction',
txType: getTransactionType(t.raw),
- hash: t.id,
+ hash: stripPrefix(t.id),
timestamp: new Date(t.timestamp).getTime(),
- onClick: () => openDialog('transactionDetails', t.id),
+ onClick: () => openDialog('transactionDetails', stripPrefix(t.id)),
+ raw: t.raw,
+ inflow: t.inflow,
+ outflow: t.outflow,
sc: new BigNumber(t.inflow).minus(t.outflow),
+ siascanUrl,
}
})
- .sort((a, b) => (a.timestamp < b.timestamp ? 1 : -1)),
+ .sort((a, b) => (a['timestamp'] < b['timestamp'] ? 1 : -1)),
]
- }, [pending.data, transactions.data, openDialog])
-
- // // This now works but the visx chart has an issue where the tooltip does not
- // // find the correct nearest datum when the data is not at a consistent
- // // interval due to axis scale issues. renterd needs to return clean data
- // // like hostd or we need to wait for this issue to be fixed:
- // // https://github.com/airbnb/visx/issues/1533
- // // until then renterd will use a line graph which does not have the issue.
- // const balances = useMemo(
- // () =>
- // transactions.data
- // ?.sort((a, b) => (a.timestamp > b.timestamp ? 1 : -1))
- // .reduce((acc, t, i) => {
- // if (i === 0) {
- // return acc.concat({
- // sc: new BigNumber(t.inflow).minus(t.outflow).toNumber(),
- // timestamp: new Date(t.timestamp).getTime(),
- // })
- // }
- // return acc.concat({
- // sc: new BigNumber(acc[i - 1].sc)
- // .plus(t.inflow)
- // .minus(t.outflow)
- // .toNumber(),
- // timestamp: new Date(t.timestamp).getTime(),
- // })
- // }, []),
- // [transactions.data]
- // )
+ }, [pending.data, transactions.data, openDialog, siascanUrl])
+
const error = transactions.error
const dataState = useDatasetEmptyState(
dataset,
@@ -127,6 +132,12 @@ function useTransactionsMain() {
n: periods,
},
})
+ // This now works but the visx chart has an issue where the tooltip does not
+ // find the correct nearest datum when the data is not at a consistent
+ // interval due to axis scale issues. renterd needs to return clean data
+ // like hostd or we need to wait for this issue to be fixed:
+ // https://github.com/airbnb/visx/issues/1533
+ // until then renterd will use a line graph which does not have the issue.
const balances = useMemo(
() =>
(metrics.data || [])
diff --git a/apps/renterd/dialogs/RenterdTransactionDetailsDialog.tsx b/apps/renterd/dialogs/RenterdTransactionDetailsDialog.tsx
index d1bba5086..4f49cceb6 100644
--- a/apps/renterd/dialogs/RenterdTransactionDetailsDialog.tsx
+++ b/apps/renterd/dialogs/RenterdTransactionDetailsDialog.tsx
@@ -1,43 +1,24 @@
import { useMemo } from 'react'
-import {
- getTransactionType,
- TransactionDetailsDialog,
-} from '@siafoundation/design-system'
-import { useWalletTransactions } from '@siafoundation/react-renterd'
+import { TransactionDetailsDialog } from '@siafoundation/design-system'
import { useDialog } from '../contexts/dialog'
+import {
+ TransactionDataConfirmed,
+ useTransactions,
+} from '../contexts/transactions'
export function RenterdTransactionDetailsDialog() {
const { id, dialog, openDialog, closeDialog } = useDialog()
// TODO: add transaction endpoint
- const transactions = useWalletTransactions({
- params: {},
- config: {
- swr: {
- revalidateOnFocus: false,
- refreshInterval: 60_000,
- },
- },
- disabled: dialog !== 'transactionDetails',
- })
+ const { dataset } = useTransactions()
const transaction = useMemo(() => {
- const txn = transactions.data?.find((t) => t.id === id)
- if (!txn) {
- return null
- }
- return {
- txType: getTransactionType(txn.raw),
- inflow: txn.inflow,
- outflow: txn.outflow,
- timestamp: txn.timestamp,
- raw: txn.raw,
- }
- }, [transactions, id])
+ return dataset?.find((t) => t['hash'] === id)
+ }, [dataset, id])
return (
(val ? openDialog(dialog) : closeDialog())}
/>
diff --git a/apps/renterd/hooks/useSiascanUrl.tsx b/apps/renterd/hooks/useSiascanUrl.tsx
new file mode 100644
index 000000000..40d371f21
--- /dev/null
+++ b/apps/renterd/hooks/useSiascanUrl.tsx
@@ -0,0 +1,9 @@
+import { webLinks } from '@siafoundation/design-system'
+import { useBusState } from '@siafoundation/react-renterd'
+
+export function useSiascanUrl() {
+ const network = useBusState()
+ return network.data?.network === 'Zen Testnet'
+ ? webLinks.explore.testnetZen
+ : webLinks.explore.mainnet
+}
diff --git a/apps/walletd/components/Profile/index.tsx b/apps/walletd/components/Profile/index.tsx
index 5faffd516..a9e3c84ec 100644
--- a/apps/walletd/components/Profile/index.tsx
+++ b/apps/walletd/components/Profile/index.tsx
@@ -28,7 +28,9 @@ export function Profile() {
- {network.data.name}
+
+ {network.data.name}
+
)}
{/*
diff --git a/apps/walletd/components/Wallet/index.tsx b/apps/walletd/components/Wallet/index.tsx
index 4cad8a431..35337e694 100644
--- a/apps/walletd/components/Wallet/index.tsx
+++ b/apps/walletd/components/Wallet/index.tsx
@@ -19,6 +19,7 @@ export function Wallet() {
dataset,
dataState,
columns,
+ cellContext,
sortableColumns,
sortDirection,
sortField,
@@ -50,6 +51,7 @@ export function Wallet() {
}
pageSize={6}
data={dataset}
+ context={cellContext}
columns={columns}
sortableColumns={sortableColumns}
sortDirection={sortDirection}
diff --git a/apps/walletd/components/WalletAddresses/index.tsx b/apps/walletd/components/WalletAddresses/index.tsx
index 010b58070..17a2ba68e 100644
--- a/apps/walletd/components/WalletAddresses/index.tsx
+++ b/apps/walletd/components/WalletAddresses/index.tsx
@@ -21,6 +21,7 @@ export function WalletAddresses() {
dataset,
dataState,
columns,
+ cellContext,
sortableColumns,
sortDirection,
sortField,
@@ -75,6 +76,7 @@ export function WalletAddresses() {
}
pageSize={6}
data={dataset}
+ context={cellContext}
columns={columns}
sortableColumns={sortableColumns}
sortDirection={sortDirection}
diff --git a/apps/walletd/contexts/addresses/columns.tsx b/apps/walletd/contexts/addresses/columns.tsx
index 5d3fda936..5b57d8f84 100644
--- a/apps/walletd/contexts/addresses/columns.tsx
+++ b/apps/walletd/contexts/addresses/columns.tsx
@@ -8,9 +8,13 @@ import {
} from '@siafoundation/design-system'
import { Draggable16 } from '@siafoundation/react-icons'
import { AddressContextMenu } from '../../components/WalletAddresses/AddressContextMenu'
-import { AddressData, TableColumnId } from './types'
+import { AddressData, CellContext, TableColumnId } from './types'
-type AddressesTableColumn = TableColumn
& {
+type AddressesTableColumn = TableColumn<
+ TableColumnId,
+ AddressData,
+ CellContext
+> & {
fixed?: boolean
category?: string
}
@@ -38,10 +42,15 @@ export const columns: AddressesTableColumn[] = [
label: 'address',
category: 'general',
fixed: true,
- render: ({ data: { address, description } }) => {
+ render: ({ data: { address, description }, context }) => {
return (
-
+
{description && (
(
+ () => ({
+ siascanUrl,
+ }),
+ [siascanUrl]
+ )
+
return {
dataState,
error: response.error,
datasetCount: datasetFiltered?.length || 0,
columns: filteredTableColumns,
dataset: datasetFiltered,
+ cellContext,
lastIndex,
configurableColumns,
enabledColumns,
diff --git a/apps/walletd/contexts/addresses/types.ts b/apps/walletd/contexts/addresses/types.ts
index 07a6d0286..86a4102f5 100644
--- a/apps/walletd/contexts/addresses/types.ts
+++ b/apps/walletd/contexts/addresses/types.ts
@@ -1,3 +1,7 @@
+export type CellContext = {
+ siascanUrl: string
+}
+
export type AddressData = {
id: string
address: string
diff --git a/apps/walletd/contexts/events/columns.tsx b/apps/walletd/contexts/events/columns.tsx
index ea4f6479f..c38bcbc0e 100644
--- a/apps/walletd/contexts/events/columns.tsx
+++ b/apps/walletd/contexts/events/columns.tsx
@@ -7,9 +7,9 @@ import {
ValueSf,
} from '@siafoundation/design-system'
import { humanDate } from '@siafoundation/sia-js'
-import { EventData, TableColumnId } from './types'
+import { CellContext, EventData, TableColumnId } from './types'
-type EventsTableColumn = TableColumn & {
+type EventsTableColumn = TableColumn & {
fixed?: boolean
category?: string
}
@@ -27,9 +27,15 @@ export const columns: EventsTableColumn[] = [
// label: 'ID',
// category: 'general',
// fixed: true,
- // render: ({ data: { id } }) => {
+ // render: ({ data: { id }, context }) => {
// return (
- //
+ //
// )
// },
// },
@@ -37,12 +43,18 @@ export const columns: EventsTableColumn[] = [
id: 'transactionId',
label: 'transaction ID',
category: 'general',
- render: ({ data: { transactionId } }) => {
+ render: ({ data: { transactionId }, context }) => {
if (!transactionId) {
return null
}
return (
-
+
)
},
},
@@ -135,11 +147,18 @@ export const columns: EventsTableColumn[] = [
id: 'contractId',
label: 'contract ID',
category: 'general',
- render: ({ data: { contractId } }) => {
+ render: ({ data: { contractId }, context }) => {
if (!contractId) {
return null
}
- return
+ return (
+
+ )
},
},
]
diff --git a/apps/walletd/contexts/events/index.tsx b/apps/walletd/contexts/events/index.tsx
index 84f3a611a..32daaa765 100644
--- a/apps/walletd/contexts/events/index.tsx
+++ b/apps/walletd/contexts/events/index.tsx
@@ -16,6 +16,7 @@ import {
useMemo,
} from 'react'
import {
+ CellContext,
EventData,
columnsDefaultVisible,
defaultSortField,
@@ -24,6 +25,7 @@ import {
import { columns } from './columns'
import { useRouter } from 'next/router'
import BigNumber from 'bignumber.js'
+import { useSiascanUrl } from '../../hooks/useSiascanUrl'
const defaultLimit = 100
@@ -181,12 +183,21 @@ export function useEventsMain() {
const dataState = useDatasetEmptyState(dataset, isValidating, error, filters)
+ const siascanUrl = useSiascanUrl()
+ const cellContext = useMemo(
+ () => ({
+ siascanUrl,
+ }),
+ [siascanUrl]
+ )
+
return {
dataState,
error: responseEvents.error,
pageCount: dataset?.length || 0,
columns: filteredTableColumns,
dataset,
+ cellContext,
configurableColumns,
enabledColumns,
sortableColumns,
diff --git a/apps/walletd/contexts/events/types.ts b/apps/walletd/contexts/events/types.ts
index 9038cb5dd..b6b617ad5 100644
--- a/apps/walletd/contexts/events/types.ts
+++ b/apps/walletd/contexts/events/types.ts
@@ -1,5 +1,9 @@
import BigNumber from 'bignumber.js'
+export type CellContext = {
+ siascanUrl: string
+}
+
export type EventData = {
id: string
transactionId?: string
diff --git a/apps/walletd/hooks/useSiascanUrl.ts b/apps/walletd/hooks/useSiascanUrl.ts
new file mode 100644
index 000000000..9abfb3ed4
--- /dev/null
+++ b/apps/walletd/hooks/useSiascanUrl.ts
@@ -0,0 +1,9 @@
+import { webLinks } from '@siafoundation/design-system'
+import { useConsensusNetwork } from '@siafoundation/react-walletd'
+
+export function useSiascanUrl() {
+ const network = useConsensusNetwork()
+ return network.data?.name === 'zen'
+ ? webLinks.explore.testnetZen
+ : webLinks.explore.mainnet
+}
diff --git a/libs/design-system/src/app/DaemonProfile/index.tsx b/libs/design-system/src/app/DaemonProfile/index.tsx
index fb952d436..86a9cd85b 100644
--- a/libs/design-system/src/app/DaemonProfile/index.tsx
+++ b/libs/design-system/src/app/DaemonProfile/index.tsx
@@ -34,6 +34,7 @@ export function DaemonProfile({
}
>
-
+
diff --git a/libs/design-system/src/components/ValueCopyable.tsx b/libs/design-system/src/components/ValueCopyable.tsx
index f39cbcdbc..a326c258f 100644
--- a/libs/design-system/src/components/ValueCopyable.tsx
+++ b/libs/design-system/src/components/ValueCopyable.tsx
@@ -3,11 +3,22 @@
import { Text } from '../core/Text'
import { Button } from '../core/Button'
import { Link } from '../core/Link'
-import { Copy16 } from '@siafoundation/react-icons'
+import { CaretDown16, Copy16, Launch16 } from '@siafoundation/react-icons'
import { copyToClipboard } from '../lib/clipboard'
import { stripPrefix } from '../lib/utils'
-import { EntityType, getEntityTypeLabel } from '../lib/entityTypes'
+import {
+ EntityType,
+ doesEntityHaveSiascanUrl,
+ getEntityDisplayLength,
+ getEntitySiascanUrl,
+ getEntityTypeCopyLabel,
+} from '../lib/entityTypes'
import { cx } from 'class-variance-authority'
+import {
+ DropdownMenu,
+ DropdownMenuItem,
+ DropdownMenuLeftSlot,
+} from '../core/DropdownMenu'
type Props = {
value: string
@@ -21,6 +32,7 @@ type Props = {
maxLength?: number
color?: React.ComponentProps
['color']
className?: string
+ siascanUrl?: string
}
export function ValueCopyable({
@@ -35,9 +47,10 @@ export function ValueCopyable({
weight,
color = 'contrast',
className,
+ siascanUrl,
}: Props) {
- const label = customLabel || getEntityTypeLabel(type)
- const maxLength = customMaxLength || (type === 'ip' ? 20 : 12)
+ const label = customLabel || getEntityTypeCopyLabel(type)
+ const maxLength = customMaxLength || getEntityDisplayLength(type)
const cleanValue = stripPrefix(value)
const renderValue = displayValue || cleanValue
@@ -71,19 +84,72 @@ export function ValueCopyable({
)}
-
+
)
}
+
+export function ValueContextMenu({
+ size,
+ cleanValue,
+ label,
+ siascanUrl,
+ type,
+}: {
+ cleanValue: string
+ type?: EntityType
+ label?: string
+ size?: React.ComponentProps
['size']
+ siascanUrl?: string
+}) {
+ return (
+
+
+
+ }
+ contentProps={{ align: 'end' }}
+ >
+ {
+ copyToClipboard(cleanValue, label)
+ }}
+ onClick={(e) => {
+ e.stopPropagation()
+ }}
+ >
+
+
+
+ Copy to clipboard
+
+ {siascanUrl && type && doesEntityHaveSiascanUrl(type) && (
+
+ {
+ e.stopPropagation()
+ }}
+ >
+
+
+
+ View on Siascan
+
+
+ )}
+
+ )
+}
diff --git a/libs/design-system/src/components/ValueMenu.tsx b/libs/design-system/src/components/ValueMenu.tsx
index d7c9a7dde..d61f000e2 100644
--- a/libs/design-system/src/components/ValueMenu.tsx
+++ b/libs/design-system/src/components/ValueMenu.tsx
@@ -1,7 +1,7 @@
import { Text } from '../core/Text'
import { Link } from '../core/Link'
import { stripPrefix } from '../lib/utils'
-import { EntityType } from '../lib/entityTypes'
+import { EntityType, getEntityDisplayLength } from '../lib/entityTypes'
import { cx } from 'class-variance-authority'
type Props = {
@@ -30,7 +30,7 @@ export function ValueMenu({
menu,
className,
}: Props) {
- const maxLength = customMaxLength || (type === 'ip' ? 20 : 12)
+ const maxLength = customMaxLength || getEntityDisplayLength(type)
const cleanValue = stripPrefix(value)
const renderValue = displayValue || cleanValue
diff --git a/libs/design-system/src/lib/entityTypes.ts b/libs/design-system/src/lib/entityTypes.ts
index e20dc0c1f..9f4dc4230 100644
--- a/libs/design-system/src/lib/entityTypes.ts
+++ b/libs/design-system/src/lib/entityTypes.ts
@@ -8,6 +8,8 @@ export type EntityType =
| 'output'
| 'address'
| 'ip'
+ | 'hostIp'
+ | 'hostPublicKey'
| 'contract'
export type TxType =
@@ -108,12 +110,25 @@ export function getTransactionType(
// return undefined
}
-const entityTypeMap: Record = {
+const entityLabels: Record = {
transaction: 'transaction',
contract: 'contract',
block: 'block',
output: 'output',
address: 'address',
+ hostIp: 'host',
+ hostPublicKey: 'host',
+ ip: 'IP',
+}
+
+const entityCopyLabels: Record = {
+ transaction: 'transaction ID',
+ contract: 'contract ID',
+ block: 'block',
+ output: 'output ID',
+ address: 'address',
+ hostIp: 'host address',
+ hostPublicKey: 'host public key',
ip: 'IP',
}
@@ -135,9 +150,53 @@ const txTypeMap: Record = {
}
export function getEntityTypeLabel(type?: EntityType): string | undefined {
- return type ? entityTypeMap[type] : undefined
+ return type ? entityLabels[type] : undefined
+}
+
+export function getEntityTypeCopyLabel(type?: EntityType): string | undefined {
+ return type ? entityCopyLabels[type] : undefined
}
export function getTxTypeLabel(type?: TxType): string | undefined {
return type ? txTypeMap[type] : undefined
}
+
+export function getEntityDisplayLength(type?: EntityType): number {
+ const longList: EntityType[] = ['ip', 'hostIp']
+ return type && longList.includes(type) ? 20 : 12
+}
+
+export function doesEntityHaveSiascanUrl(type?: EntityType) {
+ const includeList: EntityType[] = [
+ 'hostIp',
+ 'hostPublicKey',
+ 'contract',
+ 'address',
+ 'transaction',
+ 'block',
+ ]
+ return type && includeList.includes(type)
+}
+
+export function getEntitySiascanUrl(
+ baseUrl: string,
+ type: EntityType,
+ value: string
+) {
+ switch (type) {
+ case 'hostIp':
+ return `${baseUrl}/host/${value}`
+ case 'hostPublicKey':
+ return `${baseUrl}/host/${value}`
+ case 'contract':
+ return `${baseUrl}/contract/${value}`
+ case 'transaction':
+ return `${baseUrl}/tx/${value}`
+ case 'address':
+ return `${baseUrl}/address/${value}`
+ case 'block':
+ return `${baseUrl}/block/${value}`
+ default:
+ return ''
+ }
+}