Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

React redux implementation, fetching current price of BTC #245

Merged
merged 26 commits into from
Mar 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
325b952
Added redux to dApp
ioay Feb 13, 2024
e1a31a1
Show values in USD
ioay Feb 19, 2024
1c58875
Merge branch 'main' into react-redux
ioay Feb 20, 2024
ef6d159
React redux implementation, fetching current price of BTC
ioay Feb 20, 2024
553fb98
Disable eslint:no-param-reassign rule for slice state
ioay Feb 20, 2024
bcf54d4
Changed redux files naming convention, updated currency calculation
ioay Feb 21, 2024
6e5ad8f
Merge branch 'main' into react-redux
ioay Feb 21, 2024
b88ed15
Fix the lockfile: pnpm-lock
ioay Feb 21, 2024
7628f66
Added an explicit type to the action.payload
ioay Feb 22, 2024
9390ad9
Using useAppSelector in CurrencyBalanceWithConversion component
ioay Feb 22, 2024
3f07d13
Added generic currency conversion
ioay Feb 22, 2024
88171fe
Added try catch block for handling thunk error
ioay Feb 22, 2024
9c99552
Minor changes to btc->usd conversion in useCurrencyConversion
ioay Feb 25, 2024
2426636
Changed asyncWrapper to logPromiseFailure
ioay Feb 25, 2024
49a0419
Structure update: utils/externalApi -> api/coingeckoApi
ioay Feb 27, 2024
f3ac5a3
Added CurrencyConversionType
ioay Feb 27, 2024
9861fe6
Added memoization for useCurrencyConversion hook
ioay Feb 27, 2024
6b01f89
useCurrencyConversion hook update
ioay Feb 29, 2024
33362be
Moved initialization hooks to useInitApp
ioay Feb 29, 2024
a6bbfd7
Merge branch 'main' into react-redux
ioay Feb 29, 2024
ab1db58
package.json fix after merge
ioay Feb 29, 2024
cb3d8a4
Udpdated structure for exchangeApi
ioay Feb 29, 2024
2efebb8
Return 0 if error occurs in btcThunk
ioay Feb 29, 2024
7062ced
Created useFetchBTCPriceUsd hook, utils structure updated
ioay Mar 1, 2024
2594155
Merge branch 'main' into react-redux
ioay Mar 4, 2024
fee4422
Merge branch 'main' into react-redux
ioay Mar 4, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions dapp/.eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,16 @@
"react/require-default-props": [0],
"no-console": ["error", { "allow": ["error"] }],
},
"overrides": [
{
"files": [
"src/store/**/*Slice.ts"
],
"rules": {
"no-param-reassign": ["error", {"ignorePropertyModificationsFor": ["state"] }]
}
}
],
"settings": {
"import/resolver": {
"alias": {
Expand Down
2 changes: 2 additions & 0 deletions dapp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"@emotion/styled": "^11.11.0",
"@ledgerhq/wallet-api-client": "^1.5.0",
"@ledgerhq/wallet-api-client-react": "^1.3.0",
"@reduxjs/toolkit": "^2.2.0",
"@sentry/react": "^7.98.0",
"@sentry/types": "^7.102.0",
"@tanstack/react-table": "^8.11.3",
Expand All @@ -31,6 +32,7 @@
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-number-format": "^5.3.1",
"react-redux": "^9.1.0",
"react-router-dom": "^6.22.0",
"recharts": "^2.12.0"
},
Expand Down
20 changes: 10 additions & 10 deletions dapp/src/DApp.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import React from "react"
import { Box, ChakraProvider } from "@chakra-ui/react"
import { Provider as ReduxProvider } from "react-redux"
import { RouterProvider } from "react-router-dom"
import { useInitializeAcreSdk } from "./hooks"
import { useInitApp } from "./hooks"
import { store } from "./store"
import theme from "./theme"
import {
DocsDrawerContextProvider,
Expand All @@ -15,13 +17,9 @@ import Sidebar from "./components/Sidebar"
import DocsDrawer from "./components/DocsDrawer"
import GlobalStyles from "./components/GlobalStyles"
import { router } from "./router"
import { useSentry } from "./hooks/sentry"

function DApp() {
// TODO: Let's uncomment when dark mode is ready
// useDetectThemeMode()
useSentry()
useInitializeAcreSdk()
useInitApp()
kkosiorowska marked this conversation as resolved.
Show resolved Hide resolved

return (
<>
Expand All @@ -42,10 +40,12 @@ function DAppProviders() {
<AcreSdkProvider>
<DocsDrawerContextProvider>
<SidebarContextProvider>
<ChakraProvider theme={theme}>
<GlobalStyles />
<DApp />
</ChakraProvider>
<ReduxProvider store={store}>
<ChakraProvider theme={theme}>
<GlobalStyles />
<DApp />
</ChakraProvider>
</ReduxProvider>
</SidebarContextProvider>
</DocsDrawerContextProvider>
</AcreSdkProvider>
Expand Down
4 changes: 2 additions & 2 deletions dapp/src/components/Header/ConnectWallet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
import { CurrencyBalance } from "#/components/shared/CurrencyBalance"
import { TextMd } from "#/components/shared/Typography"
import { Bitcoin, EthereumIcon } from "#/assets/icons"
import { truncateAddress, asyncWrapper } from "#/utils"
import { truncateAddress, logPromiseFailure } from "#/utils"

export type ConnectButtonsProps = {
leftIcon: typeof Icon
Expand All @@ -25,7 +25,7 @@ function ConnectButton({
const colorScheme = !account ? "error" : undefined

const handleClick = () => {
asyncWrapper(requestAccount())
logPromiseFailure(requestAccount())
}

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
} from "#/hooks"
import Alert from "#/components/shared/Alert"
import { TextMd } from "#/components/shared/Typography"
import { asyncWrapper } from "#/utils"
import { logPromiseFailure } from "#/utils"
import { PROCESS_STATUSES } from "#/types"
import StakingStepsModalContent from "./StakingStepsModalContent"

Expand Down Expand Up @@ -42,7 +42,7 @@ export default function DepositBTCModal() {
// to make sure for the moment that it doesn't return an error about funds not found
// TODO: Remove the delay when SDK is updated
setTimeout(() => {
asyncWrapper(handleStake())
logPromiseFailure(handleStake())
}, 10000)
}, [setStatus, handleStake])

Expand All @@ -62,7 +62,7 @@ export default function DepositBTCModal() {
// TODO: Display the correct message for the user
if (response.verificationStatus !== "valid") return

asyncWrapper(sendBitcoinTransaction(tokenAmount?.amount, btcAddress))
logPromiseFailure(sendBitcoinTransaction(tokenAmount?.amount, btcAddress))
}, [
btcAddress,
depositReceipt,
Expand All @@ -73,7 +73,7 @@ export default function DepositBTCModal() {
])

const handledDepositBTCWrapper = useCallback(() => {
asyncWrapper(handledDepositBTC())
logPromiseFailure(handledDepositBTC())
}, [handledDepositBTC])

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
useModalFlowContext,
useStakeFlowContext,
} from "#/hooks"
import { asyncWrapper } from "#/utils"
import { logPromiseFailure } from "#/utils"
import AlertReceiveSTBTC from "#/components/shared/AlertReceiveSTBTC"
import { PROCESS_STATUSES } from "#/types"
import StakingStepsModalContent from "./StakingStepsModalContent"
Expand All @@ -15,7 +15,7 @@ export default function SignMessageModal() {
const handleSignMessage = useExecuteFunction(signMessage, goNext)

const handleSignMessageWrapper = useCallback(() => {
asyncWrapper(handleSignMessage())
logPromiseFailure(handleSignMessage())
}, [handleSignMessage])

useEffect(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { PROCESS_STATUSES } from "#/types"
import { Button, ModalBody, ModalFooter, ModalHeader } from "@chakra-ui/react"
import { TextMd } from "#/components/shared/Typography"
import AlertReceiveSTBTC from "#/components/shared/AlertReceiveSTBTC"
import { asyncWrapper } from "#/utils"
import { logPromiseFailure } from "#/utils"

export default function SignMessageModal() {
const { setStatus } = useModalFlowContext()
Expand Down Expand Up @@ -34,7 +34,7 @@ export default function SignMessageModal() {

// TODO: Remove when SDK is ready
setTimeout(() => {
asyncWrapper(handleSignMessage())
logPromiseFailure(handleSignMessage())
}, 5000)
}, [setStatus, handleSignMessage])

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
} from "#/hooks"
import { ACTION_FLOW_TYPES, ActionFlowType } from "#/types"
import { TokenAmountFormValues } from "#/components/shared/TokenAmountForm/TokenAmountFormBase"
import { asyncWrapper } from "#/utils"
import { logPromiseFailure } from "#/utils"
import StakeFormModal from "../ActiveStakingStep/StakeFormModal"
import UnstakeFormModal from "../ActiveUnstakingStep/UnstakeFormModal"

Expand Down Expand Up @@ -59,7 +59,8 @@ function ActionFormModal({ defaultType }: { defaultType: ActionFlowType }) {
)

const handleSubmitFormWrapper = useCallback(
(values: TokenAmountFormValues) => asyncWrapper(handleSubmitForm(values)),
(values: TokenAmountFormValues) =>
logPromiseFailure(handleSubmitForm(values)),
[handleSubmitForm],
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
} from "@chakra-ui/react"
import { TextMd } from "#/components/shared/Typography"
import Alert from "#/components/shared/Alert"
import { asyncWrapper, getCurrencyByType } from "#/utils"
import { logPromiseFailure, getCurrencyByType } from "#/utils"
import { CurrencyType, RequestAccountParams } from "#/types"

type MissingAccountModalProps = {
Expand All @@ -27,7 +27,7 @@ export default function MissingAccountModal({
const { name, symbol } = getCurrencyByType(currency)

const handleClick = () => {
asyncWrapper(requestAccount())
logPromiseFailure(requestAccount())
}

return (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,15 @@
import React, { useMemo } from "react"
import React from "react"
import { useCurrencyConversion } from "#/hooks"
import { CurrencyBalance, CurrencyBalanceProps } from "../CurrencyBalance"

const MOCK_CONVERSION_AMOUNT = 100

export function CurrencyBalanceWithConversion({
from,
to,
}: {
from: CurrencyBalanceProps
to: CurrencyBalanceProps
}) {
// TODO: Make the correct conversion
const conversionAmount = useMemo(() => {
if (!from.amount || BigInt(from.amount) < 0n) return undefined

return MOCK_CONVERSION_AMOUNT
}, [from.amount])
const conversionAmount = useCurrencyConversion({ from, to })

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if this is the correct approach in this case. 🤔 It seems to me that we should pass here only what we need. Without that useCurrencyConversion has access to other properties anyway.

Screenshot 2024-03-04 at 14 54 49

cc @r-czajkowski

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general, this is a classic approach and there is nothing wrong with it as long as the object contains the necessary data of a certain type. Objects in JavaScript are passed by reference, so in terms of performance, there is nothing wrong with it either.

return (
<>
Expand Down
4 changes: 2 additions & 2 deletions dapp/src/components/shared/Form/FormTokenBalanceInput.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from "react"
import { useField } from "formik"
import { asyncWrapper } from "#/utils"
import { logPromiseFailure } from "#/utils"
import TokenBalanceInput, { TokenBalanceInputProps } from "../TokenBalanceInput"

export type FormTokenBalanceInputProps = {
Expand All @@ -13,7 +13,7 @@ export function FormTokenBalanceInput({
const [field, meta, helpers] = useField(name)

const setAmount = (value?: bigint) => {
asyncWrapper(helpers.setValue(value))
logPromiseFailure(helpers.setValue(value))
}

return (
Expand Down
4 changes: 4 additions & 0 deletions dapp/src/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from "./store"
export * from "./useDetectThemeMode"
export * from "./useRequestBitcoinAccount"
export * from "./useRequestEthereumAccount"
Expand All @@ -12,4 +13,7 @@ export * from "./useInitializeAcreSdk"
export * from "./useTransactionHistoryTable"
export * from "./useExecuteFunction"
export * from "./useStakeFlowContext"
export * from "./useInitApp"
export * from "./useCurrencyConversion"
export * from "./useDepositTelemetry"
export * from "./useFetchBTCPriceUSD"
2 changes: 2 additions & 0 deletions dapp/src/hooks/store/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./useAppDispatch"
export * from "./useAppSelector"
4 changes: 4 additions & 0 deletions dapp/src/hooks/store/useAppDispatch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { useDispatch } from "react-redux"
import { AppDispatch } from "#/store"

export const useAppDispatch = useDispatch.withTypes<AppDispatch>()
4 changes: 4 additions & 0 deletions dapp/src/hooks/store/useAppSelector.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { useSelector } from "react-redux"
import { RootState } from "#/store"

export const useAppSelector = useSelector.withTypes<RootState>()
39 changes: 39 additions & 0 deletions dapp/src/hooks/useCurrencyConversion.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { CURRENCIES_BY_TYPE } from "#/constants"
import { selectBtcUsdPrice } from "#/store/btc"
import { bigIntToUserAmount } from "#/utils"
import { CurrencyType } from "#/types"
import { useMemo } from "react"
import { useAppSelector } from "./store"

type CurrencyConversionType = {
currency: CurrencyType
amount?: number | string
}

// TODO: should be updated to handle another currencies
const isBtcUsdConversion = (
from: CurrencyConversionType,
to: CurrencyConversionType,
): boolean => from.currency === "bitcoin" && to.currency === "usd"

export function useCurrencyConversion({
from,
to,
}: {
from: CurrencyConversionType
to: CurrencyConversionType
}) {
const price = useAppSelector(
isBtcUsdConversion(from, to) ? selectBtcUsdPrice : () => undefined,
)
const conversionAmount = useMemo(() => {
if (!from.amount || !price) return undefined

const { amount, currency } = from
const { decimals, desiredDecimals } = CURRENCIES_BY_TYPE[currency]

return bigIntToUserAmount(BigInt(amount), decimals, desiredDecimals) * price
}, [from, price])

return conversionAmount
}
12 changes: 12 additions & 0 deletions dapp/src/hooks/useFetchBTCPriceUSD.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { useEffect } from "react"
import { fetchBTCPriceUSD } from "#/store/btc"
import { logPromiseFailure } from "#/utils"
import { useAppDispatch } from "./store"

export function useFetchBTCPriceUSD() {
const dispatch = useAppDispatch()

useEffect(() => {
logPromiseFailure(dispatch(fetchBTCPriceUSD()))
}, [dispatch])
}
11 changes: 11 additions & 0 deletions dapp/src/hooks/useInitApp.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { useSentry } from "./sentry"
import { useInitializeAcreSdk } from "./useInitializeAcreSdk"
import { useFetchBTCPriceUSD } from "./useFetchBTCPriceUSD"

export function useInitApp() {
// TODO: Let's uncomment when dark mode is ready
// useDetectThemeMode()
useSentry()
useInitializeAcreSdk()
useFetchBTCPriceUSD()
}
4 changes: 2 additions & 2 deletions dapp/src/hooks/useInitializeAcreSdk.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useEffect } from "react"
import { ETHEREUM_NETWORK } from "#/constants"
import { asyncWrapper } from "#/utils"
import { logPromiseFailure } from "#/utils"
import { useAcreContext } from "#/acre-react/hooks"
import { useWalletContext } from "./useWalletContext"

Expand All @@ -14,6 +14,6 @@ export function useInitializeAcreSdk() {
const initSDK = async (ethAddress: string) => {
await init(ethAddress, ETHEREUM_NETWORK)
}
asyncWrapper(initSDK(ethAccount.address))
logPromiseFailure(initSDK(ethAccount.address))
}, [ethAccount?.address, init])
}
3 changes: 3 additions & 0 deletions dapp/src/store/btc/btcSelector.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { RootState } from ".."

export const selectBtcUsdPrice = (state: RootState) => state.btc.usdPrice
34 changes: 34 additions & 0 deletions dapp/src/store/btc/btcSlice.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { PayloadAction, createSlice } from "@reduxjs/toolkit"
import { fetchBTCPriceUSD } from "./btcThunk"

type BtcState = {
isLoadingPriceUSD: boolean
usdPrice: number
Comment on lines +5 to +6
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-blocking comment.

Maybe too late but I would consider using TanStack Query instead of redux at least for this feature. We can cache API response and we can set a time when the data should be re-fetched etc see https://tanstack.com/query/latest/docs/framework/react/reference/useQuery especially: staleTime and gcTime options. My point is that if we add something else to btc store we need to duplicate the logic for loading and errors indicator and we have a lot of boilerplate to create a simple feature such as fetching USD price.

We could use redux to store data that should be updated based on the emitted on-chain events but it should probably be possible with this lib as well.

Copy link
Contributor Author

@ioay ioay Feb 27, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At first glance, it looks good, but I wonder if instead of TanStack Query we should think about using of addon to the already installed redux-toolkit package: https://redux-toolkit.js.org/tutorials/rtk-query. What do you think?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good too. But I would go with TanStack Query - it looks cool and powerful, is well-documented, there are a lot of examples, and is popular and well-maintained (38k stars on GitHub, only 30 issues and 18 opened pull requests). We can achieve all the stuff we need with TanStack Query w/o redux and we can easily plugin GraphQl which will be needed once we create our subgraph for Acre network.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a task to deal with TanStack Query: #277

}

const initialState: BtcState = {
isLoadingPriceUSD: false,
usdPrice: 0,
}

// Store Bitcoin data such as balance, balance in usd and other related data to Bitcoin chain.
export const btcSlice = createSlice({
name: "btc",
initialState,
reducers: {},
extraReducers: (builder) => {
builder.addCase(fetchBTCPriceUSD.pending, (state) => {
state.isLoadingPriceUSD = true
})
builder.addCase(fetchBTCPriceUSD.rejected, (state) => {
state.isLoadingPriceUSD = false
})
builder.addCase(
fetchBTCPriceUSD.fulfilled,
(state, action: PayloadAction<number>) => {
state.isLoadingPriceUSD = false
state.usdPrice = action.payload
},
)
},
})
Loading
Loading