diff --git a/apps/portal/codegen.crowdloan.ts b/apps/portal/codegen.crowdloan.ts deleted file mode 100644 index 9b996698c..000000000 --- a/apps/portal/codegen.crowdloan.ts +++ /dev/null @@ -1,18 +0,0 @@ -import type { CodegenConfig } from '@graphql-codegen/cli' - -const config: CodegenConfig = { - overwrite: true, - schema: process.env.VITE_DOT_CROWDLOAN_INDEXER, - documents: ['src/libs/crowdloans/useCrowdloanContributions.ts'], - generates: { - 'src/generated/gql/crowdloan/gql/': { - preset: 'client', - plugins: [], - config: { - useTypeImports: true, - }, - }, - }, -} - -export default config diff --git a/apps/portal/codegen.history.ts b/apps/portal/codegen.history.ts deleted file mode 100644 index cd687ec35..000000000 --- a/apps/portal/codegen.history.ts +++ /dev/null @@ -1,18 +0,0 @@ -import type { CodegenConfig } from '@graphql-codegen/cli' - -const config: CodegenConfig = { - overwrite: true, - schema: process.env.VITE_EX_HISTORY_INDEXER, - documents: ['src/components/widgets/history/**/*.tsx', 'src/routes/history.tsx'], - generates: { - 'src/generated/gql/extrinsicHistory/gql/': { - preset: 'client', - plugins: [], - config: { - useTypeImports: true, - }, - }, - }, -} - -export default config diff --git a/apps/portal/package.json b/apps/portal/package.json index 4d803f816..04be9c6eb 100644 --- a/apps/portal/package.json +++ b/apps/portal/package.json @@ -15,8 +15,6 @@ "e2e": "playwright test", "storybook": "storybook dev -p 6008", "build-storybook": "storybook build", - "codegen:history": "graphql-codegen --require dotenv/config --config codegen.history.ts", - "codegen:crowdloan": "graphql-codegen --require dotenv/config --config codegen.crowdloan.ts", "codegen:nova": "graphql-codegen --require dotenv/config --config codegen.nova.ts" }, "dependencies": { @@ -79,7 +77,6 @@ "react-use": "^17.4.0", "react-winbox": "^1.5.0", "recoil": "^0.7.7", - "safety-match": "^0.4.4", "scale-ts": "^1.6.0", "tailwind-merge": "^2.5.4", "tailwindcss-animate": "^1.0.7", diff --git a/apps/portal/src/App.tsx b/apps/portal/src/App.tsx index 0eccfba77..389fd6f78 100644 --- a/apps/portal/src/App.tsx +++ b/apps/portal/src/App.tsx @@ -3,6 +3,7 @@ import '@polkadot/api-augment/substrate' import '@talismn/astar-types/augment-api' import '@talismn/astar-types/types-lookup' +import { BalancesProvider } from '@talismn/balances-react' import { PolkadotApiProvider } from '@talismn/react-polkadot-api' import { Toaster } from '@talismn/ui/molecules/Toaster' import { PostHogProvider } from 'posthog-js/react' @@ -21,8 +22,6 @@ import { chainDeriveState, chainQueryMultiState, chainQueryState } from '@/domai import { ExtensionWatcher } from '@/domains/extension/main' import { TalismanExtensionSynchronizer } from '@/domains/extension/TalismanExtensionSynchronizer' import { EvmProvider } from '@/domains/extension/wagmi' -import * as Portfolio from '@/libs/portfolio' -import TalismanProvider from '@/libs/talisman' import router from '@/routes' const App = () => ( @@ -43,21 +42,30 @@ const App = () => ( deriveState={chainDeriveState} queryMultiState={chainQueryMultiState} > - - - - - - - - }> - - - - - - - + + + + + + + }> + + + + + + diff --git a/apps/portal/src/components/legacy/Await.tsx b/apps/portal/src/components/legacy/Await.tsx deleted file mode 100644 index 37df39e54..000000000 --- a/apps/portal/src/components/legacy/Await.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import styled from '@emotion/styled' - -import Loader from '@/assets/icons/loader.svg?react' - -const StyledLoader = styled(({ className }: { className?: string }) => ( -
- -
-))` - display: block; - padding: 2rem; - width: 100%; - text-align: center; - font-size: inherit; - > svg { - font-size: inherit; - } -` - -/** @deprecated */ -export const Await = ({ until = true, children }: { until: boolean; children: React.ReactNode }) => - until ? <>{children} : diff --git a/apps/portal/src/components/legacy/Button.tsx b/apps/portal/src/components/legacy/Button.tsx deleted file mode 100644 index e343b638c..000000000 --- a/apps/portal/src/components/legacy/Button.tsx +++ /dev/null @@ -1,99 +0,0 @@ -import { css } from '@emotion/react' -import styled from '@emotion/styled' -import { omit } from 'lodash' -import React, { Fragment } from 'react' -import { Link, NavLink } from 'react-router-dom' - -import IconLoading from '@/assets/icons/loader.svg?react' - -/** @deprecated */ -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export const Button = styled(({ loading, children, variant = '', className, ...props }: any) => { - const wrappedChildren = loading ? ( - - -  {loading} - - ) : ( - React.Children.map(children, child => - React.isValidElement(child) ? child : {child} - ) - ) - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const _props: any = omit(props, ['loading', 'boxed', 'round', 'primary', 'tight', 'loose', 'small']) - - return props?.to ? ( - props?.navlink ? ( - - {wrappedChildren} - - ) : ( - - {wrappedChildren} - - ) - ) : ( - - ) -})` - border: none; - padding: 1.156rem 1.6rem; - margin: 0; - line-height: 1em; - display: inline-flex; - justify-content: center; - align-items: center; - cursor: pointer; - background: rgb(${({ theme }) => theme?.dim}); - color: rgb(${({ theme }) => theme?.mid}); - border-radius: 1rem; - transition: all 0.15s ease-in-out; - white-space: nowrap; - - &.outlined { - color: var(--color-text); - border: 1px solid var(--color-text); - background: transparent; - } - - .child { - white-space: nowrap; - text-overflow: ellipsis; - overflow: hidden; - } - - > * { - margin: 0 0.5rem; - font-size: inherit; - font: inherit; - color: inherit; - } - - &:hover { - opacity: 0.8; - } - - ${({ primary, theme }) => - !!primary && - css` - background: rgb(${theme?.primary}); - color: rgb(${theme?.background}); - border: 3.13px solid rgb(${theme.primary}); - `} - - ${({ small }) => - !!small && - css` - padding: 1em 1.6em; - `} - - &[disabled] { - opacity: 0.4; - filter: grayscale(100%); - color: var(--color-mid); - cursor: not-allowed; - } -` diff --git a/apps/portal/src/components/legacy/Countdown.tsx b/apps/portal/src/components/legacy/Countdown.tsx deleted file mode 100644 index 9f22474ef..000000000 --- a/apps/portal/src/components/legacy/Countdown.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import { useEffect, useMemo, useRef, useState } from 'react' - -/** @deprecated */ -const useInterval = (cb = () => {}, ms = 1000) => { - const savedCallback = useRef<() => void>() - const [id, setId] = useState(null) - - useEffect(() => { - savedCallback.current = cb - }, [cb]) - - useEffect(() => { - const tick = () => typeof savedCallback?.current !== 'undefined' && savedCallback?.current() - const _id = setInterval(tick, ms) - setId(_id) - return () => clearInterval(_id) - }, [ms]) - - return id -} - -const useCountdown = (seconds = 0) => { - const [secondsRemaining, setSecondsRemaining] = useState(Math.max(0, seconds)) - - useEffect(() => { - setSecondsRemaining(Math.max(0, seconds)) - }, [seconds]) - - useInterval(() => setSecondsRemaining(Math.max(0, secondsRemaining - 1))) - - return useMemo(() => secondsRemaining, [secondsRemaining]) -} - -const minute = 60 -const hour = 60 * minute -const day = 24 * hour -const week = 7 * day - -/** @deprecated */ -export const Countdown = ({ - seconds: countdownSeconds = 10, - showSeconds = true, -}: { - seconds: number - showSeconds: boolean -}) => { - const remaining = useCountdown(countdownSeconds) - - const weeks = Math.max(0, Math.floor(remaining / week)) - const weeksRemainder = remaining % week - - const days = Math.max(0, Math.floor(weeksRemainder / day)) - const daysRemainder = weeksRemainder % day - - const hours = Math.max(0, Math.floor(daysRemainder / hour)) - const hoursRemainder = daysRemainder % hour - - const minutes = Math.max(0, Math.floor(hoursRemainder / minute)) - const seconds = hoursRemainder % minute - - const segments = [ - weeks > 0 && `${weeks}w`, - days > 0 && `${days}d`, - `${hours}h`, - `${minutes.toString().padStart(2, '0')}m`, - showSeconds && seconds > 0 && `${seconds}s`, - ].filter(Boolean) - - return <>{segments.join(' ')} -} diff --git a/apps/portal/src/components/legacy/DesktopRequired.tsx b/apps/portal/src/components/legacy/DesktopRequired.tsx deleted file mode 100644 index af88b54dd..000000000 --- a/apps/portal/src/components/legacy/DesktopRequired.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import styled from '@emotion/styled' -import { useEffect } from 'react' -import { useTranslation } from 'react-i18next' - -import { Button } from '@/components/legacy/Button' -import { useModal } from '@/components/legacy/Modal' -import { isMobileBrowser } from '@/util/helpers' - -/** @deprecated */ -export const DesktopRequired = () => { - const { openModal } = useModal() - - useEffect(() => { - if (!isMobileBrowser()) return - - openModal(, { closable: false }) - }, [openModal]) - - return null -} - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -const DesktopRequiredModal = styled((props: any) => { - const { closeModal } = useModal() - const { t } = useTranslation() - - return ( -
-

{t('desktopRequired.title')}

-

{t('desktopRequired.subtitle')}

-

{t('desktopRequired.text')}

- -
- ) -})` - text-align: center; - - > *:not(:last-child) { - margin-bottom: 2rem; - } - - h2 { - color: var(--color-text); - font-weight: 600; - font-size: 1.8rem; - } - p { - color: #999; - font-size: 1.6rem; - } -` diff --git a/apps/portal/src/components/legacy/FieldWrapper.tsx b/apps/portal/src/components/legacy/FieldWrapper.tsx deleted file mode 100644 index 1fb3ca3d9..000000000 --- a/apps/portal/src/components/legacy/FieldWrapper.tsx +++ /dev/null @@ -1,132 +0,0 @@ -import styled from '@emotion/styled' - -// May want to figure out what prefix's and onClick actual type is -type FieldWrapperProps = { - type: string - // eslint-disable-next-line @typescript-eslint/no-explicit-any - prefix?: any - suffix?: React.ReactNode - className?: string - children?: React.ReactNode - label?: string - dim?: boolean - // eslint-disable-next-line @typescript-eslint/no-explicit-any - onClick?: any -} - -/** @deprecated */ -export const FieldWrapper = styled( - ({ type, prefix, suffix, label, dim, children, className, ...rest }: FieldWrapperProps) => ( -
- {label && {label}} - - {!!prefix && {prefix}} - {children} - {!!suffix && {suffix}} - -
- ) -)` - position: relative; - display: flex; - flex-direction: column; - align-items: center; - color: var(--color-text); - - > .children { - border: none; - box-shadow: 0 0 1.2rem rgba(0, 0, 0, 0.1); - border-radius: 1rem; - overflow: hidden; - transition: box-shadow 0.2s ease; - display: block; - width: 100%; - - &:hover { - box-shadow: 0 0 2rem rgba(0, 0, 0, 0.15); - } - - .prefix, - .suffix { - position: absolute; - top: 50%; - transform: translateY(-50%); - opacity: 0.4; - pointer-events: none; - > * { - display: block; - width: 1.5em; - height: 1.5em; - } - } - - .prefix { - left: 1em; - } - .suffix { - right: 0.7em; - } - - input, - select { - font-size: 1.2em; - font-family: inherit; - font-weight: inherit; - border: none; - padding: 1.1rem 1.5rem; - width: 100%; - ${({ prefix }) => !!prefix && `padding-left: 5rem;`} - ${({ suffix }) => !!suffix && `padding-right: 2rem;`} - &:hover { - color: var(--color-text); - } - transition: all 0.2s ease-in-out; - } - - select { - cursor: pointer; - } - } - - &.dim { - > .children { - border-radius: 0.8rem; - box-shadow: none; - - input, - select { - background: var(--color-controlBackground); - } - } - } - - ::placeholder { - color: var(--color-dim); - } -` - -type FieldLabelProps = { - className?: string - children?: string -} - -/** @deprecated */ -const FieldLabel = styled(({ children, className, ...rest }: FieldLabelProps) => - children ? ( - - ) : null -)` - position: relative; - font-size: 0.6em; - text-transform: uppercase; - font-weight: var(--font-weight-bold); - color: rgb(${({ theme }) => theme.mid}); - white-space: pre-line; - text-align: right; - line-height: 1.2em; - opacity: 0.8; -` diff --git a/apps/portal/src/components/legacy/Grid.tsx b/apps/portal/src/components/legacy/Grid.tsx deleted file mode 100644 index 068b29548..000000000 --- a/apps/portal/src/components/legacy/Grid.tsx +++ /dev/null @@ -1,52 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ - -import styled from '@emotion/styled' -import { sortBy } from 'lodash' - -const defaultColumns = 4 - -const defaultGap = '2.4rem' - -const defaultBreakpoints = { - 1320: { columns: 3 }, - 1020: { columns: 2 }, - 700: { columns: 1 }, -} - -type GridProps = { - children: React.ReactNode - className?: string - columns?: number - gap?: any - breakpoints?: any -} - -/** @deprecated */ -export const Grid = styled(({ className, children }: GridProps) => ( -
{children}
-))` - display: grid; - grid-gap: 2.4rem; - width: 100%; - grid-template-columns: repeat(${({ columns = defaultColumns }) => columns}, 1fr); - margin: ${({ gap = defaultGap }) => gap} 0; - - > * { - ${({ itemHeight }: { itemHeight?: any }) => !!itemHeight && `height: ${itemHeight as number}`} - } - - ${({ breakpoints = defaultBreakpoints, columns = defaultColumns, itemHeight }) => - sortBy(Object.entries(breakpoints), (bp: any) => parseInt(bp)) - .reverse() - .map( - (key: any) => - ` - @media only screen and (max-width: ${key[0] as string}px) { - grid-template-columns: repeat(${key[1]?.columns || columns}, 1fr); - >*{ - height: ${key[1]?.itemHeight || itemHeight || 'inherit'}; - } - }; - ` - )} -` diff --git a/apps/portal/src/components/legacy/Input.tsx b/apps/portal/src/components/legacy/Input.tsx deleted file mode 100644 index 84ca95ccf..000000000 --- a/apps/portal/src/components/legacy/Input.tsx +++ /dev/null @@ -1,27 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ - -import styled from '@emotion/styled' - -import { FieldWrapper } from '@/components/legacy/FieldWrapper' - -type InputProps = { - className?: string - onChange?: (e: any) => void - type?: string - inputMode?: string - pattern?: string - prefix?: any - suffix?: React.ReactNode - label?: string - dim?: boolean - value?: any - placeholder?: string - disabled?: boolean -} - -/** @deprecated */ -export const Input = styled(({ className, onChange = _v => {}, prefix, suffix, label, dim, ...rest }: InputProps) => ( - - onChange(e?.target?.value)} {...(rest as any)} /> - -))`` diff --git a/apps/portal/src/components/legacy/MaterialLoader.tsx b/apps/portal/src/components/legacy/MaterialLoader.tsx deleted file mode 100644 index 99063af75..000000000 --- a/apps/portal/src/components/legacy/MaterialLoader.tsx +++ /dev/null @@ -1,62 +0,0 @@ -import styled from '@emotion/styled' - -/** @deprecated */ -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export const MaterialLoader = styled((props: any) => ( -
- - - -
-))` - position: relative; - margin: 0 auto; - width: 1em; - &:before { - content: ''; - display: block; - padding-top: 100%; - } - - svg { - animation: rotate 2s linear infinite; - height: 100%; - transform-origin: center center; - width: 100%; - position: absolute; - top: 0; - bottom: 0; - left: 0; - right: 0; - margin: auto; - } - - circle { - stroke-dasharray: 1, 200; - stroke-dashoffset: 0; - animation: dash 1.5s ease-in-out infinite; - stroke: currentColor; - stroke-linecap: round; - } - - @keyframes rotate { - 100% { - transform: rotate(360deg); - } - } - - @keyframes dash { - 0% { - stroke-dasharray: 1, 200; - stroke-dashoffset: 0; - } - 50% { - stroke-dasharray: 89, 200; - stroke-dashoffset: -35px; - } - 100% { - stroke-dasharray: 89, 200; - stroke-dashoffset: -124px; - } - } -` diff --git a/apps/portal/src/components/legacy/Modal.tsx b/apps/portal/src/components/legacy/Modal.tsx deleted file mode 100644 index f15b675aa..000000000 --- a/apps/portal/src/components/legacy/Modal.tsx +++ /dev/null @@ -1,66 +0,0 @@ -// TODO: remove legacy modal completely - -import type { PropsWithChildren } from 'react' -import { AlertDialog } from '@talismn/ui/molecules/AlertDialog' -import { createContext, useCallback, useContext, useMemo, useState } from 'react' - -import useKeyDown from '@/util/useKeyDown' - -type OpenModalOptions = { - closable: boolean -} - -type ContextProps = { - open: boolean - content: JSX.Element | null - openModal: (content: JSX.Element, options?: OpenModalOptions) => void - closeModal: () => void -} - -const Context = createContext(null) -/** @deprecated */ -export function useModal(): ContextProps { - const context = useContext(Context) - if (!context) throw new Error('The modal provider is required in order to use this hook') - - return context -} - -type ProviderProps = PropsWithChildren -/** @deprecated */ -export function ModalProvider({ children }: PropsWithChildren): JSX.Element { - const [content, setContent] = useState(null) - const [closable, setClosable] = useState(true) - - const openModal = useCallback((content: JSX.Element, options?: OpenModalOptions) => { - setContent(content) - setClosable(options?.closable !== false) - }, []) - const closeModal = useCallback(() => setContent(null), []) - - const value = useMemo( - () => ({ open: content !== null, content, openModal, closeModal }), - [content, openModal, closeModal] - ) - - return ( - - - {children} - - ) -} - -/** @deprecated */ -const Modal = function Modal({ className, closable }: { className?: string; closable: boolean }) { - const { open, content, closeModal } = useModal() - - useKeyDown( - 'Escape', - useCallback(() => { - open && closable && closeModal() - }, [open, closable, closeModal]) - ) - - return -} diff --git a/apps/portal/src/components/legacy/NoResults.tsx b/apps/portal/src/components/legacy/NoResults.tsx deleted file mode 100644 index ffa35e20c..000000000 --- a/apps/portal/src/components/legacy/NoResults.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import type { PropsWithChildren } from 'react' -import styled from '@emotion/styled' -import React from 'react' - -type NoResultsMessageProps = { - className?: string - subtitle: string - text: string -} - -const NoResultsMessage = styled(({ subtitle, text, className }: NoResultsMessageProps) => ( -
- {subtitle &&

{subtitle}

} - {text &&

{text}

} -
-))` - display: block; - padding: 2rem; - text-align: center; - width: 100%; -` - -/** @deprecated */ -export type NoResultProps = PropsWithChildren<{ - require: boolean -}> & - NoResultsMessageProps - -/** @deprecated */ -export const NoResults = ({ require, children, ...props }: NoResultProps) => { - // undefined not set? await children - // require is explicitly set to false - return (require === undefined && !React.Children.count(children)) || !require ? ( - - ) : ( - <>{children} - ) -} diff --git a/apps/portal/src/components/legacy/Panel.tsx b/apps/portal/src/components/legacy/Panel.tsx deleted file mode 100644 index 3a3ed6c1f..000000000 --- a/apps/portal/src/components/legacy/Panel.tsx +++ /dev/null @@ -1,87 +0,0 @@ -import type { MotionProps } from 'framer-motion' -import { css } from '@emotion/react' -import styled from '@emotion/styled' -import { motion } from 'framer-motion' -import { type ReactNode } from 'react' - -type Props = { - title?: string - subtitle?: string | ReactNode - className?: string - children?: ReactNode - comingSoon?: boolean -} & MotionProps - -/** @deprecated */ -export const Panel = styled(({ title, subtitle, children, className, ...rest }: Props) => ( - - {title !== undefined && ( -

- {title} - {subtitle !== undefined && {subtitle}} -

- )} -
{children}
-
-))` - width: 100%; - - > h1 { - display: flex; - align-items: baseline; - margin-bottom: 0.8em; - font-size: var(--font-size-large); - font-weight: bold; - color: var(--color-text); - - > span { - font-size: var(--font-size-normal); - margin-left: 0.85em; - color: var(--color-primary); - font-weight: normal; - } - } - - > .inner { - display: block; - border-radius: 1.6rem; - user-select: none; - background: #1b1b1b; - color: rgb(${({ theme }) => theme.foreground}); - - // add a bottom border to every row except the last one - > .panel-section:not(:last-child) { - border-bottom: 1px solid rgba(${({ theme }) => theme.foreground}, 0.05); - } - - .panel-section + .panel-section { - border-top: 1px solid rgba(${({ theme }) => theme.foreground}, 0.05); - } - } -` - -/** @deprecated */ -export const PanelSection = styled(({ title, children, className, comingSoon, ...rest }: Props) => ( - - {!!title &&

{title}

} - {children} -
-))` - display: block; - padding: 1.55rem 2rem; - - h2 { - font-size: var(--font-size-xsmall); - font-weight: bold; - color: var(--color-mid); - margin-bottom: 1.4em; - } - - ${props => - props.comingSoon && - css` - padding: 6rem 2rem; - color: var(--color-mid); - text-align: center; - `} -` diff --git a/apps/portal/src/components/legacy/Pendor.tsx b/apps/portal/src/components/legacy/Pendor.tsx deleted file mode 100644 index bcbd73bf1..000000000 --- a/apps/portal/src/components/legacy/Pendor.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import { type PropsWithChildren, type ReactElement } from 'react' - -import Loader from '@/assets/icons/loader.svg?react' - -export type PendorProps = { - prefix?: React.ReactNode - suffix?: React.ReactNode - require?: boolean - loader?: ReactElement -} - -/** @deprecated */ -export const Pendor = ({ - prefix = '', - suffix = '', - require, - loader, - children, -}: PropsWithChildren): ReactElement => { - // undefined not set? await children - // require is explicitly set to false - if ((require === undefined && !children) || require === false) return loader ?? - - return ( - <> - {prefix} - {children} - {suffix} - - ) -} diff --git a/apps/portal/src/components/legacy/Pill.tsx b/apps/portal/src/components/legacy/Pill.tsx deleted file mode 100644 index b555c6f7d..000000000 --- a/apps/portal/src/components/legacy/Pill.tsx +++ /dev/null @@ -1,87 +0,0 @@ -import styled from '@emotion/styled' - -/** @deprecated */ -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export const Pill = styled(({ children, className, small, large, primary, secondary, active, ...rest }: any) => ( - - {children} - -))` - transition: all 0.2s; - line-height: 1em; - padding: 0.6em 1.2em; - display: inline-flex; - align-items: center; - border-radius: 4rem; - user-select: none; - background: rgb(${({ theme }) => theme?.background}); - color: rgb(${({ theme }) => theme?.foreground}); - box-shadow: 0 0 0.8rem rgba(0, 0, 0, 0.1); - font-size: 1em; - - * { - line-height: 1em; - } - - ${({ onClick }) => - !!onClick && - ` - cursor: pointer; - `} - - ${({ small }) => - small && - ` - font-size: 0.85em; - padding: 0.6em 0.9em; - `} - - ${({ large }) => - large && - ` - font-size: 1.1em; - padding: 0.6em 1.4em; - `} - - ${({ primary, onClick }) => - !!primary && - ` - background: var(--color-controlBackground); - color: var(--color-foreground); - box-shadow: none; - ${ - !!onClick && - ` - &:hover{ - background: var(--color-activeBackground); - color: var(--color-text); - } - ` - } - `} - - ${({ secondary, onClick }) => - !!secondary && - ` - background: var(--color-controlBackground); - color: var(--color-foreground); - box-shadow: none; - ${ - !!onClick && - ` - &:hover{ - background: var(--color-background); - color: var(--color-text); - } - ` - } - `} - - ${({ active, secondary, theme }) => - !!active && - ` - background: ${secondary ? `var(--color-activeBackground)` : `rgb(${theme?.primary as string})`}; - color: ${secondary ? `var(--color-text)` : `rgb(${theme?.background as string})`}; - box-shadow: none; - `} -` diff --git a/apps/portal/src/components/legacy/Poster.tsx b/apps/portal/src/components/legacy/Poster.tsx deleted file mode 100644 index b89a2a1fb..000000000 --- a/apps/portal/src/components/legacy/Poster.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import styled from '@emotion/styled' -import { type PropsWithChildren } from 'react' - -import useImageWithFallback from '@/util/useImageWithFallback' - -/** @deprecated */ -export const Poster = styled( - ({ - title, - subtitle, - backgroundImage, - fallbackBackgroundImage, - children, - className, - }: PropsWithChildren<{ - title?: string - subtitle?: string - backgroundImage?: string - fallbackBackgroundImage?: string - className?: string - }>) => { - const imageSrc = useImageWithFallback(backgroundImage, fallbackBackgroundImage) - - return ( -
- -

{title}

-

-
{children}
- -

- ) - } -)` - display: block; - width: 100%; - height: 24vw; - min-height: 34rem; - max-height: 60rem; - background-color: rgba(0, 0, 0, 0.1); - background-size: cover; - background-repeat: no-repeat; - background-position: 50% 50%; - position: relative; - padding: 3vw; - - .content { - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - text-align: center; - width: 100%; - - h1 { - margin: 0; - } - - h2 { - margin: 1.6vw 0 2.9vw; - line-height: 1.3em; - } - - > .children { - margin: 0; - } - } -` diff --git a/apps/portal/src/components/legacy/ProgressBar.tsx b/apps/portal/src/components/legacy/ProgressBar.tsx deleted file mode 100644 index f658a57b0..000000000 --- a/apps/portal/src/components/legacy/ProgressBar.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import styled from '@emotion/styled' - -/** @deprecated */ -export const ProgressBar = styled(({ percent, className }: { className?: string; percent?: number }) => ( -
- -
-))` - display: block; - height: 1em; - border-radius: 0.5em; - position: relative; - overflow: hidden; - background: rgb(${({ theme }) => theme.foreground}, 0.1); - color: rgb(${({ theme }) => theme.foreground}); - - > span { - content: ''; - position: absolute; - top: 0; - left: 0; - height: 1em; - background: rgb(${({ theme }) => theme.primary}); - transition: all 0.3s ease-out; - } -` diff --git a/apps/portal/src/components/legacy/RadioGroup.tsx b/apps/portal/src/components/legacy/RadioGroup.tsx deleted file mode 100644 index 9a4afb750..000000000 --- a/apps/portal/src/components/legacy/RadioGroup.tsx +++ /dev/null @@ -1,50 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ - -import styled from '@emotion/styled' - -import { FieldWrapper } from '@/components/legacy/FieldWrapper' -import { Pill } from '@/components/legacy/Pill' - -type RadioGroupProps = { - value: string - options?: any - className?: string - onChange?: (value: string) => void - small?: boolean - primary?: boolean - secondary?: boolean -} - -/** @deprecated */ -export const RadioGroup = styled( - ({ value, options = {}, onChange = () => {}, small, primary, secondary, className }: RadioGroupProps) => ( - - {options.map((option: Record) => ( - onChange(option?.['key'])} - active={option?.['key'] === value} - small={small} - primary={primary} - secondary={secondary} - > - {option?.['value']} - - ))} - - ) -)` - .children { - display: flex; - gap: 0.25rem; - box-shadow: none; - overflow: visible; - &:hover { - box-shadow: none; - } - - background: var(--color-controlBackground); - padding: 0.25rem; - border-radius: 1.5rem; - } -` diff --git a/apps/portal/src/components/legacy/Stat.tsx b/apps/portal/src/components/legacy/Stat.tsx deleted file mode 100644 index 9588a6ed0..000000000 --- a/apps/portal/src/components/legacy/Stat.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import styled from '@emotion/styled' -import { type PropsWithChildren, type ReactNode } from 'react' - -/** @deprecated */ -export const Stat = styled( - ({ title, children, className, ...rest }: PropsWithChildren<{ title: ReactNode; className?: string }>) => ( - - {!!title && {title}} - {children} - - ) -)` - display: flex; - justify-content: space-between; - align-items: center; - list-style: inherit; -` diff --git a/apps/portal/src/components/legacy/TalismanHandLike.tsx b/apps/portal/src/components/legacy/TalismanHandLike.tsx deleted file mode 100644 index 20b3b85c5..000000000 --- a/apps/portal/src/components/legacy/TalismanHandLike.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import { type ImgHTMLAttributes } from 'react' - -import TalismanHandLikeImg from '@/assets/thumbs_up_red.gif' - -/** @deprecated */ -export const TalismanHandLike = (props: ImgHTMLAttributes) => { - return Loading... -} diff --git a/apps/portal/src/components/legacy/widgets/CrowdloanBonus.tsx b/apps/portal/src/components/legacy/widgets/CrowdloanBonus.tsx deleted file mode 100644 index 1a2894ed5..000000000 --- a/apps/portal/src/components/legacy/widgets/CrowdloanBonus.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import type { ReactNode } from 'react' -import { useMemo } from 'react' - -import { useCrowdloanById } from '@/libs/talisman' -import { Maybe } from '@/util/monads' - -export type BonusProps = { - id: string - short?: boolean - full?: boolean - info?: boolean - prefix?: ReactNode -} - -/** @deprecated */ -export const CrowdloanBonus = ({ id, short, full, info, prefix }: BonusProps) => { - const bonus = useCrowdloanById(id).crowdloan?.details?.bonus - const type = short ? 'short' : full ? 'full' : info ? 'info' : undefined - - const content = useMemo(() => { - if (short) return bonus?.short - if (full) return Maybe.ofFalsy(bonus?.full).mapOrUndefined(x => {x}) - if (info) return bonus?.info - return undefined - }, [bonus, full, info, short]) - - if (!content || type === undefined) return null - - return ( - *': { - display: 'inline-block', - }, - '&.type-full': { - '> *': { - lineHeight: '1em', - verticalAlign: 'center', - display: 'inline-block', - }, - '.popup': { - marginLeft: '0.4rem', - '.icon-help': { - color: 'var(--color-primary)', - }, - }, - }, - }} - > - {prefix !== undefined && {prefix}} - {content} - - ) -} diff --git a/apps/portal/src/components/legacy/widgets/CrowdloanContribute.tsx b/apps/portal/src/components/legacy/widgets/CrowdloanContribute.tsx deleted file mode 100644 index 1f518f58a..000000000 --- a/apps/portal/src/components/legacy/widgets/CrowdloanContribute.tsx +++ /dev/null @@ -1,778 +0,0 @@ -import type { MouseEventHandler } from 'react' -import { useTheme } from '@emotion/react' -import styled from '@emotion/styled' -import { CircularProgressIndicator } from '@talismn/ui/atoms/CircularProgressIndicator' -import { Text } from '@talismn/ui/atoms/Text' -import { useCallback, useEffect, useMemo, useState } from 'react' -import { useTranslation } from 'react-i18next' -import { useRecoilValue } from 'recoil' - -import XCircle from '@/assets/icons/x-circle.svg?react' -import { Button } from '@/components/legacy/Button' -import { DesktopRequired } from '@/components/legacy/DesktopRequired' -import { Input } from '@/components/legacy/Input' -import { MaterialLoader } from '@/components/legacy/MaterialLoader' -import { useModal } from '@/components/legacy/Modal' -import { TalismanHandLike } from '@/components/legacy/TalismanHandLike' -import { TalismanHandLoader } from '@/components/legacy/TalismanHandLoader' -import { ParachainAsset } from '@/components/legacy/widgets/ParachainAsset' -import { useAccountSelector } from '@/components/widgets/AccountSelector' -import { writeableSubstrateAccountsState } from '@/domains/accounts/recoils' -import { ContributeEvent, useCrowdloanContribute } from '@/libs/crowdloans' -import { Acala, Moonbeam, overrideByIds, Polkadex } from '@/libs/crowdloans/crowdloanOverrides' -import { useCrowdloanById } from '@/libs/talisman' -import { isMobileBrowser } from '@/util/helpers' -import { Maybe } from '@/util/monads' - -export type ContributeProps = { - className?: string - id?: string -} - -/** @deprecated */ -export function CrowdloanContribute({ className, id }: ContributeProps) { - const { closeModal } = useModal() - - const [contributeState, dispatch] = useCrowdloanContribute() - - const { crowdloan } = useCrowdloanById(id) - useEffect(() => { - if (!id || !crowdloan) return - - const relayChainId = crowdloan.relayChainId - const parachainId = Number(crowdloan.parachain.paraId.split('-').slice(-1)[0]) - - dispatch(ContributeEvent.initialize({ crowdloanId: id, relayChainId, parachainId })) - }, [id, crowdloan, dispatch]) - - if (isMobileBrowser()) return - - return ( - <> - {contributeState.match({ - Uninitialized: () => null, - Initializing: () => , - - NoRpcsForRelayChain: () => 'Sorry, making contributions to this crowdloan via Talisman is not yet supported.', - NoChaindataForRelayChain: () => - 'Sorry, making contributions to this crowdloan via Talisman is not yet supported.', - IpBanned: () => 'Sorry, this crowdloan is not accepting contributions from IP addresses within your region.', - - Ready: props => ( - - ), - RegisteringUser: props => ( - - ), - ContributionSubmitting: props => ( - - ), - ContributionSuccess: props => ( - - ), - ContributionFailed: props => ( - - ), - })} - - ) -} - -const ContributeTo = styled( - ({ - className, - closeModal, - dispatch, - - relayChainId, - relayNativeToken, - parachainId, - parachainName, - - contributionAmount, - accountBalance, - email, - memoAddress, - - validationError, - submissionRequested, - }: // eslint-disable-next-line @typescript-eslint/no-explicit-any - any) => { - const theme = useTheme() - const { t } = useTranslation() - const { t: tError } = useTranslation('errors') - - const [chainHasTerms, termsAgreed, onTermsCheckboxClick] = useTerms(relayChainId, parachainId) - - const [[account], accountSelector] = useAccountSelector(useRecoilValue(writeableSubstrateAccountsState), 0) - - useEffect(() => { - dispatch(ContributeEvent.setAccount(account?.address)) - }, [dispatch, account]) - - return ( -
{ - event.preventDefault() - dispatch(ContributeEvent.contribute) - }} - > -
-

{t('Contribute to')}

- -

{parachainName}

-
-
-
- {accountSelector} -
- dispatch(ContributeEvent.setContributionAmount(amount))} - dim - type="text" - inputMode="numeric" - pattern="[.\d]*" - suffix={relayNativeToken} - disabled={submissionRequested} - /> - - { - - {/* eslint-disable-next-line @typescript-eslint/no-explicit-any */} - {Maybe.of(accountBalance).mapOr(, x => - Number(x).toFixed(2) - )}{' '} - available - - } - {validationError && ( - - {tError(validationError.i18nCode, validationError.vars)} - - )} - -
-
- {Moonbeam.is(relayChainId, parachainId) && ( -
-
- dispatch(ContributeEvent.setMemoAddress(memoAddress))} - dim - placeholder="Moonbeam Rewards Address" - disabled={submissionRequested} - /> -
- Enter the address where your rewards will be paid out. This must be a Moonbeam address or an Ethereum - address for which you have the private keys. If you do not have one,{' '} - - follow this tutorial - {' '} - to create one. Using an invalid account will result in a loss of funds. -
-
- Do not use an exchange address, an Ethereum address that is mapped to a Substrate - address, or any other kind of address where you do not have direct control over the private keys. This - will prevent you from being able to collect rewards in the future. -
-
- You can change your registered Moonbeam address by modifying this field and submiting a new - contribution. -
-
-
- )} - - {Acala.is(relayChainId, parachainId) && ( -
-
- dispatch(ContributeEvent.setEmail(email))} - dim - type="email" - placeholder="Email (optional)" - disabled={submissionRequested} - /> -
- All contributions via the Talisman dashboard are made to Acala's liquid crowdloan (lcDOT) offer and - are subject to the rewards and vesting schedule described - - here - -
-
-
- )} - - {Polkadex.is(relayChainId, parachainId) && ( -
-
-
- - If you have 1 PDEX in your Talisman account, you can contribute as much or as little DOT as you want - without worrying about the existential deposit. If you do not currently have any PDEX in your - Talisman account and wish to contribute less than 22 DOT, please buy at least 1 PDEX, so the account - has existential deposit requirement and is in active state to receive the reward. - -
-
-
- )} - - {chainHasTerms && ( -
-
-
- - -
-
-
- )} -
-
- - -
-
- ) - } -)` - max-width: 648px; - - > header { - display: flex; - flex-direction: column; - align-items: center; - } - > header > h2 { - text-align: center; - font-size: 2.4rem; - font-weight: 600; - margin-bottom: 2.4rem; - } - > header > .logo { - font-size: 6.4rem; - margin-bottom: 1.6rem; - user-select: none; - } - > header > h3 { - font-size: 1.8rem; - font-weight: 600; - margin-bottom: 4.8rem; - } - - > main > .row { - display: flex; - flex-direction: column; - margin-bottom: 3.4rem; - &:last-child { - margin-bottom: 4rem; - } - } - > main > .row.split { - flex-direction: row; - align-items: flex-start; - } - > main > .row > .amount-input { - width: 100%; - flex: 2 0 0%; - margin-right: 1.6rem; - - input { - font-size: 3.2rem; - font-weight: 600; - padding: 0.4rem 7rem 0.4rem 2.4rem; - - .suffix { - right: 2.4rem; - } - } - - > .info-row { - width: 100%; - margin-top: 1rem; - display: flex; - justify-content: space-between; - color: rgb(${({ theme }) => theme?.mid}); - font-size: var(--font-size-small); - - .error { - color: var(--color-status-error); - text-align: right; - margin-left: 1rem; - max-width: 75%; - } - } - - > .info-row.usd-and-error { - min-height: 2.2rem; - } - } - > main > .row > .switcher-column { - > .account-switcher-pill { - flex: 0 0 0%; - display: flex; - justify-content: center; - align-items: center; - height: 5.9rem; - padding: 0 0.5rem; - border-radius: 4rem; - background: rgb(${({ theme }) => theme?.background}); - color: rgb(${({ theme }) => theme?.foreground}); - box-shadow: 0 0 0.8rem rgba(0, 0, 0, 0.1); - } - > .tx-fee { - display: flex; - align-items: center; - justify-content: end; - white-space: pre; - width: 100%; - margin-top: 1rem; - text-align: right; - color: rgb(${({ theme }) => theme?.mid}); - font-size: var(--font-size-small); - min-height: 2.2rem; - } - } - > main > .row > .memo-address-input, - > main > .row > .email-input, - > main > .row > .verifier-input { - .field { - margin-bottom: 1.6rem; - - input { - font-size: 1.8rem; - &::placeholder { - color: #999; - } - } - } - .info { - color: #999; - font-size: 1.4rem; - line-height: 1.8rem; - - a { - color: var(--color-primary); - } - } - } - - > footer { - display: flex; - > * { - flex: 1 0 0%; - &:not(:last-child) { - margin-right: 1.6rem; - } - } - } -` - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export type ProgressProps = { className?: string; explorerUrl?: string; closeModal: () => unknown; error?: any } - -const InProgress = styled(({ className, explorerUrl, closeModal }: ProgressProps) => { - const { t } = useTranslation('crowdloan') - return ( -
-
-

{t('inProgress.header')}

- -
-
-
{t('inProgress.description')}
- {explorerUrl && ( - - {t('inProgress.primaryCta')} - - )} -
-
- -
-
- ) -})` - > header { - display: flex; - flex-direction: column; - align-items: center; - margin-bottom: 4rem; - } - > header > h2 { - text-align: center; - font-size: 2.4rem; - font-weight: 600; - } - > header > .logo { - font-size: 6.4rem; - margin-bottom: 8.2rem; - color: var(--color-primary); - user-select: none; - } - - > main { - display: flex; - flex-direction: column; - align-items: center; - margin-bottom: 4rem; - - div:first-child { - margin-bottom: 4rem; - } - a { - display: block; - color: var(--color-background); - background: var(--color-primary); - border-radius: 5.6rem; - padding: 0.6rem 1.2rem; - cursor: pointer; - } - } - - > footer { - display: flex; - justify-content: center; - - button { - min-width: 27.8rem; - } - } -` - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -const RegisteringUser = styled(({ className, dispatch, submissionRequested }: any) => { - const { t } = useTranslation('crowdloan') - - const terms = - 'https://glib-calendula-bf6.notion.site/Moonbeam-Crowdloan-Terms-and-Conditions-da2d8fe389214ae9a382a755110a6f45' - - return ( -
-
-

{t('registeringUser.header')}

-
-
- {submissionRequested ? ( - - ) : ( - <> -
{t('registeringUser.description')}
- - {t('registeringUser.termsNote')} - -
{t('registeringUser.feeNote')}
- - )} -
-
- -
-
- ) -})` - > header { - display: flex; - flex-direction: column; - align-items: center; - } - > header > h2 { - text-align: center; - font-size: 2.4rem; - font-weight: 600; - margin-bottom: 8.2rem; - } - > header > .logo { - font-size: 6.4rem; - margin-bottom: 8.2rem; - color: var(--color-primary); - user-select: none; - } - - > main { - display: flex; - flex-direction: column; - align-items: center; - margin-bottom: 2rem; - - > * { - margin-bottom: 2rem; - } - - > a { - color: var(--color-primary); - } - - div:nth-child(3) { - font-size: 1.5rem; - color: var(--color-mid); - font-style: italic; - } - - > .loading { - margin-top: 0; - margin-bottom: 6.2rem; - } - - > button { - min-height: 7rem; - } - } - - > footer { - display: flex; - justify-content: center; - - button { - min-width: 27.8rem; - } - } -` - -const Success = styled(({ className, closeModal, explorerUrl }: ProgressProps) => { - const { t } = useTranslation('crowdloan') - return ( -
-
-

{t('success.header')}

- -
-
-
{t('success.description')}
- {explorerUrl && ( - - {t('success.primaryCta')} - - )} -
-
- -
-
- ) -})` - > header { - display: flex; - flex-direction: column; - align-items: center; - margin-bottom: 4rem; - } - > header > h2 { - text-align: center; - font-size: 2.4rem; - font-weight: 600; - } - > header > .logo { - font-size: 6.4rem; - margin-bottom: 8.2rem; - color: var(--color-primary); - user-select: none; - } - - > main { - display: flex; - flex-direction: column; - align-items: center; - margin-bottom: 4rem; - - div:first-child { - margin-bottom: 4rem; - } - a { - display: block; - color: var(--color-background); - background: var(--color-primary); - border-radius: 5.6rem; - padding: 0.6rem 1.2rem; - cursor: pointer; - } - } - - > footer { - display: flex; - justify-content: center; - - button { - min-width: 27.8rem; - } - } -` - -const Failed = styled(({ className, explorerUrl, error, closeModal }: ProgressProps) => { - const { t } = useTranslation('crowdloan') - return ( -
-
-

{t('failed.header')}

- -
-
-
-
{t('failed.description')}
- {error &&
{error}
} -
- {explorerUrl && ( - - {t('failed.primaryCta')} - - )} -
-
- -
-
- ) -})` - > header { - display: flex; - flex-direction: column; - align-items: center; - } - > header > h2 { - text-align: center; - font-size: 2.4rem; - font-weight: 600; - margin-bottom: 8.2rem; - } - > header > .logo { - font-size: 6.4rem; - margin-bottom: 8.2rem; - color: var(--color-primary); - user-select: none; - } - - > main { - display: flex; - flex-direction: column; - align-items: center; - margin-bottom: 4rem; - - > div:first-child { - margin-bottom: 4rem; - text-align: center; - } - .error { - color: var(--color-status-error); - } - a { - display: block; - color: #f46545; - background: #fbe2dc; - border-radius: 5.6rem; - padding: 0.6rem 1.2rem; - cursor: pointer; - } - } - - > footer { - display: flex; - justify-content: center; - - button { - min-width: 27.8rem; - } - } -` - -const Loading = styled(MaterialLoader)` - font-size: 6.4rem; - margin: 4rem auto; - color: var(--color-primary); - user-select: none; -` - -function useTerms(relayId: number, paraId: number): [boolean, boolean, MouseEventHandler] { - const chainHasTerms = useMemo(() => overrideByIds(relayId, paraId)?.terms !== undefined, [paraId, relayId]) - - const [termsAgreed, setTermsAgreed] = useState(!chainHasTerms) - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const onTermsCheckboxClick = useCallback((event: any) => setTermsAgreed(event.target.checked), []) - - return [chainHasTerms, termsAgreed, onTermsCheckboxClick] -} diff --git a/apps/portal/src/components/legacy/widgets/CrowdloanCountdown.tsx b/apps/portal/src/components/legacy/widgets/CrowdloanCountdown.tsx deleted file mode 100644 index 5a383e513..000000000 --- a/apps/portal/src/components/legacy/widgets/CrowdloanCountdown.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import type { ReactNode } from 'react' -import styled from '@emotion/styled' -import { useEffect, useState } from 'react' - -import { Countdown as Cd } from '@/components/legacy/Countdown' -import { Pendor } from '@/components/legacy/Pendor' -import { useChainmetaValue, useCrowdloanById } from '@/libs/talisman' - -type OngoingProps = { - end?: number - showSeconds?: boolean - className?: string - relayChainId?: number -} - -const Ongoing = ({ end, showSeconds, relayChainId, className = '' }: OngoingProps) => { - const [secondsRemaining, setSecondsRemaining] = useState() - const blockNumber = useChainmetaValue(relayChainId!, 'blockNumber') - const blockPeriod = useChainmetaValue(relayChainId!, 'blockPeriod') - - useEffect(() => { - if (!end || !blockNumber || !blockPeriod) return - setSecondsRemaining((end - blockNumber) * blockPeriod) - }, [end, blockNumber, blockPeriod]) - - return ( - -
- -
-
- ) -} - -const Generic = styled(({ text, className }: { className?: string; text: ReactNode }) => ( - {text} -))` - display: flex; - align-items: center; -` - -type CountdownProps = { - id?: string - end?: number - showSeconds?: boolean - className?: string -} - -/** @deprecated */ -export const CrowdloanCountdown = ({ id, showSeconds, className, ...rest }: CountdownProps) => { - const { crowdloan } = useCrowdloanById(id) - - // Pendor - if (!crowdloan) return - - const { uiStatus, end } = crowdloan - - if (['active', 'capped'].includes(uiStatus)) { - return - } - if (uiStatus === 'winner') return - if (uiStatus === 'ended') return - - // Pendor - return -} diff --git a/apps/portal/src/components/legacy/widgets/CrowdloanIndex.tsx b/apps/portal/src/components/legacy/widgets/CrowdloanIndex.tsx deleted file mode 100644 index f6f36fc21..000000000 --- a/apps/portal/src/components/legacy/widgets/CrowdloanIndex.tsx +++ /dev/null @@ -1,124 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unused-vars */ - -import styled from '@emotion/styled' -import { SearchBar } from '@talismn/ui/molecules/SearchBar' -import { useTranslation } from 'react-i18next' - -import { Await } from '@/components/legacy/Await' -import { Grid } from '@/components/legacy/Grid' -import { NoResults } from '@/components/legacy/NoResults' -import { RadioGroup } from '@/components/legacy/RadioGroup' -import { useCrowdloanContributions } from '@/libs/crowdloans' -import { device } from '@/util/breakpoints' - -import { CrowdloanRootNav } from './CrowdloanRootNav' -import { CrowdloanTeaser } from './CrowdloanTeaser' -import { useCrowdloanFilter } from './useCrowdloanFilter' - -const FilterBar = styled( - ({ - search = '', - order = '', - status = null, - network = null, - setSearch = () => {}, - setOrder = () => {}, - setStatus = () => {}, - setNetwork = () => {}, - orderOptions = {}, - statusOptions = {}, - networkOptions = {}, - hasFilter = false, - reset, - count, - className, - ...rest - }: any) => { - const { t } = useTranslation() - return ( -
- { - setSearch(search) - }} - /> -
- { - setStatus(status) - }} - options={statusOptions} - small - secondary - /> - { - setNetwork(network) - }} - options={networkOptions} - small - primary - /> -
-
- ) - } -)` - margin: 2.4rem 0; - display: flex; - align-items: center; - gap: 2rem; - flex-wrap: wrap; - - .searchbar { - display: inline-block; - width: 100%; - @media ${device.lg} { - width: auto; - } - } - - .filters { - display: flex; - flex-wrap: wrap; - gap: 1rem; - justify-content: space-between; - width: 100%; - } -` - -/** @deprecated */ -export const CrowdloanIndex = styled(({ withFilter, className }: { withFilter: boolean; className?: string }) => { - const { t } = useTranslation() - const { crowdloans, count, loading, filterProps } = useCrowdloanFilter() - const { gqlContributions } = useCrowdloanContributions() - - return ( -
- - - {/* TODO: Remove for now as no Learn more link yet. */} - {/* */} - {withFilter && } - - 0} subtitle={t('noCrowdloans.text')} text={t('noCrowdloans.subtext')}> - - {crowdloans.map(({ id }) => ( - x.id === id) !== undefined} /> - ))} - - - -
- ) -})` - padding: 2.4rem; - - .await { - font-size: var(--font-size-xxlarge); - } -` diff --git a/apps/portal/src/components/legacy/widgets/CrowdloanRaised.tsx b/apps/portal/src/components/legacy/widgets/CrowdloanRaised.tsx deleted file mode 100644 index b09887895..000000000 --- a/apps/portal/src/components/legacy/widgets/CrowdloanRaised.tsx +++ /dev/null @@ -1,102 +0,0 @@ -import styled from '@emotion/styled' -import { CheckCircle } from '@talismn/web-icons' -import { useTranslation } from 'react-i18next' - -import { Pendor } from '@/components/legacy/Pendor' -import { ProgressBar } from '@/components/legacy/ProgressBar' -import { Stat } from '@/components/legacy/Stat' -import { useCrowdloanById } from '@/libs/talisman' -import { shortNumber } from '@/util/helpers' - -/** @deprecated */ -export const CrowdloanRaised = styled( - ({ - id, - title, - contributed, - className, - }: { - id: string - title?: string - contributed?: boolean - className?: string - }) => { - const { crowdloan: { percentRaised, raised, cap, uiStatus } = {} } = useCrowdloanById(id) - const { t } = useTranslation() - - const suffix = (id || '').startsWith('0-') ? ' DOT' : ' KSM' - - return ( -
-
- {uiStatus === 'capped' ? `${t('Goal reached')} ✓` : title} - - {contributed && ( - <> - {t('Contributed')} - - )} - -
- - - - - {shortNumber(raised ?? 0)} / {shortNumber(cap ?? 0)} - - } - > - - {percentRaised?.toFixed(2)} - - -
- ) - } -)` - .top { - display: flex; - align-items: center; - justify-content: space-between; - font-size: var(--font-size-small); - margin-bottom: 0.5em; - - * { - font-size: var(--font-size-small); - margin: 0; - - &:first-child { - opacity: 0.4; - } - - &:last-child { - display: flex; - align-items: center; - > svg { - margin-right: 0.4em; - font-size: 1.2em; - color: var(--color-primary); - } - } - } - } - - > .stat { - color: var(--color-text); - margin-top: 0.7rem; - } - - &[data-status='capped'] h3 { - color: var(--color-status-success); - opacity: 0.9; - } - &[data-status='capped'] > .progress-bar { - opacity: 0.6; - } - - &[data-status='winner'] { - opacity: 0.6; - } -` diff --git a/apps/portal/src/components/legacy/widgets/CrowdloanRewards.tsx b/apps/portal/src/components/legacy/widgets/CrowdloanRewards.tsx deleted file mode 100644 index 899af6fe3..000000000 --- a/apps/portal/src/components/legacy/widgets/CrowdloanRewards.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import styled from '@emotion/styled' - -import { Stat } from '@/components/legacy/Stat' -import { useCrowdloanById } from '@/libs/talisman' - -/** @deprecated */ -export const CrowdloanRewards = styled(({ id, className }: { id?: string; className?: string }) => { - const { crowdloan } = useCrowdloanById(id) - const details = crowdloan?.details - - return ( -
- {details?.rewards?.tokens && ( - - {details?.rewards?.tokens} - - )} - {details?.customRewards?.map(({ title, value }, index) => ( - - {value} - - ))} - {details?.rewards?.info && ( - <> -
-

- - )} -
- ) -})` - padding: 2.2rem 2.4rem; - - > .stat & + .stat { - margin-top: 0.7rem; - } - - > hr { - margin: 1em 0; - } - - > p { - font-size: 0.8em; - opacity: 0.8; - - &:last-child { - margin-bottom: 0; - } - } -` diff --git a/apps/portal/src/components/legacy/widgets/CrowdloanRootNav.tsx b/apps/portal/src/components/legacy/widgets/CrowdloanRootNav.tsx deleted file mode 100644 index c59c06640..000000000 --- a/apps/portal/src/components/legacy/widgets/CrowdloanRootNav.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { css, useTheme } from '@emotion/react' -import { NavLink } from 'react-router-dom' - -/** @deprecated */ -export const CrowdloanRootNav = () => { - const theme = useTheme() - - return ( -
a { - display: inline-block; - padding-bottom: 0.4em; - border-bottom: 1px solid transparent; - margin-bottom: -1px; - } - - & > a.active { - color: ${theme.color.primary}; - border-color: ${theme.color.primary}; - } - `} - > - - Participated - - - Crowdloans - -
- ) -} diff --git a/apps/portal/src/components/legacy/widgets/CrowdloanTeaser.tsx b/apps/portal/src/components/legacy/widgets/CrowdloanTeaser.tsx deleted file mode 100644 index ff1c3cb0a..000000000 --- a/apps/portal/src/components/legacy/widgets/CrowdloanTeaser.tsx +++ /dev/null @@ -1,107 +0,0 @@ -import styled from '@emotion/styled' -import { useTranslation } from 'react-i18next' -import { Link } from 'react-router-dom' - -import { Pill } from '@/components/legacy/Pill' -import { useCrowdloanById, useParachainDetailsById } from '@/libs/talisman' - -import { CrowdloanBonus } from './CrowdloanBonus' -import { CrowdloanCountdown } from './CrowdloanCountdown' -import { CrowdloanRaised } from './CrowdloanRaised' -import { ParachainAsset } from './ParachainAsset' - -/** @deprecated */ -export const CrowdloanTeaser = styled( - ({ id, contributed, className }: { id: string; contributed?: boolean; className?: string }) => { - const { t } = useTranslation() - const { crowdloan } = useCrowdloanById(id) - const parachainId = crowdloan?.parachain?.paraId - const { parachainDetails } = useParachainDetailsById(parachainId) - - return ( - - -
-
- - } /> -
-

{parachainDetails?.name}

- -
- - - - - - ) - } -)` - display: block; - background: var(--color-controlBackground); - overflow: hidden; - border-radius: 2.4rem; - position: relative; - - > .crowdloan-card { - width: 100%; - height: 0; - padding-top: 58.4%; - } - - > .content { - position: relative; - padding: 0 1.6rem 1rem 1.6rem; - - > .header { - display: flex; - align-items: center; - justify-content: space-between; - margin-top: -3rem; - - > .crowdloan-logo { - width: 6.4rem; - height: 6.4rem; - padding-top: 0; - box-shadow: 0px 0px 8px rgba(0, 0, 0, 0.1); - } - - .crowdloan-bonus { - display: flex; - align-items: center; - border-radius: 2em; - padding: 0.3em 0.8em; - font-size: 1.4rem; - background: var(--color-dark); - - .crowdloan-logo { - font-size: 1.4rem; - margin-right: 0.5em; - } - } - } - - h1 { - margin: 0; - font-size: var(--font-size-large); - font-weight: 600; - margin-top: 1.2rem; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } - } - - > .countdown { - position: absolute; - top: 1.6rem; - right: 1.6rem; - background: var(--color-activeBackground); - color: var(--color-mid); - } - - .crowdloan-raised { - font-size: 0.9em; - margin-top: calc(1.5rem + 1.5vw); - } -` diff --git a/apps/portal/src/components/legacy/widgets/ExploreCard.tsx b/apps/portal/src/components/legacy/widgets/ExploreCard.tsx deleted file mode 100644 index e1e525e59..000000000 --- a/apps/portal/src/components/legacy/widgets/ExploreCard.tsx +++ /dev/null @@ -1,181 +0,0 @@ -import styled from '@emotion/styled' -import { usePostHog } from 'posthog-js/react' -import { useCallback } from 'react' - -import { device } from '@/util/breakpoints' - -import { type Dapp } from './useFetchDapps' - -type CardProps = { - className?: string - dapp: Dapp - setSelectedTag: (tag: string) => unknown -} - -/** @deprecated */ -export const ExploreCard = styled(({ className, dapp, setSelectedTag }: CardProps) => { - const posthog = usePostHog() - - const toExternalDapp = useCallback( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - (dapp: any) => { - const categories = dapp.tags.reduce( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - (acc: any, tag: string) => ({ - ...acc, - [`category_${tag.replace(/[^\w]/, '')}'`]: true, - }), - {} - ) - - posthog?.capture('Goto Dapp', { dappName: dapp.name, dappUrl: dapp.url, ...categories }) - window.open(dapp.url, '_blank', 'noopener,noreferrer') - }, - [posthog] - ) - - return ( -
toExternalDapp(dapp)}> -
- {dapp.name - {dapp.name -
-
- -

{dapp.name}

-

{dapp.description}

-
- - {!!dapp.tags && - // eslint-disable-next-line @typescript-eslint/no-explicit-any - dapp.tags.map((tag: any) => ( - { - event.stopPropagation() - setSelectedTag(tag) - }} - > - {tag} - - ))} - -
-
- ) -})` - cursor: pointer; - background: #1e1e1e; - border-radius: 1rem; - border: 1px solid transparent; - overflow: hidden; - grid-column: span 3; - display: flex; - flex-direction: column; - justify-content: space-between; - transition: 0.2s; - .card__header { - min-height: 175px; - max-height: 175px; - overflow: hidden; - position: relative; - } - .logo { - position: absolute; - width: 100%; - height: 100%; - object-fit: contain; - padding: 1.5em; - } - .logoBG { - position: absolute; - top: 0; - left: 0; - filter: blur(150px) saturate(3); - height: 100%; - width: 100%; - } - .card-body { - flex-grow: 2; - justify-content: space-between; - - display: flex; - flex-direction: column; - - padding: 2rem; - h3 { - font-size: 2rem; - } - p { - font-size: 1.5rem; - color: var(--color-mid); - } - a { - background: #ffbd00; - border-radius: 0.5rem; - padding: 0.5rem 1rem; - color: #1e1e1e; - font-weight: bold; - text-decoration: none; - } - .tag { - font-size: 1rem; - margin: 0.5rem 0.5rem 0 0; - display: inline-block; - padding: 0.5rem 1rem; - background: var(--color-activeBackground); - border-radius: 1rem; - color: var(--color-mid); - transition: 0.2s; - } - - display: flex; - flex-direction: column; - - padding: 2rem; - h3 { - font-size: 2rem; - } - p { - font-size: 1.5rem; - color: var(--color-mid); - } - a { - background: #ffbd00; - border-radius: 0.5rem; - padding: 0.5rem 1rem; - color: #1e1e1e; - font-weight: bold; - text-decoration: none; - } - .tag { - font-size: 1rem; - margin: 0.5rem 0.5rem 0 0; - display: inline-block; - padding: 0.5rem 1rem; - background: var(--color-activeBackground); - border-radius: 1rem; - color: var(--color-mid); - transition: 0.2s; - } - - .tag:hover { - background: var(--color-dim); - transition: 0.2s; - } - } - height: 450px; - - :nth-child(-n + 3) { - grid-column: span 3; - @media ${device.lg} { - grid-column: span 4; - } - } - - :hover { - border: 1px solid rgb(90, 90, 90); - transition: 0.2s; - } -` diff --git a/apps/portal/src/components/legacy/widgets/ExploreLoading.tsx b/apps/portal/src/components/legacy/widgets/ExploreLoading.tsx deleted file mode 100644 index 7a2a4c147..000000000 --- a/apps/portal/src/components/legacy/widgets/ExploreLoading.tsx +++ /dev/null @@ -1,146 +0,0 @@ -import styled from '@emotion/styled' - -import { device } from '@/util/breakpoints' - -/** @deprecated */ -export const ExploreTagLoading = styled(({ className }: { className?: string }) => { - return ( -
-
Talisman is the coolest 🍜. The explore page will help you find all the Dapps/Daos and Applications.
-
- ) -})` - display: flex; - flex-direction: row; - flex-wrap: wrap; - width: 87vw; - margin-bottom: 2rem; - - @media ${device.md} { - width: 100%; - } - - > div { - font-size: 1.25rem; - margin: 0.5rem 0.5rem 0 0; - display: flex; - align-items: center; - justify-content: center; - padding: 0.5rem 1rem; - border-radius: 1rem; - cursor: pointer; - transition: 0.2s; - color: transparent; - - animation: shimmer 1s infinite; - background: linear-gradient(90deg, rgba(30, 30, 30, 1) 4%, rgba(60, 60, 60, 1) 25%, rgba(30, 30, 30, 1) 36%); - background-size: 4000px 100%; - } - - @keyframes shimmer { - 0% { - background-position: -600px 0; - } - 100% { - background-position: 1000px 0; - } - } -` - -/** @deprecated */ -export const ExploreCardLoading = styled(({ className, isLoading }: { className?: string; isLoading?: boolean }) => { - return ( -
- {Array.from({ length: 3 }).map((_, i) => ( - - ))} -
- ) -})` - display: grid; - - grid-template-columns: repeat(3, 1fr); - - @media ${device.sm} { - grid-template-columns: repeat(3, 1fr); - width: 87vw; - } - - @media ${device.md} { - grid-template-columns: repeat(3, 1fr); - width: 100%; - } - - @media ${device.lg} { - grid-template-columns: repeat(12, 1fr); - } - - grid-gap: 2.5rem; -` - -const Card = styled(({ className }: { className?: string; isLoading?: boolean }) => { - return ( -
-
-
-
- ) -})` - cursor: pointer; - background: #1e1e1e; - border-radius: 1rem; - overflow: hidden; - grid-column: span 3; - display: flex; - flex-direction: column; - justify-content: space-between; - transition: 0.2s; - - .card-body { - flex-grow: 2; - justify-content: space-between; - - display: flex; - flex-direction: column; - - animation: ${props => (props.isLoading ? 'shimmer 1s infinite' : 'none')}; - background: ${props => - props.isLoading - ? 'linear-gradient(90deg, rgba(30, 30, 30, 1) 4%, rgba(60, 60, 60, 1) 25%, rgba(30, 30, 30, 1) 36%)' - : '#1e1e1e'}; - background-size: 2000px 100%; - - padding: 2rem; - } - - .card-header { - height: 175px; - overflow: hidden; - position: relative; - - animation: ${props => (props.isLoading ? 'shimmer 1s infinite' : 'none')}; - background: ${props => - props.isLoading - ? 'linear-gradient(90deg, rgba(24, 24, 24, 1) 4%, rgba(50, 50, 50, 1) 25%, rgba(24, 24, 24, 1) 36%);' - : '#222'}; - background-size: 2000px 100%; - } - - @keyframes shimmer { - 0% { - background-position: -1000px 0; - } - 100% { - background-position: 400px 0; - } - } - - height: 450px; - - :nth-child(-n + 3) { - grid-column: span 3; - @media ${device.lg} { - grid-column: span 4; - } - } -` diff --git a/apps/portal/src/components/legacy/widgets/ParachainAsset.tsx b/apps/portal/src/components/legacy/widgets/ParachainAsset.tsx deleted file mode 100644 index 5c2fa9864..000000000 --- a/apps/portal/src/components/legacy/widgets/ParachainAsset.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import styled from '@emotion/styled' - -import { useParachainAssets } from '@/libs/talisman' -import useImageWithFallback from '@/util/useImageWithFallback' - -type ImageProps = { - id: string - className?: string - type: string -} - -const fallbackMap = { - card: 'https://raw.githubusercontent.com/TalismanSociety/chaindata/v3/assets/promo/generic-card.png', - logo: 'https://raw.githubusercontent.com/TalismanSociety/chaindata/v3/assets/tokens/unknown.svg', -} - -/** @deprecated */ -export const ParachainAsset = styled(({ id, type, className }: ImageProps) => { - const assets = useParachainAssets(id) - const imageSrc = useImageWithFallback(assets[type], fallbackMap[type as keyof typeof fallbackMap]) - return ( -
- ) -})` - &[data-type='logo'] { - font-size: ${({ size = 8 }: { size?: number }) => `${size}rem`}; - width: 1em; - height: 1em; - border-radius: 50%; - display: block; - } -` diff --git a/apps/portal/src/components/legacy/widgets/ParachainLinks.tsx b/apps/portal/src/components/legacy/widgets/ParachainLinks.tsx deleted file mode 100644 index 447cc50eb..000000000 --- a/apps/portal/src/components/legacy/widgets/ParachainLinks.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import styled from '@emotion/styled' - -import { Pill } from '@/components/legacy/Pill' -import { useParachainDetailsById } from '@/libs/talisman' - -export type LinksProps = { - id: number | string - className?: string -} - -/** @deprecated */ -export const ParachainLinks = styled(({ id, className }: LinksProps) => { - const { parachainDetails: { links = {} } = {} } = useParachainDetailsById(id) - - return ( -
- {Object.keys(links).map((name, index) => ( - - null}> - {name} - - - ))} -
- ) -})` - display: flex; - flex-wrap: wrap; - gap: 1rem; - align-items: center; -` diff --git a/apps/portal/src/components/legacy/widgets/WalletCrowdloans.tsx b/apps/portal/src/components/legacy/widgets/WalletCrowdloans.tsx deleted file mode 100644 index 3f9c06923..000000000 --- a/apps/portal/src/components/legacy/widgets/WalletCrowdloans.tsx +++ /dev/null @@ -1,302 +0,0 @@ -import type { SkeletonProps } from '@talismn/ui/atoms/Skeleton' -import { css } from '@emotion/react' -import styled from '@emotion/styled' -import { Chip, TonalChip } from '@talismn/ui/atoms/Chip' -import { Skeleton } from '@talismn/ui/atoms/Skeleton' -import { useSurfaceColor } from '@talismn/ui/atoms/Surface' -import { Text } from '@talismn/ui/atoms/Text' -import { ListItem } from '@talismn/ui/molecules/ListItem' -import { encodeAnyAddress, planckToTokens } from '@talismn/util' -import { Clock, Eye, Lock } from '@talismn/web-icons' -import BigNumber from 'bignumber.js' -import { Suspense, useCallback, useMemo } from 'react' -import { useTranslation } from 'react-i18next' -import { Link, useNavigate } from 'react-router-dom' -import { useRecoilValue, useRecoilValueLoadable } from 'recoil' - -import type { GqlContribution } from '@/libs/crowdloans' -import { Countdown } from '@/components/legacy/Countdown' -import { PanelSection } from '@/components/legacy/Panel' -import { Pendor } from '@/components/legacy/Pendor' -import { ParachainAsset } from '@/components/legacy/widgets/ParachainAsset' -import { AccountIcon } from '@/components/molecules/AccountIcon' -import { SectionHeader } from '@/components/molecules/SectionHeader' -import { AnimatedFiatNumber } from '@/components/widgets/AnimatedFiatNumber' -import { RedactableBalance } from '@/components/widgets/RedactableBalance' -import { WithdrawCrowdloanWidget } from '@/components/widgets/WithdrawCrowdloanWidget' -import { selectedSubstrateAccountsState, substrateAccountsState } from '@/domains/accounts/recoils' -import { chainsState, tokenPriceState } from '@/domains/chains/recoils' -import { useTotalCrowdloanTotalFiatAmount } from '@/domains/crowdloans/hooks' -import crowdloanDataState from '@/libs/@talisman-crowdloans/provider' -import { useCrowdloanContributions } from '@/libs/crowdloans' -import { calculateGqlCrowdloanPortfolioAmounts, useTaggedAmountsInPortfolio } from '@/libs/portfolio' -import { useParachainDetailsById } from '@/libs/talisman' -import { supportedRelayChainsState } from '@/libs/talisman/util/_config' -import { formatCommas, truncateAddress } from '@/util/helpers' - -const GqlCrowdloanItem = styled( - ({ contribution, className }: { contribution: GqlContribution; className?: string }) => { - const { t } = useTranslation() - const surfaceColor = useSurfaceColor() - - const accounts = useRecoilValue(substrateAccountsState) - const account = useMemo( - () => accounts.find(account => encodeAnyAddress(account.address) === encodeAnyAddress(contribution.account.id)), - [accounts, contribution.account] - ) - - const crowdloans = useRecoilValue(crowdloanDataState) - - const paraId = contribution.crowdloan.paraId - const relayChains = useRecoilValue(supportedRelayChainsState) - const relayChain = relayChains.find(chain => chain.genesisHash === contribution.relay.genesisHash) - const chain = crowdloans.find(x => x.id === `${relayChain?.id ?? NaN}-${paraId}`) - - const { tokenSymbol: relayNativeToken, coingeckoId, tokenDecimals: relayTokenDecimals } = relayChain ?? {} - const { name } = chain ?? {} - - const priceLoadable = useRecoilValueLoadable(tokenPriceState({ coingeckoId: coingeckoId! })) - - const relayTokenPrice = priceLoadable.valueMaybe()?.toString() - const relayPriceLoading = priceLoadable.state === 'loading' - - const relayTokenSymbol = relayNativeToken ?? 'Planck' - const contributedTokens = planckToTokens(contribution.amount, relayTokenDecimals ?? 0) - const contributedUsd = new BigNumber(contributedTokens).times(relayTokenPrice ?? 0).toString() - - const portfolioAmounts = useMemo( - () => calculateGqlCrowdloanPortfolioAmounts([contribution], relayTokenDecimals, relayTokenPrice), - [contribution, relayTokenDecimals, relayTokenPrice] - ) - - useTaggedAmountsInPortfolio(portfolioAmounts) - - const parachainId = `${relayChain?.id ?? NaN}-${paraId}` - const { parachainDetails } = useParachainDetailsById(parachainId) - const linkToCrowdloan = parachainDetails?.slug ? `/crowdloans/${parachainDetails?.slug}` : `/crowdloans` - - const navigate = useNavigate() - const chains = useRecoilValue(chainsState) - const goToStaking = useCallback(() => { - const relayChainId = chains.find(({ genesisHash }) => genesisHash === relayChain?.genesisHash)?.id - if (relayChainId && account?.address) - navigate(`/staking?action=stake&chain=${relayChainId}&account=${account.address}&amount=${contributedTokens}`) - }, [account?.address, chains, contributedTokens, navigate, relayChain?.genesisHash]) - - // hide returned contributions which were unlocked more than 30 days ago - if (contribution.blockNumber === null || contribution.oldAndReturned) return null - - const actions = (() => { - if (contribution.isLocked) return null - if (account?.readonly) - return ( - <> -
- -
{t('Followed account')}
-
-
- {contribution.isFundsReturned &&
{t('Withdrawn')}
} - - ) - - if (contribution.isFundsReturned) - return ( - <> - {t('Stake')} -
-
{t('Withdrawn')}
- - ) - - return ( - <> - - {({ onToggleOpen }) => {t('Withdraw & Stake')}} - - - {({ onToggleOpen }) => {t('Withdraw')}} - - - ) - })() - - return ( -
- - } - headlineContent={ - name ?? [relayChain?.name, t('Parachain'), parachainId?.split('-')?.[1]].filter(Boolean).join(' ') - } - supportingContent={ - <> - - {account && } - -
- {account?.name ?? - truncateAddress(encodeAnyAddress(contribution.account.id, relayChain?.accountPrefix ?? 42))} -
-
-
- - } - trailingContent={ - <> - {contribution.isLocked && ( -
-
- {' '} - -
-
- )} -
- - - {contributedTokens && formatCommas(Number(contributedTokens))} {relayTokenSymbol} - {' '} - - - - - - {contributedUsd && } - - - -
- - } - css={{ backgroundColor: surfaceColor }} - /> - -
- {actions} -
-
- ) - } -)`` - -const GqlCrowdloanItemSkeleton = (props: SkeletonProps) => { - const surfaceColor = useSurfaceColor() - return ( - - } - headlineContent={ - - - - } - supportingContent={ - - - - } - trailingContent={ -
- - - - -
- } - /> -
- ) -} - -const SuspendableCrowdloans = ({ className }: { className?: string }) => { - const { t } = useTranslation() - const accounts = useRecoilValue(selectedSubstrateAccountsState) - const { sortedGqlContributions, hydrated: contributionsHydrated } = useCrowdloanContributions( - useMemo(() => accounts.map(x => x.address), [accounts]) - ) - const crowdloansUsd = useTotalCrowdloanTotalFiatAmount() - - return ( -
- } /> - {!contributionsHydrated ? ( - - ) : sortedGqlContributions.length < 1 ? ( - {t('You have not contributed to any recent crowdloans')} - ) : ( -
- {sortedGqlContributions.map(contribution => ( - - ))} -
- )} -
- ) -} - -/** @deprecated */ -export const WalletCrowdloans = ({ className }: { className?: string }) => ( - - - -) diff --git a/apps/portal/src/components/legacy/widgets/useCrowdloanFilter.tsx b/apps/portal/src/components/legacy/widgets/useCrowdloanFilter.tsx deleted file mode 100644 index 0cfd5b80b..000000000 --- a/apps/portal/src/components/legacy/widgets/useCrowdloanFilter.tsx +++ /dev/null @@ -1,153 +0,0 @@ -import { filter, find, orderBy } from 'lodash' -import { useEffect, useMemo, useState } from 'react' -import { useTranslation } from 'react-i18next' - -import { useLatestCrowdloans, useParachainsDetailsIndexedById } from '@/libs/talisman' - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -type Item = { crowdloan: any; parachainDetails?: any } - -const orderOptions = [ - { - key: 'raised_desc', - value: 'Raised', - cb: (items: Item[]) => orderBy(items, ['crowdloan.raised'], ['desc']), - }, - { - key: 'id_asc', - value: 'Oldest', - cb: (items: Item[]) => orderBy(items, ['crowdloan.blockNum'], ['asc']), - }, - { - key: 'id_desc', - value: 'Newest', - cb: (items: Item[]) => orderBy(items, ['crowdloan.blockNum'], ['desc']), - }, - { - key: 'name_asc', - value: 'A⇢Z', - cb: (items: Item[]) => orderBy(items, ['parachainDetails.name'], ['asc']), - }, - { - key: 'name_desc', - value: 'Z⇢A', - cb: (items: Item[]) => orderBy(items, ['parachainDetails.name'], ['desc']), - }, -] - -const statusOptions = [ - { - key: 'all', - value: 'All', - cb: (items: Item[]) => items, - }, - { - key: 'active', - value: 'Active', - cb: (items: Item[]) => filter(items, item => ['active', 'capped'].includes(item.crowdloan.uiStatus)), - }, - { - key: 'winner', - value: 'Winner', - cb: (items: Item[]) => filter(items, item => item.crowdloan.uiStatus === 'winner'), - }, - { - key: 'ended', - value: 'Ended', - cb: (items: Item[]) => filter(items, item => item.crowdloan.uiStatus === 'ended'), - }, -] - -const networkOptions = [ - { - key: 'all', - value: 'All', - cb: (items: Item[]) => filter(items, item => !!item?.parachainDetails?.id), - }, - { - key: 'dot', - value: 'Polkadot', - cb: (items: Item[]) => filter(items, item => item?.parachainDetails?.id.split('-')[0] === '0'), - }, - { - key: 'ksm', - value: 'Kusama', - cb: (items: Item[]) => filter(items, item => item?.parachainDetails?.id.split('-')[0] === '2'), - }, -] - -/** @deprecated */ -export const useCrowdloanFilter = () => { - const { t } = useTranslation('filters') - const { crowdloans, hydrated } = useLatestCrowdloans() - const { parachains } = useParachainsDetailsIndexedById() - const items = useMemo( - () => - crowdloans - .map(crowdloan => ({ crowdloan, parachainDetails: parachains[crowdloan.parachain.paraId] })) - .filter(({ parachainDetails }) => !!parachainDetails), - [crowdloans, parachains] - ) - - const [filteredItems, setFilteredItems] = useState([]) - const [searchFilter, setSearchFilter] = useState('') - const [orderFilter, setOrderFilter] = useState(orderOptions[0]?.key) - const [statusFilter, setStatusFilter] = useState(statusOptions[1]?.key) - const [networkFilter, setNetworkFilter] = useState(networkOptions[0]?.key) - const [loading, setLoading] = useState(true) - - // do searchy/filtery stuff - useEffect(() => { - if (!hydrated) return - - // filter by status - const byStatus = find(statusOptions, { key: statusFilter })?.cb(items) ?? [] - - // filter by network - const networkFilterCb = find(networkOptions, { key: networkFilter })?.cb - const byNetwork = networkFilterCb ? networkFilterCb(byStatus) : byStatus - - // searching - const bySearch = - searchFilter !== '' - ? byNetwork.filter(item => item.parachainDetails?.name.toLowerCase().includes(searchFilter.toLowerCase())) - : byNetwork - - // ordering - const orderCallback = find(orderOptions, { key: orderFilter })?.cb - const byOrder = orderCallback ? orderCallback(bySearch) : bySearch - - setFilteredItems(byOrder) - setLoading(false) - }, [hydrated, items, networkFilter, orderFilter, searchFilter, statusFilter]) - - const filteredCrowdloans = useMemo(() => filteredItems.map(({ crowdloan }) => crowdloan), [filteredItems]) - - return { - crowdloans: filteredCrowdloans, - loading, - count: { - total: crowdloans?.length, - filtered: filteredItems?.length, - }, - filterProps: { - search: searchFilter, - order: orderFilter, - status: statusFilter, - network: networkFilter, - setSearch: setSearchFilter, - setOrder: setOrderFilter, - setStatus: setStatusFilter, - setNetwork: setNetworkFilter, - orderOptions: orderOptions.map(({ key, value }) => ({ key, value: t(value) })), - statusOptions: statusOptions.map(({ key, value }) => ({ key, value: t(value) })), - networkOptions: networkOptions.map(({ key, value }) => ({ key, value: t(value) })), - hasFilter: statusFilter !== 'all' || searchFilter !== '', - reset: () => { - setSearchFilter('') - setStatusFilter(statusOptions[0]?.key) - setNetworkFilter(networkOptions[0]?.key) - }, - }, - } -} diff --git a/apps/portal/src/components/legacy/widgets/useFetchDapps.tsx b/apps/portal/src/components/legacy/widgets/useFetchDapps.tsx deleted file mode 100644 index 87f85052a..000000000 --- a/apps/portal/src/components/legacy/widgets/useFetchDapps.tsx +++ /dev/null @@ -1,77 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ - -import { useEffect, useState } from 'react' - -export type Dapp = { id: string; name: string; description: string; logoUrl: string; tags: string[] } - -/** @deprecated */ -export const useFetchDapps = () => { - const [dapps, setDapps] = useState([]) - const [loading, setLoading] = useState(true) - const [error, setError] = useState(undefined) - const [tags, setTags] = useState(['All', '⭐ Featured']) // Hardcoded Featured for now so it appears first. - - useEffect(() => { - const fetchDapps = async () => { - try { - void fetch(`https://api.baserow.io/api/database/rows/table/141541/?user_field_names=true`, { - method: 'GET', - headers: { - Authorization: `Token ${import.meta.env.VITE_BASEROW_EXPLORE_AUTH}`, - }, - }) - .then(async res => await res.json()) - .then((data: { results: any[] }) => { - // Define a type for each item - const items = data?.results - .map((item: any) => { - if (!item.name || !item.url || !item.logo?.[0]?.url) { - return undefined - } - - return { - id: item.id, - name: item.name, - description: item.description, - url: item.url, - tags: item.tags?.map((tag: any) => tag.value) ?? [], - envs: item.envs, - score: item.score, - logoUrl: item.logo[0].url, - } - }) - .filter(item => item !== undefined) - - setTags(prevTags => - Array.from(new Set([...prevTags, ...items.flatMap(item => item?.tags)])).sort((a, b) => { - if (a.match(/^other$/i)) return 1 - if (b.match(/^other$/i)) return -1 - return 0 - }) - ) - - const sortedItems = items.slice().sort((a: any, b: any) => { - if (a.tags.includes('⭐ Featured') && b.tags.includes('⭐ Featured')) { - return b.score - a.score - } else if (a.tags.includes('⭐ Featured')) { - return -1 - } else if (b.tags.includes('⭐ Featured')) { - return 1 - } else { - return b.score - a.score - } - }) as Dapp[] - - setDapps(sortedItems) - setLoading(false) - }) - } catch (error: unknown) { - setError(error) - } - } - - void fetchDapps() - }, []) - - return { dapps, loading, error, tags } -} diff --git a/apps/portal/src/components/recipes/AccountValueInfo.tsx b/apps/portal/src/components/recipes/AccountValueInfo.tsx index 44e06863c..2b69d1b95 100644 --- a/apps/portal/src/components/recipes/AccountValueInfo.tsx +++ b/apps/portal/src/components/recipes/AccountValueInfo.tsx @@ -7,7 +7,7 @@ import { type ReactNode } from 'react' import type { Account } from '@/domains/accounts/recoils' import { AccountIcon } from '@/components/molecules/AccountIcon' -import { shortenAddress } from '@/util/shortenAddress' +import { truncateAddress } from '@/util/truncateAddress' export type AccountValueInfoProps = { account?: Account @@ -63,7 +63,7 @@ export const AccountValueInfo = ({ account, balance }: AccountValueInfoProps) => css={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: '0.8rem', width: '100%' }} > - {account === undefined ? 'My accounts' : account.name ?? shortenAddress(account.address)} + {account === undefined ? 'My accounts' : account.name ?? truncateAddress(account.address)}
diff --git a/apps/portal/src/components/recipes/AddReadOnlyAccountDialog.tsx b/apps/portal/src/components/recipes/AddReadOnlyAccountDialog.tsx index a0e7f207b..c266f1d1f 100644 --- a/apps/portal/src/components/recipes/AddReadOnlyAccountDialog.tsx +++ b/apps/portal/src/components/recipes/AddReadOnlyAccountDialog.tsx @@ -11,7 +11,7 @@ import { TextInput } from '@talismn/ui/molecules/TextInput' import Loader from '@/assets/icons/loader.svg?react' import { isNilOrWhitespace } from '@/util/nil' -import { shortenAddress } from '@/util/shortenAddress' +import { truncateAddress } from '@/util/truncateAddress' export type AddReadOnlyAccountDialogProps = { open?: boolean @@ -33,7 +33,7 @@ const PopularAccount = (props: { address: string; name: string; description?: st } headlineContent={props.name} - supportingContent={props.description ?? shortenAddress(props.address)} + supportingContent={props.description ?? truncateAddress(props.address)} css={{ borderRadius: '1.2rem', backgroundColor: useSurfaceColor() }} /> @@ -71,7 +71,7 @@ export const AddReadOnlyAccountDialog = Object.assign( } headlineContent={isNilOrWhitespace(props.name) ? undefined : props.name} - supportingContent={shortenAddress(props.resultingAddress)} + supportingContent={truncateAddress(props.resultingAddress)} css={{ marginTop: '0.8rem', border: `2px solid ${theme.color.outlineVariant}`, borderRadius: '0.8rem' }} /> )} diff --git a/apps/portal/src/components/recipes/ExtrinsicDetailsSideSheet.tsx b/apps/portal/src/components/recipes/ExtrinsicDetailsSideSheet.tsx index edfd6c3e5..7cbe498ce 100644 --- a/apps/portal/src/components/recipes/ExtrinsicDetailsSideSheet.tsx +++ b/apps/portal/src/components/recipes/ExtrinsicDetailsSideSheet.tsx @@ -14,7 +14,7 @@ import { ObjectView } from 'react-object-view' import type { Account } from '@/domains/accounts/recoils' import { AccountIcon } from '@/components/molecules/AccountIcon' import { copyAddressToClipboard, copyExtrinsicHashToClipboard } from '@/domains/common/utils/clipboard' -import { shortenAddress } from '@/util/shortenAddress' +import { truncateAddress } from '@/util/truncateAddress' export type ExtrinsicDetailsSideSheetProps = { onRequestDismiss: () => unknown @@ -51,8 +51,8 @@ const AccountItem = (props: { account: Account }) => ( } - headlineContent={props.account.name ?? shortenAddress(props.account.address)} - supportingContent={props.account.name !== undefined && shortenAddress(props.account.address)} + headlineContent={props.account.name ?? truncateAddress(props.account.address)} + supportingContent={props.account.name !== undefined && truncateAddress(props.account.address)} onClick={() => { void copyAddressToClipboard(props.account.address) }} @@ -96,7 +96,7 @@ export const ExtrinsicDetailsSideSheet = (props: ExtrinsicDetailsSideSheetProps) }} > - {shortenAddress(props.hash, 6)} + {truncateAddress(props.hash, 6, 6)} @@ -121,7 +121,7 @@ export const ExtrinsicDetailsSideSheet = (props: ExtrinsicDetailsSideSheetProps) > {' '} - {shortenAddress(props.signer.address)} + {truncateAddress(props.signer.address)} diff --git a/apps/portal/src/components/recipes/StakePosition.tsx b/apps/portal/src/components/recipes/StakePosition.tsx index 6b1c136fd..b4ad73ede 100644 --- a/apps/portal/src/components/recipes/StakePosition.tsx +++ b/apps/portal/src/components/recipes/StakePosition.tsx @@ -20,7 +20,7 @@ import { AccountIcon } from '@/components/molecules/AccountIcon' import { AssetLogoWithChain } from '@/components/recipes/AssetLogoWithChain' import { StakeStatusIndicator } from '@/components/recipes/StakeStatusIndicator' import { Account } from '@/domains/accounts/recoils' -import { shortenAddress } from '@/util/shortenAddress' +import { truncateAddress } from '@/util/truncateAddress' import { StakePositionSkeleton } from './StakePosition.skeleton' @@ -247,7 +247,7 @@ export const StakePosition = Object.assign( }, }} > - {props.account.name ?? shortenAddress(props.account.address)} + {props.account.name ?? truncateAddress(props.account.address)}
@@ -479,7 +479,7 @@ export const StakePositionErrorBoundary = (props: StakePositionErrorBoundaryProp }, }} > - {props.account.name ?? shortenAddress(props.account.address)} + {props.account.name ?? truncateAddress(props.account.address)}
diff --git a/apps/portal/src/components/recipes/TransactionLineItem.tsx b/apps/portal/src/components/recipes/TransactionLineItem.tsx index 1b1a025d8..870623f08 100644 --- a/apps/portal/src/components/recipes/TransactionLineItem.tsx +++ b/apps/portal/src/components/recipes/TransactionLineItem.tsx @@ -12,7 +12,7 @@ import { Fragment, useMemo } from 'react' import type { Account } from '@/domains/accounts/recoils' import { AccountIcon } from '@/components/molecules/AccountIcon' import { getSubstrateModuleColor } from '@/util/getSubstrateModuleColor' -import { shortenAddress } from '@/util/shortenAddress' +import { truncateAddress } from '@/util/truncateAddress' type TokenAmount = { amount: string @@ -109,7 +109,7 @@ export const TransactionLineItem = (props: TransactionLineItemProps) => {
- {props.signer === undefined ? props.chain : props.signer.name ?? shortenAddress(props.signer.address)} + {props.signer === undefined ? props.chain : props.signer.name ?? truncateAddress(props.signer.address)} } headlineContent={props.name} - supportingContent={props.description ?? shortenAddress(props.address)} + supportingContent={props.description ?? truncateAddress(props.address)} css={{ borderRadius: '1.2rem', backgroundColor: useSurfaceColor() }} /> diff --git a/apps/portal/src/components/widgets/AccountConnectionGuard.tsx b/apps/portal/src/components/widgets/AccountConnectionGuard.tsx index 6e884daae..dce7b54f0 100644 --- a/apps/portal/src/components/widgets/AccountConnectionGuard.tsx +++ b/apps/portal/src/components/widgets/AccountConnectionGuard.tsx @@ -6,7 +6,7 @@ import { Welcome } from '@/components/recipes/Welcome' import { popularAccounts } from '@/domains/accounts/consts' import { lookupAccountAddressState, readOnlyAccountsState } from '@/domains/accounts/recoils' import { useHasActiveWalletConnection, useWalletConnectionInitialised } from '@/domains/extension/main' -import { shortenAddress } from '@/util/shortenAddress' +import { truncateAddress } from '@/util/truncateAddress' import { walletConnectionSideSheetOpenState } from './WalletConnectionSideSheet' @@ -42,7 +42,7 @@ export const AccountConnectionGuard = ({ children, noSuspense }: AccountConnecti popularAccounts={popularAccounts.map((x, index) => ( setLookupAddress(x.address)} diff --git a/apps/portal/src/components/widgets/AccountSelector.tsx b/apps/portal/src/components/widgets/AccountSelector.tsx index 9d6bcc38c..da6a960c2 100644 --- a/apps/portal/src/components/widgets/AccountSelector.tsx +++ b/apps/portal/src/components/widgets/AccountSelector.tsx @@ -11,7 +11,7 @@ import { AccountIcon } from '@/components/molecules/AccountIcon' import { walletConnectionSideSheetOpenState } from '@/components/widgets/WalletConnectionSideSheet' import { type Account } from '@/domains/accounts/recoils' import { useHasActiveWalletConnection } from '@/domains/extension/main' -import { shortenAddress } from '@/util/shortenAddress' +import { truncateAddress } from '@/util/truncateAddress' export type AccountSelectorProps = { accounts: Account[] @@ -68,12 +68,12 @@ export const AccountSelector = ({ leadingIcon={} supportingContent={ selectedAccount && selectedAccount.name - ? shortenAddress(encodeAnyAddress(selectedAccount.address, prefix)) + ? truncateAddress(encodeAnyAddress(selectedAccount.address, prefix)) : '' } headlineContent={ selectedAccount - ? selectedAccount.name ?? shortenAddress(encodeAnyAddress(selectedAccount.address, prefix)) + ? selectedAccount.name ?? truncateAddress(encodeAnyAddress(selectedAccount.address, prefix)) : '' } /> @@ -92,11 +92,11 @@ export const AccountSelector = ({

- {x.name ?? shortenAddress(encodeAnyAddress(x.address, prefix))} + {x.name ?? truncateAddress(encodeAnyAddress(x.address, prefix))}

{x.name ? (

- {shortenAddress(encodeAnyAddress(x.address, prefix))} + {truncateAddress(encodeAnyAddress(x.address, prefix))}

) : null}
diff --git a/apps/portal/src/components/widgets/AccountsManagementMenu.tsx b/apps/portal/src/components/widgets/AccountsManagementMenu.tsx index 6d1133ed5..ab21eb58b 100644 --- a/apps/portal/src/components/widgets/AccountsManagementMenu.tsx +++ b/apps/portal/src/components/widgets/AccountsManagementMenu.tsx @@ -28,7 +28,7 @@ import { fiatBalanceGetterState, portfolioBalancesFiatSumState } from '@/domains import { copyAddressToClipboard } from '@/domains/common/utils/clipboard' import { useOnChainId } from '@/libs/onChainId/hooks/useOnChainId' import { Maybe } from '@/util/monads' -import { shortenAddress } from '@/util/shortenAddress' +import { truncateAddress } from '@/util/truncateAddress' const EvmChip = () => { const theme = useTheme() @@ -78,7 +78,7 @@ const AccountsManagementAddress = ({ {onChainId && (
{onChainId}
)} -
{name ?? shortenAddress(address)}
+
{name ?? truncateAddress(address)}
) } diff --git a/apps/portal/src/components/widgets/RemoveWatchedAccountConfirmationDialog.tsx b/apps/portal/src/components/widgets/RemoveWatchedAccountConfirmationDialog.tsx index 2a6b49a10..83a8c964a 100644 --- a/apps/portal/src/components/widgets/RemoveWatchedAccountConfirmationDialog.tsx +++ b/apps/portal/src/components/widgets/RemoveWatchedAccountConfirmationDialog.tsx @@ -5,7 +5,7 @@ import { RemoveWatchedAccountConfirmationDialog as RemoveWatchedAccountConfirmat import { useSetReadonlyAccounts } from '@/domains/accounts/hooks' import { type ReadonlyAccount } from '@/domains/accounts/recoils' import { isNilOrWhitespace } from '@/util/nil' -import { shortenAddress } from '@/util/shortenAddress' +import { truncateAddress } from '@/util/truncateAddress' export type RemoveWatchedAccountConfirmationDialogProps = { account: ReadonlyAccount @@ -22,7 +22,7 @@ export const RemoveWatchedAccountConfirmationDialog = (props: RemoveWatchedAccou setOpen(false), [])} - name={isNilOrWhitespace(props.account.name) ? shortenAddress(props.account.address) : props.account.name} + name={isNilOrWhitespace(props.account.name) ? truncateAddress(props.account.address) : props.account.name} onConfirm={useCallback(() => { remove(props.account) setOpen(false) diff --git a/apps/portal/src/components/widgets/SeparatedAccountSelector.tsx b/apps/portal/src/components/widgets/SeparatedAccountSelector.tsx index 317886ab7..2dbeb1082 100644 --- a/apps/portal/src/components/widgets/SeparatedAccountSelector.tsx +++ b/apps/portal/src/components/widgets/SeparatedAccountSelector.tsx @@ -15,7 +15,7 @@ import { walletConnectionSideSheetOpenState } from '@/components/widgets/WalletC import { evmAccountsState, substrateAccountsState } from '@/domains/accounts/recoils' import { AccountWithBtc, isBtcAddress } from '@/util/btc' import { cn } from '@/util/cn' -import { shortenAddress } from '@/util/shortenAddress' +import { truncateAddress } from '@/util/truncateAddress' type Props = { allowInput?: boolean @@ -52,11 +52,11 @@ const AccountRow: React.FC<{ 'text-[14px]': formattedAddress.startsWith('0x'), })} > - {name ?? shortenAddress(formattedAddress, 6)} + {name ?? truncateAddress(formattedAddress, 6, 6)}

{name ? (

- {shortenAddress(formattedAddress, 6)} + {truncateAddress(formattedAddress, 6, 6)}

) : null}
diff --git a/apps/portal/src/components/widgets/WithdrawCrowdloanWidget.tsx b/apps/portal/src/components/widgets/WithdrawCrowdloanWidget.tsx deleted file mode 100644 index 5c969c80b..000000000 --- a/apps/portal/src/components/widgets/WithdrawCrowdloanWidget.tsx +++ /dev/null @@ -1,84 +0,0 @@ -import type { ReactNode } from 'react' -import { useCallback, useEffect, useMemo, useState } from 'react' -import { useRecoilValueLoadable } from 'recoil' - -import type { Relaychain } from '@/libs/talisman/util/_config' -import { WithdrawCrowdloanDialog } from '@/components/recipes/WithdrawCrowdloanDialog' -import { type Account } from '@/domains/accounts/recoils' -import { useExtrinsic } from '@/domains/common/hooks/useExtrinsic' -import { substrateApiState } from '@/domains/common/recoils/api' - -export type WithdrawCrowdloanWidgetProps = { - account?: Account - relayChain?: Relaychain - paraId?: string - amount?: string - stakeAfterWithdrawn?: boolean - goToStaking?: () => void - children: (props: { onToggleOpen: () => unknown }) => ReactNode -} - -export const WithdrawCrowdloanWidget = ({ - account, - relayChain, - paraId, - amount, - stakeAfterWithdrawn, - goToStaking, - children, -}: WithdrawCrowdloanWidgetProps) => { - const [open, setOpen] = useState(false) - - const apiLoadable = useRecoilValueLoadable(substrateApiState(relayChain?.rpc)) - - const tx = useExtrinsic( - useMemo(() => { - const api = apiLoadable.valueMaybe() - - if (!open) return - if (api === undefined) return - if (account === undefined) return - if (paraId === undefined) return - - return api.tx.crowdloan.withdraw(account.address, paraId) - }, [open, account, apiLoadable, paraId]) - ) - - const onRequestWithdraw = useCallback(async () => account && (await tx?.signAndSend(account.address)), [account, tx]) - - useEffect(() => { - if (tx?.state === 'loading') { - if (tx?.contents === undefined) return - - // only close modal if we're doing a `withdraw` without a follow-up `stake` - if (!stakeAfterWithdrawn) setOpen(false) - } - if (tx?.state === 'hasValue') { - if (tx?.contents === undefined) return - - setOpen(false) - if (stakeAfterWithdrawn && goToStaking) goToStaking() - } - if (tx?.state === 'hasError') { - setOpen(false) - } - }, [goToStaking, stakeAfterWithdrawn, tx?.contents, tx?.state]) - - return ( - <> - setOpen(false), [])} - onRequestWithdraw={onRequestWithdraw} - /> - {children({ - onToggleOpen: useCallback(() => setOpen(x => !x), []), - })} - - ) -} diff --git a/apps/portal/src/components/widgets/staking/dappStaking/DappPickerDialog.tsx b/apps/portal/src/components/widgets/staking/dappStaking/DappPickerDialog.tsx index b2e39ec5e..f93ae5d80 100644 --- a/apps/portal/src/components/widgets/staking/dappStaking/DappPickerDialog.tsx +++ b/apps/portal/src/components/widgets/staking/dappStaking/DappPickerDialog.tsx @@ -11,7 +11,7 @@ import { useRecoilValue, waitForAll } from 'recoil' import { useNativeTokenAmountState } from '@/domains/chains/recoils' import { StakeLoadable } from '@/domains/staking/dappStaking/hooks/useStakeLoadable' import { useRegisteredDappsState } from '@/domains/staking/dappStaking/recoils' -import { shortenAddress } from '@/util/shortenAddress' +import { truncateAddress } from '@/util/truncateAddress' type DappPickerDialogProps = { title: ReactNode @@ -73,7 +73,7 @@ const DappPickerDialog = (props: DappPickerDialogProps) => { /> )} - {registeredDapp?.name ?? shortenAddress(address)}{' '} + {registeredDapp?.name ?? truncateAddress(address)}{' '}
diff --git a/apps/portal/src/components/widgets/staking/substrate/NominationPoolsStatisticsSideSheet.tsx b/apps/portal/src/components/widgets/staking/substrate/NominationPoolsStatisticsSideSheet.tsx index fe6a84616..1e6222e23 100644 --- a/apps/portal/src/components/widgets/staking/substrate/NominationPoolsStatisticsSideSheet.tsx +++ b/apps/portal/src/components/widgets/staking/substrate/NominationPoolsStatisticsSideSheet.tsx @@ -28,7 +28,7 @@ import { poolPayoutsState, totalPoolPayoutsState, } from '@/domains/staking/substrate/nominationPools/recoils' -import { shortenAddress } from '@/util/shortenAddress' +import { truncateAddress } from '@/util/truncateAddress' export type NominationPoolsStatisticsSideSheetProps = { account: Account @@ -90,8 +90,8 @@ const Stats = (props: { }} > } css={{ paddingRight: 0, paddingLeft: 0 }} /> diff --git a/apps/portal/src/components/widgets/staking/subtensor/DelegatePickerDialog.tsx b/apps/portal/src/components/widgets/staking/subtensor/DelegatePickerDialog.tsx index 97b38ed7d..32be8abcf 100644 --- a/apps/portal/src/components/widgets/staking/subtensor/DelegatePickerDialog.tsx +++ b/apps/portal/src/components/widgets/staking/subtensor/DelegatePickerDialog.tsx @@ -11,7 +11,7 @@ import type { Account } from '@/domains/accounts/recoils' import { useNativeTokenAmountState } from '@/domains/chains/recoils' import { useDelegates } from '@/domains/staking/subtensor/hooks/useDelegates' import { useStake } from '@/domains/staking/subtensor/hooks/useStake' -import { shortenAddress } from '@/util/shortenAddress' +import { truncateAddress } from '@/util/truncateAddress' type DelegatePickerDialogProps = { title: ReactNode @@ -56,7 +56,7 @@ const DelegatePickerDialog = (props: DelegatePickerDialogProps) => { ) : null} - {delegate?.name ?? shortenAddress(stake.hotkey)}{' '} + {delegate?.name ?? truncateAddress(stake.hotkey)}{' '}
diff --git a/apps/portal/src/components/widgets/swap/SwapTokenRow.tsx b/apps/portal/src/components/widgets/swap/SwapTokenRow.tsx index 3cae1b90d..5ffb53d98 100644 --- a/apps/portal/src/components/widgets/swap/SwapTokenRow.tsx +++ b/apps/portal/src/components/widgets/swap/SwapTokenRow.tsx @@ -15,7 +15,7 @@ import { selectedCurrencyState } from '@/domains/balances/currency' import { useCopied } from '@/hooks/useCopied' import { useTokenRatesFromUsd } from '@/hooks/useTokenRatesFromUsd' import { Decimal } from '@/util/Decimal' -import { truncateAddress } from '@/util/helpers' +import { truncateAddress } from '@/util/truncateAddress' import { SwappableAssetWithDecimals } from './swap-modules/common.swap-module' import { uniswapExtendedTokensList, uniswapSafeTokensList } from './swaps.api' @@ -130,7 +130,7 @@ export const SwapTokenRow: React.FC = ({ e.stopPropagation()}>

- {truncateAddress(erc20Address)} + {truncateAddress(erc20Address, 6)}

@@ -145,7 +145,7 @@ export const SwapTokenRow: React.FC = ({ }} >

- {truncateAddress(erc20Address)} + {truncateAddress(erc20Address, 6)}

{copied ? ( diff --git a/apps/portal/src/domains/crowdloans/hooks.ts b/apps/portal/src/domains/crowdloans/hooks.ts deleted file mode 100644 index 98817b468..000000000 --- a/apps/portal/src/domains/crowdloans/hooks.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { encodeAnyAddress } from '@talismn/util' -import BigNumber from 'bignumber.js' -import { useMemo } from 'react' -import { useRecoilValue } from 'recoil' - -import { selectedSubstrateAccountsState } from '@/domains/accounts/recoils' -import { usePortfolio } from '@/libs/portfolio' - -export const useTotalCrowdloanTotalFiatAmount = () => { - const accounts = useRecoilValue(selectedSubstrateAccountsState) - const { totalCrowdloansUsdByAddress } = usePortfolio() - const genericAccounts = useMemo( - () => accounts?.map(x => x.address).map(account => encodeAnyAddress(account, 42)), - [accounts] - ) - return useMemo( - () => - Object.entries(totalCrowdloansUsdByAddress || {}) - .filter(([address]) => genericAccounts?.includes(address)) - .map(([, crowdloansUsd]) => crowdloansUsd) - .reduce((prev, curr) => prev.plus(curr), new BigNumber(0)) - .toNumber(), - [totalCrowdloansUsdByAddress, genericAccounts] - ) -} diff --git a/apps/portal/src/domains/extension/TalismanExtensionSynchronizer.tsx b/apps/portal/src/domains/extension/TalismanExtensionSynchronizer.tsx index b9d754a78..bd58225e7 100644 --- a/apps/portal/src/domains/extension/TalismanExtensionSynchronizer.tsx +++ b/apps/portal/src/domains/extension/TalismanExtensionSynchronizer.tsx @@ -1,10 +1,12 @@ -import { parseCustomEvmErc20Tokens } from '../../hooks/useSetCustomTokens' -import { customTokensConfig } from './customTokensConfig' -import { useChaindataProvider, useEvmNetworks } from '@talismn/balances-react' import type { CustomChain, CustomEvmNetwork, Token } from '@talismn/chaindata-provider' +import { type CustomEvmErc20Token } from '@talismn/balances' +import { useChaindataProvider, useEvmNetworks } from '@talismn/balances-react' +import { githubUnknownTokenLogoUrl } from '@talismn/chaindata-provider' import { unionBy } from 'lodash' import { useEffect, useState } from 'react' +import { CustomTokensConfig, customTokensConfig } from './customTokensConfig' + const windowInject = globalThis as typeof globalThis & { talismanSub?: { subscribeCustomSubstrateChains?: (cb: (chains: CustomChain[]) => unknown) => () => void @@ -32,9 +34,34 @@ export const TalismanExtensionSynchronizer = () => { }, [chaindataProvider]) useEffect(() => { - const customTokens = parseCustomEvmErc20Tokens({ customTokensConfig: customTokensConfig, evmNetworks }) + const customTokens = parseCustomEvmErc20Tokens({ customTokensConfig, evmNetworks }) chaindataProvider.setCustomTokens(unionBy(customTokens, walletTokens, 'id')) }, [walletTokens, evmNetworks, chaindataProvider]) return null } + +function parseCustomEvmErc20Tokens({ + customTokensConfig, + evmNetworks, +}: { + customTokensConfig: CustomTokensConfig + evmNetworks: ReturnType +}): CustomEvmErc20Token[] { + const customTokens = customTokensConfig.map( + ({ evmChainId, symbol, decimals, contractAddress, coingeckoId, logo }): CustomEvmErc20Token => ({ + id: `${evmChainId}-evm-erc20-${contractAddress}`.toLowerCase(), + type: 'evm-erc20', + isTestnet: evmNetworks[evmChainId]?.isTestnet || false, + symbol, + decimals, + logo: logo ?? githubUnknownTokenLogoUrl, + coingeckoId, + contractAddress, + evmNetwork: { id: evmChainId }, + isCustom: true, + }) + ) + + return customTokens +} diff --git a/apps/portal/src/domains/extension/customTokensConfig.ts b/apps/portal/src/domains/extension/customTokensConfig.ts index 0a0f16424..bfeadbc60 100644 --- a/apps/portal/src/domains/extension/customTokensConfig.ts +++ b/apps/portal/src/domains/extension/customTokensConfig.ts @@ -1,4 +1,12 @@ -import { CustomTokensConfig } from '../../hooks/useSetCustomTokens' +export type CustomTokensConfig = CustomTokenConfig[] +export type CustomTokenConfig = { + evmChainId: string + contractAddress: string + symbol: string + decimals: number + coingeckoId?: string + logo?: string +} export const customTokensConfig: CustomTokensConfig = [ { diff --git a/apps/portal/src/generated/gql/crowdloan/gql/fragment-masking.ts b/apps/portal/src/generated/gql/crowdloan/gql/fragment-masking.ts deleted file mode 100644 index 25f868a30..000000000 --- a/apps/portal/src/generated/gql/crowdloan/gql/fragment-masking.ts +++ /dev/null @@ -1,67 +0,0 @@ -import type { DocumentTypeDecoration, ResultOf, TypedDocumentNode } from '@graphql-typed-document-node/core' -import type { FragmentDefinitionNode } from 'graphql' - -import type { Incremental } from './graphql' - -export type FragmentType> = - TDocumentType extends DocumentTypeDecoration - ? [TType] extends [{ ' $fragmentName'?: infer TKey }] - ? TKey extends string - ? { ' $fragmentRefs'?: { [key in TKey]: TType } } - : never - : never - : never - -// return non-nullable if `fragmentType` is non-nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: FragmentType> -): TType -// return nullable if `fragmentType` is nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: FragmentType> | null | undefined -): TType | null | undefined -// return array of non-nullable if `fragmentType` is array of non-nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: ReadonlyArray>> -): ReadonlyArray -// return array of nullable if `fragmentType` is array of nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: ReadonlyArray>> | null | undefined -): ReadonlyArray | null | undefined -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: - | FragmentType> - | ReadonlyArray>> - | null - | undefined -): TType | ReadonlyArray | null | undefined { - return fragmentType as any -} - -export function makeFragmentData, FT extends ResultOf>( - data: FT, - _fragment: F -): FragmentType { - return data as FragmentType -} -export function isFragmentReady( - queryNode: DocumentTypeDecoration, - fragmentNode: TypedDocumentNode, - data: FragmentType, any>> | null | undefined -): data is FragmentType { - const deferredFields = (queryNode as { __meta__?: { deferredFields: Record } }).__meta__ - ?.deferredFields - - if (!deferredFields) return true - - const fragDef = fragmentNode.definitions[0] as FragmentDefinitionNode | undefined - const fragName = fragDef?.name?.value - - const fields = (fragName && deferredFields[fragName]) || [] - return fields.length > 0 && fields.every(field => data && field in data) -} diff --git a/apps/portal/src/generated/gql/crowdloan/gql/gql.ts b/apps/portal/src/generated/gql/crowdloan/gql/gql.ts deleted file mode 100644 index 9f63432d8..000000000 --- a/apps/portal/src/generated/gql/crowdloan/gql/gql.ts +++ /dev/null @@ -1,51 +0,0 @@ -/* eslint-disable */ -import type { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core' - -import * as types from './graphql' - -/** - * Map of all GraphQL operations in the project. - * - * This map has several performance disadvantages: - * 1. It is not tree-shakeable, so it will include all operations in the project. - * 2. It is not minifiable, so the string of a GraphQL query will be multiple times inside the bundle. - * 3. It does not support dead code elimination, so it will add unused operations. - * - * Therefore it is highly recommended to use the babel or swc plugin for production. - */ -const documents = { - '\n query contributions($addresses: [String!]!) {\n contributions(where: { account: { id_in: $addresses } }, orderBy: id_ASC) {\n id\n crowdloan {\n id\n fundIndex\n fundAccount\n paraId\n\n depositor\n end\n cap\n firstPeriod\n lastPeriod\n lastBlock\n\n createdBlockNumber\n createdTimestamp\n\n dissolved\n dissolvedBlockNumber\n dissolvedTimestamp\n }\n account {\n id\n }\n amount\n returned\n blockNumber\n timestamp\n }\n }\n ': - types.ContributionsDocument, -} - -/** - * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. - * - * - * @example - * ```ts - * const query = graphql(`query GetUser($id: ID!) { user(id: $id) { name } }`); - * ``` - * - * The query argument is unknown! - * Please regenerate the types. - */ -export function graphql(source: string): unknown - -/** - * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. - */ -export function graphql( - source: '\n query contributions($addresses: [String!]!) {\n contributions(where: { account: { id_in: $addresses } }, orderBy: id_ASC) {\n id\n crowdloan {\n id\n fundIndex\n fundAccount\n paraId\n\n depositor\n end\n cap\n firstPeriod\n lastPeriod\n lastBlock\n\n createdBlockNumber\n createdTimestamp\n\n dissolved\n dissolvedBlockNumber\n dissolvedTimestamp\n }\n account {\n id\n }\n amount\n returned\n blockNumber\n timestamp\n }\n }\n ' -): (typeof documents)['\n query contributions($addresses: [String!]!) {\n contributions(where: { account: { id_in: $addresses } }, orderBy: id_ASC) {\n id\n crowdloan {\n id\n fundIndex\n fundAccount\n paraId\n\n depositor\n end\n cap\n firstPeriod\n lastPeriod\n lastBlock\n\n createdBlockNumber\n createdTimestamp\n\n dissolved\n dissolvedBlockNumber\n dissolvedTimestamp\n }\n account {\n id\n }\n amount\n returned\n blockNumber\n timestamp\n }\n }\n '] - -export function graphql(source: string) { - return (documents as any)[source] ?? {} -} - -export type DocumentType> = TDocumentNode extends DocumentNode< - infer TType, - any -> - ? TType - : never diff --git a/apps/portal/src/generated/gql/crowdloan/gql/graphql.ts b/apps/portal/src/generated/gql/crowdloan/gql/graphql.ts deleted file mode 100644 index e0ea1a14a..000000000 --- a/apps/portal/src/generated/gql/crowdloan/gql/graphql.ts +++ /dev/null @@ -1,768 +0,0 @@ -/* eslint-disable */ -import type { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core' - -export type Maybe = T | null -export type InputMaybe = Maybe -export type Exact = { [K in keyof T]: T[K] } -export type MakeOptional = Omit & { [SubKey in K]?: Maybe } -export type MakeMaybe = Omit & { [SubKey in K]: Maybe } -export type MakeEmpty = { [_ in K]?: never } -export type Incremental = T | { [P in keyof T]?: P extends ' $fragmentName' | '__typename' ? T[P] : never } -/** All built-in and custom scalars, mapped to their actual values */ -export type Scalars = { - ID: { input: string; output: string } - String: { input: string; output: string } - Boolean: { input: boolean; output: boolean } - Int: { input: number; output: number } - Float: { input: number; output: number } - /** Big number integer */ - BigInt: { input: any; output: any } - /** A date-time string in simplified extended ISO 8601 format (YYYY-MM-DDTHH:mm:ss.sssZ) */ - DateTime: { input: any; output: any } -} - -export type Account = { - __typename?: 'Account' - contributions: Array - /** Account address */ - id: Scalars['String']['output'] -} - -export type AccountContributionsArgs = { - limit?: InputMaybe - offset?: InputMaybe - orderBy?: InputMaybe> - where?: InputMaybe -} - -export type AccountEdge = { - __typename?: 'AccountEdge' - cursor: Scalars['String']['output'] - node: Account -} - -export enum AccountOrderByInput { - IdAsc = 'id_ASC', - IdAscNullsFirst = 'id_ASC_NULLS_FIRST', - IdDesc = 'id_DESC', - IdDescNullsLast = 'id_DESC_NULLS_LAST', -} - -export type AccountWhereInput = { - AND?: InputMaybe> - OR?: InputMaybe> - contributions_every?: InputMaybe - contributions_none?: InputMaybe - contributions_some?: InputMaybe - id_contains?: InputMaybe - id_containsInsensitive?: InputMaybe - id_endsWith?: InputMaybe - id_eq?: InputMaybe - id_gt?: InputMaybe - id_gte?: InputMaybe - id_in?: InputMaybe> - id_isNull?: InputMaybe - id_lt?: InputMaybe - id_lte?: InputMaybe - id_not_contains?: InputMaybe - id_not_containsInsensitive?: InputMaybe - id_not_endsWith?: InputMaybe - id_not_eq?: InputMaybe - id_not_in?: InputMaybe> - id_not_startsWith?: InputMaybe - id_startsWith?: InputMaybe -} - -export type AccountsConnection = { - __typename?: 'AccountsConnection' - edges: Array - pageInfo: PageInfo - totalCount: Scalars['Int']['output'] -} - -export type Contribution = { - __typename?: 'Contribution' - account: Account - amount: Scalars['BigInt']['output'] - blockNumber: Scalars['Int']['output'] - crowdloan: Crowdloan - id: Scalars['String']['output'] - returned: Scalars['Boolean']['output'] - timestamp: Scalars['DateTime']['output'] -} - -export type ContributionEdge = { - __typename?: 'ContributionEdge' - cursor: Scalars['String']['output'] - node: Contribution -} - -export enum ContributionOrderByInput { - AccountIdAsc = 'account_id_ASC', - AccountIdAscNullsFirst = 'account_id_ASC_NULLS_FIRST', - AccountIdDesc = 'account_id_DESC', - AccountIdDescNullsLast = 'account_id_DESC_NULLS_LAST', - AmountAsc = 'amount_ASC', - AmountAscNullsFirst = 'amount_ASC_NULLS_FIRST', - AmountDesc = 'amount_DESC', - AmountDescNullsLast = 'amount_DESC_NULLS_LAST', - BlockNumberAsc = 'blockNumber_ASC', - BlockNumberAscNullsFirst = 'blockNumber_ASC_NULLS_FIRST', - BlockNumberDesc = 'blockNumber_DESC', - BlockNumberDescNullsLast = 'blockNumber_DESC_NULLS_LAST', - CrowdloanCapAsc = 'crowdloan_cap_ASC', - CrowdloanCapAscNullsFirst = 'crowdloan_cap_ASC_NULLS_FIRST', - CrowdloanCapDesc = 'crowdloan_cap_DESC', - CrowdloanCapDescNullsLast = 'crowdloan_cap_DESC_NULLS_LAST', - CrowdloanCreatedBlockNumberAsc = 'crowdloan_createdBlockNumber_ASC', - CrowdloanCreatedBlockNumberAscNullsFirst = 'crowdloan_createdBlockNumber_ASC_NULLS_FIRST', - CrowdloanCreatedBlockNumberDesc = 'crowdloan_createdBlockNumber_DESC', - CrowdloanCreatedBlockNumberDescNullsLast = 'crowdloan_createdBlockNumber_DESC_NULLS_LAST', - CrowdloanCreatedTimestampAsc = 'crowdloan_createdTimestamp_ASC', - CrowdloanCreatedTimestampAscNullsFirst = 'crowdloan_createdTimestamp_ASC_NULLS_FIRST', - CrowdloanCreatedTimestampDesc = 'crowdloan_createdTimestamp_DESC', - CrowdloanCreatedTimestampDescNullsLast = 'crowdloan_createdTimestamp_DESC_NULLS_LAST', - CrowdloanDepositorAsc = 'crowdloan_depositor_ASC', - CrowdloanDepositorAscNullsFirst = 'crowdloan_depositor_ASC_NULLS_FIRST', - CrowdloanDepositorDesc = 'crowdloan_depositor_DESC', - CrowdloanDepositorDescNullsLast = 'crowdloan_depositor_DESC_NULLS_LAST', - CrowdloanDissolvedBlockNumberAsc = 'crowdloan_dissolvedBlockNumber_ASC', - CrowdloanDissolvedBlockNumberAscNullsFirst = 'crowdloan_dissolvedBlockNumber_ASC_NULLS_FIRST', - CrowdloanDissolvedBlockNumberDesc = 'crowdloan_dissolvedBlockNumber_DESC', - CrowdloanDissolvedBlockNumberDescNullsLast = 'crowdloan_dissolvedBlockNumber_DESC_NULLS_LAST', - CrowdloanDissolvedTimestampAsc = 'crowdloan_dissolvedTimestamp_ASC', - CrowdloanDissolvedTimestampAscNullsFirst = 'crowdloan_dissolvedTimestamp_ASC_NULLS_FIRST', - CrowdloanDissolvedTimestampDesc = 'crowdloan_dissolvedTimestamp_DESC', - CrowdloanDissolvedTimestampDescNullsLast = 'crowdloan_dissolvedTimestamp_DESC_NULLS_LAST', - CrowdloanDissolvedAsc = 'crowdloan_dissolved_ASC', - CrowdloanDissolvedAscNullsFirst = 'crowdloan_dissolved_ASC_NULLS_FIRST', - CrowdloanDissolvedDesc = 'crowdloan_dissolved_DESC', - CrowdloanDissolvedDescNullsLast = 'crowdloan_dissolved_DESC_NULLS_LAST', - CrowdloanEndAsc = 'crowdloan_end_ASC', - CrowdloanEndAscNullsFirst = 'crowdloan_end_ASC_NULLS_FIRST', - CrowdloanEndDesc = 'crowdloan_end_DESC', - CrowdloanEndDescNullsLast = 'crowdloan_end_DESC_NULLS_LAST', - CrowdloanFirstPeriodAsc = 'crowdloan_firstPeriod_ASC', - CrowdloanFirstPeriodAscNullsFirst = 'crowdloan_firstPeriod_ASC_NULLS_FIRST', - CrowdloanFirstPeriodDesc = 'crowdloan_firstPeriod_DESC', - CrowdloanFirstPeriodDescNullsLast = 'crowdloan_firstPeriod_DESC_NULLS_LAST', - CrowdloanFundAccountAsc = 'crowdloan_fundAccount_ASC', - CrowdloanFundAccountAscNullsFirst = 'crowdloan_fundAccount_ASC_NULLS_FIRST', - CrowdloanFundAccountDesc = 'crowdloan_fundAccount_DESC', - CrowdloanFundAccountDescNullsLast = 'crowdloan_fundAccount_DESC_NULLS_LAST', - CrowdloanFundIndexAsc = 'crowdloan_fundIndex_ASC', - CrowdloanFundIndexAscNullsFirst = 'crowdloan_fundIndex_ASC_NULLS_FIRST', - CrowdloanFundIndexDesc = 'crowdloan_fundIndex_DESC', - CrowdloanFundIndexDescNullsLast = 'crowdloan_fundIndex_DESC_NULLS_LAST', - CrowdloanIdAsc = 'crowdloan_id_ASC', - CrowdloanIdAscNullsFirst = 'crowdloan_id_ASC_NULLS_FIRST', - CrowdloanIdDesc = 'crowdloan_id_DESC', - CrowdloanIdDescNullsLast = 'crowdloan_id_DESC_NULLS_LAST', - CrowdloanLastBlockAsc = 'crowdloan_lastBlock_ASC', - CrowdloanLastBlockAscNullsFirst = 'crowdloan_lastBlock_ASC_NULLS_FIRST', - CrowdloanLastBlockDesc = 'crowdloan_lastBlock_DESC', - CrowdloanLastBlockDescNullsLast = 'crowdloan_lastBlock_DESC_NULLS_LAST', - CrowdloanLastPeriodAsc = 'crowdloan_lastPeriod_ASC', - CrowdloanLastPeriodAscNullsFirst = 'crowdloan_lastPeriod_ASC_NULLS_FIRST', - CrowdloanLastPeriodDesc = 'crowdloan_lastPeriod_DESC', - CrowdloanLastPeriodDescNullsLast = 'crowdloan_lastPeriod_DESC_NULLS_LAST', - CrowdloanParaIdAsc = 'crowdloan_paraId_ASC', - CrowdloanParaIdAscNullsFirst = 'crowdloan_paraId_ASC_NULLS_FIRST', - CrowdloanParaIdDesc = 'crowdloan_paraId_DESC', - CrowdloanParaIdDescNullsLast = 'crowdloan_paraId_DESC_NULLS_LAST', - CrowdloanRefundedAsc = 'crowdloan_refunded_ASC', - CrowdloanRefundedAscNullsFirst = 'crowdloan_refunded_ASC_NULLS_FIRST', - CrowdloanRefundedDesc = 'crowdloan_refunded_DESC', - CrowdloanRefundedDescNullsLast = 'crowdloan_refunded_DESC_NULLS_LAST', - IdAsc = 'id_ASC', - IdAscNullsFirst = 'id_ASC_NULLS_FIRST', - IdDesc = 'id_DESC', - IdDescNullsLast = 'id_DESC_NULLS_LAST', - ReturnedAsc = 'returned_ASC', - ReturnedAscNullsFirst = 'returned_ASC_NULLS_FIRST', - ReturnedDesc = 'returned_DESC', - ReturnedDescNullsLast = 'returned_DESC_NULLS_LAST', - TimestampAsc = 'timestamp_ASC', - TimestampAscNullsFirst = 'timestamp_ASC_NULLS_FIRST', - TimestampDesc = 'timestamp_DESC', - TimestampDescNullsLast = 'timestamp_DESC_NULLS_LAST', -} - -export type ContributionWhereInput = { - AND?: InputMaybe> - OR?: InputMaybe> - account?: InputMaybe - account_isNull?: InputMaybe - amount_eq?: InputMaybe - amount_gt?: InputMaybe - amount_gte?: InputMaybe - amount_in?: InputMaybe> - amount_isNull?: InputMaybe - amount_lt?: InputMaybe - amount_lte?: InputMaybe - amount_not_eq?: InputMaybe - amount_not_in?: InputMaybe> - blockNumber_eq?: InputMaybe - blockNumber_gt?: InputMaybe - blockNumber_gte?: InputMaybe - blockNumber_in?: InputMaybe> - blockNumber_isNull?: InputMaybe - blockNumber_lt?: InputMaybe - blockNumber_lte?: InputMaybe - blockNumber_not_eq?: InputMaybe - blockNumber_not_in?: InputMaybe> - crowdloan?: InputMaybe - crowdloan_isNull?: InputMaybe - id_contains?: InputMaybe - id_containsInsensitive?: InputMaybe - id_endsWith?: InputMaybe - id_eq?: InputMaybe - id_gt?: InputMaybe - id_gte?: InputMaybe - id_in?: InputMaybe> - id_isNull?: InputMaybe - id_lt?: InputMaybe - id_lte?: InputMaybe - id_not_contains?: InputMaybe - id_not_containsInsensitive?: InputMaybe - id_not_endsWith?: InputMaybe - id_not_eq?: InputMaybe - id_not_in?: InputMaybe> - id_not_startsWith?: InputMaybe - id_startsWith?: InputMaybe - returned_eq?: InputMaybe - returned_isNull?: InputMaybe - returned_not_eq?: InputMaybe - timestamp_eq?: InputMaybe - timestamp_gt?: InputMaybe - timestamp_gte?: InputMaybe - timestamp_in?: InputMaybe> - timestamp_isNull?: InputMaybe - timestamp_lt?: InputMaybe - timestamp_lte?: InputMaybe - timestamp_not_eq?: InputMaybe - timestamp_not_in?: InputMaybe> -} - -export type ContributionsConnection = { - __typename?: 'ContributionsConnection' - edges: Array - pageInfo: PageInfo - totalCount: Scalars['Int']['output'] -} - -export type Crowdloan = { - __typename?: 'Crowdloan' - cap?: Maybe - contributions: Array - createdBlockNumber: Scalars['Int']['output'] - createdTimestamp: Scalars['DateTime']['output'] - depositor?: Maybe - dissolved: Scalars['Boolean']['output'] - dissolvedBlockNumber?: Maybe - dissolvedTimestamp?: Maybe - end?: Maybe - firstPeriod?: Maybe - fundAccount: Scalars['String']['output'] - fundIndex: Scalars['Int']['output'] - id: Scalars['String']['output'] - lastBlock?: Maybe - lastPeriod?: Maybe - paraId: Scalars['Int']['output'] - refunded: Scalars['Boolean']['output'] -} - -export type CrowdloanContributionsArgs = { - limit?: InputMaybe - offset?: InputMaybe - orderBy?: InputMaybe> - where?: InputMaybe -} - -export type CrowdloanEdge = { - __typename?: 'CrowdloanEdge' - cursor: Scalars['String']['output'] - node: Crowdloan -} - -export enum CrowdloanOrderByInput { - CapAsc = 'cap_ASC', - CapAscNullsFirst = 'cap_ASC_NULLS_FIRST', - CapDesc = 'cap_DESC', - CapDescNullsLast = 'cap_DESC_NULLS_LAST', - CreatedBlockNumberAsc = 'createdBlockNumber_ASC', - CreatedBlockNumberAscNullsFirst = 'createdBlockNumber_ASC_NULLS_FIRST', - CreatedBlockNumberDesc = 'createdBlockNumber_DESC', - CreatedBlockNumberDescNullsLast = 'createdBlockNumber_DESC_NULLS_LAST', - CreatedTimestampAsc = 'createdTimestamp_ASC', - CreatedTimestampAscNullsFirst = 'createdTimestamp_ASC_NULLS_FIRST', - CreatedTimestampDesc = 'createdTimestamp_DESC', - CreatedTimestampDescNullsLast = 'createdTimestamp_DESC_NULLS_LAST', - DepositorAsc = 'depositor_ASC', - DepositorAscNullsFirst = 'depositor_ASC_NULLS_FIRST', - DepositorDesc = 'depositor_DESC', - DepositorDescNullsLast = 'depositor_DESC_NULLS_LAST', - DissolvedBlockNumberAsc = 'dissolvedBlockNumber_ASC', - DissolvedBlockNumberAscNullsFirst = 'dissolvedBlockNumber_ASC_NULLS_FIRST', - DissolvedBlockNumberDesc = 'dissolvedBlockNumber_DESC', - DissolvedBlockNumberDescNullsLast = 'dissolvedBlockNumber_DESC_NULLS_LAST', - DissolvedTimestampAsc = 'dissolvedTimestamp_ASC', - DissolvedTimestampAscNullsFirst = 'dissolvedTimestamp_ASC_NULLS_FIRST', - DissolvedTimestampDesc = 'dissolvedTimestamp_DESC', - DissolvedTimestampDescNullsLast = 'dissolvedTimestamp_DESC_NULLS_LAST', - DissolvedAsc = 'dissolved_ASC', - DissolvedAscNullsFirst = 'dissolved_ASC_NULLS_FIRST', - DissolvedDesc = 'dissolved_DESC', - DissolvedDescNullsLast = 'dissolved_DESC_NULLS_LAST', - EndAsc = 'end_ASC', - EndAscNullsFirst = 'end_ASC_NULLS_FIRST', - EndDesc = 'end_DESC', - EndDescNullsLast = 'end_DESC_NULLS_LAST', - FirstPeriodAsc = 'firstPeriod_ASC', - FirstPeriodAscNullsFirst = 'firstPeriod_ASC_NULLS_FIRST', - FirstPeriodDesc = 'firstPeriod_DESC', - FirstPeriodDescNullsLast = 'firstPeriod_DESC_NULLS_LAST', - FundAccountAsc = 'fundAccount_ASC', - FundAccountAscNullsFirst = 'fundAccount_ASC_NULLS_FIRST', - FundAccountDesc = 'fundAccount_DESC', - FundAccountDescNullsLast = 'fundAccount_DESC_NULLS_LAST', - FundIndexAsc = 'fundIndex_ASC', - FundIndexAscNullsFirst = 'fundIndex_ASC_NULLS_FIRST', - FundIndexDesc = 'fundIndex_DESC', - FundIndexDescNullsLast = 'fundIndex_DESC_NULLS_LAST', - IdAsc = 'id_ASC', - IdAscNullsFirst = 'id_ASC_NULLS_FIRST', - IdDesc = 'id_DESC', - IdDescNullsLast = 'id_DESC_NULLS_LAST', - LastBlockAsc = 'lastBlock_ASC', - LastBlockAscNullsFirst = 'lastBlock_ASC_NULLS_FIRST', - LastBlockDesc = 'lastBlock_DESC', - LastBlockDescNullsLast = 'lastBlock_DESC_NULLS_LAST', - LastPeriodAsc = 'lastPeriod_ASC', - LastPeriodAscNullsFirst = 'lastPeriod_ASC_NULLS_FIRST', - LastPeriodDesc = 'lastPeriod_DESC', - LastPeriodDescNullsLast = 'lastPeriod_DESC_NULLS_LAST', - ParaIdAsc = 'paraId_ASC', - ParaIdAscNullsFirst = 'paraId_ASC_NULLS_FIRST', - ParaIdDesc = 'paraId_DESC', - ParaIdDescNullsLast = 'paraId_DESC_NULLS_LAST', - RefundedAsc = 'refunded_ASC', - RefundedAscNullsFirst = 'refunded_ASC_NULLS_FIRST', - RefundedDesc = 'refunded_DESC', - RefundedDescNullsLast = 'refunded_DESC_NULLS_LAST', -} - -export type CrowdloanWhereInput = { - AND?: InputMaybe> - OR?: InputMaybe> - cap_eq?: InputMaybe - cap_gt?: InputMaybe - cap_gte?: InputMaybe - cap_in?: InputMaybe> - cap_isNull?: InputMaybe - cap_lt?: InputMaybe - cap_lte?: InputMaybe - cap_not_eq?: InputMaybe - cap_not_in?: InputMaybe> - contributions_every?: InputMaybe - contributions_none?: InputMaybe - contributions_some?: InputMaybe - createdBlockNumber_eq?: InputMaybe - createdBlockNumber_gt?: InputMaybe - createdBlockNumber_gte?: InputMaybe - createdBlockNumber_in?: InputMaybe> - createdBlockNumber_isNull?: InputMaybe - createdBlockNumber_lt?: InputMaybe - createdBlockNumber_lte?: InputMaybe - createdBlockNumber_not_eq?: InputMaybe - createdBlockNumber_not_in?: InputMaybe> - createdTimestamp_eq?: InputMaybe - createdTimestamp_gt?: InputMaybe - createdTimestamp_gte?: InputMaybe - createdTimestamp_in?: InputMaybe> - createdTimestamp_isNull?: InputMaybe - createdTimestamp_lt?: InputMaybe - createdTimestamp_lte?: InputMaybe - createdTimestamp_not_eq?: InputMaybe - createdTimestamp_not_in?: InputMaybe> - depositor_contains?: InputMaybe - depositor_containsInsensitive?: InputMaybe - depositor_endsWith?: InputMaybe - depositor_eq?: InputMaybe - depositor_gt?: InputMaybe - depositor_gte?: InputMaybe - depositor_in?: InputMaybe> - depositor_isNull?: InputMaybe - depositor_lt?: InputMaybe - depositor_lte?: InputMaybe - depositor_not_contains?: InputMaybe - depositor_not_containsInsensitive?: InputMaybe - depositor_not_endsWith?: InputMaybe - depositor_not_eq?: InputMaybe - depositor_not_in?: InputMaybe> - depositor_not_startsWith?: InputMaybe - depositor_startsWith?: InputMaybe - dissolvedBlockNumber_eq?: InputMaybe - dissolvedBlockNumber_gt?: InputMaybe - dissolvedBlockNumber_gte?: InputMaybe - dissolvedBlockNumber_in?: InputMaybe> - dissolvedBlockNumber_isNull?: InputMaybe - dissolvedBlockNumber_lt?: InputMaybe - dissolvedBlockNumber_lte?: InputMaybe - dissolvedBlockNumber_not_eq?: InputMaybe - dissolvedBlockNumber_not_in?: InputMaybe> - dissolvedTimestamp_eq?: InputMaybe - dissolvedTimestamp_gt?: InputMaybe - dissolvedTimestamp_gte?: InputMaybe - dissolvedTimestamp_in?: InputMaybe> - dissolvedTimestamp_isNull?: InputMaybe - dissolvedTimestamp_lt?: InputMaybe - dissolvedTimestamp_lte?: InputMaybe - dissolvedTimestamp_not_eq?: InputMaybe - dissolvedTimestamp_not_in?: InputMaybe> - dissolved_eq?: InputMaybe - dissolved_isNull?: InputMaybe - dissolved_not_eq?: InputMaybe - end_eq?: InputMaybe - end_gt?: InputMaybe - end_gte?: InputMaybe - end_in?: InputMaybe> - end_isNull?: InputMaybe - end_lt?: InputMaybe - end_lte?: InputMaybe - end_not_eq?: InputMaybe - end_not_in?: InputMaybe> - firstPeriod_eq?: InputMaybe - firstPeriod_gt?: InputMaybe - firstPeriod_gte?: InputMaybe - firstPeriod_in?: InputMaybe> - firstPeriod_isNull?: InputMaybe - firstPeriod_lt?: InputMaybe - firstPeriod_lte?: InputMaybe - firstPeriod_not_eq?: InputMaybe - firstPeriod_not_in?: InputMaybe> - fundAccount_contains?: InputMaybe - fundAccount_containsInsensitive?: InputMaybe - fundAccount_endsWith?: InputMaybe - fundAccount_eq?: InputMaybe - fundAccount_gt?: InputMaybe - fundAccount_gte?: InputMaybe - fundAccount_in?: InputMaybe> - fundAccount_isNull?: InputMaybe - fundAccount_lt?: InputMaybe - fundAccount_lte?: InputMaybe - fundAccount_not_contains?: InputMaybe - fundAccount_not_containsInsensitive?: InputMaybe - fundAccount_not_endsWith?: InputMaybe - fundAccount_not_eq?: InputMaybe - fundAccount_not_in?: InputMaybe> - fundAccount_not_startsWith?: InputMaybe - fundAccount_startsWith?: InputMaybe - fundIndex_eq?: InputMaybe - fundIndex_gt?: InputMaybe - fundIndex_gte?: InputMaybe - fundIndex_in?: InputMaybe> - fundIndex_isNull?: InputMaybe - fundIndex_lt?: InputMaybe - fundIndex_lte?: InputMaybe - fundIndex_not_eq?: InputMaybe - fundIndex_not_in?: InputMaybe> - id_contains?: InputMaybe - id_containsInsensitive?: InputMaybe - id_endsWith?: InputMaybe - id_eq?: InputMaybe - id_gt?: InputMaybe - id_gte?: InputMaybe - id_in?: InputMaybe> - id_isNull?: InputMaybe - id_lt?: InputMaybe - id_lte?: InputMaybe - id_not_contains?: InputMaybe - id_not_containsInsensitive?: InputMaybe - id_not_endsWith?: InputMaybe - id_not_eq?: InputMaybe - id_not_in?: InputMaybe> - id_not_startsWith?: InputMaybe - id_startsWith?: InputMaybe - lastBlock_eq?: InputMaybe - lastBlock_gt?: InputMaybe - lastBlock_gte?: InputMaybe - lastBlock_in?: InputMaybe> - lastBlock_isNull?: InputMaybe - lastBlock_lt?: InputMaybe - lastBlock_lte?: InputMaybe - lastBlock_not_eq?: InputMaybe - lastBlock_not_in?: InputMaybe> - lastPeriod_eq?: InputMaybe - lastPeriod_gt?: InputMaybe - lastPeriod_gte?: InputMaybe - lastPeriod_in?: InputMaybe> - lastPeriod_isNull?: InputMaybe - lastPeriod_lt?: InputMaybe - lastPeriod_lte?: InputMaybe - lastPeriod_not_eq?: InputMaybe - lastPeriod_not_in?: InputMaybe> - paraId_eq?: InputMaybe - paraId_gt?: InputMaybe - paraId_gte?: InputMaybe - paraId_in?: InputMaybe> - paraId_isNull?: InputMaybe - paraId_lt?: InputMaybe - paraId_lte?: InputMaybe - paraId_not_eq?: InputMaybe - paraId_not_in?: InputMaybe> - refunded_eq?: InputMaybe - refunded_isNull?: InputMaybe - refunded_not_eq?: InputMaybe -} - -export type CrowdloansConnection = { - __typename?: 'CrowdloansConnection' - edges: Array - pageInfo: PageInfo - totalCount: Scalars['Int']['output'] -} - -export type PageInfo = { - __typename?: 'PageInfo' - endCursor: Scalars['String']['output'] - hasNextPage: Scalars['Boolean']['output'] - hasPreviousPage: Scalars['Boolean']['output'] - startCursor: Scalars['String']['output'] -} - -export type Query = { - __typename?: 'Query' - accountById?: Maybe - /** @deprecated Use accountById */ - accountByUniqueInput?: Maybe - accounts: Array - accountsConnection: AccountsConnection - contributionById?: Maybe - /** @deprecated Use contributionById */ - contributionByUniqueInput?: Maybe - contributions: Array - contributionsConnection: ContributionsConnection - crowdloanById?: Maybe - /** @deprecated Use crowdloanById */ - crowdloanByUniqueInput?: Maybe - crowdloans: Array - crowdloansConnection: CrowdloansConnection - squidStatus?: Maybe -} - -export type QueryAccountByIdArgs = { - id: Scalars['String']['input'] -} - -export type QueryAccountByUniqueInputArgs = { - where: WhereIdInput -} - -export type QueryAccountsArgs = { - limit?: InputMaybe - offset?: InputMaybe - orderBy?: InputMaybe> - where?: InputMaybe -} - -export type QueryAccountsConnectionArgs = { - after?: InputMaybe - first?: InputMaybe - orderBy: Array - where?: InputMaybe -} - -export type QueryContributionByIdArgs = { - id: Scalars['String']['input'] -} - -export type QueryContributionByUniqueInputArgs = { - where: WhereIdInput -} - -export type QueryContributionsArgs = { - limit?: InputMaybe - offset?: InputMaybe - orderBy?: InputMaybe> - where?: InputMaybe -} - -export type QueryContributionsConnectionArgs = { - after?: InputMaybe - first?: InputMaybe - orderBy: Array - where?: InputMaybe -} - -export type QueryCrowdloanByIdArgs = { - id: Scalars['String']['input'] -} - -export type QueryCrowdloanByUniqueInputArgs = { - where: WhereIdInput -} - -export type QueryCrowdloansArgs = { - limit?: InputMaybe - offset?: InputMaybe - orderBy?: InputMaybe> - where?: InputMaybe -} - -export type QueryCrowdloansConnectionArgs = { - after?: InputMaybe - first?: InputMaybe - orderBy: Array - where?: InputMaybe -} - -export type SquidStatus = { - __typename?: 'SquidStatus' - /** The height of the processed part of the chain */ - height?: Maybe -} - -export type WhereIdInput = { - id: Scalars['String']['input'] -} - -export type ContributionsQueryVariables = Exact<{ - addresses: Array | Scalars['String']['input'] -}> - -export type ContributionsQuery = { - __typename?: 'Query' - contributions: Array<{ - __typename?: 'Contribution' - id: string - amount: any - returned: boolean - blockNumber: number - timestamp: any - crowdloan: { - __typename?: 'Crowdloan' - id: string - fundIndex: number - fundAccount: string - paraId: number - depositor?: string | null - end?: number | null - cap?: any | null - firstPeriod?: number | null - lastPeriod?: number | null - lastBlock?: number | null - createdBlockNumber: number - createdTimestamp: any - dissolved: boolean - dissolvedBlockNumber?: number | null - dissolvedTimestamp?: any | null - } - account: { __typename?: 'Account'; id: string } - }> -} - -export const ContributionsDocument = { - kind: 'Document', - definitions: [ - { - kind: 'OperationDefinition', - operation: 'query', - name: { kind: 'Name', value: 'contributions' }, - variableDefinitions: [ - { - kind: 'VariableDefinition', - variable: { kind: 'Variable', name: { kind: 'Name', value: 'addresses' } }, - type: { - kind: 'NonNullType', - type: { - kind: 'ListType', - type: { kind: 'NonNullType', type: { kind: 'NamedType', name: { kind: 'Name', value: 'String' } } }, - }, - }, - }, - ], - selectionSet: { - kind: 'SelectionSet', - selections: [ - { - kind: 'Field', - name: { kind: 'Name', value: 'contributions' }, - arguments: [ - { - kind: 'Argument', - name: { kind: 'Name', value: 'where' }, - value: { - kind: 'ObjectValue', - fields: [ - { - kind: 'ObjectField', - name: { kind: 'Name', value: 'account' }, - value: { - kind: 'ObjectValue', - fields: [ - { - kind: 'ObjectField', - name: { kind: 'Name', value: 'id_in' }, - value: { kind: 'Variable', name: { kind: 'Name', value: 'addresses' } }, - }, - ], - }, - }, - ], - }, - }, - { - kind: 'Argument', - name: { kind: 'Name', value: 'orderBy' }, - value: { kind: 'EnumValue', value: 'id_ASC' }, - }, - ], - selectionSet: { - kind: 'SelectionSet', - selections: [ - { kind: 'Field', name: { kind: 'Name', value: 'id' } }, - { - kind: 'Field', - name: { kind: 'Name', value: 'crowdloan' }, - selectionSet: { - kind: 'SelectionSet', - selections: [ - { kind: 'Field', name: { kind: 'Name', value: 'id' } }, - { kind: 'Field', name: { kind: 'Name', value: 'fundIndex' } }, - { kind: 'Field', name: { kind: 'Name', value: 'fundAccount' } }, - { kind: 'Field', name: { kind: 'Name', value: 'paraId' } }, - { kind: 'Field', name: { kind: 'Name', value: 'depositor' } }, - { kind: 'Field', name: { kind: 'Name', value: 'end' } }, - { kind: 'Field', name: { kind: 'Name', value: 'cap' } }, - { kind: 'Field', name: { kind: 'Name', value: 'firstPeriod' } }, - { kind: 'Field', name: { kind: 'Name', value: 'lastPeriod' } }, - { kind: 'Field', name: { kind: 'Name', value: 'lastBlock' } }, - { kind: 'Field', name: { kind: 'Name', value: 'createdBlockNumber' } }, - { kind: 'Field', name: { kind: 'Name', value: 'createdTimestamp' } }, - { kind: 'Field', name: { kind: 'Name', value: 'dissolved' } }, - { kind: 'Field', name: { kind: 'Name', value: 'dissolvedBlockNumber' } }, - { kind: 'Field', name: { kind: 'Name', value: 'dissolvedTimestamp' } }, - ], - }, - }, - { - kind: 'Field', - name: { kind: 'Name', value: 'account' }, - selectionSet: { - kind: 'SelectionSet', - selections: [{ kind: 'Field', name: { kind: 'Name', value: 'id' } }], - }, - }, - { kind: 'Field', name: { kind: 'Name', value: 'amount' } }, - { kind: 'Field', name: { kind: 'Name', value: 'returned' } }, - { kind: 'Field', name: { kind: 'Name', value: 'blockNumber' } }, - { kind: 'Field', name: { kind: 'Name', value: 'timestamp' } }, - ], - }, - }, - ], - }, - }, - ], -} as unknown as DocumentNode diff --git a/apps/portal/src/generated/gql/crowdloan/gql/index.ts b/apps/portal/src/generated/gql/crowdloan/gql/index.ts deleted file mode 100644 index f9bc8e591..000000000 --- a/apps/portal/src/generated/gql/crowdloan/gql/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './fragment-masking' -export * from './gql' diff --git a/apps/portal/src/generated/gql/extrinsicHistory/gql/fragment-masking.ts b/apps/portal/src/generated/gql/extrinsicHistory/gql/fragment-masking.ts deleted file mode 100644 index 25f868a30..000000000 --- a/apps/portal/src/generated/gql/extrinsicHistory/gql/fragment-masking.ts +++ /dev/null @@ -1,67 +0,0 @@ -import type { DocumentTypeDecoration, ResultOf, TypedDocumentNode } from '@graphql-typed-document-node/core' -import type { FragmentDefinitionNode } from 'graphql' - -import type { Incremental } from './graphql' - -export type FragmentType> = - TDocumentType extends DocumentTypeDecoration - ? [TType] extends [{ ' $fragmentName'?: infer TKey }] - ? TKey extends string - ? { ' $fragmentRefs'?: { [key in TKey]: TType } } - : never - : never - : never - -// return non-nullable if `fragmentType` is non-nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: FragmentType> -): TType -// return nullable if `fragmentType` is nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: FragmentType> | null | undefined -): TType | null | undefined -// return array of non-nullable if `fragmentType` is array of non-nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: ReadonlyArray>> -): ReadonlyArray -// return array of nullable if `fragmentType` is array of nullable -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: ReadonlyArray>> | null | undefined -): ReadonlyArray | null | undefined -export function useFragment( - _documentNode: DocumentTypeDecoration, - fragmentType: - | FragmentType> - | ReadonlyArray>> - | null - | undefined -): TType | ReadonlyArray | null | undefined { - return fragmentType as any -} - -export function makeFragmentData, FT extends ResultOf>( - data: FT, - _fragment: F -): FragmentType { - return data as FragmentType -} -export function isFragmentReady( - queryNode: DocumentTypeDecoration, - fragmentNode: TypedDocumentNode, - data: FragmentType, any>> | null | undefined -): data is FragmentType { - const deferredFields = (queryNode as { __meta__?: { deferredFields: Record } }).__meta__ - ?.deferredFields - - if (!deferredFields) return true - - const fragDef = fragmentNode.definitions[0] as FragmentDefinitionNode | undefined - const fragName = fragDef?.name?.value - - const fields = (fragName && deferredFields[fragName]) || [] - return fields.length > 0 && fields.every(field => data && field in data) -} diff --git a/apps/portal/src/generated/gql/extrinsicHistory/gql/gql.ts b/apps/portal/src/generated/gql/extrinsicHistory/gql/gql.ts deleted file mode 100644 index 73ff1a23a..000000000 --- a/apps/portal/src/generated/gql/extrinsicHistory/gql/gql.ts +++ /dev/null @@ -1,67 +0,0 @@ -/* eslint-disable */ -import type { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core' - -import * as types from './graphql' - -/** - * Map of all GraphQL operations in the project. - * - * This map has several performance disadvantages: - * 1. It is not tree-shakeable, so it will include all operations in the project. - * 2. It is not minifiable, so the string of a GraphQL query will be multiple times inside the bundle. - * 3. It does not support dead code elimination, so it will add unused operations. - * - * Therefore it is highly recommended to use the babel or swc plugin for production. - */ -const documents = { - '\n query extrinsicCsv($address: String!, $timestampGte: DateTime!, $timestampLte: DateTime!) {\n extrinsicCsv(where: { addressIn: [$address], timestampGte: $timestampGte, timestampLte: $timestampLte })\n }\n ': - types.ExtrinsicCsvDocument, - '\n query extrinsics(\n $after: String\n $first: Int!\n $where: ExtrinsicWhereInput\n $orderBy: ExtrinsicOrderByInput\n ) {\n extrinsics(after: $after, first: $first, where: $where, orderBy: $orderBy) {\n edges {\n node {\n chain {\n genesisHash\n name\n logo\n subscanUrl\n }\n signer\n hash\n success\n block {\n height\n timestamp\n }\n call {\n name\n args\n }\n fee {\n value\n symbol\n }\n transfers {\n edges {\n node {\n debit\n credit\n amount {\n value\n symbol\n }\n }\n }\n }\n rewards {\n edges {\n node {\n debit\n credit\n amount {\n value\n symbol\n }\n }\n }\n }\n subscanLink {\n id\n url\n }\n }\n }\n pageInfo {\n hasNextPage\n endCursor\n }\n }\n }\n ': - types.ExtrinsicsDocument, - '\n query filters {\n modules\n chains {\n genesisHash\n name\n logo\n }\n }\n ': - types.FiltersDocument, -} - -/** - * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. - * - * - * @example - * ```ts - * const query = graphql(`query GetUser($id: ID!) { user(id: $id) { name } }`); - * ``` - * - * The query argument is unknown! - * Please regenerate the types. - */ -export function graphql(source: string): unknown - -/** - * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. - */ -export function graphql( - source: '\n query extrinsicCsv($address: String!, $timestampGte: DateTime!, $timestampLte: DateTime!) {\n extrinsicCsv(where: { addressIn: [$address], timestampGte: $timestampGte, timestampLte: $timestampLte })\n }\n ' -): (typeof documents)['\n query extrinsicCsv($address: String!, $timestampGte: DateTime!, $timestampLte: DateTime!) {\n extrinsicCsv(where: { addressIn: [$address], timestampGte: $timestampGte, timestampLte: $timestampLte })\n }\n '] -/** - * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. - */ -export function graphql( - source: '\n query extrinsics(\n $after: String\n $first: Int!\n $where: ExtrinsicWhereInput\n $orderBy: ExtrinsicOrderByInput\n ) {\n extrinsics(after: $after, first: $first, where: $where, orderBy: $orderBy) {\n edges {\n node {\n chain {\n genesisHash\n name\n logo\n subscanUrl\n }\n signer\n hash\n success\n block {\n height\n timestamp\n }\n call {\n name\n args\n }\n fee {\n value\n symbol\n }\n transfers {\n edges {\n node {\n debit\n credit\n amount {\n value\n symbol\n }\n }\n }\n }\n rewards {\n edges {\n node {\n debit\n credit\n amount {\n value\n symbol\n }\n }\n }\n }\n subscanLink {\n id\n url\n }\n }\n }\n pageInfo {\n hasNextPage\n endCursor\n }\n }\n }\n ' -): (typeof documents)['\n query extrinsics(\n $after: String\n $first: Int!\n $where: ExtrinsicWhereInput\n $orderBy: ExtrinsicOrderByInput\n ) {\n extrinsics(after: $after, first: $first, where: $where, orderBy: $orderBy) {\n edges {\n node {\n chain {\n genesisHash\n name\n logo\n subscanUrl\n }\n signer\n hash\n success\n block {\n height\n timestamp\n }\n call {\n name\n args\n }\n fee {\n value\n symbol\n }\n transfers {\n edges {\n node {\n debit\n credit\n amount {\n value\n symbol\n }\n }\n }\n }\n rewards {\n edges {\n node {\n debit\n credit\n amount {\n value\n symbol\n }\n }\n }\n }\n subscanLink {\n id\n url\n }\n }\n }\n pageInfo {\n hasNextPage\n endCursor\n }\n }\n }\n '] -/** - * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. - */ -export function graphql( - source: '\n query filters {\n modules\n chains {\n genesisHash\n name\n logo\n }\n }\n ' -): (typeof documents)['\n query filters {\n modules\n chains {\n genesisHash\n name\n logo\n }\n }\n '] - -export function graphql(source: string) { - return (documents as any)[source] ?? {} -} - -export type DocumentType> = TDocumentNode extends DocumentNode< - infer TType, - any -> - ? TType - : never diff --git a/apps/portal/src/generated/gql/extrinsicHistory/gql/graphql.ts b/apps/portal/src/generated/gql/extrinsicHistory/gql/graphql.ts deleted file mode 100644 index d040ed32f..000000000 --- a/apps/portal/src/generated/gql/extrinsicHistory/gql/graphql.ts +++ /dev/null @@ -1,576 +0,0 @@ -/* eslint-disable */ -import type { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core' - -export type Maybe = T | null -export type InputMaybe = Maybe -export type Exact = { [K in keyof T]: T[K] } -export type MakeOptional = Omit & { [SubKey in K]?: Maybe } -export type MakeMaybe = Omit & { [SubKey in K]: Maybe } -export type MakeEmpty = { [_ in K]?: never } -export type Incremental = T | { [P in keyof T]?: P extends ' $fragmentName' | '__typename' ? T[P] : never } -/** All built-in and custom scalars, mapped to their actual values */ -export type Scalars = { - ID: { input: string; output: string } - String: { input: string; output: string } - Boolean: { input: boolean; output: boolean } - Int: { input: number; output: number } - Float: { input: number; output: number } - /** Big number decimal */ - BigDecimal: { input: any; output: any } - /** A date-time string in simplified extended ISO 8601 format (YYYY-MM-DDTHH:mm:ss.sssZ) */ - DateTime: { input: any; output: any } - /** A scalar that can represent any JSON value */ - JSON: { input: any; output: any } -} - -export type Block = { - __typename?: 'Block' - height: Scalars['Int']['output'] - timestamp: Scalars['DateTime']['output'] -} - -export type Call = { - __typename?: 'Call' - args?: Maybe - name: Scalars['String']['output'] -} - -export type Chain = { - __typename?: 'Chain' - genesisHash: Scalars['String']['output'] - logo?: Maybe - name?: Maybe - subscanUrl?: Maybe -} - -export type Extrinsic = { - __typename?: 'Extrinsic' - block: Block - call: Call - chain: Chain - error?: Maybe - fee?: Maybe - hash: Scalars['String']['output'] - rewards: RewardsConnection - signature?: Maybe - signer?: Maybe - subscanLink?: Maybe - subsquidId: Scalars['String']['output'] - success: Scalars['Boolean']['output'] - transfers: TransferConnection -} - -export type ExtrinsicConnection = { - __typename?: 'ExtrinsicConnection' - edges: Array - pageInfo: PageInfo -} - -export type ExtrinsicCsvWhereInput = { - addressIn: Array - timestampGte: Scalars['DateTime']['input'] - timestampLte: Scalars['DateTime']['input'] -} - -export type ExtrinsicEdge = { - __typename?: 'ExtrinsicEdge' - cursor: Scalars['String']['output'] - node: Extrinsic -} - -export enum ExtrinsicOrderByInput { - TimestampAsc = 'timestampAsc', - TimestampDesc = 'timestampDesc', -} - -export type ExtrinsicWhereInput = { - addressIn?: InputMaybe> - callEq?: InputMaybe - chainEq?: InputMaybe - hashEq?: InputMaybe - moduleEq?: InputMaybe - signerIn?: InputMaybe> - timestampGte?: InputMaybe - timestampLte?: InputMaybe -} - -export type PageInfo = { - __typename?: 'PageInfo' - endCursor?: Maybe - hasNextPage: Scalars['Boolean']['output'] - startCursor?: Maybe -} - -export type Query = { - __typename?: 'Query' - calls: Array - chains: Array - extrinsicCsv: Array> - extrinsics: ExtrinsicConnection - modules: Array - squidStatus?: Maybe -} - -export type QueryExtrinsicCsvArgs = { - where: ExtrinsicCsvWhereInput -} - -export type QueryExtrinsicsArgs = { - after?: InputMaybe - first?: Scalars['Int']['input'] - orderBy?: ExtrinsicOrderByInput - where?: InputMaybe -} - -export type RewardsConnection = { - __typename?: 'RewardsConnection' - edges: Array -} - -export type RewardsEdge = { - __typename?: 'RewardsEdge' - node: Transfer -} - -export type SquidStatus = { - __typename?: 'SquidStatus' - /** The height of the processed part of the chain */ - height?: Maybe -} - -export type SubscanLink = { - __typename?: 'SubscanLink' - id: Scalars['String']['output'] - url: Scalars['String']['output'] -} - -export type TokenAmount = { - __typename?: 'TokenAmount' - decimals?: Maybe - planck: Scalars['String']['output'] - symbol?: Maybe - value?: Maybe -} - -export type Transfer = { - __typename?: 'Transfer' - amount: TokenAmount - credit: Scalars['String']['output'] - debit: Scalars['String']['output'] -} - -export type TransferConnection = { - __typename?: 'TransferConnection' - edges: Array -} - -export type TransferEdge = { - __typename?: 'TransferEdge' - node: Transfer -} - -export type ExtrinsicCsvQueryVariables = Exact<{ - address: Scalars['String']['input'] - timestampGte: Scalars['DateTime']['input'] - timestampLte: Scalars['DateTime']['input'] -}> - -export type ExtrinsicCsvQuery = { __typename?: 'Query'; extrinsicCsv: Array> } - -export type ExtrinsicsQueryVariables = Exact<{ - after?: InputMaybe - first: Scalars['Int']['input'] - where?: InputMaybe - orderBy?: InputMaybe -}> - -export type ExtrinsicsQuery = { - __typename?: 'Query' - extrinsics: { - __typename?: 'ExtrinsicConnection' - edges: Array<{ - __typename?: 'ExtrinsicEdge' - node: { - __typename?: 'Extrinsic' - signer?: string | null - hash: string - success: boolean - chain: { - __typename?: 'Chain' - genesisHash: string - name?: string | null - logo?: string | null - subscanUrl?: string | null - } - block: { __typename?: 'Block'; height: number; timestamp: any } - call: { __typename?: 'Call'; name: string; args?: any | null } - fee?: { __typename?: 'TokenAmount'; value?: any | null; symbol?: string | null } | null - transfers: { - __typename?: 'TransferConnection' - edges: Array<{ - __typename?: 'TransferEdge' - node: { - __typename?: 'Transfer' - debit: string - credit: string - amount: { __typename?: 'TokenAmount'; value?: any | null; symbol?: string | null } - } - }> - } - rewards: { - __typename?: 'RewardsConnection' - edges: Array<{ - __typename?: 'RewardsEdge' - node: { - __typename?: 'Transfer' - debit: string - credit: string - amount: { __typename?: 'TokenAmount'; value?: any | null; symbol?: string | null } - } - }> - } - subscanLink?: { __typename?: 'SubscanLink'; id: string; url: string } | null - } - }> - pageInfo: { __typename?: 'PageInfo'; hasNextPage: boolean; endCursor?: string | null } - } -} - -export type FiltersQueryVariables = Exact<{ [key: string]: never }> - -export type FiltersQuery = { - __typename?: 'Query' - modules: Array - chains: Array<{ __typename?: 'Chain'; genesisHash: string; name?: string | null; logo?: string | null }> -} - -export const ExtrinsicCsvDocument = { - kind: 'Document', - definitions: [ - { - kind: 'OperationDefinition', - operation: 'query', - name: { kind: 'Name', value: 'extrinsicCsv' }, - variableDefinitions: [ - { - kind: 'VariableDefinition', - variable: { kind: 'Variable', name: { kind: 'Name', value: 'address' } }, - type: { kind: 'NonNullType', type: { kind: 'NamedType', name: { kind: 'Name', value: 'String' } } }, - }, - { - kind: 'VariableDefinition', - variable: { kind: 'Variable', name: { kind: 'Name', value: 'timestampGte' } }, - type: { kind: 'NonNullType', type: { kind: 'NamedType', name: { kind: 'Name', value: 'DateTime' } } }, - }, - { - kind: 'VariableDefinition', - variable: { kind: 'Variable', name: { kind: 'Name', value: 'timestampLte' } }, - type: { kind: 'NonNullType', type: { kind: 'NamedType', name: { kind: 'Name', value: 'DateTime' } } }, - }, - ], - selectionSet: { - kind: 'SelectionSet', - selections: [ - { - kind: 'Field', - name: { kind: 'Name', value: 'extrinsicCsv' }, - arguments: [ - { - kind: 'Argument', - name: { kind: 'Name', value: 'where' }, - value: { - kind: 'ObjectValue', - fields: [ - { - kind: 'ObjectField', - name: { kind: 'Name', value: 'addressIn' }, - value: { - kind: 'ListValue', - values: [{ kind: 'Variable', name: { kind: 'Name', value: 'address' } }], - }, - }, - { - kind: 'ObjectField', - name: { kind: 'Name', value: 'timestampGte' }, - value: { kind: 'Variable', name: { kind: 'Name', value: 'timestampGte' } }, - }, - { - kind: 'ObjectField', - name: { kind: 'Name', value: 'timestampLte' }, - value: { kind: 'Variable', name: { kind: 'Name', value: 'timestampLte' } }, - }, - ], - }, - }, - ], - }, - ], - }, - }, - ], -} as unknown as DocumentNode -export const ExtrinsicsDocument = { - kind: 'Document', - definitions: [ - { - kind: 'OperationDefinition', - operation: 'query', - name: { kind: 'Name', value: 'extrinsics' }, - variableDefinitions: [ - { - kind: 'VariableDefinition', - variable: { kind: 'Variable', name: { kind: 'Name', value: 'after' } }, - type: { kind: 'NamedType', name: { kind: 'Name', value: 'String' } }, - }, - { - kind: 'VariableDefinition', - variable: { kind: 'Variable', name: { kind: 'Name', value: 'first' } }, - type: { kind: 'NonNullType', type: { kind: 'NamedType', name: { kind: 'Name', value: 'Int' } } }, - }, - { - kind: 'VariableDefinition', - variable: { kind: 'Variable', name: { kind: 'Name', value: 'where' } }, - type: { kind: 'NamedType', name: { kind: 'Name', value: 'ExtrinsicWhereInput' } }, - }, - { - kind: 'VariableDefinition', - variable: { kind: 'Variable', name: { kind: 'Name', value: 'orderBy' } }, - type: { kind: 'NamedType', name: { kind: 'Name', value: 'ExtrinsicOrderByInput' } }, - }, - ], - selectionSet: { - kind: 'SelectionSet', - selections: [ - { - kind: 'Field', - name: { kind: 'Name', value: 'extrinsics' }, - arguments: [ - { - kind: 'Argument', - name: { kind: 'Name', value: 'after' }, - value: { kind: 'Variable', name: { kind: 'Name', value: 'after' } }, - }, - { - kind: 'Argument', - name: { kind: 'Name', value: 'first' }, - value: { kind: 'Variable', name: { kind: 'Name', value: 'first' } }, - }, - { - kind: 'Argument', - name: { kind: 'Name', value: 'where' }, - value: { kind: 'Variable', name: { kind: 'Name', value: 'where' } }, - }, - { - kind: 'Argument', - name: { kind: 'Name', value: 'orderBy' }, - value: { kind: 'Variable', name: { kind: 'Name', value: 'orderBy' } }, - }, - ], - selectionSet: { - kind: 'SelectionSet', - selections: [ - { - kind: 'Field', - name: { kind: 'Name', value: 'edges' }, - selectionSet: { - kind: 'SelectionSet', - selections: [ - { - kind: 'Field', - name: { kind: 'Name', value: 'node' }, - selectionSet: { - kind: 'SelectionSet', - selections: [ - { - kind: 'Field', - name: { kind: 'Name', value: 'chain' }, - selectionSet: { - kind: 'SelectionSet', - selections: [ - { kind: 'Field', name: { kind: 'Name', value: 'genesisHash' } }, - { kind: 'Field', name: { kind: 'Name', value: 'name' } }, - { kind: 'Field', name: { kind: 'Name', value: 'logo' } }, - { kind: 'Field', name: { kind: 'Name', value: 'subscanUrl' } }, - ], - }, - }, - { kind: 'Field', name: { kind: 'Name', value: 'signer' } }, - { kind: 'Field', name: { kind: 'Name', value: 'hash' } }, - { kind: 'Field', name: { kind: 'Name', value: 'success' } }, - { - kind: 'Field', - name: { kind: 'Name', value: 'block' }, - selectionSet: { - kind: 'SelectionSet', - selections: [ - { kind: 'Field', name: { kind: 'Name', value: 'height' } }, - { kind: 'Field', name: { kind: 'Name', value: 'timestamp' } }, - ], - }, - }, - { - kind: 'Field', - name: { kind: 'Name', value: 'call' }, - selectionSet: { - kind: 'SelectionSet', - selections: [ - { kind: 'Field', name: { kind: 'Name', value: 'name' } }, - { kind: 'Field', name: { kind: 'Name', value: 'args' } }, - ], - }, - }, - { - kind: 'Field', - name: { kind: 'Name', value: 'fee' }, - selectionSet: { - kind: 'SelectionSet', - selections: [ - { kind: 'Field', name: { kind: 'Name', value: 'value' } }, - { kind: 'Field', name: { kind: 'Name', value: 'symbol' } }, - ], - }, - }, - { - kind: 'Field', - name: { kind: 'Name', value: 'transfers' }, - selectionSet: { - kind: 'SelectionSet', - selections: [ - { - kind: 'Field', - name: { kind: 'Name', value: 'edges' }, - selectionSet: { - kind: 'SelectionSet', - selections: [ - { - kind: 'Field', - name: { kind: 'Name', value: 'node' }, - selectionSet: { - kind: 'SelectionSet', - selections: [ - { kind: 'Field', name: { kind: 'Name', value: 'debit' } }, - { kind: 'Field', name: { kind: 'Name', value: 'credit' } }, - { - kind: 'Field', - name: { kind: 'Name', value: 'amount' }, - selectionSet: { - kind: 'SelectionSet', - selections: [ - { kind: 'Field', name: { kind: 'Name', value: 'value' } }, - { kind: 'Field', name: { kind: 'Name', value: 'symbol' } }, - ], - }, - }, - ], - }, - }, - ], - }, - }, - ], - }, - }, - { - kind: 'Field', - name: { kind: 'Name', value: 'rewards' }, - selectionSet: { - kind: 'SelectionSet', - selections: [ - { - kind: 'Field', - name: { kind: 'Name', value: 'edges' }, - selectionSet: { - kind: 'SelectionSet', - selections: [ - { - kind: 'Field', - name: { kind: 'Name', value: 'node' }, - selectionSet: { - kind: 'SelectionSet', - selections: [ - { kind: 'Field', name: { kind: 'Name', value: 'debit' } }, - { kind: 'Field', name: { kind: 'Name', value: 'credit' } }, - { - kind: 'Field', - name: { kind: 'Name', value: 'amount' }, - selectionSet: { - kind: 'SelectionSet', - selections: [ - { kind: 'Field', name: { kind: 'Name', value: 'value' } }, - { kind: 'Field', name: { kind: 'Name', value: 'symbol' } }, - ], - }, - }, - ], - }, - }, - ], - }, - }, - ], - }, - }, - { - kind: 'Field', - name: { kind: 'Name', value: 'subscanLink' }, - selectionSet: { - kind: 'SelectionSet', - selections: [ - { kind: 'Field', name: { kind: 'Name', value: 'id' } }, - { kind: 'Field', name: { kind: 'Name', value: 'url' } }, - ], - }, - }, - ], - }, - }, - ], - }, - }, - { - kind: 'Field', - name: { kind: 'Name', value: 'pageInfo' }, - selectionSet: { - kind: 'SelectionSet', - selections: [ - { kind: 'Field', name: { kind: 'Name', value: 'hasNextPage' } }, - { kind: 'Field', name: { kind: 'Name', value: 'endCursor' } }, - ], - }, - }, - ], - }, - }, - ], - }, - }, - ], -} as unknown as DocumentNode -export const FiltersDocument = { - kind: 'Document', - definitions: [ - { - kind: 'OperationDefinition', - operation: 'query', - name: { kind: 'Name', value: 'filters' }, - selectionSet: { - kind: 'SelectionSet', - selections: [ - { kind: 'Field', name: { kind: 'Name', value: 'modules' } }, - { - kind: 'Field', - name: { kind: 'Name', value: 'chains' }, - selectionSet: { - kind: 'SelectionSet', - selections: [ - { kind: 'Field', name: { kind: 'Name', value: 'genesisHash' } }, - { kind: 'Field', name: { kind: 'Name', value: 'name' } }, - { kind: 'Field', name: { kind: 'Name', value: 'logo' } }, - ], - }, - }, - ], - }, - }, - ], -} as unknown as DocumentNode diff --git a/apps/portal/src/generated/gql/extrinsicHistory/gql/index.ts b/apps/portal/src/generated/gql/extrinsicHistory/gql/index.ts deleted file mode 100644 index f9bc8e591..000000000 --- a/apps/portal/src/generated/gql/extrinsicHistory/gql/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './fragment-masking' -export * from './gql' diff --git a/apps/portal/src/hooks/useSetCustomTokens.ts b/apps/portal/src/hooks/useSetCustomTokens.ts deleted file mode 100644 index f5b186597..000000000 --- a/apps/portal/src/hooks/useSetCustomTokens.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { type CustomEvmErc20Token } from '@talismn/balances' -import { useChaindataProvider, useEvmNetworks } from '@talismn/balances-react' -import { githubUnknownTokenLogoUrl } from '@talismn/chaindata-provider' -import { useEffect, useMemo } from 'react' - -export type CustomTokensConfig = CustomTokenConfig[] -export type CustomTokenConfig = { - evmChainId: string - contractAddress: string - symbol: string - decimals: number - coingeckoId?: string - logo?: string -} - -/** - * For app.talisman.xyz, we typically sync the custom tokens list with the user's wallet config. - * - * For other dapps which use `@talismn/balances-react`, we might want to specify a custom list of tokens - * to be fetched. - * - * This hook is an example of how to do just that. - * - * @example - * // tell `@talismn/balances-react` that we want to fetch some - * // more erc20 tokens than just the defaults from chaindata - * useSetCustomTokens([{ - * evmChainId: "11155111", - * contractAddress: "0x56BCB4864B12aB96efFc21fDd59Ea66DB2811c55", - * symbol: "TALI", - * decimals: 18, - * }]) - */ -export const useSetCustomTokens = (customTokensConfig: CustomTokensConfig) => { - const chaindataProvider = useChaindataProvider() - const customTokensConfigMemoised = useMemo( - () => customTokensConfig, - [JSON.stringify(customTokensConfig)] // eslint-disable-line react-hooks/exhaustive-deps - ) - - const evmNetworks = useEvmNetworks() - - useEffect(() => { - if (customTokensConfigMemoised.length === 0) return - - const customTokens = parseCustomEvmErc20Tokens({ customTokensConfig: customTokensConfigMemoised, evmNetworks }) - - chaindataProvider.setCustomTokens(customTokens) - }, [chaindataProvider, customTokensConfigMemoised, evmNetworks]) -} - -export const parseCustomEvmErc20Tokens = ({ - customTokensConfig, - evmNetworks, -}: { - customTokensConfig: CustomTokensConfig - evmNetworks: ReturnType -}): CustomEvmErc20Token[] => { - const customTokens = customTokensConfig.map( - ({ evmChainId, symbol, decimals, contractAddress, coingeckoId, logo }): CustomEvmErc20Token => ({ - id: `${evmChainId}-evm-erc20-${contractAddress}`.toLowerCase(), - type: 'evm-erc20', - isTestnet: evmNetworks[evmChainId]?.isTestnet || false, - symbol, - decimals, - logo: logo ?? githubUnknownTokenLogoUrl, - coingeckoId, - contractAddress, - evmNetwork: { id: evmChainId }, - isCustom: true, - }) - ) - - return customTokens -} diff --git a/apps/portal/src/libs/@talisman-crowdloans/provider.ts b/apps/portal/src/libs/@talisman-crowdloans/provider.ts deleted file mode 100644 index 148436880..000000000 --- a/apps/portal/src/libs/@talisman-crowdloans/provider.ts +++ /dev/null @@ -1,113 +0,0 @@ -import * as Sentry from '@sentry/react' -import { selector } from 'recoil' - -export type CrowdloanDetail = { - id: string - name: string - allowContribute: boolean - image?: string - headerImage?: string - token: string - slug: string - relayId: string - paraId: string - subtitle: string - info: string - links: Record - customRewards?: Array<{ - title: string - value: string - }> - rewards: { - tokens?: string - info?: string - } - bonus: { - full?: string - info?: string - short?: string - } -} - -const crowdloanDataState = selector({ - key: 'CrowdloanData', - get: async () => { - try { - const response = await fetch(`https://api.baserow.io/api/database/rows/table/146542/?user_field_names=true`, { - method: 'GET', - headers: { - Authorization: `Token ${import.meta.env.VITE_BASEROW_CROWDLOANS_AUTH}`, - }, - }) - const data = await response.json() - if (!Array.isArray(data?.results)) throw new Error('Incorrectly formatted crowdloans baserow result') - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const crowdloans: CrowdloanDetail[] = data.results.map((crowdloan: any) => { - const links: Record = Object.keys(crowdloan).reduce( - (acc: Record, key: string) => { - if (key.startsWith('links.')) { - const value = crowdloan[key] - if (value) { - const newKey = key - .replace(/^links\./, '') - .replace(/-(\w)/g, (_, c) => c.toUpperCase()) - .toLowerCase() - acc[newKey] = value - } - } - return acc - }, - {} - ) - - const customRewards = Object.keys(crowdloan) - .filter(key => key.match(/^rewards\.custom\.\d+\.title$/)) - .filter(titleKey => { - const valueKey = titleKey.replace('title', 'value') - return crowdloan[valueKey] !== undefined - }) - .map(titleKey => { - const valueKey = titleKey.replace('title', 'value') - return { - title: crowdloan[titleKey], - value: crowdloan[valueKey], - } - }) - .filter(reward => reward.title && reward.value) - - return { - id: crowdloan?.crowdloanId, - name: crowdloan?.name, - allowContribute: crowdloan?.allowContribute, - token: crowdloan?.token, - slug: crowdloan?.slug, - relayId: crowdloan?.relayId, - paraId: crowdloan?.paraId, - subtitle: crowdloan?.subtitle, - info: crowdloan?.info, - links, - image: crowdloan?.image[0], - headerImage: crowdloan?.headerImage[0], - customRewards, - rewards: { - tokens: crowdloan?.['rewards.tokens'], - info: crowdloan?.['rewards.info'], - }, - bonus: { - short: crowdloan?.['bonus.short'], - full: crowdloan?.['bonus.full'], - info: crowdloan?.['bonus.info'], - }, - } - }) - - return crowdloans - } catch (error) { - Sentry.captureException(error) - return [] - } - }, -}) - -export default crowdloanDataState diff --git a/apps/portal/src/libs/crowdloans/crowdloanOverrides.ts b/apps/portal/src/libs/crowdloans/crowdloanOverrides.ts deleted file mode 100644 index 578abbed0..000000000 --- a/apps/portal/src/libs/crowdloans/crowdloanOverrides.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { decodeAnyAddress } from '@talismn/util' - -export const Acala = makeOverride({ - relayId: 0, - paraId: 2000, - referrer: encodePolkadotAddressAsHexadecimalPublicKey('1564oSHxGVQEaSwHgeYKD1z1A8BXeuqL3hqBSWMA6zHmKnz1'), - api: 'https://crowdloan.aca-api.network', - apiBearerToken: - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoidGFsaXNtYW4iLCJpYXQiOjE2MzYwNTM5NTV9.vplu8f6JI-T7SLuKwKU001KDPof04Lp6cCFyJXLWSKg', -}) -export const Moonbeam = makeOverride({ - relayId: 0, - paraId: 2004, - api: 'https://yy9252r9jh.api.purestake.io', - apiKey: 'QIOqO3FFNj1nmpmGQ0hq7aV3MMdj7r6q8N1hJBh7', - needsVerifierSignature: true, -}) -export const Astar = makeOverride({ - relayId: 0, - paraId: 2006, - referrer: encodePolkadotAddressAsHexadecimalPublicKey('1564oSHxGVQEaSwHgeYKD1z1A8BXeuqL3hqBSWMA6zHmKnz1'), -}) -export const Polkadex = makeOverride({ - relayId: 0, - paraId: 2036, - terms: { - label: 'Zeitgeist Parachain Crowdloan Commitment Terms', - href: 'https://zeitgeist.pm/CrowdloanTerms.pdf', - }, -}) -export const Zeitgeist = makeOverride({ - relayId: 2, - paraId: 2101, - terms: { - label: 'Zeitgeist Parachain Crowdloan Commitment Terms', - href: 'https://zeitgeist.pm/CrowdloanTerms.pdf', - }, -}) -export const BifrostDOT = makeOverride({ - relayId: 0, - paraId: 2030, -}) - -export const overrides = [Acala, Astar, Moonbeam, Zeitgeist] -export const overrideByIds = (relayId: number, paraId: number) => - overrides.find(override => override.is(relayId, paraId)) - -function makeOverride(props: { - relayId: number - paraId: number - referrer?: string - api?: string - apiKey?: string - apiBearerToken?: string - needsVerifierSignature?: true - terms?: { label: string; href: string } -}) { - return { ...props, is: (relayId: number, paraId: number) => relayId === props.relayId && paraId === props.paraId } -} - -function encodePolkadotAddressAsHexadecimalPublicKey(polkadotAddress: string): string { - const byteArray = decodeAnyAddress(polkadotAddress) - const hexEncoded = [...byteArray].map(x => x.toString(16).padStart(2, '0')).join('') - return `0x${hexEncoded}` -} diff --git a/apps/portal/src/libs/crowdloans/index.ts b/apps/portal/src/libs/crowdloans/index.ts deleted file mode 100644 index 3166298ac..000000000 --- a/apps/portal/src/libs/crowdloans/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -// TODO: Integrate this lib into @talismn/api. - -export * from './useCrowdloanContribute' -export * from './useCrowdloanContributions' diff --git a/apps/portal/src/libs/crowdloans/moonbeam/moonbeamStatement.ts b/apps/portal/src/libs/crowdloans/moonbeam/moonbeamStatement.ts deleted file mode 100644 index 713f9fad3..000000000 --- a/apps/portal/src/libs/crowdloans/moonbeam/moonbeamStatement.ts +++ /dev/null @@ -1,20 +0,0 @@ -const moonbeamStatement = `By accessing the Moonbeam Crowdloan Application ("Moonbeam Crowdloan Application"), you represent and warrant to the Moonbeam Foundation LTD. ("Moonbeam Foundation") as follows: - -1. you are not a citizen or resident of a country the laws of which prohibit or conflict with the holding, sale, or trading of tokens; such countries to include (without limitation) THE UNITED STATES OF AMERICA, ITS TERRITORIES AND POSSESSIONS, ANY STATE OF THE UNITED STATES, AND THE DISTRICT OF COLUMBIA ("U.S."), CANADA, PEOPLE'S REPUBLIC OF CHINA, DEMOCRATIC PEOPLE'S REPUBLIC OF KOREA, CUBA, SYRIA, IRAN, SUDAN, PEOPLE'S REPUBLIC OF CRIMEA; -2. you agree and acknowledge that nothing in the Moonbeam Crowdloan Application constitutes a prospectus or offer document of any sort nor is intended to constitute an offer of securities of any form, units in a business trust, units in a collective investment scheme, or any other form of capital markets product or investment in any jurisdiction, nor a solicitation for any form of investment; -3. you agree and acknowledge that no regulatory authority has examined or approved the information set out in the Moonbeam Crowdloan Application and the publication, distribution, or dissemination of information under the Moonbeam Crowdloan Application does not imply to you that the applicable laws, regulatory requirements, or rules have been complied with; -4. your access to, or use of, the Moonbeam Crowdloan Application and the holding of GLMR tokens by you is not prohibited or restricted by any applicable laws, regulations, or rules in any jurisdiction to which you are subject, and where any restrictions are applicable, you have observed and complied with all such restrictions at your own expense and without liability to the Moonbeam Foundation; -5. you agree and acknowledge that the Moonbeam Foundation shall not be liable for any direct, indirect, special, incidental, consequential, or other losses of any kind (including but not limited to loss of revenue, income or profits, and loss of use or data), in tort (including negligence), contract or otherwise, arising out of or in connection with you accessing or using the Moonbeam Crowdloan Application; -6. you waive the right to participate in a class-action lawsuit or a class-wide arbitration against the Moonbeam Foundation, any person involved in the Moonbeam Crowdloan Application and/or with the creation and distribution of the GLMR tokens; -7. you are not a U.S. Person as defined in Regulation S under the Securities Act of 1933, as amended, which means that you are not a natural person resident in the United States of America, its territories and possessions, any State of the United States, and the District Of Columbia ("U.S."), an entity incorporated under the laws of the U.S., an estate/trust where the executor/administrator/trustee is a U.S. Person or a non-discretionary account held for a U.S. Person, an agency or branch of a foreign entity located in the U.S., or an entity incorporated outside the U.S. but formed by a U.S. Person principally for the purposes of investing in unregistered securities under the Securities Act (unless incorporated and owned by accredited investors who are not natural persons, estates or trusts), and you acknowledge, agree and represent as follows: - - any offer, sale, and trade of the GLMR tokens is being made in an offshore transaction, which means that the transaction was not effected in the U.S.; - - no directed selling efforts were made in the United States, which means that no marketing efforts were made to you in the U.S.; - - you are not acquiring GLMR tokens for the account or benefit of any U.S. Person; and - - you agree not to offer or sell the GLMR tokens (or create or maintain any derivative position equivalent thereto) in the U.S., to or for the account or benefit of a U.S. Person; -8. you have sufficient funds to fulfill the obligations of the Moonbeam Foundation within the Moonbeam Crowdloan Application and are not bankrupt or insolvent; -9. you are acquiring GLMR tokens as principal and for your own benefit and you are not acting on the instructions of, or as nominee or agent for or on behalf of, any other person; -10. the GLMR tokens to be delivered to and received by you will not be used for any purpose in connection with money laundering, terrorism financing, or any other acts in breach or contravention of any applicable law, regulation, or rule; -11. you bear the sole responsibility to determine what tax implications your use of the Moonbeam Crowdloan Application may have for you; and -12. all of the above representations and warranties are and will continue to be, true, complete, accurate, and non-misleading from the time of your acceptance of this attestation and notwithstanding the receipt by you of any GLMR tokens.` - -export default moonbeamStatement diff --git a/apps/portal/src/libs/crowdloans/moonbeam/remarkFlow.ts b/apps/portal/src/libs/crowdloans/moonbeam/remarkFlow.ts deleted file mode 100644 index 898d38fb8..000000000 --- a/apps/portal/src/libs/crowdloans/moonbeam/remarkFlow.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { Moonbeam } from '../crowdloanOverrides' -import moonbeamStatement from './moonbeamStatement' -import { type ApiPromise } from '@polkadot/api' -import { type Signer } from '@polkadot/api/types' -import * as crypto from 'crypto' - -export async function submitTermsAndConditions(api: ApiPromise, address: string, signer: Signer) { - if (!signer?.signRaw) throw new Error('Extension does not support signing messages') - - const hash = crypto.createHash('sha256').update(moonbeamStatement).digest('hex') - const signature = (await signer.signRaw({ address, data: hash, type: 'bytes' }))?.signature - - const remark = await agreeRemark(address, signature) - if (!remark) throw new Error('No remark') - const [extrinsicHash, blockHash] = await sendRemark(api, signer, address, remark) - if (!extrinsicHash || !blockHash) throw new Error('No tx info') - const verified = await verifyRemark(address, extrinsicHash, blockHash) - - return verified -} - -async function agreeRemark(address: string, signedMessage: string): Promise { - return ( - await ( - await fetch(`${Moonbeam.api ?? ''}/agree-remark`, { - method: 'POST', - headers: Moonbeam.apiKey ? { 'x-api-key': Moonbeam.apiKey } : undefined, - body: JSON.stringify({ - address, - 'signed-message': signedMessage, - }), - }) - ).json() - ).remark -} - -async function sendRemark(api: ApiPromise, signer: Signer, address: string, remark: string): Promise<[string, string]> { - return await new Promise(resolve => { - const tx = api.tx.system.remark(remark) - void tx.signAndSend(address, { signer, withSignedTransaction: true }, ({ status }) => { - if (!status.isFinalized) return - resolve([tx.hash.toHex(), status.asFinalized.toHex()]) - }) - }) -} - -async function verifyRemark(address: string, extrinsicHash: string, blockHash: string): Promise { - return ( - await ( - await fetch(`${Moonbeam.api ?? ''}/verify-remark`, { - method: 'POST', - headers: Moonbeam.apiKey ? { 'x-api-key': Moonbeam.apiKey } : undefined, - body: JSON.stringify({ - address, - 'extrinsic-hash': extrinsicHash, - 'block-hash': blockHash, - }), - }) - ).json() - ).verified -} diff --git a/apps/portal/src/libs/crowdloans/useCrowdloanContribute.tsx b/apps/portal/src/libs/crowdloans/useCrowdloanContribute.tsx deleted file mode 100644 index 192e459e4..000000000 --- a/apps/portal/src/libs/crowdloans/useCrowdloanContribute.tsx +++ /dev/null @@ -1,1369 +0,0 @@ -/* eslint-disable @typescript-eslint/no-non-null-asserted-optional-chain, @typescript-eslint/no-explicit-any */ - -import type { SubmittableResult } from '@polkadot/api' -import type { MemberType } from 'safety-match' -import { ApiPromise, WsProvider } from '@polkadot/api' -import { type SubmittableExtrinsic } from '@polkadot/api/submittable/types' -import { isEthereumChecksum } from '@polkadot/util-crypto' -import { encodeAnyAddress, planckToTokens, tokensToPlanck } from '@talismn/util' -import BigNumber from 'bignumber.js' -import { useCallback, useEffect, useMemo, useState } from 'react' -import { useRecoilValue } from 'recoil' -import { makeTaggedUnion, none } from 'safety-match' - -import { useConnectedSubstrateWallet } from '@/domains/extension/substrate' -import { parachainDetails, supportedRelayChainsState } from '@/libs/talisman/util/_config' -import customRpcs from '@/util/customRpcs' -import { Maybe } from '@/util/monads' - -import { Acala, Astar, Moonbeam, Zeitgeist } from './crowdloanOverrides' -import { submitTermsAndConditions } from './moonbeam/remarkFlow' -import { useCrowdloanContributions } from './useCrowdloanContributions' - -// -// TODO: Move tx handling into a generic queue, store queue in react context -// - -// -// Types -// - -// ContributeState represents the current state of the contribution modal -export const ContributeState = makeTaggedUnion({ - Uninitialized: none, - Initializing: (props: InitializeProps) => props, - NoRpcsForRelayChain: none, - NoChaindataForRelayChain: none, - IpBanned: none, - Ready: (props: ReadyProps) => props, - RegisteringUser: (props: RegisteringUserProps) => props, - ContributionSubmitting: (props: ContributionSubmittingProps) => props, - ContributionSuccess: (props: ContributionSuccessProps) => props, - ContributionFailed: (props: ContributionFailedProps) => props, -}) -export type ContributeState = MemberType -export type ContributeStateVariant = keyof typeof ContributeState - -// ContributeEvent represents the events which can be externally triggered in order to progress to the next ContributeState -export const ContributeEvent = makeTaggedUnion({ - initialize: (props: InitializeProps) => props, - _noRpcsForRelayChain: none, - _noChaindataForRelayChain: none, - _ipBanned: none, - _initialized: (props: ReadyProps) => props, - // TODO: Implement - // Sign: (props: unknown) => props, - _setApi: (api?: ApiPromise) => api, - setContributionAmount: (contributionAmount: string) => contributionAmount, - setAccount: (account?: string) => account, - setEmail: (email?: string) => email, - setVerifierSignature: (verifierSignature?: VerifierSignature) => verifierSignature, - setMemoAddress: (memoAddress?: string) => memoAddress, - _setAccountBalance: (balance: string | null) => balance, - _setTxFee: (txFee?: string | null) => txFee, - _setValidationError: (validationError?: { i18nCode: string; vars?: Record }) => validationError, - contribute: none, - _setRegisteringUser: (props: RegisteringUserProps) => props, - registerUser: none, - _userRegistered: (props: ReadyProps) => props, - _validateContribution: none, - _setContributionSubmitting: (props: ContributionSubmittingProps) => props, - _finalizedContributionSuccess: (props: ContributionSuccessProps) => props, - _finalizedContributionFailed: (props: ContributionFailedProps) => props, -}) -export type ContributeEvent = MemberType - -// The callback for dispatching ContributeEvents -// This is returned from the useCrowdloanContribute hook -export type DispatchContributeEvent = (event: ContributeEvent) => void - -type VerifierSignature = { sr25519: string } | { ed25519: string } - -// All of the types of props which are passed to the various state constructors -type InitializeProps = { - crowdloanId: string - relayChainId: number - parachainId: number -} -type ReadyProps = { - crowdloanId: string - relayChainId: number - relayNativeToken: string - relayTokenDecimals: number - relayRpcs: string[] - parachainId: number - parachainName?: string - subscanUrl?: string - - contributionAmount: string - account?: string - email?: string - verifierSignature?: VerifierSignature - memoAddress?: string - - api?: ApiPromise - accountBalance?: string | null - txFee?: string | null - validationError?: { i18nCode: string; vars?: Record } - submissionRequested: boolean - submissionValidated: boolean -} -type RegisteringUserProps = { - crowdloanId: string - relayChainId: number - relayNativeToken: string - relayTokenDecimals: number - relayRpcs: string[] - parachainId: number - parachainName?: string - subscanUrl?: string - - contributionAmount: string - account?: string - email?: string - - api?: ApiPromise - submissionRequested: boolean -} - -type ContributionSubmittingProps = { - explorerUrl?: string -} -type ContributionSuccessProps = { - explorerUrl?: string -} -type ContributionFailedProps = { - explorerUrl?: string - error?: string -} - -// -// Hooks (exported) -// - -export function useCrowdloanContribute(): [ContributeState, DispatchContributeEvent] { - // - // set up the state - // - const [state, setState] = useState(ContributeState.Uninitialized) - - // - // set up the event dispatcher - // (used by the UI to trigger events) - // - const dispatch = useCallback((event: ContributeEvent) => setState(state => contributeEventReducer(state, event)), []) - - // - // set up state thunks - // used to asynchronously react to state changes, fire off side effects and dispatch further ContributeEvents - // - useInitializeThunk(state, dispatch) - - useApiThunk(state, dispatch) - useAccountBalanceThunk(state, dispatch) - useTxFeeThunk(state, dispatch) - useValidateAccountHasContributionBalanceThunk(state, dispatch) - useValidateContributionThunk(state, dispatch) - - useMoonbeamVerifierSignatureThunk(state, dispatch) - useMoonbeamRegisterUserThunk(state, dispatch) - useSignAndSendContributionThunk(state, dispatch) - - return [state, dispatch] -} - -// -// Reducers -// - -// the event reducer -// -// it receives the current state and an event -// it then returns the new state -function contributeEventReducer(state: ContributeState, event: ContributeEvent): ContributeState { - const ignoreWithWarning = () => { - console.warn(`Ignoring ContributeEvent.${event.variant}`) - return state - } - const ignoreWithContextWarning = (context: string) => () => { - console.warn(`Ignoring ContributeEvent.${event.variant}: ${context}`) - return state - } - - return event.match({ - initialize: props => - state.match({ - Uninitialized: () => ContributeState.Initializing(props), - _: ignoreWithContextWarning('ContributeState is already initialized'), - }), - - _noRpcsForRelayChain: () => ContributeState.NoRpcsForRelayChain, - _noChaindataForRelayChain: () => ContributeState.NoChaindataForRelayChain, - _ipBanned: () => ContributeState.IpBanned, - _initialized: props => ContributeState.Ready(props), - - _setApi: (api?: ApiPromise) => - state.match({ - Ready: props => { - if (props.api) void props.api.disconnect() - return ContributeState.Ready({ ...props, api }) - }, - RegisteringUser: props => { - if (props.api) void props.api.disconnect() - return ContributeState.RegisteringUser({ ...props, api }) - }, - _: ignoreWithWarning, - }), - - setContributionAmount: (contributionAmount: string) => - state.match({ - Ready: props => - ContributeState.Ready({ - ...props, - contributionAmount: filterContributionAmount(contributionAmount), - verifierSignature: undefined, // changes in contribution amount need a new signature - submissionRequested: false, - submissionValidated: false, - }), - _: ignoreWithWarning, - }), - - setAccount: (account?: string) => - state.match({ - Ready: props => { - // don't reset verifierSignature/submissionRequested/etc when user re-selects the already-selected account - if (account === props.account) return state - - return ContributeState.Ready({ - ...props, - account, - accountBalance: undefined, - verifierSignature: undefined, // changes in account need a new signature - submissionRequested: false, - submissionValidated: false, - }) - }, - _: ignoreWithWarning, - }), - - setEmail: (email?: string) => - state.match({ - Ready: props => - ContributeState.Ready({ - ...props, - email, - submissionRequested: false, - submissionValidated: false, - }), - _: ignoreWithWarning, - }), - - setVerifierSignature: (verifierSignature?: VerifierSignature) => - state.match({ - Ready: props => - ContributeState.Ready({ - ...props, - verifierSignature, - // verifierSignature is set automatically by a thunk, not by text input from the user - // as such, we should continue with the user's request to submit their contribution - even if the verifierSignature has since changed - // submissionRequested: false, - submissionValidated: false, - }), - _: ignoreWithWarning, - }), - - setMemoAddress: (memoAddress?: string) => - state.match({ - Ready: props => - ContributeState.Ready({ - ...props, - memoAddress, - submissionRequested: false, - submissionValidated: false, - }), - _: ignoreWithWarning, - }), - - _setAccountBalance: (balance: string | null) => - state.match({ - Ready: props => - ContributeState.Ready({ - ...props, - accountBalance: balance, - }), - _: ignoreWithWarning, - }), - - _setTxFee: (txFee?: string | null) => - state.match({ - Ready: props => - ContributeState.Ready({ - ...props, - txFee, - }), - _: ignoreWithWarning, - }), - - _setValidationError: (validationError?: { i18nCode: string; vars?: Record }) => - state.match({ - Ready: props => - ContributeState.Ready({ - ...props, - validationError, - // if a validationError is shown to the user, we should cancel their request to submit their contribution - submissionRequested: validationError !== undefined ? false : props.submissionRequested, - submissionValidated: false, - }), - _: ignoreWithWarning, - }), - - contribute: () => - state.match({ - Ready: props => - ContributeState.Ready({ - ...props, - submissionRequested: true, - submissionValidated: false, - }), - _: ignoreWithWarning, - }), - - _setRegisteringUser: props => - state.match({ - Ready: () => ContributeState.RegisteringUser(props), - _: ignoreWithWarning, - }), - - registerUser: () => - state.match({ - RegisteringUser: props => - ContributeState.RegisteringUser({ - ...props, - submissionRequested: true, - }), - _: ignoreWithWarning, - }), - - _userRegistered: props => - state.match({ - RegisteringUser: () => - ContributeState.Ready({ - ...props, - submissionRequested: true, - submissionValidated: false, - }), - _: ignoreWithWarning, - }), - - _validateContribution: () => - state.match({ - Ready: props => - ContributeState.Ready({ - ...props, - validationError: undefined, - submissionValidated: true, - }), - _: ignoreWithWarning, - }), - - _setContributionSubmitting: props => - state.match({ - Ready: () => ContributeState.ContributionSubmitting(props), - ContributionSubmitting: () => ContributeState.ContributionSubmitting(props), - _: ignoreWithWarning, - }), - - _finalizedContributionSuccess: props => - state.match({ - ContributionSubmitting: () => ContributeState.ContributionSuccess(props), - _: ignoreWithWarning, - }), - - _finalizedContributionFailed: props => - state.match({ - ContributionSubmitting: () => ContributeState.ContributionFailed(props), - _: ignoreWithWarning, - }), - }) -} - -// -// Hooks (internal) -// - -// When in the ContributeState.Initializing state, this thunk will call _setInitialized as needed -function useInitializeThunk(state: ContributeState, dispatch: DispatchContributeEvent) { - const stateDeps = state.match({ - Initializing: ({ crowdloanId, relayChainId, parachainId }) => ({ crowdloanId, relayChainId, parachainId }), - _: () => false as const, - }) - - const relayChains = useRecoilValue(supportedRelayChainsState) - useEffect(() => { - void (async () => { - if (!stateDeps) return - const { crowdloanId, relayChainId, parachainId } = stateDeps - - const relayChaindata = relayChains.find(x => x.id === relayChainId) - const relayExtraChaindata = relayChains.find(x => x.id === relayChainId) - const relayChainCustomRpcs = customRpcs[relayChainId.toString()] - - const relayRpcs = - relayChainCustomRpcs?.length! > 0 - ? relayChainCustomRpcs ?? [] - : Maybe.of(relayChaindata?.rpc).mapOrUndefined(x => [x]) ?? [] - const hasRelayRpcs = relayRpcs?.length > 0 - if (!hasRelayRpcs) return dispatch(ContributeEvent._noRpcsForRelayChain) - - const { tokenSymbol: relayNativeToken, tokenDecimals: relayTokenDecimals } = relayChaindata! - if (!relayNativeToken) return dispatch(ContributeEvent._noChaindataForRelayChain) - if (!relayTokenDecimals) return dispatch(ContributeEvent._noChaindataForRelayChain) - - const parachainName = parachainDetails.find(x => x.id === `${relayChainId}-${parachainId}`)?.name - - if (Moonbeam.is(relayChainId, parachainId)) { - const ipBlockedResponse = await fetch(`${Moonbeam.api}/health`, { - headers: Moonbeam.apiKey ? { 'x-api-key': Moonbeam.apiKey } : undefined, - }) - const status = ipBlockedResponse.status - const body = await ipBlockedResponse.text() - if (status !== 200 || body !== '') return dispatch(ContributeEvent._ipBanned) - } - - dispatch( - ContributeEvent._initialized({ - crowdloanId, - relayChainId, - relayNativeToken, - relayTokenDecimals, - relayRpcs, - parachainId, - parachainName, - subscanUrl: relayExtraChaindata?.subscanUrl, - contributionAmount: '', - submissionRequested: false, - submissionValidated: false, - }) - ) - })() - }, [dispatch, JSON.stringify(stateDeps)]) // eslint-disable-line react-hooks/exhaustive-deps -} - -// When in the ContributeState.Ready state, this thunk will call setApi as needed -function useApiThunk(state: ContributeState, dispatch: DispatchContributeEvent) { - const stateDeps = state.match({ - Ready: ({ relayRpcs }) => ({ relayRpcs }), - RegisteringUser: ({ relayRpcs }) => ({ relayRpcs }), - _: () => false as const, - }) - - useEffect(() => { - if (!stateDeps) return - const { relayRpcs } = stateDeps - - let shouldDisconnect = false - - void ApiPromise.create({ provider: new WsProvider(relayRpcs), noInitWarn: true }).then(async api => { - if (shouldDisconnect) return await api.disconnect() - dispatch(ContributeEvent._setApi(api)) - }) - - return () => { - shouldDisconnect = true - dispatch(ContributeEvent._setApi()) - } - }, [dispatch, JSON.stringify(stateDeps)]) // eslint-disable-line react-hooks/exhaustive-deps -} - -// When in the ContributeState.Ready state, this thunk will call setAccountBalance as needed -function useAccountBalanceThunk(state: ContributeState, dispatch: DispatchContributeEvent) { - const { api, account, relayTokenDecimals } = state.match({ - Ready: ({ api, account, relayTokenDecimals }) => ({ api, account, relayTokenDecimals }), - _: () => ({ api: undefined, account: undefined, relayTokenDecimals: undefined }), - }) - - useEffect(() => { - if (api === undefined || account === undefined || relayTokenDecimals === undefined) { - return - } - - void (async () => { - const balances = await api.derive.balances.all(account) - - dispatch( - ContributeEvent._setAccountBalance( - new BigNumber(balances.availableBalance.toString()).shiftedBy(-relayTokenDecimals).toString() - ) - ) - })() - }, [account, api, dispatch, relayTokenDecimals]) -} - -function useValidateAccountHasContributionBalanceThunk(state: ContributeState, dispatch: DispatchContributeEvent) { - const stateDeps = state.match({ - Ready: ({ contributionAmount, accountBalance }) => ({ contributionAmount, accountBalance }), - _: () => false as const, - }) - - useEffect(() => { - if (!stateDeps) return - const { contributionAmount, accountBalance } = stateDeps - - const setBalanceError = () => dispatch(ContributeEvent._setValidationError({ i18nCode: 'Account balance too low' })) - const clearBalanceError = () => dispatch(ContributeEvent._setValidationError()) - - if (!contributionAmount || contributionAmount === '') return clearBalanceError() - if (Number.isNaN(Number(contributionAmount))) return clearBalanceError() - if (!accountBalance) return clearBalanceError() - if (new BigNumber(contributionAmount).isLessThanOrEqualTo(new BigNumber(accountBalance))) return clearBalanceError() - - setBalanceError() - }, [dispatch, JSON.stringify(stateDeps)]) // eslint-disable-line react-hooks/exhaustive-deps -} - -function useTxFeeThunk(state: ContributeState, dispatch: DispatchContributeEvent) { - const stateDeps = state.match({ - Ready: ({ - relayChainId, - relayTokenDecimals, - parachainId, - contributionAmount, - account, - email, - verifierSignature, - memoAddress, - api, - }) => ({ - relayChainId, - relayTokenDecimals, - parachainId, - contributionAmount, - account, - email, - verifierSignature, - memoAddress, - api, - }), - _: () => false as const, - }) - const { api, ...jsonCmpStateDeps } = stateDeps || {} - - useEffect(() => { - let cancelled = false - - void (async () => { - if (!stateDeps) return - const { - relayChainId, - relayTokenDecimals, - parachainId, - contributionAmount, - account, - email, - verifierSignature, - memoAddress, - api, - } = stateDeps - - dispatch(ContributeEvent._setTxFee()) - - if (!api?.isReady) return - if (!contributionAmount) return dispatch(ContributeEvent._setTxFee(null)) - if (!account) return dispatch(ContributeEvent._setTxFee(null)) - if (Moonbeam.is(relayChainId, parachainId) && !memoAddress) return dispatch(ContributeEvent._setTxFee(null)) - - const contributionPlanck = tokensToPlanck(contributionAmount, relayTokenDecimals) - - let tx - try { - tx = await buildTx({ - relayChainId, - parachainId, - - contributionPlanck, - account, - email, - verifierSignature, - memoAddress, - - api, - estimateOnly: true, - }) - } catch (error: any) { - dispatch(ContributeEvent._setValidationError({ i18nCode: error?.message || error.toString() })) - return - } - - const { partialFee } = await tx.paymentInfo(account) - const feeTokens = planckToTokens(partialFee.toString(), relayTokenDecimals) - - if (cancelled) return - - dispatch(ContributeEvent._setTxFee(feeTokens)) - })() - - return () => { - cancelled = true - dispatch(ContributeEvent._setTxFee(null)) - } - }, [dispatch, stateDeps !== false && stateDeps?.api, JSON.stringify(jsonCmpStateDeps)]) // eslint-disable-line react-hooks/exhaustive-deps -} - -function useMoonbeamVerifierSignatureThunk(state: ContributeState, dispatch: DispatchContributeEvent) { - const stateDeps = state.match({ - Ready: ({ - crowdloanId, - relayChainId, - relayNativeToken, - relayTokenDecimals, - relayRpcs, - parachainId, - parachainName, - subscanUrl, - - contributionAmount, - account, - email, - verifierSignature, - - api, - submissionRequested, - submissionValidated, - }) => ({ - crowdloanId, - relayChainId, - relayNativeToken, - relayTokenDecimals, - relayRpcs, - parachainId, - parachainName, - subscanUrl, - - contributionAmount, - account, - email, - verifierSignature, - - api, - submissionRequested, - submissionValidated, - }), - _: () => false as const, - }) - // don't fetch contributions unless we're on moonbeam crowdloan - // (if accounts is [] then useCrowdloanContributions will skip the query) - const contributionsProps = - stateDeps !== false && stateDeps.account !== undefined && Moonbeam.is(stateDeps.relayChainId, stateDeps.parachainId) - ? { accounts: [stateDeps.account], crowdloanId: stateDeps.crowdloanId } - : { accounts: [], crowdloanId: undefined } - - const { gqlContributions, hydrated: contributionsHydrated } = useCrowdloanContributions( - // memoize to prevent infinite loop - useMemo( - () => contributionsProps.accounts, - [JSON.stringify(contributionsProps.accounts)] // eslint-disable-line react-hooks/exhaustive-deps - ) - ) - const moonbeamContributions = gqlContributions.filter(c => c.crowdloan.id === contributionsProps.crowdloanId) - - useEffect(() => { - if (!stateDeps) return - const { - crowdloanId, - relayChainId, - relayNativeToken, - relayTokenDecimals, - relayRpcs, - parachainId, - parachainName, - subscanUrl, - - contributionAmount, - account, - email, - verifierSignature, - - api, - submissionRequested, - submissionValidated, - } = stateDeps - - if (!Moonbeam.is(relayChainId, parachainId)) return - if (!account) return - if (!contributionsHydrated) return - if (!api) return - - // cancel if we have already calculated a verifierSignature - if (verifierSignature) return - - // wait for user to hit submit - if (!submissionRequested) return - - // wait for clientside validations to complete - if (!submissionValidated) return - void (async () => { - // check if user is already registered for the moonbeam crowdloan - const checkRemarkResponse = await fetch( - `${Moonbeam.api}/check-remark/${encodeAnyAddress(account, relayChainId)}`, - { headers: Moonbeam.apiKey ? { 'x-api-key': Moonbeam.apiKey } : undefined } - ) - if (!checkRemarkResponse.ok) - return dispatch( - ContributeEvent._setValidationError({ - i18nCode: 'The Moonbeam API is unavailable, please try again later', - }) - ) - if (!(await checkRemarkResponse.json()).verified) - return dispatch( - ContributeEvent._setRegisteringUser({ - crowdloanId, - relayChainId, - relayNativeToken, - relayTokenDecimals, - relayRpcs, - parachainId, - parachainName, - subscanUrl, - - contributionAmount, - account, - email, - - api, - submissionRequested: false, - }) - ) - - // get verificationSignature from moonbeam api - const guid = globalThis.crypto.randomUUID() - const previousTotalContributions = moonbeamContributions - .reduce((total, contribution) => new BigNumber(total).plus(contribution.amount), new BigNumber(0)) - .toString() - const contributionPlanck = tokensToPlanck(contributionAmount, relayTokenDecimals) - const makeSignatureResponse = await fetch(`${Moonbeam.api}/make-signature`, { - method: 'POST', - headers: Moonbeam.apiKey ? { 'x-api-key': Moonbeam.apiKey } : undefined, - body: JSON.stringify({ - address: encodeAnyAddress(account, relayChainId), - 'previous-total-contribution': previousTotalContributions, - contribution: contributionPlanck, - guid, - }), - }) - if (!makeSignatureResponse.ok) - return dispatch( - ContributeEvent._setValidationError({ i18nCode: 'The Moonbeam API is unavailable, please try again later' }) - ) - const { signature } = await makeSignatureResponse.json() - - dispatch(ContributeEvent.setVerifierSignature({ sr25519: signature as string })) - })() - }, [dispatch, moonbeamContributions, contributionsHydrated, stateDeps]) -} - -function useMoonbeamRegisterUserThunk(state: ContributeState, dispatch: DispatchContributeEvent) { - const stateDeps = state.match({ - RegisteringUser: ({ - crowdloanId, - relayChainId, - relayNativeToken, - relayTokenDecimals, - relayRpcs, - parachainId, - parachainName, - subscanUrl, - - contributionAmount, - account, - email, - - api, - submissionRequested, - }) => ({ - crowdloanId, - relayChainId, - relayNativeToken, - relayTokenDecimals, - relayRpcs, - parachainId, - parachainName, - subscanUrl, - - contributionAmount, - account, - email, - - api, - submissionRequested, - }), - _: () => false as const, - }) - const { api, ...jsonCmpStateDeps } = stateDeps || {} - - const signer = useConnectedSubstrateWallet()?.signer - useEffect(() => { - if (!stateDeps) return - - const { - crowdloanId, - relayChainId, - relayNativeToken, - relayTokenDecimals, - relayRpcs, - parachainId, - parachainName, - subscanUrl, - - contributionAmount, - account, - email, - - api, - submissionRequested, - } = stateDeps - - if (signer === undefined) return - if (!api) return - if (!submissionRequested) return - if (!account) - return dispatch( - ContributeEvent._initialized({ - crowdloanId, - relayChainId, - relayNativeToken, - relayTokenDecimals, - relayRpcs, - parachainId, - parachainName, - subscanUrl, - - contributionAmount, - account, - email, - - api, - submissionRequested: false, - submissionValidated: false, - }) - ) - void (async () => { - const verified = await submitTermsAndConditions(api, encodeAnyAddress(account, relayChainId), signer) - if (!verified) throw new Error('Failed to verify user registration') - dispatch( - ContributeEvent._userRegistered({ - crowdloanId, - relayChainId, - relayNativeToken, - relayTokenDecimals, - relayRpcs, - parachainId, - parachainName, - subscanUrl, - - contributionAmount, - account, - email, - - api, - submissionRequested: true, - submissionValidated: false, - }) - ) - })() - }, [dispatch, stateDeps !== false && stateDeps?.api, JSON.stringify(jsonCmpStateDeps)]) // eslint-disable-line react-hooks/exhaustive-deps -} - -function useValidateContributionThunk(state: ContributeState, dispatch: DispatchContributeEvent) { - const stateDeps = state.match({ - Ready: ({ - relayChainId, - relayNativeToken, - relayTokenDecimals, - parachainId, - - contributionAmount, - account, - memoAddress, - - api, - accountBalance, - txFee, - submissionRequested, - }) => ({ - relayChainId, - relayNativeToken, - relayTokenDecimals, - parachainId, - - contributionAmount, - account, - memoAddress, - - api, - accountBalance, - txFee, - submissionRequested, - }), - _: () => false as const, - }) - const { api, ...jsonCmpStateDeps } = stateDeps || {} - - useEffect(() => { - if (!stateDeps) return - const { - relayChainId, - relayNativeToken, - relayTokenDecimals, - parachainId, - - contributionAmount, - account, - memoAddress, - - api, - accountBalance, - txFee, - submissionRequested, - } = stateDeps - - // these validations will only run after the user hits submit - if (!submissionRequested) return - - const setError = (error: { i18nCode: string; vars?: Record }) => - dispatch(ContributeEvent._setValidationError(error)) - - if (!contributionAmount || contributionAmount.length < 1 || Number(contributionAmount) === 0) { - setError({ i18nCode: 'Please enter an amount of {{token}}', vars: { token: relayNativeToken } }) - return - } - if (Number.isNaN(Number(contributionAmount))) { - setError({ i18nCode: 'Please enter a valid amount of {{token}}', vars: { token: relayNativeToken } }) - return - } - if (!account || account.length < 1) { - setError({ i18nCode: 'An account must be selected' }) - return - } - if (Moonbeam.is(relayChainId, parachainId) && (!memoAddress || memoAddress.length < 1)) { - setError({ i18nCode: 'Please enter your moonbeam rewards address' }) - return - } - if (Moonbeam.is(relayChainId, parachainId) && (!memoAddress || !isEthereumChecksum(memoAddress))) { - setError({ i18nCode: 'Please enter a valid moonbeam rewards address' }) - return - } - - if (!accountBalance) return - if (new BigNumber(accountBalance).isLessThan(new BigNumber(contributionAmount))) { - setError({ i18nCode: 'Account balance too low' }) - return - } - - if (!api) return - - const minContribution = Acala.is(relayChainId, parachainId) - ? '1' // acala liquid crowdloan has a minimum of 1 DOT - : api.consts.crowdloan?.minContribution?.toString() - const contributionPlanck = tokensToPlanck(contributionAmount, relayTokenDecimals) - const minimum = new BigNumber(planckToTokens(minContribution?.toString(), relayTokenDecimals) || '').toFixed(2) - - if (!contributionPlanck || new BigNumber(contributionPlanck).isLessThan(new BigNumber(minContribution ?? 0))) { - setError({ - i18nCode: 'A minimum of {{minimum}} {{token}} is required', - vars: { minimum, token: relayNativeToken }, - }) - return - } - - if (typeof txFee !== 'string') return - - // TODO: Test that contribution doesn't go above crowdloan cap - // TODO: Test that crowdloan has not ended - // TODO: Test that crowdloan is in a valid lease period - // TODO: Test that crowdloan has not already won - // TODO: Validate validator signature - // https://github.com/paritytech/polkadot/blob/dee1484760aedfd699e764f2b7c7d85855f7b077/runtime/common/src/crowdloan.rs#L432 - - const existentialDeposit = relayChainId === 2 ? 0.0000333333 : relayChainId === 0 ? 1 : 1 - const expectedLoss = new BigNumber(contributionAmount).plus(new BigNumber(txFee)) - - if ( - new BigNumber(accountBalance) - .minus(new BigNumber(expectedLoss)) - .isLessThanOrEqualTo(new BigNumber(existentialDeposit)) - ) { - setError({ - i18nCode: `Your account can't go below a minimum of {{existentialDeposit}} {{token}}`, - vars: { existentialDeposit, token: relayNativeToken }, - }) - return - } - - dispatch(ContributeEvent._validateContribution) - }, [dispatch, stateDeps !== false && stateDeps?.api, JSON.stringify(jsonCmpStateDeps)]) // eslint-disable-line react-hooks/exhaustive-deps -} - -function useSignAndSendContributionThunk(state: ContributeState, dispatch: DispatchContributeEvent) { - const stateDeps = state.match({ - Ready: ({ - relayChainId, - relayTokenDecimals, - parachainId, - subscanUrl, - contributionAmount, - account, - email, - verifierSignature, - memoAddress, - api, - submissionValidated, - }) => ({ - relayChainId, - relayTokenDecimals, - parachainId, - subscanUrl, - contributionAmount, - account, - email, - verifierSignature, - memoAddress, - api, - submissionValidated, - }), - _: () => false as const, - }) - - const { api, ...jsonCmpStateDeps } = stateDeps || {} - const signer = useConnectedSubstrateWallet()?.signer - - useEffect(() => { - let cancelled = false - - void (async () => { - if (!stateDeps) return - const { - relayChainId, - relayTokenDecimals, - parachainId, - subscanUrl, - contributionAmount, - account, - email, - verifierSignature, - memoAddress, - api, - submissionValidated, - } = stateDeps - - if (!submissionValidated) return - if (!account) return - - // after submissionValidated is set to true, useMoonbeamVerifierSignatureThunk will set the verifierSignature - // we should wait for that to happen before we make the contribution - if (Moonbeam.is(relayChainId, parachainId) && !verifierSignature) return - - if (!api) return - const contributionPlanck = tokensToPlanck(contributionAmount, relayTokenDecimals) - - if (cancelled) return - - let tx - try { - tx = await buildTx({ - relayChainId, - parachainId, - - contributionPlanck, - account, - email, - verifierSignature, - memoAddress, - - api, - }) - } catch (error: any) { - dispatch(ContributeEvent._setValidationError({ i18nCode: error?.message || error.toString() })) - return - } - - let txSigned - try { - txSigned = await tx.signAsync(account, { signer }) - } catch (error: any) { - dispatch(ContributeEvent._setValidationError({ i18nCode: error?.message || error.toString() })) - return - } - if (cancelled) return - - dispatch(ContributeEvent._setContributionSubmitting({})) - - try { - const unsub = await txSigned.send(async result => { - const { status, events = [], dispatchError } = result - - for (const { - phase, - event: { data, method, section }, - } of events) { - console.info(`\t${phase}: ${section}.${method}:: ${data}`) - } - - if (status.isInBlock) { - console.info(`Transaction included at blockHash ${status.asInBlock}`) - dispatch( - ContributeEvent._setContributionSubmitting({ - explorerUrl: await deriveExplorerUrl(api, result, subscanUrl), - }) - ) - } - if (status.isFinalized) { - console.info(`Transaction finalized at blockHash ${status.asFinalized}`) - unsub() - - let success = false - let error - if ( - events.some(({ event: { method, section } }) => section === 'system' && method === 'ExtrinsicSuccess') && - !events.some(({ event: { method, section } }) => section === 'system' && method === 'ExtrinsicFailed') - ) { - success = true - } - - // https://polkadot.js.org/docs/api/cookbook/tx#how-do-i-get-the-decoded-enum-for-an-extrinsicfailed-event - if (dispatchError?.isModule && api) { - const decoded = api.registry.findMetaError(dispatchError.asModule) - const { docs, name, section } = decoded - error = `${section}.${name}: ${docs.join(' ')}` - } else if (dispatchError) { - error = dispatchError.toString() - } - - const explorerUrl = await deriveExplorerUrl(api, result, subscanUrl) - - if (success && !error) { - dispatch(ContributeEvent._finalizedContributionSuccess({ explorerUrl })) - } else { - dispatch(ContributeEvent._finalizedContributionFailed({ error, explorerUrl })) - } - } - }) - } catch (error: any) { - dispatch(ContributeEvent._finalizedContributionFailed({ error: error?.message || error.toString() })) - } - })() - - return () => { - cancelled = true - } - }, [dispatch, stateDeps !== false && stateDeps?.api, JSON.stringify(jsonCmpStateDeps)]) // eslint-disable-line react-hooks/exhaustive-deps -} - -// -// Helpers (internal) -// - -const filterContributionAmount = (contributionAmount: string): string => - contributionAmount - // remove anything which isn't a number or a decimal point - .replace(/[^.\d]/g, '') - // remove any decimal points after the first decimal point - .replace(/\./g, (match: string, offset: number, string: string) => - match === '.' ? (string.indexOf('.') === offset ? '.' : '') : '' - ) - -type BuildTxProps = { - relayChainId: number - parachainId: number - - contributionPlanck: string - account: string - email?: string - verifierSignature?: any - memoAddress?: string - - api: ApiPromise - estimateOnly?: true -} -type BuildTxResponse = SubmittableExtrinsic<'promise', SubmittableResult> -async function buildTx(props: BuildTxProps): Promise { - if (Acala.is(props.relayChainId, props.parachainId)) return await buildAcalaTx(props) - if (Astar.is(props.relayChainId, props.parachainId)) return await buildAstarTx(props) - if (Moonbeam.is(props.relayChainId, props.parachainId)) return await buildMoonbeamTx(props) - if (Zeitgeist.is(props.relayChainId, props.parachainId)) return await buildZeitgeistTx(props) - return await buildGenericTx(props) -} - -async function buildGenericTx({ - parachainId, - contributionPlanck, - verifierSignature, - api, -}: BuildTxProps): Promise { - const txs = [ - api.tx.crowdloan?.contribute?.(parachainId, contributionPlanck, verifierSignature), - api.tx.system.remarkWithEvent('Talisman - The Journey Begins'), - ] - - return api.tx.utility.batchAll(txs as any) -} - -async function buildZeitgeistTx({ - parachainId, - contributionPlanck, - verifierSignature, - api, -}: BuildTxProps): Promise { - const txs = [ - api.tx.crowdloan?.contribute?.(parachainId, contributionPlanck, verifierSignature), - api.tx.system.remarkWithEvent( - 'I have read and agree to the terms found at https://zeitgeist.pm/CrowdloanTerms.pdf' - ), - api.tx.system.remarkWithEvent('Talisman - The Journey Begins'), - ] - - return api.tx.utility.batchAll(txs as any[]) -} - -async function buildMoonbeamTx({ - parachainId, - contributionPlanck, - verifierSignature, - memoAddress, - api, -}: BuildTxProps): Promise { - const txs = [ - api.tx.crowdloan?.contribute?.(parachainId, contributionPlanck, verifierSignature), - api.tx.crowdloan?.addMemo?.(parachainId, memoAddress ?? ''), - api.tx.system.remarkWithEvent('Talisman - The Journey Begins'), - ] - - return api.tx.utility.batchAll(txs as any) -} - -async function buildAstarTx({ - parachainId, - contributionPlanck, - verifierSignature, - api, -}: BuildTxProps): Promise { - const referrerAddress = Astar.referrer - - const txs = [ - api.tx.crowdloan?.contribute?.(parachainId, contributionPlanck, verifierSignature), - api.tx.crowdloan?.addMemo?.(parachainId, referrerAddress ?? ''), - api.tx.system.remarkWithEvent('Talisman - The Journey Begins'), - ] - - return api.tx.utility.batchAll(txs as any) -} - -async function buildAcalaTx({ - relayChainId, - contributionPlanck, - account, - email, - api, - estimateOnly, -}: BuildTxProps): Promise { - // don't bother acala's API when we only want to calculate a txFee - const statementResult = estimateOnly - ? { - paraId: Acala.paraId, - statementMsgHash: '0x39d8579eba72f48339686db6c4f6fb1c3164bc09b10226e64211c12b0b43460d', - statement: - 'I hereby agree to the terms of the statement whose SHA-256 multihash is QmeUtSuuMBAKzcfLJB2SnMfQoeifYagyWrrNhucRX1vjA8. (This may be found at the URL: https://acala.network/acala/terms)', - proxyAddress: '12CnY6b89bFfTbM6Wh3cdFvHAfxxsDUQHHXtT2Ui7SQzxBrn', - } - : await (await fetch(`${Acala.api}/statement`)).json() - - const proxyAddress = statementResult.proxyAddress - const statement = statementResult.statement - - if (!proxyAddress || proxyAddress.length < 1) throw new Error('Internal error (missing proxy address)') - if (!statement || statement.length < 1) throw new Error('Internal error (missing statement)') - - const address = encodeAnyAddress(account, relayChainId) - const amount = contributionPlanck - const referral = Acala.referrer - const receiveEmail = email !== undefined && email.length > 0 - - if (!estimateOnly) { - const response = await fetch(`${Acala.api}/transfer`, { - method: 'POST', - headers: { - Authorization: `Bearer ${Acala.apiBearerToken}`, - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - address, - amount, - referral, - email: receiveEmail ? email : undefined, - receiveEmail, - }), - }) - if (!response.ok) throw new Error('Acala API rate-limit reached. Please try again in 60 seconds.') - } - - const txs = [ - api.tx.balances.transferKeepAlive(proxyAddress, amount), - api.tx.system.remarkWithEvent(statement), - api.tx.system.remarkWithEvent(`referrer:${referral}`), - api.tx.system.remarkWithEvent('Talisman - The Journey Begins'), - ] - - return api.tx.utility.batchAll(txs) -} - -export async function deriveExplorerUrl( - api: ApiPromise, - result: SubmittableResult, - subscanUrl?: string -): Promise { - if (!subscanUrl) return - - const { status, events = [] } = result - - // - // Step 1: Get block number - // - - const blockHash = status.isFinalized - ? status.asFinalized.toHex() - : status.isInBlock - ? status.asInBlock.toHex() - : undefined - - // extrinsic has not yet been included in a block, so we cannot yet derive an explorer url - if (blockHash === undefined) return - - const { block } = await api.rpc.chain.getBlock(blockHash) - const blockNumber = block.header.number.toNumber() - - // - // Step 2: Get extrinsic index in block - // - - const txStatusEvent = events.find( - ({ event: { method, section } }) => section === 'system' && ['ExtrinsicSuccess', 'ExtrinsicFailed'].includes(method) - ) - if (!txStatusEvent) - console.error( - 'Failed to find the extrinsic status event. If you see this error, there is likely a bug in the Talisman codebase. Please report it to the Talisman team for investigation.' - ) - - const txIndex = txStatusEvent?.phase.isApplyExtrinsic ? txStatusEvent.phase.asApplyExtrinsic : undefined - if (txIndex === undefined) - console.error( - 'Failed to derive the extrinsic index. If you see this error, there is likely a bug in the Talisman codebase. Please report it to the Talisman team for investigation.' - ) - - // - // Step 3: Concatenate - // - - const txId = blockNumber && txIndex !== undefined ? `${blockNumber}-${txIndex}` : undefined - const explorerUrl = txId ? `${subscanUrl}/extrinsic/${txId}` : `${subscanUrl}/block/${blockHash}` - - return explorerUrl -} diff --git a/apps/portal/src/libs/crowdloans/useCrowdloanContributions.ts b/apps/portal/src/libs/crowdloans/useCrowdloanContributions.ts deleted file mode 100644 index 079981071..000000000 --- a/apps/portal/src/libs/crowdloans/useCrowdloanContributions.ts +++ /dev/null @@ -1,299 +0,0 @@ -import { encodeAnyAddress } from '@talismn/util' -import BigNumber from 'bignumber.js' -import { request } from 'graphql-request' -import { useCallback, useEffect, useMemo, useState } from 'react' -import { useInterval } from 'react-use' -import { useRecoilValue } from 'recoil' - -import type { ContributionsQuery } from '@/generated/gql/crowdloan/gql/graphql' -import { selectedSubstrateAccountsState } from '@/domains/accounts/recoils' -import { graphql } from '@/generated/gql/crowdloan/gql' -import { useChainmetaValue } from '@/libs/talisman' - -type ContributionsIndexerConfig = { - accountIndex: number - genesisHash: string - indexerUrl: string - setStateFn: (contributions: _GqlContribution[]) => void - setHydratedFn: (hydrated: boolean) => void -} - -const ContributionsIndexerConfigs = { - polkadot: { - indexerUrl: import.meta.env.VITE_DOT_CROWDLOAN_INDEXER, - accountIndex: 0, - genesisHash: '0x91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3', - }, - kusama: { - indexerUrl: import.meta.env.VITE_KSM_CROWDLOAN_INDEXER, - accountIndex: 2, - genesisHash: '0xb0a8d493285c2df73290dfb7e61f870f17b41801197a149ca93654499ea3dafe', - }, -} - -export type CrowdloanContribution = { - id: string - account: string - amount: string - - // the associated parachain - parachain: { paraId: string } - - // the associated crowdloan - fund: { id: string } -} - -type _GqlContribution = ContributionsQuery['contributions'][0] & { relay: { genesisHash: string } } -export type GqlContribution = ContributionsQuery['contributions'][0] & { - relay: { genesisHash: string } - blockNumber: number - blockPeriod: number - lockedSeconds: number - isLocked: boolean - isUnlockingSoon: boolean - isFundsReturned: boolean - oldAndReturned: boolean -} - -export function useCrowdloanContributions(accounts?: string[]): { - gqlContributions: GqlContribution[] - sortedGqlContributions: GqlContribution[] - hydrated: boolean -} { - const allAccounts = useRecoilValue(selectedSubstrateAccountsState) - - const [dotContributions, setDotContributions] = useState<_GqlContribution[]>([]) - const [ksmContributions, setKsmContributions] = useState<_GqlContribution[]>([]) - - const [dotHydrated, setDotHydrated] = useState(false) - const [ksmHydrated, setKsmHydrated] = useState(false) - - const fetchGqlContributions = useCallback( - ({ accountIndex, genesisHash, indexerUrl, setStateFn, setHydratedFn }: ContributionsIndexerConfig) => { - let cancelled = false - - const addresses = (accounts ?? allAccounts.map(account => account.address)).map(address => - encodeAnyAddress(address, accountIndex) - ) - - const promise = request( - indexerUrl, - graphql(` - query contributions($addresses: [String!]!) { - contributions(where: { account: { id_in: $addresses } }, orderBy: id_ASC) { - id - crowdloan { - id - fundIndex - fundAccount - paraId - - depositor - end - cap - firstPeriod - lastPeriod - lastBlock - - createdBlockNumber - createdTimestamp - - dissolved - dissolvedBlockNumber - dissolvedTimestamp - } - account { - id - } - amount - returned - blockNumber - timestamp - } - } - `), - { addresses } - ) - - promise - .then(contributions => { - if (cancelled) return - - const gqlContributions = contributions.contributions.map(contribution => ({ - ...contribution, - relay: { genesisHash }, - })) - const byAccountFundIndex = new Map() - gqlContributions.forEach(contribution => { - const accountFundIndex = `${contribution.account.id}-${contribution.crowdloan.fundIndex}` - const additionalAmount = BigInt(byAccountFundIndex.get(accountFundIndex)?.amount ?? '0') - - byAccountFundIndex.set(accountFundIndex, { - ...contribution, - amount: (BigInt(contribution.amount) + additionalAmount).toString(), - crowdloan: { - ...contribution.crowdloan, - lastBlock: contribution.crowdloan.dissolvedBlockNumber ?? contribution.crowdloan.lastBlock, - }, - }) - }) - setStateFn(Array.from(byAccountFundIndex.values())) - setHydratedFn(true) - }) - .catch(error => { - console.error('Failed to fetch contributions', error) - }) - - return () => { - cancelled = true - } - }, - [accounts, allAccounts] - ) - - const fetchDotContributions = useCallback(() => { - fetchGqlContributions({ - ...ContributionsIndexerConfigs.polkadot, - setStateFn: setDotContributions, - setHydratedFn: setDotHydrated, - }) - }, [fetchGqlContributions]) - - const fetchKsmContributions = useCallback(() => { - fetchGqlContributions({ - ...ContributionsIndexerConfigs.kusama, - setStateFn: setKsmContributions, - setHydratedFn: setKsmHydrated, - }) - }, [fetchGqlContributions]) - - const { recentDotBlock, recentDotPeriod, recentKsmBlock, recentKsmPeriod, updateRecentBlocks } = useRecentBlocks() - - // fetch on load - useEffect(() => { - updateRecentBlocks() - - const cancelDot = fetchDotContributions - const cancelKsm = fetchKsmContributions - - return () => { - cancelDot() - cancelKsm() - } - }, [fetchDotContributions, fetchKsmContributions, updateRecentBlocks]) - - // re-fetch every 2 minutes - useInterval(() => { - updateRecentBlocks() - - fetchDotContributions() - fetchKsmContributions() - }, 120_000) - - const gqlContributions = useMemo( - () => - [...dotContributions, ...ksmContributions].flatMap(contribution => { - const isDot = contribution.relay.genesisHash === ContributionsIndexerConfigs.polkadot.genesisHash - const isKsm = contribution.relay.genesisHash === ContributionsIndexerConfigs.kusama.genesisHash - - const blockNumber = (isDot ? recentDotBlock : isKsm ? recentKsmBlock : 0) ?? 0 - const blockPeriod = (isDot ? recentDotPeriod : isKsm ? recentKsmPeriod : 6) ?? 6 - - const lockedSeconds = ((contribution.crowdloan.lastBlock ?? 0) - blockNumber) * blockPeriod - - const isLocked = lockedSeconds > 0 - const isUnlockingSoon = lockedSeconds <= 60 * 60 * 72 // 72 hours - - const isFundsReturned = contribution.returned || contribution.crowdloan.dissolved - - // hide returned contributions which were unlocked more than 30 days ago - const oldAndReturned = isFundsReturned && lockedSeconds < -60 * 60 * 24 * 30 - if (oldAndReturned) return [] - - return { - ...contribution, - - /** Updated every 2 minutes */ - blockNumber, - /** Updated every 2 minutes */ - blockPeriod, - /** Updated every 2 minutes */ - lockedSeconds, - /** Updated every 2 minutes */ - isLocked, - /** Updated every 2 minutes */ - isUnlockingSoon, - /** Updated every 2 minutes */ - isFundsReturned, - /** Updated every 2 minutes */ - oldAndReturned, - } - }), - [dotContributions, ksmContributions, recentDotBlock, recentDotPeriod, recentKsmBlock, recentKsmPeriod] - ) - - const sortedGqlContributions = useMemo(() => { - return gqlContributions.slice().sort((a, b) => { - const aLockedSeconds = a.lockedSeconds ?? Number.MAX_SAFE_INTEGER - const bLockedSeconds = b.lockedSeconds ?? Number.MAX_SAFE_INTEGER - - if (aLockedSeconds !== bLockedSeconds) return aLockedSeconds - bLockedSeconds - if (a.crowdloan.fundIndex !== b.crowdloan.fundIndex) return a.crowdloan.fundIndex - b.crowdloan.fundIndex - return a.timestamp - b.timestamp - }) - }, [gqlContributions]) - - return { - gqlContributions, - sortedGqlContributions, - hydrated: dotHydrated && recentDotBlock !== null && ksmHydrated && recentKsmBlock !== null, - } -} - -// -// Helpers (exported) -// - -export function groupTotalContributionsByCrowdloan(contributions: CrowdloanContribution[]) { - return contributions.reduce>((perCrowdloan, contribution) => { - if (!perCrowdloan[contribution.fund.id]) perCrowdloan[contribution.fund.id] = '0' - perCrowdloan[contribution.fund.id] = - new BigNumber(perCrowdloan[contribution.fund.id] ?? 0).plus(contribution.amount).toString() ?? - perCrowdloan[contribution.fund.id] - return perCrowdloan - }, {}) -} - -export function getTotalContributionForCrowdloan(crowdloan: string, contributions: CrowdloanContribution[]) { - return contributions - .filter(contribution => contribution.fund.id === crowdloan) - .map(contribution => contribution.amount) - .reduce((prev, curr) => prev.plus(curr), new BigNumber(0)) - .toString() -} - -// -// Helpers (internal) -// - -// We need this for sorting, but we don't want to update every UI component every 6s -// So we'll only do the calculation every 15 minutes -function useRecentBlocks() { - const [[recentDotBlock, recentDotPeriod], setRecentDot] = useState<[number | null, number | null]>([null, null]) - const [[recentKsmBlock, recentKsmPeriod], setRecentKsm] = useState<[number | null, number | null]>([null, null]) - - const dotBlockNumber = useChainmetaValue(0, 'blockNumber') - const dotBlockPeriod = useChainmetaValue(0, 'blockPeriod') - - const ksmBlockNumber = useChainmetaValue(2, 'blockNumber') - const ksmBlockPeriod = useChainmetaValue(2, 'blockPeriod') - - const updateRecentBlocks = useCallback(() => { - if (dotBlockNumber === null || ksmBlockNumber === null) return - - setRecentDot([parseInt(dotBlockNumber ?? '0'), dotBlockPeriod]) - setRecentKsm([parseInt(ksmBlockNumber ?? '0'), ksmBlockPeriod]) - }, [dotBlockNumber, dotBlockPeriod, ksmBlockNumber, ksmBlockPeriod]) - - return { recentDotBlock, recentDotPeriod, recentKsmBlock, recentKsmPeriod, updateRecentBlocks } -} diff --git a/apps/portal/src/libs/portfolio/index.tsx b/apps/portal/src/libs/portfolio/index.tsx deleted file mode 100644 index 1a5b431b8..000000000 --- a/apps/portal/src/libs/portfolio/index.tsx +++ /dev/null @@ -1,255 +0,0 @@ -import useUniqueId from '../../util/useUniqueId' -import { type GqlContribution } from '../crowdloans' -import { encodeAnyAddress, planckToTokens } from '@talismn/util' -import BigNumber from 'bignumber.js' -import { - useContext as _useContext, - createContext, - useCallback, - useEffect, - useMemo, - useState, - type PropsWithChildren, -} from 'react' - -// TODO: Replace this lib with an @talismn/balances-react wrapper or redesign. -// Currently we get balances from the lib, then convert and aggregate them all -// downstream. This means we have to keep track of all the converted and aggregated -// values in order to provide useful summaries to the user (e.g. totalUsd, totalUsdByAddress, etc). -// -// Soon we should just provide methods for these summaries directly via @talismn/balances-react, -// which will take care of fetching and updating the underlying balances as appropriate for us. - -// Helpers (exported) -export function calculateGqlCrowdloanPortfolioAmounts( - contributions: GqlContribution[], - tokenDecimals?: number, - tokenPrice?: string -): Array<{ tags: Tag[]; amount: string | undefined }> { - const amounts: Array<{ tags: Tag[]; amount: string | undefined }> = [] - - const byAddress: Record = {} - contributions.forEach(contribution => { - if (!byAddress[encodeAnyAddress(contribution.account.id, 42)]) - byAddress[encodeAnyAddress(contribution.account.id, 42)] = [] - byAddress[encodeAnyAddress(contribution.account.id, 42)]?.push(contribution) - }) - - Object.entries(byAddress).forEach(([address, contributions]) => { - const tags: Tag[] = ['USD', 'Crowdloans', { Address: address }] - contributions.forEach(contribution => { - if (contribution.returned) return - const contributionTokens = planckToTokens(contribution.amount, tokenDecimals) - const contributionUsd = new BigNumber(contributionTokens ?? 0).times(tokenPrice ?? 0).toString() - amounts.push({ tags, amount: contributionUsd }) - }) - }) - - return amounts -} - -// -// Types -// - -export type Portfolio = { - totalUsd: string - totalUsdByAddress: Record - totalAssetsUsd: string - totalAssetsUsdByAddress: Record - totalCrowdloansUsd: string - totalCrowdloansUsdByAddress: Record - totalStakingUsd: string - totalStakingUsdByAddress: Record - - isLoading: boolean - // only true if we've finished loading all amounts and they're all zero - hasEmptyBags: boolean -} - -export type AddressTag = { Address: string } -export type Tag = 'USD' | 'Assets' | 'Crowdloans' | 'Staking' | AddressTag - -export type Total = { - tags: Tag[] - amount: string -} - -export type TotalStore = Record - -// -// Hooks (exported) -// - -export function usePortfolio(): Portfolio { - const { portfolio } = useContext() - return portfolio -} - -export function usePortfolioHasEmptyBags(): boolean { - return useContext().portfolio.hasEmptyBags -} - -export function useTaggedAmountsInPortfolio(amounts: Array<{ tags: Tag[]; amount: string | undefined }>): void { - const { storeTotal, clearTotal, setLoading } = useContext() - const uniqueId = useUniqueId() - - useEffect(() => { - if (amounts.length === 0) setLoading(uniqueId, true) - - amounts.forEach(({ tags, amount }, index) => { - if (!amount) return - storeTotal(`${uniqueId}--${index}`, tags, amount || '0') - }) - - if (amounts.length !== 0) setLoading(uniqueId, false) - - return () => { - amounts.forEach((_, index) => clearTotal(`${uniqueId}--${index}`)) - setLoading(uniqueId, false) - } - }, [uniqueId, amounts, storeTotal, clearTotal, setLoading]) -} - -export function useTaggedAmountInPortfolio(tags: Tag[], amount: string | undefined): void { - const amounts = useMemo(() => [{ tags, amount }], [tags, amount]) - return useTaggedAmountsInPortfolio(amounts) -} - -// -// Context -// - -type ContextProps = { - portfolio: Portfolio - storeTotal: (uniqueId: string, tags: Tag[], amount: string) => void - clearTotal: (uniqueId: string) => void - setLoading: (uniqueId: string, loading: boolean) => void -} - -const Context = createContext(null) - -function useContext() { - const context = _useContext(Context) - if (!context) throw new Error('The portfolio provider is required in order to use this hook') - - return context -} - -// -// Provider -// - -type ProviderProps = PropsWithChildren - -export const Provider = ({ children }: ProviderProps) => { - const [totalStore, setTotalStore] = useState({}) - const [loadingList, setLoadingList] = useState>({}) - - const storeTotal = useCallback( - (uniqueId: string, tags: Tag[], amount: string) => - setTotalStore(totalStore => ({ ...totalStore, [uniqueId]: { tags, amount } })), - [] - ) - const clearTotal = useCallback( - (uniqueId: string) => - setTotalStore(totalStore => { - delete totalStore[uniqueId] - return totalStore - }), - [] - ) - const setLoading = useCallback( - (uniqueId: string, loading: boolean) => - setLoadingList(loadingList => { - if (loading && loadingList[uniqueId]) return loadingList - if (!loading && !loadingList[uniqueId]) return loadingList - - const loadingListMut = { ...loadingList } - if (loading) loadingListMut[uniqueId] = true - else delete loadingListMut[uniqueId] - - return loadingListMut - }), - [] - ) - - const portfolio = useMemo(() => { - let totalUsd = new BigNumber(0) - const totalUsdByAddress: Record = {} - let totalAssetsUsd = new BigNumber(0) - const totalAssetsUsdByAddress: Record = {} - let totalCrowdloansUsd = new BigNumber(0) - const totalCrowdloansUsdByAddress: Record = {} - let totalStakingUsd = new BigNumber(0) - const totalStakingUsdByAddress: Record = {} - - Object.values(totalStore).forEach(({ tags, amount }) => { - if (tags.includes('USD')) { - totalUsd = totalUsd.plus(amount) - - tags - .filter((tag): tag is AddressTag => Object.keys(tag).length === 1 && Object.keys(tag)[0] === 'Address') - .map(tag => tag.Address) - .forEach(address => { - if (!totalUsdByAddress[address]) totalUsdByAddress[address] = new BigNumber(0) - totalUsdByAddress[address] = totalUsdByAddress[address]?.plus(amount) ?? BigNumber(0) - - if (tags.includes('Assets')) { - if (!totalAssetsUsdByAddress[address]) totalAssetsUsdByAddress[address] = new BigNumber(0) - totalAssetsUsdByAddress[address] = totalAssetsUsdByAddress[address]?.plus(amount) ?? BigNumber(0) - } - if (tags.includes('Crowdloans')) { - if (!totalCrowdloansUsdByAddress[address]) totalCrowdloansUsdByAddress[address] = new BigNumber(0) - totalCrowdloansUsdByAddress[address] = totalCrowdloansUsdByAddress[address]?.plus(amount) ?? BigNumber(0) - } - if (tags.includes('Staking')) { - if (!totalStakingUsdByAddress[address]) totalStakingUsdByAddress[address] = new BigNumber(0) - totalStakingUsdByAddress[address] = totalStakingUsdByAddress[address]?.plus(amount) ?? BigNumber(0) - } - }) - - if (tags.includes('Assets')) totalAssetsUsd = totalAssetsUsd.plus(amount) - if (tags.includes('Crowdloans')) totalCrowdloansUsd = totalCrowdloansUsd.plus(amount) - if (tags.includes('Staking')) totalStakingUsd = totalStakingUsd.plus(amount) - } - }) - - const isLoading = Object.keys(loadingList).length !== 0 - // const hasEmptyBags = !isLoading && totalUsd === '0' - const hasEmptyBags = false - - const parseBnRecord = (record: Record>): Portfolio => - Object.fromEntries( - Object.entries(record).map(([key, value]) => { - if (value instanceof BigNumber) { - return [key, value.toString()] - } else { - return [key, parseBnRecord(value)] - } - }) - ) - - return { - ...parseBnRecord({ - totalUsd, - totalUsdByAddress, - totalAssetsUsd, - totalAssetsUsdByAddress, - totalCrowdloansUsd, - totalCrowdloansUsdByAddress, - totalStakingUsd, - totalStakingUsdByAddress, - }), - isLoading, - hasEmptyBags, - } - }, [totalStore, loadingList]) - - const value = useMemo( - () => ({ portfolio, storeTotal, clearTotal, setLoading }), - [portfolio, storeTotal, clearTotal, setLoading] - ) - - return {children} -} diff --git a/apps/portal/src/libs/talisman/chainmeta.tsx b/apps/portal/src/libs/talisman/chainmeta.tsx deleted file mode 100644 index d500f0340..000000000 --- a/apps/portal/src/libs/talisman/chainmeta.tsx +++ /dev/null @@ -1,108 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ - -import type { PropsWithChildren } from 'react' -import { WsProvider } from '@polkadot/api' -import { get } from 'lodash' -import { useContext as _useContext, createContext, useEffect, useReducer, useState } from 'react' -import { useRecoilValue } from 'recoil' - -import { supportedRelayChainsState } from './util/_config' - -// -// Types -// - -export type Chainmeta = { - chain?: string - nodeName?: string - nodeVersion?: string - tokenSymbol?: string - tokenDecimals?: string - blockPeriod?: number - blockNumber?: number - blockHash?: string -} - -// -// Hooks (exported) -// - -export const useChainmeta = () => useContext() - -export const useChainmetaValue = (chainId: number, key: string) => { - const chains = useChainmeta() - const [val, setVal] = useState(null) - - useEffect(() => { - if (!chains[chainId as any as keyof ContextProps]) return - setVal(get(chains[chainId as any as keyof ContextProps], key) || null) - }, [chainId, key, chains]) - - return val -} - -// -// Context -// - -type ContextProps = { - chainmeta: Chainmeta | null -} - -const Context = createContext(null) - -function useContext() { - const context = _useContext(Context) - if (!context) throw new Error('The talisman extension provider is required in order to use this hook') - return context -} - -// -// Provider -// - -const ParachainReducer = (state: any = {}, data: any) => { - // not exists - if (!state[data.id]) { - state[data.id] = data - } - // exists - else { - state[data.id] = { - ...state[data.id], - ...data, - } - } - - return { ...state } -} - -export const Provider = ({ children }: PropsWithChildren) => { - const [chains, dispatch] = useReducer(ParachainReducer, {}) - - const hydrateBlock = async (chain: any) => { - const wsProvider = new WsProvider(chain.rpc) - - wsProvider.on('connected', () => { - const cb = (_error: Error | null, result: any) => { - dispatch({ - id: chain?.id, - blockNumber: result.number, - blockHash: result.parentHash, - }) - } - - void wsProvider.subscribe('chain_newHead', 'chain_subscribeNewHeads', [], cb) - }) - } - - const relayChains = useRecoilValue(supportedRelayChainsState) - useEffect(() => { - relayChains.forEach(chain => { - dispatch(chain) - void hydrateBlock(chain) - }) - }, [relayChains]) - - return {children} -} diff --git a/apps/portal/src/libs/talisman/crowdloan.tsx b/apps/portal/src/libs/talisman/crowdloan.tsx deleted file mode 100644 index 2ccce79d2..000000000 --- a/apps/portal/src/libs/talisman/crowdloan.tsx +++ /dev/null @@ -1,269 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ - -import type { AccountId } from '@polkadot/types/interfaces' -import type { PropsWithChildren } from 'react' -import { stringToU8a, u8aConcat, u8aEq } from '@polkadot/util' -import { planckToTokens } from '@talismn/util' -import BN from 'bn.js' -import { find, get } from 'lodash' -import { useContext as _useContext, createContext, useEffect, useMemo, useState } from 'react' -import { selector, useRecoilValue, useRecoilValueLoadable, waitForAll } from 'recoil' - -import type { CrowdloanDetail } from '@/libs/@talisman-crowdloans/provider' -import { substrateApiState } from '@/domains/common/recoils/api' -import crowdloanDataState from '@/libs/@talisman-crowdloans/provider' - -import { supportedRelayChainsState } from './util/_config' - -export type Crowdloan = { - // graphql fields - id: string - stash: string - depositor: string - verifier?: any - deposit: number - raised: number - end: number - cap: number - firstPeriod: number - lastPeriod: number - fundIndex: number - parachain: { - paraId: string - } - // custom fields - relayChainId: number - percentRaised: number - details: CrowdloanDetail - uiStatus: 'active' | 'capped' | 'winner' | 'ended' - isCapped: boolean - isEnded: boolean - isWinner: boolean - - bestNumber?: number - leasePeriod?: number - leaseOffset?: number - currentLeasePeriodIndex?: number - endOfCurrentLeasePeriod?: number - - fundLeaseEndBlock?: number - lockedBlocks?: number - lockedSeconds?: number -} - -type ContextProps = { - crowdloans: Crowdloan[] - hydrated: boolean -} - -const Context = createContext(null) - -function useContext() { - const context = _useContext(Context) - if (!context) throw new Error('The crowdloan provider is required in order to use this hook') - - return context -} - -export const useCrowdloans = () => useContext() - -const useFindCrowdloan = (key: string, value: any): { crowdloan?: Crowdloan; hydrated: boolean } => { - const { crowdloans, hydrated } = useCrowdloans() - - const crowdloan = useMemo( - () => find(crowdloans, crowdloan => get(crowdloan, key) === value), - [crowdloans, key, value] - ) - - return { crowdloan, hydrated } -} - -const useFindCrowdloans = (key: string, value: any): { crowdloans: Crowdloan[]; hydrated: boolean } => { - const { crowdloans, hydrated } = useCrowdloans() - - const crowdloansFiltered = useMemo( - () => crowdloans.filter(crowdloan => get(crowdloan, key) === value), - [crowdloans, key, value] - ) - - return { crowdloans: crowdloansFiltered, hydrated } -} - -// only returns one (the most recent) crowdloan per parachain -export const useLatestCrowdloans = (): { crowdloans: Crowdloan[]; hydrated: boolean } => { - const { crowdloans, hydrated } = useCrowdloans() - - const crowdloansFiltered = useMemo(() => { - const foundParachainIds: Record = {} - return crowdloans.filter(crowdloan => { - if (foundParachainIds[crowdloan.parachain.paraId]) return false - foundParachainIds[crowdloan.parachain.paraId] = true - return true - }) - }, [crowdloans]) - - return { crowdloans: crowdloansFiltered, hydrated } -} - -export const useCrowdloanById = (id?: string) => useFindCrowdloan('id', id) -// only gets the most recent matching crowdloan -export const useCrowdloanByParachainId = (id?: number | string) => useFindCrowdloan('parachain.paraId', id) -export const useCrowdloansByParachainId = (id?: number | string) => useFindCrowdloans('parachain.paraId', id) - -export const useCrowdloanAggregateStats = () => { - const crowdloanData = useRecoilValue(crowdloanDataState) - - const { crowdloans, hydrated } = useCrowdloans() - const [raised, setRaised] = useState(0) - const [projects, setProjects] = useState(0) - const [contributors /*, setContributors */] = useState(0) - - useEffect(() => { - setRaised(crowdloans.reduce((acc: number, { raised = 0 }) => acc + raised, 0)) - setProjects(crowdloanData.length) - // setContributors(crowdloans.reduce((acc: number, { contributors = [] }) => acc + contributors.length, 0)) - }, [crowdloanData.length, crowdloans]) - - return { - raised, - projects, - contributors, - hydrated, - } -} - -const CROWD_PREFIX = stringToU8a('modlpy/cfund') - -function hasCrowdloadPrefix(accountId: AccountId): boolean { - return u8aEq(accountId.slice(0, CROWD_PREFIX.length), CROWD_PREFIX) -} - -const apisState = selector({ - key: 'Crowdloans/Apis', - get: ({ get }) => { - const relayChains = get(supportedRelayChainsState) - return get(waitForAll(relayChains.map(relayChain => substrateApiState(relayChain.rpc)))) - }, - cachePolicy_UNSTABLE: { eviction: 'most-recent' }, - dangerouslyAllowMutability: true, -}) - -export const Provider = ({ children }: PropsWithChildren) => { - const [crowdloans, setCrowdloans] = useState([]) - const [hydrated, setHydrated] = useState(false) - - const relayChainsLoadable = useRecoilValueLoadable(supportedRelayChainsState) - const crowdloanLoadable = useRecoilValueLoadable(crowdloanDataState) - const apisLoadable = useRecoilValueLoadable(apisState) - - useEffect(() => { - if ( - relayChainsLoadable.state === 'hasValue' && - crowdloanLoadable.state === 'hasValue' && - apisLoadable.state === 'hasValue' - ) { - const crowdloanData = crowdloanLoadable.contents - const promises = relayChainsLoadable.contents - .map((chain, index) => ({ api: apisLoadable.contents[index]!, chain })) - .map(async ({ api, chain }) => { - const bestNumber = await api.derive.chain.bestNumber() - - const funds = await api.query.crowdloan.funds.entries() - const paraIds = await api.query.crowdloan.funds.keys().then(fund => fund.map(y => y.args[0])) - const leases = await api.query.slots.leases.multi(paraIds) - - const leasePeriod = api.consts.slots.leasePeriod - const leaseOffset = api.consts.slots.leaseOffset || new BN(0) - - const currentLeasePeriodIndex = - bestNumber.toNumber() - leaseOffset.toNumber() < 0 ? null : bestNumber.sub(leaseOffset).div(leasePeriod) - - const endOfCurrentLeasePeriod = currentLeasePeriodIndex - ? currentLeasePeriodIndex.mul(leasePeriod).add(leasePeriod).add(leaseOffset) - : null - - const leasedParaIds = paraIds.filter((_, index) => - leases[index]?.some(lease => lease.isSome && hasCrowdloadPrefix(lease.unwrap()[0])) - ) - - return funds.map(([fundId, maybeFund]) => { - const fund = maybeFund.unwrapOrDefault() - const { tokenDecimals } = chain - - const fundLeaseEndBlock = fund.lastPeriod.mul(leasePeriod).add(leasePeriod).add(leaseOffset) - - const palletId = api.consts.crowdloan.palletId.toU8a() - const fundIndex = fund.fundIndex.toU8a() - - const crowdloanFundAccountId = (() => { - const EMPTY_H256 = new Uint8Array(32) - return api.registry - .createType('AccountId32', u8aConcat(stringToU8a('modl'), palletId, fundIndex, EMPTY_H256)) - .toString() - })() - - const isCapped = fund.cap.sub(fund.raised).lt(api.consts.crowdloan.minContribution) - const isEnded = bestNumber.gt(fund.end) - const isWinner = leasedParaIds.some(lease => lease.eq(fundId.args[0])) - - return { - ...fund.toJSON(), - - stash: crowdloanFundAccountId, - - id: `${chain.id}-${fundId.args[0].toNumber()}`, - cap: Number(planckToTokens(fund.cap.toString(), tokenDecimals)), - raised: Number(planckToTokens(fund.raised.toString(), tokenDecimals)), - end: fund.end.toNumber(), - parachain: { - paraId: `${chain.id}-${fundId.args[0].toNumber()}`, - }, - relayChainId: chain.id, - percentRaised: - (100 / Number(planckToTokens(fund.cap.toString(), tokenDecimals))) * - Number(planckToTokens(fund.raised.toString(), tokenDecimals)), - details: find(crowdloanData, { - relayId: chain.id.toString(), - paraId: fundId.args[0].toNumber().toString(), - }), - uiStatus: isWinner ? 'winner' : isCapped ? 'capped' : isEnded ? 'ended' : 'active', - isCapped, - isEnded, - isWinner, - - bestNumber: bestNumber?.toNumber(), - leasePeriod: leasePeriod?.toNumber(), - leaseOffset: leaseOffset?.toNumber(), - currentLeasePeriodIndex: currentLeasePeriodIndex?.toNumber(), - endOfCurrentLeasePeriod: endOfCurrentLeasePeriod?.toNumber(), - - fundLeaseEndBlock: fundLeaseEndBlock?.toNumber(), - lockedBlocks: - (fundLeaseEndBlock?.toNumber?.() ?? 0) - (bestNumber?.toNumber?.() ?? Number.MAX_SAFE_INTEGER), - lockedSeconds: - ((fundLeaseEndBlock?.toNumber?.() ?? 0) - (bestNumber?.toNumber?.() ?? Number.MAX_SAFE_INTEGER)) * 6, - } as Crowdloan - }) - }) - - void Promise.all(promises).then(result => { - const sortByLockedSeconds = (a: { lockedSeconds?: number }, b: { lockedSeconds?: number }) => - (a.lockedSeconds ?? Number.MAX_SAFE_INTEGER) - (b.lockedSeconds ?? Number.MAX_SAFE_INTEGER) - - setCrowdloans(result.flat().slice().sort(sortByLockedSeconds)) - setHydrated(true) - }) - } - }, [ - apisLoadable.contents, - apisLoadable.state, - crowdloanLoadable.contents, - crowdloanLoadable.state, - relayChainsLoadable.contents, - relayChainsLoadable.state, - ]) - - const value = useMemo(() => ({ crowdloans, hydrated }), [crowdloans, hydrated]) - - return {children} -} diff --git a/apps/portal/src/libs/talisman/index.tsx b/apps/portal/src/libs/talisman/index.tsx deleted file mode 100644 index 3f210ee35..000000000 --- a/apps/portal/src/libs/talisman/index.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import { BalancesProvider } from '@talismn/balances-react' -import { type PropsWithChildren } from 'react' - -import * as Chainmeta from './chainmeta' -import * as Crowdloan from './crowdloan' -import * as Parachain from './parachain' - -/* publically exposed hooks */ - -// // chainmeta things -export { useChainmeta, useChainmetaValue } from './chainmeta' -// // crowdloans stuff -export { - useCrowdloanAggregateStats, - useCrowdloanById, - useCrowdloanByParachainId, - useCrowdloans, - useCrowdloansByParachainId, - useLatestCrowdloans, -} from './crowdloan' -// // parachain things -export { - useFindParachainDetails, - useParachainAssets, - useParachainDetailsById, - useParachainDetailsBySlug, - useParachainsDetails, - useParachainsDetailsIndexedById, -} from './parachain' - -/* publically exposed provider */ -const Provider = ({ children }: PropsWithChildren) => ( - - - - {children} - - - -) - -export default Provider diff --git a/apps/portal/src/libs/talisman/parachain.tsx b/apps/portal/src/libs/talisman/parachain.tsx deleted file mode 100644 index dfa4195de..000000000 --- a/apps/portal/src/libs/talisman/parachain.tsx +++ /dev/null @@ -1,124 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ - -import type { PropsWithChildren } from 'react' -import { find } from 'lodash' -import { useContext as _useContext, createContext, useEffect, useMemo, useState } from 'react' -import { selector, useRecoilValue, useRecoilValueLoadable, waitForAll } from 'recoil' - -import type { CrowdloanDetail } from '@/libs/@talisman-crowdloans/provider' -import { chainsState } from '@/domains/chains/recoils' -import { substrateApiState } from '@/domains/common/recoils/api' -import crowdloanDataState from '@/libs/@talisman-crowdloans/provider' - -export const useParachainsDetails = () => useContext() -export const useParachainsDetailsIndexedById = () => { - const { parachains, hydrated } = useParachainsDetails() - - return { - parachains: useMemo(() => Object.fromEntries(parachains.map(parachain => [parachain.id, parachain])), [parachains]), - hydrated, - } -} - -export const useParachainDetailsById = (id?: number | string) => useFindParachainDetails('id', id) -export const useParachainDetailsBySlug = (slug?: string) => useFindParachainDetails('slug', slug) - -export const useParachainAssets = ( - id?: string -): Partial<{ [key: string]: string; banner: string; card: string; logo: string }> => { - const crowdloans = useRecoilValue(crowdloanDataState) - const crowdloanDetail = crowdloans.find(x => x.id === id) - const slug = crowdloanDetail?.slug - - return { - banner: `https://raw.githubusercontent.com/TalismanSociety/chaindata/main/assets/promo/${slug ?? ''}-banner.png`, - card: `https://raw.githubusercontent.com/TalismanSociety/chaindata/main/assets/promo/${slug ?? ''}-card.png`, - logo: `https://raw.githubusercontent.com/TalismanSociety/chaindata/main/assets/chains/${slug ?? ''}.svg`, - } -} - -// -// Hooks (internal) -// - -export const useFindParachainDetails = ( - key: string, - value: any -): Partial<{ parachainDetails?: CrowdloanDetail; hydrated: boolean }> => { - const { parachains, hydrated } = useParachainsDetails() - - const parachainDetails = useMemo(() => find(parachains, { [key]: value }), [parachains, key, value]) - - return { - parachainDetails, - hydrated, - } -} - -// -// Context -// - -type ContextProps = { - parachains: CrowdloanDetail[] - hydrated: boolean -} - -const Context = createContext(null) - -function useContext() { - const context = _useContext(Context) - if (!context) throw new Error('The parachain provider is required in order to use this hook') - - return context -} - -// -// Provider -// - -const apisState = selector({ - key: 'Parachain/Apis', - get: ({ get }) => { - const chains = get(chainsState) - return get(waitForAll([substrateApiState(chains[0]?.rpc), substrateApiState(chains[1]?.rpc)])) - }, - cachePolicy_UNSTABLE: { eviction: 'most-recent' }, - dangerouslyAllowMutability: true, -}) - -export const Provider = ({ children }: PropsWithChildren) => { - const [hydrated, setHydrated] = useState(false) - const [parachains, setParachains] = useState([]) - - const crowdloansLoadable = useRecoilValueLoadable(crowdloanDataState) - const apisLoadable = useRecoilValueLoadable(apisState) - - useEffect(() => { - if (hydrated) { - return - } - - if (crowdloansLoadable.state !== 'hasValue' || apisLoadable.state !== 'hasValue') { - return - } - - void (async () => { - const crowdloans = crowdloansLoadable.contents - const [polkadotApi, kusamaApi] = apisLoadable.contents - - const polkadotFunds = await polkadotApi.query.crowdloan.funds.entries() - const kusamaFunds = await kusamaApi.query.crowdloan.funds.entries() - - const polkadotParaIds = polkadotFunds.map(x => `0-${x[0].args[0].toString()}`) - const kusamaParaIds = kusamaFunds.map(x => `2-${x[0].args[0].toString()}`) - - const paraIds = [...polkadotParaIds, ...kusamaParaIds] - - setParachains(crowdloans.filter(x => paraIds.includes(x.id))) - setHydrated(true) - })() - }, [apisLoadable.contents, apisLoadable.state, crowdloansLoadable.contents, crowdloansLoadable.state, hydrated]) - - return {children} -} diff --git a/apps/portal/src/libs/talisman/util/_config.ts b/apps/portal/src/libs/talisman/util/_config.ts deleted file mode 100644 index 51ff4cc3a..000000000 --- a/apps/portal/src/libs/talisman/util/_config.ts +++ /dev/null @@ -1,2910 +0,0 @@ -import { selector, waitForAll } from 'recoil' - -import { chainState } from '@/domains/chains/recoils' - -export const statusOptions = { - INITIALIZED: 'INITIALIZED', - PROCESSING: 'PROCESSING', - READY: 'READY', - ERROR: 'ERROR', -} - -export type Relaychain = { - id: number - name: string - accountPrefix: number - rpc: string - genesisHash: string - subscanUrl: string - tokenDecimals: number - tokenSymbol: string - coingeckoId: string - blockPeriod: number -} - -// https://wiki.polkadot.network/docs/build-ss58-registry -const SupportedRelaychains: Array> = [ - { - id: 0, - genesisHash: '0x91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3', - blockPeriod: 6, - }, - { - id: 2, - genesisHash: '0xb0a8d493285c2df73290dfb7e61f870f17b41801197a149ca93654499ea3dafe', - blockPeriod: 6, - }, -] - -export const supportedRelayChainsState = selector({ - key: 'Crowdloans/SupportedRelayChains', - get: ({ get }) => - get(waitForAll(SupportedRelaychains.map(x => chainState({ genesisHash: x.genesisHash })))).map((x): Relaychain => { - const base = SupportedRelaychains.find(y => y.genesisHash === x.genesisHash) - return { - id: base?.id ?? 0, - name: x.name ?? '', - accountPrefix: x.prefix ?? 0, - rpc: x.rpcs?.at(0)?.url ?? '', - genesisHash: x.genesisHash!, - subscanUrl: x.subscanUrl ?? '', - tokenDecimals: x.nativeToken?.decimals ?? 0, - tokenSymbol: x.nativeToken?.symbol ?? '', - coingeckoId: x.nativeToken?.coingeckoId ?? '', - blockPeriod: base?.blockPeriod ?? 6, - } - }), -}) - -export type ParachainDetails = { - id: string - name: string - slug: string - token: string - subtitle: string - info: string - links: Record - tags?: string[] -} - -export const parachainDetails: ParachainDetails[] = [ - { - id: '0-2000', - name: 'Acala', - slug: 'acala', - token: 'ACA', - subtitle: 'Acala is the DeFi and Liquidity Hub of Polkadot', - info: 'Acala is an Ethereum-compatible smart contract platform optimized for DeFi and scaling DApps to Polkadot. The blockchain has built-in DeFi protocols for application developers to leverage, including a decentralized stablecoin (Acala Dollar - aUSD), a trustless staking derivatives (liquid DOT - LDOT), and a decentralized exchange.', - links: { - Website: 'https://acala.network', - Twitter: 'https://twitter.com/AcalaNetwork', - Telegram: 'https://t.me/acalaofficial', - Discord: 'https://discord.com/invite/6QHVY4X', - Github: 'https://github.com/AcalaNetwork', - }, - }, - { - id: '0-2002', - name: 'Clover', - slug: 'clover', - token: 'CLV', - subtitle: 'A foundational layer for cross-chain compatibility.', - info: 'Clover describes itself as a blockchain operating system. It contains a storage layer, smart contract layer, Defi protocol layer, and eApp layer that work in unison to accomplish the goal of blockchain interoperability.', - links: { - Website: 'https://clover.finance/', - Twitter: 'https://twitter.com/clover_finance/', - Telegram: 'https://t.me/clover_en/', - Medium: 'https://projectclover.medium.com/', - Github: 'https://github.com/clover-network', - Discord: 'https://discord.com/invite/7EFqBwZ3aw', - }, - }, - { - id: '0-2003', - name: 'Darwinia', - slug: 'darwinia-polkadot', - token: 'RING', - subtitle: 'Darwinia Network is a decentralized cross-chain bridge network building on Substrate.', - info: 'Darwinia Network provides an entrance to the Polkadot ecology for projects that have been deployed on public blockchains such as Ethereum and BSC.', - links: { - Website: 'https://darwinia.network/', - Twitter: 'https://twitter.com/DarwiniaNetwork/', - Telegram: 'http://t.me/DarwiniaNetwork', - Medium: 'https://darwinianetwork.medium.com/', - Github: 'https://github.com/darwinia-network', - }, - }, - { - id: '0-3342', - name: 'Moonbeam', - slug: 'moonbeam', - token: 'GLMR', - subtitle: 'An Ethereum-compatible smart contract parachain on Polkadot.', - info: 'Moonbeam is a full Ethereum-like environment and works with industry-standard Ethereum tools, DApps, and protocols.', - links: { - Website: 'https://moonbeam.network/networks/moonbeam/', - Twitter: 'https://twitter.com/moonbeamnetwork', - Telegram: 'https://t.me/Moonbeam_Official', - Medium: 'https://medium.com/moonbeam-network', - Github: 'https://github.com/PureStake/moonbeam', - Discord: 'https://discord.gg/PfpUATX', - }, - }, - { - id: '0-2006', - name: 'Astar', - slug: 'astar', - token: 'ASTR', - subtitle: - 'Astar is the Polkadot-native dApp hub supporting Ethereum, WebAssembly, dApp Staking, and Layer2 solutions.', - info: 'Astar Network (previously known as Plasm) is a dApp hub on Polkadot that supports Ethereum, WebAssembly, and layer 2 solutions like ZK Rollups. Astar aims to be a multi-chain smart contract platform that will support multiple blockchains and virtual machines.', - links: { - Website: 'https://astar.network/', - Twitter: 'https://twitter.com/AstarNetwork', - Telegram: 'https://t.me/PlasmOfficial', - Medium: 'https://medium.com/astar-network', - Github: 'https://github.com/PlasmNetwork/Plasm', - Discord: 'https://discord.gg/Z3nC9U4', - }, - }, - { - id: '0-2007', - name: 'Kapex', - slug: 'kapex', - token: 'KAPEX', - subtitle: 'Building the 1st decentralised p2p accounting protocol anywhere.', - info: "Totem is building the world's first peer-to-peer accounting consensus protocol. It’s not just a first in the blockchain space – it’s a first in the accounting world too.", - links: { - Website: 'https://totemaccounting.com/', - Twitter: 'https://twitter.com/Totem_live_', - Telegram: 'https://t.me/totemchat', - Medium: 'https://medium.com/totemlive', - Gitlab: 'https://gitlab.com/totem-tech', - Discord: 'https://discord.com/invite/kmuZucW', - }, - }, - { - id: '0-2008', - name: 'Crust', - slug: 'crust', - token: 'CRU', - subtitle: 'Crust implements the incentive layer protocol for decentralized storage.', - info: "It is adaptable to multiple storage layer protocols such as IPFS, and provides support for the application layer. Crust’s architecture also has the capability of supporting a decentralized computing layer and building a decentralized cloud ecosystem. Crust's decentralized storage layer provides a distributed file system. At the same time, Crust encapsulates some standard interfaces such as Amazon S3-like. Any application scenarios involving data storage, such as cloud services, edge computing, and decentralized applications, are the scenarios that Crust can adapt. Worth mentioning is that in edge computing scenarios, compared to centralized cloud storage, Crust's decentralized storage is closer to the edge, which can achieve relatively low cost and high performance.", - links: { - Website: 'https://crust.network/', - Twitter: 'https://twitter.com/crustnetwork', - Telegram: 'https://t.me/CrustNetwork', - Medium: 'https://crustnetwork.medium.com/', - Github: 'https://github.com/crustio/crust', - Discord: 'https://discord.gg/Jbw2PAUSCR', - }, - }, - { - id: '0-2011', - name: 'Equilibrium', - slug: 'equilibrium-polkadot', - token: 'EQ', - subtitle: - 'The functionality of all key DeFi apps on one platform, with advanced bailout mechanism for system security.', - info: 'Users can: Lend - All main crypto assets, EQ tokens Borrow - All main crypto assets, decentralized stablecoins, synthetics Trade - All main crypto assets, decentralized stablecoins, synthetics, EQ tokens Stake - PoS & DPoS crypto assets, EQ tokens Solves cross-chain interoperability, unlocking $311 Bln of total remaining DeFi market potential.', - links: { - Website: 'https://equilibrium.io/', - Twitter: 'https://twitter.com/EquilibriumDeFi', - Telegram: 'https://t.me/equilibrium_eosdt_official', - Medium: 'https://medium.com/equilibrium-eosdt', - Github: 'https://github.com/equilibrium-eosdt/equilibrium-substrate-chain', - }, - }, - { - id: '0-2012', - name: 'Parallel', - slug: 'parallel', - token: 'PARA', - subtitle: - 'Parallel Finance is a decentralized money market protocol that offers lending, staking, and borrowing in the Polkadot ecosystem.', - info: "Parallel's mission is to innovate and bring DeFi to the next level. We are creating the most secure and easy-to-use decentralized platform to empower everyone access to financial services.", - links: { - Website: 'https://parallel.fi/', - Twitter: 'https://twitter.com/ParallelFi', - Telegram: 'https://t.me/parallelfi_community', - Medium: 'https://parallelfinance.medium.com/', - Github: 'https://github.com/parallel-finance', - Discord: 'https://discord.gg/buKKx4dySW', - }, - }, - { - id: '0-2013', - name: 'Litentry', - slug: 'litentry', - token: 'LIT', - subtitle: - 'Litentry is a Decentralized Identity Aggregation protocol across multiple networks, it features a DID indexing mechanism and a Substrate-based credit computation network.', - info: 'The protocol provides a decentralized, interoperable identity aggregation service that mitigates the difficulty of resolving agnostic DID mechanisms. Litentry provides a secure vehicle through which users manage their identities and dApps obtain the real-time credit/reputation of an identity owner across different blockchains.', - links: { - Website: 'https://www.litentry.com/', - Twitter: 'https://twitter.com/litentry', - Telegram: 'https://t.me/litentry', - Medium: 'https://litentry.medium.com/', - Github: 'https://github.com/litentry/', - Linkedin: 'https://www.linkedin.com/company/litentry/about', - }, - }, - { - id: '0-2015', - name: 'Manta', - slug: 'manta', - token: 'MANTA', - subtitle: 'The Privacy Preservation Layer on Polkadot.', - info: 'Manta Network is a private layer built for the entire Polkadot ecosystem. Built on the Substrate framework, Manta Network is natively compatible with other projects and parachain assets including wrapped major cryptoassets.', - links: { - Website: 'https://manta.network/', - Twitter: 'https://twitter.com/mantanetwork', - Telegram: 'https://t.me/mantanetworkofficial', - Medium: 'https://medium.com/@mantanetwork', - Github: 'https://github.com/Manta-Network', - }, - }, - { - id: '0-2017', - name: 'SubGame Network', - slug: 'subgame-polkadot', - token: 'SGB', - subtitle: 'SubGame is a public chain development team based on the Polkadot Parachain.', - info: 'It hopes to build a public chain with cross-chain interoperability. In addition to creating game applications, it can also build various types of application scenarios to create a common cross-chain industry. The blockchain infrastructure provides unlimited possibilities for distributed mobile applications.', - links: { - Website: 'https://www.subgame.org/', - Twitter: 'https://twitter.com/SubGame_Network', - Telegram: 'https://t.me/subgame_network', - Medium: 'https://medium.com/@subgame_network', - Github: 'https://github.com/SubGame-Network', - }, - }, - { - id: '0-2019', - name: 'Composable Finance', - slug: 'composable-finance', - token: 'LAYR', - subtitle: - 'Composable’s parachain is going to be able to run multiple bytecodes together in the same place, in order to run smart contracts together in a manner that allows them to communicate and collaborate.', - info: 'As a result, protocols using different smart contract languages (i.e. those on different chains, especially) will be able to unite via our parachain, facilitating cross-chain asset swaps and other functionalities.', - links: { - Website: 'https://www.composable.finance/', - Twitter: 'https://twitter.com/ComposableFin', - Telegram: 'https://t.me/ComposableFinanceAnnouncements', - Medium: 'https://composablefi.medium.com/', - Github: 'https://github.com/ComposableFi/', - }, - }, - { - id: '0-2021', - name: 'Efinity', - slug: 'efinity-polkadot', - token: 'EFI', - subtitle: 'Enjin is developing Efinity, a next-generation blockchain for digital assets, built on Polkadot.', - info: 'Businesses and developers seriously need a platform that can deliver a modern, mainstream and developer-friendly NFT experience. Since the release of Ethereum, there have been attempts to build infrastructure and tokenization around this general-purpose computing blockchain, but there’s an ever-growing thirst for a better solution.\n\nCreators are forced to work with crippling fees, inflexible smart contracts and disjointed interoperability. Adoption of today’s NFTs is still limited to die-hard crypto enthusiasts.\n\nThe blockchains that non-fungible tokens live on give actual users no incentives (other than the prices rising), because miners are given the full share of generated tokens. Prices rise, infrastructure companies create silos and paywalls, and it becomes difficult to make real progress in this industry - unless we can unify the community and think a bit differently.\n\nEfinity is built to solve these problems.', - links: { - Website: 'https://enjin.io/products/efinity', - Twitter: 'https://twitter.com/efinityio', - Blog: 'https://enjin.io/blog-tags/efinity', - GitHub: 'https://github.com/enjin', - }, - }, - { - id: '0-2026', - name: 'Nodle', - slug: 'nodle-polkadot', - token: 'NODL', - subtitle: "Nodle's mission is to connect the next trillion things to the Internet.", - info: "Nodle leverages Bluetooth Low Energy (BLE) via millions of smartphones and routers to allow enterprises and smart cities to connect IoT devices to the Internet at a low-cost while maintaining privacy and security. Nodle's decentralized wireless network is currently comprised of 5M daily active smartphones with 30 million IoT devices discovered daily in over 100 countries, moving approximately 100 GB of data.", - links: { - Website: 'https://nodle.com/', - Twitter: 'https://twitter.com/NodleNetwork', - Medium: 'https://medium.com/nodle-io', - GitHub: 'https://github.com/NodleCode/chain', - Reddit: 'https://www.reddit.com/r/Nodle/', - Discord: 'https://discord.gg/N5nTUt8RWJ', - Telegram: 'https://t.me/nodlecommunity', - }, - }, - { - id: '0-2027', - name: 'Coinversation', - slug: 'coinversation', - token: 'CTO', - subtitle: - 'Coinversation Protocol is a synthetic asset issuance protocol and decentralised contract trading exchange based on the Polkadot contract chain.', - info: 'It uses the token CTO issued by Coinversation Protocol and Polkadot(DOT) as collateral, and synthesizes any cryptocurrencies or stocks, bonds, gold and any other off-chain assets through smart contracts and oracles.', - links: { - Website: 'https://www.coinversation.io/', - Twitter: 'https://twitter.com/Coinversation_', - Medium: 'https://coinversationofficial.medium.com/', - GitHub: 'https://github.com/Coinversation/coinpro', - Telegram: 'https://t.me/coinversationofficial', - }, - }, - { - id: '0-2028', - name: 'Ares Protocol', - slug: 'ares-odyssey', - token: 'ARES', - subtitle: 'Ares is an on-chain verifying oracle protocol powered by Polkadot.', - info: "It provides reliable off-chain data efficiently and in a trustless manner. Ares is built on Substrate and constructed as a parachain to link to Polkadot's ecology and share its security consensus. It is a scalable oracle network that provides decentralized data services to the Polkadot ecosystem and its parachains.", - links: { - Website: 'https://www.aresprotocol.io/', - Twitter: 'https://twitter.com/AresProtocolLab', - Telegram: 'https://t.me/aresprotocol', - Medium: 'https://aresprotocollab.medium.com/', - Github: 'https://github.com/aresprotocols', - Discord: 'https://discord.gg/EsaFRr7xmc', - }, - }, - { - id: '0-2031', - name: 'Centrifuge', - slug: 'centrifuge-polkadot', - token: 'CFG', - subtitle: 'Centrifuge Chain is the gateway for real-world assets to the Blockchain Multiverse.', - info: 'We built Centrifuge Chain on Parity Substrate with an initial bridge to Ethereum. This allows us to move faster and use a consistent approach for certain features. We envision a larger ecosystem of many, connected blockchains- where Dapps on Ethereum could use data from other chains, value could move freely, and Centrifuge Chain can enable off-chain assets to access financing through DeFi.', - links: { - Website: 'https://centrifuge.io/', - Twitter: 'https://twitter.com/centrifuge', - Telegram: 'https://t.me/centrifuge_chat', - Medium: 'https://medium.com/centrifuge', - Github: 'https://github.com/centrifuge/centrifuge-chain/', - Discord: 'https://centrifuge.io/discord', - }, - }, - { - id: '0-2032', - name: 'Interlay', - slug: 'interlay', - token: 'INTR', - subtitle: - 'Interlay is a decentralized network dedicated to connecting crypto-currencies like Bitcoin with DeFi platforms like Polkadot and Ethereum.', - info: 'The Interlay network is hosted as a Polkadot parachain and will be connected to Cosmos, Ethereum, and other major DeFi networks. Read more about Interlay’s vision of blockchain interoperability. interBTC, Interlay’s flagship product, is Bitcoin on any blockchain. A 1:1 Bitcoin-backed asset, fully collateralized, interoperable, and censorship-resistant.', - links: { - Website: 'https://interlay.io/', - Twitter: 'https://twitter.com/interlayHQ', - Telegram: 'https://t.me/interlay_community', - Medium: 'https://medium.com/interlay', - Github: 'https://github.com/interlay/interbtc', - Discord: 'https://discord.com/invite/interlay', - }, - }, - { - id: '0-2034', - name: 'HydraDX', - slug: 'hydra', - token: 'HDX', - subtitle: 'Cross-chain liquidity protocol built on Substrate', - info: 'HydraDX is the creator of the Omnipool. Driven by the ambition to put an end to liquidity fragmentation, we have challenged the misconception that AMMs should be limited to pairs of assets. The Omnipool allows users to submerge any cryptoasset in an ocean of liquidity. One trading pool - many assets. Empowering native liquidity for the Polkadot ecosystem, and beyond.', - links: { - Website: 'https://hydradx.io/', - Twitter: 'https://twitter.com/hydra_dx', - Medium: 'https://hydradx.substack.com/archive', - Github: 'https://github.com/galacticcouncil?tab=repositories', - Discord: 'https://discord.com/invite/xtVnQgq', - }, - }, - { - id: '0-2035', - name: 'Phala Network', - slug: 'phala', - token: 'PHA', - subtitle: 'Phala Network as a confidentiality layer for Web3.0 developers', - info: 'Phala is a Polkadot parachain, and developers can invoke and interact with confidential contracts on other Polkadot parachains.', - links: { - Website: 'https://phala.network', - Twitter: 'https://twitter.com/PhalaNetwork', - Telegram: 'https://t.me/phalanetwork', - Medium: 'https://medium.com/phala-network', - Github: 'https://github.com/Phala-Network', - Discord: 'https://discord.gg/phala', - }, - }, - { - id: '0-2037', - name: 'Unique Network', - slug: 'unique-network', - token: 'UNQ', - subtitle: 'The NFT chain built for Polkadot and Kusama.', - info: 'Unique Network is a scalable blockchain for programmable NFTs with advanced features. Built with Substrate as a parachain on Polkadot Network, Unique unlocks unlimited potential for next-generation NFTs.', - links: { - Website: 'https://unique.network/', - Twitter: 'https://twitter.com/Unique_NFTchain', - Telegram: 'https://t.me/Uniquechain', - Medium: 'https://medium.com/unique-network', - Github: 'https://github.com/UniqueNetwork', - Discord: 'https://discord.gg/jHVdZhsakC', - }, - }, - { - id: '0-2038', - name: 'Geminis', - slug: 'geminis-network', - token: 'GEM', - subtitle: 'Geminis Network is the first parachain supported by the advanced ParaState tech stack.', - info: 'Geminis takes Ethereum chain support to the next level through WasmEdge. Develop and execute high-speed smart contracts with built-in Ethereum compatibility (EVM & EWASM) and interoperability in Solidity, Vyper, and next-level programming languages like Rust, C ++, and Golang.', - links: { - Website: 'https://geminis.network/', - Twitter: 'https://twitter.com/_ParaState', - Telegram: 'https://t.me/ParaState', - Discord: 'https://geminis.network/', - Facebook: 'https://www.facebook.com/ParaState.io/', - }, - }, - { - id: '0-2040', - name: 'Polkadex', - slug: 'polkadex-polkadot', - token: 'PDEX', - subtitle: 'The trading engine for Web3 and DeFi', - info: 'Polkadex is a fully decentralized peer-to-peer orderbook-based cryptocurrency exchange for the DeFi ecosystem built on Substrate.', - links: { - Website: 'https://www.polkadex.trade/', - Twitter: 'https://twitter.com/polkadex', - Telegram: 'https://t.me/Polkadex', - Medium: 'https://polkadex.medium.com/', - Github: 'https://github.com/Polkadex-Substrate/Polkadex', - Discord: 'https://discord.com/invite/qubycwPtSd', - }, - }, - { - id: '0-2030', - name: 'Bifrost', - slug: 'bifrost-polkadot', - token: 'BNC', - subtitle: 'Cross chain liquidity for staking.', - info: 'Cross chain liquidity for staking. Based on polkadot, kusama, substrate, and web3 infrastructure', - links: { - Website: 'https://bifrost.finance/', - Twitter: 'https://twitter.com/bifrost_finance', - Telegram: 'https://t.me/bifrost_finance', - Discord: 'https://discord.com/invite/XjnjdKBNXj', - }, - }, - { - id: '0-2043', - name: 'OriginTrail Parachain', - slug: 'origintrail', - token: 'OTP', - subtitle: 'Bringing real world assets to Polkadot.', - info: 'By harnessing the power of the OriginTrail Decentralized Knowledge Graph, OriginTrail Parachain extends discoverability, verifiability and greater value for interconnected Web3 assets to Polkadot.', - links: { - Website: 'https://parachain.origintrail.io/', - Twitter: 'https://twitter.com/origin_trail', - Telegram: 'https://t.me/origintrail', - }, - }, - { - id: '0-2039', - name: 'Integritee Shell', - slug: 'integritee-polkadot', - token: 'TEER', - subtitle: 'Unchain the value of sensitive data. At scale.', - info: 'Integritee is the most scalable public blockchain solution for securely processing sensitive business or personal data. Harness the speed and confidentiality of trusted execution environments, combined with the trust of a decentralized network, with Integritee.', - links: { Website: 'https://integritee.network/', Twitter: 'https://twitter.com/integri_t_e_e' }, - }, - { - id: '0-2052', - name: 'Kylin', - slug: 'kylin', - token: 'KYL', - subtitle: 'Building a Cross-chain Platfor. Powering the DeData Economy', - info: 'Kylin Network is the native Polkadot Data Blockchain to accelerate the world’s transition to DeData within a Web3.0 context by Building a Cross-chain Platform Powering the Data Economy. It is the Data Infrastructure for DeFi and Web 3.0 Powered by Polkadot.', - links: { - Website: 'https://kylin.network/', - Twitter: 'https://twitter.com/Kylin_Network', - Telegram: 'https://t.me/KylinOfficial', - Github: 'https://github.com/Kylin-Network', - }, - }, - { - id: '0-2051', - name: 'Ajuna Network', - slug: 'ajuna-network', - token: 'AJUN', - subtitle: - 'Ajuna Network is the fastest way for developers and game studios to create high-quality, decentralized games.', - info: 'Ajuna Network is a decentralized gaming platform that delivers real value to gamers, without compromising on gameplay. It empowers you to truly own your in-game assets, protect and control their functionality, and have a voice in the future of the games you love.', - links: { - Website: 'https://ajuna.io/', - Twitter: 'https://twitter.com/AjunaNetwork', - Medium: 'https://medium.com/@AjunaNetwork', - Github: 'https://github.com/ajuna-network/', - }, - }, - { - id: '0-2053', - name: 'OmniBTC', - slug: 'omnibtc', - token: 'TEER', - subtitle: 'Make Finance Omnichainable.', - info: 'OmniBTC is an omnichain financial platform for web3,including omnichain swap and BTC omnichain lending', - links: { - Website: 'https://www.omnibtc.finance/', - Twitter: 'https://twitter.com/OmniBTC', - Medium: 'https://medium.com/@omnibtc', - Github: 'https://github.com/OmniBTC', - }, - }, - { - id: '0-2090', - name: 'Oak Network', - slug: 'oak-network', - token: 'TEER', - subtitle: 'Secure and Trustless Automation Payments and finance infrastructure for the Web 3.0', - info: 'The Web 3.0 hub for DeFi and payment automation.', - links: { - Website: 'https://oak.tech/', - Twitter: 'https://twitter.com/oak_network', - Medium: 'https://medium.com/oak-blockchain', - Github: 'https://github.com/OAK-Foundation/OAK-blockchain', - }, - }, - { - id: '0-2093', - name: 'Hashed Network', - slug: 'hashed-network', - token: 'HASH', - subtitle: 'Digitally-native business on Polkadot', - info: 'Hashed Network enables the full business lifecycle for digitally-native organizations and web3-curious businesses seeking benefits of decentralized digital economies.', - links: { - Website: 'https://hashed.network/', - Twitter: 'https://twitter.com/HashedNetwork', - Medium: 'https://medium.com/hashednetwork', - Github: 'https://github.com/hashed-io', - }, - }, - { - id: '0-2094', - name: 'Pendulum', - slug: 'pendulum', - token: 'PEN', - subtitle: 'FX Blockchain Infrastructure', - info: `Pendulum’s interoperable blockchain brings together high-quality fiat tokens in a single, powerful DeFi network. Building the missing link between fiat and DeFi through a forex-optimized smart contract DeFi network allowing traditional finance services to seamlessly integrate with DeFi applications such as specialized forex AMMs, lending protocols, and yield farming and offering composable fiat solutions with 1000x performance. -‍ -Building Pendulum on Polkadot’s Substrate framework maximizes multi-chain interoperability and increased security. Pendulum will operate as a key piece of Web3 infrastructure to finally unlock DeFi use cases for fiat currencies. - -Our mission is to create an open financial future that overcomes borders and fosters economic inclusion.`, - links: { - Website: 'https://pendulumchain.org/', - Discord: 'https://discord.gg/wJ2fQh776B', - Telegram: 'https://t.me/pendulum_community', - Twitter: 'https://twitter.com/pendulum_chain', - Github: 'https://github.com/pendulum-chain', - Medium: 'https://pendulum-chain.medium.com/', - Reddit: 'https://www.reddit.com/r/Pendulum_Chain/', - LinkedIn: 'https://www.linkedin.com/company/pendulum-chain/', - }, - }, - { - id: '0-2097', - name: 'Bittensor', - slug: 'bittensor', - token: 'TAO', - subtitle: 'Incentivising Intelligence', - info: `Bittensor is an open-source protocol that powers a decentralized, blockchain-based machine learning network. - - Machine learning models train collaboratively and are rewarded in TAO according to the informational value they offer the collective. - - TAO also grants external access, allowing users to extract information from the network while tuning its activities to their needs.`, - links: { - Website: 'https://bittensor.com/', - Discord: 'https://discord.com/invite/3rUr6EcvbB', - Telegram: 'https://t.me/bittensor', - Twitter: 'https://twitter.com/bittensor_', - Github: 'https://github.com/opentensor/BitTensor', - Medium: 'https://bittensor.medium.com/', - Reddit: 'https://www.reddit.com/r/bittensor_', - LinkedIn: 'https://www.linkedin.com/company/bittensor', - }, - }, - { - id: '0-3340', - name: 'InvArch', - slug: 'invArch', - token: 'VARCH', - subtitle: 'Home of the DAO Economy', - info: `InvArch is a highly optimized network for launching decentralized communities, borderless businesses, & unstoppable organizations on Polkadot & beyond!`, - links: { - Website: 'https://invarch.network/', - Discord: 'https://discord.com/invite/InvArch', - Twitter: 'https://twitter.com/invarchnetwork', - Telegram: 'https://t.me/InvArch', - Github: 'https://github.com/InvArch', - Medium: 'https://invarch.medium.com/', - }, - }, - { - id: '2-2000', - name: 'Karura', - slug: 'karura', - token: 'KAR', - subtitle: 'Karura is the all-in-one DeFi hub of Kusama.', - info: 'Founded by the Acala Foundation, Karura is a scalable, EVM-compatible network optimized for DeFi. The platform offers a suite of financial applications including: a trustless staking derivative (liquid KSM), a multi-collateralized stablecoin backed by cross-chain assets (kUSD), and an AMM DEX – all with micro gas fees that can be paid in any token.\n\nAcala and Karura will operate in parallel and serve the users of the Polkadot and Kusama communities. Once Kusama is bridged to Polkadot, Karura and Acala will also be fully interoperable.', - links: { - Website: 'https://acala.network', - Twitter: 'https://twitter.com/AcalaNetwork', - Telegram: 'https://t.me/acalaofficial', - Discord: 'https://discord.com/invite/6QHVY4X', - Github: 'https://github.com/AcalaNetwork', - }, - }, - { - id: '2-2001', - name: 'Bifrost', - slug: 'bifrost-polkadot', - token: 'BNC', - subtitle: - 'As a DeFi project in the Polkadot ecosystem, Bifrost launches vToken which allows users to exchange PoS tokens to vTokens and obtain liquidity and Staking rewards through Bifrost protocol at any time.', - info: 'Participants staking KSMs with the Bifrost parachain slot will receive rewards in the native token BNC. There will be no new tokens released as rewards for the Kusama parachain.\n\nThe vesting schedules provided in Bifrost’s whitepaper are split into quarters. There is no indicated vesting period for Kusama parachain auction participants, however Bifrost has indicated a vesting period for rewards in the Polkadot parachain auctions. Details of Kusama auction reward vesting are to be determined.', - links: { - Website: 'https://ksm.vtoken.io/?ref=polkadotjs', - Twitter: 'https://twitter.com/bifrost_finance', - Discord: 'https://discord.gg/XjnjdKBNXj', - Telegram: 'https://t.me/bifrost_finance', - Medium: 'https://medium.com/bifrost-finance', - Github: 'https://github.com/bifrost-finance', - }, - }, - { - id: '2-2004', - name: 'Khala Network', - slug: 'khala', - token: 'PHA', - subtitle: 'Khala Network is the Phala pre-mainnet on Kusama, as published on the roadmap last year.', - info: 'Phala will implement its mainnet on Polkadot, as the parachain to serve enterprise-scale blockchains and DeFi service.\n\nKhala will implement its mainnet on Kusama, as the parachain to serve creative and growth blockchains and DeFi service.', - links: { - Website: 'https://crowdloan.phala.network/en/', - Twitter: 'https://twitter.com/PhalaNetwork', - Telegram: 'https://t.me/phalanetwork', - Medium: 'https://medium.com/phala-network', - Github: 'https://github.com/Phala-Network', - Discord: 'https://discord.gg/myBmQu5', - }, - }, - { - id: '2-2258', - name: 'Shiden', - slug: 'shiden-kusama', - token: 'SDN', - subtitle: 'Smart Contract Platform Natively Supporting Ethereum Virtual Machine and WebAssembly', - info: 'Shiden Network is a multi-chain decentralized application layer on Kusama Network.', - links: { - Website: 'https://shiden.astar.network/', - Twitter: 'https://twitter.com/ShidenNetwork', - Telegram: 'https://t.me/PlasmOfficial', - Medium: 'https://medium.com/astar-network', - Github: 'https://github.com/AstarNetwork/Astar', - Discord: 'https://discord.com/invite/Z3nC9U4', - }, - }, - { - id: '2-2120', - name: 'Shiden', - slug: 'shiden-kusama', - token: 'SDN', - subtitle: 'Shiden Network is a multi-chain decentralized application layer on Kusama Network. ', - info: 'Kusama Relaychain does not support smart contract functionality by design - Kusama Network needs a smart contract layer. This is where Shiden Network comes in. Shiden supports Ethereum Virtual Machine, WebAssembly, and Layer2 solutions from day one. The platform supports various applications like DeFi, NFTs and more.', - links: { - Website: 'https://crowdloan.plasmnet.io/', - Twitter: 'https://twitter.com/ShidenNetwork', - Telegram: 'https://t.me/PlasmOfficial', - Discord: 'https://discord.com/invite/Dnfn5eT', - }, - }, - { - id: '2-2007', - name: 'Shiden Network', - slug: 'shiden-network', - token: 'SDN', - subtitle: 'Smart Contract Platform Natively Supporting Ethereum Virtual Machine and WebAssembly', - info: 'Shiden Network is a multi-chain decentralized application layer on Kusama Network.', - links: { - Website: 'https://shiden.astar.network/', - Twitter: 'https://twitter.com/ShidenNetwork', - Telegram: 'https://t.me/PlasmOfficial', - Medium: 'https://medium.com/astar-network', - Github: 'https://github.com/AstarNetwork/Astar', - Discord: 'https://discord.com/invite/Z3nC9U4', - }, - }, - { - id: '2-2008', - name: 'Mars', - slug: 'mars', - token: 'MARS', - subtitle: 'Ares is an on-chain verifying oracle protocol powered by Polkadot.', - info: " It provides reliable off-chain data efficiently and in a trustless manner. Ares is built on Substrate and constructed as a parachain to link to Polkadot's ecology and share its security consensus. It is a scalable oracle network that provides decentralized data services to the Polkadot ecosystem and its parachains.", - links: { - Website: 'https://www.aresprotocol.io/', - Twitter: 'https://twitter.com/AresProtocolLab', - Telegram: 'https://t.me/aresprotocol', - Medium: 'https://aresprotocollab.medium.com/', - Github: 'https://github.com/aresprotocols', - Discord: 'https://discord.gg/EsaFRr7xmc', - }, - }, - { - id: '2-2009', - name: 'PolkaSmith by PolkaFoundry', - slug: 'polkasmith', - token: 'PKS', - subtitle: - 'Implemented on Kusama Network, PolkaSmith is a canary chain of PolkaFoundry, a one-stop production hub creating borderless and frictionless DeFi & NFT applications.', - info: 'PolkaSmith will be a reliable platform for early-stage startups to unleash their creativity, experiment with bold new ideas, and hack the growth.\n\nPKS is the native token of PolkaSmith. There is no pegging or mapping between PKS and PKF (PolkaFoundry’s native token).', - links: { - Website: 'https://polkasmith.polkafoundry.com/', - Twitter: 'https://twitter.com/PolkaFoundry', - Telegram: 'https://t.me/polkafoundry', - Medium: 'https://medium.com/@polkafoundry', - }, - }, - { - id: '2-2011', - name: 'Sora Kusama', - slug: 'sora-ksm', - token: 'XOR', - subtitle: 'The SORA Network provides tools for decentralized applications that use digital assets.', - info: ' The SORA Network excels at providing tools for decentralized applications that use digital assets, such as atomic token swaps, bridging tokens to other chains, and creating programmatic rules involving digital assets.', - links: { - Website: 'https://sora.org/', - Twitter: 'https://twitter.com/sora_xor', - Telegram: 'https://t.me/sora_xor', - Medium: 'https://sora-xor.medium.com/', - Github: 'https://github.com/sora-xor', - }, - }, - { - id: '2-2012', - name: 'Crust Shadow', - slug: 'crust', - token: 'CSM', - subtitle: 'CRUST provides a decentralized storage network of Web3.0 ecosystem.', - info: 'It supports multiple storage layer protocols such as IPFS, and exposes storage interfaces to application layer. Crust’s technical stack is also capable of supporting a decentralized computing layer. It is designed to build a decentralized cloud ecosystem that values data privacy and ownership.', - links: { - Website: 'https://crust.network/', - Twitter: 'https://twitter.com/CommunityCrust', - Telegram: 'https://t.me/CrustNetwork', - Medium: 'https://crustnetwork.medium.com/', - Github: 'https://github.com/crustio', - Discord: 'https://discord.com/invite/Jbw2PAUSCR', - }, - }, - { - id: '2-2013', - name: 'SherpaX', - slug: 'sherpax', - token: 'KSX', - subtitle: - 'SherpaX is the canary network of ChainX and will serve as a testbed for new developments in Bitcoin Layer 2 technology.', - info: 'As the canary network of ChainX, SherpaX will participate in the auction of the Kusama slot and access Kusama as a parachain. SherpaX will share the security of the entire Kusama network and can communicate with other parachains through the XCMP protocol to truly realize multi-chain and cross-chain.\n\nKusama parachains connect to the network by leasing a slot on the Relay Chain via permissionless auction. Kusama is rolling out the second batch of parachain auctions as we speak. In order to have the community support us in winning a slot, we’ve opened a crowdloan.', - links: { - Website: 'https://chainx.org/en/', - Twitter: 'https://twitter.com/chainx_org', - Telegram: 'https://t.me/chainx_org', - Medium: 'https://chainx-org.medium.com/', - Github: 'https://github.com/chainx-org/ChainX', - }, - }, - { - id: '2-2015', - name: 'Integritee Network', - slug: 'integritee-polkadot', - token: 'TEER', - subtitle: - 'Integritee Network enables developers and firms to process sensitive data, without compromising on privacy.', - info: 'Our platform combines the trust of blockchain with the confidentiality of off-chain, trusted execution environments (TEEs). This enables developers and firms to create decentralized data-driven apps and services that can securely process sensitive data, without revealing it on chain.\n\nThe Integritee ecosystem, across all instances on Kusama, Polkadot and elsewhere, will be powered by our native token, TEER. Backers who support our parachain bids by temporarily locking in KSM will be rewarded in TEER.', - links: { - Website: 'https://www.integritee.network/', - Twitter: 'https://twitter.com/integri_t_e_e', - Telegram: 'https://t.me/Integritee_Official', - Medium: 'https://medium.com/integritee', - Github: 'https://github.com/integritee-network', - Subsocial: 'https://app.subsocial.network/4638', - }, - }, - { - id: '2-2016', - name: 'Sakura', - slug: 'sakura', - token: 'SKU', - subtitle: - 'Sakura is a substrate-based parachain candidate specifically built for the cross-chain DeFi ecosystem on Kusama.', - info: 'Building on the success of the Rococo testnet, the stage for Kusama has been set as the first “mainnet” to utilize the power of Substrate. Kusama is regarded as a layer zero protocol and as the CanaryNet of Polkadot. The Clover Finance team envisions Sakura to live on as the innovative sister network of Clover, with both Clover and Sakura continuing to serve their communities simultaneously. The unique on-chain governance parameters of Kusama enables DeFi applications built on top of the Sakura network to have higher performance and scalability right away. This will lower the barrier to entry for the development community to deploy their dApps on Sakura without having to meet the stricter guidelines of Polkadot.\n\nSakura will utilize all of the core underlying technology stack that Clover has created and is continuously innovating. The Clover extension wallet will natively support Sakura dApps on EVM, polkadot.js based injections, and a native-built SKU<->ETH and SKU<->BSC bridge. The trustless cross-chain bridge for Ethereum and Bitcoin will be utilized on both Sakura and Clover. Sakura will ultimately aim to be a parachain Operating System with a storage layer, smart contract layer, DeFi protocol layer and eApp layer built on top of Kusama.', - links: { - Website: 'https://auction.clover.finance/#/', - Twitter: 'https://twitter.com/clover_finance/', - Telegram: 'https://t.me/clover_en/', - Medium: 'https://projectclover.medium.com/', - }, - }, - { - id: '2-2018', - name: 'SubGame Gamma', - slug: 'subgame-polkadot', - token: 'GSGB', - subtitle: 'SubGame is a public chain development team based on the Polkadot Parachain.', - info: 'It hopes to build a public chain with cross-chain interoperability. In addition to creating game applications, it can also build various types of application scenarios to create a common cross-chain industry. The blockchain infrastructure provides unlimited possibilities for distributed mobile applications.', - links: { - Website: 'https://www.subgame.org', - Twitter: 'https://twitter.com/SubgameBase', - Telegram: 'https://t.me/subgamenetwork', - }, - }, - { - id: '2-2019', - name: 'Kpron', - slug: 'kpron', - token: 'KPN', - subtitle: 'Kpron Network is the testnet that Apron Network deployed on the Kusama Network.', - info: 'The Kpron Network’s token: KPN, is the APN on Kusama.\n\nKPN was issued on Kpron Network as a portion of the tokens allocated by Apron Network and can be swapped with APN at a 1:1 rate (1KPN=1APN). There is no change on Apron’s tokenomics, and the total amount of APN remains the same.', - links: { - Website: 'http://apron.network/', - Twitter: 'https://twitter.com/apronofficial1', - Telegram: 'https://t.me/apronnetwork', - Medium: 'https://apron-network.medium.com/', - Github: 'https://github.com/Apron-Network/', - Discord: 'https://discord.gg/Bu6HzJP2YY', - }, - }, - { - id: '2-2023', - name: 'Moonriver', - slug: 'moonriver', - token: 'MOVR', - subtitle: 'A Community-Led Sister Parachain on Kusama', - info: 'Like Moonbeam, Moonriver is a complete Ethereum-like environment and works with industry-standard Ethereum tools, DApps, and protocols. Moonriver is a companion network to Moonbeam and provides a permanently incentivized canary network. New code ships to Moonriver first, where it can be tested and verified under real economic conditions. Once proven, the same code ships to Moonbeam on Polkadot. The Moonriver network is currently launching to Kusama. Track the launch status here: https://moonbeam.network/networks/moonriver/launch/', - links: { - Website: 'https://moonbeam.network/networks/moonriver/', - Twitter: 'https://twitter.com/moonbeamnetwork', - Telegram: 'https://t.me/Moonbeam_Official', - Medium: 'https://medium.com/moonbeam-network', - Github: 'https://github.com/PureStake/moonbeam', - Discord: 'https://discord.gg/PfpUATX', - }, - }, - { - id: '2-2024', - name: 'Genshiro', - slug: 'genshiro-kusama', - token: 'GENS', - subtitle: 'Genshiro is a canary network of equilibrium that shares the experimental spirit of Kusama.', - info: "Genshiro is EquilibriumDeFi's DeFi one-stop shop on Kusama that can do all things that existing DeFi primitives do, but with less risk and cross-chain.", - links: { - Website: 'https://genshiro.equilibrium.io/', - Twitter: 'https://twitter.com/GenshiroDeFi', - Telegram: 'https://t.me/genshiro_official', - Medium: 'https://medium.com/equilibrium-eosdt', - Github: 'https://github.com/equilibrium-eosdt', - }, - }, - { - id: '2-2048', - name: 'Robonomics', - slug: 'robonomics-kusama', - token: 'XRT', - subtitle: - 'Robonomics is a project with a long history that started in 2015, after the launch of the Ethereum network.', - info: 'During the project’s development, the team published more than 10 academic articles and created more than 15 R&D projects which included control of drones, industrial manipulators, sensor networks, and even a Boston Dynamics’ robot dog over Ethereum and Polkadot networks.\n\nRobonomics is a project that integrates new technologies into the real economy. However, to fuel this, a reasonable ‘gas’ price is required. Kusama makes the costs of communication between IoT devices and humans affordable.', - links: { - Website: 'https://robonomics.network/', - Twitter: 'https://twitter.com/AIRA_Robonomics', - Telegram: 'https://t.me/robonomics', - Medium: 'https://blog.aira.life/', - Github: 'https://github.com/airalab', - }, - }, - { - id: '2-2080', - name: 'Loom Network', - slug: 'loom-network', - token: 'LOOM', - subtitle: - 'At Loom Network, we want to enable developers to build dapps that are easily accessible across all major blockchains, and for users to be able to use dapps without wasting time trying to figure out the intricacies of the blockchain each dapp happens to be running on.', - info: 'To that end we’ve already built integrations with Ethereum, TRON, and Binance Smart Chain.\n\nWe are going to be giving out 100 LOOM tokens for each KSM token contributed to our Crowdloan. If we win a parachain auction, we will start distributing the rewards ASAP, there will be no vesting or lockup periods for these rewards.', - links: { - Website: 'https://loomx.io/', - Twitter: 'https://twitter.com/loomnetwork', - Telegram: 'https://t.me/loomnetwork', - Github: 'https://github.com/loomnetwork', - Reddit: 'https://www.reddit.com/r/loomnetwork/', - Medium: 'https://medium.com/loom-network', - }, - }, - { - id: '2-2084', - name: 'Calamari', - slug: 'calamari', - token: 'KMA', - subtitle: - "Calamari, Manta Network's canary-net, is the plug-and-play privacy-preservation parachain built to service the Kusama DeFi world.", - info: 'It combines Kusama and zkSNARKs to bring on-chain privacy to transactions and swaps.', - links: { - Website: 'https://www.calamari.network/', - Twitter: 'https://twitter.com/CalamariNetwork', - Telegram: 'https://t.me/mantanetworkofficial', - Medium: 'https://medium.com/@mantanetwork', - Github: 'https://github.com/Manta-Network', - }, - }, - { - id: '2-2085', - name: 'Parallel Heiko', - slug: 'parallel', - token: 'HKO', - subtitle: - 'Parallel Finance is a decentralized money market protocol that offers lending, staking, and borrowing in the Polkadot ecosystem.', - info: 'Similar to the relationship between Polkadot and its “canary network” Kusama, Heiko Finance is the sister network to Parallel, and the parachain that we hope to launch on the Kusama blockchain. We are building for a decentralized future that empowers the community to increase capital efficiency, security, and accessibility through our leverage staking and auction lending platform.', - links: { - Website: 'https://parallel.fi/', - Twitter: 'https://twitter.com/ParallelFi', - Telegram: 'https://t.me/parallelfi_community', - Medium: 'https://parallelfinance.medium.com/', - Github: 'https://github.com/parallel-finance/', - Discord: 'https://t.co/Ev6c7lI9U4', - }, - }, - { - id: '2-2086', - name: 'KILT Spiritnet', - slug: 'kilt-spiritnet', - token: 'KILT', - subtitle: - 'KILT is a blockchain protocol for issuing self-sovereign verifiable, revocable, anonymous credentials and enabling trust market business models in the Web 3.0.', - info: 'KILT is an open-source fat blockchain protocol for issuing claim-based verifiable, revocable, and anonymous credentials in the Web 3.0. It allows end users to claim arbitrary attributes about themselves, get them attested by trusted entities, and store the claims as self-sovereign credentials (certificates). As trusted entities can issue credentials in return for money, KILT aims to foster new business models for anyone who owns trust or wants to build up trust. KILT Protocol comes with a simple JavaScript SDK where useful applications can be built without requiring any blockchain development skills.', - links: { - Website: 'https://www.kilt.io/', - Twitter: 'https://twitter.com/Kiltprotocol', - Telegram: 'https://t.me/KILTProtocolChat', - Medium: 'https://kilt-protocol.medium.com/', - Github: 'https://github.com/KILTprotocol', - Element: 'https://riot.im/app/#/group/+kilt-community:matrix.org', - Youtube: 'https://www.youtube.com/channel/UC5ihHD8UyGGx0oLZt78429w', - Reddit: 'https://www.reddit.com/r/KiltProtocol/', - Linkedin: 'https://www.linkedin.com/company/kilt-protocol/', - }, - }, - { - id: '2-2087', - name: 'Picasso', - slug: 'picasso', - token: 'PICA', - subtitle: - 'Picasso is an experimental ecosystem to birth new financial primitives and build applications that communicate natively, in a composed manner.', - info: 'Composable Finance is pleased to announce the Picasso token (PICA), which will be the native token of the Picasso Network Kusama parachain. PICA token will have a multitude of important use cases on our upcoming Picasso parachain, including key governance decisions.', - links: { - Website: 'https://picasso.composable.finance/', - Twitter: 'https://twitter.com/ComposableFin', - Telegram: 'https://t.me/joinchat/uAGCJk_Cjc9iYTky', - Medium: 'https://composablefi.medium.com', - Github: 'https://github.com/ComposableFi', - Discord: 'https://discord.gg/pFZn2GCn65', - Linkedin: 'https://www.linkedin.com/company/composable-finance/', - }, - }, - { - id: '2-2088', - name: 'Altair', - slug: 'altair', - token: 'AIR', - subtitle: - 'Altair combines the industry-leading infrastructure built by Centrifuge to finance real-world assets (RWA) on Centrifuge Chain, with the newest experimental features — before they go live on Centrifuge Chain.', - info: 'Altair is built using Substrate, and will have nearly the same codebase as Centrifuge Chain (just like Kusama is to Polkadot!). It is an experimental network for users who want to test the bounds of asset financing. From art NFTs to undiscovered assets — Altair enables users to tokenize their most experimental assets and finance them. It is the next step for anyone looking to unlock financing for their assets.\n\nInteroperability is the key to increasing liquidity in DeFi. Altair will bridge across the Kusama, Polkadot, and Ethereum ecosystems to allow assets to access financing wherever it is available. In the future, Altair can connect more and more projects across these ecosystems — using Kusama to allow anyone to access DeFi liquidity. The more connected chains, protocols, and Dapps are — the greater the flow of liquidity will be.', - links: { - Website: 'https://centrifuge.io/altair', - Twitter: 'https://twitter.com/centrifuge', - Telegram: 'https://t.me/centrifuge_chat', - Medium: 'https://medium.com/centrifuge', - Github: 'https://github.com/centrifuge/', - Discord: 'https://centrifuge.io/discord', - }, - }, - { - id: '2-2090', - name: 'Basilisk', - slug: 'basilisk', - token: 'BSX', - subtitle: - 'Basilisk is a liquidity bootstrapping protocol designed to operate as a parachain in Kusama, the Substrate canary network from the Polkadot family.', - info: 'Basilisk is a natural stepping stone on our journey of building the liquidity infrastructure of the future. This plan will eventually culminate in the HydraDX Omnipool which is intended to operate as a Polkadot parachain in order to enable frictionless liquidity for any asset on any chain.\n\nTogether, Basilisk and HydraDX create a synergy which caters to the varying needs of cryptoassets throughout their entire life cycle. Bootstrap liquidity in the early stages using Basilisk, then move over to the HydraDX Omnipool to unlock unprecedented liquidity in an ocean of assets.', - links: { - Website: 'https://bsx.fi/', - Twitter: 'https://twitter.com/bsx_finance', - Telegram: 'https://t.me/bsx_fi', - Github: 'https://github.com/galacticcouncil', - Discord: 'https://discord.gg/S8YZj5aXR6', - }, - }, - { - id: '2-2092', - name: 'Kintsugi BTC', - slug: 'kintsugi', - token: 'KINT', - subtitle: - 'Kintsugi’s kBTC brings radically open Bitcoin to Kusama to kickstart liquidity for parachains like Karura, Shiden and Moonriver.', - info: 'Kintsugi is interBTC’s canary network, developed by Interlay — an R&D company focused on blockchain interoperability. Founded by ex-Imperial College CS PhDs — Alexei Zamyatin and Dominik Harz, Interlay’s mission is to make Bitcoin interoperable in a fully trustless and decentralized way.\n\nThe non-profit Kintsugi Lab will be responsible for Kintsugi’s launch and further — support the development and growth of the decentralized network.\n\nInspired by the ancient Japanese tradition of embracing the flawed and imperfect, Kintsugi accepts the nascent DeFi ecosystem on Kusama as chaotic while constantly being perfected by golden streaks of its community.', - links: { - Website: 'https://kintsugi.interlay.io/', - Twitter: 'https://twitter.com/kintsugi_btc', - Telegram: 'https://t.me/interlay_community', - Medium: 'https://medium.com/interlay/', - Github: 'https://github.com/interlay', - Discord: 'https://discord.gg/KgCYK3MKSf', - }, - }, - { - id: '2-2095', - name: 'Quartz', - slug: 'quartz', - token: 'QTZ', - subtitle: 'Quartz gives the Kusama community the essential and advanced NFT tools to unleash innovation.', - info: 'Quartz gives you easy access to test extreme innovation in NFTs and build for the next generation. Built on Substrate, Quartz gives you the most versatile options for discovery and democratization of the NFT ecosystem and marketplaces (with very low barriers for entry).\nAdvanced features like Flexible Economic Models, Scheduled Transactions, Re-fungiblity, and Nested NFTs will all be available via Quartz, allowing users to own UX/UI for your fans and customers.Quartz parachain on Kusama will allow you to build with interoperability between different blockchains, and give developers and engineers the access to the shared security of the entire network.', - links: { - Website: 'https://unique.network/quartz/', - Twitter: 'https://twitter.com/Unique_NFTchain', - Telegram: 'https://t.me/Uniquechain', - Github: 'https://github.com/UniqueNetwork', - }, - }, - { - id: '2-2096', - name: 'Bit.Country Pioneer', - slug: 'bitcountry-pioneer', - token: 'NEER', - subtitle: 'The Platform for User-created Metaverses & Games with Opportunities to Earn.', - info: 'The platform allows non-technical users to build their own metaverse. Developers can use our API to develop their games and smart contract dapps. Metaverse.Network is the blockchain network of Bit.Country application framework.', - links: { - Website: 'https://bit.country/', - Twitter: 'https://twitter.com/BitDotCountry', - Telegram: 'https://t.me/BitCountryOfficialTG', - Medium: 'https://bitcountry.medium.com/', - Github: 'https://github.com/bit-country', - Discord: 'https://discord.com/invite/PaMAXZZ59N', - }, - }, - { - id: '2-2100', - name: 'Subsocial', - slug: 'subsocial', - token: 'SUB', - subtitle: 'Subsocial - Decentralized social network on Polkadot & IPFS', - info: 'Subsocial is a Polkadot ecosystem project supported by Web3 Foundation. Subsocial follows SoFi (social finance) principles to bring DeFi features to social networking.', - links: { - Website: 'https://subsocial.network', - Twitter: 'https://twitter.com/SubsocialChain', - Telegram: 'https://t.me/Subsocial', - Discord: 'https://discord.com/invite/w2Rqy2M', - Github: 'https://github.com/dappforce', - }, - }, - { - id: '2-2101', - name: 'Zeitgeist', - slug: 'zeitgeist', - token: 'ZTG', - subtitle: 'Zeitgeist is an evolving blockchain for prediction markets and futarchy.', - info: 'Zeitgeist is a decentralized network for prediction markets. Zeitgeist is an evolving network that will change and adapt over time. It does this through a sophisticated on-chain governance process.', - links: { - Website: 'https://zeitgeist.pm/', - Twitter: 'https://twitter.com/ZeitgeistPM', - Telegram: 'https://t.me/zeitgeist_official', - Discord: 'https://discord.gg/xv8HuA4s8v', - Github: 'https://github.com/ZeitgeistPM', - Medium: 'https://blog.zeitgeist.pm/#subscribe', - }, - }, - { - id: '2-2102', - name: 'Pichiu', - slug: 'pichiu', - token: 'PCHU', - subtitle: 'Pichiu aims to build a cross-chain platform powering the data economy on Kusama.', - info: 'It will be the data infrastructure for the future DeFi and Web 3.0 powered by Kusama. Pichiu will provide valid, reliable, secure, cost-effective, and easily-coordinated data sources and data analytics.', - links: { - Website: 'https://kylin.network/', - Twitter: 'https://twitter.com/Kylin_Network', - Telegram: 'https://t.me/KylinOfficial', - Discord: 'https://discord.com/invite/PwYCssr', - Github: 'https://github.com/Kylin-Network/kylin-collator', - Medium: 'https://kylinnetwork.medium.com/', - }, - }, - { - id: '2-2105', - name: 'Darwinia Crab', - slug: 'crab-kusama', - token: 'CRAB', - subtitle: - 'Crab is the canary network of Darwinia, and is the first blockchain in the Kusama ecosystem to natively support cross-chain as well as smart contract and NFT.', - info: 'Crab Network intends to participate in the Kusama Parachain Slot Auctions.\n\nThe Crab network is a network with long-term value. Some RINGs are allocated to Crab Network as backing assets to make it serve as a canary network having real economic incentives and massive gaming theory testing, not just working a testnet.\n\nThe economic model parameters of the Crab network are the same as those of the Darwinia Mainnet, and use the same staking and inflation models.', - links: { - Website: 'https://crab.network/', - Twitter: 'https://twitter.com/DarwiniaNetwork', - Telegram: 'https://t.me/DarwiniaNetwork', - Medium: 'https://darwinianetwork.medium.com/', - Github: 'https://github.com/darwinia-network/darwinia/tree/master/runtime/crab', - }, - }, - { - id: '2-2106', - name: 'Litmus', - slug: 'litmus', - token: 'LIT', - subtitle: 'A Web3 identity hub on Kusama.', - info: 'Litmus is the aptly named canary network for Litentry, a decentralised identity aggregation protocol for DotSama. The protocol provides a decentralized, interoperable identity aggregation service that mitigates the difficulty of resolving agnostic DID mechanisms. Litentry provides a secure vehicle through which users manage their identities and dApps obtain the real-time credit/reputation of an identity owner across different blockchains.', - links: { - Website: 'https://kusama-crowdloan.litentry.com/', - Twitter: 'https://twitter.com/litentry', - Telegram: 'https://t.me/litentry', - Medium: 'https://litentry.medium.com//', - Github: 'https://github.com/litentry', - Discord: 'https://discord.gg/M7T4y4skVD', - }, - }, - { - id: '2-2107', - name: 'Kico', - slug: 'kico', - token: 'KICO', - subtitle: 'KICO is the canary network for DICO.', - info: 'The DICO chain creates a decentralized and governable ICO platform for the Polkadot environment. We provide a decentralized platform, which heavily supports its projects.', - links: { - Website: 'https://dico.io/', - Twitter: 'https://twitter.com/DICO03279704', - Telegram: 'https://t.me/dicochain', - Medium: 'https://medium.com/@DearICO/', - Github: 'https://github.com/DICO-TEAM', - Discord: 'https://discord.com/invite/V2MASPX3Ra', - }, - }, - { - id: '2-2110', - name: 'Mangata X', - slug: 'mangata-x', - token: 'MGX', - subtitle: 'Mangata X is a community-owned exchange for experienced traders & experimental tokens.', - info: 'Mangata X will build bridges to Ethereum and other networks, as well as open channels to Parachains to host tokens from the newest experimental Parachains and Protocols in the Kusama and Ethereum space.', - links: { - Website: 'https://x.mangata.finance/', - Twitter: 'https://twitter.com/MangataFinance', - Telegram: 'https://t.me/mgtfi', - Medium: 'https://blog.mangata.finance/', - Github: 'https://github.com/mangata-finance', - Discord: 'https://discord.com/invite/X4VTaejebf', - }, - }, - { - id: '2-2114', - name: 'Turing Network', - slug: 'turing', - token: 'TUR', - subtitle: 'Turing Network is the Kusama parachain of Oak Network, the Web 3.0 Hub for DeFi and Payment Automation.', - info: "OAK (On-chain Autonomous Kernel) Network is an automation layer-2 blockchain for Defi and recurring payments. Our mission is to build a Stripe for Web 3.0 across protocols. The blockchain is built on Parity Substrate with an event-driven execution model. Different from Ethereum’s transaction-based model, where a transaction requires private key signing, OAK allows transactions to be triggered by event signals such as time, price, and smart contract state changes.\n\nOAK Network aims to be the No.1 utility Layer-2 across protocols, offering on-chain automation not possible today, such as limit & stop-loss orders on Automated-Market-Making DEX and recurring payments. With OAK's on-chain finality and network security, it can empower any blockchain with its autonomous functionality, unleashing endless product potentials.", - links: { - Website: 'https://oak.tech/', - Twitter: 'https://twitter.com/oak_network', - Telegram: 'https://t.me/OAK_Announcements', - Medium: 'https://medium.com/oak-blockchain', - Github: 'https://github.com/OAK-Foundation', - Discord: 'https://discord.gg/7W9UDvsbwh', - }, - }, - { - id: '2-2115', - name: 'Dora Factory', - slug: 'dora-factory', - token: 'DORA', - subtitle: 'The DAO infrastructure of the Kusama ecosystem.', - info: 'The DAO-as-a-Service infrastructure for future organizations and communities.', - links: { - Website: 'https://dorafactory.org/kusama/', - Twitter: 'https://twitter.com/DoraFactory', - Telegram: 'https://t.me/dorafactory', - Medium: 'https://dorafactory.medium.com/', - }, - }, - { - id: '2-2113', - name: 'Kabocha', - slug: 'kabocha', - token: 'KAB', - subtitle: - 'Kabocha is a holistic self-evolving grass-roots parachain project, funding and incubating teams to build new public infrastructure.', - info: 'It is an experiment in crafting new realities through direct participation, collaborative creation and shared values.\n Its ownership and governance is fair, transparent and decentralised at launch thanks to its genesis in the Edgeware community. \n The project introduces a Network Public template and novel Store of Values currency and is built with the Substrate framework.', - links: { Website: 'https://www.kabocha.network/', Twitter: 'https://twitter.com/kabochanetwork' }, - }, - { - id: '2-2116', - name: 'Tanganika Network (DHX)', - slug: 'tanganika', - token: 'DHX', - subtitle: - 'DataHighway is a DAO that incentivizes the community to run a sophisticated IoT parachain based on Polkadot using the DHX token.', - info: "DataHighway's community members will ultimately be incentivized to operate a sophisticated IoT parachain based on Polkadot, where they may stake, govern, \n mine and otherwise participate using the new DHX token and its associated Decentralized Autonomous Organization (DAO), and Inter-Chain Data Market.", - links: { - Website: 'https://www.datahighway.com/', - Twitter: 'https://twitter.com/DataHighway_DHX', - Discord: 'https://discord.com/invite/UuZN2tE', - }, - }, - { - id: '2-2118', - name: 'Listen Network', - slug: 'listen-network', - token: 'LT', - subtitle: - 'Listen is a voice social software based on decentralized data storage technology and blockchain technology.', - info: 'Listen as a voice of social software, it is different with other communication tools, and podcasting software is also different, social support acquaintances, including voice, images, text and video, such as social practices, and social group in a stranger, using a similar round table in the form of BBS, group manager invited several guests, they have speaking right, and join hands to Listen and speak to the rest of the room, at the same time also can use the LT token to buy the speaking time, almost purchases will accumulate in the room below. Listen creates a unique experience that is different from a Feed stream. In a community of strangers, celebrities engage in voice discussions and audience interactions around topics.', - links: { - Website: 'https://listen.io/', - Twitter: 'https://twitter.com/Listen_io', - Telegram: 'https://t.me/listengroup', - }, - }, - { - id: '2-2119', - name: 'Bajun Network', - slug: 'bajun', - token: 'BAJU', - subtitle: - 'Ajuna is ready to unlock the next level of blockchain gaming. We have the power to enable responsive, immersive, graphically-rich web3 games built with industry-leading development tools.', - info: 'To get there, we need a Kusama Parachain, and WE NEED YOUR SUPPORT. We’ve launched a Crowdloan campaign to reward everyone who backs Ajuna in the upcoming Kusama Parachain auctions with BAJU — our primary token on Kusama — and other exclusive perks.', - links: { - Website: 'https://ajuna.io/', - Twitter: 'https://twitter.com/AjunaNetwork', - Telegram: 'https://t.me/ajunanetwork', - Discord: 'https://discord.com/invite/cE72GYcFgY', - }, - }, - { - id: '2-2124', - name: 'Amplitude', - slug: 'amplitude', - token: 'AMPE', - subtitle: 'Amplitude is going to launch as a canary network of its sister blockchain, Pendulum.', - info: 'It will act as a testing ground for applications and network parameters for Pendulum. Unlike a traditional testnet, users can interact on the Amplitude chain with real financial consequences. We’re expecting lots of novel concepts to be trialled on Amplitude, giving rise to many exciting opportunities for anyone using the chain.', - links: { - Website: 'https://pendulumchain.org/amplitude', - Twitter: 'https://twitter.com/pendulum_chain', - Telegram: 'https://t.me/pendulum_community', - Discord: 'https://discord.gg/wJ2fQh776B', - }, - }, - { - id: '2-2125', - name: 'Tinker', - slug: 'tinker', - token: 'TNKR', - subtitle: 'The IP Asset & Accelerated Development Hub Of Kusama', - info: 'The Tinkernet Parachain is the canary network of the InvArch Network, designed as the IP Asset & accelerated development staging grounds for the Kusama ecosystem.', - links: { - Website: 'https://invarch.network/tinkernet', - Twitter: 'https://twitter.com/invarchnetwork', - Telegram: 'https://t.me/InvArch', - Discord: 'https://discord.com/invite/invarch', - }, - }, - { - id: '2-2123', - name: 'GM Parachain', - slug: 'gm', - token: '$FREN', - subtitle: "$GM OR DIE, UNTIL IT'S TIME TO $GN", - info: "Burn $FREN to mint and send $GM, but mind the clock on the GMdapp, $GM can only be minted in the morning, $GN can only be minted at night. If you try to mint during the day, you'll burn your $FREN for nothing!", - links: { - Website: 'https://www.gmordie.com/', - Twitter: 'https://twitter.com/GmOrDie_', - Discord: 'https://discord.com/invite/JFzD2b5P2B', - }, - }, - { - id: '2-2241', - name: 'Krest', - slug: 'krest', - token: 'KREST', - subtitle: "krest is peaq’s canary network - the world's first and only Economy of Things simulation network.", - info: 'krest is your home for socio-economic, technical, community, and governance innovation and experimentation within the peaq ecosystem. Launch dApps and tools for the Economy of Things and assess their impact in a live environment on a public blockchain network, without running the risk of causing real-world harm.', - links: { - Website: 'https://krest.peaq.network/?ref=parachains-info', - Twitter: 'https://twitter.com/peaqnetwork', - Discord: 'https://discord.gg/XhJwuFKAAD', - Github: 'https://github.com/peaqnetwork/peaq-network-node', - }, - }, - { - id: '2-2256', - name: 'Mangata X', - slug: 'mangata', - token: '$MGX', - subtitle: 'Trade crypto without gas, front-running & MEV.', - info: 'Mangata offers traders and liquidity providers the best platform to access unique tokens and earn rewards. Mangata will offer access to the widest array of Polkadot tokens, better prices for traders by mitigating MEV, and the ability to trade without gas.', - links: { - Website: 'https://www.mangata.finance/', - Twitter: 'https://twitter.com/MangataFinance', - Discord: 'https://discord.com/invite/X4VTaejebf', - Github: 'https://github.com/mangata-finance', - }, - }, -] - -export type CrowdloanDetails = { - relayId: number - paraId: number - contributeUrl?: string - rewards: { - tokens: Array<{ symbol: string; perKSM: string }> | null - custom: Array<{ title: string; value: string }> | null - bonus?: { - short: string - full: string - info: string - } - info: string | null - } -} - -export const crowdloanDetails: CrowdloanDetails[] = [ - { - relayId: 0, - paraId: 2000, - rewards: { - tokens: null, - custom: [ - { - title: 'ACA per DOT', - value: '> 3', - }, - { - title: 'lcDOT per DOT', - value: '1', - }, - // this information isn't actionable, so I've clobbered it for now. - // { - // title: 'Referals', - // value: '5%', - // }, - ], - bonus: { - short: '5% bonus', - full: 'Earn 5% ACA', - info: 'Receive 5% additional ACA when you contribute via Talisman.', - }, - info: null, - }, - }, - { - relayId: 0, - paraId: 2002, - rewards: { - tokens: null, - custom: [ - { - title: 'CLV per DOT', - value: 'TBD', - }, - { - title: 'Crowdloan Allocation', - value: '200,000,000 CLV', - }, - { - title: 'Immediate Release', - value: '28% of rewarded CLV', - }, - { - title: 'Vesting Remainder', - value: '72%, over 23 months', - }, - ], - bonus: { - short: '', - full: '', - info: '', - }, - info: null, - }, - }, - { - relayId: 0, - paraId: 2003, - rewards: { - tokens: null, - custom: [ - { - title: 'RING per DOT', - value: 'TBD', - }, - { - title: 'Crowdloan Allocation', - value: '200,000,000 RING', - }, - { - title: 'Total Supply', - value: '10,000,000,000 RING', - }, - { - title: 'Immediate Release', - value: '10% of rewarded RING', - }, - { - title: 'Vesting Remainder', - value: '90%, vesting linearly', - }, - ], - bonus: { - short: '', - full: '', - info: '', - }, - info: null, - }, - }, - { - relayId: 0, - paraId: 2004, - rewards: { - tokens: null, - custom: [ - { - title: 'GLMR per DOT', - value: '> 1', - }, - ], - bonus: { - short: '', - full: '', - info: '', - }, - info: null, - }, - }, - { - relayId: 0, - paraId: 2006, - rewards: { - tokens: null, - custom: [ - { - title: 'ASTR per DOT', - value: '> 30', - }, - { - title: 'Crowdloan Allocation', - value: '1,050,000,000 ASTR', - }, - { - title: 'Bonus Pool', - value: '350,000,000 ASTR', - }, - ], - bonus: { - short: '', - full: '', - info: '', - }, - info: null, - }, - }, - { - relayId: 0, - paraId: 2007, - rewards: { - tokens: null, - custom: [ - { - title: 'KAPEX per DOT', - value: '> 0.1', - }, - ], - bonus: { - short: '', - full: '', - info: '', - }, - info: null, - }, - }, - { - relayId: 0, - paraId: 2008, - rewards: { - tokens: null, - custom: [ - { - title: 'CRU per DOT', - value: '0.2+', - }, - { - title: 'Crowdloan Allocation', - value: '1,000,000 CRU', - }, - ], - bonus: { - short: '', - full: '', - info: '', - }, - info: null, - }, - }, - { - relayId: 0, - paraId: 2011, - rewards: { - tokens: null, - custom: [ - { - title: 'EQ per DOT', - value: '200+', - }, - ], - bonus: { - short: '', - full: '', - info: '', - }, - info: null, - }, - }, - { - relayId: 0, - paraId: 2012, - rewards: { - tokens: null, - custom: [ - { - title: 'PARA per DOT', - value: '> 50', - }, - { - title: 'Crowdloan Allocation', - value: '1,500,000,000 PARA', - }, - ], - bonus: { - short: '', - full: '', - info: '', - }, - info: null, - }, - }, - { - relayId: 0, - paraId: 2013, - rewards: { - tokens: null, - custom: [ - { - title: 'LIT per DOT', - value: '> 2.5', - }, - { - title: 'Crowdloan Allocation', - value: '20,000,000 LIT', - }, - { - title: 'Early bonus', - value: '10% before auction start', - }, - { - title: 'Early bonus', - value: '5% within 7 days of auction start', - }, - ], - bonus: { - short: '', - full: '', - info: '', - }, - info: null, - }, - }, - { - relayId: 0, - paraId: 2015, - rewards: { - tokens: null, - custom: [ - { - title: 'MANTA per DOT', - value: '4', - }, - { - title: 'Total Supply', - value: '1,000,000,000 MANTA', - }, - { - title: 'Crowdloan Allocation', - value: '120,000,004 MANTA', - }, - ], - bonus: { - short: '5% bonus', - full: 'Earn 5% bonus', - info: '10% before November 11th 2021, 5% bonus before Feb 1st, 2022 + a Manta NFT', - }, - info: null, - }, - }, - { - relayId: 0, - paraId: 2017, - rewards: { - tokens: null, - custom: [ - { - title: 'SGB per DOT', - value: '13', - }, - ], - bonus: { - short: '', - full: '', - info: '', - }, - info: null, - }, - }, - { - relayId: 0, - paraId: 2019, - rewards: { - tokens: null, - custom: [ - { - title: 'LAYR per DOT', - value: '0.48+', - }, - ], - bonus: { - short: '', - full: '', - info: '', - }, - info: null, - }, - }, - { - relayId: 0, - paraId: 2021, - rewards: { - tokens: null, - custom: [ - { - title: 'EFI per DOT', - value: '> 4', - }, - { - title: 'Total Supply', - value: '2,000,000,000 EFI', - }, - { - title: 'Crowdloan Allocation', - value: '200,000,000 EFI', - }, - ], - bonus: { - short: '', - full: '', - info: '', - }, - info: null, - }, - }, - { - relayId: 0, - paraId: 2026, - rewards: { - tokens: null, - custom: [ - { - title: 'NODL per DOT', - value: '20+', - }, - ], - bonus: { - short: '', - full: '', - info: '', - }, - info: null, - }, - }, - { - relayId: 0, - paraId: 2027, - rewards: { - tokens: null, - custom: [ - { - title: 'CTO per DOT', - value: '3.75+', - }, - ], - bonus: { - short: '', - full: '', - info: '', - }, - info: null, - }, - }, - { - relayId: 0, - paraId: 2028, - rewards: { - tokens: null, - custom: [ - { - title: 'ARES per DOT', - value: 'TBA', - }, - ], - bonus: { - short: '', - full: '', - info: '', - }, - info: null, - }, - }, - { - relayId: 0, - paraId: 2031, - rewards: { - tokens: null, - custom: [ - { - title: 'CFG per DOT', - value: '3.28+', - }, - ], - bonus: { - short: '', - full: '', - info: '', - }, - info: null, - }, - }, - { - relayId: 0, - paraId: 2032, - rewards: { - tokens: null, - custom: [ - { - title: 'INTR per DOT', - value: '3.46+', - }, - ], - bonus: { - short: '', - full: '', - info: '', - }, - info: null, - }, - }, - { - relayId: 0, - paraId: 2034, - rewards: { - tokens: null, - custom: [ - { - title: 'HDX per DOT', - value: '280 and 125', - }, - ], - bonus: { - short: '', - full: '', - info: '', - }, - info: null, - }, - }, - { - relayId: 0, - paraId: 2035, - rewards: { - tokens: null, - custom: [ - { - title: 'PHA per DOT', - value: '24', - }, - { - title: 'Khala Crowdloan Contributors', - value: '5% extra reward', - }, - ], - bonus: { - short: '', - full: '', - info: '', - }, - info: null, - }, - }, - { - relayId: 0, - paraId: 2037, - rewards: { - tokens: null, - custom: [ - { - title: 'UNQ per DOT', - value: '>5.28', - }, - { - title: 'Vesting Schedule', - value: '20 months, linear vesting', - }, - ], - info: null, - }, - }, - { - relayId: 0, - paraId: 2038, - rewards: { - tokens: null, - custom: [ - { - title: 'GEM per DOT', - value: '>10', - }, - ], - info: null, - }, - }, - { - relayId: 0, - paraId: 2040, - rewards: { - tokens: null, - custom: [], - info: null, - }, - }, - { - relayId: 0, - paraId: 2030, - rewards: { - tokens: null, - custom: [ - { - title: 'BNC per DOT', - value: '>10', - }, - { - title: 'Vesting Schedule', - value: 'linearly each block (96 weeks)', - }, - { - title: 'Early Bird Bonus', - value: '20% 4/22 10am ~ 4/30 10am (UTC)', - }, - ], - info: null, - }, - }, - { - relayId: 0, - paraId: 2043, - rewards: { - tokens: null, - custom: [ - { - title: 'OTP per DOT', - value: '20', - }, - ], - info: null, - }, - }, - { - relayId: 0, - paraId: 2039, - rewards: { - tokens: null, - custom: [ - { - title: 'Supporter rewards', - value: '2.5 TEER + bonuses', - }, - ], - info: null, - }, - }, - { - relayId: 0, - paraId: 2093, - rewards: { - tokens: null, - custom: [ - { - title: 'HASH per DOT', - value: '480 HASH', - }, - { - title: 'Crowdloan Cap', - value: '125,000 DOT', - }, - { - title: 'Crowdloan Allocation', - value: '60m of 1b HASH (6%)', - }, - ], - info: null, - }, - }, - { - relayId: 0, - paraId: 2097, - rewards: { - tokens: null, - custom: [ - { - title: 'Rewards', - value: '0.0 TAO', - }, - { - title: 'Token Supply', - value: '21,000,000 TAO', - }, - ], - info: 'This crowdloan is following a "self-funded" approach, meaning any contributors to the crowdloan will not receive any TAO rewards. Therefore, we do not recommend contributing to this crowdloan - NFA/DYOR.', - }, - }, - { - relayId: 0, - paraId: 2051, - rewards: { - tokens: null, - custom: [ - { - title: 'AJUN per DOT', - value: '10+', - }, - ], - info: null, - }, - }, - { - relayId: 0, - paraId: 2090, - rewards: { - tokens: null, - custom: [ - { - title: 'OAK per DOT', - value: '75.5+', - }, - ], - info: null, - }, - }, - { - relayId: 0, - paraId: 3340, - rewards: { - tokens: null, - custom: [ - { - title: 'VARCH per DOT', - value: '100 VARCH', - }, - { - title: 'Crowdloan Cap', - value: '150,000 DOT', - }, - { - title: 'Reward Pool', - value: '15m of 1b (1.5%)', - }, - ], - info: '20% of VARCH rewards will be available to claim immediately. The remaining 80% of VARCH rewards will vest/unlock linearly (every block) over a 96 week period.', - }, - }, - { - relayId: 2, - paraId: 2000, - contributeUrl: 'https://acala.network/karura/join-karura', - rewards: { - tokens: [ - { - symbol: 'KAR', - perKSM: '12', - }, - ], - custom: [], - info: null, - }, - }, - { - relayId: 2, - paraId: 2001, - contributeUrl: 'https://ksm.vtoken.io/?ref=polkadotjs', - rewards: { - tokens: [ - { - symbol: 'Instant BNC', - perKSM: '2', - }, - { - symbol: 'Success BNC', - perKSM: '20', - }, - ], - custom: [], - info: 'Instant BNS is distributed regardless of win. Success BNC is distributed if bid is won.', - }, - }, - { - relayId: 2, - paraId: 2004, - contributeUrl: 'https://crowdloan.phala.network/en/', - rewards: { - tokens: [ - { - symbol: 'PHA', - perKSM: '150', - }, - ], - custom: [], - info: 'If Phala wins the Slot Auction, rewards will be distributed according to the Phala payment schedule. If a slot is not won, you can unbond your KSM immediately after the Auctions end.', - }, - }, - { - relayId: 2, - paraId: 2008, - contributeUrl: - 'https://polkadot.js.org/apps/?rpc=wss%3A%2F%2Fkusama.api.onfinality.io%2Fpublic-ws#/parachains/crowdloan', - rewards: { - tokens: [ - { - symbol: 'AMAS', - perKSM: '1200', - }, - ], - custom: [ - { - title: 'Auction Total', - value: '160,000,000 tokens', - }, - ], - info: 'More information: crowdloan rewards.', - }, - }, - { - relayId: 2, - paraId: 2009, - contributeUrl: 'https://redkite.polkafoundry.com/#/join-polkasmith/', - rewards: { - tokens: [ - { - symbol: 'Reward', - perKSM: '350 PKS + 500 RedKite points', - }, - ], - custom: [ - { - title: 'Reward pool', - value: '10,500,000 PKS', - }, - { - title: 'Lock up period (win)', - value: '48 Weeks', - }, - { - title: 'Lock up period (lose)', - value: '6 Weeks', - }, - ], - info: 'After KSM contribution, 100% Red Kite point delivered immediately. After PolkaSmith wins, 35% of PKS delivered immediately and 65% PKS vested over 10 months', - }, - }, - { - relayId: 2, - paraId: 2011, - contributeUrl: '', - rewards: { - tokens: null, - custom: [ - { - title: 'Total rewards', - value: '5000 XOR', - }, - { - title: 'Used to buy PSWAP', - value: '2000 XOR', - }, - { - title: 'Used to buy VAL', - value: '2000 XOR', - }, - { - title: 'Converted to XSTUSD', - value: '500 XOR', - }, - { - title: '500 XOR', - value: 'Distributed proportionally', - }, - ], - info: 'Medium article with more info on rewards.', - }, - }, - { - relayId: 2, - paraId: 2012, - contributeUrl: '', - rewards: { - tokens: [ - { - symbol: 'Reward', - perKSM: '1 CRU + 1000 CSM', - }, - ], - custom: null, - info: 'Medium article with more info on rewards.', - }, - }, - { - relayId: 2, - paraId: 2013, - contributeUrl: 'https://chainx-org.medium.com/sherpax-crowdloan-tutorial-88086714ff1', - rewards: { - tokens: [ - { - symbol: 'KSX', - perKSM: '> 15', - }, - ], - custom: [ - { - title: '> 50 KSM contribution', - value: '20% Bonus', - }, - { - title: '> 100 KSM contribution', - value: '40% Bonus', - }, - { - title: '> 200 KSM contribution', - value: '60% Bonus', - }, - { - title: '> 300 KSM contribution', - value: '90% Bonus', - }, - { - title: '> 500 KSM contribution', - value: '120% Bonus', - }, - ], - info: 'KSX has an initial supply of 21 million, increased with 10% additional issuance every year. If you also refer a friend, both of you get a 5% referral bonus reward.', - }, - }, - { - relayId: 2, - paraId: 2015, - contributeUrl: 'https://crowdloan.integritee.network/', - rewards: { - tokens: [ - { - symbol: 'TEER', - perKSM: '> 10', - }, - ], - custom: [ - { - title: '10% of total token allocation', - value: '1,000,000 TEER', - }, - ], - info: '10% of the total Integritee token allocation will be fairly distributed to KSM holders who support us in the Kusama parachain auctions. The quantity of TEER each supporter receives will thereby depend on the amount of KSM they lock-in, relative to the total amount locked-in by all supporters.', - }, - }, - { - relayId: 2, - paraId: 2016, - contributeUrl: 'https://auction.clover.finance/#/', - rewards: { - tokens: [ - { - symbol: 'Reward', - perKSM: '150-210 SKU + 1sKSM LP token', - }, - ], - custom: null, - info: 'New Users get up to 20% More bonus SKU. Invite friends to get 5% More bonus SKU.', - }, - }, - { - relayId: 2, - paraId: 2018, - contributeUrl: 'https://www.subgame.org/#/contribute', - rewards: { - tokens: [ - { - symbol: 'GSGB', - perKSM: '28', - }, - ], - custom: [ - { - title: 'Maximum', - value: '34,000,000 GSGB', - }, - ], - info: 'Crowdloaned 10 KSM unlocked and returned after Slot Duration finished, otherwise immediatly on unsuccessful slot auction.\n\nGSGB runs on SubGame Gamma. It is a reward token for participating in SubGame Gamma crowd loan activities. It has the same value as the token running on the SubGame mainnet. It can be exchanged 1:1 with SGB through SubGame Bridge', - }, - }, - { - relayId: 2, - paraId: 2019, - contributeUrl: - 'https://polkadot.js.org/apps/?rpc=wss%3A%2F%2Fkusama.api.onfinality.io%2Fpublic-ws#/parachains/crowdloan', - rewards: { - tokens: [ - { - symbol: 'KPN', - perKSM: '80', - }, - ], - custom: null, - info: null, - }, - }, - { - relayId: 2, - paraId: 2023, - contributeUrl: 'https://moonbeam.foundation/moonriver-crowdloan/', - rewards: { - tokens: [ - { - symbol: 'MOVR', - perKSM: '14.5677', - }, - ], - custom: [ - { - title: 'Reward Pool', - value: '3,000,000 MOVR', - }, - { - title: 'Initial Distribution', - value: '900,000 MOVR', - }, - { - title: 'Vested Distribution', - value: '2,100,000 MOVR', - }, - { - title: 'Vesting Period', - value: '48 Weeks', - }, - ], - info: null, - }, - }, - { - relayId: 2, - paraId: 2024, - contributeUrl: 'https://genshiro.equilibrium.io/en/plo', - rewards: { - tokens: [ - { - symbol: 'GENS', - perKSM: '> 2000', - }, - ], - custom: [ - { - title: '> 50 KSM contribution', - value: '20% Bonus', - }, - { - title: 'Contribute before September 10', - value: '25% Bonus', - }, - ], - info: null, - }, - }, - { - relayId: 2, - paraId: 2048, - contributeUrl: 'https://robonomics.network/kusama-slot', - rewards: { - tokens: [ - { - symbol: 'XRT', - perKSM: '> 3.5', - }, - ], - custom: [ - { - title: 'First 35,000 KSM', - value: '5 XRT', - }, - { - title: 'Total Collection Limit', - value: '135,000 KSM', - }, - { - title: 'Distribution (1 Month Post Launch)', - value: '50%', - }, - ], - info: null, - }, - }, - { - relayId: 2, - paraId: 2080, - contributeUrl: 'https://loomx.io/', - rewards: { - tokens: [ - { - symbol: 'LOOM', - perKSM: '100', - }, - ], - custom: null, - info: null, - }, - }, - { - relayId: 2, - paraId: 2084, - contributeUrl: 'https://crowdloan.calamari.manta.network/', - rewards: { - tokens: [ - { - symbol: 'KMA', - perKSM: '10,000', - }, - ], - custom: [ - { - title: 'First 500 participants', - value: '10% Bonus', - }, - { - title: 'Participants 501-1,000', - value: '5% Bonus', - }, - ], - info: null, - }, - }, - { - relayId: 2, - paraId: 2085, - contributeUrl: 'https://docs.parallel.fi/parallel-heiko-crowdloan/how-to-contribute', - rewards: { - tokens: [ - { - symbol: 'HKO', - perKSM: '> 200', - }, - ], - custom: [ - { - title: 'Vesting Period', - value: '1 year', - }, - ], - info: null, - }, - }, - { - relayId: 2, - paraId: 2086, - contributeUrl: 'https://medium.com/kilt-protocol/kilts-crowdloan-how-to-participate-d0333cc952ef', - rewards: { - tokens: [ - { - symbol: 'KILT', - perKSM: '> 25', - }, - ], - custom: null, - info: null, - }, - }, - { - relayId: 2, - paraId: 2087, - contributeUrl: 'https://composablefi.medium.com/how-to-participate-in-the-picasso-crowdloan-c17272f6aa0e', - rewards: { - tokens: [ - { - symbol: 'PICA', - perKSM: '20,000', - }, - ], - custom: null, - info: null, - }, - }, - { - relayId: 2, - paraId: 2088, - contributeUrl: 'https://centrifuge.io/altair/crowdloan', - rewards: { - tokens: [ - { - symbol: 'AIR', - perKSM: '400', - }, - ], - custom: [ - { - title: 'First 250 participants', - value: '10% Bonus', - }, - ], - info: null, - }, - }, - { - relayId: 2, - paraId: 2090, - contributeUrl: 'https://loan.bsx.fi/', - rewards: { - tokens: [ - { - symbol: 'Reward', - perKSM: '> 67,500 BSK + % HDX', - }, - ], - custom: null, - info: null, - }, - }, - { - relayId: 2, - paraId: 2092, - contributeUrl: 'https://kintsugi.interlay.io/', - rewards: { - tokens: [ - { - symbol: 'KINT', - perKSM: '> 3.75', - }, - ], - custom: null, - info: null, - }, - }, - { - relayId: 2, - paraId: 2095, - contributeUrl: 'https://unique.network/quartz/crowdloan/', - rewards: { - tokens: [ - { - symbol: 'QTZ', - perKSM: '> 237', - }, - ], - custom: null, - info: null, - }, - }, - { - relayId: 2, - paraId: 2096, - contributeUrl: 'https://ksmcrowdloan.bit.country/crowdloan', - rewards: { - tokens: [ - { - symbol: 'NEER', - perKSM: '> 68', - }, - ], - custom: [ - { - title: 'First 1000 contributors', - value: '10% Bonus', - }, - { - title: 'Referrals', - value: '2.5% Bonus', - }, - ], - info: null, - }, - }, - { - relayId: 2, - paraId: 2100, - contributeUrl: 'https://app.subsocial.network/crowdloan', - rewards: { - tokens: null, - custom: [ - { - title: 'SUB per KSM', - value: '> 150 SUB', - }, - { - title: 'Crowdloan Cap', - value: '100,000.420 KSM', - }, - { - title: 'Total Supply', - value: '100,000,000 SUB', - }, - { - title: 'Crowdloan Allocation', - value: '16,500,000 SUB', - }, - { - title: 'Initial Unlock', - value: '20%', - }, - ], - info: null, - }, - }, - { - relayId: 2, - paraId: 2101, - contributeUrl: 'https://crowdloan.zeitgeist.pm/', - rewards: { - tokens: null, - custom: [ - { - title: 'ZTG per KSM', - value: '>80 ZTG', - }, - { - title: 'Genesis ZTG Supply', - value: '100,000,000 ZTG', - }, - { - title: 'Crowdloan Allocation', - value: '12,500,000 ZTG', - }, - ], - info: null, - }, - }, - { - relayId: 2, - paraId: 2102, - contributeUrl: '', - rewards: { - tokens: null, - custom: [ - { - title: 'PCHU per KSM', - value: '350+', - }, - ], - info: null, - }, - }, - { - relayId: 2, - paraId: 2105, - contributeUrl: 'https://crab.network/plo#crowdloan', - rewards: { - tokens: null, - custom: [ - { - title: 'CRAB Crowdloan Allocation', - value: '200,000,000 CRAB', - }, - { - title: 'CKTON Crowdloan Allocation', - value: '8,000 CKTON', - }, - ], - info: 'If successful, these allocations will be distributed to users according to the number of KSM they supported. After contributing to the Crab crowdloan, there is no need to wait for tokens to finish vesting or getting listed.', - }, - }, - { - relayId: 2, - paraId: 2106, - contributeUrl: 'https://kusama-crowdloan.litentry.com/', - rewards: { - tokens: [ - { - symbol: 'LIT', - perKSM: '30 LIT', - }, - ], - custom: null, - info: 'LIT rewards will be distributed linearly in each block. The distribution starts once the Litmus parachain runs on the Kusama relay chain and balance transfer is enabled. Distribution ends when the parachain slot expires (after 48 weeks).', - }, - }, - { - relayId: 2, - paraId: 2107, - contributeUrl: '', - rewards: { - tokens: [ - { - symbol: 'KICO', - perKSM: '20,000 KICO', - }, - ], - custom: null, - info: 'After the launch of the main network, 30% of KICO tokens will be released directly, and the remaining will be released linearly over 48 weeks.', - }, - }, - { - relayId: 2, - paraId: 2110, - contributeUrl: '', - rewards: { - tokens: null, - custom: [ - { - title: 'Crowdloan Allocation', - value: '140,000,000 MGX', - }, - { - title: 'Distributed Immediately', - value: '30,000,000 MGX', - }, - ], - info: 'After the launch of the main network, ~21% (30M) of MGX tokens will be released directly, and the remaining will be released linearly over 48 weeks.', - }, - }, - { - relayId: 2, - paraId: 2114, - contributeUrl: '', - rewards: { - tokens: null, - custom: [ - { - title: 'Crowdloan Allocation', - value: '160,000,000 TUR', - }, - { - title: 'Total Supply', - value: '1,000,000,000 TUR', - }, - ], - info: null, - }, - }, - { - relayId: 2, - paraId: 2115, - contributeUrl: '', - rewards: { - tokens: [ - { - symbol: 'DORA', - perKSM: '3 DORA', - }, - ], - custom: null, - info: null, - }, - }, - { - relayId: 2, - paraId: 2113, - contributeUrl: '', - rewards: { - tokens: [ - { - symbol: 'KAB', - perKSM: '> 124 KAB', - }, - ], - custom: [ - { - title: 'Crowdloan Allocation', - value: '3,100,000 KAB', - }, - ], - info: null, - }, - }, - { - relayId: 2, - paraId: 2116, - contributeUrl: '', - rewards: { - tokens: [ - { - symbol: 'DHX', - perKSM: '> 20 DHX', - }, - ], - custom: [ - { - title: 'Crowdloan Allocation', - value: '300,000 DHX', - }, - ], - info: null, - }, - }, - { - relayId: 2, - paraId: 2118, - contributeUrl: '', - rewards: { - tokens: [ - { - symbol: 'LT', - perKSM: '5000 LT', - }, - ], - custom: [ - { - title: 'Crowdloan Allocation', - value: '100,000,000 LT', - }, - ], - info: null, - }, - }, - { - relayId: 2, - paraId: 2119, - contributeUrl: '', - rewards: { - tokens: [ - { - symbol: 'BAJU', - perKSM: '> 110 BAJU (Dynamic)', - }, - ], - custom: [ - { - title: 'First 500 Contributions', - value: '> 5 KSM + 10% Bonus', - }, - { - title: 'First 1000 Contributions', - value: '> 1 KSM + 5% Bonus', - }, - { - title: 'NFT Rewards', - value: '2 Free AAA mints per 1 KSM', - }, - ], - info: null, - }, - }, - { - relayId: 2, - paraId: 2124, - contributeUrl: '', - rewards: { - tokens: [ - { - symbol: 'AMPE', - perKSM: '> 600 AMPE', - }, - ], - custom: [ - { - title: 'Total Supply', - value: '200,000,000 AMPE', - }, - ], - info: null, - }, - }, - { - relayId: 2, - paraId: 2125, - contributeUrl: '', - rewards: { - tokens: [ - { - symbol: 'TNKR', - perKSM: '87.75 TNKR', - }, - ], - custom: [ - { - title: 'Crowdloan Allocation', - value: '1,755,000 TNKR', - }, - ], - info: null, - }, - }, - { - relayId: 2, - paraId: 2123, - contributeUrl: '', - rewards: { - tokens: [ - { - symbol: '$FREN', - perKSM: '> 21,693', - }, - ], - custom: [ - { - title: 'Crowdloan Allocation', - value: '208,000,000 $FREN', - }, - ], - info: null, - }, - }, - { - relayId: 2, - paraId: 2241, - contributeUrl: - 'https://polkadot.js.org/apps/?rpc=wss%3A%2F%2Fkusama-rpc.polkadot.io&ref=parachains-info#/parachains/crowdloan', - rewards: { - tokens: [ - { - symbol: 'KREST', - perKSM: '800+', - }, - ], - custom: [ - { - title: 'Crowdloan Cap', - value: '15,000 KSM', - }, - ], - info: null, - }, - }, - { - relayId: 2, - paraId: 2256, - contributeUrl: 'https://crowdloan.mangata.finance/', - rewards: { - tokens: [ - { - symbol: 'MGX', - perKSM: '> 5,000', - }, - ], - custom: [ - { - title: 'Min. contribution', - value: '1 KSM', - }, - { - title: 'Crowdloan Cap', - value: '6,000 KSM', - }, - ], - info: null, - }, - }, - { - relayId: 2, - paraId: 2258, - contributeUrl: '', - rewards: { - tokens: [], - custom: [ - { - title: 'Total Supply', - value: '70,000,000 SDN', - }, - { - title: 'Circulating Supply', - value: '~59,000,000 SDN', - }, - ], - info: 'Shiden is already a Kusama Parachain, and are renewing their slot. Crowdloan rewards details are unknown.', - }, - }, -] diff --git a/apps/portal/src/routes/crowdloans/index.tsx b/apps/portal/src/routes/crowdloans/index.tsx deleted file mode 100644 index 2e5c30384..000000000 --- a/apps/portal/src/routes/crowdloans/index.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import { CrowdloanDetail } from './item' -import { CrowdloanIndex } from './main' -import { CrowdloanParticipated } from './participated' -import type { RouteObject } from 'react-router-dom' - -const routes = { - children: [ - { path: '', element: }, - { path: ':slug', element: }, - { path: 'participated', element: }, - ], -} satisfies RouteObject - -export default routes diff --git a/apps/portal/src/routes/crowdloans/item.tsx b/apps/portal/src/routes/crowdloans/item.tsx deleted file mode 100644 index 973247031..000000000 --- a/apps/portal/src/routes/crowdloans/item.tsx +++ /dev/null @@ -1,206 +0,0 @@ -import styled from '@emotion/styled' -import { Button } from '@talismn/ui/atoms/Button' -import { useTranslation } from 'react-i18next' -import { useParams } from 'react-router-dom' - -import { useModal } from '@/components/legacy/Modal' -import { Panel, PanelSection } from '@/components/legacy/Panel' -import { Poster } from '@/components/legacy/Poster' -import { CrowdloanBonus } from '@/components/legacy/widgets/CrowdloanBonus' -import { CrowdloanContribute } from '@/components/legacy/widgets/CrowdloanContribute' -import { CrowdloanCountdown } from '@/components/legacy/widgets/CrowdloanCountdown' -import { CrowdloanRaised } from '@/components/legacy/widgets/CrowdloanRaised' -import { CrowdloanRewards } from '@/components/legacy/widgets/CrowdloanRewards' -import { ParachainAsset } from '@/components/legacy/widgets/ParachainAsset' -import { ParachainLinks } from '@/components/legacy/widgets/ParachainLinks' -import { useCrowdloanContributions } from '@/libs/crowdloans' -import { useCrowdloanByParachainId, useParachainAssets, useParachainDetailsBySlug } from '@/libs/talisman' - -export const CrowdloanDetail = styled(({ className }: { className?: string }) => { - const { t } = useTranslation() - const { slug } = useParams<{ slug: string }>() - - const { parachainDetails } = useParachainDetailsBySlug(slug) - const { banner } = useParachainAssets(parachainDetails?.id) - - const { crowdloan: { id, uiStatus } = {} } = useCrowdloanByParachainId(parachainDetails?.id) - const { gqlContributions } = useCrowdloanContributions() - - const { openModal } = useModal() - - const parachainId = parachainDetails?.id - - return ( -
- -
-
- -
-

{parachainDetails?.name}

-

{parachainDetails?.subtitle}

-
-

{parachainDetails?.info}

- -
- -
-
- ) -})` - > .poster { - height: 21vw; - min-height: 20rem; - } - - > .content { - width: 100%; - margin: 0 auto; - - padding: 0 5vw; - display: flex; - justify-content: space-between; - position: relative; - - > article { - margin-top: -4rem; - padding-right: 4vw; - width: 61%; - color: var(--color-text); - - .crowdloan-logo { - width: 8rem; - height: 8rem; - } - - header { - h1 { - margin-top: 1.5rem; - font-family: 'SurtExpanded', sans-serif; - } - - h2 { - font-size: var(--font-size-xlarge); - opacity: 0.5; - line-height: 1.4em; - } - } - - .tags { - display: block; - > * { - display: inline-block; - margin-right: 0.5rem; - } - } - - .info { - margin: 3rem 0 4rem; - white-space: pre-line; - } - } - - > aside { - margin-top: 6.3rem; - width: 39%; - - .stat { - .value { - color: rgb(${({ theme }) => theme.primary}); - } - - & + .stat { - margin-top: 0.425em; - } - } - - .panel > h1 { - font-family: 'SurtExpanded', sans-serif; - } - - .panel + .panel { - margin-top: 1.4em; - } - - .button { - display: block; - width: 100%; - } - - .stat .title { - font-weight: var(--font-weight-bold); - } - - .crowdloan-raised .stat { - font-weight: var(--font-weight-bold); - font-size: var(--font-size-xlarge); - font-family: 'SurtExpanded', sans-serif; - } - - .crowdloan-countdown { - font-weight: var(--font-weight-bold); - font-size: var(--font-size-xlarge); - font-family: 'SurtExpanded', sans-serif; - color: var(--color-text); - } - - .crowdloan-bonus { - font-weight: var(--font-weight-bold); - font-size: var(--font-size-normal); - margin: 0 0 1em 0; - display: flex; - gap: 0.5rem; - //color: var(--color-text); - } - - .crowdloan-logo { - font-size: inherit; - } - } - - @media only screen and (max-width: 1000px) { - > article, - > aside { - width: 50%; - } - } - - @media only screen and (max-width: 800px) { - display: block; - > article, - > aside { - width: 100%; - } - } - } -` diff --git a/apps/portal/src/routes/crowdloans/main.tsx b/apps/portal/src/routes/crowdloans/main.tsx deleted file mode 100644 index 374d3b3c0..000000000 --- a/apps/portal/src/routes/crowdloans/main.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import styled from '@emotion/styled' - -import { CrowdloanIndex as CrowdloanIndexx } from '@/components/legacy/widgets/CrowdloanIndex' - -export const CrowdloanIndex = styled(({ className }: { className?: string }) => ( -
- -
-))` - margin: 0 auto; -` diff --git a/apps/portal/src/routes/crowdloans/participated.tsx b/apps/portal/src/routes/crowdloans/participated.tsx deleted file mode 100644 index 6dc6ec570..000000000 --- a/apps/portal/src/routes/crowdloans/participated.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import styled from '@emotion/styled' - -import { CrowdloanRootNav } from '@/components/legacy/widgets/CrowdloanRootNav' -import { WalletCrowdloans as ParticipatedCrowdloans } from '@/components/legacy/widgets/WalletCrowdloans' - -export const CrowdloanParticipated = styled(({ className }: { className?: string }) => ( -
- - -
-))` - margin: 0 auto; -` diff --git a/apps/portal/src/routes/explore.tsx b/apps/portal/src/routes/explore.tsx deleted file mode 100644 index 6b7f59db0..000000000 --- a/apps/portal/src/routes/explore.tsx +++ /dev/null @@ -1,174 +0,0 @@ -import styled from '@emotion/styled' -import { Text } from '@talismn/ui/atoms/Text' -import { HiddenDetails } from '@talismn/ui/molecules/HiddenDetails' -import { SearchBar } from '@talismn/ui/molecules/SearchBar' -import { useState } from 'react' -import { useDebounce } from 'react-use' - -import type { Dapp } from '@/components/legacy/widgets/useFetchDapps' -import { ExploreCard } from '@/components/legacy/widgets/ExploreCard' -import { ExploreCardLoading, ExploreTagLoading } from '@/components/legacy/widgets/ExploreLoading' -import { useFetchDapps } from '@/components/legacy/widgets/useFetchDapps' -import { device } from '@/util/breakpoints' - -const ExploreGrid = ({ className }: { className?: string }) => { - const [searchQuery, setSearchQuery] = useState('') - const [searchQueryDebounced, setSearchQueryDebounced] = useState('') - useDebounce(() => setSearchQueryDebounced(searchQuery), 250, [searchQuery]) - const { dapps, loading, tags } = useFetchDapps() - const [selectedTag, setSelectedTag] = useState('All') - - const filteredDapps = dapps?.filter( - (dapp: Dapp) => - dapp.name.toLowerCase().includes(searchQueryDebounced.toLowerCase()) && - (dapp.tags.includes(selectedTag) || selectedTag === 'All') - ) - - return ( -
- {loading ? ( - <> - - - - ) : !loading ? ( - // Create a 4 column grid - <> -
- -
- {tags.map(tag => ( -
setSelectedTag(tag)} - className={selectedTag === tag ? 'selected-tag' : ''} - > - {tag} -
- ))} -
-
- - {filteredDapps.length > 0 ? ( -
- {filteredDapps.map((dapp, index) => ( - setSelectedTag(tag)} /> - ))} -
- ) : ( - - {filteredDapps.length === 0 && dapps.length > 0 - ? 'Your search returned no results' - : 'No Dapps Found'} - - } - hidden={filteredDapps.length === 0} - > - - - )} - - ) : ( -

Error

- )} -
- ) -} - -const StyledExploreGrid = styled(ExploreGrid)` - .tags { - display: flex; - flex-direction: row; - flex-wrap: wrap; - width: 87vw; - height: 100%; - justify-content: flex-start; - - @media ${device.md} { - width: 100%; - } - - > div { - height: 26px; - font-size: 1.25rem; - margin: 0.5rem 0.5rem 0 0; - display: flex; - align-items: center; - justify-content: center; - padding: 0.5rem 1rem; - background: var(--color-activeBackground); - border-radius: 1rem; - cursor: pointer; - transition: 0.2s; - } - - .selected-tag { - background: var(--talisman-connect-button-foreground); - color: black; - } - - > div:hover { - background: var(--color-dim); - transition: 0.2s; - } - } - - .grid { - display: grid; - - grid-template-columns: repeat(3, 1fr); - - @media ${device.sm} { - grid-template-columns: repeat(3, 1fr); - width: 87vw; - } - - @media ${device.md} { - grid-template-columns: repeat(3, 1fr); - width: 100%; - } - - @media ${device.lg} { - grid-template-columns: repeat(12, 1fr); - } - - grid-gap: 2.5rem; - } -` - -const Explore = styled(({ className }: { className?: string }) => ( -
- -
-))` - color: var(--color-text); - width: 100%; -` - -export default Explore diff --git a/apps/portal/src/routes/history.tsx b/apps/portal/src/routes/history.tsx deleted file mode 100644 index c2efd15fc..000000000 --- a/apps/portal/src/routes/history.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { Text } from '@talismn/ui/atoms/Text' - -export default () => ( -
- Transaction history is deprecated due to an increase in infrastructure cost - - To view your transaction history, we recommend{' '} - - Subscan - {' '} - and{' '} - - Etherscan - - . - -
-) diff --git a/apps/portal/src/routes/layout.tsx b/apps/portal/src/routes/layout.tsx index 1795cb65b..3a8666f11 100644 --- a/apps/portal/src/routes/layout.tsx +++ b/apps/portal/src/routes/layout.tsx @@ -2,7 +2,6 @@ import { usePostHog } from 'posthog-js/react' import { Suspense, useEffect } from 'react' import { Outlet, useLocation } from 'react-router-dom' -import { ModalProvider } from '@/components/legacy/Modal' import { FullscreenLoader } from '@/components/molecules/FullscreenLoader' import { SiteFooter } from '@/components/widgets/SiteFooter' import { SiteNav } from '@/components/widgets/SiteNav' @@ -47,17 +46,14 @@ export const Layout = () => { }>
- {/* TODO: remove legacy imperative modals */} - - - - - - - - - - + + + + + + + +
diff --git a/apps/portal/src/routes/portfolio/collectibles.tsx b/apps/portal/src/routes/portfolio/collectibles.tsx index 8d3b1fe41..42ee92c0e 100644 --- a/apps/portal/src/routes/portfolio/collectibles.tsx +++ b/apps/portal/src/routes/portfolio/collectibles.tsx @@ -35,7 +35,7 @@ import { } from '@/domains/nfts/core' import { usePagination } from '@/hooks/usePagination' import { Maybe } from '@/util/monads' -import { shortenAddress } from '@/util/shortenAddress' +import { truncateAddress } from '@/util/truncateAddress' const COLLECTION_KEY = 'collectionKey' @@ -66,7 +66,7 @@ const AccountHeader = (props: { className?: string; account: Account; loading?: leadingContent={} headlineContent={ <> - {props.account.name ?? shortenAddress(props.account.address)}{' '} + {props.account.name ?? truncateAddress(props.account.address)}{' '} diff --git a/apps/portal/src/routes/portfolio/overview.tsx b/apps/portal/src/routes/portfolio/overview.tsx index 59e70329f..8cbbf487b 100644 --- a/apps/portal/src/routes/portfolio/overview.tsx +++ b/apps/portal/src/routes/portfolio/overview.tsx @@ -7,7 +7,6 @@ import { Link } from 'react-router-dom' import { useRecoilState } from 'recoil' import { useAssetsFiltered } from '@/components/legacy/widgets/useAssets' -import { WalletCrowdloans } from '@/components/legacy/widgets/WalletCrowdloans' import { SectionHeader } from '@/components/molecules/SectionHeader' import { Asset, AssetsList, AssetsListLocked } from '@/components/recipes/Asset' import { AnimatedFiatNumber } from '@/components/widgets/AnimatedFiatNumber' @@ -124,18 +123,6 @@ const AssetsOverview = () => (
) -// const HistoryOverview = () => ( -//
-// -// -//
-// -//
-//
-// ) - const Overview = () => (
(
- {/*
- - - -
*/}
(
-
- - - -
) diff --git a/apps/portal/src/util/breakpoints.ts b/apps/portal/src/util/breakpoints.ts deleted file mode 100644 index 78d20b61f..000000000 --- a/apps/portal/src/util/breakpoints.ts +++ /dev/null @@ -1,19 +0,0 @@ -const size = { - xs: '320px', - sm: '375px', - md: '425px', - lg: '768px', - xl: '1024px', - xxl: '1440px', - xxxl: '2560px', -} - -export const device = { - xs: `(min-width: ${size.xs})`, - sm: `(min-width: ${size.sm})`, - md: `(min-width: ${size.md})`, - lg: `(min-width: ${size.lg})`, - xl: `(min-width: ${size.xl})`, - xxl: `(min-width: ${size.xxl})`, - xxxl: `(min-width: ${size.xxxl})`, -} diff --git a/apps/portal/src/util/customRpcs.tsx b/apps/portal/src/util/customRpcs.tsx deleted file mode 100644 index 1588a17e7..000000000 --- a/apps/portal/src/util/customRpcs.tsx +++ /dev/null @@ -1,67 +0,0 @@ -const customRpcs: Record = { - '0': [], // ['wss://polkadot.api.onfinality.io/ws?apikey=e1b2f3ea-f003-42f5-adf6-d2e6aa3ecfe4'], // Polkadot Relay - '0-1000': [], // ['wss://statemine.api.onfinality.io/ws?apikey=e1b2f3ea-f003-42f5-adf6-d2e6aa3ecfe4'], // Statemint - '0-2000': [], // ['wss://acal.api.onfinality.io/ws?apikey=e1b2f3ea-f003-42f5-adf6-d2e6aa3ecfe4'], // Acala - // '0-2001': [], // Bifrost - // '0-2002': [], // ['wss://clover.api.onfinality.io/ws?apikey=e1b2f3ea-f003-42f5-adf6-d2e6aa3ecfe4'], // Clover - // '0-2003': [], // Darwinia - '0-2004': [], // ['wss://moonbeam.api.onfinality.io/ws?apikey=e1b2f3ea-f003-42f5-adf6-d2e6aa3ecfe4'], // Moonbeam - '0-2006': [], // ['wss://astar.api.onfinality.io/ws?apikey=e1b2f3ea-f003-42f5-adf6-d2e6aa3ecfe4'], // Astar - '0-2008': [], // Crust - '0-2011': [], // Equilibrium - '0-2012': [], // ['wss://parallel.api.onfinality.io/ws?apikey=e1b2f3ea-f003-42f5-adf6-d2e6aa3ecfe4'], // Parallel - // '0-2013': [], // Litentry - '0-2015': [], // Manta - '0-2017': [], // SubGame Gamma - // '0-2018': [], // SubDAO - // '0-2019': [], // Composable Finance - // '0-2021': [], // Efinity - // '0-2026': [], // Nodle - // '0-2027': [], // Coinversation - // '0-2028': [], // Ares Odyssey - // '0-2031': [], // Centrifuge - '0-2032': [], // Interlay - // '0-2034': [], // HydraDX - // '0-2035': [], // Phala Network - '2': [], // ['wss://kusama.api.onfinality.io/ws?apikey=e1b2f3ea-f003-42f5-adf6-d2e6aa3ecfe4'], // Kusama Relay - '2-1000': [], // ['wss://statemine.api.onfinality.io/ws?apikey=e1b2f3ea-f003-42f5-adf6-d2e6aa3ecfe4'], // Statemine - // '2-1001': [], // Encointer Network - '2-2000': [], // ['wss://karura.api.onfinality.io/ws?apikey=e1b2f3ea-f003-42f5-adf6-d2e6aa3ecfe4'], // Karura - '2-2001': [], // ['wss://bifrost-parachain.api.onfinality.io/ws?apikey=e1b2f3ea-f003-42f5-adf6-d2e6aa3ecfe4'], // Bifrost - '2-2004': [], // ['wss://khala.api.onfinality.io/ws?apikey=e1b2f3ea-f003-42f5-adf6-d2e6aa3ecfe4'], // Khala - // '2-2005': [], // KILT Spiritnet - // '2-2006': [], // Darwinia Crab Redirect - '2-2007': [], // ['wss://shiden.api.onfinality.io/ws?apikey=e1b2f3ea-f003-42f5-adf6-d2e6aa3ecfe4'], // Shiden - // '2-2008': [], // Mars - // '2-2009': [], // PolkaSmith by PolkaFoundry - // '2-2012': [], // Crust Shadow - // '2-2013': [], // SherpaX - // '2-2014': [], // Encointer Canary - // '2-2015': [], // Integritee Network - // '2-2016': [], // Sakura - // '2-2018': [], // SubGame Gamma - // '2-2019': [], // Kpron - // '2-2021': [], // Altair - '2-2023': [], // ['wss://moonriver.api.onfinality.io/ws?apikey=e1b2f3ea-f003-42f5-adf6-d2e6aa3ecfe4'], // Moonriver - // '2-2024': [], // Genshiro - // '2-2048': [], // Robonomics - // '2-2080': [], // Loom Network - // '2-2082': [], // Basilisk - '2-2084': [], // Calamari - // '2-2085': [], // Parallel Heiko - '2-2086': [], // KILT Spiritnet - // '2-2087': [], // Picasso - '2-2088': [], // Altair - // '2-2089': [], // Genshiro - '2-2090': [], // Basilisk - // '2-2092': [], // Kintsugi BTC - // '2-2094': [], // Unorthodox - // '2-2095': [], // QUARTZ by UNIQUE - // '2-2096': [], // Bit.Country Pioneer - // '2-2100': [], // Subsocial - // '2-2101': [], // Zeitgeist - // '2-2102': [], // Pichiu - // '2-2105': [], // Darwinia Crab -} - -export default customRpcs diff --git a/apps/portal/src/util/helpers.ts b/apps/portal/src/util/helpers.ts deleted file mode 100644 index d3a088c8a..000000000 --- a/apps/portal/src/util/helpers.ts +++ /dev/null @@ -1,52 +0,0 @@ -export const truncateAddress = (addr: string | null | undefined, start = 6, end = 4) => - addr ? `${addr.substring(0, start)}...${addr.substring(addr.length - end)}` : null - -const unitPrefixes = [ - { multiplier: 1e-24, symbol: 'y' }, - { multiplier: 1e-21, symbol: 'z' }, - { multiplier: 1e-18, symbol: 'a' }, - { multiplier: 1e-15, symbol: 'f' }, - { multiplier: 1e-12, symbol: 'p' }, - { multiplier: 1e-9, symbol: 'n' }, - { multiplier: 1e-6, symbol: 'μ' }, - { multiplier: 1e-3, symbol: 'm' }, - { multiplier: 1, symbol: '' }, - { multiplier: 1e3, symbol: 'k' }, - { multiplier: 1e6, symbol: 'M' }, - { multiplier: 1e9, symbol: 'G' }, - { multiplier: 1e12, symbol: 'T' }, - { multiplier: 1e15, symbol: 'P' }, - { multiplier: 1e18, symbol: 'E' }, - { multiplier: 1e21, symbol: 'Z' }, - { multiplier: 1e24, symbol: 'Y' }, -] -export const shortNumber = (num: number, decimals = 2) => { - const prefix = unitPrefixes - .slice() - .reverse() - .find(({ multiplier }) => multiplier <= num) - - if (!prefix) return num.toFixed(decimals) - - return (num / prefix.multiplier).toFixed(decimals).replace(/\.0+$|(\.[0-9]*[1-9])0+$/, '$1') + prefix.symbol -} - -type FormatProperties = { - locale?: string - currency?: string - maximumFractionDigits?: number -} - -export const formatCommas = (val: number, props?: FormatProperties) => - new Intl.NumberFormat(props?.locale ?? 'en-US', { - maximumFractionDigits: props?.maximumFractionDigits ?? 4, - }).format(val) - -export const isMobileBrowser = () => - // Method taken from https://developer.mozilla.org/en-US/docs/Web/HTTP/Browser_detection_using_the_user_agent#mobile_tablet_or_desktop - ( - navigator.userAgent || - navigator.vendor || - // @ts-expect-error - window.opera - ).includes('Mobi') diff --git a/apps/portal/src/util/shortenAddress.ts b/apps/portal/src/util/shortenAddress.ts deleted file mode 100644 index 8f56b2814..000000000 --- a/apps/portal/src/util/shortenAddress.ts +++ /dev/null @@ -1,2 +0,0 @@ -export const shortenAddress = (address: string, padding = 4) => - `${address.slice(0, padding)}…${address.slice(-padding)}` diff --git a/apps/portal/src/util/truncateAddress.ts b/apps/portal/src/util/truncateAddress.ts new file mode 100644 index 000000000..c168444cc --- /dev/null +++ b/apps/portal/src/util/truncateAddress.ts @@ -0,0 +1,2 @@ +export const truncateAddress = (address?: string | null, start = 4, end = 4) => + typeof address === 'string' ? `${address.slice(0, start)}…${address.slice(-end)}` : '' diff --git a/apps/portal/src/util/useEventListener.tsx b/apps/portal/src/util/useEventListener.tsx deleted file mode 100644 index 8338061d1..000000000 --- a/apps/portal/src/util/useEventListener.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import { useEffect } from 'react' - -function useEventListener( - eventType: KD, - listener: (this: Document, evt: DocumentEventMap[KD]) => void, - element?: Document | null -): void -function useEventListener( - eventType: KH, - listener: (this: HTMLElement, evt: HTMLElementEventMap[KH]) => void, - element?: HTMLElement | null -): void -function useEventListener( - eventType: KW, - listener: (this: Window, evt: WindowEventMap[KW]) => void, - element?: Window | null -): void -function useEventListener( - eventType: string, - listener: (evt: Event) => void, - element?: Document | HTMLElement | Window | null -): void - -function useEventListener< - KD extends keyof DocumentEventMap, - KH extends keyof HTMLElementEventMap, - KW extends keyof WindowEventMap ->( - eventType: KD | KH | KW | string, - listener: ( - this: typeof element, - evt: DocumentEventMap[KD] | HTMLElementEventMap[KH] | WindowEventMap[KW] | Event - ) => void, - element: Document | HTMLElement | Window | null = window -): void { - useEffect(() => { - if (!element) return - - element.addEventListener(eventType, listener) - return () => element.removeEventListener(eventType, listener) - }, [element, eventType, listener]) -} - -export default useEventListener diff --git a/apps/portal/src/util/useImageWithFallback.ts b/apps/portal/src/util/useImageWithFallback.ts deleted file mode 100644 index 8a4e13924..000000000 --- a/apps/portal/src/util/useImageWithFallback.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { useEffect, useState } from 'react' - -const useImageWithFallback = (original: string | undefined, fallback: string | undefined) => { - const [src, setSrc] = useState(original ?? fallback) - - useEffect(() => { - if (fallback === undefined) { - setSrc(original) - } - - if (original === undefined) { - setSrc(fallback) - return - } - - const image = new Image() - - image.onload = () => setSrc(original) - image.onerror = () => setSrc(fallback) - image.src = original - }, [fallback, original]) - - return src -} - -export default useImageWithFallback diff --git a/apps/portal/src/util/useKeyDown.tsx b/apps/portal/src/util/useKeyDown.tsx deleted file mode 100644 index 93f4d3d77..000000000 --- a/apps/portal/src/util/useKeyDown.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import { useCallback } from 'react' - -import useEventListener from './useEventListener' - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export default function useKeyDown(targetKey: string, callback: () => any) { - useEventListener( - 'keydown', - useCallback(({ key }) => key === targetKey && callback(), [targetKey, callback]) - ) -} diff --git a/apps/portal/src/util/useUniqueId.ts b/apps/portal/src/util/useUniqueId.ts deleted file mode 100644 index 207f4f67b..000000000 --- a/apps/portal/src/util/useUniqueId.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { useRef } from 'react' - -const uniqueIds: Record = {} - -export default function useUniqueId(): string { - const idRef = useRef(null) - if (idRef.current !== null) return idRef.current - - idRef.current = generateId() - while (uniqueIds[idRef.current] !== undefined) { - idRef.current = generateId() - } - uniqueIds[idRef.current] = true - - return idRef.current -} - -function generateId() { - return Math.trunc(Math.random() * Math.pow(10, 8)).toString(16) -} diff --git a/yarn.lock b/yarn.lock index 81212aa4e..40c362a2d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9239,7 +9239,6 @@ __metadata: react-use: "npm:^17.4.0" react-winbox: "npm:^1.5.0" recoil: "npm:^0.7.7" - safety-match: "npm:^0.4.4" scale-ts: "npm:^1.6.0" storybook: "npm:^7.6.5" tailwind-merge: "npm:^2.5.4" @@ -22354,13 +22353,6 @@ __metadata: languageName: node linkType: hard -"safety-match@npm:^0.4.4": - version: 0.4.4 - resolution: "safety-match@npm:0.4.4" - checksum: 10c0/3dcdeaa68b38a774607fddd37698d4d117ff15a9f9bbc230e2960f437491981dff0890e4338968ff6260b583c510ff401614ea7229333539502aebeb52312d6a - languageName: node - linkType: hard - "scale-ts@npm:^1.6.0": version: 1.6.0 resolution: "scale-ts@npm:1.6.0"