fix: for upcoming v0.53.1 (#731)
* fix: swap error caused by invalid id and batchcount * fix: enhance creation messages for admin drive and user drives (#238) * fix: enhance creation messages for admin drive and user drives * fix: update creation message to indicate longer processing time * fix: identity and wallet creation (#240) * fix: asset preview types * fix: fm search unicode text * fix: feed identity and stamp usage * fix: ui display changes (#239) * fix: ui layout changes * fix: stamp buy and dilute (#242) fix: vite polyfill warning for stream refactor: stamp depth and amount validation * fix: spdv-917 (#243) * fix: spdv-917 * refactor: spdv-917 * fix: add syncing message for Bee node and update page state handling spdv-955 (#244) * fix: spdv-1037 (#245) * fix: spdv-1038 (#246) * fix: spdv-1038 * refactor: spdv-1038 * fix: validate stamp before every upgrade click (#247) * fix: validate stamp before every upgrade click --------- Co-authored-by: Roland Seres <roland.seres90@gmail.com> * fix: use tochecksum() and toplurbigint() for ethers v6 compatibility (#248) --------- Co-authored-by: Balint Ujvari <balint.ujvari@solarpunk.buzz> Co-authored-by: Bálint Ujvári <58116288+bosi95@users.noreply.github.com> Co-authored-by: rolandlor <33499567+rolandlor@users.noreply.github.com> Co-authored-by: Roland Seres <roland.seres90@gmail.com>
This commit is contained in:
+18
-2
@@ -1,9 +1,10 @@
|
||||
import { PostageBatch } from '@ethersphere/bee-js'
|
||||
import { Bee, PostageBatch } from '@ethersphere/bee-js'
|
||||
import { DriveInfo, FileInfo } from '@solarpunkltd/file-manager-lib'
|
||||
import { ReactElement, useEffect, useMemo, useState } from 'react'
|
||||
import { createPortal } from 'react-dom'
|
||||
import AlertIcon from 'remixicon-react/AlertLineIcon'
|
||||
|
||||
import { validateStampStillExists } from '../../utils/bee'
|
||||
import { getDaysLeft } from '../../utils/common'
|
||||
import { Button } from '../Button/Button'
|
||||
import { UpgradeDriveModal } from '../UpgradeDriveModal/UpgradeDriveModal'
|
||||
@@ -16,19 +17,23 @@ import '../../styles/global.scss'
|
||||
const EXPIRING_ITEMS_PAGE_SIZE = 3
|
||||
|
||||
interface ExpiringNotificationModalProps {
|
||||
bee: Bee
|
||||
stamps: PostageBatch[]
|
||||
drives: DriveInfo[]
|
||||
files: FileInfo[]
|
||||
onCancelClick: () => void
|
||||
setErrorMessage?: (error: string) => void
|
||||
setShowError: (show: boolean) => void
|
||||
}
|
||||
|
||||
export function ExpiringNotificationModal({
|
||||
bee,
|
||||
stamps,
|
||||
drives,
|
||||
files,
|
||||
onCancelClick,
|
||||
setErrorMessage,
|
||||
setShowError,
|
||||
}: ExpiringNotificationModalProps): ReactElement {
|
||||
const [showUpgradeDriveModal, setShowUpgradeDriveModal] = useState(false)
|
||||
const [actualStamp, setActualStamp] = useState<PostageBatch | undefined>(undefined)
|
||||
@@ -75,7 +80,18 @@ export function ExpiringNotificationModal({
|
||||
files={files}
|
||||
currentPage={currentPage}
|
||||
index={index}
|
||||
onUpgradeClick={(stamp, drive) => {
|
||||
onUpgradeClick={async (stamp, drive) => {
|
||||
const isStampValid = await validateStampStillExists(bee, stamp.batchID)
|
||||
|
||||
if (!isStampValid) {
|
||||
setErrorMessage?.(
|
||||
`Drive ${drive.name} has expired. Please clear the browser cache and reload the page.`,
|
||||
)
|
||||
setShowError(true)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
setActualStamp(stamp)
|
||||
setActualDrive(drive)
|
||||
setShowUpgradeDriveModal(true)
|
||||
|
||||
@@ -10,6 +10,7 @@ import React, {
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react'
|
||||
import { createPortal } from 'react-dom'
|
||||
|
||||
import { useSearch } from '../../../../pages/filemanager/SearchContext'
|
||||
import { useView } from '../../../../pages/filemanager/ViewContext'
|
||||
@@ -87,7 +88,9 @@ function ErrorModalBlock({
|
||||
return null
|
||||
}
|
||||
|
||||
return <ErrorModal label={label} onClick={onOk} />
|
||||
const modalRoot = document.querySelector('.fm-main') || document.body
|
||||
|
||||
return createPortal(<ErrorModal label={label} onClick={onOk} />, modalRoot)
|
||||
}
|
||||
|
||||
const extractFilesFromClipboardEvent = (e: React.ClipboardEvent): File[] => {
|
||||
@@ -431,8 +434,10 @@ export function FileBrowser({ errorMessage, setErrorMessage }: FileBrowserProps)
|
||||
if (rafIdRef.current) {
|
||||
cancelAnimationFrame(rafIdRef.current)
|
||||
}
|
||||
|
||||
setShowError(false)
|
||||
}
|
||||
}, [])
|
||||
}, [setShowError])
|
||||
|
||||
useEffect(() => {
|
||||
let title = currentDrive?.name || ''
|
||||
|
||||
@@ -30,7 +30,7 @@ export function NotificationBar({ setErrorMessage }: NotificationBarProps): Reac
|
||||
const [stampsToExpire, setStampsToExpire] = useState<PostageBatch[]>([])
|
||||
const [drivesToExpire, setDrivesToExpire] = useState<DriveInfo[]>([])
|
||||
const { beeApi } = useContext(SettingsContext)
|
||||
const { drives, files, adminDrive } = useContext(FMContext)
|
||||
const { drives, files, adminDrive, setShowError } = useContext(FMContext)
|
||||
|
||||
const showExpiration = stampsToExpire.length > 0
|
||||
|
||||
@@ -109,8 +109,9 @@ export function NotificationBar({ setErrorMessage }: NotificationBarProps): Reac
|
||||
<div className="fm-notification-bar fm-red-font" onClick={() => setShowExpiringModal(true)}>
|
||||
{stampsToExpire.length} drive{stampsToExpire.length > 1 ? 's' : ''} expiring soon <UpIcon size="16px" />
|
||||
</div>
|
||||
{showExpiringModal && (
|
||||
{showExpiringModal && beeApi && (
|
||||
<ExpiringNotificationModal
|
||||
bee={beeApi}
|
||||
stamps={stampsToExpire}
|
||||
drives={drivesToExpire}
|
||||
files={files}
|
||||
@@ -118,6 +119,7 @@ export function NotificationBar({ setErrorMessage }: NotificationBarProps): Reac
|
||||
setShowExpiringModal(false)
|
||||
}}
|
||||
setErrorMessage={setErrorMessage}
|
||||
setShowError={setShowError}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { BeeModes } from '@ethersphere/bee-js'
|
||||
import { DriveInfo, FileManagerBase } from '@solarpunkltd/file-manager-lib'
|
||||
import { ReactElement, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'
|
||||
|
||||
@@ -44,6 +45,18 @@ function InitializationErrorBlock({ onOk }: { onOk: () => void }) {
|
||||
)
|
||||
}
|
||||
|
||||
function UltraLightNodeErrorBlock() {
|
||||
return (
|
||||
<div className="fm-main">
|
||||
<div className="fm-loading">
|
||||
<div className="fm-loading-title">
|
||||
File Manager is not available with an Ultra-light node. Please upgrade to a Light node to continue.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function ResetModalBlock({ cacheHelpUrl, onConfirm }: { cacheHelpUrl: string; onConfirm: () => void }) {
|
||||
return (
|
||||
<div className="fm-main">
|
||||
@@ -169,6 +182,7 @@ function FileManagerMainContent(props: {
|
||||
|
||||
enum PageState {
|
||||
Connecting = 'connecting', // still warming up — show nothing / loader
|
||||
UltraLightNode = 'ultra-light-node', // ultra-light node — file manager not available
|
||||
NoPrivateKey = 'no-pk', // private key not set
|
||||
Loading = 'loading', // bee ready, pk present, FM init in progress
|
||||
Reset = 'reset', // STATE_INVALID emitted and user has not yet acknowledged
|
||||
@@ -189,7 +203,7 @@ export function FileManagerPage(): ReactElement {
|
||||
const [connectionErrorDismissed, setConnectionErrorDismissed] = useState<boolean>(false)
|
||||
const [cacheHelpUrl, setCacheHelpUrl] = useState<string>(cacheClearUrls[BrowserPlatform.Chrome])
|
||||
|
||||
const { status, chainState } = useContext(BeeContext)
|
||||
const { status, chainState, nodeInfo } = useContext(BeeContext)
|
||||
const { fm, initDone, shallReset, adminDrive, initializationError, notifyPkSaved } = useContext(FMContext)
|
||||
|
||||
useEffect(() => {
|
||||
@@ -228,6 +242,8 @@ export function FileManagerPage(): ReactElement {
|
||||
|
||||
if (!isBeeReady && !initDone) return PageState.Connecting
|
||||
|
||||
if (nodeInfo?.beeMode === BeeModes.ULTRA_LIGHT) return PageState.UltraLightNode
|
||||
|
||||
if (!hasPk) return PageState.NoPrivateKey
|
||||
|
||||
if (!initDone) return PageState.Loading
|
||||
@@ -259,6 +275,7 @@ export function FileManagerPage(): ReactElement {
|
||||
adminDrive,
|
||||
isCreationInProgress,
|
||||
chainState,
|
||||
nodeInfo?.beeMode,
|
||||
])
|
||||
|
||||
const handlePrivateKeySaved = useCallback(() => {
|
||||
@@ -274,6 +291,10 @@ export function FileManagerPage(): ReactElement {
|
||||
const loading = !fm?.adminStamp || !adminDrive
|
||||
const isFormbricksActive = Boolean(fm && fm.adminStamp && adminDrive && !loading)
|
||||
|
||||
if (pageState === PageState.UltraLightNode) {
|
||||
return <UltraLightNodeErrorBlock />
|
||||
}
|
||||
|
||||
if (pageState === PageState.Connecting || pageState === PageState.Loading) {
|
||||
return <LoadingBlock />
|
||||
}
|
||||
|
||||
@@ -191,7 +191,6 @@ export function PostageStampStandardCreation({ onFinished }: Props): ReactElemen
|
||||
<Grid container justifyContent="space-between">
|
||||
<Typography>Corresponding TTL (Time to live)</Typography>
|
||||
<Typography>{amountInput ? getTtl(amountInput) : '-'}</Typography>
|
||||
<Typography>{amountInput ? getTtl(amountInput) : '-'}</Typography>
|
||||
</Grid>
|
||||
</Box>
|
||||
<Box display="flex" justifyContent={'right'} mt={0.5}>
|
||||
|
||||
+2
-2
@@ -84,7 +84,7 @@ export async function sendNativeTransaction(
|
||||
const feedData = await signer.provider.getFeeData()
|
||||
const gasPrice = externalGasPrice ?? DAI.fromWei(feedData.gasPrice?.toString() || '0')
|
||||
const transaction = await signer.sendTransaction({
|
||||
to: to.toHex(),
|
||||
to: to.toChecksum(),
|
||||
value: BigInt(value.toWeiString()),
|
||||
gasPrice: BigInt(gasPrice.toWeiString()),
|
||||
gasLimit: BigInt(21000),
|
||||
@@ -117,7 +117,7 @@ export async function sendBzzTransaction(
|
||||
const feeData = await signer.provider.getFeeData()
|
||||
const gasPrice = feeData.gasPrice || BigInt(0)
|
||||
const bzz = new Contract(BZZ_TOKEN_ADDRESS, bzzABI, signer)
|
||||
const transaction = await bzz.transfer(to.toChecksum(), value, { gasPrice })
|
||||
const transaction = await bzz.transfer(to.toChecksum(), value.toPLURBigInt(), { gasPrice })
|
||||
const receipt = await transaction.wait(1)
|
||||
|
||||
if (!receipt) {
|
||||
|
||||
+52
-4
@@ -1,6 +1,6 @@
|
||||
import { BZZ } from '@ethersphere/bee-js'
|
||||
import { BZZ, DAI } from '@ethersphere/bee-js'
|
||||
|
||||
import { sendBzzTransaction } from '../../src/utils/rpc'
|
||||
import { sendBzzTransaction, sendNativeTransaction } from '../../src/utils/rpc'
|
||||
|
||||
interface MockProvider {
|
||||
getFeeData: jest.Mock
|
||||
@@ -11,12 +11,14 @@ const mockWait = jest.fn()
|
||||
const mockTransfer = jest.fn()
|
||||
const mockGetFeeData = jest.fn()
|
||||
const mockGetNetwork = jest.fn()
|
||||
const mockSendTransaction = jest.fn()
|
||||
const mockProvider: MockProvider = {
|
||||
getFeeData: mockGetFeeData,
|
||||
getNetwork: mockGetNetwork,
|
||||
}
|
||||
|
||||
const value = BZZ.fromDecimalString('1')
|
||||
const bzzValue = BZZ.fromDecimalString('1')
|
||||
const daiValue = DAI.fromDecimalString('1')
|
||||
const privateKey = 'FFFF000000000000000000000000000000000000000000000000000000000000'
|
||||
const jsonRpcProvider = 'http://mock-json-rpc-provider'
|
||||
|
||||
@@ -39,6 +41,7 @@ jest.mock('ethers', () => {
|
||||
|
||||
class Wallet {
|
||||
provider: MockProvider
|
||||
sendTransaction = mockSendTransaction
|
||||
|
||||
constructor(_privateKey: string, provider: MockProvider) {
|
||||
this.provider = provider
|
||||
@@ -64,8 +67,53 @@ describe('sendBzzTransaction', () => {
|
||||
})
|
||||
|
||||
it.each(addresses)('sendBzzTransaction to address: %s', async (address: string) => {
|
||||
await sendBzzTransaction(privateKey, address, value, jsonRpcProvider)
|
||||
await sendBzzTransaction(privateKey, address, bzzValue, jsonRpcProvider)
|
||||
const to = mockTransfer.mock.calls[0][0] as string
|
||||
expect(to.startsWith('0x')).toBe(true)
|
||||
})
|
||||
|
||||
it('passes BZZ value as bigint (not BZZ object)', async () => {
|
||||
await sendBzzTransaction(privateKey, '0x52908400098527886e0f7030069857d2e4169ee7', bzzValue, jsonRpcProvider)
|
||||
const transferredValue = mockTransfer.mock.calls[0][1]
|
||||
expect(typeof transferredValue).toBe('bigint')
|
||||
expect(transferredValue).toBe(bzzValue.toPLURBigInt())
|
||||
})
|
||||
})
|
||||
|
||||
describe('sendNativeTransaction', () => {
|
||||
const addresses = ['52908400098527886e0f7030069857d2e4169ee7', '0x52908400098527886e0f7030069857d2e4169ee7']
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
mockWait.mockResolvedValue({ status: 1 })
|
||||
mockSendTransaction.mockResolvedValue({ wait: mockWait })
|
||||
mockGetFeeData.mockResolvedValue({ gasPrice: BigInt(1) })
|
||||
mockGetNetwork.mockResolvedValue({ chainId: BigInt(100) })
|
||||
})
|
||||
|
||||
it.each(addresses)('sendNativeTransaction to address: %s passes 0x-prefixed address', async (address: string) => {
|
||||
await sendNativeTransaction(privateKey, address, daiValue, jsonRpcProvider)
|
||||
const tx = mockSendTransaction.mock.calls[0][0] as { to: string }
|
||||
expect(tx.to.startsWith('0x')).toBe(true)
|
||||
})
|
||||
|
||||
it('passes DAI value as bigint', async () => {
|
||||
await sendNativeTransaction(privateKey, '0x52908400098527886e0f7030069857d2e4169ee7', daiValue, jsonRpcProvider)
|
||||
const tx = mockSendTransaction.mock.calls[0][0] as { value: bigint }
|
||||
expect(typeof tx.value).toBe('bigint')
|
||||
expect(tx.value).toBe(BigInt(daiValue.toWeiString()))
|
||||
})
|
||||
|
||||
it('uses externalGasPrice when provided', async () => {
|
||||
const externalGasPrice = DAI.fromWei('9999')
|
||||
await sendNativeTransaction(
|
||||
privateKey,
|
||||
'0x52908400098527886e0f7030069857d2e4169ee7',
|
||||
daiValue,
|
||||
jsonRpcProvider,
|
||||
externalGasPrice,
|
||||
)
|
||||
const tx = mockSendTransaction.mock.calls[0][0] as { gasPrice: bigint }
|
||||
expect(tx.gasPrice).toBe(BigInt(externalGasPrice.toWeiString()))
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user