From 2cf55b9ace66a3e62adb8117b80f6c48c6a79b25 Mon Sep 17 00:00:00 2001 From: Alex Freska Date: Fri, 9 Aug 2024 07:55:25 -0700 Subject: [PATCH] feat: hostd v2 changes --- .changeset/chatty-hats-flash.md | 5 + .changeset/famous-pears-melt.md | 7 + .changeset/kind-trains-bathe.md | 9 + .changeset/old-hounds-camp.md | 5 + .changeset/two-olives-whisper.md | 6 + apps/hostd-e2e/src/specs/volumes.spec.ts | 4 +- .../components/CmdRoot/WalletCmdGroup.tsx | 6 +- .../components/Config/AnnounceButton.tsx | 4 +- .../components/HostdTestnetWarningBanner.tsx | 8 +- apps/hostd/components/Profile/index.tsx | 24 ++- apps/hostd/components/Wallet/StateError.tsx | 15 ++ .../components/Wallet/StateNoneMatching.tsx | 15 ++ apps/hostd/components/Wallet/StateNoneYet.tsx | 15 ++ apps/hostd/components/Wallet/index.tsx | 52 +++-- apps/hostd/config/routes.ts | 4 +- apps/hostd/contexts/config/index.tsx | 4 +- apps/hostd/contexts/config/useForm.tsx | 4 +- apps/hostd/contexts/config/useOnValid.tsx | 12 +- apps/hostd/contexts/transactions/columns.tsx | 182 ++++++++++++++++++ apps/hostd/contexts/transactions/index.tsx | 175 +++++++++++------ apps/hostd/contexts/transactions/types.ts | 52 +++++ apps/hostd/hooks/useHostOSPathSeparator.ts | 4 +- apps/hostd/hooks/useSiascanUrl.ts | 6 +- apps/hostd/hooks/useSyncStatus.ts | 38 ++-- .../RenterdTransactionDetailsDialog.tsx | 15 +- apps/walletd/contexts/events/columns.tsx | 5 +- apps/walletd/contexts/events/index.tsx | 14 +- apps/walletd/contexts/events/types.ts | 4 +- .../src/app/TransactionDetailsDialog.tsx | 12 +- libs/hostd-js/src/api.ts | 70 ++++--- libs/hostd-react/src/api.ts | 78 ++++++-- libs/hostd-types/src/api.ts | 99 ++++++++-- libs/types/src/events.ts | 91 +++++++++ libs/types/src/index.ts | 1 + libs/units/package.json | 3 +- .../utils.ts => libs/units/src/events.ts | 8 +- libs/units/src/index.ts | 2 + .../units/src}/transactionValue.spec.ts | 16 +- .../units/src}/transactionValue.ts | 2 +- libs/walletd-types/src/api.ts | 9 +- libs/walletd-types/src/types.ts | 116 +---------- 41 files changed, 861 insertions(+), 340 deletions(-) create mode 100644 .changeset/chatty-hats-flash.md create mode 100644 .changeset/famous-pears-melt.md create mode 100644 .changeset/kind-trains-bathe.md create mode 100644 .changeset/old-hounds-camp.md create mode 100644 .changeset/two-olives-whisper.md create mode 100644 apps/hostd/components/Wallet/StateError.tsx create mode 100644 apps/hostd/components/Wallet/StateNoneMatching.tsx create mode 100644 apps/hostd/components/Wallet/StateNoneYet.tsx create mode 100644 apps/hostd/contexts/transactions/columns.tsx create mode 100644 apps/hostd/contexts/transactions/types.ts create mode 100644 libs/types/src/events.ts rename apps/walletd/contexts/events/utils.ts => libs/units/src/events.ts (80%) rename {apps/walletd/contexts/events => libs/units/src}/transactionValue.spec.ts (96%) rename {apps/walletd/contexts/events => libs/units/src}/transactionValue.ts (98%) diff --git a/.changeset/chatty-hats-flash.md b/.changeset/chatty-hats-flash.md new file mode 100644 index 000000000..ccba8de0e --- /dev/null +++ b/.changeset/chatty-hats-flash.md @@ -0,0 +1,5 @@ +--- +'@siafoundation/design-system': minor +--- + +The TransactionDetailsDialog now supports an Event type instead of txType. diff --git a/.changeset/famous-pears-melt.md b/.changeset/famous-pears-melt.md new file mode 100644 index 000000000..d37910885 --- /dev/null +++ b/.changeset/famous-pears-melt.md @@ -0,0 +1,7 @@ +--- +'@siafoundation/hostd-js': minor +'@siafoundation/hostd-react': minor +'@siafoundation/hostd-types': minor +--- + +Updated with v2 endpoints and data types. Closes https://github.com/SiaFoundation/hostd/issues/440 diff --git a/.changeset/kind-trains-bathe.md b/.changeset/kind-trains-bathe.md new file mode 100644 index 000000000..ca2e0d58a --- /dev/null +++ b/.changeset/kind-trains-bathe.md @@ -0,0 +1,9 @@ +--- +'@siafoundation/hostd-js': minor +'@siafoundation/hostd-react': minor +'@siafoundation/hostd-types': minor +'@siafoundation/types': minor +'@siafoundation/walletd-types': minor +--- + +Core Event types have been moved to the core types library. Closes https://github.com/SiaFoundation/hostd/issues/440 diff --git a/.changeset/old-hounds-camp.md b/.changeset/old-hounds-camp.md new file mode 100644 index 000000000..516bc4c9b --- /dev/null +++ b/.changeset/old-hounds-camp.md @@ -0,0 +1,5 @@ +--- +'hostd': minor +--- + +The app has been updated to use the new v2 endpoints and data types. Closes https://github.com/SiaFoundation/hostd/issues/440 diff --git a/.changeset/two-olives-whisper.md b/.changeset/two-olives-whisper.md new file mode 100644 index 000000000..335c746db --- /dev/null +++ b/.changeset/two-olives-whisper.md @@ -0,0 +1,6 @@ +--- +'walletd': minor +'@siafoundation/units': minor +--- + +Event and transaction utility methods have been moved to the units library. diff --git a/apps/hostd-e2e/src/specs/volumes.spec.ts b/apps/hostd-e2e/src/specs/volumes.spec.ts index a9f95a37b..05155aeb6 100644 --- a/apps/hostd-e2e/src/specs/volumes.spec.ts +++ b/apps/hostd-e2e/src/specs/volumes.spec.ts @@ -8,8 +8,8 @@ import { } from '../fixtures/volumes' test('can create and delete a volume', async ({ page }) => { - const name = 'my-new-bucket' - const path = '/tmp' + const name = 'my-new-volume' + const path = '/data' await login({ page }) await navigateToVolumes({ page }) await deleteVolumeIfExists(page, name, path) diff --git a/apps/hostd/components/CmdRoot/WalletCmdGroup.tsx b/apps/hostd/components/CmdRoot/WalletCmdGroup.tsx index cdf41cd8b..ea3decf25 100644 --- a/apps/hostd/components/CmdRoot/WalletCmdGroup.tsx +++ b/apps/hostd/components/CmdRoot/WalletCmdGroup.tsx @@ -3,7 +3,7 @@ import { routes } from '../../config/routes' import { useRouter } from 'next/router' import { useDialog } from '../../contexts/dialog' import { CommandGroup, CommandItemNav, CommandItemSearch } from './Item' -import { useStateHost } from '@siafoundation/hostd-react' +import { useWallet } from '@siafoundation/hostd-react' import { Page } from './types' const commandPage = { @@ -20,7 +20,7 @@ type Props = { export function WalletCmdGroup({ currentPage, parentPage, pushPage }: Props) { const { openDialog, closeDialog } = useDialog() const router = useRouter() - const state = useStateHost({ + const wallet = useWallet({ config: { swr: { revalidateOnFocus: false, @@ -81,7 +81,7 @@ export function WalletCmdGroup({ currentPage, parentPage, pushPage }: Props) { currentPage={currentPage} commandPage={commandPage} onSelect={() => { - copyToClipboard(state.data?.walletAddress, 'wallet address') + copyToClipboard(wallet.data?.address, 'wallet address') closeDialog() }} > diff --git a/apps/hostd/components/Config/AnnounceButton.tsx b/apps/hostd/components/Config/AnnounceButton.tsx index 847eaefdc..1b7db4420 100644 --- a/apps/hostd/components/Config/AnnounceButton.tsx +++ b/apps/hostd/components/Config/AnnounceButton.tsx @@ -10,7 +10,7 @@ import { useDialog } from '../../contexts/dialog' import { useSettings, useSettingsAnnounce, - useStateHost, + useHostState, useTxPoolFee, } from '@siafoundation/hostd-react' import { humanSiacoin } from '@siafoundation/units' @@ -21,7 +21,7 @@ export function AnnounceButton() { const { openConfirmDialog } = useDialog() const txpoolFee = useTxPoolFee() const settingsAnnounce = useSettingsAnnounce() - const host = useStateHost() + const host = useHostState() const settings = useSettings({ config: { swr: { diff --git a/apps/hostd/components/HostdTestnetWarningBanner.tsx b/apps/hostd/components/HostdTestnetWarningBanner.tsx index 4ea2ae6ea..cf2ab7c51 100644 --- a/apps/hostd/components/HostdTestnetWarningBanner.tsx +++ b/apps/hostd/components/HostdTestnetWarningBanner.tsx @@ -1,8 +1,8 @@ import { TestnetWarningBanner } from '@siafoundation/design-system' -import { useStateHost } from '@siafoundation/hostd-react' +import { useConsensusNetwork } from '@siafoundation/hostd-react' export function HostdTestnetWarningBanner() { - const host = useStateHost({ + const host = useConsensusNetwork({ config: { swr: { revalidateOnFocus: false, @@ -10,9 +10,9 @@ export function HostdTestnetWarningBanner() { }, }) - if (!host.data || host.data.network === 'Mainnet') { + if (!host.data || host.data.name === 'mainnet') { return null } - return + return } diff --git a/apps/hostd/components/Profile/index.tsx b/apps/hostd/components/Profile/index.tsx index 0cc2fe218..643273d68 100644 --- a/apps/hostd/components/Profile/index.tsx +++ b/apps/hostd/components/Profile/index.tsx @@ -7,8 +7,10 @@ import { } from '@siafoundation/design-system' import { useSettings, - useStateHost, + useHostState, useSyncerPeers, + useWallet, + useConsensusNetwork, } from '@siafoundation/hostd-react' import { useSyncStatus } from '../../hooks/useSyncStatus' import { useDialog } from '../../contexts/dialog' @@ -17,7 +19,21 @@ import { humanTime } from '@siafoundation/units' export function Profile() { const { openDialog } = useDialog() - const state = useStateHost({ + const state = useHostState({ + config: { + swr: { + revalidateOnFocus: false, + }, + }, + }) + const wallet = useWallet({ + config: { + swr: { + revalidateOnFocus: false, + }, + }, + }) + const network = useConsensusNetwork({ config: { swr: { revalidateOnFocus: false, @@ -94,7 +110,7 @@ export function Profile() { @@ -114,7 +130,7 @@ export function Profile() { Network
- {state.data?.network} + {network.data?.name}
diff --git a/apps/hostd/components/Wallet/StateError.tsx b/apps/hostd/components/Wallet/StateError.tsx new file mode 100644 index 000000000..e7e6d84fc --- /dev/null +++ b/apps/hostd/components/Wallet/StateError.tsx @@ -0,0 +1,15 @@ +import { Text } from '@siafoundation/design-system' +import { MisuseOutline32 } from '@siafoundation/react-icons' + +export function StateError() { + return ( +
+ + + + + Error fetching transactions. + +
+ ) +} diff --git a/apps/hostd/components/Wallet/StateNoneMatching.tsx b/apps/hostd/components/Wallet/StateNoneMatching.tsx new file mode 100644 index 000000000..db7256e7d --- /dev/null +++ b/apps/hostd/components/Wallet/StateNoneMatching.tsx @@ -0,0 +1,15 @@ +import { Text } from '@siafoundation/design-system' +import { Filter32 } from '@siafoundation/react-icons' + +export function StateNoneMatching() { + return ( +
+ + + + + No transactions matching filters. + +
+ ) +} diff --git a/apps/hostd/components/Wallet/StateNoneYet.tsx b/apps/hostd/components/Wallet/StateNoneYet.tsx new file mode 100644 index 000000000..3608574d9 --- /dev/null +++ b/apps/hostd/components/Wallet/StateNoneYet.tsx @@ -0,0 +1,15 @@ +import { Text } from '@siafoundation/design-system' +import { Money32 } from '@siafoundation/react-icons' + +export function StateNoneYet() { + return ( +
+ + + + + The wallet has no transactions yet. + +
+ ) +} diff --git a/apps/hostd/components/Wallet/index.tsx b/apps/hostd/components/Wallet/index.tsx index 63e5cea4d..03a1cd0bb 100644 --- a/apps/hostd/components/Wallet/index.tsx +++ b/apps/hostd/components/Wallet/index.tsx @@ -1,8 +1,7 @@ import { - EntityList, WalletLayoutActions, BalanceEvolution, - PaginatorUnknownTotal, + Table, } from '@siafoundation/design-system' import { useWallet } from '@siafoundation/hostd-react' import { useDialog } from '../../contexts/dialog' @@ -11,9 +10,11 @@ import BigNumber from 'bignumber.js' import { HostdSidenav } from '../HostdSidenav' import { HostdAuthedLayout } from '../HostdAuthedLayout' import { useSyncStatus } from '../../hooks/useSyncStatus' -import { EmptyState } from './EmptyState' import { useTransactions } from '../../contexts/transactions' import { WalletFilterBar } from './WalletFilterBar' +import { StateNoneMatching } from './StateNoneMatching' +import { StateNoneYet } from './StateNoneYet' +import { StateError } from './StateError' export function Wallet() { const { openDialog } = useDialog() @@ -21,8 +22,18 @@ export function Wallet() { const { isSynced, isWalletSynced, syncPercent, walletScanPercent } = useSyncStatus() - const { dataset, balances, metrics, offset, limit, dataState, pageCount } = - useTransactions() + const { + balances, + metrics, + dataset, + dataState, + columns, + cellContext, + sortableColumns, + sortDirection, + sortField, + toggleSort, + } = useTransactions() return ( } > -
+
{balances?.length && balances.find((b) => b.sc) ? ( ) : null} - } - actions={ - + emptyState={ + dataState === 'noneMatchingFilters' ? ( + + ) : dataState === 'noneYet' ? ( + + ) : dataState === 'error' ? ( + + ) : null } + pageSize={6} + data={dataset} + context={cellContext} + columns={columns} + sortableColumns={sortableColumns} + sortDirection={sortDirection} + sortField={sortField} + toggleSort={toggleSort} />
diff --git a/apps/hostd/config/routes.ts b/apps/hostd/config/routes.ts index e24d5eeb9..21b329a71 100644 --- a/apps/hostd/config/routes.ts +++ b/apps/hostd/config/routes.ts @@ -1,4 +1,4 @@ -import { stateHostRoute } from '@siafoundation/hostd-types' +import { hostStateRoute } from '@siafoundation/hostd-types' export const routes = { home: '/', @@ -28,4 +28,4 @@ export const routes = { login: '/login', } -export const connectivityRoute = stateHostRoute +export const connectivityRoute = hostStateRoute diff --git a/apps/hostd/contexts/config/index.tsx b/apps/hostd/contexts/config/index.tsx index bb5aebf05..db297921e 100644 --- a/apps/hostd/contexts/config/index.tsx +++ b/apps/hostd/contexts/config/index.tsx @@ -17,7 +17,7 @@ import { checkIfAnyResourcesErrored, } from './resources' import { useOnValid } from './useOnValid' -import { useStateHost } from '@siafoundation/hostd-react' +import { useHostState } from '@siafoundation/hostd-react' export function useConfigMain() { const { settings, settingsPinned, dynDNSCheck } = useResources() @@ -54,7 +54,7 @@ export function useConfigMain() { [resources] ) - const state = useStateHost() + const state = useHostState() const pinningEnabled = state.data?.explorer.enabled const revalidateAndResetForm = useCallback(async () => { const _settings = await settings.mutate() diff --git a/apps/hostd/contexts/config/useForm.tsx b/apps/hostd/contexts/config/useForm.tsx index 34454f476..36dd80139 100644 --- a/apps/hostd/contexts/config/useForm.tsx +++ b/apps/hostd/contexts/config/useForm.tsx @@ -4,7 +4,7 @@ import { useEffect, useMemo, useRef } from 'react' import { getFields } from './fields' import useLocalStorageState from 'use-local-storage-state' import { useSiaCentralExchangeRates } from '@siafoundation/sia-central-react' -import { useStateHost } from '@siafoundation/hostd-react' +import { useHostState } from '@siafoundation/hostd-react' import { useAutoCalculatedFields } from './useAutoCalculatedFields' export function useForm() { @@ -25,7 +25,7 @@ export function useForm() { }) const rates = useSiaCentralExchangeRates() - const state = useStateHost() + const state = useHostState() const pinningEnabled = state.data?.explorer.enabled // Field validation is only re-applied on re-mount, // so we pass a ref with latest data that can be used interally. diff --git a/apps/hostd/contexts/config/useOnValid.tsx b/apps/hostd/contexts/config/useOnValid.tsx index 6250f8e29..e7fbf9360 100644 --- a/apps/hostd/contexts/config/useOnValid.tsx +++ b/apps/hostd/contexts/config/useOnValid.tsx @@ -8,9 +8,9 @@ import { SettingsData } from './types' import { transformUpSettings, transformUpSettingsPinned } from './transform' import { Resources } from './resources' import { + useHostState, useSettingsPinnedUpdate, useSettingsUpdate, - useStateHost, } from '@siafoundation/hostd-react' export function useOnValid({ @@ -20,16 +20,15 @@ export function useOnValid({ resources: Resources revalidateAndResetForm: () => Promise }) { - const state = useStateHost() - const settingsUpdate = useSettingsUpdate() - const settingsPinnedUpdate = useSettingsPinnedUpdate() - const host = useStateHost({ + const state = useHostState({ config: { swr: { refreshInterval: minutesInMilliseconds(1), }, }, }) + const settingsUpdate = useSettingsUpdate() + const settingsPinnedUpdate = useSettingsPinnedUpdate() const onValid = useCallback( async (values: SettingsData) => { if (!resources) { @@ -60,7 +59,7 @@ export function useOnValid({ } const needsToAnnounce = - host.data?.lastAnnouncement?.address !== values.netAddress + state.data?.lastAnnouncement?.address !== values.netAddress if (needsToAnnounce) { triggerSuccessToast({ title: 'Settings have been saved', @@ -86,7 +85,6 @@ export function useOnValid({ settingsUpdate, settingsPinnedUpdate, revalidateAndResetForm, - host.data, state.data, ] ) diff --git a/apps/hostd/contexts/transactions/columns.tsx b/apps/hostd/contexts/transactions/columns.tsx new file mode 100644 index 000000000..a747ae2b8 --- /dev/null +++ b/apps/hostd/contexts/transactions/columns.tsx @@ -0,0 +1,182 @@ +import { + Text, + TableColumn, + ValueCopyable, + LoadingDots, + ValueScFiat, + ValueSf, + Tooltip, + Badge, +} from '@siafoundation/design-system' +import { humanDate, getEventLabel } from '@siafoundation/units' +import { CellContext, EventData, TableColumnId } from './types' +import { Locked16, Unlocked16 } from '@siafoundation/react-icons' + +type EventsTableColumn = TableColumn & { + fixed?: boolean + category?: string +} + +export const columns: EventsTableColumn[] = [ + // { + // id: 'actions', + // label: '', + // fixed: true, + // cellClassName: 'w-[50px] !pl-2 !pr-4 [&+*]:!pl-0', + // render: ({ data: { name } }) => null, + // }, + { + id: 'transactionId', + label: 'transaction ID', + category: 'general', + render: ({ data: { id }, context }) => { + if (!id) { + return null + } + return ( + + ) + }, + }, + { + id: 'type', + label: 'type', + category: 'general', + fixed: true, + render: ({ data: { type } }) => { + return {getEventLabel(type)} + }, + }, + { + id: 'height', + label: 'height', + category: 'general', + contentClassName: 'justify-end', + render: ({ data: { height, pending, maturityHeight, isMature } }) => { + if (pending) { + return ( + + + + ) + } + if (!height) { + return null + } + if (height && maturityHeight && maturityHeight > height) { + return ( + +
+
+ + {isMature ? : } + {maturityHeight.toLocaleString()} + +
+
+
+
+
+ + {height.toLocaleString()} + +
+
+ + ) + } + return ( + + {height.toLocaleString()} + + ) + }, + }, + { + id: 'timestamp', + label: 'timestamp', + category: 'general', + contentClassName: 'justify-end', + render: ({ data: { timestamp, pending } }) => { + if (pending) { + return ( + + + + ) + } + return ( + + {humanDate(timestamp, { timeStyle: 'short' })} + + ) + }, + }, + { + id: 'amount', + label: 'amount', + category: 'general', + contentClassName: 'w-[120px] justify-end', + render: ({ data: { amountSc, amountSf } }) => { + if (!amountSc) { + return null + } + return ( +
+ {!amountSc.isZero() && ( + + )} + {!!amountSf && } +
+ ) + }, + }, + { + id: 'fee', + label: 'fee', + category: 'general', + contentClassName: 'w-[120px] justify-end', + render: ({ data: { fee } }) => { + if (!fee) { + return null + } + return + }, + }, + { + id: 'contractId', + label: 'contract ID', + category: 'general', + render: ({ data: { contractId }, context }) => { + if (!contractId) { + return null + } + return ( + + ) + }, + }, +] diff --git a/apps/hostd/contexts/transactions/index.tsx b/apps/hostd/contexts/transactions/index.tsx index 6e17e556b..77bc88d94 100644 --- a/apps/hostd/contexts/transactions/index.tsx +++ b/apps/hostd/contexts/transactions/index.tsx @@ -1,24 +1,37 @@ import { TxType, daysInMilliseconds, - getTransactionType, useDatasetEmptyState, + useServerFilters, + useTableState, } from '@siafoundation/design-system' import { useMetricsPeriod, + useWalletEvents, useWalletPending, - useWalletTransactions, } from '@siafoundation/hostd-react' 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/types' import { defaultDatasetRefreshInterval } from '../../config/swr' +import { useSyncStatus } from '../../hooks/useSyncStatus' +import { columns } from './columns' +import { + calculateScValue, + getEventContractId, + getEventFee, +} from '@siafoundation/units' +import { + CellContext, + EventData, + columnsDefaultVisible, + defaultSortField, + sortOptions, +} from './types' const defaultLimit = 50 -const filters = [] export type TransactionData = { id: string @@ -39,7 +52,7 @@ function useTransactionsMain() { const router = useRouter() const limit = Number(router.query.limit || defaultLimit) const offset = Number(router.query.offset || 0) - const transactions = useWalletTransactions({ + const events = useWalletEvents({ params: { limit, offset, @@ -58,52 +71,90 @@ function useTransactionsMain() { }, }) - const { openDialog } = useDialog() - const siascanUrl = useSiascanUrl() + const { filters, setFilter, removeFilter, removeLastFilter, resetFilters } = + useServerFilters() - const dataset: TransactionData[] | null = useMemo(() => { - if (!pending.data || !transactions.data) { + const syncStatus = useSyncStatus() + const dataset = useMemo(() => { + if (!events.data || !pending.data) { return null } - return [ - ...(pending.data || []).map((t): TransactionData => { - const notRealTxn = t.source !== 'transaction' - return { - id: t.id, - type: 'transaction', - unconfirmed: true, - txType: getTransactionType(t.transaction, t.source), - hash: t.id, - inflow: t.inflow, - outflow: t.outflow, - sc: new BigNumber(t.inflow).minus(t.outflow), - siascanUrl: notRealTxn ? undefined : siascanUrl, - timestamp: new Date(t.timestamp).getTime(), - onClick: () => openDialog('transactionDetails', t.id), - raw: t.transaction, - } - }), - ...(transactions.data || []) - .map((t): TransactionData => { - const notRealTxn = t.source !== 'transaction' - return { - id: t.id, - type: 'transaction', - unconfirmed: false, - txType: getTransactionType(t.transaction, t.source), - hash: t.id, - inflow: t.inflow, - outflow: t.outflow, - sc: new BigNumber(t.inflow).minus(t.outflow), - siascanUrl: notRealTxn ? undefined : siascanUrl, - timestamp: new Date(t.timestamp).getTime(), - onClick: () => openDialog('transactionDetails', t.id), - raw: t.transaction, - } - }) - .sort((a, b) => (a.timestamp < b.timestamp ? 1 : -1)), - ] - }, [pending, transactions, openDialog, siascanUrl]) + const dataPending: EventData[] = pending.data.map((e) => { + const amountSc = calculateScValue(e) + const fee = getEventFee(e) + const event: EventData = { + id: e.id, + timestamp: 0, + pending: true, + type: e.type, + isMature: false, + amountSc, + fee, + } + return event + }) + const dataEvents: EventData[] = events.data.map((e) => { + const amountSc = calculateScValue(e) + const fee = getEventFee(e) + const contractId = getEventContractId(e) + const isMature = e.maturityHeight <= syncStatus.nodeBlockHeight + const res: EventData = { + id: e.id, + type: e.type, + timestamp: new Date(e.timestamp).getTime(), + maturityHeight: e.maturityHeight, + isMature, + height: e.index.height, + pending: false, + amountSc, + fee, + contractId, + } + return res + }) + return [...dataPending.reverse(), ...dataEvents] + }, [events.data, pending.data, syncStatus.nodeBlockHeight]) + + const { + configurableColumns, + enabledColumns, + sortableColumns, + toggleColumnVisibility, + setColumnsVisible, + setColumnsHidden, + toggleSort, + setSortDirection, + setSortField, + sortField, + sortDirection, + resetDefaultColumnVisibility, + } = useTableState('walletd/v0/events', { + columns, + columnsDefaultVisible, + sortOptions, + defaultSortField, + }) + + const filteredTableColumns = useMemo( + () => + columns.filter( + (column) => column.fixed || enabledColumns.includes(column.id) + ), + [enabledColumns] + ) + + const isValidating = events.isValidating || pending.isValidating + const error = events.error || pending.error + + const dataState = useDatasetEmptyState(dataset, isValidating, error, filters) + + const siascanUrl = useSiascanUrl() + const cellContext = useMemo( + () => ({ + siascanUrl, + }), + [siascanUrl] + ) const dayPeriods = 30 const start = useMemo(() => { @@ -132,14 +183,6 @@ function useTransactionsMain() { [metrics.data] ) - const error = transactions.error - const dataState = useDatasetEmptyState( - dataset, - transactions.isValidating, - error, - filters - ) - return { balances, metrics, @@ -149,6 +192,26 @@ function useTransactionsMain() { offset, limit, pageCount: dataset?.length || 0, + cellContext, + configurableColumns, + enabledColumns, + sortableColumns, + toggleColumnVisibility, + setColumnsVisible, + setColumnsHidden, + toggleSort, + setSortDirection, + setSortField, + sortField, + sortDirection, + resetDefaultColumnVisibility, + filters, + setFilter, + removeFilter, + removeLastFilter, + resetFilters, + filteredTableColumns, + columns, } } diff --git a/apps/hostd/contexts/transactions/types.ts b/apps/hostd/contexts/transactions/types.ts new file mode 100644 index 000000000..08492fad0 --- /dev/null +++ b/apps/hostd/contexts/transactions/types.ts @@ -0,0 +1,52 @@ +import { WalletEventType } from '@siafoundation/types' +import BigNumber from 'bignumber.js' + +export type CellContext = { + siascanUrl: string +} + +export type EventData = { + id: string + transactionId?: string + timestamp: number + height?: number + maturityHeight?: number + isMature?: boolean + pending: boolean + type: WalletEventType + fee?: BigNumber + amountSc?: BigNumber + amountSf?: number + contractId?: string + className?: string +} + +export type TableColumnId = + // | 'actions' + | 'transactionId' + | 'type' + | 'height' + | 'timestamp' + | 'amount' + | 'fee' + | 'transactionId' + | 'contractId' + +export const columnsDefaultVisible: TableColumnId[] = [ + 'transactionId', + 'type', + 'height', + 'timestamp', + 'amount', + 'fee', +] + +export type SortField = 'id' + +export const defaultSortField: SortField = 'id' + +export const sortOptions: { + id: SortField + label: string + category: string +}[] = [] diff --git a/apps/hostd/hooks/useHostOSPathSeparator.ts b/apps/hostd/hooks/useHostOSPathSeparator.ts index 89e69acbc..ed5de51ae 100644 --- a/apps/hostd/hooks/useHostOSPathSeparator.ts +++ b/apps/hostd/hooks/useHostOSPathSeparator.ts @@ -1,7 +1,7 @@ -import { useStateHost } from '@siafoundation/hostd-react' +import { useHostState } from '@siafoundation/hostd-react' export function useHostOSPathSeparator() { - const state = useStateHost({ + const state = useHostState({ config: { swr: { revalidateOnFocus: false, diff --git a/apps/hostd/hooks/useSiascanUrl.ts b/apps/hostd/hooks/useSiascanUrl.ts index 92ce44e0c..059255883 100644 --- a/apps/hostd/hooks/useSiascanUrl.ts +++ b/apps/hostd/hooks/useSiascanUrl.ts @@ -1,9 +1,9 @@ import { webLinks } from '@siafoundation/design-system' -import { useStateHost } from '@siafoundation/hostd-react' +import { useConsensusNetwork } from '@siafoundation/hostd-react' export function useSiascanUrl() { - const state = useStateHost() - return state.data?.network === 'Zen Testnet' + const state = useConsensusNetwork() + return state.data?.name === 'zen' ? webLinks.explore.testnetZen : webLinks.explore.mainnet } diff --git a/apps/hostd/hooks/useSyncStatus.ts b/apps/hostd/hooks/useSyncStatus.ts index 8b18be4d6..e576cf0f9 100644 --- a/apps/hostd/hooks/useSyncStatus.ts +++ b/apps/hostd/hooks/useSyncStatus.ts @@ -1,26 +1,29 @@ import { useAppSettings } from '@siafoundation/react-core' import { useEstimatedNetworkBlockHeight, - useStateConsensus, - useWallet, + useConsensusTipState, + useIndexTip, } from '@siafoundation/hostd-react' +import { hoursInMilliseconds } from '@siafoundation/design-system' export function useSyncStatus() { const { isUnlockedAndAuthedRoute } = useAppSettings() - const state = useStateConsensus({ + + const state = useConsensusTipState({ config: { swr: { - refreshInterval: (data) => (data?.synced ? 60_000 : 10_000), + refreshInterval: (data) => (getIsSynced(data) ? 60_000 : 10_000), }, }, }) + const isSynced = getIsSynced(state.data) const estimatedBlockHeight = useEstimatedNetworkBlockHeight() - const nodeBlockHeight = state.data ? state.data?.chainIndex.height : 0 - const wallet = useWallet({ + const nodeBlockHeight = state.data ? state.data?.index.height : 0 + const scan = useIndexTip({ config: { swr: { refreshInterval: (data) => - data?.scanHeight >= nodeBlockHeight ? 60_000 : 10_000, + data?.height >= nodeBlockHeight ? 60_000 : 10_000, }, }, }) @@ -33,11 +36,11 @@ export function useSyncStatus() { : 0 const walletScanPercent = - isUnlockedAndAuthedRoute && nodeBlockHeight && wallet.data + isUnlockedAndAuthedRoute && nodeBlockHeight && scan.data ? Number( - ( - Math.min(wallet.data.scanHeight / estimatedBlockHeight, 1) * 100 - ).toFixed(1) + (Math.min(scan.data.height / estimatedBlockHeight, 1) * 100).toFixed( + 1 + ) ) : 0 @@ -52,9 +55,8 @@ export function useSyncStatus() { : false return { - isSynced: state.data?.synced, - isWalletSynced: - state.data?.synced && wallet.data?.scanHeight >= nodeBlockHeight - 1, + isSynced, + isWalletSynced: isSynced && scan.data?.height >= nodeBlockHeight - 1, nodeBlockHeight, estimatedBlockHeight, syncPercent, @@ -63,3 +65,11 @@ export function useSyncStatus() { firstTimeSyncing, } } + +function getIsSynced(data?: { prevTimestamps: string[] }) { + // Last block is greater than 12 hours ago + return data?.prevTimestamps[0] + ? new Date(data?.prevTimestamps[0]).getTime() > + Date.now() - hoursInMilliseconds(12) + : false +} diff --git a/apps/renterd/dialogs/RenterdTransactionDetailsDialog.tsx b/apps/renterd/dialogs/RenterdTransactionDetailsDialog.tsx index 917887560..4fe6ae20f 100644 --- a/apps/renterd/dialogs/RenterdTransactionDetailsDialog.tsx +++ b/apps/renterd/dialogs/RenterdTransactionDetailsDialog.tsx @@ -1,10 +1,8 @@ import { useMemo } from 'react' import { TransactionDetailsDialog } from '@siafoundation/design-system' import { useDialog } from '../contexts/dialog' -import { - TransactionDataConfirmed, - useTransactions, -} from '../contexts/transactions' +import { useTransactions } from '../contexts/transactions' +import { omit } from '@technically/lodash' export function RenterdTransactionDetailsDialog() { const { id, dialog, onOpenChange } = useDialog() @@ -12,13 +10,18 @@ export function RenterdTransactionDetailsDialog() { // TODO: add transaction endpoint const { dataset } = useTransactions() const transaction = useMemo(() => { - return dataset?.find((t) => t['hash'] === id) + const txn = dataset?.find((t) => t['hash'] === id) + if (!txn) { + return null + } + // The incoming type is not a WalletEventType + return omit(txn, 'type') }, [dataset, id]) return ( diff --git a/apps/walletd/contexts/events/columns.tsx b/apps/walletd/contexts/events/columns.tsx index 179231228..5ab7f9d9b 100644 --- a/apps/walletd/contexts/events/columns.tsx +++ b/apps/walletd/contexts/events/columns.tsx @@ -8,10 +8,9 @@ import { Tooltip, Badge, } from '@siafoundation/design-system' -import { humanDate } from '@siafoundation/units' +import { getEventLabel, humanDate } from '@siafoundation/units' import { CellContext, EventData, TableColumnId } from './types' import { Locked16, Unlocked16 } from '@siafoundation/react-icons' -import { eventTypeToLabel } from './utils' type EventsTableColumn = TableColumn & { fixed?: boolean @@ -51,7 +50,7 @@ export const columns: EventsTableColumn[] = [ category: 'general', fixed: true, render: ({ data: { type } }) => { - return {eventTypeToLabel(type)} + return {getEventLabel(type)} }, }, { diff --git a/apps/walletd/contexts/events/index.tsx b/apps/walletd/contexts/events/index.tsx index fb36de4ac..cb5499027 100644 --- a/apps/walletd/contexts/events/index.tsx +++ b/apps/walletd/contexts/events/index.tsx @@ -7,6 +7,12 @@ import { useWalletEvents, useWalletEventsUnconfirmed, } from '@siafoundation/walletd-react' +import { + calculateScValue, + calculateSfValue, + getEventContractId, + getEventFee, +} from '@siafoundation/units' import { createContext, useContext, useMemo } from 'react' import { CellContext, @@ -20,8 +26,6 @@ import { useRouter } from 'next/router' import { useSiascanUrl } from '../../hooks/useSiascanUrl' import { defaultDatasetRefreshInterval } from '../../config/swr' import { useSyncStatus } from '../../hooks/useSyncStatus' -import { getContractId, getFee } from './utils' -import { calculateScValue, calculateSfValue } from './transactionValue' const defaultLimit = 100 @@ -65,7 +69,7 @@ export function useEventsMain() { const dataTxPool: EventData[] = responseTxPool.data.map((e) => { const amountSc = calculateScValue(e) const amountSf = calculateSfValue(e) - const fee = getFee(e) + const fee = getEventFee(e) const event: EventData = { id: e.id, timestamp: 0, @@ -81,8 +85,8 @@ export function useEventsMain() { const dataEvents: EventData[] = responseEvents.data.map((e) => { const amountSc = calculateScValue(e) const amountSf = calculateSfValue(e) - const fee = getFee(e) - const contractId = getContractId(e) + const fee = getEventFee(e) + const contractId = getEventContractId(e) const isMature = e.maturityHeight <= syncStatus.nodeBlockHeight const res: EventData = { id: e.id, diff --git a/apps/walletd/contexts/events/types.ts b/apps/walletd/contexts/events/types.ts index 0c9b9abb9..66d11afdd 100644 --- a/apps/walletd/contexts/events/types.ts +++ b/apps/walletd/contexts/events/types.ts @@ -1,4 +1,4 @@ -import { WalletEvent } from '@siafoundation/walletd-types' +import { WalletEventType } from '@siafoundation/types' import BigNumber from 'bignumber.js' export type CellContext = { @@ -13,7 +13,7 @@ export type EventData = { maturityHeight?: number isMature?: boolean pending: boolean - type: WalletEvent['type'] + type: WalletEventType fee?: BigNumber amountSc?: BigNumber amountSf?: number diff --git a/libs/design-system/src/app/TransactionDetailsDialog.tsx b/libs/design-system/src/app/TransactionDetailsDialog.tsx index 14b65303e..4f1be70ba 100644 --- a/libs/design-system/src/app/TransactionDetailsDialog.tsx +++ b/libs/design-system/src/app/TransactionDetailsDialog.tsx @@ -1,18 +1,20 @@ import { Codeblock } from '../core/Codeblock' import { Text } from '../core/Text' import { ValueSc } from '../components/ValueSc' -import { humanDate } from '@siafoundation/units' +import { getEventLabel, humanDate } from '@siafoundation/units' import BigNumber from 'bignumber.js' import { Dialog } from '../core/Dialog' import { getTitleId } from '../lib/utils' -import { Transaction } from '@siafoundation/types' +import { Transaction, WalletEventType } from '@siafoundation/types' import { getTxTypeLabel, TxType } from '../lib/entityTypes' import { upperFirst } from '@technically/lodash' type Props = { id: string transaction?: { - txType: TxType + // deprecated + txType?: TxType + type?: WalletEventType inflow?: string outflow?: string timestamp?: string | number @@ -33,7 +35,9 @@ export function TransactionDetailsDialog({ return ( (axios, 'get', stateHostRoute), - stateConsensus: buildRequestHandler< - StateConsensusParams, - StateConsensusPayload, - StateConsensusResponse - >(axios, 'get', stateConsensusRoute), + HostStateParams, + HostStatePayload, + HostStateResponse + >(axios, 'get', hostStateRoute), + consensusTip: buildRequestHandler< + ConsensusTipParams, + ConsensusTipPayload, + ConsensusTipResponse + >(axios, 'get', consensusTipRoute), + consensusTipState: buildRequestHandler< + ConsensusTipStateParams, + ConsensusTipStatePayload, + ConsensusTipStateResponse + >(axios, 'get', consensusTipStateRoute), + indexTip: buildRequestHandler< + IndexTipParams, + IndexTipPayload, + IndexTipResponse + >(axios, 'get', indexTipRoute), syncerPeers: buildRequestHandler< SyncerPeersParams, SyncerPeersPayload, @@ -141,16 +159,16 @@ export function Hostd({ api, password }: { api: string; password?: string }) { 'get', walletRoute ), - walletTransactions: buildRequestHandler< - WalletTransactionsParams, - WalletTransactionsPayload, - WalletTransactionsResponse - >(axios, 'get', walletTransactionsRoute), walletPending: buildRequestHandler< WalletPendingParams, WalletPendingPayload, WalletPendingResponse >(axios, 'get', walletPendingRoute), + walletEvents: buildRequestHandler< + WalletEventsParams, + WalletEventsPayload, + WalletEventsResponse + >(axios, 'get', walletEventsRoute), walletSend: buildRequestHandler< WalletSendParams, WalletSendPayload, diff --git a/libs/hostd-react/src/api.ts b/libs/hostd-react/src/api.ts index 300d51239..d454e3003 100644 --- a/libs/hostd-react/src/api.ts +++ b/libs/hostd-react/src/api.ts @@ -48,10 +48,8 @@ import { SettingsUpdateParams, SettingsUpdatePayload, SettingsUpdateResponse, - StateConsensusParams, - StateConsensusResponse, - StateHostParams, - StateHostResponse, + HostStateParams, + HostStateResponse, SyncerConnectParams, SyncerConnectPayload, SyncerConnectResponse, @@ -90,8 +88,6 @@ import { WalletSendParams, WalletSendPayload, WalletSendResponse, - WalletTransactionsParams, - WalletTransactionsResponse, alertsDismissRoute, alertsRoute, contractsIdIntegrityRoute, @@ -102,8 +98,7 @@ import { settingsAnnounceRoute, settingsDdnsUpdateRoute, settingsRoute, - stateConsensusRoute, - stateHostRoute, + hostStateRoute, syncerPeersRoute, systemDirRoute, tpoolFeeRoute, @@ -114,31 +109,72 @@ import { walletPendingRoute, walletRoute, walletSendRoute, - walletTransactionsRoute, + ConsensusNetworkParams, + ConsensusNetworkResponse, + consensusNetworkRoute, + IndexTipParams, + IndexTipResponse, + indexTipRoute, + ConsensusTipParams, + ConsensusTipResponse, + ConsensusTipStateResponse, + ConsensusTipStateParams, + consensusTipStateRoute, + consensusTipRoute, + WalletEventsParams, + WalletEventsResponse, + walletEventsRoute, } from '@siafoundation/hostd-types' // state -export function useStateHost( - args?: HookArgsSwr +export function useHostState( + args?: HookArgsSwr ) { return useGetSwr({ ...args, - route: stateHostRoute, + route: hostStateRoute, }) } -export function useStateConsensus( - args?: HookArgsSwr +export function useConsensusTip( + args?: HookArgsSwr ) { return useGetSwr({ ...args, - route: stateConsensusRoute, + route: consensusTipRoute, + }) +} + +export function useConsensusTipState( + args?: HookArgsSwr +) { + return useGetSwr({ + ...args, + route: consensusTipStateRoute, + }) +} + +export function useConsensusNetwork( + args?: HookArgsSwr +) { + return useGetSwr({ + ...args, + route: consensusNetworkRoute, + }) +} + +export function useIndexTip( + args?: HookArgsSwr +) { + return useGetSwr({ + ...args, + route: indexTipRoute, }) } export function useEstimatedNetworkBlockHeight(): number { - const state = useStateHost({ + const state = useConsensusNetwork({ config: { swr: { revalidateOnFocus: false, @@ -148,7 +184,7 @@ export function useEstimatedNetworkBlockHeight(): number { const res = useSWR( state, () => { - if (state.data?.network === 'Zen Testnet') { + if (state.data?.name === 'zen') { return getTestnetZenBlockHeight() } return getMainnetBlockHeight() @@ -199,12 +235,12 @@ export function useWallet(args?: HookArgsSwr) { }) } -export function useWalletTransactions( - args?: HookArgsSwr +export function useWalletEvents( + args?: HookArgsSwr ) { return useGetSwr({ ...args, - route: walletTransactionsRoute, + route: walletEventsRoute, }) } @@ -309,7 +345,7 @@ export function useSettingsUpdate( ) { return usePatchFunc({ ...args, route: settingsRoute }, async (mutate) => { await mutate((key) => { - return key.startsWith(settingsRoute) || key.startsWith(stateHostRoute) + return key.startsWith(settingsRoute) || key.startsWith(hostStateRoute) }) }) } diff --git a/libs/hostd-types/src/api.ts b/libs/hostd-types/src/api.ts index 0fd443fb0..7917d4ec3 100644 --- a/libs/hostd-types/src/api.ts +++ b/libs/hostd-types/src/api.ts @@ -4,14 +4,18 @@ import { Currency, TransactionID, ChainIndex, + WalletEvent, } from '@siafoundation/types' import { Contract, ContractStatus, WalletTransaction } from './types' -export const stateHostRoute = '/state/host' -export const stateConsensusRoute = '/state/consensus' +export const hostStateRoute = '/state' +export const consensusNetworkRoute = '/consensus/network' +export const consensusTipRoute = '/consensus/tip' +export const consensusTipStateRoute = '/consensus/tipstate' +export const indexTipRoute = '/index/tip' export const syncerPeersRoute = '/syncer/peers' export const walletRoute = '/wallet' -export const walletTransactionsRoute = '/wallet/transactions' +export const walletEventsRoute = '/wallet/events' export const walletPendingRoute = '/wallet/pending' export const walletSendRoute = '/wallet/send' export const tpoolFeeRoute = '/tpool/fee' @@ -33,13 +37,11 @@ export const alertsDismissRoute = '/alerts/dismiss' // state -export type StateHostParams = void -export type StateHostPayload = void -export type StateHostResponse = { +export type HostStateParams = void +export type HostStatePayload = void +export type HostStateResponse = { name?: string publicKey: string - walletAddress: string - network: 'Mainnet' | 'Zen Testnet' version: string commit: string os: string @@ -56,16 +58,75 @@ export type StateHostResponse = { } } -export type StateConsensusParams = void -export type StateConsensusPayload = void -export type StateConsensusResponse = { - chainIndex: { +export type ConsensusNetworkParams = void +export type ConsensusNetworkPayload = void +export type ConsensusNetworkResponse = { + name: 'mainnet' | 'zen' + initialCoinbase: string + minimumCoinbase: string + initialTarget: string + hardforkDevAddr: { height: number - id: string + oldAddress: string + newAddress: string } - synced: boolean + hardforkTax: { + height: number + } + hardforkStorageProof: { + height: number + } + hardforkOak: { + height: number + fixHeight: number + genesisTimestamp: string + } + hardforkASIC: { + height: number + oakTime: number + oakTarget: string + } + hardforkFoundation: { + height: number + primaryAddress: string + failsafeAddress: string + } + hardforkV2: { + allowHeight: number + requireHeight: number + } +} + +export type ConsensusTipParams = void +export type ConsensusTipPayload = void +export type ConsensusTipResponse = ChainIndex + +export type ConsensusTipStateParams = void +export type ConsensusTipStatePayload = void +export type ConsensusTipStateResponse = { + index: ChainIndex + prevTimestamps: string[] + depth: string + childTarget: string + siafundPool: string + oakTime: number + oakTarget: string + foundationPrimaryAddress: string + foundationFailsafeAddress: string + totalWork: string + difficulty: string + oakWork: string + elements: { + numLeaves: number + trees: string[] + } + attestations: number } +export type IndexTipParams = void +export type IndexTipPayload = void +export type IndexTipResponse = ChainIndex + // syncer type Peer = { @@ -86,20 +147,24 @@ export type SyncerConnectResponse = never export type WalletParams = void export type WalletPayload = void export type WalletResponse = { - scanHeight: number - address: string spendable: string confirmed: string unconfirmed: string + immature: string + address: string } export type WalletTransactionsParams = { limit?: number; offset?: number } export type WalletTransactionsPayload = void export type WalletTransactionsResponse = WalletTransaction[] +export type WalletEventsParams = { limit?: number; offset?: number } +export type WalletEventsPayload = void +export type WalletEventsResponse = WalletEvent[] + export type WalletPendingParams = void export type WalletPendingPayload = void -export type WalletPendingResponse = WalletTransaction[] +export type WalletPendingResponse = WalletEvent[] export type WalletSendParams = void export type WalletSendPayload = { diff --git a/libs/types/src/events.ts b/libs/types/src/events.ts new file mode 100644 index 000000000..d2787a474 --- /dev/null +++ b/libs/types/src/events.ts @@ -0,0 +1,91 @@ +import { + ChainIndex, + Transaction, + SiacoinElement, + SiafundElement, + FileContractElement, + Hash256, + Address, +} from './core' +import { + V2Transaction, + V2FileContractResolutionType, + V2FileContractElement, +} from './v2' + +export type UnconfirmedChainIndex = { + height: number +} + +export type WalletEventBase = { + id: Hash256 + timestamp: string + index: ChainIndex | UnconfirmedChainIndex + maturityHeight: number + relevant: Address[] +} + +export type WalletEventTransactionV1 = WalletEventBase & { + type: 'v1Transaction' + data: { + transaction: Transaction + spentSiacoinElements?: SiacoinElement[] + spentSiafundElements?: SiafundElement[] + } +} + +export type WalletEventTransactionV2 = WalletEventBase & { + type: 'v2Transaction' + data: V2Transaction +} + +export type WalletEventContractResolutionV1 = WalletEventBase & { + type: 'v1ContractResolution' + data: { + parent: FileContractElement + siacoinElement: SiacoinElement + missed: boolean + } +} + +export type WalletEventContractResolutionV2 = WalletEventBase & { + type: 'v2ContractResolution' + data: { + parent: V2FileContractElement + resolution: V2FileContractResolutionType + siacoinElement: SiacoinElement + missed: boolean + } +} + +export type WalletEventMinerPayout = WalletEventBase & { + type: 'miner' + data: { + siacoinElement: SiacoinElement + } +} + +export type WalletEventSiafundClaim = WalletEventBase & { + type: 'siafundClaim' + data: { + siacoinElement: SiacoinElement + } +} + +export type WalletEventFoundationSubsidy = WalletEventBase & { + type: 'foundation' + data: { + siacoinElement: SiacoinElement + } +} + +export type WalletEvent = + | WalletEventTransactionV1 + | WalletEventTransactionV2 + | WalletEventContractResolutionV1 + | WalletEventContractResolutionV2 + | WalletEventMinerPayout + | WalletEventFoundationSubsidy + | WalletEventSiafundClaim + +export type WalletEventType = WalletEvent['type'] diff --git a/libs/types/src/index.ts b/libs/types/src/index.ts index afc97d4a9..0957f1243 100644 --- a/libs/types/src/index.ts +++ b/libs/types/src/index.ts @@ -1,2 +1,3 @@ export * from './core' +export * from './events' export * from './v2' diff --git a/libs/units/package.json b/libs/units/package.json index c1b4cfb9e..df3cdb440 100644 --- a/libs/units/package.json +++ b/libs/units/package.json @@ -8,7 +8,8 @@ "bignumber.js": "^9.0.2", "@technically/lodash": "^4.17.0", "blakejs": "^1.2.1", - "@siafoundation/sia-central-types": "0.1.1" + "@siafoundation/sia-central-types": "0.1.1", + "@siafoundation/types": "0.4.0" }, "types": "./src/index.d.ts" } diff --git a/apps/walletd/contexts/events/utils.ts b/libs/units/src/events.ts similarity index 80% rename from apps/walletd/contexts/events/utils.ts rename to libs/units/src/events.ts index 52dd12c49..8673a3aca 100644 --- a/apps/walletd/contexts/events/utils.ts +++ b/libs/units/src/events.ts @@ -1,7 +1,7 @@ import BigNumber from 'bignumber.js' -import { WalletEvent } from '@siafoundation/walletd-types' +import { WalletEvent, WalletEventType } from '@siafoundation/types' -export function getFee(e: WalletEvent) { +export function getEventFee(e: WalletEvent) { if (e.type === 'v2Transaction') { return new BigNumber(e.data.minerFee) } @@ -10,7 +10,7 @@ export function getFee(e: WalletEvent) { : undefined } -export function getContractId(e: WalletEvent) { +export function getEventContractId(e: WalletEvent) { if (e.type === 'v1ContractResolution') { return e.data.parent.id } @@ -20,7 +20,7 @@ export function getContractId(e: WalletEvent) { return undefined } -export function eventTypeToLabel(type: WalletEvent['type']) { +export function getEventLabel(type: WalletEventType) { if (type === 'v1Transaction') { return 'v1 transaction' } diff --git a/libs/units/src/index.ts b/libs/units/src/index.ts index 138440db2..823a94ab6 100644 --- a/libs/units/src/index.ts +++ b/libs/units/src/index.ts @@ -5,3 +5,5 @@ export * from './currency' export * from './blockTime' export * from './bytes' export * from './valuePer' +export * from './events' +export * from './transactionValue' diff --git a/apps/walletd/contexts/events/transactionValue.spec.ts b/libs/units/src/transactionValue.spec.ts similarity index 96% rename from apps/walletd/contexts/events/transactionValue.spec.ts rename to libs/units/src/transactionValue.spec.ts index b02028475..b3e5ff0ef 100644 --- a/apps/walletd/contexts/events/transactionValue.spec.ts +++ b/libs/units/src/transactionValue.spec.ts @@ -1,6 +1,6 @@ -import { WalletEvent } from '@siafoundation/walletd-types' import { calculateScValue, calculateSfValue } from './transactionValue' -import { toHastings } from '@siafoundation/units' +import { toHastings } from './currency' +import { WalletEvent } from '@siafoundation/types' test('v1TxnCalculateScValue', () => { const e: WalletEvent = { @@ -62,7 +62,7 @@ test('v1TxnCalculateScValue', () => { }, }, ], - spentSiafundElements: null, + spentSiafundElements: undefined, }, relevant: ['addr:1', 'addr:2'], } @@ -95,8 +95,8 @@ test('v1TxnCalculateScValue when no relevant / spentSiacoinElements is null', () minerFees: ['1000000000000000000000000'], signatures: [], }, - spentSiacoinElements: null, - spentSiafundElements: null, + spentSiacoinElements: undefined, + spentSiafundElements: undefined, }, relevant: ['addr:1'], } @@ -193,7 +193,7 @@ test('v1TxnCalculateSfValue', () => { minerFees: ['1000000000000000000000000'], signatures: [], }, - spentSiacoinElements: null, + spentSiacoinElements: undefined, spentSiafundElements: [ { id: 'id-1', @@ -258,8 +258,8 @@ test('v1TxnCalculateSfValue when no relevant / spentSiafundElements is null', () minerFees: ['1000000000000000000000000'], signatures: [], }, - spentSiacoinElements: null, - spentSiafundElements: null, + spentSiacoinElements: undefined, + spentSiafundElements: undefined, }, relevant: ['addr:1'], } diff --git a/apps/walletd/contexts/events/transactionValue.ts b/libs/units/src/transactionValue.ts similarity index 98% rename from apps/walletd/contexts/events/transactionValue.ts rename to libs/units/src/transactionValue.ts index 0d9e7ca81..27d1a6add 100644 --- a/apps/walletd/contexts/events/transactionValue.ts +++ b/libs/units/src/transactionValue.ts @@ -3,7 +3,7 @@ import { WalletEvent, WalletEventTransactionV1, WalletEventTransactionV2, -} from '@siafoundation/walletd-types' +} from '@siafoundation/types' export function calculateScValue(e: WalletEvent) { if (e.type === 'v2Transaction') { diff --git a/libs/walletd-types/src/api.ts b/libs/walletd-types/src/api.ts index bff9cb54c..108b01560 100644 --- a/libs/walletd-types/src/api.ts +++ b/libs/walletd-types/src/api.ts @@ -10,14 +10,9 @@ import { SiafundElement, Transaction, V2Transaction, -} from '@siafoundation/types' -import { WalletEvent, - GatewayPeer, - Wallet, - WalletAddress, - WalletMetadata, -} from './types' +} from '@siafoundation/types' +import { GatewayPeer, Wallet, WalletAddress, WalletMetadata } from './types' export const stateRoute = '/state' export const consensusTipRoute = '/consensus/tip' diff --git a/libs/walletd-types/src/types.ts b/libs/walletd-types/src/types.ts index 911b2c0bd..200666e8b 100644 --- a/libs/walletd-types/src/types.ts +++ b/libs/walletd-types/src/types.ts @@ -1,129 +1,15 @@ -import { - ChainIndex, - Transaction, - SiacoinElement, - SiafundElement, - FileContractElement, - Hash256, - FileContract, - PublicKey, - SpendPolicy, - UnlockConditions, - V2Transaction, - V2FileContractResolutionType, - Address, - V2FileContractElement, -} from '@siafoundation/types' +import { SpendPolicy, UnlockConditions } from '@siafoundation/types' export type GatewayPeer = { addr: string inbound: boolean version: string - firstSeen?: string connectedSince?: string syncedBlocks?: number syncDuration?: number } -export type UnconfirmedChainIndex = { - height: number -} - -export type WalletEventBase = { - id: Hash256 - timestamp: string - index: ChainIndex | UnconfirmedChainIndex - maturityHeight: number - relevant: Address[] -} - -export type WalletSiafundInput = { - siafundElement: SiafundElement - claimElement: SiacoinElement -} - -export type WalletFileContract = { - fileContract: FileContractElement - revision?: FileContract - validOutputs?: SiacoinElement[] -} - -export type WalletV2FileContract = { - fileContract: FileContractElement - revision?: FileContract - resolution?: V2FileContractResolutionType - outputs?: SiacoinElement[] -} - -export type WalletHostAnnouncement = { - publicKey: PublicKey - netAddress: string -} - -export type WalletEventTransactionV1 = WalletEventBase & { - type: 'v1Transaction' - data: { - transaction: Transaction - spentSiacoinElements?: SiacoinElement[] - spentSiafundElements?: SiafundElement[] - } -} - -export type WalletEventTransactionV2 = WalletEventBase & { - type: 'v2Transaction' - data: V2Transaction -} - -export type WalletEventContractResolutionV1 = WalletEventBase & { - type: 'v1ContractResolution' - data: { - parent: FileContractElement - siacoinElement: SiacoinElement - missed: boolean - } -} - -export type WalletEventContractResolutionV2 = WalletEventBase & { - type: 'v2ContractResolution' - data: { - parent: V2FileContractElement - resolution: V2FileContractResolutionType - siacoinElement: SiacoinElement - missed: boolean - } -} - -export type WalletEventMinerPayout = WalletEventBase & { - type: 'miner' - data: { - siacoinElement: SiacoinElement - } -} - -export type WalletEventSiafundClaim = WalletEventBase & { - type: 'siafundClaim' - data: { - siacoinElement: SiacoinElement - } -} - -export type WalletEventFoundationSubsidy = WalletEventBase & { - type: 'foundation' - data: { - siacoinElement: SiacoinElement - } -} - -export type WalletEvent = - | WalletEventTransactionV1 - | WalletEventTransactionV2 - | WalletEventContractResolutionV1 - | WalletEventContractResolutionV2 - | WalletEventMinerPayout - | WalletEventFoundationSubsidy - | WalletEventSiafundClaim - export type Metadata = Record export type WalletType = 'seed' | 'ledger' | 'watch'