1
0
mirror of https://github.com/oceanprotocol/market.git synced 2024-06-23 01:36:47 +02:00

Token approval split-up (#640)

* Token approval component

* check if datatoken approved

* display token amount on button, add approve function

* approve token based on token type

* approve token for trade, remove for pool lequidity remove action

* verify approval on amount change

* show action button only if amount is approved

* catch approve error and stop loadin

* display token amount with 2 decimals on trade token approval

* infinite approval and UI fixes

* fixed alert warning not showing, account id for approve

* wip

* fixed displayed token amount to approve for swap

* token amount text fix

* lint error fix

* package version update

* version fix

* downgrade version

* fixed error for no wallet connected

* update package-lock

* display token name, and changed amount precision

* removed empty file, fixed token switch error

* refactor for better user experience

* move content

* ExplorerLink console error fixes

* UI tweaks

* slightly changed button logic

* fix Trade form approvals

* cleanup

* don't block add liquidity button

* merge fixes

* hook dependency cleanup

* dtItem fix, error fixes based on asset network match

* disable action button if field is not valid, undefined trade tokens

* fix infiniteApproval user preference saving

* remove unneccessary string conversion

* used Decimal for dtAmount and oceanAmount

* changed token spender address

* bump ocean.js to vo.17.5

* fix lint

* replace Number with Decimal

* fix getting to add liquidity screen without wallet connected

* fix crash when switching coins after value input

Co-authored-by: Norbi <katunanorbert@gmai.com>
Co-authored-by: Matthias Kretschmann <m@kretschmann.io>
Co-authored-by: mihaisc <mihai@oceanprotocol.com>
This commit is contained in:
Norbi 2021-09-30 13:54:44 +03:00 committed by GitHub
parent 9b3cb3963e
commit 4b34e2f347
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 348 additions and 89 deletions

View File

@ -34,7 +34,9 @@
"pool": {
"tooltips": {
"price": "The price is determined by an automated market maker, which is a type of decentralized exchange protocol that relies on a mathematical formula. It is an alternative to a traditional order book.",
"liquidity": "Providing liquidity will earn you SWAPFEE% on every transaction in this pool, proportionally to your share of the pool."
"liquidity": "Providing liquidity will earn you SWAPFEE% on every transaction in this pool, proportionally to your share of the pool.",
"approveSpecific": "Give the smart contract permission to spend your COIN which has to be done for each transaction. You can optionally set this to infinite in your user preferences.",
"approveInfinite": "Give the smart contract permission to spend infinte amounts of your COIN so you have to do this only once. You can disable allowing infinite amounts in your user preferences."
},
"add": {
"title": "Add Liquidity",
@ -43,7 +45,7 @@
"titleIn": "You will receive",
"titleOut": "Pool conversion"
},
"action": "Approve & Supply",
"action": "Supply",
"warning": "Use at your own risk. Please familiarize yourself [with the risks](https://blog.oceanprotocol.com/on-staking-on-data-in-ocean-market-3d8e09eb0a13) and the [Terms of Use](/terms)."
},
"remove": {
@ -54,11 +56,11 @@
"titleIn": "You will spend",
"titleOut": "You will receive"
},
"action": "Approve & Remove"
"action": "Remove"
}
},
"trade": {
"action": "Approve & Swap",
"action": "Swap",
"warning": "Use at your own risk. Please familiarize yourself [with the risks](https://blog.oceanprotocol.com/on-staking-on-data-in-ocean-market-3d8e09eb0a13) and the [Terms of Use](/terms)."
}
}

4
package-lock.json generated
View File

@ -5681,7 +5681,7 @@
"save-file": "^2.3.1",
"uuid": "^8.3.2",
"web3": "^1.5.2",
"web3-core": "^1.5.3",
"web3-core": "^1.5.2",
"web3-eth-contract": "^1.5.2"
},
"peerDependencies": {
@ -63142,7 +63142,7 @@
"save-file": "^2.3.1",
"uuid": "^8.3.2",
"web3": "^1.5.2",
"web3-core": "^1.5.3",
"web3-core": "^1.5.2",
"web3-eth-contract": "^1.5.2"
},
"dependencies": {

View File

@ -8,7 +8,7 @@ export default function DebugOutput({
output: any
}): ReactElement {
return (
<div>
<div style={{ marginTop: 'var(--spacer)' }}>
<h5>{title}</h5>
<pre>
<code>{JSON.stringify(output, null, 2)}</code>

View File

@ -28,6 +28,8 @@ export default function ExplorerLink({
})
useEffect(() => {
if (!networkId) return
async function initOcean() {
const oceanInitialConfig = getOceanConfig(networkId)
setOceanConfig(oceanInitialConfig)
@ -36,7 +38,7 @@ export default function ExplorerLink({
if (oceanConfig === undefined) {
initOcean()
}
}, [config, networkId, ocean])
}, [config, oceanConfig, networkId, ocean])
return (
<a

View File

@ -0,0 +1,144 @@
import React, { ReactElement, useCallback, useEffect, useState } from 'react'
import Button from '../atoms/Button'
import { useOcean } from '../../providers/Ocean'
import { useAsset } from '../../providers/Asset'
import Loader from '../atoms/Loader'
import { useWeb3 } from '../../providers/Web3'
import { useUserPreferences } from '../../providers/UserPreferences'
import Tooltip from '../atoms/Tooltip'
import { graphql, useStaticQuery } from 'gatsby'
import Decimal from 'decimal.js'
import { getOceanConfig } from '../../utils/ocean'
const query = graphql`
query {
content: allFile(filter: { relativePath: { eq: "price.json" } }) {
edges {
node {
childContentJson {
pool {
tooltips {
approveSpecific
approveInfinite
}
}
}
}
}
}
}
`
function ButtonApprove({
amount,
coin,
approveTokens,
isLoading
}: {
amount: string
coin: string
approveTokens: (amount: string) => void
isLoading: boolean
}) {
// Get content
const data = useStaticQuery(query)
const content = data.content.edges[0].node.childContentJson.pool.tooltips
const { infiniteApproval } = useUserPreferences()
return isLoading ? (
<Loader message={`Approving ${coin}...`} />
) : infiniteApproval ? (
<Button
style="primary"
size="small"
disabled={parseInt(amount) < 1}
onClick={() => approveTokens(`${2 ** 53 - 1}`)}
>
Approve {coin}{' '}
<Tooltip content={content.approveInfinite.replace('COIN', coin)} />
</Button>
) : (
<Button style="primary" size="small" onClick={() => approveTokens(amount)}>
Approve {amount} {coin}
<Tooltip content={content.approveSpecific.replace('COIN', coin)} />
</Button>
)
}
export default function TokenApproval({
actionButton,
disabled,
amount,
coin
}: {
actionButton: JSX.Element
disabled: boolean
amount: string
coin: string
}): ReactElement {
const { ddo, price } = useAsset()
const [tokenApproved, setTokenApproved] = useState(false)
const [loading, setLoading] = useState(false)
const { ocean } = useOcean()
const { accountId } = useWeb3()
const config = getOceanConfig(ddo.chainId)
const tokenAddress =
coin === 'OCEAN' ? config.oceanTokenAddress : ddo.dataTokenInfo.address
const spender = price.address
const checkTokenApproval = useCallback(async () => {
if (!ocean || !tokenAddress || !spender) return
const allowance = await ocean.datatokens.allowance(
tokenAddress,
accountId,
spender
)
amount &&
new Decimal(amount).greaterThan(new Decimal('0')) &&
setTokenApproved(
new Decimal(allowance).greaterThanOrEqualTo(new Decimal(amount))
)
}, [ocean, tokenAddress, spender, accountId, amount])
useEffect(() => {
checkTokenApproval()
}, [checkTokenApproval])
async function approveTokens(amount: string) {
setLoading(true)
try {
await ocean.datatokens.approve(tokenAddress, spender, amount, accountId)
} catch (error) {
setLoading(false)
}
await checkTokenApproval()
setLoading(false)
}
return (
<>
{tokenApproved ||
disabled ||
amount === '0' ||
amount === '' ||
!amount ||
typeof amount === 'undefined' ? (
actionButton
) : (
<ButtonApprove
amount={amount}
coin={coin}
approveTokens={approveTokens}
isLoading={loading}
/>
)}
</>
)
}

View File

@ -0,0 +1,21 @@
import React, { ReactElement } from 'react'
import { useUserPreferences } from '../../../providers/UserPreferences'
import Input from '../../atoms/Input'
export default function TokenApproval(): ReactElement {
const { infiniteApproval, setInfiniteApproval } = useUserPreferences()
return (
<li>
<Input
label="Token Approvals"
help="Use infinite amount when approving tokens in _Use_, _Pool_, or _Trade_."
name="infiniteApproval"
type="checkbox"
options={['Allow infinite amount']}
defaultChecked={infiniteApproval === true}
onChange={() => setInfiniteApproval(!infiniteApproval)}
/>
</li>
)
}

View File

@ -8,6 +8,7 @@ import { ReactComponent as Caret } from '../../../images/caret.svg'
import useDarkMode from 'use-dark-mode'
import Appearance from './Appearance'
import { darkModeConfig } from '../../../../app.config'
import TokenApproval from './TokenApproval'
export default function UserPreferences(): ReactElement {
// Calling this here because <Style /> is not mounted on first load
@ -18,6 +19,7 @@ export default function UserPreferences(): ReactElement {
content={
<ul className={styles.preferencesDetails}>
<Currency />
<TokenApproval />
<Appearance darkMode={darkMode} />
<Debug />
</ul>

View File

@ -79,7 +79,13 @@ export default function Consume({
}, [ddo, accountId, hasPreviousOrder, isMounted])
useEffect(() => {
if (!data || !assetTimeout || data.tokenOrders.length === 0 || !accountId)
if (
!data ||
!assetTimeout ||
data.tokenOrders.length === 0 ||
!accountId ||
!isAssetNetwork
)
return
const lastOrder = data.tokenOrders[0]
@ -96,11 +102,11 @@ export default function Consume({
setHasPreviousOrder(false)
}
}
}, [data, assetTimeout, accountId])
}, [data, assetTimeout, accountId, isAssetNetwork])
useEffect(() => {
const { timeout } = ddo.findServiceByType('access').attributes.main
setAssetTimeout(timeout.toString())
setAssetTimeout(`${timeout}`)
}, [ddo])
useEffect(() => {
@ -137,8 +143,8 @@ export default function Consume({
pricingIsLoading,
isConsumablePrice,
hasDatatoken,
accountId,
isConsumable
isConsumable,
accountId
])
async function handleConsume() {

View File

@ -19,3 +19,7 @@
margin-left: calc(var(--spacer) / 4);
margin-right: calc(var(--spacer) / 4);
}
.actions button svg {
fill: currentColor;
}

View File

@ -6,6 +6,7 @@ import ExplorerLink from '../../../atoms/ExplorerLink'
import SuccessConfetti from '../../../atoms/SuccessConfetti'
import { useOcean } from '../../../../providers/Ocean'
import { useWeb3 } from '../../../../providers/Web3'
import TokenApproval from '../../../molecules/TokenApproval'
export default function Actions({
isLoading,
@ -13,6 +14,8 @@ export default function Actions({
successMessage,
txId,
actionName,
amount,
coin,
action,
isDisabled
}: {
@ -21,26 +24,39 @@ export default function Actions({
successMessage: string
txId: string
actionName: string
amount?: string
coin?: string
action: () => void
isDisabled?: boolean
}): ReactElement {
const { networkId } = useWeb3()
const { ocean } = useOcean()
const actionButton = (
<Button
style="primary"
size="small"
onClick={() => action()}
disabled={!ocean || isDisabled}
>
{actionName}
</Button>
)
return (
<>
<div className={styles.actions}>
{isLoading ? (
<Loader message={loaderMessage} />
) : (
<Button
style="primary"
size="small"
onClick={() => action()}
) : actionName === 'Supply' || actionName === 'Swap' ? (
<TokenApproval
actionButton={actionButton}
amount={amount}
coin={coin}
disabled={!ocean || isDisabled}
>
{actionName}
</Button>
/>
) : (
actionButton
)}
</div>
{txId && (

View File

@ -24,6 +24,7 @@ export default function FormAdd({
dtSymbol,
amountMax,
setCoin,
setAmount,
totalPoolTokens,
totalBalance,
poolAddress,
@ -35,6 +36,7 @@ export default function FormAdd({
dtSymbol: string
amountMax: string
setCoin: (value: string) => void
setAmount: (value: string) => void
totalPoolTokens: string
totalBalance: PoolBalance
poolAddress: string
@ -56,6 +58,7 @@ export default function FormAdd({
function handleFieldChange(e: ChangeEvent<HTMLInputElement>) {
// Workaround so validation kicks in on first touch
!touched?.amount && setTouched({ amount: true })
setAmount(e.target.value)
// Manually handle change events instead of using `handleChange` from Formik.
// Solves bug where 0.0 can't be typed.

View File

@ -58,7 +58,8 @@ export default function Output({
const [poolDatatoken, setPoolDatatoken] = useState('0')
useEffect(() => {
if (!values.amount || !totalBalance || !totalPoolTokens) return
if (!values.amount || !totalBalance || !totalPoolTokens || !newPoolTokens)
return
const newPoolSupply = new Decimal(totalPoolTokens).plus(newPoolTokens)
const ratio = new Decimal(newPoolTokens).div(newPoolSupply)
const newOceanReserve =
@ -73,7 +74,14 @@ export default function Output({
const poolDatatoken = newDtReserve.mul(ratio).toString()
setPoolOcean(poolOcean)
setPoolDatatoken(poolDatatoken)
}, [values.amount, coin, totalBalance, totalPoolTokens, newPoolShare])
}, [
values.amount,
coin,
totalBalance,
totalPoolTokens,
newPoolShare,
newPoolTokens
])
return (
<>

View File

@ -72,6 +72,7 @@ export default function Add({
const [coin, setCoin] = useState('OCEAN')
const [dtBalance, setDtBalance] = useState<string>()
const [amountMax, setAmountMax] = useState<string>()
const [amount, setAmount] = useState<string>('0')
const [newPoolTokens, setNewPoolTokens] = useState('0')
const [newPoolShare, setNewPoolShare] = useState('0')
const [isWarningAccepted, setIsWarningAccepted] = useState(false)
@ -146,7 +147,6 @@ export default function Add({
return (
<>
<Header title={content.title} backAction={() => setShowAdd(false)} />
<Formik
initialValues={initialValues}
validationSchema={validationSchema}
@ -155,7 +155,7 @@ export default function Add({
setSubmitting(false)
}}
>
{({ isSubmitting, submitForm, values }) => (
{({ isSubmitting, submitForm, values, isValid }) => (
<>
<div className={styles.addInput}>
{isWarningAccepted ? (
@ -165,6 +165,7 @@ export default function Add({
dtSymbol={dtSymbol}
amountMax={amountMax}
setCoin={setCoin}
setAmount={setAmount}
totalPoolTokens={totalPoolTokens}
totalBalance={totalBalance}
poolAddress={poolAddress}
@ -172,16 +173,18 @@ export default function Add({
setNewPoolShare={setNewPoolShare}
/>
) : (
<Alert
className={styles.warning}
text={content.warning}
state="info"
action={{
name: 'I understand',
style: 'text',
handleAction: () => setIsWarningAccepted(true)
}}
/>
content.warning && (
<Alert
className={styles.warning}
text={content.warning.toString()}
state="info"
action={{
name: 'I understand',
style: 'text',
handleAction: () => setIsWarningAccepted(true)
}}
/>
)
)}
</div>
@ -196,12 +199,19 @@ export default function Add({
/>
<Actions
isDisabled={!isWarningAccepted}
isDisabled={
!isValid ||
!isWarningAccepted ||
amount === '' ||
amount === '0'
}
isLoading={isSubmitting}
loaderMessage="Adding Liquidity..."
successMessage="Successfully added liquidity."
actionName={content.action}
action={submitForm}
amount={amount}
coin={coin}
txId={txId}
/>
{debug && <DebugOutput title="Collected values" output={values} />}

View File

@ -487,16 +487,14 @@ export default function Pool(): ReactElement {
</div>
<div className={stylesActions.actions}>
{!isInPurgatory && (
<Button
style="primary"
size="small"
onClick={() => setShowAdd(true)}
disabled={isInPurgatory}
>
Add Liquidity
</Button>
)}
<Button
style="primary"
size="small"
onClick={() => setShowAdd(true)}
disabled={isInPurgatory}
>
Add Liquidity
</Button>
{hasAddedLiquidity && !isRemoveDisabled && (
<Button

View File

@ -54,6 +54,7 @@ export default function FormTrade({
const { isAssetNetwork } = useAsset()
const { debug } = useUserPreferences()
const [txId, setTxId] = useState<string>()
const [coinFrom, setCoinFrom] = useState<string>('OCEAN')
const [maximumOcean, setMaximumOcean] = useState(maxOcean)
const [maximumDt, setMaximumDt] = useState(maxDt)
@ -114,6 +115,7 @@ export default function FormTrade({
toast.error(error.message)
}
}
return (
<Formik
initialValues={initialValues}
@ -124,7 +126,7 @@ export default function FormTrade({
setSubmitting(false)
}}
>
{({ isSubmitting, submitForm, values }) => (
{({ isSubmitting, submitForm, values, isValid }) => (
<>
{isWarningAccepted ? (
<Swap
@ -133,6 +135,7 @@ export default function FormTrade({
maxDt={maxDt}
maxOcean={maxOcean}
price={price}
setCoin={setCoinFrom}
setMaximumOcean={setMaximumOcean}
setMaximumDt={setMaximumDt}
/>
@ -150,12 +153,22 @@ export default function FormTrade({
</div>
)}
<Actions
isDisabled={!isWarningAccepted || !isAssetNetwork}
isDisabled={
!isValid ||
!isWarningAccepted ||
!isAssetNetwork ||
values.datatoken === undefined ||
values.ocean === undefined
}
isLoading={isSubmitting}
loaderMessage="Swapping tokens..."
successMessage="Successfully swapped tokens."
actionName={content.action}
amount={`${
values.type === 'sell' ? values.datatoken : values.ocean
}`}
action={submitForm}
coin={coinFrom}
txId={txId}
/>

View File

@ -1,6 +1,7 @@
import { FormikContextType, useFormikContext } from 'formik'
import React, { ReactElement, useEffect, useState } from 'react'
import { FormTradeData } from '../../../../models/FormTrade'
import { useAsset } from '../../../../providers/Asset'
import { useOcean } from '../../../../providers/Ocean'
import Token from '../Pool/Token'
import styles from './Output.module.css'
@ -19,6 +20,7 @@ export default function Output({
oceanSymbol: string
poolAddress: string
}): ReactElement {
const { isAssetNetwork } = useAsset()
const { ocean } = useOcean()
const [maxOutput, setMaxOutput] = useState<string>()
const [swapFee, setSwapFee] = useState<string>()
@ -28,7 +30,7 @@ export default function Output({
// Get swap fee
useEffect(() => {
if (!ocean || !poolAddress) return
if (!ocean || !poolAddress || !isAssetNetwork) return
async function getSwapFee() {
const swapFee = await ocean.pool.getSwapFee(poolAddress)
@ -49,11 +51,11 @@ export default function Output({
setSwapFeeValue(value.toString())
}
getSwapFee()
}, [ocean, poolAddress, values])
}, [ocean, poolAddress, values, isAssetNetwork])
// Get output values
useEffect(() => {
if (!ocean || !poolAddress) return
if (!ocean || !poolAddress || !isAssetNetwork) return
async function getOutput() {
// Minimum received
@ -73,7 +75,7 @@ export default function Output({
setMaxOutput(maxPrice)
}
getOutput()
}, [ocean, poolAddress, values])
}, [ocean, poolAddress, values, isAssetNetwork])
return (
<div className={styles.output}>

View File

@ -51,7 +51,7 @@ export default function PriceImpact({
return (
<div className={styles.priceImpact}>
<strong>Price impact</strong>
<strong>Price Impact</strong>
<div>
<span
className={`${styles.number} ${

View File

@ -6,10 +6,10 @@ import Button from '../../../atoms/Button'
import { ReactComponent as Arrow } from '../../../../images/arrow.svg'
import { FormikContextType, useFormikContext } from 'formik'
import { PoolBalance } from '../../../../@types/TokenBalance'
import Output from './Output'
import Slippage from './Slippage'
import { FormTradeData, TradeItem } from '../../../../models/FormTrade'
import { useOcean } from '../../../../providers/Ocean'
import Output from './Output'
import Slippage from './Slippage'
import PriceImpact from './PriceImpact'
import Decimal from 'decimal.js'
@ -24,7 +24,8 @@ export default function Swap({
balance,
price,
setMaximumDt,
setMaximumOcean
setMaximumOcean,
setCoin
}: {
ddo: DDO
maxDt: string
@ -33,6 +34,7 @@ export default function Swap({
price: BestPrice
setMaximumDt: (value: string) => void
setMaximumOcean: (value: string) => void
setCoin: (value: string) => void
}): ReactElement {
const { ocean, config } = useOcean()
const [oceanItem, setOceanItem] = useState<TradeItem>({
@ -53,63 +55,82 @@ export default function Swap({
validateForm
}: FormikContextType<FormTradeData> = useFormikContext()
/// Values used for calculation of price impact
// Values used for calculation of price impact
const [spotPrice, setSpotPrice] = useState<string>()
const [totalValue, setTotalValue] = useState<string>()
const [tokenAmount, setTokenAmount] = useState<string>()
///
useEffect(() => {
if (!ddo || !balance || !values || !price) return
if (!ddo || !balance || !values?.type || !price) return
async function calculateMaximum() {
const dtAmount = values.type === 'buy' ? maxDt : balance.datatoken
const oceanAmount = values.type === 'buy' ? balance.ocean : maxOcean
const dtAmount =
values.type === 'buy'
? new Decimal(maxDt)
: new Decimal(balance.datatoken)
const oceanAmount =
values.type === 'buy'
? new Decimal(balance.ocean)
: new Decimal(maxOcean)
const maxBuyOcean = await ocean.pool.getOceanReceived(
price.address,
dtAmount.toString()
`${dtAmount.toString()}`
)
const maxBuyDt = await ocean.pool.getDTReceived(
price.address,
oceanAmount.toString()
`${oceanAmount.toString()}`
)
const maximumDt =
values.type === 'buy'
? Number(dtAmount) > Number(maxBuyDt)
? new Decimal(maxBuyDt)
: new Decimal(dtAmount)
: Number(dtAmount) > Number(balance.datatoken)
? new Decimal(balance.datatoken)
: new Decimal(dtAmount)
? dtAmount.greaterThan(new Decimal(maxBuyDt))
? maxBuyDt
: dtAmount
: dtAmount.greaterThan(new Decimal(balance.datatoken))
? balance.datatoken
: dtAmount
const maximumOcean =
values.type === 'sell'
? Number(oceanAmount) > Number(maxBuyOcean)
? new Decimal(maxBuyOcean)
: new Decimal(oceanAmount)
: Number(oceanAmount) > Number(balance.ocean)
? new Decimal(balance.ocean)
: new Decimal(oceanAmount)
? oceanAmount.greaterThan(new Decimal(maxBuyOcean))
? maxBuyOcean
: oceanAmount
: oceanAmount.greaterThan(new Decimal(balance.ocean))
? balance.ocean
: oceanAmount
setMaximumDt(maximumDt.toString())
setMaximumOcean(maximumOcean.toString())
setOceanItem({
...oceanItem,
setOceanItem((prevState) => ({
...prevState,
amount: oceanAmount.toString(),
maxAmount: maximumOcean.toString()
})
setDtItem({
...dtItem,
}))
setDtItem((prevState) => ({
...prevState,
amount: dtAmount.toString(),
maxAmount: maximumDt.toString()
})
}))
}
calculateMaximum()
}, [ddo, maxOcean, maxDt, balance, price?.value, values.type])
}, [
ddo,
maxOcean,
maxDt,
balance,
price,
values?.type,
ocean,
setMaximumDt,
setMaximumOcean
])
const switchTokens = () => {
setFieldValue('type', values.type === 'buy' ? 'sell' : 'buy')
setCoin(values.type === 'sell' ? 'OCEAN' : ddo.dataTokenInfo.symbol)
// don't reset form because we don't want to reset type
setFieldValue('datatoken', 0)
setFieldValue('ocean', 0)

View File

@ -12,15 +12,17 @@ import { useSiteMetadata } from '../hooks/useSiteMetadata'
interface UserPreferencesValue {
debug: boolean
currency: string
locale: string
chainIds: number[]
bookmarks: string[]
setChainIds: (chainIds: number[]) => void
setDebug: (value: boolean) => void
currency: string
setCurrency: (value: string) => void
chainIds: number[]
setChainIds: (chainIds: number[]) => void
bookmarks: string[]
addBookmark: (did: string) => void
removeBookmark: (did: string) => void
infiniteApproval: boolean
setInfiniteApproval: (value: boolean) => void
locale: string
}
const UserPreferencesContext = createContext(null)
@ -58,11 +60,14 @@ function UserPreferencesProvider({
const [chainIds, setChainIds] = useState(
localStorage?.chainIds || appConfig.chainIds
)
const [infiniteApproval, setInfiniteApproval] = useState(
localStorage?.infiniteApproval || false
)
// Write values to localStorage on change
useEffect(() => {
setLocalStorage({ chainIds, debug, currency, bookmarks })
}, [chainIds, debug, currency, bookmarks])
setLocalStorage({ chainIds, debug, currency, bookmarks, infiniteApproval })
}, [chainIds, debug, currency, bookmarks, infiniteApproval])
// Set ocean.js log levels, default: Error
useEffect(() => {
@ -108,6 +113,8 @@ function UserPreferencesProvider({
locale,
chainIds,
bookmarks,
infiniteApproval,
setInfiniteApproval,
setChainIds,
setDebug,
setCurrency,