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

feat: add topper and paymonade #10024

Merged
merged 28 commits into from
Jul 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
e64593c
chore: add topper and paymonade
ChefBingbong Jun 15, 2024
607810a
chore: add topper to campaign
ChefBingbong Jun 15, 2024
5cf5ed7
chore: add topper to ip fetch initial data
ChefBingbong Jun 15, 2024
1e358f7
chore: add paymonade to onram api provider config
ChefBingbong Jun 17, 2024
61f872d
Merge branch 'develop' into feat--topper-&-paymonade-onramp-FE-integr…
ChefBingbong Jun 19, 2024
b2b65f5
chore: use logos from assets repo and update hooks to use hook level …
ChefBingbong Jun 19, 2024
b7f942c
chore: add staging endpoint
ChefBingbong Jun 25, 2024
9f7ad03
chore: remove paymonade
ChefBingbong Jun 25, 2024
e179034
Merge branch 'develop' into feat--topper-&-paymonade-onramp-FE-integr…
ChefBingbong Jun 25, 2024
d95c6e2
chore: vs code bug caused missing imports fix
ChefBingbong Jun 25, 2024
442f64b
chore: remove log
ChefBingbong Jun 25, 2024
1413b23
Merge branch 'develop' into feat--topper-&-paymonade-onramp-FE-integr…
ChefBingbong Jul 3, 2024
5fbb3fc
feat: integrate topper prod and modify ip check to form server api to…
ChefBingbong Jul 3, 2024
3d34947
chore: add back endpoints
ChefBingbong Jul 3, 2024
ad3f941
chore: fix no quotes error
ChefBingbong Jul 4, 2024
c7e6a6a
swap out localhost api
ChefBingbong Jul 4, 2024
3cffe36
chore: fix translation
ChefBingbong Jul 9, 2024
6818e28
Merge branch 'develop' into feat--topper-&-paymonade-onramp-FE-integr…
ChefBingbong Jul 9, 2024
bf7a5e7
chore: topper updates
ChefBingbong Jul 10, 2024
70061e5
Merge branch 'develop' into feat--topper-&-paymonade-onramp-FE-integr…
ChefBingbong Jul 10, 2024
5d6d507
chore: fix ip whitelisting
ChefBingbong Jul 17, 2024
f30775e
Merge branch 'develop' into feat--topper-&-paymonade-onramp-FE-integr…
ChefBingbong Jul 17, 2024
7ee0566
chore: use proper url
ChefBingbong Jul 17, 2024
a2ea46c
chore: qa fixes
ChefBingbong Jul 17, 2024
cd9ef8e
Merge branch 'develop' into feat--topper-&-paymonade-onramp-FE-integr…
ChefBingbong Jul 18, 2024
3d2a652
chore: replace prod domain
ChefBingbong Jul 18, 2024
061b294
chore: revert change in file
ChefBingbong Jul 18, 2024
124d506
topper request items
ChefBingbong Jul 18, 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
8 changes: 3 additions & 5 deletions apps/web/src/state/buyCrypto/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import { useRouter } from 'next/router'
import type { ParsedUrlQuery } from 'querystring'
import { useCallback, useEffect } from 'react'
import { buyCryptoReducerAtom, type BuyCryptoState } from 'state/buyCrypto/reducer'

import { OnRampChainId as ChainId, type OnRampCurrency as Currency } from 'views/BuyCrypto/constants'
import { useAccount } from 'wagmi'
import { Field, replaceBuyCryptoState, selectCurrency, switchCurrencies, typeInput } from './actions'

const useEnableBtcPurchases = atomWithStorage<boolean>('pcs:enable-buy-btc-native', false)
Expand Down Expand Up @@ -77,13 +77,11 @@ export async function queryParametersToBuyCryptoState(
}
}

export function useDefaultsFromURLSearch(account: string | undefined) {
export function useDefaultsFromURLSearch() {
const [, dispatch] = useAtom(buyCryptoReducerAtom)
const { chainId } = useActiveChainId()
const { address } = useAccount()
const { query, isReady } = useRouter()

// biome-ignore lint/correctness/useExhaustiveDependencies: <explanation>
useEffect(() => {
const fetchData = async () => {
if (!isReady || !chainId) return
Expand All @@ -99,5 +97,5 @@ export function useDefaultsFromURLSearch(account: string | undefined) {
)
}
fetchData()
}, [dispatch, query, isReady, account, chainId, address])
}, [dispatch, query, isReady, chainId])
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const activeCampaigns: { [provider in keyof typeof ONRAMP_PROVIDERS]: boolean }
[ONRAMP_PROVIDERS.Mercuryo]: false,
[ONRAMP_PROVIDERS.MoonPay]: false,
[ONRAMP_PROVIDERS.Transak]: false,
[ONRAMP_PROVIDERS.Topper]: false,
}

const ProviderCampaign = ({ provider }: { provider: keyof typeof ONRAMP_PROVIDERS }) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,6 @@ export const ProviderSelector = ({
className="open-currency-select-button"
selected={!!selectedQuote}
onClick={() => onQuoteSelect?.(true)}
disabled={quoteLoading}
>
<Flex alignItems="center" width="100%">
<OnRampProviderLogo size={32} provider={selectedQuote?.provider} loading={quoteLoading} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,21 +20,21 @@ import BuyCryptoTooltip from '../Tooltip/Tooltip'

type FeeComponents = { providerFee: number; networkFee: number; price: number }
interface TransactionFeeDetailsProps {
selectedQuote: OnRampProviderQuote | undefined
selectedQuote?: OnRampProviderQuote
currency: Currency
independentField: Field
inputError: string | undefined
quotesError: string | undefined
loading: boolean | undefined
inputError?: string
loading?: boolean
quotesError?: boolean
}

export const TransactionFeeDetails = ({
selectedQuote,
currency,
independentField,
inputError,
quotesError,
loading,
quotesError,
}: TransactionFeeDetailsProps) => {
const [elementHeight, setElementHeight] = useState<number>(51)
const [show, setShow] = useState<boolean>(false)
Expand All @@ -48,7 +48,6 @@ export const TransactionFeeDetails = ({

const handleExpandClick = useCallback(() => setShow(!show), [show])

// biome-ignore lint/correctness/useExhaustiveDependencies: <explanation>
useEffect(() => {
const elRef = contentRef.current
if (elRef) setElementHeight(elRef.scrollHeight)
Expand All @@ -72,15 +71,11 @@ export const TransactionFeeDetails = ({
</StyledFeesContainer3>
</Flex>

<StyledFeesContainer
width="100%"
onClick={handleExpandClick}
disabled={Boolean(loading || quotesError || inputError)}
>
<StyledFeesContainer width="100%" onClick={handleExpandClick} disabled={Boolean(loading || inputError)}>
<StyledArrowHead />
<Flex justifyContent="space-between" alignItems="center">
<Flex alignItems="center">
{selectedQuote && !quotesError ? (
{selectedQuote && (
<>
<Text fontWeight="600" fontSize="14px" px="2px">
{t('Est total fees:')}
Expand All @@ -95,11 +90,15 @@ export const TransactionFeeDetails = ({
})}
</SkeletonText>
</>
) : (
<Text fontWeight="600" fontSize="14px" px="2px">
{quotesError}
)}
{quotesError && (
<Text fontWeight="600" fontSize="14px" px="2px" color="textSubtle">
{t('No quotes available for %cryptoCurrency% right now.', {
cryptoCurrency: currency?.symbol,
})}
</Text>
)}

<BuyCryptoTooltip
opacity={0.7}
iconSize="17px"
Expand Down
10 changes: 10 additions & 0 deletions apps/web/src/views/BuyCrypto/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export enum ONRAMP_PROVIDERS {
MoonPay = 'MoonPay',
Mercuryo = 'Mercuryo',
Transak = 'Transak',
Topper = 'Topper',
}

export enum FeeTypes {
Expand All @@ -62,12 +63,14 @@ export const PROVIDER_ICONS = {
[ONRAMP_PROVIDERS.MoonPay]: `${ASSET_CDN}/web/onramp/moonpay.svg`,
[ONRAMP_PROVIDERS.Mercuryo]: `${ASSET_CDN}/web/onramp/mercuryo.svg`,
[ONRAMP_PROVIDERS.Transak]: `${ASSET_CDN}/web/onramp/transak.svg`,
[ONRAMP_PROVIDERS.Topper]: `${ASSET_CDN}/web/onramp/topper.png`,
} satisfies Record<keyof typeof ONRAMP_PROVIDERS, string>

export const providerFeeTypes: { [provider in ONRAMP_PROVIDERS]: FeeTypes[] } = {
[ONRAMP_PROVIDERS.MoonPay]: MOONPAY_FEE_TYPES,
[ONRAMP_PROVIDERS.Mercuryo]: MERCURYO_FEE_TYPES,
[ONRAMP_PROVIDERS.Transak]: MOONPAY_FEE_TYPES,
[ONRAMP_PROVIDERS.Topper]: MOONPAY_FEE_TYPES,
}

export const getNetworkDisplay = (chainId: number | undefined): string => {
Expand Down Expand Up @@ -149,12 +152,19 @@ export const chainIdToTransakNetworkId: { [id: number]: string } = {
[OnRampChainId.BTC]: 'mainnet',
}

export const chainIdToTopperNetworkId: { [id: number]: string } = {
[OnRampChainId.ETHEREUM]: 'ethereum',
[OnRampChainId.ARBITRUM_ONE]: 'arbitrum',
0: 'bitcoin',
Copy link
Contributor

Choose a reason for hiding this comment

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

why bitcoin is 0?

Copy link
Contributor Author

@ChefBingbong ChefBingbong Jul 19, 2024

Choose a reason for hiding this comment

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

btc doesnt have a chanid so its a work around so that it works with our token list and chaind mapings system

}

export const combinedNetworkIdMap: {
[provider in keyof typeof ONRAMP_PROVIDERS]: { [id: number]: string }
} = {
[ONRAMP_PROVIDERS.MoonPay]: chainIdToMoonPayNetworkId,
[ONRAMP_PROVIDERS.Mercuryo]: chainIdToMercuryoNetworkId,
[ONRAMP_PROVIDERS.Transak]: chainIdToTransakNetworkId,
[ONRAMP_PROVIDERS.Topper]: chainIdToTopperNetworkId,
}

export const selectCurrencyField = (unit: OnRampUnit, mode: string) => {
Expand Down
37 changes: 19 additions & 18 deletions apps/web/src/views/BuyCrypto/containers/BuyCryptoForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import {
useState,
type ChangeEvent,
type Dispatch,
type RefObject,
type SetStateAction,
} from 'react'
import { useBuyCryptoActionHandlers, useBuyCryptoState } from 'state/buyCrypto/hooks'
Expand All @@ -32,6 +31,7 @@ import { useIsBtc } from '../hooks/useIsBtc'
import { useOnRampCurrencyOrder } from '../hooks/useOnRampCurrencyOrder'
import { useLimitsAndInputError } from '../hooks/useOnRampInputError'
import { useOnRampQuotes } from '../hooks/useOnRampQuotes'
import type { ProviderAvailabilities } from '../hooks/useProviderAvailabilities'
import InputExtended, { StyledVerticalLine } from '../styles'
import { FormContainer } from './FormContainer'
import { FormHeader } from './FormHeader'
Expand All @@ -48,7 +48,7 @@ interface OnRampCurrencySelectPopOverProps {
}
type InputEvent = ChangeEvent<HTMLInputElement>

export function BuyCryptoForm() {
export function BuyCryptoForm({ providerAvailabilities }: { providerAvailabilities: ProviderAvailabilities }) {
const { typedValue, independentField } = useBuyCryptoState()

const { t } = useTranslation()
Expand Down Expand Up @@ -86,15 +86,17 @@ export function BuyCryptoForm() {
enabled: Boolean(!inputError),
})

const quotes = useMemo(() => data?.quotes, [data?.quotes])
const quotesError = useMemo(() => data?.quotesError, [data?.quotesError])
const { quotes, quotesError } = useMemo(() => {
const filteredQuotes = data?.quotes.filter((q) => providerAvailabilities[q.provider])
return { quotes: filteredQuotes, quotesError: isError || data?.quotes.length === 0 }
}, [data?.quotes, providerAvailabilities, isError])

const outputValue = useMemo((): string | undefined => {
if (inputError || quotesError || !selectedQuote) return undefined
if (inputError || !selectedQuote) return undefined
const { amount, quote } = selectedQuote
const output = isFiat(unit) ? quote : amount
return formatQuoteDecimals(output, unit)
}, [unit, selectedQuote, inputError, quotesError])
}, [unit, selectedQuote, inputError])

const handleTypeInput = useCallback((value: string) => onUserInput(Field.INPUT, value), [onUserInput])
const handleAddressInput = useCallback((event: InputEvent) => setSearchQuery(event.target.value), [])
Expand All @@ -118,13 +120,12 @@ export function BuyCryptoForm() {
}, [handleTypeInput, defaultAmt, unit, onFlip, searchQuery, onCurrencySelection])

useEffect(() => {
if (!quotes || quotes?.length === 0) return
setSelectedQuote(quotes[0])
if (!quotes || quotesError) return
if (bestQuoteRef.current !== quotes[0]) {
bestQuoteRef.current = quotes[0]
setSelectedQuote(quotes[0])
}
}, [quotes])
}, [quotes, quotesError])

useEffect(() => {
if (!defaultAmt || !isFiat(unit)) return
Expand All @@ -140,7 +141,7 @@ export function BuyCryptoForm() {
<OnRampCurrencySelectPopOver
quotes={quotes}
selectedQuote={selectedQuote}
isError={isError}
isError={quotesError}
inputError={inputError}
isFetching={isLoading}
setSelectedQuote={setSelectedQuote}
Expand All @@ -166,13 +167,13 @@ export function BuyCryptoForm() {
</Box>
<BuyCryptoSelector
id={isFiat(unit) ? 'onramp-crypto' : 'onramp-fiat'}
inputLoading={Boolean(isLoading || inputError || quotesError)}
onCurrencySelect={onCurrencySelection}
selectedCurrency={currencyIn}
currencyLoading={Boolean(!currencyIn)}
value={outputValue ?? ''}
value={outputValue ?? '0.0'}
disableInput
unit={unit}
inputLoading={Boolean(isLoading || inputError || quotesError)}
/>
<BitcoinAddressInput
isBtc={isBtc}
Expand All @@ -193,20 +194,20 @@ export function BuyCryptoForm() {
currency={cryptoCurrency}
independentField={independentField}
inputError={inputError}
quotesError={quotesError}
loading={isLoading}
quotesError={quotesError}
/>

<Box>
<FiatOnRampModalButton
externalTxIdRef={externalTxIdRef}
cryptoCurrency={cryptoCurrency}
selectedQuote={selectedQuote}
disabled={Boolean(isError || quotesError || inputError || btcError)}
loading={Boolean(quotesError || isLoading)}
disabled={Boolean(inputError || btcError || quotesError)}
loading={isLoading}
resetBuyCryptoState={resetBuyCryptoState}
btcAddress={debouncedQuery}
errorText={amountError}
errorText={quotesError ? t('No Quotes') : amountError}
onRampUnit={unit}
/>
<Flex alignItems="center" justifyContent="center">
Expand Down Expand Up @@ -294,7 +295,7 @@ const BitcoinAddressInput = ({
validAddress: GetBtcAddrValidationReturnType | undefined
handleInput: (event) => void
}) => {
const inputRef = useRef<HTMLInputElement>()
const inputRef = useRef<HTMLInputElement>(null)
const { isMobile } = useMatchBreakpoints()
const { t } = useTranslation()

Expand All @@ -311,7 +312,7 @@ const BitcoinAddressInput = ({
scale="lg"
autoComplete="off"
value={searchQuery}
ref={inputRef as RefObject<HTMLInputElement>}
ref={inputRef}
onChange={handleInput}
color="primary"
isSuccess={Boolean(validAddress?.result)}
Expand Down
5 changes: 1 addition & 4 deletions apps/web/src/views/BuyCrypto/hooks/useOnRampLimits.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,7 @@ export const useOnRampLimit = <selectData = GetOnRampLimitReturnType>(
network,
},
]),
queryFn: async ({ queryKey }) => {
// eslint-disable-next-line @typescript-eslint/no-shadow
const { fiatCurrency, cryptoCurrency, network } = queryKey[1]

queryFn: async () => {
if (!fiatCurrency || !cryptoCurrency) {
throw new Error('Invalid parameters')
}
Expand Down
29 changes: 4 additions & 25 deletions apps/web/src/views/BuyCrypto/hooks/useOnRampQuotes.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { useQuery, type UseQueryResult } from '@tanstack/react-query'
import { ONRAMP_API_BASE_URL } from 'config/constants/endpoints'
import { getNetworkDisplay, type ONRAMP_PROVIDERS } from '../constants'
import {
createQueryKey,
type Evaluate,
Expand All @@ -16,7 +15,7 @@ const getOnRampQuotesQueryKey = createQueryKey<'fetch-onramp-quotes', [ExactPart

type GetOnRampQuotesQueryKey = ReturnType<typeof getOnRampQuotesQueryKey>

type GetOnRampQuoteReturnType = { quotes: OnRampProviderQuote[]; quotesError: string | undefined }
type GetOnRampQuoteReturnType = { quotes: OnRampProviderQuote[] }

export type UseOnRampQuotesReturnType<selectData = GetOnRampQuoteReturnType> = UseQueryResult<selectData, Error>

Expand All @@ -29,7 +28,6 @@ export const useOnRampQuotes = <selectData = GetOnRampQuoteReturnType>(
parameters: UseOnRampQuotesParameters<selectData>,
) => {
const { fiatAmount, enabled, cryptoCurrency, fiatCurrency, network, onRampUnit, ...query } = parameters

return useQuery({
...query,
queryKey: getOnRampQuotesQueryKey([
Expand All @@ -44,25 +42,19 @@ export const useOnRampQuotes = <selectData = GetOnRampQuoteReturnType>(
refetchInterval: 40 * 1_000,
staleTime: 40 * 1_000,
enabled: Boolean(enabled),
queryFn: async ({ queryKey }) => {
// eslint-disable-next-line @typescript-eslint/no-shadow
const { cryptoCurrency, fiatAmount, fiatCurrency, network, onRampUnit } = queryKey[1]
queryFn: async () => {
if (!cryptoCurrency || !fiatAmount || !fiatCurrency || !onRampUnit) {
throw new Error('Missing buy-crypto fetch-provider-quotes params')
}
const providerQuotes = await fetchProviderQuotes({
const quotes = await fetchProviderQuotes({
cryptoCurrency,
fiatAmount,
fiatCurrency,
network,
onRampUnit,
})
const quotes = providerQuotes.filter((q) => Boolean(q.quote !== 0)).sort((a, b) => b.quote - a.quote)

const networkDisplay = getNetworkDisplay(network)
const error =
providerQuotes.length === 0 ? `No quotes available for ${cryptoCurrency} on ${networkDisplay}` : undefined
return { quotes, quotesError: error }
return { quotes }
},
})
}
Expand All @@ -83,16 +75,3 @@ async function fetchProviderQuotes(payload: OnRampQuotesPayload): Promise<OnRamp
const result = await response.json()
return result.result
}

export async function fetchProviderAvailabilities(): Promise<{ [provider in keyof typeof ONRAMP_PROVIDERS]: boolean }> {
// Fetch data from endpoint 1
const response = await fetch(`${ONRAMP_API_BASE_URL}/fetch-provider-availability`, {
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
method: 'POST',
})
const result = await response.json()
return result.result
}
Loading
Loading