diff --git a/.env.production b/.env.production
index ad5b04eaba..3d88b3641c 100644
--- a/.env.production
+++ b/.env.production
@@ -5,6 +5,6 @@ REACT_APP_GOOGLE_ANALYTICS_ID="G-KDP9B6W4H8"
REACT_APP_INFURA_KEY="099fc58e0de9451d80b18d7c74caa7c1"
REACT_APP_MOONPAY_API="https://api.moonpay.com"
REACT_APP_MOONPAY_LINK="https://us-central1-uniswap-mobile.cloudfunctions.net/signMoonpayLink"
-REACT_APP_MOONPAY_PUBLISHABLE_KEY=""
+REACT_APP_MOONPAY_PUBLISHABLE_KEY="pk_live_uQG4BJC4w3cxnqpcSqAfohdBFDTsY6E"
REACT_APP_FIREBASE_KEY="AIzaSyBcZWwTcTJHj_R6ipZcrJkXdq05PuX0Rs0"
THE_GRAPH_SCHEMA_ENDPOINT="https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v3"
diff --git a/src/components/Button/LoadingButtonSpinner.tsx b/src/components/Button/LoadingButtonSpinner.tsx
new file mode 100644
index 0000000000..f429ffac04
--- /dev/null
+++ b/src/components/Button/LoadingButtonSpinner.tsx
@@ -0,0 +1,13 @@
+import { SpinnerSVG } from 'theme'
+
+const ButtonLoadingSpinner = (props: React.ComponentPropsWithoutRef<'svg'>) => (
+
+
+
+
+)
+
+export default ButtonLoadingSpinner
diff --git a/src/components/Button/index.tsx b/src/components/Button/index.tsx
index 252bc75a55..dd245272b7 100644
--- a/src/components/Button/index.tsx
+++ b/src/components/Button/index.tsx
@@ -5,6 +5,8 @@ import styled, { DefaultTheme, useTheme } from 'styled-components/macro'
import { RowBetween } from '../Row'
+export { default as LoadingButtonSpinner } from './LoadingButtonSpinner'
+
type ButtonProps = Omit
export const BaseButton = styled(RebassButton)<
@@ -362,18 +364,6 @@ export function ButtonRadioChecked({ active = false, children, ...rest }: { acti
}
}
-const ButtonOverlay = styled.div`
- background-color: transparent;
- bottom: 0;
- border-radius: 16px;
- height: 100%;
- left: 0;
- position: absolute;
- right: 0;
- top: 0;
- transition: 150ms ease background-color;
- width: 100%;
-`
export enum ButtonSize {
small,
medium,
@@ -466,7 +456,18 @@ function pickThemeButtonTextColor({ theme, emphasis }: { theme: DefaultTheme; em
return theme.textPrimary
}
}
-
+const ButtonOverlay = styled.div`
+ background-color: transparent;
+ bottom: 0;
+ border-radius: inherit;
+ height: 100%;
+ left: 0;
+ position: absolute;
+ right: 0;
+ top: 0;
+ transition: 150ms ease background-color;
+ width: 100%;
+`
const BaseThemeButton = styled.button`
align-items: center;
background-color: ${pickThemeButtonBackgroundColor};
@@ -484,16 +485,13 @@ const BaseThemeButton = styled.button`
padding: ${pickThemeButtonPadding};
position: relative;
transition: 150ms ease opacity;
+ user-select: none;
:active {
${ButtonOverlay} {
background-color: ${({ theme }) => theme.stateOverlayPressed};
}
}
- :disabled {
- cursor: default;
- opacity: 0.6;
- }
:focus {
${ButtonOverlay} {
background-color: ${({ theme }) => theme.stateOverlayPressed};
@@ -504,6 +502,17 @@ const BaseThemeButton = styled.button`
background-color: ${({ theme }) => theme.stateOverlayHover};
}
}
+ :disabled {
+ cursor: default;
+ opacity: 0.6;
+ }
+ :disabled:active,
+ :disabled:focus,
+ :disabled:hover {
+ ${ButtonOverlay} {
+ background-color: transparent;
+ }
+ }
`
interface ThemeButtonProps extends React.ComponentPropsWithoutRef<'button'>, BaseButtonProps {}
diff --git a/src/components/FiatOnrampAnnouncement/index.tsx b/src/components/FiatOnrampAnnouncement/index.tsx
index 18f1eca500..ef4c4c62f9 100644
--- a/src/components/FiatOnrampAnnouncement/index.tsx
+++ b/src/components/FiatOnrampAnnouncement/index.tsx
@@ -93,13 +93,13 @@ const Body = styled(ThemedText.BodySmall)`
export function FiatOnrampAnnouncement() {
const { account } = useWeb3React()
- const [fiatOnrampAcknowledged, acknowledge] = useFiatOnrampAck()
+ const [acks, acknowledge] = useFiatOnrampAck()
const handleClose = useCallback(
(e: MouseEvent) => {
e.preventDefault()
e.stopPropagation()
- acknowledge()
+ acknowledge({ user: false })
},
[acknowledge]
)
@@ -107,11 +107,11 @@ export function FiatOnrampAnnouncement() {
const toggleWalletDropdown = useToggleWalletDropdown()
const handleClick = useCallback(() => {
toggleWalletDropdown()
- acknowledge()
+ acknowledge({ user: true })
}, [acknowledge, toggleWalletDropdown])
const fiatOnrampFlag = useFiatOnrampFlag()
- if (!account || fiatOnrampAcknowledged || fiatOnrampFlag === BaseVariant.Control) {
+ if (!account || acks?.user || fiatOnrampFlag === BaseVariant.Control) {
return null
}
return (
diff --git a/src/components/NavBar/MenuDropdown.tsx b/src/components/NavBar/MenuDropdown.tsx
index 3db7a98fc1..981028eb4e 100644
--- a/src/components/NavBar/MenuDropdown.tsx
+++ b/src/components/NavBar/MenuDropdown.tsx
@@ -14,8 +14,7 @@ import {
} from 'nft/components/icons'
import { body, bodySmall } from 'nft/css/common.css'
import { themeVars } from 'nft/css/sprinkles.css'
-import { ReactNode, useCallback, useReducer, useRef } from 'react'
-import { CreditCard } from 'react-feather'
+import { ReactNode, useReducer, useRef } from 'react'
import { NavLink, NavLinkProps } from 'react-router-dom'
import styled from 'styled-components/macro'
import { isDevelopmentEnv, isStagingEnv } from 'utils/env'
@@ -26,28 +25,6 @@ import * as styles from './MenuDropdown.css'
import { NavDropdown } from './NavDropdown'
import { NavIcon } from './NavIcon'
-const BuyCryptoButton = styled.button`
- background-color: transparent;
- border: 1px solid transparent;
- border-radius: 12px;
- color: ${({ theme }) => theme.textPrimary};
- cursor: pointer;
- display: flex;
- line-height: 24px;
- outline: none;
- padding: 8px;
- text-decoration: none;
- width: full;
- white-space: nowrap;
- transition-duration: ${({ theme }) => theme.transition.duration.fast};
- transition-timing-function: ease-in-out;
- transition-property: opacity, color, background-color;
-
- :hover {
- background: ${({ theme }) => theme.stateOverlayHover};
- }
-`
-
const PrimaryMenuRow = ({
to,
href,
@@ -74,8 +51,13 @@ const PrimaryMenuRow = ({
)
}
+const StyledBox = styled(Box)`
+ align-items: center;
+ display: flex;
+ justify-content: center;
+`
const PrimaryMenuRowText = ({ children }: { children: ReactNode }) => {
- return {children}
+ return {children}
}
PrimaryMenuRow.Text = PrimaryMenuRowText
@@ -139,14 +121,9 @@ export const MenuDropdown = () => {
const [isOpen, toggleOpen] = useReducer((s) => !s, false)
const togglePrivacyPolicy = useToggleModal(ApplicationModal.PRIVACY_POLICY)
const openFeatureFlagsModal = useToggleModal(ApplicationModal.FEATURE_FLAGS)
-
const ref = useRef(null)
useOnClickOutside(ref, isOpen ? toggleOpen : undefined)
- const handleBuyCryptoClick = useCallback(() => {
- toggleOpen()
- }, [])
-
return (
<>
@@ -158,14 +135,6 @@ export const MenuDropdown = () => {
-
-
-
-
-
- Buy Crypto
-
-
diff --git a/src/components/WalletDropdown/AuthenticatedHeader.tsx b/src/components/WalletDropdown/AuthenticatedHeader.tsx
index d0c90e82bc..38e5d52a10 100644
--- a/src/components/WalletDropdown/AuthenticatedHeader.tsx
+++ b/src/components/WalletDropdown/AuthenticatedHeader.tsx
@@ -1,6 +1,8 @@
import { Trans } from '@lingui/macro'
import { CurrencyAmount, Token } from '@uniswap/sdk-core'
import { useWeb3React } from '@web3-react/core'
+import { ButtonEmphasis, ButtonSize, LoadingButtonSpinner, ThemeButton } from 'components/Button'
+import Tooltip from 'components/Tooltip'
import { getConnection } from 'connection/utils'
import { getChainInfoOrDefault } from 'constants/chainInfo'
import { SupportedChainId } from 'constants/chains'
@@ -9,31 +11,58 @@ import { useFiatOnrampFlag } from 'featureFlags/flags/fiatOnramp'
import useCopyClipboard from 'hooks/useCopyClipboard'
import useStablecoinPrice from 'hooks/useStablecoinPrice'
import useNativeCurrency from 'lib/hooks/useNativeCurrency'
+import ms from 'ms.macro'
import { useProfilePageState, useSellAsset, useWalletCollections } from 'nft/hooks'
import { useIsNftClaimAvailable } from 'nft/hooks/useIsNftClaimAvailable'
import { ProfilePageStateType } from 'nft/types'
-import { useCallback, useMemo } from 'react'
-import { Copy, CreditCard, ExternalLink, Power } from 'react-feather'
+import { useCallback, useEffect, useMemo, useState } from 'react'
+import { Copy, CreditCard, ExternalLink as ExternalLinkIcon, Info, Power } from 'react-feather'
import { useNavigate } from 'react-router-dom'
import { Text } from 'rebass'
import { useCurrencyBalanceString } from 'state/connection/hooks'
import { useAppDispatch } from 'state/hooks'
+import { useFiatOnrampAck } from 'state/user/hooks'
import { updateSelectedWallet } from 'state/user/reducer'
-import styled, { css } from 'styled-components/macro'
-import { ThemedText } from 'theme'
+import styled, { css, keyframes } from 'styled-components/macro'
+import { ExternalLink, ThemedText } from 'theme'
import { shortenAddress } from '../../nft/utils/address'
-import { useCloseModal, useOpenModal, useToggleModal } from '../../state/application/hooks'
+import { useCloseModal, useFiatOnrampAvailability, useOpenModal, useToggleModal } from '../../state/application/hooks'
import { ApplicationModal } from '../../state/application/reducer'
import { useUserHasAvailableClaim, useUserUnclaimedAmount } from '../../state/claim/hooks'
-import { ButtonEmphasis, ButtonSize, ThemeButton } from '../Button'
import StatusIcon from '../Identicon/StatusIcon'
import IconButton, { IconHoverText } from './IconButton'
-const BuyCryptoButton = styled(ThemeButton)`
- margin-top: 12px;
+const BuyCryptoButtonBorderKeyframes = keyframes`
+ 0% {
+ border-color: transparent;
+ }
+ 33% {
+ border-color: hsla(225, 95%, 63%, 1);
+ }
+ 66% {
+ border-color: hsla(267, 95%, 63%, 1);
+ }
+ 100% {
+ border-color: transparent;
+ }
`
+const BuyCryptoButton = styled(ThemeButton)<{ $animateBorder: boolean }>`
+ border-color: transparent;
+ border-radius: 12px;
+ border-style: solid;
+ border-width: 1px;
+ height: 40px;
+ margin-top: 12px;
+ animation-direction: alternate;
+ animation-duration: ${({ theme }) => theme.transition.duration.slow};
+ animation-fill-mode: none;
+ animation-iteration-count: 2;
+ animation-name: ${BuyCryptoButtonBorderKeyframes};
+ animation-play-state: ${({ $animateBorder }) => ($animateBorder ? 'running' : 'paused')};
+ animation-timing-function: ${({ theme }) => theme.transition.timing.inOut};
+`
const WalletButton = styled(ThemeButton)`
border-radius: 12px;
padding-top: 10px;
@@ -81,7 +110,20 @@ const USDText = styled.div`
color: ${({ theme }) => theme.textSecondary};
margin-top: 8px;
`
-
+const FiatOnrampNotAvailableText = styled(ThemedText.Caption)`
+ align-items: center;
+ color: ${({ theme }) => theme.textSecondary};
+ display: flex;
+ justify-content: center;
+`
+const FiatOnrampAvailabilityExternalLink = styled(ExternalLink)`
+ align-items: center;
+ display: flex;
+ height: 14px;
+ justify-content: center;
+ margin-left: 6px;
+ width: 14px;
+`
const FlexContainer = styled.div`
display: flex;
`
@@ -114,6 +156,11 @@ const AccountContainer = styled(ThemedText.BodySmall)`
color: ${({ theme }) => theme.textSecondary};
margin-top: 2.5px;
`
+const StyledInfoIcon = styled(Info)`
+ height: 12px;
+ width: 12px;
+ flex: 1 1 auto;
+`
const BalanceWrapper = styled.div`
padding: 16px 0;
@@ -178,7 +225,49 @@ const AuthenticatedHeader = () => {
}, [clearCollectionFilters, closeModal, navigate, resetSellAssets, setSellPageState])
const fiatOnrampFlag = useFiatOnrampFlag()
+
+ // animate the border of the buy crypto button when a user navigates here from the feature announcement
+ // can be removed when components/FiatOnrampAnnouncment.tsx is no longer used
+ const [acknowledgements, acknowledge] = useFiatOnrampAck()
+ const animateBuyCryptoButtonBorder = acknowledgements?.user && !acknowledgements.system
+ useEffect(() => {
+ let stale = false
+ let timeoutId = 0
+ if (animateBuyCryptoButtonBorder) {
+ timeoutId = setTimeout(() => {
+ if (stale) return
+ acknowledge({ system: true })
+ }, ms`2 seconds`) as unknown as number
+ // as unknown as number is necessary so it's not incorrectly typed as a NodeJS.Timeout
+ }
+ return () => {
+ stale = true
+ clearTimeout(timeoutId)
+ }
+ }, [acknowledge, animateBuyCryptoButtonBorder])
+
const openFiatOnrampModal = useOpenModal(ApplicationModal.FIAT_ONRAMP)
+ const [shouldCheck, setShouldCheck] = useState(false)
+ const {
+ available: fiatOnrampAvailable,
+ availabilityChecked: fiatOnrampAvailabilityChecked,
+ error,
+ loading: fiatOnrampAvailabilityLoading,
+ } = useFiatOnrampAvailability(shouldCheck, openFiatOnrampModal)
+
+ const handleBuyCryptoClick = useCallback(() => {
+ if (!fiatOnrampAvailabilityChecked) {
+ setShouldCheck(true)
+ } else if (fiatOnrampAvailable) {
+ openFiatOnrampModal()
+ }
+ }, [fiatOnrampAvailabilityChecked, fiatOnrampAvailable, openFiatOnrampModal])
+ const disableBuyCryptoButton = Boolean(
+ error || (!fiatOnrampAvailable && fiatOnrampAvailabilityChecked) || fiatOnrampAvailabilityLoading
+ )
+ const [showFiatOnrampUnavailableTooltip, setShow] = useState(false)
+ const openFiatOnrampUnavailableTooltip = useCallback(() => setShow(true), [setShow])
+ const closeFiatOnrampUnavailableTooltip = useCallback(() => setShow(false), [setShow])
return (
@@ -200,7 +289,7 @@ const AuthenticatedHeader = () => {
{isCopied ? Copied! : Copy}
-
+
Explore
@@ -224,9 +313,50 @@ const AuthenticatedHeader = () => {
View and sell NFTs
{fiatOnrampFlag === BaseVariant.Enabled && (
-
- Buy crypto
-
+ <>
+
+ {fiatOnrampAvailabilityLoading ? (
+ <>
+
+ Checking availability
+ >
+ ) : error ? (
+ {error}
+ ) : (
+ <>
+ Buy crypto
+ >
+ )}
+
+ {Boolean(!fiatOnrampAvailable && fiatOnrampAvailabilityChecked) && (
+
+ Not available in your region
+
+ Moonpay is not supported in some regions in and outside of the US. Click to learn more.
+
+ }
+ >
+
+
+
+
+
+ )}
+ >
)}
{isUnclaimed && (
diff --git a/src/state/application/hooks.ts b/src/state/application/hooks.ts
index 442866ddb4..d0f7307f85 100644
--- a/src/state/application/hooks.ts
+++ b/src/state/application/hooks.ts
@@ -1,15 +1,85 @@
import { DEFAULT_TXN_DISMISS_MS } from 'constants/misc'
-import { useCallback, useMemo } from 'react'
+import { useCallback, useEffect, useMemo, useState } from 'react'
import { useAppDispatch, useAppSelector } from 'state/hooks'
import { AppState } from '../index'
-import { addPopup, ApplicationModal, PopupContent, removePopup, setOpenModal } from './reducer'
+import {
+ addPopup,
+ ApplicationModal,
+ PopupContent,
+ removePopup,
+ setFiatOnrampAvailability,
+ setOpenModal,
+} from './reducer'
export function useModalIsOpen(modal: ApplicationModal): boolean {
const openModal = useAppSelector((state: AppState) => state.application.openModal)
return openModal === modal
}
+/** @ref https://dashboard.moonpay.com/api_reference/client_side_api#ip_addresses */
+interface MoonpayIPAddressesResponse {
+ alpha3?: string
+ isAllowed?: boolean
+ isBuyAllowed?: boolean
+ isSellAllowed?: boolean
+}
+
+async function getMoonpayAvailability(): Promise {
+ const moonpayPublishableKey = process.env.REACT_APP_MOONPAY_PUBLISHABLE_KEY
+ if (!moonpayPublishableKey) {
+ throw new Error('Must provide a publishable key for moonpay.')
+ }
+ const moonpayApiURI = process.env.REACT_APP_MOONPAY_API
+ if (!moonpayApiURI) {
+ throw new Error('Must provide an api endpoint for moonpay.')
+ }
+ const res = await fetch(`${moonpayApiURI}/v4/ip_address?apiKey=${moonpayPublishableKey}`)
+ const data = await (res.json() as Promise)
+ return data.isBuyAllowed ?? false
+}
+
+export function useFiatOnrampAvailability(shouldCheck: boolean, callback?: () => void) {
+ const dispatch = useAppDispatch()
+ const { available, availabilityChecked } = useAppSelector((state: AppState) => state.application.fiatOnramp)
+ const [error, setError] = useState(null)
+ const [loading, setLoading] = useState(false)
+
+ useEffect(() => {
+ async function checkAvailability() {
+ setError(null)
+ setLoading(true)
+ try {
+ const result = await getMoonpayAvailability()
+ if (stale) return
+ dispatch(setFiatOnrampAvailability(result))
+ if (result && callback) {
+ callback()
+ }
+ } catch (e) {
+ console.error('Error checking onramp availability', e.toString())
+ if (stale) return
+ setError('Error, try again later.')
+ dispatch(setFiatOnrampAvailability(false))
+ } finally {
+ if (stale) return
+ setLoading(false)
+ }
+ }
+
+ if (!availabilityChecked && shouldCheck) {
+ checkAvailability()
+ }
+
+ let stale = false
+ return () => {
+ stale = true
+ }
+ }, [availabilityChecked, callback, dispatch, shouldCheck])
+
+ return { available, availabilityChecked, loading, error }
+}
+
export function useToggleModal(modal: ApplicationModal): () => void {
const isOpen = useModalIsOpen(modal)
const dispatch = useAppDispatch()
diff --git a/src/state/application/reducer.ts b/src/state/application/reducer.ts
index 5c293b207a..e74bd0077c 100644
--- a/src/state/application/reducer.ts
+++ b/src/state/application/reducer.ts
@@ -41,11 +41,13 @@ type PopupList = Array<{ key: string; show: boolean; content: PopupContent; remo
export interface ApplicationState {
readonly chainId: number | null
+ readonly fiatOnramp: { available: boolean; availabilityChecked: boolean }
readonly openModal: ApplicationModal | null
readonly popupList: PopupList
}
const initialState: ApplicationState = {
+ fiatOnramp: { available: false, availabilityChecked: false },
chainId: null,
openModal: null,
popupList: [],
@@ -55,6 +57,9 @@ const applicationSlice = createSlice({
name: 'application',
initialState,
reducers: {
+ setFiatOnrampAvailability(state, { payload: available }) {
+ state.fiatOnramp = { available, availabilityChecked: true }
+ },
updateChainId(state, action) {
const { chainId } = action.payload
state.chainId = chainId
@@ -82,5 +87,6 @@ const applicationSlice = createSlice({
},
})
-export const { updateChainId, setOpenModal, addPopup, removePopup } = applicationSlice.actions
+export const { updateChainId, setFiatOnrampAvailability, setOpenModal, addPopup, removePopup } =
+ applicationSlice.actions
export default applicationSlice.reducer
diff --git a/src/state/user/hooks.tsx b/src/state/user/hooks.tsx
index cb365246b5..6354978a38 100644
--- a/src/state/user/hooks.tsx
+++ b/src/state/user/hooks.tsx
@@ -17,7 +17,7 @@ import { AppState } from '../index'
import {
addSerializedPair,
addSerializedToken,
- updateFiatOnrampAcknowledged,
+ updateFiatOnrampAcknowledgments,
updateHideClosedPositions,
updateHideNFTWelcomeModal,
updateShowNftPromoBanner,
@@ -26,7 +26,7 @@ import {
updateUserDeadline,
updateUserExpertMode,
updateUserLocale,
- updateUserSlippageTolerance
+ updateUserSlippageTolerance,
} from './reducer'
import { SerializedPair, SerializedToken } from './types'
@@ -106,18 +106,23 @@ export function useExpertModeManager(): [boolean, () => void] {
return [expertMode, toggleSetExpertMode]
}
-export function useFiatOnrampAck(): [boolean, (b?: boolean) => void] {
+interface FiatOnrampAcknowledgements {
+ user: boolean
+ system: boolean
+}
+export function useFiatOnrampAck(): [
+ FiatOnrampAcknowledgements,
+ (acknowledgements: Partial) => void
+] {
const dispatch = useAppDispatch()
- const fiatOnrampAcknowledged = useAppSelector((state) => state.user.fiatOnrampAcknowledged)
-
- const toggleSetExpertMode = useCallback(
- (b = true) => {
- dispatch(updateFiatOnrampAcknowledged(b))
+ const fiatOnrampAcknowledgments = useAppSelector((state) => state.user.fiatOnrampAcknowledgments)
+ const setAcknowledgements = useCallback(
+ (acks: Partial) => {
+ dispatch(updateFiatOnrampAcknowledgments(acks))
},
[dispatch]
)
-
- return [fiatOnrampAcknowledged, toggleSetExpertMode]
+ return [fiatOnrampAcknowledgments, setAcknowledgements]
}
export function useHideNFTWelcomeModal(): [boolean | undefined, () => void] {
const dispatch = useAppDispatch()
diff --git a/src/state/user/reducer.ts b/src/state/user/reducer.ts
index 4c6e0af0bc..b46dce19fc 100644
--- a/src/state/user/reducer.ts
+++ b/src/state/user/reducer.ts
@@ -9,7 +9,7 @@ import { SerializedPair, SerializedToken } from './types'
const currentTimestamp = () => new Date().getTime()
export interface UserState {
- fiatOnrampAcknowledged: boolean
+ fiatOnrampAcknowledgments: { user: boolean; system: boolean }
selectedWallet?: ConnectionType
@@ -63,7 +63,7 @@ function pairKey(token0Address: string, token1Address: string) {
}
export const initialState: UserState = {
- fiatOnrampAcknowledged: false,
+ fiatOnrampAcknowledgments: { user: false, system: false },
selectedWallet: undefined,
matchesDarkMode: false,
userDarkMode: null,
@@ -87,8 +87,8 @@ const userSlice = createSlice({
name: 'user',
initialState,
reducers: {
- updateFiatOnrampAcknowledged(state, { payload }) {
- state.fiatOnrampAcknowledged = payload
+ updateFiatOnrampAcknowledgments(state, { payload }: { payload: { user?: boolean; system?: boolean } }) {
+ state.fiatOnrampAcknowledgments = { ...state.fiatOnrampAcknowledgments, ...payload }
},
updateSelectedWallet(state, { payload: { wallet } }) {
state.selectedWallet = wallet
@@ -189,7 +189,7 @@ const userSlice = createSlice({
export const {
addSerializedPair,
addSerializedToken,
- updateFiatOnrampAcknowledged,
+ updateFiatOnrampAcknowledgments,
updateSelectedWallet,
updateHideClosedPositions,
updateMatchesDarkMode,