Skip to content

Commit

Permalink
feat: renterd wallet send
Browse files Browse the repository at this point in the history
  • Loading branch information
alexfreska committed Aug 30, 2024
1 parent a8c22bb commit 81d374f
Show file tree
Hide file tree
Showing 30 changed files with 673 additions and 166 deletions.
6 changes: 6 additions & 0 deletions .changeset/moody-zebras-fly.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'hostd': minor
'renterd': minor
---

The send siacoin feature now calculates the fee using the daemon's recommended fee per byte and a standard transaction size.
7 changes: 7 additions & 0 deletions .changeset/shy-dots-complain.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@siafoundation/hostd-types': minor
'@siafoundation/hostd-js': minor
'@siafoundation/hostd-react': minor
---

The wallet send API now supports the subtractMinerFee option.
7 changes: 7 additions & 0 deletions .changeset/thick-rats-smell.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@siafoundation/renterd-js': patch
'@siafoundation/renterd-react': patch
'@siafoundation/renterd-types': patch
---

Fixed the route for the recommended fee API.
7 changes: 7 additions & 0 deletions .changeset/young-gifts-return.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@siafoundation/renterd-js': minor
'@siafoundation/renterd-react': minor
'@siafoundation/renterd-types': minor
---

Added the wallet send API.
27 changes: 27 additions & 0 deletions apps/hostd-e2e/src/fixtures/beforeTest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import {
mockApiSiaCentralExchangeRates,
mockApiSiaCentralHostsNetworkAverages,
} from '@siafoundation/sia-central-mock'
import { login } from './login'
import { navigateToConfig } from './navigate'
import { configResetAllSettings } from './configResetAllSettings'
import { setViewMode } from './configViewMode'
import { Page } from 'playwright'
import { mockApiSiaScanExchangeRates } from './siascan'
import { setCurrencyDisplay } from './preferences'

export async function beforeTest(page: Page, shouldResetConfig = true) {
await mockApiSiaCentralExchangeRates({ page })
await mockApiSiaCentralHostsNetworkAverages({ page })
await mockApiSiaScanExchangeRates({ page })
await login({ page })

// Reset state.
await setCurrencyDisplay(page, 'bothPreferSc')
if (shouldResetConfig) {
await navigateToConfig({ page })
await configResetAllSettings({ page })
await setViewMode({ page, state: 'basic' })
await navigateToConfig({ page })
}
}
11 changes: 8 additions & 3 deletions apps/hostd-e2e/src/fixtures/navigate.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,23 @@
import { Page, expect } from '@playwright/test'

export async function navigateToDashboard({ page }: { page: Page }) {
await page.getByLabel('Overview').click()
await page.getByTestId('sidenav').getByLabel('Overview').click()
await expect(page.getByTestId('navbar').getByText('Overview')).toBeVisible()
}

export async function navigateToConfig({ page }: { page: Page }) {
await page.getByLabel('Configuration').click()
await page.getByTestId('sidenav').getByLabel('Configuration').click()
await expect(
page.getByTestId('navbar').getByText('Configuration')
).toBeVisible()
}

export async function navigateToVolumes({ page }: { page: Page }) {
await page.getByLabel('Volumes').click()
await page.getByTestId('sidenav').getByLabel('Volumes').click()
await expect(page.getByTestId('navbar').getByText('Volumes')).toBeVisible()
}

export async function navigateToWallet(page: Page) {
await page.getByTestId('sidenav').getByLabel('Wallet').click()
await expect(page.getByTestId('navbar').getByText('Wallet')).toBeVisible()
}
11 changes: 11 additions & 0 deletions apps/hostd-e2e/src/fixtures/preferences.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Page } from 'playwright'
import { fillSelectInputByName } from './selectInput'

export async function setCurrencyDisplay(
page: Page,
display: 'sc' | 'fiat' | 'bothPreferSc' | 'bothPreferFiat'
) {
await page.getByLabel('App preferences').click()
await fillSelectInputByName(page, 'currencyDisplay', display)
await page.getByRole('dialog').getByLabel('close').click()
}
10 changes: 10 additions & 0 deletions apps/hostd-e2e/src/fixtures/siascan.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { Page } from 'playwright'

export async function mockApiSiaScanExchangeRates({ page }: { page: Page }) {
await page.route(
'https://api.siascan.com/exchange-rate/siacoin/*',
async (route) => {
await route.fulfill({ json: 0.003944045283 })
}
)
}
25 changes: 5 additions & 20 deletions apps/hostd-e2e/src/specs/config.spec.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,25 @@
import { test, expect } from '@playwright/test'
import { login } from '../fixtures/login'
import {
expectSwitchByLabel,
expectSwitchVisible,
setSwitchByLabel,
} from '../fixtures/switchValue'
import { setViewMode } from '../fixtures/configViewMode'
import { navigateToConfig } from '../fixtures/navigate'
import { mockApiSiaCentralExchangeRates } from '@siafoundation/sia-central-mock'
import { configResetAllSettings } from '../fixtures/configResetAllSettings'
import {
expectTextInputByName,
expectTextInputNotVisible,
fillTextInputByName,
} from '../fixtures/textInput'
import { fillSelectInputByName } from '../fixtures/selectInput'
import { beforeTest } from '../fixtures/beforeTest'

test('basic field change and save behaviour', async ({ page }) => {
// Set up.
await mockApiSiaCentralExchangeRates({ page })
await login({ page })
test.beforeEach(async ({ page }) => {
await beforeTest(page)
})

test('basic field change and save behaviour', async ({ page }) => {
// Reset state.
await navigateToConfig({ page })
await configResetAllSettings({ page })
await setViewMode({ page, state: 'advanced' })

// Test that values can be updated.
Expand Down Expand Up @@ -63,10 +59,6 @@ test('basic field change and save behaviour', async ({ page }) => {
})

test('pin switches should show in both view modes', async ({ page }) => {
// Set up.
await mockApiSiaCentralExchangeRates({ page })
await login({ page })

await navigateToConfig({ page })
await setViewMode({ page, state: 'basic' })
await expectSwitchVisible(page, 'shouldPinStoragePrice')
Expand All @@ -83,14 +75,7 @@ test('pin switches should show in both view modes', async ({ page }) => {
})

test('dynamic max collateral suggestion', async ({ page }) => {
// Set up.
await mockApiSiaCentralExchangeRates({ page })
await login({ page })

// Reset state.
await navigateToConfig({ page })
await configResetAllSettings({ page })
await setViewMode({ page, state: 'basic' })
await fillTextInputByName(page, 'maxCollateral', '777')
await expect(
page
Expand Down
7 changes: 5 additions & 2 deletions apps/hostd-e2e/src/specs/volumes.spec.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
import { test } from '@playwright/test'
import { navigateToVolumes } from '../fixtures/navigate'
import { login } from '../fixtures/login'
import {
createVolume,
deleteVolume,
deleteVolumeIfExists,
} from '../fixtures/volumes'
import { beforeTest } from '../fixtures/beforeTest'

test.beforeEach(async ({ page }) => {
await beforeTest(page, false)
})

test('can create and delete a volume', async ({ page }) => {
const name = 'my-new-volume'
const path = '/data'
await login({ page })
await navigateToVolumes({ page })
await deleteVolumeIfExists(page, name, path)
await createVolume(page, name, path)
Expand Down
156 changes: 156 additions & 0 deletions apps/hostd-e2e/src/specs/wallet.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
import { test, expect } from '@playwright/test'
import { navigateToWallet } from '../fixtures/navigate'
import { random } from '@technically/lodash'
import { beforeTest } from '../fixtures/beforeTest'
import { fillTextInputByName } from '../fixtures/textInput'
import { setSwitchByLabel } from '../fixtures/switchValue'
import BigNumber from 'bignumber.js'

test.beforeEach(async ({ page }) => {
await beforeTest(page, false)
})

test('send siacoin with include fee off', async ({ page }) => {
const receiveAddress =
'5739945c21e60afd70eaf97ccd33ea27836e0219212449f39e4b38acaa8b3119aa4150a9ef0f'
const amount = String(random(1, 5))
const amountString = `${amount}.000 SC`
const amountWithFeeString = `${amount}.012 SC`

await navigateToWallet(page)

// Setup.
await page.getByLabel('send').click()
const sendDialog = page.getByRole('dialog', { name: 'Send siacoin' })
await fillTextInputByName(page, 'address', receiveAddress)
await fillTextInputByName(page, 'siacoin', amount)
await expect(
sendDialog.getByTestId('networkFee').getByText('0.012 SC')
).toBeVisible()
await expect(
sendDialog.getByTestId('total').getByText(amountWithFeeString)
).toBeVisible()

// Confirm.
await page.getByRole('button', { name: 'Generate transaction' }).click()
await expect(
sendDialog.getByTestId('address').getByText(receiveAddress.slice(0, 5))
).toBeVisible()
await expect(
sendDialog.getByTestId('amount').getByText(amountString)
).toBeVisible()
await expect(
sendDialog.getByTestId('networkFee').getByText('0.012 SC')
).toBeVisible()
await expect(
sendDialog.getByTestId('total').getByText(amountWithFeeString)
).toBeVisible()

// Complete.
await page.getByRole('button', { name: 'Broadcast transaction' }).click()
await expect(
page.getByText('Transaction successfully broadcasted.')
).toBeVisible()
await expect(
sendDialog.getByTestId('address').getByText(receiveAddress.slice(0, 5))
).toBeVisible()
await expect(
sendDialog.getByTestId('amount').getByText(amountString)
).toBeVisible()
await expect(
sendDialog.getByTestId('networkFee').getByText('0.012 SC')
).toBeVisible()
await expect(
sendDialog.getByTestId('total').getByText(amountWithFeeString)
).toBeVisible()
await expect(sendDialog.getByTestId('transactionId')).toBeVisible()

// List.
// TODO: Add this after we migrate to the new events API.
// await sendDialog.getByRole('button', { name: 'Close' }).click()
// await expect(page.getByTestId('eventsTable')).toBeVisible()
// await expect(
// page.getByTestId('eventsTable').locator('tbody tr').first()
// ).toBeVisible()
// await expect(
// page
// .getByTestId('eventsTable')
// .locator('tbody tr')
// .first()
// .getByTestId('amount')
// .getByText(`-${amountWithFeeString}`)
// ).toBeVisible()
})

test('send siacoin with include fee on', async ({ page }) => {
const receiveAddress =
'5739945c21e60afd70eaf97ccd33ea27836e0219212449f39e4b38acaa8b3119aa4150a9ef0f'
const amount = new BigNumber(random(1, 5))
const amountString = `${amount.toFixed(3)} SC`
const amountWithoutFeeString = `${amount.minus(0.012).toFixed(3)} SC`

await navigateToWallet(page)

// Setup.
await page.getByLabel('send').click()
const sendDialog = page.getByRole('dialog', { name: 'Send siacoin' })
await fillTextInputByName(page, 'address', receiveAddress)
await fillTextInputByName(page, 'siacoin', amount.toString())
await setSwitchByLabel(page, 'include fee', true)
await expect(
sendDialog.getByTestId('networkFee').getByText('0.012 SC')
).toBeVisible()
await expect(
sendDialog.getByTestId('total').getByText(amountString)
).toBeVisible()

// Confirm.
await page.getByRole('button', { name: 'Generate transaction' }).click()
await expect(
sendDialog.getByTestId('address').getByText(receiveAddress.slice(0, 5))
).toBeVisible()
await expect(
sendDialog.getByTestId('amount').getByText(amountWithoutFeeString)
).toBeVisible()
await expect(
sendDialog.getByTestId('networkFee').getByText('0.012 SC')
).toBeVisible()
await expect(
sendDialog.getByTestId('total').getByText(amountString)
).toBeVisible()

// Complete.
await page.getByRole('button', { name: 'Broadcast transaction' }).click()
await expect(
page.getByText('Transaction successfully broadcasted.')
).toBeVisible()
await expect(
sendDialog.getByTestId('address').getByText(receiveAddress.slice(0, 5))
).toBeVisible()
await expect(
sendDialog.getByTestId('amount').getByText(amountWithoutFeeString)
).toBeVisible()
await expect(
sendDialog.getByTestId('networkFee').getByText('0.012 SC')
).toBeVisible()
await expect(
sendDialog.getByTestId('total').getByText(amountString)
).toBeVisible()
await expect(sendDialog.getByTestId('transactionId')).toBeVisible()

// List.
// TODO: Add this after we migrate to the new events API.
// await sendDialog.getByRole('button', { name: 'Close' }).click()
// await expect(page.getByTestId('eventsTable')).toBeVisible()
// await expect(
// page.getByTestId('eventsTable').locator('tbody tr').first()
// ).toBeVisible()
// await expect(
// page
// .getByTestId('eventsTable')
// .locator('tbody tr')
// .first()
// .getByTestId('amount')
// .getByText(`-${amountWithFeeString}`)
// ).toBeVisible()
})
Loading

0 comments on commit 81d374f

Please sign in to comment.