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 17 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
3 changes: 3 additions & 0 deletions dapp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,17 @@
"@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",
"@tanstack/react-table": "^8.11.3",
"axios": "^1.6.7",
"ethers": "^6.10.0",
"formik": "^2.4.5",
"framer-motion": "^10.16.5",
"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
15 changes: 10 additions & 5 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 { useSentry, useInitializeAcreSdk } from "./hooks"
import { useSentry, useInitializeAcreSdk, useInitApp } from "./hooks"
import { store } from "./store"
import theme from "./theme"
import {
DocsDrawerContextProvider,
Expand All @@ -21,6 +23,7 @@ function DApp() {
// useDetectThemeMode()
useSentry()
useInitializeAcreSdk()
useInitApp()
kkosiorowska marked this conversation as resolved.
Show resolved Hide resolved

return (
<>
Expand All @@ -41,10 +44,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
20 changes: 20 additions & 0 deletions dapp/src/api/coingeckoApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import axios from "axios"
import { CoingeckoIdType, CoingeckoCurrencyType } from "#/types"

const coingeckoApiURL = "https://api.coingecko.com/api/v3"

type CoingeckoSimplePriceResponse = {
data: {
[id in CoingeckoIdType]: Record<CoingeckoCurrencyType, number>
}
}

export const fetchCryptoCurrencyPriceUSD = async (
coingeckoId: CoingeckoIdType,
): Promise<number> => {
const response: CoingeckoSimplePriceResponse = await axios.get(
`${coingeckoApiURL}/simple/price?ids=${coingeckoId}&vs_currencies=usd`,
)

return response.data[coingeckoId].usd
}
1 change: 1 addition & 0 deletions dapp/src/api/index.ts
kkosiorowska marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./coingeckoApi"
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 @@ -8,7 +8,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 @@ -38,7 +38,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 @@ -48,7 +48,7 @@ export default function DepositBTCModal() {
const handledDepositBTC = useCallback(() => {
if (!tokenAmount?.amount || !btcAddress) return

asyncWrapper(sendBitcoinTransaction(tokenAmount?.amount, btcAddress))
logPromiseFailure(sendBitcoinTransaction(tokenAmount?.amount, btcAddress))
}, [btcAddress, sendBitcoinTransaction, tokenAmount])

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 { REFERRAL } from "#/constants"
import StakeFormModal from "../ActiveStakingStep/StakeFormModal"
import UnstakeFormModal from "../ActiveUnstakingStep/UnstakeFormModal"
Expand Down Expand Up @@ -60,7 +60,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,19 @@
import React, { useMemo } from "react"
import React from "react"
import { useCurrencyConversion } from "#/hooks"
import { CurrencyConversionType } from "#/types"
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 as CurrencyConversionType,
to as CurrencyConversionType,
)

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
3 changes: 3 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 @@ -13,3 +14,5 @@ export * from "./useTransactionHistoryTable"
export * from "./useSentry"
export * from "./useExecuteFunction"
export * from "./useStakeFlowContext"
export * from "./useInitApp"
export * from "./useCurrencyConversion"
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>()
35 changes: 35 additions & 0 deletions dapp/src/hooks/useCurrencyConversion.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { CURRENCIES_BY_TYPE } from "#/constants"
import { selectBtcUsdPrice } from "#/store/btc"
import { bigIntToUserAmount } from "#/utils"
import { CurrencyConversionType } from "#/types"
import { useMemo } from "react"
import { useAppSelector } from "./store"

export function useCurrencyConversion(
from: CurrencyConversionType,
to: CurrencyConversionType,
) {
const usdPrice = useAppSelector(selectBtcUsdPrice)

const convertedValue = useMemo(() => {
if (!from.amount || BigInt(from.amount) < 0n) return undefined

switch (from.currency) {
case "bitcoin":
if (to.currency === "usd") {
const fromAmount = bigIntToUserAmount(
BigInt(from.amount),
CURRENCIES_BY_TYPE[from.currency].decimals,
CURRENCIES_BY_TYPE[from.currency].desiredDecimals,
)
const conversionAmount = fromAmount * usdPrice
return conversionAmount
}
return undefined
default:
return undefined
}
}, [from, to, usdPrice])

return convertedValue
}
12 changes: 12 additions & 0 deletions dapp/src/hooks/useInitApp.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 useInitApp() {
const dispatch = useAppDispatch()

useEffect(() => {
logPromiseFailure(dispatch(fetchBTCPriceUSD()))
}, [dispatch])
}
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