Skip to content

Commit

Permalink
Feat: Manta staking (#1151)
Browse files Browse the repository at this point in the history
* feat: added Manta & vManta to slpx config

* feat: added mantaSlpxPair to StakeProviders

* chore: added manta chain to wagmiConfig

* chore: updated copy for dynamic slpx

* chore: revert previous commit

* feat: added manta pacific slpx contract abi

* feat: added allowance check and approval fn for Manta useMintForm hook

* feat: implemented Manta approval on staking providers

* feat: mint Manta

* feat: update increase stake to support Manta approval and stake

* feat: redeem Manta

* fix: remove Mint event from abi to please typescript overlords

* chore: updated bifrost api

* chore: updated Manta and vManta tokenId

* chore: moved slpx stakes to top of staking list

* refactor: useSetCustomTokens, separated parseCustomEvmErc20Tokens as standalone fn

* feat: added customTokensConfig, support Manta and vManta tokens

* chore: update vManta token logo

* fix: staked % progress bar not working as expected

* refactor: removed unused import

* fix: chain name overflow

* fix: manta evm wallet connection (#1157)

---------

Co-authored-by: Chris Ling <[email protected]>
  • Loading branch information
UrbanWill and chrisling-dev authored Jul 26, 2024
1 parent 4b59d3a commit 30f3545
Show file tree
Hide file tree
Showing 20 changed files with 620 additions and 76 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export type AddStakeFormProps = {
isError?: boolean
inputSupportingText?: string
onConfirm: () => unknown
approvalNeeded?: boolean
}

export type AddStakeDialogProps = AddStakeFormProps & {
Expand Down Expand Up @@ -65,7 +66,7 @@ const AddStakeForm = (props: AddStakeFormProps) => (
loading={props.confirmState === 'pending'}
css={{ width: '100%', marginTop: '4.6rem' }}
>
Stake
{props.approvalNeeded ? 'Approve' : 'Stake'}
</Button>
</div>
)
Expand Down
13 changes: 9 additions & 4 deletions apps/portal/src/components/widgets/WalletConnectionSideSheet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@ import {
useSubstrateWalletConnect,
} from '../../domains/extension'
import AddReadOnlyAccountDialog from './AddReadOnlyAccountDialog'
import { writeableEvmAccountsState } from '@/domains/accounts'
import { ClassNames, useTheme } from '@emotion/react'
import { useSignetSdk } from '@talismn/signet-apps-sdk'
import { Chip, Hr, ListItem, SIDE_SHEET_WIDE_BREAK_POINT_SELECTOR, SideSheet, Surface, Text } from '@talismn/ui'
import { Chip, Hr, ListItem, SIDE_SHEET_WIDE_BREAK_POINT_SELECTOR, SideSheet, Surface, Text, toast } from '@talismn/ui'
import { Ethereum, Eye, Polkadot, Wallet } from '@talismn/web-icons'
import { Suspense, useState, type ButtonHTMLAttributes, type DetailedHTMLProps } from 'react'
import { atom, useRecoilState } from 'recoil'
import { atom, useRecoilState, useRecoilValue } from 'recoil'
import { useAccount as useEvmAccount, useDisconnect as useDisconnectEvm } from 'wagmi'

const talismanInstalled = 'talismanEth' in globalThis
Expand Down Expand Up @@ -160,6 +161,7 @@ const EvmWalletConnections = () => {
const { connector } = useEvmAccount()
const { connectAsync } = useConnectEvm()
const { disconnectAsync } = useDisconnectEvm()
const writeableEvmAccounts = useRecoilValue(writeableEvmAccountsState)

return (
<section>
Expand All @@ -174,10 +176,13 @@ const EvmWalletConnections = () => {
key={x.uid}
name={x.name}
iconUrl={x.icon}
connected={x === connector}
connected={x.id === connector?.id && writeableEvmAccounts.length > 0}
onConnectRequest={async () => {
await disconnectAsync()
await connectAsync({ connector: x })
const res = await connectAsync({ connector: x })
if (res.accounts.length === 0) {
toast.error('Please enable ethereum account in your wallet.')
}
}}
onDisconnectRequest={async () => await disconnectAsync()}
/>
Expand Down
6 changes: 3 additions & 3 deletions apps/portal/src/components/widgets/staking/StakeProviders.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ import SubtensorStakeProviders from './subtensor/StakeProviders'
const StakeProviders = () => {
return (
<StakeProviderList>
<ErrorBoundary orientation="horizontal">
<SlpxStakeProviders />
</ErrorBoundary>
<ErrorBoundary orientation="horizontal">
<NominationPoolsStakeProviders />
</ErrorBoundary>
Expand All @@ -21,9 +24,6 @@ const StakeProviders = () => {
<ErrorBoundary orientation="horizontal">
<LidoStakeProviders />
</ErrorBoundary>
<ErrorBoundary orientation="horizontal">
<SlpxStakeProviders />
</ErrorBoundary>
</StakeProviderList>
)
}
Expand Down
11 changes: 5 additions & 6 deletions apps/portal/src/components/widgets/staking/Stakes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,11 @@ const Stakes = (props: { hideHeader?: boolean }) => {
}}
>
{shouldRenderLoadingSkeleton && <StakePosition.Skeleton className={skellyClassName} css={{ order: 1 }} />}

<ErrorBoundary orientation="horizontal">
<SuspenseSkeleton>
<SlpxStakes setShouldRenderLoadingSkeleton={setShouldRenderLoadingSkeleton} />
</SuspenseSkeleton>
</ErrorBoundary>
{chains.map((chain, index) => {
return (
<Fragment key={index}>
Expand Down Expand Up @@ -110,11 +114,6 @@ const Stakes = (props: { hideHeader?: boolean }) => {
<DappStakes setShouldRenderLoadingSkeleton={setShouldRenderLoadingSkeleton} />
</SuspenseSkeleton>
</ErrorBoundary>
<ErrorBoundary orientation="horizontal">
<SuspenseSkeleton>
<SlpxStakes setShouldRenderLoadingSkeleton={setShouldRenderLoadingSkeleton} />
</SuspenseSkeleton>
</ErrorBoundary>
<ErrorBoundary orientation="horizontal">
<SuspenseSkeleton>
<LidoStakes setShouldRenderLoadingSkeleton={setShouldRenderLoadingSkeleton} />
Expand Down
18 changes: 16 additions & 2 deletions apps/portal/src/components/widgets/staking/slpx/AddStakeDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ const AddStakeDialog = (props: AddStakeDialogProps) => {
setAmount,
newDestTokenAmount: newAmount,
available,
approvalNeeded,
approve,
approveTransaction,
mint,
rate,
ready,
Expand All @@ -31,7 +34,14 @@ const AddStakeDialog = (props: AddStakeDialogProps) => {

return (
<SlpxAddStakeDialog
confirmState={!ready ? 'disabled' : mint.isPending ? 'pending' : undefined}
confirmState={
!ready
? 'disabled'
: mint.isPending || approve.isPending || approveTransaction.isLoading
? 'pending'
: undefined
}
approvalNeeded={approvalNeeded}
open
onDismiss={props.onRequestDismiss}
amount={amount}
Expand All @@ -45,7 +55,11 @@ const AddStakeDialog = (props: AddStakeDialogProps) => {
rate => `1 ${props.slpxPair.nativeToken.symbol} = ${rate.toLocaleString()} ${props.slpxPair.vToken.symbol}`
)}
onConfirm={async () => {
await mint.writeContractAsync()
if (approvalNeeded) {
await approve.writeContractAsync()
} else {
await mint.writeContractAsync()
}
}}
onRequestMaxAmount={() => {
if (available !== undefined) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import RedactableBalance from '../../RedactableBalance'
import ErrorBoundaryFallback from '../ErrorBoundaryFallback'
import Apr from './Apr'
import UnlockDuration from './UnlockDuration'
import { githubChainLogoUrl } from '@talismn/chaindata-provider'
import { Decimal } from '@talismn/math'
import { useSuspenseQuery } from '@tanstack/react-query'
import BigNumber from 'bignumber.js'
Expand Down Expand Up @@ -82,8 +81,7 @@ const StakeProviders = () => {
return (
<>
{slpxPairs.map((slpxPair, index) => {
const logo = githubChainLogoUrl('moonbeam')
const { symbol } = slpxPair.nativeToken
const { symbol, logo } = slpxPair.nativeToken
const provider = 'Bifrost SLPx'
return (
<ChainProvider
Expand Down
45 changes: 36 additions & 9 deletions apps/portal/src/components/widgets/staking/slpx/StakeSideSheet.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { evmSignableAccountsState } from '../../../../domains/accounts'
import { evmSignableAccountsState, writeableEvmAccountsState } from '../../../../domains/accounts'
import { ChainProvider } from '../../../../domains/chains'
import { slpxPairsState, useMintForm, type SlpxPair } from '../../../../domains/staking/slpx'
import { Maybe } from '../../../../util/monads'
import { SlpxAddStakeForm } from '../../../recipes/AddStakeDialog'
import { useAccountSelector } from '../../AccountSelector'
import { walletConnectionSideSheetOpenState } from '../../WalletConnectionSideSheet'
import Apr from './Apr'
import UnlockDuration from './UnlockDuration'
import {
Button,
CircularProgressIndicator,
InfoCard,
SIDE_SHEET_WIDE_BREAK_POINT_SELECTOR,
Expand All @@ -17,7 +19,7 @@ import {
import { Zap } from '@talismn/web-icons'
import { Suspense, useMemo } from 'react'
import { useSearchParams } from 'react-router-dom'
import { useRecoilValue } from 'recoil'
import { useRecoilValue, useSetRecoilState } from 'recoil'

type AddStakeSideSheetProps = {
slpxPair: SlpxPair
Expand All @@ -26,12 +28,17 @@ type AddStakeSideSheetProps = {

const AddStakeSideSheet = (props: AddStakeSideSheetProps) => {
const [[account], accountSelector] = useAccountSelector(useRecoilValue(evmSignableAccountsState), 0)
const setWalletConnectionSideSheetOpen = useSetRecoilState(walletConnectionSideSheetOpenState)
const writeableEvmAccounts = useRecoilValue(writeableEvmAccountsState)

const {
input: { amount, localizedFiatAmount },
setAmount,
newDestTokenAmount: newAmount,
available,
approvalNeeded,
approve,
approveTransaction,
mint,
rate,
ready,
Expand Down Expand Up @@ -69,8 +76,23 @@ const AddStakeSideSheet = (props: AddStakeSideSheetProps) => {
</div>
<Surface css={{ padding: '1.6rem', borderRadius: '1.6rem' }}>
<SlpxAddStakeForm
confirmState={!ready ? 'disabled' : mint.isPending ? 'pending' : undefined}
accountSelector={accountSelector}
confirmState={
!ready || +amount === 0
? 'disabled'
: mint.isPending || approve.isPending || approveTransaction.isLoading
? 'pending'
: undefined
}
approvalNeeded={approvalNeeded}
accountSelector={
writeableEvmAccounts.length > 0 ? (
accountSelector
) : (
<Button className="!w-full !rounded-[12px]" onClick={() => setWalletConnectionSideSheetOpen(true)}>
Connect Ethereum Wallet
</Button>
)
}
amount={amount}
fiatAmount={localizedFiatAmount ?? '...'}
newAmount={newAmount?.toLocaleString() ?? '...'}
Expand All @@ -82,7 +104,11 @@ const AddStakeSideSheet = (props: AddStakeSideSheetProps) => {
rate => `1 ${props.slpxPair.nativeToken.symbol} = ${rate.toLocaleString()} ${props.slpxPair.vToken.symbol}`
)}
onConfirm={async () => {
await mint.writeContractAsync()
if (approvalNeeded) {
await approve.writeContractAsync()
} else {
await mint.writeContractAsync()
}
}}
onRequestMaxAmount={() => {
if (available !== undefined) {
Expand All @@ -94,16 +120,17 @@ const AddStakeSideSheet = (props: AddStakeSideSheetProps) => {
/>
</Surface>
<Text.Body as="p" css={{ marginTop: '4.8rem' }}>
Talisman has integrated the liquid staking protocol by Bifrost, which allows users to easily stake GLMR, without
the need for complex staking processes. After staking, users receive vGLMR (voucher GLMR), a liquid staking
token of GLMR, which has fully underlying GLMR reserve and is directly yield bearing from GLMR rewards.{' '}
{`Talisman has integrated the liquid staking protocol by Bifrost, which allows users to easily stake ${props.slpxPair.nativeToken.symbol}, without
the need for complex staking processes. After staking, users receive ${props.slpxPair.vToken.symbol} (voucher ${props.slpxPair.nativeToken.symbol}), a liquid staking
token of ${props.slpxPair.nativeToken.symbol}, which has fully underlying ${props.slpxPair.nativeToken.symbol} reserve and is directly yield bearing from ${props.slpxPair.nativeToken.symbol} rewards.
`}
<Text.Noop.A
target="blank"
href="https://bifrost.finance/news/bifrost-announces-the-official-launch-of-the-first-parachain-derivatives-v-glmr-and-v-movr"
>
Learn more
</Text.Noop.A>
.
</Text.Body>
</SideSheet>
)
Expand Down
2 changes: 1 addition & 1 deletion apps/portal/src/domains/accounts/recoils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ export const writeableEvmAccountsState = selector({

export const evmSignableAccountsState = selector({
key: 'EvmSignableAccounts',
get: ({ get }) => get(writeableAccountsState).filter(x => x.type === 'ethereum' && x.canSignEvm),
get: ({ get }) => get(writeableAccountsState).filter(x => x.type === 'ethereum'),
})

export const substrateAccountsState = selector({
Expand Down
6 changes: 1 addition & 5 deletions apps/portal/src/domains/balances/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,14 +73,10 @@ export const writeableBalancesState = selector({
export const BalancesWatcher = () => {
const accounts = useRecoilValue(accountsState)
const addresses = useMemo(() => accounts.map(x => x.address), [accounts])
const currency = useRecoilValue(selectedCurrencyState)
useSetBalancesAddresses(addresses)

const unfilteredBalances = _useBalances()
const balances = useMemo(
() => unfilteredBalances.filterNonZeroFiat('total', currency).filterMirrorTokens(),
[currency, unfilteredBalances]
)
const balances = useMemo(() => unfilteredBalances, [unfilteredBalances])

// eslint-disable-next-line react-hooks/exhaustive-deps
useEffect(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { useChaindataProvider } from '@talismn/balances-react'
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 { useEffect } from 'react'
import { unionBy } from 'lodash'
import { useEffect, useState } from 'react'

const windowInject = globalThis as typeof globalThis & {
talismanSub?: {
Expand All @@ -11,19 +14,27 @@ const windowInject = globalThis as typeof globalThis & {
}

export const TalismanExtensionSynchronizer = () => {
const [walletTokens, setWalletTokens] = useState<Token[]>([])
const chaindataProvider = useChaindataProvider()

const evmNetworks = useEvmNetworks()

useEffect(() => {
const sub = windowInject.talismanSub

const unsubs = [
sub?.subscribeCustomSubstrateChains?.(async custom => await chaindataProvider.setCustomChains(custom)),
sub?.subscribeCustomEvmNetworks?.(async custom => await chaindataProvider.setCustomEvmNetworks(custom)),
sub?.subscribeCustomTokens?.(async custom => await chaindataProvider.setCustomTokens(custom)),
sub?.subscribeCustomTokens?.(custom => setWalletTokens(custom)),
]

return () => unsubs.forEach(unsub => unsub?.())
}, [chaindataProvider])

useEffect(() => {
const customTokens = parseCustomEvmErc20Tokens({ customTokensConfig: customTokensConfig, evmNetworks })
chaindataProvider.setCustomTokens(unionBy(customTokens, walletTokens, 'id'))
}, [walletTokens, evmNetworks, chaindataProvider])

return null
}
20 changes: 20 additions & 0 deletions apps/portal/src/domains/extension/customTokensConfig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { CustomTokensConfig } from '../../hooks/useSetCustomTokens'

export const customTokensConfig: CustomTokensConfig = [
{
coingeckoId: 'manta-network',
contractAddress: '0x95CeF13441Be50d20cA4558CC0a27B601aC544E5',
decimals: 18,
evmChainId: '169',
logo: 'https://raw.githubusercontent.com/TalismanSociety/chaindata/main/assets/chains/manta.svg',
symbol: 'MANTA',
},
{
coingeckoId: 'bifrost-voucher-manta',
contractAddress: '0x7746ef546d562b443AE4B4145541a3b1a3D75717',
decimals: 18,
evmChainId: '169',
logo: 'https://raw.githubusercontent.com/TalismanSociety/chaindata/main/assets/tokens/coingecko/bifrost-voucher-manta.webp',
symbol: 'vMANTA',
},
]
12 changes: 9 additions & 3 deletions apps/portal/src/domains/extension/wagmi.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { PropsWithChildren } from 'react'
import type { EIP1193Provider } from 'viem'
import { WagmiProvider, createConfig, http } from 'wagmi'
import type {} from 'wagmi/'
import { arbitrum, mainnet, moonbeam, moonriver } from 'wagmi/chains'
import { arbitrum, mainnet, moonbeam, moonriver, manta } from 'wagmi/chains'
import { injected } from 'wagmi/connectors'

declare global {
Expand All @@ -14,9 +14,15 @@ declare global {
}

export const wagmiConfig = createConfig({
chains: [mainnet, moonbeam, moonriver, arbitrum],
chains: [mainnet, moonbeam, moonriver, arbitrum, manta],
connectors: [injected()],
transports: { [mainnet.id]: http(), [arbitrum.id]: http(), [moonbeam.id]: http(), [moonriver.id]: http() },
transports: {
[mainnet.id]: http(),
[arbitrum.id]: http(),
[moonbeam.id]: http(),
[moonriver.id]: http(),
[manta.id]: http(),
},
})

const migrate = () => {
Expand Down
Loading

0 comments on commit 30f3545

Please sign in to comment.