From 3c479f2d46563ec89fa3d2d788b8ce7bb1255cfd Mon Sep 17 00:00:00 2001 From: Matias Poblete Date: Tue, 4 Jun 2024 17:10:21 -0400 Subject: [PATCH 01/23] =?UTF-8?q?=F0=9F=9A=A7Add=20Buy=20page?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pages/buy/index.tsx | 24 +++ src/components/Buy/BuyComponent.tsx | 230 ++++++++++++++++++++++++++++ src/components/Layout/Header.tsx | 2 +- src/components/Swap/SwapHeader.tsx | 40 +++-- 4 files changed, 286 insertions(+), 10 deletions(-) create mode 100644 pages/buy/index.tsx create mode 100644 src/components/Buy/BuyComponent.tsx diff --git a/pages/buy/index.tsx b/pages/buy/index.tsx new file mode 100644 index 00000000..e668b68f --- /dev/null +++ b/pages/buy/index.tsx @@ -0,0 +1,24 @@ +import { useSorobanReact } from '@soroban-react/core'; +import { BuyComponent } from 'components/Buy/BuyComponent'; +import SEO from 'components/SEO'; +import { xlmTokenList } from 'constants/xlmToken'; +import { useEffect, useState } from 'react'; + +export default function BuyPage() { + const { activeChain } = useSorobanReact(); + const [xlmToken, setXlmToken] = useState(null); + + useEffect(() => { + const newXlmToken = + xlmTokenList.find((tList) => tList.network === activeChain?.id)?.assets[0].contract ?? null; + setXlmToken(newXlmToken); + }, [activeChain, xlmToken]); + + return ( + <> + + {xlmToken && } + + + ); +} diff --git a/src/components/Buy/BuyComponent.tsx b/src/components/Buy/BuyComponent.tsx new file mode 100644 index 00000000..aa50a99f --- /dev/null +++ b/src/components/Buy/BuyComponent.tsx @@ -0,0 +1,230 @@ +import { Box, Button, CircularProgress, Modal, styled } from '@mui/material'; +import { setTrustline } from '@soroban-react/contracts'; +import { useSorobanReact } from '@soroban-react/core'; +import { ButtonPrimary } from 'components/Buttons/Button'; +import { AutoColumn } from 'components/Column'; +import { AppContext, SnackbarIconType } from 'contexts'; +import { sendNotification } from 'functions/sendNotification'; +import { formatTokenAmount } from 'helpers/format'; +import { requiresTrustline } from 'helpers/stellar'; +import { useToken } from 'hooks/tokens/useToken'; +import useGetNativeTokenBalance from 'hooks/useGetNativeTokenBalance'; +import { + ReactNode, + useCallback, + useContext, + useEffect, + useMemo, + useReducer, + useState, +} from 'react'; +import { InterfaceTrade, TradeState } from 'state/routing/types'; +import { Field } from 'state/swap/actions'; +import { useDerivedSwapInfo, useSwapActionHandlers } from 'state/swap/hooks'; +import swapReducer, { SwapState, initialState as initialSwapState } from 'state/swap/reducer'; +import { opacify } from 'themes/utils'; +import SwapHeader from 'components/Swap/SwapHeader'; +import { SwapWrapper } from 'components/Swap/styleds'; + +export const SwapSection = styled('div')(({ theme }) => ({ + position: 'relative', + backgroundColor: theme.palette.customBackground.module, + borderRadius: 12, + padding: 16, + color: theme.palette.secondary.main, + fontSize: 14, + lineHeight: '20px', + fontWeight: 500, + '&:before': { + boxSizing: 'border-box', + backgroundSize: '100%', + borderRadius: 'inherit', + position: 'absolute', + top: 0, + left: 0, + width: '100%', + height: '100%', + pointerEvents: 'none', + border: `1px solid ${theme.palette.customBackground.module}`, + }, + '&:hover:before': { + borderColor: opacify(8, theme.palette.secondary.main), + }, + '&:focus-within:before': { + borderColor: opacify(24, theme.palette.secondary.light), + }, +})); + +export const OutputSwapSection = styled(SwapSection)` + border-bottom: ${({ theme }) => `1px solid ${theme.palette.customBackground.module}`}; + border-radius: 16px; + border: 1px solid rgba(180, 239, 175, 0.2); + background: ${({ theme }) => theme.palette.customBackground.outputBackground}; +`; + +export const ArrowContainer = styled('div')` + display: inline-flex; + align-items: center; + justify-content: center; + + width: 100%; + height: 100%; +`; + +function getIsValidSwapQuote( + trade: InterfaceTrade | undefined, + tradeState: TradeState, + swapInputError?: ReactNode, +): boolean { + return Boolean(!swapInputError && trade && tradeState === TradeState.VALID); +} + +interface BuyStateProps { + showConfirm: boolean; + tradeToConfirm?: InterfaceTrade; + swapError?: Error; + swapResult?: any; +} + +const INITIAL_SWAP_STATE = { + showConfirm: false, + tradeToConfirm: undefined, + swapError: undefined, + swapResult: undefined, +}; + +export function BuyComponent({ + prefilledState = {}, + disableTokenInputs = false, +}: { + prefilledState?: Partial; + disableTokenInputs?: boolean; +}) { + const sorobanContext = useSorobanReact(); + const { SnackbarContext } = useContext(AppContext); + const [txError, setTxError] = useState(false); + + const [needTrustline, setNeedTrustline] = useState(true); + + const { token: prefilledToken } = useToken(prefilledState.INPUT?.currencyId!); + + // modal and loading + const [{ showConfirm, tradeToConfirm, swapError, swapResult }, setSwapState] = + useState(INITIAL_SWAP_STATE); + + const [state, dispatch] = useReducer(swapReducer, { ...initialSwapState, ...prefilledState }); + const { typedValue, recipient, independentField } = state; + + const { onSwitchTokens, onCurrencySelection, onUserInput, onChangeRecipient } = + useSwapActionHandlers(dispatch); + const dependentField: Field = independentField === Field.INPUT ? Field.OUTPUT : Field.INPUT; + + useEffect(() => { + if (prefilledToken) { + onCurrencySelection(Field.INPUT, prefilledToken); + } + }, [onCurrencySelection, prefilledToken]); + + const { + trade: { state: tradeState, trade, resetRouterSdkCache }, + allowedSlippage, + currencyBalances, + parsedAmount, + currencies, + inputError: swapInputError, + } = useDerivedSwapInfo(state); + + + + const decimals = useMemo( + () => ({ + [Field.INPUT]: + independentField === Field.INPUT + ? trade?.outputAmount?.currency.decimals ?? 7 + : trade?.inputAmount?.currency.decimals ?? 7, + [Field.OUTPUT]: + independentField === Field.OUTPUT + ? trade?.inputAmount?.currency.decimals ?? 7 + : trade?.outputAmount?.currency.decimals ?? 7, + }), + [independentField, trade], + ); + + + const formattedAmounts = useMemo( + () => ({ + [independentField]: typedValue, + [dependentField]: formatTokenAmount(trade?.expectedAmount, decimals[independentField]), + }), + [decimals, dependentField, independentField, trade?.expectedAmount, typedValue], + ); + + const handleTrustline = () => { + const asset = trade?.outputAmount?.currency; + if (!asset?.issuer) return; + + setTrustline({ tokenSymbol: asset.code, tokenAdmin: asset.issuer, sorobanContext }) + .then((result) => { + setNeedTrustline(false); + sendNotification( + `for ${asset.code}`, + 'Trustline set', + SnackbarIconType.MINT, + SnackbarContext, + ); + }) + .catch((error) => { + // console.log(error); + setTxError(true); + setSwapState((currentState) => ({ + ...currentState, + showConfirm: false, + })); + }); + }; + + + + const nativeBalance = useGetNativeTokenBalance(); + + useEffect(() => { + const checkTrustline = async () => { + if (!trade) return; + if (sorobanContext.address) { + // Check if we need trustline + const needTrustline = await requiresTrustline( + sorobanContext, + trade?.outputAmount?.currency, + trade?.outputAmount?.value, + ); + + if (needTrustline) { + setNeedTrustline(true); + } else { + setNeedTrustline(false); + } + } else { + // Until the user does not connects the wallet, we will think that we need trustline + setNeedTrustline(true); + } + }; + + checkTrustline(); + }, [sorobanContext, trade, nativeBalance.data?.validAccount]); + + + + return ( + <> + + + + { + console.log('button clicked!') + alert('button clicked!') + }}>Buy USDC + + + + ); +} diff --git a/src/components/Layout/Header.tsx b/src/components/Layout/Header.tsx index 54fae0d7..3de614c0 100644 --- a/src/components/Layout/Header.tsx +++ b/src/components/Layout/Header.tsx @@ -196,7 +196,7 @@ export default function Header({ isDrawerOpen, setDrawerOpen }: HeaderProps) { diff --git a/src/components/Swap/SwapHeader.tsx b/src/components/Swap/SwapHeader.tsx index 1258fd33..3da8eac0 100644 --- a/src/components/Swap/SwapHeader.tsx +++ b/src/components/Swap/SwapHeader.tsx @@ -1,10 +1,10 @@ // import { Trans } from '@lingui/macro' //This is for localization and translation on all languages import { styled, useTheme } from '@mui/material/styles'; import { RowBetween, RowFixed } from '../Row'; -import { SubHeader } from '../Text'; -import TuneRoundedIcon from '@mui/icons-material/TuneRounded'; import SettingsTab from '../Settings/index'; -import { useMediaQuery } from '@mui/material'; +import { Typography, useMediaQuery } from '@mui/material'; +import Link from 'next/link'; +import { useEffect, useState } from 'react'; const StyledSwapHeader = styled(RowBetween)(({ theme }) => ({ marginBottom: 10, color: theme.palette.secondary.main, @@ -15,33 +15,55 @@ const HeaderButtonContainer = styled(RowFixed)` gap: 16px; `; +const SwapLink = styled(Link, { + shouldForwardProp: (prop) => prop !== 'active', +}) <{ active?: boolean }>` + display: flex; + padding: 4px 4px; + align-items: center; + gap: 10px; + text-align: center; + color: ${({ theme, active }) => (active ? '#FFFFFF' : '#7780A0')}; + font-family: Inter; + font-size: 20px; + font-weight: 600; + line-height: 140%; +`; + export default function SwapHeader({ autoSlippage, chainId, trade, + active, }: { autoSlippage?: number; chainId?: number; trade?: boolean; + active?: string; }) { const theme = useTheme(); const fiatOnRampButtonEnabled = true; const isMobile = useMediaQuery(theme.breakpoints.down('md')); + const [activeAction, setActiveAction] = useState<'swap' | 'buy'>('swap'); + const href = window.location.pathname; + useEffect(() => { + setActiveAction(href === '/swap' ? 'swap' : 'buy'); + }, [href]) + return ( - + Swap - {/* Swap */} - + {fiatOnRampButtonEnabled && ( - + Buy - + )} - + From 3eaf3c38f1208aab117ec8fad9ebfcbfaabed68f Mon Sep 17 00:00:00 2001 From: Matias Poblete Date: Tue, 4 Jun 2024 18:00:51 -0400 Subject: [PATCH 02/23] =?UTF-8?q?=F0=9F=9A=A7Display=20change=20active=20c?= =?UTF-8?q?hain=20menu=20in=20all=20wallets?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Layout/ProfileSection.tsx | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/components/Layout/ProfileSection.tsx b/src/components/Layout/ProfileSection.tsx index eed2d26b..2a390a7b 100644 --- a/src/components/Layout/ProfileSection.tsx +++ b/src/components/Layout/ProfileSection.tsx @@ -105,10 +105,9 @@ export const HeaderChip = ({ display: 'flex', flexDirection: 'row', height: isSmall ? 30 : 56, - padding: isSmall && canDisconnect ? '8px 1px 16px 1px' : isSmall ? '8px 16px' : '16px 24px', + width: isSmall ? 100 : 200, + paddingRight: '16px', justifyContent: 'center', - alignItems: 'center', - gap: 0.5, flexShrink: 0, borderRadius: isSmall ? '4px' : '16px', backgroundColor: '#8866DD', @@ -124,15 +123,15 @@ export const HeaderChip = ({ fontFamily: 'Inter', fontWeight: 600, lineHeight: '140%', - padding: 0 + padding: '0px' }, ':hover': { backgroundColor: '#8866DD', }, '.MuiChip-action-icon':{ - position:'relative', - top: isSmall ? '7px' :'5px', - left: '5px' + position: 'absolute', + marginLeft: '2px', + marginTop: '2px', } } return ( @@ -203,14 +202,15 @@ export const HeaderChip = ({ export const ActiveChainHeaderChip = ({ isMobile }: { isMobile?: boolean }) => { const sorobanContext: SorobanContextType = useSorobanReact(); const { activeChain, chains, activeConnector, address} = sorobanContext; - + console.log(activeChain) return ( <> - {activeChain && chains && activeConnector?.id == 'xbull' && address ? + {/* {activeChain && chains && activeConnector?.id == 'xbull' && address ? ]} isSmall={isMobile} chains={chains}/> - : + : - } + } */} + ]} isSmall={isMobile} chains={chains} /> ); }; From c75a114bb4e1d51fa992546f4ddd8cbb602ac2b3 Mon Sep 17 00:00:00 2001 From: Matias Poblete Date: Tue, 4 Jun 2024 18:04:20 -0400 Subject: [PATCH 03/23] =?UTF-8?q?=F0=9F=92=84Fix=20connect=20wallet=20butt?= =?UTF-8?q?on=20height?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Layout/ProfileSection.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Layout/ProfileSection.tsx b/src/components/Layout/ProfileSection.tsx index 2a390a7b..f10e2e80 100644 --- a/src/components/Layout/ProfileSection.tsx +++ b/src/components/Layout/ProfileSection.tsx @@ -239,7 +239,7 @@ export default function ProfileSection() { canDisconnect /> ):( - + ))} ); From 9e49ac7df1e9ef0defa2ef674534e6f420d397e5 Mon Sep 17 00:00:00 2001 From: Matias Poblete Date: Wed, 5 Jun 2024 14:44:10 -0400 Subject: [PATCH 04/23] =?UTF-8?q?=F0=9F=90=9BVisual=20bugs=20fixed?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Layout/Header.tsx | 2 +- src/components/Swap/SwapHeader.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/Layout/Header.tsx b/src/components/Layout/Header.tsx index 3de614c0..2bd50e4a 100644 --- a/src/components/Layout/Header.tsx +++ b/src/components/Layout/Header.tsx @@ -230,7 +230,7 @@ export default function Header({ isDrawerOpen, setDrawerOpen }: HeaderProps) { {item.label} diff --git a/src/components/Swap/SwapHeader.tsx b/src/components/Swap/SwapHeader.tsx index 3da8eac0..1e816555 100644 --- a/src/components/Swap/SwapHeader.tsx +++ b/src/components/Swap/SwapHeader.tsx @@ -47,7 +47,7 @@ export default function SwapHeader({ const [activeAction, setActiveAction] = useState<'swap' | 'buy'>('swap'); const href = window.location.pathname; useEffect(() => { - setActiveAction(href === '/swap' ? 'swap' : 'buy'); + setActiveAction(href === '/swap' || href === '/' ? 'swap' : 'buy'); }, [href]) From b0edb5e4da3f215a3cd1ed5184ce002b5564b4e7 Mon Sep 17 00:00:00 2001 From: Matias Poblete Date: Wed, 5 Jun 2024 17:26:42 -0400 Subject: [PATCH 05/23] =?UTF-8?q?=F0=9F=94=A5Removed=20unused=20console.lo?= =?UTF-8?q?g?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Layout/ProfileSection.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Layout/ProfileSection.tsx b/src/components/Layout/ProfileSection.tsx index f10e2e80..8eb221c9 100644 --- a/src/components/Layout/ProfileSection.tsx +++ b/src/components/Layout/ProfileSection.tsx @@ -202,7 +202,7 @@ export const HeaderChip = ({ export const ActiveChainHeaderChip = ({ isMobile }: { isMobile?: boolean }) => { const sorobanContext: SorobanContextType = useSorobanReact(); const { activeChain, chains, activeConnector, address} = sorobanContext; - console.log(activeChain) + return ( <> {/* {activeChain && chains && activeConnector?.id == 'xbull' && address ? From 6f19d55915daa779653ea9e461e1ad5df2723d6d Mon Sep 17 00:00:00 2001 From: Matias Poblete Date: Wed, 5 Jun 2024 17:27:33 -0400 Subject: [PATCH 06/23] =?UTF-8?q?=F0=9F=9A=A7Created=20buy=20crypto=20pane?= =?UTF-8?q?l?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Buy/BuyComponent.tsx | 8 +-- src/components/Buy/BuyCryptoPanel.tsx | 89 +++++++++++++++++++++++++++ 2 files changed, 92 insertions(+), 5 deletions(-) create mode 100644 src/components/Buy/BuyCryptoPanel.tsx diff --git a/src/components/Buy/BuyComponent.tsx b/src/components/Buy/BuyComponent.tsx index aa50a99f..70818cc9 100644 --- a/src/components/Buy/BuyComponent.tsx +++ b/src/components/Buy/BuyComponent.tsx @@ -25,6 +25,7 @@ import swapReducer, { SwapState, initialState as initialSwapState } from 'state/ import { opacify } from 'themes/utils'; import SwapHeader from 'components/Swap/SwapHeader'; import { SwapWrapper } from 'components/Swap/styleds'; +import DepositFiatInputPanel from './BuyCryptoPanel'; export const SwapSection = styled('div')(({ theme }) => ({ position: 'relative', @@ -218,11 +219,8 @@ export function BuyComponent({ <> - - { - console.log('button clicked!') - alert('button clicked!') - }}>Buy USDC + + diff --git a/src/components/Buy/BuyCryptoPanel.tsx b/src/components/Buy/BuyCryptoPanel.tsx new file mode 100644 index 00000000..deff7703 --- /dev/null +++ b/src/components/Buy/BuyCryptoPanel.tsx @@ -0,0 +1,89 @@ +import { useState, useEffect } from 'react' +import { FormControl, InputLabel, Select, MenuItem, Container } from '@mui/material' +import { ButtonPrimary } from 'components/Buttons/Button'; +import { borderRadius } from 'polished'; + +interface Options { + name: string; + value: string; +} +interface InputPanelProps { + header: string; + options: Options[]; + selected: Options; + setSelected: (value: Options) => void; +} +function InputPanel(props: InputPanelProps) { + const { header, options, selected, setSelected } = props; + const inputContainerStyle = { + mt: 2, + mb: 4, + p: 0, + backgroundColor: '#13141E', + borderRadius: '12px', + height: '10vh', + alignContent: 'center' + } + const inputSelectStyle = { + borderRadius: '32px', + backgroundColor: '#98A1C014', + color: 'white', + fontSize: '1.5rem', + '& .MuiSelect-icon': { + color: 'white' + } + } + return ( + <> + + + + + + + ) +} + +function DepositFiatInputPanel() { + const anchorsArray = [ + { name: 'Stellar test', value: 'https://testanchor.stellar.org/.well-known/stellar.toml' }, + { name: 'MoneyGram', value: 'https://stellar.moneygram.com/.well-known/stellar.toml' }, + { name: 'MyKobo', value: 'https://mykobo.co/.well-known/stellar.toml' }, + ]; + const fiatArray = [ + { name: 'USDC', value: 'USDC' }, + { name: 'EURC', value: 'EURC' }, + { name: 'ARS', value: 'ARS' }, + { name: 'XLM', value: 'XLM' }, + { name: 'CNY', value: 'CNY' } + ]; + const [selectedFiat, setSelectedFiat] = useState(fiatArray[0]) + const [selectedAnchor, setSelectedAnchor] = useState(anchorsArray[0]); + + useEffect(() => { + console.log('Deposit', [selectedFiat.value, selectedAnchor.value]) + }, [selectedFiat, selectedAnchor]) + + const [modalOpen, setModalOpen] = useState(false); + + return ( + <> + + + + { + console.log('button clicked!') + alert('button clicked!') + }}>Buy USDC + + ) +} + +export default DepositFiatInputPanel \ No newline at end of file From bd94a84b4fd94d7059b86fbcf6f30b7459ab518e Mon Sep 17 00:00:00 2001 From: Matias Poblete Date: Thu, 6 Jun 2024 09:46:01 -0400 Subject: [PATCH 07/23] =?UTF-8?q?=E2=9C=A8Add=20on-ramp=20helper=20functio?= =?UTF-8?q?ns?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/functions/buy/SEP-1.ts | 33 ++++ src/functions/buy/sep10Auth/stellarAuth.ts | 118 ++++++++++++++ .../buy/sep24Deposit/InteractiveDeposit.ts | 81 ++++++++++ src/functions/buy/sep24Deposit/checkInfo.ts | 34 ++++ .../sep24Deposit/pollDepositUntilComplete.ts | 153 ++++++++++++++++++ src/interfaces/tomlFields.ts | 16 ++ 6 files changed, 435 insertions(+) create mode 100644 src/functions/buy/SEP-1.ts create mode 100644 src/functions/buy/sep10Auth/stellarAuth.ts create mode 100644 src/functions/buy/sep24Deposit/InteractiveDeposit.ts create mode 100644 src/functions/buy/sep24Deposit/checkInfo.ts create mode 100644 src/functions/buy/sep24Deposit/pollDepositUntilComplete.ts create mode 100644 src/interfaces/tomlFields.ts diff --git a/src/functions/buy/SEP-1.ts b/src/functions/buy/SEP-1.ts new file mode 100644 index 00000000..790090c4 --- /dev/null +++ b/src/functions/buy/SEP-1.ts @@ -0,0 +1,33 @@ +import { TomlFields } from '../../interfaces/tomlFields'; +import axios from 'axios'; +import toml from 'toml'; + +export enum Anchors { + TEST = 'https://testanchor.stellar.org', +} +export async function getStellarToml(home_domain: string) { + const formattedDomain = home_domain.replace(/^https?:\/\//, ''); + const tomlResponse = await axios.get(`https://${formattedDomain}/.well-known/stellar.toml`); + const parsedResponse = toml.parse(tomlResponse.data) + return parsedResponse; +} + +export async function getAuthUrl(home_domain: string) { + const toml = await getStellarToml(home_domain); + return toml[TomlFields.WEB_AUTH_ENDPOINT]; +} + +export async function getKycUrl(home_domain: string) { + const toml = await getStellarToml(home_domain); + return toml[TomlFields.WEB_AUTH_ENDPOINT]; +} + +export async function getTransferServerUrl(home_domain: string) { + const toml = await getStellarToml(home_domain); + return toml[TomlFields.TRANSFER_SERVER_SEP0024]; +} + +export async function getDepositServerUrl(home_domain: string) { + const toml = await getStellarToml(home_domain); + return toml[TomlFields.TRANSFER_SERVER_SEP0024]; +} \ No newline at end of file diff --git a/src/functions/buy/sep10Auth/stellarAuth.ts b/src/functions/buy/sep10Auth/stellarAuth.ts new file mode 100644 index 00000000..dad5dce9 --- /dev/null +++ b/src/functions/buy/sep10Auth/stellarAuth.ts @@ -0,0 +1,118 @@ +import { Operation, WebAuth, xdr } from "@stellar/stellar-sdk"; +import { getStellarToml, getAuthUrl } from "../SEP-1"; + +//TODO: Add memo to operation +export async function getChallengeTransaction({ + publicKey, + homeDomain +}:{ + publicKey: string, + homeDomain: string +}): Promise<{ + transaction:any, + network_passphrase:string +}>{ + let { WEB_AUTH_ENDPOINT, TRANSFER_SERVER, SIGNING_KEY } = await getStellarToml(homeDomain) + + // In order for the SEP-10 flow to work, we must have at least a server + // signing key, and a web auth endpoint (which can be the transfer server as + // a fallback) + if (!(WEB_AUTH_ENDPOINT || TRANSFER_SERVER) || !SIGNING_KEY) { + console.error(500, { + message: 'could not get challenge transaction (server missing toml entry or entries)', + }) + } + + // Request a challenge transaction for the users's account + let res = await fetch( + `${WEB_AUTH_ENDPOINT || TRANSFER_SERVER}?${new URLSearchParams({ + // Possible parameters are `account`, `memo`, `home_domain`, and + // `client_domain`. For our purposes, we only supply `account`. + account: publicKey, + })}` + ).catch((e)=>{ + console.log(e) + }) + console.log(res) + let json = await res?.json() + // Validate the challenge transaction meets all the requirements for SEP-10 + validateChallengeTransaction({ + transactionXDR: json.transaction, + serverSigningKey: SIGNING_KEY, + network: json.network_passphrase, + clientPublicKey: publicKey, + homeDomain: homeDomain, + }) + return json +} + +//TODO: Fix Err400 { message: '{"name":"InvalidChallengeError"}' +// decode transaction & validate the values +function validateChallengeTransaction({ + transactionXDR, + serverSigningKey, + network, + clientPublicKey, + homeDomain, + clientDomain, +}: { + transactionXDR: any, + serverSigningKey: string, + network: string, + clientPublicKey: any, + homeDomain: string, + clientDomain?: string, +}) { + if (!clientDomain) { + clientDomain = homeDomain + } + + try { + // Use the `readChallengeTx` function from Stellar SDK to read and + // verify most of the challenge transaction information + let results = WebAuth.readChallengeTx( + transactionXDR, + serverSigningKey, + network, + homeDomain, + clientDomain + ) + + // Also make sure the transaction was created for the correct user + if (results.clientAccountID === clientPublicKey) { + return + } else { + console.error(400, { message: 'clientAccountID does not match challenge transaction' }) + } + } catch (err) { + console.error(400, { message: JSON.stringify(err) }) + } +} + +//TODO: Implement token validations https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0010.md#token +export async function submitChallengeTransaction({ + transactionXDR, + homeDomain +}: { + transactionXDR: string | undefined, + homeDomain: string +}) { + if (!transactionXDR || transactionXDR === undefined){ + console.error('invalid transaction xdr') + } + let webAuthEndpoint = await getAuthUrl(homeDomain) + if (!webAuthEndpoint) + console.error(500, { message: 'could not authenticate with server (missing toml entry)' }) + let res = await fetch(webAuthEndpoint, { + method: 'POST', + mode: 'cors', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ transaction: transactionXDR }), + }) + let json = await res.json() + + if (!res.ok) { + console.error(400, { message: json.error }) + } + return json.token +} \ No newline at end of file diff --git a/src/functions/buy/sep24Deposit/InteractiveDeposit.ts b/src/functions/buy/sep24Deposit/InteractiveDeposit.ts new file mode 100644 index 00000000..d1254aeb --- /dev/null +++ b/src/functions/buy/sep24Deposit/InteractiveDeposit.ts @@ -0,0 +1,81 @@ +import { getTransferServerUrl } from "../SEP-1"; + +export async function checkInfo(homeDomain : string) { + let transferServerSep24 = await getTransferServerUrl(homeDomain) + + let res = await fetch(`${transferServerSep24}/info`) + let json = await res.json() + + if (!res.ok) { + console.error(res.status, { + message: json.error, + }) + } else { + return json + } +} + +export async function initInteractiveDepositFlow({ + authToken, + homeDomain, + urlFields = {} +}:{ + authToken: string, + homeDomain: string, + urlFields?: object +}) { + let transferServerSep24 = await getTransferServerUrl(homeDomain) + console.log(JSON.stringify(urlFields)) + let res = await fetch(`${transferServerSep24}/transactions/deposit/interactive`, { + method: 'POST', + mode: 'cors', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${authToken}`, + }, + body: JSON.stringify(urlFields), + }) + let json = await res.json() + + if (!res.ok) { + console.error(res.status, { + message: json.error, + }) + } else { + return json + } +} + +export async function queryTransfers24({ + authToken, + assetCode, + homeDomain +} : { + authToken: string, + assetCode: string, + homeDomain: string +}) { + let transferServerSep24 = await getTransferServerUrl(homeDomain) + + let res = await fetch( + `${transferServerSep24}/transactions?${new URLSearchParams({ + asset_code: assetCode, + })}`, + { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${authToken}`, + }, + } + ) + let json = await res.json() + + if (!res.ok) { + console.error(res.status, { + message: json.error, + }) + } else { + return json + } +} \ No newline at end of file diff --git a/src/functions/buy/sep24Deposit/checkInfo.ts b/src/functions/buy/sep24Deposit/checkInfo.ts new file mode 100644 index 00000000..d0737488 --- /dev/null +++ b/src/functions/buy/sep24Deposit/checkInfo.ts @@ -0,0 +1,34 @@ +import { get } from "lodash"; +import { log } from "../../helpers/log"; +import { isNativeAsset } from "../../helpers/isNativeAsset"; +import { AnchorActionType } from "../../types/types"; + +export const checkInfo = async ({ + type, + toml, + assetCode, +}: { + type: AnchorActionType; + toml: any; + assetCode: string; +}) => { + log.instruction({ + title: `Checking \`/info\` endpoint to ensure this currency is enabled for ${ + type === AnchorActionType.DEPOSIT ? "deposit" : "withdrawal" + }`, + }); + const infoURL = `${toml.TRANSFER_SERVER_SEP0024}/info`; + log.request({ title: `GET \`${infoURL}\`` }); + + const info = await fetch(infoURL); + const infoJson = await info.json(); + const isNative = isNativeAsset(assetCode); + + log.response({ title: `GET \`${infoURL}\``, body: infoJson }); + + if (!get(infoJson, [type, isNative ? "native" : assetCode, "enabled"])) { + throw new Error("Asset is not enabled in the `/info` endpoint"); + } + + return infoJson; +}; diff --git a/src/functions/buy/sep24Deposit/pollDepositUntilComplete.ts b/src/functions/buy/sep24Deposit/pollDepositUntilComplete.ts new file mode 100644 index 00000000..48cfa697 --- /dev/null +++ b/src/functions/buy/sep24Deposit/pollDepositUntilComplete.ts @@ -0,0 +1,153 @@ +import { getErrorMessage } from "../../helpers/getErrorMessage"; +import { log } from "../../helpers/log"; +import { TransactionStatus } from "../../types/types"; + +export const pollDepositUntilComplete = async ({ + popup, + transactionId, + token, + sep24TransferServerUrl, + trustAssetCallback, + custodialMemoId, +}: { + popup: any; + transactionId: string; + token: string; + sep24TransferServerUrl: string; + trustAssetCallback: () => Promise; + custodialMemoId?: string; +}) => { + let currentStatus = TransactionStatus.INCOMPLETE; + let trustedAssetAdded; + + const transactionUrl = new URL( + `${sep24TransferServerUrl}/transaction?id=${transactionId}`, + ); + log.instruction({ + title: `Polling for updates \`${transactionUrl.toString()}\``, + }); + + const endStatuses = [ + TransactionStatus.PENDING_EXTERNAL, + TransactionStatus.COMPLETED, + TransactionStatus.ERROR, + ]; + + const initResponse = await fetch(transactionUrl.toString(), { + headers: { Authorization: `Bearer ${token}` }, + }); + + const initTransactionJson = await initResponse.json(); + + if (initTransactionJson?.transaction?.more_info_url) { + log.instruction({ + title: "Transaction MORE INFO URL:", + link: initTransactionJson.transaction.more_info_url, + }); + } + + while (!popup.closed && !endStatuses.includes(currentStatus)) { + // eslint-disable-next-line no-await-in-loop + const response = await fetch(transactionUrl.toString(), { + headers: { Authorization: `Bearer ${token}` }, + }); + + // eslint-disable-next-line no-await-in-loop + const transactionJson = await response.json(); + + if (transactionJson.transaction.status !== currentStatus) { + currentStatus = transactionJson.transaction.status; + + log.instruction({ + title: "Transaction MORE INFO URL:", + link: initTransactionJson.transaction.more_info_url, + }); + + log.response({ + title: `Transaction \`${transactionId}\` is in \`${transactionJson.transaction.status}\` status.`, + body: transactionJson.transaction, + }); + + switch (currentStatus) { + case TransactionStatus.PENDING_USER_TRANSFER_START: { + if ( + custodialMemoId && + transactionJson.transaction.deposit_memo !== custodialMemoId + ) { + log.warning({ + title: "SEP-24 deposit custodial memo doesn’t match", + body: `Expected ${custodialMemoId}, got ${transactionJson.transaction.deposit_memo}`, + }); + } + + log.instruction({ + title: + "The anchor is waiting on you to take the action described in the popup", + }); + break; + } + case TransactionStatus.PENDING_ANCHOR: { + log.instruction({ + title: "The anchor is processing the transaction", + }); + break; + } + case TransactionStatus.PENDING_STELLAR: { + log.instruction({ + title: "The Stellar network is processing the transaction", + }); + break; + } + case TransactionStatus.PENDING_EXTERNAL: { + log.instruction({ + title: "The transaction is being processed by an external system", + }); + break; + } + case TransactionStatus.PENDING_TRUST: { + log.instruction({ + title: + "You must add a trustline to the asset in order to receive your deposit", + }); + + try { + // eslint-disable-next-line no-await-in-loop + trustedAssetAdded = await trustAssetCallback(); + } catch (error) { + throw new Error(getErrorMessage(error)); + } + break; + } + case TransactionStatus.PENDING_USER: { + log.instruction({ + title: + "The anchor is waiting for you to take the action described in the popup", + }); + break; + } + case TransactionStatus.ERROR: { + log.instruction({ + title: "There was a problem processing your transaction", + }); + break; + } + default: + // do nothing + } + } + + // run loop every 2 seconds + // eslint-disable-next-line no-await-in-loop + await new Promise((resolve) => setTimeout(resolve, 2000)); + } + + log.instruction({ title: `Transaction status \`${currentStatus}\`` }); + + if (!endStatuses.includes(currentStatus) && popup.closed) { + log.instruction({ + title: `The popup was closed before the transaction reached a terminal status, if your balance is not updated soon, the transaction may have failed.`, + }); + } + + return { currentStatus, trustedAssetAdded }; +}; diff --git a/src/interfaces/tomlFields.ts b/src/interfaces/tomlFields.ts new file mode 100644 index 00000000..77bb7296 --- /dev/null +++ b/src/interfaces/tomlFields.ts @@ -0,0 +1,16 @@ +export enum TomlFields { + ACCOUNTS = "ACCOUNTS", + ANCHOR_QUOTE_SERVER = "ANCHOR_QUOTE_SERVER", + AUTH_SERVER = "AUTH_SERVER", + DIRECT_PAYMENT_SERVER = "DIRECT_PAYMENT_SERVER", + FEDERATION_SERVER = "FEDERATION_SERVER", + HORIZON_URL = "HORIZON_URL", + KYC_SERVER = "KYC_SERVER", + NETWORK_PASSPHRASE = "NETWORK_PASSPHRASE", + SIGNING_KEY = "SIGNING_KEY", + TRANSFER_SERVER = "TRANSFER_SERVER", + TRANSFER_SERVER_SEP0024 = "TRANSFER_SERVER_SEP0024", + URI_REQUEST_SIGNING_KEY = "URI_REQUEST_SIGNING_KEY", + VERSION = "VERSION", + WEB_AUTH_ENDPOINT = "WEB_AUTH_ENDPOINT", +} \ No newline at end of file From 249c0d2691e28a7b62b97355b5abfd3064ecc16b Mon Sep 17 00:00:00 2001 From: Matias Poblete Date: Thu, 6 Jun 2024 09:48:26 -0400 Subject: [PATCH 08/23] =?UTF-8?q?=F0=9F=9A=A7Add=20on-ramp=20dev=20functio?= =?UTF-8?q?n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Buy/BuyCryptoPanel.tsx | 87 ++++++++++++++++++++++++--- 1 file changed, 79 insertions(+), 8 deletions(-) diff --git a/src/components/Buy/BuyCryptoPanel.tsx b/src/components/Buy/BuyCryptoPanel.tsx index deff7703..351d2312 100644 --- a/src/components/Buy/BuyCryptoPanel.tsx +++ b/src/components/Buy/BuyCryptoPanel.tsx @@ -1,7 +1,10 @@ import { useState, useEffect } from 'react' import { FormControl, InputLabel, Select, MenuItem, Container } from '@mui/material' import { ButtonPrimary } from 'components/Buttons/Button'; -import { borderRadius } from 'polished'; +import { getChallengeTransaction, submitChallengeTransaction } from 'functions/buy/sep10Auth/stellarAuth'; +import { useSorobanReact } from '@soroban-react/core'; +import { initInteractiveDepositFlow } from 'functions/buy/sep24Deposit/InteractiveDeposit'; + interface Options { name: string; @@ -31,6 +34,9 @@ function InputPanel(props: InputPanelProps) { fontSize: '1.5rem', '& .MuiSelect-icon': { color: 'white' + }, + '& .MuiSelect-select': { + color: 'white', } } return ( @@ -53,9 +59,9 @@ function InputPanel(props: InputPanelProps) { function DepositFiatInputPanel() { const anchorsArray = [ - { name: 'Stellar test', value: 'https://testanchor.stellar.org/.well-known/stellar.toml' }, - { name: 'MoneyGram', value: 'https://stellar.moneygram.com/.well-known/stellar.toml' }, - { name: 'MyKobo', value: 'https://mykobo.co/.well-known/stellar.toml' }, + { name: 'Stellar test', value: 'https://testanchor.stellar.org' }, + { name: 'MoneyGram', value: 'https://stellar.moneygram.com' }, + { name: 'MyKobo', value: 'https://mykobo.co' }, ]; const fiatArray = [ { name: 'USDC', value: 'USDC' }, @@ -66,6 +72,74 @@ function DepositFiatInputPanel() { ]; const [selectedFiat, setSelectedFiat] = useState(fiatArray[0]) const [selectedAnchor, setSelectedAnchor] = useState(anchorsArray[0]); + const sorobanContext = useSorobanReact() + const { activeChain, address } = useSorobanReact() + + const sign = async (txn: any) => { + const signedTransaction = await sorobanContext?.activeConnector?.signTransaction(txn, { + networkPassphrase: activeChain?.networkPassphrase, + network: activeChain?.id, + accountToSign: address + }) + return signedTransaction; + } + + const dev = async (homeDomain: string) => { + //#Auth flow + + //First, we define the anchor home domain + //const homeDomain = 'https://testanchor.stellar.org' + console.log(homeDomain) + //Then, we get the challenge transaction, giving as input the user address to sign and the home domain of the anchor + const { transaction, network_passphrase } = await getChallengeTransaction({ + publicKey: address! && address, + homeDomain: homeDomain + }) + //Once recived the Challenge transaction we sign it with our wallet + const signedTransaction = await sign(transaction) + + //And submit the signed Challenge transaction to get the JWT + const submittedTransaction = await submitChallengeTransaction({ + transactionXDR: signedTransaction, + homeDomain: homeDomain + }) + + //#Interactive Deposit flow + //We get the url of the interactive deposit flow, giving as input the JWT (Obtained from the authentication flow), the home domain of the anchor and the + //asset info from the asset we expect to recieve + const { url } = await initInteractiveDepositFlow({ + authToken: submittedTransaction, + homeDomain: homeDomain, + urlFields: { + asset_code: 'SRT', + asset_issuer: 'GCDNJUBQSX7AJWLJACMJ7I4BC3Z47BQUTMHEICZLE6MU4KQBRYG5JY6B' + } + }) + + //once we got the url we open the popup with a callback parameter to get the transaction status + const interactiveUrl = `${url}&callback=postMessage` + let popup = window.open(interactiveUrl, 'interactiveDeposit', 'width=450,height=750') + + if (!popup) { + alert( + "Popups are blocked. You’ll need to enable popups for this demo to work", + ); + console.error( + "Popups are blocked. You’ll need to enable popups for this demo to work", + ) + } + + popup?.focus() + + window.addEventListener('message', (event) => { + if (event.origin === homeDomain) { + console.log(event.data) + const transaction = event.data.transaction + if (transaction.status == 'complete') + popup?.close() + } + }) + } useEffect(() => { console.log('Deposit', [selectedFiat.value, selectedAnchor.value]) @@ -78,10 +152,7 @@ function DepositFiatInputPanel() { - { - console.log('button clicked!') - alert('button clicked!') - }}>Buy USDC + { dev(selectedAnchor.value) }}>Buy USDC ) } From 2fc89f905c1e1550f12df599d3340605c6b67d1d Mon Sep 17 00:00:00 2001 From: Matias Poblete Date: Fri, 7 Jun 2024 12:32:23 -0400 Subject: [PATCH 09/23] =?UTF-8?q?=F0=9F=9A=A7Add=20types=20&=20fix=20build?= =?UTF-8?q?=20issues?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 1 + src/functions/buy/SEP-1.ts | 2 +- src/functions/buy/sep10Auth/stellarAuth.ts | 7 +- src/functions/buy/sep24Deposit/checkInfo.ts | 14 +- .../sep24Deposit/pollDepositUntilComplete.ts | 38 +- src/functions/buy/types.ts | 468 ++++++++++++++++++ src/interfaces/tomlFields.ts | 16 - 7 files changed, 504 insertions(+), 42 deletions(-) create mode 100644 src/functions/buy/types.ts delete mode 100644 src/interfaces/tomlFields.ts diff --git a/package.json b/package.json index 7ca31699..ffb9939e 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "@soroban-react/wallet-data": "9.1.4", "@soroban-react/xbull": "9.1.2", "@stellar/freighter-api": "1.7.1", + "@stellar/frontend-helpers": "^2.1.4", "@stellar/stellar-sdk": "11.3.0", "@types/qs": "^6.9.7", "@types/react": "18.2.33", diff --git a/src/functions/buy/SEP-1.ts b/src/functions/buy/SEP-1.ts index 790090c4..e549305e 100644 --- a/src/functions/buy/SEP-1.ts +++ b/src/functions/buy/SEP-1.ts @@ -1,4 +1,4 @@ -import { TomlFields } from '../../interfaces/tomlFields'; +import { TomlFields } from './types'; import axios from 'axios'; import toml from 'toml'; diff --git a/src/functions/buy/sep10Auth/stellarAuth.ts b/src/functions/buy/sep10Auth/stellarAuth.ts index dad5dce9..7af58c79 100644 --- a/src/functions/buy/sep10Auth/stellarAuth.ts +++ b/src/functions/buy/sep10Auth/stellarAuth.ts @@ -4,10 +4,12 @@ import { getStellarToml, getAuthUrl } from "../SEP-1"; //TODO: Add memo to operation export async function getChallengeTransaction({ publicKey, - homeDomain + homeDomain, + clientDomain }:{ publicKey: string, - homeDomain: string + homeDomain: string, + clientDomain?: string, }): Promise<{ transaction:any, network_passphrase:string @@ -29,6 +31,7 @@ export async function getChallengeTransaction({ // Possible parameters are `account`, `memo`, `home_domain`, and // `client_domain`. For our purposes, we only supply `account`. account: publicKey, + client_domain: clientDomain!, })}` ).catch((e)=>{ console.log(e) diff --git a/src/functions/buy/sep24Deposit/checkInfo.ts b/src/functions/buy/sep24Deposit/checkInfo.ts index d0737488..3aa7b9f6 100644 --- a/src/functions/buy/sep24Deposit/checkInfo.ts +++ b/src/functions/buy/sep24Deposit/checkInfo.ts @@ -1,7 +1,9 @@ import { get } from "lodash"; -import { log } from "../../helpers/log"; -import { isNativeAsset } from "../../helpers/isNativeAsset"; -import { AnchorActionType } from "../../types/types"; + +import { AnchorActionType } from "../types" + const isNativeAsset = (assetCode: string) => { + return ["XLM", "NATIVE"].includes(assetCode.toLocaleUpperCase()); +} export const checkInfo = async ({ type, @@ -12,19 +14,19 @@ export const checkInfo = async ({ toml: any; assetCode: string; }) => { - log.instruction({ + console.log({ title: `Checking \`/info\` endpoint to ensure this currency is enabled for ${ type === AnchorActionType.DEPOSIT ? "deposit" : "withdrawal" }`, }); const infoURL = `${toml.TRANSFER_SERVER_SEP0024}/info`; - log.request({ title: `GET \`${infoURL}\`` }); + console.log({ title: `GET \`${infoURL}\`` }); const info = await fetch(infoURL); const infoJson = await info.json(); const isNative = isNativeAsset(assetCode); - log.response({ title: `GET \`${infoURL}\``, body: infoJson }); + console.log({ title: `GET \`${infoURL}\``, body: infoJson }); if (!get(infoJson, [type, isNative ? "native" : assetCode, "enabled"])) { throw new Error("Asset is not enabled in the `/info` endpoint"); diff --git a/src/functions/buy/sep24Deposit/pollDepositUntilComplete.ts b/src/functions/buy/sep24Deposit/pollDepositUntilComplete.ts index 48cfa697..d58c224d 100644 --- a/src/functions/buy/sep24Deposit/pollDepositUntilComplete.ts +++ b/src/functions/buy/sep24Deposit/pollDepositUntilComplete.ts @@ -1,6 +1,10 @@ -import { getErrorMessage } from "../../helpers/getErrorMessage"; -import { log } from "../../helpers/log"; -import { TransactionStatus } from "../../types/types"; +import { TransactionStatus } from "../types"; +import { getCatchError } from "@stellar/frontend-helpers"; + +export const getErrorMessage = (error: Error | unknown) => { + const e = getCatchError(error); + return e.message || e.toString(); +}; export const pollDepositUntilComplete = async ({ popup, @@ -23,7 +27,7 @@ export const pollDepositUntilComplete = async ({ const transactionUrl = new URL( `${sep24TransferServerUrl}/transaction?id=${transactionId}`, ); - log.instruction({ + console.log({ title: `Polling for updates \`${transactionUrl.toString()}\``, }); @@ -40,7 +44,7 @@ export const pollDepositUntilComplete = async ({ const initTransactionJson = await initResponse.json(); if (initTransactionJson?.transaction?.more_info_url) { - log.instruction({ + console.log({ title: "Transaction MORE INFO URL:", link: initTransactionJson.transaction.more_info_url, }); @@ -58,12 +62,12 @@ export const pollDepositUntilComplete = async ({ if (transactionJson.transaction.status !== currentStatus) { currentStatus = transactionJson.transaction.status; - log.instruction({ + console.log({ title: "Transaction MORE INFO URL:", link: initTransactionJson.transaction.more_info_url, }); - log.response({ + console.log({ title: `Transaction \`${transactionId}\` is in \`${transactionJson.transaction.status}\` status.`, body: transactionJson.transaction, }); @@ -74,38 +78,38 @@ export const pollDepositUntilComplete = async ({ custodialMemoId && transactionJson.transaction.deposit_memo !== custodialMemoId ) { - log.warning({ + console.log({ title: "SEP-24 deposit custodial memo doesn’t match", body: `Expected ${custodialMemoId}, got ${transactionJson.transaction.deposit_memo}`, }); } - log.instruction({ + console.log({ title: "The anchor is waiting on you to take the action described in the popup", }); break; } case TransactionStatus.PENDING_ANCHOR: { - log.instruction({ + console.log({ title: "The anchor is processing the transaction", }); break; } case TransactionStatus.PENDING_STELLAR: { - log.instruction({ + console.log({ title: "The Stellar network is processing the transaction", }); break; } case TransactionStatus.PENDING_EXTERNAL: { - log.instruction({ + console.log({ title: "The transaction is being processed by an external system", }); break; } case TransactionStatus.PENDING_TRUST: { - log.instruction({ + console.log({ title: "You must add a trustline to the asset in order to receive your deposit", }); @@ -119,14 +123,14 @@ export const pollDepositUntilComplete = async ({ break; } case TransactionStatus.PENDING_USER: { - log.instruction({ + console.log({ title: "The anchor is waiting for you to take the action described in the popup", }); break; } case TransactionStatus.ERROR: { - log.instruction({ + console.log({ title: "There was a problem processing your transaction", }); break; @@ -141,10 +145,10 @@ export const pollDepositUntilComplete = async ({ await new Promise((resolve) => setTimeout(resolve, 2000)); } - log.instruction({ title: `Transaction status \`${currentStatus}\`` }); + console.log({ title: `Transaction status \`${currentStatus}\`` }); if (!endStatuses.includes(currentStatus) && popup.closed) { - log.instruction({ + console.log({ title: `The popup was closed before the transaction reached a terminal status, if your balance is not updated soon, the transaction may have failed.`, }); } diff --git a/src/functions/buy/types.ts b/src/functions/buy/types.ts new file mode 100644 index 00000000..bd932dc3 --- /dev/null +++ b/src/functions/buy/types.ts @@ -0,0 +1,468 @@ +import React, { ReactNode } from "react"; +import { Horizon } from "@stellar/stellar-sdk"; +import BigNumber from "bignumber.js"; + +declare global { + interface Window { + _env_: { + AMPLITUDE_API_KEY: string; + SENTRY_API_KEY: string; + HORIZON_PASSPHRASE?: string; + HORIZON_URL?: string; + WALLET_BACKEND_ENDPOINT?: string; + CLIENT_DOMAIN?: string; + }; + } +} + +export const XLM_NATIVE_ASSET = "XLM:native"; + +export enum SearchParams { + SECRET_KEY = "secretKey", + UNTRUSTED_ASSETS = "untrustedAssets", + ASSET_OVERRIDES = "assetOverrides", + CLAIMABLE_BALANCE_SUPPORTED = "claimableBalanceSupported", +} + +export enum AssetCategory { + TRUSTED = "trusted", + UNTRUSTED = "untrusted", +} + +export enum TomlFields { + ACCOUNTS = "ACCOUNTS", + ANCHOR_QUOTE_SERVER = "ANCHOR_QUOTE_SERVER", + AUTH_SERVER = "AUTH_SERVER", + DIRECT_PAYMENT_SERVER = "DIRECT_PAYMENT_SERVER", + FEDERATION_SERVER = "FEDERATION_SERVER", + HORIZON_URL = "HORIZON_URL", + KYC_SERVER = "KYC_SERVER", + NETWORK_PASSPHRASE = "NETWORK_PASSPHRASE", + SIGNING_KEY = "SIGNING_KEY", + TRANSFER_SERVER = "TRANSFER_SERVER", + TRANSFER_SERVER_SEP0024 = "TRANSFER_SERVER_SEP0024", + URI_REQUEST_SIGNING_KEY = "URI_REQUEST_SIGNING_KEY", + VERSION = "VERSION", + WEB_AUTH_ENDPOINT = "WEB_AUTH_ENDPOINT", +} + +export interface PresetAsset { + assetCode: string; + homeDomain?: string; + issuerPublicKey?: string; +} + +export interface Asset { + assetString: string; + assetCode: string; + assetIssuer: string; + assetType: string; + total: string; + homeDomain?: string; + supportedActions?: AssetSupportedActions; + isUntrusted?: boolean; + isOverride?: boolean; + isClaimableBalance?: boolean; + notExist?: boolean; + source: any; + category?: AssetCategory; +} + +export interface SearchParamAsset { + assetString: string; + homeDomain?: string; +} + +export interface AssetSupportedActions { + sep6?: boolean; + sep8?: boolean; + sep24?: boolean; + sep31?: boolean; +} + +export interface AccountInitialState { + data: AccountDetails | null; + assets: Asset[]; + errorString?: string; + isAuthenticated: boolean; + isUnfunded: boolean; + secretKey: string; + status: ActionStatus | undefined; +} + +export interface ActiveAssetInitialState { + action: ActiveAssetAction | undefined; + status: ActionStatus | undefined; +} + +export interface AllAssetsInitialState { + data: Asset[]; + errorString?: string; + status: ActionStatus | undefined; +} + +export interface AssetOverridesInitialState { + data: Asset[]; + errorString?: string; + status: ActionStatus | undefined; +} + +export interface ClaimAssetInitialState { + data: { + result: any; + trustedAssetAdded?: string; + }; + errorString?: string; + status: ActionStatus | undefined; +} + +export interface ClaimableBalancesInitialState { + data: { + records: ClaimableAsset[] | null; + }; + errorString?: string; + status: ActionStatus | undefined; +} + +export interface Sep24DepositAssetInitialState { + data: { + currentStatus: string; + trustedAssetAdded?: string; + }; + errorString?: string; + status: ActionStatus | undefined; +} + +export interface LogsInitialState { + items: LogItemProps[]; + errorString?: string; + status: ActionStatus | undefined; +} + +export interface SendPaymentInitialState { + data: Horizon.HorizonApi.SubmitTransactionResponse | null; + errorString?: string; + status: ActionStatus | undefined; +} + +export interface SettingsInitialState { + assetOverrides: string; + secretKey: string; + untrustedAssets: string; + claimableBalanceSupported: boolean; +} + +export interface UntrustedAssetsInitialState { + data: Asset[]; + errorString?: string; + status: ActionStatus | undefined; +} + +export interface AnyObject { + [key: string]: any; +} + +export interface AssetsObject { + [key: string]: Asset; +} + +export interface StringObject { + [key: string]: string; +} + +export interface NestedStringObject { + [key: string]: { + [key: string]: string; + }; +} + +export interface CustomerTypeItem { + type: string; + description: string; +} + +export interface Setting { + [key: string]: any; +} + +export interface TrustAssetInitialState { + assetString: string; + data: any; + errorString?: string; + status: ActionStatus | undefined; +} + +export interface Sep24WithdrawAssetInitialState { + data: { + currentStatus: string; + }; + errorString?: string; + status: ActionStatus | undefined; +} + +export interface TrustAssetParam { + assetString: string; + assetCode: string; + assetIssuer: string; +} + +export enum LogType { + REQUEST = "request", + RESPONSE = "response", + INSTRUCTION = "instruction", + ERROR = "error", + WARNING = "warning", +} + +export interface LogItemProps { + timestamp: number; + type: LogType; + title: string; + body?: string | AnyObject; + link?: string; +} + +export interface Store { + account: AccountInitialState; + activeAsset: ActiveAssetInitialState; + allAssets: AllAssetsInitialState; + assetOverrides: AssetOverridesInitialState; + claimAsset: ClaimAssetInitialState; + claimableBalances: ClaimableBalancesInitialState; + logs: LogsInitialState; + sendPayment: SendPaymentInitialState; + sep24DepositAsset: Sep24DepositAssetInitialState; + sep24WithdrawAsset: Sep24WithdrawAssetInitialState; + settings: SettingsInitialState; + trustAsset: TrustAssetInitialState; + untrustedAssets: UntrustedAssetsInitialState; +} + +export type StoreKey = keyof Store; + +export enum ActionStatus { + ERROR = "ERROR", + PENDING = "PENDING", + SUCCESS = "SUCCESS", + NEEDS_INPUT = "NEEDS_INPUT", + CAN_PROCEED = "CAN_PROCEED", +} + +export interface RejectMessage { + errorString: string; +} + +export interface PaymentTransactionParams { + amount: string; + assetCode?: string; + assetIssuer?: string; + destination: string; + isDestinationFunded: boolean; + publicKey: string; +} + +export interface ClaimableAsset extends Asset { + id: string; + sponsor: string; + lastModifiedLedger: number; + claimants: any[]; +} + +export interface ActiveAssetAction { + assetString: string; + title: string; + description?: string | React.ReactNode; + callback: (args?: any) => void; + options?: ReactNode; +} + +export interface AssetActionItem extends ActiveAssetAction { + balance: Asset; +} + +export enum AssetActionId { + SEND_PAYMENT = "send-payment", + SEP6_DEPOSIT = "sep6-deposit", + SEP6_WITHDRAW = "sep6-withdraw", + SEP8_SEND_PAYMENT = "sep8-send-payment", + SEP24_DEPOSIT = "sep24-deposit", + SEP24_WITHDRAW = "sep24-withdraw", + SEP31_SEND = "sep31-send", + TRUST_ASSET = "trust-asset", + REMOVE_ASSET = "remove-asset", + ADD_ASSET_OVERRIDE = "add-asset-override", + REMOVE_ASSET_OVERRIDE = "remove-asset-override", +} + +export enum AssetType { + NATIVE = "native", +} + +export enum TransactionStatus { + COMPLETED = "completed", + ERROR = "error", + INCOMPLETE = "incomplete", + NON_INTERACTIVE_CUSTOMER_INFO_NEEDED = "non_interactive_customer_info_needed", + PENDING_ANCHOR = "pending_anchor", + PENDING_CUSTOMER_INFO_UPDATE = "pending_customer_info_update", + PENDING_EXTERNAL = "pending_external", + PENDING_RECEIVER = "pending_receiver", + PENDING_SENDER = "pending_sender", + PENDING_STELLAR = "pending_stellar", + PENDING_TRANSACTION_INFO_UPDATE = "pending_transaction_info_update", + PENDING_TRUST = "pending_trust", + PENDING_USER = "pending_user", + PENDING_USER_TRANSFER_START = "pending_user_transfer_start", +} + +export enum MemoTypeString { + TEXT = "text", + ID = "id", + HASH = "hash", +} + +export enum AnchorActionType { + DEPOSIT = "deposit", + WITHDRAWAL = "withdraw", +} + +interface InfoTypeData { + // eslint-disable-next-line camelcase + authentication_required: boolean; + enabled: boolean; + fields: AnyObject; + types: AnyObject; + // eslint-disable-next-line camelcase + min_amount?: number; + // eslint-disable-next-line camelcase + max_amount?: number; +} + +export interface CheckInfoData { + [AnchorActionType.DEPOSIT]: { + [asset: string]: InfoTypeData; + }; + [AnchorActionType.WITHDRAWAL]: { + [asset: string]: InfoTypeData; + }; +} + +// Anchor quotes +export type AnchorDeliveryMethod = { + name: string; + description: string; +}; + +export type AnchorQuoteAsset = { + asset: string; + /* eslint-disable camelcase */ + sell_delivery_methods?: AnchorDeliveryMethod[]; + buy_delivery_methods?: AnchorDeliveryMethod[]; + country_codes?: string[]; + /* eslint-enable camelcase */ +}; + +export type AnchorBuyAsset = { + asset: string; + price: string; + decimals: number; +}; + +export type AnchorQuote = { + id: string; + price: string; + fee: AnchorFee; + /* eslint-disable camelcase */ + expires_at: string; + total_price: string; + sell_asset: string; + sell_amount: string; + buy_asset: string; + buy_amount: string; + /* eslint-enable camelcase */ +}; + +export type AnchorFee = { + total: string; + asset: string; + details?: AnchorFeeDetail[]; +}; + +export type AnchorFeeDetail = { + name: string; + description?: string; + amount: string; +}; + +export type SepInstructions = { + [key: string]: { + description: string; + value: string; + }; +}; + +// js-stellar-wallets types +export interface Issuer { + key: string; + name?: string; + url?: string; + hostName?: string; +} + +export interface NativeToken { + type: AssetType; + code: string; +} + +export interface AssetToken { + type: AssetType; + code: string; + issuer: Issuer; + anchorAsset?: string; + numAccounts?: BigNumber; + amount?: BigNumber; + bidCount?: BigNumber; + askCount?: BigNumber; + spread?: BigNumber; +} + +export type Token = NativeToken | AssetToken; +export interface Balance { + token: Token; + + // for non-native tokens, this should be total - sellingLiabilities + // for native, it should also subtract the minimumBalance + available: BigNumber; + total: BigNumber; + buyingLiabilities: BigNumber; + sellingLiabilities: BigNumber; +} + +export interface AssetBalance extends Balance { + token: AssetToken; + sponsor?: string; +} + +export interface NativeBalance extends Balance { + token: NativeToken; + minimumBalance: BigNumber; +} + +export interface BalanceMap { + [key: string]: AssetBalance | NativeBalance; + native: NativeBalance; +} + +export interface AccountDetails { + id: string; + subentryCount: number; + sponsoringCount: number; + sponsoredCount: number; + sponsor?: string; + inflationDestination?: string; + thresholds: Horizon.HorizonApi.AccountThresholds; + signers: Horizon.ServerApi.AccountRecordSigners[]; + flags: Horizon.HorizonApi.Flags; + balances: BalanceMap; + sequenceNumber: string; +} diff --git a/src/interfaces/tomlFields.ts b/src/interfaces/tomlFields.ts deleted file mode 100644 index 77bb7296..00000000 --- a/src/interfaces/tomlFields.ts +++ /dev/null @@ -1,16 +0,0 @@ -export enum TomlFields { - ACCOUNTS = "ACCOUNTS", - ANCHOR_QUOTE_SERVER = "ANCHOR_QUOTE_SERVER", - AUTH_SERVER = "AUTH_SERVER", - DIRECT_PAYMENT_SERVER = "DIRECT_PAYMENT_SERVER", - FEDERATION_SERVER = "FEDERATION_SERVER", - HORIZON_URL = "HORIZON_URL", - KYC_SERVER = "KYC_SERVER", - NETWORK_PASSPHRASE = "NETWORK_PASSPHRASE", - SIGNING_KEY = "SIGNING_KEY", - TRANSFER_SERVER = "TRANSFER_SERVER", - TRANSFER_SERVER_SEP0024 = "TRANSFER_SERVER_SEP0024", - URI_REQUEST_SIGNING_KEY = "URI_REQUEST_SIGNING_KEY", - VERSION = "VERSION", - WEB_AUTH_ENDPOINT = "WEB_AUTH_ENDPOINT", -} \ No newline at end of file From f9a16c7f74095e0c7ac9b4f4f2aa3f2522f3886e Mon Sep 17 00:00:00 2001 From: "matias.pobleteduran" Date: Mon, 10 Jun 2024 13:41:50 +0000 Subject: [PATCH 10/23] =?UTF-8?q?=E2=99=BB=EF=B8=8Fadd=20get=20currencies?= =?UTF-8?q?=20from=20toml=20file?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/functions/buy/SEP-1.ts | 5 +++++ src/functions/buy/sep10Auth/stellarAuth.ts | 3 --- src/functions/buy/types.ts | 1 + 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/functions/buy/SEP-1.ts b/src/functions/buy/SEP-1.ts index e549305e..8196d232 100644 --- a/src/functions/buy/SEP-1.ts +++ b/src/functions/buy/SEP-1.ts @@ -30,4 +30,9 @@ export async function getTransferServerUrl(home_domain: string) { export async function getDepositServerUrl(home_domain: string) { const toml = await getStellarToml(home_domain); return toml[TomlFields.TRANSFER_SERVER_SEP0024]; +} + +export async function getCurrencies(home_domain: string){ + const toml = await getStellarToml(home_domain); + return toml[TomlFields.CURRENCIES]; } \ No newline at end of file diff --git a/src/functions/buy/sep10Auth/stellarAuth.ts b/src/functions/buy/sep10Auth/stellarAuth.ts index 7af58c79..2a4584a7 100644 --- a/src/functions/buy/sep10Auth/stellarAuth.ts +++ b/src/functions/buy/sep10Auth/stellarAuth.ts @@ -5,11 +5,9 @@ import { getStellarToml, getAuthUrl } from "../SEP-1"; export async function getChallengeTransaction({ publicKey, homeDomain, - clientDomain }:{ publicKey: string, homeDomain: string, - clientDomain?: string, }): Promise<{ transaction:any, network_passphrase:string @@ -31,7 +29,6 @@ export async function getChallengeTransaction({ // Possible parameters are `account`, `memo`, `home_domain`, and // `client_domain`. For our purposes, we only supply `account`. account: publicKey, - client_domain: clientDomain!, })}` ).catch((e)=>{ console.log(e) diff --git a/src/functions/buy/types.ts b/src/functions/buy/types.ts index bd932dc3..3226f2f1 100644 --- a/src/functions/buy/types.ts +++ b/src/functions/buy/types.ts @@ -44,6 +44,7 @@ export enum TomlFields { URI_REQUEST_SIGNING_KEY = "URI_REQUEST_SIGNING_KEY", VERSION = "VERSION", WEB_AUTH_ENDPOINT = "WEB_AUTH_ENDPOINT", + CURRENCIES= "CURRENCIES" } export interface PresetAsset { From a87c52f9b31c52bd6f5e43725e199b6c12646671 Mon Sep 17 00:00:00 2001 From: "matias.pobleteduran" Date: Mon, 10 Jun 2024 13:42:30 +0000 Subject: [PATCH 11/23] =?UTF-8?q?=E2=99=BB=EF=B8=8FRefactor=20buy=20compon?= =?UTF-8?q?ent?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Buy/BuyComponent.tsx | 404 +++++++++++------------- src/components/Buy/BuyCryptoPanel.tsx | 160 ---------- src/components/Layout/StyledSelect.tsx | 60 ++++ src/components/Layout/StyledWrapper.tsx | 31 ++ src/components/Swap/SwapComponent.tsx | 2 +- 5 files changed, 285 insertions(+), 372 deletions(-) delete mode 100644 src/components/Buy/BuyCryptoPanel.tsx create mode 100644 src/components/Layout/StyledSelect.tsx create mode 100644 src/components/Layout/StyledWrapper.tsx diff --git a/src/components/Buy/BuyComponent.tsx b/src/components/Buy/BuyComponent.tsx index 70818cc9..89b8c2d9 100644 --- a/src/components/Buy/BuyComponent.tsx +++ b/src/components/Buy/BuyComponent.tsx @@ -1,228 +1,210 @@ -import { Box, Button, CircularProgress, Modal, styled } from '@mui/material'; -import { setTrustline } from '@soroban-react/contracts'; -import { useSorobanReact } from '@soroban-react/core'; -import { ButtonPrimary } from 'components/Buttons/Button'; -import { AutoColumn } from 'components/Column'; -import { AppContext, SnackbarIconType } from 'contexts'; -import { sendNotification } from 'functions/sendNotification'; -import { formatTokenAmount } from 'helpers/format'; -import { requiresTrustline } from 'helpers/stellar'; -import { useToken } from 'hooks/tokens/useToken'; -import useGetNativeTokenBalance from 'hooks/useGetNativeTokenBalance'; -import { - ReactNode, - useCallback, - useContext, - useEffect, - useMemo, - useReducer, - useState, -} from 'react'; -import { InterfaceTrade, TradeState } from 'state/routing/types'; -import { Field } from 'state/swap/actions'; -import { useDerivedSwapInfo, useSwapActionHandlers } from 'state/swap/hooks'; -import swapReducer, { SwapState, initialState as initialSwapState } from 'state/swap/reducer'; -import { opacify } from 'themes/utils'; -import SwapHeader from 'components/Swap/SwapHeader'; -import { SwapWrapper } from 'components/Swap/styleds'; -import DepositFiatInputPanel from './BuyCryptoPanel'; - -export const SwapSection = styled('div')(({ theme }) => ({ - position: 'relative', - backgroundColor: theme.palette.customBackground.module, - borderRadius: 12, - padding: 16, - color: theme.palette.secondary.main, - fontSize: 14, - lineHeight: '20px', - fontWeight: 500, - '&:before': { - boxSizing: 'border-box', - backgroundSize: '100%', - borderRadius: 'inherit', - position: 'absolute', - top: 0, - left: 0, - width: '100%', - height: '100%', - pointerEvents: 'none', - border: `1px solid ${theme.palette.customBackground.module}`, - }, - '&:hover:before': { - borderColor: opacify(8, theme.palette.secondary.main), - }, - '&:focus-within:before': { - borderColor: opacify(24, theme.palette.secondary.light), - }, -})); - -export const OutputSwapSection = styled(SwapSection)` - border-bottom: ${({ theme }) => `1px solid ${theme.palette.customBackground.module}`}; - border-radius: 16px; - border: 1px solid rgba(180, 239, 175, 0.2); - background: ${({ theme }) => theme.palette.customBackground.outputBackground}; -`; - -export const ArrowContainer = styled('div')` - display: inline-flex; - align-items: center; - justify-content: center; - - width: 100%; - height: 100%; -`; - -function getIsValidSwapQuote( - trade: InterfaceTrade | undefined, - tradeState: TradeState, - swapInputError?: ReactNode, -): boolean { - return Boolean(!swapInputError && trade && tradeState === TradeState.VALID); +import { WalletButton } from 'components/Buttons/WalletButton' +import StyledWrapper from 'components/Layout/StyledWrapper' +import React, { useEffect, useState } from 'react' +import SwapHeader from 'components/Swap/SwapHeader' +import { useSorobanReact } from '@soroban-react/core' +import { SwapSection } from 'components/Swap/SwapComponent' +import { InputPanel, Container, Aligner, StyledTokenName, StyledDropDown } from 'components/CurrencyInputPanel/SwapCurrencyInputPanel' +import { StyledSelect } from 'components/Layout/StyledSelect' +import { RowFixed } from 'components/Row' +import { ButtonPrimary } from 'components/Buttons/Button' +import { BodyPrimary } from 'components/Text' +import { getCurrencies } from 'functions/buy/SEP-1' +import { getChallengeTransaction, submitChallengeTransaction } from 'functions/buy/sep10Auth/stellarAuth' +import { initInteractiveDepositFlow } from 'functions/buy/sep24Deposit/InteractiveDeposit' +import { setTrustline } from '@soroban-react/contracts' + +interface anchor { + name: string + home_domain: string } -interface BuyStateProps { - showConfirm: boolean; - tradeToConfirm?: InterfaceTrade; - swapError?: Error; - swapResult?: any; +interface token { + name: string + issuer: string } -const INITIAL_SWAP_STATE = { - showConfirm: false, - tradeToConfirm: undefined, - swapError: undefined, - swapResult: undefined, -}; - -export function BuyComponent({ - prefilledState = {}, - disableTokenInputs = false, -}: { - prefilledState?: Partial; - disableTokenInputs?: boolean; -}) { - const sorobanContext = useSorobanReact(); - const { SnackbarContext } = useContext(AppContext); - const [txError, setTxError] = useState(false); - - const [needTrustline, setNeedTrustline] = useState(true); - - const { token: prefilledToken } = useToken(prefilledState.INPUT?.currencyId!); - - // modal and loading - const [{ showConfirm, tradeToConfirm, swapError, swapResult }, setSwapState] = - useState(INITIAL_SWAP_STATE); - - const [state, dispatch] = useReducer(swapReducer, { ...initialSwapState, ...prefilledState }); - const { typedValue, recipient, independentField } = state; - - const { onSwitchTokens, onCurrencySelection, onUserInput, onChangeRecipient } = - useSwapActionHandlers(dispatch); - const dependentField: Field = independentField === Field.INPUT ? Field.OUTPUT : Field.INPUT; - - useEffect(() => { - if (prefilledToken) { - onCurrencySelection(Field.INPUT, prefilledToken); +const anchors: anchor[] = [ + { + name: 'Stellar TestAnchor 1', + home_domain: 'https://testanchor.stellar.org' + }, + { + name: 'MoneyGram', + home_domain: 'https://testanchor.stellar.org' + }, + { + name: 'MyKobo', + home_domain: 'https://testanchor.stellar.org' + }, +] + +function BuyComponent() { + const sorobanContext = useSorobanReact() + const {address, serverHorizon, activeChain} = sorobanContext + const [selectedAnchor, setSelectedAnchor] = useState(undefined) + const [selectedToken, setSelectedToken] = useState(undefined) + const [needTrustline, setNeedTrustline] = useState(true) + + const checkTrustline = async () => { + if(address){ + const account = await serverHorizon?.loadAccount(address) + const balances = account?.balances + const hasTrustline = balances?.find((bal) => + 'asset_code' in bal && 'asset_issuer' in bal && + bal.asset_code === selectedToken?.name && bal.asset_issuer === selectedToken?.issuer + ) + setNeedTrustline(!hasTrustline) } - }, [onCurrencySelection, prefilledToken]); - - const { - trade: { state: tradeState, trade, resetRouterSdkCache }, - allowedSlippage, - currencyBalances, - parsedAmount, - currencies, - inputError: swapInputError, - } = useDerivedSwapInfo(state); - - - - const decimals = useMemo( - () => ({ - [Field.INPUT]: - independentField === Field.INPUT - ? trade?.outputAmount?.currency.decimals ?? 7 - : trade?.inputAmount?.currency.decimals ?? 7, - [Field.OUTPUT]: - independentField === Field.OUTPUT - ? trade?.inputAmount?.currency.decimals ?? 7 - : trade?.outputAmount?.currency.decimals ?? 7, - }), - [independentField, trade], - ); - - - const formattedAmounts = useMemo( - () => ({ - [independentField]: typedValue, - [dependentField]: formatTokenAmount(trade?.expectedAmount, decimals[independentField]), - }), - [decimals, dependentField, independentField, trade?.expectedAmount, typedValue], - ); - - const handleTrustline = () => { - const asset = trade?.outputAmount?.currency; - if (!asset?.issuer) return; - - setTrustline({ tokenSymbol: asset.code, tokenAdmin: asset.issuer, sorobanContext }) - .then((result) => { - setNeedTrustline(false); - sendNotification( - `for ${asset.code}`, - 'Trustline set', - SnackbarIconType.MINT, - SnackbarContext, - ); - }) - .catch((error) => { - // console.log(error); - setTxError(true); - setSwapState((currentState) => ({ - ...currentState, - showConfirm: false, - })); - }); + }; + const buy = async () => { + await checkTrustline() + if(needTrustline){ + try { + setTrustline( + { + tokenSymbol: selectedToken?.name!, + tokenAdmin: selectedToken?.issuer!, + sorobanContext + } + ) + + } catch (error) { + console.error(error) + } + } + try { + InitDeposit(selectedAnchor?.home_domain!) + } catch (error) { + console.error(error) + } + } + + + const sign = async (txn: any) => { + const signedTransaction = await sorobanContext?.activeConnector?.signTransaction(txn, { + networkPassphrase: activeChain?.networkPassphrase, + network: activeChain?.id, + accountToSign: address + }) + return signedTransaction; + } + + const InitDeposit = async (homeDomain: string) => { + console.log(homeDomain) + const { transaction } = await getChallengeTransaction({ + publicKey: address! && address, + homeDomain: homeDomain + }) + const signedTransaction = await sign(transaction) + const submittedTransaction = await submitChallengeTransaction({ + transactionXDR: signedTransaction, + homeDomain: homeDomain + }) + const { url } = await initInteractiveDepositFlow({ + authToken: submittedTransaction, + homeDomain: homeDomain, + urlFields: { + asset_code: selectedToken?.name, + asset_issuer: selectedToken?.issuer + } + }) + + const interactiveUrl = `${url}&callback=postMessage` + let popup = window.open(interactiveUrl, 'interactiveDeposit', 'width=450,height=750') + + if (!popup) { + alert( + "Popups are blocked. You’ll need to enable popups for this demo to work", + ); + console.error( + "Popups are blocked. You’ll need to enable popups for this demo to work", + ) + } + popup?.focus() - const nativeBalance = useGetNativeTokenBalance(); - - useEffect(() => { - const checkTrustline = async () => { - if (!trade) return; - if (sorobanContext.address) { - // Check if we need trustline - const needTrustline = await requiresTrustline( - sorobanContext, - trade?.outputAmount?.currency, - trade?.outputAmount?.value, - ); - - if (needTrustline) { - setNeedTrustline(true); - } else { - setNeedTrustline(false); - } - } else { - // Until the user does not connects the wallet, we will think that we need trustline - setNeedTrustline(true); + window.addEventListener('message', (event) => { + if (event.origin === homeDomain) { + console.log(event.data) + const transaction = event.data.transaction + if (transaction.status == 'complete') + popup?.close() } - }; - - checkTrustline(); - }, [sorobanContext, trade, nativeBalance.data?.validAccount]); + }) + } + useEffect(() => { + checkTrustline() + }, [selectedToken, address, activeChain]) + const fetchCurrencies = async () => { + console.log('fetching currencies') + const currencies = await getCurrencies(selectedAnchor?.home_domain!) + setSelectedToken({name: currencies[0].code, issuer: currencies[0].issuer}) + } return ( <> - - - - - - + + + + + +
Deposit to:
+ + + setSelectedAnchor(anchors[0])}> + + {selectedAnchor ? selectedAnchor.name : 'Select Anchor'} + + {} + + + +
+
+
+ + + +
Recieve:
+ + + fetchCurrencies()}> + + {selectedToken ? selectedToken.name : 'Select token'} + + {} + + + +
+
+
+ {address ? + ( + + Buy {selectedToken ? selectedToken.name : ''} + + ): + () + } +
- ); + ) } + +export { BuyComponent } \ No newline at end of file diff --git a/src/components/Buy/BuyCryptoPanel.tsx b/src/components/Buy/BuyCryptoPanel.tsx deleted file mode 100644 index 351d2312..00000000 --- a/src/components/Buy/BuyCryptoPanel.tsx +++ /dev/null @@ -1,160 +0,0 @@ -import { useState, useEffect } from 'react' -import { FormControl, InputLabel, Select, MenuItem, Container } from '@mui/material' -import { ButtonPrimary } from 'components/Buttons/Button'; -import { getChallengeTransaction, submitChallengeTransaction } from 'functions/buy/sep10Auth/stellarAuth'; -import { useSorobanReact } from '@soroban-react/core'; -import { initInteractiveDepositFlow } from 'functions/buy/sep24Deposit/InteractiveDeposit'; - - -interface Options { - name: string; - value: string; -} -interface InputPanelProps { - header: string; - options: Options[]; - selected: Options; - setSelected: (value: Options) => void; -} -function InputPanel(props: InputPanelProps) { - const { header, options, selected, setSelected } = props; - const inputContainerStyle = { - mt: 2, - mb: 4, - p: 0, - backgroundColor: '#13141E', - borderRadius: '12px', - height: '10vh', - alignContent: 'center' - } - const inputSelectStyle = { - borderRadius: '32px', - backgroundColor: '#98A1C014', - color: 'white', - fontSize: '1.5rem', - '& .MuiSelect-icon': { - color: 'white' - }, - '& .MuiSelect-select': { - color: 'white', - } - } - return ( - <> - - - - - - - ) -} - -function DepositFiatInputPanel() { - const anchorsArray = [ - { name: 'Stellar test', value: 'https://testanchor.stellar.org' }, - { name: 'MoneyGram', value: 'https://stellar.moneygram.com' }, - { name: 'MyKobo', value: 'https://mykobo.co' }, - ]; - const fiatArray = [ - { name: 'USDC', value: 'USDC' }, - { name: 'EURC', value: 'EURC' }, - { name: 'ARS', value: 'ARS' }, - { name: 'XLM', value: 'XLM' }, - { name: 'CNY', value: 'CNY' } - ]; - const [selectedFiat, setSelectedFiat] = useState(fiatArray[0]) - const [selectedAnchor, setSelectedAnchor] = useState(anchorsArray[0]); - const sorobanContext = useSorobanReact() - const { activeChain, address } = useSorobanReact() - - const sign = async (txn: any) => { - const signedTransaction = await sorobanContext?.activeConnector?.signTransaction(txn, { - networkPassphrase: activeChain?.networkPassphrase, - network: activeChain?.id, - accountToSign: address - }) - return signedTransaction; - } - - const dev = async (homeDomain: string) => { - //#Auth flow - - //First, we define the anchor home domain - //const homeDomain = 'https://testanchor.stellar.org' - console.log(homeDomain) - //Then, we get the challenge transaction, giving as input the user address to sign and the home domain of the anchor - const { transaction, network_passphrase } = await getChallengeTransaction({ - publicKey: address! && address, - homeDomain: homeDomain - }) - //Once recived the Challenge transaction we sign it with our wallet - const signedTransaction = await sign(transaction) - - //And submit the signed Challenge transaction to get the JWT - const submittedTransaction = await submitChallengeTransaction({ - transactionXDR: signedTransaction, - homeDomain: homeDomain - }) - - //#Interactive Deposit flow - //We get the url of the interactive deposit flow, giving as input the JWT (Obtained from the authentication flow), the home domain of the anchor and the - //asset info from the asset we expect to recieve - const { url } = await initInteractiveDepositFlow({ - authToken: submittedTransaction, - homeDomain: homeDomain, - urlFields: { - asset_code: 'SRT', - asset_issuer: 'GCDNJUBQSX7AJWLJACMJ7I4BC3Z47BQUTMHEICZLE6MU4KQBRYG5JY6B' - } - }) - - //once we got the url we open the popup with a callback parameter to get the transaction status - const interactiveUrl = `${url}&callback=postMessage` - let popup = window.open(interactiveUrl, 'interactiveDeposit', 'width=450,height=750') - - if (!popup) { - alert( - "Popups are blocked. You’ll need to enable popups for this demo to work", - ); - console.error( - "Popups are blocked. You’ll need to enable popups for this demo to work", - ) - } - - popup?.focus() - - window.addEventListener('message', (event) => { - if (event.origin === homeDomain) { - console.log(event.data) - const transaction = event.data.transaction - if (transaction.status == 'complete') - popup?.close() - } - }) - } - - useEffect(() => { - console.log('Deposit', [selectedFiat.value, selectedAnchor.value]) - }, [selectedFiat, selectedAnchor]) - - const [modalOpen, setModalOpen] = useState(false); - - return ( - <> - - - - { dev(selectedAnchor.value) }}>Buy USDC - - ) -} - -export default DepositFiatInputPanel \ No newline at end of file diff --git a/src/components/Layout/StyledSelect.tsx b/src/components/Layout/StyledSelect.tsx new file mode 100644 index 00000000..1c937f73 --- /dev/null +++ b/src/components/Layout/StyledSelect.tsx @@ -0,0 +1,60 @@ +// import { Trans } from '@lingui/macro' +import { ButtonBase, styled, useMediaQuery, useTheme } from '@mui/material'; +import { opacify } from '../../themes/utils'; + + +export const StyledSelect = styled(ButtonBase, { + shouldForwardProp: (prop) => prop !== 'selected', +})<{ + visible: boolean; + selected?: boolean; + hideInput?: boolean; + disabled?: boolean; +}>` + align-items: center; + background-color: ${({ selected, theme }) => + selected ? theme.palette.customBackground.interactive : theme.palette.custom.borderColor}; + opacity: ${({ disabled }) => (!disabled ? 1 : 0.4)}; + box-shadow: ${({ selected }) => (selected ? 'none' : '0px 6px 10px rgba(0, 0, 0, 0.075)')}; + color: ${({ selected, theme }) => (selected ? theme.palette.primary.main : '#FFFFFF')}; + height: unset; + border-radius: 16px; + outline: none; + user-select: none; + border: none; + font-size: 24px; + font-weight: 400; + width: ${({ hideInput }) => (hideInput ? '100%' : 'initial')}; + padding: ${({ selected }) => (selected ? '4px 4px 4px 4px' : '6px 6px 6px 8px')}; + gap: 8px; + justify-content: space-between; + + &:hover, + &:active { + background-color: ${({ theme, selected }) => + selected ? theme.palette.customBackground.interactive : theme.palette.custom.borderColor}; + } + + &:before { + background-size: 100%; + border-radius: inherit; + + position: absolute; + top: 0; + left: 0; + + width: 100%; + height: 100%; + content: ''; + } + + &:hover:before { + background-color: ${({ theme }) => opacify(8, theme.palette.secondary.main)}; + } + + &:active:before { + background-color: ${({ theme }) => opacify(24, theme.palette.secondary.light)}; + } + + visibility: ${({ visible }) => (visible ? 'visible' : 'hidden')}; +`; \ No newline at end of file diff --git a/src/components/Layout/StyledWrapper.tsx b/src/components/Layout/StyledWrapper.tsx new file mode 100644 index 00000000..205596cb --- /dev/null +++ b/src/components/Layout/StyledWrapper.tsx @@ -0,0 +1,31 @@ +import { styled } from '@mui/material/styles'; +import { opacify } from 'themes/utils'; + +export const StyledWrapper = styled('main')` + position: relative; + background: ${({ theme }) => `linear-gradient(${theme.palette.customBackground.bg1}, ${ + theme.palette.customBackground.bg1 + }) padding-box, + linear-gradient(150deg, rgba(136,102,221,1) 0%, rgba(${ + theme.palette.mode == 'dark' ? '33,29,50,1' : '255,255,255,1' + }) 35%, rgba(${ + theme.palette.mode == 'dark' ? '33,29,50,1' : '255,255,255,1' + }) 65%, rgba(136,102,221,1) 100%) border-box`}; + border: 1px solid transparent; + border-radius: 16px; + padding: 32px; + padding-top: 12px; + box-shadow: 0px 40px 120px 0px #f00bdd29; + transition: transform 250ms ease; + max-width: 480px; + width: 100%; + &:hover: { + border: 1px solid ${({ theme }) => opacify(24, theme.palette.secondary.main)}; + } + + @media (max-width: ${({ theme }) => theme.breakpoints.values.md}px) { + padding: 16px; + } +`; + +export default StyledWrapper \ No newline at end of file diff --git a/src/components/Swap/SwapComponent.tsx b/src/components/Swap/SwapComponent.tsx index 85adde99..a14569c6 100644 --- a/src/components/Swap/SwapComponent.tsx +++ b/src/components/Swap/SwapComponent.tsx @@ -388,7 +388,7 @@ export function SwapComponent({ return ( <> - + setTxError(false)} From bf725988325826e7d446421846dc481f6cd35a9b Mon Sep 17 00:00:00 2001 From: "matias.pobleteduran" Date: Mon, 10 Jun 2024 13:51:59 +0000 Subject: [PATCH 12/23] =?UTF-8?q?=E2=99=BB=EF=B8=8FRefactor=20swapHeader?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Swap/SwapHeader.tsx | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/components/Swap/SwapHeader.tsx b/src/components/Swap/SwapHeader.tsx index 1e816555..fa0e41d1 100644 --- a/src/components/Swap/SwapHeader.tsx +++ b/src/components/Swap/SwapHeader.tsx @@ -35,11 +35,13 @@ export default function SwapHeader({ chainId, trade, active, + showConfig, }: { autoSlippage?: number; chainId?: number; trade?: boolean; - active?: string; + active?: string; + showConfig: boolean; }) { const theme = useTheme(); const fiatOnRampButtonEnabled = true; @@ -63,9 +65,12 @@ export default function SwapHeader({ )} - + {showConfig ? + ( + + ) : + (false)} + ); } From fe100b2bae3eab246fbf23111477a59c1535c0a6 Mon Sep 17 00:00:00 2001 From: Matias Poblete Date: Mon, 10 Jun 2024 17:14:18 -0400 Subject: [PATCH 13/23] =?UTF-8?q?=E2=99=BB=EF=B8=8FImprove=20trustline=20f?= =?UTF-8?q?low=20+=20add=20new=20anchors?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Buy/BuyComponent.tsx | 80 ++++++++++++++++------------- 1 file changed, 45 insertions(+), 35 deletions(-) diff --git a/src/components/Buy/BuyComponent.tsx b/src/components/Buy/BuyComponent.tsx index 89b8c2d9..4b4d205e 100644 --- a/src/components/Buy/BuyComponent.tsx +++ b/src/components/Buy/BuyComponent.tsx @@ -31,11 +31,11 @@ const anchors: anchor[] = [ }, { name: 'MoneyGram', - home_domain: 'https://testanchor.stellar.org' + home_domain: 'https://stellar.moneygram.com' }, { name: 'MyKobo', - home_domain: 'https://testanchor.stellar.org' + home_domain: 'https://mykobo.co' }, ] @@ -48,7 +48,12 @@ function BuyComponent() { const checkTrustline = async () => { if(address){ - const account = await serverHorizon?.loadAccount(address) + let account + try { + account = await serverHorizon?.loadAccount(address) + } catch (error) { + console.error(error) + } const balances = account?.balances const hasTrustline = balances?.find((bal) => 'asset_code' in bal && 'asset_issuer' in bal && @@ -59,30 +64,6 @@ function BuyComponent() { }; - const buy = async () => { - await checkTrustline() - if(needTrustline){ - try { - setTrustline( - { - tokenSymbol: selectedToken?.name!, - tokenAdmin: selectedToken?.issuer!, - sorobanContext - } - ) - - } catch (error) { - console.error(error) - } - } - try { - InitDeposit(selectedAnchor?.home_domain!) - } catch (error) { - console.error(error) - } - } - - const sign = async (txn: any) => { const signedTransaction = await sorobanContext?.activeConnector?.signTransaction(txn, { networkPassphrase: activeChain?.networkPassphrase, @@ -126,14 +107,42 @@ function BuyComponent() { popup?.focus() - window.addEventListener('message', (event) => { - if (event.origin === homeDomain) { - console.log(event.data) - const transaction = event.data.transaction - if (transaction.status == 'complete') - popup?.close() + + } + + const buy = async () => { + if (!selectedToken || !selectedAnchor) { + return + } + await checkTrustline() + if (needTrustline) { + try { + console.log('setting trustline') + const res = await setTrustline( + { + tokenSymbol: selectedToken?.name!, + tokenAdmin: selectedToken?.issuer!, + sorobanContext + } + ) + console.log('response', res) + if (res === undefined) { + setNeedTrustline(true) + console.error('error setting trustline') + } + } catch (error) { + console.log('error setting trustline') + console.error(error) + setNeedTrustline(true) + } - }) + } else { + try { + InitDeposit(selectedAnchor?.home_domain!) + } catch (error) { + console.error(error) + } + } } useEffect(() => { @@ -156,7 +165,7 @@ function BuyComponent() {
Deposit to:
- setSelectedAnchor(anchors[0])}> + setSelectedAnchor(anchors[2])}> + {needTrustline ? 'Create Trustline and ' : ''} Buy {selectedToken ? selectedToken.name : ''} ): From 7395a6080f08e0524f1b0a70c8924d7e2103eba7 Mon Sep 17 00:00:00 2001 From: Matias Poblete Date: Mon, 24 Jun 2024 20:50:56 -0400 Subject: [PATCH 14/23] =?UTF-8?q?=F0=9F=A9=B9Fix=20Button=20behaviour?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Buy/BuyComponent.tsx | 55 +++++++++++++++++++---------- 1 file changed, 37 insertions(+), 18 deletions(-) diff --git a/src/components/Buy/BuyComponent.tsx b/src/components/Buy/BuyComponent.tsx index 4b4d205e..194ed420 100644 --- a/src/components/Buy/BuyComponent.tsx +++ b/src/components/Buy/BuyComponent.tsx @@ -1,6 +1,6 @@ import { WalletButton } from 'components/Buttons/WalletButton' import StyledWrapper from 'components/Layout/StyledWrapper' -import React, { useEffect, useState } from 'react' +import React, { useEffect, useMemo, useState } from 'react' import SwapHeader from 'components/Swap/SwapHeader' import { useSorobanReact } from '@soroban-react/core' import { SwapSection } from 'components/Swap/SwapComponent' @@ -13,6 +13,10 @@ import { getCurrencies } from 'functions/buy/SEP-1' import { getChallengeTransaction, submitChallengeTransaction } from 'functions/buy/sep10Auth/stellarAuth' import { initInteractiveDepositFlow } from 'functions/buy/sep24Deposit/InteractiveDeposit' import { setTrustline } from '@soroban-react/contracts' +import { set } from 'cypress/types/lodash' +import { balances } from '@polkadot/types/interfaces/definitions' +import { a } from 'react-spring' + interface anchor { name: string @@ -41,10 +45,11 @@ const anchors: anchor[] = [ function BuyComponent() { const sorobanContext = useSorobanReact() - const {address, serverHorizon, activeChain} = sorobanContext + const { address, serverHorizon, activeChain, activeConnector } = sorobanContext const [selectedAnchor, setSelectedAnchor] = useState(undefined) const [selectedToken, setSelectedToken] = useState(undefined) const [needTrustline, setNeedTrustline] = useState(true) + const [buttonText, setButtonText] = useState('Select Anchor') const checkTrustline = async () => { if(address){ @@ -55,15 +60,11 @@ function BuyComponent() { console.error(error) } const balances = account?.balances - const hasTrustline = balances?.find((bal) => - 'asset_code' in bal && 'asset_issuer' in bal && - bal.asset_code === selectedToken?.name && bal.asset_issuer === selectedToken?.issuer - ) - setNeedTrustline(!hasTrustline) + const hasTrustline = balances?.find((bal: any) => bal.asset_code === selectedToken?.name && bal.asset_issuer == selectedToken?.issuer) + setNeedTrustline(!!!hasTrustline) } - }; - + } const sign = async (txn: any) => { const signedTransaction = await sorobanContext?.activeConnector?.signTransaction(txn, { networkPassphrase: activeChain?.networkPassphrase, @@ -115,6 +116,7 @@ function BuyComponent() { return } await checkTrustline() + console.log('need trustline', needTrustline) if (needTrustline) { try { console.log('setting trustline') @@ -125,16 +127,15 @@ function BuyComponent() { sorobanContext } ) - console.log('response', res) if (res === undefined) { - setNeedTrustline(true) - console.error('error setting trustline') + throw new Error('The response is undefined') } + await checkTrustline() + console.log('trustline set') } catch (error) { console.log('error setting trustline') console.error(error) setNeedTrustline(true) - } } else { try { @@ -149,9 +150,28 @@ function BuyComponent() { checkTrustline() }, [selectedToken, address, activeChain]) + useEffect(() => { + if ((selectedAnchor != undefined) && (selectedToken == undefined)) { + console.log('seleciona token') + setButtonText('Select Token') + } else if (selectedAnchor && selectedToken) { + if (needTrustline) { + setButtonText(`Set trustline to ${selectedToken.name}`) + } else { + setButtonText(`Buy ${selectedToken.name}`) + } + } else { + setButtonText('Select Anchor') + } + }, [needTrustline, address, selectedToken, selectedAnchor]) + + useEffect(() => { + console.log('selected anchor', selectedAnchor) + }, [selectedAnchor]) const fetchCurrencies = async () => { - console.log('fetching currencies') + console.log('fetching currencieeees') const currencies = await getCurrencies(selectedAnchor?.home_domain!) + console.log(currencies) setSelectedToken({name: currencies[0].code, issuer: currencies[0].issuer}) } @@ -162,10 +182,10 @@ function BuyComponent() { -
Deposit to:
+
You pay:
- setSelectedAnchor(anchors[2])}> + setSelectedAnchor(anchors[0])}> - {needTrustline ? 'Create Trustline and ' : ''} - Buy {selectedToken ? selectedToken.name : ''} + {buttonText} ): () From c8f6661b919227e00a2daf3d74f3dffd3a473a18 Mon Sep 17 00:00:00 2001 From: Matias Poblete Date: Tue, 25 Jun 2024 18:25:01 -0400 Subject: [PATCH 15/23] =?UTF-8?q?=E2=9C=A8Created=20modal=20for=20selectio?= =?UTF-8?q?n=20in=20buy=20section?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Buy/BuyModal.tsx | 104 ++++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 src/components/Buy/BuyModal.tsx diff --git a/src/components/Buy/BuyModal.tsx b/src/components/Buy/BuyModal.tsx new file mode 100644 index 00000000..67e20749 --- /dev/null +++ b/src/components/Buy/BuyModal.tsx @@ -0,0 +1,104 @@ +import { useEffect } from 'react' +import ModalBox from 'components/Modals/ModalBox' +import { styled } from '@mui/material/styles'; +import { Box, Container, Modal, useMediaQuery } from '@mui/material'; +import { BodyPrimary, BodySmall, Caption } from 'components/Text'; +import { anchor, currency } from './BuyComponent' + +const ContentWrapper = styled('div') <{ isMobile: boolean }>` + display: flex; + flex-direction: column; + gap: 24px; + font-family: Inter; + text-align: ${({ isMobile }) => (isMobile ? 'center' : 'left')}; +`; + +const ContainerBox = styled('div')` + cursor: pointer; + display: flex; + background-color: ${({ theme }) => theme.palette.customBackground.surface}; + border-radius: 12px; + padding: 16px; + flex-direction: row; + justify-content: space-between; + align-items: center; + align-self: stretch; +`; + +const BoxGroup = styled('div')` + display: flex; + flex-direction: column; + justify-content: space-between; + align-items: center; + gap: 16px; + align-self: stretch; + max-height: 50vh; + padding-right: 4px; + overflow-y: auto; + ::-webkit-scrollbar { + width: 8px; + } + + ::-webkit-scrollbar-track { + background: rgba(0, 0, 0, 0.15); /* color of the tracking area */ + } + + ::-webkit-scrollbar-thumb { + background-color: rgb(100, 102, 108, 0.25); /* color of the scroll thumb */ + border-radius: 6px; + border: solid 1px rgba(0, 0, 0, 1); + } +`; +const BuyModal = ({ + isOpen, + anchors, + assets, + onClose, + handleSelect +}: { + isOpen: boolean; + anchors?: anchor[]; + assets?: currency[]; + onClose: () => void; + handleSelect: (data: any) => void; +}) => { + useEffect(() => { + + }, []) + return ( + + <> + + + + + {anchors ?

Pay

:

Receive

} + {anchors ? Select a fiat currency to pay. : Select a crypto asset to receive} +
+ + {anchors ? anchors.map((anchor) => ( + handleSelect(anchor)}> + {anchor.currency} + {anchor.name} + + )) : + assets ? assets.map((asset) => ( + handleSelect(asset)}> + {asset.code} + + )) : + Please, select a fiat currency first. + } + +
+
+
+ +
+ ) +} + +export default BuyModal \ No newline at end of file From 27c2251ae41d844b6717c1318d3056f6d080e7e4 Mon Sep 17 00:00:00 2001 From: Matias Poblete Date: Tue, 25 Jun 2024 18:25:33 -0400 Subject: [PATCH 16/23] =?UTF-8?q?=E2=99=BB=EF=B8=8FRefactor=20BuyComponent?= =?UTF-8?q?=20to=20add=20modal=20behaviour?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Buy/BuyComponent.tsx | 283 ++++++++++++++++--------- src/components/Layout/StyledSelect.tsx | 2 +- 2 files changed, 183 insertions(+), 102 deletions(-) diff --git a/src/components/Buy/BuyComponent.tsx b/src/components/Buy/BuyComponent.tsx index 194ed420..e1262d9e 100644 --- a/src/components/Buy/BuyComponent.tsx +++ b/src/components/Buy/BuyComponent.tsx @@ -1,67 +1,96 @@ -import { WalletButton } from 'components/Buttons/WalletButton' -import StyledWrapper from 'components/Layout/StyledWrapper' -import React, { useEffect, useMemo, useState } from 'react' -import SwapHeader from 'components/Swap/SwapHeader' +import React, { useEffect, useState } from 'react' +import { CircularProgress } from '@mui/material' +import { setTrustline } from '@soroban-react/contracts' import { useSorobanReact } from '@soroban-react/core' -import { SwapSection } from 'components/Swap/SwapComponent' +import BuyModal from './BuyModal' +import { WalletButton } from 'components/Buttons/WalletButton' +import { ButtonPrimary } from 'components/Buttons/Button' import { InputPanel, Container, Aligner, StyledTokenName, StyledDropDown } from 'components/CurrencyInputPanel/SwapCurrencyInputPanel' +import StyledWrapper from 'components/Layout/StyledWrapper' import { StyledSelect } from 'components/Layout/StyledSelect' import { RowFixed } from 'components/Row' -import { ButtonPrimary } from 'components/Buttons/Button' +import SwapHeader from 'components/Swap/SwapHeader' +import { SwapSection } from 'components/Swap/SwapComponent' import { BodyPrimary } from 'components/Text' -import { getCurrencies } from 'functions/buy/SEP-1' import { getChallengeTransaction, submitChallengeTransaction } from 'functions/buy/sep10Auth/stellarAuth' import { initInteractiveDepositFlow } from 'functions/buy/sep24Deposit/InteractiveDeposit' -import { setTrustline } from '@soroban-react/contracts' -import { set } from 'cypress/types/lodash' -import { balances } from '@polkadot/types/interfaces/definitions' -import { a } from 'react-spring' +import { getCurrencies } from 'functions/buy/SEP-1' -interface anchor { +export interface anchor { name: string home_domain: string -} + currency?: string +}; -interface token { - name: string - issuer: string -} +export interface currency { + code: string; + desc?: string; + is_asset_anchored?: boolean; + issuer: string; + status?: string; +}; const anchors: anchor[] = [ { name: 'Stellar TestAnchor 1', - home_domain: 'https://testanchor.stellar.org' + home_domain: 'testanchor.stellar.org', + currency: 'SRT' }, { name: 'MoneyGram', - home_domain: 'https://stellar.moneygram.com' + home_domain: 'stellar.moneygram.com', + currency: 'USD' }, { name: 'MyKobo', - home_domain: 'https://mykobo.co' + home_domain: 'mykobo.co', + currency: 'EURC' }, -] +]; function BuyComponent() { - const sorobanContext = useSorobanReact() - const { address, serverHorizon, activeChain, activeConnector } = sorobanContext - const [selectedAnchor, setSelectedAnchor] = useState(undefined) - const [selectedToken, setSelectedToken] = useState(undefined) - const [needTrustline, setNeedTrustline] = useState(true) - const [buttonText, setButtonText] = useState('Select Anchor') + const sorobanContext = useSorobanReact(); + const { address, serverHorizon, activeChain, activeConnector } = sorobanContext; + const [selectedAnchor, setSelectedAnchor] = useState(undefined); + const [currencies, setCurrencies] = useState(undefined); + const [selectedAsset, setSelectedAsset] = useState(undefined); + const [needTrustline, setNeedTrustline] = useState(true); + const [isLoading, setIsLoading] = useState(false); + const [buttonText, setButtonText] = useState('Select Anchor'); + const [modalState, setModalState] = useState<{ + anchorModal: { + visible: boolean, + selectedAnchor: anchor | undefined, + }, + assetModal: { + visible: boolean, + selectedAsset: anchor | undefined, + }, + }>({ + anchorModal: { + visible: false, + selectedAnchor: undefined, + }, + assetModal: { + visible: false, + selectedAsset: undefined, + } + }); const checkTrustline = async () => { if(address){ - let account + setIsLoading(true); + let account; try { - account = await serverHorizon?.loadAccount(address) + account = await serverHorizon?.loadAccount(address); } catch (error) { - console.error(error) + console.error(error); } const balances = account?.balances - const hasTrustline = balances?.find((bal: any) => bal.asset_code === selectedToken?.name && bal.asset_issuer == selectedToken?.issuer) - setNeedTrustline(!!!hasTrustline) + const hasTrustline = balances?.find((bal: any) => bal.asset_code === selectedAsset?.code && bal.asset_issuer == selectedAsset?.issuer); + setNeedTrustline(!!!hasTrustline); + setIsLoading(false); } } @@ -70,32 +99,31 @@ function BuyComponent() { networkPassphrase: activeChain?.networkPassphrase, network: activeChain?.id, accountToSign: address - }) + }); return signedTransaction; } const InitDeposit = async (homeDomain: string) => { - console.log(homeDomain) const { transaction } = await getChallengeTransaction({ publicKey: address! && address, homeDomain: homeDomain - }) + }); const signedTransaction = await sign(transaction) const submittedTransaction = await submitChallengeTransaction({ transactionXDR: signedTransaction, homeDomain: homeDomain - }) + }); const { url } = await initInteractiveDepositFlow({ authToken: submittedTransaction, homeDomain: homeDomain, urlFields: { - asset_code: selectedToken?.name, - asset_issuer: selectedToken?.issuer + asset_code: selectedAsset?.code, + asset_issuer: selectedAsset?.issuer } - }) + }); const interactiveUrl = `${url}&callback=postMessage` - let popup = window.open(interactiveUrl, 'interactiveDeposit', 'width=450,height=750') + let popup = window.open(interactiveUrl, 'interactiveDeposit', 'width=450,height=750'); if (!popup) { alert( @@ -103,94 +131,144 @@ function BuyComponent() { ); console.error( "Popups are blocked. You’ll need to enable popups for this demo to work", - ) + ); } - popup?.focus() + popup?.focus(); } const buy = async () => { - if (!selectedToken || !selectedAnchor) { - return + if (!selectedAsset || !selectedAnchor) { + console.error('No asset or anchor selected'); + return; } - await checkTrustline() - console.log('need trustline', needTrustline) + await checkTrustline(); if (needTrustline) { try { - console.log('setting trustline') - const res = await setTrustline( - { - tokenSymbol: selectedToken?.name!, - tokenAdmin: selectedToken?.issuer!, - sorobanContext - } - ) - if (res === undefined) { - throw new Error('The response is undefined') - } - await checkTrustline() - console.log('trustline set') + setIsLoading(true); + const res = await setTrustline({ + tokenSymbol: selectedAsset?.code!, + tokenAdmin: selectedAsset?.issuer!, + sorobanContext + }); + if (res === undefined) throw new Error('The response is undefined'); + await checkTrustline(); + console.log('trustline set'); } catch (error) { - console.log('error setting trustline') - console.error(error) - setNeedTrustline(true) + console.log('error setting trustline'); + console.error(error); + setNeedTrustline(true); + setIsLoading(false); } } else { try { + setIsLoading(true); InitDeposit(selectedAnchor?.home_domain!) + setIsLoading(false); } catch (error) { - console.error(error) + console.error(error); + setIsLoading(false); } } } useEffect(() => { - checkTrustline() - }, [selectedToken, address, activeChain]) + checkTrustline(); + }, [selectedAsset, address, activeChain, selectedAnchor, activeConnector]) useEffect(() => { - if ((selectedAnchor != undefined) && (selectedToken == undefined)) { - console.log('seleciona token') - setButtonText('Select Token') - } else if (selectedAnchor && selectedToken) { + if ((selectedAnchor != undefined) && (selectedAsset == undefined)) { + setButtonText('Select Token'); + } else if (selectedAnchor && selectedAsset) { if (needTrustline) { - setButtonText(`Set trustline to ${selectedToken.name}`) - } else { - setButtonText(`Buy ${selectedToken.name}`) + setButtonText(`Set trustline to ${selectedAsset.code}`); + } else if (!needTrustline) { + setButtonText(`Buy ${selectedAsset.code}`); } } else { - setButtonText('Select Anchor') + setButtonText('Select Anchor'); } - }, [needTrustline, address, selectedToken, selectedAnchor]) + }, [needTrustline, address, selectedAsset, selectedAnchor]); - useEffect(() => { - console.log('selected anchor', selectedAnchor) - }, [selectedAnchor]) const fetchCurrencies = async () => { - console.log('fetching currencieeees') - const currencies = await getCurrencies(selectedAnchor?.home_domain!) - console.log(currencies) - setSelectedToken({name: currencies[0].code, issuer: currencies[0].issuer}) + setIsLoading(true); + const currencies = await getCurrencies(selectedAnchor?.home_domain!); + setCurrencies(currencies); + setIsLoading(false); + handleOpen('asset'); + } + + const handleOpen = (modal: string) => { + if (modal == 'anchor') { + setModalState({ + ...modalState, + anchorModal: { + visible: true, + selectedAnchor: modalState.anchorModal.selectedAnchor + } + }); + } else if (modal == 'asset') { + setModalState({ + ...modalState, + assetModal: { + visible: true, + selectedAsset: modalState.assetModal.selectedAsset + } + }); + } + } + + const handleClose = (modal: string) => { + if (modal == 'anchor') { + setModalState({ + ...modalState, + anchorModal: { + visible: false, + selectedAnchor: modalState.anchorModal.selectedAnchor + } + }); + } else if (modal == 'asset') { + setModalState({ + ...modalState, + assetModal: { + visible: false, + selectedAsset: modalState.anchorModal.selectedAnchor + } + }); + } + } + + + const handleSelect = (modal: string, anchor?: anchor, asset?: currency) => { + if (anchor) { + setSelectedAnchor(anchor); + setSelectedAsset(undefined); + } else if (modal) { + setSelectedAsset(asset); + } + handleClose(modal); } return ( <> - + { handleClose('anchor') }} handleSelect={(e) => handleSelect('anchor', e)} /> + { handleClose('asset') }} handleSelect={(e) => handleSelect('asset', undefined, e)} /> + - + - -
You pay:
+ +
You pay with:
- - setSelectedAnchor(anchors[0])}> + + handleOpen('anchor')}> - {selectedAnchor ? selectedAnchor.name : 'Select Anchor'} + {selectedAnchor ? selectedAnchor.name : 'Select currency'} {} @@ -199,24 +277,24 @@ function BuyComponent() {
- +
Recieve:
- - fetchCurrencies()}> + + fetchCurrencies()} disabled={!!!selectedAnchor}> - {selectedToken ? selectedToken.name : 'Select token'} + {selectedAsset ? selectedAsset.code : 'Select asset'} - {} + {} @@ -224,12 +302,15 @@ function BuyComponent() {
{address ? - ( - - {buttonText} - - ): - () + ( + {isLoading ? + : + + {buttonText} + + } + ) : + () }
diff --git a/src/components/Layout/StyledSelect.tsx b/src/components/Layout/StyledSelect.tsx index 1c937f73..acd9f791 100644 --- a/src/components/Layout/StyledSelect.tsx +++ b/src/components/Layout/StyledSelect.tsx @@ -6,7 +6,7 @@ import { opacify } from '../../themes/utils'; export const StyledSelect = styled(ButtonBase, { shouldForwardProp: (prop) => prop !== 'selected', })<{ - visible: boolean; + visible: boolean | string; selected?: boolean; hideInput?: boolean; disabled?: boolean; From da4fcfa697bf9e0393a9b387a9d63c54b5d9e0f9 Mon Sep 17 00:00:00 2001 From: Matias Poblete Date: Wed, 26 Jun 2024 11:08:45 -0400 Subject: [PATCH 17/23] =?UTF-8?q?=E2=9C=A8Add=20deposit=20status=20modal?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Buy/BuyComponent.tsx | 202 +++++++++++++++++++++----- src/components/Buy/BuyStatusModal.tsx | 158 ++++++++++++++++++++ 2 files changed, 324 insertions(+), 36 deletions(-) create mode 100644 src/components/Buy/BuyStatusModal.tsx diff --git a/src/components/Buy/BuyComponent.tsx b/src/components/Buy/BuyComponent.tsx index e1262d9e..c0a8a434 100644 --- a/src/components/Buy/BuyComponent.tsx +++ b/src/components/Buy/BuyComponent.tsx @@ -15,7 +15,7 @@ import { BodyPrimary } from 'components/Text' import { getChallengeTransaction, submitChallengeTransaction } from 'functions/buy/sep10Auth/stellarAuth' import { initInteractiveDepositFlow } from 'functions/buy/sep24Deposit/InteractiveDeposit' import { getCurrencies } from 'functions/buy/SEP-1' - +import BuyStatusModal from './BuyStatusModal' export interface anchor { name: string @@ -78,6 +78,80 @@ function BuyComponent() { } }); + const [statusModalState, setStatusModalState] = useState<{ + isOpen: boolean, + status: { + activeStep: number, + trustline: boolean, + trustlineError: string, + settingTrustline: boolean, + depositError: string, + }, + }>({ + isOpen: false, + status: { + activeStep: 0, + trustline: false, + trustlineError: '', + settingTrustline: false, + depositError: '', + }, + }); + + const handleNextStep = () => { + setStatusModalState({ + isOpen: true, + status: { + ...statusModalState.status, + activeStep: ++statusModalState.status.activeStep, + }, + }); + } + + const handlePrevStep = () => { + if (statusModalState.status.activeStep == 0) return; + setStatusModalState({ + isOpen: true, + status: { + ...statusModalState.status, + activeStep: statusModalState.status.activeStep - 1, + settingTrustline: false, + }, + }); + } + + const handleCloseStatusModal = () => { + setStatusModalState({ + isOpen: false, + status: { + activeStep: 0, + trustline: false, + trustlineError: '', + settingTrustline: false, + depositError: '', + }, + }); + } + + const openModal = () => { + setStatusModalState({ + isOpen: true, + status: { + ...statusModalState.status, + }, + }); + } + + const setDepositError = (error: string) => { + setStatusModalState({ + isOpen: true, + status: { + ...statusModalState.status, + depositError: error, + }, + }); + } + const checkTrustline = async () => { if(address){ setIsLoading(true); @@ -94,6 +168,7 @@ function BuyComponent() { } } + const sign = async (txn: any) => { const signedTransaction = await sorobanContext?.activeConnector?.signTransaction(txn, { networkPassphrase: activeChain?.networkPassphrase, @@ -104,40 +179,56 @@ function BuyComponent() { } const InitDeposit = async (homeDomain: string) => { - const { transaction } = await getChallengeTransaction({ - publicKey: address! && address, - homeDomain: homeDomain - }); - const signedTransaction = await sign(transaction) - const submittedTransaction = await submitChallengeTransaction({ - transactionXDR: signedTransaction, - homeDomain: homeDomain - }); - const { url } = await initInteractiveDepositFlow({ - authToken: submittedTransaction, - homeDomain: homeDomain, - urlFields: { - asset_code: selectedAsset?.code, - asset_issuer: selectedAsset?.issuer - } - }); + try { + openModal(); + const { transaction } = await getChallengeTransaction({ + publicKey: address! && address, + homeDomain: homeDomain + }); + const signedTransaction = await sign(transaction) + const submittedTransaction = await submitChallengeTransaction({ + transactionXDR: signedTransaction, + homeDomain: homeDomain + }); - const interactiveUrl = `${url}&callback=postMessage` - let popup = window.open(interactiveUrl, 'interactiveDeposit', 'width=450,height=750'); + handleNextStep(); - if (!popup) { - alert( - "Popups are blocked. You’ll need to enable popups for this demo to work", - ); - console.error( - "Popups are blocked. You’ll need to enable popups for this demo to work", - ); - } + const { url } = await initInteractiveDepositFlow({ + authToken: submittedTransaction, + homeDomain: homeDomain, + urlFields: { + asset_code: selectedAsset?.code, + asset_issuer: selectedAsset?.issuer + } + }); - popup?.focus(); + const interactiveUrl = `${url}&callback=postMessage` + setTimeout(() => { + handleNextStep(); + }, 20000); - } + let popup = window.open(interactiveUrl, 'interactiveDeposit', 'width=450,height=750'); + if (!popup) { + alert( + "Popups are blocked. You’ll need to enable popups for this demo to work", + ); + console.error( + "Popups are blocked. You’ll need to enable popups for this demo to work", + ); + throw new Error("Popups are blocked. You’ll need to enable popups for this demo to work",) + } + popup?.focus(); + setTimeout(() => { + popup?.close() + }, 35000); + + } catch (error: any) { + setDepositError(error.toString()); + console.error(error); + setIsLoading(false); + } + } const buy = async () => { if (!selectedAsset || !selectedAnchor) { @@ -148,6 +239,15 @@ function BuyComponent() { if (needTrustline) { try { setIsLoading(true); + setStatusModalState({ + isOpen: true, + status: { + ...statusModalState.status, + trustline: true, + trustlineError: '', + settingTrustline: true, + }, + }); const res = await setTrustline({ tokenSymbol: selectedAsset?.code!, tokenAdmin: selectedAsset?.issuer!, @@ -156,18 +256,28 @@ function BuyComponent() { if (res === undefined) throw new Error('The response is undefined'); await checkTrustline(); console.log('trustline set'); - } catch (error) { + handleCloseStatusModal(); + } catch (error: any) { console.log('error setting trustline'); + setStatusModalState({ + isOpen: true, + status: { + ...statusModalState.status, + trustlineError: error.toString(), + }, + }); console.error(error); setNeedTrustline(true); setIsLoading(false); + handleCloseStatusModal(); } } else { try { setIsLoading(true); InitDeposit(selectedAnchor?.home_domain!) setIsLoading(false); - } catch (error) { + } catch (error: any) { + setDepositError(error.toString()); console.error(error); setIsLoading(false); } @@ -240,7 +350,6 @@ function BuyComponent() { } } - const handleSelect = (modal: string, anchor?: anchor, asset?: currency) => { if (anchor) { setSelectedAnchor(anchor); @@ -253,8 +362,26 @@ function BuyComponent() { return ( <> - { handleClose('anchor') }} handleSelect={(e) => handleSelect('anchor', e)} /> - { handleClose('asset') }} handleSelect={(e) => handleSelect('asset', undefined, e)} /> + + { handleClose('anchor') }} + handleSelect={(e) => handleSelect('anchor', e)} /> + { handleClose('asset') }} + handleSelect={(e) => handleSelect('asset', undefined, e)} /> @@ -263,7 +390,10 @@ function BuyComponent() {
You pay with:
- handleOpen('anchor')}> + handleOpen('anchor')}> ` + display: flex; + flex-direction: column; + gap: 24px; + font-family: Inter; + text-align: ${({ isMobile }) => (isMobile ? 'center' : 'left')}; +`; + + +function BuyStatusModal({ + isOpen, + activeStep, + handleNext, + handlePrev, + handleClose, + trustline, + trustlineError, + settingTrustline, + depositError +}: { + isOpen: boolean, + activeStep: number, + handleNext: () => void, + handlePrev: () => void, + handleClose: () => void, + trustline?: boolean, + trustlineError?: string, + settingTrustline?: boolean, + depositError?: string, +} +) { + const theme = useTheme() + + return ( + + <> + + + + {trustline ? ( + <> + Setting up trustline + + Setting up trustline is required to buy this token. This will allow you to receive the token after the purchase. + + {settingTrustline && (trustlineError == '') ? + <> + + Waiting for transaction completed + + + + + + : ( + <> + + + + {trustlineError} + + + + + )} + + ) : + ((depositError === '') ? ( + + + Requesting authorization + + + Please, confirm the transaction in your wallet to allow Soroswap send your addres to anchor. + + + + + + + + Fill the interactive deposit + + + Please, fill the requested information in the new window and wait for the deposit + + + + + + + + Await for the deposit: + + + Everything is handled on our end. Please relax and take a break. Your funds should update automatically once the anchor completes the deposit. + + + + + + This process may take several minutes. Please, be patient. + + + + + + ) : ( + <> + + + + {depositError} + + + + + )) + } + + + + + + ) +} + +export default BuyStatusModal \ No newline at end of file From d1db5967c4e53bd1e54c96c767604033e7c4fe4c Mon Sep 17 00:00:00 2001 From: Matias Poblete Date: Wed, 26 Jun 2024 11:43:34 -0400 Subject: [PATCH 18/23] =?UTF-8?q?=E2=9C=A8Add=20events=20listening=20to=20?= =?UTF-8?q?interactive=20deposit?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Buy/BuyComponent.tsx | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src/components/Buy/BuyComponent.tsx b/src/components/Buy/BuyComponent.tsx index c0a8a434..611c4def 100644 --- a/src/components/Buy/BuyComponent.tsx +++ b/src/components/Buy/BuyComponent.tsx @@ -204,10 +204,6 @@ function BuyComponent() { const interactiveUrl = `${url}&callback=postMessage` - setTimeout(() => { - handleNextStep(); - }, 20000); - let popup = window.open(interactiveUrl, 'interactiveDeposit', 'width=450,height=750'); if (!popup) { alert( @@ -219,10 +215,21 @@ function BuyComponent() { throw new Error("Popups are blocked. You’ll need to enable popups for this demo to work",) } popup?.focus(); - setTimeout(() => { - popup?.close() - }, 35000); - + window.addEventListener('message', (event): void => { + if (event.origin.includes(homeDomain)) { + console.log(event) + popup?.close() + handleNextStep(); + } + }) + function checkPopupClosed() { + if (popup?.closed) { + setDepositError('The popup was closed before submitting the transaction') + // Limpia el intervalo si el popup está cerrado + clearInterval(popupCheckInterval); + } + } + let popupCheckInterval = setInterval(checkPopupClosed, 500); } catch (error: any) { setDepositError(error.toString()); console.error(error); From 9bd5adf35be19021283e4e0f97af2f949b984a58 Mon Sep 17 00:00:00 2001 From: Matias Poblete Date: Mon, 8 Jul 2024 08:38:34 -0400 Subject: [PATCH 19/23] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Remove=20console.log?= =?UTF-8?q?=20statements=20and=20add=20error=20handling=20in=20SEP-1=20fun?= =?UTF-8?q?ctions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Buy/BuyComponent.tsx | 1 - src/functions/buy/SEP-1.ts | 9 +++++++-- src/functions/buy/sep10Auth/stellarAuth.ts | 3 ++- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/components/Buy/BuyComponent.tsx b/src/components/Buy/BuyComponent.tsx index 611c4def..bd115f3d 100644 --- a/src/components/Buy/BuyComponent.tsx +++ b/src/components/Buy/BuyComponent.tsx @@ -217,7 +217,6 @@ function BuyComponent() { popup?.focus(); window.addEventListener('message', (event): void => { if (event.origin.includes(homeDomain)) { - console.log(event) popup?.close() handleNextStep(); } diff --git a/src/functions/buy/SEP-1.ts b/src/functions/buy/SEP-1.ts index 8196d232..ca4b1c2a 100644 --- a/src/functions/buy/SEP-1.ts +++ b/src/functions/buy/SEP-1.ts @@ -8,31 +8,36 @@ export enum Anchors { export async function getStellarToml(home_domain: string) { const formattedDomain = home_domain.replace(/^https?:\/\//, ''); const tomlResponse = await axios.get(`https://${formattedDomain}/.well-known/stellar.toml`); - const parsedResponse = toml.parse(tomlResponse.data) + const parsedResponse = toml.parse(tomlResponse.data); return parsedResponse; } export async function getAuthUrl(home_domain: string) { + if(!home_domain) return const toml = await getStellarToml(home_domain); return toml[TomlFields.WEB_AUTH_ENDPOINT]; } export async function getKycUrl(home_domain: string) { + if(!home_domain) return const toml = await getStellarToml(home_domain); return toml[TomlFields.WEB_AUTH_ENDPOINT]; } export async function getTransferServerUrl(home_domain: string) { + if(!home_domain) return const toml = await getStellarToml(home_domain); return toml[TomlFields.TRANSFER_SERVER_SEP0024]; } export async function getDepositServerUrl(home_domain: string) { + if(!home_domain) return const toml = await getStellarToml(home_domain); return toml[TomlFields.TRANSFER_SERVER_SEP0024]; } export async function getCurrencies(home_domain: string){ + if(!home_domain) return const toml = await getStellarToml(home_domain); return toml[TomlFields.CURRENCIES]; -} \ No newline at end of file +} diff --git a/src/functions/buy/sep10Auth/stellarAuth.ts b/src/functions/buy/sep10Auth/stellarAuth.ts index 2a4584a7..bc627a55 100644 --- a/src/functions/buy/sep10Auth/stellarAuth.ts +++ b/src/functions/buy/sep10Auth/stellarAuth.ts @@ -29,11 +29,12 @@ export async function getChallengeTransaction({ // Possible parameters are `account`, `memo`, `home_domain`, and // `client_domain`. For our purposes, we only supply `account`. account: publicKey, + /* memo: '1', */ +/* client_domain: 'lobstr.co', */ })}` ).catch((e)=>{ console.log(e) }) - console.log(res) let json = await res?.json() // Validate the challenge transaction meets all the requirements for SEP-10 validateChallengeTransaction({ From 36a8fad861022430deacca3e2006cf831482cc84 Mon Sep 17 00:00:00 2001 From: Matias Poblete Date: Mon, 8 Jul 2024 11:08:11 -0400 Subject: [PATCH 20/23] =?UTF-8?q?=F0=9F=9A=A7Merge=20to=20main?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 + src/components/Buy/BuyComponent.tsx | 10 ++- src/components/Buy/BuyModal.tsx | 2 +- src/components/Buy/BuyStatusModal.tsx | 5 +- src/components/Layout/StyledSelect.tsx | 2 +- src/components/Layout/StyledWrapper.tsx | 2 +- src/components/Swap/SwapHeader.tsx | 7 +- yarn.lock | 88 +++++++++++++++++++++++++ 8 files changed, 107 insertions(+), 11 deletions(-) diff --git a/.gitignore b/.gitignore index 1228ac9d..f686d3ee 100644 --- a/.gitignore +++ b/.gitignore @@ -115,3 +115,5 @@ dist # TernJS port file .tern-port + +certificates \ No newline at end of file diff --git a/src/components/Buy/BuyComponent.tsx b/src/components/Buy/BuyComponent.tsx index bd115f3d..9aedd5c8 100644 --- a/src/components/Buy/BuyComponent.tsx +++ b/src/components/Buy/BuyComponent.tsx @@ -45,7 +45,12 @@ const anchors: anchor[] = [ { name: 'MyKobo', home_domain: 'mykobo.co', - currency: 'EURC' + currency: 'EUR' + }, + { + name: 'Anclap', + home_domain: 'api.anclap.com', + currency: 'ARS/PEN' }, ]; @@ -198,7 +203,8 @@ function BuyComponent() { homeDomain: homeDomain, urlFields: { asset_code: selectedAsset?.code, - asset_issuer: selectedAsset?.issuer + asset_issuer: selectedAsset?.issuer, + account: address } }); diff --git a/src/components/Buy/BuyModal.tsx b/src/components/Buy/BuyModal.tsx index 67e20749..581a5b8a 100644 --- a/src/components/Buy/BuyModal.tsx +++ b/src/components/Buy/BuyModal.tsx @@ -1,6 +1,6 @@ import { useEffect } from 'react' import ModalBox from 'components/Modals/ModalBox' -import { styled } from '@mui/material/styles'; +import { styled } from 'soroswap-ui'; import { Box, Container, Modal, useMediaQuery } from '@mui/material'; import { BodyPrimary, BodySmall, Caption } from 'components/Text'; import { anchor, currency } from './BuyComponent' diff --git a/src/components/Buy/BuyStatusModal.tsx b/src/components/Buy/BuyStatusModal.tsx index acb38cf8..83501a3e 100644 --- a/src/components/Buy/BuyStatusModal.tsx +++ b/src/components/Buy/BuyStatusModal.tsx @@ -1,7 +1,7 @@ import { useEffect } from 'react' import ModalBox from 'components/Modals/ModalBox' -import { styled } from '@mui/material/styles'; -import { Box, Button, CircularProgress, Container, Modal, Typography, useMediaQuery } from '@mui/material'; +import { styled } from 'soroswap-ui'; +import { Box, Button, CircularProgress, Container, Modal, Typography, useMediaQuery } from 'soroswap-ui'; import { BodyPrimary, BodySmall, Caption } from 'components/Text'; import { Step, StepContent, StepLabel, Stepper } from '@mui/material'; import { AlertTriangle, CheckCircle } from 'react-feather' @@ -13,6 +13,7 @@ const ContentWrapper = styled('div') <{ isMobile: boolean }>` gap: 24px; font-family: Inter; text-align: ${({ isMobile }) => (isMobile ? 'center' : 'left')}; + bacjground-color: red; `; diff --git a/src/components/Layout/StyledSelect.tsx b/src/components/Layout/StyledSelect.tsx index acd9f791..ef9a9cd3 100644 --- a/src/components/Layout/StyledSelect.tsx +++ b/src/components/Layout/StyledSelect.tsx @@ -1,5 +1,5 @@ // import { Trans } from '@lingui/macro' -import { ButtonBase, styled, useMediaQuery, useTheme } from '@mui/material'; +import { ButtonBase, styled, useMediaQuery, useTheme } from 'soroswap-ui'; import { opacify } from '../../themes/utils'; diff --git a/src/components/Layout/StyledWrapper.tsx b/src/components/Layout/StyledWrapper.tsx index 205596cb..d35c5684 100644 --- a/src/components/Layout/StyledWrapper.tsx +++ b/src/components/Layout/StyledWrapper.tsx @@ -1,4 +1,4 @@ -import { styled } from '@mui/material/styles'; +import { styled } from 'soroswap-ui'; import { opacify } from 'themes/utils'; export const StyledWrapper = styled('main')` diff --git a/src/components/Swap/SwapHeader.tsx b/src/components/Swap/SwapHeader.tsx index b9f39657..b3dd29e1 100644 --- a/src/components/Swap/SwapHeader.tsx +++ b/src/components/Swap/SwapHeader.tsx @@ -1,8 +1,9 @@ // import { Trans } from '@lingui/macro' //This is for localization and translation on all languages -import { styled, useTheme } from 'soroswap-ui'; +import { Link, styled, useTheme } from 'soroswap-ui'; import { RowBetween, RowFixed } from '../Row'; import SettingsTab from '../Settings/index'; import { useMediaQuery } from 'soroswap-ui'; +import { useState } from 'react'; const StyledSwapHeader = styled(RowBetween)(({ theme }) => ({ marginBottom: 10, color: theme.palette.secondary.main, @@ -46,9 +47,7 @@ export default function SwapHeader({ const isMobile = useMediaQuery(theme.breakpoints.down('md')); const [activeAction, setActiveAction] = useState<'swap' | 'buy'>('swap'); const href = window.location.pathname; - useEffect(() => { - setActiveAction(href === '/swap' || href === '/' ? 'swap' : 'buy'); - }, [href]) + return ( diff --git a/yarn.lock b/yarn.lock index 4db6f898..3b6949ac 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1902,6 +1902,69 @@ resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.7.tgz#fe973311a5c6267846aa131bc72e96c5d40d2b30" integrity sha512-PPNYBslrLNNUQ/Yad37MHYsNQtK67EhWb6WtSvNLLPo7SdVZgkUjD6Dg+5On7zNwmskf8OX7I7Nx5oN+MIWE0g== +"@sentry/browser@^6.13.2": + version "6.19.7" + resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-6.19.7.tgz#a40b6b72d911b5f1ed70ed3b4e7d4d4e625c0b5f" + integrity sha512-oDbklp4O3MtAM4mtuwyZLrgO1qDVYIujzNJQzXmi9YzymJCuzMLSRDvhY83NNDCRxf0pds4DShgYeZdbSyKraA== + dependencies: + "@sentry/core" "6.19.7" + "@sentry/types" "6.19.7" + "@sentry/utils" "6.19.7" + tslib "^1.9.3" + +"@sentry/core@6.19.7": + version "6.19.7" + resolved "https://registry.yarnpkg.com/@sentry/core/-/core-6.19.7.tgz#156aaa56dd7fad8c89c145be6ad7a4f7209f9785" + integrity sha512-tOfZ/umqB2AcHPGbIrsFLcvApdTm9ggpi/kQZFkej7kMphjT+SGBiQfYtjyg9jcRW+ilAR4JXC9BGKsdEQ+8Vw== + dependencies: + "@sentry/hub" "6.19.7" + "@sentry/minimal" "6.19.7" + "@sentry/types" "6.19.7" + "@sentry/utils" "6.19.7" + tslib "^1.9.3" + +"@sentry/hub@6.19.7": + version "6.19.7" + resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-6.19.7.tgz#58ad7776bbd31e9596a8ec46365b45cd8b9cfd11" + integrity sha512-y3OtbYFAqKHCWezF0EGGr5lcyI2KbaXW2Ik7Xp8Mu9TxbSTuwTe4rTntwg8ngPjUQU3SUHzgjqVB8qjiGqFXCA== + dependencies: + "@sentry/types" "6.19.7" + "@sentry/utils" "6.19.7" + tslib "^1.9.3" + +"@sentry/minimal@6.19.7": + version "6.19.7" + resolved "https://registry.yarnpkg.com/@sentry/minimal/-/minimal-6.19.7.tgz#b3ee46d6abef9ef3dd4837ebcb6bdfd01b9aa7b4" + integrity sha512-wcYmSJOdvk6VAPx8IcmZgN08XTXRwRtB1aOLZm+MVHjIZIhHoBGZJYTVQS/BWjldsamj2cX3YGbGXNunaCfYJQ== + dependencies: + "@sentry/hub" "6.19.7" + "@sentry/types" "6.19.7" + tslib "^1.9.3" + +"@sentry/tracing@^6.13.2": + version "6.19.7" + resolved "https://registry.yarnpkg.com/@sentry/tracing/-/tracing-6.19.7.tgz#54bb99ed5705931cd33caf71da347af769f02a4c" + integrity sha512-ol4TupNnv9Zd+bZei7B6Ygnr9N3Gp1PUrNI761QSlHtPC25xXC5ssSD3GMhBgyQrcvpuRcCFHVNNM97tN5cZiA== + dependencies: + "@sentry/hub" "6.19.7" + "@sentry/minimal" "6.19.7" + "@sentry/types" "6.19.7" + "@sentry/utils" "6.19.7" + tslib "^1.9.3" + +"@sentry/types@6.19.7": + version "6.19.7" + resolved "https://registry.yarnpkg.com/@sentry/types/-/types-6.19.7.tgz#c6b337912e588083fc2896eb012526cf7cfec7c7" + integrity sha512-jH84pDYE+hHIbVnab3Hr+ZXr1v8QABfhx39KknxqKWr2l0oEItzepV0URvbEhB446lk/S/59230dlUUIBGsXbg== + +"@sentry/utils@6.19.7": + version "6.19.7" + resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-6.19.7.tgz#6edd739f8185fd71afe49cbe351c1bbf5e7b7c79" + integrity sha512-z95ECmE3i9pbWoXQrD/7PgkBAzJYR+iXtPuTkpBjDKs86O3mT+PXOT3BAn79w2wkn7/i3vOGD2xVr1uiMl26dA== + dependencies: + "@sentry/types" "6.19.7" + tslib "^1.9.3" + "@sigstore/bundle@^2.3.2": version "2.3.2" resolved "https://registry.yarnpkg.com/@sigstore/bundle/-/bundle-2.3.2.tgz#ad4dbb95d665405fd4a7a02c8a073dbd01e4e95e" @@ -2108,6 +2171,16 @@ resolved "https://registry.yarnpkg.com/@stellar/freighter-api/-/freighter-api-1.7.1.tgz#d62b432abc7e0140a6025cd672455ecee7b3199a" integrity sha512-XvPO+XgEbkeP0VhP0U1edOkds+rGS28+y8GRGbCVXeZ9ZslbWqRFQoETAdX8IXGuykk2ib/aPokiLc5ZaWYP7w== +"@stellar/frontend-helpers@^2.1.4": + version "2.1.4" + resolved "https://registry.yarnpkg.com/@stellar/frontend-helpers/-/frontend-helpers-2.1.4.tgz#f3d1b9daaeee8e9cfb605cc23305dfde80ec4971" + integrity sha512-snKsHWDiS51zcEtysoxJ1qPzkhEAqd9la7QdZoFd19DYpf6q1gxzUN+bBAKP5D/SANQE2iMH7oWc/tP7ru3a2A== + dependencies: + "@sentry/browser" "^6.13.2" + "@sentry/tracing" "^6.13.2" + lodash.throttle "^4.1.1" + typescript "^4.4.3" + "@stellar/js-xdr@^3.0.1", "@stellar/js-xdr@^3.1.1": version "3.1.1" resolved "https://registry.yarnpkg.com/@stellar/js-xdr/-/js-xdr-3.1.1.tgz#be0ff90c8a861d6e1101bca130fa20e74d5599bb" @@ -5911,6 +5984,11 @@ lodash.takeright@^4.1.1: resolved "https://registry.yarnpkg.com/lodash.takeright/-/lodash.takeright-4.1.1.tgz#c98d84aa9b80e4d2ff675335a62e02e9a65bb210" integrity sha512-/I41i2h8VkHtv3PYD8z1P4dkLIco5Z3z35hT/FJl18AxwSdifcATaaiBOxuQOT3T/F1qfRTct3VWMFSj1xCtAw== +lodash.throttle@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/lodash.throttle/-/lodash.throttle-4.1.1.tgz#c23e91b710242ac70c37f1e1cda9274cc39bf2f4" + integrity sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ== + lodash@^4.17.14, lodash@^4.17.21: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" @@ -8345,6 +8423,11 @@ tslib@1.9.3: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286" integrity sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ== +tslib@^1.9.3: + version "1.14.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" + integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== + tslib@^2.0.1, tslib@^2.1.0, tslib@^2.4.0, tslib@^2.5.3, tslib@^2.6.2: version "2.6.3" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.3.tgz#0438f810ad7a9edcde7a241c3d80db693c8cbfe0" @@ -8469,6 +8552,11 @@ typescript@5.3.3: resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.3.3.tgz#b3ce6ba258e72e6305ba66f5c9b452aaee3ffe37" integrity sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw== +typescript@^4.4.3: + version "4.9.5" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a" + integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g== + unbox-primitive@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz#29032021057d5e6cdbd08c5129c226dff8ed6f9e" From 5d0261321d6c7d2ed6d3740d58b26a97fa8a0bfe Mon Sep 17 00:00:00 2001 From: Matias Poblete Date: Thu, 15 Aug 2024 11:30:00 -0400 Subject: [PATCH 21/23] =?UTF-8?q?=F0=9F=9A=A7Fix=20display=20bugs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Buy/BuyComponent.tsx | 79 ++++++++++++++++++++------- src/components/Buy/BuyStatusModal.tsx | 20 ++++--- 2 files changed, 73 insertions(+), 26 deletions(-) diff --git a/src/components/Buy/BuyComponent.tsx b/src/components/Buy/BuyComponent.tsx index 9aedd5c8..e21765fb 100644 --- a/src/components/Buy/BuyComponent.tsx +++ b/src/components/Buy/BuyComponent.tsx @@ -1,5 +1,5 @@ import React, { useEffect, useState } from 'react' -import { CircularProgress } from '@mui/material' +import { CircularProgress, Skeleton } from '@mui/material' import { setTrustline } from '@soroban-react/contracts' import { useSorobanReact } from '@soroban-react/core' import BuyModal from './BuyModal' @@ -49,7 +49,7 @@ const anchors: anchor[] = [ }, { name: 'Anclap', - home_domain: 'api.anclap.com', + home_domain: 'api-stage.anclap.ar', currency: 'ARS/PEN' }, ]; @@ -67,19 +67,23 @@ function BuyComponent() { anchorModal: { visible: boolean, selectedAnchor: anchor | undefined, + isLoading: boolean }, assetModal: { visible: boolean, selectedAsset: anchor | undefined, + isLoading: boolean }, }>({ anchorModal: { visible: false, selectedAnchor: undefined, + isLoading: false, }, assetModal: { visible: false, selectedAsset: undefined, + isLoading: false, } }); @@ -234,7 +238,7 @@ function BuyComponent() { clearInterval(popupCheckInterval); } } - let popupCheckInterval = setInterval(checkPopupClosed, 500); + let popupCheckInterval = setInterval(checkPopupClosed, 200); } catch (error: any) { setDepositError(error.toString()); console.error(error); @@ -316,9 +320,23 @@ function BuyComponent() { const fetchCurrencies = async () => { setIsLoading(true); + setModalState({ + ...modalState, + assetModal: { + ...modalState.assetModal, + isLoading: true, + } + }) const currencies = await getCurrencies(selectedAnchor?.home_domain!); setCurrencies(currencies); setIsLoading(false); + setModalState({ + ...modalState, + assetModal: { + ...modalState.assetModal, + isLoading: false, + } + }) handleOpen('asset'); } @@ -327,16 +345,18 @@ function BuyComponent() { setModalState({ ...modalState, anchorModal: { + ...modalState.anchorModal, visible: true, - selectedAnchor: modalState.anchorModal.selectedAnchor + selectedAnchor: modalState.anchorModal.selectedAnchor, } }); } else if (modal == 'asset') { setModalState({ ...modalState, assetModal: { + ...modalState.assetModal, visible: true, - selectedAsset: modalState.assetModal.selectedAsset + selectedAsset: modalState.assetModal.selectedAsset, } }); } @@ -347,16 +367,18 @@ function BuyComponent() { setModalState({ ...modalState, anchorModal: { + ...modalState.anchorModal, visible: false, - selectedAnchor: modalState.anchorModal.selectedAnchor + selectedAnchor: modalState.anchorModal.selectedAnchor, } }); } else if (modal == 'asset') { setModalState({ ...modalState, assetModal: { + ...modalState.assetModal, visible: false, - selectedAsset: modalState.anchorModal.selectedAnchor + selectedAsset: modalState.anchorModal.selectedAnchor, } }); } @@ -425,19 +447,38 @@ function BuyComponent() {
Recieve:
- fetchCurrencies()} disabled={!!!selectedAnchor}> - + fetchCurrencies()} disabled={!!!selectedAnchor}> + + {selectedAsset ? selectedAsset.code : 'Select asset'} + + {} + + + )} + {!modalState.assetModal.isLoading && ( + fetchCurrencies()} disabled={!!!selectedAnchor}> + - {selectedAsset ? selectedAsset.code : 'Select asset'} - - {} - + > + {selectedAsset ? selectedAsset.code : 'Select asset'} + + {} + + )}
diff --git a/src/components/Buy/BuyStatusModal.tsx b/src/components/Buy/BuyStatusModal.tsx index 83501a3e..9f8ee175 100644 --- a/src/components/Buy/BuyStatusModal.tsx +++ b/src/components/Buy/BuyStatusModal.tsx @@ -86,11 +86,13 @@ function BuyStatusModal({ ) : ((depositError === '') ? ( - - Requesting authorization + + + Requesting authorization + - Please, confirm the transaction in your wallet to allow Soroswap send your addres to anchor. + Please, confirm the transaction in your wallet to allow Soroswap send your addres to anchor. @@ -98,10 +100,12 @@ function BuyStatusModal({ - Fill the interactive deposit + + Fill the interactive deposit + - Please, fill the requested information in the new window and wait for the deposit + Please, fill the requested information in the new window and wait for the deposit @@ -109,10 +113,12 @@ function BuyStatusModal({ - Await for the deposit: + + Await for the deposit: + - Everything is handled on our end. Please relax and take a break. Your funds should update automatically once the anchor completes the deposit. + Everything is handled on our end. Please relax and take a break. Your funds should update automatically once the anchor completes the deposit. From 5c34d396d869a1ca63a4e824113992786ecdb77b Mon Sep 17 00:00:00 2001 From: Matias Poblete Date: Mon, 19 Aug 2024 11:38:04 -0400 Subject: [PATCH 22/23] =?UTF-8?q?=F0=9F=9A=A7improving=20styles?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Buy/BuyStatusModal.tsx | 7 +++++-- src/components/Swap/SwapHeader.tsx | 1 + 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/components/Buy/BuyStatusModal.tsx b/src/components/Buy/BuyStatusModal.tsx index 9f8ee175..d9a22367 100644 --- a/src/components/Buy/BuyStatusModal.tsx +++ b/src/components/Buy/BuyStatusModal.tsx @@ -13,9 +13,12 @@ const ContentWrapper = styled('div') <{ isMobile: boolean }>` gap: 24px; font-family: Inter; text-align: ${({ isMobile }) => (isMobile ? 'center' : 'left')}; - bacjground-color: red; `; +const stepperStyle = { + +}; + function BuyStatusModal({ isOpen, @@ -86,7 +89,7 @@ function BuyStatusModal({ ) : ((depositError === '') ? ( - + Requesting authorization diff --git a/src/components/Swap/SwapHeader.tsx b/src/components/Swap/SwapHeader.tsx index b3dd29e1..9b3f8658 100644 --- a/src/components/Swap/SwapHeader.tsx +++ b/src/components/Swap/SwapHeader.tsx @@ -27,6 +27,7 @@ const SwapLink = styled(Link, { font-size: 20px; font-weight: 600; line-height: 140%; + text-decoration: none; `; export default function SwapHeader({ From 007e1822bc6990c1d5138f79295d4864974ce013 Mon Sep 17 00:00:00 2001 From: Matias Poblete <86752543+MattPoblete@users.noreply.github.com> Date: Thu, 22 Aug 2024 09:09:16 -0400 Subject: [PATCH 23/23] =?UTF-8?q?=F0=9F=92=84Improved=20UI=20in=20buy=20se?= =?UTF-8?q?ction?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Buy/BuyComponent.tsx | 65 +++++++++------ src/components/Buy/BuyModal.tsx | 4 +- src/components/Buy/BuyStatusModal.tsx | 113 ++++++++++++++++++++------ src/components/Swap/SwapHeader.tsx | 10 ++- 4 files changed, 142 insertions(+), 50 deletions(-) diff --git a/src/components/Buy/BuyComponent.tsx b/src/components/Buy/BuyComponent.tsx index e21765fb..93834090 100644 --- a/src/components/Buy/BuyComponent.tsx +++ b/src/components/Buy/BuyComponent.tsx @@ -11,7 +11,7 @@ import { StyledSelect } from 'components/Layout/StyledSelect' import { RowFixed } from 'components/Row' import SwapHeader from 'components/Swap/SwapHeader' import { SwapSection } from 'components/Swap/SwapComponent' -import { BodyPrimary } from 'components/Text' +import { BodyPrimary, ButtonText } from 'components/Text' import { getChallengeTransaction, submitChallengeTransaction } from 'functions/buy/sep10Auth/stellarAuth' import { initInteractiveDepositFlow } from 'functions/buy/sep24Deposit/InteractiveDeposit' import { getCurrencies } from 'functions/buy/SEP-1' @@ -31,28 +31,41 @@ export interface currency { status?: string; }; -const anchors: anchor[] = [ +const anchors = [ { - name: 'Stellar TestAnchor 1', - home_domain: 'testanchor.stellar.org', - currency: 'SRT' - }, - { - name: 'MoneyGram', - home_domain: 'stellar.moneygram.com', - currency: 'USD' - }, - { - name: 'MyKobo', - home_domain: 'mykobo.co', - currency: 'EUR' + network: 'testnet', + anchors: [{ + name: 'Stellar TestAnchor 1', + home_domain: 'testanchor.stellar.org', + currency: 'SRT' + }, + { + name: 'MoneyGram', + home_domain: 'stellar.moneygram.com', + currency: 'USD' + }, + { + name: 'MyKobo', + home_domain: 'mykobo.co', + currency: 'EUR' + }, + { + name: 'Anclap', + home_domain: 'api-stage.anclap.ar', + currency: 'ARS/PEN' + },] }, { - name: 'Anclap', - home_domain: 'api-stage.anclap.ar', - currency: 'ARS/PEN' - }, -]; + network: 'mainnet', + anchors: [ + { + name: 'Anclap', + home_domain: 'api-stage.anclap.ar', + currency: 'ARS/PEN' + } + ] + } +] function BuyComponent() { const sorobanContext = useSorobanReact(); @@ -300,6 +313,10 @@ function BuyComponent() { } } + const getAnchors = () => { + return anchors.find((anchor) => anchor.network === activeChain?.id)?.anchors; + } + useEffect(() => { checkTrustline(); }, [selectedAsset, address, activeChain, selectedAnchor, activeConnector]) @@ -408,7 +425,7 @@ function BuyComponent() { depositError={statusModalState.status.depositError} /> { handleClose('anchor') }} handleSelect={(e) => handleSelect('anchor', e)} /> {modalState.assetModal.isLoading && ( - + fetchCurrencies()} disabled={!!!selectedAnchor}> : - {buttonText} + + {buttonText} + } ) : diff --git a/src/components/Buy/BuyModal.tsx b/src/components/Buy/BuyModal.tsx index 581a5b8a..b1ac98c7 100644 --- a/src/components/Buy/BuyModal.tsx +++ b/src/components/Buy/BuyModal.tsx @@ -2,7 +2,7 @@ import { useEffect } from 'react' import ModalBox from 'components/Modals/ModalBox' import { styled } from 'soroswap-ui'; import { Box, Container, Modal, useMediaQuery } from '@mui/material'; -import { BodyPrimary, BodySmall, Caption } from 'components/Text'; +import { BodyPrimary, BodySmall, Caption, SubHeaderLarge } from 'components/Text'; import { anchor, currency } from './BuyComponent' const ContentWrapper = styled('div') <{ isMobile: boolean }>` @@ -75,7 +75,7 @@ const BuyModal = ({ - {anchors ?

Pay

:

Receive

} + {anchors ? Pay : Receive} {anchors ? Select a fiat currency to pay. : Select a crypto asset to receive}
diff --git a/src/components/Buy/BuyStatusModal.tsx b/src/components/Buy/BuyStatusModal.tsx index d9a22367..8c2e9045 100644 --- a/src/components/Buy/BuyStatusModal.tsx +++ b/src/components/Buy/BuyStatusModal.tsx @@ -1,11 +1,74 @@ -import { useEffect } from 'react' import ModalBox from 'components/Modals/ModalBox' import { styled } from 'soroswap-ui'; -import { Box, Button, CircularProgress, Container, Modal, Typography, useMediaQuery } from 'soroswap-ui'; -import { BodyPrimary, BodySmall, Caption } from 'components/Text'; -import { Step, StepContent, StepLabel, Stepper } from '@mui/material'; +import { Box, Button, CircularProgress, Container, Modal } from 'soroswap-ui'; +import { BodyPrimary, BodySmall, ButtonText, Caption, HeadlineSmall } from 'components/Text'; +import { Step, StepContent, StepIconProps, StepLabel, Stepper } from '@mui/material'; +import StepConnector, { stepConnectorClasses } from '@mui/material/StepConnector'; import { AlertTriangle, CheckCircle } from 'react-feather' import { useTheme } from '@mui/material' +import { Check } from '@mui/icons-material'; + +const StepperConnector = styled(StepConnector)(({ theme }) => ({ + [`&.${stepConnectorClasses.alternativeLabel}`]: { + top: 10, + left: 'calc(-50% + 16px)', + right: 'calc(50% + 16px)', + }, + [`&.${stepConnectorClasses.active}`]: { + [`& .${stepConnectorClasses.line}`]: { + borderColor: '#784af4', + }, + }, + [`&.${stepConnectorClasses.completed}`]: { + [`& .${stepConnectorClasses.line}`]: { + borderColor: '#784af4', + }, + }, + [`& .${stepConnectorClasses.line}`]: { + borderColor: theme.palette.mode === 'dark' ? theme.palette.grey[800] : '#eaeaf0', + borderTopWidth: 3, + borderRadius: 1, + }, +})); + +const StepperIconRoot = styled('div')<{ ownerState: { active?: boolean } }>( + ({ theme, ownerState }) => ({ + color: theme.palette.mode === 'dark' ? theme.palette.grey[700] : '#eaeaf0', + display: 'flex', + height: 22, + alignItems: 'center', + ...(ownerState.active && { + color: '#784af4', + }), + '& .StepperIcon-completedIcon': { + color: '#784af4', + zIndex: 1, + fontSize: 18, + }, + '& .StepperIcon-circle': { + width: 8, + height: 8, + borderRadius: '50%', + backgroundColor: 'currentColor', + }, + }), +); + +function StepperIcon(props: StepIconProps) { + const { active, completed, className } = props; + + return ( + + {completed ? ( + + ) : ( +
+ )} + + ); +} + + const ContentWrapper = styled('div') <{ isMobile: boolean }>` display: flex; @@ -16,7 +79,7 @@ const ContentWrapper = styled('div') <{ isMobile: boolean }>` `; const stepperStyle = { - + border: '1px solid red', }; @@ -55,7 +118,7 @@ function BuyStatusModal({ {trustline ? ( <> - Setting up trustline + Setting up trustline Setting up trustline is required to buy this token. This will allow you to receive the token after the purchase. @@ -87,41 +150,41 @@ function BuyStatusModal({ )} ) : - ((depositError === '') ? ( + ((depositError === '') ? (}> - - + + Requesting authorization - + - Please, confirm the transaction in your wallet to allow Soroswap send your addres to anchor. + Please, confirm the transaction in your wallet to allow Soroswap send your addres to anchor. - - + + Fill the interactive deposit - + - Please, fill the requested information in the new window and wait for the deposit + Please, fill the requested information in the new window and wait for the deposit - - + + Await for the deposit: - + - Everything is handled on our end. Please relax and take a break. Your funds should update automatically once the anchor completes the deposit. + Everything is handled on our end. Please relax and take a break. Your funds should update automatically once the anchor completes the deposit. @@ -134,7 +197,9 @@ function BuyStatusModal({ onClick={handleClose} sx={{ mt: 2, mr: 1 }} > - Close + + Close + @@ -143,15 +208,17 @@ function BuyStatusModal({ <> - + {depositError} - + diff --git a/src/components/Swap/SwapHeader.tsx b/src/components/Swap/SwapHeader.tsx index 9b3f8658..b434631d 100644 --- a/src/components/Swap/SwapHeader.tsx +++ b/src/components/Swap/SwapHeader.tsx @@ -3,7 +3,7 @@ import { Link, styled, useTheme } from 'soroswap-ui'; import { RowBetween, RowFixed } from '../Row'; import SettingsTab from '../Settings/index'; import { useMediaQuery } from 'soroswap-ui'; -import { useState } from 'react'; +import { useEffect, useState } from 'react'; const StyledSwapHeader = styled(RowBetween)(({ theme }) => ({ marginBottom: 10, color: theme.palette.secondary.main, @@ -49,7 +49,13 @@ export default function SwapHeader({ const [activeAction, setActiveAction] = useState<'swap' | 'buy'>('swap'); const href = window.location.pathname; - + useEffect(() => { + if (href == '/swap') { + setActiveAction('swap'); + } else if (href == '/buy') { + setActiveAction('buy'); + } + }, [href]); return (