> {
39 | const response = await this._api({
40 | method: 'POST',
41 | data: {
42 | app: this.APP_ID,
43 | method: this.APP_METHOD,
44 | nSign: this.nSign,
45 | params: requestParams,
46 | },
47 | })
48 |
49 | if (response.status !== 200) {
50 | return new Error('Unable to reach the Muon Network.')
51 | }
52 | return response.data
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/components/Web3ReactManager/index.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react'
2 | import { useWeb3React } from '@web3-react/core'
3 |
4 | import { network } from '../../connectors'
5 | import { useEagerConnect, useInactiveListener } from 'hooks/useWeb3'
6 | import { NETWORK_CONTEXT_NAME } from 'constants/misc'
7 |
8 | export default function Web3ReactManager({ children }: { children: JSX.Element }) {
9 | const { active } = useWeb3React()
10 | const { active: networkActive, error: networkError, activate: activateNetwork } = useWeb3React(NETWORK_CONTEXT_NAME)
11 |
12 | // try to eagerly connect to an injected provider, if it exists and has granted access already
13 | const triedEager = useEagerConnect()
14 |
15 | useEffect(() => {
16 | if (triedEager && !networkActive && !networkError && !active) {
17 | activateNetwork(network)
18 | }
19 | }, [triedEager, networkActive, networkError, activateNetwork, active])
20 |
21 | // when there's no account connected, react to logins (broadly speaking) on the injected provider, if it exists
22 | useInactiveListener(!triedEager)
23 |
24 | if (!triedEager) {
25 | return null
26 | }
27 |
28 | // if the account context isn't active, and there's an error on the network context, it's an irrecoverable error
29 | if (!active && networkError) {
30 | console.log('Oops! An unknown error occurred. Please refresh the page, or visit from another browser or device.')
31 | }
32 |
33 | return children
34 | }
35 |
--------------------------------------------------------------------------------
/src/components/Icons/Copy.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | export default function Copy({ size = 12, ...rest }: { size?: number; [x: string]: any }) {
4 | return (
5 |
23 | )
24 | }
25 |
--------------------------------------------------------------------------------
/public/static/images/footer/Github.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/constants/chains.ts:
--------------------------------------------------------------------------------
1 | // We allow the user to connect with these chains, so we can force them to change to Fantom.
2 | // E.g. if the user's chain is not in the list, web3react will deny connection and then we can't change to Fantom.
3 | export enum SupportedChainId {
4 | MAINNET = 1,
5 | ROPSTEN = 3,
6 | RINKEBY = 4,
7 | GOERLI = 5,
8 | KOVAN = 42,
9 |
10 | TELOS = 40,
11 | XDAI = 100,
12 | FUSE = 122,
13 | CELO = 42220,
14 |
15 | BSC = 56,
16 | BSC_TESTNET = 97,
17 |
18 | OKEX_TESTNET = 65,
19 | OKEX = 66,
20 |
21 | HECO = 128,
22 | HECO_TESTNET = 256,
23 |
24 | POLYGON = 137,
25 | POLYGON_TESTNET = 80001,
26 |
27 | FANTOM = 250,
28 | FANTOM_TESTNET = 4002,
29 |
30 | MOONRIVER = 1285,
31 | MOONBEAM_TESTNET = 1287,
32 |
33 | ARBITRUM = 42161,
34 | ARBITRUM_TESTNET = 79377087078960,
35 |
36 | AVALANCHE_TESTNET = 43113,
37 | AVALANCHE = 43114,
38 |
39 | HARMONY = 1666600000,
40 | HARMONY_TESTNET = 1666700000,
41 |
42 | PALM = 11297108109,
43 | PALM_TESTNET = 11297108099,
44 | }
45 |
46 | export const SUPPORTED_CHAIN_IDS: SupportedChainId[] = Object.values(SupportedChainId).filter(
47 | (id) => typeof id === 'number'
48 | ) as SupportedChainId[]
49 |
50 | export const SolidlyChains = [SupportedChainId.FANTOM, SupportedChainId.ARBITRUM]
51 |
52 | export const FALLBACK_CHAIN_ID = SupportedChainId.FANTOM
53 |
54 | export const NETWORK_URLS: { [chainId: number]: string } = {
55 | [SupportedChainId.FANTOM]: 'https://rpc.ftm.tools',
56 | }
57 |
--------------------------------------------------------------------------------
/public/static/images/tokens/lqdr.svg:
--------------------------------------------------------------------------------
1 |
11 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "@typescript-eslint/parser",
3 | "parserOptions": {
4 | "ecmaVersion": 2020,
5 | "sourceType": "module",
6 | "ecmaFeatures": {
7 | "jsx": true
8 | }
9 | },
10 | "extends": [
11 | "next",
12 | "plugin:react/recommended",
13 | "plugin:@typescript-eslint/recommended",
14 | "plugin:react-hooks/recommended",
15 | "plugin:prettier/recommended",
16 | "prettier"
17 | ],
18 | "plugins": ["unused-imports"],
19 | "rules": {
20 | "unused-imports/no-unused-imports": "error",
21 | "@typescript-eslint/explicit-function-return-type": "off",
22 | "@typescript-eslint/no-explicit-any": "off",
23 | "@typescript-eslint/ban-ts-comment": "off",
24 | "@typescript-eslint/ban-ts-ignore": "off",
25 | "@typescript-eslint/explicit-module-boundary-types": "off",
26 | "react/react-in-jsx-scope": "off",
27 | "object-shorthand": ["error", "always"],
28 | "prettier/prettier": [
29 | "error", {
30 | "endOfLine": "auto"
31 | }
32 | ],
33 | "no-restricted-imports": [
34 | "error",
35 | {
36 | "paths": [
37 | {
38 | "name": "lodash",
39 | "message": "Please import from 'lodash/module' directly to support tree-shaking."
40 | },
41 | {
42 | "name": "ethers",
43 | "message": "Please import from '@ethersproject/module' directly to support tree-shaking."
44 | }
45 | ]
46 | }
47 | ]
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/components/StableCoin/Navigation/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import styled from 'styled-components'
3 |
4 | const Wrapper = styled.div`
5 | display: flex;
6 | flex-flow: row nowrap;
7 | justify-content: flex-start;
8 | gap: 20px;
9 | margin-left: 30px;
10 | `
11 |
12 | const Item = styled.div<{
13 | selected: boolean
14 | }>`
15 | font-size: 15px;
16 | transition: all 0.3s ease;
17 | border-bottom: 1px solid ${({ selected, theme }) => (selected ? theme.text1 : 'transparent')};
18 | color: ${({ selected, theme }) => (selected ? theme.text1 : theme.text3)};
19 | &:hover {
20 | cursor: pointer;
21 | }
22 | `
23 |
24 | export enum NavigationTypes {
25 | MINT = 'MINT',
26 | SWAP = 'SWAP',
27 | REDEEM = 'REDEEM',
28 | }
29 |
30 | const NavigationLabels = {
31 | [NavigationTypes.MINT]: 'Mint',
32 | [NavigationTypes.SWAP]: 'Swap',
33 | [NavigationTypes.REDEEM]: 'Redeem',
34 | }
35 |
36 | export default function Navigation({
37 | selected,
38 | setSelected,
39 | }: {
40 | selected: string
41 | setSelected: (value: NavigationTypes) => void
42 | }) {
43 | return (
44 |
45 | {(Object.keys(NavigationTypes) as Array).map((key, index) => {
46 | const label = NavigationLabels[key]
47 | return (
48 | - setSelected(NavigationTypes[key])} key={index}>
49 | {label}
50 |
51 | )
52 | })}
53 |
54 | )
55 | }
56 |
--------------------------------------------------------------------------------
/src/components/Web3Network/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useMemo } from 'react'
2 | import styled from 'styled-components'
3 | import Image from 'next/image'
4 |
5 | import useWeb3React from 'hooks/useWeb3'
6 | import { ChainInfo } from 'constants/chainInfo'
7 |
8 | import { NavButton } from 'components/Button'
9 | import { SolidlyChains } from 'constants/chains'
10 |
11 | const Button = styled(NavButton)`
12 | background: ${({ theme }) => theme.bg1};
13 | justify-content: space-between;
14 | gap: 5px;
15 |
16 | &:focus,
17 | &:hover {
18 | cursor: default;
19 | border: 1px solid ${({ theme }) => theme.text3};
20 | }
21 |
22 | ${({ theme }) => theme.mediaWidth.upToMedium`
23 | & > * {
24 | &:nth-child(2) {
25 | display: none;
26 | }
27 | }
28 | `};
29 | `
30 |
31 | const Text = styled.p`
32 | width: fit-content;
33 | /* overflow: hidden; */
34 | text-overflow: ellipsis;
35 | white-space: nowrap;
36 | font-weight: bold;
37 | `
38 |
39 | export default function Web3Network() {
40 | const { account, chainId } = useWeb3React()
41 |
42 | const Chain = useMemo(() => {
43 | return chainId && chainId in ChainInfo ? ChainInfo[chainId] : null
44 | }, [chainId])
45 |
46 | if (!account || !chainId || !Chain || !SolidlyChains.includes(chainId)) {
47 | return null
48 | }
49 |
50 | return (
51 |
55 | )
56 | }
57 |
--------------------------------------------------------------------------------
/src/components/analytics/GoogleAnalyticsProvider.tsx:
--------------------------------------------------------------------------------
1 | import ReactGA from 'react-ga4'
2 | import { GaOptions, InitOptions, UaEventOptions } from 'react-ga4/types/ga4'
3 |
4 | /**
5 | * Google Analytics Provider containing all methods used throughout app to log events to Google Analytics.
6 | */
7 | export default class GoogleAnalyticsProvider {
8 | public sendEvent(event: string | UaEventOptions, params?: any) {
9 | ReactGA.event(event, params)
10 | }
11 |
12 | public initialize(
13 | GA_MEASUREMENT_ID: InitOptions[] | string,
14 | options?: {
15 | legacyDimensionMetric?: boolean
16 | nonce?: string
17 | testMode?: boolean
18 | gaOptions?: GaOptions | any
19 | gtagOptions?: any
20 | }
21 | ) {
22 | ReactGA.initialize(GA_MEASUREMENT_ID, options)
23 | }
24 |
25 | public set(fieldsObject: any) {
26 | ReactGA.set(fieldsObject)
27 | }
28 |
29 | public outboundLink(
30 | {
31 | label,
32 | }: {
33 | label: string
34 | },
35 | hitCallback: () => unknown
36 | ) {
37 | ReactGA.outboundLink({ label }, hitCallback)
38 | }
39 |
40 | public pageview(path?: string, _?: string[], title?: string) {
41 | ReactGA.pageview(path, _, title)
42 | }
43 |
44 | public ga(...args: any[]) {
45 | ReactGA.ga(...args)
46 | }
47 |
48 | public gaCommandSendTiming(timingCategory: any, timingVar: any, timingValue: any, timingLabel: any) {
49 | ReactGA._gaCommandSendTiming(timingCategory, timingVar, timingValue, timingLabel)
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 |
3 | # Logs
4 | logs
5 | *.log
6 | npm-debug.log*
7 | yarn-debug.log*
8 | yarn-error.log*
9 |
10 | # Runtime data
11 | pids
12 | *.pid
13 | *.seed
14 | *.pid.lock
15 |
16 | # Directory for instrumented libs generated by jscoverage/JSCover
17 | lib-cov
18 |
19 | # Coverage directory used by tools like istanbul
20 | coverage
21 |
22 | # nyc test coverage
23 | .nyc_output
24 |
25 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
26 | .grunt
27 |
28 | # Bower dependency directory (https://bower.io/)
29 | bower_components
30 |
31 | # node-waf configuration
32 | .lock-wscript
33 |
34 | # Compiled binary addons (https://nodejs.org/api/addons.html)
35 | build/Release
36 |
37 | # Dependency directories
38 | node_modules/
39 | jspm_packages/
40 |
41 | # TypeScript v1 declaration files
42 | typings/
43 |
44 | # vscode
45 | .vscode/
46 |
47 | # Optional npm cache directory
48 | .npm
49 |
50 | # Optional eslint cache
51 | .eslintcache
52 |
53 | # Optional REPL history
54 | .node_repl_history
55 |
56 | # Output of 'npm pack'
57 | *.tgz
58 |
59 | # Yarn Integrity file
60 | .yarn-integrity
61 |
62 | # dotenv environment variables file
63 | .env
64 | .env.development
65 |
66 | # parcel-bundler cache (https://parceljs.org/)
67 | .cache
68 |
69 | # next.js build output
70 | .next
71 | out/
72 |
73 | # nuxt.js build output
74 | .nuxt
75 |
76 | # vuepress build output
77 | .vuepress/dist
78 |
79 | # Serverless directories
80 | .serverless
81 |
82 | # FuseBox cache
83 | .fusebox/
84 |
85 | .DS_Store
86 |
--------------------------------------------------------------------------------
/src/state/user/reducer.ts:
--------------------------------------------------------------------------------
1 | import { createReducer } from '@reduxjs/toolkit'
2 |
3 | import { updateUserSlippageTolerance, updateMatchesDarkMode, updateUserDarkMode } from './actions'
4 |
5 | const currentTimestamp = () => new Date().getTime()
6 |
7 | export interface UserState {
8 | matchesDarkMode: boolean // whether the dark mode media query matches
9 | userDarkMode: boolean | null // the user's choice for dark mode or light mode
10 |
11 | // user defined slippage tolerance in percentages (userSlipperageTolerance of 80 means 80%)
12 | // TODO upgrade to a strongly typed version of this, similar to (but not exactly) like Uniswap's Percent type
13 | userSlippageTolerance: number | 'auto'
14 |
15 | timestamp: number
16 | }
17 |
18 | export const initialState: UserState = {
19 | matchesDarkMode: false,
20 | userDarkMode: true,
21 | userSlippageTolerance: 'auto',
22 | timestamp: currentTimestamp(),
23 | }
24 |
25 | export default createReducer(initialState, (builder) =>
26 | builder
27 | .addCase(updateUserDarkMode, (state, action) => {
28 | state.userDarkMode = action.payload.userDarkMode
29 | state.timestamp = currentTimestamp()
30 | })
31 | .addCase(updateMatchesDarkMode, (state, action) => {
32 | state.matchesDarkMode = action.payload.matchesDarkMode
33 | state.timestamp = currentTimestamp()
34 | })
35 | .addCase(updateUserSlippageTolerance, (state, action) => {
36 | state.userSlippageTolerance = action.payload.userSlippageTolerance
37 | state.timestamp = currentTimestamp()
38 | })
39 | )
40 |
--------------------------------------------------------------------------------
/src/components/InfoHeader/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import styled from 'styled-components'
3 | import { Info } from 'components/Icons'
4 | import { Close as CloseIcon } from 'components/Icons'
5 |
6 | const Wrapper = styled.div<{ bg?: string }>`
7 | width: 100%;
8 | display: flex;
9 | justify-content: center;
10 | background: ${({ theme, bg }) => (bg ? (bg === 'gray' ? theme.text3 : bg) : theme.primary6)};
11 | `
12 |
13 | const Value = styled.div`
14 | font-weight: 600;
15 | font-size: 12px;
16 | line-height: 16px;
17 | margin: 8px 24px;
18 | `
19 |
20 | const CloseIconWrapper = styled.button`
21 | position: absolute;
22 | padding: 5px;
23 | right: 25px;
24 | cursor: pointer;
25 |
26 | ${({ theme }) => theme.mediaWidth.upToSmall`
27 | right: 6px;
28 | `}
29 | `
30 |
31 | const InfoIcon = styled(Info)`
32 | color: ${({ theme }) => theme.white};
33 | margin-top: 6px;
34 | margin-right: -15px;
35 | cursor: default !important;
36 |
37 | ${({ theme }) => theme.mediaWidth.upToSmall`
38 | margin-left: 6px;
39 | `}
40 | `
41 |
42 | export default function InfoHeader({
43 | text,
44 | onClose,
45 | bg,
46 | hasInfoIcon,
47 | }: {
48 | text: string
49 | onClose: (status: boolean) => void
50 | bg?: string
51 | hasInfoIcon?: boolean
52 | }) {
53 | return (
54 |
55 | {hasInfoIcon && }
56 | {text}
57 | onClose(false)}>
58 |
59 |
60 |
61 | )
62 | }
63 |
--------------------------------------------------------------------------------
/src/utils/time.ts:
--------------------------------------------------------------------------------
1 | import dayjs from 'dayjs'
2 | import utc from 'dayjs/plugin/utc'
3 |
4 | dayjs.extend(utc)
5 |
6 | export enum RoundMode {
7 | ROUND_DOWN,
8 | ROUND_UP,
9 | }
10 |
11 | export function getDurationSeconds(targetDate: Date, ROUND_MODE: RoundMode): number {
12 | const now = dayjs.utc().unix()
13 | const then = dayjs.utc(targetDate).unix()
14 | const result = then - now
15 | return ROUND_MODE === RoundMode.ROUND_DOWN ? Math.floor(result) : Math.ceil(result)
16 | }
17 |
18 | export function getRemainingTime(timeStamp: number): {
19 | diff: number
20 | day: number
21 | hours: number
22 | minutes: number
23 | seconds: number
24 | } {
25 | const now = dayjs().utc()
26 | const endTime = dayjs.utc(timeStamp)
27 | const diff = endTime.diff(now)
28 |
29 | const day = endTime.diff(now, 'day')
30 | const hours = dayjs.utc(diff).hour()
31 | const minutes = dayjs.utc(diff).minute()
32 | const seconds = dayjs.utc(diff).second()
33 |
34 | return { diff, day, hours, minutes, seconds }
35 | }
36 |
37 | export function getTimeLength(timeLength: number): {
38 | hours: string
39 | minutes: string
40 | seconds: string
41 | fullLength: string
42 | } {
43 | const hours = dayjs.utc(timeLength).hour() + ' hr'
44 | const minutes = dayjs.utc(timeLength).minute() + ' min'
45 | const seconds = dayjs.utc(timeLength).second() + ' sec'
46 |
47 | let fullLength = ''
48 | if (hours[0] !== '0') fullLength += hours
49 | if (minutes[0] !== '0') fullLength += minutes
50 | if (seconds[0] !== '0') fullLength += seconds
51 |
52 | return { hours, minutes, seconds, fullLength }
53 | }
54 |
--------------------------------------------------------------------------------
/public/static/images/pages/clqdr/ic_clqdr.svg:
--------------------------------------------------------------------------------
1 |
15 |
--------------------------------------------------------------------------------
/src/components/Popups/index.tsx:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components'
2 |
3 | import { useActivePopups } from 'state/application/hooks'
4 | import useWindowSize from 'hooks/useWindowSize'
5 |
6 | import PopupItem from './PopupItem'
7 | import { Z_INDEX } from 'theme'
8 |
9 | const Container = styled.div`
10 | display: flex;
11 | flex-direction: column;
12 | justify-content: flex-end;
13 | position: fixed;
14 | height: auto;
15 | top: 75px;
16 | z-index: ${Z_INDEX.popover};
17 | `
18 |
19 | const ContainerLarge = styled(Container)`
20 | right: 30px;
21 | width: 300px;
22 | `
23 |
24 | const ContainerSmall = styled(Container)`
25 | margin-left: 50%;
26 | transform: translateX(-50%);
27 | width: 90vw;
28 | `
29 |
30 | export default function Popups() {
31 | const activePopups = useActivePopups()
32 | const { width } = useWindowSize()
33 |
34 | return (
35 | <>
36 | {typeof width == 'number' && width >= 500 ? (
37 |
38 | {activePopups.map((item) => {
39 | return (
40 |
41 | )
42 | })}
43 |
44 | ) : (
45 |
46 | {activePopups // reverse so new items up front
47 | .slice(0)
48 | .reverse()
49 | .map((item) => (
50 |
51 | ))}
52 |
53 | )}
54 | >
55 | )
56 | }
57 |
--------------------------------------------------------------------------------
/src/components/ReviewModal/ThemeSelector.tsx:
--------------------------------------------------------------------------------
1 | import React, { useMemo } from 'react'
2 | import styled from 'styled-components'
3 |
4 | import ThemeIconDark from 'components/Icons/ThemeIconDark'
5 | import ThemeIconLight from 'components/Icons/ThemeIconLight'
6 |
7 | const Wrapper = styled.div`
8 | display: flex;
9 | align-items: center;
10 | justify-content: space-between;
11 | padding: 10px 20px;
12 | margin-top: -10px;
13 | `
14 |
15 | const RightElement = styled.div`
16 | margin-right: 25px;
17 | cursor: pointer;
18 |
19 | &:hover {
20 | filter: brightness(0.75);
21 | }
22 | `
23 |
24 | const HorizontalLine = styled.div`
25 | height: 1px;
26 | background: ${({ theme }) => theme.border2};
27 | `
28 |
29 | const Title = styled.div`
30 | font-weight: 400;
31 | color: ${({ theme }) => theme.text2};
32 | ${({ theme }) => theme.mediaWidth.upToMedium`
33 | font-size:12px;
34 | `}
35 | `
36 |
37 | export default function ThemeSelector({
38 | title,
39 | amount,
40 | setAmount,
41 | }: {
42 | title: string
43 | amount: boolean
44 | setAmount: (value: boolean) => void
45 | }) {
46 | return useMemo(() => {
47 | return (
48 | <>
49 |
50 | {title}
51 | {
53 | setAmount(!amount)
54 | }}
55 | >
56 | {amount ? : }
57 |
58 |
59 |
60 | >
61 | )
62 | }, [title, amount, setAmount])
63 | }
64 |
--------------------------------------------------------------------------------
/src/constants/wallet.ts:
--------------------------------------------------------------------------------
1 | import { AbstractConnector } from '@web3-react/abstract-connector'
2 |
3 | import { injected, walletconnect, walletlink } from '../connectors'
4 |
5 | interface WalletInfo {
6 | readonly connector?: AbstractConnector
7 | readonly name: string
8 | readonly iconURL: StaticImageData
9 | readonly description: string
10 | readonly color: string
11 | readonly href?: string
12 | readonly primary?: true
13 | readonly mobile?: true
14 | readonly mobileOnly?: true
15 | }
16 |
17 | export const SUPPORTED_WALLETS: { [key: string]: WalletInfo } = {
18 | INJECTED: {
19 | connector: injected,
20 | name: 'Injected',
21 | iconURL: require('/public/static/images/wallets/injected.svg'),
22 | description: 'Injected web3 provider.',
23 | color: '#010101',
24 | primary: true,
25 | },
26 | METAMASK: {
27 | connector: injected,
28 | name: 'MetaMask',
29 | iconURL: require('/public/static/images/wallets/metamask.png'),
30 | description: 'Easy-to-use browser extension.',
31 | color: '#E8831D',
32 | },
33 | WALLET_CONNECT: {
34 | connector: walletconnect,
35 | name: 'WalletConnect',
36 | iconURL: require('/public/static/images/wallets/walletConnect.png'),
37 | description: 'Connect to Trust Wallet, Rainbow Wallet and more...',
38 | color: '#4196FC',
39 | mobile: true,
40 | },
41 | WALLET_LINK: {
42 | connector: walletlink,
43 | name: 'Coinbase Wallet',
44 | iconURL: require('/public/static/images/wallets/coinbaseWalletIcon.png'),
45 | description: 'Use Coinbase Wallet app on mobile device',
46 | color: '#315CF5',
47 | },
48 | }
49 |
--------------------------------------------------------------------------------
/public/static/images/LegacyDeiLogo.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/constants/stakings.ts:
--------------------------------------------------------------------------------
1 | import { Token } from '@sushiswap/core-sdk'
2 | import { BDEI_TOKEN, DEI_BDEI_LP_TOKEN } from 'constants/tokens'
3 |
4 | export type StakingType = {
5 | name: string
6 | pid: number
7 | token: Token
8 | provideLink?: string
9 | }
10 |
11 | export type vDeusStakingType = {
12 | id: number
13 | name: string
14 | pid: number
15 | apr: number
16 | lockDuration: number
17 | lpToken: string
18 | provideLink?: string
19 | }
20 |
21 | export const StakingPools: StakingType[] = [
22 | {
23 | name: 'bDEI',
24 | pid: 0,
25 | token: BDEI_TOKEN,
26 | provideLink: '/bdei',
27 | },
28 | {
29 | name: 'DEI-bDEI',
30 | pid: 1,
31 | token: DEI_BDEI_LP_TOKEN,
32 | provideLink: '/bdei',
33 | },
34 | ]
35 |
36 | export type UserDeposit = {
37 | nftId: number
38 | poolId: number
39 | amount: number
40 | depositTimestamp: number
41 | isWithdrawn: boolean
42 | isExited: boolean
43 | }
44 |
45 | export const vDeusStakingPools: vDeusStakingType[] = [
46 | {
47 | id: 0,
48 | name: '3 Months',
49 | pid: 0,
50 | apr: 10,
51 | lpToken: '0x24651a470D08009832d62d702d1387962A2E5d60',
52 | lockDuration: 180,
53 | provideLink: '/redeem',
54 | },
55 | {
56 | id: 1,
57 | name: '6 Months',
58 | pid: 1,
59 | lpToken: '0x65875f75d5CDEf890ea97ADC43E216D3f0c2b2D8',
60 | apr: 20,
61 | lockDuration: 360,
62 | provideLink: '/redeem',
63 | },
64 | {
65 | id: 2,
66 | name: '1 Year',
67 | pid: 2,
68 | lpToken: '0xCf18eCa0EaC101eb47828BFd460D1922000213db',
69 | apr: 40,
70 | lockDuration: 720,
71 | provideLink: '/redeem',
72 | },
73 | ]
74 |
--------------------------------------------------------------------------------
/public/static/images/tokens/deus.svg:
--------------------------------------------------------------------------------
1 |
10 |
--------------------------------------------------------------------------------
/public/static/images/tokens/usdc.svg:
--------------------------------------------------------------------------------
1 |
15 |
--------------------------------------------------------------------------------
/src/components/Icons/Redeem.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | export default function Redeem({ size = 8, ...rest }: { size?: number; [x: string]: any }) {
4 | return (
5 |
13 | )
14 | }
15 |
--------------------------------------------------------------------------------
/src/hooks/useCurrencyLogo.ts:
--------------------------------------------------------------------------------
1 | import { useMemo } from 'react'
2 |
3 | import NotFound from '/public/static/images/fallback/not_found.png'
4 | import DEI_LOGO from '/public/static/images/tokens/dei.svg'
5 | import DEUS_LOGO from '/public/static/images/tokens/deus.svg'
6 | import USDC_LOGO from '/public/static/images/tokens/usdc.svg'
7 | import BDEI_LOGO from '/public/static/images/tokens/bdei.svg'
8 | import LQDR_ICON from '/public/static/images/tokens/lqdr.svg'
9 | import CLQDR_ICON from '/public/static/images/tokens/clqdr.svg'
10 |
11 | const LogoMap: { [contractOrSymbol: string]: string } = {
12 | // symbols
13 | FTM: 'https://assets.spooky.fi/tokens/FTM.png',
14 | DEI: DEI_LOGO,
15 | DEUS: DEUS_LOGO,
16 | // contracts
17 | // make sure these values are checksummed! https://ethsum.netlify.app/
18 | '0x21be370D5312f44cB42ce377BC9b8a0cEF1A4C83': 'https://assets.spooky.fi/tokens/wFTM.png', // wFTM
19 | '0x04068DA6C83AFCFA0e13ba15A6696662335D5B75': USDC_LOGO,
20 | '0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8': USDC_LOGO,
21 | '0xDE1E704dae0B4051e80DAbB26ab6ad6c12262DA0': DEI_LOGO,
22 | '0xDE5ed76E7c05eC5e4572CfC88d1ACEA165109E44': DEUS_LOGO,
23 | '0x05f6ea7F80BDC07f6E0728BbBBAbebEA4E142eE8': BDEI_LOGO,
24 | '0x10b620b2dbAC4Faa7D7FFD71Da486f5D44cd86f9': LQDR_ICON,
25 | '0x814c66594a22404e101FEcfECac1012D8d75C156': CLQDR_ICON,
26 | usdc: USDC_LOGO,
27 | deus: DEUS_LOGO,
28 | }
29 |
30 | export default function useCurrencyLogo(contractOrSymbol?: string): string {
31 | return useMemo(() => {
32 | try {
33 | if (contractOrSymbol && contractOrSymbol in LogoMap) {
34 | return LogoMap[contractOrSymbol]
35 | }
36 | return `https://assets.spooky.fi/tokens/${contractOrSymbol}.png`
37 | } catch (err) {
38 | return NotFound.src
39 | }
40 | }, [contractOrSymbol])
41 | }
42 |
--------------------------------------------------------------------------------
/src/components/Icons/Error.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | export default function Error({ size = 12, ...rest }: { size?: number; [x: string]: any }) {
4 | return (
5 |
13 | )
14 | }
15 |
--------------------------------------------------------------------------------
/src/components/Icons/Mint.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | export default function Mint({ size, ...rest }: { size: number; [x: string]: any }) {
4 | return (
5 |
11 | )
12 | }
13 |
--------------------------------------------------------------------------------
/src/components/InvalidContext/index.tsx:
--------------------------------------------------------------------------------
1 | import { useMemo } from 'react'
2 |
3 | import { useWalletModalToggle } from 'state/application/hooks'
4 | import { useSupportedChainId } from 'hooks/useSupportedChainId'
5 | import useRpcChangerCallback from 'hooks/useRpcChangerCallback'
6 | import useWeb3React from 'hooks/useWeb3'
7 |
8 | import { PrimaryButton } from 'components/Button'
9 | import { SupportedChainId } from 'constants/chains'
10 |
11 | export enum ContextError {
12 | ACCOUNT,
13 | CHAIN_ID,
14 | VALID,
15 | }
16 |
17 | export function useInvalidContext() {
18 | const { chainId, account } = useWeb3React()
19 | const isSupportedChainId = useSupportedChainId()
20 | return useMemo(
21 | () =>
22 | !account || !chainId ? ContextError.ACCOUNT : !isSupportedChainId ? ContextError.CHAIN_ID : ContextError.VALID,
23 | [account, chainId, isSupportedChainId]
24 | )
25 | }
26 |
27 | export function InvalidContext({ connectText }: { connectText?: string }) {
28 | const invalidContext = useInvalidContext()
29 | const toggleWalletModal = useWalletModalToggle()
30 | const rpcChangerCallback = useRpcChangerCallback()
31 |
32 | return useMemo(() => {
33 | if (invalidContext === ContextError.ACCOUNT) {
34 | return (
35 | <>
36 | {connectText ?? 'Connect your Wallet'}
37 | Connect Wallet
38 | >
39 | )
40 | }
41 | if (invalidContext === ContextError.CHAIN_ID) {
42 | return (
43 | <>
44 | You are not connected to the Fantom Opera Network.
45 | rpcChangerCallback(SupportedChainId.FANTOM)}>Switch to Fantom
46 | >
47 | )
48 | }
49 | return null
50 | }, [invalidContext, connectText, rpcChangerCallback, toggleWalletModal])
51 | }
52 |
--------------------------------------------------------------------------------
/src/components/ReviewModal/index.tsx:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components'
2 |
3 | import { OptionButton } from 'components/Button'
4 |
5 | export const DefaultOptionButton = styled(OptionButton)`
6 | display: inline-flex;
7 | margin: 1px;
8 | margin-right: 10px;
9 |
10 | ${({ theme }) => theme.mediaWidth.upToSmall`
11 | margin-right: 5px;
12 | white-space: normal;
13 | `}
14 | `
15 |
16 | export const CustomOption = styled(DefaultOptionButton)`
17 | justify-content: flex-end;
18 | width: 85px;
19 | margin-right: 0px;
20 | padding: 0 5px;
21 | border-radius: 8px;
22 | `
23 |
24 | export const InputAmount = styled.input.attrs({ type: 'number' })<{ active?: boolean }>`
25 | color: ${({ theme }) => theme.text1};
26 | border: 0;
27 | outline: none;
28 | width: 100%;
29 | margin-right: 2px;
30 | margin-left: 2px;
31 | font-size: 0.95rem;
32 | background: transparent;
33 | ${({ active, theme }) =>
34 | active &&
35 | `
36 | color: ${theme.text2};
37 | `}
38 | `
39 |
40 | export const AmountsWrapper = styled.div<{ hasCustom?: boolean }>`
41 | display: flex;
42 | flex-wrap: nowrap;
43 | justify-content: space-between;
44 | margin-top: 16px;
45 | `
46 |
47 | export const AmountsInnerWrapper = styled.div<{ hasCustom?: boolean }>`
48 | ${({ hasCustom, theme }) =>
49 | !hasCustom &&
50 | theme.mediaWidth.upToSmall`
51 | width: 100%;
52 | display: flex;
53 | flex-wrap: nowrap;
54 | justify-content: space-between;
55 | `}
56 | `
57 |
58 | export const QuestionMarkWrap = styled.div`
59 | margin-left: 6px;
60 | display: inline;
61 | background: transparent;
62 | `
63 |
64 | export const Title = styled.div`
65 | font-weight: 400;
66 | color: ${({ theme }) => theme.text2};
67 | display: flex;
68 | direction: row;
69 | justify-content: space-between;
70 |
71 | ${({ theme }) => theme.mediaWidth.upToMedium`
72 | font-size: 12px;
73 | `}
74 | `
75 |
--------------------------------------------------------------------------------
/src/hooks/useRpcChangerCallback.ts:
--------------------------------------------------------------------------------
1 | import { useCallback } from 'react'
2 |
3 | import useWeb3React from './useWeb3'
4 | import { ChainInfo } from 'constants/chainInfo'
5 | import { SupportedChainId } from 'constants/chains'
6 |
7 | export default function useRpcChangerCallback() {
8 | const { account, chainId, library } = useWeb3React()
9 |
10 | return useCallback(
11 | async (targetChainId: SupportedChainId) => {
12 | if (!chainId) return false
13 | if (!targetChainId || !ChainInfo[targetChainId]) return false
14 | if (targetChainId === chainId) return true
15 | if (!window.ethereum) return false
16 |
17 | try {
18 | await library?.send('wallet_switchEthereumChain', [{ chainId: ChainInfo[targetChainId].chainId }])
19 | return true
20 | } catch (switchError) {
21 | // This error code indicates that the chain has not been added to MetaMask.
22 | if (switchError.code === 4902) {
23 | try {
24 | const params = {
25 | chainId: ChainInfo[targetChainId].chainId,
26 | chainName: ChainInfo[targetChainId].chainName,
27 | nativeCurrency: ChainInfo[targetChainId].nativeCurrency,
28 | rpcUrls: [ChainInfo[targetChainId].rpcUrl],
29 | }
30 | await library?.send('wallet_addEthereumChain', [params, account])
31 | return true
32 | } catch (addError) {
33 | console.log('Something went wrong trying to add a new network RPC: ')
34 | console.error(addError)
35 | return false
36 | }
37 | }
38 | // handle other "switch" errors
39 | console.log('Unknown error occured when trying to change the network RPC: ')
40 | console.error(switchError)
41 | return false
42 | }
43 | // eslint-disable-next-line react-hooks/exhaustive-deps
44 | },
45 | [chainId, library, account]
46 | )
47 | }
48 |
--------------------------------------------------------------------------------
/public/static/images/footer/Discord.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/pages/_app.tsx:
--------------------------------------------------------------------------------
1 | import { Provider as ReduxProvider } from 'react-redux'
2 | import { Web3ReactProvider } from '@web3-react/core'
3 | import { ModalProvider } from 'styled-react-modal'
4 | import dynamic from 'next/dynamic'
5 | import type { AppProps } from 'next/app'
6 | import { Toaster } from 'react-hot-toast'
7 |
8 | import Web3ReactManager from '../components/Web3ReactManager'
9 | import ThemeProvider, { ThemedGlobalStyle } from '../theme'
10 | import Popups from '../components/Popups'
11 | import Layout from '../components/Layout'
12 | import { ModalBackground } from '../components/Modal'
13 | import { useAnalyticsReporter } from '../components/analytics'
14 | import LiveChat from 'components/LiveChat'
15 |
16 | import store from '../state'
17 | import { getLibrary } from '../utils/library'
18 |
19 | const Updaters = dynamic(() => import('../state/updaters'), { ssr: false })
20 | const Web3ProviderNetwork = dynamic(() => import('../components/Web3ProviderNetwork'), {
21 | ssr: false,
22 | })
23 |
24 | if (typeof window !== 'undefined' && !!window.ethereum) {
25 | window.ethereum.autoRefreshOnNetworkChange = false
26 | }
27 |
28 | export default function MyApp({ Component, pageProps }: AppProps) {
29 | useAnalyticsReporter()
30 | return (
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 | )
52 | }
53 |
--------------------------------------------------------------------------------
/src/theme/styled.d.ts:
--------------------------------------------------------------------------------
1 | import { ThemedCssFunction } from 'styled-components/macro'
2 | import { SupportedThemes } from 'theme'
3 |
4 | export type Color = string
5 | export interface Colors {
6 | themeName: SupportedThemes
7 |
8 | // base
9 | white: Color
10 | black: Color
11 |
12 | // text
13 | text1: Color
14 | text2: Color
15 | text3: Color
16 | text4: Color
17 |
18 | // backgrounds
19 | bg0: Color
20 | bg1: Color
21 | bg2: Color
22 | bg3: Color
23 | bg4: Color
24 | bg5: Color
25 |
26 | // borders
27 | border1: Color
28 | border2: Color
29 | border3: Color
30 |
31 | specialBG1: Color
32 | specialBG2: Color
33 |
34 | //blues
35 | primary1: Color
36 | primary2: Color
37 | primary3: Color
38 | primary4: Color
39 | primary5: Color
40 | primary6: Color
41 | primary7: Color
42 |
43 | primaryText1: Color
44 |
45 | // pinks
46 | secondary1: Color
47 | secondary2: Color
48 |
49 | // other
50 | red1: Color
51 | red2: Color
52 | red3: Color
53 | green1: Color
54 | yellow1: Color
55 | yellow2: Color
56 | yellow3: Color
57 | yellow4: Color
58 | blue1: Color
59 | blue2: Color
60 | darkPink: Color
61 |
62 | error: Color
63 | success: Color
64 | warning: Color
65 |
66 | deusColor: Color
67 | deusColorReverse: Color
68 | deiColor: Color
69 | cLqdrColor: Color
70 | deiPrimaryColor: Color
71 | deiSecondaryColor: Color
72 | }
73 |
74 | export type Shadow = string
75 | export interface Shadows {
76 | shadow1: Shadow
77 | boxShadow1: Shadow
78 | boxShadow2: Shadow
79 | }
80 |
81 | declare module 'styled-components' {
82 | export interface DefaultTheme extends Colors, Shadows {
83 | grids: Grids
84 |
85 | // media queries
86 | mediaWidth: {
87 | upToExtraSmall: ThemedCssFunction
88 | upToSmall: ThemedCssFunction
89 | upToMedium: ThemedCssFunction
90 | upToLarge: ThemedCssFunction
91 | upToExtraLarge: ThemedCssFunction
92 | }
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/src/components/Pagination/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import styled from 'styled-components'
3 | import ReactPaginate from 'react-paginate'
4 |
5 | const Wrapper = styled.div`
6 | margin: 0.8rem auto;
7 | font-size: 0.8rem;
8 | .pagination {
9 | display: flex;
10 | justify-content: center;
11 | list-style-type: none;
12 | align-items: flex-end;
13 | margin: 0;
14 | padding: 0;
15 | overflow: hidden;
16 | & > li {
17 | float: left;
18 | }
19 | & > li a {
20 | display: block;
21 | text-align: bottom;
22 | padding: 1rem;
23 | text-decoration: none;
24 | :hover {
25 | cursor: pointer;
26 | }
27 | ${({ theme }) => theme.mediaWidth.upToSmall`
28 | padding: 1rem 0.8rem;
29 | `}
30 | }
31 | }
32 | .break {
33 | pointer-events: none;
34 | }
35 | .active {
36 | & > * {
37 | font-size: 1rem;
38 | font-weight: 700;
39 | background: ${({ theme }) => theme.bg1};
40 | color: ${({ theme }) => theme.darkPink};
41 | }
42 | }
43 | ${({ theme }) => theme.mediaWidth.upToSmall`
44 | font-size: 0.7rem;
45 | `}
46 | `
47 |
48 | export default function Pagination({
49 | pageCount,
50 | onPageChange,
51 | count,
52 | }: {
53 | pageCount: number
54 | onPageChange: ({ selected }: { selected: number }) => void
55 | count: number
56 | }) {
57 | return (
58 |
59 | '}
62 | breakLabel={''}
63 | breakClassName={'break'}
64 | pageCount={pageCount}
65 | marginPagesDisplayed={0} // how much to show at the beginning and end (using 2) => Previous 1, 2, .. , 9, 10 Next
66 | pageRangeDisplayed={4} // how much to show left and right from the current page (using 2) => Previous 1, 2 .. 9 10 (11) 12 13 ... 23 24 Next
67 | onPageChange={onPageChange}
68 | containerClassName={'pagination'}
69 | activeClassName={'active'}
70 | />
71 |
72 | )
73 | }
74 |
--------------------------------------------------------------------------------
/src/components/Layout/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import styled from 'styled-components'
3 |
4 | import { useInjectedAddress } from 'hooks/useInjectedAddress'
5 |
6 | import NavBar from './NavBar'
7 | import Warning from './Warning'
8 | import Footer from 'components/Disclaimer'
9 |
10 | const Wrapper = styled.div`
11 | display: flex;
12 | height: 100%;
13 | flex-flow: column nowrap;
14 | `
15 |
16 | const Content = styled.div`
17 | position: relative;
18 | min-height: calc(970px - 55px - 67px);
19 | overflow: scroll;
20 | padding-bottom: 50px;
21 |
22 | ${({ theme }) => theme.mediaWidth.upToSmall`
23 | padding-bottom: 30px;
24 | height:100%;
25 | `}
26 |
27 | @media screen and (min-height: 1040px) {
28 | height: calc(100vh - 55px - 60px);
29 | }
30 | `
31 |
32 | export default function Layout({ children }: { children: React.ReactNode }) {
33 | const hasInjected = useInjectedAddress()
34 |
35 | return (
36 |
37 |
38 | {hasInjected && (
39 | {`❌ You are in "READ-ONLY" mode. Please do not confirm any transactions! ❌ `}
40 | )}
41 | {/*
42 | <>
43 | The DEUS team is actively monitoring the{' '}
44 |
48 | USDC situation
49 |
50 | . We started to actively diversify USDC backing of DEI into multiple stables. For more info, read the
51 | announcement on{' '}
52 |
56 | Discord
57 |
58 | .
59 | >
60 | */}
61 | {children}
62 |
63 |
64 | )
65 | }
66 |
--------------------------------------------------------------------------------
/src/components/ReviewModal/TokenBox.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import styled from 'styled-components'
3 | import { Token } from '@sushiswap/core-sdk'
4 | import { isMobile } from 'react-device-detect'
5 |
6 | import useCurrencyLogo from 'hooks/useCurrencyLogo'
7 | import ImageWithFallback from 'components/ImageWithFallback'
8 | import { RowBetween } from 'components/Row'
9 |
10 | const Wrapper = styled(RowBetween).attrs({
11 | align: 'center',
12 | })`
13 | display: flex;
14 | border-radius: 16px;
15 | background: ${({ theme }) => theme.bg2};
16 | color: ${({ theme }) => theme.text2};
17 | white-space: nowrap;
18 | height: 60px;
19 | gap: 10px;
20 | padding: 0px 1rem;
21 | margin: 0 1rem;
22 |
23 | ${({ theme }) => theme.mediaWidth.upToSmall`
24 | padding: 0.5rem;
25 | `}
26 | `
27 |
28 | const Row = styled.div`
29 | display: flex;
30 | flex-flow: row nowrap;
31 | align-items: flex-start;
32 | font-size: 1.5rem;
33 | align-items: center;
34 | ${({ theme }) => theme.mediaWidth.upToMedium`
35 | gap: 3px;
36 | `}
37 | `
38 |
39 | const Balance = styled.div`
40 | font-size: 1rem;
41 | text-align: center;
42 | color: ${({ theme }) => theme.text1};
43 | `
44 |
45 | const Symbol = styled.p`
46 | margin-left: 8px;
47 | font-size: 1rem;
48 | color: ${({ theme }) => theme.text1};
49 | `
50 |
51 | export default function TokenBox({ currency, value }: { currency: Token; value: string }) {
52 | const logo = useCurrencyLogo(currency?.address)
53 |
54 | function getImageSize() {
55 | return isMobile ? 20 : 24
56 | }
57 |
58 | return (
59 | <>
60 |
61 | {value}
62 |
63 |
64 |
71 | {currency?.symbol}
72 |
73 |
74 |
75 | >
76 | )
77 | }
78 |
--------------------------------------------------------------------------------
/src/state/user/hooks.ts:
--------------------------------------------------------------------------------
1 | import { useCallback, useMemo } from 'react'
2 | import { shallowEqual } from 'react-redux'
3 | import { useAppDispatch, useAppSelector } from 'state'
4 |
5 | import { updateUserSlippageTolerance, updateUserDarkMode } from './actions'
6 |
7 | export function useIsDarkMode(): boolean {
8 | const { userDarkMode, matchesDarkMode } = useAppSelector(
9 | ({ user: { matchesDarkMode, userDarkMode } }) => ({
10 | userDarkMode,
11 | matchesDarkMode,
12 | }),
13 | shallowEqual
14 | )
15 | return userDarkMode === null ? matchesDarkMode : userDarkMode
16 | }
17 |
18 | export function useDarkModeManager(): [boolean, () => void] {
19 | const dispatch = useAppDispatch()
20 | const darkMode = useIsDarkMode()
21 |
22 | const toggleSetDarkMode = useCallback(() => {
23 | dispatch(updateUserDarkMode({ userDarkMode: !darkMode }))
24 | }, [darkMode, dispatch])
25 |
26 | return [darkMode, toggleSetDarkMode]
27 | }
28 |
29 | export function useSetUserSlippageTolerance(): (slippageTolerance: number | 'auto') => void {
30 | const dispatch = useAppDispatch()
31 | return useCallback(
32 | (userSlippageTolerance: number | 'auto') => {
33 | dispatch(
34 | updateUserSlippageTolerance({
35 | userSlippageTolerance,
36 | })
37 | )
38 | },
39 | [dispatch]
40 | )
41 | }
42 |
43 | /**
44 | * Return the user's slippage tolerance
45 | */
46 | export function useUserSlippageTolerance(): number | 'auto' {
47 | return useAppSelector((state) => {
48 | return state.user.userSlippageTolerance
49 | })
50 | }
51 |
52 | /**
53 | * Same as above but replaces the auto with a default value
54 | * @param defaultSlippageTolerance the default value to replace auto with
55 | */
56 | export function useUserSlippageToleranceWithDefault(defaultSlippageTolerance: number): number {
57 | const allowedSlippage = useUserSlippageTolerance()
58 | return useMemo(
59 | () => (allowedSlippage === 'auto' ? defaultSlippageTolerance : allowedSlippage),
60 | [allowedSlippage, defaultSlippageTolerance]
61 | )
62 | }
63 |
--------------------------------------------------------------------------------
/src/components/App/Bond/Search.tsx:
--------------------------------------------------------------------------------
1 | import React, { useMemo } from 'react'
2 | import Fuse from 'fuse.js'
3 | import { useSelect, SelectSearchOption } from 'react-select-search'
4 |
5 | import { Search as SearchIcon } from 'components/Icons'
6 | import { InputWrapper, InputField } from 'components/Input'
7 | import { useUserBondNFTs } from 'hooks/useBondsPage'
8 |
9 | function fuzzySearch(options: SelectSearchOption[]): any {
10 | const config = {
11 | keys: ['name', 'value', 'tokenId'],
12 | isCaseSensitive: false,
13 | threshold: 0.15,
14 | }
15 |
16 | const fuse = new Fuse(options, config)
17 |
18 | return (query: string) => {
19 | if (!query) {
20 | return options
21 | }
22 |
23 | return fuse.search(query)
24 | }
25 | }
26 |
27 | export function useSearch() {
28 | const nfts = useUserBondNFTs()
29 | const nftIdsList = useMemo(() => {
30 | return [
31 | ...nfts.map((nft) => {
32 | return { nftId: nft.tokenId, ...nft }
33 | }),
34 | ]
35 | }, [nfts])
36 |
37 | const list: SelectSearchOption[] = useMemo(() => {
38 | return nftIdsList.map((o) => ({ ...o, name: 'DeiBond #' + o.nftId.toString(), value: o.nftId }))
39 | }, [nftIdsList])
40 |
41 | const [snapshot, searchProps, optionProps] = useSelect({
42 | options: list,
43 | value: '',
44 | search: true,
45 | filterOptions: fuzzySearch,
46 | allowEmpty: true,
47 | closeOnSelect: false,
48 | })
49 |
50 | return {
51 | snapshot,
52 | searchProps,
53 | optionProps,
54 | }
55 | }
56 |
57 | export function SearchField({ searchProps, modalSearch }: { searchProps: any; modalSearch?: boolean }) {
58 | return (
59 |
60 |
61 | null}
69 | style={{ marginLeft: '15px', fontSize: '16px' }}
70 | />
71 |
72 | )
73 | }
74 |
--------------------------------------------------------------------------------
/src/components/App/Vest/Search.tsx:
--------------------------------------------------------------------------------
1 | import React, { useMemo } from 'react'
2 | import Fuse from 'fuse.js'
3 | import { useSelect, SelectSearchOption } from 'react-select-search'
4 |
5 | import { useOwnerVeDeusNFTs } from 'hooks/useOwnerNfts'
6 |
7 | import { Search as SearchIcon } from 'components/Icons'
8 | import { InputWrapper, InputField } from 'components/Input'
9 |
10 | function fuzzySearch(options: SelectSearchOption[]): any {
11 | const config = {
12 | keys: ['name', 'value', 'nftId'],
13 | isCaseSensitive: false,
14 | threshold: 0.15,
15 | }
16 |
17 | const fuse = new Fuse(options, config)
18 |
19 | return (query: string) => {
20 | if (!query) {
21 | return options
22 | }
23 |
24 | return fuse.search(query)
25 | }
26 | }
27 |
28 | export function useSearch() {
29 | const nftIds = useOwnerVeDeusNFTs().results
30 | const nftIdsList = useMemo(() => {
31 | return [
32 | ...nftIds.map((id) => {
33 | return { nftId: id }
34 | }),
35 | ]
36 | }, [nftIds])
37 |
38 | const list: SelectSearchOption[] = useMemo(() => {
39 | return nftIdsList.map((o) => ({ ...o, name: 'veDEUS #' + o.nftId.toString(), value: o.nftId }))
40 | }, [nftIdsList])
41 |
42 | const [snapshot, searchProps, optionProps] = useSelect({
43 | options: list,
44 | value: '',
45 | search: true,
46 | filterOptions: fuzzySearch,
47 | allowEmpty: true,
48 | closeOnSelect: false,
49 | })
50 |
51 | return {
52 | snapshot,
53 | searchProps,
54 | optionProps,
55 | }
56 | }
57 |
58 | export function SearchField({ searchProps, modalSearch }: { searchProps: any; modalSearch?: boolean }) {
59 | return (
60 |
61 |
62 | null}
70 | style={{ marginLeft: '15px', fontSize: '16px' }}
71 | />
72 |
73 | )
74 | }
75 |
--------------------------------------------------------------------------------
/src/components/App/StableCoin/Search.tsx:
--------------------------------------------------------------------------------
1 | import React, { useMemo } from 'react'
2 | import styled from 'styled-components'
3 | import Fuse from 'fuse.js'
4 | import { Token } from '@sushiswap/core-sdk'
5 | import { useSelect, SelectSearchOption } from 'react-select-search'
6 |
7 | import { Search as SearchIcon } from 'components/Icons'
8 | import { InputWrapper, InputField } from 'components/Input'
9 |
10 | const SearchWrapper = styled(InputWrapper)`
11 | height: 60px;
12 | background: ${({ theme }) => theme.bg0};
13 | border: 1px solid ${({ theme }) => theme.border3};
14 | border-radius: 12px;
15 | & > * {
16 | &:last-child {
17 | font-size: 1rem;
18 | margin-left: 16px;
19 | }
20 | }
21 | `
22 |
23 | function fuzzySearch(options: SelectSearchOption[]): any {
24 | const config = {
25 | keys: ['symbol', 'name', 'address'],
26 | isCaseSensitive: false,
27 | threshold: 0.2,
28 | }
29 |
30 | const fuse = new Fuse(options, config)
31 |
32 | return (query: string) => {
33 | if (!query) return options
34 | return fuse.search(query)
35 | }
36 | }
37 |
38 | export function useSearch(tokens: Token[]) {
39 | // const tokens = allTokens
40 | const list = useMemo(() => {
41 | return tokens.map((token) => ({ ...token, name: token.name, value: token.symbol }))
42 | }, [tokens])
43 |
44 | const [snapshot, searchProps, optionProps] = useSelect({
45 | options: list as SelectSearchOption[],
46 | value: '',
47 | search: true,
48 | filterOptions: fuzzySearch,
49 | allowEmpty: true,
50 | closeOnSelect: false,
51 | })
52 | return {
53 | snapshot,
54 | searchProps,
55 | optionProps,
56 | }
57 | }
58 |
59 | export function SearchField({ searchProps }: { searchProps: any }) {
60 | return (
61 |
62 |
63 |
64 | null}
72 | />
73 |
74 | )
75 | }
76 |
--------------------------------------------------------------------------------
/src/components/Icons/Trade.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import { useTheme } from 'styled-components'
4 |
5 | export default function Trade({ size, ...rest }: { size: number; [x: string]: any }) {
6 | const theme = useTheme()
7 |
8 | return (
9 |
26 | )
27 | }
28 |
--------------------------------------------------------------------------------
/src/constants/path.ts:
--------------------------------------------------------------------------------
1 | import { ProxyPath } from 'utils/address'
2 | import { Collateral } from './addresses'
3 | import { SupportedChainId } from './chains'
4 | import { Tokens } from './tokens'
5 |
6 | export const MINT__PATHS: ProxyPath = {
7 | [SupportedChainId.MAINNET]: {
8 | DEUS: [
9 | Tokens.DEUS[SupportedChainId.MAINNET]['address'],
10 | Tokens.DEI[SupportedChainId.MAINNET]['address'],
11 | Collateral[SupportedChainId.MAINNET],
12 | ],
13 | ETH: [Tokens.WNATIVE[SupportedChainId.MAINNET]['address'], Collateral[SupportedChainId.MAINNET]],
14 | USDC: [Collateral[SupportedChainId.MAINNET]],
15 | DAI: [Tokens.DAI[SupportedChainId.MAINNET]['address'], Collateral[SupportedChainId.MAINNET]],
16 | DEI: [Tokens.DEI[SupportedChainId.MAINNET]['address'], Collateral[SupportedChainId.MAINNET]],
17 | WBTC: [
18 | Tokens.WBTC[SupportedChainId.MAINNET]['address'],
19 | Tokens.WETH[SupportedChainId.MAINNET]['address'],
20 | Collateral[SupportedChainId.MAINNET],
21 | ],
22 | },
23 |
24 | [SupportedChainId.POLYGON]: {
25 | DEUS: [
26 | Tokens.DEUS[SupportedChainId.POLYGON]['address'],
27 | Tokens.DEI[SupportedChainId.POLYGON]['address'],
28 | Collateral[SupportedChainId.POLYGON],
29 | ],
30 | DEI: [Tokens.DEI[SupportedChainId.POLYGON]['address'], Collateral[SupportedChainId.POLYGON]],
31 | WETH: [Tokens.WETH[SupportedChainId.POLYGON]['address'], Collateral[SupportedChainId.POLYGON]],
32 | USDC: [Collateral[SupportedChainId.POLYGON]],
33 | MATIC: [Tokens.WNATIVE[SupportedChainId.POLYGON]['address'], Collateral[SupportedChainId.POLYGON]],
34 | },
35 |
36 | [SupportedChainId.FANTOM]: {
37 | DEUS: [Tokens.DEUS[SupportedChainId.FANTOM]['address'], Collateral[SupportedChainId.FANTOM]],
38 | DEI: [Tokens.DEI[SupportedChainId.FANTOM]['address'], Collateral[SupportedChainId.FANTOM]],
39 | WETH: [Tokens.WETH[SupportedChainId.FANTOM]['address'], Collateral[SupportedChainId.FANTOM]],
40 | USDC: [Collateral[SupportedChainId.FANTOM]],
41 | FTM: [Tokens.WNATIVE[SupportedChainId.FANTOM]['address'], Collateral[SupportedChainId.FANTOM]],
42 | },
43 | }
44 |
--------------------------------------------------------------------------------
/src/hooks/useClqdrCallback.ts:
--------------------------------------------------------------------------------
1 | import { useCallback, useMemo } from 'react'
2 |
3 | import { useTransactionAdder } from 'state/transactions/hooks'
4 | import useWeb3React from 'hooks/useWeb3'
5 | import { useCLQDRContract } from 'hooks/useContract'
6 | import { createTransactionCallback, TransactionCallbackState } from 'utils/web3'
7 | import { toBN } from 'utils/numbers'
8 | import { useCalcSharesFromAmount } from 'hooks/useClqdrPage'
9 |
10 | export function useDepositLQDRCallback(amountIn: string): {
11 | state: TransactionCallbackState
12 | callback: null | (() => Promise)
13 | error: string | null
14 | } {
15 | const { account, chainId, library } = useWeb3React()
16 | const addTransaction = useTransactionAdder()
17 | const CLQDRContract = useCLQDRContract()
18 | const shares = useCalcSharesFromAmount(amountIn)
19 |
20 | const constructCall = useCallback(() => {
21 | try {
22 | if (!account || !library || !CLQDRContract) {
23 | throw new Error('Missing dependencies.')
24 | }
25 |
26 | const amountInBN = toBN(amountIn).times(1e18).toFixed(0)
27 | const args = [amountInBN, shares]
28 | return {
29 | address: CLQDRContract.address,
30 | calldata: CLQDRContract.interface.encodeFunctionData('deposit', args) ?? '',
31 | value: 0,
32 | }
33 | } catch (error) {
34 | return {
35 | error,
36 | }
37 | }
38 | }, [account, library, CLQDRContract, amountIn, shares])
39 |
40 | return useMemo(() => {
41 | if (!account || !chainId || !library || !CLQDRContract) {
42 | return {
43 | state: TransactionCallbackState.INVALID,
44 | callback: null,
45 | error: 'Missing dependencies',
46 | }
47 | }
48 |
49 | const summary = `Minting ${toBN(shares).div(1e18).toFixed()} cLQDR by ${amountIn} LQDR`
50 |
51 | return {
52 | state: TransactionCallbackState.VALID,
53 | error: null,
54 | callback: () => createTransactionCallback('deposit', constructCall, addTransaction, summary, account, library),
55 | }
56 | }, [account, chainId, library, shares, CLQDRContract, amountIn, constructCall, addTransaction])
57 | }
58 |
--------------------------------------------------------------------------------
/src/components/AccountDetails/Transaction.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import styled from 'styled-components'
3 |
4 | import useWeb3React from 'hooks/useWeb3'
5 | import { useAllTransactions } from 'state/transactions/hooks'
6 | import { ExplorerLink } from 'components/Link'
7 | import { CheckMark, Loader, Error, Link } from 'components/Icons'
8 | import { ExplorerDataType } from 'utils/explorers'
9 | import { RowEnd } from 'components/Row'
10 |
11 | const Row = styled.div`
12 | display: flex;
13 | flex-flow: row nowrap;
14 | justify-content: flex-start;
15 | gap: 10px;
16 | align-items: center;
17 | font-size: 0.8rem;
18 | margin-bottom: 1px;
19 | white-space: nowrap;
20 |
21 | ${({ theme }) => theme.mediaWidth.upToSmall`
22 | font-size: 0.7rem;
23 | `};
24 |
25 | &:hover {
26 | text-decoration: underline;
27 | }
28 | `
29 |
30 | const Summary = styled.div`
31 | font-size: 12px;
32 | color: ${({ theme }) => theme.text1};
33 | `
34 |
35 | const TransactionStatus = styled(RowEnd)`
36 | padding-right: 14px;
37 | `
38 |
39 | export default function Transaction({ hash }: { hash: string }) {
40 | const { chainId } = useWeb3React()
41 | const allTransactions = useAllTransactions()
42 |
43 | const tx = allTransactions?.[hash]
44 | const summary = tx?.summary
45 | const pending = !tx?.receipt
46 | const success = !pending && tx && (tx.receipt?.status === 1 || typeof tx.receipt?.status === 'undefined')
47 | const cancelled = tx?.receipt && tx.receipt.status === 1337
48 |
49 | if (!chainId) return null
50 |
51 | return (
52 |
53 |
54 |
55 | {summary ?? hash}
56 |
57 |
58 |
59 | {success ? (
60 |
61 | ) : cancelled ? (
62 |
63 | ) : (
64 |
65 | )}
66 |
67 |
68 |
69 | )
70 | }
71 |
--------------------------------------------------------------------------------
/src/components/Icons/QuestionMark.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | export default function QuestionMark({ width, height, ...rest }: { width: number; height: number; [x: string]: any }) {
4 | return (
5 |
21 | )
22 | }
23 |
--------------------------------------------------------------------------------
/src/components/App/CLqdr/FirebirdBox2.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import styled from 'styled-components'
3 | import ImageWithFallback from 'components/ImageWithFallback'
4 |
5 | import FIREBIRD_ICON from '/public/static/images/pages/clqdr/ic_firebird.svg'
6 | import LQDR_ICON from '/public/static/images/tokens/lqdr.svg'
7 | import { ExternalLink } from 'components/Link'
8 | import { RowCenter } from 'components/Row'
9 | import { BuyButton, BuyButtonWrapper, Wrapper } from '.'
10 |
11 | const MainWrapper = styled(Wrapper)`
12 | padding: 12px 16px 16px 16px;
13 | height: 158px;
14 | `
15 |
16 | const BuyWrapper = styled(RowCenter)`
17 | margin-top: 16px;
18 | `
19 |
20 | const MainBuyButton = styled(BuyButton)`
21 | position: relative;
22 | `
23 |
24 | const Icon = styled.div`
25 | position: absolute;
26 | left: -45px;
27 | top: -15px;
28 |
29 | ${({ theme }) => theme.mediaWidth.upToExtraSmall`
30 | height: 50px;
31 | `}
32 | `
33 |
34 | const LittleIcon = styled.div`
35 | position: absolute;
36 | right: 26px;
37 | `
38 |
39 | const Text = styled.div`
40 | font-weight: 500;
41 | font-family: 'IBM Plex Mono';
42 | font-size: 14px;
43 | text-align: center;
44 | margin-top: 12px;
45 | color: ${({ theme }) => theme.text1};
46 | `
47 |
48 | export default function FirebirdBox2() {
49 | return (
50 |
51 |
52 |
53 |
54 |
55 |
56 | You have no LQDR to mint cLQDR with!
57 |
58 |
59 |
60 |
61 |
62 | Buy LQDR from Firebird
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 | )
72 | }
73 |
--------------------------------------------------------------------------------
/src/components/Icons/index.tsx:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components'
2 |
3 | export { default as ArrowBubble } from './ArrowBubble'
4 | export { default as CheckMark } from './CheckMark'
5 | export { default as CreditCard } from './CreditCard'
6 | export { default as Dashboard } from './Dashboard'
7 | export { default as VeDeus } from './VeDeus'
8 | export { default as Mint } from './Mint'
9 | export { default as DeiBonds } from './DeiBonds'
10 | export { default as Analytics } from './Analytics'
11 | export { Close } from './Close'
12 | export { ConfirmationAnimation } from './Confirmation'
13 | export { ChevronLeft, ChevronDown, ChevronUp } from './Chevron'
14 | export { default as DotFlashing } from './DotFlashing'
15 | export { default as Droplet } from './Droplet'
16 | export { default as Info } from './Info'
17 | export { default as Gift } from './Gift'
18 | export { default as GreenCircle } from './GreenCircle'
19 | export { default as Connected } from './Connected'
20 | export { default as Copy } from './Copy'
21 | export { default as Loader } from './Loader'
22 | export { default as Lock } from './Lock'
23 | export { default as NavToggle } from './NavToggle'
24 | export { Network } from './Network'
25 | export { default as Markets } from './Markets'
26 | export { default as Portfolio } from './Portfolio'
27 | export { default as Search } from './Search'
28 | export { Settings } from './Settings'
29 | export { Twitter, Telegram, Github } from './Socials'
30 | export { default as ThemeToggle } from './ThemeToggle'
31 | export { default as Trade } from './Trade'
32 | export { Wallet } from './Wallet'
33 | export { default as Redeem } from './Redeem'
34 | export { default as Link } from './Link'
35 | export { default as Error } from './Error'
36 |
37 | // for wrapping react feather icons
38 | export const IconWrapper = styled.div<{ stroke?: string; size?: string; marginRight?: string; marginLeft?: string }>`
39 | display: flex;
40 | align-items: center;
41 | justify-content: center;
42 | width: ${({ size }) => size ?? '20px'};
43 | height: ${({ size }) => size ?? '20px'};
44 | margin-right: ${({ marginRight }) => marginRight ?? 0};
45 | margin-left: ${({ marginLeft }) => marginLeft ?? 0};
46 | & > * {
47 | stroke: ${({ theme, stroke }) => stroke && theme.text1};
48 | }
49 | `
50 |
--------------------------------------------------------------------------------
/src/components/Popups/PopupItem.tsx:
--------------------------------------------------------------------------------
1 | import { useCallback, useEffect } from 'react'
2 | import styled from 'styled-components'
3 | import { animated } from 'react-spring'
4 | import { useSpring } from '@react-spring/web'
5 |
6 | import { PopupContent } from 'state/application/reducer'
7 | import { useRemovePopup } from 'state/application/hooks'
8 | import TransactionPopup from './TransactionPopup'
9 |
10 | const Wrapper = styled.div`
11 | display: flex;
12 | position: relative;
13 | flex-flow: column nowrap;
14 | width: 100%;
15 | margin-bottom: 10px;
16 | background: ${({ theme }) => theme.bg0};
17 | border-radius: 10px;
18 | border: 1px solid ${({ theme }) => theme.border2};
19 | `
20 |
21 | const Fader = styled.div<{
22 | success: boolean
23 | }>`
24 | position: absolute;
25 | bottom: 0px;
26 | left: 0px;
27 | width: 100%;
28 | height: 1px;
29 | background-color: ${({ theme }) => theme.primary1};
30 | `
31 |
32 | const AnimatedFader = animated(Fader)
33 |
34 | export default function PopupItem({
35 | removeAfterMs,
36 | content,
37 | popKey,
38 | }: {
39 | removeAfterMs: number | null
40 | content: PopupContent
41 | popKey: string
42 | }) {
43 | const removePopup = useRemovePopup()
44 | const removeThisPopup = useCallback(() => removePopup(popKey), [popKey, removePopup])
45 | useEffect(() => {
46 | if (removeAfterMs === null) return undefined
47 |
48 | const timeout = setTimeout(() => {
49 | removeThisPopup()
50 | }, removeAfterMs)
51 |
52 | return () => {
53 | clearTimeout(timeout)
54 | }
55 | }, [removeAfterMs, removeThisPopup])
56 |
57 | const faderStyle = useSpring({
58 | from: { width: '100%' },
59 | to: { width: '0%' },
60 | config: { duration: removeAfterMs ?? undefined },
61 | })
62 |
63 | function getPopupContent(): JSX.Element | null {
64 | if ('txn' in content) {
65 | const {
66 | txn: { hash, success, summary },
67 | } = content
68 | return
69 | } else {
70 | return null
71 | }
72 | }
73 |
74 | return (
75 |
76 | {getPopupContent()}
77 | {removeAfterMs !== null ? : null}
78 |
79 | )
80 | }
81 |
--------------------------------------------------------------------------------
/src/pages/_document.tsx:
--------------------------------------------------------------------------------
1 | import Document, { Html, Head, Main, NextScript, DocumentContext, DocumentInitialProps } from 'next/document'
2 | import { ServerStyleSheet } from 'styled-components'
3 |
4 | class MyDocument extends Document {
5 | static async getInitialProps(ctx: DocumentContext): Promise {
6 | const sheet = new ServerStyleSheet()
7 | const originalRenderPage = ctx.renderPage
8 |
9 | try {
10 | ctx.renderPage = () =>
11 | originalRenderPage({
12 | // eslint-disable-next-line react/display-name
13 | enhanceApp: (App) => (props) => sheet.collectStyles(),
14 | })
15 |
16 | const initialProps = await Document.getInitialProps(ctx)
17 | return {
18 | ...initialProps,
19 | styles: (
20 | <>
21 | {initialProps.styles}
22 | {sheet.getStyleElement()}
23 | >
24 | ),
25 | }
26 | } finally {
27 | sheet.seal()
28 | }
29 | }
30 |
31 | render() {
32 | return (
33 |
34 |
35 | <>
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | DEI stablecoin
45 |
46 |
47 |
48 |
49 |
53 | >
54 |
55 |
56 |
57 |
58 |
59 |
60 | )
61 | }
62 | }
63 |
64 | export default MyDocument
65 |
--------------------------------------------------------------------------------
/src/components/App/CLqdr/FirebirdBox1.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import styled from 'styled-components'
3 | import FIREBIRD_ICON from '/public/static/images/pages/clqdr/ic_firebird.svg'
4 |
5 | import { RowBetween, RowCenter, RowStart } from 'components/Row'
6 | import ImageWithFallback from 'components/ImageWithFallback'
7 | import { ExternalLink } from 'components/Link'
8 | import { BuyButton, BuyButtonWrapper, Wrapper } from '.'
9 | import { LQDR_ADDRESS, CLQDR_ADDRESS } from 'constants/addresses'
10 |
11 | const MainWrapper = styled(Wrapper)`
12 | height: 170px;
13 | padding: 12px 16px 16px 16px;
14 | `
15 |
16 | const RatioWrap = styled(RowBetween)`
17 | white-space: nowrap;
18 | font-size: 0.75rem;
19 | margin-top: 6px;
20 | margin-bottom: 16px;
21 | height: 30px;
22 | `
23 |
24 | const Name = styled.div`
25 | font-style: normal;
26 | font-weight: 500;
27 | font-size: 14px;
28 | color: ${({ theme }) => theme.text2};
29 | `
30 |
31 | const Value = styled.div`
32 | color: ${({ theme }) => theme.green1};
33 | `
34 |
35 | const Text = styled(RowStart)`
36 | font-weight: 500;
37 | font-size: 14px;
38 | text-align: center;
39 | margin-top: 16px;
40 | color: ${({ theme }) => theme.text1};
41 | `
42 |
43 | const Icon = styled.div`
44 | position: absolute;
45 | left: -45px;
46 | top: -15px;
47 |
48 | ${({ theme }) => theme.mediaWidth.upToExtraSmall`
49 | height: 50px;
50 | `}
51 | `
52 |
53 | export default function FirebirdBox1({ ratio }: { ratio: string | number }) {
54 | return (
55 |
56 |
57 |
58 |
59 | Buy on Firebird for better ratio
60 |
61 | cLQDR/LQDR Ratio:
62 | {ratio}
63 |
64 |
65 |
66 |
70 | Buy cLQDR from Firebird
71 |
72 |
73 |
74 |
75 | )
76 | }
77 |
--------------------------------------------------------------------------------
/src/components/App/CLqdr/FirebirdBox3.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import styled from 'styled-components'
3 | import { LQDR_ADDRESS } from 'constants/addresses'
4 |
5 | import ImageWithFallback from 'components/ImageWithFallback'
6 | import FIREBIRD_ICON from '/public/static/images/pages/clqdr/ic_firebird.svg'
7 | import { ExternalLink } from 'components/Link'
8 | import { RowEnd } from 'components/Row'
9 | import { Wrapper, BuyButtonWrapper, BuyButton } from '.'
10 |
11 | const BuyWrapper = styled(RowEnd)`
12 | margin-top: 2px;
13 | `
14 |
15 | const ButtonWrapper = styled(BuyButtonWrapper)`
16 | width: 165px;
17 |
18 | ${({ theme }) => theme.mediaWidth.upToExtraSmall`
19 | height: 36px;
20 | width:100px;
21 | `}
22 | `
23 |
24 | const Icon = styled.div`
25 | position: absolute;
26 | left: -18px;
27 | top: -20px;
28 |
29 | ${({ theme }) => theme.mediaWidth.upToExtraSmall`
30 | height: 50px;
31 | `}
32 | `
33 |
34 | const FirebirdText = styled.div`
35 | font-size: 14px;
36 | font-weight: 500;
37 | margin-top: 17px;
38 | background: linear-gradient(90deg, #f78c2a 0%, #f34038 100%);
39 | -webkit-background-clip: text;
40 | -webkit-text-fill-color: transparent;
41 | background-clip: text;
42 | `
43 |
44 | const Text = styled.div`
45 | font-weight: 500;
46 | font-size: 16px;
47 | color: ${({ theme }) => theme.text1};
48 | `
49 |
50 | const FireBirdWrap = styled.div`
51 | display: flex;
52 | white-space: nowrap;
53 | `
54 |
55 | export default function FirebirdBox3() {
56 | return (
57 |
58 |
59 |
60 |
61 |
62 |
63 | Need more LQDR?
64 | Buy it on Firebird Finance
65 |
66 |
67 |
68 |
72 | Buy LQDR
73 |
74 |
75 |
76 |
77 |
78 | )
79 | }
80 |
--------------------------------------------------------------------------------
/src/state/mint/reducer.ts:
--------------------------------------------------------------------------------
1 | import { createSlice, PayloadAction } from '@reduxjs/toolkit'
2 | import { AppState, useAppSelector } from 'state'
3 |
4 | // deiAmountOut, usdcAmountIn, deusAmountIn
5 | export type ProxyValues = Array
6 |
7 | export interface MintState {
8 | value: string
9 | proxyLoading: boolean
10 | isProxyMinter: boolean
11 | proxyValues: ProxyValues
12 | attemptingTxn: boolean
13 | showReview: boolean
14 | error?: string
15 | }
16 |
17 | const initialState: MintState = {
18 | value: '',
19 | proxyLoading: false,
20 | isProxyMinter: false,
21 | proxyValues: [],
22 | attemptingTxn: false,
23 | showReview: false,
24 | error: undefined,
25 | }
26 |
27 | export const mintSlice = createSlice({
28 | name: 'mint',
29 | initialState,
30 | reducers: {
31 | setMintState: (state, action: PayloadAction) => {
32 | state.value = action.payload.value
33 | state.proxyLoading = action.payload.proxyLoading
34 | state.isProxyMinter = action.payload.isProxyMinter
35 | state.proxyValues = action.payload.proxyValues
36 | state.attemptingTxn = action.payload.attemptingTxn
37 | state.showReview = action.payload.showReview
38 | state.error = action.payload.error
39 | },
40 | setProxyLoading: (state, action: PayloadAction) => {
41 | state.proxyLoading = action.payload
42 | },
43 | setIsProxyMinter: (state, action: PayloadAction) => {
44 | state.isProxyMinter = action.payload
45 | },
46 | setProxyValues: (state, action: PayloadAction) => {
47 | state.proxyValues = action.payload
48 | },
49 | setAttemptingTxn: (state, action: PayloadAction) => {
50 | state.attemptingTxn = action.payload
51 | },
52 | setShowReview: (state, action: PayloadAction) => {
53 | state.showReview = action.payload
54 | },
55 | setError: (state, action: PayloadAction) => {
56 | state.error = action.payload
57 | },
58 | },
59 | })
60 |
61 | export const {
62 | setMintState,
63 | setProxyLoading,
64 | setIsProxyMinter,
65 | setProxyValues,
66 | setAttemptingTxn,
67 | setShowReview,
68 | setError,
69 | } = mintSlice.actions
70 |
71 | export function useMintState(): MintState {
72 | return useAppSelector((state: AppState) => state.mint)
73 | }
74 |
75 | export default mintSlice.reducer
76 |
--------------------------------------------------------------------------------
/src/components/Input/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import styled from 'styled-components'
3 |
4 | import Box from 'components/Box'
5 |
6 | export const InputWrapper = styled(Box)<{ ModalSearch?: boolean }>`
7 | padding: 0 20px;
8 | border-radius: ${({ ModalSearch }) => (ModalSearch ? '12px' : '8px')};
9 | `
10 |
11 | export const InputField = styled.input<{
12 | [x: string]: any
13 | }>`
14 | height: 50px;
15 | flex: 1;
16 | border: none;
17 | background: transparent;
18 | font-size: 1.1rem;
19 | font-family: 'IBM Plex Mono';
20 | color: ${({ theme }) => theme.text1};
21 | text-align: left;
22 | font-size: 24px;
23 | margin-left: 5px;
24 |
25 | &:focus,
26 | &:hover {
27 | outline: none;
28 | }
29 | ${({ theme }) => theme.mediaWidth.upToSmall`
30 | font-size: 16px !important;
31 | width: 120px;
32 | height: 40px;
33 | `}
34 | `
35 |
36 | function escapeRegExp(string: string): string {
37 | return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') // $& means the whole matched string
38 | }
39 |
40 | const inputRegex = RegExp(`^\\d*(?:\\\\[.])?\\d*$`) // match escaped "." characters via in a non-capturing group
41 | export const NumericalInput = ({
42 | value,
43 | onUserInput,
44 | placeholder = '0.0',
45 | ...rest
46 | }: {
47 | value: string | number
48 | onUserInput: (input: string) => void
49 | placeholder?: string
50 | } & Omit, 'ref' | 'onChange' | 'as'>) => {
51 | const enforcer = (nextUserInput: string) => {
52 | if (nextUserInput === '' || inputRegex.test(escapeRegExp(nextUserInput))) {
53 | onUserInput(nextUserInput)
54 | }
55 | }
56 |
57 | return (
58 | ) => {
62 | // replace commas with periods
63 | enforcer(event.target.value.replace(/,/g, '.'))
64 | enforcer(event.target.value.replace(/^\./g, '0.'))
65 | }}
66 | // universal input options
67 | inputMode="decimal"
68 | title="Amount"
69 | autoComplete="off"
70 | autoCorrect="off"
71 | // text-specific options
72 | type="text"
73 | pattern="^[0-9]*[.,]?[0-9]*$"
74 | placeholder={placeholder || '0.00'}
75 | min={0}
76 | minLength={1}
77 | maxLength={79}
78 | spellCheck="false"
79 | />
80 | )
81 | }
82 |
--------------------------------------------------------------------------------