Skip to content

Commit

Permalink
feat: walletd resubscribe
Browse files Browse the repository at this point in the history
  • Loading branch information
alexfreska committed Apr 15, 2024
1 parent 0f3b9f0 commit 6cd613c
Show file tree
Hide file tree
Showing 39 changed files with 1,037 additions and 168 deletions.
5 changes: 5 additions & 0 deletions .changeset/dull-trees-grow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'walletd': minor
---

Address generation and addition dialogs now have an option to rescan from a specified height. Closes https://github.com/SiaFoundation/walletd/issues/96
5 changes: 5 additions & 0 deletions .changeset/loud-bottles-help.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@siafoundation/design-system': minor
---

Added async defaultValues support to useDialogFormHelpers via initKey prop.
5 changes: 5 additions & 0 deletions .changeset/mighty-days-remain.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@siafoundation/design-system': patch
---

Fixed an issue where Dialog and useDialogFormHelpers were not calling onOpenChange on open events.
5 changes: 5 additions & 0 deletions .changeset/plenty-ligers-pay.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'walletd': minor
---

There is now a dedicated rescan dialog that can be opened from the wallet list and wallet context menus. Closes https://github.com/SiaFoundation/walletd/issues/96
5 changes: 5 additions & 0 deletions .changeset/rare-bags-live.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'walletd': minor
---

Rescan progress and status including errors is now shown in a sticky status bar. Closes https://github.com/SiaFoundation/walletd/issues/96
5 changes: 5 additions & 0 deletions .changeset/wicked-buses-tap.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@siafoundation/react-walletd': minor
---

Added useRescanStart, useRescanStatus.
7 changes: 6 additions & 1 deletion apps/walletd-e2e/src/fixtures/createWallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export async function createWallet({
mnemonic,
newWallet,
responses = {},
expects = {},
}: {
page: Page
mnemonic: string
Expand All @@ -23,12 +24,16 @@ export async function createWallet({
fund?: WalletFundResponse
addresses?: WalletAddressesResponse
}
expects?: {
fundSiacoinPost?: (data: string | null) => void
}
}) {
const wallets = await mockApiWallets({ page, createWallet: newWallet })
await mockApiWallet({
page,
wallet: newWallet,
responses,
expects,
})

await expect(page.getByRole('button', { name: 'Add wallet' })).toBeVisible()
Expand All @@ -45,5 +50,5 @@ export async function createWallet({
page.getByText(`Wallet ${newWallet.name.slice(0, 5)}`)
).toBeVisible()
await page.locator('input[name=mnemonic]').fill(mnemonic)
await page.getByRole('button', { name: 'Continue' }).click()
await page.getByRole('button', { name: 'Generate addresses' }).click()
}
60 changes: 58 additions & 2 deletions apps/walletd-e2e/src/specs/seedGenerateAddresses.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { navigateToWallet } from '../fixtures/navigateToWallet'
import {
getMockScenarioSeedWallet,
mockApiDefaults,
getMockRescanResponse,
} from '@siafoundation/mock-walletd'
import { WalletAddressesResponse } from '@siafoundation/react-walletd'

Expand All @@ -19,7 +20,53 @@ function getDefaultMockWalletResponses() {
}

test('generate new addresses', async ({ page }) => {
await mockApiDefaults({ page })
const rescanResponse = getMockRescanResponse()
await mockApiDefaults({
page,
responses: {
rescan: rescanResponse,
},
})
await login({ page })

const mocks = getMockScenarioSeedWallet()
const { newWallet, mnemonic } = mocks
await createWallet({
page,
newWallet,
mnemonic,
responses: getDefaultMockWalletResponses(),
})
await navigateToWallet({ page, wallet: newWallet })
await page.getByLabel('view addresses').click()
await page.getByRole('button', { name: 'Add addresses' }).click()
await page.locator('input[name=count]').fill('5')
await page.getByRole('button', { name: 'Generate addresses' }).click()
await expect(
page.getByText('65b40f6a720352ad5b9546b9f5077209672914cc...')
).toBeVisible()
await expect(
page.getByText('e94e8113563a549f95ff3904dccf77f1b8fbaad4...')
).toBeVisible()
await expect(
page.getByText('cc7241334772c6d10d47882b06b21a60242a19c3...')
).toBeVisible()
await expect(
page.getByText('170173c40ca0f39f9618da30af14c390c7ce7024...')
).toBeVisible()
await expect(
page.getByText('90c6057cdd2463eca61f83796e83152dbba28b6c...')
).toBeVisible()
})

test('generate new addresses and rescan', async ({ page }) => {
const rescanResponse = getMockRescanResponse()
await mockApiDefaults({
page,
responses: {
rescan: rescanResponse,
},
})
await login({ page })

const mocks = getMockScenarioSeedWallet()
Expand All @@ -34,7 +81,15 @@ test('generate new addresses', async ({ page }) => {
await page.getByLabel('view addresses').click()
await page.getByRole('button', { name: 'Add addresses' }).click()
await page.locator('input[name=count]').fill('5')
await page.getByRole('button', { name: 'Continue' }).click()
await page.getByLabel('shouldRescan').click()
await expect(page.locator('input[name=rescanStartHeight]')).toHaveValue(
'61,676'
)
rescanResponse.index.height = 30_000
await page
.getByRole('button', { name: 'Generate addresses and rescan' })
.click()
await expect(page.getByText('Rescanning the blockchain')).toBeVisible()
await expect(
page.getByText('65b40f6a720352ad5b9546b9f5077209672914cc...')
).toBeVisible()
Expand All @@ -50,4 +105,5 @@ test('generate new addresses', async ({ page }) => {
await expect(
page.getByText('90c6057cdd2463eca61f83796e83152dbba28b6c...')
).toBeVisible()
await expect(page.getByText('Scanning...')).toBeVisible()
})
90 changes: 90 additions & 0 deletions apps/walletd-e2e/src/specs/seedSendSiacoin.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,24 @@ test('send siacoin with a seed wallet', async ({ page }) => {
newWallet,
mnemonic,
responses: getDefaultMockWalletResponses(mocks),
expects: {
fundSiacoinPost: (data) =>
expect(data).toEqual(
JSON.stringify({
amount: '1003930000000000000000000',
changeAddress: mocks.changeAddress,
transaction: {
minerFees: ['3930000000000000000000'],
siacoinOutputs: [
{
value: '1000000000000000000000000',
address: mocks.receiveAddress,
},
],
},
})
),
},
})
await navigateToWallet({ page, wallet: newWallet })
await page.getByLabel('send').click()
Expand Down Expand Up @@ -71,6 +89,24 @@ test('errors if the input to sign is not found on the transaction', async ({
...getDefaultMockWalletResponses(mocks),
fund: mockFundInvalid,
},
expects: {
fundSiacoinPost: (data) =>
expect(data).toEqual(
JSON.stringify({
amount: '1003930000000000000000000',
changeAddress: mocks.changeAddress,
transaction: {
minerFees: ['3930000000000000000000'],
siacoinOutputs: [
{
value: '1000000000000000000000000',
address: mocks.receiveAddress,
},
],
},
})
),
},
})
await navigateToWallet({ page, wallet: newWallet })
await page.getByLabel('send').click()
Expand Down Expand Up @@ -107,6 +143,24 @@ test('errors if the inputs matching utxo is not found', async ({ page }) => {
...getDefaultMockWalletResponses(mocks),
outputsSiacoin: mockOutputsInvalid,
},
expects: {
fundSiacoinPost: (data) =>
expect(data).toEqual(
JSON.stringify({
amount: '1003930000000000000000000',
changeAddress: mocks.changeAddress,
transaction: {
minerFees: ['3930000000000000000000'],
siacoinOutputs: [
{
value: '1000000000000000000000000',
address: mocks.receiveAddress,
},
],
},
})
),
},
})
await navigateToWallet({ page, wallet: newWallet })
await page.getByLabel('send').click()
Expand Down Expand Up @@ -144,6 +198,24 @@ test('errors if the address is missing its index', async ({ page }) => {
...getDefaultMockWalletResponses(mocks),
addresses: mockAddressesInvalid,
},
expects: {
fundSiacoinPost: (data) =>
expect(data).toEqual(
JSON.stringify({
amount: '1003930000000000000000000',
changeAddress: mocks.changeAddress,
transaction: {
minerFees: ['3930000000000000000000'],
siacoinOutputs: [
{
value: '1000000000000000000000000',
address: mocks.receiveAddress,
},
],
},
})
),
},
})
await navigateToWallet({ page, wallet: newWallet })
await page.getByLabel('send').click()
Expand Down Expand Up @@ -181,6 +253,24 @@ test('errors if the address is missing its public key', async ({ page }) => {
...getDefaultMockWalletResponses(mocks),
addresses: mockAddressesInvalid,
},
expects: {
fundSiacoinPost: (data) =>
expect(data).toEqual(
JSON.stringify({
amount: '1003930000000000000000000',
changeAddress: mocks.changeAddress,
transaction: {
minerFees: ['3930000000000000000000'],
siacoinOutputs: [
{
value: '1000000000000000000000000',
address: mocks.receiveAddress,
},
],
},
})
),
},
})
await navigateToWallet({ page, wallet: newWallet })
await page.getByLabel('send').click()
Expand Down
77 changes: 77 additions & 0 deletions apps/walletd/components/RescanStatus.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import {
Panel,
ProgressBar,
Separator,
Text,
} from '@siafoundation/design-system'
import { useRescanStatus } from '@siafoundation/react-walletd'
import { useSyncStatus } from '../hooks/useSyncStatus'
import { formatRelative } from 'date-fns'
import { defaultDatasetRefreshInterval } from '../config/swr'

export function RescanStatus() {
const syncStatus = useSyncStatus()
const rescanStatus = useRescanStatus({
config: {
swr: {
refreshInterval: defaultDatasetRefreshInterval,
},
},
})

if (!rescanStatus.data) {
return null
}

const isScanning = rescanStatus.data.index.height < syncStatus.nodeBlockHeight
const showAsRescanning = syncStatus.isSynced && isScanning

if (!showAsRescanning) {
return null
}

return (
<div className="z-20 fixed bottom-5 left-1/2 -translate-x-1/2 flex justify-center">
<Panel className="px-2 py-2 w-[400px] overflow-hidden">
<Text weight="medium" className="pb-2">
Rescanning the blockchain
</Text>
<div className="flex flex-col gap-1">
<ProgressBar
variant="accent"
value={rescanStatus.data.index.height}
max={syncStatus.nodeBlockHeight}
/>
<div className="flex justify-between gap-3">
<Text color="verySubtle" size="12" ellipsis>
{rescanStatus.data.error ? 'Stopped' : 'Scanning...'}
</Text>
<Text color="verySubtle" size="12" noWrap>
{`${rescanStatus.data.index.height.toLocaleString()} / ${syncStatus.nodeBlockHeight.toLocaleString()}`}
</Text>
</div>
</div>
<Separator className="w-full mt-2 mb-1" />
<div className="flex justify-between items-center">
{rescanStatus.data.error && (
<Text color="red" size="12">
Error rescanning the blockchain
</Text>
)}
<div className="flex-1" />
<Text color="subtle" size="12">
Started{' '}
{formatRelative(new Date(rescanStatus.data.startTime), new Date())}
</Text>
</div>
{rescanStatus.data.error && (
<div className="flex flex-col gap-1 overflow-hidden pt-1">
<Text color="contrast" size="12">
{rescanStatus.data.error}
</Text>
</div>
)}
</Panel>
</div>
)
}
10 changes: 10 additions & 0 deletions apps/walletd/components/WalletContextMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
Unlocked16,
Edit16,
Delete16,
Scan16,
} from '@siafoundation/react-icons'
import { useDialog } from '../contexts/dialog'
import { WalletData } from '../contexts/wallets/types'
Expand Down Expand Up @@ -66,6 +67,15 @@ export function WalletContextMenu({
</DropdownMenuLeftSlot>
Delete wallet
</DropdownMenuItem>
<DropdownMenuItem
onClick={(e) => e.stopPropagation()}
onSelect={() => openDialog('walletsRescan')}
>
<DropdownMenuLeftSlot>
<Scan16 />
</DropdownMenuLeftSlot>
Rescan blockchain
</DropdownMenuItem>
</DropdownMenu>
)
}

0 comments on commit 6cd613c

Please sign in to comment.