diff --git a/.changeset/cool-buses-exercise.md b/.changeset/cool-buses-exercise.md new file mode 100644 index 000000000..eb7c1fd4a --- /dev/null +++ b/.changeset/cool-buses-exercise.md @@ -0,0 +1,5 @@ +--- +'@siafoundation/types': minor +--- + +The library now includes v2 types. diff --git a/.changeset/fair-rats-return.md b/.changeset/fair-rats-return.md new file mode 100644 index 000000000..217f433ed --- /dev/null +++ b/.changeset/fair-rats-return.md @@ -0,0 +1,5 @@ +--- +'walletd': minor +--- + +The UI now uses the new daemon endpoints and changes. Closes https://github.com/SiaFoundation/walletd/issues/73 diff --git a/.changeset/neat-pumpkins-cheat.md b/.changeset/neat-pumpkins-cheat.md new file mode 100644 index 000000000..583a5cbad --- /dev/null +++ b/.changeset/neat-pumpkins-cheat.md @@ -0,0 +1,5 @@ +--- +'@siafoundation/react-walletd': minor +--- + +The React hooks have been updated for the daemon endpoint changes. Closes https://github.com/SiaFoundation/walletd/issues/73 diff --git a/apps/walletd/components/Node/index.tsx b/apps/walletd/components/Node/index.tsx index d59d09703..b57f2392c 100644 --- a/apps/walletd/components/Node/index.tsx +++ b/apps/walletd/components/Node/index.tsx @@ -28,7 +28,8 @@ export function Node() { const { openDialog } = useDialog() const transactionCount = txPool.data - ? txPool.data.transactions.length + txPool.data.v2Transactions.length + ? (txPool.data.transactions?.length || 0) + + (txPool.data.v2Transactions?.length || 0) : 0 return ( diff --git a/apps/walletd/components/Wallet/WalletActionsMenu.tsx b/apps/walletd/components/Wallet/WalletActionsMenu.tsx index 990e4becc..3181575b1 100644 --- a/apps/walletd/components/Wallet/WalletActionsMenu.tsx +++ b/apps/walletd/components/Wallet/WalletActionsMenu.tsx @@ -30,16 +30,16 @@ export function WalletActionsMenu() { isSynced={status.isSynced} /> - {wallet?.type !== 'watch' && ( + {wallet?.metadata.type !== 'watch' && ( { - if (wallet?.type === 'seed') { + if (wallet?.metadata.type === 'seed') { openDialog('walletSendSeed', { walletId, }) - } else if (wallet?.type === 'ledger') { + } else if (wallet?.metadata.type === 'ledger') { openDialog('walletSendLedger', { walletId, }) diff --git a/apps/walletd/components/Wallet/WalletNavMenu.tsx b/apps/walletd/components/Wallet/WalletNavMenu.tsx index 07a6cd100..acee93ccf 100644 --- a/apps/walletd/components/Wallet/WalletNavMenu.tsx +++ b/apps/walletd/components/Wallet/WalletNavMenu.tsx @@ -17,9 +17,9 @@ export function WalletNavMenu() { }) } className="!p-0" - tip={walletTypes[wallet?.type]?.title} + tip={walletTypes[wallet?.metadata.type]?.title} > - {walletTypes[wallet?.type]?.icon} + {walletTypes[wallet?.metadata.type]?.icon} ) diff --git a/apps/walletd/components/WalletAddresses/AddressesActionsMenu.tsx b/apps/walletd/components/WalletAddresses/AddressesActionsMenu.tsx index e13f5ded5..73edb4743 100644 --- a/apps/walletd/components/WalletAddresses/AddressesActionsMenu.tsx +++ b/apps/walletd/components/WalletAddresses/AddressesActionsMenu.tsx @@ -15,15 +15,15 @@ export function AddressesActionsMenu() { { - if (wallet?.type === 'seed') { + if (wallet?.metadata.type === 'seed') { openDialog('walletAddressesGenerate', { walletId: id }) return } - if (wallet?.type === 'watch') { + if (wallet?.metadata.type === 'watch') { openDialog('walletAddressesAdd', { walletId: id }) return } - if (wallet?.type === 'ledger') { + if (wallet?.metadata.type === 'ledger') { openDialog('walletLedgerAddressGenerate', { walletId: id }) return } diff --git a/apps/walletd/contexts/addresses/index.tsx b/apps/walletd/contexts/addresses/index.tsx index c2dce8f5b..c0645ad10 100644 --- a/apps/walletd/contexts/addresses/index.tsx +++ b/apps/walletd/contexts/addresses/index.tsx @@ -40,13 +40,14 @@ export function useAddressesMain() { if (!response.data) { return null } - const data: AddressData[] = Object.entries(response.data || {}).map( - ([address, meta]) => ({ + const data: AddressData[] = response.data.map( + ({ address, description, metadata, spendPolicy }) => ({ id: address, address, - description: meta.description as string, - index: meta.index as number, - publicKey: meta.publicKey as string, + description: description, + index: metadata?.index as number, + publicKey: metadata?.publicKey as string, + spendPolicy: spendPolicy, walletId, onClick: () => openDialog('addressUpdate', { diff --git a/apps/walletd/contexts/addresses/types.ts b/apps/walletd/contexts/addresses/types.ts index 86a4102f5..9b341683b 100644 --- a/apps/walletd/contexts/addresses/types.ts +++ b/apps/walletd/contexts/addresses/types.ts @@ -7,6 +7,7 @@ export type AddressData = { address: string description?: string publicKey?: string + spendPolicy?: string index?: number walletId: string } diff --git a/apps/walletd/contexts/events/index.tsx b/apps/walletd/contexts/events/index.tsx index ddad87dd9..727afd3ec 100644 --- a/apps/walletd/contexts/events/index.tsx +++ b/apps/walletd/contexts/events/index.tsx @@ -4,17 +4,11 @@ import { useServerFilters, } from '@siafoundation/design-system' import { + useResubscribe, useWalletEvents, - useWalletSubscribe, useWalletTxPool, } from '@siafoundation/react-walletd' -import { - createContext, - useCallback, - useContext, - useEffect, - useMemo, -} from 'react' +import { createContext, useCallback, useContext, useMemo } from 'react' import { CellContext, EventData, @@ -62,25 +56,12 @@ export function useEventsMain() { }, }) - const walletSub = useWalletSubscribe() - const subscribe = useCallback(async () => { - // do not handle error because the common case of - // already being subscribed returns a 500. - walletSub.post({ - params: { - id, - }, + const _resubscribe = useResubscribe() + const resubscribe = useCallback(async () => { + _resubscribe.post({ payload: 0, }) - }, [walletSub, id]) - - // Make sure the wallet is subscribed - useEffect(() => { - if (id) { - subscribe() - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [id]) + }, [_resubscribe]) const dataset = useMemo(() => { if (!responseEvents.data || !responseTxPool.data) { @@ -134,10 +115,12 @@ export function useEventsMain() { if (e.type === 'miner payout') { amountSc = new BigNumber(e.val.siacoinOutput.siacoinOutput.value) } + if (e.type === 'foundation subsidy') { + amountSc = new BigNumber(e.val.siacoinOutput.siacoinOutput.value) + } - const id = String(index) const res: EventData = { - id, + id: e.id, type: e.type, timestamp: new Date(e.timestamp).getTime(), height: e.index.height, @@ -145,16 +128,12 @@ export function useEventsMain() { amountSc, amountSf, } - if ('fee' in e.val) { + if (e.type === 'transaction') { res.fee = new BigNumber(e.val.fee) } - if ('fileContract' in e.val) { + if (e.type === 'contract payout') { res.contractId = e.val.fileContract.id } - if ('id' in e.val) { - res.id += e.val.id - res.transactionId = e.val.id - } return res }) return [...dataTxPool.reverse(), ...dataEvents] @@ -226,6 +205,7 @@ export function useEventsMain() { removeFilter, removeLastFilter, resetFilters, + resubscribe, offset, limit, } diff --git a/apps/walletd/contexts/wallets/columns.tsx b/apps/walletd/contexts/wallets/columns.tsx index 5404eba96..53c9adf59 100644 --- a/apps/walletd/contexts/wallets/columns.tsx +++ b/apps/walletd/contexts/wallets/columns.tsx @@ -105,7 +105,11 @@ export const columns: WalletsTableColumn[] = [ id: 'type', label: 'type', category: 'general', - render: ({ data: { type } }) => { + render: ({ + data: { + metadata: { type }, + }, + }) => { return ( @@ -123,9 +127,12 @@ export const columns: WalletsTableColumn[] = [ label: 'status', category: 'general', render: ({ - data: { type, status, activityAt, unlock, lock }, + data, context: { walletAutoLockEnabled, walletAutoLockTimeout }, }) => { + const { type } = data.metadata + const { status, activityAt } = data.state + const { unlock, lock } = data.actions if (type === 'seed') { const sinceActivityMs = new Date().getTime() - activityAt const remainingMs = Math.max(walletAutoLockTimeout - sinceActivityMs, 0) diff --git a/apps/walletd/contexts/wallets/index.tsx b/apps/walletd/contexts/wallets/index.tsx index 458848b45..3e640646a 100644 --- a/apps/walletd/contexts/wallets/index.tsx +++ b/apps/walletd/contexts/wallets/index.tsx @@ -11,7 +11,7 @@ import { columnsDefaultVisible, defaultSortField, sortOptions, - WalletType, + WalletMetadata, } from './types' import { columns } from './columns' import { useRouter } from 'next/router' @@ -54,19 +54,23 @@ function useWalletsMain() { if (!response.data) { return null } - const data: WalletData[] = Object.entries(response.data || {}).map( - ([id, meta]) => ({ + const data: WalletData[] = response.data.map( + ({ id, name, description, dateCreated, lastUpdated, metadata }) => ({ id, - name: meta.name as string, - seed: seedCache[id], - status: seedCache[id] ? 'unlocked' : 'locked', - activityAt: walletActivityAt[id], - seedHash: meta.seedHash as string, - description: meta.description as string, - createdAt: (meta.createdAt as number) || 0, - type: meta.type as WalletType, - unlock: () => openDialog('walletUnlock', { walletId: id }), - lock: () => saveWalletSeed(id, undefined), + name, + description, + createdAt: new Date(dateCreated).getTime() || 0, + updatedAt: new Date(lastUpdated).getTime() || 0, + metadata: (metadata || {}) as WalletMetadata, + state: { + seed: seedCache[id], + status: seedCache[id] ? 'unlocked' : 'locked', + activityAt: walletActivityAt[id], + }, + actions: { + unlock: () => openDialog('walletUnlock', { walletId: id }), + lock: () => saveWalletSeed(id, undefined), + }, onClick: () => router.push({ pathname: routes.wallet.view, @@ -145,7 +149,7 @@ function useWalletsMain() { dataState, error: response.error, datasetCount: datasetFiltered?.length || 0, - unlockedCount: dataset?.filter((d) => d.seed).length || 0, + unlockedCount: dataset?.filter((d) => d.state.seed).length || 0, columns: filteredTableColumns, dataset: datasetFiltered, context, diff --git a/apps/walletd/contexts/wallets/types.ts b/apps/walletd/contexts/wallets/types.ts index dfccc36ee..efff9bc74 100644 --- a/apps/walletd/contexts/wallets/types.ts +++ b/apps/walletd/contexts/wallets/types.ts @@ -1,17 +1,26 @@ export type WalletType = 'seed' | 'watch' | 'ledger' +export type WalletMetadata = { + type: WalletType + seedHash?: string +} + export type WalletData = { id: string name?: string description?: string - type?: WalletType - seed?: string - activityAt?: number - status: 'unlocked' | 'locked' - seedHash?: string createdAt?: number - unlock: () => void - lock: () => void + updatedAt?: number + metadata: WalletMetadata + state: { + seed?: string + activityAt?: number + status: 'unlocked' | 'locked' + } + actions: { + unlock: () => void + lock: () => void + } } export type TableColumnId = diff --git a/apps/walletd/dialogs/WalletAddNewDialog/index.tsx b/apps/walletd/dialogs/WalletAddNewDialog/index.tsx index f8bcb1555..8508a4156 100644 --- a/apps/walletd/dialogs/WalletAddNewDialog/index.tsx +++ b/apps/walletd/dialogs/WalletAddNewDialog/index.tsx @@ -16,7 +16,6 @@ import { useForm } from 'react-hook-form' import { useWalletAdd } from '@siafoundation/react-walletd' import { useDialog } from '../../contexts/dialog' import { useWallets } from '../../contexts/wallets' -import { v4 as uuidv4 } from 'uuid' import { walletAddTypes } from '../../config/walletTypes' import { blake2bHex } from 'blakejs' import { SeedLayout } from '../SeedLayout' @@ -30,13 +29,15 @@ const defaultValues = { hasCopied: false, } +type Values = typeof defaultValues + function getFields({ walletNames, copySeed, }: { walletNames: string[] copySeed: () => void -}): ConfigFields { +}): ConfigFields { return { name: { type: 'text', @@ -136,27 +137,24 @@ export function WalletAddNewDialog({ trigger, open, onOpenChange }: Props) { const fields = getFields({ walletNames, copySeed }) const onSubmit = useCallback( - async (values) => { - const id = uuidv4() + async (values: Values) => { const { seed } = getWalletWasm().seedFromPhrase(values.mnemonic) const seedHash = blake2bHex(seed) - const response = await walletAdd.put({ - params: { - id, - }, + const response = await walletAdd.post({ payload: { - type: 'seed', - seedHash, name: values.name, - createdAt: new Date().getTime(), description: values.description, + metadata: { + type: 'seed', + seedHash, + }, }, }) if (response.error) { triggerErrorToast(response.error) } else { openDialog('walletAddressesGenerate', { - walletId: id, + walletId: response.data.id, }) form.reset(defaultValues) } diff --git a/apps/walletd/dialogs/WalletAddRecoverDialog/index.tsx b/apps/walletd/dialogs/WalletAddRecoverDialog/index.tsx index fb83ae049..3a4dd87c8 100644 --- a/apps/walletd/dialogs/WalletAddRecoverDialog/index.tsx +++ b/apps/walletd/dialogs/WalletAddRecoverDialog/index.tsx @@ -12,7 +12,6 @@ import { useCallback } from 'react' import { useForm } from 'react-hook-form' import { useDialog } from '../../contexts/dialog' import { useWallets } from '../../contexts/wallets' -import { v4 as uuidv4 } from 'uuid' import { useWalletAdd } from '@siafoundation/react-walletd' import { blake2bHex } from 'blakejs' import { SeedLayout } from '../SeedLayout' @@ -26,11 +25,13 @@ const defaultValues = { mnemonic: '', } +type Values = typeof defaultValues + function getFields({ walletNames, }: { walletNames: string[] -}): ConfigFields { +}): ConfigFields { return { name: { type: 'text', @@ -93,27 +94,24 @@ export function WalletAddRecoverDialog({ trigger, open, onOpenChange }: Props) { const walletAdd = useWalletAdd() const onSubmit = useCallback( - async (values) => { - const id = uuidv4() + async (values: Values) => { const { seed } = getWalletWasm().seedFromPhrase(values.mnemonic) const seedHash = blake2bHex(seed) - const response = await walletAdd.put({ - params: { - id, - }, + const response = await walletAdd.post({ payload: { - type: 'seed', - seedHash, name: values.name, - createdAt: new Date().getTime(), description: values.description, + metadata: { + type: 'seed', + seedHash, + }, }, }) if (response.error) { triggerErrorToast(response.error) } else { openDialog('walletAddressesGenerate', { - walletId: id, + walletId: response.data.id, }) form.reset(defaultValues) } diff --git a/apps/walletd/dialogs/WalletAddWatchDialog/index.tsx b/apps/walletd/dialogs/WalletAddWatchDialog/index.tsx index 6e58b7cf3..c59d91d74 100644 --- a/apps/walletd/dialogs/WalletAddWatchDialog/index.tsx +++ b/apps/walletd/dialogs/WalletAddWatchDialog/index.tsx @@ -14,7 +14,6 @@ import { useForm } from 'react-hook-form' import { useWalletAdd } from '@siafoundation/react-walletd' import { useDialog } from '../../contexts/dialog' import { useWallets } from '../../contexts/wallets' -import { v4 as uuidv4 } from 'uuid' import { walletAddTypes } from '../../config/walletTypes' const defaultValues = { @@ -22,11 +21,13 @@ const defaultValues = { description: '', } +type Values = typeof defaultValues + function getFields({ walletNames, }: { walletNames: string[] -}): ConfigFields { +}): ConfigFields { return { name: { type: 'text', @@ -75,24 +76,21 @@ export function WalletAddWatchDialog({ trigger, open, onOpenChange }: Props) { const fields = getFields({ walletNames }) const onSubmit = useCallback( - async (values) => { - const id = uuidv4() - const response = await walletAdd.put({ - params: { - id, - }, + async (values: Values) => { + const response = await walletAdd.post({ payload: { - type: 'watch', name: values.name, description: values.description, - createdAt: new Date().getTime(), + metadata: { + type: 'watch', + }, }, }) if (response.error) { triggerErrorToast(response.error) } else { openDialog('walletAddressesAdd', { - walletId: id, + walletId: response.data.id, }) form.reset(defaultValues) } diff --git a/apps/walletd/dialogs/WalletAddressesAddDialog.tsx b/apps/walletd/dialogs/WalletAddressesAddDialog.tsx index 992144d27..311320ff4 100644 --- a/apps/walletd/dialogs/WalletAddressesAddDialog.tsx +++ b/apps/walletd/dialogs/WalletAddressesAddDialog.tsx @@ -31,6 +31,8 @@ const defaultValues = { addresses: '', } +type Values = typeof defaultValues + const placeholder = `91acbc0feb9e20d538db1f8a509d508362d1b1f3d725d9b6639306531d770c1ef9eb637b4903 b58849e347356878bb0098908191550ff3e46cc35ed166d0c571fe184d2f17b835747991c266 b811a04859809fe081884c10d50ca069f1429112ba4a8dc9181c95de41f7eca01416923daa6d @@ -51,7 +53,7 @@ function isCorrectLength(addr: string) { return addr.length === 76 } -function getFields(): ConfigFields { +function getFields(): ConfigFields { return { addresses: { type: 'text', @@ -111,9 +113,12 @@ export function WalletAddressesAddDialog({ const response = await addressAdd.put({ params: { id: walletId, - addr, }, - payload: {}, + payload: { + address: addr, + description: '', + metadata: {}, + }, }) if (response.error) { if (count === 1) { @@ -139,7 +144,7 @@ export function WalletAddressesAddDialog({ ) const onSubmit = useCallback( - (values: typeof defaultValues) => { + (values: Values) => { return addAllAddresses(values.addresses) }, [addAllAddresses] diff --git a/apps/walletd/dialogs/WalletAddressesGenerateSeedDialog/index.tsx b/apps/walletd/dialogs/WalletAddressesGenerateSeedDialog/index.tsx index 708fc6e5c..4e1ec85c4 100644 --- a/apps/walletd/dialogs/WalletAddressesGenerateSeedDialog/index.tsx +++ b/apps/walletd/dialogs/WalletAddressesGenerateSeedDialog/index.tsx @@ -113,7 +113,7 @@ export function WalletAddressesGenerateSeedDialog({ const count = form.watch('count') const fields = getFields({ - seedHash: wallet?.seedHash, + seedHash: wallet?.metadata.seedHash, mnemonicFieldType, setMnemonicFieldType, }) @@ -137,11 +137,15 @@ export function WalletAddressesGenerateSeedDialog({ const response = await addressAdd.put({ params: { id: walletId, - addr: pkResponse.address, }, payload: { - index: i, - publicKey: pkResponse.publicKey, + address: pkResponse.address, + description: '', + // spendPolicy: '', // TODO: use sdk and replace + metadata: { + index: i, + publicKey: pkResponse.publicKey, + }, }, }) if (response.error) { diff --git a/apps/walletd/dialogs/WalletSendLedgerDialog/useSign.tsx b/apps/walletd/dialogs/WalletSendLedgerDialog/useSign.tsx index 84db2330f..4bf4a50a8 100644 --- a/apps/walletd/dialogs/WalletSendLedgerDialog/useSign.tsx +++ b/apps/walletd/dialogs/WalletSendLedgerDialog/useSign.tsx @@ -1,5 +1,8 @@ import { Transaction } from '@siafoundation/types' -import { useWalletOutputs } from '@siafoundation/react-walletd' +import { + useWalletOutputsSiacoin, + useWalletOutputsSiafund, +} from '@siafoundation/react-walletd' import { useWallets } from '../../contexts/wallets' import { useCallback } from 'react' import { useWalletAddresses } from '../../hooks/useWalletAddresses' @@ -9,7 +12,13 @@ import { useLedger } from '../../contexts/ledger' export function useSign({ cancel }: { cancel: (t: Transaction) => void }) { const { wallet } = useWallets() const walletId = wallet?.id - const outputs = useWalletOutputs({ + const siacoinOutputs = useWalletOutputsSiacoin({ + disabled: !walletId, + params: { + id: walletId, + }, + }) + const siafundOutputs = useWalletOutputsSiafund({ disabled: !walletId, params: { id: walletId, @@ -35,8 +44,8 @@ export function useSign({ cancel }: { cancel: (t: Transaction) => void }) { transaction: fundedTransaction, toSign, addresses, - siacoinOutputs: outputs.data?.siacoinOutputs, - siafundOutputs: outputs.data?.siafundOutputs, + siacoinOutputs: siacoinOutputs.data, + siafundOutputs: siafundOutputs.data, }) if (signResponse.error) { cancel(fundedTransaction) @@ -48,7 +57,7 @@ export function useSign({ cancel }: { cancel: (t: Transaction) => void }) { signedTransaction: signResponse.transaction, } }, - [device, addresses, outputs.data, cancel] + [device, addresses, siacoinOutputs.data, siafundOutputs.data, cancel] ) return sign diff --git a/apps/walletd/dialogs/WalletSendSeedDialog/useSignAndBroadcast.tsx b/apps/walletd/dialogs/WalletSendSeedDialog/useSignAndBroadcast.tsx index cad46ced9..e6d09baae 100644 --- a/apps/walletd/dialogs/WalletSendSeedDialog/useSignAndBroadcast.tsx +++ b/apps/walletd/dialogs/WalletSendSeedDialog/useSignAndBroadcast.tsx @@ -1,7 +1,8 @@ import { useConsensusNetwork, - useWalletOutputs, + useWalletOutputsSiacoin, useConsensusTipState, + useWalletOutputsSiafund, } from '@siafoundation/react-walletd' import { useWallets } from '../../contexts/wallets' import { useCallback } from 'react' @@ -16,7 +17,13 @@ export function useSignAndBroadcast() { const { wallet, saveWalletSeed } = useWallets() const walletId = wallet?.id - const outputs = useWalletOutputs({ + const siacoinOutputs = useWalletOutputsSiacoin({ + disabled: !walletId, + params: { + id: walletId, + }, + }) + const siafundOutputs = useWalletOutputsSiafund({ disabled: !walletId, params: { id: walletId, @@ -47,17 +54,16 @@ export function useSignAndBroadcast() { error: fundingError, } } - const { signedTransaction, error: signingError } = - await signTransactionSeed({ - seed, - transaction: fundedTransaction, - toSign, - cs: cs.data, - cn: cn.data, - addresses, - siacoinOutputs: outputs.data?.siacoinOutputs, - siafundOutputs: outputs.data?.siafundOutputs, - }) + const { signedTransaction, error: signingError } = signTransactionSeed({ + seed, + transaction: fundedTransaction, + toSign, + cs: cs.data, + cn: cn.data, + addresses, + siacoinOutputs: siacoinOutputs.data, + siafundOutputs: siafundOutputs.data, + }) if (signingError) { cancel(fundedTransaction) return { @@ -78,7 +84,8 @@ export function useSignAndBroadcast() { walletId, cs.data, cn.data, - outputs.data, + siacoinOutputs.data, + siafundOutputs.data, saveWalletSeed, broadcast, ] diff --git a/apps/walletd/dialogs/WalletUpdateDialog/index.tsx b/apps/walletd/dialogs/WalletUpdateDialog/index.tsx index c050460ce..23fe73e44 100644 --- a/apps/walletd/dialogs/WalletUpdateDialog/index.tsx +++ b/apps/walletd/dialogs/WalletUpdateDialog/index.tsx @@ -11,7 +11,7 @@ import { } from '@siafoundation/design-system' import { useCallback, useEffect, useMemo } from 'react' import { useForm } from 'react-hook-form' -import { useWalletAdd } from '@siafoundation/react-walletd' +import { useWalletUpdate } from '@siafoundation/react-walletd' import { useWallets } from '../../contexts/wallets' const defaultValues = { @@ -19,11 +19,13 @@ const defaultValues = { description: '', } +type Values = typeof defaultValues + function getFields({ walletNames, }: { walletNames: string[] -}): ConfigFields { +}): ConfigFields { return { name: { type: 'text', @@ -69,7 +71,7 @@ export function WalletUpdateDialog({ const { walletId } = params || {} const { dataset } = useWallets() const wallet = dataset?.find((d) => d.id === walletId) - const walletAdd = useWalletAdd() + const walletUpdate = useWalletUpdate() const form = useForm({ mode: 'all', defaultValues, @@ -80,18 +82,14 @@ export function WalletUpdateDialog({ defaultValues, }) useEffect(() => { - // timeout 0 gets around a react-hook-form glitch where both fields - // do not get initialized. - setTimeout(() => { - form.reset( - wallet - ? { - name: wallet.name, - description: wallet.description, - } - : defaultValues - ) - }, 0) + form.reset( + wallet + ? { + name: wallet.name, + description: wallet.description, + } + : defaultValues + ) // eslint-disable-next-line react-hooks/exhaustive-deps }, [walletId]) @@ -107,15 +105,15 @@ export function WalletUpdateDialog({ ) const fields = getFields({ walletNames }) const onSubmit = useCallback( - async (values) => { - const response = await walletAdd.put({ + async (values: Values) => { + const response = await walletUpdate.post({ params: { id: walletId, }, payload: { - ...wallet, name: values.name, description: values.description, + metadata: wallet.metadata, }, }) if (response.error) { @@ -124,7 +122,7 @@ export function WalletUpdateDialog({ closeAndReset() } }, - [walletId, walletAdd, wallet, closeAndReset] + [walletId, walletUpdate, wallet, closeAndReset] ) return ( @@ -150,7 +148,7 @@ export function WalletUpdateDialog({ Type - {wallet?.type} + {wallet?.metadata.type} diff --git a/apps/walletd/dialogs/_sharedWalletSend/useFund.tsx b/apps/walletd/dialogs/_sharedWalletSend/useFund.tsx index 8ce9672f6..9784f5e01 100644 --- a/apps/walletd/dialogs/_sharedWalletSend/useFund.tsx +++ b/apps/walletd/dialogs/_sharedWalletSend/useFund.tsx @@ -1,4 +1,7 @@ -import { useWalletFund, useWalletFundSf } from '@siafoundation/react-walletd' +import { + useWalletFundSiacoin, + useWalletFundSiafund, +} from '@siafoundation/react-walletd' import { useWallets } from '../../contexts/wallets' import { useCallback } from 'react' import { SendParams } from './types' @@ -6,8 +9,8 @@ import { SendParams } from './types' export function useFund() { const { wallet } = useWallets() const walletId = wallet?.id - const walletFund = useWalletFund() - const walletFundSf = useWalletFundSf() + const walletFundSc = useWalletFundSiacoin() + const walletFundSf = useWalletFundSiafund() const fund = useCallback( async ({ @@ -27,7 +30,7 @@ export function useFund() { // fund if (mode === 'siacoin') { - const fundResponse = await walletFund.post({ + const fundResponse = await walletFundSc.post({ params: { id: walletId, }, @@ -83,7 +86,7 @@ export function useFund() { } } toSign.push(...fundResponse.data.toSign) - fundResponse = await walletFund.post({ + fundResponse = await walletFundSc.post({ params: { id: walletId, }, @@ -105,7 +108,7 @@ export function useFund() { } } }, - [walletFund, walletFundSf, walletId] + [walletFundSc, walletFundSf, walletId] ) return fund diff --git a/apps/walletd/hooks/useWalletCachedSeed.tsx b/apps/walletd/hooks/useWalletCachedSeed.tsx index aa8d1f70a..3f1f309a5 100644 --- a/apps/walletd/hooks/useWalletCachedSeed.tsx +++ b/apps/walletd/hooks/useWalletCachedSeed.tsx @@ -4,10 +4,14 @@ import { useWallets } from '../contexts/wallets' export function useWalletCachedSeed(walletId: string) { const { dataset } = useWallets() const wallet = dataset?.find((w) => w.id === walletId) - const cachedSeed = wallet?.seed + const cachedSeed = wallet?.state.seed const isSeedCached = !!cachedSeed - const getSeedFromCacheOrForm = ({ mnemonic }: { mnemonic: string }) => { + const getSeedFromCacheOrForm = ({ + mnemonic, + }: { + mnemonic: string + }): { seed?: string; error?: string } => { if (isSeedCached) { return { seed: cachedSeed } } else { diff --git a/libs/react-walletd/src/api.ts b/libs/react-walletd/src/api.ts index 18c5ca267..1a121b493 100644 --- a/libs/react-walletd/src/api.ts +++ b/libs/react-walletd/src/api.ts @@ -21,8 +21,16 @@ import { SiacoinElement, SiafundElement, Transaction, + V2Transaction, } from '@siafoundation/types' -import { PoolTransaction, WalletEvent, GatewayPeer } from './siaTypes' +import { + PoolTransaction, + WalletEvent, + GatewayPeer, + Wallet, + Metadata, + WalletAddress, +} from './siaTypes' // consensus @@ -100,20 +108,29 @@ export function useSyncerConnect(args?: HookArgsCallback) { // txpool -type TxPoolTransactions = { +type TxPoolTransactionsResponse = { transactions: Transaction[] - v2Transactions: unknown[] + v2Transactions: V2Transaction[] } const txPoolTransactionsRoute = '/txpool/transactions' export function useTxPoolTransactions( - args?: HookArgsSwr + args?: HookArgsSwr ) { return useGetSwr({ ...args, route: txPoolTransactionsRoute }) } +export function useTxPoolFee(args?: HookArgsSwr) { + return useGetSwr({ ...args, route: '/txpool/fee' }) +} + +type TxPoolBroadcastPayload = { + transactions: Transaction[] + v2Transactions: V2Transaction[] +} + export function useTxPoolBroadcast( - args?: HookArgsCallback + args?: HookArgsCallback ) { return usePostFunc( { @@ -133,37 +150,52 @@ export function useTxPoolBroadcast( ) } -// wallet +// subscribe -type Wallet = Record -type Wallets = Record +export function useResubscribe( + args?: HookArgsCallback +) { + return usePostFunc({ + ...args, + route: '/resubscribe', + }) +} + +// wallet const walletsRoute = '/wallets' -export function useWallets(args?: HookArgsSwr) { +export function useWallets(args?: HookArgsSwr) { return useGetSwr({ ...args, route: walletsRoute, }) } -type Address = Record -type Addresses = Record +type WalletUpdatePayload = { + name: string + description: string + metadata: Metadata +} -export const walletAddressesRoute = '/wallets/:id/addresses' -export function useWalletAddresses( - args: HookArgsSwr<{ id: string }, Addresses> +export function useWalletAdd( + args?: HookArgsCallback ) { - return useGetSwr({ - ...args, - route: walletAddressesRoute, - }) + return usePostFunc( + { + ...args, + route: '/wallets', + }, + async (mutate) => { + mutate((key) => key.startsWith(walletsRoute)) + } + ) } -export function useWalletAdd( - args?: HookArgsCallback<{ id: string }, Wallet, void> +export function useWalletUpdate( + args?: HookArgsCallback<{ id: string }, WalletUpdatePayload, Wallet> ) { - return usePutFunc( + return usePostFunc( { ...args, route: '/wallets/:id', @@ -187,22 +219,25 @@ export function useWalletDelete( ) } -export function useWalletSubscribe( - args?: HookArgsCallback<{ id: string }, BlockHeight, void> +// addresses + +export const walletAddressesRoute = '/wallets/:id/addresses' +export function useWalletAddresses( + args: HookArgsSwr<{ id: string }, WalletAddress[]> ) { - return usePostFunc({ + return useGetSwr({ ...args, - route: '/wallets/:id/subscribe', + route: walletAddressesRoute, }) } export function useWalletAddressAdd( - args?: HookArgsCallback<{ id: string; addr: string }, Address, void> + args?: HookArgsCallback<{ id: string }, WalletAddress, void> ) { return usePutFunc( { ...args, - route: '/wallets/:id/addresses/:addr', + route: '/wallets/:id/addresses', }, async (mutate, data) => { mutate((key) => @@ -227,7 +262,10 @@ export function useWalletAddressDelete( const walletBalanceRoute = '/wallets/:id/balance' export function useWalletBalance( - args: HookArgsSwr<{ id: string }, { siacoins: string; siafunds: number }> + args: HookArgsSwr< + { id: string }, + { siacoins: Currency; immatureSiacoins: Currency; siafunds: number } + > ) { return useGetSwr({ ...args, @@ -259,21 +297,25 @@ export function useWalletTxPool( }) } -type WalletOutputsResponse = { - siacoinOutputs: SiacoinElement[] - siafundOutputs: SiafundElement[] +export function useWalletOutputsSiacoin( + args: HookArgsSwr<{ id: string }, SiacoinElement[]> +) { + return useGetSwr({ + ...args, + route: '/wallets/:id/outputs/siacoin', + }) } -export function useWalletOutputs( - args: HookArgsSwr<{ id: string }, WalletOutputsResponse> +export function useWalletOutputsSiafund( + args: HookArgsSwr<{ id: string }, SiafundElement[]> ) { return useGetSwr({ ...args, - route: '/wallets/:id/outputs', + route: '/wallets/:id/outputs/siafund', }) } -type WalletFundRequest = { +type WalletFundSiacoinPayload = { transaction: Transaction amount: Currency changeAddress: string @@ -285,48 +327,52 @@ export type WalletFundResponse = { dependsOn: Transaction[] } -export function useWalletFund( - args?: HookArgsCallback<{ id: string }, WalletFundRequest, WalletFundResponse> +export function useWalletFundSiacoin( + args?: HookArgsCallback< + { id: string }, + WalletFundSiacoinPayload, + WalletFundResponse + > ) { return usePostFunc({ ...args, route: '/wallets/:id/fund' }) } -type WalletFundRequestSf = { +type WalletFundSiafundPayload = { transaction: Transaction amount: number changeAddress: string claimAddress: string } -export function useWalletFundSf( +export function useWalletFundSiafund( args?: HookArgsCallback< { id: string }, - WalletFundRequestSf, + WalletFundSiafundPayload, WalletFundResponse > ) { return usePostFunc({ ...args, route: '/wallets/:id/fundsf' }) } -type WalletReserveRequest = { +type WalletReservePayload = { siacoinOutputs?: SiacoinOutputID[] siafundOutputs?: SiafundOutputID[] duration: number } export function useWalletReserve( - args?: HookArgsCallback<{ id: string }, WalletReserveRequest, void> + args?: HookArgsCallback<{ id: string }, WalletReservePayload, void> ) { return usePostFunc({ ...args, route: '/wallets/:id/reserve' }) } -type WalletReleaseRequest = { +type WalletReleasePayload = { siacoinOutputs?: SiacoinOutputID[] siafundOutputs?: SiafundOutputID[] } export function useWalletRelease( - args?: HookArgsCallback<{ id: string }, WalletReleaseRequest, void> + args?: HookArgsCallback<{ id: string }, WalletReleasePayload, void> ) { return usePostFunc({ ...args, route: '/wallets/:id/release' }) } diff --git a/libs/react-walletd/src/siaTypes.ts b/libs/react-walletd/src/siaTypes.ts index 432c84303..ecf82b185 100644 --- a/libs/react-walletd/src/siaTypes.ts +++ b/libs/react-walletd/src/siaTypes.ts @@ -4,9 +4,14 @@ import { Transaction, Address, SiacoinElement, - SiafundElementAndClaim, SiafundElement, FileContractElement, + Hash256, + FileContract, + V2FileContractResolutionType, + PublicKey, + TransactionID, + SpendPolicy, } from '@siafoundation/types' export type GatewayPeer = { @@ -14,14 +19,14 @@ export type GatewayPeer = { inbound: boolean version: string - firstSeen: string - connectedSince: string - syncedBlocks: number - syncDuration: number + firstSeen?: string + connectedSince?: string + syncedBlocks?: number + syncDuration?: number } export type PoolTransaction = { - id: string + id: TransactionID raw: Transaction type: string sent: Currency @@ -29,42 +34,72 @@ export type PoolTransaction = { locked: Currency } -export type WalletEventTransaction = { +export type WalletEventBase = { + id: Hash256 timestamp: string index: ChainIndex 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 WalletEventTransaction = WalletEventBase & { type: 'transaction' val: { - // transactionID: string - // transaction: Transaction - id: string - siacoinInputs: SiacoinElement[] - siacoinOutputs: SiacoinElement[] - siafundInputs: SiafundElementAndClaim[] - siafundOutputs: SiafundElement[] - fileContracts: FileContractElement[] - v2FileContracts: null - hostAnnouncements: null + siacoinInputs?: SiacoinElement[] + siacoinOutputs?: SiacoinElement[] + siafundInputs?: WalletSiafundInput[] + siafundOutputs?: SiafundElement[] + fileContracts?: WalletFileContract[] + v2FileContracts?: WalletV2FileContract[] + hostAnnouncements?: WalletHostAnnouncement[] fee: number } } -export type WalletEventMissedFileContract = { - timestamp: string - index: ChainIndex - relevant: Address[] - type: 'missed file contract' +export type WalletEventMinerPayout = WalletEventBase & { + type: 'miner payout' + val: { + siacoinOutput: SiacoinElement + } +} + +export type WalletEventContractPayout = WalletEventBase & { + type: 'contract payout' val: { fileContract: FileContractElement - missedOutputs: SiacoinElement[] + siacoinOutput: SiacoinElement + missed: boolean } } -export type WalletEventMinerPayout = { - timestamp: string - index: ChainIndex - relevant: Address[] - type: 'miner payout' +export type WalletEventSiafundClaim = WalletEventBase & { + type: 'siafund claim' +} + +export type WalletEventFoundationSubsidy = WalletEventBase & { + type: 'foundation subsidy' val: { siacoinOutput: SiacoinElement } @@ -72,5 +107,25 @@ export type WalletEventMinerPayout = { export type WalletEvent = | WalletEventTransaction - | WalletEventMissedFileContract | WalletEventMinerPayout + | WalletEventContractPayout + | WalletEventSiafundClaim + | WalletEventFoundationSubsidy + +export type Metadata = Record + +export type Wallet = { + id: string + name: string + description: string + dateCreated: string + lastUpdated: string + metadata: Metadata +} + +export type WalletAddress = { + address: string + description: string + spendPolicy?: SpendPolicy + metadata: Metadata +} diff --git a/libs/types/src/core.ts b/libs/types/src/core.ts new file mode 100644 index 000000000..aff0d7e63 --- /dev/null +++ b/libs/types/src/core.ts @@ -0,0 +1,197 @@ +export type ID = string +export type Hash256 = string +export type Signature = string +export type Currency = string +export type BlockHeight = number +export type Hash = string +export type OutputID = string +export type EncryptionKey = string +export type FileContractID = string +export type PublicKey = string +export type TransactionID = Hash256 +export type SiacoinOutputID = Hash256 +export type SiafundOutputID = Hash256 +export type Address = string + +export type StateElement = { + id: string + leafIndex: number + merkleProof: Hash256[] | null +} + +export type UnlockConditions = { + timelock: number + publicKeys?: PublicKey[] + signaturesRequired: number +} + +export type FileContractRevision = { + parentID: string + unlockConditions: UnlockConditions + revisionNumber: number + filesize: number + fileMerkleRoot: string + windowStart: number + windowEnd: number + validProofOutputs?: SiacoinOutput[] + missedProofOutputs?: SiacoinOutput[] + unlockHash: string +} + +export type TransactionSignature = { + parentID: string + publicKeyIndex: number + timelock: number + coveredFields: CoveredFields + signature?: string +} + +export type SiacoinInput = { + parentID: string + unlockConditions: UnlockConditions +} + +export type SiacoinOutput = { + value: Currency + address: string +} + +export type SiacoinElement = StateElement & { + siacoinOutput: SiacoinOutput + maturityHeight: number +} + +export type SiafundElement = StateElement & { + siafundOutput: SiafundOutput + claimStart: string +} + +export type CoveredFields = { + wholeTransaction: boolean + siacoinInputs?: number[] + siacoinOutputs?: number[] + fileContracts?: number[] + fileContractRevisions?: number[] + storageProofs?: number[] + siafundInputs?: number[] + siafundOutputs?: number[] + minerFees?: number[] + arbitraryData?: number[] + signatures?: number[] +} + +export type FileContractElement = StateElement & { + fileContract: FileContract +} + +export type FileContract = { + filesize: number + fileMerkleRoot: string + windowStart: number + windowEnd: number + payout: Currency + validProofOutputs?: SiacoinOutput[] + missedProofOutputs?: SiacoinOutput[] + unlockHash: string + revisionNumber: number +} + +export type StorageProof = { + parentID: string + segment: string + hashset?: Hash[] +} + +export type SiafundInput = { + parentID: string + unlockConditions: UnlockConditions + claimAddress: string +} + +export type SiafundOutput = { + value: number + address: string +} + +export type Transaction = { + siacoinInputs?: SiacoinInput[] + siacoinOutputs?: SiacoinOutput[] + fileContracts?: FileContract[] + fileContractRevisions?: FileContractRevision[] + storageProofs?: StorageProof[] + siafundInputs?: SiafundInput[] + siafundOutputs?: SiafundOutput[] + minerFees?: Currency[] + arbitraryData?: string[] + signatures?: TransactionSignature[] +} + +export type Block = { + parentID: string + nonce: string + timestamp: number + minerPayouts?: SiacoinOutput[] + transactions?: Transaction[] +} + +export type ChainIndex = { + height: number + id: string +} + +export type ConsensusNetwork = { + name: 'mainnet' | 'zen' + initialCoinbase: Currency + minimumCoinbase: Currency + initialTarget: string + hardforkDevAddr: { + height: number + oldAddress: string + newAddress: string + } + 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 ConsensusState = { + 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 +} diff --git a/libs/types/src/index.ts b/libs/types/src/index.ts index 76ff4623f..afc97d4a9 100644 --- a/libs/types/src/index.ts +++ b/libs/types/src/index.ts @@ -1,205 +1,2 @@ -export type ID = string -export type Hash256 = string -export type Signature = string -export type Currency = string -export type BlockHeight = number -export type Hash = string -export type OutputID = string -export type EncryptionKey = string -export type FileContractID = string -export type PublicKey = string -export type TransactionID = string -export type SiacoinOutputID = Hash256 -export type SiafundOutputID = Hash256 -export type Address = string - -export type UnlockConditions = { - timelock: number - publicKeys?: PublicKey[] - signaturesRequired: number -} - -export type FileContractRevision = { - parentID: string - unlockConditions: UnlockConditions - revisionNumber: number - filesize: number - fileMerkleRoot: string - windowStart: number - windowEnd: number - validProofOutputs?: SiacoinOutput[] - missedProofOutputs?: SiacoinOutput[] - unlockHash: string -} - -export type TransactionSignature = { - parentID: string - publicKeyIndex: number - timelock: number - coveredFields: CoveredFields - signature?: string -} - -export type SiacoinInput = { - parentID: string - unlockConditions: UnlockConditions -} - -export type SiacoinOutput = { - value: Currency - address: string -} - -export type SiacoinElement = { - id: string - leafIndex: number - merkleProof: string[] | null - siacoinOutput: SiacoinOutput - maturityHeight: number -} - -export type SiafundElement = { - id: string - leafIndex: number - merkleProof: string[] | null - siafundOutput: SiafundOutput - claimStart: string -} - -export type SiafundElementAndClaim = { - siafundElement: SiafundElement - claimElement: SiacoinElement -} - -export type CoveredFields = { - wholeTransaction: boolean - siacoinInputs?: number[] - siacoinOutputs?: number[] - fileContracts?: number[] - fileContractRevisions?: number[] - storageProofs?: number[] - siafundInputs?: number[] - siafundOutputs?: number[] - minerFees?: number[] - arbitraryData?: number[] - signatures?: number[] -} - -export type FileContractElement = { - id: string - leafIndex: number - merkleProof: string[] | null - fileContract: FileContract -} - -export type FileContract = { - filesize: number - fileMerkleRoot: string - windowStart: number - windowEnd: number - payout: Currency - validProofOutputs?: SiacoinOutput[] - missedProofOutputs?: SiacoinOutput[] - unlockHash: string - revisionNumber: number -} - -export type StorageProof = { - parentID: string - segment: string - hashset?: Hash[] -} - -export type SiafundInput = { - parentID: string - unlockConditions: UnlockConditions - claimAddress: string -} - -export type SiafundOutput = { - value: number - address: string -} - -export type Transaction = { - siacoinInputs?: SiacoinInput[] - siacoinOutputs?: SiacoinOutput[] - fileContracts?: FileContract[] - fileContractRevisions?: FileContractRevision[] - storageProofs?: StorageProof[] - siafundInputs?: SiafundInput[] - siafundOutputs?: SiafundOutput[] - minerFees?: Currency[] - arbitraryData?: string[] - signatures?: TransactionSignature[] -} - -export type Block = { - parentID: string - nonce: string - timestamp: number - minerPayouts?: SiacoinOutput[] - transactions?: Transaction[] -} - -export type ChainIndex = { - height: number - id: string -} - -export type ConsensusNetwork = { - name: 'mainnet' | 'zen' - initialCoinbase: Currency - minimumCoinbase: Currency - initialTarget: string - hardforkDevAddr: { - height: number - oldAddress: string - newAddress: string - } - 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 ConsensusState = { - 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 * from './core' +export * from './v2' diff --git a/libs/types/src/v2.ts b/libs/types/src/v2.ts new file mode 100644 index 000000000..e54932482 --- /dev/null +++ b/libs/types/src/v2.ts @@ -0,0 +1,97 @@ +import { + Address, + Currency, + Hash256, + PublicKey, + SiacoinElement, + SiacoinOutput, + SiafundElement, + SiafundOutput, + Signature, + StateElement, +} from './core' + +export type SpendPolicy = string + +export type V2SiacoinInput = { + parent: SiacoinElement + satisfiedPolicy: SatisfiedPolicy +} + +export type V2SiafundInput = { + parent: SiafundElement + claimAddress: Address + satisfiedPolicy: SatisfiedPolicy +} + +export type V2FileContractRevision = { + parent: V2FileContractElement + revision: V2FileContract +} + +export type V2FileContract = { + filesize: number + fileMerkleRoot: Hash256 + proofHeight: number + expirationHeight: number + renterOutput: SiacoinOutput + hostOutput: SiacoinOutput + missedHostValue: Currency + totalCollateral: Currency + renterPublicKey: PublicKey + hostPublicKey: PublicKey + revisionNumber: number + renterSignature?: Signature + hostSignature?: Signature +} + +export type V2FileContractResolution = { + parent: V2FileContractElement + resolution: V2FileContractResolutionType +} + +export type V2FileContractRenewal = { + finalRevision: V2FileContract + initialRevision: V2FileContract + renterRollover: Currency + hostRollover: Currency + renterSignature?: Signature + hostSignature?: Signature +} + +export type V2Transaction = { + siacoinInputs?: V2SiacoinInput[] + siacoinOutputs?: SiacoinOutput[] + siafundInputs?: V2SiafundInput[] + siafundOutputs?: SiafundOutput[] + fileContracts?: V2FileContract[] + fileContractRevisions?: V2FileContractRevision[] + fileContractResolutions?: V2FileContractResolution[] + attestations?: Attestation[] + arbitraryData?: Uint8Array + newFoundationAddress?: Address + minerFee: Currency +} + +export type Attestation = { + publicKey: PublicKey + key: string + value: Uint8Array + signature: Signature +} + +export type SatisfiedPolicy = { + policy: SpendPolicy + signatures?: Signature[] + preimages?: string[] +} + +export type V2FileContractElement = StateElement & { + v2FileContract: V2FileContract +} + +export type AttestationElement = StateElement & { + attestation: Attestation +} + +export type V2FileContractResolutionType = unknown // TODO: replace `unknown` with the actual type