Skip to content

Commit

Permalink
332/Store slippage info on appData (#629)
Browse files Browse the repository at this point in the history
* Bumped latest cow-sdk version

* Creating cow-sdk instances for all networks

* Removed redundant import

* Updated .env files with now mandatory env var for IPFS uploading

* Added/moved appCode related consts to constants file

* New hook useAppCode

* Added appData/atoms using Jotai

WARNING! Still needs refactoring

* New utils for dealing with appData

* New hook useAppData

* Adding newly calculated appData to order and storing it in the to-be-uploaded queue

* Added appData/updater

WARNING! Still needs refactoring

* Disabling affiliate data IPFS upload; will be handled on every order

WARNING! Needs to review if affiliate flow is still working as before

* Removing helper files no longer in use

Most have been replaced by the correspondent sdk methods

* Refactored state/appData/types

* Refactored state/appData/atoms

* Refactored state/AppData/hooks

* Updater state/appData/updater does not need to be tsx file

* Added state/appData/utils to handle key creation/parsing

* Refactored upload queue to a flat object rather than nested by network

* Removing debug loggs from state/appData/atoms

* Refactored state/appData/updater

* `environment` is now part of the appData rather than a metadata

* Forcing all inputs in an attempt to fix cypress occasional failures

* Refactor: removed redundant initial state on state/appData/atoms

* Refactor: improved logging in case of appDataHash generation

* 332/exponential back off (#726)

* Updated stored types to contain lastAttempt rather than tryAfter

* New helper function to check when we can try to upload to ipfs again

* Improved logging for ipfs upload updater

* Changed soft upload failures log level from debug to warn

* Refactor: extracted helper function _actuallyUploadToIpfs

* Refactor: Renamed BASE_TIME_BETWEEN_ATTEMPTS to BASE_FOR_EXPONENTIAL_BACKOFF

* 332/update affiliate flow (#647)

* Updated state/affiliate

- Removed no longer needed state (appDataHash)
- Added new status referralAddress.isActive
- Updated associated actions, hooks and reducer
- Updated AffiliateStatusCheck to use new state

* Updated hooks/useAppData to use new state/affiliate state

* Updated useEffect deps to prevent unecessary re-renders

* Refactor: removed redundant variable

* Fixed issue where invalid referral would not be tagged as so

* 332/warning when pinata envs not set locally (#659)

* Added `localWarning` for PINATA keys

* Displaying localWarning if any

* Added alternative warning display:

As a permanent toast notification

* Refactor: Renamed WarningPopupContent `message` to `warning`

* Added warning icon to toast notification

* Moved localWarning from Header to state/application

* Changed warning popup key to a more generic value

* Removed banner with warning in favor of the popup notification

* 332/quote id on metadata (#750)

* Ignore quoteId when checking if the order is unfillable

* Persiste quoteId from api to redux state

* Add quoteId to GpTrade class

* Pass quoteId down to appData

* Include quoteId on order placement

* Bumped cow-sdk to 0.0.15-RC.0

* Refactor: Replaced map upload queue with arrary (#747)

* Refactor: Replaced map upload queue with arrary

* Refactor: Using slice(0) to clone array instead of spread operator

* Refactor: using Array.some instead of Array.find

As I do not need to the stored element

* 332/slippage bips rather than amounts on metadata (#758)

* Bumop quote metadata version

* Added helper function to transfor Percent instances to bip string

* Refactored appData utils functions to use new quote metadata schema

Also changed the fn signature to accomodate different options if needed

* Updated useAppData to use slippage in the quote metadata

* Refactored useAppData interface

* Removed code that is not related to slippageBips for quote metadata

* Actually, _buildQuoteMetadata will never return undefined

* 332/upload right away (#767)

* Increased upload to IPFS queue check interval to 1m

* Try to upload docs added to the upload queue right away

* 332/refactor use address (#748)

* Refactor: renamed useAddress to useAffiliateAddress

* Typo fix on comment

* Fixed issue with affiliate not valid displayed when there was no affiliate

* Refeset affiliate state whenever error is reset

* Referral address cannot be null, but undefined 🤦
  • Loading branch information
alfetopito committed Jul 4, 2022
1 parent c756c19 commit 97887c5
Show file tree
Hide file tree
Showing 42 changed files with 650 additions and 213 deletions.
4 changes: 4 additions & 0 deletions .env
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,7 @@ REACT_APP_MOCK=true

# Locales
REACT_APP_LOCALES="locales"

# IPFS uploading
REACT_APP_PINATA_API_KEY=
REACT_APP_PINATA_SECRET_API_KEY=
4 changes: 4 additions & 0 deletions .env.production
Original file line number Diff line number Diff line change
Expand Up @@ -73,3 +73,7 @@ REACT_APP_MOCK=false

# Locales
REACT_APP_LOCALES="locales"

# IPFS uploading
#REACT_APP_PINATA_API_KEY=
#REACT_APP_PINATA_SECRET_API_KEY=
12 changes: 6 additions & 6 deletions cypress-custom/integration/swapMod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,22 @@ describe('Swap (mod)', () => {

it('can enter an amount into input', () => {
cy.get('#swap-currency-input .token-amount-input')
.clear()
.type('0.001', { delay: 400, force: true })
.type('{selectall}{backspace}{selectall}{backspace}')
.type('0.001')
.should('have.value', '0.001')
})

it('zero swap amount', () => {
cy.get('#swap-currency-input .token-amount-input')
.clear()
.type('0.0', { delay: 400, force: true })
.type('{selectall}{backspace}{selectall}{backspace}')
.type('0.0')
.should('have.value', '0.0')
})

it('invalid swap amount', () => {
cy.get('#swap-currency-input .token-amount-input')
.clear()
.type('\\', { delay: 400, force: true })
.type('{selectall}{backspace}{selectall}{backspace}')
.type('\\')
.should('have.value', '')
})

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
"@babel/preset-env": "^7.16.11",
"@babel/preset-react": "^7.16.7",
"@babel/preset-typescript": "^7.16.7",
"@cowprotocol/cow-sdk": "^0.0.14",
"@cowprotocol/cow-sdk": "^0.0.15-RC.0",
"@craco/craco": "^5.7.0",
"@ethersproject/experimental": "^5.4.0",
"@graphql-codegen/cli": "1.21.5",
Expand Down
37 changes: 0 additions & 37 deletions src/custom/api/ipfs/index.ts

This file was deleted.

82 changes: 52 additions & 30 deletions src/custom/components/AffiliateStatusCheck/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,8 @@ import React, { useCallback, useEffect, useState, useRef, useMemo } from 'react'
import { useHistory, useLocation } from 'react-router-dom'
import { useActiveWeb3React } from 'hooks/web3'
import NotificationBanner from 'components/NotificationBanner'
import { useReferralAddress, useResetReferralAddress } from 'state/affiliate/hooks'
import { updateAppDataHash } from 'state/affiliate/actions'
import { useAppDispatch } from 'state/hooks'
import { useReferralAddress, useResetReferralAddress, useSetReferralAddressActive } from 'state/affiliate/hooks'
import { hasTrades } from 'utils/trade'
import { generateReferralMetadataDoc, uploadMetadataDocToIpfs } from 'utils/metadata'
import { retry, RetryOptions } from 'utils/retry'
import { SupportedChainId } from 'constants/chains'
import useParseReferralQueryParam from 'hooks/useParseReferralQueryParam'
Expand All @@ -26,39 +23,59 @@ const STATUS_TO_MESSAGE_MAPPING: Record<AffiliateStatus, string> = {
const DEFAULT_RETRY_OPTIONS: RetryOptions = { n: 3, minWait: 1000, maxWait: 3000 }

export default function AffiliateStatusCheck() {
const appDispatch = useAppDispatch()
const resetReferralAddress = useResetReferralAddress()
const setReferralAddressActive = useSetReferralAddressActive()
const history = useHistory()
const location = useLocation()
const { account, chainId } = useActiveWeb3React()
const referralAddress = useReferralAddress()
const referralAddressQueryParam = useParseReferralQueryParam()
const allRecentActivity = useRecentActivity()
const [affiliateState, setAffiliateState] = useState<AffiliateStatus | null>()
const [affiliateState, _setAffiliateState] = useState<AffiliateStatus | null>()

/**
* Wrapper around setAffiliateState (local) and setReferralAddressActive (global)
* Need to keep track when affiliate is ACTIVE to know whether it should be included in the
* metadata, no longer uploaded to IPFS here
*/
const setAffiliateState = useCallback(
(state: AffiliateStatus | null) => {
_setAffiliateState(state)
setReferralAddressActive(state === 'ACTIVE')
},
[setReferralAddressActive]
)

// De-normalized to avoid unnecessary useEffect triggers
const isReferralAddressNotSet = !referralAddress
const referralAddressAccount = referralAddress?.value
const referralAddressIsValid = referralAddress?.isValid

const [error, setError] = useState('')
const isFirstTrade = useRef(false)
const fulfilledOrders = allRecentActivity.filter((data) => {
return 'appData' in data && data.status === OrderStatus.FULFILLED
})

const notificationBannerId = useMemo(() => {
if (!referralAddress?.value) {
if (!referralAddressAccount) {
return
}

if (!account) {
return `referral-${referralAddress.value}`
return `referral-${referralAddressAccount}`
}

return `wallet-${account}:referral-${referralAddress.value}:chain-${chainId}`
}, [account, chainId, referralAddress?.value])
return `wallet-${account}:referral-${referralAddressAccount}:chain-${chainId}`
}, [account, chainId, referralAddressAccount])

const handleAffiliateState = useCallback(async () => {
if (!chainId || !account || !referralAddress) {
if (!chainId || !account) {
return
}

if (!referralAddress.isValid) {
// Note: comparing with `false` because in case `undefined` msg shouldn't be displayed
if (referralAddressIsValid === false) {
setError('Affiliate program: The referral address is invalid.')
return
}
Expand All @@ -85,28 +102,23 @@ export default function AffiliateStatusCheck() {

setAffiliateState('ACTIVE')
isFirstTrade.current = true
}, [referralAddress, chainId, account, fulfilledOrders.length, history, resetReferralAddress])

useEffect(() => {
async function handleReferralAddress(referralAddress: { value: string; isValid: boolean } | undefined) {
if (!referralAddress?.value) return
try {
const appDataHash = await uploadMetadataDocToIpfs(generateReferralMetadataDoc(referralAddress.value))
appDispatch(updateAppDataHash(appDataHash))
} catch (e) {
console.error(e)
setError('Affiliate program: There was an error while uploading your referral data. Please try again later.')
}
}
if (affiliateState === 'ACTIVE') handleReferralAddress(referralAddress)
}, [referralAddress, affiliateState, appDispatch])
}, [
account,
chainId,
fulfilledOrders.length,
history,
referralAddressIsValid,
resetReferralAddress,
setAffiliateState,
])

useEffect(() => {
if (!referralAddress) {
if (isReferralAddressNotSet) {
return
}

setError('')
setAffiliateState(null)

if (!account) {
setAffiliateState('NOT_CONNECTED')
Expand All @@ -118,7 +130,7 @@ export default function AffiliateStatusCheck() {
return
}

if (referralAddress.value === account) {
if (referralAddressAccount === account) {
// clean-up saved referral address if the user follows its own referral link
setAffiliateState('OWN_LINK')

Expand All @@ -129,7 +141,17 @@ export default function AffiliateStatusCheck() {
}

handleAffiliateState()
}, [referralAddress, account, history, chainId, handleAffiliateState, location.search, referralAddressQueryParam])
}, [
account,
history,
chainId,
handleAffiliateState,
location.search,
referralAddressQueryParam,
setAffiliateState,
referralAddressAccount,
isReferralAddressNotSet,
])

if (error) {
return (
Expand Down
1 change: 1 addition & 0 deletions src/custom/components/Header/URLWarning/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ export default function URLWarning() {

const announcementVisible = useAnnouncementVisible(contentHash)
const closeAnnouncement = useCloseAnnouncement()

const announcement = announcementVisible && announcementText && (
<>
<div style={{ display: 'flex' }}>
Expand Down
4 changes: 4 additions & 0 deletions src/custom/components/Popups/PopupItemMod.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import TransactionPopup from './TransactionPopupMod'

// MOD imports
import ListUpdatePopup from 'components/Popups/ListUpdatePopup'
import { WarningPopup } from 'components/Popups/WarningPopup'

export const StyledClose = styled(X)`
position: absolute;
Expand Down Expand Up @@ -84,6 +85,7 @@ export default function PopupItem({
const isListUpdate = 'listUpdate' in content
const isUnsupportedNetwork = 'unsupportedNetwork' in content
const isMetaTxn = 'metatxn' in content
const isWarningTxn = 'warning' in content

let popupContent
if (isTxn) {
Expand All @@ -105,6 +107,8 @@ export default function PopupItem({
popupContent = <FailedNetworkSwitchPopup chainId={content.failedSwitchNetwork} isUnsupportedNetwork />
} else if ('failedSwitchNetwork' in content) {
popupContent = <FailedNetworkSwitchPopup chainId={content.failedSwitchNetwork} />
} else if (isWarningTxn) {
popupContent = <WarningPopup warning={content.warning} />
}

const faderStyle = useSpring({
Expand Down
28 changes: 28 additions & 0 deletions src/custom/components/Popups/WarningPopup.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { useContext } from 'react'
import styled, { ThemeContext } from 'styled-components/macro'

import { ThemedText } from 'theme'
import { AutoColumn } from 'components/Column'
import { AutoRow } from 'components/Row'
import { AlertCircle } from 'react-feather'

const RowNoFlex = styled(AutoRow)`
flex-wrap: nowrap;
`

export function WarningPopup({ warning }: { warning: string | JSX.Element }) {
const theme = useContext(ThemeContext)

return (
<RowNoFlex>
<div style={{ paddingRight: 16 }}>
<AlertCircle color={theme.red1} size={24} />
</div>
<AutoColumn gap="sm">
<ThemedText.Body fontWeight={'bold'} color={theme.danger}>
{warning}
</ThemedText.Body>
</AutoColumn>
</RowNoFlex>
)
}
4 changes: 4 additions & 0 deletions src/custom/constants/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ export const SHORT_LOAD_THRESHOLD = 500
export const LONG_LOAD_THRESHOLD = 2000

export const APP_DATA_HASH = getAppDataHash()
export const DEFAULT_APP_CODE = 'CowSwap'
export const SAFE_APP_CODE = `${DEFAULT_APP_CODE}-SafeApp`

export const PRODUCTION_URL = 'cowswap.exchange'
export const BARN_URL = `barn.${PRODUCTION_URL}`

Expand Down Expand Up @@ -170,6 +173,7 @@ export const SWR_OPTIONS = {
revalidateOnFocus: false,
}

// TODO: show banner warning when PINATA env vars are missing
const COW_SDK_OPTIONS = {
ipfs: { pinataApiKey: PINATA_API_KEY, pinataApiSecret: PINATA_SECRET_API_KEY },
}
Expand Down
2 changes: 0 additions & 2 deletions src/custom/constants/ipfs.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,2 @@
export const PINATA_API_KEY = process.env.REACT_APP_PINATA_API_KEY as string
export const PINATA_SECRET_API_KEY = process.env.REACT_APP_PINATA_SECRET_API_KEY as string
export const PINATA_API_URL = process.env.REACT_APP_PINATA_API_URL || 'https://api.pinata.cloud'
export const IPFS_URI = process.env.REACT_APP_IPFS_URI || 'https://ipfs.infura.io:5001/api/v0'
15 changes: 15 additions & 0 deletions src/custom/hooks/useAppCode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { useIsGnosisSafeApp } from 'hooks/useWalletInfo'
import { DEFAULT_APP_CODE, SAFE_APP_CODE } from 'constants/index'

const APP_CODE = process.env.REACT_APP_APP_CODE

export function useAppCode(): string {
const isSafeApp = useIsGnosisSafeApp()

if (APP_CODE) {
// appCode coming from env var has priority
return APP_CODE
}

return isSafeApp ? SAFE_APP_CODE : DEFAULT_APP_CODE
}
Loading

0 comments on commit 97887c5

Please sign in to comment.