├── .editorconfig
├── .env
├── .env.example
├── .eslintrc
├── .gitignore
├── .prettierignore
├── .prettierrc.json
├── LICENSE.md
├── README.md
├── components
├── ConnectedWalletButton.tsx
├── ErrorBoundary.tsx
├── Input
│ ├── LiquidityInput.tsx
│ └── index.ts
├── Layout
│ ├── AppLayout.tsx
│ ├── ExtensionSidebar.tsx
│ ├── FooterBar.tsx
│ ├── NavigationSidebar.tsx
│ ├── PageHeader.tsx
│ └── index.ts
├── MigrationCard.tsx
├── NextJsAppRoot.tsx
├── TestnetDialog.tsx
├── TokenSelectList.tsx
├── index.ts
└── types.ts
├── features
├── assets
│ ├── components
│ │ ├── AssetCard.tsx
│ │ ├── AssetsList.tsx
│ │ ├── DepositRedirectDialog.tsx
│ │ ├── TransferDialog
│ │ │ ├── AmountInput.tsx
│ │ │ ├── AssetSelector.tsx
│ │ │ ├── TokenOptionsList.tsx
│ │ │ ├── TransferDialog.tsx
│ │ │ ├── WalletInfo.tsx
│ │ │ ├── index.tsx
│ │ │ ├── types.ts
│ │ │ └── useTransferAssetMutation.ts
│ │ └── index.ts
│ ├── hooks
│ │ └── useGetSupportedAssetsBalancesOnChain.ts
│ └── index.ts
├── liquidity
│ ├── components
│ │ ├── AdditionalUnderlyingAssetsRow.tsx
│ │ ├── AprPill.tsx
│ │ ├── BaseCardForEmptyState.tsx
│ │ ├── BondLiquidityDialog.tsx
│ │ ├── ButtonWithDropdownForSorting.tsx
│ │ ├── LiquidityBreakdown.tsx
│ │ ├── LiquidityHeader.tsx
│ │ ├── LiquidityInputSelector.tsx
│ │ ├── LiquidityRewardsCard.tsx
│ │ ├── ManageBondedLiquidityCard.tsx
│ │ ├── ManageLiquidityCard.tsx
│ │ ├── ManagePoolDialog
│ │ │ ├── ManagePoolDialog.tsx
│ │ │ ├── TokenToTokenRates.tsx
│ │ │ ├── index.ts
│ │ │ └── usePoolDialogController.tsx
│ │ ├── PercentageSelection.tsx
│ │ ├── PoolCard.tsx
│ │ ├── SegmentedRewardsSimulator.tsx
│ │ ├── StakingSummary.tsx
│ │ ├── StateSwitchButtons.tsx
│ │ ├── StepIcon.tsx
│ │ ├── UnbondingLiquidityCard.tsx
│ │ ├── UnbondingLiquidityStatusList.tsx
│ │ └── UnderlyingAssetRow.tsx
│ ├── hooks
│ │ ├── index.ts
│ │ ├── useClaimTokens.ts
│ │ ├── usePoolPairTokenAmount.ts
│ │ ├── usePoolTokensDollarValue.ts
│ │ ├── useSortPools.ts
│ │ ├── useStakedTokenBalance.ts
│ │ ├── useStakingClaims.ts
│ │ └── useUnstakingDuration.ts
│ └── index.ts
└── swap
│ ├── components
│ ├── ConvenienceBalanceButtons.tsx
│ ├── QueryInput.tsx
│ ├── SelectorInput.tsx
│ ├── SelectorToggle.tsx
│ ├── SlippageSelector.tsx
│ ├── TokenOptionsList.tsx
│ ├── TokenSelector.tsx
│ ├── TokenSwapModule.tsx
│ ├── TransactionAction.tsx
│ ├── TransactionTips.tsx
│ └── index.ts
│ ├── hooks
│ ├── index.ts
│ ├── usePriceForOneToken.ts
│ ├── useSwapFee.ts
│ ├── useTokenSwap.tsx
│ ├── useTokenToTokenPrice.tsx
│ └── useTxRates.ts
│ ├── index.ts
│ └── swapAtoms.ts
├── hooks
├── useBondTokens.ts
├── useChainInfo.ts
├── useConnectIBCWallet.ts
├── useConnectWallet.ts
├── useCosmWasmClient.ts
├── useIBCAssetInfo.ts
├── useIBCTokenBalance.tsx
├── useIbcAssetList.ts
├── useQueriesDataSelector.ts
├── useRefetchQueries.ts
├── useRewardsQueries.ts
├── useSwapInfo.ts
├── useTokenBalance.tsx
├── useTokenDollarValue.tsx
├── useTokenInfo.ts
├── useTokenList.ts
└── useWalletConnectionStatus.ts
├── icons
├── Analytics.tsx
├── Dollar.tsx
├── Logo.tsx
├── LogoBrighter.tsx
├── LogoText.tsx
├── Pools.tsx
├── PriceData.tsx
├── Swap.tsx
├── Transfer.tsx
└── index.ts
├── jest.config.js
├── jest.setup.js
├── next-env.d.ts
├── next.config.js
├── package.json
├── pages
├── _app.tsx
├── _document.tsx
├── index.tsx
├── pools
│ ├── [pool].tsx
│ └── index.tsx
└── transfer
│ └── index.tsx
├── public
├── bg@1x.png
├── bg@2x.png
├── chain_info.local.json
├── chain_info.testnet.json
├── crab.png
├── favicons
│ ├── android-chrome-192x192.png
│ ├── android-chrome-512x512.png
│ ├── apple-touch-icon.png
│ ├── favicon-16x16.png
│ ├── favicon-32x32.png
│ ├── favicon.ico
│ └── site.webmanifest
├── fonts
│ ├── Inter
│ │ ├── Inter-Black.woff
│ │ ├── Inter-Black.woff2
│ │ ├── Inter-BlackItalic.woff
│ │ ├── Inter-BlackItalic.woff2
│ │ ├── Inter-Bold.woff
│ │ ├── Inter-Bold.woff2
│ │ ├── Inter-BoldItalic.woff
│ │ ├── Inter-BoldItalic.woff2
│ │ ├── Inter-ExtraBold.woff
│ │ ├── Inter-ExtraBold.woff2
│ │ ├── Inter-ExtraBoldItalic.woff
│ │ ├── Inter-ExtraBoldItalic.woff2
│ │ ├── Inter-ExtraLight.woff
│ │ ├── Inter-ExtraLight.woff2
│ │ ├── Inter-ExtraLightItalic.woff
│ │ ├── Inter-ExtraLightItalic.woff2
│ │ ├── Inter-Italic.woff
│ │ ├── Inter-Italic.woff2
│ │ ├── Inter-Light.woff
│ │ ├── Inter-Light.woff2
│ │ ├── Inter-LightItalic.woff
│ │ ├── Inter-LightItalic.woff2
│ │ ├── Inter-Medium.woff
│ │ ├── Inter-Medium.woff2
│ │ ├── Inter-MediumItalic.woff
│ │ ├── Inter-MediumItalic.woff2
│ │ ├── Inter-Regular.woff
│ │ ├── Inter-Regular.woff2
│ │ ├── Inter-SemiBold.woff
│ │ ├── Inter-SemiBold.woff2
│ │ ├── Inter-SemiBoldItalic.woff
│ │ ├── Inter-SemiBoldItalic.woff2
│ │ ├── Inter-Thin.woff
│ │ ├── Inter-Thin.woff2
│ │ ├── Inter-ThinItalic.woff
│ │ ├── Inter-ThinItalic.woff2
│ │ ├── Inter-italic.var.woff2
│ │ └── Inter-roman.var.woff2
│ └── JetBrainsMono
│ │ ├── JetBrainsMono-Bold.woff2
│ │ ├── JetBrainsMono-BoldItalic.woff2
│ │ ├── JetBrainsMono-ExtraBold.woff2
│ │ ├── JetBrainsMono-ExtraBoldItalic.woff2
│ │ ├── JetBrainsMono-ExtraLight.woff2
│ │ ├── JetBrainsMono-ExtraLightItalic.woff2
│ │ ├── JetBrainsMono-Italic.woff2
│ │ ├── JetBrainsMono-Light.woff2
│ │ ├── JetBrainsMono-LightItalic.woff2
│ │ ├── JetBrainsMono-Medium.woff2
│ │ ├── JetBrainsMono-MediumItalic.woff2
│ │ ├── JetBrainsMono-Regular.woff2
│ │ ├── JetBrainsMono-SemiBold.woff2
│ │ ├── JetBrainsMono-SemiBoldItalic.woff2
│ │ ├── JetBrainsMono-Thin.woff2
│ │ └── JetBrainsMono-ThinItalic.woff2
├── ibc_assets.json
├── img
│ └── keplr-icon.png
├── junoswap.png
├── pools_list.local.json
├── pools_list.testnet.json
├── spinner.svg
├── spring-left.png
├── spring-right.png
├── spring.gltf
├── springs-bg.png
├── switch.svg
└── vercel.svg
├── queries
├── queryMyLiquidity.ts
├── queryRewardsContracts.ts
├── queryStakedLiquidity.ts
├── querySwapInfo.ts
├── tokenDollarValueQuery
│ ├── fetchDollarPriceByTokenIds.ts
│ ├── index.ts
│ ├── pricingServiceIsDownAlert.tsx
│ └── tokenDollarValueQuery.tsx
├── tokenToTokenPriceQuery.ts
├── types.ts
├── useGetTokenDollarValueQuery.ts
├── usePoolsListQuery.ts
├── useQueryMatchingPoolForSwap
│ ├── index.ts
│ ├── types.ts
│ ├── useQueryMatchingPoolsForSwap.ts
│ └── util
│ │ ├── selectEligiblePoolsForTokenToTokenSwap.spec.ts
│ │ ├── selectEligiblePoolsForTokenToTokenSwap.ts
│ │ └── validateIfPassThroughPoolMatchIsUnique.ts
├── useQueryPoolUnstakingDuration.ts
└── useQueryPools.ts
├── services
├── cw20.tsx
├── liquidity
│ ├── executeAddLiquidity.ts
│ ├── executeRemoveLiquidity.ts
│ ├── index.ts
│ └── queryLiquidityBalance.ts
├── queryClient.ts
├── rewards.ts
├── staking.ts
└── swap
│ ├── directTokenSwap.ts
│ ├── index.ts
│ ├── passThroughTokenSwap.ts
│ └── price.ts
├── state
└── atoms
│ ├── transactionAtoms.ts
│ └── walletAtoms.ts
├── styles
├── globals.scss
├── inter.font.scss
└── jetbrains.font.scss
├── tsconfig.json
├── types
└── window.d.ts
├── util
├── constants.ts
├── conversion
│ ├── conversion.ts
│ ├── index.ts
│ └── pools.ts
├── cosmWasmClientRouter.ts
├── externalLinkPopup.ts
├── formatCompactNumber.ts
├── formatSdkErrorMessage.ts
├── getPropsForInteractiveElement.ts
├── localStorageEffect.ts
├── maybePluralize.ts
├── messages
│ ├── createExecuteMessage.ts
│ ├── createIncreaseAllowanceMessage.ts
│ ├── index.ts
│ └── validateTransactionSuccess.ts
└── testutils.ts
├── webpack.functions.js
└── yarn.lock
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*.{ts,tsx,json,js,jsx}]
4 | end_of_line = lf
5 | charset = utf-8
6 | trim_trailing_whitespace = true
7 | insert_final_newline = true
8 | indent_style = space
9 | indent_size = 2
10 |
11 | [*.md]
12 | trim_trailing_whitespace = true
13 | insert_final_newline = true
--------------------------------------------------------------------------------
/.env:
--------------------------------------------------------------------------------
1 | # Basic
2 |
3 | NEXT_PUBLIC_SITE_TITLE=Wasmswap
4 | NEXT_PUBLIC_TEST_MODE_DISABLED=false
5 | NEXT_PUBLIC_APP_VERSION=1.4.1
6 | NEXT_PUBLIC_GAS_PRICE=0.0025ujunox
7 |
8 | # Asset Information
9 |
10 | NEXT_PUBLIC_CHAIN_INFO_URL=/chain_info.testnet.json
11 | NEXT_PUBLIC_POOLS_LIST_URL=/pools_list.testnet.json
12 | NEXT_PUBLIC_IBC_ASSETS_URL=/ibc_assets.json
13 |
14 | # Feature flags
15 |
16 | NEXT_PUBLIC_ENABLE_FEATURE_TRANSFERS=true
17 | NEXT_PUBLIC_ENABLE_FEATURE_STAKING=true
18 | NEXT_PUBLIC_ENABLE_FEATURE_REWARDS=true
19 | NEXT_PUBLIC_DARK_MODE_ENABLED_BY_DEFAULT=false
20 |
21 | # Contact Information
22 |
23 | NEXT_PUBLIC_FEEDBACK_LINK=#
24 | NEXT_PUBLIC_INTERFACE_GITHUB_LINK=https://github.com/Wasmswap/wasmswap-interface
25 | NEXT_PUBLIC_CONTRACTS_GITHUB_LINK=https://github.com/ben2x4/crustacean-swap
26 | NEXT_PUBLIC_TELEGRAM_LINK=#
27 | NEXT_PUBLIC_DISCORD_LINK=#
28 | NEXT_PUBLIC_TWITTER_LINK=#
29 |
30 | # Governance link
31 |
32 | NEXT_PUBLIC_GOVERNANCE_LINK_URL=#
33 | NEXT_PUBLIC_GOVERNANCE_LINK_LABEL=Governance
34 |
35 | NEXT_PUBLIC_PRICE_LINK_URL=#
36 | NEXT_PUBLIC_PRICE_LINK_LABEL=Price Data
37 |
38 |
--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------
1 | # Basic
2 | NEXT_PUBLIC_SITE_TITLE=Wasmswap
3 | NEXT_PUBLIC_TEST_MODE_DISABLED=false
4 | NEXT_PUBLIC_APP_VERSION=1.0.0
5 | NEXT_PUBLIC_GAS_PRICE=0.0025ujuno
6 |
7 | # Asset Information
8 | NEXT_PUBLIC_CHAIN_INFO_URL=/chain_info.local.json
9 | NEXT_PUBLIC_POOLS_LIST_URL=/pools_list.local.json
10 | NEXT_PUBLIC_IBC_ASSETS_URL=/ibc_assets.local.json
11 |
12 | # Feature flags
13 | NEXT_PUBLIC_ENABLE_FEATURE_TRANSFERS=false
14 | NEXT_PUBLIC_ENABLE_FEATURE_STAKING=false
15 | NEXT_PUBLIC_ENABLE_FEATURE_REWARDS=false
16 | NEXT_PUBLIC_DARK_MODE_ENABLED_BY_DEFAULT=false
17 |
18 | # Contact Information
19 | NEXT_PUBLIC_FEEDBACK_LINK=#
20 | NEXT_PUBLIC_INTERFACE_GITHUB_LINK=https://github.com/Wasmswap/wasmswap-interface
21 | NEXT_PUBLIC_CONTRACTS_GITHUB_LINK=https://github.com/ben2x4/crustacean-swap
22 | NEXT_PUBLIC_TELEGRAM_LINK=#
23 | NEXT_PUBLIC_DISCORD_LINK=#
24 | NEXT_PUBLIC_TWITTER_LINK=#
25 |
26 | # Governance link
27 | NEXT_PUBLIC_GOVERNANCE_LINK_URL=#
28 | NEXT_PUBLIC_GOVERNANCE_LINK_LABEL=Governance
29 |
30 | NEXT_PUBLIC_PRICE_LINK_URL=#
31 | NEXT_PUBLIC_PRICE_LINK_LABEL=Price Data
32 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["next", "next/core-web-vitals"],
3 | "plugins": ["testing-library"],
4 | "rules": {
5 | "import/no-duplicates": ["error"]
6 | },
7 | "overrides": [
8 | // Only uses Testing Library lint rules in test files
9 | {
10 | "files": [
11 | "**/__tests__/**/*.[jt]s?(x)",
12 | "**/?(*.)+(spec|test).[jt]s?(x)"
13 | ],
14 | "extends": ["plugin:testing-library/react"]
15 | }
16 | ]
17 | }
18 |
--------------------------------------------------------------------------------
/.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 | .idea
22 | *.client-test.*
23 | .yalc
24 | yalc.lock
25 |
26 | # debug
27 | npm-debug.log*
28 | yarn-debug.log*
29 | yarn-error.log*
30 |
31 | # local env files
32 | .env.local
33 | .env.development.local
34 | .env.test.local
35 | .env.production.local
36 |
37 | # vercel
38 | .vercel
39 |
40 | # emacs
41 | *~
42 | \#*\#
43 | /.emacs.desktop
44 | /.emacs.desktop.lock
45 | *.elc
46 | auto-save-list
47 | tramp
48 | .\#*
49 | .log
50 |
51 | # vim
52 | # swap
53 | [._]*.s[a-v][a-z]
54 | [._]*.sw[a-p]
55 | [._]s[a-v][a-z]
56 | [._]sw[a-p]
57 | # session
58 | Session.vim
59 | # temporary
60 | .netrwhist
61 | *~
62 | # auto-generated tag files
63 | tags
64 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | **/.next/**
3 | **/_next/**
4 | **/dist/**
5 |
--------------------------------------------------------------------------------
/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "semi": false,
3 | "trailingComma": "es5",
4 | "singleQuote": true,
5 | "tabWidth": 2,
6 | "useTabs": false
7 | }
8 |
--------------------------------------------------------------------------------
/components/ErrorBoundary.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | export class ErrorBoundary extends React.Component<{}, { hasError: boolean }> {
4 | constructor(props) {
5 | super(props)
6 | this.state = { hasError: false }
7 | }
8 |
9 | static getDerivedStateFromError() {
10 | // Update state so the next render will show the fallback UI.
11 | return { hasError: true }
12 | }
13 |
14 | componentDidCatch(error, errorInfo) {
15 | // You can also log the error to an error reporting service
16 | console.error(error)
17 | console.log(errorInfo)
18 | }
19 |
20 | render() {
21 | if (this.state.hasError) {
22 | // You can render any custom fallback UI
23 | return
Something went wrong.
24 | }
25 |
26 | return this.props.children
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/components/Input/LiquidityInput.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | BasicNumberInput,
3 | formatTokenBalance,
4 | ImageForTokenLogo,
5 | styled,
6 | Text,
7 | } from 'junoblocks'
8 | import React, { FC, useRef, useState } from 'react'
9 |
10 | import { useTokenInfo } from '../../hooks/useTokenInfo'
11 |
12 | type LiquidityInputProps = {
13 | tokenSymbol: string
14 | availableAmount: number
15 | maxApplicableAmount: number
16 | amount: number
17 | onAmountChange: (value: number) => void
18 | }
19 |
20 | export const LiquidityInput: FC = ({
21 | tokenSymbol,
22 | availableAmount,
23 | maxApplicableAmount,
24 | amount,
25 | onAmountChange,
26 | }) => {
27 | const [focusedOnInput, setFocusedOnInput] = useState(false)
28 | const inputRef = useRef()
29 |
30 | const { name: tokenName, logoURI } = useTokenInfo(tokenSymbol)
31 |
32 | const handleAmountChange = (value: number) => onAmountChange(value)
33 |
34 | return (
35 | {
37 | inputRef.current.focus()
38 | }}
39 | active={focusedOnInput}
40 | >
41 |
42 |
43 |
44 |
45 | {tokenName}
46 |
47 |
48 | {formatTokenBalance(availableAmount)} available
49 |
50 |
51 |
52 |
53 |
54 | {
61 | setFocusedOnInput(true)
62 | }}
63 | onBlur={() => {
64 | setFocusedOnInput(false)
65 | }}
66 | />
67 |
68 |
69 |
70 | )
71 | }
72 |
73 | const StyledDivForWrapper = styled('div', {
74 | display: 'flex',
75 | alignItems: 'center',
76 | justifyContent: 'space-between',
77 | backgroundColor: '$colors$dark10',
78 | borderRadius: '$1',
79 | padding: '$6 $12 $5 $5',
80 | width: '100%',
81 | transition: 'background 0.1s ease-out',
82 | '&:hover': {
83 | backgroundColor: '$colors$dark15',
84 | },
85 | '&:active': {
86 | backgroundColor: '$colors$dark5',
87 | },
88 | variants: {
89 | active: {
90 | true: {
91 | backgroundColor: '$colors$dark10 !important',
92 | },
93 | },
94 | },
95 | })
96 |
97 | const StyledDivForColumn = styled('div', {
98 | variants: {
99 | kind: {
100 | info: {
101 | display: 'flex',
102 | alignItems: 'center',
103 | columnGap: '$space$6',
104 | },
105 | input: {
106 | textAlign: 'right',
107 | },
108 | },
109 | },
110 | })
111 |
--------------------------------------------------------------------------------
/components/Input/index.ts:
--------------------------------------------------------------------------------
1 | export { LiquidityInput } from './LiquidityInput'
2 |
--------------------------------------------------------------------------------
/components/Layout/AppLayout.tsx:
--------------------------------------------------------------------------------
1 | import { media, styled, useMedia } from 'junoblocks'
2 | import { APP_MAX_WIDTH, MAIN_PANE_MAX_WIDTH } from 'util/constants'
3 |
4 | import { ExtensionSidebar } from './ExtensionSidebar'
5 | import { FooterBar } from './FooterBar'
6 | import { NavigationSidebar } from './NavigationSidebar'
7 |
8 | export const AppLayout = ({
9 | navigationSidebar = ,
10 | extensionSidebar = ,
11 | footerBar = ,
12 | children,
13 | }) => {
14 | const isSmallScreen = useMedia('sm')
15 | const isMediumScreen = useMedia('md')
16 |
17 | if (isSmallScreen) {
18 | return (
19 |
20 |
21 | {navigationSidebar}
22 |
23 | {children}
24 |
25 |
26 |
27 | {footerBar}
28 |
29 |
30 | )
31 | }
32 |
33 | return (
34 |
35 | {navigationSidebar}
36 |
37 |
38 | {children}
39 |
40 |
41 | {!isMediumScreen && extensionSidebar}
42 |
43 | )
44 | }
45 |
46 | const StyledWrapper = styled('div', {
47 | display: 'flex',
48 | flexDirection: 'row',
49 | justifyContent: 'center',
50 | minHeight: '100vh',
51 | backgroundColor: '$backgroundColors$base',
52 | width: APP_MAX_WIDTH,
53 | maxWidth: '100%',
54 | margin: '0 auto',
55 | [media.md]: {
56 | gridTemplateColumns: '15rem 1fr',
57 | },
58 | })
59 |
60 | const StyledContainer = styled('div', {
61 | position: 'relative',
62 | display: 'flex',
63 | flexDirection: 'column',
64 | justifyContent: 'space-between',
65 | padding: '0 $12 $12 $12',
66 | '& main': {
67 | margin: '0 auto',
68 | width: '100%',
69 | },
70 | maxWidth: '100%',
71 | width: MAIN_PANE_MAX_WIDTH,
72 | [media.sm]: {},
73 | })
74 |
75 | const StyledWrapperForMobile = styled('div', {
76 | display: 'flex',
77 | flexDirection: 'column',
78 | justifyContent: 'space-between',
79 | minHeight: '100vh',
80 | backgroundColor: '$backgroundColors$base',
81 | })
82 |
83 | const StyledContainerForMobile = styled('div', {
84 | position: 'relative',
85 | zIndex: '$1',
86 | '& [data-content]': {
87 | margin: '0 auto',
88 | width: '100%',
89 | padding: '0 $12',
90 | },
91 | })
92 |
--------------------------------------------------------------------------------
/components/Layout/ExtensionSidebar.tsx:
--------------------------------------------------------------------------------
1 | import { Button, ErrorIcon, styled, Text, UpRightArrowIcon } from 'junoblocks'
2 |
3 | import { __TEST_MODE__ } from '../../util/constants'
4 |
5 | export const ExtensionSidebar = () => {
6 | return (
7 |
8 |
9 |
10 | This is a {__TEST_MODE__ ? 'testnet' : 'beta'} version
11 |
12 |
13 | This website is currently{' '}
14 | {__TEST_MODE__ ? 'operates in testnet mode' : 'in beta'}. Please provide
15 | feedback.
16 |
17 | }
23 | css={{ width: '100%' }}
24 | >
25 | Report an issue
26 |
27 |
28 |
29 | )
30 | }
31 |
32 | const StyledDivForWrapper = styled('div', {
33 | flexBasis: '16.5rem',
34 | flexGrow: 0,
35 | flexShrink: 0,
36 | zIndex: 1,
37 | position: 'sticky',
38 | borderLeft: '1px solid $borderColors$inactive',
39 | backgroundColor: '$backgroundColors$base',
40 | top: 0,
41 | right: 0,
42 | width: '100%',
43 | height: '100%',
44 | maxHeight: '100vh',
45 | minHeight: '100vh',
46 | padding: '$11 $12',
47 | })
48 |
49 | const StyledDivForTitleWrapper = styled('div', {
50 | display: 'flex',
51 | alignItems: 'center',
52 | columnGap: '$4',
53 | })
54 |
55 | const StyledSpringBottom = styled('img', {
56 | position: 'absolute',
57 | right: 0,
58 | bottom: 0,
59 | width: '125%',
60 | maxWidth: '500px',
61 | zIndex: '$1',
62 | userSelect: 'none',
63 | userDrag: 'none',
64 | })
65 |
--------------------------------------------------------------------------------
/components/Layout/FooterBar.tsx:
--------------------------------------------------------------------------------
1 | import { Button, IconWrapper, styled, Text, UpRightArrow } from 'junoblocks'
2 | import { APP_NAME } from 'util/constants'
3 |
4 | export const FooterBar = () => {
5 | return (
6 |
7 |
8 | {APP_NAME} v{process.env.NEXT_PUBLIC_APP_VERSION}
9 |
10 |
11 | } />}
19 | >
20 | Provide feedback
21 |
22 |
23 |
24 | )
25 | }
26 |
27 | const StyledFooter = styled('footer', {
28 | padding: '$12 0',
29 | alignItems: 'center',
30 | display: 'flex',
31 | })
32 |
33 | const StyledDivForGrid = styled('div', {
34 | display: 'flex',
35 | alignItems: 'center',
36 | columnGap: '$space$14',
37 | })
38 |
--------------------------------------------------------------------------------
/components/Layout/PageHeader.tsx:
--------------------------------------------------------------------------------
1 | import { Text, useMedia } from 'junoblocks'
2 | import Head from 'next/head'
3 | import React from 'react'
4 |
5 | import { APP_NAME } from '../../util/constants'
6 |
7 | export const PageHeader = ({ title, subtitle }) => {
8 | const isSmall = useMedia('sm')
9 |
10 | return (
11 | <>
12 |
13 |
14 | {APP_NAME} — {title}
15 |
16 |
17 |
18 | {title}
19 |
20 |
21 | {subtitle}
22 |
23 | >
24 | )
25 | }
26 |
--------------------------------------------------------------------------------
/components/Layout/index.ts:
--------------------------------------------------------------------------------
1 | export { AppLayout } from './AppLayout'
2 | export { ExtensionSidebar } from './ExtensionSidebar'
3 | export { FooterBar } from './FooterBar'
4 | export { NavigationSidebar } from './NavigationSidebar'
5 | export { PageHeader } from './PageHeader'
6 |
--------------------------------------------------------------------------------
/components/MigrationCard.tsx:
--------------------------------------------------------------------------------
1 | import { styled, Text } from 'junoblocks'
2 | import Link from 'next/link'
3 |
4 | export const MigrationCard = () => {
5 | return (
6 |
7 |
8 | Junoswap is migrating liquidity to WYND DEX. As part of this transition,
9 | most RAW pools have had swaps and deposits frozen. These pools will be
10 | automatically migrated upon the launch of WYND DEX. Users with liquidity
11 | in these pools may still withdraw funds if they do not want to be
12 | migrated. All other pools are trading as normal and most will also be
13 | automatically migrated. Please see RAW DAO prop 18 for the full details
14 | and list of pools eligible for automatic migration.{' '}
15 |
16 | More details
17 |
18 |
19 |
20 | )
21 | }
22 |
23 | const StyledCard = styled('div', {
24 | padding: '$8',
25 | marginTop: '22px',
26 | backgroundColor: '$colors$dark10',
27 | borderRadius: '8px',
28 | })
29 |
30 | const StyledA = styled('a', {
31 | color: '$textColors$brand',
32 | '&:hover': {
33 | textDecoration: 'underline',
34 | },
35 | })
36 |
--------------------------------------------------------------------------------
/components/NextJsAppRoot.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | globalCss,
3 | styled,
4 | useSubscribeDefaultAppTheme,
5 | useThemeClassName,
6 | } from 'junoblocks'
7 | import { useEffect, useState } from 'react'
8 |
9 | const applyGlobalStyles = globalCss({
10 | body: {
11 | backgroundColor: '$backgroundColors$base',
12 | },
13 | })
14 |
15 | const useIsRenderingOnServerSide = () => {
16 | const [isRenderingOnServerSide, setIsRenderingOnServerSide] = useState(true)
17 |
18 | useEffect(() => {
19 | setIsRenderingOnServerSide(false)
20 | }, [])
21 |
22 | return isRenderingOnServerSide
23 | }
24 |
25 | export function NextJsAppRoot({ children }) {
26 | const isRenderingOnServerSide = useIsRenderingOnServerSide()
27 |
28 | const themeClassName = useThemeClassName()
29 | useSubscribeDefaultAppTheme()
30 |
31 | /* apply theme class on body also */
32 | useEffect(() => {
33 | document.body.classList.add(themeClassName)
34 | applyGlobalStyles()
35 | return () => {
36 | document.body.classList.remove(themeClassName)
37 | }
38 | }, [themeClassName])
39 |
40 | return (
41 |
47 | {isRenderingOnServerSide ? null : children}
48 |
49 | )
50 | }
51 |
52 | const StyledContentWrapper = styled('div', {
53 | backgroundColor: '$backgroundColors$base',
54 | })
55 |
--------------------------------------------------------------------------------
/components/TestnetDialog.tsx:
--------------------------------------------------------------------------------
1 | import { Button, Dialog, DialogContent, DialogHeader, Text } from 'junoblocks'
2 | import { useState } from 'react'
3 |
4 | export const TestnetDialog = () => {
5 | const [isShowing, setShowing] = useState(true)
6 |
7 | const requestClose = () => setShowing(false)
8 |
9 | return (
10 |
25 | )
26 | }
27 |
--------------------------------------------------------------------------------
/components/index.ts:
--------------------------------------------------------------------------------
1 | export * from './ConnectedWalletButton'
2 | export * from './Input'
3 | export * from './Layout'
4 | export * from './TokenSelectList'
5 |
--------------------------------------------------------------------------------
/components/types.ts:
--------------------------------------------------------------------------------
1 | import { ComponentPropsWithRef, ElementType, HTMLProps } from 'react'
2 |
3 | export type RenderAsType = keyof JSX.IntrinsicElements | ElementType
4 |
5 | export type GetRenderAsProps =
6 | T extends JSX.IntrinsicElements
7 | ? HTMLProps
8 | : T extends ElementType
9 | ? ComponentPropsWithRef
10 | : {}
11 |
--------------------------------------------------------------------------------
/features/assets/components/DepositRedirectDialog.tsx:
--------------------------------------------------------------------------------
1 | import { Button, Dialog, DialogContent, DialogHeader, Text } from 'junoblocks'
2 |
3 | type DepositRedirectDialogProps = {
4 | isShowing: boolean
5 | onRequestClose: () => void
6 | tokenSymbol: string
7 | href: string
8 | }
9 |
10 | export const DepositRedirectDialog = ({
11 | isShowing,
12 | onRequestClose,
13 | tokenSymbol,
14 | href,
15 | }: DepositRedirectDialogProps) => {
16 | return (
17 |
38 | )
39 | }
40 |
--------------------------------------------------------------------------------
/features/assets/components/TransferDialog/AmountInput.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | BasicNumberInput,
3 | Button,
4 | ButtonForWrapper,
5 | formatTokenBalance,
6 | styled,
7 | Text,
8 | useTriggerInputFocus,
9 | } from 'junoblocks'
10 |
11 | type AmountInputProps = {
12 | amount: number
13 | maxApplicableAmount: number
14 | onAmountChange: (amount: number) => void
15 | }
16 |
17 | export const AmountInput = ({
18 | amount,
19 | maxApplicableAmount,
20 | onAmountChange,
21 | }: AmountInputProps) => {
22 | const { isFocused, bind } = useTriggerInputFocus()
23 |
24 | return (
25 |
30 |
31 |
38 |
45 |
46 |
47 |
48 |
55 |
56 |
57 |
58 | )
59 | }
60 |
61 | const StyledButtonForWrapper = styled(ButtonForWrapper, {
62 | padding: '$8 $12 $8 $8 !important',
63 | })
64 |
65 | const StyledDivForButtons = styled('div', {
66 | columnGap: '$space$4',
67 | display: 'flex',
68 | alignItems: 'center',
69 | })
70 |
71 | const StyledDivForInputWrapper = styled('div', {})
72 |
--------------------------------------------------------------------------------
/features/assets/components/TransferDialog/TokenOptionsList.tsx:
--------------------------------------------------------------------------------
1 | import { TokenSelectList } from 'components'
2 | import { useIBCAssetList } from 'hooks/useIbcAssetList'
3 |
4 | export const TokenOptionsList = ({
5 | activeTokenSymbol,
6 | onSelect,
7 | fetchingBalanceMode,
8 | ...props
9 | }) => {
10 | const [tokenList] = useIBCAssetList()
11 | return (
12 |
20 | )
21 | }
22 |
--------------------------------------------------------------------------------
/features/assets/components/TransferDialog/WalletInfo.tsx:
--------------------------------------------------------------------------------
1 | import { CSS } from '@stitches/react'
2 | import { Logo } from 'icons'
3 | import { ConnectIcon, IconWrapper, styled, Text } from 'junoblocks'
4 | import { useRecoilValue } from 'recoil'
5 | import { ibcWalletState, walletState } from 'state/atoms/walletAtoms'
6 | import { APP_NAME } from 'util/constants'
7 |
8 | export const WalletInfo = ({ label, icon, address, css }) => {
9 | return (
10 |
11 | {icon}
12 |
13 | {label}
14 |
15 |
16 |
17 | {address || "address wasn't identified yet"}
18 |
19 |
20 |
21 |
22 | )
23 | }
24 |
25 | type WalletInfoProps = {
26 | css?: CSS
27 | depositing?: boolean
28 | }
29 |
30 | export const KeplrWalletInfo = ({ css, depositing }: WalletInfoProps) => {
31 | const { address: ibcWalletAddress } = useRecoilValue(ibcWalletState)
32 |
33 | return (
34 | }
38 | address={ibcWalletAddress}
39 | />
40 | )
41 | }
42 |
43 | export const AppWalletInfo = ({ css, depositing }: WalletInfoProps) => {
44 | const { address: walletAddress } = useRecoilValue(walletState)
45 |
46 | return (
47 | } />}
51 | address={walletAddress}
52 | />
53 | )
54 | }
55 |
56 | const StyledDivForWrapper = styled('div', {
57 | display: 'flex',
58 | alignItems: 'center',
59 | columnGap: '$space$10',
60 | })
61 |
62 | const StyledDivForAddressRow = styled('div', {
63 | columnGap: '$space$2',
64 | display: 'flex',
65 | alignItems: 'center',
66 | })
67 |
68 | const StyledImgForIcon = styled('img', {
69 | width: 32,
70 | height: 32,
71 | })
72 |
--------------------------------------------------------------------------------
/features/assets/components/TransferDialog/index.tsx:
--------------------------------------------------------------------------------
1 | export { TransferDialog } from './TransferDialog'
2 |
--------------------------------------------------------------------------------
/features/assets/components/TransferDialog/types.ts:
--------------------------------------------------------------------------------
1 | export type TransactionKind = 'deposit' | 'withdraw'
2 | export type TransactionOrigin = 'platform' | 'wallet'
3 | export type TransactionType = 'incoming' | 'outgoing'
4 |
--------------------------------------------------------------------------------
/features/assets/components/TransferDialog/useTransferAssetMutation.ts:
--------------------------------------------------------------------------------
1 | import { SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate'
2 | import {
3 | Coin,
4 | DeliverTxResponse,
5 | MsgTransferEncodeObject,
6 | } from '@cosmjs/stargate'
7 | import { MsgTransfer } from 'cosmjs-types/ibc/applications/transfer/v1/tx'
8 | import { Height } from 'cosmjs-types/ibc/core/client/v1/client'
9 | import { IBCAssetInfo } from 'hooks/useIbcAssetList'
10 | import Long from 'long'
11 | import { useMutation } from 'react-query'
12 | import { useRecoilValue } from 'recoil'
13 | import { ibcWalletState, walletState } from 'state/atoms/walletAtoms'
14 | import { convertDenomToMicroDenom } from 'util/conversion'
15 |
16 | import { TransactionKind } from './types'
17 |
18 | type UseTransferAssetMutationArgs = {
19 | transactionKind: TransactionKind
20 | tokenAmount: number
21 | tokenInfo: IBCAssetInfo
22 | } & Parameters[2]
23 |
24 | const sendIbcTokens = (
25 | senderAddress: string,
26 | recipientAddress: string,
27 | transferAmount: Coin,
28 | sourcePort: string,
29 | sourceChannel: string,
30 | timeoutHeight: Height | undefined,
31 | /** timeout in seconds */
32 | timeoutTimestamp: number | undefined,
33 | memo = '',
34 | client: SigningCosmWasmClient
35 | ): Promise => {
36 | const timeoutTimestampNanoseconds = timeoutTimestamp
37 | ? Long.fromNumber(timeoutTimestamp).multiply(1_000_000_000)
38 | : undefined
39 | const transferMsg: MsgTransferEncodeObject = {
40 | typeUrl: '/ibc.applications.transfer.v1.MsgTransfer',
41 | value: MsgTransfer.fromPartial({
42 | sourcePort: sourcePort,
43 | sourceChannel: sourceChannel,
44 | sender: senderAddress,
45 | receiver: recipientAddress,
46 | token: transferAmount,
47 | timeoutHeight: timeoutHeight,
48 | timeoutTimestamp: timeoutTimestampNanoseconds,
49 | }),
50 | }
51 | return client.signAndBroadcast(senderAddress, [transferMsg], 'auto', memo)
52 | }
53 |
54 | export const useTransferAssetMutation = ({
55 | transactionKind,
56 | tokenAmount,
57 | tokenInfo,
58 | ...mutationArgs
59 | }: UseTransferAssetMutationArgs) => {
60 | const { address, client } = useRecoilValue(walletState)
61 | const { address: ibcAddress, client: ibcClient } =
62 | useRecoilValue(ibcWalletState)
63 |
64 | return useMutation(async () => {
65 | const timeout = Math.floor(new Date().getTime() / 1000) + 600
66 |
67 | if (transactionKind == 'deposit') {
68 | return await ibcClient.sendIbcTokens(
69 | ibcAddress,
70 | address,
71 | {
72 | amount: convertDenomToMicroDenom(
73 | tokenAmount,
74 | tokenInfo.decimals
75 | ).toString(),
76 | denom: tokenInfo.denom,
77 | },
78 | 'transfer',
79 | tokenInfo.channel,
80 | undefined,
81 | timeout,
82 | 'auto'
83 | )
84 | }
85 |
86 | if (transactionKind == 'withdraw') {
87 | return await sendIbcTokens(
88 | address,
89 | ibcAddress,
90 | {
91 | amount: convertDenomToMicroDenom(
92 | tokenAmount,
93 | tokenInfo.decimals
94 | ).toString(),
95 | denom: tokenInfo.juno_denom,
96 | },
97 | 'transfer',
98 | tokenInfo.juno_channel,
99 | undefined,
100 | timeout,
101 | '',
102 | client
103 | )
104 | }
105 | }, mutationArgs)
106 | }
107 |
--------------------------------------------------------------------------------
/features/assets/components/index.ts:
--------------------------------------------------------------------------------
1 | export { AssetCard } from './AssetCard'
2 | export { AssetsList } from './AssetsList'
3 | export * from './TransferDialog'
4 |
--------------------------------------------------------------------------------
/features/assets/hooks/useGetSupportedAssetsBalancesOnChain.ts:
--------------------------------------------------------------------------------
1 | import { useIBCAssetList } from 'hooks/useIbcAssetList'
2 | import { useMultipleTokenBalance } from 'hooks/useTokenBalance'
3 | import { useMemo } from 'react'
4 |
5 | export const useGetSupportedAssetsBalancesOnChain = () => {
6 | const [ibcAssetList] = useIBCAssetList()
7 | const tokensList = useMemo(
8 | () => ibcAssetList?.tokens.map(({ symbol }) => symbol),
9 | [ibcAssetList]
10 | )
11 |
12 | const [tokenBalances, loadingBalances] = useMultipleTokenBalance(tokensList)
13 |
14 | const categorizedBalances = useMemo((): [
15 | typeof tokenBalances,
16 | typeof tokenBalances
17 | ] => {
18 | if (!tokenBalances?.length) {
19 | const fallbackTokensList =
20 | tokensList?.map((tokenSymbol) => ({
21 | balance: 0,
22 | tokenSymbol,
23 | })) ?? []
24 | return [[], fallbackTokensList]
25 | }
26 |
27 | const userTokens = []
28 | const otherTokens = []
29 |
30 | for (const token of tokenBalances) {
31 | if (token.balance > 0) {
32 | userTokens.push(token)
33 | } else {
34 | otherTokens.push(token)
35 | }
36 | }
37 |
38 | return [userTokens, otherTokens]
39 | }, [tokenBalances, tokensList])
40 |
41 | return [loadingBalances, categorizedBalances] as const
42 | }
43 |
--------------------------------------------------------------------------------
/features/assets/index.ts:
--------------------------------------------------------------------------------
1 | export * from './components'
2 |
--------------------------------------------------------------------------------
/features/liquidity/components/AdditionalUnderlyingAssetsRow.tsx:
--------------------------------------------------------------------------------
1 | import { useTokenDollarValue } from 'hooks/useTokenDollarValue'
2 | import {
3 | Button,
4 | Column,
5 | dollarValueFormatterWithDecimals,
6 | formatTokenBalance,
7 | ImageForTokenLogo,
8 | InfoIcon,
9 | Inline,
10 | protectAgainstNaN,
11 | Text,
12 | Tooltip,
13 | } from 'junoblocks'
14 | import { useMemo } from 'react'
15 |
16 | import { TokenInfo } from '../../../queries/usePoolsListQuery'
17 |
18 | type AdditionalUnderlyingAssetsRowProps = {
19 | assets: Array<{
20 | tokenInfo: Pick
21 | tokenAmount: number
22 | }>
23 | }
24 |
25 | export const AdditionalUnderlyingAssetsRow = ({
26 | assets,
27 | }: AdditionalUnderlyingAssetsRowProps) => {
28 | const assetsBreakdownInfo = useMemo(
29 | () => (
30 |
31 | {assets.map(({ tokenInfo, tokenAmount }) => (
32 |
33 | {formatTokenBalance(tokenAmount, { includeCommaSeparation: true })}{' '}
34 | {tokenInfo.symbol} (
35 |
39 | )
40 |
41 | ))}
42 |
43 | ),
44 | [assets]
45 | )
46 |
47 | return (
48 |
49 |
50 |
51 | {assets.slice(0, 3).map(({ tokenInfo }) => {
52 | return (
53 |
59 | )
60 | })}
61 |
62 | {assets.length} more tokens
63 |
64 |
68 | } />
69 |
70 |
71 | )
72 | }
73 |
74 | const DisplayTokenPrice = ({ tokenSymbol, tokenAmount }) => {
75 | const [dollarPrice] = useTokenDollarValue(tokenSymbol)
76 | const formattedDollarPrice = dollarValueFormatterWithDecimals(
77 | protectAgainstNaN(tokenAmount * dollarPrice),
78 | { includeCommaSeparation: true }
79 | )
80 | return <>${formattedDollarPrice}>
81 | }
82 |
--------------------------------------------------------------------------------
/features/liquidity/components/AprPill.tsx:
--------------------------------------------------------------------------------
1 | import { ArrowUpIcon, Inline, styled, Text } from 'junoblocks'
2 |
3 | export const AprPill = ({ value, ...props }) => (
4 |
5 |
6 |
7 |
8 | {value}% APR
9 |
10 |
11 |
12 | )
13 |
14 | const StyledDivForPill = styled('div', {
15 | borderRadius: '16px',
16 | backgroundColor: '$colors$brand20',
17 | padding: '$2 $8 $2 $3',
18 | })
19 |
--------------------------------------------------------------------------------
/features/liquidity/components/BaseCardForEmptyState.tsx:
--------------------------------------------------------------------------------
1 | import { Card, CardContent, Column } from 'junoblocks'
2 |
3 | export const BaseCardForEmptyState = ({ content, footer, ...props }) => {
4 | return (
5 |
15 |
16 |
27 | {content}
28 |
29 | {footer}
30 |
31 |
32 | )
33 | }
34 |
--------------------------------------------------------------------------------
/features/liquidity/components/ButtonWithDropdownForSorting.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | Button,
3 | ButtonWithDropdown,
4 | ChevronIcon,
5 | Column,
6 | Divider,
7 | Text,
8 | ValidIcon,
9 | } from 'junoblocks'
10 |
11 | import { SortDirections, SortParameters } from '../hooks/useSortPools'
12 | type Props = {
13 | sortParameter: SortParameters
14 | sortDirection: SortDirections
15 | onSortParameterChange: (parameter: SortParameters) => void
16 | onSortDirectionChange: (direction: SortDirections) => void
17 | }
18 |
19 | export const ButtonWithDropdownForSorting = ({
20 | sortParameter,
21 | sortDirection,
22 | onSortParameterChange,
23 | onSortDirectionChange,
24 | }: Props) => {
25 | function getSortByLabel() {
26 | if (sortParameter === 'alphabetical') {
27 | return 'Sort alphabetically'
28 | }
29 | if (sortParameter === 'rewards') {
30 | return 'Sort by rewards'
31 | }
32 | return `Sort by ${sortParameter}`
33 | }
34 |
35 | return (
36 |
39 |
40 |
50 |
58 |
59 |
60 |
61 | Sorting order
62 |
63 |
64 |
72 |
80 |
81 | >
82 | }
83 | iconRight={}
84 | >
85 | {getSortByLabel()}
86 |
87 | )
88 | }
89 |
--------------------------------------------------------------------------------
/features/liquidity/components/LiquidityHeader.tsx:
--------------------------------------------------------------------------------
1 | import { ArrowUp, Button, IconWrapper, Inline, styled, Text } from 'junoblocks'
2 | import Link from 'next/link'
3 | import React from 'react'
4 |
5 | export const LiquidityHeader = ({ tokenA, tokenB, size = 'large' }) => {
6 | if (size === 'small') {
7 | return (
8 | <>
9 |
14 | Pool {tokenA.name} + {tokenB.name}
15 |
16 | >
17 | )
18 | }
19 |
20 | return (
21 |
22 |
23 |
24 | } rotation="-90deg" />}
26 | variant="ghost"
27 | as="a"
28 | >
29 | Back
30 |
31 |
32 |
33 |
34 |
35 | Pool {tokenA.name} + {tokenB.name}
36 |
37 |
38 |
39 | )
40 | }
41 |
42 | const StyledNavElement = styled('div', {
43 | display: 'flex',
44 | variants: {
45 | position: {
46 | left: {
47 | flex: 0.1,
48 | justifyContent: 'flex-start',
49 | },
50 | center: {
51 | flex: 0.8,
52 | justifyContent: 'center',
53 | },
54 | right: {
55 | flex: 0.1,
56 | justifyContent: 'flex-end',
57 | },
58 | },
59 | },
60 | })
61 |
--------------------------------------------------------------------------------
/features/liquidity/components/ManagePoolDialog/TokenToTokenRates.tsx:
--------------------------------------------------------------------------------
1 | import { usePriceForOneToken, useTxRates } from 'features/swap'
2 | import {
3 | dollarValueFormatterWithDecimals,
4 | formatTokenBalance,
5 | styled,
6 | Text,
7 | } from 'junoblocks'
8 |
9 | export const TokenToTokenRates = ({
10 | tokenASymbol,
11 | tokenBSymbol,
12 | tokenAAmount,
13 | isLoading,
14 | }) => {
15 | const [oneTokenToTokenPrice] = usePriceForOneToken({
16 | tokenASymbol,
17 | tokenBSymbol,
18 | })
19 |
20 | const { isShowing, conversionRate, conversionRateInDollar, dollarValue } =
21 | useTxRates({
22 | tokenASymbol,
23 | tokenBSymbol,
24 | tokenAAmount,
25 | tokenToTokenPrice: oneTokenToTokenPrice * tokenAAmount,
26 | isLoading,
27 | })
28 |
29 | return (
30 |
31 |
32 | 1 {tokenASymbol} ≈ {formatTokenBalance(conversionRate)} {tokenBSymbol}
33 | {' ≈ '}$
34 | {dollarValueFormatterWithDecimals(conversionRateInDollar, {
35 | includeCommaSeparation: true,
36 | })}
37 |
38 |
39 | $
40 | {dollarValueFormatterWithDecimals(dollarValue * 2, {
41 | includeCommaSeparation: true,
42 | })}
43 |
44 |
45 | )
46 | }
47 |
48 | const StyledDivForGrid = styled('div', {
49 | display: 'flex',
50 | justifyContent: 'space-between',
51 | alignItems: 'center',
52 | variants: {
53 | active: {
54 | true: {
55 | opacity: 1,
56 | },
57 | false: {
58 | opacity: 0,
59 | },
60 | },
61 | },
62 | })
63 |
--------------------------------------------------------------------------------
/features/liquidity/components/ManagePoolDialog/index.ts:
--------------------------------------------------------------------------------
1 | export { ManagePoolDialog } from './ManagePoolDialog'
2 |
--------------------------------------------------------------------------------
/features/liquidity/components/PercentageSelection.tsx:
--------------------------------------------------------------------------------
1 | import { Button, styled } from 'junoblocks'
2 |
3 | type PercentageSelectionProps = {
4 | maxLiquidity: number
5 | liquidity: number
6 | onChangeLiquidity: (liquidity: number) => void
7 | }
8 |
9 | export const PercentageSelection = ({
10 | liquidity,
11 | onChangeLiquidity,
12 | maxLiquidity,
13 | }: PercentageSelectionProps): JSX.Element => {
14 | const valuesForSteps = [0.1, 0.25, 0.5, 0.75, 1]
15 | const percentage = liquidity / maxLiquidity
16 |
17 | return (
18 |
19 | {valuesForSteps.map((valueForStep) => {
20 | return (
21 |
32 | )
33 | })}
34 |
35 | )
36 | }
37 |
38 | const StyledDivForGrid = styled('div', {
39 | display: 'flex',
40 | alignItems: 'center',
41 | justifyContent: 'space-between',
42 | })
43 |
--------------------------------------------------------------------------------
/features/liquidity/components/SegmentedRewardsSimulator.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | dollarValueFormatterWithDecimals,
3 | protectAgainstNaN,
4 | SegmentedControl,
5 | Text,
6 | } from 'junoblocks'
7 | import { useRef, useState } from 'react'
8 |
9 | type SegmentedRewardsSimulatorProps = {
10 | interestOnStakedBalance: number
11 | stakedLiquidityDollarValue: number
12 | }
13 |
14 | export const SegmentedRewardsSimulator = ({
15 | interestOnStakedBalance,
16 | stakedLiquidityDollarValue,
17 | }: SegmentedRewardsSimulatorProps) => {
18 | const values = useRef([
19 | { value: 'year', label: 'Year' },
20 | { value: 'month', label: 'Month' },
21 | { value: 'day', label: 'Day' },
22 | ]).current
23 |
24 | const [activeValue, setActiveValue] =
25 | useState('year')
26 |
27 | const hasStakedLiquidity = stakedLiquidityDollarValue > 0
28 |
29 | const yearRewardOnStakedBalance =
30 | stakedLiquidityDollarValue * interestOnStakedBalance
31 |
32 | let divider = 1
33 | if (activeValue === 'month') {
34 | divider = 12
35 | } else if (activeValue === 'day') {
36 | divider = 365
37 | }
38 |
39 | const rewardsAmountInDollarValue = dollarValueFormatterWithDecimals(
40 | protectAgainstNaN(yearRewardOnStakedBalance / divider),
41 | { includeCommaSeparation: true }
42 | )
43 |
44 | return (
45 | <>
46 | {
50 | event.stopPropagation()
51 | setActiveValue(value)
52 | }}
53 | />
54 |
55 |
60 | {hasStakedLiquidity
61 | ? `+ $${rewardsAmountInDollarValue} /${activeValue}`
62 | : '-- /day'}
63 |
64 | >
65 | )
66 | }
67 |
--------------------------------------------------------------------------------
/features/liquidity/components/StateSwitchButtons.tsx:
--------------------------------------------------------------------------------
1 | import { Button, styled } from 'junoblocks'
2 |
3 | type StateSwitchButtonsProps = {
4 | activeValue: string
5 | values: Array
6 | onStateChange: (state: string) => void
7 | }
8 |
9 | export const StateSwitchButtons = ({
10 | activeValue,
11 | values,
12 | onStateChange,
13 | }: StateSwitchButtonsProps) => {
14 | return (
15 |
16 | {values.map((value) => (
17 |
27 | ))}
28 |
29 | )
30 | }
31 |
32 | const StyledDivForGrid = styled('div', {
33 | display: 'flex',
34 | alignItems: 'center',
35 | columnGap: '4px',
36 | '& *': {
37 | textTransform: 'capitalize',
38 | },
39 | })
40 |
--------------------------------------------------------------------------------
/features/liquidity/components/StepIcon.tsx:
--------------------------------------------------------------------------------
1 | import { Inline, Text } from 'junoblocks'
2 |
3 | export const StepIcon = ({ step }) => {
4 | return (
5 |
12 |
13 | {step}
14 |
15 |
16 | )
17 | }
18 |
--------------------------------------------------------------------------------
/features/liquidity/components/UnderlyingAssetRow.tsx:
--------------------------------------------------------------------------------
1 | import { useTokenDollarValue } from 'hooks/useTokenDollarValue'
2 | import {
3 | Button,
4 | dollarValueFormatterWithDecimals,
5 | formatTokenBalance,
6 | ImageForTokenLogo,
7 | InfoIcon,
8 | Inline,
9 | protectAgainstNaN,
10 | Text,
11 | Tooltip,
12 | } from 'junoblocks'
13 |
14 | import { TokenInfo } from '../../../queries/usePoolsListQuery'
15 |
16 | type UnderlyingAssetRowProps = {
17 | tokenInfo?: TokenInfo
18 | tokenAmount?: number
19 | visible?: boolean
20 | symbolVisible?: boolean
21 | }
22 |
23 | export const UnderlyingAssetRow = ({
24 | tokenInfo,
25 | tokenAmount,
26 | visible = true,
27 | }: // symbolVisible = true,
28 | UnderlyingAssetRowProps) => {
29 | const token = visible ? tokenInfo : undefined
30 | const [tokenDollarValue] = useTokenDollarValue(
31 | visible ? tokenInfo?.symbol : undefined
32 | )
33 |
34 | const tokenAmountDollarValue = dollarValueFormatterWithDecimals(
35 | protectAgainstNaN(tokenAmount * tokenDollarValue),
36 | { includeCommaSeparation: true }
37 | )
38 |
39 | const infoTooltipLabel = `≈ $${tokenAmountDollarValue} USD`
40 |
41 | return (
42 |
47 |
48 |
53 | {token?.symbol}
54 |
55 |
56 |
57 |
58 | {formatTokenBalance(tokenAmount, { includeCommaSeparation: true })}
59 |
60 | {token?.symbol}
61 |
62 |
63 | }
67 | iconColor={tokenAmount ? 'secondary' : 'disabled'}
68 | disabled={!tokenAmount}
69 | />
70 |
71 |
72 |
73 | )
74 | }
75 |
--------------------------------------------------------------------------------
/features/liquidity/hooks/index.ts:
--------------------------------------------------------------------------------
1 | export { useClaimTokens } from './useClaimTokens'
2 | export { usePoolPairTokenAmount } from './usePoolPairTokenAmount'
3 | export { usePoolTokensDollarValue } from './usePoolTokensDollarValue'
4 | export { useStakedTokenBalance } from './useStakedTokenBalance'
5 | export { useStakingClaims } from './useStakingClaims'
6 | export { useUnstakingDuration } from './useUnstakingDuration'
7 |
--------------------------------------------------------------------------------
/features/liquidity/hooks/useClaimTokens.ts:
--------------------------------------------------------------------------------
1 | import { usePoolFromListQueryById } from 'queries/usePoolsListQuery'
2 | import { useMutation } from 'react-query'
3 | import { useRecoilValue } from 'recoil'
4 | import { claimTokens } from 'services/staking'
5 | import { walletState } from 'state/atoms/walletAtoms'
6 |
7 | type UseClaimTokensMutationArgs = {
8 | poolId: string
9 | } & Parameters[2]
10 |
11 | export const useClaimTokens = ({
12 | poolId,
13 | ...mutationArgs
14 | }: UseClaimTokensMutationArgs) => {
15 | const { address, client } = useRecoilValue(walletState)
16 | const [pool] = usePoolFromListQueryById({ poolId })
17 |
18 | return useMutation(
19 | `claimTokens/${poolId}`,
20 | async () => {
21 | return claimTokens(address, pool.staking_address, client)
22 | },
23 | mutationArgs
24 | )
25 | }
26 |
--------------------------------------------------------------------------------
/features/liquidity/hooks/usePoolPairTokenAmount.ts:
--------------------------------------------------------------------------------
1 | import { useSwapInfo } from 'hooks/useSwapInfo'
2 | import { calcPoolTokenValue } from 'util/conversion'
3 |
4 | type UsePoolPairTokenAmountArgs = {
5 | tokenAmountInMicroDenom: number
6 | tokenPairIndex: 0 | 1
7 | poolId: string
8 | }
9 |
10 | /* probably not the best but can get what's a token worth in a given pool */
11 | export const usePoolPairTokenAmount = ({
12 | tokenAmountInMicroDenom,
13 | tokenPairIndex,
14 | poolId,
15 | }: UsePoolPairTokenAmountArgs) => {
16 | const [swapInfo, isLoading] = useSwapInfo({ poolId })
17 | const tokenReserves =
18 | swapInfo?.[tokenPairIndex === 0 ? 'token1_reserve' : 'token2_reserve'] ?? 0
19 |
20 | const amount = tokenReserves
21 | ? calcPoolTokenValue({
22 | tokenAmountInMicroDenom,
23 | tokenSupply: swapInfo.lp_token_supply,
24 | tokenReserves,
25 | })
26 | : 0
27 |
28 | return [amount, isLoading] as const
29 | }
30 |
--------------------------------------------------------------------------------
/features/liquidity/hooks/usePoolTokensDollarValue.ts:
--------------------------------------------------------------------------------
1 | import { useSwapInfo } from 'hooks/useSwapInfo'
2 | import { useTokenDollarValue } from 'hooks/useTokenDollarValue'
3 | import {
4 | PoolEntityType,
5 | usePoolFromListQueryById,
6 | } from 'queries/usePoolsListQuery'
7 | import { useCallback } from 'react'
8 | import { calcPoolTokenValue } from 'util/conversion'
9 |
10 | type UseGetPoolTokensDollarValueArgs = {
11 | poolId: PoolEntityType['pool_id']
12 | }
13 |
14 | export const useGetPoolTokensDollarValue = ({
15 | poolId,
16 | }: UseGetPoolTokensDollarValueArgs) => {
17 | const [pool] = usePoolFromListQueryById({ poolId })
18 | const [tokenAPrice, isPriceLoading] = useTokenDollarValue(
19 | pool?.pool_assets[0].symbol
20 | )
21 |
22 | const enabled = pool && typeof tokenAPrice === 'number' && !isPriceLoading
23 |
24 | return [
25 | useCallback(
26 | function getPoolTokensDollarValue({ swapInfo, tokenAmountInMicroDenom }) {
27 | if (swapInfo) {
28 | return (
29 | calcPoolTokenValue({
30 | tokenAmountInMicroDenom,
31 | tokenSupply: swapInfo.lp_token_supply,
32 | tokenReserves: swapInfo.token1_reserve,
33 | }) *
34 | tokenAPrice *
35 | 2
36 | )
37 | }
38 | return 0
39 | },
40 | [tokenAPrice]
41 | ),
42 | enabled,
43 | ] as const
44 | }
45 |
46 | type UsePoolTokensDollarValueArgs = {
47 | poolId: string
48 | tokenAmountInMicroDenom: number
49 | }
50 |
51 | export const usePoolTokensDollarValue = ({
52 | poolId,
53 | tokenAmountInMicroDenom,
54 | }: UsePoolTokensDollarValueArgs) => {
55 | const [getPoolTokensDollarValue, enabled] = useGetPoolTokensDollarValue({
56 | poolId,
57 | })
58 |
59 | const [swapInfo, isLoading] = useSwapInfo({ poolId })
60 |
61 | if (swapInfo) {
62 | return [
63 | getPoolTokensDollarValue({ swapInfo, tokenAmountInMicroDenom }),
64 | isLoading || !enabled,
65 | ] as const
66 | }
67 |
68 | return [0, isLoading || !enabled] as const
69 | }
70 |
--------------------------------------------------------------------------------
/features/liquidity/hooks/useSortPools.ts:
--------------------------------------------------------------------------------
1 | import { PoolEntityTypeWithLiquidity } from 'queries/useQueryPools'
2 | import { useMemo } from 'react'
3 |
4 | export type SortParameters = 'liquidity' | 'rewards' | 'alphabetical'
5 | export type SortDirections = 'asc' | 'desc'
6 |
7 | type UseSortPoolsArgs = {
8 | pools?: Array
9 | filter?: {
10 | tokenSymbol: string
11 | }
12 | sortBy?: {
13 | parameter: SortParameters
14 | direction: SortDirections
15 | }
16 | }
17 |
18 | export const useSortPools = ({ pools, filter, sortBy }: UseSortPoolsArgs) => {
19 | return useMemo((): readonly [
20 | Array,
21 | Array
22 | ] => {
23 | const myPools = [] as Array
24 | const otherPools = [] as Array
25 |
26 | if (!pools?.length) {
27 | return [myPools, otherPools]
28 | }
29 |
30 | /* split up liquidity in my liquidity pools and other pools buckets */
31 | pools.forEach((pool) => {
32 | const providedLiquidityAmount = pool.liquidity.providedTotal.tokenAmount
33 | const poolsBucket = providedLiquidityAmount > 0 ? myPools : otherPools
34 |
35 | poolsBucket.push(pool)
36 | })
37 |
38 | /* sort and filter pools */
39 | return [
40 | sortPools(filterPools(myPools, filter), sortBy),
41 | sortPools(filterPools(otherPools, filter), sortBy),
42 | ] as const
43 | }, [pools, filter, sortBy])
44 | }
45 |
46 | function sortPools(
47 | pools: Array,
48 | sortBy?: UseSortPoolsArgs['sortBy']
49 | ) {
50 | if (!sortBy) return pools
51 | const result = pools.sort((poolA, poolB) => {
52 | /* sort by total liquidity */
53 | if (sortBy.parameter === 'liquidity') {
54 | const poolATotalLiquidity = poolA.liquidity.available.total.dollarValue
55 | const poolBTotalLiquidity = poolB.liquidity.available.total.dollarValue
56 |
57 | if (poolATotalLiquidity > poolBTotalLiquidity) {
58 | return 1
59 | } else if (poolATotalLiquidity < poolBTotalLiquidity) {
60 | return -1
61 | }
62 | }
63 |
64 | /* sort by tokenB names */
65 | if (sortBy.parameter === 'alphabetical') {
66 | const poolATokenName = poolA.pool_assets[0].symbol
67 | const poolBTokenName = poolB.pool_assets[0].symbol
68 |
69 | if (poolATokenName > poolBTokenName) {
70 | return 1
71 | } else if (poolATokenName < poolBTokenName) {
72 | return -1
73 | }
74 | }
75 |
76 | return 0
77 | })
78 |
79 | if (sortBy.direction === 'desc') {
80 | return result.reverse()
81 | }
82 |
83 | return result
84 | }
85 |
86 | function filterPools(
87 | pools: Array,
88 | filter?: UseSortPoolsArgs['filter']
89 | ) {
90 | if (!filter || !filter.tokenSymbol) return pools
91 | return pools.filter(
92 | ({ pool_assets: [tokenA, tokenB] }) =>
93 | tokenA.symbol === filter.tokenSymbol ||
94 | tokenB.symbol === filter.tokenSymbol
95 | )
96 | }
97 |
--------------------------------------------------------------------------------
/features/liquidity/hooks/useStakedTokenBalance.ts:
--------------------------------------------------------------------------------
1 | import { usePoolFromListQueryById } from 'queries/usePoolsListQuery'
2 | import { useQuery } from 'react-query'
3 | import { useRecoilValue } from 'recoil'
4 | import { getProvidedStakedAmount } from 'services/staking'
5 | import { walletState, WalletStatusType } from 'state/atoms/walletAtoms'
6 | import { DEFAULT_TOKEN_BALANCE_REFETCH_INTERVAL } from 'util/constants'
7 |
8 | export const useStakedTokenBalance = ({ poolId, enabled = true }) => {
9 | const { address, status, client } = useRecoilValue(walletState)
10 |
11 | const [pool] = usePoolFromListQueryById({ poolId })
12 |
13 | const { data = 0, isLoading } = useQuery(
14 | `stakedTokenBalance/${poolId}/${address}`,
15 | async () => {
16 | return Number(
17 | await getProvidedStakedAmount(address, pool.staking_address, client)
18 | )
19 | },
20 | {
21 | enabled: Boolean(
22 | pool?.staking_address &&
23 | status === WalletStatusType.connected &&
24 | enabled
25 | ),
26 | refetchOnMount: 'always',
27 | refetchInterval: DEFAULT_TOKEN_BALANCE_REFETCH_INTERVAL,
28 | refetchIntervalInBackground: true,
29 | }
30 | )
31 |
32 | return [data, isLoading] as const
33 | }
34 |
--------------------------------------------------------------------------------
/features/liquidity/hooks/useStakingClaims.ts:
--------------------------------------------------------------------------------
1 | import dayjs from 'dayjs'
2 | import { useQuery } from 'react-query'
3 | import { useRecoilValue } from 'recoil'
4 | import { Claim, getClaims } from 'services/staking'
5 | import { walletState, WalletStatusType } from 'state/atoms/walletAtoms'
6 | import { DEFAULT_TOKEN_BALANCE_REFETCH_INTERVAL } from 'util/constants'
7 |
8 | import { usePoolFromListQueryById } from '../../../queries/usePoolsListQuery'
9 |
10 | type StakingClaimsType = {
11 | redeemableClaims?: Array
12 | awaitedClaims?: Array
13 | allClaims?: Array
14 | }
15 |
16 | export const useStakingClaims = ({ poolId }) => {
17 | const [pool] = usePoolFromListQueryById({ poolId })
18 | const { address, status, client } = useRecoilValue(walletState)
19 |
20 | const { data = {} as StakingClaimsType, isLoading } =
21 | useQuery(
22 | `@staking-claims/${poolId}/${address}`,
23 | async () => {
24 | const claims = await getClaims(address, pool.staking_address, client)
25 |
26 | return claims.reduce(
27 | (claims, claim) => {
28 | const isClaimRedeemable = dayjs(claim.release_at).diff(dayjs()) <= 0
29 |
30 | if (isClaimRedeemable) {
31 | claims.redeemableClaims.push(claim)
32 | } else {
33 | claims.awaitedClaims.push(claim)
34 | }
35 |
36 | return claims
37 | },
38 | {
39 | redeemableClaims: [] as Array,
40 | awaitedClaims: [] as Array,
41 | allClaims: claims,
42 | }
43 | )
44 | },
45 | {
46 | enabled: Boolean(
47 | pool?.staking_address && status === WalletStatusType.connected
48 | ),
49 | refetchOnMount: 'always',
50 | refetchInterval: DEFAULT_TOKEN_BALANCE_REFETCH_INTERVAL,
51 | refetchIntervalInBackground: true,
52 | }
53 | )
54 |
55 | return [data, isLoading] as const
56 | }
57 |
--------------------------------------------------------------------------------
/features/liquidity/hooks/useUnstakingDuration.ts:
--------------------------------------------------------------------------------
1 | import { useChainInfo } from 'hooks/useChainInfo'
2 | import { useQuery } from 'react-query'
3 | import { getUnstakingDuration } from 'services/staking'
4 | import { WalletStatusType } from 'state/atoms/walletAtoms'
5 |
6 | import {
7 | PoolEntityType,
8 | usePoolFromListQueryById,
9 | } from '../../../queries/usePoolsListQuery'
10 | import { cosmWasmClientRouter } from '../../../util/cosmWasmClientRouter'
11 |
12 | type UseUnstakingDurationArgs = {
13 | poolId: PoolEntityType['pool_id']
14 | }
15 |
16 | export const useUnstakingDuration = ({ poolId }: UseUnstakingDurationArgs) => {
17 | const [pool] = usePoolFromListQueryById({ poolId })
18 | const [chainInfo] = useChainInfo()
19 |
20 | const { data = 0, isLoading } = useQuery(
21 | `unstakingDuration/${poolId}`,
22 | async () => {
23 | const client = await cosmWasmClientRouter.connect(chainInfo.rpc)
24 | return getUnstakingDuration(pool?.staking_address, client)
25 | },
26 | {
27 | enabled: Boolean(
28 | pool?.staking_address && status === WalletStatusType.connected
29 | ),
30 | refetchOnMount: false,
31 | refetchIntervalInBackground: false,
32 | }
33 | )
34 |
35 | return [data, isLoading] as const
36 | }
37 |
--------------------------------------------------------------------------------
/features/liquidity/index.ts:
--------------------------------------------------------------------------------
1 | export { BondLiquidityDialog } from './components/BondLiquidityDialog'
2 | export { ButtonWithDropdownForSorting } from './components/ButtonWithDropdownForSorting'
3 | export { LiquidityBreakdown } from './components/LiquidityBreakdown'
4 | export { LiquidityHeader } from './components/LiquidityHeader'
5 | export { LiquidityRewardsCard } from './components/LiquidityRewardsCard'
6 | export { ManageBondedLiquidityCard } from './components/ManageBondedLiquidityCard'
7 | export { ManageLiquidityCard } from './components/ManageLiquidityCard'
8 | export { ManagePoolDialog } from './components/ManagePoolDialog'
9 | export { PoolCard } from './components/PoolCard'
10 | export { StakingSummary } from './components/StakingSummary'
11 | export { UnbondingLiquidityCard } from './components/UnbondingLiquidityCard'
12 | export { UnbondingLiquidityStatusList } from './components/UnbondingLiquidityStatusList'
13 | export {
14 | useGetPoolTokensDollarValue,
15 | usePoolTokensDollarValue,
16 | } from './hooks/usePoolTokensDollarValue'
17 | export type { SortDirections, SortParameters } from './hooks/useSortPools'
18 | export { useSortPools } from './hooks/useSortPools'
19 | export {}
20 |
--------------------------------------------------------------------------------
/features/swap/components/ConvenienceBalanceButtons.tsx:
--------------------------------------------------------------------------------
1 | import { useBaseTokenInfo } from 'hooks/useTokenInfo'
2 | import { Button } from 'junoblocks'
3 | import React from 'react'
4 |
5 | type ConvenienceBalanceButtonsProps = {
6 | disabled?: boolean
7 | tokenSymbol: string
8 | availableAmount: number
9 | onChange: (amount: number) => void
10 | }
11 |
12 | export const ConvenienceBalanceButtons = ({
13 | tokenSymbol,
14 | availableAmount,
15 | disabled,
16 | onChange,
17 | }: ConvenienceBalanceButtonsProps) => {
18 | const baseToken = useBaseTokenInfo()
19 | return (
20 | !disabled && (
21 | <>
22 |
37 |
43 | >
44 | )
45 | )
46 | }
47 |
--------------------------------------------------------------------------------
/features/swap/components/QueryInput.tsx:
--------------------------------------------------------------------------------
1 | import { Inline, SearchIcon, Text } from 'junoblocks'
2 | import React, { HTMLProps, useEffect, useRef } from 'react'
3 |
4 | type QueryInputProps = {
5 | searchQuery: string
6 | onQueryChange: (query: string) => void
7 | } & HTMLProps
8 |
9 | export const QueryInput = ({
10 | searchQuery,
11 | onQueryChange,
12 | ...inputProps
13 | }: QueryInputProps) => {
14 | const inputRef = useRef()
15 |
16 | useEffect(() => {
17 | inputRef.current.focus()
18 |
19 | return () => {
20 | onQueryChange('')
21 | }
22 | }, [onQueryChange])
23 |
24 | return (
25 |
26 |
27 |
28 | onQueryChange(value)}
35 | autoComplete="off"
36 | style={{ width: '100%' }}
37 | {...inputProps}
38 | />
39 |
40 |
41 | )
42 | }
43 |
--------------------------------------------------------------------------------
/features/swap/components/SelectorInput.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | calculateCharactersLength,
3 | styled,
4 | Text,
5 | useAmountChangeController,
6 | } from 'junoblocks'
7 | import React, { HTMLProps, Ref } from 'react'
8 |
9 | type SelectorInputProps = {
10 | amount: number
11 | disabled: boolean
12 | onAmountChange: (amount: number) => void
13 | inputRef?: Ref
14 | } & Omit, 'ref'>
15 |
16 | export const SelectorInput = ({
17 | amount,
18 | disabled,
19 | onAmountChange,
20 | inputRef,
21 | ...inputProps
22 | }: SelectorInputProps) => {
23 | const { value, setValue } = useAmountChangeController({
24 | amount,
25 | onAmountChange,
26 | })
27 |
28 | return (
29 |
30 | setValue(value) : undefined
39 | }
40 | autoComplete="off"
41 | readOnly={disabled}
42 | style={{ width: `${calculateCharactersLength(value)}ch` }}
43 | {...inputProps}
44 | />
45 |
46 | )
47 | }
48 |
49 | const StyledInput = styled('input', {
50 | width: 'auto',
51 | textAlign: 'right',
52 | color: 'inherit',
53 | })
54 |
--------------------------------------------------------------------------------
/features/swap/components/SelectorToggle.tsx:
--------------------------------------------------------------------------------
1 | import { useTokenInfo } from 'hooks/useTokenInfo'
2 | import {
3 | ButtonForWrapper,
4 | Chevron,
5 | formatTokenBalance,
6 | IconWrapper,
7 | ImageForTokenLogo,
8 | styled,
9 | Text,
10 | } from 'junoblocks'
11 | import React from 'react'
12 | import { getPropsForInteractiveElement } from 'util/getPropsForInteractiveElement'
13 |
14 | type SelectorToggleProps = {
15 | isSelecting: boolean
16 | onToggle: () => void
17 | tokenSymbol: string
18 | availableAmount: number
19 | }
20 |
21 | export const SelectorToggle = ({
22 | isSelecting,
23 | onToggle,
24 | availableAmount,
25 | tokenSymbol,
26 | }: SelectorToggleProps) => {
27 | const { logoURI } = useTokenInfo(tokenSymbol) || {}
28 |
29 | const formattedAvailableAmount = formatTokenBalance(availableAmount, {
30 | includeCommaSeparation: true,
31 | })
32 |
33 | const hasTokenSelected = Boolean(tokenSymbol)
34 |
35 | return (
36 |
41 | {(isSelecting || !hasTokenSelected) && (
42 | <>
43 | Select a token
44 | }
49 | />
50 | >
51 | )}
52 | {!isSelecting && hasTokenSelected && (
53 | <>
54 |
55 |
56 | {tokenSymbol}
57 |
58 | {formattedAvailableAmount} available
59 |
60 |
61 | }
66 | />
67 | >
68 | )}
69 |
70 | )
71 | }
72 |
73 | const StyledDivForSelector = styled(ButtonForWrapper, {
74 | cursor: 'pointer',
75 | display: 'grid',
76 | alignItems: 'center',
77 | backgroundColor: '$colors$dark0',
78 | borderRadius: '$1',
79 | transition: 'background-color .1s ease-out',
80 | userSelect: 'none',
81 | whiteSpace: 'pre',
82 |
83 | variants: {
84 | state: {
85 | selected: {
86 | padding: '$4 $6',
87 | columnGap: '$space$6',
88 | gridTemplateColumns: '$space$15 1fr $space$8',
89 | minWidth: 231,
90 | },
91 | selecting: {
92 | margin: '$space$1 0',
93 | padding: '$space$6 $8',
94 | columnGap: '$space$4',
95 | gridTemplateColumns: '1fr $space$8',
96 | },
97 | },
98 | },
99 | })
100 |
--------------------------------------------------------------------------------
/features/swap/components/TokenOptionsList.tsx:
--------------------------------------------------------------------------------
1 | import { TokenSelectList, TokenSelectListProps } from 'components'
2 | import { useTokenList } from 'hooks/useTokenList'
3 |
4 | export const TokenOptionsList = ({
5 | activeTokenSymbol,
6 | onSelect,
7 | ...props
8 | }: Omit) => {
9 | const [tokenList] = useTokenList()
10 | return (
11 |
18 | )
19 | }
20 |
--------------------------------------------------------------------------------
/features/swap/components/index.ts:
--------------------------------------------------------------------------------
1 | export { TokenSwapModule } from './TokenSwapModule'
2 |
--------------------------------------------------------------------------------
/features/swap/hooks/index.ts:
--------------------------------------------------------------------------------
1 | export { usePriceForOneToken } from './usePriceForOneToken'
2 | export { useTokenSwap } from './useTokenSwap'
3 | export { useTokenToTokenPrice } from './useTokenToTokenPrice'
4 | export { useTxRates } from './useTxRates'
5 | export { useSwapFee } from './useSwapFee'
6 |
--------------------------------------------------------------------------------
/features/swap/hooks/usePriceForOneToken.ts:
--------------------------------------------------------------------------------
1 | import { usePersistance } from 'junoblocks'
2 |
3 | import { useTokenToTokenPrice } from './useTokenToTokenPrice'
4 |
5 | export const usePriceForOneToken = ({ tokenASymbol, tokenBSymbol }) => {
6 | const [{ price: currentTokenPrice }, isPriceLoading] = useTokenToTokenPrice({
7 | tokenASymbol: tokenASymbol,
8 | tokenBSymbol: tokenBSymbol,
9 | tokenAmount: 1,
10 | })
11 |
12 | const persistPrice = usePersistance(
13 | isPriceLoading ? undefined : currentTokenPrice
14 | )
15 |
16 | return [persistPrice, isPriceLoading] as const
17 | }
18 |
--------------------------------------------------------------------------------
/features/swap/hooks/useSwapFee.ts:
--------------------------------------------------------------------------------
1 | import { NETWORK_FEE } from 'util/constants'
2 | import { useCosmWasmClient } from '../../../hooks/useCosmWasmClient'
3 | import { getSwapFee, FeeResponse } from '../../../services/swap'
4 | import { useTokenToTokenPrice } from './'
5 | import { usePoolsListQuery } from '../../../queries/usePoolsListQuery'
6 | import { useQuery } from 'react-query'
7 |
8 | export const useSwapFee = ({ tokenA, tokenB }) => {
9 | const [tokenToTokenPrice] = useTokenToTokenPrice({
10 | tokenASymbol: tokenA.tokenSymbol,
11 | tokenBSymbol: tokenB.tokenSymbol,
12 | tokenAmount: 1,
13 | })
14 |
15 | // pool_id for direct token swap pools OR inputPool for passthrough swap
16 | let poolId1: string = ''
17 | // pool_id for outputPool if passthrough swap
18 | let poolId2: string = ''
19 | if (tokenToTokenPrice.poolForDirectTokenAToTokenBSwap) {
20 | poolId1 = tokenToTokenPrice.poolForDirectTokenAToTokenBSwap.pool_id
21 | } else if (tokenToTokenPrice.poolForDirectTokenBToTokenASwap) {
22 | poolId1 = tokenToTokenPrice.poolForDirectTokenBToTokenASwap.pool_id
23 | } else if (tokenToTokenPrice.passThroughPools) {
24 | poolId1 = tokenToTokenPrice.passThroughPools[0].inputPool.pool_id
25 | poolId2 = tokenToTokenPrice.passThroughPools[0].outputPool.pool_id
26 | }
27 | const { data: fee1 } = useSwapFeeQuery(poolId1)
28 | const { data: fee2 } = useSwapFeeQuery(poolId2)
29 |
30 | // default to NETWORK_FEE constant
31 | let swapFee = NETWORK_FEE * 100
32 |
33 | // use fee for direct token swap or inputPool passthrough if set
34 | if (fee1 && (fee1.lp_fee_percent || fee1.protocol_fee_percent)) {
35 | swapFee =
36 | Number(fee1.lp_fee_percent || 0) + Number(fee1.protocol_fee_percent || 0)
37 | }
38 | // add fee for outputPool passthrough pool if set
39 | if (fee2 && (fee2.lp_fee_percent || fee2.protocol_fee_percent)) {
40 | swapFee +=
41 | Number(fee2.lp_fee_percent || 0) + Number(fee2.protocol_fee_percent || 0)
42 |
43 | // add default network fee if not set in outputPool passhthrough
44 | } else if (poolId2 !== '') {
45 | swapFee += NETWORK_FEE * 100
46 | }
47 |
48 | return swapFee
49 | }
50 |
51 | export const useSwapFeeQuery = (poolId: string, options = {}) => {
52 | const { data: poolsListResponse } = usePoolsListQuery()
53 | const client = useCosmWasmClient()
54 | const pool = poolsListResponse?.poolsById[poolId]
55 |
56 | return useQuery(
57 | [`swapFee/${poolId}`],
58 | () => getSwapFee(pool.swap_address, client),
59 | {
60 | enabled: Boolean(client && poolId && pool),
61 | refetchOnMount: false,
62 | ...options,
63 | }
64 | )
65 | }
66 |
--------------------------------------------------------------------------------
/features/swap/hooks/useTokenToTokenPrice.tsx:
--------------------------------------------------------------------------------
1 | import { useTokenInfo } from 'hooks/useTokenInfo'
2 | import { usePersistance } from 'junoblocks'
3 | import { useQueryMatchingPoolsForSwap } from 'queries/useQueryMatchingPoolForSwap'
4 | import { useQuery } from 'react-query'
5 | import { DEFAULT_TOKEN_BALANCE_REFETCH_INTERVAL } from 'util/constants'
6 |
7 | import { useCosmWasmClient } from '../../../hooks/useCosmWasmClient'
8 | import { tokenToTokenPriceQueryWithPools } from '../../../queries/tokenToTokenPriceQuery'
9 | import { TokenInfo } from '../../../queries/usePoolsListQuery'
10 |
11 | type UseTokenPairsPricesArgs = {
12 | tokenASymbol: TokenInfo['symbol']
13 | tokenBSymbol: TokenInfo['symbol']
14 | tokenAmount: number
15 | enabled?: boolean
16 | refetchInBackground?: boolean
17 | }
18 |
19 | export const useTokenToTokenPriceQuery = ({
20 | tokenAmount,
21 | tokenASymbol,
22 | tokenBSymbol,
23 | enabled = true,
24 | refetchInBackground,
25 | }: UseTokenPairsPricesArgs) => {
26 | const client = useCosmWasmClient()
27 |
28 | const tokenA = useTokenInfo(tokenASymbol)
29 | const tokenB = useTokenInfo(tokenBSymbol)
30 |
31 | const [matchingPools] = useQueryMatchingPoolsForSwap({ tokenA, tokenB })
32 |
33 | return useQuery(
34 | `tokenToTokenPrice/${tokenBSymbol}/${tokenASymbol}/${tokenAmount}`,
35 | async () => {
36 | if (tokenA && tokenB && matchingPools) {
37 | return await tokenToTokenPriceQueryWithPools({
38 | matchingPools,
39 | tokenA,
40 | tokenB,
41 | client,
42 | amount: tokenAmount,
43 | })
44 | }
45 | },
46 | {
47 | enabled: Boolean(
48 | enabled &&
49 | client &&
50 | matchingPools &&
51 | tokenA &&
52 | tokenB &&
53 | tokenAmount > 0 &&
54 | tokenBSymbol !== tokenASymbol
55 | ),
56 | refetchOnMount: false,
57 | refetchInterval: refetchInBackground
58 | ? DEFAULT_TOKEN_BALANCE_REFETCH_INTERVAL
59 | : undefined,
60 | refetchIntervalInBackground: Boolean(refetchInBackground),
61 | }
62 | )
63 | }
64 |
65 | export const useTokenToTokenPrice = (args: UseTokenPairsPricesArgs) => {
66 | const { data: currentTokenPrice, isLoading } = useTokenToTokenPriceQuery(args)
67 | /* persist token price when querying a new one */
68 | const persistTokenPrice = usePersistance(
69 | isLoading ? undefined : currentTokenPrice
70 | )
71 | /* select token price */
72 | const tokenPrice = isLoading ? persistTokenPrice : currentTokenPrice
73 | return [tokenPrice || { price: 0 }, isLoading] as const
74 | }
75 |
--------------------------------------------------------------------------------
/features/swap/hooks/useTxRates.ts:
--------------------------------------------------------------------------------
1 | import { useTokenDollarValue } from 'hooks/useTokenDollarValue'
2 | import { usePersistance } from 'junoblocks'
3 | import { protectAgainstNaN } from 'util/conversion'
4 |
5 | import { usePriceForOneToken } from './usePriceForOneToken'
6 |
7 | function calculateTokenToTokenConversionRate({
8 | tokenAAmount,
9 | tokenToTokenPrice,
10 | oneTokenToTokenPrice,
11 | }) {
12 | if (tokenAAmount === 0) {
13 | return oneTokenToTokenPrice
14 | }
15 |
16 | return tokenToTokenPrice / tokenAAmount
17 | }
18 |
19 | function calculateTokenToTokenConversionDollarRate({
20 | conversionRate,
21 | tokenADollarPrice,
22 | oneTokenToTokenPrice,
23 | tokenAAmount,
24 | }) {
25 | if (tokenAAmount === 0) {
26 | return tokenADollarPrice
27 | }
28 |
29 | return (tokenADollarPrice * conversionRate) / oneTokenToTokenPrice
30 | }
31 |
32 | export const useTxRates = ({
33 | tokenASymbol,
34 | tokenBSymbol,
35 | tokenAAmount,
36 | tokenToTokenPrice,
37 | isLoading,
38 | }) => {
39 | const [tokenADollarPrice, fetchingTokenDollarPrice] =
40 | useTokenDollarValue(tokenASymbol)
41 |
42 | const [oneTokenToTokenPrice] = usePriceForOneToken({
43 | tokenASymbol: tokenASymbol,
44 | tokenBSymbol: tokenBSymbol,
45 | })
46 |
47 | const dollarValue = (tokenADollarPrice || 0) * (tokenAAmount || 0)
48 |
49 | const shouldShowRates =
50 | (tokenASymbol &&
51 | tokenBSymbol &&
52 | tokenToTokenPrice > 0 &&
53 | typeof tokenAAmount === 'number' &&
54 | typeof tokenToTokenPrice === 'number') ||
55 | (oneTokenToTokenPrice && tokenAAmount === 0)
56 |
57 | const conversionRate = usePersistance(
58 | isLoading || fetchingTokenDollarPrice || !shouldShowRates
59 | ? undefined
60 | : protectAgainstNaN(
61 | calculateTokenToTokenConversionRate({
62 | tokenAAmount,
63 | tokenToTokenPrice,
64 | oneTokenToTokenPrice,
65 | })
66 | )
67 | )
68 |
69 | const conversionRateInDollar = usePersistance(
70 | isLoading || fetchingTokenDollarPrice || !shouldShowRates
71 | ? undefined
72 | : protectAgainstNaN(
73 | calculateTokenToTokenConversionDollarRate({
74 | tokenAAmount,
75 | conversionRate,
76 | tokenADollarPrice,
77 | oneTokenToTokenPrice,
78 | })
79 | )
80 | )
81 |
82 | return {
83 | isShowing: Boolean(shouldShowRates),
84 | conversionRate,
85 | conversionRateInDollar,
86 | dollarValue,
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/features/swap/index.ts:
--------------------------------------------------------------------------------
1 | export { TokenSwapModule } from './components'
2 | export { usePriceForOneToken, useTokenToTokenPrice, useTxRates } from './hooks'
3 |
--------------------------------------------------------------------------------
/features/swap/swapAtoms.ts:
--------------------------------------------------------------------------------
1 | import { atom } from 'recoil'
2 |
3 | export type TokenItemState = {
4 | tokenSymbol: string
5 | amount: number
6 | }
7 |
8 | export const tokenSwapAtom = atom<[TokenItemState, TokenItemState]>({
9 | key: 'tokenSwap',
10 | default: [
11 | {
12 | tokenSymbol: null,
13 | amount: 0,
14 | },
15 | {
16 | tokenSymbol: null,
17 | amount: 0,
18 | },
19 | ],
20 | effects_UNSTABLE: [
21 | function validateIfTokensAreSame({ onSet, setSelf }) {
22 | onSet((newValue, oldValue) => {
23 | const [tokenA, tokenB] = newValue
24 | if (tokenA.tokenSymbol === tokenB.tokenSymbol) {
25 | requestAnimationFrame(() => {
26 | setSelf([oldValue[1], oldValue[0]])
27 | })
28 | }
29 | })
30 | },
31 | ],
32 | })
33 |
34 | export const slippageAtom = atom({
35 | key: 'slippageForSwap',
36 | default: 0.01,
37 | })
38 |
--------------------------------------------------------------------------------
/hooks/useBondTokens.ts:
--------------------------------------------------------------------------------
1 | import { useMutation } from 'react-query'
2 | import { useRecoilValue } from 'recoil'
3 |
4 | import { usePoolFromListQueryById } from '../queries/usePoolsListQuery'
5 | import { stakeTokens, unstakeTokens } from '../services/staking'
6 | import { walletState } from '../state/atoms/walletAtoms'
7 | import { useSwapInfo } from './useSwapInfo'
8 |
9 | type UseBondTokensArgs = {
10 | poolId: string
11 | } & Parameters[2]
12 |
13 | export const useBondTokens = ({ poolId, ...options }: UseBondTokensArgs) => {
14 | const [pool] = usePoolFromListQueryById({ poolId })
15 | const { address, client } = useRecoilValue(walletState)
16 | const [swap] = useSwapInfo({ poolId })
17 |
18 | return useMutation(async (amount: number) => {
19 | return stakeTokens(
20 | address,
21 | pool.staking_address,
22 | swap.lp_token_address,
23 | amount,
24 | client
25 | )
26 | }, options)
27 | }
28 |
29 | type UseUnbondTokensArgs = {
30 | poolId: string
31 | } & Parameters[2]
32 |
33 | export const useUnbondTokens = ({
34 | poolId,
35 | ...options
36 | }: UseUnbondTokensArgs) => {
37 | const [pool] = usePoolFromListQueryById({ poolId })
38 | const { address, client } = useRecoilValue(walletState)
39 |
40 | return useMutation(async (amount: number) => {
41 | return unstakeTokens(address, pool.staking_address, amount, client)
42 | }, options)
43 | }
44 |
--------------------------------------------------------------------------------
/hooks/useChainInfo.ts:
--------------------------------------------------------------------------------
1 | import { ChainInfo } from '@keplr-wallet/types'
2 | import { useQuery } from 'react-query'
3 |
4 | import { queryClient } from '../services/queryClient'
5 |
6 | const chainInfoQueryKey = '@chain-info'
7 |
8 | export const unsafelyReadChainInfoCache = () =>
9 | queryClient.getQueryCache().find(chainInfoQueryKey)?.state?.data as
10 | | ChainInfo
11 | | undefined
12 |
13 | export const useChainInfo = () => {
14 | const { data, isLoading } = useQuery(
15 | chainInfoQueryKey,
16 | async () => {
17 | const response = await fetch(process.env.NEXT_PUBLIC_CHAIN_INFO_URL)
18 | return await response.json()
19 | },
20 | {
21 | onError(e) {
22 | console.error('Error loading chain info:', e)
23 | },
24 | }
25 | )
26 |
27 | return [data, isLoading] as const
28 | }
29 |
--------------------------------------------------------------------------------
/hooks/useConnectIBCWallet.ts:
--------------------------------------------------------------------------------
1 | import { createWasmAminoConverters } from '@cosmjs/cosmwasm-stargate'
2 | import {
3 | AminoTypes,
4 | createIbcAminoConverters,
5 | GasPrice,
6 | SigningStargateClient,
7 | } from '@cosmjs/stargate'
8 | import { useEffect } from 'react'
9 | import { useMutation } from 'react-query'
10 | import { useRecoilState } from 'recoil'
11 |
12 | import { ibcWalletState, WalletStatusType } from '../state/atoms/walletAtoms'
13 | import { GAS_PRICE } from '../util/constants'
14 | import { useIBCAssetInfo } from './useIBCAssetInfo'
15 |
16 | /* shares very similar logic with `useConnectWallet` and is a subject to refactor */
17 | export const useConnectIBCWallet = (
18 | tokenSymbol: string,
19 | mutationOptions?: Parameters[2]
20 | ) => {
21 | const [{ status, tokenSymbol: storedTokenSymbol }, setWalletState] =
22 | useRecoilState(ibcWalletState)
23 |
24 | const assetInfo = useIBCAssetInfo(tokenSymbol || storedTokenSymbol)
25 |
26 | const mutation = useMutation(async () => {
27 | if (window && !window?.keplr) {
28 | alert('Please install Keplr extension and refresh the page.')
29 | return
30 | }
31 |
32 | if (!tokenSymbol && !storedTokenSymbol) {
33 | throw new Error(
34 | 'You must provide `tokenSymbol` before connecting to the wallet.'
35 | )
36 | }
37 |
38 | if (!assetInfo) {
39 | throw new Error(
40 | 'Asset info for the provided `tokenSymbol` was not found. Check your internet connection.'
41 | )
42 | }
43 |
44 | /* set the fetching state */
45 | setWalletState((value) => ({
46 | ...value,
47 | tokenSymbol,
48 | client: null,
49 | state: WalletStatusType.connecting,
50 | }))
51 |
52 | try {
53 | const { chain_id, rpc } = assetInfo
54 |
55 | await window.keplr.enable(chain_id)
56 | const offlineSigner = await window.getOfflineSignerAuto(chain_id)
57 |
58 | const wasmChainClient = await SigningStargateClient.connectWithSigner(
59 | rpc,
60 | offlineSigner,
61 | {
62 | gasPrice: GasPrice.fromString(GAS_PRICE),
63 | /*
64 | * passing ibc amino types for all the amino signers (eg ledger, wallet connect)
65 | * to enable ibc & wasm transactions
66 | * */
67 | aminoTypes: new AminoTypes(
68 | Object.assign(
69 | createIbcAminoConverters(),
70 | createWasmAminoConverters()
71 | )
72 | ),
73 | }
74 | )
75 |
76 | const [{ address }] = await offlineSigner.getAccounts()
77 |
78 | /* successfully update the wallet state */
79 | setWalletState({
80 | tokenSymbol,
81 | address,
82 | client: wasmChainClient,
83 | status: WalletStatusType.connected,
84 | })
85 | } catch (e) {
86 | /* set the error state */
87 | setWalletState({
88 | tokenSymbol: null,
89 | address: '',
90 | client: null,
91 | status: WalletStatusType.error,
92 | })
93 |
94 | throw e
95 | }
96 | }, mutationOptions)
97 |
98 | const connectWallet = mutation.mutate
99 |
100 | useEffect(() => {
101 | /* restore wallet connection */
102 | if (status === WalletStatusType.restored && assetInfo) {
103 | connectWallet(null)
104 | }
105 | }, [status, connectWallet, assetInfo])
106 |
107 | useEffect(() => {
108 | function reconnectWallet() {
109 | if (assetInfo && status === WalletStatusType.connected) {
110 | connectWallet(null)
111 | }
112 | }
113 |
114 | window.addEventListener('keplr_keystorechange', reconnectWallet)
115 | return () => {
116 | window.removeEventListener('keplr_keystorechange', reconnectWallet)
117 | }
118 | }, [connectWallet, status, assetInfo])
119 |
120 | return mutation
121 | }
122 |
--------------------------------------------------------------------------------
/hooks/useConnectWallet.ts:
--------------------------------------------------------------------------------
1 | import {
2 | createWasmAminoConverters,
3 | SigningCosmWasmClient,
4 | } from '@cosmjs/cosmwasm-stargate'
5 | import {
6 | AminoTypes,
7 | createIbcAminoConverters,
8 | GasPrice,
9 | } from '@cosmjs/stargate'
10 | import { useEffect } from 'react'
11 | import { useMutation } from 'react-query'
12 | import { useRecoilState } from 'recoil'
13 |
14 | import { walletState, WalletStatusType } from '../state/atoms/walletAtoms'
15 | import { GAS_PRICE } from '../util/constants'
16 | import { useChainInfo } from './useChainInfo'
17 |
18 | export const useConnectWallet = (
19 | mutationOptions?: Parameters[2]
20 | ) => {
21 | const [{ status }, setWalletState] = useRecoilState(walletState)
22 | const [chainInfo] = useChainInfo()
23 |
24 | const mutation = useMutation(async () => {
25 | if (window && !window?.keplr) {
26 | alert('Please install Keplr extension and refresh the page.')
27 | return
28 | }
29 |
30 | /* set the fetching state */
31 | setWalletState((value) => ({
32 | ...value,
33 | client: null,
34 | state: WalletStatusType.connecting,
35 | }))
36 |
37 | try {
38 | await window.keplr.experimentalSuggestChain(chainInfo)
39 | await window.keplr.enable(chainInfo.chainId)
40 |
41 | const offlineSigner = await window.getOfflineSignerAuto(chainInfo.chainId)
42 | const wasmChainClient = await SigningCosmWasmClient.connectWithSigner(
43 | chainInfo.rpc,
44 | offlineSigner,
45 | {
46 | gasPrice: GasPrice.fromString(GAS_PRICE),
47 | /*
48 | * passing ibc amino types for all the amino signers (eg ledger, wallet connect)
49 | * to enable ibc & wasm transactions
50 | * */
51 | aminoTypes: new AminoTypes(
52 | Object.assign(
53 | createIbcAminoConverters(),
54 | createWasmAminoConverters()
55 | )
56 | ),
57 | }
58 | )
59 |
60 | const [{ address }] = await offlineSigner.getAccounts()
61 | const key = await window.keplr.getKey(chainInfo.chainId)
62 |
63 | /* successfully update the wallet state */
64 | setWalletState({
65 | key,
66 | address,
67 | client: wasmChainClient,
68 | status: WalletStatusType.connected,
69 | })
70 | } catch (e) {
71 | /* set the error state */
72 | setWalletState({
73 | key: null,
74 | address: '',
75 | client: null,
76 | status: WalletStatusType.error,
77 | })
78 |
79 | /* throw the error for the UI */
80 | throw e
81 | }
82 | }, mutationOptions)
83 |
84 | useEffect(
85 | function restoreWalletConnectionIfHadBeenConnectedBefore() {
86 | /* restore wallet connection if the state has been set with the */
87 | if (chainInfo?.rpc && status === WalletStatusType.restored) {
88 | mutation.mutate(null)
89 | }
90 | }, // eslint-disable-next-line
91 | [status, chainInfo?.rpc]
92 | )
93 |
94 | useEffect(
95 | function listenToWalletAddressChangeInKeplr() {
96 | function reconnectWallet() {
97 | if (status === WalletStatusType.connected) {
98 | mutation.mutate(null)
99 | }
100 | }
101 |
102 | window.addEventListener('keplr_keystorechange', reconnectWallet)
103 | return () => {
104 | window.removeEventListener('keplr_keystorechange', reconnectWallet)
105 | }
106 | },
107 | // eslint-disable-next-line
108 | [status]
109 | )
110 |
111 | return mutation
112 | }
113 |
--------------------------------------------------------------------------------
/hooks/useCosmWasmClient.ts:
--------------------------------------------------------------------------------
1 | import { useQuery } from 'react-query'
2 |
3 | import { cosmWasmClientRouter } from '../util/cosmWasmClientRouter'
4 | import { useChainInfo } from './useChainInfo'
5 |
6 | export const useCosmWasmClient = () => {
7 | const [chainInfo] = useChainInfo()
8 |
9 | const { data } = useQuery(
10 | '@cosmwasm-client',
11 | () => cosmWasmClientRouter.connect(chainInfo.rpc),
12 | { enabled: Boolean(chainInfo?.rpc) }
13 | )
14 |
15 | return data
16 | }
17 |
--------------------------------------------------------------------------------
/hooks/useIBCAssetInfo.ts:
--------------------------------------------------------------------------------
1 | import { useCallback, useMemo } from 'react'
2 |
3 | import { IBCAssetInfo, useIBCAssetList } from './useIbcAssetList'
4 |
5 | export const getIBCAssetInfoFromList = (
6 | assetSymbol: string,
7 | assetList: Array
8 | ): IBCAssetInfo | undefined => assetList?.find((x) => x.symbol === assetSymbol)
9 |
10 | export const useGetMultipleIBCAssetInfo = () => {
11 | const [assetList] = useIBCAssetList()
12 | return useCallback(
13 | function getMultipleIBCAssetInfo(assetSymbols: Array) {
14 | return assetSymbols?.map((assetSymbol) =>
15 | getIBCAssetInfoFromList(assetSymbol, assetList?.tokens)
16 | )
17 | },
18 | [assetList]
19 | )
20 | }
21 |
22 | export const useIBCAssetInfo = (assetSymbol: string) => {
23 | const getMultipleIBCAssetInfo = useGetMultipleIBCAssetInfo()
24 | return useMemo(
25 | () => getMultipleIBCAssetInfo([assetSymbol])?.[0],
26 | [assetSymbol, getMultipleIBCAssetInfo]
27 | )
28 | }
29 |
--------------------------------------------------------------------------------
/hooks/useIBCTokenBalance.tsx:
--------------------------------------------------------------------------------
1 | import { SigningStargateClient } from '@cosmjs/stargate'
2 | import { useQuery } from 'react-query'
3 | import { useRecoilValue } from 'recoil'
4 | import { convertMicroDenomToDenom } from 'util/conversion'
5 |
6 | import { walletState } from '../state/atoms/walletAtoms'
7 | import { DEFAULT_TOKEN_BALANCE_REFETCH_INTERVAL } from '../util/constants'
8 | import { useIBCAssetInfo } from './useIBCAssetInfo'
9 |
10 | export const useIBCTokenBalance = (tokenSymbol) => {
11 | const { address: nativeWalletAddress } = useRecoilValue(walletState)
12 | const ibcAsset = useIBCAssetInfo(tokenSymbol)
13 | const { data: balance = 0, isLoading } = useQuery(
14 | [`ibcTokenBalance/${tokenSymbol}`, nativeWalletAddress],
15 | async () => {
16 | const { denom, decimals, chain_id, rpc } = ibcAsset
17 |
18 | await window.keplr.enable(chain_id)
19 | const offlineSigner = await window.getOfflineSigner(chain_id)
20 |
21 | const wasmChainClient = await SigningStargateClient.connectWithSigner(
22 | rpc,
23 | offlineSigner
24 | )
25 |
26 | const [{ address }] = await offlineSigner.getAccounts()
27 | const coin = await wasmChainClient.getBalance(address, denom)
28 |
29 | const amount = coin ? Number(coin.amount) : 0
30 | return convertMicroDenomToDenom(amount, decimals)
31 | },
32 | {
33 | enabled: Boolean(nativeWalletAddress && ibcAsset),
34 | refetchOnMount: 'always',
35 | refetchInterval: DEFAULT_TOKEN_BALANCE_REFETCH_INTERVAL,
36 | refetchIntervalInBackground: true,
37 | }
38 | )
39 |
40 | return { balance, isLoading: isLoading }
41 | }
42 |
--------------------------------------------------------------------------------
/hooks/useIbcAssetList.ts:
--------------------------------------------------------------------------------
1 | import { useQuery } from 'react-query'
2 |
3 | export type IBCAssetInfo = {
4 | id: string
5 | name: string
6 | symbol: string
7 | chain_id: string
8 | rpc: string
9 | denom: string
10 | decimals: number
11 | juno_denom: string
12 | juno_channel: string
13 | channel: string
14 | logoURI: string
15 | deposit_gas_fee?: number
16 | external_deposit_uri?: string
17 | }
18 |
19 | export type IBCAssetList = {
20 | tokens: Array
21 | }
22 |
23 | export const useIBCAssetList = () => {
24 | const { data, isLoading } = useQuery(
25 | '@ibc-asset-list',
26 | async () => {
27 | const response = await fetch(process.env.NEXT_PUBLIC_IBC_ASSETS_URL)
28 | return await response.json()
29 | },
30 | {
31 | onError(e) {
32 | console.error('Error loading ibc asset list:', e)
33 | },
34 | refetchOnMount: false,
35 | refetchIntervalInBackground: true,
36 | refetchInterval: 1000 * 60,
37 | }
38 | )
39 |
40 | return [data, isLoading] as const
41 | }
42 |
--------------------------------------------------------------------------------
/hooks/useQueriesDataSelector.ts:
--------------------------------------------------------------------------------
1 | import { usePersistance } from 'junoblocks'
2 | import { useMemo } from 'react'
3 | import { useQueries } from 'react-query'
4 |
5 | export function useQueriesDataSelector<
6 | TQueries extends ReturnType
7 | >(queriesResult: TQueries) {
8 | const [data, isLoading, isError] = useMemo(() => {
9 | const loading = queriesResult.some(
10 | ({ isLoading, data }) => isLoading && !data
11 | )
12 | const error = queriesResult.some(({ isError }) => isError)
13 |
14 | const queriesData: Array = queriesResult.map(
15 | ({ data }) => data
16 | )
17 |
18 | const didFetchEveryQuery = !queriesData.includes(undefined)
19 |
20 | return [
21 | didFetchEveryQuery ? queriesData : undefined,
22 | loading,
23 | error,
24 | ] as const
25 | }, [queriesResult])
26 |
27 | const persistData = usePersistance(data?.[0] ? data : undefined)
28 |
29 | return [persistData, isLoading, isError] as const
30 | }
31 |
--------------------------------------------------------------------------------
/hooks/useRefetchQueries.ts:
--------------------------------------------------------------------------------
1 | import { useCallback, useRef } from 'react'
2 | import { useQueryClient } from 'react-query'
3 |
4 | const sleep = (delayMs: number) =>
5 | new Promise((resolve) => setTimeout(resolve, delayMs))
6 |
7 | export const useRefetchQueries = (
8 | queryKey?: string | Array,
9 | delayMs?: number
10 | ) => {
11 | const queryClient = useQueryClient()
12 |
13 | const queriesToRefetchRef = useRef(queryKey)
14 | queriesToRefetchRef.current = queryKey
15 |
16 | return useCallback(
17 | async function refetchQueries(queryKeyArg?: string | Array) {
18 | const queriesToRefetch = queryKeyArg || queriesToRefetchRef.current
19 |
20 | if (delayMs) {
21 | await sleep(delayMs)
22 | }
23 |
24 | if (Array.isArray(queriesToRefetch)) {
25 | return Promise.all(
26 | queriesToRefetch.map((query) =>
27 | queryClient.refetchQueries(new RegExp(query) as unknown as string)
28 | )
29 | )
30 | }
31 |
32 | return queryClient.refetchQueries(
33 | typeof queriesToRefetch === 'string' &&
34 | (new RegExp(queriesToRefetch) as unknown as string)
35 | )
36 | },
37 | [queryClient, delayMs]
38 | )
39 | }
40 |
--------------------------------------------------------------------------------
/hooks/useSwapInfo.ts:
--------------------------------------------------------------------------------
1 | import { useQuery } from 'react-query'
2 |
3 | import { usePoolsListQuery } from '../queries/usePoolsListQuery'
4 | import { getSwapInfo, InfoResponse } from '../services/swap'
5 | import { DEFAULT_TOKEN_BALANCE_REFETCH_INTERVAL } from '../util/constants'
6 | import { useCosmWasmClient } from './useCosmWasmClient'
7 |
8 | export type SwapInfo = Pick<
9 | InfoResponse,
10 | 'token1_denom' | 'token2_denom' | 'lp_token_address'
11 | > & {
12 | swap_address: string
13 | lp_token_supply: number
14 | token1_reserve: number
15 | token2_reserve: number
16 | lp_fee_percent?: number
17 | protocol_fee_percent?: number
18 | }
19 |
20 | type UseMultipleSwapInfoArgs = {
21 | poolId?: string
22 | refetchInBackground?: boolean
23 | }
24 |
25 | export const useSwapInfo = ({
26 | poolId,
27 | refetchInBackground,
28 | }: UseMultipleSwapInfoArgs) => {
29 | const { data: poolsListResponse } = usePoolsListQuery()
30 | const client = useCosmWasmClient()
31 |
32 | const { data, isLoading } = useQuery(
33 | `swapInfo/${poolId}`,
34 | async () => {
35 | const pool = poolsListResponse.poolsById[poolId]
36 | const swap = await getSwapInfo(pool.swap_address, client)
37 |
38 | return {
39 | ...swap,
40 | swap_address: pool.swap_address,
41 | token1_reserve: Number(swap.token1_reserve),
42 | token2_reserve: Number(swap.token2_reserve),
43 | lp_token_supply: Number(swap.lp_token_supply),
44 | lp_fee_percent: swap.lp_fee_percent
45 | ? Number(swap.lp_fee_percent)
46 | : undefined,
47 | protocol_fee_percent: swap.protocol_fee_percent
48 | ? Number(swap.protocol_fee_percent)
49 | : undefined,
50 | }
51 | },
52 | {
53 | enabled: Boolean(poolsListResponse?.pools.length && client && poolId),
54 | refetchOnMount: false,
55 | refetchInterval: refetchInBackground
56 | ? DEFAULT_TOKEN_BALANCE_REFETCH_INTERVAL
57 | : undefined,
58 | refetchIntervalInBackground: refetchInBackground,
59 | }
60 | )
61 |
62 | return [data, isLoading] as const
63 | }
64 |
--------------------------------------------------------------------------------
/hooks/useTokenDollarValue.tsx:
--------------------------------------------------------------------------------
1 | import { usePriceForOneToken } from 'features/swap'
2 | import { useQuery } from 'react-query'
3 |
4 | import { tokenDollarValueQuery } from '../queries/tokenDollarValueQuery'
5 | import { DEFAULT_TOKEN_BALANCE_REFETCH_INTERVAL } from '../util/constants'
6 | import { useGetMultipleIBCAssetInfo } from './useIBCAssetInfo'
7 | import {
8 | useBaseTokenInfo,
9 | useGetMultipleTokenInfo,
10 | useTokenInfo,
11 | } from './useTokenInfo'
12 |
13 | export const useTokenDollarValue = (tokenSymbol?: string) => {
14 | const { symbol: baseTokenSymbol } = useBaseTokenInfo() || {}
15 | const tokenInfo = useTokenInfo(tokenSymbol)
16 |
17 | const tokenSymbolToLookupDollarValueFor = tokenInfo?.id
18 | ? tokenSymbol
19 | : baseTokenSymbol
20 |
21 | const [[tokenDollarPrice], fetchingTokenDollarPrice] =
22 | useTokenDollarValueQuery(
23 | tokenSymbolToLookupDollarValueFor
24 | ? [tokenSymbolToLookupDollarValueFor]
25 | : null
26 | )
27 |
28 | const [oneTokenToTokenPrice, fetchingTokenToTokenPrice] = usePriceForOneToken(
29 | {
30 | tokenASymbol: tokenSymbol,
31 | tokenBSymbol: baseTokenSymbol,
32 | }
33 | )
34 |
35 | /* if the token has an id or it's the baseToken then let's return pure price from the api */
36 | const shouldRenderPureDollarPrice =
37 | tokenSymbol === baseTokenSymbol || Boolean(tokenInfo?.id)
38 | if (shouldRenderPureDollarPrice) {
39 | return [tokenDollarPrice, fetchingTokenDollarPrice] as const
40 | }
41 |
42 | /* otherwise, let's query the chain and calculate the dollar price based on ratio to base token */
43 | return [
44 | tokenDollarPrice * oneTokenToTokenPrice,
45 | fetchingTokenDollarPrice || fetchingTokenToTokenPrice,
46 | ] as const
47 | }
48 |
49 | export const useTokenDollarValueQuery = (tokenSymbols?: Array) => {
50 | const getMultipleTokenInfo = useGetMultipleTokenInfo()
51 | const getMultipleIBCAssetInfo = useGetMultipleIBCAssetInfo()
52 |
53 | const { data, isLoading } = useQuery(
54 | `tokenDollarValue/${tokenSymbols?.join('/')}`,
55 | async (): Promise> => {
56 | const tokenIds = tokenSymbols?.map(
57 | (tokenSymbol) =>
58 | (
59 | getMultipleTokenInfo([tokenSymbol])?.[0] ||
60 | getMultipleIBCAssetInfo([tokenSymbol])?.[0]
61 | )?.id
62 | )
63 |
64 | if (tokenIds) {
65 | return tokenDollarValueQuery(tokenIds)
66 | }
67 | },
68 | {
69 | enabled: Boolean(tokenSymbols?.length),
70 | refetchOnMount: 'always',
71 | refetchInterval: DEFAULT_TOKEN_BALANCE_REFETCH_INTERVAL,
72 | refetchIntervalInBackground: true,
73 | }
74 | )
75 |
76 | return [data || [], isLoading] as const
77 | }
78 |
--------------------------------------------------------------------------------
/hooks/useTokenInfo.ts:
--------------------------------------------------------------------------------
1 | import { useCallback, useMemo } from 'react'
2 |
3 | import { TokenInfo } from '../queries/usePoolsListQuery'
4 | import { useTokenList } from './useTokenList'
5 |
6 | /* token selector functions */
7 | export const getBaseTokenFromTokenList = (tokenList): TokenInfo | undefined =>
8 | tokenList?.base_token
9 |
10 | export const getTokenInfoFromTokenList = (
11 | tokenSymbol: string,
12 | tokensList: Array
13 | ): TokenInfo | undefined => tokensList?.find((x) => x.symbol === tokenSymbol)
14 | /* /token selector functions */
15 |
16 | /* returns a selector for getting multiple tokens info at once */
17 | export const useGetMultipleTokenInfo = () => {
18 | const [tokenList] = useTokenList()
19 | return useCallback(
20 | (tokenSymbols: Array) =>
21 | tokenSymbols?.map((tokenSymbol) =>
22 | getTokenInfoFromTokenList(tokenSymbol, tokenList?.tokens)
23 | ),
24 | [tokenList]
25 | )
26 | }
27 |
28 | /* hook for token info retrieval based on multiple `tokenSymbol` */
29 | export const useMultipleTokenInfo = (tokenSymbols: Array) => {
30 | const getMultipleTokenInfo = useGetMultipleTokenInfo()
31 | return useMemo(
32 | () => getMultipleTokenInfo(tokenSymbols),
33 | [tokenSymbols, getMultipleTokenInfo]
34 | )
35 | }
36 |
37 | /* hook for token info retrieval based on `tokenSymbol` */
38 | export const useTokenInfo = (tokenSymbol: string) => {
39 | return useMultipleTokenInfo(useMemo(() => [tokenSymbol], [tokenSymbol]))?.[0]
40 | }
41 |
42 | /* hook for base token info retrieval */
43 | export const useBaseTokenInfo = () => {
44 | const [tokenList] = useTokenList()
45 | return useMemo(() => getBaseTokenFromTokenList(tokenList), [tokenList])
46 | }
47 |
--------------------------------------------------------------------------------
/hooks/useTokenList.ts:
--------------------------------------------------------------------------------
1 | import { useQuery } from 'react-query'
2 |
3 | import { TokenInfo, usePoolsListQuery } from '../queries/usePoolsListQuery'
4 |
5 | export type TokenList = {
6 | base_token: TokenInfo
7 | tokens: Array
8 | tokensBySymbol: Map
9 | }
10 |
11 | export const useTokenList = () => {
12 | const { data: poolsListResponse } = usePoolsListQuery()
13 |
14 | /* generate token list off pool list and store it in cache */
15 | const { data } = useQuery(
16 | '@token-list',
17 | () => {
18 | const tokenMapBySymbol = new Map()
19 | /* serialize pool assets */
20 | poolsListResponse.pools.forEach(({ pool_assets }) => {
21 | pool_assets?.forEach((token) => {
22 | if (!tokenMapBySymbol.has(token.symbol)) {
23 | tokenMapBySymbol.set(token.symbol, token)
24 | }
25 | })
26 | })
27 |
28 | return {
29 | base_token: poolsListResponse.base_token,
30 | tokens: Array.from(tokenMapBySymbol.values()),
31 | tokensBySymbol: tokenMapBySymbol,
32 | }
33 | },
34 | {
35 | enabled: Boolean(poolsListResponse?.pools),
36 | refetchOnMount: false,
37 | onError(e) {
38 | console.error('Error generating token list:', e)
39 | },
40 | }
41 | )
42 |
43 | const isLoading = !poolsListResponse?.pools
44 |
45 | return [data, isLoading] as const
46 | }
47 |
--------------------------------------------------------------------------------
/hooks/useWalletConnectionStatus.ts:
--------------------------------------------------------------------------------
1 | import { useRecoilValue } from 'recoil'
2 |
3 | import {
4 | ibcWalletState,
5 | walletState,
6 | WalletStatusType,
7 | } from '../state/atoms/walletAtoms'
8 |
9 | export const useWalletConnectionStatus = (
10 | wallet: typeof walletState | typeof ibcWalletState
11 | ) => {
12 | const { status: walletStatus } = useRecoilValue(wallet as typeof walletState)
13 | const isConnected = walletStatus === WalletStatusType.connected
14 | const isConnecting =
15 | walletStatus === WalletStatusType.connecting ||
16 | walletStatus === WalletStatusType.restored
17 |
18 | return {
19 | isConnecting,
20 | isConnected,
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/icons/Analytics.tsx:
--------------------------------------------------------------------------------
1 | export const Analytics = () => (
2 |
17 | )
18 |
--------------------------------------------------------------------------------
/icons/Dollar.tsx:
--------------------------------------------------------------------------------
1 | export const Dollar = () => (
2 |
17 | )
18 |
--------------------------------------------------------------------------------
/icons/Pools.tsx:
--------------------------------------------------------------------------------
1 | import { SVGProps } from 'react'
2 |
3 | export const PoolsIcon = (props: SVGProps) => (
4 |
17 | )
18 |
--------------------------------------------------------------------------------
/icons/PriceData.tsx:
--------------------------------------------------------------------------------
1 | export const PriceData = () => (
2 |
17 | )
18 |
--------------------------------------------------------------------------------
/icons/Swap.tsx:
--------------------------------------------------------------------------------
1 | import { SVGProps } from 'react'
2 |
3 | export const SwapIcon = (props: SVGProps) => (
4 |
17 | )
18 |
--------------------------------------------------------------------------------
/icons/Transfer.tsx:
--------------------------------------------------------------------------------
1 | import { SVGProps } from 'react'
2 |
3 | export const TransferIcon = (props: SVGProps) => (
4 |
17 | )
18 |
--------------------------------------------------------------------------------
/icons/index.ts:
--------------------------------------------------------------------------------
1 | export * from './Logo'
2 | export * from './LogoBrighter'
3 | export * from './LogoText'
4 | export * from './Transfer'
5 | export * from './Swap'
6 | export * from './Pools'
7 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | const nextJest = require('next/jest')
2 |
3 | const createJestConfig = nextJest({
4 | // Provide the path to your Next.js app to load next.config.js and .env files in your test environment
5 | dir: './',
6 | })
7 |
8 | // Add any custom config to be passed to Jest
9 | const customJestConfig = {
10 | setupFilesAfterEnv: ['/jest.setup.js'],
11 | moduleNameMapper: {
12 | // Handle module aliases (this will be automatically configured for you soon)
13 | '^@/components/(.*)$': '/components/$1',
14 |
15 | '^@/pages/(.*)$': '/pages/$1',
16 | },
17 | testEnvironment: 'jest-environment-jsdom',
18 | }
19 |
20 | // createJestConfig is exported this way to ensure that next/jest can load the Next.js config which is async
21 | module.exports = createJestConfig(customJestConfig)
22 |
--------------------------------------------------------------------------------
/jest.setup.js:
--------------------------------------------------------------------------------
1 | // Optional: configure or set up a testing framework before each test.
2 | // If you delete this file, remove `setupFilesAfterEnv` from `jest.config.js`
3 |
4 | // Used for __tests__/testing-library.js
5 | // Learn more: https://github.com/testing-library/jest-dom
6 | import '@testing-library/jest-dom/extend-expect'
7 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | const withBundleAnalyzer = require('@next/bundle-analyzer')({
2 | enabled: process.env.ANALYZE === 'true',
3 | })
4 |
5 | const withBundleJunoblocks = require('next-bundle-junoblocks')
6 |
7 | const config = {
8 | reactStrictMode: true,
9 | target: 'serverless',
10 | images: {
11 | loader: 'cloudinary',
12 | path: 'https://res.cloudinary.com/dk8s7xjsl/image/upload/',
13 | },
14 | webpack(config, { webpack }) {
15 | config.plugins.push(
16 | new webpack.ProvidePlugin({
17 | Buffer: ['buffer', 'Buffer'],
18 | })
19 | )
20 |
21 | if (!config.resolve.fallback) {
22 | config.resolve.fallback = {}
23 | }
24 |
25 | Object.assign(config.resolve.fallback, {
26 | buffer: false,
27 | crypto: false,
28 | events: false,
29 | path: false,
30 | stream: false,
31 | string_decoder: false,
32 | })
33 |
34 | return config
35 | },
36 | }
37 |
38 | module.exports = withBundleAnalyzer(
39 | process.env.BUNDLE_JUNOBLOCKS === 'true'
40 | ? withBundleJunoblocks(config)
41 | : config
42 | )
43 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "wasmswap-interface",
3 | "version": "1.7.0",
4 | "scripts": {
5 | "dev": "next dev",
6 | "dev:w-blocks": "BUNDLE_JUNOBLOCKS=true next dev",
7 | "build": "next build && next export",
8 | "start": "next start",
9 | "lint": "next lint",
10 | "test": "jest --watch",
11 | "prettier:write": "prettier --write .",
12 | "typecheck": "tsc"
13 | },
14 | "dependencies": {
15 | "@cosmjs/amino": "0.28.10",
16 | "@cosmjs/cosmwasm": "0.25.6",
17 | "@cosmjs/cosmwasm-stargate": "0.28.10",
18 | "@cosmjs/crypto": "0.28.10",
19 | "@cosmjs/encoding": "0.28.10",
20 | "@cosmjs/launchpad": "0.27.1",
21 | "@cosmjs/math": "0.28.10",
22 | "@cosmjs/proto-signing": "0.28.10",
23 | "@cosmjs/stargate": "0.28.10",
24 | "@next/bundle-analyzer": "^12.1.0",
25 | "@react-spring/web": "^9.4.2",
26 | "@stitches/react": "^1.2.6",
27 | "body-scroll-lock": "^4.0.0-beta.0",
28 | "color": "^4.1.0",
29 | "dayjs": "^1.10.7",
30 | "eslint-plugin-simple-import-sort": "^7.0.0",
31 | "eslint-plugin-testing-library": "^5.5.1",
32 | "focus-visible": "^5.2.0",
33 | "gsap": "^3.7.1",
34 | "junoblocks": "^0.2.7",
35 | "next": "^12.2.2",
36 | "normalize.css": "^8.0.1",
37 | "react": "^18.2.0",
38 | "react-dom": "^18.2.0",
39 | "react-hot-toast": "^2.2.0",
40 | "react-query": "^3.39.1",
41 | "react-use": "^17.4.0",
42 | "recoil": "^0.7.4",
43 | "sass": "^1.45.0"
44 | },
45 | "devDependencies": {
46 | "@keplr-wallet/types": "^0.9.9",
47 | "@testing-library/jest-dom": "^5.16.4",
48 | "@testing-library/react": "^13.3.0",
49 | "@testing-library/user-event": "^14.2.1",
50 | "@types/react": "^17.0.0",
51 | "@types/react-dom": "^17.0.0",
52 | "autoprefixer": "^10.3.1",
53 | "eslint": "7.30.0",
54 | "eslint-config-next": "11.0.1",
55 | "jest": "^28.1.2",
56 | "jest-environment-jsdom": "^28.1.2",
57 | "next-bundle-junoblocks": "^1.0.2",
58 | "postcss": "^8.3.5",
59 | "prettier": "^2.3.2",
60 | "typescript": "4.5"
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/pages/_app.tsx:
--------------------------------------------------------------------------------
1 | import 'normalize.css'
2 | import 'styles/globals.scss'
3 | import 'focus-visible'
4 |
5 | import { ErrorBoundary } from 'components/ErrorBoundary'
6 | import { TestnetDialog } from 'components/TestnetDialog'
7 | import { css, media, useMedia } from 'junoblocks'
8 | import type { AppProps } from 'next/app'
9 | import { Toaster } from 'react-hot-toast'
10 | import { QueryClientProvider } from 'react-query'
11 | import { RecoilRoot } from 'recoil'
12 | import { queryClient } from 'services/queryClient'
13 |
14 | import { NextJsAppRoot } from '../components/NextJsAppRoot'
15 | import { __TEST_MODE__ } from '../util/constants'
16 |
17 | const toasterClassName = css({
18 | [media.sm]: {
19 | width: '100%',
20 | padding: 0,
21 | bottom: '$6 !important',
22 | },
23 | }).toString()
24 |
25 | function MyApp({ Component, pageProps }: AppProps) {
26 | const isSmallScreen = useMedia('sm')
27 | return (
28 |
29 |
30 |
31 |
32 |
33 | {__TEST_MODE__ && }
34 |
40 |
41 |
42 |
43 |
44 | )
45 | }
46 |
47 | export default MyApp
48 |
--------------------------------------------------------------------------------
/pages/_document.tsx:
--------------------------------------------------------------------------------
1 | import Document, { Html, Head, Main, NextScript } from 'next/document'
2 |
3 | class MyDocument extends Document {
4 | render() {
5 | return (
6 |
7 |
8 |
13 |
19 |
25 |
31 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | )
48 | }
49 | }
50 |
51 | export default MyDocument
52 |
--------------------------------------------------------------------------------
/pages/index.tsx:
--------------------------------------------------------------------------------
1 | import { AppLayout, PageHeader } from 'components'
2 | import { TokenSwapModule } from 'features/swap'
3 | import { styled } from 'junoblocks'
4 | import React from 'react'
5 | import { MigrationCard } from '../components/MigrationCard'
6 |
7 | import { APP_NAME } from '../util/constants'
8 |
9 | function getInitialTokenPairFromSearchParams() {
10 | const params = new URLSearchParams(location.search)
11 | const from = params.get('from')
12 | const to = params.get('to')
13 | return from || to ? ([from, to] as const) : undefined
14 | }
15 |
16 | export default function Home() {
17 | return (
18 |
19 |
20 |
21 |
25 |
28 |
29 |
30 | )
31 | }
32 |
33 | const StyledContainer = styled('div', {
34 | padding: '$4',
35 | margin: 0,
36 | })
37 |
--------------------------------------------------------------------------------
/public/bg@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CosmosContracts/junoswap-interface/f3d1567bec5c47dc748a20c2fafd15646d729f7f/public/bg@1x.png
--------------------------------------------------------------------------------
/public/bg@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CosmosContracts/junoswap-interface/f3d1567bec5c47dc748a20c2fafd15646d729f7f/public/bg@2x.png
--------------------------------------------------------------------------------
/public/chain_info.local.json:
--------------------------------------------------------------------------------
1 | {
2 | "chainId": "testing",
3 | "chainName": "Juno Local",
4 | "rpc": "http://localhost:26657/",
5 | "rest": "http://localhost:1317/",
6 | "stakeCurrency": {
7 | "coinDenom": "JUNO",
8 | "coinMinimalDenom": "ujuno",
9 | "coinDecimals": 6,
10 | "coinGeckoId": "juno-network"
11 | },
12 | "bip44": {
13 | "coinType": 118
14 | },
15 | "bech32Config": {
16 | "bech32PrefixAccAddr": "juno",
17 | "bech32PrefixAccPub": "junopub",
18 | "bech32PrefixValAddr": "junovaloper",
19 | "bech32PrefixValPub": "junovaloperpub",
20 | "bech32PrefixConsAddr": "junovalcons",
21 | "bech32PrefixConsPub": "junovalconspub"
22 | },
23 | "currencies": [
24 | {
25 | "coinDenom": "JUNO",
26 | "coinMinimalDenom": "ujuno",
27 | "coinDecimals": 6,
28 | "coinGeckoId": "juno-network"
29 | },
30 | {
31 | "coinDenom": "COSM",
32 | "coinMinimalDenom": "ucosm",
33 | "coinDecimals": 6
34 | }
35 | ],
36 | "feeCurrencies": [
37 | {
38 | "coinDenom": "JUNO",
39 | "coinMinimalDenom": "ujuno",
40 | "coinDecimals": 6,
41 | "coinGeckoId": "juno-network"
42 | }
43 | ],
44 | "coinType": 118,
45 | "gasPriceStep": {
46 | "low": 0.025,
47 | "average": 0.05,
48 | "high": 0.1
49 | },
50 | "features": ["cosmwasm"]
51 | }
52 |
--------------------------------------------------------------------------------
/public/chain_info.testnet.json:
--------------------------------------------------------------------------------
1 | {
2 | "chainId": "uni-5",
3 | "chainName": "Juno Uni Testnet",
4 | "rpc": "https://rpc.uni.juno.deuslabs.fi:443/",
5 | "rest": "https://lcd.uni.juno.deuslabs.fi:443/",
6 | "stakeCurrency": {
7 | "coinDenom": "JUNOX",
8 | "coinMinimalDenom": "ujunox",
9 | "coinDecimals": 6
10 | },
11 | "bip44": {
12 | "coinType": 118
13 | },
14 | "bech32Config": {
15 | "bech32PrefixAccAddr": "juno",
16 | "bech32PrefixAccPub": "junopub",
17 | "bech32PrefixValAddr": "junovaloper",
18 | "bech32PrefixValPub": "junovaloperpub",
19 | "bech32PrefixConsAddr": "junovalcons",
20 | "bech32PrefixConsPub": "junovalconspub"
21 | },
22 | "currencies": [
23 | {
24 | "coinDenom": "JUNOX",
25 | "coinMinimalDenom": "ujunox",
26 | "coinDecimals": 6,
27 | "coinGeckoId": "juno-network"
28 | }
29 | ],
30 | "feeCurrencies": [
31 | {
32 | "coinDenom": "JUNOX",
33 | "coinMinimalDenom": "ujunox",
34 | "coinDecimals": 6,
35 | "coinGeckoId": "juno-network"
36 | }
37 | ],
38 | "coinType": 118,
39 | "gasPriceStep": {
40 | "low": 0.025,
41 | "average": 0.05,
42 | "high": 0.1
43 | },
44 | "features": ["cosmwasm"]
45 | }
46 |
--------------------------------------------------------------------------------
/public/crab.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CosmosContracts/junoswap-interface/f3d1567bec5c47dc748a20c2fafd15646d729f7f/public/crab.png
--------------------------------------------------------------------------------
/public/favicons/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CosmosContracts/junoswap-interface/f3d1567bec5c47dc748a20c2fafd15646d729f7f/public/favicons/android-chrome-192x192.png
--------------------------------------------------------------------------------
/public/favicons/android-chrome-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CosmosContracts/junoswap-interface/f3d1567bec5c47dc748a20c2fafd15646d729f7f/public/favicons/android-chrome-512x512.png
--------------------------------------------------------------------------------
/public/favicons/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CosmosContracts/junoswap-interface/f3d1567bec5c47dc748a20c2fafd15646d729f7f/public/favicons/apple-touch-icon.png
--------------------------------------------------------------------------------
/public/favicons/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CosmosContracts/junoswap-interface/f3d1567bec5c47dc748a20c2fafd15646d729f7f/public/favicons/favicon-16x16.png
--------------------------------------------------------------------------------
/public/favicons/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CosmosContracts/junoswap-interface/f3d1567bec5c47dc748a20c2fafd15646d729f7f/public/favicons/favicon-32x32.png
--------------------------------------------------------------------------------
/public/favicons/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CosmosContracts/junoswap-interface/f3d1567bec5c47dc748a20c2fafd15646d729f7f/public/favicons/favicon.ico
--------------------------------------------------------------------------------
/public/favicons/site.webmanifest:
--------------------------------------------------------------------------------
1 | {
2 | "name": "",
3 | "short_name": "",
4 | "icons": [
5 | {
6 | "src": "/android-chrome-192x192.png",
7 | "sizes": "192x192",
8 | "type": "image/png"
9 | },
10 | {
11 | "src": "/android-chrome-512x512.png",
12 | "sizes": "512x512",
13 | "type": "image/png"
14 | }
15 | ],
16 | "theme_color": "#ffffff",
17 | "background_color": "#ffffff",
18 | "display": "standalone"
19 | }
20 |
--------------------------------------------------------------------------------
/public/fonts/Inter/Inter-Black.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CosmosContracts/junoswap-interface/f3d1567bec5c47dc748a20c2fafd15646d729f7f/public/fonts/Inter/Inter-Black.woff
--------------------------------------------------------------------------------
/public/fonts/Inter/Inter-Black.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CosmosContracts/junoswap-interface/f3d1567bec5c47dc748a20c2fafd15646d729f7f/public/fonts/Inter/Inter-Black.woff2
--------------------------------------------------------------------------------
/public/fonts/Inter/Inter-BlackItalic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CosmosContracts/junoswap-interface/f3d1567bec5c47dc748a20c2fafd15646d729f7f/public/fonts/Inter/Inter-BlackItalic.woff
--------------------------------------------------------------------------------
/public/fonts/Inter/Inter-BlackItalic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CosmosContracts/junoswap-interface/f3d1567bec5c47dc748a20c2fafd15646d729f7f/public/fonts/Inter/Inter-BlackItalic.woff2
--------------------------------------------------------------------------------
/public/fonts/Inter/Inter-Bold.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CosmosContracts/junoswap-interface/f3d1567bec5c47dc748a20c2fafd15646d729f7f/public/fonts/Inter/Inter-Bold.woff
--------------------------------------------------------------------------------
/public/fonts/Inter/Inter-Bold.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CosmosContracts/junoswap-interface/f3d1567bec5c47dc748a20c2fafd15646d729f7f/public/fonts/Inter/Inter-Bold.woff2
--------------------------------------------------------------------------------
/public/fonts/Inter/Inter-BoldItalic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CosmosContracts/junoswap-interface/f3d1567bec5c47dc748a20c2fafd15646d729f7f/public/fonts/Inter/Inter-BoldItalic.woff
--------------------------------------------------------------------------------
/public/fonts/Inter/Inter-BoldItalic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CosmosContracts/junoswap-interface/f3d1567bec5c47dc748a20c2fafd15646d729f7f/public/fonts/Inter/Inter-BoldItalic.woff2
--------------------------------------------------------------------------------
/public/fonts/Inter/Inter-ExtraBold.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CosmosContracts/junoswap-interface/f3d1567bec5c47dc748a20c2fafd15646d729f7f/public/fonts/Inter/Inter-ExtraBold.woff
--------------------------------------------------------------------------------
/public/fonts/Inter/Inter-ExtraBold.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CosmosContracts/junoswap-interface/f3d1567bec5c47dc748a20c2fafd15646d729f7f/public/fonts/Inter/Inter-ExtraBold.woff2
--------------------------------------------------------------------------------
/public/fonts/Inter/Inter-ExtraBoldItalic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CosmosContracts/junoswap-interface/f3d1567bec5c47dc748a20c2fafd15646d729f7f/public/fonts/Inter/Inter-ExtraBoldItalic.woff
--------------------------------------------------------------------------------
/public/fonts/Inter/Inter-ExtraBoldItalic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CosmosContracts/junoswap-interface/f3d1567bec5c47dc748a20c2fafd15646d729f7f/public/fonts/Inter/Inter-ExtraBoldItalic.woff2
--------------------------------------------------------------------------------
/public/fonts/Inter/Inter-ExtraLight.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CosmosContracts/junoswap-interface/f3d1567bec5c47dc748a20c2fafd15646d729f7f/public/fonts/Inter/Inter-ExtraLight.woff
--------------------------------------------------------------------------------
/public/fonts/Inter/Inter-ExtraLight.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CosmosContracts/junoswap-interface/f3d1567bec5c47dc748a20c2fafd15646d729f7f/public/fonts/Inter/Inter-ExtraLight.woff2
--------------------------------------------------------------------------------
/public/fonts/Inter/Inter-ExtraLightItalic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CosmosContracts/junoswap-interface/f3d1567bec5c47dc748a20c2fafd15646d729f7f/public/fonts/Inter/Inter-ExtraLightItalic.woff
--------------------------------------------------------------------------------
/public/fonts/Inter/Inter-ExtraLightItalic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CosmosContracts/junoswap-interface/f3d1567bec5c47dc748a20c2fafd15646d729f7f/public/fonts/Inter/Inter-ExtraLightItalic.woff2
--------------------------------------------------------------------------------
/public/fonts/Inter/Inter-Italic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CosmosContracts/junoswap-interface/f3d1567bec5c47dc748a20c2fafd15646d729f7f/public/fonts/Inter/Inter-Italic.woff
--------------------------------------------------------------------------------
/public/fonts/Inter/Inter-Italic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CosmosContracts/junoswap-interface/f3d1567bec5c47dc748a20c2fafd15646d729f7f/public/fonts/Inter/Inter-Italic.woff2
--------------------------------------------------------------------------------
/public/fonts/Inter/Inter-Light.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CosmosContracts/junoswap-interface/f3d1567bec5c47dc748a20c2fafd15646d729f7f/public/fonts/Inter/Inter-Light.woff
--------------------------------------------------------------------------------
/public/fonts/Inter/Inter-Light.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CosmosContracts/junoswap-interface/f3d1567bec5c47dc748a20c2fafd15646d729f7f/public/fonts/Inter/Inter-Light.woff2
--------------------------------------------------------------------------------
/public/fonts/Inter/Inter-LightItalic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CosmosContracts/junoswap-interface/f3d1567bec5c47dc748a20c2fafd15646d729f7f/public/fonts/Inter/Inter-LightItalic.woff
--------------------------------------------------------------------------------
/public/fonts/Inter/Inter-LightItalic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CosmosContracts/junoswap-interface/f3d1567bec5c47dc748a20c2fafd15646d729f7f/public/fonts/Inter/Inter-LightItalic.woff2
--------------------------------------------------------------------------------
/public/fonts/Inter/Inter-Medium.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CosmosContracts/junoswap-interface/f3d1567bec5c47dc748a20c2fafd15646d729f7f/public/fonts/Inter/Inter-Medium.woff
--------------------------------------------------------------------------------
/public/fonts/Inter/Inter-Medium.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CosmosContracts/junoswap-interface/f3d1567bec5c47dc748a20c2fafd15646d729f7f/public/fonts/Inter/Inter-Medium.woff2
--------------------------------------------------------------------------------
/public/fonts/Inter/Inter-MediumItalic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CosmosContracts/junoswap-interface/f3d1567bec5c47dc748a20c2fafd15646d729f7f/public/fonts/Inter/Inter-MediumItalic.woff
--------------------------------------------------------------------------------
/public/fonts/Inter/Inter-MediumItalic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CosmosContracts/junoswap-interface/f3d1567bec5c47dc748a20c2fafd15646d729f7f/public/fonts/Inter/Inter-MediumItalic.woff2
--------------------------------------------------------------------------------
/public/fonts/Inter/Inter-Regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CosmosContracts/junoswap-interface/f3d1567bec5c47dc748a20c2fafd15646d729f7f/public/fonts/Inter/Inter-Regular.woff
--------------------------------------------------------------------------------
/public/fonts/Inter/Inter-Regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CosmosContracts/junoswap-interface/f3d1567bec5c47dc748a20c2fafd15646d729f7f/public/fonts/Inter/Inter-Regular.woff2
--------------------------------------------------------------------------------
/public/fonts/Inter/Inter-SemiBold.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CosmosContracts/junoswap-interface/f3d1567bec5c47dc748a20c2fafd15646d729f7f/public/fonts/Inter/Inter-SemiBold.woff
--------------------------------------------------------------------------------
/public/fonts/Inter/Inter-SemiBold.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CosmosContracts/junoswap-interface/f3d1567bec5c47dc748a20c2fafd15646d729f7f/public/fonts/Inter/Inter-SemiBold.woff2
--------------------------------------------------------------------------------
/public/fonts/Inter/Inter-SemiBoldItalic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CosmosContracts/junoswap-interface/f3d1567bec5c47dc748a20c2fafd15646d729f7f/public/fonts/Inter/Inter-SemiBoldItalic.woff
--------------------------------------------------------------------------------
/public/fonts/Inter/Inter-SemiBoldItalic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CosmosContracts/junoswap-interface/f3d1567bec5c47dc748a20c2fafd15646d729f7f/public/fonts/Inter/Inter-SemiBoldItalic.woff2
--------------------------------------------------------------------------------
/public/fonts/Inter/Inter-Thin.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CosmosContracts/junoswap-interface/f3d1567bec5c47dc748a20c2fafd15646d729f7f/public/fonts/Inter/Inter-Thin.woff
--------------------------------------------------------------------------------
/public/fonts/Inter/Inter-Thin.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CosmosContracts/junoswap-interface/f3d1567bec5c47dc748a20c2fafd15646d729f7f/public/fonts/Inter/Inter-Thin.woff2
--------------------------------------------------------------------------------
/public/fonts/Inter/Inter-ThinItalic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CosmosContracts/junoswap-interface/f3d1567bec5c47dc748a20c2fafd15646d729f7f/public/fonts/Inter/Inter-ThinItalic.woff
--------------------------------------------------------------------------------
/public/fonts/Inter/Inter-ThinItalic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CosmosContracts/junoswap-interface/f3d1567bec5c47dc748a20c2fafd15646d729f7f/public/fonts/Inter/Inter-ThinItalic.woff2
--------------------------------------------------------------------------------
/public/fonts/Inter/Inter-italic.var.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CosmosContracts/junoswap-interface/f3d1567bec5c47dc748a20c2fafd15646d729f7f/public/fonts/Inter/Inter-italic.var.woff2
--------------------------------------------------------------------------------
/public/fonts/Inter/Inter-roman.var.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CosmosContracts/junoswap-interface/f3d1567bec5c47dc748a20c2fafd15646d729f7f/public/fonts/Inter/Inter-roman.var.woff2
--------------------------------------------------------------------------------
/public/fonts/JetBrainsMono/JetBrainsMono-Bold.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CosmosContracts/junoswap-interface/f3d1567bec5c47dc748a20c2fafd15646d729f7f/public/fonts/JetBrainsMono/JetBrainsMono-Bold.woff2
--------------------------------------------------------------------------------
/public/fonts/JetBrainsMono/JetBrainsMono-BoldItalic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CosmosContracts/junoswap-interface/f3d1567bec5c47dc748a20c2fafd15646d729f7f/public/fonts/JetBrainsMono/JetBrainsMono-BoldItalic.woff2
--------------------------------------------------------------------------------
/public/fonts/JetBrainsMono/JetBrainsMono-ExtraBold.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CosmosContracts/junoswap-interface/f3d1567bec5c47dc748a20c2fafd15646d729f7f/public/fonts/JetBrainsMono/JetBrainsMono-ExtraBold.woff2
--------------------------------------------------------------------------------
/public/fonts/JetBrainsMono/JetBrainsMono-ExtraBoldItalic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CosmosContracts/junoswap-interface/f3d1567bec5c47dc748a20c2fafd15646d729f7f/public/fonts/JetBrainsMono/JetBrainsMono-ExtraBoldItalic.woff2
--------------------------------------------------------------------------------
/public/fonts/JetBrainsMono/JetBrainsMono-ExtraLight.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CosmosContracts/junoswap-interface/f3d1567bec5c47dc748a20c2fafd15646d729f7f/public/fonts/JetBrainsMono/JetBrainsMono-ExtraLight.woff2
--------------------------------------------------------------------------------
/public/fonts/JetBrainsMono/JetBrainsMono-ExtraLightItalic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CosmosContracts/junoswap-interface/f3d1567bec5c47dc748a20c2fafd15646d729f7f/public/fonts/JetBrainsMono/JetBrainsMono-ExtraLightItalic.woff2
--------------------------------------------------------------------------------
/public/fonts/JetBrainsMono/JetBrainsMono-Italic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CosmosContracts/junoswap-interface/f3d1567bec5c47dc748a20c2fafd15646d729f7f/public/fonts/JetBrainsMono/JetBrainsMono-Italic.woff2
--------------------------------------------------------------------------------
/public/fonts/JetBrainsMono/JetBrainsMono-Light.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CosmosContracts/junoswap-interface/f3d1567bec5c47dc748a20c2fafd15646d729f7f/public/fonts/JetBrainsMono/JetBrainsMono-Light.woff2
--------------------------------------------------------------------------------
/public/fonts/JetBrainsMono/JetBrainsMono-LightItalic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CosmosContracts/junoswap-interface/f3d1567bec5c47dc748a20c2fafd15646d729f7f/public/fonts/JetBrainsMono/JetBrainsMono-LightItalic.woff2
--------------------------------------------------------------------------------
/public/fonts/JetBrainsMono/JetBrainsMono-Medium.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CosmosContracts/junoswap-interface/f3d1567bec5c47dc748a20c2fafd15646d729f7f/public/fonts/JetBrainsMono/JetBrainsMono-Medium.woff2
--------------------------------------------------------------------------------
/public/fonts/JetBrainsMono/JetBrainsMono-MediumItalic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CosmosContracts/junoswap-interface/f3d1567bec5c47dc748a20c2fafd15646d729f7f/public/fonts/JetBrainsMono/JetBrainsMono-MediumItalic.woff2
--------------------------------------------------------------------------------
/public/fonts/JetBrainsMono/JetBrainsMono-Regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CosmosContracts/junoswap-interface/f3d1567bec5c47dc748a20c2fafd15646d729f7f/public/fonts/JetBrainsMono/JetBrainsMono-Regular.woff2
--------------------------------------------------------------------------------
/public/fonts/JetBrainsMono/JetBrainsMono-SemiBold.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CosmosContracts/junoswap-interface/f3d1567bec5c47dc748a20c2fafd15646d729f7f/public/fonts/JetBrainsMono/JetBrainsMono-SemiBold.woff2
--------------------------------------------------------------------------------
/public/fonts/JetBrainsMono/JetBrainsMono-SemiBoldItalic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CosmosContracts/junoswap-interface/f3d1567bec5c47dc748a20c2fafd15646d729f7f/public/fonts/JetBrainsMono/JetBrainsMono-SemiBoldItalic.woff2
--------------------------------------------------------------------------------
/public/fonts/JetBrainsMono/JetBrainsMono-Thin.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CosmosContracts/junoswap-interface/f3d1567bec5c47dc748a20c2fafd15646d729f7f/public/fonts/JetBrainsMono/JetBrainsMono-Thin.woff2
--------------------------------------------------------------------------------
/public/fonts/JetBrainsMono/JetBrainsMono-ThinItalic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CosmosContracts/junoswap-interface/f3d1567bec5c47dc748a20c2fafd15646d729f7f/public/fonts/JetBrainsMono/JetBrainsMono-ThinItalic.woff2
--------------------------------------------------------------------------------
/public/ibc_assets.json:
--------------------------------------------------------------------------------
1 | {
2 | "tokens": [
3 | {
4 | "id": "cosmos",
5 | "name": "Atom",
6 | "symbol": "ATOM",
7 | "chain_id": "cosmoshub-4",
8 | "rpc": "https://cosmoshub.validator.network:443",
9 | "denom": "uatom",
10 | "decimals": 6,
11 | "channel": "channel-207",
12 | "juno_channel": "channel-1",
13 | "juno_denom": "ibc/C4CFF46FD6DE35CA4CF4CE031E643C8FDC9BA4B99AE598E9B0ED98FE3A2319F9",
14 | "logoURI": "https://cryptologos.cc/logos/cosmos-atom-logo.svg?v=014"
15 | },
16 | {
17 | "id": "terrausd",
18 | "name": "UST",
19 | "symbol": "UST",
20 | "chain_id": "terra-1",
21 | "rpc": "https://cosmoshub.validator.network:443",
22 | "denom": "uust",
23 | "decimals": 6,
24 | "channel": "info",
25 | "juno_channel": "channel-7",
26 | "juno_denom": "ibc/test",
27 | "logoURI": "https://cryptologos.cc/logos/usd-coin-usdc-logo.svg?v=014",
28 | "external_deposit_uri": "http://localhost:3000"
29 | }
30 | ]
31 | }
32 |
--------------------------------------------------------------------------------
/public/img/keplr-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CosmosContracts/junoswap-interface/f3d1567bec5c47dc748a20c2fafd15646d729f7f/public/img/keplr-icon.png
--------------------------------------------------------------------------------
/public/junoswap.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CosmosContracts/junoswap-interface/f3d1567bec5c47dc748a20c2fafd15646d729f7f/public/junoswap.png
--------------------------------------------------------------------------------
/public/spinner.svg:
--------------------------------------------------------------------------------
1 |
2 |
7 |
--------------------------------------------------------------------------------
/public/spring-left.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CosmosContracts/junoswap-interface/f3d1567bec5c47dc748a20c2fafd15646d729f7f/public/spring-left.png
--------------------------------------------------------------------------------
/public/spring-right.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CosmosContracts/junoswap-interface/f3d1567bec5c47dc748a20c2fafd15646d729f7f/public/spring-right.png
--------------------------------------------------------------------------------
/public/springs-bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CosmosContracts/junoswap-interface/f3d1567bec5c47dc748a20c2fafd15646d729f7f/public/springs-bg.png
--------------------------------------------------------------------------------
/public/switch.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/queries/queryMyLiquidity.ts:
--------------------------------------------------------------------------------
1 | import { queryLiquidityBalance } from '../services/liquidity'
2 | import { protectAgainstNaN } from '../util/conversion'
3 |
4 | export async function queryMyLiquidity({ swap, address, context: { client } }) {
5 | const providedLiquidityInMicroDenom = address
6 | ? await queryLiquidityBalance({
7 | tokenAddress: swap.lp_token_address,
8 | client,
9 | address,
10 | })
11 | : 0
12 |
13 | /* provide dollar value for reserves as well */
14 | const totalReserve: [number, number] = [
15 | protectAgainstNaN(swap.token1_reserve),
16 | protectAgainstNaN(swap.token2_reserve),
17 | ]
18 |
19 | const providedReserve: [number, number] = [
20 | protectAgainstNaN(
21 | totalReserve[0] * (providedLiquidityInMicroDenom / swap.lp_token_supply)
22 | ),
23 | protectAgainstNaN(
24 | totalReserve[1] * (providedLiquidityInMicroDenom / swap.lp_token_supply)
25 | ),
26 | ]
27 |
28 | return {
29 | totalReserve,
30 | providedReserve,
31 | providedLiquidityInMicroDenom,
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/queries/queryRewardsContracts.ts:
--------------------------------------------------------------------------------
1 | import { getRewardsInfo, RewardsInfoResponse } from '../services/rewards'
2 | import { convertMicroDenomToDenom } from '../util/conversion'
3 | import { InternalQueryContext } from './types'
4 | import { PoolEntityType, TokenInfoWithReward } from './usePoolsListQuery'
5 | import { PoolTokenValue } from './useQueryPools'
6 |
7 | const blocksPerSecond = 6
8 | const blocksPerYear = (525600 * 60) / blocksPerSecond
9 |
10 | export type QueryRewardsContractsArgs = {
11 | swapAddress: PoolEntityType['swap_address']
12 | rewardsTokens: PoolEntityType['rewards_tokens']
13 | context: InternalQueryContext
14 | }
15 |
16 | export type SerializedRewardsContract = {
17 | contract: RewardsInfoResponse
18 | tokenInfo: TokenInfoWithReward
19 | rewardRate: {
20 | ratePerBlock: PoolTokenValue
21 | ratePerYear: PoolTokenValue
22 | }
23 | }
24 |
25 | export async function queryRewardsContracts({
26 | rewardsTokens,
27 | context: { client, getTokenDollarValue },
28 | }: QueryRewardsContractsArgs): Promise> {
29 | const rewardsContractsInfo = await Promise.all(
30 | rewardsTokens.map(({ rewards_address }) =>
31 | getRewardsInfo(rewards_address, client)
32 | )
33 | )
34 |
35 | const currentHeight = await client.getHeight()
36 |
37 | return await Promise.all(
38 | rewardsContractsInfo.map(async (contractInfo, index) => {
39 | const tokenInfo = rewardsTokens[index]
40 | const expired = currentHeight > contractInfo.reward.period_finish
41 |
42 | const rewardRatePerBlockInTokens = expired
43 | ? 0
44 | : convertMicroDenomToDenom(
45 | contractInfo.reward.reward_rate,
46 | tokenInfo.decimals
47 | )
48 |
49 | const rewardRatePerBlockInDollarValue = expired
50 | ? 0
51 | : await getTokenDollarValue({
52 | tokenInfo,
53 | tokenAmountInDenom: rewardRatePerBlockInTokens,
54 | })
55 |
56 | const rewardRate = {
57 | ratePerBlock: {
58 | tokenAmount: rewardRatePerBlockInTokens,
59 | dollarValue: rewardRatePerBlockInDollarValue,
60 | },
61 | ratePerYear: {
62 | tokenAmount: rewardRatePerBlockInTokens * blocksPerYear,
63 | dollarValue: rewardRatePerBlockInDollarValue * blocksPerYear,
64 | },
65 | }
66 |
67 | return {
68 | contract: contractInfo,
69 | rewardRate,
70 | tokenInfo,
71 | }
72 | })
73 | )
74 | }
75 |
--------------------------------------------------------------------------------
/queries/queryStakedLiquidity.ts:
--------------------------------------------------------------------------------
1 | import {
2 | getProvidedStakedAmount,
3 | getTotalStakedAmount,
4 | } from '../services/staking'
5 | import { protectAgainstNaN } from '../util/conversion'
6 |
7 | export async function queryStakedLiquidity({
8 | address,
9 | stakingAddress,
10 | totalReserve,
11 | swap,
12 | context: { client },
13 | }) {
14 | const [providedStakedAmountInMicroDenom, totalStakedAmountInMicroDenom] =
15 | stakingAddress
16 | ? await Promise.all([
17 | address
18 | ? getProvidedStakedAmount(address, stakingAddress, client)
19 | : new Promise((resolve) => resolve(0)),
20 | getTotalStakedAmount(stakingAddress, client),
21 | ])
22 | : [0, 0]
23 |
24 | const totalStakedReserve: [number, number] = [
25 | protectAgainstNaN(
26 | totalReserve[0] * (totalStakedAmountInMicroDenom / swap.lp_token_supply)
27 | ),
28 | protectAgainstNaN(
29 | totalReserve[1] * (totalStakedAmountInMicroDenom / swap.lp_token_supply)
30 | ),
31 | ]
32 |
33 | const providedStakedReserve: [number, number] = [
34 | protectAgainstNaN(
35 | totalReserve[0] *
36 | (providedStakedAmountInMicroDenom / swap.lp_token_supply)
37 | ),
38 | protectAgainstNaN(
39 | totalReserve[1] *
40 | (providedStakedAmountInMicroDenom / swap.lp_token_supply)
41 | ),
42 | ]
43 |
44 | return {
45 | providedStakedAmountInMicroDenom,
46 | totalStakedAmountInMicroDenom,
47 | totalStakedReserve,
48 | providedStakedReserve,
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/queries/querySwapInfo.ts:
--------------------------------------------------------------------------------
1 | import { getSwapInfo } from '../services/swap'
2 |
3 | const safeNum = (attr: string | undefined): number | undefined =>
4 | attr != undefined ? Number(attr) : undefined
5 |
6 | export type SwapInfoResponse = {
7 | swap_address: string
8 | lp_token_supply: number
9 | lp_token_address: string
10 | token1_denom: string
11 | token1_reserve: number
12 | token2_denom: string
13 | token2_reserve: number
14 | owner?: string
15 | lp_fee_percent?: number
16 | protocol_fee_percent?: number
17 | protocol_fee_recipient?: string
18 | }
19 |
20 | export async function querySwapInfo({
21 | context: { client },
22 | swap_address,
23 | }): Promise {
24 | const swap = await getSwapInfo(swap_address, client)
25 |
26 | return {
27 | ...swap,
28 | swap_address,
29 | token1_reserve: Number(swap.token1_reserve),
30 | token2_reserve: Number(swap.token2_reserve),
31 | lp_token_supply: Number(swap.lp_token_supply),
32 | protocol_fee_percent: safeNum(swap.protocol_fee_percent),
33 | lp_fee_percent: safeNum(swap.lp_fee_percent),
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/queries/tokenDollarValueQuery/fetchDollarPriceByTokenIds.ts:
--------------------------------------------------------------------------------
1 | type ApiResponse = Record
2 |
3 | export const fetchDollarPriceByTokenIds = debounce(
4 | async (tokenIds: Array): Promise => {
5 | const apiIds = tokenIds.flat().join(',')
6 |
7 | const response = await fetch(
8 | `https://api.coingecko.com/api/v3/simple/price?ids=${apiIds}&vs_currencies=usd`,
9 | {
10 | method: 'GET',
11 | }
12 | )
13 |
14 | return response.json()
15 | },
16 | 100
17 | )
18 |
19 | function debounce Promise>(
20 | getPromise: T,
21 | timeoutMs: number
22 | ) {
23 | let timeout
24 | let argsState = []
25 | let resolvers = []
26 |
27 | const debouncedPromise = (args: Parameters) => {
28 | argsState.push(args)
29 |
30 | return new Promise((resolve, reject) => {
31 | resolvers.push([resolve, reject])
32 |
33 | clearTimeout(timeout)
34 | timeout = setTimeout(function resolvePromises() {
35 | const promise = getPromise([...argsState])
36 | resolvers.forEach((promiseResolvers) =>
37 | promise.then(...promiseResolvers)
38 | )
39 |
40 | resolvers = []
41 | argsState = []
42 | }, timeoutMs)
43 | })
44 | }
45 |
46 | return debouncedPromise as T
47 | }
48 |
--------------------------------------------------------------------------------
/queries/tokenDollarValueQuery/index.ts:
--------------------------------------------------------------------------------
1 | export * from './tokenDollarValueQuery'
2 |
--------------------------------------------------------------------------------
/queries/tokenDollarValueQuery/pricingServiceIsDownAlert.tsx:
--------------------------------------------------------------------------------
1 | import { ErrorIcon, Toast } from 'junoblocks'
2 | import React from 'react'
3 | import { toast } from 'react-hot-toast'
4 |
5 | function createAlertPricingServiceIsDown() {
6 | let hasRenderedAlert
7 | let timeout
8 |
9 | function renderAlert() {
10 | toast.custom((t) => (
11 | }
13 | title="Oops, sorry! Our pricing service is temporarily down"
14 | onClose={() => toast.dismiss(t.id)}
15 | />
16 | ))
17 | }
18 |
19 | return () => {
20 | if (hasRenderedAlert) {
21 | clearTimeout(timeout)
22 | timeout = setTimeout(renderAlert, 60 * 1000)
23 | return
24 | }
25 |
26 | hasRenderedAlert = true
27 | renderAlert()
28 | }
29 | }
30 |
31 | export const pricingServiceIsDownAlert = createAlertPricingServiceIsDown()
32 |
--------------------------------------------------------------------------------
/queries/tokenDollarValueQuery/tokenDollarValueQuery.tsx:
--------------------------------------------------------------------------------
1 | import { TokenInfo } from 'queries/usePoolsListQuery'
2 |
3 | import { fetchDollarPriceByTokenIds } from './fetchDollarPriceByTokenIds'
4 | import { pricingServiceIsDownAlert } from './pricingServiceIsDownAlert'
5 |
6 | export async function tokenDollarValueQuery(tokenIds: Array) {
7 | if (!tokenIds?.length) {
8 | throw new Error('Provide token ids in order to query their price')
9 | }
10 |
11 | try {
12 | const prices = await fetchDollarPriceByTokenIds(tokenIds)
13 | return tokenIds.map((id): number => prices[id]?.usd || 0)
14 | } catch (e) {
15 | pricingServiceIsDownAlert()
16 |
17 | throw e
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/queries/tokenToTokenPriceQuery.ts:
--------------------------------------------------------------------------------
1 | import { CosmWasmClient } from '@cosmjs/cosmwasm-stargate'
2 |
3 | import {
4 | getToken1ForToken2Price,
5 | getToken2ForToken1Price,
6 | getTokenForTokenPrice,
7 | } from '../services/swap'
8 | import {
9 | convertDenomToMicroDenom,
10 | convertMicroDenomToDenom,
11 | } from '../util/conversion'
12 | import { TokenInfo } from './usePoolsListQuery'
13 | import { MatchingPoolsForTokenToTokenSwap } from './useQueryMatchingPoolForSwap'
14 |
15 | type TokenToTokenPriceQueryArgs = {
16 | matchingPools: MatchingPoolsForTokenToTokenSwap
17 | tokenA: TokenInfo
18 | tokenB: TokenInfo
19 | amount: number
20 | client: CosmWasmClient
21 | }
22 |
23 | type TokenToTokenPriceQueryWithPoolsReturns = {
24 | price: number
25 | } & MatchingPoolsForTokenToTokenSwap
26 |
27 | export async function tokenToTokenPriceQueryWithPools({
28 | matchingPools,
29 | tokenA,
30 | tokenB,
31 | amount,
32 | client,
33 | }: TokenToTokenPriceQueryArgs): Promise {
34 | if (tokenA.symbol === tokenB.symbol) {
35 | return { price: 1 }
36 | }
37 |
38 | const formatPrice = (price) =>
39 | convertMicroDenomToDenom(price, tokenB.decimals)
40 |
41 | const convertedTokenAmount = convertDenomToMicroDenom(amount, tokenA.decimals)
42 |
43 | const {
44 | poolForDirectTokenAToTokenBSwap,
45 | poolForDirectTokenBToTokenASwap,
46 | passThroughPools,
47 | } = matchingPools
48 |
49 | const pricingQueries: Array> =
50 | []
51 |
52 | if (poolForDirectTokenAToTokenBSwap) {
53 | pricingQueries.push(
54 | getToken1ForToken2Price({
55 | nativeAmount: convertedTokenAmount,
56 | swapAddress: poolForDirectTokenAToTokenBSwap.swap_address,
57 | client,
58 | }).then((price) => ({
59 | price: formatPrice(price),
60 | poolForDirectTokenAToTokenBSwap,
61 | }))
62 | )
63 | }
64 |
65 | if (poolForDirectTokenBToTokenASwap) {
66 | pricingQueries.push(
67 | getToken2ForToken1Price({
68 | tokenAmount: convertedTokenAmount,
69 | swapAddress: poolForDirectTokenBToTokenASwap.swap_address,
70 | client,
71 | }).then((price) => ({
72 | price: formatPrice(price),
73 | poolForDirectTokenBToTokenASwap,
74 | }))
75 | )
76 | }
77 |
78 | if (passThroughPools?.length) {
79 | passThroughPools.forEach((passThroughPool) => {
80 | pricingQueries.push(
81 | getTokenForTokenPrice({
82 | tokenAmount: convertedTokenAmount,
83 | tokenA,
84 | tokenB,
85 | inputPool: passThroughPool.inputPool,
86 | outputPool: passThroughPool.outputPool,
87 | client,
88 | }).then((price) => ({
89 | price: formatPrice(price),
90 | passThroughPools: [passThroughPool],
91 | }))
92 | )
93 | })
94 | }
95 |
96 | const prices: Array =
97 | await Promise.all(pricingQueries)
98 |
99 | /*
100 | * pick the best price among all the available swap routes.
101 | * the best price is the highest one.
102 | * */
103 | return prices.reduce((result, tokenPrice) => {
104 | return result?.price < tokenPrice.price ? tokenPrice : result
105 | }, prices[0])
106 | }
107 |
--------------------------------------------------------------------------------
/queries/types.ts:
--------------------------------------------------------------------------------
1 | import { CosmWasmClient } from '@cosmjs/cosmwasm-stargate'
2 |
3 | import { useGetTokenDollarValueQuery } from './useGetTokenDollarValueQuery'
4 |
5 | export type InternalQueryContext = {
6 | client: CosmWasmClient
7 | getTokenDollarValue: ReturnType[0]
8 | }
9 |
--------------------------------------------------------------------------------
/queries/useGetTokenDollarValueQuery.ts:
--------------------------------------------------------------------------------
1 | import { protectAgainstNaN } from 'junoblocks'
2 |
3 | import { useCosmWasmClient } from '../hooks/useCosmWasmClient'
4 | import { useTokenDollarValue } from '../hooks/useTokenDollarValue'
5 | import { useBaseTokenInfo } from '../hooks/useTokenInfo'
6 | import { tokenToTokenPriceQueryWithPools } from './tokenToTokenPriceQuery'
7 | import { useGetQueryMatchingPoolForSwap } from './useQueryMatchingPoolForSwap'
8 |
9 | /*
10 | * takes base token price, fetches the ratio of the token provided vs the base token
11 | * and calculates the dollar value of the provided token
12 | * */
13 | export const useGetTokenDollarValueQuery = () => {
14 | const tokenA = useBaseTokenInfo()
15 | const client = useCosmWasmClient()
16 | const [tokenADollarPrice, fetchingDollarPrice] = useTokenDollarValue(
17 | tokenA?.symbol
18 | )
19 |
20 | const [getMatchingPoolForSwap, isLoadingPoolForSwapMatcher] =
21 | useGetQueryMatchingPoolForSwap()
22 |
23 | return [
24 | async function getTokenDollarValue({ tokenInfo, tokenAmountInDenom }) {
25 | if (!tokenAmountInDenom) return 0
26 |
27 | const priceForOneToken = (
28 | await tokenToTokenPriceQueryWithPools({
29 | matchingPools: getMatchingPoolForSwap({ tokenA, tokenB: tokenInfo }),
30 | tokenA,
31 | tokenB: tokenInfo,
32 | client,
33 | amount: 1,
34 | })
35 | )?.price
36 |
37 | return protectAgainstNaN(
38 | (tokenAmountInDenom / priceForOneToken) * tokenADollarPrice
39 | )
40 | },
41 | Boolean(
42 | tokenA && client && !fetchingDollarPrice && !isLoadingPoolForSwapMatcher
43 | ),
44 | ] as const
45 | }
46 |
--------------------------------------------------------------------------------
/queries/usePoolsListQuery.ts:
--------------------------------------------------------------------------------
1 | import { useQuery } from 'react-query'
2 |
3 | export type TokenInfo = {
4 | id: string
5 | chain_id: string
6 | token_address: string
7 | symbol: string
8 | name: string
9 | decimals: number
10 | logoURI: string
11 | tags: string[]
12 | denom: string
13 | native: boolean
14 | }
15 |
16 | export type TokenInfoWithReward = TokenInfo & {
17 | rewards_address: string
18 | }
19 |
20 | export type PoolEntityType = {
21 | pool_id: string
22 | pool_assets: [TokenInfo, TokenInfo]
23 | swap_address: string
24 | staking_address: string
25 | rewards_tokens: Array
26 | }
27 |
28 | type PoolsListQueryResponse = {
29 | base_token: TokenInfo
30 | pools: Array
31 | poolsById: Record
32 | name: string
33 | logoURI: string
34 | keywords: Array
35 | tags: Record
36 | }
37 |
38 | export const usePoolsListQuery = (options?: Parameters[1]) => {
39 | return useQuery(
40 | '@pools-list',
41 | async () => {
42 | const response = await fetch(process.env.NEXT_PUBLIC_POOLS_LIST_URL)
43 | const tokenList = await response.json()
44 |
45 | return {
46 | ...tokenList,
47 | poolsById: tokenList.pools.reduce(
48 | (poolsById, pool) => ((poolsById[pool.pool_id] = pool), poolsById),
49 | {}
50 | ),
51 | }
52 | },
53 | Object.assign(
54 | {
55 | refetchOnMount: false,
56 | },
57 | options || {}
58 | )
59 | )
60 | }
61 |
62 | export const usePoolFromListQueryById = ({ poolId }: { poolId: string }) => {
63 | const { data: poolListResponse, isLoading } = usePoolsListQuery()
64 | return [poolListResponse?.poolsById[poolId], isLoading] as const
65 | }
66 |
--------------------------------------------------------------------------------
/queries/useQueryMatchingPoolForSwap/index.ts:
--------------------------------------------------------------------------------
1 | export * from './types'
2 | export * from './useQueryMatchingPoolsForSwap'
3 |
--------------------------------------------------------------------------------
/queries/useQueryMatchingPoolForSwap/types.ts:
--------------------------------------------------------------------------------
1 | import { PoolEntityType } from '../usePoolsListQuery'
2 |
3 | export type PassThroughPoolsForTokenToTokenSwap = {
4 | inputPool: PoolEntityType
5 | outputPool: PoolEntityType
6 | }
7 |
8 | export type MatchingPoolsForTokenToTokenSwap = {
9 | poolForDirectTokenAToTokenBSwap?: PoolEntityType
10 | poolForDirectTokenBToTokenASwap?: PoolEntityType
11 | passThroughPools?: Array
12 | }
13 |
--------------------------------------------------------------------------------
/queries/useQueryMatchingPoolForSwap/useQueryMatchingPoolsForSwap.ts:
--------------------------------------------------------------------------------
1 | import { useCallback, useMemo } from 'react'
2 |
3 | import { TokenInfo, usePoolsListQuery } from '../usePoolsListQuery'
4 | import { selectEligiblePoolsForTokenToTokenSwap } from './util/selectEligiblePoolsForTokenToTokenSwap'
5 |
6 | type GetMatchingPoolArgs = {
7 | tokenA: TokenInfo
8 | tokenB: TokenInfo
9 | }
10 |
11 | export const useGetQueryMatchingPoolForSwap = () => {
12 | const { data: poolsListResponse, isLoading } = usePoolsListQuery()
13 |
14 | const getMatchingPool = useCallback(
15 | ({ tokenA, tokenB }: GetMatchingPoolArgs) => {
16 | if (!poolsListResponse?.pools || !tokenA || !tokenB) return undefined
17 |
18 | return selectEligiblePoolsForTokenToTokenSwap({
19 | tokenA,
20 | tokenB,
21 | poolsList: poolsListResponse.pools,
22 | })
23 | },
24 | [poolsListResponse]
25 | )
26 |
27 | return [getMatchingPool, isLoading] as const
28 | }
29 |
30 | export const useQueryMatchingPoolsForSwap = ({
31 | tokenA,
32 | tokenB,
33 | }: GetMatchingPoolArgs) => {
34 | const [getMatchingPoolsForSwap, isLoading] = useGetQueryMatchingPoolForSwap()
35 |
36 | return useMemo(() => {
37 | return [
38 | getMatchingPoolsForSwap({
39 | tokenA,
40 | tokenB,
41 | }),
42 | isLoading,
43 | ] as const
44 | }, [getMatchingPoolsForSwap, isLoading, tokenA, tokenB])
45 | }
46 |
--------------------------------------------------------------------------------
/queries/useQueryMatchingPoolForSwap/util/selectEligiblePoolsForTokenToTokenSwap.spec.ts:
--------------------------------------------------------------------------------
1 | import {
2 | BASE_TOKEN_CRAB_POOL,
3 | BASE_TOKEN_DAO_POOL,
4 | CRAB_DAO_POOL,
5 | CRAB_TOKEN,
6 | DAO_TOKEN,
7 | } from '../../../util/testutils'
8 | import { selectEligiblePoolsForTokenToTokenSwap } from './selectEligiblePoolsForTokenToTokenSwap'
9 |
10 | describe('selectEligiblePoolsForTokenToTokenSwap', () => {
11 | const poolsList = [CRAB_DAO_POOL, BASE_TOKEN_CRAB_POOL, BASE_TOKEN_DAO_POOL]
12 |
13 | it('selects token A to token B pool for swap', () => {
14 | const { poolForDirectTokenAToTokenBSwap } =
15 | selectEligiblePoolsForTokenToTokenSwap({
16 | tokenA: CRAB_TOKEN,
17 | tokenB: DAO_TOKEN,
18 | poolsList,
19 | })
20 | expect(poolForDirectTokenAToTokenBSwap).toBe(CRAB_DAO_POOL)
21 | })
22 |
23 | it('selects token B to token A pool for swap', () => {
24 | const { poolForDirectTokenBToTokenASwap } =
25 | selectEligiblePoolsForTokenToTokenSwap({
26 | tokenA: DAO_TOKEN,
27 | tokenB: CRAB_TOKEN,
28 | poolsList,
29 | })
30 | expect(poolForDirectTokenBToTokenASwap).toBe(CRAB_DAO_POOL)
31 | })
32 |
33 | it('selects pass through pools for swap', () => {
34 | const { passThroughPools } = selectEligiblePoolsForTokenToTokenSwap({
35 | tokenA: CRAB_TOKEN,
36 | tokenB: DAO_TOKEN,
37 | poolsList,
38 | })
39 |
40 | expect(passThroughPools).toEqual([
41 | { inputPool: BASE_TOKEN_CRAB_POOL, outputPool: BASE_TOKEN_DAO_POOL },
42 | ])
43 | })
44 | })
45 |
--------------------------------------------------------------------------------
/queries/useQueryMatchingPoolForSwap/util/selectEligiblePoolsForTokenToTokenSwap.ts:
--------------------------------------------------------------------------------
1 | import { PoolEntityType, TokenInfo } from '../../usePoolsListQuery'
2 | import { MatchingPoolsForTokenToTokenSwap } from '../types'
3 | import { validateIfPassThroughPoolMatchIsUnique } from './validateIfPassThroughPoolMatchIsUnique'
4 |
5 | type SelectEligiblePoolsForTokenToTokenSwapArgs = {
6 | tokenA: TokenInfo
7 | tokenB: TokenInfo
8 | poolsList: Array
9 | }
10 |
11 | /* find all the eligible direct and indirect swap pools */
12 | export function selectEligiblePoolsForTokenToTokenSwap({
13 | tokenA,
14 | tokenB,
15 | poolsList,
16 | }: SelectEligiblePoolsForTokenToTokenSwapArgs) {
17 | let eligiblePools = poolsList.reduce(
18 | (result, pool) => {
19 | const [poolAssetA, poolAssetB] = pool.pool_assets
20 |
21 | /*
22 | * validate if this pool can be used for
23 | * direct token a token b pool pair match
24 | * */
25 | const eligibleForDirectTokenAToTokenBSwap =
26 | poolAssetA.symbol === tokenA.symbol &&
27 | poolAssetB.symbol === tokenB.symbol
28 |
29 | const eligibleForDirectTokenBToTokenASwap =
30 | poolAssetA.symbol === tokenB.symbol &&
31 | poolAssetB.symbol === tokenA.symbol
32 |
33 | if (eligibleForDirectTokenAToTokenBSwap) {
34 | result.poolForDirectTokenAToTokenBSwap = pool
35 | return result
36 | }
37 |
38 | if (eligibleForDirectTokenBToTokenASwap) {
39 | result.poolForDirectTokenBToTokenASwap = pool
40 | return result
41 | }
42 |
43 | /*
44 | * validate if the pool can be used as a pass through
45 | * token a token b pair match
46 | * */
47 | const eligibleAsPassThroughInputPool =
48 | tokenA.symbol === poolAssetA.symbol ||
49 | tokenA.symbol === poolAssetB.symbol
50 |
51 | if (eligibleAsPassThroughInputPool) {
52 | const intermediaryToken =
53 | tokenA.symbol === poolAssetA.symbol ? poolAssetB : poolAssetA
54 |
55 | const passThroughSwapOutputPool = poolsList.find(
56 | ({ pool_assets: [assetA, assetB] }) => {
57 | return (
58 | (intermediaryToken.symbol === assetA.symbol &&
59 | tokenB.symbol === assetB.symbol) ||
60 | (tokenB.symbol === assetA.symbol &&
61 | intermediaryToken.symbol === assetB.symbol)
62 | )
63 | }
64 | )
65 |
66 | const passThroughPoolPair = {
67 | inputPool: pool,
68 | outputPool: passThroughSwapOutputPool,
69 | }
70 |
71 | const hasEligiblePassThroughPoolPair =
72 | passThroughSwapOutputPool &&
73 | validateIfPassThroughPoolMatchIsUnique(
74 | result.passThroughPools,
75 | passThroughPoolPair
76 | )
77 |
78 | if (hasEligiblePassThroughPoolPair) {
79 | result.passThroughPools.push(passThroughPoolPair)
80 | }
81 | }
82 |
83 | return result
84 | },
85 | {
86 | poolForDirectTokenAToTokenBSwap: null,
87 | poolForDirectTokenBToTokenASwap: null,
88 | passThroughPools: [],
89 | } as MatchingPoolsForTokenToTokenSwap
90 | )
91 | if (
92 | eligiblePools.poolForDirectTokenAToTokenBSwap ||
93 | eligiblePools.poolForDirectTokenBToTokenASwap
94 | ) {
95 | eligiblePools.passThroughPools = []
96 | }
97 | console.log(eligiblePools)
98 | return eligiblePools
99 | }
100 |
--------------------------------------------------------------------------------
/queries/useQueryMatchingPoolForSwap/util/validateIfPassThroughPoolMatchIsUnique.ts:
--------------------------------------------------------------------------------
1 | import { PassThroughPoolsForTokenToTokenSwap } from '../types'
2 |
3 | export function validateIfPassThroughPoolMatchIsUnique(
4 | passThroughPools: Array,
5 | passThroughPoolMatch: PassThroughPoolsForTokenToTokenSwap
6 | ) {
7 | const hasPassThroughPoolMatch = passThroughPools.find((pool) => {
8 | return (
9 | pool.inputPool.pool_id === passThroughPoolMatch.inputPool.pool_id &&
10 | pool.outputPool.pool_id === passThroughPoolMatch.outputPool.pool_id
11 | )
12 | })
13 |
14 | return !hasPassThroughPoolMatch
15 | }
16 |
--------------------------------------------------------------------------------
/queries/useQueryPoolUnstakingDuration.ts:
--------------------------------------------------------------------------------
1 | import { useQuery } from 'react-query'
2 |
3 | import { useCosmWasmClient } from '../hooks/useCosmWasmClient'
4 | import { getUnstakingDuration } from '../services/staking'
5 | import { PoolEntityType, usePoolFromListQueryById } from './usePoolsListQuery'
6 |
7 | type UseQueryPoolUnstakingDurationArgs = {
8 | poolId: PoolEntityType['pool_id']
9 | }
10 |
11 | export const useQueryPoolUnstakingDuration = ({
12 | poolId,
13 | }: UseQueryPoolUnstakingDurationArgs) => {
14 | const [pool] = usePoolFromListQueryById({ poolId })
15 | const client = useCosmWasmClient()
16 | return useQuery(
17 | `@unstaking-duration/${poolId}`,
18 | async () => {
19 | const seconds = await getUnstakingDuration(pool.staking_address, client)
20 | const minutes = seconds / 60
21 | const hours = minutes / 60
22 | const days = Math.round(hours / 24)
23 | return { seconds, minutes, hours, days }
24 | },
25 | {
26 | enabled: Boolean(pool?.staking_address && client),
27 | refetchOnMount: false,
28 | }
29 | )
30 | }
31 |
--------------------------------------------------------------------------------
/services/liquidity/executeAddLiquidity.ts:
--------------------------------------------------------------------------------
1 | import {
2 | MsgExecuteContractEncodeObject,
3 | SigningCosmWasmClient,
4 | } from '@cosmjs/cosmwasm-stargate'
5 | import { coin } from '@cosmjs/stargate'
6 |
7 | import { TokenInfo } from '../../queries/usePoolsListQuery'
8 | import {
9 | createExecuteMessage,
10 | createIncreaseAllowanceMessage,
11 | validateTransactionSuccess,
12 | } from '../../util/messages'
13 |
14 | type ExecuteAddLiquidityArgs = {
15 | tokenA: TokenInfo
16 | tokenB: TokenInfo
17 | tokenAAmount: number
18 | /*
19 | * The contract calculates `tokenBAmount` automatically.
20 | * However, the user needs to set max amount of `tokenB` they're willing to spend.
21 | * If the calculated amount exceeds the max amount, the transaction then fails.
22 | */
23 | maxTokenBAmount: number
24 | senderAddress: string
25 | swapAddress: string
26 | client: SigningCosmWasmClient
27 | }
28 |
29 | export const executeAddLiquidity = async ({
30 | tokenA,
31 | tokenB,
32 | tokenAAmount,
33 | maxTokenBAmount,
34 | client,
35 | swapAddress,
36 | senderAddress,
37 | }: ExecuteAddLiquidityArgs): Promise => {
38 | const addLiquidityMessage = {
39 | add_liquidity: {
40 | token1_amount: `${tokenAAmount}`,
41 | max_token2: `${maxTokenBAmount}`,
42 | min_liquidity: `${0}`,
43 | },
44 | }
45 |
46 | if (!tokenA.native || !tokenB.native) {
47 | const increaseAllowanceMessages: Array = []
48 |
49 | /* increase allowance for each non-native token */
50 | if (!tokenA.native) {
51 | increaseAllowanceMessages.push(
52 | createIncreaseAllowanceMessage({
53 | tokenAmount: tokenAAmount,
54 | tokenAddress: tokenA.token_address,
55 | senderAddress,
56 | swapAddress,
57 | })
58 | )
59 | }
60 | if (!tokenB.native) {
61 | increaseAllowanceMessages.push(
62 | createIncreaseAllowanceMessage({
63 | tokenAmount: maxTokenBAmount,
64 | tokenAddress: tokenB.token_address,
65 | senderAddress,
66 | swapAddress,
67 | })
68 | )
69 | }
70 |
71 | const executeAddLiquidityMessage = createExecuteMessage({
72 | message: addLiquidityMessage,
73 | senderAddress,
74 | contractAddress: swapAddress,
75 | /* each native token needs to be added to the funds */
76 | funds: [
77 | tokenA.native && coin(tokenAAmount, tokenA.denom),
78 | tokenB.native && coin(maxTokenBAmount, tokenB.denom),
79 | ].filter(Boolean),
80 | })
81 |
82 | return validateTransactionSuccess(
83 | await client.signAndBroadcast(
84 | senderAddress,
85 | [...increaseAllowanceMessages, executeAddLiquidityMessage],
86 | 'auto'
87 | )
88 | )
89 | }
90 |
91 | const funds = [
92 | coin(tokenAAmount, tokenA.denom),
93 | coin(maxTokenBAmount, tokenB.denom),
94 | ].sort((a, b) => (a.denom > b.denom ? 1 : -1))
95 |
96 | return await client.execute(
97 | senderAddress,
98 | swapAddress,
99 | addLiquidityMessage,
100 | 'auto',
101 | undefined,
102 | funds
103 | )
104 | }
105 |
--------------------------------------------------------------------------------
/services/liquidity/executeRemoveLiquidity.ts:
--------------------------------------------------------------------------------
1 | import { SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate'
2 | import {
3 | createExecuteMessage,
4 | createIncreaseAllowanceMessage,
5 | validateTransactionSuccess,
6 | } from 'util/messages'
7 |
8 | type ExecuteRemoveLiquidityArgs = {
9 | tokenAmount: number
10 | senderAddress: string
11 | swapAddress: string
12 | lpTokenAddress: string
13 | client: SigningCosmWasmClient
14 | }
15 |
16 | export const executeRemoveLiquidity = async ({
17 | tokenAmount,
18 | swapAddress,
19 | senderAddress,
20 | lpTokenAddress,
21 | client,
22 | }: ExecuteRemoveLiquidityArgs) => {
23 | const increaseAllowanceMessage = createIncreaseAllowanceMessage({
24 | tokenAmount,
25 | senderAddress,
26 | tokenAddress: lpTokenAddress,
27 | swapAddress,
28 | })
29 |
30 | const executeMessage = createExecuteMessage({
31 | senderAddress,
32 | contractAddress: swapAddress,
33 | message: {
34 | remove_liquidity: {
35 | amount: `${tokenAmount}`,
36 | min_token1: `${0}`,
37 | min_token2: `${0}`,
38 | },
39 | },
40 | })
41 |
42 | return validateTransactionSuccess(
43 | await client.signAndBroadcast(
44 | senderAddress,
45 | [increaseAllowanceMessage, executeMessage],
46 | 'auto'
47 | )
48 | )
49 | }
50 |
--------------------------------------------------------------------------------
/services/liquidity/index.ts:
--------------------------------------------------------------------------------
1 | export * from './executeAddLiquidity'
2 | export * from './executeRemoveLiquidity'
3 | export * from './queryLiquidityBalance'
4 |
--------------------------------------------------------------------------------
/services/liquidity/queryLiquidityBalance.ts:
--------------------------------------------------------------------------------
1 | import { CosmWasmClient } from '@cosmjs/cosmwasm-stargate'
2 | import { protectAgainstNaN } from 'junoblocks'
3 |
4 | type QueryLiquidityBalanceArgs = {
5 | address: string
6 | tokenAddress: string
7 | client: CosmWasmClient
8 | }
9 |
10 | export const queryLiquidityBalance = async ({
11 | client,
12 | tokenAddress,
13 | address,
14 | }: QueryLiquidityBalanceArgs) => {
15 | try {
16 | const query = await client.queryContractSmart(tokenAddress, {
17 | balance: { address },
18 | })
19 |
20 | return protectAgainstNaN(Number(query.balance))
21 | } catch (e) {
22 | console.error('Cannot get liquidity balance:', e)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/services/queryClient.ts:
--------------------------------------------------------------------------------
1 | import { QueryClient } from 'react-query'
2 | import { DEFAULT_REFETCH_ON_WINDOW_FOCUS_STALE_TIME } from '../util/constants'
3 |
4 | export const queryClient = new QueryClient({
5 | defaultOptions: {
6 | queries: {
7 | refetchOnWindowFocus: true,
8 | staleTime: DEFAULT_REFETCH_ON_WINDOW_FOCUS_STALE_TIME,
9 | },
10 | },
11 | })
12 |
--------------------------------------------------------------------------------
/services/rewards.ts:
--------------------------------------------------------------------------------
1 | import {
2 | CosmWasmClient,
3 | SigningCosmWasmClient,
4 | } from '@cosmjs/cosmwasm-stargate'
5 |
6 | import {
7 | createExecuteMessage,
8 | validateTransactionSuccess,
9 | } from '../util/messages'
10 |
11 | type Denom =
12 | | {
13 | native: string
14 | }
15 | | {
16 | /* cw20 token_address */
17 | cw20: string
18 | }
19 |
20 | export const claimRewards = async (
21 | senderAddress: string,
22 | rewardsAddresses: Array,
23 | client: SigningCosmWasmClient
24 | ) => {
25 | const claimRewardsMsg = { claim: {} }
26 |
27 | const messages = rewardsAddresses.map((rewardsAddress) =>
28 | createExecuteMessage({
29 | senderAddress,
30 | contractAddress: rewardsAddress,
31 | message: claimRewardsMsg,
32 | })
33 | )
34 |
35 | return validateTransactionSuccess(
36 | await client.signAndBroadcast(senderAddress, messages, 'auto')
37 | )
38 | }
39 |
40 | type PendingRewardsResponse = {
41 | address: string
42 | pending_rewards: number
43 | denom: Denom
44 | }
45 |
46 | export const getPendingRewards = async (
47 | address: string,
48 | rewardsAddress: string,
49 | client: CosmWasmClient
50 | ): Promise => {
51 | const msg = { get_pending_rewards: { address } }
52 | return await client.queryContractSmart(rewardsAddress, msg)
53 | }
54 |
55 | export type RewardsInfoResponse = {
56 | config: {
57 | owner?: string
58 | manager?: string
59 | staking_contract: string
60 | reward_token: Denom
61 | }
62 | reward: {
63 | period_finish: number
64 | reward_rate: number
65 | reward_duration: number
66 | }
67 | }
68 |
69 | export const getRewardsInfo = async (
70 | rewardsAddress: string,
71 | client: CosmWasmClient
72 | ): Promise => {
73 | const msg = { info: {} }
74 | return await client.queryContractSmart(rewardsAddress, msg)
75 | }
76 |
--------------------------------------------------------------------------------
/services/staking.ts:
--------------------------------------------------------------------------------
1 | import {
2 | CosmWasmClient,
3 | SigningCosmWasmClient,
4 | } from '@cosmjs/cosmwasm-stargate'
5 | import { toBase64, toUtf8 } from '@cosmjs/encoding'
6 |
7 | export const stakeTokens = async (
8 | senderAddress: string,
9 | stakeContractAddress: string,
10 | lpTokenAddress: string,
11 | amount: number,
12 | client: SigningCosmWasmClient
13 | ) => {
14 | const subMsg = { stake: {} }
15 | const encodedMsg = toBase64(toUtf8(JSON.stringify(subMsg)))
16 | const msg = {
17 | send: {
18 | contract: stakeContractAddress,
19 | amount: String(amount),
20 | msg: encodedMsg,
21 | },
22 | }
23 |
24 | return await client.execute(
25 | senderAddress,
26 | lpTokenAddress,
27 | msg,
28 | 'auto',
29 | undefined,
30 | []
31 | )
32 | }
33 |
34 | export const unstakeTokens = async (
35 | senderAddress: string,
36 | stakingContractAddress: string,
37 | amount: number,
38 | client: SigningCosmWasmClient
39 | ) => {
40 | amount = Math.floor(amount)
41 | const msg = { unstake: { amount: amount.toString() } }
42 |
43 | return await client.execute(
44 | senderAddress,
45 | stakingContractAddress,
46 | msg,
47 | 'auto',
48 | undefined,
49 | []
50 | )
51 | }
52 |
53 | export const claimTokens = async (
54 | senderAddress: string,
55 | stakingContractAddress: string,
56 | client: SigningCosmWasmClient
57 | ) => {
58 | const msg = { claim: {} }
59 | return await client.execute(
60 | senderAddress,
61 | stakingContractAddress,
62 | msg,
63 | 'auto',
64 | undefined,
65 | []
66 | )
67 | }
68 |
69 | export const getProvidedStakedAmount = async (
70 | address: string,
71 | stakingContractAddress: string,
72 | client: CosmWasmClient
73 | ) => {
74 | const msg = { staked_value: { address: address } }
75 | const result = await client.queryContractSmart(stakingContractAddress, msg)
76 | return Number(result.value)
77 | }
78 |
79 | export const getTotalStakedAmount = async (
80 | stakingContractAddress: string,
81 | client: CosmWasmClient
82 | ) => {
83 | const msg = { total_value: {} }
84 | const result = await client.queryContractSmart(stakingContractAddress, msg)
85 | return Number(result.total)
86 | }
87 |
88 | export type Claim = {
89 | amount: number
90 | release_at: number
91 | }
92 |
93 | export const getClaims = async (
94 | address: string,
95 | stakingContractAddress: string,
96 | client: CosmWasmClient
97 | ): Promise> => {
98 | const msg = { claims: { address: address } }
99 | const resp = await client.queryContractSmart(stakingContractAddress, msg)
100 |
101 | return resp.claims.map((c) => {
102 | return {
103 | amount: Number(c.amount),
104 | release_at: Number(c.release_at.at_time) / 1000000,
105 | }
106 | })
107 | }
108 |
109 | export const getUnstakingDuration = async (
110 | stakingContractAddress: string,
111 | client: CosmWasmClient
112 | ): Promise => {
113 | const msg = { get_config: {} }
114 | const result = await client.queryContractSmart(stakingContractAddress, msg)
115 | return result.unstaking_duration.time
116 | }
117 |
--------------------------------------------------------------------------------
/services/swap/directTokenSwap.ts:
--------------------------------------------------------------------------------
1 | import { SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate'
2 | import { coin } from '@cosmjs/stargate'
3 |
4 | import { TokenInfo } from '../../queries/usePoolsListQuery'
5 | import {
6 | createExecuteMessage,
7 | createIncreaseAllowanceMessage,
8 | validateTransactionSuccess,
9 | } from '../../util/messages'
10 |
11 | type DirectTokenSwapArgs = {
12 | swapDirection: 'tokenAtoTokenB' | 'tokenBtoTokenA'
13 | tokenAmount: number
14 | price: number
15 | slippage: number
16 | senderAddress: string
17 | swapAddress: string
18 | tokenA: TokenInfo
19 | client: SigningCosmWasmClient
20 | }
21 |
22 | export const directTokenSwap = async ({
23 | tokenA,
24 | swapDirection,
25 | swapAddress,
26 | senderAddress,
27 | slippage,
28 | price,
29 | tokenAmount,
30 | client,
31 | }: DirectTokenSwapArgs) => {
32 | const minToken = Math.floor(price * (1 - slippage))
33 |
34 | const swapMessage = {
35 | swap: {
36 | input_token: swapDirection === 'tokenAtoTokenB' ? 'Token1' : 'Token2',
37 | input_amount: `${tokenAmount}`,
38 | min_output: `${minToken}`,
39 | },
40 | }
41 |
42 | if (!tokenA.native) {
43 | const increaseAllowanceMessage = createIncreaseAllowanceMessage({
44 | senderAddress,
45 | tokenAmount,
46 | tokenAddress: tokenA.token_address,
47 | swapAddress,
48 | })
49 |
50 | const executeMessage = createExecuteMessage({
51 | senderAddress,
52 | contractAddress: swapAddress,
53 | message: swapMessage,
54 | })
55 |
56 | return validateTransactionSuccess(
57 | await client.signAndBroadcast(
58 | senderAddress,
59 | [increaseAllowanceMessage, executeMessage],
60 | 'auto'
61 | )
62 | )
63 | }
64 |
65 | return await client.execute(
66 | senderAddress,
67 | swapAddress,
68 | swapMessage,
69 | 'auto',
70 | undefined,
71 | [coin(tokenAmount, tokenA.denom)]
72 | )
73 | }
74 |
--------------------------------------------------------------------------------
/services/swap/index.ts:
--------------------------------------------------------------------------------
1 | export * from './directTokenSwap'
2 | export * from './passThroughTokenSwap'
3 | export * from './price'
4 |
--------------------------------------------------------------------------------
/services/swap/passThroughTokenSwap.ts:
--------------------------------------------------------------------------------
1 | import { SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate'
2 | import { coin } from '@cosmjs/stargate'
3 |
4 | import { PoolEntityType, TokenInfo } from '../../queries/usePoolsListQuery'
5 | import {
6 | createExecuteMessage,
7 | createIncreaseAllowanceMessage,
8 | validateTransactionSuccess,
9 | } from '../../util/messages'
10 |
11 | type PassThroughTokenSwapArgs = {
12 | tokenAmount: number
13 | price: number
14 | slippage: number
15 | senderAddress: string
16 | inputPool: PoolEntityType
17 | outputPool: PoolEntityType
18 | tokenA: TokenInfo
19 | client: SigningCosmWasmClient
20 | }
21 |
22 | export const passThroughTokenSwap = async ({
23 | tokenAmount,
24 | tokenA,
25 | outputPool,
26 | inputPool,
27 | senderAddress,
28 | slippage,
29 | price,
30 | client,
31 | }: PassThroughTokenSwapArgs): Promise => {
32 | const minOutputToken = Math.floor(price * (1 - slippage))
33 |
34 | const input_token =
35 | inputPool.pool_assets[0].symbol === tokenA.symbol ? 'Token1' : 'Token2'
36 |
37 | const swapMessage = {
38 | pass_through_swap: {
39 | output_min_token: `${minOutputToken}`,
40 | input_token,
41 | input_token_amount: `${tokenAmount}`,
42 | output_amm_address: outputPool.swap_address,
43 | },
44 | }
45 |
46 | if (!tokenA.native) {
47 | const increaseAllowanceMessage = createIncreaseAllowanceMessage({
48 | senderAddress,
49 | tokenAmount,
50 | tokenAddress: tokenA.token_address,
51 | swapAddress: inputPool.swap_address,
52 | })
53 |
54 | const executeMessage = createExecuteMessage({
55 | senderAddress,
56 | contractAddress: inputPool.swap_address,
57 | message: swapMessage,
58 | })
59 |
60 | return validateTransactionSuccess(
61 | await client.signAndBroadcast(
62 | senderAddress,
63 | [increaseAllowanceMessage, executeMessage],
64 | 'auto'
65 | )
66 | )
67 | }
68 |
69 | return await client.execute(
70 | senderAddress,
71 | inputPool.swap_address,
72 | swapMessage,
73 | 'auto',
74 | undefined,
75 | [coin(tokenAmount, tokenA.denom)]
76 | )
77 | }
78 |
--------------------------------------------------------------------------------
/state/atoms/transactionAtoms.ts:
--------------------------------------------------------------------------------
1 | import { atom } from 'recoil'
2 |
3 | export enum TransactionStatus {
4 | IDLE = '@transaction-status/idle',
5 | EXECUTING = '@transaction-status/executing',
6 | }
7 |
8 | export const transactionStatusState = atom({
9 | key: 'transactionState',
10 | default: TransactionStatus.IDLE,
11 | })
12 |
--------------------------------------------------------------------------------
/state/atoms/walletAtoms.ts:
--------------------------------------------------------------------------------
1 | import { SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate'
2 | import { SigningStargateClient } from '@cosmjs/stargate'
3 | import { Key } from '@keplr-wallet/types'
4 | import { atom } from 'recoil'
5 |
6 | export enum WalletStatusType {
7 | /* nothing happens to the wallet */
8 | idle = '@wallet-state/idle',
9 | /* restored wallets state from the cache */
10 | restored = '@wallet-state/restored',
11 | /* the wallet is fully connected */
12 | connected = '@wallet-state/connected',
13 | /* connecting to the wallet */
14 | connecting = '@wallet-state/connecting',
15 | /* error when tried to connect */
16 | error = '@wallet-state/error',
17 | }
18 |
19 | type GeneratedWalletState<
20 | TClient extends any,
21 | TStateExtension extends {}
22 | > = TStateExtension & {
23 | client: TClient | null
24 | status: WalletStatusType
25 | address: string
26 | }
27 |
28 | type CreateWalletStateArgs = {
29 | key: string
30 | default: TState
31 | }
32 |
33 | function createWalletState({
34 | key,
35 | default: defaultState,
36 | }: CreateWalletStateArgs) {
37 | return atom>({
38 | key,
39 | default: {
40 | status: WalletStatusType.idle,
41 | client: null,
42 | address: '',
43 | ...defaultState,
44 | },
45 | dangerouslyAllowMutability: true,
46 | effects_UNSTABLE: [
47 | ({ onSet, setSelf }) => {
48 | const CACHE_KEY = `@wasmswap/wallet-state/type-${key}`
49 |
50 | const savedValue = localStorage.getItem(CACHE_KEY)
51 |
52 | if (savedValue) {
53 | try {
54 | const parsedSavedState = JSON.parse(savedValue)
55 | if (parsedSavedState?.address) {
56 | setSelf({
57 | ...parsedSavedState,
58 | client: null,
59 | status: WalletStatusType.restored,
60 | })
61 | }
62 | } catch (e) {}
63 | }
64 |
65 | onSet((newValue, oldValue) => {
66 | const isReset = !newValue.address && (oldValue as any)?.address
67 |
68 | if (isReset) {
69 | localStorage.removeItem(CACHE_KEY)
70 | } else {
71 | localStorage.setItem(
72 | CACHE_KEY,
73 | /* let's not store the client in the cache */
74 | JSON.stringify({ ...newValue, client: null, status: null })
75 | )
76 | }
77 | })
78 | },
79 | ],
80 | })
81 | }
82 |
83 | export const walletState = createWalletState<
84 | SigningCosmWasmClient,
85 | { key?: Key }
86 | >({
87 | key: 'internal-wallet',
88 | default: {
89 | key: null,
90 | },
91 | })
92 |
93 | export const ibcWalletState = createWalletState<
94 | SigningStargateClient,
95 | {
96 | /* ibc wallet is connected */
97 | tokenSymbol?: string
98 | }
99 | >({
100 | key: 'ibc-wallet',
101 | default: {
102 | tokenSymbol: null,
103 | },
104 | })
105 |
--------------------------------------------------------------------------------
/styles/globals.scss:
--------------------------------------------------------------------------------
1 | @import './inter.font.scss';
2 | @import './jetbrains.font.scss';
3 |
4 | html,
5 | body {
6 | padding: 0;
7 | margin: 0;
8 | font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
9 | Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',
10 | 'Segoe UI Symbol';
11 | min-width: 320px;
12 | width: 100%;
13 | font-size: 16px;
14 | -webkit-font-smoothing: antialiased;
15 | -moz-osx-font-smoothing: grayscale;
16 | text-rendering: optimizeLegibility;
17 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
18 | -webkit-tap-highlight-color: transparent;
19 | }
20 |
21 | body {
22 | background-color: #ffffff;
23 | }
24 |
25 | ::-webkit-scrollbar {
26 | width: 5px;
27 | }
28 |
29 | ::-webkit-scrollbar-thumb {
30 | background: rgba(0, 0, 0, 0.2);
31 | border-radius: 8px;
32 | }
33 |
34 | ::-webkit-scrollbar-track {
35 | background: rgba(0, 0, 0, 0);
36 | }
37 |
38 | a {
39 | color: inherit;
40 | text-decoration: none;
41 | }
42 |
43 | * {
44 | box-sizing: border-box;
45 | }
46 |
47 | input::-webkit-outer-spin-button,
48 | input::-webkit-inner-spin-button {
49 | -webkit-appearance: none;
50 | margin: 0;
51 | }
52 |
53 | /* Firefox */
54 | input[type='number'] {
55 | -moz-appearance: textfield;
56 | }
57 |
58 | p {
59 | margin: 0;
60 | }
61 |
62 | button {
63 | text-transform: none;
64 | -webkit-appearance: button;
65 | padding: 0;
66 | border: none;
67 | outline: none;
68 | font: inherit;
69 | color: inherit;
70 | background: none;
71 | cursor: pointer;
72 | user-select: none;
73 | outline: none;
74 | }
75 |
76 | input {
77 | border: none;
78 | outline: none;
79 | display: block;
80 | width: 100%;
81 | font: inherit;
82 | color: inherit;
83 | padding: 0;
84 | background: transparent;
85 | line-height: inherit;
86 | text-align: inherit;
87 | outline: none;
88 | }
89 |
90 | [role='button'] {
91 | outline: none;
92 | }
93 |
--------------------------------------------------------------------------------
/styles/inter.font.scss:
--------------------------------------------------------------------------------
1 | $InterPath: '/fonts' !default;
2 | $InterVersion: '3.19' !default;
3 |
4 | @mixin fontdef($FontType: 'Regular') {
5 | src: url('#{$InterPath}/Inter/Inter-#{$FontType}.woff2?v=#{$InterVersion}')
6 | format('woff2'),
7 | url('#{$InterPath}/Inter/Inter-#{$FontType}.woff?v=#{$InterVersion}')
8 | format('woff');
9 | }
10 |
11 | @font-face {
12 | font-family: 'Inter';
13 | @include fontdef('Bold');
14 | font-weight: 700;
15 | font-style: normal;
16 | }
17 |
18 | @font-face {
19 | font-family: 'Inter';
20 | @include fontdef('SemiBold');
21 | font-weight: 600;
22 | font-style: normal;
23 | }
24 |
25 | @font-face {
26 | font-family: 'Inter';
27 | @include fontdef('Medium');
28 | font-weight: 500;
29 | font-style: normal;
30 | }
31 |
32 | @font-face {
33 | font-family: 'Inter';
34 | @include fontdef('Regular');
35 | font-weight: 400;
36 | font-style: normal;
37 | }
38 |
--------------------------------------------------------------------------------
/styles/jetbrains.font.scss:
--------------------------------------------------------------------------------
1 | $JetBrainsMonoPath: '/fonts' !default;
2 | $JetBrainsMonoVersion: '1.101' !default;
3 |
4 | @mixin fontdef($FontType: 'Regular') {
5 | src: url('#{$JetBrainsMonoPath}/JetBrainsMono/JetBrainsMono-#{$FontType}.woff2?v=#{$JetBrainsMonoVersion}')
6 | format('woff2');
7 | }
8 |
9 | //@font-face {
10 | // font-family: "JetBrains Mono";
11 | // @include fontdef("Bold");
12 | // font-weight: 600;
13 | // font-style: normal;
14 | //}
15 |
16 | @font-face {
17 | font-family: 'JetBrains Mono';
18 | @include fontdef('Regular');
19 | font-weight: 500;
20 | font-style: normal;
21 | }
22 |
23 | @font-face {
24 | font-family: 'JetBrains Mono';
25 | @include fontdef('Light');
26 | font-weight: 400;
27 | font-style: normal;
28 | }
29 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "preserveConstEnums": true,
4 | "removeComments": false,
5 | "target": "es5",
6 | "lib": ["dom", "dom.iterable", "esnext"], // List of library files to be included in the compilation
7 | "allowJs": true, // Allow JavaScript files to be compiled
8 | "skipLibCheck": true, // Skip type checking of all declaration files
9 | "esModuleInterop": true, // Disables namespace imports (import * as fs from "fs") and enables CJS/AMD/UMD style imports (import fs from "fs")
10 | "allowSyntheticDefaultImports": true, // Allow default imports from modules with no default export
11 | "strict": false, // Enable all strict type checking options
12 | "forceConsistentCasingInFileNames": true, // Disallow inconsistently-cased references to the same file.
13 | "module": "esnext", // Specify module code generation
14 | "moduleResolution": "node", // Resolve modules using Node.js style
15 | "isolatedModules": true, // Unconditionally emit imports for unresolved files
16 | "resolveJsonModule": true, // Include modules imported with .json extension
17 | "noEmit": true, // Do not emit output (meaning do not compile code, only perform type checking)
18 | "sourceMap": true, // Generate corrresponding .map file
19 | "declaration": true, // Generate corresponding .d.ts file
20 | "noUnusedLocals": true, // Report errors on unused locals
21 | "noUnusedParameters": true, // Report errors on unused parameters
22 | "incremental": false, // Enable incremental compilation by reading/writing information from prior compilations to a file on disk
23 | "noFallthroughCasesInSwitch": true, // Report errors for fallthrough cases in switch statement
24 | "baseUrl": ".",
25 | "jsx": "preserve",
26 | "paths": {
27 | "": ["./*"]
28 | }
29 | },
30 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
31 | "exclude": ["node_modules"] // *** The files to not type check ***
32 | }
33 |
--------------------------------------------------------------------------------
/types/window.d.ts:
--------------------------------------------------------------------------------
1 | import { Window as KeplrWindow } from '@keplr-wallet/types'
2 |
3 | declare global {
4 | // eslint-disable-next-line
5 | interface Window extends KeplrWindow {}
6 | }
7 |
--------------------------------------------------------------------------------
/util/constants.ts:
--------------------------------------------------------------------------------
1 | export const DEFAULT_TOKEN_BALANCE_REFETCH_INTERVAL = 15000
2 | export const DEFAULT_REFETCH_ON_WINDOW_FOCUS_STALE_TIME = 60000 // 1 minute
3 | export const SLIPPAGE_OPTIONS = [0.01, 0.02, 0.03, 0.05]
4 | export const NETWORK_FEE = 0.003
5 | export const GAS_PRICE = process.env.NEXT_PUBLIC_GAS_PRICE
6 |
7 | export const APP_NAME = process.env.NEXT_PUBLIC_SITE_TITLE
8 | export const APP_MAX_WIDTH = '1920px'
9 |
10 | export const MAIN_PANE_MAX_WIDTH = '880px'
11 |
12 | export const POOL_TOKENS_DECIMALS = 6
13 |
14 | /* the app operates in test mode */
15 | export const __TEST_MODE__ = !JSON.parse(
16 | process.env.NEXT_PUBLIC_TEST_MODE_DISABLED
17 | )
18 |
19 | /* feature flags */
20 | export const __POOL_STAKING_ENABLED__ = JSON.parse(
21 | process.env.NEXT_PUBLIC_ENABLE_FEATURE_STAKING
22 | )
23 | export const __POOL_REWARDS_ENABLED__ = JSON.parse(
24 | process.env.NEXT_PUBLIC_ENABLE_FEATURE_REWARDS
25 | )
26 | export const __TRANSFERS_ENABLED__ = JSON.parse(
27 | process.env.NEXT_PUBLIC_ENABLE_FEATURE_TRANSFERS
28 | )
29 | export const __DARK_MODE_ENABLED_BY_DEFAULT__ = JSON.parse(
30 | process.env.NEXT_PUBLIC_DARK_MODE_ENABLED_BY_DEFAULT
31 | )
32 | /* /feature flags */
33 |
--------------------------------------------------------------------------------
/util/conversion/conversion.ts:
--------------------------------------------------------------------------------
1 | export const protectAgainstNaN = (value: number) => (isNaN(value) ? 0 : value)
2 |
3 | export function convertMicroDenomToDenom(
4 | value: number | string,
5 | decimals: number
6 | ): number {
7 | if (decimals === 0) return Number(value)
8 |
9 | return protectAgainstNaN(Number(value) / Math.pow(10, decimals))
10 | }
11 |
12 | export function convertDenomToMicroDenom(
13 | value: number | string,
14 | decimals: number
15 | ): number {
16 | if (decimals === 0) return Number(value)
17 |
18 | return protectAgainstNaN(
19 | parseInt(String(Number(value) * Math.pow(10, decimals)), 10)
20 | )
21 | }
22 |
23 | export function convertFromMicroDenom(denom: string) {
24 | return denom?.substring(1).toUpperCase()
25 | }
26 |
27 | export function convertToFixedDecimals(value: number | string): string {
28 | const amount = Number(value)
29 | return amount > 0.01 ? amount.toFixed(2) : String(amount)
30 | }
31 |
32 | export const formatTokenName = (name: string) => {
33 | if (name) {
34 | return name.slice(0, 1).toUpperCase() + name.slice(1).toLowerCase()
35 | }
36 | return ''
37 | }
38 |
--------------------------------------------------------------------------------
/util/conversion/index.ts:
--------------------------------------------------------------------------------
1 | export * from './conversion'
2 | export * from './pools'
3 |
--------------------------------------------------------------------------------
/util/conversion/pools.ts:
--------------------------------------------------------------------------------
1 | import { POOL_TOKENS_DECIMALS } from '../constants'
2 | import { convertMicroDenomToDenom } from './conversion'
3 |
4 | export const calcPoolTokenValue = ({
5 | tokenAmountInMicroDenom,
6 | tokenSupply,
7 | tokenReserves,
8 | }) => {
9 | return convertMicroDenomToDenom(
10 | (tokenAmountInMicroDenom / tokenSupply) * tokenReserves,
11 | POOL_TOKENS_DECIMALS
12 | )
13 | }
14 |
15 | export const calcPoolTokenDollarValue = ({
16 | tokenAmountInMicroDenom,
17 | tokenSupply,
18 | tokenReserves,
19 | tokenDollarPrice,
20 | }) => {
21 | return (
22 | calcPoolTokenValue({
23 | tokenAmountInMicroDenom,
24 | tokenSupply,
25 | tokenReserves,
26 | }) *
27 | tokenDollarPrice *
28 | 2
29 | )
30 | }
31 |
--------------------------------------------------------------------------------
/util/cosmWasmClientRouter.ts:
--------------------------------------------------------------------------------
1 | import { CosmWasmClient } from '@cosmjs/cosmwasm-stargate'
2 |
3 | type CosmWasmClients = {
4 | [rpcEndpoint: string]: CosmWasmClient
5 | }
6 |
7 | class CosmWasmClientRouter {
8 | clients: CosmWasmClients = {}
9 |
10 | async connect(rpcEndpoint) {
11 | if (!this.getClientInstance(rpcEndpoint)) {
12 | this.setClientInstance(rpcEndpoint, CosmWasmClient.connect(rpcEndpoint))
13 | }
14 |
15 | return this.getClientInstance(rpcEndpoint)
16 | }
17 |
18 | getClientInstance(rpcEndpoint) {
19 | return this.clients[rpcEndpoint]
20 | }
21 | setClientInstance(rpcEndpoint, client) {
22 | this.clients[rpcEndpoint] = client
23 | }
24 | }
25 |
26 | export const cosmWasmClientRouter = new CosmWasmClientRouter()
27 |
--------------------------------------------------------------------------------
/util/externalLinkPopup.ts:
--------------------------------------------------------------------------------
1 | type ExternalLinkPopupArgs = {
2 | url: string
3 | title: string
4 | width: number
5 | height: number
6 | }
7 |
8 | export function externalLinkPopup({
9 | url,
10 | title,
11 | width: w,
12 | height: h,
13 | }: ExternalLinkPopupArgs) {
14 | const dualScreenLeft =
15 | window.screenLeft !== undefined ? window.screenLeft : window.screenX
16 | const dualScreenTop =
17 | window.screenTop !== undefined ? window.screenTop : window.screenY
18 |
19 | const width = window.innerWidth
20 | ? window.innerWidth
21 | : document.documentElement.clientWidth
22 | ? document.documentElement.clientWidth
23 | : screen.width
24 | const height = window.innerHeight
25 | ? window.innerHeight
26 | : document.documentElement.clientHeight
27 | ? document.documentElement.clientHeight
28 | : screen.height
29 |
30 | const systemZoom = width / window.screen.availWidth
31 | const left = (width - w) / 2 / systemZoom + dualScreenLeft
32 | const top = (height - h) / 2 / systemZoom + dualScreenTop
33 | const newWindow = window.open(
34 | url,
35 | title,
36 | `
37 | scrollbars=yes,
38 | width=${w / systemZoom},
39 | height=${h / systemZoom},
40 | top=${top},
41 | left=${left},
42 | location=yes,
43 | status=yes
44 | `
45 | )
46 |
47 | if (window.focus) newWindow.focus()
48 |
49 | return newWindow
50 | }
51 |
52 | export function externalLinkPopupAutoWidth(
53 | args: Omit
54 | ) {
55 | const width = Math.max(450, Math.round(window.innerWidth * 0.35))
56 | const height = Math.max(300, Math.round(window.innerHeight * 0.8))
57 | return externalLinkPopup({
58 | ...args,
59 | width,
60 | height,
61 | })
62 | }
63 |
--------------------------------------------------------------------------------
/util/formatCompactNumber.ts:
--------------------------------------------------------------------------------
1 | import { dollarValueFormatter, formatTokenBalance } from 'junoblocks'
2 |
3 | const formatWithOneDecimal = new Intl.NumberFormat('en-US', {
4 | minimumFractionDigits: 0,
5 | maximumFractionDigits: 1,
6 | })
7 |
8 | const oneMillion = 1e6
9 |
10 | export const formatCompactNumber = (
11 | value: number,
12 | kind: 'tokenAmount' | 'dollarValue' = 'dollarValue'
13 | ) => {
14 | if (value > oneMillion) {
15 | return `${formatWithOneDecimal.format(value / oneMillion)}M`
16 | }
17 | if (value > 10000) {
18 | return `${formatWithOneDecimal.format(Math.round(value / 1000))}K`
19 | }
20 | if (value > 1000) {
21 | return dollarValueFormatter(Math.round(value), {
22 | includeCommaSeparation: true,
23 | })
24 | }
25 | if (kind === 'dollarValue') {
26 | return dollarValueFormatter(value, {
27 | includeCommaSeparation: true,
28 | })
29 | }
30 | return formatTokenBalance(value)
31 | }
32 |
33 | export const formatCurrency = (
34 | value: number,
35 | options?: Intl.NumberFormatOptions
36 | ) => {
37 | return Intl.NumberFormat('en-US', {
38 | style: 'currency',
39 | currency: 'USD',
40 | ...options,
41 | }).format(value)
42 | }
43 |
--------------------------------------------------------------------------------
/util/formatSdkErrorMessage.ts:
--------------------------------------------------------------------------------
1 | export function formatSdkErrorMessage(e) {
2 | return String(e).length > 300
3 | ? `${String(e).substring(0, 150)} ... ${String(e).substring(
4 | String(e).length - 150
5 | )}`
6 | : String(e)
7 | }
8 |
--------------------------------------------------------------------------------
/util/getPropsForInteractiveElement.ts:
--------------------------------------------------------------------------------
1 | import { KeyboardEvent, MouseEvent } from 'react'
2 |
3 | type Args = {
4 | onClick: (event: MouseEvent | KeyboardEvent) => void
5 | }
6 |
7 | export const getPropsForInteractiveElement = ({ onClick }: Args) => ({
8 | role: 'button',
9 | onClick,
10 | onKeyDown(event: KeyboardEvent) {
11 | const { code } = event
12 | const shouldToggleSelector = ['space', 'enter'].includes(code.toLowerCase())
13 | if (shouldToggleSelector) {
14 | onClick(event)
15 | }
16 | },
17 | tabIndex: 0,
18 | })
19 |
--------------------------------------------------------------------------------
/util/localStorageEffect.ts:
--------------------------------------------------------------------------------
1 | export const localStorageEffect =
2 | (key) =>
3 | ({ setSelf, onSet }) => {
4 | if (typeof window !== 'undefined') {
5 | const savedValue = localStorage.getItem(key)
6 | if (savedValue != null) {
7 | setSelf(JSON.parse(savedValue))
8 | }
9 |
10 | onSet((newValue, _, isReset) => {
11 | isReset
12 | ? localStorage.removeItem(key)
13 | : localStorage.setItem(key, JSON.stringify(newValue))
14 | })
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/util/maybePluralize.ts:
--------------------------------------------------------------------------------
1 | export const maybePluralize = (count: number, noun: string, suffix = 's') =>
2 | `${count} ${noun}${count !== 1 ? suffix : ''}`
3 |
--------------------------------------------------------------------------------
/util/messages/createExecuteMessage.ts:
--------------------------------------------------------------------------------
1 | import { MsgExecuteContractEncodeObject } from '@cosmjs/cosmwasm-stargate'
2 | import { toUtf8 } from '@cosmjs/encoding'
3 | import { Coin } from '@cosmjs/launchpad'
4 | import { MsgExecuteContract } from 'cosmjs-types/cosmwasm/wasm/v1/tx'
5 |
6 | type CreateExecuteMessageArgs = {
7 | senderAddress: string
8 | message: Record>
9 | contractAddress: string
10 | funds?: Array
11 | }
12 |
13 | export const createExecuteMessage = ({
14 | senderAddress,
15 | contractAddress,
16 | message,
17 | funds,
18 | }: CreateExecuteMessageArgs): MsgExecuteContractEncodeObject => ({
19 | typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract',
20 | value: MsgExecuteContract.fromPartial({
21 | sender: senderAddress,
22 | contract: contractAddress,
23 | msg: toUtf8(JSON.stringify(message)),
24 | funds: funds || [],
25 | }),
26 | })
27 |
--------------------------------------------------------------------------------
/util/messages/createIncreaseAllowanceMessage.ts:
--------------------------------------------------------------------------------
1 | import { MsgExecuteContractEncodeObject } from '@cosmjs/cosmwasm-stargate'
2 | import { toUtf8 } from '@cosmjs/encoding'
3 | import { MsgExecuteContract } from 'cosmjs-types/cosmwasm/wasm/v1/tx'
4 |
5 | type CreateIncreaseAllowanceMessageArgs = {
6 | senderAddress: string
7 | tokenAmount: number
8 | tokenAddress: string
9 | swapAddress: string
10 | }
11 |
12 | export const createIncreaseAllowanceMessage = ({
13 | senderAddress,
14 | tokenAmount,
15 | tokenAddress,
16 | swapAddress,
17 | }: CreateIncreaseAllowanceMessageArgs): MsgExecuteContractEncodeObject => ({
18 | typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract',
19 | value: MsgExecuteContract.fromPartial({
20 | sender: senderAddress,
21 | contract: tokenAddress,
22 | msg: toUtf8(
23 | JSON.stringify({
24 | increase_allowance: {
25 | amount: `${tokenAmount}`,
26 | spender: `${swapAddress}`,
27 | },
28 | })
29 | ),
30 | funds: [],
31 | }),
32 | })
33 |
--------------------------------------------------------------------------------
/util/messages/index.ts:
--------------------------------------------------------------------------------
1 | export { createExecuteMessage } from './createExecuteMessage'
2 | export { createIncreaseAllowanceMessage } from './createIncreaseAllowanceMessage'
3 | export { validateTransactionSuccess } from './validateTransactionSuccess'
4 |
--------------------------------------------------------------------------------
/util/messages/validateTransactionSuccess.ts:
--------------------------------------------------------------------------------
1 | import { DeliverTxResponse, isDeliverTxFailure } from '@cosmjs/stargate'
2 |
3 | export function validateTransactionSuccess(result: DeliverTxResponse) {
4 | if (isDeliverTxFailure(result)) {
5 | throw new Error(
6 | `Error when broadcasting tx ${result.transactionHash} at height ${result.height}. Code: ${result.code}; Raw log: ${result.rawLog}`
7 | )
8 | }
9 |
10 | return result
11 | }
12 |
--------------------------------------------------------------------------------
/util/testutils.ts:
--------------------------------------------------------------------------------
1 | import { PoolEntityType, TokenInfo } from '../queries/usePoolsListQuery'
2 |
3 | export const BASE_TOKEN: TokenInfo = {
4 | id: '',
5 | chain_id: 'lucina',
6 | token_address: '',
7 | symbol: 'WSWAP',
8 | name: 'Wasmswap',
9 | decimals: 6,
10 | logoURI: 'https://cryptologos.cc/logos/anyswap-any-logo.svg?v=022',
11 | tags: ['native'],
12 | native: true,
13 | denom: 'ujuno',
14 | }
15 |
16 | export const CRAB_TOKEN: TokenInfo = {
17 | id: '',
18 | chain_id: 'lucina',
19 | token_address:
20 | 'juno14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9skjuwg8',
21 | symbol: 'CRAB',
22 | name: 'Crab',
23 | decimals: 6,
24 | logoURI: 'https://cryptologos.cc/logos/irisnet-iris-logo.svg?v=022',
25 | tags: ['memecoin'],
26 | native: false,
27 | denom: 'ucrab',
28 | }
29 |
30 | export const DAO_TOKEN: TokenInfo = {
31 | id: '',
32 | chain_id: 'lucina',
33 | token_address:
34 | 'juno1yw4xvtc43me9scqfr2jr2gzvcxd3a9y4eq7gaukreugw2yd2f8ts9z8cq8',
35 | symbol: 'DAO',
36 | name: 'DAO',
37 | decimals: 6,
38 | logoURI: 'https://cryptologos.cc/logos/torn-torn-logo.svg?v=022',
39 | tags: ['memecoin'],
40 | native: false,
41 | denom: 'udao',
42 | }
43 |
44 | export const BASE_TOKEN_CRAB_POOL: PoolEntityType = {
45 | pool_id: 'WSWAP-CRAB',
46 | pool_assets: [BASE_TOKEN, CRAB_TOKEN],
47 | swap_address:
48 | 'juno1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv737ktmt4eswrq68ev2p',
49 | staking_address:
50 | 'juno16y344e8ryydmeu2g8yyfznq79j7jfnar4p59ngpvaazcj83jzsmsnvh4g8',
51 | rewards_tokens: [],
52 | }
53 |
54 | export const BASE_TOKEN_DAO_POOL: PoolEntityType = {
55 | pool_id: 'WSWAP-DAO',
56 | pool_assets: [BASE_TOKEN, DAO_TOKEN],
57 | swap_address:
58 | 'juno1hulx7cgvpfcvg83wk5h96sedqgn72n026w6nl47uht554xhvj9ns263mx9',
59 | staking_address:
60 | 'juno1q23d30x94cm8ve243pxdc52m398r4l5ecgcfp8tud3vggcsq8s2qv7kt2z',
61 | rewards_tokens: [],
62 | }
63 |
64 | export const CRAB_DAO_POOL: PoolEntityType = {
65 | pool_id: 'CRAB-DAO',
66 | pool_assets: [CRAB_TOKEN, DAO_TOKEN],
67 | swap_address:
68 | 'juno187zds75uenfxht2zqz7e0wxn3ushcawvf2ndrns6q63hgfn6ptqq7d7smp',
69 | staking_address: '',
70 | rewards_tokens: [],
71 | }
72 |
--------------------------------------------------------------------------------
/webpack.functions.js:
--------------------------------------------------------------------------------
1 | const nodeExternals = require('webpack-node-externals')
2 |
3 | module.exports = {
4 | externals: [nodeExternals()],
5 | }
6 |
--------------------------------------------------------------------------------