diff --git a/README.md b/README.md index cf93a314..ca9df5e7 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ Before you begin, ensure you have met the following requirements: ## 🛠 Setting Up Soroswap 🛠 -1. Clone the Repository +### 1. Clone the Repository ```bash @@ -17,40 +17,41 @@ Before you begin, ensure you have met the following requirements: cd frontend ``` -2. Set Up Environment Variables +### 2.Set Up Environment Variables - Copy the .env.example file to create a new .env file: +Copy the .env.example file to create a new .env file: - ```bash - cp .env.local.example .env - ``` +```bash +cp .env.local.example .env +``` +Edit the `.env` file and provide the following variables: +```md +NEXT_PUBLIC_BACKEND_URL=http://localhost:8010 // If you are following the instructions in `https://github.com/soroswap/core` +NEXT_PUBLIC_SOROSWAP_BACKEND_URL=http://localhost:8000// Your local soroswap backend url +NEXT_PUBLIC_SOROSWAP_BACKEND_ENABLED= false // Enables or disables the soroswap backend +NEXT_PUBLIC_DEFAULT_NETWORK= standalone // The default network to connect +NEXT_PUBLIC_AGGREGATOR_ENABLED= false // Enables or disables the aggregator +NEXT_PUBLIC_SOROSWAP_BACKEND_API_KEY= BACKEND_API_KEY // The API key to autenthicate in the soroswap backend +NEXT_PUBLIC_TRUSTLINE_WALLET_PUBLIC_KEY= STELLAR_PUBLIC_KEY// The public key of the trustline wallet +NEXT_PUBLIC_TEST_TOKENS_ADMIN_SECRET_KEY= STELLAR_SECRET_KEY // The secret key of the test tokens admin +``` +Hints: +The `NEXT_PUBLIC_BACKEND_URL` should serve: +- the list of known tokens +- the SoroswapFactory address +- the SoroswapRouter address - Now, edit the `.env` file and provide the `NEXT_PUBLIC_BACKEND_URL`, `NEXT_PUBLIC_SOROSWAP_BACKEND_API_KEY` and `NEXT_PUBLIC_TEST_TOKENS_ADMIN_SECRET_KEY` variables. - This will tell the frontend where to look for: +The `NEXT_PUBLIC_TEST_TOKENS_ADMIN_SECRET_KEY` should be the same as the one that deployed the tokens in the `core` repository. - - the list of known tokens - - the SoroswapFactory address - - the SoroswapRouter address - - the admin key for token minting +To enable or disable features like the `Soroswap backend` or the `aggregator`, switch the `NEXT_PUBLIC_SOROSWAP_BACKEND_ENABLED` and `NEXT_PUBLIC_AGGREGATOR_ENABLED` variables to `true` or `false`. - If you are following the instructions in `https://github.com/soroswap/core` in order to deploy the smart contacts in your local environment and serve the API. you should have: - ```bash - NEXT_PUBLIC_BACKEND_URL=http://localhost:8010 - ``` - If you don't want to use the backend, you should also set the following variable: - ```bash - NEXT_PUBLIC_SOROSWAP_BACKEND_ENABLED=false - ``` - - Also, the variable `NEXT_PUBLIC_TEST_TOKENS_ADMIN_SECRET_KEY` should be the same as the one that deployed the tokens in the `core` repository. +Then, when you are ready for production, you can take Futurenet Contracts information from `https://api.soroswap.finance` and use the production env file: - If you are ready for production, you can take Futurenet Contracts information from `https://api.soroswap.finance` and just do - - ```bash - cp .env.production.example .env - ``` +```bash +cp .env.production.example .env +``` - ❗️❗️ Note that some Futurenet RPC's might not have the same version, so we recomend you to connect to a local quickstart node following the instructions in `https://github.com/soroswap/core`; and setting up your Freighter Wallet as in step 6. +> [!IMPORTANT] Note that some Futurenet RPC's might not have the same version, so we strongly recomend you to connect to a local quickstart node following the instructions in `https://github.com/soroswap/core`; and setting up your Freighter Wallet as in step 6. 1. Start Docker diff --git a/cypress/e2e/flows.test.ts b/cypress/e2e/flows.test.ts index c8826067..d6bc8184 100644 --- a/cypress/e2e/flows.test.ts +++ b/cypress/e2e/flows.test.ts @@ -133,19 +133,12 @@ describe('Select tokens & input amount', () => { describe('Input & output amount validation', () => { it('should type an input amount & wait for output amount', () => { cy.visit('/swap'); - //Select input asset -/* cy.get('[data-testid="swap__input__panel"]').within(() => { - cy.get('[data-testid="swap__token__select"]').click(); - }); - cy.get('[data-testid="currency__list__XLM"]').click(); - */ //Select output asset cy.get('[data-testid="swap__output__panel"]').within(() => { cy.get('[data-testid="swap__token__select"]').click(); }); - cy.get('[data-testid="token-search-input"]').type('ngnt'); - cy.get('[data-testid="currency__list__NGNT"]').click(); - + cy.get('[data-testid="token-search-input"]').type('usdc'); + cy.get('[data-testid="currency__list__USDC"]').click(); //Input amount cy.get('[data-testid="swap__input__panel"]').within(() => { @@ -153,7 +146,6 @@ describe('Input & output amount validation', () => { }); //await for calcs cy.wait(5000); - cy.screenshot() //Get the output amount cy.get('[data-testid="swap-output-input-panel"]').invoke('val').as('outputAmount'); //Get the input amount @@ -168,8 +160,8 @@ describe('Input & output amount validation', () => { cy.get('[data-testid="swap__input__panel"]').within(() => { cy.get('[data-testid="swap__token__select"]').click(); }); - cy.get('[data-testid="token-search-input"]').type('ngnt'); - cy.get('[data-testid="currency__list__NGNT"]').click(); + cy.get('[data-testid="token-search-input"]').type('usdc'); + cy.get('[data-testid="currency__list__USDC"]').click(); //Input amount cy.get('[data-testid="swap__output__panel"]').within(() => { cy.get('.token-amount-input').type('{backspace}'); @@ -177,16 +169,63 @@ describe('Input & output amount validation', () => { cy.get('.token-amount-input').type('1'); }); cy.wait(2500); - cy.screenshot() cy.get('[data-testid="swap-input-input-panel"]').invoke('val').then((inputAmount: any)=>{ - const belowOutput = Math.floor(parseFloat(outputAmount) * 0.9); - const aboveOutput = Math.ceil(parseFloat(outputAmount) * 1.1); + const belowOutput = Math.floor(parseFloat(outputAmount) * 0.5); + const aboveOutput = Math.ceil(parseFloat(outputAmount) * 1.5); expect(parseFloat(inputAmount)).within(belowOutput, aboveOutput) }) }) }) }); + +describe('Slippage tolerance config', ()=>{ + it('should change slippage tolerance', ()=>{ + cy.visit('/swap'); + cy.get('[data-testid="open-settings-dialog-button"]').click(); + cy.get('[data-testid="max-slippage-settings"]').click(); + cy.get('[data-testid="slippage-input"]').type('1.05'); + cy.get('[data-testid="max-slippage-settings"]').contains('1.05%'); + }) + it('Show display an alert if slippage is too high', ()=>{ + cy.visit('/swap'); + cy.get('[data-testid="open-settings-dialog-button"]').click(); + cy.get('[data-testid="max-slippage-settings"]').click(); + cy.get('[data-testid="slippage-input"]').type('10'); + cy.get('[data-testid="max-slippage-settings"]').contains('10%'); + cy.get('[data-testid="slippage-alert"]').should('exist'); + cy.get('[data-testid="slippage-alert-too-high"]').should('exist'); + }) + it('Show display an alert if slippage is too low', ()=>{ + cy.visit('/swap'); + cy.get('[data-testid="open-settings-dialog-button"]').click(); + cy.get('[data-testid="max-slippage-settings"]').click(); + cy.get('[data-testid="slippage-input"]').type('0.01'); + cy.get('[data-testid="max-slippage-settings"]').contains('0.01%'); + cy.get('[data-testid="slippage-alert"]').should('exist'); + cy.get('[data-testid="slippage-alert-too-low"]').should('exist'); + }) + it('should keep the slippage tolerance after closing the settings', ()=>{ + cy.visit('/swap'); + cy.get('[data-testid="open-settings-dialog-button"]').click(); + cy.get('[data-testid="max-slippage-settings"]').click(); + cy.get('[data-testid="slippage-input"]').type('1.05'); + cy.get('[data-testid="open-settings-dialog-button"]').click(); + cy.get('[data-testid="max-slippage-settings"]').should('not.exist'); + cy.get('[data-testid="open-settings-dialog-button"]').click(); + cy.get('[data-testid="max-slippage-settings"]').should('exist') + cy.get('[data-testid="max-slippage-settings"]').contains('1.05%'); + }) + it('Should set the slippage tolerance to auto when pressing "auto" button', ()=>{ + cy.visit('/swap'); + cy.get('[data-testid="open-settings-dialog-button"]').click(); + cy.get('[data-testid="max-slippage-settings"]').click(); + cy.get('[data-testid="slippage-input"]').type('1.05'); + cy.get('[data-testid="max-slippage-settings"]').contains('1.05%'); + cy.get('[data-testid="slippage-auto-button"]').click(); + cy.get('[data-testid="max-slippage-settings"]').contains('Auto'); + }) +}) // Navigation flow describe('Navigation flow', () => { it('should render the navbar', () => { diff --git a/src/components/CopyTxHash/CopyTxHash.tsx b/src/components/CopyTxHash/CopyTxHash.tsx index 964afb2f..b6b5e509 100644 --- a/src/components/CopyTxHash/CopyTxHash.tsx +++ b/src/components/CopyTxHash/CopyTxHash.tsx @@ -20,32 +20,62 @@ const getExplorerUrl = ({ chain, txHash }: { chain: WalletChain; txHash: string } }; +enum ExplorerType { + STELLAR_EXPERT = 'STELLAR_EXPERT', + STELLAR_CHAIN = 'STELLAR_CHAIN', +} + +interface ExplorerLinks { + stellarExpert: string; + stellarChain: string; +} + const CopyTxHash = ({ txHash }: { txHash: string }) => { const { notify } = useNotification(); const sorobanContext = useSorobanReact(); - const [explorerLink, setExplorerLink] = useState(undefined); + const activeChain = sorobanContext?.activeChain; - useEffect(() => { - if (!sorobanContext) return; + const [explorersLinks, setExplorersLinks] = useState(undefined); - const activeChain = sorobanContext.activeChain; - if (!activeChain) return; + useEffect(() => { + if (!sorobanContext || !activeChain) return; - if (activeChain.name === testnet.name || activeChain.name === mainnet.name) { - setExplorerLink(getExplorerUrl({ chain: activeChain, txHash })); + if (activeChain.name === testnet.name) { + setExplorersLinks({ + stellarExpert: `https://stellar.expert/explorer/testnet/tx/${txHash}`, + stellarChain: `https://testnet.stellarchain.io/transactions/${txHash}`, + }); } - }, [sorobanContext, txHash]); - const handleClickViewOnExplorer = () => { - if (!explorerLink) return; + if (activeChain.name === mainnet.name) { + setExplorersLinks({ + stellarExpert: `https://stellar.expert/explorer/public/tx/${txHash}`, + stellarChain: `https://stellarchain.io/transactions/${txHash}`, + }); + } + }, [sorobanContext, txHash]); - window.open(explorerLink, '_blank'); - }; return ( - + + {(explorersLinks?.stellarChain && explorersLinks.stellarExpert) && ( + <> + + + View on Stellar.Expert + + + + + View on StellarChain.io + + + + )} @@ -61,11 +91,6 @@ const CopyTxHash = ({ txHash }: { txHash: string }) => { - {explorerLink && ( - - View on explorer - - )} ); }; diff --git a/src/components/Settings/MaxSlippageSettings/index.tsx b/src/components/Settings/MaxSlippageSettings/index.tsx index 603199cd..77facf7f 100644 --- a/src/components/Settings/MaxSlippageSettings/index.tsx +++ b/src/components/Settings/MaxSlippageSettings/index.tsx @@ -103,15 +103,16 @@ export default function MaxSlippageSettings({ autoSlippage }: { autoSlippage: nu function setCustomSlippage() { // When switching to custom slippage, use `auto` value as a default. - setUserSlippageTolerance(autoSlippage); selectSlippageInput(); } - const removeTrailingZeroes = (num: number) => { - return num + const removeTrailingZeroes = (num: number | SlippageTolerance.Auto) => { + if (num === SlippageTolerance.Auto) return; + const parsedNum = num .toFixed(2) .toString() .replace(/0{1,}$/, ''); + return parsedNum[parsedNum.length - 1] === '.' ? parsedNum.slice(0, -1) : parsedNum; }; useEffect(function () { @@ -139,8 +140,8 @@ export default function MaxSlippageSettings({ autoSlippage }: { autoSlippage: nu } button={ - - {isAuto ? <>Auto : `${removeTrailingZeroes(userSlippageTolerance)}%`} + + {isAuto ? 'Auto' : `${removeTrailingZeroes(userSlippageTolerance)}%`} } > @@ -154,7 +155,7 @@ export default function MaxSlippageSettings({ autoSlippage }: { autoSlippage: nu }} isActive={isAuto} > - + Auto @@ -173,11 +174,11 @@ export default function MaxSlippageSettings({ autoSlippage }: { autoSlippage: nu onChange={(e) => parseSlippageInput(e.target.value)} onBlur={() => { // When the input field is blurred, reset the input field to the default value - setSlippageInput(DEFAULT_SLIPPAGE_INPUT_VALUE); - setSlippageError(false); + //setSlippageInput(DEFAULT_SLIPPAGE_INPUT_VALUE); + //setSlippageError(false); }} onFocus={setCustomSlippage} - type="number" + type="text" step="0.1" /> % @@ -185,14 +186,14 @@ export default function MaxSlippageSettings({ autoSlippage }: { autoSlippage: nu {tooLow || tooHigh ? ( - + {tooLow ? ( -
+
Slippage below {MINIMUM_RECOMMENDED_SLIPPAGE.toFixed(2)}% may result in a failed transaction
) : ( -
Your transaction may be frontrun and result in an unfavorable trade.
+
Your transaction may be frontrun and result in an unfavorable trade.
)}