├── .babelrc ├── .env.sample ├── .eslintrc.json ├── .github └── workflows │ ├── deploy.yml │ └── lint_tsc.yml ├── .gitignore ├── .husky └── pre-commit ├── .nvmrc ├── .prettierrc ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── cypress.json ├── cypress ├── integration │ ├── add-liquidity.test.ts │ ├── landing.test.ts │ ├── lists.test.ts │ ├── migrate-v1.test.ts │ ├── pool.test.ts │ ├── remove-liquidity.test.ts │ ├── send.test.ts │ ├── swap.test.ts │ └── token-warning.ts ├── support │ ├── commands.d.ts │ ├── commands.js │ └── index.js └── tsconfig.json ├── index.html ├── package.json ├── patches └── @web3-react+core+6.0.9.patch ├── public ├── favicon.png ├── images │ ├── 192x192_App_Icon.png │ ├── 384x384_App_Icon.png │ └── flags │ │ ├── de.svg │ │ ├── en.svg │ │ ├── es.svg │ │ ├── fr.svg │ │ ├── jp.svg │ │ ├── pt-br.svg │ │ ├── tr.svg │ │ ├── vn.svg │ │ └── zh.svg └── manifest.json ├── scripts └── build.js ├── src ├── apollo │ ├── block.ts │ ├── client.ts │ ├── pair.ts │ └── vote.ts ├── assets │ ├── images │ │ ├── beta-image.jpg │ │ ├── giftbox.png │ │ ├── gnosis_safe.png │ │ ├── hashConnect.png │ │ ├── logo.png │ │ ├── migration_vector.png │ │ ├── minus.png │ │ ├── plus.png │ │ └── xDefi.png │ └── svg │ │ ├── PNG │ │ ├── PNG.svg │ │ ├── PNG_EVMOS.svg │ │ ├── PNG_HEDERA.svg │ │ ├── PNR.svg │ │ └── PSB.svg │ │ ├── avalancheCore.svg │ │ ├── backward.svg │ │ ├── bitkeep.svg │ │ ├── blue-loader.svg │ │ ├── circleTick.svg │ │ ├── coinbaseWalletIcon.svg │ │ ├── discord.svg │ │ ├── forward.svg │ │ ├── lightMode.svg │ │ ├── logoIcon.svg │ │ ├── logoSloganDark.svg │ │ ├── logoSloganLight.svg │ │ ├── menu │ │ ├── bridge.svg │ │ ├── logout.svg │ │ └── statatics.svg │ │ ├── near.svg │ │ ├── nightMode.svg │ │ ├── social │ │ ├── discord.svg │ │ ├── github.svg │ │ ├── medium.svg │ │ ├── substack.svg │ │ ├── telegram.svg │ │ ├── twitter.svg │ │ └── youtube.svg │ │ ├── stake.svg │ │ ├── transaction.svg │ │ ├── unstake.svg │ │ ├── wallet.svg │ │ ├── walletConnectIcon.svg │ │ └── x.svg ├── components │ ├── AccountDetails │ │ ├── Copy.tsx │ │ ├── Transaction.tsx │ │ ├── index.tsx │ │ └── styled.tsx │ ├── AccountDetailsModal │ │ └── index.tsx │ ├── Beta │ │ ├── ComingSoon │ │ │ ├── index.tsx │ │ │ └── styled.ts │ │ └── TransactionSubmitted │ │ │ ├── index.tsx │ │ │ └── styled.ts │ ├── Button │ │ └── index.tsx │ ├── Card │ │ └── index.tsx │ ├── Column │ │ └── index.tsx │ ├── Confetti │ │ └── index.tsx │ ├── Header │ │ ├── HederaPoolWarning.tsx │ │ ├── Polling.tsx │ │ └── URLWarning.tsx │ ├── Icons │ │ ├── Bridge.tsx │ │ ├── Buy.tsx │ │ ├── C14.tsx │ │ ├── CoinbasePay.tsx │ │ ├── Dashboard.tsx │ │ ├── Logo.tsx │ │ ├── MoonPay.tsx │ │ ├── Pool.tsx │ │ ├── Stake.tsx │ │ ├── Swap.tsx │ │ ├── Vote.tsx │ │ └── index.ts │ ├── Identicon │ │ └── index.tsx │ ├── Loader │ │ └── index.tsx │ ├── MigrationCard │ │ ├── index.tsx │ │ └── styleds.tsx │ ├── MigrationModal │ │ ├── ChoosePool │ │ │ ├── PairData.tsx │ │ │ ├── index.tsx │ │ │ └── styleds.tsx │ │ ├── Loader │ │ │ ├── index.tsx │ │ │ └── styleds.tsx │ │ ├── PoolInfo │ │ │ ├── index.tsx │ │ │ └── styleds.tsx │ │ ├── Stake │ │ │ ├── index.tsx │ │ │ └── styleds.tsx │ │ ├── StepView │ │ │ ├── index.tsx │ │ │ └── styleds.tsx │ │ ├── Unstake │ │ │ ├── index.tsx │ │ │ └── styleds.tsx │ │ ├── index.tsx │ │ └── styleds.tsx │ ├── NewVersionModal │ │ └── index.tsx │ ├── Popups │ │ ├── PopupItem.tsx │ │ ├── TransactionPopup.tsx │ │ └── index.tsx │ ├── Row │ │ └── index.tsx │ ├── Stat │ │ ├── index.tsx │ │ └── styled.tsx │ ├── StyledMenu │ │ └── index.tsx │ ├── SwitchSubgraph │ │ └── index.tsx │ ├── Web3ReactManager │ │ └── index.tsx │ ├── Web3Status │ │ └── index.tsx │ └── earn │ │ └── styled.ts ├── constants │ ├── Policies │ │ ├── CPolicy.ts │ │ ├── PrivacyPolicy.ts │ │ └── TermsService.ts │ ├── abis │ │ └── staking-rewards.ts │ ├── accessPermissions.ts │ └── index.ts ├── hooks │ ├── index.ts │ ├── mixpanel.tsx │ ├── useCopyClipboard.ts │ ├── useTransactionDeadline.ts │ └── useWindowSize.ts ├── index.tsx ├── layout │ ├── Footer │ │ ├── PolicyModal.tsx │ │ ├── index.tsx │ │ └── styled.ts │ ├── Header │ │ ├── MenuIcon.tsx │ │ ├── MobileHeader.tsx │ │ ├── MobileMenu │ │ │ ├── MobileWeb3Status.tsx │ │ │ ├── TransactionModal.tsx │ │ │ ├── index.tsx │ │ │ └── styled.ts │ │ ├── index.tsx │ │ └── styled.ts │ ├── Logo │ │ ├── index.tsx │ │ └── styled.ts │ ├── Sidebar │ │ ├── MenuLinks.tsx │ │ ├── NavItem.tsx │ │ ├── index.tsx │ │ └── styled.tsx │ ├── SocialMedia │ │ ├── index.tsx │ │ └── styled.ts │ ├── index.tsx │ └── styled.ts ├── pages │ ├── App.tsx │ ├── Beta │ │ ├── Bridge │ │ │ ├── index.tsx │ │ │ └── styleds.tsx │ │ ├── Buy │ │ │ ├── C14 │ │ │ │ └── index.tsx │ │ │ ├── CoinbasePay │ │ │ │ └── index.tsx │ │ │ ├── Moonpay │ │ │ │ └── index.tsx │ │ │ ├── index.tsx │ │ │ └── styled.tsx │ │ ├── Governance │ │ │ └── index.tsx │ │ ├── GovernanceDetail │ │ │ └── index.tsx │ │ ├── Policy │ │ │ ├── index.tsx │ │ │ └── styled.ts │ │ ├── Pool │ │ │ └── index.tsx │ │ ├── Stake │ │ │ ├── ClaimDrawer │ │ │ │ └── index.tsx │ │ │ ├── ClaimWidget │ │ │ │ ├── index.tsx │ │ │ │ └── styled.tsx │ │ │ ├── DetailModal │ │ │ │ ├── Details │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── styled.ts │ │ │ │ ├── EarnedWidget │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── styled.tsx │ │ │ │ ├── Header │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── styled.tsx │ │ │ │ ├── StakeWidget │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── styled.ts │ │ │ │ ├── StatDetail │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── styleds.tsx │ │ │ │ ├── UnstakeDrawer │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── styled.ts │ │ │ │ ├── index.tsx │ │ │ │ └── styled.tsx │ │ │ ├── PoolCard │ │ │ │ ├── StakeDrawer │ │ │ │ │ └── index.tsx │ │ │ │ ├── StakeWidget │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── styled.ts │ │ │ │ ├── index.tsx │ │ │ │ └── styleds.tsx │ │ │ ├── RewardStakeDrawer │ │ │ │ └── index.tsx │ │ │ ├── index.tsx │ │ │ └── styleds.tsx │ │ └── Swap │ │ │ ├── LimitOrderList │ │ │ ├── CancelOrder │ │ │ │ ├── index.tsx │ │ │ │ └── styleds.tsx │ │ │ ├── CancelOrderModal │ │ │ │ ├── index.tsx │ │ │ │ └── styleds.tsx │ │ │ ├── LimitOrderDetail.tsx │ │ │ ├── LimitOrderRow.tsx │ │ │ ├── MobileLimitOrderRow.tsx │ │ │ ├── index.tsx │ │ │ └── styleds.tsx │ │ │ ├── PairInfo │ │ │ ├── PairChart │ │ │ │ ├── index.tsx │ │ │ │ └── styleds.tsx │ │ │ ├── PairStat │ │ │ │ ├── index.tsx │ │ │ │ └── styleds.tsx │ │ │ └── index.tsx │ │ │ ├── SwapUI │ │ │ ├── index.tsx │ │ │ └── styleds.tsx │ │ │ └── index.tsx │ ├── Dashboard │ │ ├── index.tsx │ │ └── styleds.tsx │ ├── Migrate │ │ ├── Migrate.tsx │ │ ├── index.tsx │ │ └── styleds.tsx │ └── SarStake │ │ ├── StakeStat │ │ ├── index.tsx │ │ └── styleds.tsx │ │ ├── index.tsx │ │ └── styleds.tsx ├── react-app-env.d.ts ├── setupProxy.js ├── state │ ├── application │ │ ├── actions.ts │ │ ├── hooks.ts │ │ ├── reducer.test.ts │ │ └── reducer.ts │ ├── global │ │ └── actions.ts │ ├── index.ts │ ├── migrate │ │ └── hooks.ts │ ├── multicall │ │ ├── actions.test.ts │ │ ├── actions.ts │ │ ├── hooks.ts │ │ ├── reducer.test.ts │ │ ├── reducer.ts │ │ ├── updater.test.ts │ │ └── updater.tsx │ ├── pair │ │ ├── actions.ts │ │ ├── hooks.ts │ │ └── reducer.ts │ ├── stake │ │ ├── hooks.ts │ │ ├── multiChainsHooks.ts │ │ └── singleSideConfig.ts │ ├── token │ │ ├── actions.ts │ │ ├── hooks.ts │ │ └── reducer.ts │ ├── transactions │ │ ├── actions.ts │ │ ├── hooks.tsx │ │ ├── reducer.test.ts │ │ ├── reducer.ts │ │ ├── updater.test.ts │ │ └── updater.tsx │ ├── user │ │ ├── actions.ts │ │ ├── hooks.tsx │ │ ├── reducer.test.ts │ │ ├── reducer.ts │ │ └── updater.tsx │ └── watchlists │ │ ├── actions.ts │ │ └── reducer.ts ├── theme │ ├── components.tsx │ ├── index.tsx │ └── styled.d.ts ├── utils │ ├── getLibrary.ts │ ├── index.test.ts │ ├── index.ts │ ├── retry.test.ts │ └── retry.ts └── vite-env.d.ts ├── tsconfig.json ├── vite.config.ts ├── wrangler.toml └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | "babel-plugin-styled-components", 4 | "@babel/plugin-transform-runtime", 5 | "@babel/plugin-proposal-optional-chaining", 6 | "@babel/plugin-proposal-nullish-coalescing-operator"] 7 | } 8 | -------------------------------------------------------------------------------- /.env.sample: -------------------------------------------------------------------------------- 1 | VITE_MOONPAY_PK="" 2 | VITE_COINBASE_PK="" 3 | VITE_SUBGRAPH_BASE_URL="https://api.thegraph.com/subgraphs/name/pangolindex" 4 | VITE_MIXPANEL="" 5 | VITE_HASURAKEY="" 6 | VITE_WALLETCONNECT_PROJECTID="" -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@typescript-eslint/parser", 3 | "parserOptions": { 4 | "ecmaVersion": 2020, 5 | "sourceType": "module", 6 | "ecmaFeatures": { 7 | // Allows for the parsing of JSX 8 | "jsx": true 9 | } 10 | }, 11 | "ignorePatterns": ["node_modules/**/*"], 12 | "settings": { 13 | "react": { 14 | "version": "detect" 15 | } 16 | }, 17 | "extends": [ 18 | "plugin:react/recommended", 19 | "plugin:@typescript-eslint/recommended", 20 | "plugin:react-hooks/recommended", 21 | "prettier/@typescript-eslint", 22 | "plugin:prettier/recommended" 23 | ], 24 | "plugins": ["react", "react-hooks", "prettier"], 25 | "rules": { 26 | "react/jsx-filename-extension": [ 27 | 1, 28 | { 29 | "extensions": [".js", ".jsx", ".ts", ".tsx"] 30 | } 31 | ], 32 | "@typescript-eslint/explicit-function-return-type": "off", 33 | "prettier/prettier": "error", 34 | "@typescript-eslint/no-explicit-any": "off", 35 | "@typescript-eslint/camelcase": "off", 36 | "@typescript-eslint/no-non-null-assertion": "off", 37 | "@typescript-eslint/no-use-before-define": "off", 38 | "@typescript-eslint/ban-types": "off", 39 | "@typescript-eslint/explicit-module-boundary-types": "off" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Deployment 2 | 3 | env: 4 | VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} 5 | VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} 6 | 7 | on: 8 | push: 9 | 10 | jobs: 11 | Deploy: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Checkout Repo 15 | uses: actions/checkout@v4 16 | 17 | - name: Setup node 18 | uses: actions/setup-node@v3 19 | with: 20 | node-version: 18.x 21 | cache: 'yarn' 22 | 23 | - name: Install Vercel CLI 24 | run: npm install --global vercel@latest 25 | 26 | ### Production Deployment ### 27 | - name: Pull Vercel Environment Information ( Production ) 28 | if: github.ref == 'refs/heads/master' 29 | run: vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }} 30 | 31 | - name: Build Project Artifacts ( Production ) 32 | if: github.ref == 'refs/heads/master' 33 | run: vercel build --prod --token=${{ secrets.VERCEL_TOKEN }} 34 | 35 | - name: Deploy Project Artifacts to Vercel ( Production ) 36 | if: github.ref == 'refs/heads/master' 37 | run: vercel deploy --prod --prebuilt --token=${{ secrets.VERCEL_TOKEN }} 38 | 39 | ### Preview Deployment ### 40 | - name: Pull Vercel Environment Information ( Preview ) 41 | if: github.ref != 'refs/heads/master' 42 | run: vercel pull --yes --environment=preview --token=${{ secrets.VERCEL_TOKEN }} 43 | 44 | - name: Build Project Artifacts ( Preview ) 45 | if: github.ref != 'refs/heads/master' 46 | run: vercel build --token=${{ secrets.VERCEL_TOKEN }} 47 | 48 | - name: Deploy Project Artifacts to Vercel ( Preview ) 49 | if: github.ref != 'refs/heads/master' 50 | run: vercel deploy --prebuilt --token=${{ secrets.VERCEL_TOKEN }} 51 | -------------------------------------------------------------------------------- /.github/workflows/lint_tsc.yml: -------------------------------------------------------------------------------- 1 | name: Linting & Typescript Cheking 2 | 3 | env: 4 | CI: true 5 | 6 | on: 7 | pull_request: 8 | 9 | jobs: 10 | lint_tsc: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v2 15 | with: 16 | # we actually need "github.event.pull_request.commits + 1" commit 17 | fetch-depth: 0 18 | 19 | - uses: actions/setup-node@v3 20 | with: 21 | node-version: '18.x' 22 | cache: 'yarn' 23 | registry-url: 'https://registry.npmjs.org/' 24 | 25 | - run: yarn --frozen-lockfile 26 | - run: yarn type-check 27 | - run: yarn lint 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env 15 | .env.local 16 | .env.development.local 17 | .env.test.local 18 | .env.production.local 19 | 20 | /.netlify 21 | 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* 25 | 26 | notes.txt 27 | .idea/ 28 | 29 | .vscode/ 30 | 31 | package-lock.json 32 | 33 | 34 | cypress/videos 35 | cypress/screenshots 36 | cypress/fixtures/example.json 37 | # Local Netlify folder 38 | .netlify 39 | .yalc 40 | yalc.lock 41 | 42 | dist 43 | .vercel 44 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npm run pre-commit-hook 5 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v18 -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true, 4 | "printWidth": 120 5 | } 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Pangolin Interface 2 | 3 | An open source interface for Pangolin -- a community-driven decentralized exchange for Avalanche and Ethereum assets with fast settlement, low transaction fees, and a democratic distribution -- powered by Avalanche. 4 | 5 | - Website: [pangolin.exchange](https://pangolin.exchange/) 6 | - Interface: [app.pangolin.exchange](https://app.pangolin.exchange) 7 | - Telegram: [Pangolin](https://t.me/pangolindexV2) 8 | - Discord: [Pangolin](https://discord.com/invite/CZttnRaYjK) 9 | - Twitter: [@pangolindex](https://twitter.com/pangolindex) 10 | 11 | 12 | 13 | ## Accessing the Pangolin Interface 14 | 15 | Visit [app.pangolin.exchange](https://app.pangolin.exchange). 16 | 17 | ## Development 18 | 19 | ### Install Dependencies 20 | 21 | ```bash 22 | yarn 23 | ``` 24 | 25 | 26 | ### Run 27 | 28 | ```bash 29 | yarn start 30 | ``` 31 | 32 | ### Configuring the environment (optional) 33 | 34 | To have the interface default to a different network when a wallet is not connected: 35 | 36 | 1. Make a copy of `.env.sample` named `.env` 37 | 38 | Note that the interface only works on testnets where both 39 | [Pangolin](https://github.com/pangolindex/exchange-contracts) and 40 | [multicall](https://github.com/makerdao/multicall) are deployed. 41 | The interface will not work on other networks. 42 | 43 | ## Attribution 44 | This code was adapted from this Uniswap repo: [uniswap-interface](https://github.com/Uniswap/uniswap-interface) 45 | -------------------------------------------------------------------------------- /cypress.json: -------------------------------------------------------------------------------- 1 | { 2 | "baseUrl": "http://localhost:3000", 3 | "pluginsFile": false, 4 | "fixturesFolder": false, 5 | "supportFile": "cypress/support/index.js", 6 | "video": false, 7 | "defaultCommandTimeout": 10000 8 | } 9 | -------------------------------------------------------------------------------- /cypress/integration/landing.test.ts: -------------------------------------------------------------------------------- 1 | import { TEST_ADDRESS_NEVER_USE_SHORTENED } from '../support/commands' 2 | 3 | describe('Landing Page', () => { 4 | beforeEach(() => cy.visit('/')) 5 | it('loads swap page', () => { 6 | cy.get('#swap-page') 7 | }) 8 | 9 | it('redirects to url /swap', () => { 10 | cy.url().should('include', '/swap') 11 | }) 12 | 13 | it('allows navigation to pool', () => { 14 | cy.get('#pool-nav-link').click() 15 | cy.url().should('include', '/pool') 16 | }) 17 | 18 | it('is connected', () => { 19 | cy.get('#web3-status-connected').click() 20 | cy.get('#web3-account-identifier-row').contains(TEST_ADDRESS_NEVER_USE_SHORTENED) 21 | }) 22 | }) 23 | -------------------------------------------------------------------------------- /cypress/integration/lists.test.ts: -------------------------------------------------------------------------------- 1 | describe('Lists', () => { 2 | beforeEach(() => { 3 | cy.visit('/swap') 4 | }) 5 | 6 | it('defaults to uniswap list', () => { 7 | cy.get('#swap-currency-output .open-currency-select-button').click() 8 | cy.get('#currency-search-selected-list-name').should('contain', 'Uniswap') 9 | }) 10 | 11 | it('change list', () => { 12 | cy.get('#swap-currency-output .open-currency-select-button').click() 13 | cy.get('#currency-search-change-list-button').click() 14 | cy.get('#list-row-tokens-1inch-eth .select-button').click() 15 | cy.get('#currency-search-selected-list-name').should('contain', '1inch') 16 | cy.get('#currency-search-change-list-button').click() 17 | cy.get('#list-row-tokens-uniswap-eth .select-button').click() 18 | cy.get('#currency-search-selected-list-name').should('contain', 'Uniswap') 19 | }) 20 | }) 21 | -------------------------------------------------------------------------------- /cypress/integration/migrate-v1.test.ts: -------------------------------------------------------------------------------- 1 | describe('Migrate V1 Liquidity', () => { 2 | describe('Remove V1 liquidity', () => { 3 | it('renders the correct page', () => { 4 | cy.visit('/remove/v1/0x93bB63aFe1E0180d0eF100D774B473034fd60C36') 5 | cy.get('#remove-v1-exchange').should('contain', 'MKR/ETH') 6 | }) 7 | }) 8 | }) 9 | -------------------------------------------------------------------------------- /cypress/integration/pool.test.ts: -------------------------------------------------------------------------------- 1 | describe('Pool', () => { 2 | beforeEach(() => cy.visit('/pool')) 3 | it('add liquidity links to /add/ETH', () => { 4 | cy.get('#join-pool-button').click() 5 | cy.url().should('contain', '/add/ETH') 6 | }) 7 | 8 | it('import pool links to /import', () => { 9 | cy.get('#import-pool-link').click() 10 | cy.url().should('contain', '/find') 11 | }) 12 | }) 13 | -------------------------------------------------------------------------------- /cypress/integration/remove-liquidity.test.ts: -------------------------------------------------------------------------------- 1 | describe('Remove Liquidity', () => { 2 | it('redirects', () => { 3 | cy.visit('/remove/0xc778417E063141139Fce010982780140Aa0cD5Ab-0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85') 4 | cy.url().should( 5 | 'contain', 6 | '/remove/0xc778417E063141139Fce010982780140Aa0cD5Ab/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85' 7 | ) 8 | }) 9 | 10 | it('eth remove', () => { 11 | cy.visit('/remove/ETH/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85') 12 | cy.get('#remove-liquidity-tokena-symbol').should('contain.text', 'ETH') 13 | cy.get('#remove-liquidity-tokenb-symbol').should('contain.text', 'MKR') 14 | }) 15 | 16 | it('eth remove swap order', () => { 17 | cy.visit('/remove/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85/ETH') 18 | cy.get('#remove-liquidity-tokena-symbol').should('contain.text', 'MKR') 19 | cy.get('#remove-liquidity-tokenb-symbol').should('contain.text', 'ETH') 20 | }) 21 | 22 | it('loads the two correct tokens', () => { 23 | cy.visit('/remove/0xc778417E063141139Fce010982780140Aa0cD5Ab-0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85') 24 | cy.get('#remove-liquidity-tokena-symbol').should('contain.text', 'WETH') 25 | cy.get('#remove-liquidity-tokenb-symbol').should('contain.text', 'MKR') 26 | }) 27 | 28 | it('does not crash if ETH is duplicated', () => { 29 | cy.visit('/remove/0xc778417E063141139Fce010982780140Aa0cD5Ab-0xc778417E063141139Fce010982780140Aa0cD5Ab') 30 | cy.get('#remove-liquidity-tokena-symbol').should('contain.text', 'WETH') 31 | cy.get('#remove-liquidity-tokenb-symbol').should('contain.text', 'WETH') 32 | }) 33 | 34 | it('token not in storage is loaded', () => { 35 | cy.visit('/remove/0xb290b2f9f8f108d03ff2af3ac5c8de6de31cdf6d-0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85') 36 | cy.get('#remove-liquidity-tokena-symbol').should('contain.text', 'SKL') 37 | cy.get('#remove-liquidity-tokenb-symbol').should('contain.text', 'MKR') 38 | }) 39 | }) 40 | -------------------------------------------------------------------------------- /cypress/integration/send.test.ts: -------------------------------------------------------------------------------- 1 | describe('Send', () => { 2 | it('should redirect', () => { 3 | cy.visit('/send') 4 | cy.url().should('include', '/swap') 5 | }) 6 | 7 | it('should redirect with url params', () => { 8 | cy.visit('/send?outputCurrency=ETH&recipient=bob.argent.xyz') 9 | cy.url().should('contain', '/swap?outputCurrency=ETH&recipient=bob.argent.xyz') 10 | }) 11 | }) 12 | -------------------------------------------------------------------------------- /cypress/integration/token-warning.ts: -------------------------------------------------------------------------------- 1 | describe('Warning', () => { 2 | beforeEach(() => { 3 | cy.visit('/swap?outputCurrency=0x0a40f26d74274b7f22b28556a27b35d97ce08e0a') 4 | }) 5 | 6 | it('Check that warning is displayed', () => { 7 | cy.get('.token-warning-container').should('be.visible') 8 | }) 9 | 10 | it('Check that warning hides after button dismissal', () => { 11 | cy.get('.token-dismiss-button').should('be.disabled') 12 | cy.get('.understand-checkbox').click() 13 | cy.get('.token-dismiss-button').should('not.be.disabled') 14 | cy.get('.token-dismiss-button').click() 15 | cy.get('.token-warning-container').should('not.be.visible') 16 | }) 17 | }) 18 | -------------------------------------------------------------------------------- /cypress/support/commands.d.ts: -------------------------------------------------------------------------------- 1 | export const TEST_ADDRESS_NEVER_USE: string 2 | 3 | export const TEST_ADDRESS_NEVER_USE_SHORTENED: string 4 | 5 | // declare namespace Cypress { 6 | // // eslint-disable-next-line @typescript-eslint/class-name-casing 7 | // interface cy { 8 | // additionalCommands(): void 9 | // } 10 | // } 11 | -------------------------------------------------------------------------------- /cypress/support/index.js: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This file is processed and loaded automatically before your test files. 3 | // 4 | // You can read more here: 5 | // https://on.cypress.io/configuration 6 | // *********************************************************** 7 | 8 | // Import commands.ts using ES2015 syntax: 9 | import './commands' 10 | -------------------------------------------------------------------------------- /cypress/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "baseUrl": "../node_modules", 5 | "target": "es5", 6 | "lib": ["es5", "dom"], 7 | "types": ["cypress"] 8 | }, 9 | "include": ["**/*.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 20 | 21 | Pangolin 22 | 23 | 24 |
25 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /patches/@web3-react+core+6.0.9.patch: -------------------------------------------------------------------------------- 1 | diff --git a/node_modules/@web3-react/core/dist/core.esm.js b/node_modules/@web3-react/core/dist/core.esm.js 2 | index e5bea1e..55f37ff 100644 3 | --- a/node_modules/@web3-react/core/dist/core.esm.js 4 | +++ b/node_modules/@web3-react/core/dist/core.esm.js 5 | @@ -179,18 +179,20 @@ var augmentConnectorUpdate = function augmentConnectorUpdate(connector, update) 6 | return Promise.resolve(Promise.all([update.chainId === undefined ? connector.getChainId() : update.chainId, update.account === undefined ? connector.getAccount() : update.account])).then(function (_ref2) { 7 | var _chainId = _ref2[0], 8 | _account = _ref2[1]; 9 | - var chainId = normalizeChainId(_chainId); 10 | + 11 | + var chainId = connector.normalizeChainId === false ? _chainId : normalizeChainId(_chainId); 12 | 13 | if (!!connector.supportedChainIds && !connector.supportedChainIds.includes(chainId)) { 14 | throw new UnsupportedChainIdError(chainId, connector.supportedChainIds); 15 | } 16 | 17 | - var account = _account === null ? _account : normalizeAccount(_account); 18 | + var account = _account === null ? _account : connector.normalizeAccount === false ? _account : normalizeAccount(_account); 19 | return { 20 | provider: provider, 21 | chainId: chainId, 22 | account: account 23 | }; 24 | + 25 | }); 26 | }; 27 | 28 | -------------------------------------------------------------------------------- /public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pangolindex/interface/22f3eb07b757a9306a7e1c81eddeb1fa392edd23/public/favicon.png -------------------------------------------------------------------------------- /public/images/192x192_App_Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pangolindex/interface/22f3eb07b757a9306a7e1c81eddeb1fa392edd23/public/images/192x192_App_Icon.png -------------------------------------------------------------------------------- /public/images/384x384_App_Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pangolindex/interface/22f3eb07b757a9306a7e1c81eddeb1fa392edd23/public/images/384x384_App_Icon.png -------------------------------------------------------------------------------- /public/images/flags/de.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | DE 5 | Created with sketchtool. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /public/images/flags/fr.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | FR 5 | Created with sketchtool. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /public/images/flags/jp.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | JP 5 | Created with sketchtool. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /public/images/flags/tr.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | TR 5 | Created with sketchtool. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /public/images/flags/vn.svg: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Pangolin", 3 | "name": "Pangolin", 4 | "description": "Swap or provide liquidity on the Pangolin Exchange", 5 | "iconPath": "./images/384x384_App_Icon.png", 6 | "icons": [ 7 | { 8 | "src": "./images/192x192_App_Icon.png", 9 | "sizes": "192x192", 10 | "type": "image/png", 11 | "purpose": "any maskable" 12 | }, 13 | { 14 | "src": "./images/512x512_App_Icon.png", 15 | "sizes": "512x512", 16 | "type": "image/png", 17 | "purpose": "any maskable" 18 | } 19 | ], 20 | "start_url": ".", 21 | "orientation": "portrait", 22 | "display": "standalone", 23 | "theme_color": "#FF6B00", 24 | "background_color": "#fff" 25 | } 26 | -------------------------------------------------------------------------------- /scripts/build.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | const { spawn } = require('child_process') 3 | 4 | // console.log('setting NODE_OPTIONS to --max-old-space-size=8192') 5 | // Step 1: Set the environment variable 6 | // process.env.NODE_OPTIONS = '--max-old-space-size=8192' 7 | 8 | // Step 2: Run the "yarn build" command 9 | const yarnBuild = spawn('yarn', ['vite:build'], { 10 | stdio: 'inherit', // This option will pipe the stdio of the child process to the parent, effectively displaying it in the terminal. 11 | shell: true, // This option will run the command in a shell, allowing for environment variable expansion. 12 | env: process.env // Pass the modified environment variables to the child process. 13 | }) 14 | 15 | // Step 4: Handle errors (though with 'stdio: inherit', errors will be displayed automatically) 16 | yarnBuild.on('error', error => { 17 | console.error(`Error executing "yarn build": ${error}`) 18 | }) 19 | 20 | yarnBuild.on('close', code => { 21 | if (code !== 0) { 22 | console.error(`"yarn build" exited with code ${code}`) 23 | console.log('spawning another build') 24 | spawn('yarn', ['vite:build'], { 25 | stdio: 'inherit', // This option will pipe the stdio of the child process to the parent, effectively displaying it in the terminal. 26 | shell: true, // This option will run the command in a shell, allowing for environment variable expansion. 27 | env: process.env // Pass the modified environment variables to the child process. 28 | }) 29 | } 30 | }) 31 | /* eslint-enable */ 32 | -------------------------------------------------------------------------------- /src/apollo/block.ts: -------------------------------------------------------------------------------- 1 | import gql from 'graphql-tag' 2 | 3 | export const GET_BLOCK = gql` 4 | query blocks($timestampFrom: Int!, $timestampTo: Int!) { 5 | blocks( 6 | first: 1 7 | orderBy: timestamp 8 | orderDirection: asc 9 | where: { timestamp_gt: $timestampFrom, timestamp_lt: $timestampTo } 10 | ) { 11 | id 12 | number 13 | timestamp 14 | } 15 | } 16 | ` 17 | 18 | export const GET_BLOCKS = (timestamps: Array) => { 19 | let queryString = 'query blocks {' 20 | queryString += timestamps.map(timestamp => { 21 | return `t${timestamp}:blocks(first: 1, orderBy: timestamp, orderDirection: asc, where: { timestamp_gt: ${timestamp}, timestamp_lt: ${timestamp + 22 | 60 * 60 * 24 * 7} }) { 23 | number 24 | }` 25 | }) 26 | queryString += '}' 27 | return gql(queryString) 28 | } 29 | 30 | export const PRICES_BY_BLOCK = (tokenAddress: string, blocks: Array) => { 31 | let queryString = 'query blocks {' 32 | queryString += blocks.map( 33 | block => ` 34 | t${block.timestamp}:token(id:"${tokenAddress}", block: { number: ${block.number} }) { 35 | derivedETH 36 | } 37 | ` 38 | ) 39 | queryString += ',' 40 | queryString += blocks.map( 41 | block => ` 42 | b${block.timestamp}: bundle(id:"1", block: { number: ${block.number} }) { 43 | ethPrice 44 | } 45 | ` 46 | ) 47 | 48 | queryString += '}' 49 | return gql(queryString) 50 | } 51 | -------------------------------------------------------------------------------- /src/apollo/client.ts: -------------------------------------------------------------------------------- 1 | import { ApolloClient } from 'apollo-client' 2 | import { InMemoryCache, NormalizedCacheObject } from 'apollo-cache-inmemory' 3 | import { HttpLink } from 'apollo-link-http' 4 | import { SUBGRAPH_BASE_URL } from 'src/constants' 5 | import { ChainId } from '@pangolindex/sdk' 6 | 7 | export const client = new ApolloClient({ 8 | link: new HttpLink({ 9 | uri: `${SUBGRAPH_BASE_URL}/exchange` 10 | }), 11 | cache: new InMemoryCache() 12 | }) 13 | 14 | export const governanceClient = new ApolloClient({ 15 | link: new HttpLink({ 16 | uri: `${SUBGRAPH_BASE_URL}/governance` 17 | }), 18 | cache: new InMemoryCache() 19 | }) 20 | 21 | export const avalancheBlockClient = new ApolloClient({ 22 | link: new HttpLink({ 23 | uri: 'https://api.thegraph.com/subgraphs/name/dasconnor/avalanche-blocks' 24 | }), 25 | cache: new InMemoryCache() 26 | }) 27 | 28 | export const blockClients: { [chainId in ChainId]: ApolloClient | undefined } = { 29 | [ChainId.AVALANCHE]: avalancheBlockClient, 30 | [ChainId.FUJI]: undefined, 31 | [ChainId.WAGMI]: undefined, 32 | [ChainId.COSTON]: undefined, 33 | [ChainId.SONGBIRD]: undefined, 34 | [ChainId.FLARE_MAINNET]: undefined, 35 | [ChainId.HEDERA_TESTNET]: undefined, 36 | [ChainId.HEDERA_MAINNET]: undefined, 37 | [ChainId.NEAR_MAINNET]: undefined, 38 | [ChainId.NEAR_TESTNET]: undefined, 39 | [ChainId.COSTON2]: undefined, 40 | [ChainId.EVMOS_TESTNET]: undefined, 41 | [ChainId.EVMOS_MAINNET]: undefined, 42 | [ChainId.ETHEREUM]: undefined, 43 | [ChainId.POLYGON]: undefined, 44 | [ChainId.FANTOM]: undefined, 45 | [ChainId.XDAI]: undefined, 46 | [ChainId.BSC]: undefined, 47 | [ChainId.ARBITRUM]: undefined, 48 | [ChainId.CELO]: undefined, 49 | [ChainId.OKXCHAIN]: undefined, 50 | [ChainId.VELAS]: undefined, 51 | [ChainId.AURORA]: undefined, 52 | [ChainId.CRONOS]: undefined, 53 | [ChainId.FUSE]: undefined, 54 | [ChainId.MOONRIVER]: undefined, 55 | [ChainId.MOONBEAM]: undefined, 56 | [ChainId.OP]: undefined, 57 | [ChainId.SKALE_BELLATRIX_TESTNET]: undefined 58 | } 59 | -------------------------------------------------------------------------------- /src/apollo/pair.ts: -------------------------------------------------------------------------------- 1 | import gql from 'graphql-tag' 2 | 3 | export const HOURLY_PAIR_RATES = (pairAddress: string, blocks: Array) => { 4 | let queryString = 'query blocks {' 5 | queryString += blocks.map( 6 | block => ` 7 | t${block.timestamp}: pair(id:"${pairAddress}", block: { number: ${block.number} }) { 8 | token0Price 9 | token1Price 10 | } 11 | ` 12 | ) 13 | 14 | queryString += '}' 15 | return gql(queryString) 16 | } 17 | -------------------------------------------------------------------------------- /src/apollo/vote.ts: -------------------------------------------------------------------------------- 1 | import gql from 'graphql-tag' 2 | 3 | export const GET_PROPOSALS = gql` 4 | query proposals($where: Proposal_filter) { 5 | proposals(orderBy: startTime, orderDirection: desc, where: $where) { 6 | id 7 | description 8 | eta 9 | startTime 10 | endTime 11 | proposer 12 | calldatas 13 | signatures 14 | forVotes 15 | againstVotes 16 | canceled 17 | executed 18 | targets 19 | } 20 | } 21 | ` 22 | -------------------------------------------------------------------------------- /src/assets/images/beta-image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pangolindex/interface/22f3eb07b757a9306a7e1c81eddeb1fa392edd23/src/assets/images/beta-image.jpg -------------------------------------------------------------------------------- /src/assets/images/giftbox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pangolindex/interface/22f3eb07b757a9306a7e1c81eddeb1fa392edd23/src/assets/images/giftbox.png -------------------------------------------------------------------------------- /src/assets/images/gnosis_safe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pangolindex/interface/22f3eb07b757a9306a7e1c81eddeb1fa392edd23/src/assets/images/gnosis_safe.png -------------------------------------------------------------------------------- /src/assets/images/hashConnect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pangolindex/interface/22f3eb07b757a9306a7e1c81eddeb1fa392edd23/src/assets/images/hashConnect.png -------------------------------------------------------------------------------- /src/assets/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pangolindex/interface/22f3eb07b757a9306a7e1c81eddeb1fa392edd23/src/assets/images/logo.png -------------------------------------------------------------------------------- /src/assets/images/migration_vector.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pangolindex/interface/22f3eb07b757a9306a7e1c81eddeb1fa392edd23/src/assets/images/migration_vector.png -------------------------------------------------------------------------------- /src/assets/images/minus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pangolindex/interface/22f3eb07b757a9306a7e1c81eddeb1fa392edd23/src/assets/images/minus.png -------------------------------------------------------------------------------- /src/assets/images/plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pangolindex/interface/22f3eb07b757a9306a7e1c81eddeb1fa392edd23/src/assets/images/plus.png -------------------------------------------------------------------------------- /src/assets/images/xDefi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pangolindex/interface/22f3eb07b757a9306a7e1c81eddeb1fa392edd23/src/assets/images/xDefi.png -------------------------------------------------------------------------------- /src/assets/svg/PNG/PNG.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/svg/avalancheCore.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/svg/backward.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/assets/svg/bitkeep.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/svg/blue-loader.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/svg/discord.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/svg/forward.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/assets/svg/logoIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/assets/svg/menu/logout.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/assets/svg/menu/statatics.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/assets/svg/near.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/svg/nightMode.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/assets/svg/social/discord.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 9 | 11 | 13 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/assets/svg/social/github.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 18 | 19 | -------------------------------------------------------------------------------- /src/assets/svg/social/medium.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/svg/social/substack.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 17 | 18 | -------------------------------------------------------------------------------- /src/assets/svg/social/telegram.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/svg/social/twitter.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/svg/social/youtube.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/assets/svg/stake.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 9 | 17 | 22 | 24 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/assets/svg/transaction.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | -------------------------------------------------------------------------------- /src/assets/svg/unstake.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 9 | 11 | 13 | 21 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/assets/svg/wallet.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/svg/x.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/AccountDetails/Copy.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | import useCopyClipboard from '../../hooks/useCopyClipboard' 4 | import { LinkStyledButton } from '../../theme' 5 | import { CheckCircle, Copy } from 'react-feather' 6 | import { useTranslation } from '@honeycomb-finance/shared' 7 | 8 | const CopyIcon = styled(LinkStyledButton)` 9 | color: ${({ theme }) => theme.text3}; 10 | flex-shrink: 0; 11 | display: flex; 12 | text-decoration: none; 13 | font-size: 0.825rem; 14 | :hover, 15 | :active, 16 | :focus { 17 | text-decoration: none; 18 | color: ${({ theme }) => theme.text2}; 19 | } 20 | ` 21 | const TransactionStatusText = styled.span` 22 | margin-left: 0.25rem; 23 | font-size: 0.825rem; 24 | ${({ theme }) => theme.flexRowNoWrap}; 25 | align-items: center; 26 | ` 27 | 28 | export default function CopyHelper(props: { 29 | toCopy: string 30 | children?: React.ReactNode 31 | size?: string 32 | color?: string 33 | }) { 34 | const [isCopied, setCopied] = useCopyClipboard() 35 | const { t } = useTranslation() 36 | 37 | return ( 38 | setCopied(props.toCopy)}> 39 | {isCopied ? ( 40 | 41 | 42 | {t('accountDetails.copied')} 43 | 44 | ) : ( 45 | 46 | 47 | 48 | )} 49 | {isCopied ? '' : props.children} 50 | 51 | ) 52 | } 53 | -------------------------------------------------------------------------------- /src/components/AccountDetailsModal/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | import AccountDetails from '../AccountDetails' 4 | import { Modal } from '@honeycomb-finance/core' 5 | 6 | const Wrapper = styled.div` 7 | ${({ theme }) => theme.flexColumnNoWrap} 8 | margin: 0; 9 | padding: 0; 10 | width: 100%; 11 | ` 12 | 13 | export default function AccountDetailsModal({ 14 | pendingTransactions, 15 | confirmedTransactions, 16 | ENSName, 17 | open, 18 | closeModal, 19 | onWalletChange 20 | }: { 21 | pendingTransactions: string[] // hashes of pending 22 | confirmedTransactions: string[] // hashes of confirmed 23 | ENSName?: string 24 | open: boolean 25 | closeModal: () => void 26 | onWalletChange: () => void 27 | }) { 28 | const getModalContent = () => { 29 | return ( 30 | 37 | ) 38 | } 39 | 40 | return ( 41 | 42 | {getModalContent()} 43 | 44 | ) 45 | } 46 | -------------------------------------------------------------------------------- /src/components/Beta/ComingSoon/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { useTranslation } from '@honeycomb-finance/shared' 3 | import { Text } from '@honeycomb-finance/core' 4 | 5 | import { Wrapper } from './styled' 6 | 7 | const ComingSoon = () => { 8 | const { t } = useTranslation() 9 | 10 | return ( 11 | 12 | 13 | {t('swapPage.comingSoon')} 14 | 15 | 16 | ) 17 | } 18 | export default ComingSoon 19 | -------------------------------------------------------------------------------- /src/components/Beta/ComingSoon/styled.ts: -------------------------------------------------------------------------------- 1 | import { Box } from '@honeycomb-finance/core' 2 | import styled from 'styled-components' 3 | 4 | export const Wrapper = styled(Box)` 5 | display: flex; 6 | align-items: center; 7 | justify-content: center; 8 | width: 100%; 9 | height: 100%; 10 | border-radius: 10px; 11 | padding: 10px; 12 | min-height: 350px; 13 | background-color: ${({ theme }) => theme.bg2}; 14 | ` 15 | -------------------------------------------------------------------------------- /src/components/Beta/TransactionSubmitted/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useContext } from 'react' 2 | import { Box, Text, Button } from '@honeycomb-finance/core' 3 | import { useTranslation, getEtherscanLink } from '@honeycomb-finance/shared' 4 | import { Root, Link } from './styled' 5 | import { ArrowUpCircle } from 'react-feather' 6 | import { useChainId } from 'src/hooks' 7 | import { ThemeContext } from 'styled-components' 8 | 9 | interface Props { 10 | onClose: () => void 11 | hash?: string 12 | } 13 | 14 | const TransactionSubmitted = ({ onClose, hash }: Props) => { 15 | const chainId = useChainId() 16 | const { t } = useTranslation() 17 | const theme = useContext(ThemeContext) 18 | 19 | return ( 20 | 21 | 22 | 23 | 24 | 25 | 26 | {t('earn.transactionSubmitted')} 27 | 28 | {chainId && hash && ( 29 | 36 | {t('transactionConfirmation.viewExplorer')} 37 | 38 | )} 39 | 40 | 43 | 44 | ) 45 | } 46 | export default TransactionSubmitted 47 | -------------------------------------------------------------------------------- /src/components/Beta/TransactionSubmitted/styled.ts: -------------------------------------------------------------------------------- 1 | import { Box, Text } from '@honeycomb-finance/core' 2 | import styled from 'styled-components' 3 | 4 | export const Root = styled(Box)` 5 | display: flex; 6 | flex-direction: column; 7 | height: 100%; 8 | padding: 10px; 9 | ` 10 | export const Link = styled(Text)` 11 | text-decoration: none; 12 | color: ${({ theme }) => theme.blue1}; 13 | ` 14 | -------------------------------------------------------------------------------- /src/components/Card/index.tsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components' 2 | import { Box } from 'rebass/styled-components' 3 | 4 | const Card = styled(Box)<{ padding?: string; border?: string; borderRadius?: string }>` 5 | width: 100%; 6 | border-radius: 16px; 7 | padding: 1.25rem; 8 | padding: ${({ padding }) => padding}; 9 | border: ${({ border }) => border}; 10 | border-radius: ${({ borderRadius }) => borderRadius}; 11 | ` 12 | export default Card 13 | 14 | export const BlackCard = styled(Card)` 15 | background-color: ${({ theme }) => theme.bg2}; 16 | color: ${({ theme }) => theme.text1}; 17 | ` 18 | -------------------------------------------------------------------------------- /src/components/Column/index.tsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components' 2 | 3 | const Column = styled.div` 4 | display: flex; 5 | flex-direction: column; 6 | justify-content: flex-start; 7 | ` 8 | export const ColumnCenter = styled(Column)` 9 | width: 100%; 10 | align-items: center; 11 | ` 12 | 13 | export const AutoColumn = styled.div<{ 14 | gap?: 'sm' | 'md' | 'lg' | string 15 | justify?: 'stretch' | 'center' | 'start' | 'end' | 'flex-start' | 'flex-end' | 'space-between' 16 | }>` 17 | display: grid; 18 | grid-auto-rows: auto; 19 | grid-row-gap: ${({ gap }) => (gap === 'sm' && '8px') || (gap === 'md' && '12px') || (gap === 'lg' && '24px') || gap}; 20 | justify-items: ${({ justify }) => justify && justify}; 21 | ` 22 | -------------------------------------------------------------------------------- /src/components/Confetti/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactConfetti from 'react-confetti' 3 | import { useWindowSize } from '../../hooks/useWindowSize' 4 | 5 | // eslint-disable-next-line react/prop-types 6 | export default function Confetti({ start, variant }: { start: boolean; variant?: string }) { 7 | const { width, height } = useWindowSize() 8 | 9 | const _variant = variant ? variant : height && width && height > 1.5 * width ? 'bottom' : variant 10 | 11 | return start && width && height ? ( 12 | 31 | ) : null 32 | } 33 | -------------------------------------------------------------------------------- /src/components/Header/HederaPoolWarning.tsx: -------------------------------------------------------------------------------- 1 | import React, { useCallback, useEffect, useState } from 'react' 2 | import styled from 'styled-components' 3 | import { AlertTriangle, X } from 'react-feather' 4 | import { isMobile } from 'react-device-detect' 5 | import { useLocation } from 'react-router-dom' 6 | import { useChainId } from 'src/hooks' 7 | import { ChainId } from '@pangolindex/sdk' 8 | 9 | const Alert = styled.div<{ isActive: any }>` 10 | width: 100%; 11 | padding: 6px 6px; 12 | background-color: ${({ theme }) => theme.blue1}; 13 | color: white; 14 | font-size: 11px; 15 | justify-content: space-between; 16 | align-items: center; 17 | display: ${({ isActive }) => (isActive ? 'flex' : 'none')}; 18 | ` 19 | 20 | export const StyledClose = styled(X)` 21 | :hover { 22 | cursor: pointer; 23 | } 24 | ` 25 | const message = `The wHBAR rewards have already been replenished, check the "Your farms" tab, stake some liquidity in the pool 26 | and click remove, this way your liquidity will be removed from the farm with your wHBAR rewards.` 27 | 28 | export default function HederaPoolWarning() { 29 | const chainId = useChainId() 30 | const { pathname } = useLocation() 31 | 32 | const [isActive, setIsActive] = useState(chainId === ChainId.HEDERA_MAINNET && pathname === '/pool/standard') 33 | 34 | useEffect(() => { 35 | if (chainId === ChainId.HEDERA_MAINNET && pathname === '/pool/standard') { 36 | setIsActive(true) 37 | } 38 | if (isActive && pathname !== '/pool/standard') { 39 | setIsActive(false) 40 | } 41 | // eslint-disable-next-line react-hooks/exhaustive-deps 42 | }, [setIsActive, pathname, chainId]) 43 | 44 | const onClose = useCallback(() => setIsActive(false), [setIsActive]) 45 | 46 | return isMobile ? ( 47 | 48 |
49 | 50 | {message} 51 |
52 | 53 |
54 | ) : ( 55 | 56 |
57 | 58 | {message} 59 |
60 | 61 |
62 | ) 63 | } 64 | -------------------------------------------------------------------------------- /src/components/Header/URLWarning.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | import { AlertTriangle, X } from 'react-feather' 4 | import { useURLWarningToggle, useURLWarningVisible } from '../../state/user/hooks' 5 | import { isMobile } from 'react-device-detect' 6 | import { useTranslation } from '@honeycomb-finance/shared' 7 | 8 | const PhishAlert = styled.div<{ isActive: any }>` 9 | width: 100%; 10 | padding: 6px 6px; 11 | background-color: ${({ theme }) => theme.blue1}; 12 | color: white; 13 | font-size: 11px; 14 | justify-content: space-between; 15 | align-items: center; 16 | display: ${({ isActive }) => (isActive ? 'flex' : 'none')}; 17 | ` 18 | 19 | export const StyledClose = styled(X)` 20 | :hover { 21 | cursor: pointer; 22 | } 23 | ` 24 | 25 | export default function URLWarning() { 26 | const toggleURLWarning = useURLWarningToggle() 27 | const showURLWarning = useURLWarningVisible() 28 | const { t } = useTranslation() 29 | 30 | return isMobile ? ( 31 | 32 |
33 | {t('header.makeSureURLWarning')} 34 | app.pangolin.exchange 35 |
36 | 37 |
38 | ) : window.location.hostname === 'app.pangolin.exchange' ? ( 39 | 40 |
41 | {t('header.alwaysMakeSureWarning')} 42 | app.pangolin.exchange - 43 | {t('header.bookmarkIt')} 44 |
45 | 46 |
47 | ) : null 48 | } 49 | -------------------------------------------------------------------------------- /src/components/Icons/C14.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | interface Props { 4 | size: number 5 | fillColor: string 6 | } 7 | const C14: React.FC = props => { 8 | const { size } = props 9 | return ( 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | ) 39 | } 40 | 41 | export default C14 42 | -------------------------------------------------------------------------------- /src/components/Icons/CoinbasePay.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | interface Props { 4 | size: number 5 | fillColor: string 6 | } 7 | const CoinbasePay: React.FC = props => { 8 | const { size } = props 9 | return ( 10 | 11 | 12 | 16 | 17 | ) 18 | } 19 | 20 | export default CoinbasePay 21 | -------------------------------------------------------------------------------- /src/components/Icons/Dashboard.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | interface Props { 4 | size: number 5 | fillColor: string 6 | } 7 | const Dashboard: React.FC = props => { 8 | const { size, fillColor } = props 9 | return ( 10 | 11 | 12 | 13 | 14 | 23 | 32 | 41 | 42 | 43 | 44 | ) 45 | } 46 | 47 | export default Dashboard 48 | -------------------------------------------------------------------------------- /src/components/Icons/MoonPay.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | interface Props { 4 | size: number 5 | fillColor: string 6 | } 7 | const MoonPay: React.FC = props => { 8 | const { size } = props 9 | return ( 10 | 11 | 12 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | ) 24 | } 25 | 26 | export default MoonPay 27 | -------------------------------------------------------------------------------- /src/components/Icons/Swap.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | interface Props { 4 | size: number 5 | fillColor: string 6 | } 7 | const Swap: React.FC = props => { 8 | const { size, fillColor } = props 9 | return ( 10 | 11 | 16 | 17 | ) 18 | } 19 | 20 | export default Swap 21 | -------------------------------------------------------------------------------- /src/components/Icons/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Dashboard } from './Dashboard' 2 | export { default as Swap } from './Swap' 3 | export { default as Stake } from './Stake' 4 | export { default as Buy } from './Buy' 5 | export { default as Pool } from './Pool' 6 | export { default as Vote } from './Vote' 7 | export { default as Logo } from './Logo' 8 | export { default as Bridge } from './Bridge' 9 | export { default as CoinbasePay } from './CoinbasePay' 10 | export { default as MoonPay } from './MoonPay' 11 | export { default as C14 } from './C14' 12 | -------------------------------------------------------------------------------- /src/components/Identicon/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useRef } from 'react' 2 | import styled from 'styled-components' 3 | import Jazzicon from '@metamask/jazzicon' 4 | import { useActiveWeb3React } from '@honeycomb-finance/shared' 5 | 6 | const StyledIdenticonContainer = styled.div` 7 | height: 100%; 8 | display: flex; 9 | align-items: center; 10 | justify-content: center; 11 | ` 12 | 13 | export default function Identicon() { 14 | const ref = useRef() 15 | 16 | const { account } = useActiveWeb3React() 17 | 18 | useEffect(() => { 19 | if (account && ref.current) { 20 | ref.current.innerHTML = '' 21 | ref.current.appendChild(Jazzicon(16, parseInt(account.slice(2, 10), 16))) 22 | } 23 | }, [account]) 24 | 25 | // https://github.com/DefinitelyTyped/DefinitelyTyped/issues/30451 26 | return 27 | } 28 | -------------------------------------------------------------------------------- /src/components/Loader/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import styled, { keyframes } from 'styled-components' 4 | 5 | const rotate = keyframes` 6 | from { 7 | transform: rotate(0deg); 8 | } 9 | to { 10 | transform: rotate(360deg); 11 | } 12 | ` 13 | 14 | const StyledSVG = styled.svg<{ size: string; stroke?: string }>` 15 | animation: 2s ${rotate} linear infinite; 16 | height: ${({ size }) => size}; 17 | width: ${({ size }) => size}; 18 | path { 19 | stroke: ${({ stroke, theme }) => stroke ?? theme.primary1}; 20 | } 21 | ` 22 | 23 | /** 24 | * Takes in custom size and stroke for circle color, default to primary color as fill, 25 | * need ...rest for layered styles on top 26 | */ 27 | export default function Loader({ 28 | size = '16px', 29 | stroke, 30 | ...rest 31 | }: { 32 | size?: string 33 | stroke?: string 34 | [k: string]: any 35 | }) { 36 | return ( 37 | 38 | 44 | 45 | ) 46 | } 47 | -------------------------------------------------------------------------------- /src/components/MigrationCard/styleds.tsx: -------------------------------------------------------------------------------- 1 | import { Box, Button } from '@honeycomb-finance/core' 2 | import styled from 'styled-components' 3 | 4 | export const Panel = styled(Box)` 5 | background-color: ${({ theme }) => theme.bg2}; 6 | position: relative; 7 | width: 100%; 8 | height: 100%; 9 | display: flex; 10 | flex-direction: column; 11 | justify-content: flex-start; 12 | padding: 20px; 13 | border-radius: 10px; 14 | ` 15 | 16 | export const OptionButton = styled.div<{ active?: boolean; disabled?: boolean }>` 17 | font-weight: 500; 18 | width: fit-content; 19 | white-space: nowrap; 20 | padding: 6px; 21 | border-radius: 6px; 22 | border: 1px solid ${({ theme }) => theme.bg4}; 23 | background-color: ${({ active, theme }) => (active ? theme.yellow2 : theme.bg7)}; 24 | color: ${({ theme }) => theme.text1}; 25 | 26 | :hover { 27 | cursor: ${({ disabled }) => !disabled && 'pointer'}; 28 | } 29 | ` 30 | 31 | export const OptionsWrapper = styled.div` 32 | display: grid; 33 | grid-template-columns: auto auto auto auto; 34 | grid-gap: 10px; 35 | ` 36 | 37 | export const Divider = styled(Box)` 38 | height: 1px; 39 | background-color: ${({ theme }) => theme.bg7}; 40 | margin: 10px 0px 10px 0px; 41 | width: 100%; 42 | ` 43 | 44 | export const MigrateButton = styled(Button)` 45 | width: fit-content; 46 | ${({ theme }) => theme.mediaWidth.upToSmall` 47 | width: 48%; 48 | `}; 49 | ` 50 | 51 | export const InnerWrapper = styled(Box)` 52 | display: inline-flex; 53 | justify-content: space-between; 54 | align-items: center; 55 | border: 1px solid #2c2d33; 56 | border-radius: 4px; 57 | padding: 10px; 58 | margin-top: 10px; 59 | ` 60 | -------------------------------------------------------------------------------- /src/components/MigrationModal/ChoosePool/PairData.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Pair } from '@pangolindex/sdk' 3 | import { PairBox } from './styleds' 4 | import { MinichefStakingInfo } from '@honeycomb-finance/pools' 5 | import { useTranslation } from '@honeycomb-finance/shared' 6 | import { Text, Box, DoubleCurrencyLogo, Checkbox } from '@honeycomb-finance/core' 7 | import { useGetPairDataFromPair } from '../../../state/stake/hooks' 8 | 9 | export interface PairDataProps { 10 | pair: Pair 11 | stakingData: MinichefStakingInfo | undefined 12 | selected: boolean 13 | address: string 14 | toggleIndividualSelect: (address: string) => void 15 | } 16 | 17 | const PairData = ({ pair, selected, address, toggleIndividualSelect }: PairDataProps) => { 18 | const { currency0, currency1 } = useGetPairDataFromPair(pair) 19 | 20 | const { t } = useTranslation() 21 | return ( 22 | { 24 | toggleIndividualSelect(address) 25 | }} 26 | > 27 | 28 | toggleIndividualSelect(address)} /> 29 | 30 | 31 | 32 | 33 | {currency0.symbol}-{currency1.symbol} {t('migratePage.pool')} 34 | 35 | 36 | 37 | ) 38 | } 39 | 40 | export default PairData 41 | -------------------------------------------------------------------------------- /src/components/MigrationModal/ChoosePool/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Wrapper } from './styleds' 3 | import { MinichefStakingInfo } from '@honeycomb-finance/pools' 4 | import { Text, Checkbox, Box, Button } from '@honeycomb-finance/core' 5 | import { useTranslation } from '@honeycomb-finance/shared' 6 | 7 | import { Pair } from '@pangolindex/sdk' 8 | import PairData from './PairData' 9 | 10 | export interface ChoosePoolProps { 11 | allChoosePool: { [address: string]: { pair: Pair; staking: MinichefStakingInfo } } 12 | allPool: { [address: string]: { pair: Pair; staking: MinichefStakingInfo } } 13 | v2IsLoading: boolean 14 | toggleSelectAll: (value: boolean) => void 15 | toggleIndividualSelect: (address: string) => void 16 | goNext: () => void 17 | } 18 | 19 | const ChoosePool = ({ 20 | allChoosePool, 21 | allPool, 22 | v2IsLoading, 23 | toggleSelectAll, 24 | toggleIndividualSelect, 25 | goNext 26 | }: ChoosePoolProps) => { 27 | const { t } = useTranslation() 28 | return ( 29 | 30 | 31 | {t('migratePage.migrationModalDescription')} 32 | 33 | 34 | { 37 | toggleSelectAll(check) 38 | }} 39 | checked={(Object.keys(allPool) || []).length === (Object.keys(allChoosePool) || []).length} 40 | /> 41 | 42 | 43 | 44 | {(Object.keys(allPool) || []).map(address => { 45 | return ( 46 | 54 | ) 55 | })} 56 | 57 | 58 | 69 | 70 | 71 | ) 72 | } 73 | export default ChoosePool 74 | -------------------------------------------------------------------------------- /src/components/MigrationModal/ChoosePool/styleds.tsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components' 2 | import { Box } from '@honeycomb-finance/core' 3 | 4 | export const Wrapper = styled.div` 5 | margin: 0; 6 | width: 100%; 7 | ` 8 | export const PairBox = styled(Box)` 9 | background-color: ${({ theme }) => theme.bg6}; 10 | padding: 15px; 11 | display: flex; 12 | justify-content: space-between; 13 | align-items: center; 14 | border-radius: 4px; 15 | border: 1px solid ${({ theme }) => theme.text2}; 16 | margin: 5px 0px 5px 0px; 17 | cursor: pointer; 18 | ` 19 | -------------------------------------------------------------------------------- /src/components/MigrationModal/Loader/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Text } from '@honeycomb-finance/core' 3 | import Circle from '../../../assets/svg/blue-loader.svg' 4 | import { CustomLightSpinner } from '../../../theme/components' 5 | import { Wrapper, ConfirmedIcon, Section } from './styleds' 6 | import { AutoColumn } from '../../Column' 7 | 8 | interface LoaderProps { 9 | loadingText?: string 10 | } 11 | 12 | const Loader = ({ loadingText }: LoaderProps) => { 13 | return ( 14 | 15 |
16 | 17 | 18 | 19 | 20 | {loadingText && ( 21 | 22 | 23 | {loadingText} 24 | 25 | 26 | )} 27 |
28 |
29 | ) 30 | } 31 | 32 | export default Loader 33 | -------------------------------------------------------------------------------- /src/components/MigrationModal/Loader/styleds.tsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components' 2 | import { ColumnCenter, AutoColumn } from '../../Column' 3 | 4 | export const Wrapper = styled.div` 5 | margin: 0; 6 | width: 100%; 7 | ` 8 | export const ConfirmedIcon = styled(ColumnCenter)` 9 | padding: 60px 0; 10 | ` 11 | 12 | export const Section = styled(AutoColumn)` 13 | padding: 24px; 14 | ` 15 | -------------------------------------------------------------------------------- /src/components/MigrationModal/PoolInfo/styleds.tsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components' 2 | import { Box, TextInput } from '@honeycomb-finance/core' 3 | 4 | export const InfoWrapper = styled.div` 5 | margin: 0; 6 | width: 100%; 7 | ` 8 | 9 | export const ContentBox = styled(Box)` 10 | background-color: ${({ theme }) => theme.bg6}; 11 | padding: 15px; 12 | border-radius: 4px; 13 | ` 14 | 15 | export const DataBox = styled(Box)` 16 | align-items: center; 17 | justify-content: space-between; 18 | display: flex; 19 | margin: 5px 0px 5px 0px; 20 | ` 21 | 22 | export const TextBox = styled(TextInput)` 23 | background-color: ${({ theme }) => theme.bg6}; 24 | padding: 15px; 25 | align-items: center; 26 | border-radius: 4px; 27 | ` 28 | 29 | export const StyledBalanceMax = styled.button` 30 | height: 28px; 31 | background-color: ${({ theme }) => theme.bg2}; 32 | border: 1px solid ${({ theme }) => theme.bg2}; 33 | border-radius: 0.5rem; 34 | font-size: 0.875rem; 35 | 36 | font-weight: 500; 37 | cursor: pointer; 38 | margin-right: 0.5rem; 39 | color: ${({ theme }) => theme.text2}; 40 | :hover { 41 | border: 1px solid ${({ theme }) => theme.primary1}; 42 | } 43 | :focus { 44 | border: 1px solid ${({ theme }) => theme.primary1}; 45 | outline: none; 46 | } 47 | 48 | ${({ theme }) => theme.mediaWidth.upToExtraSmall` 49 | margin-right: 0.5rem; 50 | `}; 51 | ` 52 | -------------------------------------------------------------------------------- /src/components/MigrationModal/Stake/styleds.tsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components' 2 | 3 | export const Wrapper = styled.div` 4 | margin: 0; 5 | width: 100%; 6 | ` 7 | -------------------------------------------------------------------------------- /src/components/MigrationModal/StepView/styleds.tsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components' 2 | import { ColumnCenter, AutoColumn } from '../../Column' 3 | 4 | export const Wrapper = styled.div` 5 | margin: 0; 6 | width: 100%; 7 | ` 8 | export const ConfirmedIcon = styled(ColumnCenter)` 9 | padding: 60px 0; 10 | ` 11 | 12 | export const Section = styled(AutoColumn)` 13 | padding: 24px; 14 | ` 15 | -------------------------------------------------------------------------------- /src/components/MigrationModal/Unstake/styleds.tsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components' 2 | 3 | export const Wrapper = styled.div` 4 | margin: 0; 5 | width: 100%; 6 | ` 7 | -------------------------------------------------------------------------------- /src/components/MigrationModal/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useContext } from 'react' 2 | import { ApplicationModal } from '../../state/application/actions' 3 | import { useModalOpen, useMigrationModalToggle } from '../../state/application/hooks' 4 | import { Wrapper } from './styleds' 5 | import StepView from './StepView' 6 | import { Pair } from '@pangolindex/sdk' 7 | import { MinichefStakingInfo } from '@honeycomb-finance/pools' 8 | import { Modal } from '@honeycomb-finance/core' 9 | import { ThemeContext } from 'styled-components' 10 | 11 | export interface MigrationModalProps { 12 | selectedPool?: { [address: string]: { pair: Pair; staking: MinichefStakingInfo } } 13 | version: number 14 | } 15 | 16 | const MigrationModal = ({ selectedPool, version }: MigrationModalProps) => { 17 | const migrationModalOpen = useModalOpen(ApplicationModal.MIGRATION) 18 | const toggleMigrationModal = useMigrationModalToggle() 19 | const theme = useContext(ThemeContext) 20 | 21 | return ( 22 | 23 | 24 | 25 | 26 | 27 | ) 28 | } 29 | export default MigrationModal 30 | -------------------------------------------------------------------------------- /src/components/MigrationModal/styleds.tsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components' 2 | 3 | export const Wrapper = styled.div` 4 | ${({ theme }) => theme.flexColumnNoWrap} 5 | margin: 0; 6 | padding: 30px; 7 | width: 100%; 8 | ` 9 | -------------------------------------------------------------------------------- /src/components/Popups/TransactionPopup.tsx: -------------------------------------------------------------------------------- 1 | import React, { useContext } from 'react' 2 | import { AlertCircle, CheckCircle } from 'react-feather' 3 | import styled, { ThemeContext } from 'styled-components' 4 | import { useChainId } from '../../hooks' 5 | import { TYPE } from '../../theme' 6 | import { ExternalLink } from '../../theme/components' 7 | import { AutoColumn } from '../Column' 8 | import { AutoRow } from '../Row' 9 | import { useTranslation, getEtherscanLink } from '@honeycomb-finance/shared' 10 | 11 | const RowNoFlex = styled(AutoRow)` 12 | flex-wrap: nowrap; 13 | ` 14 | 15 | export default function TransactionPopup({ 16 | hash, 17 | success, 18 | summary 19 | }: { 20 | hash: string 21 | success?: boolean 22 | summary?: string 23 | }) { 24 | const chainId = useChainId() 25 | 26 | const { t } = useTranslation() 27 | const theme = useContext(ThemeContext) 28 | 29 | return ( 30 | 31 |
32 | {success ? : } 33 |
34 | 35 | 36 | {summary ?? t('popups.hash') + hash.slice(0, 8) + '...' + hash.slice(58, 65)} 37 | 38 | {chainId && ( 39 | {t('popups.viewExplorer')} 40 | )} 41 | 42 |
43 | ) 44 | } 45 | -------------------------------------------------------------------------------- /src/components/Popups/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | import { useActivePopups } from '../../state/application/hooks' 4 | import { AutoColumn } from '../Column' 5 | import PopupItem from './PopupItem' 6 | import { useURLWarningVisible } from '../../state/user/hooks' 7 | import { Portal } from 'react-portal' 8 | 9 | const MobilePopupWrapper = styled.div<{ height: string | number }>` 10 | position: relative; 11 | max-width: 100%; 12 | height: ${({ height }) => height}; 13 | margin: ${({ height }) => (height ? '0 auto;' : 0)}; 14 | margin-bottom: ${({ height }) => (height ? '20px' : 0)}; 15 | 16 | display: none; 17 | ${({ theme }) => theme.mediaWidth.upToSmall` 18 | display: block; 19 | `}; 20 | ` 21 | 22 | const MobilePopupInner = styled.div` 23 | height: 99%; 24 | overflow-x: auto; 25 | overflow-y: hidden; 26 | display: flex; 27 | flex-direction: row; 28 | -webkit-overflow-scrolling: touch; 29 | ::-webkit-scrollbar { 30 | display: none; 31 | } 32 | ` 33 | 34 | const FixedPopupColumn = styled(AutoColumn)<{ extraPadding: boolean }>` 35 | position: fixed; 36 | top: ${({ extraPadding }) => (extraPadding ? '108px' : '88px')}; 37 | right: 1rem; 38 | max-width: 355px !important; 39 | width: 100%; 40 | z-index: 1000; 41 | 42 | ${({ theme }) => theme.mediaWidth.upToSmall` 43 | display: none; 44 | `}; 45 | ` 46 | 47 | export default function Popups() { 48 | // get all popups 49 | const activePopups = useActivePopups() 50 | 51 | const urlWarningActive = useURLWarningVisible() 52 | 53 | return ( 54 | 55 | 56 | {activePopups.map((item: any) => ( 57 | 58 | ))} 59 | 60 | 0 ? 'fit-content' : 0}> 61 | 62 | {activePopups // reverse so new items up front 63 | .slice(0) 64 | .reverse() 65 | .map((item: any) => ( 66 | 67 | ))} 68 | 69 | 70 | 71 | ) 72 | } 73 | -------------------------------------------------------------------------------- /src/components/Row/index.tsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components' 2 | import { Box } from 'rebass/styled-components' 3 | 4 | const Row = styled(Box)<{ align?: string; padding?: string; border?: string; borderRadius?: string }>` 5 | width: 100%; 6 | display: flex; 7 | padding: 0; 8 | align-items: ${({ align }) => (align ? align : 'center')}; 9 | padding: ${({ padding }) => padding}; 10 | border: ${({ border }) => border}; 11 | border-radius: ${({ borderRadius }) => borderRadius}; 12 | ` 13 | 14 | export const RowBetween = styled(Row)` 15 | justify-content: space-between; 16 | ` 17 | 18 | export const AutoRow = styled(Row)<{ gap?: string; justify?: string }>` 19 | flex-wrap: wrap; 20 | margin: ${({ gap }) => gap && `-${gap}`}; 21 | justify-content: ${({ justify }) => justify && justify}; 22 | 23 | & > * { 24 | margin: ${({ gap }) => gap} !important; 25 | } 26 | ` 27 | 28 | export const RowFixed = styled(Row)<{ gap?: string; justify?: string }>` 29 | width: fit-content; 30 | margin: ${({ gap }) => gap && `-${gap}`}; 31 | ` 32 | -------------------------------------------------------------------------------- /src/components/Stat/styled.tsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components' 2 | 3 | export const AnalyticsLink = styled.a` 4 | display: flex; 5 | align-items: center; 6 | padding: 4px; 7 | border-radius: 4px; 8 | 9 | svg { 10 | height: 16px; 11 | } 12 | 13 | path { 14 | fill: ${({ theme }) => theme.text1}; 15 | } 16 | 17 | &:hover, 18 | &focus { 19 | path { 20 | fill: ${({ theme }) => theme.yellow1}; 21 | } 22 | } 23 | ` 24 | -------------------------------------------------------------------------------- /src/components/StyledMenu/index.tsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components' 2 | 3 | export const StyledMenuButton = styled.button` 4 | position: relative; 5 | height: 100%; 6 | border: none; 7 | background-color: transparent; 8 | margin: 0; 9 | padding: 0; 10 | background-color: ${({ theme }) => theme.bg2}; 11 | 12 | padding: 8px 12px; 13 | border-radius: 4px; 14 | 15 | :hover, 16 | :focus { 17 | cursor: pointer; 18 | outline: none; 19 | background-color: ${({ theme }) => theme.bg4}; 20 | } 21 | 22 | svg { 23 | margin-top: 2px; 24 | } 25 | ` 26 | 27 | export const StyledMenu = styled.div` 28 | margin-left: 0.5rem; 29 | display: flex; 30 | justify-content: center; 31 | align-items: center; 32 | position: relative; 33 | border: none; 34 | text-align: left; 35 | ` 36 | 37 | export const MenuFlyout = styled.span` 38 | min-width: 20.125rem; 39 | background-color: ${({ theme }) => theme.bg2}; 40 | box-shadow: 0px 0px 1px rgba(0, 0, 0, 0.01), 0px 4px 8px rgba(0, 0, 0, 0.04), 0px 16px 24px rgba(0, 0, 0, 0.04), 41 | 0px 24px 32px rgba(0, 0, 0, 0.01); 42 | border-radius: 12px; 43 | display: flex; 44 | flex-direction: column; 45 | font-size: 1rem; 46 | position: absolute; 47 | top: 4rem; 48 | right: 0rem; 49 | z-index: 100; 50 | ` 51 | -------------------------------------------------------------------------------- /src/components/SwitchSubgraph/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { ToggleButtons } from '@honeycomb-finance/core' 3 | import { useApplicationState } from '@honeycomb-finance/state-hooks' 4 | import { useChainId } from 'src/hooks' 5 | 6 | export default function SwitchSubgraph() { 7 | const chainId = useChainId() 8 | const { useSubgraph, setUseSubgraph } = useApplicationState() 9 | 10 | return ( 11 | { 15 | setUseSubgraph((state: any) => ({ 16 | ...state, 17 | [chainId]: value === 'Subgraph' 18 | })) 19 | window.location.reload() 20 | }} 21 | /> 22 | ) 23 | } 24 | -------------------------------------------------------------------------------- /src/components/Web3ReactManager/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react' 2 | import { useWeb3React } from '@web3-react/core' 3 | import styled from 'styled-components' 4 | import { NetworkContextName, useTranslation } from '@honeycomb-finance/shared' 5 | import Loader from '../Loader' 6 | 7 | const MessageWrapper = styled.div` 8 | display: flex; 9 | align-items: center; 10 | justify-content: center; 11 | height: 20rem; 12 | ` 13 | 14 | const Message = styled.h2` 15 | color: ${({ theme }) => theme.secondary1}; 16 | ` 17 | 18 | export default function Web3ReactManager({ children }: { children: JSX.Element }) { 19 | const { t } = useTranslation() 20 | const { active } = useWeb3React() 21 | const { active: networkActive, error: networkError } = useWeb3React(NetworkContextName) 22 | 23 | // handle delayed loader state 24 | const [showLoader, setShowLoader] = useState(false) 25 | useEffect(() => { 26 | const timeout = setTimeout(() => { 27 | setShowLoader(true) 28 | }, 600) 29 | 30 | return () => { 31 | clearTimeout(timeout) 32 | } 33 | }, []) 34 | 35 | // if the account context isn't active, and there's an error on the network context, it's an irrecoverable error 36 | if (!active && networkError) { 37 | return ( 38 | 39 | {t('web3ReactManager.unknownError')} 40 | 41 | ) 42 | } 43 | 44 | // if neither context is active, spin 45 | if (!active && !networkActive) { 46 | return showLoader ? ( 47 | 48 | 49 | 50 | ) : null 51 | } 52 | 53 | return children 54 | } 55 | -------------------------------------------------------------------------------- /src/components/earn/styled.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components' 2 | import { AutoColumn } from '../Column' 3 | 4 | export const DataCard = styled(AutoColumn)<{ disabled?: boolean }>` 5 | background: radial-gradient(76.02% 75.41% at 1.84% 0%, #f97316 0%, #e84142 100%); 6 | border-radius: 12px; 7 | width: 100%; 8 | position: relative; 9 | overflow: hidden; 10 | ` 11 | 12 | export const CardSection = styled(AutoColumn)<{ disabled?: boolean }>` 13 | padding: 1rem; 14 | z-index: 1; 15 | opacity: ${({ disabled }) => disabled && '0.4'}; 16 | ` 17 | -------------------------------------------------------------------------------- /src/constants/abis/staking-rewards.ts: -------------------------------------------------------------------------------- 1 | import { Interface } from '@ethersproject/abi' 2 | import StakingRewards from '@pangolindex/governance/artifacts/contracts/StakingRewards.sol/StakingRewards.json' 3 | 4 | const STAKING_REWARDS_INTERFACE = new Interface(StakingRewards.abi) 5 | 6 | export { STAKING_REWARDS_INTERFACE } 7 | -------------------------------------------------------------------------------- /src/hooks/index.ts: -------------------------------------------------------------------------------- 1 | import { ChainId, ALL_CHAINS, CHAINS, TokenAmount } from '@pangolindex/sdk' 2 | import { PANGOLIN_API_BASE_URL, Tokens, useActiveWeb3React } from '@honeycomb-finance/shared' 3 | import { useQuery } from 'react-query' 4 | import axios from 'axios' 5 | 6 | export function useChainId() { 7 | const { chainId } = useActiveWeb3React() 8 | return (chainId || ChainId.AVALANCHE) as ChainId 9 | } 10 | 11 | export const useChain = (chainId: number) => { 12 | return ALL_CHAINS.filter(chain => chain.chain_id === chainId)[0] 13 | } 14 | 15 | export const usePngSymbol = () => { 16 | const chainId = useChainId() 17 | return CHAINS[chainId || ChainId.AVALANCHE].png_symbol! 18 | } 19 | 20 | export function usePNGCirculationSupply() { 21 | const chainId = useChainId() 22 | const { PNG } = Tokens 23 | const png = PNG[chainId] 24 | 25 | return useQuery( 26 | ['png-circulation-supply', png.chainId, png.address], 27 | async () => { 28 | if (!png) return undefined 29 | 30 | try { 31 | const response = await axios.get(`${PANGOLIN_API_BASE_URL}/v2/${chainId}/png/circulating-supply`, { 32 | timeout: 3 * 1000 // 3 seconds 33 | }) 34 | 35 | const data = response.data 36 | return new TokenAmount(png, data) 37 | } catch (error) { 38 | return undefined 39 | } 40 | }, 41 | { 42 | cacheTime: 60 * 5 * 1000 // 5 minutes 43 | } 44 | ) 45 | } 46 | -------------------------------------------------------------------------------- /src/hooks/mixpanel.tsx: -------------------------------------------------------------------------------- 1 | import { useCallback } from 'react' 2 | import { 3 | useMixpanel as useComponentMixpanel, 4 | MixPanelEvents as ComponentsMixPanelEvents 5 | } from '@honeycomb-finance/shared' 6 | 7 | export enum MixPanelEvents {} 8 | 9 | export function useMixpanel() { 10 | const { track } = useComponentMixpanel() 11 | 12 | const overrideTrack = useCallback( 13 | (event: MixPanelEvents | ComponentsMixPanelEvents, properties: { [x: string]: any }) => { 14 | track(event as any, { source: 'interface', ...properties }) 15 | }, 16 | [track] 17 | ) 18 | 19 | return { track: overrideTrack } 20 | } 21 | -------------------------------------------------------------------------------- /src/hooks/useCopyClipboard.ts: -------------------------------------------------------------------------------- 1 | import copy from 'copy-to-clipboard' 2 | import { useCallback, useEffect, useState } from 'react' 3 | 4 | export default function useCopyClipboard(timeout = 500): [boolean, (toCopy: string) => void] { 5 | const [isCopied, setIsCopied] = useState(false) 6 | 7 | const staticCopy = useCallback(text => { 8 | const didCopy = copy(text) 9 | setIsCopied(didCopy) 10 | }, []) 11 | 12 | useEffect(() => { 13 | if (isCopied) { 14 | const hide = setTimeout(() => { 15 | setIsCopied(false) 16 | }, timeout) 17 | 18 | return () => { 19 | clearTimeout(hide) 20 | } 21 | } 22 | return undefined 23 | }, [isCopied, setIsCopied, timeout]) 24 | 25 | return [isCopied, staticCopy] 26 | } 27 | -------------------------------------------------------------------------------- /src/hooks/useTransactionDeadline.ts: -------------------------------------------------------------------------------- 1 | import { BigNumber } from 'ethers' 2 | import { useSelector } from '../state' 3 | 4 | // combines the current timestamp with the user setting to give the deadline that should be used for any submitted transaction 5 | // enforces a minimum deadline of 10 seconds from now 6 | export default function useTransactionDeadline(): BigNumber | undefined { 7 | const ttl = useSelector(state => state.user.userDeadline) 8 | const currentTimestampSeconds = BigNumber.from(Math.ceil(Date.now() / 1000)) 9 | return ttl && ttl > 10 ? currentTimestampSeconds.add(ttl) : currentTimestampSeconds.add(10) 10 | } 11 | -------------------------------------------------------------------------------- /src/hooks/useWindowSize.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react' 2 | 3 | const isClient = typeof window === 'object' 4 | 5 | function getSize() { 6 | return { 7 | width: isClient ? window.innerWidth : undefined, 8 | height: isClient ? window.innerHeight : undefined 9 | } 10 | } 11 | 12 | // https://usehooks.com/useWindowSize/ 13 | export function useWindowSize() { 14 | const [windowSize, setWindowSize] = useState(getSize) 15 | 16 | useEffect(() => { 17 | function handleResize() { 18 | setWindowSize(getSize()) 19 | } 20 | 21 | if (isClient) { 22 | window.addEventListener('resize', handleResize) 23 | return () => { 24 | window.removeEventListener('resize', handleResize) 25 | } 26 | } 27 | return undefined 28 | }, []) 29 | 30 | return windowSize 31 | } 32 | -------------------------------------------------------------------------------- /src/layout/Footer/PolicyModal.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Box, Modal } from '@honeycomb-finance/core' 3 | import ReactMarkdown from 'react-markdown' 4 | import { Scrollbars } from 'react-custom-scrollbars' 5 | import { CloseButton, Content, PolicyText } from './styled' 6 | 7 | interface Props { 8 | selectPolicy: string 9 | open: boolean 10 | closeModal: () => void 11 | } 12 | 13 | export default function PolicyModal({ selectPolicy, open, closeModal }: Props) { 14 | return ( 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | {selectPolicy} 23 | 24 | 25 | 26 | 27 | ) 28 | } 29 | -------------------------------------------------------------------------------- /src/layout/Footer/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useContext, useState } from 'react' 2 | import { Circle } from 'react-feather' 3 | import { Box, Text } from '@honeycomb-finance/core' 4 | import { ThemeContext } from 'styled-components' 5 | 6 | import { FooterFrame, Button, Policies } from './styled' 7 | import PolicyModal from './PolicyModal' 8 | import { PrivacyPolicy } from '../../constants/Policies/PrivacyPolicy' 9 | import { TermsService } from '../../constants/Policies/TermsService' 10 | import { CookiePolicy } from '../../constants/Policies/CPolicy' 11 | 12 | export default function Footer() { 13 | const [selectedPolicy, setSelectPolicy] = useState('') 14 | const theme = useContext(ThemeContext) 15 | 16 | const openModal = (policy: string) => { 17 | setSelectPolicy(policy) 18 | } 19 | 20 | const closeModal = () => { 21 | setSelectPolicy('') 22 | } 23 | 24 | return ( 25 | 26 | 0} closeModal={closeModal} /> 27 | 28 | 31 | 32 | 35 | 36 | 39 | 40 | 41 | Powered by 42 | 43 | Pangolin DAO 44 | 45 | 46 | 47 | ) 48 | } 49 | -------------------------------------------------------------------------------- /src/layout/Footer/styled.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components' 2 | import { Box, Text } from '@honeycomb-finance/core' 3 | import { CloseIcon } from 'src/theme' 4 | 5 | export const FooterFrame = styled.footer` 6 | width: 100%; 7 | height: 50px; 8 | display: grid; 9 | grid-template-columns: 75% 25%; 10 | align-items: center; 11 | background-color: ${({ theme }) => theme.color2}; 12 | 13 | ${({ theme }) => theme.mediaWidth.upToMedium` 14 | grid-template-columns: 1fr; 15 | `}; 16 | ` 17 | 18 | export const Policies = styled.div` 19 | display: flex; 20 | align-items: center; 21 | margin-left: 10px; 22 | 23 | ${({ theme }) => theme.mediaWidth.upToMedium` 24 | justify-content: center; 25 | `}; 26 | ` 27 | 28 | export const Button = styled(Text)` 29 | color: ${({ theme }) => theme.text1}; 30 | cursor: pointer; 31 | &:hover { 32 | text-decoration: underline; 33 | } 34 | ` 35 | 36 | export const CloseButton = styled(CloseIcon)` 37 | color: ${({ theme }) => theme.text1}; 38 | position: absolute; 39 | right: 9px; 40 | top: 9px; 41 | ` 42 | 43 | export const Content = styled(Box)` 44 | width: 70vw; 45 | height: 70vh; 46 | padding: 30px; 47 | 48 | ${({ theme }) => theme.mediaWidth.upToSmall` 49 | width: 100%; 50 | height: 100%; 51 | `}; 52 | ` 53 | 54 | export const PolicyText = styled(Box)` 55 | width: 100%; 56 | color: ${({ theme }) => theme.text1}; 57 | & a { 58 | color: ${({ theme }) => theme.text1}; 59 | } 60 | overflow-x: hidden; 61 | ` 62 | -------------------------------------------------------------------------------- /src/layout/Header/MenuIcon.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { IconMenu } from './styled' 3 | 4 | interface Props { 5 | active: boolean 6 | handleMobileMenu: () => void 7 | } 8 | 9 | export const MenuIcon: React.FC = ({ active, handleMobileMenu }) => { 10 | return ( 11 | 12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | 22 | ) 23 | } 24 | -------------------------------------------------------------------------------- /src/layout/Header/MobileHeader.tsx: -------------------------------------------------------------------------------- 1 | import { Box } from '@honeycomb-finance/core' 2 | import React from 'react' 3 | import Logo from '../Logo' 4 | import { MenuIcon } from './MenuIcon' 5 | import { MobileHeaderFrame, MobileLogoWrapper } from './styled' 6 | 7 | interface Props { 8 | activeMobileMenu: boolean 9 | handleMobileMenu: () => void 10 | } 11 | 12 | export const MobileHeader: React.FC = ({ activeMobileMenu, handleMobileMenu }) => { 13 | return ( 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | ) 24 | } 25 | -------------------------------------------------------------------------------- /src/layout/Logo/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Box } from '@honeycomb-finance/core' 3 | import Logo from 'src/assets/svg/logoIcon.svg' 4 | import LogoDark from 'src/assets/svg/logoSloganDark.svg' 5 | import LogoLight from 'src/assets/svg/logoSloganLight.svg' 6 | 7 | import { Title, LogoWrapper } from './styled' 8 | import { useIsDarkMode } from 'src/state/user/hooks' 9 | 10 | interface LogoProps { 11 | collapsed: boolean 12 | } 13 | 14 | export default function LogoIcon({ collapsed }: LogoProps) { 15 | const isDark = useIsDarkMode() 16 | return ( 17 | 18 | 19 | 20 | {!collapsed ? ( 21 | <img height="28px" src={isDark ? LogoDark : LogoLight} alt="logo" /> 22 | ) : ( 23 | <img width="28px" src={Logo} alt="logo" /> 24 | )} 25 | 26 | 27 | 28 | ) 29 | } 30 | -------------------------------------------------------------------------------- /src/layout/Logo/styled.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components' 2 | 3 | export const Title = styled.a` 4 | display: flex; 5 | align-items: center; 6 | pointer-events: auto; 7 | ${({ theme }) => theme.mediaWidth.upToSmall` 8 | justify-self: center; 9 | `}; 10 | cursor: pointer; 11 | overflow: hidden !important; 12 | ` 13 | 14 | export const LogoWrapper = styled.div<{ collapsed: boolean }>` 15 | display: flex; 16 | align-items: center; 17 | height: 50px; 18 | padding: 10px; 19 | ` 20 | -------------------------------------------------------------------------------- /src/layout/Sidebar/NavItem.tsx: -------------------------------------------------------------------------------- 1 | import React, { useContext, useState, useCallback, useEffect } from 'react' 2 | import { ThemeContext } from 'styled-components' 3 | import { MenuItem, MenuLink, MenuName } from './styled' 4 | import { MENU_LINK } from 'src/constants' 5 | 6 | export interface LinkProps { 7 | link: MENU_LINK | string 8 | icon: 9 | | string 10 | | React.FC<{ 11 | size: number 12 | fillColor: string 13 | }> 14 | //icon: any 15 | title: string 16 | id: string 17 | isActive?: boolean 18 | childrens?: Array 19 | } 20 | 21 | interface NavItemProps { 22 | item: LinkProps 23 | collapsed?: boolean 24 | onClick?: () => void 25 | } 26 | 27 | const NavItem = ({ item, collapsed, onClick }: NavItemProps) => { 28 | const [expanded, setExpand] = useState(false) 29 | const theme = useContext(ThemeContext) 30 | 31 | const onExpandChange = useCallback(() => { 32 | setExpand(expanded => !expanded) 33 | }, [setExpand]) 34 | 35 | useEffect(() => { 36 | setExpand(false) 37 | }, [collapsed]) 38 | 39 | const renderMenuItem = (x: LinkProps, isChildren: boolean) => { 40 | const Icon = x.icon 41 | const isHaveChildren = x?.childrens && x?.childrens?.length > 0 42 | const menuItemInnerProps = isHaveChildren ? { onClick: onExpandChange } : { to: x.link, onClick } 43 | 44 | return ( 45 | 46 | 47 | 48 | {!collapsed && ( 49 | 50 | {x.title} 51 | 52 | )} 53 | 54 | 55 | ) 56 | } 57 | 58 | return ( 59 | <> 60 | {renderMenuItem(item, false)} 61 | 62 | {expanded && 63 | (item?.childrens || []).map((subItem: LinkProps) => { 64 | return renderMenuItem(subItem, true) 65 | })} 66 | 67 | ) 68 | } 69 | export default NavItem 70 | -------------------------------------------------------------------------------- /src/layout/Sidebar/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { useWindowSize } from '../../hooks/useWindowSize' 3 | import SocialMedia from '../SocialMedia' 4 | import { Sider, CollapseBar, BottomBar } from './styled' 5 | import Backward from '../../assets/svg/backward.svg' 6 | import Forward from '../../assets/svg/forward.svg' 7 | import { Scrollbars } from 'react-custom-scrollbars' 8 | import Logo from '../Logo' 9 | import { MenuLinks } from './MenuLinks' 10 | interface SidebarProps { 11 | collapsed: boolean 12 | onCollapsed: (isCollapsed: boolean) => void 13 | } 14 | 15 | export default function Sidebar({ collapsed, onCollapsed }: SidebarProps) { 16 | const { height } = useWindowSize() 17 | 18 | return ( 19 | { 22 | onCollapsed(false) 23 | }} 24 | onMouseLeave={() => { 25 | if (!collapsed) { 26 | onCollapsed(true) 27 | } 28 | }} 29 | > 30 | 31 | 32 | 38 | 39 | 40 | 41 | 42 | onCollapsed(!collapsed)}> 43 | {collapsed ? ( 44 | {'Forward'} 45 | ) : ( 46 | {'Backward'} 47 | )} 48 | 49 | 50 | 51 | ) 52 | } 53 | -------------------------------------------------------------------------------- /src/layout/SocialMedia/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { useTranslation } from '@honeycomb-finance/shared' 3 | import { Text, Box } from '@honeycomb-finance/core' 4 | import { Wrapper, IconWrapper, Icon, Link } from './styled' 5 | import Telegram from '../../assets/svg/social/telegram.svg' 6 | import Twitter from '../../assets/svg/social/twitter.svg' 7 | import Youtube from '../../assets/svg/social/youtube.svg' 8 | import Medium from '../../assets/svg/social/medium.svg' 9 | import Github from '../../assets/svg/social/github.svg' 10 | import Discord from '../../assets/svg/social/discord.svg' 11 | import Substack from '../../assets/svg/social/substack.svg' 12 | 13 | interface SocialMediaProps { 14 | collapsed: boolean 15 | } 16 | 17 | export default function SocialMedia({ collapsed }: SocialMediaProps) { 18 | const { t } = useTranslation() 19 | 20 | const socialLinks = [ 21 | { 22 | link: 'https://twitter.com/pangolindex', 23 | icon: Twitter, 24 | title: 'Twitter' 25 | }, 26 | { 27 | link: 'https://t.me/pangolindexV2', 28 | icon: Telegram, 29 | title: 'Telegram' 30 | }, 31 | { 32 | link: 'https://www.youtube.com/channel/UClJJTG4FRL4z3AOf-ZWXZLw', 33 | icon: Youtube, 34 | title: 'Youtube' 35 | }, 36 | { 37 | link: 'https://pangolindex.medium.com/', 38 | icon: Medium, 39 | title: 'Medium' 40 | }, 41 | { 42 | link: 'https://github.com/pangolindex', 43 | icon: Github, 44 | title: 'Github' 45 | }, 46 | { 47 | link: 'https://discord.gg/pangolin', 48 | icon: Discord, 49 | title: 'Discord' 50 | }, 51 | { 52 | link: 'https://pangolin.substack.com/', 53 | icon: Substack, 54 | title: 'Substack' 55 | } 56 | ] 57 | 58 | return ( 59 | 60 | {!collapsed && ( 61 | 62 | 63 | {t('header.comeAndJoinUs')} 64 | 65 | 66 | )} 67 | 68 | 69 | {socialLinks.map((x, index) => { 70 | return ( 71 | 72 | 73 | 74 | ) 75 | })} 76 | 77 | 78 | ) 79 | } 80 | -------------------------------------------------------------------------------- /src/layout/SocialMedia/styled.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components' 2 | import { ExternalLink } from '../../theme' 3 | 4 | export const Wrapper = styled.div` 5 | background-color: ${({ theme }) => theme.bg2}; 6 | width: 100%; 7 | padding: 10px; 8 | color: ${({ theme }) => theme.text2}; 9 | text-align: center; 10 | background-color: ${({ theme }) => theme.bg6}; 11 | margin-bottom: 10px; 12 | ` 13 | 14 | export const IconWrapper = styled.div<{ collapsed: boolean }>` 15 | display: flex; 16 | justify-content: center; 17 | flex-direction: ${({ collapsed }) => (collapsed ? 'column' : 'row')}; 18 | align-items: center; 19 | margin: 5px 0px 10px 0px; 20 | ` 21 | 22 | export const Icon = styled.img` 23 | margin-right: 5px; 24 | margin-top: 5px; 25 | 26 | ${({ theme }) => theme.mediaWidth.upToSmall` 27 | height: 24px; 28 | `}; 29 | ` 30 | 31 | export const Link = styled(ExternalLink)` 32 | ${({ theme }) => theme.flexRowNoWrap} 33 | 34 | cursor: pointer; 35 | text-decoration: none; 36 | ` 37 | -------------------------------------------------------------------------------- /src/layout/styled.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components' 2 | import { Box } from '@honeycomb-finance/core' 3 | 4 | export const MainContent = styled.div<{ collapsed: boolean }>` 5 | &&& { 6 | min-height: 100vh; 7 | margin-left: 70px; 8 | width: calc(100% - 70px); 9 | display: flex; 10 | flex-direction: column; 11 | 12 | ${({ theme }) => theme.mediaWidth.upToSmall` 13 | margin-left: 0; 14 | width : 100%; 15 | `}; 16 | } 17 | ` 18 | 19 | export const AppContent = styled.div` 20 | display: flex; 21 | flex-direction: column; 22 | width: 100%; 23 | padding-top: 100px; 24 | padding: 0px 50px; 25 | height: 100%; 26 | flex: 1; 27 | overflow-y: auto; 28 | overflow-x: hidden; 29 | z-index: 10; 30 | 31 | ${({ theme }) => theme.mediaWidth.upToSmall` 32 | padding:16px; 33 | padding-bottom: 70px; 34 | `}; 35 | 36 | z-index: 1; 37 | ` 38 | 39 | export const Wrapper = styled(Box)` 40 | flex: 1; 41 | ` 42 | -------------------------------------------------------------------------------- /src/pages/Beta/Bridge/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Bridge } from '@honeycomb-finance/bridge' 3 | import { PageWrapper } from './styleds' 4 | // import { QuestionAnswer } from './TabulationBox' 5 | 6 | const BridgeUI = () => { 7 | return ( 8 | 9 | 10 | 11 | ) 12 | } 13 | 14 | export default BridgeUI 15 | -------------------------------------------------------------------------------- /src/pages/Beta/Buy/C14/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { C14_ASSET_ID } from 'src/constants' 3 | import styled from 'styled-components' 4 | 5 | export default function C14() { 6 | const Iframe = styled.iframe` 7 | border: 0; 8 | position: absolute; 9 | left: 0; 10 | right: 0; 11 | bottom: 0; 12 | 13 | @media (max-width: 960px) { 14 | top: 80px; 15 | } 16 | ` 17 | 18 | return ( 19 | 28 | ) 29 | } 30 | -------------------------------------------------------------------------------- /src/pages/Beta/Buy/Moonpay/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { MOONPAY_PK } from 'src/constants' 3 | import styled from 'styled-components' 4 | 5 | export default function Moonpay() { 6 | const Iframe = styled.iframe` 7 | border: 0; 8 | position: absolute; 9 | left: 0; 10 | right: 0; 11 | bottom: 0; 12 | 13 | @media (max-width: 960px) { 14 | top: 80px; 15 | } 16 | ` 17 | 18 | return ( 19 | 28 | ) 29 | } 30 | -------------------------------------------------------------------------------- /src/pages/Beta/Buy/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { useParams } from 'react-router-dom' 3 | import { BUY_MENU_LINK } from 'src/constants' 4 | import CoinbasePay from './CoinbasePay' 5 | import Moonpay from './Moonpay' 6 | import C14 from './C14' 7 | 8 | export type BuyProps = Record<'type', BUY_MENU_LINK> 9 | 10 | export default function BuyV2() { 11 | const params = useParams() 12 | 13 | if (params?.type === BUY_MENU_LINK.coinbasePay) { 14 | return 15 | } 16 | 17 | if (params?.type === BUY_MENU_LINK.c14) { 18 | return 19 | } 20 | 21 | return 22 | } 23 | -------------------------------------------------------------------------------- /src/pages/Beta/Buy/styled.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from '@honeycomb-finance/core' 2 | import styled from 'styled-components' 3 | import { ReactComponent as Wallet } from 'src/assets/svg/wallet.svg' 4 | 5 | export const ToggleWalletButton = styled(Button)` 6 | gap: 10px; 7 | display: flex; 8 | flex-direction: row; 9 | align-items: center; 10 | justify-content: center; 11 | overflow: hidden; 12 | ` 13 | 14 | export const WalletIcon = styled(Wallet)<{ color?: string }>` 15 | path { 16 | fill: ${({ color, theme }) => color || theme.text1}; 17 | } 18 | ` 19 | -------------------------------------------------------------------------------- /src/pages/Beta/Governance/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { GovernanceList } from '@honeycomb-finance/governance' 3 | 4 | function Governance() { 5 | return ( 6 |
7 | 8 |
9 | ) 10 | } 11 | 12 | export default Governance 13 | -------------------------------------------------------------------------------- /src/pages/Beta/GovernanceDetail/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { useParams } from 'react-router-dom' 3 | import { GovernanceDetail } from '@honeycomb-finance/governance' 4 | 5 | export type GovernanceDetailProps = Record<'id', 'string'> 6 | 7 | function GovernanceDetailV2() { 8 | const params = useParams() 9 | return ( 10 |
11 | 12 |
13 | ) 14 | } 15 | 16 | export default GovernanceDetailV2 17 | -------------------------------------------------------------------------------- /src/pages/Beta/Policy/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactMarkdown from 'react-markdown' 3 | 4 | import { PageWrapper, PolicyText } from './styled' 5 | 6 | import { CookiePolicy } from 'src/constants/Policies/CPolicy' 7 | import { PrivacyPolicy } from 'src/constants/Policies/PrivacyPolicy' 8 | import { TermsService } from 'src/constants/Policies/TermsService' 9 | 10 | interface Props { 11 | policy: string 12 | } 13 | 14 | const policies = { 15 | cookie: CookiePolicy, 16 | privacy: PrivacyPolicy, 17 | terms: TermsService 18 | } 19 | 20 | export default function Policy({ policy }: Props) { 21 | const text = policies[policy as keyof typeof policies] 22 | return ( 23 | 24 | 25 | {text} 26 | 27 | 28 | ) 29 | } 30 | -------------------------------------------------------------------------------- /src/pages/Beta/Policy/styled.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components' 2 | import { Box } from '@honeycomb-finance/core' 3 | 4 | export const PageWrapper = styled(Box)` 5 | width: 100%; 6 | ` 7 | 8 | export const PolicyText = styled(Box)` 9 | margin: 10px; 10 | color: ${({ theme }) => theme.text1}; 11 | & a { 12 | color: ${({ theme }) => theme.text1}; 13 | } 14 | ` 15 | -------------------------------------------------------------------------------- /src/pages/Beta/Pool/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react' 2 | import styled from 'styled-components' 3 | import { PoolsUI } from '@honeycomb-finance/pools' 4 | import { Elixir } from '@honeycomb-finance/elixir' 5 | import { useTranslation } from '@honeycomb-finance/shared' 6 | import { useNavigate, useParams } from 'react-router-dom' 7 | import { MENU_LINK, POOL_MENU_LINK } from 'src/constants' 8 | import { useChainId } from 'src/hooks' 9 | import { shouldHideChildItem } from 'src/utils' 10 | import { AlertTriangle } from 'react-feather' 11 | export type PoolProps = Record<'type', POOL_MENU_LINK> 12 | 13 | const PhishAlert = styled.div` 14 | width: 100%; 15 | padding: 6px 6px; 16 | background-color: ${({ theme }) => theme.red1}; 17 | color: white; 18 | font-size: 11px; 19 | justify-content: space-between; 20 | align-items: center; 21 | display: flex; 22 | margin-bottom: 10px; 23 | ` 24 | 25 | const Alert = () => { 26 | const { t } = useTranslation() 27 | 28 | return ( 29 | 30 |
31 | {t('elixir.auditWarning')} 32 |
33 |
34 | ) 35 | } 36 | 37 | const Pool = () => { 38 | const params = useParams() 39 | const chainId = useChainId() 40 | const navigate = useNavigate() 41 | 42 | useEffect(() => { 43 | if (chainId && shouldHideChildItem(chainId, MENU_LINK.pool, params?.type as POOL_MENU_LINK)) { 44 | navigate('/') 45 | } 46 | }, [chainId, params?.type]) 47 | 48 | if (params?.type === POOL_MENU_LINK.standard) { 49 | return 50 | } 51 | 52 | if (params?.type === POOL_MENU_LINK.elixir) { 53 | return ( 54 | <> 55 | 56 | 57 | 58 | ) 59 | } 60 | 61 | return 62 | } 63 | export default Pool 64 | -------------------------------------------------------------------------------- /src/pages/Beta/Stake/ClaimDrawer/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useCallback, useState } from 'react' 2 | import { Box, Drawer } from '@honeycomb-finance/core' 3 | import { SingleSideStakingInfo } from 'src/state/stake/hooks' 4 | import ClaimWidget from '../ClaimWidget' 5 | import RewardStakeDrawer from '../RewardStakeDrawer' 6 | 7 | type Props = { 8 | isOpen: boolean 9 | stakingInfo: SingleSideStakingInfo 10 | onClose: () => void 11 | } 12 | 13 | const ClaimDrawer: React.FC = ({ isOpen, onClose, stakingInfo }) => { 14 | const [isRewardStakeDrawerVisible, setShowRewardStakeDrawer] = useState(false) 15 | 16 | const onCloseRewardStakeDrawer = useCallback(() => { 17 | setShowRewardStakeDrawer(false) 18 | onClose() 19 | }, [onClose]) 20 | 21 | return ( 22 | 23 | 24 | {isOpen && ( 25 | setShowRewardStakeDrawer(true)} 29 | /> 30 | )} 31 | 32 | 33 | 38 | 39 | ) 40 | } 41 | 42 | export default ClaimDrawer 43 | -------------------------------------------------------------------------------- /src/pages/Beta/Stake/ClaimWidget/styled.tsx: -------------------------------------------------------------------------------- 1 | import { Box } from '@honeycomb-finance/core' 2 | import styled from 'styled-components' 3 | 4 | export const WidgetWrapper = styled(Box)` 5 | display: flex; 6 | flex-direction: column; 7 | flex: 1; 8 | text-align: center; 9 | padding: 20px; 10 | ` 11 | 12 | export const Root = styled(Box)` 13 | display: grid; 14 | grid-template-rows: auto max-content; 15 | height: 100%; 16 | ` 17 | -------------------------------------------------------------------------------- /src/pages/Beta/Stake/DetailModal/Details/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { SingleSideStakingInfo } from 'src/state/stake/hooks' 3 | import { DetailsContainer } from './styled' 4 | import { Box } from '@honeycomb-finance/core' 5 | import { CoinDescription } from '@honeycomb-finance/pools' 6 | import StatDetails from '../StatDetail' 7 | 8 | type Props = { 9 | stakingInfo: SingleSideStakingInfo 10 | } 11 | 12 | const Details: React.FC = ({ stakingInfo }) => { 13 | const amountInPNG = stakingInfo?.totalStakedInPng 14 | const isStaking = stakingInfo?.stakedAmount?.greaterThan('0') 15 | const bothCurrencySame = stakingInfo?.rewardToken?.equals(stakingInfo?.totalStakedAmount?.token) 16 | 17 | return ( 18 | <> 19 | 20 | 21 | 26 | 27 | {isStaking && ( 28 | 29 | 34 | 35 | )} 36 | 37 | 38 | 39 | {!bothCurrencySame && ( 40 | 41 | 42 | 43 | )} 44 | 45 | 46 | ) 47 | } 48 | 49 | export default Details 50 | -------------------------------------------------------------------------------- /src/pages/Beta/Stake/DetailModal/Details/styled.ts: -------------------------------------------------------------------------------- 1 | import { Box } from '@honeycomb-finance/core' 2 | import styled from 'styled-components' 3 | 4 | export const DetailsContainer = styled(Box)` 5 | overflow: hidden; 6 | width: 100%; 7 | background-color: ${({ theme }) => theme.bg2}; 8 | padding: 40px; 9 | flex: 1; 10 | display: flex; 11 | flex-direction: column; 12 | ${({ theme }) => theme.mediaWidth.upToSmall` 13 | border-radius: 0 10px 10px 10px; 14 | `}; 15 | ` 16 | -------------------------------------------------------------------------------- /src/pages/Beta/Stake/DetailModal/EarnedWidget/styled.tsx: -------------------------------------------------------------------------------- 1 | import { Box } from '@honeycomb-finance/core' 2 | import styled from 'styled-components' 3 | 4 | export const Root = styled(Box)` 5 | padding: 20px; 6 | background-color: ${({ theme }) => theme.bg2}; 7 | border-radius: 10px; 8 | margin-top: 25px; 9 | width: 100%; 10 | position: relative; 11 | overflow: hidden; 12 | height: 280px; 13 | ${({ theme }) => theme.mediaWidth.upToSmall` 14 | padding: 10px 20px; 15 | `}; 16 | ` 17 | 18 | export const StatWrapper = styled(Box)` 19 | display: grid; 20 | grid-template-columns: 50% 50%; 21 | grid-gap: 12px; 22 | margin-top: 10px; 23 | ` 24 | -------------------------------------------------------------------------------- /src/pages/Beta/Stake/DetailModal/Header/styled.tsx: -------------------------------------------------------------------------------- 1 | import { Box } from '@honeycomb-finance/core' 2 | import styled from 'styled-components' 3 | 4 | export const HeaderRoot = styled(Box)` 5 | display: flex; 6 | align-items: center; 7 | justify-content: space-between; 8 | padding: 20px; 9 | border-bottom: 1px solid ${({ theme }) => theme.text6}; 10 | 11 | ${({ theme }) => theme.mediaWidth.upToMedium` 12 | border-bottom: none; 13 | display: flex; 14 | flex-direction: column; 15 | justify-content: flex-start; 16 | `}; 17 | ` 18 | 19 | export const StatsWrapper = styled(Box)<{ isStake?: boolean }>` 20 | display: grid; 21 | grid-template-columns: repeat(5, auto); 22 | grid-gap: 20px; 23 | align-items: center; 24 | ${({ theme, isStake }) => theme.mediaWidth.upToMedium` 25 | width: 100%; 26 | grid-gap: 10px; 27 | margin-top: 10px; 28 | grid-template-columns: ${isStake ? '50% 50%' : 'repeat(3, auto)'}; 29 | `}; 30 | ` 31 | 32 | export const HeaderWrapper = styled(Box)` 33 | display: flex; 34 | align-items: center; 35 | justify-content: space-between; 36 | ${({ theme }) => theme.mediaWidth.upToMedium` 37 | width: 100% 38 | `}; 39 | ` 40 | export const PoolRewardsWrapper = styled(Box)` 41 | display: flex; 42 | flex-direction: column; 43 | justify-content: space-between; 44 | ` 45 | -------------------------------------------------------------------------------- /src/pages/Beta/Stake/DetailModal/StakeWidget/styled.ts: -------------------------------------------------------------------------------- 1 | import { Box, Text } from '@honeycomb-finance/core' 2 | import styled from 'styled-components' 3 | 4 | export const Root = styled(Box)` 5 | padding: 20px; 6 | background-color: ${({ theme }) => theme.bg2}; 7 | border-radius: 10px; 8 | position: relative; 9 | display: flex; 10 | flex-direction: column; 11 | height: 400px; 12 | ` 13 | 14 | export const MaxButton = styled.button` 15 | height: 28px; 16 | background-color: ${({ theme }) => theme.bg2}; 17 | border: 1px solid ${({ theme }) => theme.bg2}; 18 | border-radius: 0.5rem; 19 | font-size: 0.875rem; 20 | font-weight: 500; 21 | cursor: pointer; 22 | color: ${({ theme }) => theme.text2}; 23 | ` 24 | 25 | export const Balance = styled(Text)` 26 | font-size: 12px; 27 | display: flex; 28 | align-items: center; 29 | color: ${({ theme }) => theme.text2}; 30 | ` 31 | 32 | export const Buttons = styled(Box)<{ isStaked?: boolean }>` 33 | display: grid; 34 | grid-auto-flow: ${({ isStaked }) => (isStaked ? 'column' : 'row')}; 35 | grid-auto-columns: minmax(0, 1fr); 36 | grid-gap: 10px; 37 | margin-top: 20px; 38 | ` 39 | 40 | export const StakeWrapper = styled(Box)` 41 | width: 100%; 42 | position: relative; 43 | border-radius: 10px; 44 | background-color: ${({ theme }) => theme.bg6}; 45 | padding: 10px; 46 | ` 47 | export const GridContainer = styled(Box)` 48 | display: grid; 49 | grid-template-columns: minmax(auto, 50%) minmax(auto, 50%); 50 | grid-gap: 8px; 51 | ` 52 | -------------------------------------------------------------------------------- /src/pages/Beta/Stake/DetailModal/StatDetail/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Box, Text, Stat } from '@honeycomb-finance/core' 3 | import { useUSDCPrice } from '@honeycomb-finance/state-hooks' 4 | import { Currency, TokenAmount, CHAINS } from '@pangolindex/sdk' 5 | import { StateContainer } from './styleds' 6 | import numeral from 'numeral' 7 | import { useChainId } from 'src/hooks' 8 | 9 | interface Props { 10 | title: string 11 | amountInPNG: TokenAmount 12 | currency0: Currency | undefined 13 | } 14 | 15 | const StatDetails: React.FC = ({ title, amountInPNG, currency0 }) => { 16 | const chainId = useChainId() 17 | 18 | const usdcPriceTmp = useUSDCPrice(amountInPNG?.token) 19 | const usdcPrice = CHAINS[chainId]?.mainnet ? usdcPriceTmp : undefined 20 | const amountInUSD = CHAINS[chainId]?.mainnet 21 | ? numeral(usdcPrice?.quote(amountInPNG, chainId).toSignificant(6)).format('$0.00a') 22 | : undefined 23 | 24 | return ( 25 | 26 | 27 | {title} 28 | 29 | 30 | 31 | 39 | 48 | 49 | 50 | ) 51 | } 52 | 53 | export default StatDetails 54 | -------------------------------------------------------------------------------- /src/pages/Beta/Stake/DetailModal/StatDetail/styleds.tsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components' 2 | 3 | export const StateContainer = styled.div` 4 | grid-template-columns: repeat(2, 1fr); 5 | gap: 12px; 6 | display: grid; 7 | width: 100%; 8 | align-items: center; 9 | margin-top: 12px; 10 | 11 | @media screen and (max-width: 1024px) { 12 | grid-template-columns: 1fr; 13 | align-items: stretch; 14 | } 15 | 16 | ${({ theme }) => theme.mediaWidth.upToSmall` 17 | grid-template-columns: 50% 50%; 18 | grid-gap: 8px; 19 | `}; 20 | ${({ theme }) => theme.mediaWidth.upToMedium` 21 | grid-template-columns: repeat(3, 1fr); 22 | align-items: start; 23 | `}; 24 | ` 25 | -------------------------------------------------------------------------------- /src/pages/Beta/Stake/DetailModal/UnstakeDrawer/styled.ts: -------------------------------------------------------------------------------- 1 | import { Box } from '@honeycomb-finance/core' 2 | import styled from 'styled-components' 3 | 4 | export const Wrapper = styled.div` 5 | display: flex; 6 | flex-direction: column; 7 | flex: 1; 8 | text-align: center; 9 | padding: 20px; 10 | ` 11 | 12 | export const ConfirmWrapper = styled(Box)` 13 | display: flex; 14 | flex-direction: column; 15 | height: 100%; 16 | text-align: center; 17 | ` 18 | -------------------------------------------------------------------------------- /src/pages/Beta/Stake/DetailModal/styled.tsx: -------------------------------------------------------------------------------- 1 | import { Box } from '@honeycomb-finance/core' 2 | import styled from 'styled-components' 3 | 4 | export const DesktopWrapper = styled(Box)` 5 | width: 1080px; 6 | overflow: auto; 7 | border-radius: 10px; 8 | ${({ theme }) => theme.mediaWidth.upToMedium` 9 | display: none; 10 | `}; 11 | ` 12 | 13 | export const MobileWrapper = styled(Box)` 14 | width: 100%; 15 | height: 100%; 16 | display: none; 17 | ${({ theme }) => theme.mediaWidth.upToMedium` 18 | display: block; 19 | overflow: scroll; 20 | `}; 21 | ` 22 | 23 | export const LeftSection = styled(Box)` 24 | border-right: 2px solid ${({ theme }) => theme.text6}; 25 | display: flex; 26 | flex-direction: column; 27 | ` 28 | 29 | export const DetailsWrapper = styled(Box)` 30 | display: grid; 31 | grid-template-columns: minmax(auto, 65%) minmax(auto, 35%); 32 | grid-gap: 0px; 33 | ` 34 | 35 | export const Tabs = styled(Box)` 36 | width: 100%; 37 | display: flex; 38 | align-items: center; 39 | ` 40 | 41 | export const Tab = styled(Box)` 42 | padding: 15px 50px; 43 | font-size: 14px; 44 | color: ${({ theme }) => theme.text10}; 45 | background-color: ${({ theme }) => theme.bg2}; 46 | 47 | ${({ theme }) => theme.mediaWidth.upToSmall` 48 | border-radius: 10px 10px 0 0; 49 | `}; 50 | ` 51 | 52 | export const RightSection = styled(Box)` 53 | padding: 20px; 54 | ` 55 | -------------------------------------------------------------------------------- /src/pages/Beta/Stake/PoolCard/StakeDrawer/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { SingleSideStakingInfo } from 'src/state/stake/hooks' 3 | import StakeWidet from '../StakeWidget' 4 | import { useTranslation } from '@honeycomb-finance/shared' 5 | import { Drawer } from '@honeycomb-finance/core' 6 | 7 | type Props = { 8 | isOpen: boolean 9 | stakingInfo: SingleSideStakingInfo 10 | onClose: () => void 11 | } 12 | 13 | const StakeDrawer: React.FC = ({ isOpen, onClose, stakingInfo }) => { 14 | const { t } = useTranslation() 15 | return ( 16 | 17 | {isOpen && } 18 | 19 | ) 20 | } 21 | 22 | export default StakeDrawer 23 | -------------------------------------------------------------------------------- /src/pages/Beta/Stake/PoolCard/StakeWidget/styled.ts: -------------------------------------------------------------------------------- 1 | import { Box, TextInput } from '@honeycomb-finance/core' 2 | import styled from 'styled-components' 3 | 4 | export const Root = styled(Box)` 5 | padding: 0px 20px; 6 | background-color: ${({ theme }) => theme.bg2}; 7 | border-radius: 10px; 8 | position: relative; 9 | display: flex; 10 | flex-direction: column; 11 | height: 400px; 12 | ` 13 | 14 | export const MaxButton = styled.button` 15 | height: 28px; 16 | background-color: ${({ theme }) => theme.bg2}; 17 | border: 1px solid ${({ theme }) => theme.bg2}; 18 | border-radius: 0.5rem; 19 | font-size: 0.875rem; 20 | font-weight: 500; 21 | cursor: pointer; 22 | color: ${({ theme }) => theme.text2}; 23 | ` 24 | 25 | export const Buttons = styled(Box)<{ isStaked?: boolean }>` 26 | display: grid; 27 | grid-auto-flow: ${({ isStaked }) => (isStaked ? 'column' : 'row')}; 28 | grid-auto-columns: minmax(0, 1fr); 29 | grid-gap: 10px; 30 | margin-top: 5px; 31 | ` 32 | 33 | export const StakeWrapper = styled(Box)` 34 | width: 100%; 35 | position: relative; 36 | border-radius: 10px; 37 | background-color: ${({ theme }) => theme.bg6}; 38 | padding: 8px; 39 | margin-top: 10px; 40 | ` 41 | export const GridContainer = styled(Box)` 42 | display: grid; 43 | grid-template-columns: minmax(auto, 50%) minmax(auto, 50%); 44 | grid-gap: 8px; 45 | ` 46 | export const InputText = styled(TextInput)` 47 | padding: 5px; 48 | ` 49 | -------------------------------------------------------------------------------- /src/pages/Beta/Stake/PoolCard/styleds.tsx: -------------------------------------------------------------------------------- 1 | import { Box, Button, Text } from '@honeycomb-finance/core' 2 | import styled from 'styled-components' 3 | 4 | export const Card = styled(Box)` 5 | /* width: 480px; */ 6 | padding: 25px; 7 | box-sizing: border-box; 8 | border-radius: 10px; 9 | background: ${({ theme }) => theme.color2}; 10 | position: relative; 11 | overflow: hidden; 12 | & img { 13 | border-radius: 100px; 14 | } 15 | 16 | ${({ theme }) => theme.mediaWidth.upToMedium` 17 | width: 100%; 18 | margin-bottom: 22px; 19 | `}; 20 | ` 21 | 22 | export const CardHeader = styled(Box)` 23 | display: flex; 24 | justify-content: space-between; 25 | align-items: center; 26 | padding-bottom: 25px; 27 | border-bottom: 1px solid ${({ theme }) => theme.text8}; 28 | ` 29 | 30 | export const Stats = styled(Box)`` 31 | 32 | export const StatValue = styled(Text)` 33 | font-size: 24px; 34 | font-weight: 500; 35 | 36 | ${({ theme }) => theme.mediaWidth.upToSmall` 37 | font-size: 22px; 38 | `}; 39 | ` 40 | 41 | export const CardStats = styled(Box)` 42 | display: grid; 43 | grid-auto-columns: minmax(0, 1fr); 44 | grid-auto-flow: column; 45 | grid-gap: 20px; 46 | padding: 20px 0 0px; 47 | ` 48 | 49 | export const TokenName = styled(Box)` 50 | font-weight: 800; 51 | font-size: 24px; 52 | line-height: 33px; 53 | color: ${({ theme }) => theme.text7}; 54 | ` 55 | 56 | export const StakeButton = styled(Button)` 57 | /* background-color: ${({ theme }) => theme.color5} !important; */ 58 | height: 46px; 59 | border-radius: 4px !important; 60 | font-size: 14px; 61 | 62 | ${({ theme }) => theme.mediaWidth.upToSmall` 63 | font-size: 16px; 64 | `}; 65 | ` 66 | 67 | export const DetailButton = styled(Button)` 68 | border: solid 1px ${({ theme }) => theme.color4} !important; 69 | height: 46px; 70 | border-radius: 4px !important; 71 | font-size: 14px; 72 | 73 | ${({ theme }) => theme.mediaWidth.upToSmall` 74 | font-size: 16px; 75 | `}; 76 | ` 77 | -------------------------------------------------------------------------------- /src/pages/Beta/Stake/RewardStakeDrawer/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { SingleSideStakingInfo } from 'src/state/stake/hooks' 3 | import StakeWidget from '../DetailModal/StakeWidget' 4 | import { Drawer } from '@honeycomb-finance/core' 5 | import { useTranslation } from '@honeycomb-finance/shared' 6 | 7 | type Props = { 8 | isOpen: boolean 9 | stakingInfo: SingleSideStakingInfo 10 | onClose: () => void 11 | } 12 | 13 | const RewardStakeDrawer: React.FC = ({ isOpen, onClose, stakingInfo }) => { 14 | const { t } = useTranslation() 15 | return ( 16 | 17 | {isOpen && } 18 | 19 | ) 20 | } 21 | 22 | export default RewardStakeDrawer 23 | -------------------------------------------------------------------------------- /src/pages/Beta/Stake/styleds.tsx: -------------------------------------------------------------------------------- 1 | import { Box } from '@honeycomb-finance/core' 2 | import styled from 'styled-components' 3 | 4 | export const PageWrapper = styled(Box)` 5 | width: 100%; 6 | ` 7 | 8 | export const PageTitle = styled(Box)` 9 | font-weight: 500; 10 | font-size: 32px; 11 | color: ${({ theme }) => theme.text7}; 12 | margin-top: 105px; 13 | margin-bottom: 92px; 14 | display: flex; 15 | justify-content: center; 16 | ${({ theme }) => theme.mediaWidth.upToSmall` 17 | font-size: 24px; 18 | text-align: center; 19 | margin-top: 30px; 20 | margin-bottom: 30px; 21 | `}; 22 | ` 23 | 24 | export const PoolsWrapper = styled(Box)` 25 | display: flex; 26 | justify-content: center; 27 | 28 | ${({ theme }) => theme.mediaWidth.upToMedium` 29 | flex-direction: column; 30 | `}; 31 | ` 32 | 33 | export const PoolCards = styled(Box)` 34 | display: grid; 35 | grid-auto-columns: minmax(0, 1fr); 36 | grid-auto-flow: column; 37 | grid-gap: 20px; 38 | min-width: 320px; 39 | margin: auto; 40 | ${({ theme }) => theme.mediaWidth.upToMedium` 41 | grid-auto-columns: minmax(0, 1fr); 42 | grid-auto-flow: row; 43 | `}; 44 | ` 45 | -------------------------------------------------------------------------------- /src/pages/Beta/Swap/LimitOrderList/CancelOrder/styleds.tsx: -------------------------------------------------------------------------------- 1 | import { Box } from '@honeycomb-finance/core' 2 | import styled from 'styled-components' 3 | 4 | export const CancelOrderRoot = styled(Box)` 5 | width: 100%; 6 | ` 7 | 8 | export const PendingWrapper = styled(Box)` 9 | display: flex; 10 | justify-content: center; 11 | align-items: center; 12 | flex-direction: column; 13 | height: 100%; 14 | ` 15 | 16 | export const Root = styled(Box)` 17 | display: grid; 18 | grid-template-rows: auto max-content; 19 | height: 100%; 20 | ` 21 | 22 | export const Header = styled(Box)` 23 | padding: 0px 10px; 24 | ` 25 | 26 | export const Footer = styled(Box)` 27 | padding: 0px 10px; 28 | ` 29 | -------------------------------------------------------------------------------- /src/pages/Beta/Swap/LimitOrderList/CancelOrderModal/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useContext } from 'react' 2 | import { Text, Box, Modal } from '@honeycomb-finance/core' 3 | import { useTranslation } from '@honeycomb-finance/shared' 4 | import { Wrapper } from './styleds' 5 | import { ThemeContext } from 'styled-components' 6 | // import { Order } from '@gelatonetwork/limit-orders-react' 7 | import { CloseIcon } from 'src/theme/components' 8 | import CancelOrder from '../CancelOrder' 9 | 10 | interface ClaimRewardModalProps { 11 | isOpen: boolean 12 | onClose: () => void 13 | order: any 14 | } 15 | 16 | const CancelOrderModal = ({ isOpen, onClose, order }: ClaimRewardModalProps) => { 17 | const theme = useContext(ThemeContext) 18 | const { t } = useTranslation() 19 | 20 | return ( 21 | 22 | 23 | 24 | 25 | {t('swapPage.cancelOrder')} 26 | 27 | onClose()} color={theme.text1} /> 28 | 29 | onClose()} /> 30 | 31 | 32 | ) 33 | } 34 | export default CancelOrderModal 35 | -------------------------------------------------------------------------------- /src/pages/Beta/Swap/LimitOrderList/CancelOrderModal/styleds.tsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components' 2 | 3 | export const Wrapper = styled.div` 4 | ${({ theme }) => theme.flexColumnNoWrap} 5 | margin: 0; 6 | width: 100%; 7 | align-items: center; 8 | max-width: 420px; 9 | min-width: 420px; 10 | padding: 10px; 11 | ` 12 | -------------------------------------------------------------------------------- /src/pages/Beta/Swap/LimitOrderList/LimitOrderRow.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { useGelatoLimitOrderDetail, LimitOrderInfo } from '@honeycomb-finance/swap' 3 | import { Text, Box } from '@honeycomb-finance/core' 4 | import { useTranslation } from '@honeycomb-finance/shared' 5 | import { DesktopRowWrapper } from './styleds' 6 | 7 | type Props = { 8 | order: LimitOrderInfo 9 | onClick: () => void 10 | isSelected: boolean 11 | } 12 | 13 | const LimitOrderRow: React.FC = ({ order, onClick, isSelected }) => { 14 | const { t } = useTranslation() 15 | const { currency0, currency1, inputAmount, outputAmount } = useGelatoLimitOrderDetail(order) 16 | 17 | return ( 18 | 19 | 20 | {t('swapPage.cancelLimitOrder', { 21 | outputCurrency: currency1?.symbol, 22 | inputCurrency: currency0?.symbol 23 | })} 24 | 25 | 26 | 27 | 28 | 29 | {inputAmount ? inputAmount.toSignificant(4) : '-'} / {outputAmount ? outputAmount.toSignificant(4) : '-'} 30 | 31 | 32 | 33 | {order?.status} 34 | {order?.pending && `(P)`} 35 | 36 | 37 | 38 | 39 | ) 40 | } 41 | 42 | export default LimitOrderRow 43 | -------------------------------------------------------------------------------- /src/pages/Beta/Swap/PairInfo/PairChart/styleds.tsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components' 2 | 3 | export const ChartWrapper = styled.div` 4 | width: 100%; 5 | padding: 10px; 6 | border-radius: 10px; 7 | background-color: ${({ theme }) => theme.color2}; 8 | position: relative; 9 | ${({ theme }) => theme.mediaWidth.upToSmall` 10 | display: none; 11 | `}; 12 | ` 13 | 14 | export const ChartContainer = styled.div` 15 | width: 100%; 16 | height: 100%; 17 | bottom: 0px; 18 | left: 0px; 19 | position: absolute; 20 | ` 21 | -------------------------------------------------------------------------------- /src/pages/Beta/Swap/PairInfo/PairStat/styleds.tsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components' 2 | 3 | export const PanelWrapper = styled.div` 4 | grid-template-columns: repeat(6, 1fr); 5 | grid-template-rows: max-content; 6 | gap: 12px; 7 | display: inline-grid; 8 | width: 100%; 9 | align-items: start; 10 | border-radius: 10px; 11 | background-color: ${({ theme }) => theme.color2}; 12 | @media screen and (max-width: 1024px) { 13 | grid-template-columns: 1fr; 14 | align-items: stretch; 15 | > * { 16 | grid-column: 1 / 6; 17 | } 18 | 19 | > * { 20 | &:first-child { 21 | width: 100%; 22 | } 23 | } 24 | } 25 | 26 | ${({ theme }) => theme.mediaWidth.upToSmall` 27 | display: none; 28 | `}; 29 | ` 30 | 31 | export const MobileStat = styled.div` 32 | border-radius: 10px; 33 | background-color: ${({ theme }) => theme.color2}; 34 | display: none; 35 | padding: 10px; 36 | ${({ theme }) => theme.mediaWidth.upToSmall` 37 | display: flex; 38 | align-items: center; 39 | justify-content: space-between; 40 | `}; 41 | ` 42 | -------------------------------------------------------------------------------- /src/pages/Beta/Swap/PairInfo/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PairStat from './PairStat' 3 | import PairChart from './PairChart' 4 | import { useDerivedSwapInfo } from '@honeycomb-finance/swap' 5 | import { wrappedCurrency, Tokens } from '@honeycomb-finance/shared' 6 | import { usePair } from '@honeycomb-finance/state-hooks' 7 | import { useChainId } from 'src/hooks' 8 | 9 | export enum Field { 10 | INPUT = 'INPUT', 11 | OUTPUT = 'OUTPUT' 12 | } 13 | 14 | const PairInfo = () => { 15 | const chainId = useChainId() 16 | 17 | const { currencies } = useDerivedSwapInfo() 18 | 19 | const token1 = currencies[Field.OUTPUT] 20 | 21 | const tokenB = wrappedCurrency(token1 ?? undefined, chainId) 22 | const { USDC } = Tokens 23 | // should show the price of tokenB in USD 24 | const [, tokenPair] = usePair(USDC[chainId], tokenB) 25 | 26 | return ( 27 | <> 28 | 35 | 36 | 37 | ) 38 | } 39 | export default PairInfo 40 | -------------------------------------------------------------------------------- /src/pages/Beta/Swap/SwapUI/styleds.tsx: -------------------------------------------------------------------------------- 1 | import { Box } from '@honeycomb-finance/core' 2 | import styled from 'styled-components' 3 | 4 | export const PageWrapper = styled(Box)` 5 | width: 100%; 6 | padding-top: 25px; 7 | display: flex; 8 | flex-direction: column; 9 | flex: 1; 10 | ${({ theme }) => theme.mediaWidth.upToSmall` 11 | padding-top: 10px; 12 | `}; 13 | ` 14 | 15 | export const GridWrapper = styled(Box)` 16 | display: grid; 17 | grid-template-columns: minmax(auto, 75%) minmax(auto, 25%); 18 | grid-gap: 12px; 19 | padding: 10px 0px; 20 | ${({ theme }) => theme.mediaWidth.upToSmall` 21 | grid-template-columns: none; 22 | grid-template-rows: max-content; 23 | `}; 24 | ` 25 | 26 | export const TopContainer = styled(Box)` 27 | display: grid; 28 | grid-template-columns: auto minmax(auto, 400px); 29 | grid-gap: 12px; 30 | 31 | ${({ theme }) => theme.mediaWidth.upToSmall` 32 | grid-template-columns: none; 33 | grid-template-rows: max-content auto; 34 | `}; 35 | ` 36 | 37 | export const StatsWrapper = styled(Box)` 38 | display: grid; 39 | grid-template-rows: max-content auto; 40 | grid-gap: 12px; 41 | ` 42 | 43 | export const GridContainer = styled(Box)<{ isLimitOrders: boolean }>` 44 | display: grid; 45 | grid-template-columns: ${({ isLimitOrders }) => 46 | isLimitOrders ? `minmax(auto, 50%) minmax(auto, 25%) minmax(auto, 25%)` : `minmax(auto, 50%) minmax(auto, 50%)`}; 47 | grid-gap: 12px; 48 | padding: 10px 0px; 49 | 50 | ${({ theme }) => theme.mediaWidth.upToSmall` 51 | grid-template-columns: none; 52 | grid-template-rows: max-content; 53 | `}; 54 | ` 55 | -------------------------------------------------------------------------------- /src/pages/Beta/Swap/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import SwapUI from './SwapUI' 3 | import { useLibrary, useActiveWeb3React } from '@honeycomb-finance/shared' 4 | import { useChainId } from 'src/hooks' 5 | import { GelatoProvider, galetoStore } from '@honeycomb-finance/swap' 6 | import { Web3Provider } from '@ethersproject/providers' 7 | import { Provider } from 'react-redux' 8 | 9 | const Swap = () => { 10 | const chainId = useChainId() 11 | const { account } = useActiveWeb3React() 12 | const { library } = useLibrary() 13 | 14 | const ethersLibrary = library && !library?._isProvider ? new Web3Provider(library) : library 15 | return ( 16 | 17 | 25 | 26 | 27 | 28 | ) 29 | } 30 | export default Swap 31 | -------------------------------------------------------------------------------- /src/pages/Dashboard/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { PageTitle, PageDescription, PageWrapper, TopContainer, StatsWrapper } from './styleds' 3 | import { useTranslation } from '@honeycomb-finance/shared' 4 | import { WatchList, Portfolio } from '@honeycomb-finance/portfolio' 5 | import { NewsWidget } from '@honeycomb-finance/core' 6 | import { CHAINS } from '@pangolindex/sdk' 7 | import { useChainId } from 'src/hooks' 8 | import { Hidden, Visible } from 'src/theme' 9 | 10 | const Dashboard = () => { 11 | const { t } = useTranslation() 12 | const chainId = useChainId() 13 | 14 | const isMainnet = CHAINS[chainId]?.mainnet 15 | 16 | return ( 17 | 18 | {t('dashboardPage.dashboard')} 19 | {t('dashboardPage.greetings')} 20 | 21 | 22 | {isMainnet && ( 23 | 24 | 25 | 26 | 27 | )} 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | ) 39 | } 40 | 41 | export default Dashboard 42 | -------------------------------------------------------------------------------- /src/pages/Dashboard/styleds.tsx: -------------------------------------------------------------------------------- 1 | import { Box } from '@honeycomb-finance/core' 2 | import styled from 'styled-components' 3 | 4 | export const PageWrapper = styled(Box)` 5 | width: 100%; 6 | height: 100%; 7 | display: flex; 8 | flex: 1; 9 | flex-direction: column; 10 | ` 11 | 12 | export const TopContainer = styled(Box)<{ isMainnet: boolean }>` 13 | display: grid; 14 | grid-template-columns: ${({ isMainnet }) => (isMainnet ? `50% 50%` : `100%`)}; 15 | grid-gap: 12px; 16 | margin-bottom: 22px; 17 | flex: 1; 18 | grid-template-rows: minmax(500px, 1fr); 19 | ${({ theme }) => theme.mediaWidth.upToSmall` 20 | grid-template-columns: none; 21 | grid-template-rows: max-content auto; 22 | margin-bottom: 0px; 23 | `}; 24 | ` 25 | 26 | export const StatsWrapper = styled(Box)` 27 | display: grid; 28 | grid-template-rows: auto auto; 29 | grid-gap: 12px; 30 | align-items: stretch; 31 | ` 32 | 33 | export const PageTitle = styled.div` 34 | font-size: 28px; 35 | color: ${({ theme }) => theme.text7}; 36 | 37 | ${({ theme }) => theme.mediaWidth.upToSmall` 38 | font-size: 22px; 39 | `}; 40 | ` 41 | 42 | export const PageDescription = styled.div` 43 | font-size: 18px; 44 | margin-bottom: 16px; 45 | color: ${({ theme }) => theme.text8}; 46 | 47 | ${({ theme }) => theme.mediaWidth.upToSmall` 48 | font-size: 14px; 49 | `}; 50 | ` 51 | -------------------------------------------------------------------------------- /src/pages/Migrate/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react' 2 | import MigrateUI from './Migrate' 3 | import { ApplicationModal } from '../../state/application/actions' 4 | import { useModalOpen } from '../../state/application/hooks' 5 | 6 | const Migrate = () => { 7 | const isModalOpen = useModalOpen(ApplicationModal.MIGRATION) 8 | const [refreshData, setRefreshData] = useState(true) 9 | 10 | // Here we have done some hacky things to refresh migration data once user complete migrate process 11 | // we are unmounting and mounting MigrateUI component so it loads all data fresh 12 | useEffect(() => { 13 | if (!isModalOpen) { 14 | setRefreshData(false) 15 | setTimeout(() => { 16 | setRefreshData(true) 17 | }, 50) 18 | } 19 | }, [isModalOpen]) 20 | 21 | return refreshData ? : null 22 | } 23 | 24 | export default Migrate 25 | -------------------------------------------------------------------------------- /src/pages/SarStake/StakeStat/styleds.tsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components' 2 | import { Box } from '@honeycomb-finance/core' 3 | 4 | export const Wrapper = styled(Box)` 5 | display: grid; 6 | grid-template-columns: max-content 1fr; 7 | gap: 30px; 8 | background-color: ${({ theme }) => theme.color2}; 9 | border-radius: 10px; 10 | padding: 16px; 11 | 12 | @media (max-width: 920px) { 13 | grid-template-columns: 1fr 1fr; 14 | } 15 | 16 | @media (max-width: 450px) { 17 | grid-template-columns: 1fr; 18 | } 19 | ` 20 | 21 | export const Title = styled(Box)` 22 | display: inline-grid; 23 | grid-template-columns: auto 1fr; 24 | gap: 8px; 25 | align-items: center; 26 | justify-items: center; 27 | width: auto; 28 | ` 29 | 30 | export const StatWrapper = styled(Box)` 31 | display: grid; 32 | grid-template-columns: repeat(auto-fit, minmax(0, max-content)); 33 | gap: 20px; 34 | justify-content: end; 35 | align-items: center; 36 | flex-wrap: wrap; 37 | 38 | @media (max-width: 1200px) { 39 | grid-template-columns: 1fr 1fr; 40 | grid-template-rows: 1fr 1fr; 41 | } 42 | ` 43 | -------------------------------------------------------------------------------- /src/pages/SarStake/styleds.tsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components' 2 | import { Box } from '@honeycomb-finance/core' 3 | import { CloseIcon } from 'src/theme' 4 | 5 | export const PageWrapper = styled(Box)` 6 | width: 100%; 7 | padding-top: 25px; 8 | display: grid; 9 | flex-grow: 1; 10 | grid-gap: 16px; 11 | grid-template-columns: 75% 25%; 12 | grid-template-rows: max-content 1fr; 13 | grid-template-areas: 14 | 'details stake' 15 | 'images stake'; 16 | 17 | ${({ theme }) => theme.mediaWidth.upToLarge` 18 | grid-template-columns: 65% 35%; 19 | `} 20 | 21 | ${({ theme }) => theme.mediaWidth.upToMedium` 22 | grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); 23 | `} 24 | 25 | ${({ theme }) => theme.mediaWidth.upToSmall` 26 | grid-template-columns: none; 27 | grid-template-areas: 28 | 'details' 29 | 'stake' 30 | 'images'; 31 | `}; 32 | ` 33 | export const CloseButton = styled(CloseIcon)` 34 | color: ${({ theme }) => theme.text1}; 35 | position: absolute; 36 | right: 9px; 37 | top: 9px; 38 | ` 39 | 40 | export const StyledSVG = styled(Box)` 41 | svg { 42 | width: 100%; 43 | height: 100%; 44 | } 45 | 46 | height: 400px; 47 | ` 48 | -------------------------------------------------------------------------------- /src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | declare module '@metamask/jazzicon' { 2 | export default function(diameter: number, seed: number): HTMLElement 3 | } 4 | 5 | interface Window { 6 | WalletLinkProvider?: any 7 | walletLinkExtension?: any 8 | xfi?: any 9 | bitkeep?: any 10 | isBitKeep?: true 11 | ethereum?: { 12 | isCoinbaseWallet?: boolean 13 | isMetaMask?: true 14 | isXDEFI?: true 15 | isRabby?: true 16 | isTalisman?: true 17 | on?: (...args: any[]) => void 18 | removeListener?: (...args: any[]) => void 19 | request: (...args: any[]) => Promise 20 | getBlock?: (block) => Promise 21 | getTransactionReceipt?: (hash) => Promise 22 | getBlockNumber?: () => Promise 23 | execute?: (method, params) => Promise 24 | } 25 | web3?: {} 26 | avalanche?: { 27 | isAvalanche?: boolean 28 | once(eventName: string | symbol, listener: (...args: any[]) => void): this 29 | on(eventName: string | symbol, listener: (...args: any[]) => void): this 30 | off(eventName: string | symbol, listener: (...args: any[]) => void): this 31 | addListener(eventName: string | symbol, listener: (...args: any[]) => void): this 32 | removeListener(eventName: string | symbol, listener: (...args: any[]) => void): this 33 | removeAllListeners(event?: string | symbol): this 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/setupProxy.js: -------------------------------------------------------------------------------- 1 | // App is an express application, we can add an express middleware that will set headers for manifest.json request 2 | // https://create-react-app.dev/docs/proxying-api-requests-in-development/#configuring-the-proxy-manually 3 | 4 | module.exports = function(app) { 5 | app.use('/manifest.json', function(req, res, next) { 6 | res.set({ 7 | 'Access-Control-Allow-Origin': '*', 8 | 'Access-Control-Allow-Methods': 'GET', 9 | 'Access-Control-Allow-Headers': 'X-Requested-With, content-type, Authorization' 10 | }) 11 | 12 | next() 13 | }) 14 | } 15 | -------------------------------------------------------------------------------- /src/state/application/actions.ts: -------------------------------------------------------------------------------- 1 | import { createAction } from '@reduxjs/toolkit' 2 | 3 | export type PopupContent = { 4 | txn: { 5 | hash: string 6 | success: boolean 7 | summary?: string 8 | } 9 | } 10 | 11 | export enum ApplicationModal { 12 | WALLET, 13 | CLAIM_POPUP, 14 | DELEGATE, 15 | VOTE, 16 | LANGUAGE, 17 | MIGRATION, 18 | FARM, 19 | SINGLE_SIDE_STAKE_DETAIL, 20 | ACCOUNT_DETAIL 21 | } 22 | 23 | export const updateBlockNumber = createAction<{ chainId: number; blockNumber: number }>('application/updateBlockNumber') 24 | export const setOpenModal = createAction('application/setOpenModal') 25 | -------------------------------------------------------------------------------- /src/state/application/hooks.ts: -------------------------------------------------------------------------------- 1 | import { useCallback, useMemo } from 'react' 2 | import { useDispatch } from 'src/state' 3 | import { useActiveWeb3React } from '@honeycomb-finance/shared' 4 | import { useActivePopups as useActiveComponentsPopup } from '@honeycomb-finance/state-hooks' 5 | import { AppState, useSelector } from '../index' 6 | import { ApplicationModal, setOpenModal } from './actions' 7 | 8 | export function useBlockNumber(): number | undefined { 9 | const { chainId } = useActiveWeb3React() 10 | return useSelector((state: AppState) => state.application.blockNumber[chainId ?? -1]) 11 | } 12 | 13 | export function useModalOpen(modal: ApplicationModal): boolean { 14 | const openModal = useSelector((state: AppState) => state.application.openModal) 15 | return openModal === modal 16 | } 17 | 18 | export function useToggleModal(modal: ApplicationModal): () => void { 19 | const open = useModalOpen(modal) 20 | const dispatch = useDispatch() 21 | return useCallback(() => dispatch(setOpenModal(open ? null : modal)), [dispatch, modal, open]) 22 | } 23 | 24 | export function useMigrationModalToggle(): () => void { 25 | return useToggleModal(ApplicationModal.MIGRATION) 26 | } 27 | 28 | export function useSingleSideStakingDetailnModalToggle(): () => void { 29 | return useToggleModal(ApplicationModal.SINGLE_SIDE_STAKE_DETAIL) 30 | } 31 | 32 | export function useToggleDelegateModal(): () => void { 33 | return useToggleModal(ApplicationModal.DELEGATE) 34 | } 35 | 36 | export function useAccountDetailToggle(): () => void { 37 | return useToggleModal(ApplicationModal.ACCOUNT_DETAIL) 38 | } 39 | 40 | // get the list of active popups 41 | export function useActivePopups() { 42 | const popups = useActiveComponentsPopup() 43 | return useMemo(() => popups.filter((item: any) => item.show), [popups]) 44 | } 45 | -------------------------------------------------------------------------------- /src/state/application/reducer.ts: -------------------------------------------------------------------------------- 1 | import { createReducer } from '@reduxjs/toolkit' 2 | import { updateBlockNumber, ApplicationModal, setOpenModal } from './actions' 3 | 4 | export interface ApplicationState { 5 | readonly blockNumber: { readonly [chainId: number]: number } 6 | readonly openModal: ApplicationModal | null 7 | } 8 | 9 | const initialState: ApplicationState = { 10 | blockNumber: {}, 11 | openModal: null 12 | } 13 | 14 | export default createReducer(initialState, builder => 15 | builder 16 | .addCase(updateBlockNumber, (state, action) => { 17 | const { chainId, blockNumber } = action.payload 18 | if (typeof state.blockNumber[chainId] !== 'number') { 19 | state.blockNumber[chainId] = blockNumber 20 | } else { 21 | state.blockNumber[chainId] = Math.max(blockNumber, state.blockNumber[chainId]) 22 | } 23 | }) 24 | .addCase(setOpenModal, (state, action) => { 25 | state.openModal = action.payload 26 | }) 27 | ) 28 | -------------------------------------------------------------------------------- /src/state/global/actions.ts: -------------------------------------------------------------------------------- 1 | import { createAction } from '@reduxjs/toolkit' 2 | 3 | // fired once when the app reloads but before the app renders 4 | // allows any updates to be applied to store data loaded from localStorage 5 | export const updateVersion = createAction('global/updateVersion') 6 | -------------------------------------------------------------------------------- /src/state/index.ts: -------------------------------------------------------------------------------- 1 | import { configureStore, getDefaultMiddleware } from '@reduxjs/toolkit' 2 | import { save, load } from 'redux-localstorage-simple' 3 | import application from './application/reducer' 4 | import { updateVersion } from './global/actions' 5 | import user from './user/reducer' 6 | import transactions from './transactions/reducer' 7 | import multicall from './multicall/reducer' 8 | import watchlists from './watchlists/reducer' 9 | import token from './token/reducer' 10 | import pair from './pair/reducer' 11 | import { createDispatchHook, createSelectorHook, createStoreHook } from 'react-redux' 12 | import React from 'react' 13 | 14 | const PERSISTED_KEYS: string[] = ['user', 'transactions', 'lists', 'watchlists'] 15 | 16 | const store = configureStore({ 17 | reducer: { 18 | application, 19 | user, 20 | transactions, 21 | multicall, 22 | watchlists, 23 | token, 24 | pair 25 | }, 26 | middleware: [...getDefaultMiddleware({ thunk: false }), save({ states: PERSISTED_KEYS })], 27 | preloadedState: load({ states: PERSISTED_KEYS }) 28 | }) 29 | 30 | store.dispatch(updateVersion()) 31 | 32 | export default store 33 | 34 | export type AppState = ReturnType 35 | export type AppDispatch = typeof store.dispatch 36 | 37 | export const InterfaceContext = React.createContext(null as any) 38 | 39 | // Export your custom hooks if you wish to use them in other files. 40 | export const useStore = createStoreHook(InterfaceContext) 41 | export const useDispatch = createDispatchHook(InterfaceContext) 42 | export const useSelector = createSelectorHook(InterfaceContext) 43 | -------------------------------------------------------------------------------- /src/state/migrate/hooks.ts: -------------------------------------------------------------------------------- 1 | import { useMemo, useState, useEffect } from 'react' 2 | import { Pair } from '@pangolindex/sdk' 3 | import { useGetStakingDataWithAPR } from '../../state/stake/hooks' 4 | import { useGetUserLP, useMinichefPools, MinichefStakingInfo } from '@honeycomb-finance/pools' 5 | 6 | export function useGetMigrationData(version: number) { 7 | const { v2IsLoading, allV2PairsWithLiquidity, allPairs } = useGetUserLP() 8 | 9 | const [allPool, setAllPool] = useState({} as { [address: string]: { pair: Pair; staking: MinichefStakingInfo } }) 10 | 11 | const stakingInfos = useGetStakingDataWithAPR(Number(version)) 12 | 13 | const poolMap = useMinichefPools() 14 | /* eslint-disable prefer-const */ 15 | useEffect(() => { 16 | let pairs = {} as { [address: string]: { pair: Pair; staking: MinichefStakingInfo } } 17 | 18 | for (const stakingInfo of stakingInfos) { 19 | let pairAddress = stakingInfo?.stakedAmount?.token?.address 20 | let stakingData = stakingInfo 21 | 22 | let pair = (allPairs as Pair[]).find( 23 | data => data?.liquidityToken?.address === stakingData?.stakedAmount?.token?.address 24 | ) as Pair 25 | 26 | if (stakingData?.stakedAmount.greaterThan('0') && poolMap.hasOwnProperty(pairAddress)) { 27 | pairs[pairAddress] = { 28 | pair: pair, 29 | staking: stakingData 30 | } 31 | } 32 | } 33 | 34 | setAllPool(pairs) 35 | 36 | // eslint-disable-next-line react-hooks/exhaustive-deps 37 | }, [(stakingInfos || []).length]) 38 | 39 | return useMemo( 40 | () => ({ allPool, v2IsLoading: v2IsLoading || stakingInfos.length === 0, allV2PairsWithLiquidity, allPairs }), 41 | [allPool, v2IsLoading, stakingInfos, allV2PairsWithLiquidity, allPairs] 42 | ) 43 | } 44 | -------------------------------------------------------------------------------- /src/state/multicall/actions.test.ts: -------------------------------------------------------------------------------- 1 | import { parseCallKey, toCallKey } from './actions' 2 | 3 | describe('actions', () => { 4 | describe('#parseCallKey', () => { 5 | it('does not throw for invalid address', () => { 6 | expect(parseCallKey('0x-0x')).toEqual({ address: '0x', callData: '0x' }) 7 | }) 8 | it('does not throw for invalid calldata', () => { 9 | expect(parseCallKey('0x6b175474e89094c44da98b954eedeac495271d0f-abc')).toEqual({ 10 | address: '0x6b175474e89094c44da98b954eedeac495271d0f', 11 | callData: 'abc' 12 | }) 13 | }) 14 | it('throws for invalid format', () => { 15 | expect(() => parseCallKey('abc')).toThrow('Invalid call key: abc') 16 | }) 17 | it('throws for uppercase calldata', () => { 18 | expect(parseCallKey('0x6b175474e89094c44da98b954eedeac495271d0f-0xabcD')).toEqual({ 19 | address: '0x6b175474e89094c44da98b954eedeac495271d0f', 20 | callData: '0xabcD' 21 | }) 22 | }) 23 | it('parses pieces into address', () => { 24 | expect(parseCallKey('0x6b175474e89094c44da98b954eedeac495271d0f-0xabcd')).toEqual({ 25 | address: '0x6b175474e89094c44da98b954eedeac495271d0f', 26 | callData: '0xabcd' 27 | }) 28 | }) 29 | }) 30 | 31 | describe('#toCallKey', () => { 32 | it('throws for invalid address', () => { 33 | expect(() => toCallKey({ callData: '0x', address: '0x' })).toThrow('Invalid address: 0x') 34 | }) 35 | it('throws for invalid calldata', () => { 36 | expect(() => 37 | toCallKey({ 38 | address: '0x6b175474e89094c44da98b954eedeac495271d0f', 39 | callData: 'abc' 40 | }) 41 | ).toThrow('Invalid hex: abc') 42 | }) 43 | it('throws for uppercase hex', () => { 44 | expect(() => 45 | toCallKey({ 46 | address: '0x6b175474e89094c44da98b954eedeac495271d0f', 47 | callData: '0xabcD' 48 | }) 49 | ).toThrow('Invalid hex: 0xabcD') 50 | }) 51 | it('concatenates address to data', () => { 52 | expect(toCallKey({ address: '0x6b175474e89094c44da98b954eedeac495271d0f', callData: '0xabcd' })).toEqual( 53 | '0x6b175474e89094c44da98b954eedeac495271d0f-0xabcd' 54 | ) 55 | }) 56 | }) 57 | }) 58 | -------------------------------------------------------------------------------- /src/state/multicall/actions.ts: -------------------------------------------------------------------------------- 1 | import { createAction } from '@reduxjs/toolkit' 2 | 3 | export interface Call { 4 | address: string 5 | callData: string 6 | } 7 | 8 | const ADDRESS_REGEX = /^0x[a-fA-F0-9]{40}$/ 9 | const LOWER_HEX_REGEX = /^0x[a-f0-9]*$/ 10 | export function toCallKey(call: Call): string { 11 | if (!ADDRESS_REGEX.test(call.address)) { 12 | throw new Error(`Invalid address: ${call.address}`) 13 | } 14 | if (!LOWER_HEX_REGEX.test(call.callData)) { 15 | throw new Error(`Invalid hex: ${call.callData}`) 16 | } 17 | return `${call.address}-${call.callData}` 18 | } 19 | 20 | export function parseCallKey(callKey: string): Call { 21 | const pcs = callKey.split('-') 22 | if (pcs.length !== 2) { 23 | throw new Error(`Invalid call key: ${callKey}`) 24 | } 25 | return { 26 | address: pcs[0], 27 | callData: pcs[1] 28 | } 29 | } 30 | 31 | export interface ListenerOptions { 32 | // how often this data should be fetched, by default 1 33 | readonly blocksPerFetch?: number 34 | } 35 | 36 | export const addMulticallListeners = createAction<{ chainId: number; calls: Call[]; options?: ListenerOptions }>( 37 | 'multicall/addMulticallListeners' 38 | ) 39 | export const removeMulticallListeners = createAction<{ chainId: number; calls: Call[]; options?: ListenerOptions }>( 40 | 'multicall/removeMulticallListeners' 41 | ) 42 | export const fetchingMulticallResults = createAction<{ chainId: number; calls: Call[]; fetchingBlockNumber: number }>( 43 | 'multicall/fetchingMulticallResults' 44 | ) 45 | export const errorFetchingMulticallResults = createAction<{ 46 | chainId: number 47 | calls: Call[] 48 | fetchingBlockNumber: number 49 | }>('multicall/errorFetchingMulticallResults') 50 | export const updateMulticallResults = createAction<{ 51 | chainId: number 52 | blockNumber: number 53 | results: { 54 | [callKey: string]: string | null 55 | } 56 | }>('multicall/updateMulticallResults') 57 | -------------------------------------------------------------------------------- /src/state/pair/actions.ts: -------------------------------------------------------------------------------- 1 | import { createAction } from '@reduxjs/toolkit' 2 | import { Time } from 'lightweight-charts' 3 | 4 | export const updatePairChartData = createAction<{ 5 | address: string 6 | chartData: Array> 7 | }>('pair/updatePairChartData') 8 | 9 | export const updatePairTokensChartData = createAction<{ 10 | address: string 11 | chartData: Array> 12 | }>('pair/updatePairTokensChartData') 13 | -------------------------------------------------------------------------------- /src/state/pair/reducer.ts: -------------------------------------------------------------------------------- 1 | import { createReducer } from '@reduxjs/toolkit' 2 | import { updatePairChartData, updatePairTokensChartData } from './actions' 3 | import { Time } from 'lightweight-charts' 4 | 5 | export interface ChartState { 6 | [address: string]: Array> 7 | } 8 | 9 | export interface TokenChartState { 10 | readonly pairData: ChartState 11 | readonly tokenPairData: ChartState 12 | } 13 | 14 | const initialState: TokenChartState = { 15 | pairData: {}, 16 | tokenPairData: {} 17 | } 18 | 19 | export default createReducer(initialState, builder => 20 | builder 21 | .addCase(updatePairChartData, (state, { payload: { address, chartData } }) => { 22 | const container = {} as ChartState 23 | container[address] = chartData 24 | const existingChartData = { 25 | ...(state.pairData || {}), 26 | ...container 27 | } 28 | state.pairData = existingChartData 29 | }) 30 | 31 | .addCase(updatePairTokensChartData, (state, { payload: { address, chartData } }) => { 32 | const container = {} as ChartState 33 | container[address] = chartData 34 | const existingChartData = { 35 | ...(state.tokenPairData || {}), 36 | ...container 37 | } 38 | state.tokenPairData = existingChartData 39 | }) 40 | ) 41 | -------------------------------------------------------------------------------- /src/state/stake/multiChainsHooks.ts: -------------------------------------------------------------------------------- 1 | import { ChainId } from '@pangolindex/sdk' 2 | import { useTotalPngEarned, useNearTotalPngEarned } from 'src/state/stake/hooks' 3 | 4 | export function useDummyHook() { 5 | return undefined 6 | } 7 | 8 | export type UseTotalPngEarnedHookType = { 9 | [chainId in ChainId]: typeof useTotalPngEarned | typeof useNearTotalPngEarned | typeof useDummyHook 10 | } 11 | 12 | export const useTotalPngEarnedHook: UseTotalPngEarnedHookType = { 13 | [ChainId.FUJI]: useTotalPngEarned, 14 | [ChainId.AVALANCHE]: useTotalPngEarned, 15 | [ChainId.WAGMI]: useTotalPngEarned, 16 | [ChainId.COSTON]: useTotalPngEarned, 17 | [ChainId.SONGBIRD]: useTotalPngEarned, 18 | [ChainId.FLARE_MAINNET]: useTotalPngEarned, 19 | [ChainId.HEDERA_TESTNET]: useTotalPngEarned, 20 | [ChainId.HEDERA_MAINNET]: useTotalPngEarned, 21 | [ChainId.NEAR_MAINNET]: useNearTotalPngEarned, 22 | [ChainId.NEAR_TESTNET]: useNearTotalPngEarned, 23 | [ChainId.COSTON2]: useTotalPngEarned, 24 | [ChainId.EVMOS_TESTNET]: useTotalPngEarned, 25 | [ChainId.EVMOS_MAINNET]: useDummyHook, 26 | [ChainId.ETHEREUM]: useDummyHook, 27 | [ChainId.POLYGON]: useDummyHook, 28 | [ChainId.FANTOM]: useDummyHook, 29 | [ChainId.XDAI]: useDummyHook, 30 | [ChainId.BSC]: useDummyHook, 31 | [ChainId.ARBITRUM]: useDummyHook, 32 | [ChainId.CELO]: useDummyHook, 33 | [ChainId.OKXCHAIN]: useDummyHook, 34 | [ChainId.VELAS]: useDummyHook, 35 | [ChainId.AURORA]: useDummyHook, 36 | [ChainId.CRONOS]: useDummyHook, 37 | [ChainId.FUSE]: useDummyHook, 38 | [ChainId.MOONRIVER]: useDummyHook, 39 | [ChainId.MOONBEAM]: useDummyHook, 40 | [ChainId.OP]: useDummyHook, 41 | [ChainId.SKALE_BELLATRIX_TESTNET]: useTotalPngEarned 42 | } 43 | -------------------------------------------------------------------------------- /src/state/stake/singleSideConfig.ts: -------------------------------------------------------------------------------- 1 | import { ChainId, WAVAX, AVALANCHE_FUJI, Token } from '@pangolindex/sdk' 2 | import { Tokens } from '@honeycomb-finance/shared' 3 | 4 | const { PNG } = Tokens 5 | 6 | export interface SingleSideStaking { 7 | rewardToken: Token 8 | conversionRouteHops: Token[] 9 | stakingRewardAddress: string 10 | version: number 11 | } 12 | 13 | export const SINGLE_SIDE_STAKING: { [key: string]: SingleSideStaking } = { 14 | WAVAX_V0: { 15 | rewardToken: WAVAX[ChainId.AVALANCHE], 16 | conversionRouteHops: [], 17 | stakingRewardAddress: '0xD49B406A7A29D64e081164F6C3353C599A2EeAE9', 18 | version: 0 19 | }, 20 | PNG_V0: { 21 | rewardToken: PNG[ChainId.AVALANCHE], 22 | conversionRouteHops: [WAVAX[ChainId.AVALANCHE]], 23 | stakingRewardAddress: '0x88afdaE1a9F58Da3E68584421937E5F564A0135b', 24 | version: 0 25 | } 26 | } 27 | 28 | export const SINGLE_SIDE_STAKING_V0: SingleSideStaking[] = Object.values(SINGLE_SIDE_STAKING).filter( 29 | staking => staking.version === 0 30 | ) 31 | 32 | const FUJI_SINGLE_SIDE_STAKING: SingleSideStaking[] = 33 | AVALANCHE_FUJI.contracts?.staking 34 | ?.filter(contract => contract.active) 35 | .map(contract => ({ 36 | rewardToken: PNG[ChainId.FUJI], 37 | conversionRouteHops: [WAVAX[ChainId.FUJI]], 38 | stakingRewardAddress: contract.address, 39 | version: 0 40 | })) ?? [] 41 | 42 | export const SINGLE_SIDE_STAKING_REWARDS_INFO: { 43 | [chainId in ChainId]?: SingleSideStaking[][] 44 | } = { 45 | [ChainId.AVALANCHE]: [SINGLE_SIDE_STAKING_V0], 46 | [ChainId.FUJI]: [FUJI_SINGLE_SIDE_STAKING] 47 | } 48 | -------------------------------------------------------------------------------- /src/state/token/actions.ts: -------------------------------------------------------------------------------- 1 | import { createAction } from '@reduxjs/toolkit' 2 | 3 | export const updateTokenWeeklyPriceChartData = createAction<{ 4 | address: string 5 | chartData: Array<{ priceUSD: number; date: string }> 6 | }>('token/updateTokenWeeklyPriceChartData') 7 | 8 | export const updateTokenPriceChartData = createAction<{ 9 | address: string 10 | chartData: Array<{ priceUSD: number; timestamp: string }> 11 | }>('token/updateTokenPriceChartData') 12 | -------------------------------------------------------------------------------- /src/state/token/hooks.ts: -------------------------------------------------------------------------------- 1 | import { blockClients } from '../../apollo/client' 2 | import { GET_BLOCKS } from '../../apollo/block' 3 | import dayjs from 'dayjs' 4 | import utc from 'dayjs/plugin/utc' 5 | import { ChainId } from '@pangolindex/sdk' 6 | import { splitQuery } from '@honeycomb-finance/shared' 7 | 8 | dayjs.extend(utc) 9 | 10 | export async function getBlocksFromTimestamps(timestamps: Array, chainId: ChainId, skipCount = 500) { 11 | const blockClient = blockClients[chainId] 12 | if (timestamps?.length === 0 || !blockClient) { 13 | return [] 14 | } 15 | const fetchedData: any = await splitQuery(GET_BLOCKS, blockClient, [], timestamps, skipCount) 16 | const blocks = [] 17 | if (fetchedData) { 18 | for (const t in fetchedData) { 19 | if (fetchedData[t].length > 0) { 20 | blocks.push({ 21 | timestamp: t.split('t')[1], 22 | number: fetchedData[t][0]['number'] 23 | }) 24 | } 25 | } 26 | } 27 | 28 | return blocks 29 | } 30 | -------------------------------------------------------------------------------- /src/state/token/reducer.ts: -------------------------------------------------------------------------------- 1 | import { createReducer } from '@reduxjs/toolkit' 2 | import { updateTokenWeeklyPriceChartData, updateTokenPriceChartData } from './actions' 3 | 4 | export interface WeeklyState { 5 | [address: string]: Array<{ priceUSD: number; date: string }> 6 | } 7 | 8 | export interface ChartState { 9 | [address: string]: Array<{ priceUSD: number; timestamp: string }> 10 | } 11 | 12 | export interface TokenChartState { 13 | readonly weekly: WeeklyState 14 | readonly tokenPrices: ChartState 15 | } 16 | 17 | const initialState: TokenChartState = { 18 | weekly: {}, 19 | tokenPrices: {} 20 | } 21 | 22 | export default createReducer(initialState, builder => 23 | builder 24 | 25 | .addCase(updateTokenWeeklyPriceChartData, (state, { payload: { address, chartData } }) => { 26 | const container = {} as WeeklyState 27 | container[address] = chartData 28 | const existingChartData = { 29 | ...(state.weekly || {}), 30 | ...container 31 | } 32 | state.weekly = existingChartData 33 | }) 34 | 35 | .addCase(updateTokenPriceChartData, (state, { payload: { address, chartData } }) => { 36 | const container = {} as ChartState 37 | container[address] = chartData 38 | const existingChartData = { 39 | ...(state.tokenPrices || {}), 40 | ...container 41 | } 42 | state.tokenPrices = existingChartData 43 | }) 44 | ) 45 | -------------------------------------------------------------------------------- /src/state/transactions/actions.ts: -------------------------------------------------------------------------------- 1 | import { createAction } from '@reduxjs/toolkit' 2 | import { ChainId } from '@pangolindex/sdk' 3 | 4 | export interface SerializableTransactionReceipt { 5 | to: string 6 | from: string 7 | contractAddress: string 8 | transactionIndex: number 9 | blockHash: string 10 | transactionHash: string 11 | blockNumber: number 12 | status?: number 13 | } 14 | 15 | export const addTransaction = createAction<{ 16 | chainId: ChainId 17 | hash: string 18 | from: string 19 | approval?: { tokenAddress: string; spender: string } 20 | claim?: { recipient: string } 21 | summary?: string 22 | }>('transactions/addTransaction') 23 | export const clearAllTransactions = createAction<{ chainId: ChainId }>('transactions/clearAllTransactions') 24 | export const finalizeTransaction = createAction<{ 25 | chainId: ChainId 26 | hash: string 27 | receipt: SerializableTransactionReceipt 28 | }>('transactions/finalizeTransaction') 29 | export const checkedTransaction = createAction<{ 30 | chainId: ChainId 31 | hash: string 32 | blockNumber: number 33 | }>('transactions/checkedTransaction') 34 | -------------------------------------------------------------------------------- /src/state/transactions/reducer.ts: -------------------------------------------------------------------------------- 1 | import { createReducer } from '@reduxjs/toolkit' 2 | import { 3 | addTransaction, 4 | checkedTransaction, 5 | clearAllTransactions, 6 | finalizeTransaction, 7 | SerializableTransactionReceipt 8 | } from './actions' 9 | 10 | const now = () => new Date().getTime() 11 | 12 | export interface TransactionDetails { 13 | hash: string 14 | approval?: { tokenAddress: string; spender: string } 15 | summary?: string 16 | claim?: { recipient: string } 17 | receipt?: SerializableTransactionReceipt 18 | lastCheckedBlockNumber?: number 19 | addedTime: number 20 | confirmedTime?: number 21 | from: string 22 | } 23 | 24 | export interface TransactionState { 25 | [chainId: number]: { 26 | [txHash: string]: TransactionDetails 27 | } 28 | } 29 | 30 | export const initialState: TransactionState = {} 31 | 32 | export default createReducer(initialState, builder => 33 | builder 34 | .addCase(addTransaction, (transactions, { payload: { chainId, from, hash, approval, summary, claim } }) => { 35 | if (transactions[chainId]?.[hash]) { 36 | throw Error('Attempted to add existing transaction.') 37 | } 38 | const txs = transactions[chainId] ?? {} 39 | txs[hash] = { hash, approval, summary, claim, from, addedTime: now() } 40 | transactions[chainId] = txs 41 | }) 42 | .addCase(clearAllTransactions, (transactions, { payload: { chainId } }) => { 43 | if (!transactions[chainId]) return 44 | transactions[chainId] = {} 45 | }) 46 | .addCase(checkedTransaction, (transactions, { payload: { chainId, hash, blockNumber } }) => { 47 | const tx = transactions[chainId]?.[hash] 48 | if (!tx) { 49 | return 50 | } 51 | if (!tx.lastCheckedBlockNumber) { 52 | tx.lastCheckedBlockNumber = blockNumber 53 | } else { 54 | tx.lastCheckedBlockNumber = Math.max(blockNumber, tx.lastCheckedBlockNumber) 55 | } 56 | }) 57 | .addCase(finalizeTransaction, (transactions, { payload: { hash, chainId, receipt } }) => { 58 | const tx = transactions[chainId]?.[hash] 59 | if (!tx) { 60 | return 61 | } 62 | tx.receipt = receipt 63 | tx.confirmedTime = now() 64 | }) 65 | ) 66 | -------------------------------------------------------------------------------- /src/state/transactions/updater.test.ts: -------------------------------------------------------------------------------- 1 | // import { shouldCheck } from './updater' 2 | 3 | describe('transactions updater', () => { 4 | describe('shouldCheck', () => { 5 | it('returns true if no receipt and never checked', () => { 6 | // expect(shouldCheck(10, { addedTime: 100 })).toEqual(true) 7 | }) 8 | // it('returns false if has receipt and never checked', () => { 9 | // expect(shouldCheck(10, { addedTime: 100, receipt: {} })).toEqual(false) 10 | // }) 11 | // it('returns true if has not been checked in 1 blocks', () => { 12 | // expect(shouldCheck(10, { addedTime: new Date().getTime(), lastCheckedBlockNumber: 9 })).toEqual(true) 13 | // }) 14 | // it('returns false if checked in last 3 blocks and greater than 20 minutes old', () => { 15 | // expect(shouldCheck(10, { addedTime: new Date().getTime() - 21 * 60 * 1000, lastCheckedBlockNumber: 8 })).toEqual( 16 | // false 17 | // ) 18 | // }) 19 | // it('returns true if not checked in last 5 blocks and greater than 20 minutes old', () => { 20 | // expect(shouldCheck(10, { addedTime: new Date().getTime() - 21 * 60 * 1000, lastCheckedBlockNumber: 5 })).toEqual( 21 | // true 22 | // ) 23 | // }) 24 | // it('returns false if checked in last 10 blocks and greater than 60 minutes old', () => { 25 | // expect(shouldCheck(20, { addedTime: new Date().getTime() - 61 * 60 * 1000, lastCheckedBlockNumber: 11 })).toEqual( 26 | // false 27 | // ) 28 | // }) 29 | // it('returns true if checked in last 3 blocks and greater than 20 minutes old', () => { 30 | // expect(shouldCheck(20, { addedTime: new Date().getTime() - 61 * 60 * 1000, lastCheckedBlockNumber: 10 })).toEqual( 31 | // true 32 | // ) 33 | // }) 34 | }) 35 | }) 36 | -------------------------------------------------------------------------------- /src/state/user/actions.ts: -------------------------------------------------------------------------------- 1 | import { createAction } from '@reduxjs/toolkit' 2 | 3 | export const updateMatchesDarkMode = createAction<{ matchesDarkMode: boolean }>('user/updateMatchesDarkMode') 4 | export const updateUserDarkMode = createAction<{ userDarkMode: boolean }>('user/updateUserDarkMode') 5 | export const toggleURLWarning = createAction('app/toggleURLWarning') 6 | export const updateWallet = createAction<{ wallet: string | null }>('user/updateWallet') 7 | -------------------------------------------------------------------------------- /src/state/user/hooks.tsx: -------------------------------------------------------------------------------- 1 | import { useCallback } from 'react' 2 | import { shallowEqual } from 'react-redux' 3 | import { AppState, useDispatch, useSelector } from '../index' 4 | import { updateUserDarkMode, toggleURLWarning, updateWallet } from './actions' 5 | 6 | export function useIsDarkMode(): boolean { 7 | const { userDarkMode, matchesDarkMode } = useSelector<{ userDarkMode: boolean | null; matchesDarkMode: boolean }>( 8 | ({ user: { matchesDarkMode, userDarkMode } }) => ({ 9 | userDarkMode, 10 | matchesDarkMode 11 | }), 12 | shallowEqual 13 | ) 14 | 15 | if (userDarkMode === null && !matchesDarkMode) { 16 | // by default we want to show dark mode 17 | return true 18 | } 19 | 20 | return userDarkMode === null ? matchesDarkMode : userDarkMode 21 | } 22 | 23 | export function useDarkModeManager(): [boolean, () => void] { 24 | const dispatch = useDispatch() 25 | const darkMode = useIsDarkMode() 26 | 27 | const toggleSetDarkMode = useCallback(() => { 28 | dispatch(updateUserDarkMode({ userDarkMode: !darkMode })) 29 | }, [darkMode, dispatch]) 30 | 31 | return [darkMode, toggleSetDarkMode] 32 | } 33 | 34 | export function useURLWarningVisible(): boolean { 35 | return useSelector((state: AppState) => state.user.URLWarningVisible) 36 | } 37 | 38 | export function useURLWarningToggle(): () => void { 39 | const dispatch = useDispatch() 40 | return useCallback(() => dispatch(toggleURLWarning()), [dispatch]) 41 | } 42 | 43 | export function useWallet(): [string | null, (wallet: string | null) => void] { 44 | const dispatch = useDispatch() 45 | const wallet = useSelector(state => state.user.wallet) 46 | 47 | const setWallet = useCallback( 48 | (walletKey: string | null) => { 49 | dispatch(updateWallet({ wallet: walletKey })) 50 | }, 51 | [dispatch] 52 | ) 53 | 54 | return [wallet, setWallet] 55 | } 56 | -------------------------------------------------------------------------------- /src/state/user/reducer.test.ts: -------------------------------------------------------------------------------- 1 | import { createStore, Store } from 'redux' 2 | import { DEFAULT_DEADLINE_FROM_NOW, INITIAL_ALLOWED_SLIPPAGE } from '../../constants' 3 | import { updateVersion } from '../global/actions' 4 | import reducer, { initialState, UserState } from './reducer' 5 | 6 | describe('swap reducer', () => { 7 | let store: Store 8 | 9 | beforeEach(() => { 10 | store = createStore(reducer, initialState) 11 | }) 12 | 13 | describe('updateVersion', () => { 14 | it('has no timestamp originally', () => { 15 | expect(store.getState().lastUpdateVersionTimestamp).toBeUndefined() 16 | }) 17 | it('sets the lastUpdateVersionTimestamp', () => { 18 | const time = new Date().getTime() 19 | store.dispatch(updateVersion()) 20 | expect(store.getState().lastUpdateVersionTimestamp).toBeGreaterThanOrEqual(time) 21 | }) 22 | it('sets allowed slippage and deadline', () => { 23 | store = createStore(reducer, { 24 | ...initialState, 25 | userDeadline: undefined, 26 | userSlippageTolerance: undefined 27 | } as any) 28 | store.dispatch(updateVersion()) 29 | expect(store.getState().userDeadline).toEqual(DEFAULT_DEADLINE_FROM_NOW) 30 | expect(store.getState().userSlippageTolerance).toEqual(INITIAL_ALLOWED_SLIPPAGE) 31 | }) 32 | }) 33 | }) 34 | -------------------------------------------------------------------------------- /src/state/user/updater.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react' 2 | import { useDispatch } from 'src/state' 3 | import { updateMatchesDarkMode } from './actions' 4 | 5 | export default function Updater(): null { 6 | const dispatch = useDispatch() 7 | 8 | // keep dark mode in sync with the system 9 | useEffect(() => { 10 | const darkHandler = (_match: MediaQueryListEvent) => { 11 | dispatch(updateMatchesDarkMode({ matchesDarkMode: _match.matches })) 12 | } 13 | 14 | const match = window?.matchMedia('(prefers-color-scheme: dark)') 15 | dispatch(updateMatchesDarkMode({ matchesDarkMode: match.matches })) 16 | 17 | if (match?.addEventListener) { 18 | match?.addEventListener('change', darkHandler) 19 | } 20 | 21 | return () => { 22 | if (match?.removeEventListener) { 23 | match?.removeEventListener('change', darkHandler) 24 | } 25 | } 26 | }, [dispatch]) 27 | 28 | return null 29 | } 30 | -------------------------------------------------------------------------------- /src/state/watchlists/actions.ts: -------------------------------------------------------------------------------- 1 | import { createAction } from '@reduxjs/toolkit' 2 | 3 | export const addCurrency = createAction('watchlists/addCurrency') 4 | export const removeCurrency = createAction('watchlists/removeCurrency') 5 | -------------------------------------------------------------------------------- /src/state/watchlists/reducer.ts: -------------------------------------------------------------------------------- 1 | import { createReducer } from '@reduxjs/toolkit' 2 | import { addCurrency, removeCurrency } from './actions' 3 | 4 | export interface WatchlistState { 5 | readonly currencies: string[] 6 | } 7 | 8 | const initialState: WatchlistState = { 9 | currencies: [] 10 | } 11 | 12 | export default createReducer(initialState, builder => 13 | builder 14 | .addCase(addCurrency, (state, { payload: address }) => { 15 | const existingSelectedListUrl = ([] as string[]).concat(state.currencies || []) 16 | 17 | existingSelectedListUrl.push(address) 18 | state.currencies = existingSelectedListUrl 19 | }) 20 | .addCase(removeCurrency, (state, { payload: address }) => { 21 | const existingList = ([] as string[]).concat(state.currencies || []) 22 | const index = existingList.indexOf(address) 23 | 24 | if (index !== -1) { 25 | if (existingList?.length === 1) { 26 | // if user want to remove the list and if there is only one item in the selected list 27 | state.currencies = [] as string[] 28 | } else { 29 | existingList.splice(index, 1) 30 | state.currencies = existingList 31 | } 32 | } 33 | }) 34 | ) 35 | -------------------------------------------------------------------------------- /src/utils/getLibrary.ts: -------------------------------------------------------------------------------- 1 | import { Web3Provider } from '@ethersproject/providers' 2 | 3 | export default function getLibrary(provider: any): Web3Provider { 4 | try { 5 | const library = new Web3Provider(provider, 'any') 6 | library.pollingInterval = 15000 7 | return library 8 | } catch (error) { 9 | return provider 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/utils/retry.test.ts: -------------------------------------------------------------------------------- 1 | import { retry, RetryableError } from './retry' 2 | 3 | describe('retry', () => { 4 | function makeFn(fails: number, result: T, retryable = true): () => Promise { 5 | return async () => { 6 | if (fails > 0) { 7 | fails-- 8 | throw retryable ? new RetryableError('failure') : new Error('bad failure') 9 | } 10 | return result 11 | } 12 | } 13 | 14 | it('fails for non-retryable error', async () => { 15 | await expect(retry(makeFn(1, 'abc', false), { n: 3, maxWait: 0, minWait: 0 }).promise).rejects.toThrow( 16 | 'bad failure' 17 | ) 18 | }) 19 | 20 | it('works after one fail', async () => { 21 | await expect(retry(makeFn(1, 'abc'), { n: 3, maxWait: 0, minWait: 0 }).promise).resolves.toEqual('abc') 22 | }) 23 | 24 | it('works after two fails', async () => { 25 | await expect(retry(makeFn(2, 'abc'), { n: 3, maxWait: 0, minWait: 0 }).promise).resolves.toEqual('abc') 26 | }) 27 | 28 | it('throws if too many fails', async () => { 29 | await expect(retry(makeFn(4, 'abc'), { n: 3, maxWait: 0, minWait: 0 }).promise).rejects.toThrow('failure') 30 | }) 31 | 32 | it('cancel causes promise to reject', async () => { 33 | const { promise, cancel } = retry(makeFn(2, 'abc'), { n: 3, minWait: 100, maxWait: 100 }) 34 | cancel() 35 | await expect(promise).rejects.toThrow('Cancelled') 36 | }) 37 | 38 | it('cancel no-op after complete', async () => { 39 | const { promise, cancel } = retry(makeFn(0, 'abc'), { n: 3, minWait: 100, maxWait: 100 }) 40 | // defer 41 | setTimeout(cancel, 0) 42 | await expect(promise).resolves.toEqual('abc') 43 | }) 44 | 45 | async function checkTime(fn: () => Promise, min: number, max: number) { 46 | const time = new Date().getTime() 47 | await fn() 48 | const diff = new Date().getTime() - time 49 | expect(diff).toBeGreaterThanOrEqual(min) 50 | expect(diff).toBeLessThanOrEqual(max) 51 | } 52 | 53 | it('waits random amount of time between min and max', async () => { 54 | const promises = [] 55 | for (let i = 0; i < 10; i++) { 56 | promises.push( 57 | checkTime( 58 | () => expect(retry(makeFn(4, 'abc'), { n: 3, maxWait: 100, minWait: 50 }).promise).rejects.toThrow('failure'), 59 | 150, 60 | 400 61 | ) 62 | ) 63 | } 64 | await Promise.all(promises) 65 | }) 66 | }) 67 | -------------------------------------------------------------------------------- /src/utils/retry.ts: -------------------------------------------------------------------------------- 1 | export function wait(ms: number): Promise { 2 | return new Promise(resolve => setTimeout(resolve, ms)) 3 | } 4 | 5 | function waitRandom(min: number, max: number): Promise { 6 | return wait(min + Math.round(Math.random() * Math.max(0, max - min))) 7 | } 8 | 9 | /** 10 | * This error is thrown if the function is cancelled before completing 11 | */ 12 | export class CancelledError extends Error { 13 | constructor() { 14 | super('Cancelled') 15 | } 16 | } 17 | 18 | /** 19 | * Throw this error if the function should retry 20 | */ 21 | export class RetryableError extends Error {} 22 | 23 | /** 24 | * Retries the function that returns the promise until the promise successfully resolves up to n retries 25 | * @param fn function to retry 26 | * @param n how many times to retry 27 | * @param minWait min wait between retries in ms 28 | * @param maxWait max wait between retries in ms 29 | */ 30 | export function retry( 31 | fn: () => Promise, 32 | { n, minWait, maxWait }: { n: number; minWait: number; maxWait: number } 33 | ): { promise: Promise; cancel: () => void } { 34 | let completed = false 35 | let rejectCancelled: (error: Error) => void 36 | const promise = new Promise(async (resolve, reject) => { 37 | rejectCancelled = reject 38 | while (true) { 39 | let result: T 40 | try { 41 | result = await fn() 42 | if (!completed) { 43 | resolve(result) 44 | completed = true 45 | } 46 | break 47 | } catch (error) { 48 | if (completed) { 49 | break 50 | } 51 | if (n <= 0 || !(error instanceof RetryableError)) { 52 | reject(error) 53 | completed = true 54 | break 55 | } 56 | n-- 57 | } 58 | await waitRandom(minWait, maxWait) 59 | } 60 | }) 61 | return { 62 | promise, 63 | cancel: () => { 64 | if (completed) return 65 | completed = true 66 | rejectCancelled(new CancelledError()) 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES6", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "noEmit": true, 13 | "esModuleInterop": true, 14 | "module": "esnext", 15 | "strict": true, 16 | "alwaysStrict": true, 17 | "strictNullChecks": true, 18 | "noUnusedLocals": true, 19 | "noFallthroughCasesInSwitch": true, 20 | "noImplicitAny": true, 21 | "noImplicitThis": true, 22 | "noImplicitReturns": true, 23 | "moduleResolution": "node", 24 | "resolveJsonModule": true, 25 | "isolatedModules": true, 26 | "jsx": "react", 27 | "downlevelIteration": true, 28 | "allowSyntheticDefaultImports": true, 29 | "types": [ 30 | "react-spring", 31 | "jest" 32 | ], 33 | "baseUrl": "." 34 | }, 35 | "exclude": [ 36 | "node_modules", 37 | "cypress", 38 | "./src/**/*.test.ts" 39 | ], 40 | "include": [ 41 | "./src/**/*.ts", 42 | "./src/**/*.tsx", 43 | "src/components/Confetti/index.js" 44 | ] 45 | } 46 | -------------------------------------------------------------------------------- /wrangler.toml: -------------------------------------------------------------------------------- 1 | name = "pangolin-app-temp" 2 | type = "javascript" 3 | route = "app.pangolin.exchange/*" 4 | 5 | [site] 6 | bucket = "./build" --------------------------------------------------------------------------------