├── .dockerignore ├── .github └── workflows │ └── docker-image.yml ├── .gitignore ├── .husky └── pre-commit ├── .prettierrc ├── Dockerfile ├── LICENSE ├── README.md ├── banner.png ├── docker-compose.yml ├── example.env ├── index.html ├── package-lock.json ├── package.json ├── postcss.config.js ├── public ├── PriceIdsString.txt ├── _headers ├── favicon.svg ├── fonts │ └── runddisplay │ │ ├── runddisplay-black.woff │ │ ├── runddisplay-bold.woff │ │ ├── runddisplay-extrabold.woff │ │ ├── runddisplay-light.woff │ │ ├── runddisplay-medium.woff │ │ ├── runddisplay-regular.woff │ │ ├── runddisplay-semibold.woff │ │ └── runddisplay-thin.woff ├── img │ └── assets │ │ ├── abusd.svg │ │ ├── adai.svg │ │ ├── adm.svg │ │ ├── afrax.svg │ │ ├── akt.svg │ │ ├── alink.svg │ │ ├── alter.svg │ │ ├── amber.jpg │ │ ├── ampkuji.svg │ │ ├── ampluna.svg │ │ ├── ampwhale.svg │ │ ├── andr.png │ │ ├── archway.svg │ │ ├── atom.svg │ │ ├── atom_datom_lp.svg │ │ ├── atom_statom_lp.svg │ │ ├── auni.svg │ │ ├── axl.svg │ │ ├── binj.png │ │ ├── bkuji.png │ │ ├── bld.svg │ │ ├── bluna.png │ │ ├── butt.png │ │ ├── celestia.svg │ │ ├── cheq.svg │ │ ├── cmdx.svg │ │ ├── cmst.svg │ │ ├── composable.svg │ │ ├── coreum.svg │ │ ├── cre.svg │ │ ├── datom.svg │ │ ├── deposit.svg │ │ ├── dot.svg │ │ ├── dshd.svg │ │ ├── dvpn.svg │ │ ├── dydx.svg │ │ ├── dymension.svg │ │ ├── eclip.svg │ │ ├── evmos.svg │ │ ├── fina.png │ │ ├── fina.webp │ │ ├── flix.svg │ │ ├── grav.svg │ │ ├── harbor.svg │ │ ├── huahua.svg │ │ ├── ibc-black.svg │ │ ├── inj.svg │ │ ├── ist.svg │ │ ├── jkl.svg │ │ ├── juno.svg │ │ ├── kava.svg │ │ ├── keplr.svg │ │ ├── ksm.svg │ │ ├── kuji.svg │ │ ├── leap.svg │ │ ├── luna.png │ │ ├── luna2.svg │ │ ├── lvn.svg │ │ ├── migaloo.svg │ │ ├── milktia.svg │ │ ├── mnta.svg │ │ ├── noble.svg │ │ ├── nolus.svg │ │ ├── nstk.svg │ │ ├── ntrn.svg │ │ ├── nyx.png │ │ ├── orai.svg │ │ ├── osmo.svg │ │ ├── page.png │ │ ├── pica.svg │ │ ├── pstake.svg │ │ ├── qatom.svg │ │ ├── qck.svg │ │ ├── robots.txt │ │ ├── rowan.svg │ │ ├── saga.svg │ │ ├── scrt-white.svg │ │ ├── scrt.svg │ │ ├── shd.svg │ │ ├── shill.svg │ │ ├── silk.svg │ │ ├── stars.svg │ │ ├── starshell.svg │ │ ├── statom.svg │ │ ├── stinj.svg │ │ ├── stjuno.svg │ │ ├── stkatom.svg │ │ ├── stkd-scrt.svg │ │ ├── stkdydx.svg │ │ ├── stluna.svg │ │ ├── stosmo.svg │ │ ├── stride.svg │ │ ├── sttia.svg │ │ ├── swth.svg │ │ ├── syn.png │ │ ├── umee.svg │ │ ├── usdc.svg │ │ ├── usdt.svg │ │ ├── usk.svg │ │ ├── wbnb.svg │ │ ├── wbtc.svg │ │ ├── weth.svg │ │ ├── wsteth.svg │ │ ├── xprt.svg │ │ └── xrp.svg ├── robots.txt └── sitemap.xml ├── src ├── App.tsx ├── assets │ └── scss │ │ ├── components │ │ ├── _fonts.scss │ │ └── fonts │ │ │ └── _runddisplay.scss │ │ └── index.scss ├── components │ ├── BalanceUI.tsx │ ├── FeeGrant │ │ ├── FeeGrant.tsx │ │ └── components │ │ │ └── ActionableStatus.tsx │ ├── FeedbackButton.tsx │ ├── FloatingCTAButton.scss │ ├── FloatingCTAButton.tsx │ ├── Footer.tsx │ ├── Header.tsx │ ├── Navigation.tsx │ ├── PercentagePicker.tsx │ ├── Settings │ │ └── Settings.tsx │ ├── Title.tsx │ ├── UI │ │ ├── Badge │ │ │ └── Badge.tsx │ │ ├── Button │ │ │ └── Button.tsx │ │ └── Modal │ │ │ └── Modal.tsx │ └── Wallet │ │ ├── ManageBalances │ │ ├── Balances.scss │ │ └── ManageBalances.tsx │ │ ├── StatusDot.tsx │ │ └── Wallet.tsx ├── context │ ├── APIContext.tsx │ ├── ConnectWalletModal.tsx │ └── ThemeContext.tsx ├── custom.d.ts ├── hooks │ └── useClickOutside.tsx ├── layouts │ └── DefaultLayout.tsx ├── pages │ ├── analytics │ │ ├── Analytics.tsx │ │ └── components │ │ │ ├── AccountsChart.tsx │ │ │ ├── ContractsChart.tsx │ │ │ ├── RelayerChartTotal.tsx │ │ │ ├── RelayerChartWithChainSlider.tsx │ │ │ ├── RelayerChartWithDateSlider.tsx │ │ │ ├── RelayerChartWithProviderSlider.tsx │ │ │ ├── TransactionsChart.tsx │ │ │ ├── UnbondingsChart.tsx │ │ │ ├── ValidatorsChart.tsx │ │ │ └── WeeklyContractsChart.tsx │ ├── apps │ │ ├── Apps.tsx │ │ └── components │ │ │ ├── FilterTag.tsx │ │ │ ├── SkeletonLoaders │ │ │ ├── SkeletonLoader.tsx │ │ │ └── SkeletonLoaders.tsx │ │ │ └── tile │ │ │ ├── AppTile.tsx │ │ │ └── Tag.tsx │ ├── bridge │ │ ├── Bridge.tsx │ │ ├── SquidModal.tsx │ │ ├── SwingModal.tsx │ │ ├── SwingModal_Dark.scss │ │ └── SwingModal_Light.scss │ ├── dashboard │ │ ├── Dashboard.tsx │ │ └── components │ │ │ ├── CurrentPrice.tsx │ │ │ ├── HexTile.tsx │ │ │ ├── MiniTile.tsx │ │ │ ├── PriceVolTVLChart │ │ │ ├── PriceVolumeTVL.tsx │ │ │ └── components │ │ │ │ ├── RangeSwitch.tsx │ │ │ │ └── TypeSwitch.tsx │ │ │ ├── QuadTile.tsx │ │ │ ├── SocialMedia.tsx │ │ │ └── StakingChart.tsx │ ├── get-scrt │ │ └── GetScrt.tsx │ ├── ibc │ │ ├── Ibc.tsx │ │ └── components │ │ │ ├── AddressInfo.tsx │ │ │ ├── BridgingFees.tsx │ │ │ ├── IbcForm.tsx │ │ │ ├── IbcSelect.tsx │ │ │ └── ibcSchema.ts │ ├── portfolio │ │ ├── Portfolio.tsx │ │ └── components │ │ │ ├── AddressQR.tsx │ │ │ ├── BalanceChart.tsx │ │ │ └── BalanceItem.tsx │ ├── powertools │ │ ├── Powertools.tsx │ │ └── components │ │ │ ├── ApiStatusIcon.tsx │ │ │ ├── Message.tsx │ │ │ └── Messages.tsx │ ├── send │ │ ├── Send.tsx │ │ ├── components │ │ │ └── SendForm.tsx │ │ └── sendSchema.ts │ ├── staking │ │ ├── Staking.tsx │ │ └── components │ │ │ ├── ClaimRewardsModal.tsx │ │ │ ├── ManageAutoRestakeModal.tsx │ │ │ ├── MyValidatorsItem.tsx │ │ │ ├── NoScrtWarning.tsx │ │ │ ├── RestakeValidatorItem.tsx │ │ │ ├── StakingStats │ │ │ ├── AvailableBalance.tsx │ │ │ ├── ClaimableRewards.tsx │ │ │ ├── StakingAmount.tsx │ │ │ └── StakingStats.tsx │ │ │ ├── ValidatorItem.tsx │ │ │ ├── ValidatorModal.tsx │ │ │ └── validatorModalComponents │ │ │ ├── RedelegateForm.tsx │ │ │ ├── StakingForm.tsx │ │ │ └── UndelegateForm.tsx │ └── wrap │ │ ├── Wrap.tsx │ │ └── components │ │ ├── FeeGrantInfoModal.tsx │ │ ├── SCRTUnwrapWarning.tsx │ │ ├── WrapForm.tsx │ │ └── wrap.scss ├── services │ ├── ibc.service.ts │ ├── notification.service.ts │ ├── send.service.ts │ ├── staking.service.ts │ ├── wallet.service.ts │ └── wrap.service.ts ├── store │ ├── TokenPrices.ts │ ├── UserPreferences.ts │ └── secretNetworkClient.ts ├── types │ ├── ApiStatus.ts │ ├── Currency.ts │ ├── FeeGrantStatus.ts │ ├── GetBalanceError.ts │ ├── IbcMode.ts │ ├── MessageType.ts │ ├── NotificationType.ts │ ├── Nullable.ts │ ├── StakingView.ts │ ├── Theme.ts │ ├── TokenBalances.ts │ ├── Validator.ts │ ├── ValidatorRestakeStatus.ts │ ├── WalletAPIType.ts │ └── WrappingMode.ts └── utils │ ├── commons.ts │ ├── config.ts │ ├── env.d.ts │ ├── tokens.ts │ ├── useHoverOutside.ts │ └── vite-env.d.ts ├── tailwind.config.ts ├── tsconfig.json ├── vite-plugin-whip-003.ts └── vite.config.ts /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | build 4 | .vscode/launch.json 5 | .DS_Store -------------------------------------------------------------------------------- /.github/workflows/docker-image.yml: -------------------------------------------------------------------------------- 1 | name: Docker Image CI 2 | 3 | permissions: 4 | contents: read 5 | 6 | on: 7 | push: 8 | branches: ["master"] 9 | pull_request: 10 | branches: ["master"] 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - uses: actions/checkout@v3 18 | - name: Build the Docker image 19 | run: docker build . --file Dockerfile --tag my-image-name:$(date +%s) 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # General 2 | dist 3 | build 4 | .vscode/launch.json 5 | 6 | # Dependency directories 7 | node_modules/ 8 | jspm_packages/ 9 | 10 | # Optional npm cache directory 11 | .npm 12 | 13 | # dotenv environment variable files 14 | .env 15 | .env.test 16 | 17 | # OS generated files 18 | .DS_Store* 19 | ehthumbs.db 20 | Icon? 21 | Thumbs.db -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npx lint-staged 5 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "arrowParens": "always", 4 | "singleQuote": true, 5 | "trailingComma": "none", 6 | "endOfLine": "auto", 7 | "printWidth": 120, 8 | "useTabs": false, 9 | "tabWidth": 2 10 | } 11 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:20-alpine 2 | WORKDIR /app 3 | COPY package.json . 4 | COPY package-lock.json . 5 | RUN npm install 6 | COPY . . 7 | EXPOSE 3000 8 | CMD ["npm", "start"] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 SCRT Labs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Secret Network Banner](banner.png) 2 | 3 | # Secret Dashboard 4 | 5 | Secret Dashboard is an entry point for new users into Secret Network. Features include a Dashboard UI for Secret Network data, IBC Transfer to and from Secret, a Wrap/Unwrap interface, a list of all active Secret dApps, a link collection to useful secret tools and more. 6 | 7 | ## System Requirements 8 | 9 | - [Node.js 20 LTS](https://nodejs.org/) 10 | 11 | ## Setup 12 | 13 | After verifying the system requirements you should be able to run a few commands to get everything set up: 14 | 15 | ``` 16 | git clone https://github.com/scrtlabs/dash.scrt.network.git 17 | cd dash.scrt.network 18 | npm install 19 | ``` 20 | 21 | ## Running the app 22 | 23 | ### The good ol' classic way 24 | 25 | To get the app up and running, run: 26 | 27 | ``` 28 | npm run start 29 | ``` 30 | 31 | The App runs on port 3000. 32 | 33 | ### Docker 34 | 35 | To get the app up and running inside Docker, run: 36 | 37 | ``` 38 | docker compose up 39 | ``` 40 | 41 | The App runs on port 3000. For further information check the `Dockerfile` and the `docker-compose.yml`. 42 | 43 | ## License 44 | 45 | Developed by [Secret Saturn](https://x.com/Secret_Saturn_) and [Secret Jupiter](https://x.com/secretjupiter_) 46 | 47 | Licensed under the [MIT license](https://github.com/scrtlabs/dash.scrt.network/blob/master/LICENSE.md) 48 | -------------------------------------------------------------------------------- /banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scrtlabs/dash.scrt.network/c828f76d2aeece4190dbc47606a2779a4d05e998/banner.png -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.8" 2 | services: 3 | dashboard_react_app: 4 | container_name: dashboard_frontend2 5 | build: ./ 6 | ports: 7 | - "3000:3000" 8 | stdin_open: true 9 | tty: true 10 | environment: 11 | - CHOKIDAR_USEPOLLING=true 12 | -------------------------------------------------------------------------------- /example.env: -------------------------------------------------------------------------------- 1 | # Mixpanel 2 | VITE_MIXPANEL_ENABLED=false 3 | VITE_MIXPANEL_PROJECT_TOKEN= 4 | 5 | # Overrides debug mode in user settings 6 | VITE_DEBUG_MODE=false 7 | 8 | 9 | # Transak 10 | TRANSAK_API_KEY= -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Secret Dashboard 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 18 | 19 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "secret_dashboard", 3 | "description": "An entry point for new users into Secret Network", 4 | "version": "1.0.0", 5 | "scripts": { 6 | "start": "vite --host", 7 | "dev": "vite --host", 8 | "build": "tsc && vite build", 9 | "preview": "vite preview", 10 | "prepare": "husky install" 11 | }, 12 | "dependencies": { 13 | "@axelar-network/axelarjs-sdk": "^0.17.2", 14 | "@buf/evmos_evmos.bufbuild_es": "^2.2.3-20240408103001-f65bdd0ffeff.1", 15 | "@cosmjs/stargate": "^0.33.0", 16 | "@emotion/styled": "^11.14.0", 17 | "@evmos/proto": "^0.2.1", 18 | "@evmos/transactions": "^0.3.2", 19 | "@floating-ui/react": "^0.27.5", 20 | "@fortawesome/free-brands-svg-icons": "^6.7.2", 21 | "@fortawesome/free-solid-svg-icons": "^6.7.2", 22 | "@fortawesome/react-fontawesome": "^0.2.2", 23 | "@mui/material": "5.16.7", 24 | "@shadeprotocol/shadejs": "^1.7.0", 25 | "@skip-go/client": "^0.16.17", 26 | "@swing.xyz/ui": "^0.67.5", 27 | "@vitejs/plugin-react": "^4.3.4", 28 | "autoprefixer": "^10.4.21", 29 | "bignumber.js": "^9.1.2", 30 | "chart.js": "^4.4.8", 31 | "formik": "^2.4.6", 32 | "long": "^5.3.1", 33 | "mixpanel-browser": "^2.61.1", 34 | "query-string": "^9.1.1", 35 | "react": "18.3.1", 36 | "react-chartjs-2": "^5.3.0", 37 | "react-copy-to-clipboard": "5.1.0", 38 | "react-dom": "18.3.1", 39 | "react-helmet-async": "^1.3.0", 40 | "react-hot-toast": "^2.5.1", 41 | "react-qrcode-logo": "^3.0.0", 42 | "react-router-dom": "^6.28.1", 43 | "react-select": "^5.9.0", 44 | "secretjs": "1.15.1", 45 | "tailwindcss": "^3.4.17", 46 | "vite": "5.4.19", 47 | "vite-tsconfig-paths": "^4.3.2", 48 | "yup": "^1.4.0", 49 | "zustand": "^4.4.6" 50 | }, 51 | "devDependencies": { 52 | "@keplr-wallet/types": "^0.12.196", 53 | "@types/mixpanel-browser": "^2.51.0", 54 | "@types/react": "^18.3.18", 55 | "@types/react-copy-to-clipboard": "5.0.7", 56 | "@types/react-dom": "^18.3.5", 57 | "husky": "^9.1.7", 58 | "lint-staged": "^15.4.3", 59 | "prettier": "3.5.3", 60 | "typescript": "^5.6.3" 61 | }, 62 | "overrides": { 63 | "protobufjs": "^7.4.0", 64 | "elliptic": "6.6.1", 65 | "axios": "1.8.2", 66 | "esbuild": "0.25.1", 67 | "ws": "8.18.1", 68 | "secretjs": "1.15.1", 69 | "@babel/runtime": "7.26.10", 70 | "vite": "5.4.19" 71 | }, 72 | "private": true, 73 | "lint-staged": { 74 | "**/*": "prettier --write --ignore-unknown" 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /public/PriceIdsString.txt: -------------------------------------------------------------------------------- 1 | secret,akash-network,,eris-staked-kuji,eris-amplified-luna,eris-amplified-whale,andromeda-2,archway,cosmos,,,agoric,,cheqd-network,comdex,composite,cosmos,polkadot,sentinel,dydx,dymension,eclipse-fi,omniflix-network,graviton,harbor-2,chihuahua-token,injective-protocol,inter-stable-token,jackal-protocol,juno-network,kava,kujira,kusama,nolus,unstake-fi,neutron-3,nym,terra-luna-2,levana-protocol,milkyway-staked-tia,mantadao,oraichain-token,osmosis,page,picasso,pstake-finance,qatom,quicksilver,kujira,usd-coin,saga-2,stargaze,stride-staked-atom,stride-staked-injective,stride-staked-juno,stkatom,,stride-staked-luna,stride-staked-osmo,stride,stride-staked-tia,switcheo,,celestia,umee,tether,bitcoin,white-whale,wrapped-steth,persistence,alter,,,amberdao,shade-protocol,fina,shade-protocol,,sienna,silk-bcec1136-561c-4706-a42c-8b67d0d7f7d2,stkd-scrt,usd-coin,axelar,ethereum,bridged-wrapped-steth-axelar,bitcoin,binancecoin,busd,dai,chainlink,uniswap,tether,frax -------------------------------------------------------------------------------- /public/_headers: -------------------------------------------------------------------------------- 1 | /* 2 | X-Frame-Options: SAMEORIGIN 3 | Content-Security-Policy: frame-ancestors 'self' -------------------------------------------------------------------------------- /public/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /public/fonts/runddisplay/runddisplay-black.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scrtlabs/dash.scrt.network/c828f76d2aeece4190dbc47606a2779a4d05e998/public/fonts/runddisplay/runddisplay-black.woff -------------------------------------------------------------------------------- /public/fonts/runddisplay/runddisplay-bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scrtlabs/dash.scrt.network/c828f76d2aeece4190dbc47606a2779a4d05e998/public/fonts/runddisplay/runddisplay-bold.woff -------------------------------------------------------------------------------- /public/fonts/runddisplay/runddisplay-extrabold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scrtlabs/dash.scrt.network/c828f76d2aeece4190dbc47606a2779a4d05e998/public/fonts/runddisplay/runddisplay-extrabold.woff -------------------------------------------------------------------------------- /public/fonts/runddisplay/runddisplay-light.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scrtlabs/dash.scrt.network/c828f76d2aeece4190dbc47606a2779a4d05e998/public/fonts/runddisplay/runddisplay-light.woff -------------------------------------------------------------------------------- /public/fonts/runddisplay/runddisplay-medium.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scrtlabs/dash.scrt.network/c828f76d2aeece4190dbc47606a2779a4d05e998/public/fonts/runddisplay/runddisplay-medium.woff -------------------------------------------------------------------------------- /public/fonts/runddisplay/runddisplay-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scrtlabs/dash.scrt.network/c828f76d2aeece4190dbc47606a2779a4d05e998/public/fonts/runddisplay/runddisplay-regular.woff -------------------------------------------------------------------------------- /public/fonts/runddisplay/runddisplay-semibold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scrtlabs/dash.scrt.network/c828f76d2aeece4190dbc47606a2779a4d05e998/public/fonts/runddisplay/runddisplay-semibold.woff -------------------------------------------------------------------------------- /public/fonts/runddisplay/runddisplay-thin.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scrtlabs/dash.scrt.network/c828f76d2aeece4190dbc47606a2779a4d05e998/public/fonts/runddisplay/runddisplay-thin.woff -------------------------------------------------------------------------------- /public/img/assets/abusd.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /public/img/assets/adai.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /public/img/assets/adm.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 9 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /public/img/assets/afrax.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 11 | 12 | 13 | 14 | 15 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /public/img/assets/akt.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /public/img/assets/alink.svg: -------------------------------------------------------------------------------- 1 | Asset 1 -------------------------------------------------------------------------------- /public/img/assets/amber.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scrtlabs/dash.scrt.network/c828f76d2aeece4190dbc47606a2779a4d05e998/public/img/assets/amber.jpg -------------------------------------------------------------------------------- /public/img/assets/ampluna.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 17 | 19 | 20 | -------------------------------------------------------------------------------- /public/img/assets/andr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scrtlabs/dash.scrt.network/c828f76d2aeece4190dbc47606a2779a4d05e998/public/img/assets/andr.png -------------------------------------------------------------------------------- /public/img/assets/archway.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /public/img/assets/axl.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 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 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /public/img/assets/binj.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scrtlabs/dash.scrt.network/c828f76d2aeece4190dbc47606a2779a4d05e998/public/img/assets/binj.png -------------------------------------------------------------------------------- /public/img/assets/bkuji.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scrtlabs/dash.scrt.network/c828f76d2aeece4190dbc47606a2779a4d05e998/public/img/assets/bkuji.png -------------------------------------------------------------------------------- /public/img/assets/bld.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /public/img/assets/bluna.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scrtlabs/dash.scrt.network/c828f76d2aeece4190dbc47606a2779a4d05e998/public/img/assets/bluna.png -------------------------------------------------------------------------------- /public/img/assets/butt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scrtlabs/dash.scrt.network/c828f76d2aeece4190dbc47606a2779a4d05e998/public/img/assets/butt.png -------------------------------------------------------------------------------- /public/img/assets/cmdx.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /public/img/assets/cmst.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /public/img/assets/coreum.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /public/img/assets/cre.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /public/img/assets/deposit.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | 10 | 15 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /public/img/assets/dot.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /public/img/assets/dshd.svg: -------------------------------------------------------------------------------- 1 | 8 | 12 | 13 | 17 | 23 | 27 | 33 | 34 | 35 | 43 | 44 | 48 | 49 | 57 | 58 | 62 | 66 | 67 | 75 | 76 | 80 | 84 | 85 | 86 | 92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /public/img/assets/dvpn.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /public/img/assets/dydx.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /public/img/assets/eclip.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /public/img/assets/evmos.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /public/img/assets/fina.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scrtlabs/dash.scrt.network/c828f76d2aeece4190dbc47606a2779a4d05e998/public/img/assets/fina.png -------------------------------------------------------------------------------- /public/img/assets/fina.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scrtlabs/dash.scrt.network/c828f76d2aeece4190dbc47606a2779a4d05e998/public/img/assets/fina.webp -------------------------------------------------------------------------------- /public/img/assets/harbor.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/img/assets/ibc-black.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /public/img/assets/inj.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /public/img/assets/juno.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /public/img/assets/kava.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /public/img/assets/keplr.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Artboard Copy 6@960w 5 | Created with Sketch. 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 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /public/img/assets/ksm.svg: -------------------------------------------------------------------------------- 1 | kusama-ksm-logo -------------------------------------------------------------------------------- /public/img/assets/leap.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /public/img/assets/luna.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scrtlabs/dash.scrt.network/c828f76d2aeece4190dbc47606a2779a4d05e998/public/img/assets/luna.png -------------------------------------------------------------------------------- /public/img/assets/luna2.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/img/assets/migaloo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /public/img/assets/mnta.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 12 | -------------------------------------------------------------------------------- /public/img/assets/nolus.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/img/assets/nstk.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/img/assets/ntrn.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /public/img/assets/nyx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scrtlabs/dash.scrt.network/c828f76d2aeece4190dbc47606a2779a4d05e998/public/img/assets/nyx.png -------------------------------------------------------------------------------- /public/img/assets/orai.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /public/img/assets/page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scrtlabs/dash.scrt.network/c828f76d2aeece4190dbc47606a2779a4d05e998/public/img/assets/page.png -------------------------------------------------------------------------------- /public/img/assets/pica.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /public/img/assets/pstake.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /public/img/assets/robots.txt: -------------------------------------------------------------------------------- 1 | User-Agent: * 2 | 3 | User-agent: facebookexternalhit 4 | Disallow: 5 | 6 | User-agent: Twitterbot 7 | Disallow: -------------------------------------------------------------------------------- /public/img/assets/rowan.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /public/img/assets/saga.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /public/img/assets/scrt-white.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /public/img/assets/scrt.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /public/img/assets/shd.svg: -------------------------------------------------------------------------------- 1 | 8 | 12 | 16 | 20 | 24 | 32 | 33 | 37 | 41 | 42 | 50 | 51 | 55 | 59 | 60 | 68 | 69 | 73 | 77 | 78 | 86 | 87 | 91 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /public/img/assets/silk.svg: -------------------------------------------------------------------------------- 1 | 8 | 12 | 16 | 24 | 25 | 29 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /public/img/assets/stars.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /public/img/assets/statom.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /public/img/assets/stinj.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /public/img/assets/stjuno.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /public/img/assets/stkdydx.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 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 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /public/img/assets/stluna.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /public/img/assets/stride.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /public/img/assets/syn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scrtlabs/dash.scrt.network/c828f76d2aeece4190dbc47606a2779a4d05e998/public/img/assets/syn.png -------------------------------------------------------------------------------- /public/img/assets/usdc.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /public/img/assets/usdt.svg: -------------------------------------------------------------------------------- 1 | tether-usdt-logo -------------------------------------------------------------------------------- /public/img/assets/usk.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/img/assets/wbnb.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 8 | 11 | 15 | 16 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /public/img/assets/wsteth.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /public/img/assets/xprt.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /public/img/assets/xrp.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Allow: / -------------------------------------------------------------------------------- /public/sitemap.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | https://dash.scrt.network/ 5 | 6 | 7 | https://dash.scrt.network/ibc 8 | 9 | 10 | https://dash.scrt.network/wrap 11 | 12 | 13 | https://dash.scrt.network/bridge 14 | 15 | 16 | https://dash.scrt.network/restake 17 | 18 | 19 | https://dash.scrt.network/apps 20 | 21 | -------------------------------------------------------------------------------- /src/assets/scss/components/_fonts.scss: -------------------------------------------------------------------------------- 1 | @use './fonts/runddisplay'; 2 | -------------------------------------------------------------------------------- /src/assets/scss/components/fonts/_runddisplay.scss: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'RundDisplay'; 3 | font-style: normal; 4 | font-weight: 400; 5 | src: url('/fonts/runddisplay/runddisplay-regular.woff') format('woff'); 6 | } 7 | 8 | @font-face { 9 | font-family: 'RundDisplay'; 10 | font-style: normal; 11 | font-weight: 100; 12 | src: url('/fonts/runddisplay/runddisplay-thin.woff') format('woff'); 13 | } 14 | 15 | @font-face { 16 | font-family: 'RundDisplay'; 17 | font-style: normal; 18 | font-weight: 300; 19 | src: url('/fonts/runddisplay/runddisplay-light.woff') format('woff'); 20 | } 21 | 22 | @font-face { 23 | font-family: 'RundDisplay'; 24 | font-style: normal; 25 | font-weight: 500; 26 | src: url('/fonts/runddisplay/runddisplay-medium.woff') format('woff'); 27 | } 28 | 29 | @font-face { 30 | font-family: 'RundDisplay'; 31 | font-style: normal; 32 | font-weight: 600; 33 | src: url('/fonts/runddisplay/runddisplay-semibold.woff') format('woff'); 34 | } 35 | 36 | @font-face { 37 | font-family: 'RundDisplay'; 38 | font-style: normal; 39 | font-weight: 700; 40 | src: url('/fonts/runddisplay/runddisplay-bold.woff') format('woff'); 41 | } 42 | 43 | @font-face { 44 | font-family: 'RundDisplay'; 45 | font-style: normal; 46 | font-weight: 800; 47 | src: url('/fonts/runddisplay/runddisplay-extrabold.woff') format('woff'); 48 | } 49 | 50 | @font-face { 51 | font-family: 'RundDisplay'; 52 | font-style: normal; 53 | font-weight: 900; 54 | src: url('/fonts/runddisplay/runddisplay-black.woff') format('woff'); 55 | } 56 | -------------------------------------------------------------------------------- /src/components/FeeGrant/FeeGrant.tsx: -------------------------------------------------------------------------------- 1 | import { faInfoCircle } from '@fortawesome/free-solid-svg-icons' 2 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' 3 | import Tooltip from '@mui/material/Tooltip' 4 | import ActionableStatus from './components/ActionableStatus' 5 | 6 | export default function FeeGrant() { 7 | return ( 8 |
9 |
10 | 15 | 16 | Fee Grant 17 | 18 | 19 | 20 | 21 | 22 |
23 |
24 | 25 |
26 |
27 | ) 28 | } 29 | -------------------------------------------------------------------------------- /src/components/FeeGrant/components/ActionableStatus.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react' 2 | import { faCheckCircle, faXmarkCircle } from '@fortawesome/free-solid-svg-icons' 3 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' 4 | import Button from 'components/UI/Button/Button' 5 | import { useSecretNetworkClientStore } from 'store/secretNetworkClient' 6 | import toast from 'react-hot-toast' 7 | import { NotificationService } from 'services/notification.service' 8 | 9 | export default function ActionableStatus() { 10 | const { feeGrantStatus, requestFeeGrant, isConnected } = useSecretNetworkClientStore() 11 | 12 | const [isLoading, setIsLoading] = useState(false) 13 | 14 | async function handleRequestFeeGrant() { 15 | setIsLoading(true) 16 | const toastId = NotificationService.notify(`Requesting Fee Grant...`, 'loading') 17 | 18 | try { 19 | const res = requestFeeGrant() 20 | 21 | res 22 | .then(() => { 23 | NotificationService.notify(`Request for Fee Grant successful`, 'success', toastId) 24 | }) 25 | .catch((error) => { 26 | NotificationService.notify(`Request for Fee Grant failed: ${error}`, 'error', toastId) 27 | }) 28 | } catch (error: any) { 29 | console.error(error) 30 | NotificationService.notify(`Request for Fee Grant failed: ${error}`, 'error', toastId) 31 | } 32 | setIsLoading(false) 33 | } 34 | 35 | if (isLoading) { 36 | return ( 37 |
38 | 44 | 45 | 50 | 51 | Requesting... 52 |
53 | ) 54 | } 55 | 56 | // Untouched 57 | if (feeGrantStatus === 'untouched') { 58 | return ( 59 | 68 | ) 69 | } 70 | 71 | // Success 72 | if (feeGrantStatus === 'success') { 73 | return ( 74 |
75 | 76 | Fee Granted 77 |
78 | ) 79 | } 80 | 81 | // Fail 82 | if (feeGrantStatus === 'fail') { 83 | return ( 84 |
85 | 86 | Request failed 87 |
88 | ) 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/components/FeedbackButton.tsx: -------------------------------------------------------------------------------- 1 | import './FloatingCTAButton.scss' 2 | 3 | interface Props { 4 | url: string 5 | } 6 | 7 | function FeedbackButton(props: Props) { 8 | return ( 9 |
10 | 15 | Feedback 16 | 17 |
18 | ) 19 | } 20 | 21 | export default FeedbackButton 22 | -------------------------------------------------------------------------------- /src/components/FloatingCTAButton.scss: -------------------------------------------------------------------------------- 1 | @-webkit-keyframes textFadeIn { 2 | from { 3 | opacity: 0; 4 | -webkit-transform: translate3d(-100%, 0, 0); 5 | transform: translate3d(-100%, 0, 0); 6 | } 7 | 8 | to { 9 | opacity: 1; 10 | -webkit-transform: translate3d(0, 0, 0); 11 | transform: translate3d(0, 0, 0); 12 | } 13 | } 14 | @keyframes textFadeIn { 15 | from { 16 | opacity: 0; 17 | -webkit-transform: translate3d(-100%, 0, 0); 18 | transform: translate3d(-100%, 0, 0); 19 | } 20 | 21 | to { 22 | opacity: 1; 23 | -webkit-transform: translate3d(0, 0, 0); 24 | transform: translate3d(0, 0, 0); 25 | } 26 | } 27 | 28 | .floatingCTAButton { 29 | position: fixed; 30 | bottom: 20px; 31 | right: 20px; 32 | z-index: 40; 33 | overflow: hidden; 34 | color: #fff; 35 | border-radius: 9999px; 36 | display: flex; 37 | flex-direction: row; 38 | align-items: center; 39 | padding: 0.5rem; 40 | 41 | .icon-circle { 42 | width: 1.75rem; 43 | height: 1.75rem; 44 | font-size: 1.5rem; 45 | margin: 0.5rem; 46 | display: inline-block; 47 | z-index: 5000 !important; 48 | transition: padding-right 350ms ease; 49 | flex: 0 1 auto; 50 | } 51 | 52 | .cta-text { 53 | flex: 0 1 auto; 54 | visibility: hidden; 55 | padding: 0; 56 | width: 0; 57 | height: 0; 58 | z-index: 1; 59 | transition: padding 350ms ease-in-out; 60 | } 61 | 62 | &:hover { 63 | .icon-circle { 64 | } 65 | .cta-text { 66 | visibility: visible; 67 | padding: 0 1.25rem 0 0.25rem; 68 | width: inherit; 69 | height: inherit; 70 | 71 | animation: textFadeIn; 72 | animation-duration: 350ms; 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/components/FloatingCTAButton.tsx: -------------------------------------------------------------------------------- 1 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' 2 | import { faComment } from '@fortawesome/free-solid-svg-icons' 3 | import './FloatingCTAButton.scss' 4 | import { Link } from 'react-router-dom' 5 | 6 | interface Props { 7 | url: string 8 | text: string 9 | } 10 | 11 | function FloatingCTAButton(props: Props) { 12 | return ( 13 | 14 |
15 | 16 | {props.text} 17 |
18 | 19 | ) 20 | } 21 | 22 | export default FloatingCTAButton 23 | -------------------------------------------------------------------------------- /src/components/Header.tsx: -------------------------------------------------------------------------------- 1 | import Title from './Title' 2 | 3 | interface Props { 4 | title: string 5 | description?: string 6 | } 7 | 8 | const Header = (props: Props) => { 9 | return ( 10 | <> 11 | {/* Title */} 12 | 13 | {/* Description */} 14 | {props.description && ( 15 | <p className="mt-4 sm:max-w-lg mx-auto mb-6 text-center text-neutral-500 dark:text-neutral-500"> 16 | {props.description} 17 | </p> 18 | )} 19 | </> 20 | ) 21 | } 22 | 23 | export default Header 24 | -------------------------------------------------------------------------------- /src/components/PercentagePicker.tsx: -------------------------------------------------------------------------------- 1 | interface Props { 2 | setAmountByPercentage: (percentage: number) => void 3 | disabled?: boolean 4 | } 5 | 6 | export default function PercentagePicker(props: Props) { 7 | return ( 8 | <div className="inline-flex rounded-full text-xs font-extrabold"> 9 | <button 10 | type="button" 11 | onClick={() => props.setAmountByPercentage(25)} 12 | className="bg-gray-300 dark:bg-neutral-800 py-1.5 px-2.5 rounded-l-lg enabled:hover:bg-gray-400 enabled:dark:hover:bg-neutral-600 transition" 13 | disabled={props.disabled} 14 | > 15 | 25% 16 | </button> 17 | <button 18 | type="button" 19 | onClick={() => props.setAmountByPercentage(50)} 20 | className="bg-gray-300 dark:bg-neutral-800 py-1.5 px-2.5 enabled:hover:bg-gray-400 enabled:dark:hover:bg-neutral-600 transition" 21 | disabled={props.disabled} 22 | > 23 | 50% 24 | </button> 25 | <button 26 | type="button" 27 | onClick={() => props.setAmountByPercentage(75)} 28 | className="bg-gray-300 dark:bg-neutral-800 py-1.5 px-2.5 enabled:hover:bg-gray-400 enabled:dark:hover:bg-neutral-600 transition" 29 | disabled={props.disabled} 30 | > 31 | 75% 32 | </button> 33 | <button 34 | type="button" 35 | onClick={() => props.setAmountByPercentage(100)} 36 | className="bg-gray-300 dark:bg-neutral-800 py-1.5 px-2.5 rounded-r-lg enabled:hover:bg-gray-400 enabled:dark:hover:bg-neutral-600 transition" 37 | disabled={props.disabled} 38 | > 39 | 100% 40 | </button> 41 | </div> 42 | ) 43 | } 44 | -------------------------------------------------------------------------------- /src/components/Title.tsx: -------------------------------------------------------------------------------- 1 | import Tooltip from '@mui/material/Tooltip' 2 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' 3 | import { faInfoCircle } from '@fortawesome/free-solid-svg-icons' 4 | 5 | interface IProps { 6 | title: string 7 | tooltip?: string 8 | className?: string 9 | } 10 | 11 | export default function Title(props: IProps) { 12 | function TitleContent() { 13 | return ( 14 | <div className={[`group text-center mb-4 max-w-6xl mx-auto`, props.className ? props.className : ''].join(' ')}> 15 | <h1 className="font-bold text-4xl inline text-transparent bg-clip-text bg-[#FF3912]">{props.title}</h1> 16 | {props.tooltip ? ( 17 | <span className="ml-2 relative bottom-1.5 text-neutral-600 dark:text-neutral-400 group-hover:text-black dark:group-hover:text-white transition-colors cursor-pointer"> 18 | <FontAwesomeIcon icon={faInfoCircle} /> 19 | </span> 20 | ) : null} 21 | </div> 22 | ) 23 | } 24 | 25 | if (props.tooltip) { 26 | return ( 27 | <Tooltip title={props.tooltip} placement="bottom" arrow> 28 | <span> 29 | <TitleContent /> 30 | </span> 31 | </Tooltip> 32 | ) 33 | } else { 34 | return <TitleContent /> 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/components/UI/Badge/Badge.tsx: -------------------------------------------------------------------------------- 1 | type Color = 'primary' | 'dark' | 'red' | 'green' | 'yellow' | 'indigo' | 'purple' | 'pink' 2 | 3 | type Size = 'default' | 'large' 4 | 5 | type Props = { 6 | color?: Color 7 | size?: Size 8 | bordered?: boolean 9 | pill?: boolean 10 | children: React.ReactNode 11 | } 12 | 13 | const colorClasses: Record<Color, string> = { 14 | primary: 'bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-300', 15 | dark: 'bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-300', 16 | red: 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-300', 17 | green: 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-300', 18 | yellow: 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-300', 19 | indigo: 'bg-indigo-100 text-indigo-800 dark:bg-indigo-900 dark:text-indigo-300"', 20 | purple: 'bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-300', 21 | pink: 'bg-pink-100 text-pink-800 dark:bg-pink-900 dark:text-pink-300' 22 | } 23 | 24 | const borderClasses: Record<Color, string> = { 25 | primary: 'border border-blue-400 dark:bg-gray-700 dark:text-blue-400', 26 | dark: 'border border-dark-500 dark:bg-gray-700 dark:text-gray-400', 27 | red: 'border border-red-400 dark:bg-gray-700 dark:text-red-400', 28 | green: 'border border-green-400 dark:bg-gray-700 dark:text-green-400', 29 | yellow: 'border border-yellow-300 dark:bg-gray-700 dark:text-yellow-300', 30 | indigo: 'border border-indigo-400 dark:bg-gray-700 dark:text-indigo-400', 31 | purple: 'border border-purple-400 dark:bg-gray-700 dark:text-purple-400', 32 | pink: 'border border-pink-400 dark:bg-gray-700 dark:text-pink-400' 33 | } 34 | 35 | const sizeClasses: Record<Size, string> = { 36 | default: 'text-xs', 37 | large: 'text-sm' 38 | } 39 | 40 | function Badge(props: Props) { 41 | return ( 42 | <span 43 | className={[ 44 | colorClasses[props.color || 'primary'], 45 | sizeClasses[props.size || 'default'], 46 | props.bordered && borderClasses[props.color || 'primary'], 47 | props.pill ? 'rounded-full' : 'rounded', 48 | 'inline-block font-medium px-2.5 py-0.5 rounded dark:bg-blue-900 dark:text-blue-300' 49 | ].join(' ')} 50 | > 51 | {props.children} 52 | </span> 53 | ) 54 | } 55 | 56 | export default Badge 57 | -------------------------------------------------------------------------------- /src/components/UI/Button/Button.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | type Type = 'button' | 'submit' | 'reset' 4 | type Size = 'default' | 'large' | 'small' 5 | type Color = 'primary' | 'secondary' | 'red' | 'blue' | 'emerald' | 'yellow' 6 | 7 | const colorClasses: Record<Color, string> = { 8 | primary: 9 | 'enabled:bg-sky-500 dark:enabled:bg-sky-600 enabled:hover:bg-sky-600 dark:enabled:hover:bg-sky-700 enabled:text-white disabled:bg-neutral-600 disabled:text-neutral-400 enabled:ring-sky-500/40 dark:enabled:ring-sky-600/40', 10 | secondary: 11 | 'enabled:bg-gray-500 dark:enabled:bg-gray-600 enabled:hover:bg-gray-600 dark:enabled:hover:bg-gray-700 enabled:text-white disabled:bg-neutral-600 disabled:text-neutral-400 enabled:ring-gray-500/40 dark:enabled:ring-gray-600/40', 12 | red: 'enabled:bg-red-500 dark:enabled:bg-red-600 enabled:hover:bg-red-600 dark:enabled:hover:bg-red-700 enabled:text-white disabled:bg-neutral-600 disabled:text-neutral-400 enabled:ring-red-500/40 dark:enabled:ring-red-600/40', 13 | blue: 'enabled:bg-blue-500 dark:enabled:bg-blue-600 enabled:hover:bg-blue-600 dark:enabled:hover:bg-blue-700 enabled:text-white disabled:bg-neutral-600 disabled:text-neutral-400 enabled:ring-blue-500/40 dark:enabled:ring-blue-600/40', 14 | emerald: 15 | 'enabled:bg-emerald-500 dark:enabled:bg-emerald-600 enabled:hover:bg-emerald-600 dark:enabled:hover:bg-emerald-700 enabled:text-white disabled:bg-neutral-600 disabled:text-neutral-400 enabled:ring-emerald-500/40 dark:enabled:ring-emerald-600/40', 16 | yellow: 17 | 'enabled:bg-yellow-500 dark:enabled:bg-yellow-600 enabled:hover:bg-yellow-600 dark:enabled:hover:bg-yellow-700 enabled:text-black disabled:bg-neutral-600 disabled:text-neutral-400 enabled:ring-yellow-500/40 dark:enabled:ring-yellow-600/40' 18 | } 19 | 20 | const sizeClasses: Record<Size, string> = { 21 | default: 'py-2 px-4 text-sm', 22 | large: 'py-3 px-4 text-sm', 23 | small: 'py-1.5 px-2 text-xs' 24 | } 25 | 26 | interface Props { 27 | children?: React.ReactNode 28 | type?: Type 29 | onClick?: (event: React.MouseEvent<HTMLButtonElement>) => void 30 | color?: Color 31 | size?: Size 32 | disabled?: boolean 33 | className?: string 34 | } 35 | 36 | export default function Button(props: Props) { 37 | return ( 38 | <button 39 | disabled={props.disabled || false} 40 | className={`focus:outline-none focus-visible:ring-4 text-center font-bold rounded transition-colors 41 | ${colorClasses[props.color || 'primary']} 42 | ${sizeClasses[props.size || 'default']} 43 | ${props.className || ''} 44 | `} 45 | type={props.type || 'button'} 46 | onClick={props.onClick} 47 | > 48 | <>{props.children}</> 49 | </button> 50 | ) 51 | } 52 | -------------------------------------------------------------------------------- /src/components/Wallet/ManageBalances/Balances.scss: -------------------------------------------------------------------------------- 1 | .balance-item div { 2 | &:first-of-type { 3 | @apply rounded-t-lg; 4 | } 5 | &:last-of-type { 6 | @apply rounded-b-lg; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/components/Wallet/ManageBalances/ManageBalances.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react' 2 | import { Token } from 'utils/config' 3 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' 4 | import { faMagnifyingGlass } from '@fortawesome/free-solid-svg-icons' 5 | import './Balances.scss' 6 | import { SendService } from 'services/send.service' 7 | import BalanceItem from 'pages/portfolio/components/BalanceItem' 8 | 9 | export const ManageBalances = () => { 10 | const [searchQuery, setSearchQuery] = useState<string>('') 11 | 12 | const tokens = SendService.getSupportedTokens() 13 | 14 | const displayedAssets = tokens.filter( 15 | (token: Token) => 16 | token.name?.toLowerCase().includes(searchQuery?.toLowerCase()) || 17 | ('s' + token.name)?.toLowerCase().includes(searchQuery?.toLowerCase()) || 18 | token.description?.toLowerCase().includes(searchQuery?.toLowerCase()) 19 | ) 20 | 21 | return ( 22 | <> 23 | {/* All Balances */} 24 | <div className="flex flex-col gap-4 sm:flex-row items-center mb-4"> 25 | {/* Search */} 26 | <div className="flex-1 w-full xs:w-auto"> 27 | <div className="relative w-full"> 28 | <div className="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none"> 29 | <FontAwesomeIcon icon={faMagnifyingGlass} className="" /> 30 | </div> 31 | <input 32 | value={searchQuery} 33 | onChange={(e) => setSearchQuery(e.target.value)} 34 | type="text" 35 | id="search" 36 | className="block w-full sm:w-72 p-2.5 pl-10 text-sm rounded-lg text-neutral-800 dark:text-white bg-white dark:bg-neutral-800 placeholder-neutral-600 dark:placeholder-neutral-400 border border-neutral-300 dark:border-neutral-700 focus:outline-none focus:ring-2 focus:ring-cyan-500 dark:focus:ring-cyan-500" 37 | placeholder="Search Asset" 38 | /> 39 | </div> 40 | </div> 41 | </div> 42 | 43 | <div className="balance-item flex flex-col"> 44 | {tokens 45 | ? displayedAssets.map((token: Token, i: number) => <BalanceItem token={token} key={i} />) 46 | : [...Array(10)].map((_, index) => <BalanceItem key={index} />)} 47 | </div> 48 | </> 49 | ) 50 | } 51 | -------------------------------------------------------------------------------- /src/components/Wallet/StatusDot.tsx: -------------------------------------------------------------------------------- 1 | type Status = 'connected' | 'disconnected' 2 | 3 | type Props = { 4 | status: Status 5 | } 6 | 7 | function StatusDot(props: Props) { 8 | if (props.status === 'connected') { 9 | return ( 10 | <span className="flex relative h-2 w-2"> 11 | <span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-1/2"></span> 12 | <span className="relative inline-flex rounded-full size-2 h-2 w-2 bg-emerald-500"></span> 13 | </span> 14 | ) 15 | } 16 | 17 | return <span className="relative inline-flex rounded-full h-2 w-2 bg-red-500"></span> 18 | } 19 | 20 | export default StatusDot 21 | -------------------------------------------------------------------------------- /src/context/ThemeContext.tsx: -------------------------------------------------------------------------------- 1 | import { createContext, useEffect, useState } from 'react' 2 | import { useUserPreferencesStore } from 'store/UserPreferences' 3 | import { Theme } from 'types/Theme' 4 | 5 | const ThemeContext = createContext(null) 6 | 7 | const ThemeContextProvider = ({ children }: any) => { 8 | // the value that will be given to the context 9 | const { theme } = useUserPreferencesStore() 10 | 11 | function setThemeClassToBody(theme: Theme) { 12 | if (theme === 'light') { 13 | document.body.classList.remove('dark') 14 | } else if (theme === 'dark') { 15 | document.body.classList.add('dark') 16 | } 17 | } 18 | 19 | // always save option to localStorage 20 | useEffect(() => { 21 | if (theme !== null) { 22 | setThemeClassToBody(theme) 23 | } 24 | }, [theme]) 25 | 26 | return <ThemeContext.Provider value={{ theme }}>{children}</ThemeContext.Provider> 27 | } 28 | 29 | export { ThemeContext, ThemeContextProvider } 30 | -------------------------------------------------------------------------------- /src/custom.d.ts: -------------------------------------------------------------------------------- 1 | interface Window { 2 | leap?: any 3 | keplr?: any 4 | wallet?: any 5 | } 6 | -------------------------------------------------------------------------------- /src/hooks/useClickOutside.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react' 2 | 3 | const useClickOutside = (ref: any, handler: any) => { 4 | useEffect(() => { 5 | const listener = (event: any) => { 6 | if (!ref.current || ref.current.contains(event.target)) { 7 | return 8 | } 9 | handler(event) 10 | } 11 | document.addEventListener('mousedown', listener) 12 | document.addEventListener('touchstart', listener) 13 | return () => { 14 | document.removeEventListener('mousedown', listener) 15 | document.removeEventListener('touchstart', listener) 16 | } 17 | }, [ref, handler]) 18 | } 19 | 20 | export default useClickOutside 21 | -------------------------------------------------------------------------------- /src/pages/apps/components/FilterTag.tsx: -------------------------------------------------------------------------------- 1 | interface Props { 2 | name: string 3 | toggleTagFilter: any 4 | isTagInFilterList: (name: string) => string 5 | } 6 | 7 | const FilterTag = (props: Props) => { 8 | return ( 9 | <button 10 | onClick={() => props.toggleTagFilter(props.name)} 11 | className={ 12 | 'inline-block text-sm px-1.5 py-0.5 rounded-md overflow-hidden transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-cyan-500 dark:focus-visible:ring-cyan-500' + 13 | (props.isTagInFilterList(props.name) 14 | ? ' text-white dark:text-white font-semibold bg-gradient-to-r from-cyan-600 to-purple-600 hover:from-cyan-500 hover:to-purple-500' 15 | : ' bg-white dark:bg-neutral-800 hover:bg-gray-200 dark:hover:bg-neutral-700 font-medium') 16 | } 17 | > 18 | {props.name} 19 | </button> 20 | ) 21 | } 22 | 23 | export default FilterTag 24 | -------------------------------------------------------------------------------- /src/pages/apps/components/SkeletonLoaders/SkeletonLoader.tsx: -------------------------------------------------------------------------------- 1 | export default function SkeletonLoader() { 2 | return ( 3 | <div className="animate-pulse col-span-12 sm:col-span-6 lg:col-span-6 xl:col-span-4 2xl:col-span-3"> 4 | <div className="h-72 bg-white dark:bg-neutral-800 rounded-xl"></div> 5 | </div> 6 | ) 7 | } 8 | -------------------------------------------------------------------------------- /src/pages/apps/components/SkeletonLoaders/SkeletonLoaders.tsx: -------------------------------------------------------------------------------- 1 | import SkeletonLoader from './SkeletonLoader' 2 | 3 | interface Props { 4 | amount?: number 5 | } 6 | 7 | /** 8 | * Render multiple SkeletonLoader components based on the specified amount. 9 | * 10 | * @param {number} [amount=20] - The number of SkeletonLoaders to render. Default is 20. 11 | * 12 | * @remarks 13 | * This function is intended for use in the /apps context. 14 | * 15 | * @example 16 | * ```tsx 17 | * <SkeletonLoaders amount={10} /> 18 | * ``` 19 | */ 20 | export default function SkeletonLoaders({ amount = 20 }: Props) { 21 | return ( 22 | <> 23 | {Array.from({ length: amount }).map((_, index) => ( 24 | <SkeletonLoader key={index} /> 25 | ))} 26 | </> 27 | ) 28 | } 29 | -------------------------------------------------------------------------------- /src/pages/apps/components/tile/AppTile.tsx: -------------------------------------------------------------------------------- 1 | import Tag from './Tag' 2 | import mixpanel from 'mixpanel-browser' 3 | 4 | interface Props { 5 | name: string 6 | description: string 7 | image?: string 8 | tags?: string[] 9 | url?: string 10 | } 11 | 12 | const AppTile = (props: Props) => { 13 | const handleClick = () => { 14 | if (import.meta.env.VITE_MIXPANEL_ENABLED === 'true') { 15 | mixpanel.init(import.meta.env.VITE_MIXPANEL_PROJECT_TOKEN, { 16 | debug: false 17 | }) 18 | mixpanel.identify('Dashboard-App') 19 | mixpanel.track('dApp opened', { 20 | 'dApp name': props.name 21 | }) 22 | } 23 | } 24 | 25 | return ( 26 | <a 27 | href={props.url || '#'} 28 | target={props.url ? '_blank' : '_self'} 29 | onClick={handleClick} 30 | className="group col-span-12 sm:col-span-6 lg:col-span-6 xl:col-span-4 2xl:col-span-3" 31 | > 32 | <div className="bg-white transition-all group-hover:relative bottom-0 group-hover:bottom-1 group-hover:bg-white/95 dark:bg-neutral-800 group-hover:dark:bg-neutral-750 p-4 flex flex-col h-full rounded-xl overflow-hidden text-center sm:text-left"> 33 | {/* Image */} 34 | {props.image && ( 35 | <div className="flex-initial"> 36 | <img 37 | src={`https://raw.githubusercontent.com/SecretFoundation/DappRegistry/main/${props.image}`} 38 | alt={`${props.name} logo`} 39 | className="w-16 h-16 rounded-xl block mb-4 bg-neutral-100 dark:bg-neutral-900 flex-initial mx-auto sm:mx-0" 40 | /> 41 | </div> 42 | )} 43 | {/* Name */} 44 | <div className="text-xl font-semibold flex-initial mb-1">{props.name}</div> 45 | 46 | {/* Description */} 47 | <div className="text-neutral-400 flex-1">{props.description}</div> 48 | 49 | {/* Tags */} 50 | {props.tags?.length! > 0 && ( 51 | <div className="flex flex-wrap gap-2 mt-4 flex-initial"> 52 | {props.tags?.map((tag) => <Tag key={tag} name={tag} />)} 53 | </div> 54 | )} 55 | </div> 56 | </a> 57 | ) 58 | } 59 | 60 | export default AppTile 61 | -------------------------------------------------------------------------------- /src/pages/apps/components/tile/Tag.tsx: -------------------------------------------------------------------------------- 1 | import Badge from 'components/UI/Badge/Badge' 2 | 3 | interface Props { 4 | name: string 5 | } 6 | const Tag = (props: Props) => { 7 | return ( 8 | <> 9 | <Badge color="dark">{props.name}</Badge> 10 | </> 11 | ) 12 | } 13 | 14 | export default Tag 15 | -------------------------------------------------------------------------------- /src/pages/bridge/SwingModal.tsx: -------------------------------------------------------------------------------- 1 | import { faXmark } from '@fortawesome/free-solid-svg-icons' 2 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' 3 | import { Theme } from 'types/Theme' 4 | import { Swap } from '@swing.xyz/ui' 5 | import { useEffect } from 'react' 6 | 7 | interface Props { 8 | open: boolean 9 | onClose: any 10 | theme: Theme 11 | secretAddress: string 12 | } 13 | 14 | const SwingModal = (props: Props) => { 15 | if (!props.open) return null 16 | 17 | useEffect(() => { 18 | if (props.theme === 'light') { 19 | import('./SwingModal_Light.scss') 20 | } else { 21 | import('./SwingModal_Dark.scss') 22 | } 23 | }, [props.theme]) 24 | return ( 25 | <> 26 | {/* Outer */} 27 | <div 28 | className="fixed top-0 left-0 right-0 bottom-0 bg-black/80 dark:bg-black/80 z-50 flex items-center justify-center" 29 | //onClick={props.onClose} 30 | > 31 | {/* Inner */} 32 | <div className="relative onEnter_fadeInDown h-full overflow-scroll scrollbar-hide flex items-center justify-center"> 33 | <div className="mx-auto px-4" style={{ minWidth: '500px' }}> 34 | <div 35 | className="bg-white dark:bg-neutral-900 rounded-2xl p-6 relative w-full" 36 | onClick={(e) => { 37 | e.stopPropagation() 38 | }} 39 | > 40 | {/* Close Button */} 41 | <button 42 | onClick={props.onClose} 43 | className="text-neutral-500 dark:text-neutral-500 hover:bg-neutral-200 dark:hover:bg-neutral-800 transition-colors px-1.5 py-2 rounded-lg text-xl absolute top-3 right-3 z-10" 44 | > 45 | <FontAwesomeIcon icon={faXmark} className="fa-fw" /> 46 | </button> 47 | <Swap projectId="secret-network" environment="production" title="" /> 48 | </div> 49 | </div> 50 | </div> 51 | </div> 52 | </> 53 | ) 54 | } 55 | 56 | export default SwingModal 57 | -------------------------------------------------------------------------------- /src/pages/dashboard/components/CurrentPrice.tsx: -------------------------------------------------------------------------------- 1 | import { faArrowUpRightFromSquare } from '@fortawesome/free-solid-svg-icons' 2 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' 3 | import { toCurrencyString, trackMixPanelEvent } from 'utils/commons' 4 | import { Link } from 'react-router-dom' 5 | import { useUserPreferencesStore } from 'store/UserPreferences' 6 | 7 | interface Props { 8 | price?: number 9 | } 10 | 11 | export default function CurrentPrice(props: Props) { 12 | const { currency } = useUserPreferencesStore() 13 | return ( 14 | <div className="rounded-xl bg-white border border-neutral-200 dark:border-neutral-700 dark:bg-neutral-800 h-full flex items-center px-4 py-2"> 15 | <div className="flex-1"> 16 | <div className="text-center inline-block"> 17 | <div className="text-neutral-400 dark:text-neutral-500 text-sm font-semibold mb-0.5">Current Price</div> 18 | <div className="text-xl"> 19 | {props.price ? ( 20 | <>{toCurrencyString(props.price, currency)}</> 21 | ) : ( 22 | <div className="animate-pulse bg-neutral-300/40 dark:bg-neutral-700/40 rounded col-span-2 w-20 h-8 mx-auto"></div> 23 | )} 24 | </div> 25 | </div> 26 | </div> 27 | <div className="flex-1 text-right"> 28 | <Link 29 | to="/get-scrt" 30 | className="w-full md:w-auto md:px-4 inline-block bg-cyan-500 dark:bg-cyan-500/20 text-white dark:text-cyan-200 dark:hover:text-cyan-100 hover:bg-cyan-400 dark:hover:bg-cyan-500/50 text-center transition-colors py-2.5 rounded-xl font-semibold text-sm" 31 | onClick={() => { 32 | trackMixPanelEvent('Clicked buy SCRT on current price') 33 | }} 34 | > 35 | Get SCRT 36 | <FontAwesomeIcon icon={faArrowUpRightFromSquare} className="text-xs ml-2" /> 37 | </Link> 38 | </div> 39 | </div> 40 | ) 41 | } 42 | -------------------------------------------------------------------------------- /src/pages/dashboard/components/MiniTile.tsx: -------------------------------------------------------------------------------- 1 | interface Props { 2 | name: string 3 | value?: string 4 | } 5 | 6 | export default function MiniTile(props: Props) { 7 | return ( 8 | <> 9 | <div className="rounded-xl bg-white border border-neutral-200 dark:border-neutral-700 dark:bg-neutral-800 h-full flex items-center px-4 py-4"> 10 | <div className="flex-1 text-center"> 11 | <div className="text-neutral-500 dark:text-neutral-500 text-sm font-semibold mb-0.5">{props.name}</div> 12 | <div className="text-xl"> 13 | {props.value ? ( 14 | <>{props.value}</> 15 | ) : ( 16 | <div className="animate-pulse bg-neutral-300/40 dark:bg-neutral-700/40 rounded col-span-2 w-20 h-7 mx-auto"></div> 17 | )} 18 | </div> 19 | </div> 20 | </div> 21 | </> 22 | ) 23 | } 24 | -------------------------------------------------------------------------------- /src/pages/dashboard/components/PriceVolTVLChart/components/RangeSwitch.tsx: -------------------------------------------------------------------------------- 1 | import { useContext } from 'react' 2 | import { PriceVolumeHistoryContext } from '../PriceVolumeTVL' 3 | 4 | function RangeSwitch() { 5 | const { chartType, chartRange, setChartRange } = useContext(PriceVolumeHistoryContext) 6 | 7 | return ( 8 | <> 9 | {chartType !== 'TVL' && ( 10 | <> 11 | <button 12 | onClick={() => setChartRange('Day')} 13 | type="button" 14 | className={ 15 | 'py-1.5 px-3 text-xs font-semibold rounded-l-lg bg-neutral-100 dark:bg-neutral-700 ' + 16 | (chartRange === 'Day' 17 | ? ' cursor-default bg-neutral-300 dark:bg-cyan-500/20 text-black dark:text-cyan-200 font-semibold' 18 | : ' text-neutral-800 dark:text-neutral-200 hover:bg-neutral-300 dark:hover:bg-neutral-700 focus:bg-neutral-500 dark:focus:bg-neutral-500') 19 | } 20 | > 21 | Day 22 | </button> 23 | <button 24 | onClick={() => setChartRange('Month')} 25 | type="button" 26 | className={ 27 | 'py-1.5 px-3 text-xs font-semibold bg-neutral-100 dark:bg-neutral-700' + 28 | (chartRange === 'Month' 29 | ? ' cursor-default bg-neutral-300 dark:bg-cyan-500/20 text-black dark:text-cyan-200 font-semibold' 30 | : ' text-neutral-800 dark:text-neutral-200 hover:bg-neutral-300 dark:hover:bg-neutral-700 focus:bg-neutral-500 dark:focus:bg-neutral-500') 31 | } 32 | > 33 | Month 34 | </button> 35 | <button 36 | onClick={() => setChartRange('Year')} 37 | type="button" 38 | className={ 39 | 'py-1.5 px-3 text-xs font-semibold rounded-r-lg bg-neutral-100 dark:bg-neutral-700' + 40 | (chartRange === 'Year' 41 | ? ' cursor-default bg-neutral-300 dark:bg-cyan-500/20 text-black dark:text-cyan-200 font-semibold' 42 | : ' text-neutral-800 dark:text-neutral-200 hover:bg-neutral-300 dark:hover:bg-neutral-700 focus:bg-neutral-500 dark:focus:bg-neutral-500') 43 | } 44 | > 45 | Year 46 | </button> 47 | </> 48 | )} 49 | 50 | {chartType === 'TVL' && ( 51 | <> 52 | <button 53 | onClick={() => setChartRange('Year')} 54 | type="button" 55 | className={ 56 | 'py-1.5 px-3 text-xs font-semibold rounded-lg bg-neutral-100 dark:bg-neutral-900' + 57 | (chartRange === 'Year' 58 | ? ' cursor-default bg-neutral-300 dark:bg-cyan-500/20 text-black dark:text-cyan-200 font-semibold' 59 | : ' text-neutral-800 dark:text-neutral-200 hover:bg-neutral-300 dark:hover:bg-neutral-700 focus:bg-neutral-500 dark:focus:bg-neutral-500') 60 | } 61 | > 62 | Year 63 | </button> 64 | </> 65 | )} 66 | </> 67 | ) 68 | } 69 | 70 | export default RangeSwitch 71 | -------------------------------------------------------------------------------- /src/pages/dashboard/components/PriceVolTVLChart/components/TypeSwitch.tsx: -------------------------------------------------------------------------------- 1 | import { useContext } from 'react' 2 | import { PriceVolumeHistoryContext } from '../PriceVolumeTVL' 3 | 4 | function TypeSwitch() { 5 | const { chartType, setChartType } = useContext(PriceVolumeHistoryContext) 6 | 7 | return ( 8 | <> 9 | <div className="flex-initial inline-flex rounded-md shadow-sm" role="group"> 10 | <button 11 | onClick={() => setChartType('Price')} 12 | type="button" 13 | className={ 14 | 'py-1.5 px-3 text-xs font-semibold rounded-l-lg bg-neutral-100 dark:bg-neutral-700' + 15 | (chartType === 'Price' 16 | ? ' cursor-default bg-neutral-300 dark:bg-cyan-500/20 text-black dark:text-cyan-200 font-semibold' 17 | : ' text-neutral-800 dark:text-neutral-200 hover:bg-neutral-300 dark:hover:bg-neutral-700 focus:bg-neutral-500 dark:focus:bg-neutral-500') 18 | } 19 | > 20 | Price 21 | </button> 22 | <button 23 | onClick={() => setChartType('Volume')} 24 | type="button" 25 | className={ 26 | 'py-1.5 px-3 text-xs font-semibold bg-neutral-100 dark:bg-neutral-700' + 27 | (chartType === 'Volume' 28 | ? ' cursor-default bg-neutral-300 dark:bg-cyan-500/20 text-black dark:text-cyan-200 font-semibold' 29 | : ' text-neutral-800 dark:text-neutral-200 hover:bg-neutral-300 dark:hover:bg-neutral-700 focus:bg-neutral-500 dark:focus:bg-neutral-500') 30 | } 31 | > 32 | Volume 33 | </button> 34 | <button 35 | onClick={() => setChartType('TVL')} 36 | type="button" 37 | className={ 38 | 'py-1.5 px-3 text-xs font-semibold rounded-r-lg bg-neutral-100 dark:bg-neutral-700' + 39 | (chartType === 'TVL' 40 | ? ' cursor-default bg-neutral-300 dark:bg-cyan-500/20 text-black dark:text-cyan-200 font-semibold' 41 | : ' text-neutral-800 dark:text-neutral-200 hover:bg-neutral-300 dark:hover:bg-neutral-700 focus:bg-neutral-500 dark:focus:bg-neutral-500') 42 | } 43 | > 44 | TVL 45 | </button> 46 | </div> 47 | </> 48 | ) 49 | } 50 | 51 | export default TypeSwitch 52 | -------------------------------------------------------------------------------- /src/pages/dashboard/components/QuadTile.tsx: -------------------------------------------------------------------------------- 1 | interface Item { 2 | key: string 3 | value: string 4 | } 5 | 6 | interface Props { 7 | item1?: Item 8 | item2?: Item 9 | item3?: Item 10 | item4?: Item 11 | } 12 | 13 | export default function QuadTile(props: Props) { 14 | return ( 15 | <> 16 | <div className="rounded-xl bg-white border border-neutral-200 dark:border-neutral-700 dark:bg-neutral-800 p-8 h-full"> 17 | <div className="flex flex-col h-full"> 18 | <div className="flex-1 flex text-center items-center"> 19 | {/* First Item */} 20 | <div className="flex-1 h-full flex flex-col justify-center border-r border-b border-neutral-200 dark:border-neutral-700"> 21 | <div className="py-4"> 22 | <div className="text-neutral-400 dark:text-neutral-500 text-sm font-semibold mb-0.5"> 23 | {props.item1?.key} 24 | </div> 25 | <div className="text-xl"> 26 | {props.item1?.value ? ( 27 | <>{props.item1.value}</> 28 | ) : ( 29 | <div className="animate-pulse bg-neutral-300/40 dark:bg-neutral-700/40 rounded col-span-2 w-16 h-7 mx-auto"></div> 30 | )} 31 | </div> 32 | </div> 33 | </div> 34 | {/* Second Item */} 35 | <div className="flex-1 h-full flex flex-col justify-center border-b border-neutral-200 dark:border-neutral-700"> 36 | <div className="py-4"> 37 | <div className="text-neutral-400 dark:text-neutral-500 text-sm font-semibold mb-0.5"> 38 | {props.item2?.key} 39 | </div> 40 | <div className="text-xl"> 41 | {props.item2?.value ? ( 42 | <>{props.item2?.value}</> 43 | ) : ( 44 | <div className="animate-pulse bg-neutral-300/40 dark:bg-neutral-700/40 rounded col-span-2 w-16 h-7 mx-auto"></div> 45 | )} 46 | </div> 47 | </div> 48 | </div> 49 | </div> 50 | <div className="flex-1 flex text-center items-center"> 51 | {/* Third Item */} 52 | <div className="flex-1 h-full flex flex-col justify-center border-r border-neutral-200 dark:border-neutral-700"> 53 | <div className="py-4"> 54 | <div className="text-neutral-400 dark:text-neutral-500 text-sm font-semibold mb-0.5"> 55 | {props.item3?.key} 56 | </div> 57 | <div className="text-xl"> 58 | {props.item3?.value ? ( 59 | <>{props.item3.value}</> 60 | ) : ( 61 | <div className="animate-pulse bg-neutral-300/40 dark:bg-neutral-700/40 rounded col-span-2 w-16 h-7 mx-auto"></div> 62 | )} 63 | </div> 64 | </div> 65 | </div> 66 | {/* Fourth Item */} 67 | <div className="flex-1 h-full flex flex-col justify-center"> 68 | <div className="py-4"> 69 | <div className="text-neutral-400 dark:text-neutral-500 text-sm font-semibold mb-0.5"> 70 | {props.item4?.key} 71 | </div> 72 | <div className="text-xl"> 73 | {props.item4?.value ? ( 74 | <>{props.item4.value}</> 75 | ) : ( 76 | <div className="animate-pulse bg-neutral-300/40 dark:bg-neutral-700/40 rounded col-span-2 w-16 h-7 mx-auto"></div> 77 | )} 78 | </div> 79 | </div> 80 | </div> 81 | </div> 82 | </div> 83 | </div> 84 | </> 85 | ) 86 | } 87 | -------------------------------------------------------------------------------- /src/pages/dashboard/components/SocialMedia.tsx: -------------------------------------------------------------------------------- 1 | import { faDiscord, faTelegram, faTwitter } from '@fortawesome/free-brands-svg-icons' 2 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' 3 | import { trackMixPanelEvent } from 'utils/commons' 4 | 5 | export default function SocialMedia() { 6 | return ( 7 | <> 8 | <div className="rounded-xl bg-white border border-neutral-200 dark:border-neutral-700 dark:bg-neutral-800 h-full flex items-center px-4 py-4"> 9 | <div className="flex-1 text-center border-r border-neutral-300 dark:border-neutral-700"> 10 | <a 11 | href="https://twitter.com/SecretNetwork" 12 | target="_blank" 13 | className="group text-neutral-700 dark:text-neutral-300 hover:text-black dark:hover:text-white transition-colors" 14 | onClick={() => { 15 | trackMixPanelEvent('Clicked Twitter on dashboard') 16 | }} 17 | > 18 | <FontAwesomeIcon icon={faTwitter} size="xl" className="fa-fw sm:mb-1" /> 19 | <br /> 20 | <span className="group-hover:text-black dark:group-hover:text-white transition-colors text-sm font-semibold text-neutral-500 dark:text-neutral-500 hidden sm:inline-block"> 21 | Twitter 22 | </span> 23 | </a> 24 | </div> 25 | 26 | <div className="flex-1 text-center border-r border-neutral-300 dark:border-neutral-700"> 27 | <a 28 | href="https://discord.com/invite/SJK32GY" 29 | target="_blank" 30 | className="group text-neutral-700 dark:text-neutral-300 hover:text-black dark:hover:text-white transition-colors" 31 | onClick={() => { 32 | trackMixPanelEvent('Clicked Discord on dashboard') 33 | }} 34 | > 35 | <FontAwesomeIcon icon={faDiscord} size="xl" className="fa-fw sm:mb-1" /> 36 | <br /> 37 | <span className="group-hover:text-black dark:group-hover:text-white transition-colors text-sm font-semibold text-neutral-500 dark:text-neutral-500 hidden sm:inline-block"> 38 | Discord 39 | </span> 40 | </a> 41 | </div> 42 | 43 | <div className="flex-1 text-center"> 44 | <a 45 | href="https://t.me/SCRTCommunity" 46 | target="_blank" 47 | className="group text-center text-neutral-700 dark:text-neutral-300 hover:text-black dark:hover:text-white transition-colors" 48 | onClick={() => { 49 | trackMixPanelEvent('Clicked Telegram on dashboard') 50 | }} 51 | > 52 | <FontAwesomeIcon icon={faTelegram} size="xl" className="fa-fw sm:mb-1" /> 53 | <br /> 54 | <span className="group-hover:text-black dark:group-hover:text-white transition-colors text-sm font-semibold text-neutral-500 dark:text-neutral-500 hidden sm:inline-block"> 55 | Telegram 56 | </span> 57 | </a> 58 | </div> 59 | </div> 60 | </> 61 | ) 62 | } 63 | -------------------------------------------------------------------------------- /src/pages/ibc/components/IbcSelect.tsx: -------------------------------------------------------------------------------- 1 | interface IProps { 2 | imgSrc: string 3 | altText: string 4 | optionName: string 5 | } 6 | 7 | export default function IbcSelect(props: IProps) { 8 | return ( 9 | <div className="flex items-center"> 10 | <img src={props.imgSrc} alt={props.altText} className="w-6 h-6 mr-2 rounded-full" /> 11 | <span className="font-semibold text-sm">{props.optionName}</span> 12 | </div> 13 | ) 14 | } 15 | -------------------------------------------------------------------------------- /src/pages/ibc/components/ibcSchema.ts: -------------------------------------------------------------------------------- 1 | import * as yup from 'yup' 2 | import { isIbcMode } from 'types/IbcMode' 3 | import { chains } from 'utils/config' 4 | 5 | export const ibcSchema = yup.object().shape({ 6 | amount: yup 7 | .number() 8 | .typeError('Please enter a valid amount') 9 | .required('Please enter a valid amount') 10 | .test('min-amount', 'Please enter a valid amount', function (value) { 11 | const { token } = this.parent 12 | if (token && typeof token.decimals === 'number') { 13 | const minAmount = Math.pow(10, -token.decimals) 14 | if (value < minAmount) { 15 | return this.createError({ 16 | message: `Please enter an amount of at least ${minAmount}` 17 | }) 18 | } 19 | } 20 | return true 21 | }), 22 | token: yup.mixed().required('Token is required'), 23 | chain: yup 24 | .mixed() 25 | .required('Please select a chain') 26 | .test('isValidChain', 'Please select a valid chain', (chainValue: any) => 27 | Object.values(chains).some((chain) => chain.chain_name === chainValue.chain_name) 28 | ), 29 | ibcMode: yup 30 | .string() 31 | .required('Please pick an IBC Mode') 32 | .test('isIbcMode', 'Invalid IBC Mode', (value) => isIbcMode(value)) 33 | }) 34 | -------------------------------------------------------------------------------- /src/pages/powertools/components/ApiStatusIcon.tsx: -------------------------------------------------------------------------------- 1 | import Tooltip from '@mui/material/Tooltip' 2 | import React from 'react' 3 | import { ApiStatus } from 'types/ApiStatus' 4 | 5 | type Props = { 6 | apiStatus?: ApiStatus 7 | } 8 | 9 | function ApiStatusIcon({ apiStatus = 'loading', ...props }: Props) { 10 | const colorClass: Record<ApiStatus, string> = { 11 | online: 'bg-emerald-500', 12 | offline: 'bg-rose-500', 13 | loading: '', 14 | unknown: 'bg-neutral-500' 15 | } 16 | 17 | const tooltipText: Record<ApiStatus, string> = { 18 | online: 'Online', 19 | offline: 'Offline', 20 | loading: 'Loading', 21 | unknown: 'Unknown' 22 | } 23 | 24 | return ( 25 | <> 26 | <Tooltip title={tooltipText[apiStatus]} placement="bottom" arrow> 27 | <span> 28 | {apiStatus === 'loading' ? ( 29 | <span> 30 | <svg 31 | className="animate-spin h-5 w-5 text-white" 32 | xmlns="http://www.w3.org/2000/svg" 33 | fill="none" 34 | viewBox="0 0 24 24" 35 | > 36 | <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle> 37 | <path 38 | className="opacity-75" 39 | fill="currentColor" 40 | d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" 41 | ></path> 42 | </svg> 43 | </span> 44 | ) : ( 45 | <span className={`flex w-3 h-3 rounded-full ${colorClass[apiStatus]}`}></span> 46 | )} 47 | </span> 48 | </Tooltip> 49 | </> 50 | ) 51 | } 52 | 53 | export default ApiStatusIcon 54 | -------------------------------------------------------------------------------- /src/pages/send/Send.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react' 2 | import { sendPageTitle, sendPageDescription, sendJsonLdSchema } from 'utils/commons' 3 | import Title from 'components/Title' 4 | import { Helmet } from 'react-helmet-async' 5 | import mixpanel from 'mixpanel-browser' 6 | import { useSecretNetworkClientStore } from 'store/secretNetworkClient' 7 | import SendForm from './components/SendForm' 8 | 9 | export function Send() { 10 | const { isConnected, setIsConnectWalletModalOpen, setIsGetWalletModalOpen } = useSecretNetworkClientStore() 11 | 12 | useEffect(() => { 13 | if (import.meta.env.VITE_MIXPANEL_ENABLED === 'true') { 14 | mixpanel.init(import.meta.env.VITE_MIXPANEL_PROJECT_TOKEN, { 15 | debug: false 16 | }) 17 | mixpanel.identify('Dashboard-App') 18 | mixpanel.track('Open Wrap Tab') 19 | } 20 | }, []) 21 | 22 | return ( 23 | <> 24 | <Helmet> 25 | <title>{sendPageTitle} 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | {/* */} 38 | 39 | 40 | 41 | {/* */} 42 | 43 | 44 | 45 | 46 |
47 | {/* Title*/} 48 | 49 | {/* Content */} 50 | <div className="rounded-3xl px-6 py-6 bg-white border border-neutral-200 dark:border-neutral-700 dark:bg-neutral-800"> 51 | <SendForm /> 52 | </div> 53 | </div> 54 | </> 55 | ) 56 | } 57 | -------------------------------------------------------------------------------- /src/pages/send/sendSchema.ts: -------------------------------------------------------------------------------- 1 | import * as yup from 'yup' 2 | import { validateAddress } from 'secretjs' 3 | 4 | export const sendSchema = yup.object().shape({ 5 | amount: yup 6 | .number() 7 | .typeError('Please enter a valid amount') 8 | .required('Please enter a valid amount') 9 | .test('min-amount', 'Please enter a valid amount', function (value) { 10 | const { token } = this.parent 11 | if (token && typeof token.decimals === 'number') { 12 | const minAmount = Math.pow(10, -token.decimals) 13 | if (value < minAmount) { 14 | return this.createError({ 15 | message: `Please enter an amount of at least ${minAmount}` 16 | }) 17 | } 18 | } 19 | return true 20 | }), 21 | token: yup.mixed().required('Token is required'), 22 | recipient: yup 23 | .string() 24 | .required('Add a recipient') 25 | .test('isValidAddress', 'Please enter a valid recipient', (value) => { 26 | if (!value) return false 27 | return validateAddress(value).isValid 28 | }), 29 | memo: yup.string().max(255, 'Memo too long') 30 | }) 31 | -------------------------------------------------------------------------------- /src/pages/staking/components/ClaimRewardsModal.tsx: -------------------------------------------------------------------------------- 1 | import { useContext, useState } from 'react' 2 | import { StakingContext } from 'pages/staking/Staking' 3 | import FeeGrant from '../../../components/FeeGrant/FeeGrant' 4 | import { useSecretNetworkClientStore } from 'store/secretNetworkClient' 5 | import Modal from 'components/UI/Modal/Modal' 6 | import { StakingService } from 'services/staking.service' 7 | import Button from 'components/UI/Button/Button' 8 | import { tokens } from 'utils/config' 9 | 10 | interface Props { 11 | open: boolean 12 | onClose: any 13 | } 14 | 15 | export default function ClaimRewardsModal(props: Props) { 16 | const { secretNetworkClient, feeGrantStatus } = useSecretNetworkClientStore() 17 | 18 | const { delegatorDelegations, totalPendingRewards } = useContext(StakingContext) 19 | 20 | if (!props.open) return null 21 | 22 | async function handleClaimRewards() { 23 | await StakingService.performClaimStakingRewards({ 24 | delegatorDelegations: delegatorDelegations, 25 | secretNetworkClient: secretNetworkClient, 26 | feeGrantStatus: feeGrantStatus 27 | }) 28 | } 29 | 30 | return ( 31 | <> 32 | <Modal 33 | isOpen={props.open} 34 | onClose={props.onClose} 35 | title="Your Staking Rewards" 36 | subTitle="Claim your staking rewards" 37 | > 38 | {/* Body */} 39 | <div className="flex flex-col gap-4"> 40 | <div className="my-4 text-lg text-center text-black dark:text-white"> 41 | <div className="font-bold">Claimable Amount</div> 42 | <div className="mt-2 text-emerald-500 dark:text-emerald-500"> 43 | <span className="font-medium font-mono">{totalPendingRewards}</span> 44 | <span className="text-sm">{` SCRT`}</span> 45 | </div> 46 | </div> 47 | <div> 48 | <FeeGrant /> 49 | </div> 50 | <Button onClick={handleClaimRewards} color="emerald" size="default"> 51 | Claim Rewards 52 | </Button> 53 | </div> 54 | </Modal> 55 | </> 56 | ) 57 | } 58 | function getBalance(arg0: any, arg1: boolean) { 59 | throw new Error('Function not implemented.') 60 | } 61 | -------------------------------------------------------------------------------- /src/pages/staking/components/NoScrtWarning.tsx: -------------------------------------------------------------------------------- 1 | import { faArrowUpRightFromSquare, faInfoCircle } from '@fortawesome/free-solid-svg-icons' 2 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' 3 | 4 | function NoScrtWarning() { 5 | return ( 6 | <> 7 | <div className="px-4 mb-4 max-w-6xl mx-auto text-sm"> 8 | <div className="w-full dark:bg-neutral-800 p-4 rounded-lg"> 9 | <div className="flex gap-4 text-yellow-800 dark:text-yellow-500"> 10 | <FontAwesomeIcon icon={faInfoCircle} className="mt-[3px]" /> 11 | <div> 12 | <div className="font-semibold flex flex-row items-center"> 13 | <span>{`You do not have any SCRT for Staking`}</span> 14 | </div> 15 | <ul className="list-disc mt-3 ml-4 flex flex-col gap-1"> 16 | <li> 17 | <a href="/get-scrt" className="hover:underline"> 18 | Get SCRT 19 | <FontAwesomeIcon icon={faArrowUpRightFromSquare} className="text-xs ml-2" size={'xs'} /> 20 | </a> 21 | </li> 22 | <li> 23 | <a 24 | href="https://medium.com/secret-network-ecosystem-and-technology/how-to-get-store-and-stake-scrt-64ae2740b98e" 25 | className="hover:underline" 26 | target="_blank" 27 | rel="noopener noreferrer" 28 | > 29 | Learn more about Staking 30 | <FontAwesomeIcon icon={faArrowUpRightFromSquare} className="text-xs ml-2" size={'xs'} /> 31 | </a> 32 | </li> 33 | </ul> 34 | </div> 35 | </div> 36 | </div> 37 | </div> 38 | </> 39 | ) 40 | } 41 | 42 | export default NoScrtWarning 43 | -------------------------------------------------------------------------------- /src/pages/staking/components/StakingStats/AvailableBalance.tsx: -------------------------------------------------------------------------------- 1 | import BigNumber from 'bignumber.js' 2 | import { APIContext } from 'context/APIContext' 3 | import { useContext, useEffect, useState } from 'react' 4 | import { useTokenPricesStore } from 'store/TokenPrices' 5 | import { useUserPreferencesStore } from 'store/UserPreferences' 6 | import { useSecretNetworkClientStore } from 'store/secretNetworkClient' 7 | import { toCurrencyString } from 'utils/commons' 8 | import { tokens } from 'utils/config' 9 | import { scrtToken } from 'utils/tokens' 10 | 11 | function AvailableBalance() { 12 | const { getBalance } = useSecretNetworkClientStore() 13 | 14 | const scrtBalance = getBalance( 15 | tokens.find((token) => token.name === 'SCRT'), 16 | false 17 | ) 18 | 19 | const { currency } = useUserPreferencesStore() 20 | const { convertCurrency } = useContext(APIContext) 21 | 22 | const { getValuePrice, priceMapping } = useTokenPricesStore() 23 | 24 | const [availableBalanceInCurrency, setAvailableBalanceInCurrency] = useState<string>('') 25 | 26 | useEffect(() => { 27 | if (priceMapping !== null && scrtBalance !== null) { 28 | const valuePrice = getValuePrice(scrtToken, BigNumber(scrtBalance)) 29 | if (valuePrice) { 30 | const priceInCurrency = convertCurrency('USD', valuePrice, currency) 31 | if (priceInCurrency !== null) { 32 | setAvailableBalanceInCurrency(toCurrencyString(priceInCurrency, currency)) 33 | } 34 | } 35 | } 36 | }, [priceMapping, scrtBalance]) 37 | 38 | return ( 39 | <div className="flex-1"> 40 | <div className="font-bold mb-2">Available Balance</div> 41 | <div className="mb-1"> 42 | <span className="font-medium font-mono"> 43 | {scrtBalance !== null ? ( 44 | <>{new BigNumber(scrtBalance).dividedBy(`1e${scrtToken.decimals}`).toNumber()}</> 45 | ) : ( 46 | <div className="animate-pulse inline-block"> 47 | <div className="h-5 w-24 bg-white dark:bg-neutral-700 rounded-xl"></div> 48 | </div> 49 | )} 50 | </span> 51 | <span className="text-xs font-semibold text-neutral-400"> SCRT</span> 52 | </div> 53 | <div className="text-xs text-neutral-400 font-medium font-mono"> 54 | {availableBalanceInCurrency ? ( 55 | <>{availableBalanceInCurrency}</> 56 | ) : ( 57 | <div className="animate-pulse inline-block"> 58 | <div className="h-5 w-12 bg-white dark:bg-neutral-700 rounded-xl"></div> 59 | </div> 60 | )} 61 | </div> 62 | </div> 63 | ) 64 | } 65 | 66 | export default AvailableBalance 67 | -------------------------------------------------------------------------------- /src/pages/staking/components/StakingStats/ClaimableRewards.tsx: -------------------------------------------------------------------------------- 1 | import BigNumber from 'bignumber.js' 2 | import Button from 'components/UI/Button/Button' 3 | import { APIContext } from 'context/APIContext' 4 | import { StakingContext } from 'pages/staking/Staking' 5 | import { useContext, useEffect, useState } from 'react' 6 | import { useTokenPricesStore } from 'store/TokenPrices' 7 | import { useUserPreferencesStore } from 'store/UserPreferences' 8 | import { toCurrencyString } from 'utils/commons' 9 | import { scrtToken } from 'utils/tokens' 10 | 11 | function ClaimableRewards() { 12 | const { setIsClaimRewardsModalOpen, totalPendingRewards } = useContext(StakingContext) 13 | const { convertCurrency } = useContext(APIContext) 14 | const { getValuePrice, priceMapping } = useTokenPricesStore() 15 | const { currency } = useUserPreferencesStore() 16 | 17 | const [claimableRewardsInCurrency, setClaimableRewardsInCurrency] = useState<string>('') 18 | 19 | useEffect(() => { 20 | if (priceMapping !== null && totalPendingRewards !== null) { 21 | const valuePrice = getValuePrice( 22 | scrtToken, 23 | BigNumber(totalPendingRewards).multipliedBy(`1e${scrtToken.decimals}`) 24 | ) 25 | if (valuePrice) { 26 | const priceInCurrency = convertCurrency('USD', valuePrice, currency) 27 | if (priceInCurrency !== null) { 28 | setClaimableRewardsInCurrency(toCurrencyString(priceInCurrency, currency)) 29 | } 30 | setClaimableRewardsInCurrency(toCurrencyString(valuePrice, currency)) 31 | } 32 | } 33 | }, [priceMapping, totalPendingRewards]) 34 | 35 | function openClaimRewardsModal() { 36 | setIsClaimRewardsModalOpen(true) 37 | } 38 | 39 | return ( 40 | <div className="flex items-center gap-4 p-4 rounded-xl dark:bg-neutral-800 bg-white"> 41 | <div className="flex-1"> 42 | <div className="font-bold mb-2">Claimable Rewards</div> 43 | <div className="mb-1"> 44 | {totalPendingRewards !== null ? ( 45 | <> 46 | <span className="font-medium font-mono">{totalPendingRewards}</span> 47 | <span className="text-xs font-semibold text-neutral-400"> SCRT</span> 48 | </> 49 | ) : ( 50 | <div className="animate-pulse inline-block"> 51 | <div className="h-5 w-12 bg-white dark:bg-neutral-700 rounded-xl"></div> 52 | </div> 53 | )} 54 | </div> 55 | <div className="text-xs text-neutral-400 font-medium font-mono"> 56 | {claimableRewardsInCurrency ? ( 57 | <>{claimableRewardsInCurrency}</> 58 | ) : ( 59 | <div className="animate-pulse inline-block"> 60 | <div className="h-5 w-12 bg-white dark:bg-neutral-700 rounded-xl"></div> 61 | </div> 62 | )} 63 | </div> 64 | </div> 65 | <div> 66 | <Button type="button" onClick={openClaimRewardsModal} color="emerald"> 67 | Claim 68 | </Button> 69 | </div> 70 | </div> 71 | ) 72 | } 73 | 74 | export default ClaimableRewards 75 | -------------------------------------------------------------------------------- /src/pages/staking/components/StakingStats/StakingAmount.tsx: -------------------------------------------------------------------------------- 1 | import BigNumber from 'bignumber.js' 2 | import { APIContext } from 'context/APIContext' 3 | import { StakingContext } from 'pages/staking/Staking' 4 | import { useContext, useEffect, useState } from 'react' 5 | import { useTokenPricesStore } from 'store/TokenPrices' 6 | import { useUserPreferencesStore } from 'store/UserPreferences' 7 | import { Nullable } from 'types/Nullable' 8 | import { toCurrencyString } from 'utils/commons' 9 | import { scrtToken } from 'utils/tokens' 10 | 11 | type Props = { 12 | stakedAmount?: Nullable<number> 13 | } 14 | 15 | function StakingAmount(props: Props) { 16 | const { convertCurrency } = useContext(APIContext) 17 | const { getValuePrice, priceMapping } = useTokenPricesStore() 18 | const { currency } = useUserPreferencesStore() 19 | const [stakedAmountInCurrency, setStakedAmountInCurrency] = useState<string>('') 20 | 21 | useEffect(() => { 22 | if (priceMapping !== null && props.stakedAmount !== null) { 23 | const valuePrice = getValuePrice(scrtToken, BigNumber(props.stakedAmount).multipliedBy(`1e${scrtToken.decimals}`)) 24 | if (valuePrice) { 25 | const priceInCurrency = convertCurrency('USD', valuePrice, currency) 26 | if (priceInCurrency !== null) { 27 | setStakedAmountInCurrency(toCurrencyString(priceInCurrency, currency)) 28 | } 29 | } 30 | } 31 | }, [priceMapping, props.stakedAmount]) 32 | 33 | return ( 34 | <div className="flex-1"> 35 | <div className="font-bold mb-2">Total Staked</div> 36 | <div className="mb-1"> 37 | {props.stakedAmount !== null ? ( 38 | <> 39 | <span className="font-medium font-mono">{props.stakedAmount}</span> 40 | <span className="text-xs font-semibold text-neutral-400"> SCRT</span> 41 | </> 42 | ) : ( 43 | <div className="animate-pulse inline-block"> 44 | <div className="h-5 w-12 bg-white dark:bg-neutral-700 rounded-xl"></div> 45 | </div> 46 | )} 47 | </div> 48 | <div className="text-xs text-neutral-400 font-medium font-mono"> 49 | {stakedAmountInCurrency ? ( 50 | <>{stakedAmountInCurrency}</> 51 | ) : ( 52 | <div className="animate-pulse inline-block"> 53 | <div className="h-5 w-12 bg-white dark:bg-neutral-700 rounded-xl"></div> 54 | </div> 55 | )} 56 | </div> 57 | </div> 58 | ) 59 | } 60 | 61 | export default StakingAmount 62 | -------------------------------------------------------------------------------- /src/pages/staking/components/StakingStats/StakingStats.tsx: -------------------------------------------------------------------------------- 1 | import { useContext } from 'react' 2 | import StakingAmount from './StakingAmount' 3 | import AvailableBalance from './AvailableBalance' 4 | import ClaimableRewards from './ClaimableRewards' 5 | import { StakingContext } from 'pages/staking/Staking' 6 | 7 | function StakingStats() { 8 | const { getTotalAmountStaked } = useContext(StakingContext) 9 | 10 | const stakedAmount = getTotalAmountStaked() || null 11 | 12 | return ( 13 | <div className="max-w-6xl mx-auto px-4"> 14 | <div className="grid grid-cols-12"> 15 | {/* Total Staked */} 16 | <div className="col-span-12 md:col-span-3 lg:col-span-12 xl:col-span-3 py-4"> 17 | <StakingAmount stakedAmount={stakedAmount} /> 18 | </div> 19 | 20 | {/* Available Balance */} 21 | <div className="col-span-12 md:col-span-3 lg:col-span-12 xl:col-span-3 py-4"> 22 | <AvailableBalance /> 23 | </div> 24 | 25 | {/* Claimable Rewards */} 26 | <div className="col-span-12 md:col-span-6 lg:col-span-12 xl:col-span-6"> 27 | <ClaimableRewards /> 28 | </div> 29 | </div> 30 | </div> 31 | ) 32 | } 33 | 34 | export default StakingStats 35 | -------------------------------------------------------------------------------- /src/pages/wrap/Wrap.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState, useContext, createContext } from 'react' 2 | import { Token, tokens } from 'utils/config' 3 | import { wrapPageTitle, wrapPageDescription, wrapJsonLdSchema } from 'utils/commons' 4 | import { Helmet } from 'react-helmet-async' 5 | import FeeGrantInfoModal from './components/FeeGrantInfoModal' 6 | import mixpanel from 'mixpanel-browser' 7 | import { WrappingMode, isWrappingMode } from 'types/WrappingMode' 8 | import { useSecretNetworkClientStore } from 'store/secretNetworkClient' 9 | import Title from 'components/Title' 10 | import WrapForm from './components/WrapForm' 11 | import SCRTUnwrapWarning from './components/SCRTUnwrapWarning' 12 | 13 | export function Wrap() { 14 | const { getBalance } = useSecretNetworkClientStore() 15 | const scrtBalance = getBalance( 16 | tokens.find((token) => token.name === 'SCRT'), 17 | false 18 | ) 19 | 20 | useEffect(() => { 21 | if (import.meta.env.VITE_MIXPANEL_ENABLED === 'true') { 22 | mixpanel.init(import.meta.env.VITE_MIXPANEL_PROJECT_TOKEN, { 23 | debug: false 24 | }) 25 | mixpanel.identify('Dashboard-App') 26 | mixpanel.track('Open Wrap Tab') 27 | } 28 | }, []) 29 | 30 | const infoMsg = `Converting publicly visible tokens into its privacy-preserving equivalent Secret Token and vice versa. These tokens are not publicly visible and require a viewing key.` 31 | 32 | const [isFeeGrantInfoModalOpen, setIsFeeGrantInfoModalOpen] = useState(false) 33 | 34 | return ( 35 | <> 36 | <Helmet> 37 | <title>{wrapPageTitle} 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | {/* */} 50 | 51 | 52 | 53 | {/* */} 54 | 55 | 56 | 57 | 58 | { 61 | setIsFeeGrantInfoModalOpen(false) 62 | document.body.classList.remove('overflow-hidden') 63 | }} 64 | /> 65 | 66 | {/* Content */} 67 |
68 | {/* Title: Secret Wrap / Secret Unwrap */} 69 | 70 | {Number(scrtBalance) === 0 && scrtBalance !== null ? <SCRTUnwrapWarning /> : null} 71 | {/* Content */} 72 | <div className="rounded-3xl px-6 py-6 bg-white border border-neutral-200 dark:border-neutral-700 dark:bg-neutral-800"> 73 | <WrapForm /> 74 | </div> 75 | </div> 76 | </> 77 | ) 78 | } 79 | -------------------------------------------------------------------------------- /src/pages/wrap/components/FeeGrantInfoModal.tsx: -------------------------------------------------------------------------------- 1 | import { faInfoCircle, faXmark } from '@fortawesome/free-solid-svg-icons' 2 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' 3 | import { useEffect } from 'react' 4 | 5 | interface Props { 6 | open?: boolean 7 | onClose?: any 8 | } 9 | 10 | const FeeGrantInfoModal = (props: Props) => { 11 | // disable body scroll on open 12 | useEffect(() => { 13 | if (props.open) { 14 | document.body.classList.add('overflow-hidden') 15 | } else { 16 | document.body.classList.remove('overflow-hidden') 17 | } 18 | }, [props.open]) 19 | 20 | if (!props.open) return null 21 | 22 | return ( 23 | <div className="fixed top-0 left-0 right-0 bottom-0 bg-black/80 z-50" onClick={props.onClose}> 24 | <div className="absolute top-[15%] w-full onEnter_fadeInDown"> 25 | <div className="mx-auto max-w-xl px-4"> 26 | <div 27 | className="bg-neutral-900 p-8 rounded-2xl" 28 | onClick={(e) => { 29 | e.stopPropagation() 30 | }} 31 | > 32 | {/* Header */} 33 | <div className="mb-0 text-right"> 34 | <button 35 | onClick={props.onClose} 36 | className="text-neutral-500 hover:bg-neutral-800 transition-colors px-1.5 py-1 rounded-lg text-xl" 37 | > 38 | <FontAwesomeIcon icon={faXmark} className="fa-fw" /> 39 | </button> 40 | </div> 41 | 42 | {/* Header */} 43 | <div className="mb-4 text-center"> 44 | <h2 className="text-2xl font-medium mb-4"> 45 | <FontAwesomeIcon icon={faInfoCircle} className="mr-2 text-neutral-500" /> 46 | Fee Grant Feature 47 | </h2> 48 | <p className="text-neutral-400 max-w-sm mx-auto mb-6"> 49 | Lorem, ipsum dolor sit amet consectetur adipisicing elit. Corrupti, ducimus? Modi inventore, deleniti 50 | culpa quasi quis reiciendis sint, consectetur voluptate eaque ea aliquam rerum natus vel nostrum, quod 51 | praesentium quidem! 52 | </p> 53 | <button 54 | onClick={props.onClose} 55 | className="sm:max-w-[225px] w-full md:px-4 bg-cyan-600 text-cyan-00 hover:text-cyan-100 hover:bg-cyan-500 text-center transition-colors py-2.5 rounded-xl font-semibold text-sm" 56 | > 57 | Close 58 | </button> 59 | </div> 60 | 61 | {/* Body */} 62 | <div className="text-center"></div> 63 | </div> 64 | </div> 65 | </div> 66 | </div> 67 | ) 68 | } 69 | 70 | export default FeeGrantInfoModal 71 | -------------------------------------------------------------------------------- /src/pages/wrap/components/SCRTUnwrapWarning.tsx: -------------------------------------------------------------------------------- 1 | import { faTriangleExclamation } from '@fortawesome/free-solid-svg-icons' 2 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' 3 | 4 | function SCRTUnwrapWarning() { 5 | return ( 6 | <div className="px-4 mb-4 max-w-6xl mx-auto"> 7 | <div className="inline-block w-full md:w-auto bg-yellow-800/10 dark:bg-yellow-800/40 border border-yellow-700 dark:border-yellow-600 p-4 rounded-lg"> 8 | <div className="font-semibold text-yellow-700 dark:text-yellow-600 flex flex-row items-center"> 9 | <FontAwesomeIcon icon={faTriangleExclamation} className="mr-3" /> 10 | <ul className="list-disc ml-4 flex flex-col gap-0.5"> 11 | <div>You do not have any SCRT to pay for gas</div> 12 | <li>Please unwrap some sSCRT into SCRT using a fee grant.</li> 13 | <li> 14 | Do <b>NOT</b> try to create a viewing key first, instead unwrap 0.1 sSCRT directly. 15 | </li> 16 | </ul> 17 | </div> 18 | </div> 19 | </div> 20 | ) 21 | } 22 | 23 | export default SCRTUnwrapWarning 24 | -------------------------------------------------------------------------------- /src/pages/wrap/components/wrap.scss: -------------------------------------------------------------------------------- 1 | .no-spinner::-webkit-inner-spin-button, 2 | .no-spinner::-webkit-outer-spin-button { 3 | -webkit-appearance: none; 4 | margin: 0; 5 | } 6 | 7 | .no-spinner { 8 | -moz-appearance: textfield; 9 | } 10 | -------------------------------------------------------------------------------- /src/services/notification.service.ts: -------------------------------------------------------------------------------- 1 | import toast from 'react-hot-toast' 2 | import { NotificationType } from 'types/NotificationType' 3 | 4 | function notify(notification: string, type: NotificationType, toastId: string = null) { 5 | // Define an options object for toast, if toastId is provided 6 | const options = { 7 | id: toastId || undefined, 8 | duration: type === 'success' ? 10000 : Infinity, 9 | onClick: () => { 10 | toast.dismiss() 11 | }, 12 | style: { 13 | maxWidth: '35vw', 14 | whiteSpace: 'pre-wrap' as any, 15 | wordBreak: 'break-word' as any 16 | }, 17 | position: 'bottom-right' as any 18 | } 19 | 20 | switch (type) { 21 | case 'error': 22 | return toast.error(notification, options) 23 | 24 | case 'loading': 25 | return toast.loading(notification, options) 26 | 27 | case 'success': 28 | return toast.success(notification, options) 29 | 30 | default: 31 | return null 32 | } 33 | } 34 | 35 | export const NotificationService = { 36 | notify 37 | } 38 | -------------------------------------------------------------------------------- /src/services/send.service.ts: -------------------------------------------------------------------------------- 1 | import BigNumber from 'bignumber.js' 2 | import { BroadcastMode, MsgExecuteContract, MsgSend, SecretNetworkClient } from 'secretjs' 3 | import { FeeGrantStatus } from 'types/FeeGrantStatus' 4 | import { Nullable } from 'types/Nullable' 5 | import { allTokens, faucetAddress, queryTxResult, randomPadding } from 'utils/commons' 6 | import { Token, tokens } from 'utils/config' 7 | 8 | function getSupportedTokens() { 9 | return [allTokens[0]].concat( 10 | allTokens.map((token: Token) => (token.name === 'SCRT' ? { ...token, address: 'native' } : token)) 11 | ) 12 | } 13 | 14 | interface IBaseProps { 15 | amount: string 16 | recipient: string 17 | memo?: string 18 | secretNetworkClient: SecretNetworkClient 19 | feeGrantStatus: FeeGrantStatus 20 | } 21 | 22 | interface IPropsToken extends IBaseProps { 23 | token: Token 24 | } 25 | 26 | // Main function to perform the sending 27 | async function performSending(props: IPropsToken): Promise<string> { 28 | let token = props.token 29 | 30 | if (!token) { 31 | console.error('token', token) 32 | throw new Error('Token not found') 33 | } 34 | 35 | const baseAmount = props.amount 36 | const amount = new BigNumber(Number(baseAmount)).multipliedBy(`1e${token.decimals}`).toFixed(0, BigNumber.ROUND_DOWN) 37 | 38 | if (amount === 'NaN') { 39 | console.error('NaN amount', baseAmount) 40 | throw new Error('Amount is not a valid number') 41 | } 42 | 43 | // Broadcast the transaction 44 | const broadcastResult = await props.secretNetworkClient.tx.broadcast( 45 | [ 46 | token.address === 'native' 47 | ? new MsgSend({ 48 | from_address: props.secretNetworkClient?.address, 49 | to_address: props.recipient, 50 | amount: [ 51 | { 52 | amount: amount, 53 | denom: 'uscrt' 54 | } 55 | ] 56 | }) 57 | : new MsgExecuteContract({ 58 | sender: props.secretNetworkClient?.address, 59 | contract_address: token.address, 60 | code_hash: token.code_hash, 61 | sent_funds: [], 62 | msg: { 63 | transfer: { 64 | recipient: props.recipient, 65 | amount: amount, 66 | padding: randomPadding() 67 | } 68 | } 69 | }) 70 | ], 71 | { 72 | gasLimit: 150_000, 73 | gasPriceInFeeDenom: 0.25, 74 | feeDenom: 'uscrt', 75 | feeGranter: props.feeGrantStatus === 'success' ? faucetAddress : '', 76 | broadcastMode: BroadcastMode.Sync, 77 | memo: props.memo, 78 | waitForCommit: false 79 | } 80 | ) 81 | 82 | // Poll the LCD for the transaction result every 10 seconds, 10 retries 83 | await queryTxResult(props.secretNetworkClient, broadcastResult.transactionHash, 6000, 10) 84 | .catch((error: any) => { 85 | console.error(error) 86 | throw error 87 | }) 88 | .then((tx: any) => { 89 | if (tx) { 90 | if (tx.code === 0) { 91 | return 'success' 92 | } else { 93 | console.error(tx.rawLog) 94 | throw new Error(tx.rawLog) 95 | } 96 | } 97 | }) 98 | return 99 | } 100 | 101 | export const SendService = { 102 | performSending, 103 | getSupportedTokens 104 | } 105 | -------------------------------------------------------------------------------- /src/services/staking.service.ts: -------------------------------------------------------------------------------- 1 | import { BroadcastMode, MsgWithdrawDelegatorReward, SecretNetworkClient } from 'secretjs' 2 | import { FeeGrantStatus } from 'types/FeeGrantStatus' 3 | import { faucetAddress, queryTxResult } from 'utils/commons' 4 | import { NotificationService } from './notification.service' 5 | 6 | interface Props { 7 | delegatorDelegations: any 8 | secretNetworkClient: SecretNetworkClient 9 | feeGrantStatus: FeeGrantStatus 10 | } 11 | 12 | /** 13 | * Attempts to perform staking via secret.js API 14 | * 15 | * @param {TProps} props 16 | * @returns {Promise<{success: boolean, errorMsg: Nullable<string>}>} A promise that resolves to an object containing: 17 | * - `success`: A boolean indicating whether the wrapping operation was successful. 18 | * - `errorMsg`: A string containing an error message if something went wrong, or null if there were no errors. 19 | * @async 20 | */ 21 | 22 | const performClaimStakingRewards = async (props: Props) => { 23 | const toastId = NotificationService.notify(`Claiming Staking Rewards`, 'loading') 24 | 25 | try { 26 | const txs = props.delegatorDelegations.map((delegation: any) => { 27 | return new MsgWithdrawDelegatorReward({ 28 | delegator_address: props.secretNetworkClient.address, 29 | validator_address: delegation?.delegation?.validator_address 30 | }) 31 | }) 32 | 33 | const broadcastResult = await props.secretNetworkClient.tx.broadcast(txs, { 34 | gasLimit: 100_000 * txs.length, 35 | gasPriceInFeeDenom: 0.25, 36 | feeDenom: 'uscrt', 37 | feeGranter: props.feeGrantStatus === 'success' ? faucetAddress : '', 38 | broadcastMode: BroadcastMode.Sync, 39 | waitForCommit: false 40 | }) 41 | 42 | // Poll the LCD for the transaction result every 10 seconds, 10 retries 43 | await queryTxResult(props.secretNetworkClient, broadcastResult.transactionHash, 6000, 10) 44 | .catch((error: any) => { 45 | console.error(error) 46 | if (error?.tx?.rawLog) { 47 | NotificationService.notify(`Claiming staking rewards failed: ${error.tx.rawLog}`, 'error', toastId) 48 | } else { 49 | NotificationService.notify(`Claiming staking rewards failed: ${error.message}`, 'error', toastId) 50 | } 51 | }) 52 | .then((tx: any) => { 53 | if (tx) { 54 | if (tx.code === 0) { 55 | NotificationService.notify(`Claimed staking rewards successfully`, 'success', toastId) 56 | } else { 57 | NotificationService.notify(`Claiming staking rewards failed: ${tx.rawLog}`, 'error', toastId) 58 | } 59 | } 60 | }) 61 | } catch (e: any) { 62 | console.error(e) 63 | } 64 | } 65 | 66 | export const StakingService = { 67 | performClaimStakingRewards 68 | } 69 | -------------------------------------------------------------------------------- /src/store/TokenPrices.ts: -------------------------------------------------------------------------------- 1 | import BigNumber from 'bignumber.js' 2 | import { Nullable } from 'types/Nullable' 3 | import { allTokens, toCurrencyString } from 'utils/commons' 4 | import { Token, tokens } from 'utils/config' 5 | import { create } from 'zustand' 6 | 7 | export interface CoinPrice { 8 | coingecko_id: string 9 | priceUsd: number 10 | } 11 | 12 | interface TokenPricesState { 13 | priceMapping: Map<Token, number> 14 | init: () => void 15 | isInitialized: boolean 16 | getPrice: (token: Token) => Nullable<string> 17 | getValuePrice: (token: Token, amount: BigNumber) => Nullable<number> 18 | } 19 | 20 | export const useTokenPricesStore = create<TokenPricesState>()((set, get) => ({ 21 | priceMapping: null, 22 | isInitialized: false, 23 | init: () => { 24 | let prices: CoinPrice[] 25 | 26 | /*let coinGeckoIdsString: string = allTokens.map((token) => token.coingecko_id).join(',') 27 | console.log(coinGeckoIdsString)*/ 28 | 29 | // fetch(`https://api.coingecko.com/api/v3/simple/price?ids=${coinGeckoIdsString}&vs_currencies=USD`) 30 | fetch(`https://priceapibuffer.secretsaturn.net/getPrices`) 31 | .then((resp) => resp.json()) 32 | .then((result: { [coingecko_id: string]: { usd: number } }) => { 33 | const formattedPrices = Object.entries(result).map(([coingecko_id, { usd }]) => ({ 34 | coingecko_id, 35 | priceUsd: usd 36 | })) 37 | prices = formattedPrices 38 | const priceMapping = new Map<Token, number>() 39 | allTokens.forEach((token: Token) => { 40 | priceMapping.set(token, prices.find((price: any) => price.coingecko_id === token.coingecko_id)?.priceUsd) 41 | }) 42 | 43 | set({ 44 | priceMapping: priceMapping 45 | }) 46 | }) 47 | .catch((error) => { 48 | console.error(error) 49 | const priceMapping = new Map<Token, number>() 50 | tokens.forEach((token: Token) => { 51 | priceMapping.set(token, undefined) 52 | }) 53 | set({ 54 | priceMapping: priceMapping 55 | }) 56 | }) 57 | set({ 58 | priceMapping: new Map<Token, number>(), 59 | isInitialized: true 60 | }) 61 | }, 62 | getPrice: (token: Token) => { 63 | if (!get().isInitialized) { 64 | get().init() 65 | } 66 | const tokenPrice = get().priceMapping.get(token) 67 | if (tokenPrice !== undefined) { 68 | return toCurrencyString(tokenPrice) 69 | } 70 | return null 71 | }, 72 | getValuePrice: (token: Token, amount: BigNumber = new BigNumber(1)): Nullable<number> => { 73 | if (!get().isInitialized) { 74 | get().init() 75 | } 76 | const tokenPrice = get().priceMapping.get(token) 77 | if (tokenPrice !== undefined) { 78 | const result = new BigNumber(tokenPrice).multipliedBy(amount).dividedBy(`1e${token.decimals}`) 79 | return Number(result) 80 | } 81 | return null 82 | } 83 | })) 84 | -------------------------------------------------------------------------------- /src/store/UserPreferences.ts: -------------------------------------------------------------------------------- 1 | import { Currency } from 'types/Currency' 2 | import { Theme } from 'types/Theme' 3 | import { create } from 'zustand' 4 | import { persist } from 'zustand/middleware' 5 | 6 | interface UserPreferencesState { 7 | theme: Theme 8 | debugMode: boolean 9 | currency: Currency 10 | } 11 | 12 | interface UserPreferencesActions { 13 | setTheme: (theme: UserPreferencesState['theme']) => void 14 | setDebugMode: (debugMode: boolean) => void 15 | setCurrency: (currency: UserPreferencesState['currency']) => void 16 | } 17 | 18 | type UserPreferencesStore = UserPreferencesState & UserPreferencesActions 19 | 20 | export const useUserPreferencesStore = create<UserPreferencesStore>()( 21 | persist( 22 | (set, get) => ({ 23 | // Default preferences 24 | theme: 'dark', 25 | debugMode: false, 26 | currency: 'USD', 27 | 28 | // Actions to update preferences 29 | setTheme: (theme: Theme) => set(() => ({ theme })), 30 | setDebugMode: (debugMode: boolean) => set(() => ({ debugMode })), 31 | setCurrency: (currency: Currency) => set(() => ({ currency })) 32 | }), 33 | { 34 | name: 'user-preferences', 35 | getStorage: () => localStorage 36 | } 37 | ) 38 | ) 39 | -------------------------------------------------------------------------------- /src/types/ApiStatus.ts: -------------------------------------------------------------------------------- 1 | export type ApiStatus = 'online' | 'offline' | 'loading' | 'unknown' 2 | 3 | export function isApiStatus(x: any): boolean { 4 | return x === 'online' || x === 'offline' || x === 'loading' || x === 'unknown' 5 | } 6 | -------------------------------------------------------------------------------- /src/types/Currency.ts: -------------------------------------------------------------------------------- 1 | import { Nullable } from './Nullable' 2 | 3 | export type Currency = 'USD' | 'EUR' | 'JPY' | 'GBP' | 'AUD' | 'CAD' | 'CHF' 4 | 5 | export function isCurrency(x: any): boolean { 6 | return x === 'USD' || x === 'EUR' || x === 'JPY' || x === 'GBP' || x === 'AUD' || x === 'CAD' || x === 'CHF' 7 | } 8 | -------------------------------------------------------------------------------- /src/types/FeeGrantStatus.ts: -------------------------------------------------------------------------------- 1 | export type FeeGrantStatus = 'success' | 'fail' | 'untouched' 2 | 3 | export function isFeeGrantStatus(x: any): boolean { 4 | return x === 'success' || x === 'fail' || x === 'untouched' 5 | } 6 | -------------------------------------------------------------------------------- /src/types/GetBalanceError.ts: -------------------------------------------------------------------------------- 1 | export type GetBalanceError = 'viewingKeyError' | 'GenericFetchError' 2 | 3 | export function isGetBalanceError(x: String): boolean { 4 | return x === 'viewingKeyError' || x === 'GenericFetchError' 5 | } 6 | -------------------------------------------------------------------------------- /src/types/IbcMode.ts: -------------------------------------------------------------------------------- 1 | export type IbcMode = 'deposit' | 'withdrawal' 2 | 3 | export function isIbcMode(x: String): boolean { 4 | return x === 'deposit' || x === 'withdrawal' 5 | } 6 | -------------------------------------------------------------------------------- /src/types/MessageType.ts: -------------------------------------------------------------------------------- 1 | export type MessageType = 2 | | 'MultiSend' 3 | | 'Send' 4 | | 'ExecuteContract' 5 | | 'InstantiateContract' 6 | | 'FundCommunityPool' 7 | | 'SetAutoRestake' 8 | | 'SetWithdrawAddress' 9 | | 'WithdrawDelegatorReward' 10 | | 'WithdrawValidatorCommission' 11 | | 'Deposit' 12 | | 'Vote' 13 | | 'VoteWeighted' 14 | | 'Transfer' 15 | | 'Unjail' 16 | | 'BeginRedelegate' 17 | | 'CreateValidator' 18 | | 'Delegate' 19 | | 'EditValidator' 20 | | 'Undelegate' 21 | | 'CreateVestingAccount' 22 | 23 | export function isMessageType(x: String): boolean { 24 | return ( 25 | x === 'MultiSend' || 26 | x === 'Send' || 27 | x === 'ExecuteContract' || 28 | x === 'InstantiateContract' || 29 | x === 'FundCommunityPool' || 30 | x === 'SetAutoRestake' || 31 | x === 'SetWithdrawAddress' || 32 | x === 'WithdrawDelegatorReward' || 33 | x === 'WithdrawValidatorCommission' || 34 | x === 'Deposit' || 35 | x === 'Vote' || 36 | x === 'VoteWeighted' || 37 | x === 'Transfer' || 38 | x === 'Unjail' || 39 | x === 'BeginRedelegate' || 40 | x === 'CreateValidator' || 41 | x === 'Delegate' || 42 | x === 'EditValidator' || 43 | x === 'Undelegate' || 44 | x === 'CreateVestingAccount' 45 | ) 46 | } 47 | -------------------------------------------------------------------------------- /src/types/NotificationType.ts: -------------------------------------------------------------------------------- 1 | export type NotificationType = 'success' | 'error' | 'loading' 2 | 3 | export function isNotificationType(x: String): boolean { 4 | return x === 'success' || x === 'error' || x === 'loading' 5 | } 6 | -------------------------------------------------------------------------------- /src/types/Nullable.ts: -------------------------------------------------------------------------------- 1 | export type Nullable<X> = X | null 2 | -------------------------------------------------------------------------------- /src/types/StakingView.ts: -------------------------------------------------------------------------------- 1 | export type StakingView = 'delegate' | 'redelegate' | 'undelegate' 2 | 3 | export const isStakingView = (x: string) => { 4 | return x === 'delegate' || x === 'redelegate' || x === 'undelegate' 5 | } 6 | -------------------------------------------------------------------------------- /src/types/Theme.ts: -------------------------------------------------------------------------------- 1 | export type Theme = 'light' | 'dark' 2 | 3 | export function isTheme(x: String): boolean { 4 | return x === 'light' || x === 'dark' 5 | } 6 | -------------------------------------------------------------------------------- /src/types/TokenBalances.ts: -------------------------------------------------------------------------------- 1 | import BigNumber from 'bignumber.js' 2 | import { Nullable } from './Nullable' 3 | import { GetBalanceError } from './GetBalanceError' 4 | 5 | export type TokenBalances = { 6 | balance: Nullable<BigNumber> 7 | secretBalance?: Nullable<BigNumber | GetBalanceError> 8 | } 9 | -------------------------------------------------------------------------------- /src/types/Validator.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Represents a dummy type for better code readability. 3 | * @typedef {Object.<string, any>} Validator 4 | */ 5 | 6 | export type Validator = { 7 | [prop: string]: any 8 | } 9 | -------------------------------------------------------------------------------- /src/types/ValidatorRestakeStatus.ts: -------------------------------------------------------------------------------- 1 | export type ValidatorRestakeStatus = { 2 | validatorAddress: string 3 | autoRestake: boolean 4 | stakedAmount: string 5 | } 6 | -------------------------------------------------------------------------------- /src/types/WalletAPIType.ts: -------------------------------------------------------------------------------- 1 | export type WalletAPIType = 'keplr' | 'leap' 2 | 3 | export function isWalletAPIType(x: String): boolean { 4 | return x === 'keplr' || x === 'leap' 5 | } 6 | -------------------------------------------------------------------------------- /src/types/WrappingMode.ts: -------------------------------------------------------------------------------- 1 | export type WrappingMode = 'wrap' | 'unwrap' 2 | 3 | export function isWrappingMode(x: String): boolean { 4 | return x === 'wrap' || x === 'unwrap' 5 | } 6 | -------------------------------------------------------------------------------- /src/utils/env.d.ts: -------------------------------------------------------------------------------- 1 | interface ImportMeta { 2 | readonly env: ImportMetaEnv 3 | } 4 | -------------------------------------------------------------------------------- /src/utils/tokens.ts: -------------------------------------------------------------------------------- 1 | import { Token, tokens } from './config' 2 | 3 | export const scrtToken: Token = tokens.find((token) => token.name === 'SCRT') 4 | -------------------------------------------------------------------------------- /src/utils/useHoverOutside.ts: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react' 2 | 3 | export function useHoverOutside(ref: any, handler: any) { 4 | useEffect(() => { 5 | const listener = (event: any) => { 6 | if (!ref.current || ref.current.contains(event.target)) { 7 | return 8 | } 9 | handler(event) 10 | } 11 | document.addEventListener('mouseover', listener) 12 | document.addEventListener('touchstart', listener) 13 | return () => { 14 | document.removeEventListener('mouseover', listener) 15 | document.removeEventListener('touchstart', listener) 16 | } 17 | }, [ref, handler]) 18 | } 19 | -------------------------------------------------------------------------------- /src/utils/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// <reference types="vite/client" /> 2 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "lib": ["DOM", "DOM.Iterable", "ESNext"], 5 | "allowJs": false, 6 | "skipLibCheck": true, 7 | "esModuleInterop": false, 8 | "allowSyntheticDefaultImports": true, 9 | "strict": true, 10 | "strictNullChecks": false, 11 | "forceConsistentCasingInFileNames": true, 12 | "module": "ESNext", 13 | "moduleResolution": "Node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "noEmit": true, 17 | "jsx": "react-jsx", 18 | "baseUrl": "src" 19 | }, 20 | "include": ["src"] 21 | } 22 | -------------------------------------------------------------------------------- /vite-plugin-whip-003.ts: -------------------------------------------------------------------------------- 1 | import { Plugin } from 'vite' 2 | 3 | export function whip003(): Plugin { 4 | return { 5 | name: 'build-tokens', 6 | async buildStart(options) { 7 | const { chains, tokens, snips } = await import('./src/utils/config') 8 | 9 | const whip003 = { 10 | chains: {}, 11 | contracts: {} 12 | } 13 | 14 | // add chain definitions 15 | for (const chain of Object.values(chains)) { 16 | whip003.chains[chain.chain_id] = { 17 | namespace: 'cosmos', 18 | reference: chain.chain_id, 19 | label: chain.chain_name, 20 | bech32s: chain.bech32_prefix 21 | } 22 | } 23 | 24 | // add IBC tokens 25 | for (const token of Object.values(tokens)) { 26 | whip003.contracts[token.name] = { 27 | chain: 'cosmos:secret-4', 28 | address: token.address, 29 | label: 30 | 1 === token.withdrawals.length 31 | ? token.deposits[0].chain_name 32 | : 'SCRT' === token.name 33 | ? 'Secret Network' 34 | : token.name, 35 | interfaces: { 36 | snip20: { 37 | symbol: token.name, 38 | decimals: token.decimals, 39 | coingeckoId: token.coingecko_id 40 | } 41 | } 42 | } 43 | } 44 | 45 | // add IBC tokens 46 | for (const token of Object.values(tokens)) { 47 | whip003.contracts[token.name] = { 48 | chain: 'cosmos:secret-4', 49 | address: token.address, 50 | label: 51 | 1 === token.withdrawals.length 52 | ? token.deposits[0].chain_name 53 | : 'SCRT' === token.name 54 | ? 'Secret Network' 55 | : token.name, 56 | interfaces: { 57 | snip20: { 58 | symbol: token.name, 59 | decimals: token.decimals, 60 | coingeckoId: token.coingecko_id 61 | } 62 | } 63 | } 64 | } 65 | 66 | // add SNIPs 67 | for (const snip of Object.values(snips)) { 68 | whip003.contracts[snip.name] = { 69 | chain: 'cosmos:secret-4', 70 | address: snip.address, 71 | label: snip.coingecko_id 72 | ? snip.coingecko_id.replace(/-/g, ' ').replace(/\b[a-z]/g, (s) => s.toUpperCase()) 73 | : snip.name, 74 | interfaces: { 75 | snip20: { 76 | symbol: snip.name, 77 | decimals: snip.decimals, 78 | coingeckoId: snip.coingecko_id 79 | } 80 | } 81 | } 82 | } 83 | 84 | // emit the whip-003 JSON definition 85 | this.emitFile({ 86 | type: 'asset', 87 | fileName: 'whip-003.json', 88 | source: JSON.stringify(whip003, null, '\t') 89 | }) 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | import tsconfigPaths from 'vite-tsconfig-paths' 4 | import { whip003 } from './vite-plugin-whip-003' 5 | 6 | export default defineConfig({ 7 | plugins: [ 8 | { ...react() }, 9 | { ...tsconfigPaths() }, 10 | { 11 | ...whip003(), 12 | apply: 'build' 13 | } 14 | ], 15 | server: { 16 | host: true, 17 | port: 3000 18 | }, 19 | resolve: { 20 | alias: [ 21 | { 22 | find: '@buf/evmos_evmos.bufbuild_es/evmos/vesting/v1/tx_pb.js', 23 | replacement: '@buf/evmos_evmos.bufbuild_es/evmos/vesting/v2/tx_pb.js' 24 | }, 25 | { 26 | find: '@buf/evmos_evmos.bufbuild_es/evmos/revenue/v1/tx_pb.js', 27 | replacement: '@evmos/proto/dist/proto/evmos/revenue/v1/tx.js' 28 | } 29 | ] 30 | }, 31 | build: { 32 | minify: 'esbuild' 33 | } 34 | }) 35 | --------------------------------------------------------------------------------