├── .husky ├── .gitignore └── pre-commit ├── .eslintignore ├── src ├── types │ └── use-wallet │ │ └── index.d.ts ├── strings.ts ├── utils │ ├── shortenAddress.ts │ ├── index.ts │ └── equals.ts ├── pages │ ├── index.tsx │ ├── api │ │ └── hello.ts │ ├── _document.tsx │ ├── stake.tsx │ ├── pool.tsx │ ├── borrow.tsx │ └── landing.tsx ├── components │ ├── HeadingBase.tsx │ ├── Layout │ │ ├── StyledBox.tsx │ │ ├── StyledContainer.tsx │ │ ├── StyledFlex.tsx │ │ ├── VStackBase.tsx │ │ ├── CardBase.tsx │ │ ├── Layout.tsx │ │ ├── Navbar.tsx │ │ ├── GlassCard.tsx │ │ └── Footer.tsx │ ├── Badge.tsx │ ├── Staking │ │ ├── Staking.tsx │ │ ├── StakingManagerAction.tsx │ │ ├── StakingGainsAction.tsx │ │ ├── context │ │ │ ├── StakingViewContext.tsx │ │ │ └── StakingViewProvider.tsx │ │ ├── NoStake.tsx │ │ ├── StakingPreview.tsx │ │ └── ReadOnlyStake.tsx │ ├── DisclaimerLink.tsx │ ├── Trove │ │ ├── context │ │ │ ├── TroveViewContext.tsx │ │ │ ├── types.ts │ │ │ └── TroveViewProvider.tsx │ │ ├── index.d.ts │ │ ├── NoTrove.tsx │ │ ├── TrovePreview.tsx │ │ ├── TroveAction.tsx │ │ ├── Trove.tsx │ │ ├── RedeemedTrove.tsx │ │ ├── LiquidatedTrove.tsx │ │ ├── CollateralRatio.tsx │ │ ├── ReadOnlyTrove.tsx │ │ ├── TroveEditor.tsx │ │ └── ExpensiveTroveChangeWarning.tsx │ ├── utils │ │ └── useWindowSize.tsx │ ├── Stability │ │ ├── RemainingLQTY.tsx │ │ ├── context │ │ │ ├── StabilityViewContext.tsx │ │ │ ├── types.ts │ │ │ ├── StabilityViewProvider.tsx │ │ │ └── fetchLqtyPrice.ts │ │ ├── Stability.tsx │ │ ├── actions │ │ │ ├── ClaimRewards.tsx │ │ │ └── ClaimAndMove.tsx │ │ ├── NoDeposit.tsx │ │ ├── StabilityPreview.tsx │ │ ├── StabilityDepositAction.tsx │ │ ├── StabilityActionDescription.tsx │ │ ├── validation │ │ │ └── validateStabilityDepositChange.tsx │ │ ├── Yield.tsx │ │ └── ActiveDeposit.tsx │ ├── InfoMessage.tsx │ ├── Warning.tsx │ ├── ErrorDescription.tsx │ ├── ActionDescription.tsx │ ├── ConnectionConfirmationDialog.tsx │ ├── InfoIcon.tsx │ ├── LiquityStoreProvider.tsx │ ├── Statistic.tsx │ ├── NavbarTheme.tsx │ ├── NavbarWallet.tsx │ ├── WaitingDialog.tsx │ ├── RetryDialog.tsx │ ├── CollateralSurplusAction.tsx │ ├── UserAccount.tsx │ ├── NavbarMobile.tsx │ ├── Icon.tsx │ ├── PreviewConnector.tsx │ ├── Tooltip.tsx │ ├── SystemStatsPopup.tsx │ ├── Dialog.tsx │ ├── LoadingOverlay.tsx │ ├── MetaMaskIcon.tsx │ ├── ExpensiveTroveChangeWarning.tsx │ └── NavbarLinks.tsx ├── state │ ├── home │ │ ├── hooks.ts │ │ ├── actions.ts │ │ └── reducer.ts │ ├── hooks.ts │ └── index.ts ├── theme │ ├── components │ │ ├── input.ts │ │ ├── number-input.ts │ │ ├── box.ts │ │ ├── section.ts │ │ ├── checkbox.ts │ │ ├── popover.ts │ │ ├── heading.ts │ │ ├── text.ts │ │ └── link.ts │ ├── fonts.ts │ ├── styles.ts │ ├── colors.ts │ └── index.ts ├── hooks │ ├── usePrevious.ts │ ├── WalletContext.tsx │ ├── useLiquityStore.ts │ ├── useIsMobile.ts │ ├── useLiquitySelector.ts │ ├── ModalContext.tsx │ ├── useStableTroveChange.ts │ ├── useAuthorizedConnection.ts │ ├── useLiquityReducer.ts │ ├── useWalletReducer.ts │ └── AsyncValue.ts ├── styles │ └── globals.css ├── connectors │ └── connectors.ts ├── config │ └── index.ts └── testUtils │ └── DisposableWalletProvider.ts ├── .env.template ├── public ├── cbw.png ├── favicon.ico ├── portis-icon.png ├── walletIcon.jpg ├── authereum-logo.png ├── bankless-logo.png ├── fortmatic-logo.png ├── metamask-logo.png ├── wallet-connect.png ├── header-logo-onBlack.png ├── Sk-Modernist-Regular.otf ├── futuristic-background.png ├── vercel.svg └── metamask-fox.svg ├── data └── network │ ├── NetworkProvider.ts │ ├── graph │ ├── interface │ │ ├── GraphQLAPIMapper.ts │ │ └── GraphQLAPIClient.ts │ ├── queries │ │ └── index.ts │ └── implementation │ │ ├── TheGraphAPIMapper.ts │ │ └── TheGraphAPIClient.ts │ └── DefaultNetworkProvider.ts ├── .prettierrc ├── .lintstagedrc.js ├── next-env.d.ts ├── services ├── ServiceProvider.ts ├── discovery │ ├── interface │ │ └── DiscoveryService.ts │ └── implementation │ │ └── DefaultDiscoveryService.ts └── DefaultServiceProvider.ts ├── .gitignore ├── models └── LiquityState.ts ├── .github ├── dependabot.yml └── workflows │ └── codeql-analysis.yml ├── tsconfig.json ├── .eslintrc.json ├── README.md ├── next.config.js ├── package.json └── LICENSE.md /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | /public 2 | /node_modules 3 | -------------------------------------------------------------------------------- /src/types/use-wallet/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'use-wallet' -------------------------------------------------------------------------------- /.env.template: -------------------------------------------------------------------------------- 1 | FRONTEND_TAG= 2 | INFURA_ID= 3 | LIQUITY_SUBGRAPH_ID= 4 | -------------------------------------------------------------------------------- /public/cbw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BanklessDAO/bankless-loans/HEAD/public/cbw.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BanklessDAO/bankless-loans/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /public/portis-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BanklessDAO/bankless-loans/HEAD/public/portis-icon.png -------------------------------------------------------------------------------- /public/walletIcon.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BanklessDAO/bankless-loans/HEAD/public/walletIcon.jpg -------------------------------------------------------------------------------- /public/authereum-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BanklessDAO/bankless-loans/HEAD/public/authereum-logo.png -------------------------------------------------------------------------------- /public/bankless-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BanklessDAO/bankless-loans/HEAD/public/bankless-logo.png -------------------------------------------------------------------------------- /public/fortmatic-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BanklessDAO/bankless-loans/HEAD/public/fortmatic-logo.png -------------------------------------------------------------------------------- /public/metamask-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BanklessDAO/bankless-loans/HEAD/public/metamask-logo.png -------------------------------------------------------------------------------- /public/wallet-connect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BanklessDAO/bankless-loans/HEAD/public/wallet-connect.png -------------------------------------------------------------------------------- /public/header-logo-onBlack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BanklessDAO/bankless-loans/HEAD/public/header-logo-onBlack.png -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | echo "pre-commit" 3 | . "$(dirname "$0")/_/husky.sh" 4 | 5 | npx lint-staged --allow-empty 6 | -------------------------------------------------------------------------------- /public/Sk-Modernist-Regular.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BanklessDAO/bankless-loans/HEAD/public/Sk-Modernist-Regular.otf -------------------------------------------------------------------------------- /public/futuristic-background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BanklessDAO/bankless-loans/HEAD/public/futuristic-background.png -------------------------------------------------------------------------------- /src/strings.ts: -------------------------------------------------------------------------------- 1 | export const COIN = 'LUSD' 2 | export const GT = 'LQTY' 3 | export const LP = 'UNI LP' 4 | // export const LP = "ETH/LUSD LP"; 5 | -------------------------------------------------------------------------------- /src/utils/shortenAddress.ts: -------------------------------------------------------------------------------- 1 | export const shortenAddress = (address: string): string => 2 | address.substr(0, 6) + '...' + address.substr(-4) 3 | -------------------------------------------------------------------------------- /data/network/NetworkProvider.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLAPIClient } from "./graph/interface/GraphQLAPIClient"; 2 | 3 | export interface NetworkProvider { 4 | graph(): GraphQLAPIClient 5 | } -------------------------------------------------------------------------------- /src/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import Landing from './landing' 2 | 3 | const LiquityFrontend = (): JSX.Element => { 4 | return 5 | } 6 | 7 | export default LiquityFrontend 8 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "avoid", 3 | "singleQuote": true, 4 | "tabWidth": 4, 5 | "jsxSingleQuote": true, 6 | "jsxBracketSameLine": true, 7 | "semi": false 8 | } 9 | -------------------------------------------------------------------------------- /src/components/HeadingBase.tsx: -------------------------------------------------------------------------------- 1 | import { Heading } from '@chakra-ui/react' 2 | 3 | export const HeadingBase: React.FC = ({ children }) => ( 4 | {children} 5 | ) 6 | -------------------------------------------------------------------------------- /.lintstagedrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | '**/*.ts?(x)': filenames => 3 | `next lint --fix --file ${filenames 4 | .map(file => file.split(process.cwd())[1]) 5 | .join(' --file ')}`, 6 | }; 7 | -------------------------------------------------------------------------------- /src/state/home/hooks.ts: -------------------------------------------------------------------------------- 1 | import type { RootState } from '../index' 2 | 3 | // TODO: Map it from the home state instead of the root state 4 | export const selectLiquityState = (state: RootState) => state.homeState.currentLiquityState -------------------------------------------------------------------------------- /next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/basic-features/typescript for more information. 6 | -------------------------------------------------------------------------------- /src/components/Layout/StyledBox.tsx: -------------------------------------------------------------------------------- 1 | import { Box } from '@chakra-ui/react' 2 | 3 | const StyledBox: React.FC = ({ children }) => ( 4 | {children} 5 | ) 6 | 7 | export default StyledBox 8 | -------------------------------------------------------------------------------- /src/theme/components/input.ts: -------------------------------------------------------------------------------- 1 | const Popover = { 2 | parts: ['field', 'addon'], 3 | baseStyle: { 4 | background: 'red', 5 | }, 6 | sizes: {}, 7 | variants: {}, 8 | defaultProps: {}, 9 | } 10 | 11 | export default Popover 12 | -------------------------------------------------------------------------------- /src/components/Layout/StyledContainer.tsx: -------------------------------------------------------------------------------- 1 | import { Container } from '@chakra-ui/react' 2 | 3 | const StyledContainer: React.FC = ({ children }) => ( 4 | {children} 5 | ) 6 | 7 | export default StyledContainer 8 | -------------------------------------------------------------------------------- /src/hooks/usePrevious.ts: -------------------------------------------------------------------------------- 1 | import { useRef } from 'react' 2 | 3 | export function usePrevious(value: T) { 4 | const ref = useRef(value) 5 | 6 | const previousValue = ref.current 7 | ref.current = value 8 | 9 | return previousValue 10 | } 11 | -------------------------------------------------------------------------------- /src/state/hooks.ts: -------------------------------------------------------------------------------- 1 | import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux' 2 | import type { RootState, AppDispatch } from './index' 3 | 4 | export const useAppDispatch = () => useDispatch() 5 | export const useAppSelector: TypedUseSelectorHook = useSelector -------------------------------------------------------------------------------- /data/network/graph/interface/GraphQLAPIMapper.ts: -------------------------------------------------------------------------------- 1 | import LiquityState from "../../../../models/LiquityState"; 2 | 3 | /** 4 | * Abstract GraphQL response mapper. 5 | */ 6 | interface GraphQLAPIMapper { 7 | mapGlobalState(rawState: any): LiquityState 8 | } 9 | 10 | export default GraphQLAPIMapper -------------------------------------------------------------------------------- /src/theme/components/number-input.ts: -------------------------------------------------------------------------------- 1 | const NumberInput = { 2 | parts: [ 3 | "root", 4 | "field", 5 | ], 6 | baseStyle: { 7 | 8 | }, 9 | sizes: { 10 | 11 | }, 12 | variants: { 13 | 14 | }, 15 | defaultProps: {} 16 | } 17 | 18 | export default NumberInput; -------------------------------------------------------------------------------- /src/theme/fonts.ts: -------------------------------------------------------------------------------- 1 | import '@fontsource/space-grotesk' 2 | import '@fontsource/clear-sans' 3 | 4 | const fonts = { 5 | heading: 'Space Grotesk, sans-serif', 6 | body: 'Space Grotesk, sans-serif', 7 | buttons: { 8 | max: 'Clear Sans, sans-serif', 9 | }, 10 | } 11 | 12 | export default fonts 13 | -------------------------------------------------------------------------------- /services/ServiceProvider.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLAPIClient } from "../data/network/graph/interface/GraphQLAPIClient"; 2 | import DiscoveryService from "./discovery/interface/DiscoveryService"; 3 | 4 | interface ServiceProvider { 5 | discovery( 6 | graph: GraphQLAPIClient 7 | ): DiscoveryService 8 | } 9 | 10 | export default ServiceProvider -------------------------------------------------------------------------------- /src/pages/api/hello.ts: -------------------------------------------------------------------------------- 1 | // Next.js API route support: https://nextjs.org/docs/api-routes/introduction 2 | import type { NextApiRequest, NextApiResponse } from 'next' 3 | 4 | type Data = { 5 | name: string 6 | } 7 | 8 | export default (req: NextApiRequest, res: NextApiResponse) => { 9 | res.status(200).json({ name: 'John Doe' }) 10 | } 11 | -------------------------------------------------------------------------------- /src/components/Layout/StyledFlex.tsx: -------------------------------------------------------------------------------- 1 | import { Flex, useStyleConfig } from '@chakra-ui/react'; 2 | 3 | 4 | export default function StyledFlex(props: any) { 5 | 6 | const { variant, children, ...rest } = props; 7 | 8 | const styles = useStyleConfig("Section", { variant }); 9 | 10 | return ( 11 | 12 | { children } 13 | 14 | ) 15 | } -------------------------------------------------------------------------------- /src/state/index.ts: -------------------------------------------------------------------------------- 1 | import { configureStore } from '@reduxjs/toolkit' 2 | import thunk from 'redux-thunk'; 3 | import homeStateReducer from './home/reducer' 4 | 5 | export const store = configureStore({ 6 | reducer: { 7 | homeState: homeStateReducer, 8 | }, 9 | middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(thunk) 10 | }) 11 | 12 | export type RootState = ReturnType 13 | export type AppDispatch = typeof store.dispatch -------------------------------------------------------------------------------- /src/theme/components/box.ts: -------------------------------------------------------------------------------- 1 | const Box = { 2 | baseStyle: { 3 | fontWeight: 'medium', 4 | boxSizing: 'border-box', 5 | mx: '20px', 6 | }, 7 | sizes: { 8 | primary: {}, 9 | secondary: {}, 10 | }, 11 | variants: { 12 | // TODO: Relative units 13 | tooltip: { 14 | bg: ['red', 'black', 'orange'], 15 | }, 16 | }, 17 | defaultProps: {}, 18 | } 19 | 20 | export default Box 21 | -------------------------------------------------------------------------------- /src/theme/styles.ts: -------------------------------------------------------------------------------- 1 | import { mode } from '@chakra-ui/theme-tools' 2 | 3 | const styles = { 4 | global: (props: object) => ({ 5 | html: { 6 | fontSize: '18px', 7 | }, 8 | body: { 9 | background: '#000000', 10 | //backgroundSize: '100% auto', 11 | }, 12 | tippyPopper: { 13 | bg: '#ff0000', 14 | color: 'ff0000', 15 | }, 16 | }), 17 | } 18 | 19 | export default styles 20 | -------------------------------------------------------------------------------- /src/hooks/WalletContext.tsx: -------------------------------------------------------------------------------- 1 | import React, { Dispatch } from 'react' 2 | import { ConnectionState, ConnectionAction } from './useWalletReducer' 3 | 4 | interface WalletContextProps { 5 | connectionState: ConnectionState 6 | dispatch: Dispatch 7 | } 8 | 9 | const WalletContext = React.createContext({} as WalletContextProps) 10 | 11 | export function useWalletContext() { 12 | return React.useContext(WalletContext) 13 | } 14 | 15 | export default WalletContext 16 | -------------------------------------------------------------------------------- /services/discovery/interface/DiscoveryService.ts: -------------------------------------------------------------------------------- 1 | import LiquityState from '../../../models/LiquityState' 2 | import { GraphQLAPIClient } from '../../../data/network/graph/interface/GraphQLAPIClient' 3 | 4 | /** 5 | * Discovery service for global historical data consumption. 6 | */ 7 | interface DiscoveryService { 8 | readonly graphQLAPIClient: GraphQLAPIClient 9 | 10 | /** 11 | * Get the most recent state of the protocol. 12 | */ 13 | getCurrentState(): Promise 14 | } 15 | 16 | export default DiscoveryService -------------------------------------------------------------------------------- /src/hooks/useLiquityStore.ts: -------------------------------------------------------------------------------- 1 | import { useContext } from 'react' 2 | 3 | import { LiquityStore } from '@liquity/lib-base' 4 | 5 | import { LiquityStoreContext } from '../components/LiquityStoreProvider' 6 | 7 | export const useLiquityStore = (): LiquityStore => { 8 | const store = useContext(LiquityStoreContext) 9 | 10 | if (!store) { 11 | throw new Error( 12 | 'You must provide a LiquityStore via LiquityStoreProvider' 13 | ) 14 | } 15 | 16 | return store as LiquityStore 17 | } 18 | -------------------------------------------------------------------------------- /data/network/graph/queries/index.ts: -------------------------------------------------------------------------------- 1 | import { DocumentNode } from '@apollo/client/core' 2 | import gql from 'graphql-tag'; 3 | 4 | const GLOBAL_STATE_QUERY: DocumentNode = gql` 5 | query { 6 | globals { 7 | currentSystemState { 8 | id 9 | price 10 | sequenceNumber 11 | totalCollateral 12 | totalCollateralRatio 13 | tokensInStabilityPool 14 | totalLQTYTokensStaked 15 | } 16 | liquidationCount 17 | } 18 | } 19 | ` 20 | 21 | export { 22 | GLOBAL_STATE_QUERY 23 | } 24 | -------------------------------------------------------------------------------- /data/network/DefaultNetworkProvider.ts: -------------------------------------------------------------------------------- 1 | import { NetworkProvider } from "./NetworkProvider"; 2 | import { GraphQLAPIClient } from "./graph/interface/GraphQLAPIClient"; 3 | import TheGraphAPIClient from "./graph/implementation/TheGraphAPIClient"; 4 | import TheGraphAPIMapper from "./graph/implementation/TheGraphAPIMapper"; 5 | 6 | class DefaultNetworkProvider implements NetworkProvider { 7 | graph(): GraphQLAPIClient { 8 | return new TheGraphAPIClient( 9 | new TheGraphAPIMapper() 10 | ) 11 | } 12 | } 13 | 14 | export default DefaultNetworkProvider -------------------------------------------------------------------------------- /src/components/Layout/VStackBase.tsx: -------------------------------------------------------------------------------- 1 | import { VStack } from '@chakra-ui/react' 2 | 3 | type VStackBaseProps = { 4 | children: React.ReactNode 5 | maxH: string 6 | } 7 | 8 | export const VStackBase = ({ children, maxH = '960px' }: VStackBaseProps) => ( 9 | 18 | {children} 19 | 20 | ) 21 | -------------------------------------------------------------------------------- /src/hooks/useIsMobile.ts: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react' 2 | 3 | const useIsMobile = () => { 4 | const [width, setWidth] = useState(window.innerWidth) 5 | const handleWindowSizeChange = () => { 6 | setWidth(window.innerWidth) 7 | } 8 | 9 | useEffect(() => { 10 | window.addEventListener('resize', handleWindowSizeChange) 11 | return () => { 12 | window.removeEventListener('resize', handleWindowSizeChange) 13 | } 14 | }, []) 15 | 16 | return width <= 768 17 | } 18 | 19 | export default useIsMobile 20 | -------------------------------------------------------------------------------- /src/components/Badge.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Flex } from '@chakra-ui/react' 3 | 4 | export const Badge: React.FC = ({ children }) => { 5 | return ( 6 | 18 | {children} 19 | 20 | ) 21 | } 22 | -------------------------------------------------------------------------------- /src/components/Staking/Staking.tsx: -------------------------------------------------------------------------------- 1 | import { useStakingView } from './context/StakingViewContext' 2 | import { ReadOnlyStake } from './ReadOnlyStake' 3 | import { StakingManager } from './StakingManager' 4 | import { NoStake } from './NoStake' 5 | 6 | export const Staking: React.FC = () => { 7 | const { view } = useStakingView() 8 | 9 | switch (view) { 10 | case 'ACTIVE': 11 | return 12 | 13 | case 'ADJUSTING': 14 | return 15 | 16 | case 'NONE': 17 | return 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /services/DefaultServiceProvider.ts: -------------------------------------------------------------------------------- 1 | import ServiceProvider from "./ServiceProvider"; 2 | import DiscoveryService from "./discovery/interface/DiscoveryService"; 3 | import DefaultDiscoveryService from "./discovery/implementation/DefaultDiscoveryService"; 4 | import { GraphQLAPIClient } from "../data/network/graph/interface/GraphQLAPIClient"; 5 | 6 | class DefaultServiceProvider implements ServiceProvider { 7 | discovery( 8 | graph: GraphQLAPIClient 9 | ): DiscoveryService { 10 | return new DefaultDiscoveryService( 11 | graph 12 | ) 13 | } 14 | } 15 | 16 | export default DefaultServiceProvider -------------------------------------------------------------------------------- /src/theme/components/section.ts: -------------------------------------------------------------------------------- 1 | const Section = { 2 | baseStyle: { 3 | bg: "interactive.dark", 4 | borderRadius: "18px", 5 | padding: "40px", 6 | mx: "10px", 7 | my: "5px" 8 | }, 9 | sizes: {}, 10 | variants: { 11 | nav: { 12 | bg: "interactive.dark", 13 | width: "100%", 14 | display: "flex", 15 | justifyContent: "space-around", 16 | alignItems: "center", 17 | height: "100px" 18 | } 19 | }, 20 | defaultProps: { 21 | } 22 | }; 23 | 24 | export default Section; 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env*.local 29 | .env.local 30 | .env.development.local 31 | .env.test.local 32 | .env.production.local 33 | 34 | # vercel 35 | .vercel 36 | 37 | .eslintcache 38 | /utils/connectors.ts 39 | 40 | .vscode 41 | -------------------------------------------------------------------------------- /src/components/DisclaimerLink.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Link } from '@chakra-ui/react' 3 | import NextLink from 'next/link' 4 | 5 | export default function DisclaimerLink() { 6 | return ( 7 | 8 | 16 | Disclaimer 17 | 18 | 19 | ) 20 | } 21 | -------------------------------------------------------------------------------- /src/state/home/actions.ts: -------------------------------------------------------------------------------- 1 | import { createAsyncThunk } from '@reduxjs/toolkit' 2 | import DefaultServiceProvider from '../../../services/DefaultServiceProvider' 3 | import DefaultNetworkProvider from '../../../data/network/DefaultNetworkProvider' 4 | 5 | /* 6 | TODO: Keep the active Service references somethere else. 7 | */ 8 | const graphAPIClient = (new DefaultNetworkProvider()).graph() 9 | const discoveryService = (new DefaultServiceProvider()).discovery(graphAPIClient) 10 | 11 | export const fetchHomeState = createAsyncThunk( 12 | 'homeState/fetch', 13 | async () => { 14 | return await discoveryService.getCurrentState() 15 | } 16 | ) -------------------------------------------------------------------------------- /data/network/graph/interface/GraphQLAPIClient.ts: -------------------------------------------------------------------------------- 1 | import { DocumentNode, OperationVariables } from '@apollo/client/core' 2 | import { 3 | GLOBAL_STATE_QUERY 4 | } from '../queries' 5 | import GraphQLAPIMapper from './GraphQLAPIMapper' 6 | 7 | /** 8 | * Abstract GraphQL API client 9 | * @property {GraphQLAPIMapper} mapper Model mapper 10 | */ 11 | export interface GraphQLAPIClient { 12 | readonly mapper: GraphQLAPIMapper 13 | 14 | /** 15 | * Universal query. 16 | */ 17 | query( 18 | query: DocumentNode, 19 | vars: OperationVariables, 20 | mappingCallback: (mapper: GraphQLAPIMapper, response: any) => T 21 | ): Promise 22 | } 23 | 24 | export { 25 | GLOBAL_STATE_QUERY 26 | } -------------------------------------------------------------------------------- /src/styles/globals.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | font-size: 8px; 4 | padding: 0; 5 | margin: 0; 6 | font-family: Sk-Modernist-Regular, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, 7 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; 8 | } 9 | 10 | a { 11 | color: inherit; 12 | text-decoration: none; 13 | } 14 | 15 | * { 16 | box-sizing: border-box; 17 | } 18 | 19 | .tippy-popper[x-placement=top] .tippy-tooltip.mythemename-theme [x-arrow] { 20 | border-top: 7px solid red; /* your color here */ 21 | } 22 | 23 | .tippy-popper[x-placement=bottom] .tippy-tooltip.mythemename-theme [x-arrow] { 24 | border-bottom: 7px solid red; /* your color here */ 25 | } 26 | -------------------------------------------------------------------------------- /src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export function escapeRegExp(string: string): string { 2 | return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') // $& means the whole matched string 3 | } 4 | 5 | export const truncateAddress = (address: string | null) => { 6 | if (!address) return '' 7 | const match = address.match( 8 | /^(0x[a-zA-Z0-9]{2})[a-zA-Z0-9]+([a-zA-Z0-9]{2})$/ 9 | ) 10 | if (!match) return address 11 | return `${match[1]}…${match[2]}` 12 | } 13 | 14 | export const toHex = (num: string) => { 15 | const val = Number(num) 16 | return '0x' + val.toString(16) 17 | } 18 | 19 | export const hasClass = (className: string) => 20 | document.body.classList.contains(className) 21 | -------------------------------------------------------------------------------- /src/components/Trove/context/TroveViewContext.tsx: -------------------------------------------------------------------------------- 1 | import { createContext, useContext } from 'react' 2 | import type { TroveView, TroveEvent } from './types' 3 | 4 | type TroveViewContextType = { 5 | view: TroveView 6 | dispatchEvent: (event: TroveEvent) => void 7 | } 8 | 9 | export const TroveViewContext = createContext(null) 10 | 11 | export const useTroveView = (): TroveViewContextType => { 12 | const context: TroveViewContextType | null = useContext(TroveViewContext) 13 | 14 | if (context === null) { 15 | throw new Error( 16 | 'You must add a into the React tree' 17 | ) 18 | } 19 | 20 | return context 21 | } 22 | -------------------------------------------------------------------------------- /src/components/utils/useWindowSize.tsx: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from 'react' 2 | 3 | function getWindowSize() { 4 | const { innerWidth: width, innerHeight: height } = window 5 | return { 6 | width, 7 | height, 8 | } 9 | } 10 | 11 | export function useWindowSize() { 12 | const [windowDimensions, setWindowDimensions] = useState(getWindowSize()) 13 | 14 | useEffect(() => { 15 | function handleResize() { 16 | setWindowDimensions(getWindowSize()) 17 | } 18 | 19 | window.addEventListener('resize', handleResize) 20 | return () => window.removeEventListener('resize', handleResize) 21 | }, []) 22 | 23 | return windowDimensions 24 | } 25 | -------------------------------------------------------------------------------- /src/components/Stability/RemainingLQTY.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Flex } from '@chakra-ui/react' 3 | 4 | import { LiquityStoreState } from '@liquity/lib-base' 5 | import { useLiquitySelector } from '../../hooks/useLiquitySelector' 6 | 7 | const selector = ({ remainingStabilityPoolLQTYReward }: LiquityStoreState) => ({ 8 | remainingStabilityPoolLQTYReward, 9 | }) 10 | 11 | export const RemainingLQTY: React.FC = () => { 12 | const { remainingStabilityPoolLQTYReward } = useLiquitySelector(selector) 13 | 14 | return ( 15 | 16 | {remainingStabilityPoolLQTYReward.prettify(0)} LQTY remaining 17 | 18 | ) 19 | } 20 | -------------------------------------------------------------------------------- /src/pages/_document.tsx: -------------------------------------------------------------------------------- 1 | import { ColorModeScript } from '@chakra-ui/react' 2 | import NextDocument, { Html, Head, Main, NextScript } from 'next/document' 3 | import theme from '../theme' 4 | 5 | export default class Document extends NextDocument { 6 | render() { 7 | return ( 8 | 9 | 10 | 11 | {/* 👇 Here's the script */} 12 | 15 |
16 | 17 | 18 | 19 | ) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/utils/equals.ts: -------------------------------------------------------------------------------- 1 | type UnknownObject = Record 2 | 3 | const hasOwnProperty = (o: UnknownObject, key: string) => 4 | Object.prototype.hasOwnProperty.call(o, key) 5 | 6 | const shallowEquals = (a: UnknownObject, b: UnknownObject) => { 7 | const keysA = Object.keys(a) 8 | const keysB = Object.keys(b) 9 | 10 | return ( 11 | keysA.length === keysB.length && 12 | keysA.every(key => hasOwnProperty(b, key) && Object.is(a[key], b[key])) 13 | ) 14 | } 15 | 16 | const isObject = (a: unknown): a is UnknownObject => 17 | a !== null && typeof a === 'object' 18 | 19 | export const equals = (a: unknown, b: unknown): boolean => 20 | isObject(a) && isObject(b) ? shallowEquals(a, b) : Object.is(a, b) 21 | -------------------------------------------------------------------------------- /src/components/InfoMessage.tsx: -------------------------------------------------------------------------------- 1 | import { Box, Flex, Heading, Text } from '@chakra-ui/react' 2 | import { Icon } from './Icon' 3 | 4 | type InfoMessageProps = { 5 | title: string 6 | icon?: React.ReactNode 7 | } 8 | 9 | export const InfoMessage: React.FC = ({ 10 | title, 11 | children, 12 | icon, 13 | }) => ( 14 | 15 | 16 | 17 | {icon || } 18 | 19 | 20 | {title} 21 | 22 | 23 | {children} 24 | 25 | ) 26 | -------------------------------------------------------------------------------- /src/components/Stability/context/StabilityViewContext.tsx: -------------------------------------------------------------------------------- 1 | import { createContext, useContext } from 'react' 2 | import type { StabilityView, StabilityEvent } from './types' 3 | 4 | type StabilityViewContextType = { 5 | view: StabilityView 6 | dispatchEvent: (event: StabilityEvent) => void 7 | } 8 | 9 | export const StabilityViewContext = 10 | createContext(null) 11 | 12 | export const useStabilityView = (): StabilityViewContextType => { 13 | const context: StabilityViewContextType | null = 14 | useContext(StabilityViewContext) 15 | 16 | if (context === null) { 17 | throw new Error( 18 | 'You must add a into the React tree' 19 | ) 20 | } 21 | 22 | return context 23 | } 24 | -------------------------------------------------------------------------------- /src/components/Warning.tsx: -------------------------------------------------------------------------------- 1 | import { Box, Flex, Text } from '@chakra-ui/react' 2 | import { Icon } from './Icon' 3 | 4 | export const Warning: React.FC = ({ children }) => ( 5 | 19 | 20 | 21 | {children} 22 | 23 | 24 | ) 25 | -------------------------------------------------------------------------------- /src/hooks/useLiquitySelector.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useReducer } from 'react' 2 | 3 | import { LiquityStoreState } from '@liquity/lib-base' 4 | 5 | import { equals } from '../utils/equals' 6 | import { useLiquityStore } from './useLiquityStore' 7 | 8 | export const useLiquitySelector = ( 9 | select: (state: LiquityStoreState) => S 10 | ): S => { 11 | const store = useLiquityStore() 12 | const [, rerender] = useReducer(() => ({}), {}) 13 | 14 | useEffect( 15 | () => 16 | store.subscribe(({ newState, oldState }) => { 17 | if (!equals(select(newState), select(oldState))) { 18 | rerender() 19 | } 20 | }), 21 | [store, select] 22 | ) 23 | 24 | return select(store.state) 25 | } 26 | -------------------------------------------------------------------------------- /models/LiquityState.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Global Liquity protocol state. 3 | */ 4 | class LiquityState { 5 | readonly totalCollateral: number 6 | readonly totalCollateralRatio: number 7 | readonly totalLQTYTokensStaked: number 8 | readonly tokensInStabilityPool: number 9 | readonly ethPrice: number 10 | 11 | constructor( 12 | totalCollateral: number, 13 | totalCollateralRatio: number, 14 | totalLQTYTokensStaked: number, 15 | tokensInStabilityPool: number, 16 | ethPrice: number 17 | ) { 18 | this.totalCollateral = totalCollateral 19 | this.totalCollateralRatio = totalCollateralRatio 20 | this.totalLQTYTokensStaked = totalLQTYTokensStaked 21 | this.tokensInStabilityPool = tokensInStabilityPool 22 | this.ethPrice = ethPrice 23 | } 24 | } 25 | 26 | export default LiquityState -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | 8 | updates: 9 | - package-ecosystem: "github-actions" 10 | # Workflow files stored in the 11 | # default location of `.github/workflows` 12 | directory: "/" 13 | schedule: 14 | interval: "daily" 15 | target-branch: "develop" 16 | 17 | - package-ecosystem: "npm" # See documentation for possible values 18 | directory: "/" # Location of package manifests 19 | schedule: 20 | interval: "daily" 21 | target-branch: "develop" 22 | -------------------------------------------------------------------------------- /src/components/Stability/context/types.ts: -------------------------------------------------------------------------------- 1 | type NoneView = 'NONE' 2 | type DepositingView = 'DEPOSITING' 3 | type ActiveView = 'ACTIVE' 4 | type AdjustingView = 'ADJUSTING' 5 | 6 | export type StabilityView = 7 | | NoneView 8 | | DepositingView 9 | | ActiveView 10 | | AdjustingView 11 | 12 | type DepositPressedEvent = 'DEPOSIT_PRESSED' 13 | type AdjustDepositPressedEvent = 'ADJUST_DEPOSIT_PRESSED' 14 | type CancelPressedEvent = 'CANCEL_PRESSED' 15 | type DepositConfirmedEvent = 'DEPOSIT_CONFIRMED' 16 | type RewardsClaimedEvent = 'REWARDS_CLAIMED' 17 | type DepositEmptiedEvemt = 'DEPOSIT_EMPTIED' 18 | 19 | export type StabilityEvent = 20 | | DepositPressedEvent 21 | | AdjustDepositPressedEvent 22 | | CancelPressedEvent 23 | | DepositConfirmedEvent 24 | | RewardsClaimedEvent 25 | | DepositEmptiedEvemt 26 | -------------------------------------------------------------------------------- /src/components/Stability/Stability.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { StabilityDepositManager } from './StabilityDepositManager' 3 | import { ActiveDeposit } from './ActiveDeposit' 4 | import { NoDeposit } from './NoDeposit' 5 | import { useStabilityView } from './context/StabilityViewContext' 6 | 7 | export const Stability: React.FC = props => { 8 | const { view } = useStabilityView() 9 | 10 | switch (view) { 11 | case 'NONE': { 12 | return 13 | } 14 | case 'DEPOSITING': { 15 | return 16 | } 17 | case 'ADJUSTING': { 18 | return 19 | } 20 | case 'ACTIVE': { 21 | return 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/components/ErrorDescription.tsx: -------------------------------------------------------------------------------- 1 | import { Box, Flex, Text } from '@chakra-ui/react' 2 | import { Icon } from './Icon' 3 | 4 | export const ErrorDescription: React.FC = ({ children }) => ( 5 | 22 | 23 | 24 | {children} 25 | 26 | 27 | ) 28 | -------------------------------------------------------------------------------- /src/theme/components/checkbox.ts: -------------------------------------------------------------------------------- 1 | const Checkbox = { 2 | parts: [ 3 | "container", 4 | "control", 5 | "label", 6 | "icon" 7 | ], 8 | baseStyle: { 9 | control: { 10 | borderColor: 'interactive.dark', 11 | _hover: { 12 | borderColor: "interactive.purple" 13 | }, 14 | _disabled: { 15 | borderColor: "rgba(0,0,0,0)", 16 | pointerEvents: "none", 17 | backgroundColor: "interacitve.grey", 18 | border: "none" 19 | }, 20 | _checked: { 21 | borderColor: "rgba(0,0,0,0)", 22 | background: "supplementary.green" 23 | } 24 | } 25 | }, 26 | sizes: { 27 | }, 28 | variants: { 29 | }, 30 | defaultProps: {} 31 | } 32 | 33 | export default Checkbox; -------------------------------------------------------------------------------- /src/theme/colors.ts: -------------------------------------------------------------------------------- 1 | const colors = { 2 | background: { 3 | grey: '#1f1f1f', 4 | blue: '#89A7F4', 5 | }, 6 | interactive: { 7 | dark: '#1E2230', 8 | dimmedBlue: '#4C5E9D', 9 | purple: '#6d29fe', 10 | darkPurple: '#20113f', 11 | darkGrey: '#2f2f2f', 12 | grey: '#CECECE', 13 | white: '#FFFFFF', 14 | transparentWhite: 'rgba(255, 255, 255, 0.35)', 15 | gray: { 16 | '13': '#131313', 17 | '22': '#222222', 18 | '24': '#242424', 19 | '2F': '#2F2F2F', 20 | '50': '#505050', 21 | '7D': '#7D7D7D', 22 | A1: '#A1A1A1', 23 | B5: '#B5B5B5', 24 | }, 25 | }, 26 | supplementary: { 27 | green: '#1EC02E', 28 | red: '#FA2D2D', 29 | orange: '#FF8A00', 30 | }, 31 | } 32 | 33 | export default colors 34 | -------------------------------------------------------------------------------- /src/components/Layout/CardBase.tsx: -------------------------------------------------------------------------------- 1 | import { Box } from '@chakra-ui/react' 2 | import { useWindowSize } from '../utils/useWindowSize' 3 | 4 | export const CardBase: React.FC = ({ children }) => { 5 | const { width } = useWindowSize() 6 | const stylesPaddingTotal = 19 * 2 // 19px on each side 7 | 8 | return ( 9 | 24 | {children && children} 25 | 26 | ) 27 | } 28 | -------------------------------------------------------------------------------- /data/network/graph/implementation/TheGraphAPIMapper.ts: -------------------------------------------------------------------------------- 1 | import LiquityState from "../../../../models/LiquityState" 2 | import GraphQLAPIMapper from "../interface/GraphQLAPIMapper" 3 | 4 | /** 5 | * GraphQL mapper implementation accepting objects returned from the subgraph. 6 | */ 7 | class TheGraphAPIMapper implements GraphQLAPIMapper { 8 | mapGlobalState(rawState: any): LiquityState { 9 | console.log('Mapping: ' + rawState) 10 | 11 | try { 12 | return new LiquityState( 13 | Number.parseFloat(rawState.totalCollateral), 14 | Number.parseFloat(rawState.totalCollateralRatio), 15 | Number.parseFloat(rawState.totalLQTYTokensStaked), 16 | Number.parseFloat(rawState.tokensInStabilityPool), 17 | Number.parseFloat(rawState.price) 18 | ) 19 | } catch { 20 | throw new Error("Mapping failed") 21 | } 22 | } 23 | } 24 | 25 | export default TheGraphAPIMapper -------------------------------------------------------------------------------- /src/theme/components/popover.ts: -------------------------------------------------------------------------------- 1 | const Popover = { 2 | parts: [ 3 | "pop", 4 | "content", 5 | "header", 6 | "body", 7 | "footer", 8 | "arrow" 9 | ], 10 | baseStyle: { 11 | content: { 12 | background: "interactive.dark", 13 | minWidth: "393px", 14 | lineHeight: "22px", 15 | bg: "interactive.dark", 16 | border: "none", 17 | fontSize: "1rem" 18 | }, 19 | header: { 20 | padding: "14px 26px 0 20px", 21 | border: "none", 22 | color:"interactive.darkGrey" 23 | }, 24 | body: { 25 | padding: "14px 26px 14px 20px", 26 | color: "white" 27 | } 28 | }, 29 | sizes: { 30 | 31 | }, 32 | variants: { 33 | 34 | }, 35 | defaultProps: {} 36 | } 37 | 38 | export default Popover; -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "esnext", 5 | "moduleResolution": "node", 6 | "declaration": true, 7 | "lib": [ 8 | "dom", 9 | "dom.iterable", 10 | "esnext" 11 | ], 12 | "baseUrl": "src", 13 | "allowJs": true, 14 | "skipLibCheck": true, 15 | "strict": true, 16 | "forceConsistentCasingInFileNames": true, 17 | "noEmit": true, 18 | "esModuleInterop": true, 19 | "resolveJsonModule": true, 20 | "isolatedModules": true, 21 | "jsx": "preserve", 22 | "allowSyntheticDefaultImports": true, 23 | "typeRoots": [ 24 | "./types", 25 | "./node_modules/@types" 26 | ], 27 | "incremental": true 28 | }, 29 | "include": [ 30 | "next-env.d.ts", 31 | "**/*.ts", 32 | "**/*.tsx", 33 | "src" 34 | ], 35 | "exclude": [ 36 | "node_modules", 37 | "typings" 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@typescript-eslint/parser", 3 | "parserOptions": { 4 | "ecmaVersion": 2017, 5 | "ecmaFeatures": { 6 | "tsx": true, 7 | "jsx": true 8 | }, 9 | "sourceType": "module", 10 | "experimentalObjectRestSpread": true 11 | }, 12 | "env": { 13 | "browser": true, 14 | "node": true, 15 | "es6": true 16 | }, 17 | "extends": [ 18 | "plugin:@typescript-eslint/recommended", 19 | "next", 20 | "prettier" 21 | ], 22 | "plugins": [ 23 | "@typescript-eslint", 24 | "prettier" 25 | ], 26 | "settings": { 27 | "react": { 28 | "version": "detect" 29 | } 30 | }, 31 | "rules": { 32 | "react/prop-types": "off", 33 | "react/jsx-key": [1, { "checkFragmentShorthand": true }] 34 | }, 35 | "ignorePatterns": [ 36 | "dist" 37 | ] 38 | } 39 | -------------------------------------------------------------------------------- /src/components/Staking/StakingManagerAction.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from '@chakra-ui/react' 2 | import { Decimal, LQTYStakeChange } from '@liquity/lib-base' 3 | import { useLiquity } from '../../hooks/LiquityContext' 4 | import { useTransactionFunction } from '../Transaction' 5 | 6 | type StakingActionProps = { 7 | change: LQTYStakeChange 8 | } 9 | 10 | export const StakingManagerAction: React.FC = ({ 11 | change, 12 | children, 13 | }) => { 14 | const { liquity } = useLiquity() 15 | 16 | const [sendTransaction] = useTransactionFunction( 17 | 'stake', 18 | change.stakeLQTY 19 | ? liquity.send.stakeLQTY.bind(liquity.send, change.stakeLQTY) 20 | : liquity.send.unstakeLQTY.bind(liquity.send, change.unstakeLQTY) 21 | ) 22 | 23 | return ( 24 | 27 | ) 28 | } 29 | -------------------------------------------------------------------------------- /src/state/home/reducer.ts: -------------------------------------------------------------------------------- 1 | import { createSlice, PayloadAction } from '@reduxjs/toolkit' 2 | import LiquityState from '../../../models/LiquityState' 3 | import { fetchHomeState } from './actions' 4 | 5 | export interface HomeState { 6 | currentLiquityState: LiquityState | null 7 | } 8 | 9 | const initialState: HomeState = { 10 | currentLiquityState: null, 11 | } as HomeState 12 | 13 | const homeState = createSlice({ 14 | name: 'homeState', 15 | initialState, 16 | reducers: { 17 | setLiquityState: (state, action: PayloadAction) => { 18 | state.currentLiquityState = action.payload 19 | }, 20 | }, 21 | extraReducers: builder => { 22 | builder.addCase(fetchHomeState.fulfilled, (state, action) => { 23 | state.currentLiquityState = action.payload 24 | }) 25 | }, 26 | }) 27 | 28 | export const { setLiquityState } = homeState.actions 29 | 30 | export default homeState.reducer 31 | -------------------------------------------------------------------------------- /src/components/Stability/actions/ClaimRewards.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Button } from '@chakra-ui/react' 3 | 4 | import { useLiquity } from '../../../hooks/LiquityContext' 5 | import { useTransactionFunction } from '../../Transaction' 6 | 7 | type ClaimRewardsProps = { 8 | disabled?: boolean 9 | } 10 | 11 | export const ClaimRewards: React.FC = ({ 12 | disabled, 13 | children, 14 | }) => { 15 | const { liquity } = useLiquity() 16 | 17 | const [sendTransaction] = useTransactionFunction( 18 | 'stability-deposit', 19 | liquity.send.withdrawGainsFromStabilityPool.bind(liquity.send) 20 | ) 21 | 22 | return ( 23 | 32 | ) 33 | } 34 | -------------------------------------------------------------------------------- /src/components/ActionDescription.tsx: -------------------------------------------------------------------------------- 1 | import { Text, HStack } from '@chakra-ui/react' 2 | import { Icon } from './Icon' 3 | 4 | export const ActionDescription: React.FC = ({ children }) => ( 5 | *:first-of-type': { 18 | marginRight: '0.25em', 19 | }, 20 | }} 21 | > 22 | 23 | 24 | {children} 25 | 26 | 27 | ) 28 | 29 | export const Amount: React.FC = ({ children }) => ( 30 | {children} 31 | ) 32 | -------------------------------------------------------------------------------- /src/components/Trove/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'TroveType' { 2 | interface Trove { 3 | // loan: string 4 | // addLoan: (name: string) => void 5 | children?: JSX.Element 6 | } 7 | 8 | interface StaticAmountsProps { 9 | inputID: string; 10 | labelledBy?: string; 11 | amount: string; 12 | unit?: string; 13 | color?: string; 14 | pendingAmount?: string; 15 | pendingColor?: string; 16 | onClick?: () => void; 17 | } 18 | 19 | type DisabledEditableRowProps = Omit & { 20 | label: string 21 | } 22 | 23 | type EditableRowProps = DisabledEditableRowProps & { 24 | label: string 25 | editingState: [string | undefined, (editing: string | undefined) => void] 26 | editedAmount: string 27 | setEditedAmount: (editedAmount: string) => void 28 | maxAmount?: string 29 | maxedOut?: boolean 30 | }; 31 | } 32 | 33 | -------------------------------------------------------------------------------- /src/hooks/ModalContext.tsx: -------------------------------------------------------------------------------- 1 | import React, { useContext, createContext, ReactNode } from 'react' 2 | import { useDisclosure } from '@chakra-ui/hooks' 3 | 4 | interface ModalContextProps { 5 | isModalOpen: boolean 6 | closeModal: () => void 7 | openModal: () => void 8 | children?: ReactNode 9 | } 10 | 11 | const ModalContext = createContext({} as ModalContextProps) 12 | 13 | export function ModalProvider({ children }: ModalContextProps) { 14 | const modal = useModalDisclosure() 15 | return ( 16 | {children} 17 | ) 18 | } 19 | 20 | export const useModal = () => { 21 | return useContext(ModalContext) 22 | } 23 | 24 | function useModalDisclosure() { 25 | const { isOpen, onOpen, onClose } = useDisclosure() 26 | const isModalOpen = isOpen 27 | const closeModal = onClose 28 | const openModal = onOpen 29 | return { 30 | isModalOpen, 31 | closeModal, 32 | openModal, 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/components/Staking/StakingGainsAction.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from '@chakra-ui/react' 2 | import { LiquityStoreState } from '@liquity/lib-base' 3 | import { useLiquitySelector } from '../../hooks/useLiquitySelector' 4 | import { useLiquity } from '../../hooks/LiquityContext' 5 | import { useTransactionFunction } from '../Transaction' 6 | 7 | const selectLQTYStake = ({ lqtyStake }: LiquityStoreState) => lqtyStake 8 | 9 | export const StakingGainsAction: React.FC = () => { 10 | const { liquity } = useLiquity() 11 | const { collateralGain, lusdGain } = useLiquitySelector(selectLQTYStake) 12 | 13 | const [sendTransaction] = useTransactionFunction( 14 | 'stake', 15 | liquity.send.withdrawGainsFromStaking.bind(liquity.send) 16 | ) 17 | return ( 18 | 25 | ) 26 | } 27 | -------------------------------------------------------------------------------- /src/theme/components/heading.ts: -------------------------------------------------------------------------------- 1 | const Heading = { 2 | sizes: { 3 | // Move sizes here? 4 | }, 5 | variants: { // variants more for presentation. 6 | h1:{ 7 | fontSize: 72, 8 | lineHeight: "70px", 9 | fontWeight: "500", 10 | letterSpacing: "-1.5px" 11 | }, 12 | h2:{ 13 | fontSize: 48, 14 | lineHeight: "50px", 15 | fontWeight: "500", 16 | letterSpacing: "-0.5px" 17 | }, 18 | h3:{ 19 | fontSize: 30, 20 | lineHeight: "32px", 21 | fontWeight: "500", 22 | letterSpacing: "-0.5px" 23 | }, 24 | h4:{ 25 | fontSize: 24, 26 | lineHeight: "26px", 27 | fontWeight: "500", 28 | }, 29 | h5:{ 30 | fontSize: 18, 31 | lineHeight: "22px", 32 | fontWeight: "500", 33 | } 34 | }, 35 | defaultProps: {} 36 | 37 | } 38 | 39 | 40 | export default Heading; -------------------------------------------------------------------------------- /src/components/ConnectionConfirmationDialog.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Text, Box } from '@chakra-ui/react' 3 | import { WaitingDialog } from './WaitingDialog' 4 | 5 | type ConnectionConfirmationDialogProps = { 6 | title: string 7 | icon?: React.ReactNode 8 | onCancel: () => void 9 | } 10 | 11 | export const ConnectionConfirmationDialog: React.FC = 12 | ({ title, icon, onCancel, children }) => ( 13 | 18 | 19 | Waiting for connection confirmation... 20 | 21 | This won’t cost you any Ether 22 | 23 | } 24 | cancelLabel='Cancel connection' 25 | onCancel={onCancel} 26 | > 27 | {children} 28 | 29 | ) 30 | -------------------------------------------------------------------------------- /src/components/InfoIcon.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Tippy, { TippyProps } from '@tippyjs/react' 3 | import { Icon } from './Icon' 4 | import { FontAwesomeIconProps } from '@fortawesome/react-fontawesome' 5 | import { roundArrow } from 'tippy.js' 6 | import 'tippy.js/dist/svg-arrow.css' 7 | import { color } from '@chakra-ui/styled-system' 8 | export type InfoIconProps = Pick & 9 | Pick & { 10 | tooltip: React.ReactNode 11 | } 12 | 13 | export const InfoIcon: React.FC = ({ 14 | placement = 'right', 15 | tooltip, 16 | size = '1x', 17 | }) => { 18 | return ( 19 | 26 | 27 |   28 | 29 | 30 | 31 | ) 32 | } 33 | -------------------------------------------------------------------------------- /src/components/Staking/context/StakingViewContext.tsx: -------------------------------------------------------------------------------- 1 | import { createContext, useContext } from 'react' 2 | 3 | export type StakingView = 'ACTIVE' | 'ADJUSTING' | 'NONE' 4 | 5 | export type StakingViewAction = { type: 'startAdjusting' | 'cancelAdjusting' } 6 | 7 | export type StakingViewContextType = { 8 | view: StakingView 9 | 10 | // Indicates that a staking TX is pending. 11 | // The panel should be covered with a spinner overlay when this is true. 12 | changePending: boolean 13 | 14 | // Dispatch an action that changes the Staking panel's view. 15 | dispatch: (action: StakingViewAction) => void 16 | } 17 | 18 | export const StakingViewContext = createContext( 19 | null 20 | ) 21 | 22 | export const useStakingView = (): StakingViewContextType => { 23 | const context = useContext(StakingViewContext) 24 | 25 | if (context === null) { 26 | throw new Error( 27 | 'You must add a into the React tree' 28 | ) 29 | } 30 | 31 | return context 32 | } 33 | -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /src/components/LiquityStoreProvider.tsx: -------------------------------------------------------------------------------- 1 | import { LiquityStore } from '@liquity/lib-base' 2 | import React, { createContext, useEffect, useState } from 'react' 3 | 4 | export const LiquityStoreContext = createContext( 5 | undefined 6 | ) 7 | 8 | type LiquityStoreProviderProps = { 9 | store: LiquityStore 10 | loader?: React.ReactNode 11 | } 12 | 13 | export const LiquityStoreProvider: React.FC = ({ 14 | store, 15 | loader, 16 | children, 17 | }) => { 18 | const [loadedStore, setLoadedStore] = useState() 19 | 20 | useEffect(() => { 21 | store.onLoaded = () => setLoadedStore(store) 22 | const stop = store.start() 23 | 24 | return () => { 25 | store.onLoaded = undefined 26 | setLoadedStore(undefined) 27 | stop() 28 | } 29 | }, [store]) 30 | 31 | if (!loadedStore) { 32 | return <>{loader} 33 | } 34 | 35 | return ( 36 | 37 | {children} 38 | 39 | ) 40 | } 41 | -------------------------------------------------------------------------------- /services/discovery/implementation/DefaultDiscoveryService.ts: -------------------------------------------------------------------------------- 1 | import DiscoveryService from '../interface/DiscoveryService' 2 | import LiquityState from '../../../models/LiquityState' 3 | import { 4 | GraphQLAPIClient, 5 | GLOBAL_STATE_QUERY 6 | } from '../../../data/network/graph/interface/GraphQLAPIClient' 7 | 8 | class DefaultDiscoveryService implements DiscoveryService { 9 | readonly graphQLAPIClient: GraphQLAPIClient 10 | 11 | constructor ( 12 | graphQLAPIClient: GraphQLAPIClient 13 | ) { 14 | this.graphQLAPIClient = graphQLAPIClient 15 | } 16 | 17 | /** 18 | * Get the most recent state of the protocol. 19 | */ 20 | async getCurrentState(): Promise { 21 | // Get indexed on-chain data 22 | 23 | var liquityState = await this.graphQLAPIClient 24 | .query( 25 | GLOBAL_STATE_QUERY, 26 | {}, 27 | (mapper, response) => { 28 | return mapper.mapGlobalState(response.data.globals[0].currentSystemState) 29 | } 30 | ) 31 | 32 | console.log('Mapped data:') 33 | console.log(liquityState) 34 | 35 | return liquityState 36 | } 37 | } 38 | 39 | export default DefaultDiscoveryService -------------------------------------------------------------------------------- /src/theme/components/text.ts: -------------------------------------------------------------------------------- 1 | const Text = { 2 | baseStyle: { 3 | // html fontsize set to 18px 4 | }, 5 | sizes: {}, 6 | variants: { 7 | body: {}, 8 | datapoint: { 9 | fontSize: 'sm', 10 | }, 11 | bold: { 12 | fontWeight: 'semibold', 13 | }, 14 | 'home-title': { 15 | as: 'h3', 16 | color: '#FFFFFF', 17 | fontSize: ['3xl', '4xl', '5xl', '6xl'], 18 | fontWeight: 'extrabold', 19 | letterSpacing: 'tighter', 20 | lineHeight: 'none', 21 | textAlign: 'center', 22 | }, 23 | 'home-subtitle': { 24 | as: 'h4', 25 | color: '#D8D8D8', 26 | fontSize: ['md', 'lg', 'xl', '24px'], 27 | fontWeight: '400', 28 | letterSpacing: 'tight', 29 | textAlign: 'center', 30 | }, 31 | 'sitemap-title': { 32 | fontSize: 'sm', 33 | fontWeight: 'bold', 34 | letterSpacing: 'widest', 35 | textTransform: 'uppercase', 36 | }, 37 | }, 38 | defaultProps: {}, 39 | } 40 | 41 | export default Text 42 | -------------------------------------------------------------------------------- /src/components/Staking/NoStake.tsx: -------------------------------------------------------------------------------- 1 | import { Box, Button, HStack } from '@chakra-ui/react' 2 | import { ActionDescription } from 'components/ActionDescription' 3 | import { HeadingBase } from 'components/HeadingBase' 4 | import { GT } from '../../strings' 5 | import { CardBase } from '../Layout/CardBase' 6 | import { useStakingView } from './context/StakingViewContext' 7 | 8 | export const NoStake: React.FC = () => { 9 | const { dispatch } = useStakingView() 10 | 11 | return ( 12 | 13 | Staking 14 | 15 | 16 | {`You have not staked ${GT} yet. Stake ${GT} to earn a share of borrowing and redemption fees.`} 17 | 18 | 19 | 26 | 27 | 28 | 29 | ) 30 | } 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Bankless-Loans 2 | 3 | [Liquity](https://www.liquity.org/) front-end built by BanklessDAO. 4 | 5 | ### Tech Stack 6 | #### Built with: 7 | - [Next.js](https://nextjs.org/) (with TypeScript) 8 | - [Chakra-UI](https://chakra-ui.com/) (with styled components for customization) 9 | - [Liquity SDK](https://github.com/liquity/dev) 10 | - [web3-react](https://www.npmjs.com/package/web3-react) 11 | - [Ethers](https://docs.ethers.io/v5/) 12 | 13 | #### Configured with: 14 | - [ESLint](https://eslint.org/) 15 | - [Prettier](https://prettier.io/) 16 | - [Husky](https://typicode.github.io/husky/#/) 17 | 18 | ### Tools/Services: 19 | - [Liquity](https://www.liquity.org/) 20 | - [Gnosis MultiSig](https://gnosis-safe.io/) 21 | 22 | ### How to get started 23 | 24 | - Get familiar with [Liquity](https://www.liquity.org/) project. 25 | - Check out issues. 26 | - Start a discussion. 27 | - Ping @birdman if help is needed. 28 | 29 | ### Setup 30 | 31 | `git clone https://github.com/BanklessDAO/bankless-loans.git` 32 | `yarn install` 33 | `yarn prepare` 34 | `yarn dev` 35 | 36 | Note: On Windows dev env, the app might complain about missing graphql. If this happens, just run `yarn install graphql` and restart the server. 37 | 38 | -------------------------------------------------------------------------------- /src/components/Stability/NoDeposit.tsx: -------------------------------------------------------------------------------- 1 | import React, { useCallback } from 'react' 2 | import { Box, Flex, Button } from '@chakra-ui/react' 3 | import { useStabilityView } from './context/StabilityViewContext' 4 | import { CardBase } from 'components/Layout/CardBase' 5 | import { HeadingBase } from 'components/HeadingBase' 6 | import { ActionDescription } from 'components/ActionDescription' 7 | 8 | export const NoDeposit: React.FC = () => { 9 | const { dispatchEvent } = useStabilityView() 10 | 11 | const handleOpenTrove = useCallback(() => { 12 | dispatchEvent('DEPOSIT_PRESSED') 13 | }, [dispatchEvent]) 14 | 15 | return ( 16 | 17 | Stability Pool 18 | 19 | 20 | {`You have no LUSD in the Stability Pool. You can earn ETH and LQTY rewards by depositing LUSD.`} 21 | 22 | 23 | 24 | 27 | 28 | 29 | 30 | ) 31 | } 32 | -------------------------------------------------------------------------------- /src/components/Stability/StabilityPreview.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Flex, Box, Heading } from '@chakra-ui/react' 3 | import { WalletConnector } from '../WalletConnector' 4 | import { DisabledEditableRow } from '../Trove/Editor' 5 | import { useModal } from 'hooks/ModalContext' 6 | import { CardBase } from '../Layout/CardBase' 7 | 8 | export const StabilityPreview = (): JSX.Element => { 9 | const modal = useModal() 10 | //will need to address hard-coded width for mobile 11 | return ( 12 | 13 | Stability Pool 14 | 15 | 21 | 22 | 23 |

Please connect your wallet to use our services

24 |
25 | 30 |
31 |
32 | ) 33 | } 34 | -------------------------------------------------------------------------------- /src/components/Staking/StakingPreview.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Flex, Box, Heading } from '@chakra-ui/react' 3 | import { WalletConnector } from '../WalletConnector' 4 | import { DisabledEditableRow } from '../Trove/Editor' 5 | import { useModal } from 'hooks/ModalContext' 6 | import { CardBase } from '../Layout/CardBase' 7 | 8 | export const StakingPreview = (): JSX.Element => { 9 | //will need to address hard-coded width for mobile 10 | const modal = useModal() 11 | 12 | return ( 13 | 14 | LQTY Staking 15 | 16 | 22 | 23 | 24 |

Please connect your wallet to use our services

25 |
26 | 31 |
32 |
33 | ) 34 | } 35 | -------------------------------------------------------------------------------- /src/components/Stability/actions/ClaimAndMove.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Button } from '@chakra-ui/react' 3 | import { useLiquity } from '../../../hooks/LiquityContext' 4 | import { useTransactionFunction } from '../../Transaction' 5 | 6 | type ClaimAndMoveProps = { 7 | disabled?: boolean 8 | } 9 | 10 | export const ClaimAndMove: React.FC = ({ 11 | disabled, 12 | children, 13 | }) => { 14 | const { liquity } = useLiquity() 15 | 16 | const [sendTransaction] = useTransactionFunction( 17 | 'stability-deposit', 18 | liquity.send.transferCollateralGainToTrove.bind(liquity.send) 19 | ) 20 | 21 | if (disabled) { 22 | return ( 23 | 26 | ) 27 | } 28 | 29 | return ( 30 | 41 | ) 42 | } 43 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | const ContentSecurityPolicy = ` 2 | default-src 'self'; 3 | ` 4 | 5 | const securityHeaders = [ 6 | { 7 | key: 'X-Frame-Options', 8 | value: 'DENY', 9 | }, 10 | // { 11 | // key: 'Content-Security-Policy', 12 | // value: ContentSecurityPolicy.replace(/\s{2,}/g, ' ').trim(), 13 | // }, 14 | ] 15 | 16 | module.exports = { 17 | reactStrictMode: true, 18 | exportTrailingSlash: true, 19 | async headers() { 20 | return [ 21 | { 22 | // Apply these headers to all routes in your application. 23 | source: '/:path*', 24 | headers: securityHeaders, 25 | }, 26 | ] 27 | }, 28 | experimental: { 29 | images: { 30 | unoptimized: true, 31 | }, 32 | }, 33 | exportPathMap: async function ( 34 | defaultPathMap, 35 | { dev, dir, outDir, distDir, buildId } 36 | ) { 37 | return { 38 | '/': { page: '/' }, 39 | '/borrow': { page: '/borrow' }, 40 | '/pool': { page: '/pool' }, 41 | '/stake': { page: '/stake' }, 42 | '/disclaimer': { page: '/disclaimer' }, 43 | } 44 | }, 45 | } 46 | -------------------------------------------------------------------------------- /src/components/Statistic.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Flex, Box } from '@chakra-ui/react' 3 | import { InfoIcon } from './InfoIcon' 4 | 5 | type StatisticProps = { 6 | name: React.ReactNode 7 | tooltip?: React.ReactNode 8 | } 9 | 10 | export const Statistic: React.FC = ({ 11 | name, 12 | tooltip, 13 | children, 14 | }) => { 15 | return ( 16 | 17 | 25 | {name} 26 | {tooltip && ( 27 | {tooltip}} /> 28 | )} 29 | 30 | 37 | {children} 38 | 39 | 40 | ) 41 | } 42 | -------------------------------------------------------------------------------- /src/components/NavbarTheme.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from '@emotion/styled' 3 | 4 | const StyledSection = styled.section` 5 | display: flex; 6 | align-items: center; 7 | padding: 8px 10px; 8 | background-color: #131313; 9 | border-radius: 18px; 10 | border: 0.8px solid #7d7d7d; 11 | ` 12 | 13 | const StyledButton = styled.button` 14 | height: 28px; 15 | width: 28px; 16 | ` 17 | 18 | export default function NavbarTheme() { 19 | return ( 20 | 21 | 22 | 27 | 31 | 32 | 33 | 34 | ) 35 | } 36 | -------------------------------------------------------------------------------- /src/components/Trove/NoTrove.tsx: -------------------------------------------------------------------------------- 1 | import React, { useCallback } from 'react' 2 | import { Box, Button, HStack } from '@chakra-ui/react' 3 | import { useTroveView } from './context/TroveViewContext' 4 | import { CardBase } from 'components/Layout/CardBase' 5 | import { ActionDescription } from 'components/ActionDescription' 6 | import { HeadingBase } from 'components/HeadingBase' 7 | 8 | export const NoTrove: React.FC = props => { 9 | const { dispatchEvent } = useTroveView() 10 | 11 | const handleOpenTrove = useCallback(() => { 12 | dispatchEvent('OPEN_TROVE_PRESSED') 13 | }, [dispatchEvent]) 14 | 15 | return ( 16 | 17 | Trove 18 | 19 | 20 | {`You haven't borrowed any LUSD yet. You can borrow LUSD by opening a Trove.`} 21 | 22 | 23 | 30 | 31 | 32 | 33 | ) 34 | } 35 | -------------------------------------------------------------------------------- /src/components/NavbarWallet.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Box, Text, chakra, HStack, Button } from '@chakra-ui/react' 3 | import walletImage from '../../public/walletIcon.jpg' 4 | import { useWeb3React } from '@web3-react/core' 5 | import { truncateAddress } from '../utils' 6 | 7 | type NavbarWalletProps = { 8 | onClick: () => void 9 | } 10 | 11 | export const NavbarWallet = ({ onClick }: NavbarWalletProps): JSX.Element => { 12 | const { account } = useWeb3React() 13 | const address = account ? account : '' 14 | 15 | return ( 16 | 36 | ) 37 | } 38 | -------------------------------------------------------------------------------- /src/components/Trove/context/types.ts: -------------------------------------------------------------------------------- 1 | type NoneView = 'NONE' 2 | type LiquidatedView = 'LIQUIDATED' 3 | type RedeemedView = 'REDEEMED' 4 | type OpeningView = 'OPENING' 5 | type AdjustingView = 'ADJUSTING' 6 | type ClosingView = 'CLOSING' 7 | type ActiveView = 'ACTIVE' 8 | 9 | export type TroveView = 10 | | NoneView 11 | | LiquidatedView 12 | | RedeemedView 13 | | OpeningView 14 | | AdjustingView 15 | | ClosingView 16 | | ActiveView 17 | 18 | type OpenTrovePressedEvent = 'OPEN_TROVE_PRESSED' 19 | type AdjustTrovePressedEvent = 'ADJUST_TROVE_PRESSED' 20 | type CloseTrovePressedEvent = 'CLOSE_TROVE_PRESSED' 21 | type CancelAdjustTrovePressed = 'CANCEL_ADJUST_TROVE_PRESSED' 22 | type TroveAdjustedEvent = 'TROVE_ADJUSTED' 23 | type TroveOpenedEvent = 'TROVE_OPENED' 24 | type TroveClosedEvent = 'TROVE_CLOSED' 25 | type TroveLiquidatedEvent = 'TROVE_LIQUIDATED' 26 | type TroveRedeemedEvent = 'TROVE_REDEEMED' 27 | type TroveSurplusCollateralClaimedEvent = 'TROVE_SURPLUS_COLLATERAL_CLAIMED' 28 | 29 | export type TroveEvent = 30 | | OpenTrovePressedEvent 31 | | AdjustTrovePressedEvent 32 | | CloseTrovePressedEvent 33 | | CancelAdjustTrovePressed 34 | | TroveClosedEvent 35 | | TroveLiquidatedEvent 36 | | TroveRedeemedEvent 37 | | TroveAdjustedEvent 38 | | TroveSurplusCollateralClaimedEvent 39 | | TroveOpenedEvent 40 | -------------------------------------------------------------------------------- /src/components/Trove/TrovePreview.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Flex, Box, Heading } from '@chakra-ui/react' 3 | import { WalletConnector } from '../WalletConnector' 4 | import { DisabledEditableRow } from './Editor' 5 | import { useModal } from 'hooks/ModalContext' 6 | import { CardBase } from '../Layout/CardBase' 7 | 8 | export const TrovePreview = (): JSX.Element => { 9 | //will need to address hard-coded width for mobile 10 | const modal = useModal() 11 | return ( 12 | 13 | Trove 14 | 15 | 21 | 22 | 28 | 29 | 30 |

Please connect your wallet to use our services

31 |
32 | 37 |
38 |
39 | ) 40 | } 41 | -------------------------------------------------------------------------------- /src/hooks/useStableTroveChange.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react' 2 | import { Decimal, TroveChange } from '@liquity/lib-base' 3 | 4 | type ValidTroveChange = Exclude< 5 | TroveChange, 6 | { type: 'invalidCreation' } 7 | > 8 | 9 | const paramsEq = (a?: Decimal, b?: Decimal) => (a && b ? a.eq(b) : !a && !b) 10 | 11 | const equals = (a: ValidTroveChange, b: ValidTroveChange): boolean => { 12 | return ( 13 | a.type === b.type && 14 | paramsEq(a.params.borrowLUSD, b.params.borrowLUSD) && 15 | paramsEq(a.params.repayLUSD, b.params.repayLUSD) && 16 | paramsEq(a.params.depositCollateral, b.params.depositCollateral) && 17 | paramsEq(a.params.withdrawCollateral, b.params.withdrawCollateral) 18 | ) 19 | } 20 | 21 | export const useStableTroveChange = ( 22 | troveChange: ValidTroveChange | undefined 23 | ): ValidTroveChange | undefined => { 24 | const [stableTroveChange, setStableTroveChange] = useState(troveChange) 25 | 26 | useEffect(() => { 27 | if (!!stableTroveChange !== !!troveChange) { 28 | setStableTroveChange(troveChange) 29 | } else if ( 30 | stableTroveChange && 31 | troveChange && 32 | !equals(stableTroveChange, troveChange) 33 | ) { 34 | setStableTroveChange(troveChange) 35 | } 36 | }, [stableTroveChange, troveChange]) 37 | 38 | return stableTroveChange 39 | } 40 | -------------------------------------------------------------------------------- /src/components/WaitingDialog.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Box, Flex, Spinner } from '@chakra-ui/react' 3 | import { Dialog } from './Dialog' 4 | 5 | type WaitingDialogProps = { 6 | title: string 7 | icon?: React.ReactNode 8 | waitReason: React.ReactNode 9 | cancelLabel?: string 10 | onCancel: () => void 11 | } 12 | 13 | export const WaitingDialog: React.FC = ({ 14 | title, 15 | icon, 16 | waitReason, 17 | cancelLabel, 18 | onCancel, 19 | children, 20 | }) => ( 21 | 27 | {children} 28 | 29 | 37 | 38 | 44 | {waitReason} 45 | 46 | 47 | 48 | 49 | ) 50 | -------------------------------------------------------------------------------- /src/connectors/connectors.ts: -------------------------------------------------------------------------------- 1 | import { InjectedConnector } from '@web3-react/injected-connector' 2 | import { WalletLinkConnector } from '@web3-react/walletlink-connector' 3 | import { WalletConnectConnector } from '@web3-react/walletconnect-connector' 4 | import { AbstractConnector } from '@web3-react/abstract-connector' 5 | 6 | interface WalletInfo { 7 | connector?: AbstractConnector 8 | name: string 9 | icon: string 10 | } 11 | 12 | export const CoinbaseWallet = new WalletLinkConnector({ 13 | url: `https://mainnet.infura.io/v3/${process.env.INFURA_ID}`, 14 | appName: 'BanklessLoans', 15 | supportedChainIds: [1, 3, 4, 5, 42], 16 | }) 17 | 18 | export const WalletConnect = new WalletConnectConnector({ 19 | infuraId: `${process.env.INFURA_ID}`, 20 | bridge: 'https://bridge.walletconnect.org', 21 | qrcode: true, 22 | }) 23 | 24 | export const Injected = new InjectedConnector({ 25 | supportedChainIds: [1, 3, 4, 5, 42], 26 | }) 27 | 28 | export const connectors: { [key: string]: WalletInfo } = { 29 | INJECTED: { 30 | connector: Injected, 31 | name: 'Metamask', 32 | icon: '/metamask-logo.png', 33 | }, 34 | WALLET_CONNECT: { 35 | connector: WalletConnect, 36 | name: 'Wallet Connect', 37 | icon: '/wallet-connect.png', 38 | }, 39 | COINBASE_WALLET: { 40 | connector: CoinbaseWallet, 41 | name: 'Coinbase Wallet', 42 | icon: '/cbw.png', 43 | }, 44 | } 45 | -------------------------------------------------------------------------------- /src/pages/stake.tsx: -------------------------------------------------------------------------------- 1 | import { Flex } from '@chakra-ui/react' 2 | import { Wallet } from '@ethersproject/wallet' 3 | import { Decimal, Difference, Trove } from '@liquity/lib-base' 4 | import { useLiquity } from '../hooks/LiquityContext' 5 | import { Staking } from '../components/Staking/Staking' 6 | import { StakingViewProvider } from '../components/Staking/context/StakingViewProvider' 7 | import { LiquityStoreProvider } from 'components/LiquityStoreProvider' 8 | import { VStackBase } from 'components/Layout/VStackBase' 9 | import { TransactionMonitor } from '../components/Transaction' 10 | 11 | type LiquityFrontendProps = { 12 | loader?: React.ReactNode 13 | } 14 | const Stake = ({ loader }: LiquityFrontendProps): JSX.Element => { 15 | const { account, provider, liquity } = useLiquity() 16 | 17 | // For console tinkering ;-) 18 | Object.assign(window, { 19 | account, 20 | provider, 21 | liquity, 22 | Trove, 23 | Decimal, 24 | Difference, 25 | Wallet, 26 | }) 27 | 28 | return ( 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | ) 40 | } 41 | 42 | export default Stake 43 | -------------------------------------------------------------------------------- /src/pages/pool.tsx: -------------------------------------------------------------------------------- 1 | import { Flex } from '@chakra-ui/react' 2 | import { Wallet } from '@ethersproject/wallet' 3 | import { Decimal, Difference, Trove } from '@liquity/lib-base' 4 | import { useLiquity } from '../hooks/LiquityContext' 5 | import { Stability } from '../components/Stability/Stability' 6 | import { StabilityViewProvider } from '../components/Stability/context/StabilityViewProvider' 7 | import { LiquityStoreProvider } from 'components/LiquityStoreProvider' 8 | import { VStackBase } from 'components/Layout/VStackBase' 9 | import { TransactionMonitor } from '../components/Transaction' 10 | 11 | type LiquityFrontendProps = { 12 | loader?: React.ReactNode 13 | } 14 | const Pool = ({ loader }: LiquityFrontendProps): JSX.Element => { 15 | const { account, provider, liquity } = useLiquity() 16 | 17 | // For console tinkering ;-) 18 | Object.assign(window, { 19 | account, 20 | provider, 21 | liquity, 22 | Trove, 23 | Decimal, 24 | Difference, 25 | Wallet, 26 | }) 27 | 28 | return ( 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | ) 40 | } 41 | 42 | export default Pool 43 | -------------------------------------------------------------------------------- /src/components/Trove/TroveAction.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from '@chakra-ui/react' 2 | 3 | import { Decimal, TroveChange } from '@liquity/lib-base' 4 | 5 | import { useLiquity } from '../../hooks/LiquityContext' 6 | import { useTransactionFunction } from '../Transaction' 7 | 8 | type TroveActionProps = { 9 | transactionId: string 10 | change: Exclude, { type: 'invalidCreation' }> 11 | maxBorrowingRate: Decimal 12 | borrowingFeeDecayToleranceMinutes: number 13 | } 14 | 15 | export const TroveAction: React.FC = ({ 16 | children, 17 | transactionId, 18 | change, 19 | maxBorrowingRate, 20 | borrowingFeeDecayToleranceMinutes, 21 | }) => { 22 | const { liquity } = useLiquity() 23 | 24 | const [sendTransaction] = useTransactionFunction( 25 | transactionId, 26 | change.type === 'creation' 27 | ? liquity.send.openTrove.bind(liquity.send, change.params, { 28 | maxBorrowingRate, 29 | borrowingFeeDecayToleranceMinutes, 30 | }) 31 | : change.type === 'closure' 32 | ? liquity.send.closeTrove.bind(liquity.send) 33 | : liquity.send.adjustTrove.bind(liquity.send, change.params, { 34 | maxBorrowingRate, 35 | borrowingFeeDecayToleranceMinutes, 36 | }) 37 | ) 38 | 39 | return ( 40 | 43 | ) 44 | } 45 | -------------------------------------------------------------------------------- /src/theme/components/link.ts: -------------------------------------------------------------------------------- 1 | const Link = { 2 | baseStyle: ({ onDark } : { onDark?: boolean}) => ({ 3 | color: onDark ? "interactive.white" : 'black', 4 | textDecoration: onDark ? "none": "underline", 5 | fontWeight: 500, 6 | fontSize: { 7 | base: '1rem', 8 | }, 9 | _hover: { 10 | color: onDark ? "interactive.purple" : "interactive.darkPurple", 11 | textDecoration: onDark ? "none" : "underline" 12 | }, 13 | _active: { 14 | color: onDark ? "background.blueBg" : "interactive.white" 15 | }, 16 | _visited: { 17 | color: onDark ? "interactive.grey": "black" 18 | } 19 | }), 20 | sizes: { 21 | lg: { 22 | fontSize: '1rem', 23 | lineHeight: '1.22rem', 24 | }, 25 | md: { 26 | fontSize: '.7778rem', 27 | lineHeight: '.8889rem' 28 | }, 29 | sm: { 30 | fontSize: '.67rem', 31 | lineHeight: '.67rem' 32 | } 33 | }, 34 | variants: { 35 | body: { 36 | 37 | }, 38 | light: { 39 | color:"interactive.white", 40 | _hover: { 41 | color: "interactive.purple" 42 | } 43 | }, 44 | dark: { 45 | color:"interactive.dark", 46 | _hover: { 47 | color: "interactive.purple" 48 | } 49 | } 50 | }, 51 | defaultProps: {} 52 | } 53 | 54 | 55 | export default Link; -------------------------------------------------------------------------------- /src/pages/borrow.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Flex } from '@chakra-ui/react' 3 | import { Wallet } from '@ethersproject/wallet' 4 | import { Decimal, Difference, Trove } from '@liquity/lib-base' 5 | import { LiquityStoreProvider } from '../components/LiquityStoreProvider' 6 | import { useLiquity } from '../hooks/LiquityContext' 7 | import { TransactionMonitor } from '../components/Transaction' 8 | import { VStackBase } from '../components/Layout/VStackBase' 9 | import { Trove as TroveEditor } from '../components/Trove/Trove' 10 | import { TroveViewProvider } from '../components/Trove/context/TroveViewProvider' 11 | 12 | type LiquityFrontendProps = { 13 | loader?: React.ReactNode 14 | } 15 | const Borrow = ({ loader }: LiquityFrontendProps): JSX.Element => { 16 | const { account, provider, liquity } = useLiquity() 17 | 18 | // For console tinkering ;-) 19 | Object.assign(window, { 20 | account, 21 | provider, 22 | liquity, 23 | Trove, 24 | Decimal, 25 | Difference, 26 | Wallet, 27 | }) 28 | 29 | return ( 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | ) 41 | } 42 | 43 | export default Borrow 44 | -------------------------------------------------------------------------------- /src/components/RetryDialog.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Box, Button, Flex } from '@chakra-ui/react' 3 | import { Dialog } from './Dialog' 4 | 5 | type RetryDialogProps = { 6 | title: string 7 | cancelLabel?: string 8 | retryLabel?: string 9 | onCancel: () => void 10 | onRetry: () => void 11 | } 12 | 13 | export const RetryDialog: React.FC = ({ 14 | title, 15 | cancelLabel, 16 | onCancel, 17 | retryLabel, 18 | onRetry, 19 | children, 20 | }) => ( 21 | 27 | {children} 28 | 38 | 49 | 52 | 53 | 54 | ) 55 | -------------------------------------------------------------------------------- /src/components/Trove/Trove.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { TroveManager } from './TroveManager' 3 | import { ReadOnlyTrove } from './ReadOnlyTrove' 4 | import { NoTrove } from './NoTrove' 5 | import { Opening } from './Opening' 6 | import { Adjusting } from './Adjusting' 7 | import { RedeemedTrove } from './RedeemedTrove' 8 | import { useTroveView } from './context/TroveViewContext' 9 | import { LiquidatedTrove } from './LiquidatedTrove' 10 | import { Decimal } from '@liquity/lib-base' 11 | 12 | export const Trove: React.FC = props => { 13 | const { view } = useTroveView() 14 | 15 | switch (view) { 16 | // loading state not needed, as main app has a loading spinner that blocks render until the liquity backend data is available 17 | case 'ACTIVE': { 18 | return 19 | } 20 | case 'ADJUSTING': { 21 | return 22 | } 23 | case 'CLOSING': { 24 | return ( 25 | 30 | ) 31 | } 32 | case 'OPENING': { 33 | return 34 | } 35 | case 'LIQUIDATED': { 36 | return 37 | } 38 | case 'REDEEMED': { 39 | return 40 | } 41 | case 'NONE': { 42 | return 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/hooks/useAuthorizedConnection.ts: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from 'react' 2 | import { useWeb3React } from '@web3-react/core' 3 | import { Injected } from '../connectors/connectors' 4 | 5 | /** 6 | * React hook that tries to activate the InjectedConnector if the app's already authorized in the 7 | * browser's wallet (in the case of dApp-enabled browsers) or its wallet extension (e.g. MetaMask). 8 | * 9 | * Example: user has a browser with the MetaMask extension. MetaMask injects an Ethereum provider 10 | * into the window object. We check via InjectedConnector if our app is already authorized to use 11 | * the wallet through this provider, and in that case we try to activate the connector. 12 | * 13 | * @returns true when finished trying to activate the InjectedConnector, false otherwise 14 | */ 15 | 16 | export function useAuthorizedConnection(): boolean { 17 | const { activate, active } = useWeb3React() 18 | const [tried, setTried] = useState(false) 19 | 20 | useEffect(() => { 21 | const tryToActivateIfAuthorized = async () => { 22 | try { 23 | if (await Injected.isAuthorized()) { 24 | await activate(Injected, undefined, true) 25 | } else { 26 | throw new Error('Unauthorized') 27 | } 28 | } catch { 29 | setTried(true) 30 | } 31 | } 32 | tryToActivateIfAuthorized() 33 | }, [activate]) 34 | 35 | useEffect(() => { 36 | if (active) { 37 | setTried(true) 38 | } 39 | }, [active]) 40 | 41 | return tried 42 | } 43 | -------------------------------------------------------------------------------- /src/hooks/useLiquityReducer.ts: -------------------------------------------------------------------------------- 1 | import { useCallback, useEffect, useReducer, useRef } from 'react' 2 | 3 | import { LiquityStoreState } from '@liquity/lib-base' 4 | 5 | import { equals } from '../utils/equals' 6 | import { useLiquityStore } from './useLiquityStore' 7 | 8 | export type LiquityStoreUpdate = { 9 | type: 'updateStore' 10 | newState: LiquityStoreState 11 | oldState: LiquityStoreState 12 | stateChange: Partial> 13 | } 14 | 15 | export const useLiquityReducer = ( 16 | reduce: (state: S, action: A | LiquityStoreUpdate) => S, 17 | init: (storeState: LiquityStoreState) => S 18 | ): [S, (action: A | LiquityStoreUpdate) => void] => { 19 | const store = useLiquityStore() 20 | const oldStore = useRef(store) 21 | const state = useRef(init(store.state)) 22 | const [, rerender] = useReducer(() => ({}), {}) 23 | 24 | const dispatch = useCallback( 25 | (action: A | LiquityStoreUpdate) => { 26 | const newState = reduce(state.current, action) 27 | 28 | if (!equals(newState, state.current)) { 29 | state.current = newState 30 | rerender() 31 | } 32 | }, 33 | [reduce] 34 | ) 35 | 36 | useEffect( 37 | () => 38 | store.subscribe(params => 39 | dispatch({ type: 'updateStore', ...params }) 40 | ), 41 | [store, dispatch] 42 | ) 43 | 44 | if (oldStore.current !== store) { 45 | state.current = init(store.state) 46 | oldStore.current = store 47 | } 48 | 49 | return [state.current, dispatch] 50 | } 51 | -------------------------------------------------------------------------------- /src/components/Stability/StabilityDepositAction.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from '@chakra-ui/react' 2 | import { 3 | Decimal, 4 | LiquityStoreState, 5 | StabilityDepositChange, 6 | } from '@liquity/lib-base' 7 | import { useLiquitySelector } from '../../hooks/useLiquitySelector' 8 | 9 | import { useLiquity } from '../../hooks/LiquityContext' 10 | import { useTransactionFunction } from '../Transaction' 11 | 12 | type StabilityDepositActionProps = { 13 | transactionId: string 14 | change: StabilityDepositChange 15 | } 16 | 17 | const selectFrontendRegistered = ({ frontend }: LiquityStoreState) => 18 | frontend.status === 'registered' 19 | 20 | export const StabilityDepositAction: React.FC = ({ 21 | children, 22 | transactionId, 23 | change, 24 | }) => { 25 | const { config, liquity } = useLiquity() 26 | const frontendRegistered = useLiquitySelector(selectFrontendRegistered) 27 | 28 | const frontendTag = frontendRegistered ? config.frontendTag : undefined 29 | 30 | const [sendTransaction] = useTransactionFunction( 31 | transactionId, 32 | change.depositLUSD 33 | ? liquity.send.depositLUSDInStabilityPool.bind( 34 | liquity.send, 35 | change.depositLUSD, 36 | frontendTag 37 | ) 38 | : liquity.send.withdrawLUSDFromStabilityPool.bind( 39 | liquity.send, 40 | change.withdrawLUSD 41 | ) 42 | ) 43 | 44 | return ( 45 | 48 | ) 49 | } 50 | -------------------------------------------------------------------------------- /src/theme/index.ts: -------------------------------------------------------------------------------- 1 | import { extendTheme, type ThemeConfig } from '@chakra-ui/react' 2 | import { createBreakpoints } from '@chakra-ui/theme-tools' 3 | 4 | const config: ThemeConfig = { 5 | initialColorMode: 'light', 6 | useSystemColorMode: false, 7 | } 8 | 9 | const breakpoints = createBreakpoints({ 10 | sm: '30em', 11 | md: '48em', 12 | lg: '62em', 13 | xl: '80em', 14 | '2xl': '96em', 15 | }) 16 | 17 | // Global style overrides 18 | import styles from './styles' 19 | 20 | // Font overrides. 21 | import fonts from './fonts' 22 | 23 | // Color overrides. 24 | import colors from './colors' 25 | 26 | // Component style overrides. 27 | import Section from './components/section' 28 | import { Box, Container } from '@chakra-ui/layout' 29 | import Button from './components/button' 30 | import Link from './components/link' 31 | import Text from './components/text' 32 | import Heading from './components/heading' 33 | import Popover from './components/popover' 34 | import Checkbox from './components/checkbox' 35 | 36 | const customTheme = extendTheme({ 37 | config, 38 | colors, 39 | styles, 40 | fonts, 41 | components: { 42 | Section, 43 | Box, 44 | Container, 45 | Button, 46 | Text, 47 | }, 48 | layerStyles: { 49 | baseStyle: { 50 | width: '555px', 51 | height: '622px', 52 | bg: '#131313', 53 | color: '#FFFFFF', 54 | borderRadius: '31px', 55 | }, 56 | otherStyle: { 57 | width: '555px', 58 | height: '622px', 59 | bg: '#131313', 60 | color: 'red', 61 | borderRadius: '0px', 62 | }, 63 | }, 64 | }) 65 | 66 | export default customTheme 67 | -------------------------------------------------------------------------------- /src/components/Layout/Layout.tsx: -------------------------------------------------------------------------------- 1 | import { Container, VStack, useBreakpointValue } from '@chakra-ui/react' 2 | import Navbar from './Navbar' 3 | import NavbarMobile from '../NavbarMobile' 4 | import { useRouter } from 'next/router' 5 | import { Footer } from '../../components/Layout/Footer' 6 | 7 | type LayoutProps = { 8 | children?: React.ReactNode 9 | } 10 | 11 | const gradient = `radial-gradient(circle at left, rgba(109, 41, 254, 0.3) 0%, 12 | rgba(109, 41, 254, 0) 30%), radial-gradient(circle at right, 13 | rgba(255, 4, 16, 0.24) 0%, rgba(255, 4, 16, 0) 30%), #000000` 14 | 15 | const gradientAndImage = `radial-gradient(circle at left, rgba(109, 41, 254, 0.3) 0%, 16 | rgba(109, 41, 254, 0) 30%), radial-gradient(circle at right, 17 | rgba(255, 4, 16, 0.24) 0%, rgba(255, 4, 16, 0) 30%), 18 | center top url(https://images.ctfassets.net/0kmtt14e2vpq/6oqorGAv5LOchoNPjo6kBu/72870577492b4ae20a4827bf3635b58c/futuristic-background.png), #000000` 19 | 20 | export const Layout = ({ children }: LayoutProps): JSX.Element => { 21 | const router = useRouter() 22 | const footer = router.pathname === '/' &&