Skip to content

Commit

Permalink
feat: TAO staking (#1682)
Browse files Browse the repository at this point in the history
* feat: display stake button for TAO token

* feat: created bittensor validator hook, dinamically display pool info by chain id

* feat: useGetStakingInfo hook, and auxiliary hooks to fetch staking for a given asset

* feat: updated NoomPoolBondWizard, updated component types

* feat: hooks for bittensor hotkeys and validators

* feat: display bittensor validator name and unbound button

* feat: hooks for fetching nomPooldata, consolidated data in  useGetUnbondInfo

* feat: unbond TAO, totalStaked and unbondPayload hooks

* fix: poolname, unbond btn and loading sekeleton displayed when it should not

* fix: staking TAO with remark

* chore: added taostats api key to env and webpack

* feat: unbond TAO, display tao unbond info

* feat: useGetBittensorMinJoinBond

* refactor: conslidated data in useGetStakeInfo hook

* fix: max button not working before input,
fetch payload with 0n plancks and invalidate form if pancks is < 0n

* chore: tidy up useGetUnbondInfo

* feat: added hooks to fetch and calculated if can stake bittensor

* feat: display stake warning message and tooltip

* chore: renamed NomPoolBond folder and items to "Bond"

* chore: renamed NomPoolUnboond folder and items to "Unbond"

* chore: created hooks folder, organized bittensor and nomPools hooks

* fix: bittensor cooldown when switching accounts back and forth

* chore: added comment

* chore: renamed component name to be generic to bonds

* feat: added BondSelectDrawer for popup UI

* feat: added BittensorBondSelectDrawer

* feat: hooks to fetch and merged Bittensor delegators data

* feat: bondSelectDrawer, bond skeleton option

* feat: sort delegators

* feat: open modal with "select" option when asset is TAO

* refactor: delegate select drawer to form in modal

* fix: add 10 min staleTime for taostats queries

* feat: hooks for fetching staked balance per delegator

* fix: bondPoolName when doing anything but staking

* refactor: display delegator stake position on AssetDetails components

* fix: bondPooName should receive chainId as props

* feat: unstake by delegator data

* fix: stake cooldown when switching accounts

* feat: track unstake block number and use it for stake cooldown

* fix: bondOption token formartting, icon

* chore: added changeset to util package

* feat: fetch data from rpc instead of taostats, display TAO staked in all accounts

* refactor: fetch total staked from rpc when unbonding, remove unused hooks

* fix: delegators sorting race condition

* chore: minor RemoteConfigStore type updates

* chore: updated taostats api endpoint

* chore: pr review change requests, added horizontal scrolling for sorting actions

* chore: bond row label, bond form form grid spacing

* feat: added taostats proxy api

* feat: handle missing origin/auth headers

* refactor: display UI errors

* refactor: error handling

* refactor: apr error msg, duplicated var assignement, null query key error

* perf: memoized values and disabled queries when token is not tao or tao bal is 0

* chore: memoized variables, fixed transalation, removed eslint comment

* refactor: broke down BondPoolName into multiple child components

* refactor: broke down StakingUnbondingPeriod into multiple child components

* fix: only display error msg in BondOption if there is actually an error

* refactor: out with formatTokenDecials in planckToTokens

* refactor: use minJoinBond to get an accurate fake fee estimation

* refactor: moved onSuccess ot its opwn useEffect in TxProgressBase

* chore: removed mocked remote config

* chore: removed redundant translation, early bittensor delegator name button return

* chore: early bittensor delegator name button return, cath api error

* refactor: handle unbond success on useUnbondWizard, revert TxProgress changes

* refactor: create store and custom hooks for keeping track of bittensor unbond block numbers

* refactor: change bittensor staking flow,
default poolId to the currently staked delegator, allow user delegator selection

* feat: place recommented on top of delegators list

* feat: recommended delegator loading skeleton

* refactor: use app store to track usntaking tx block number

* revert: uncommented code

* style: changed color of non-blocking action messages

* fix: center tooltip info icon, wait for blocknumber in stake cooldown warning on first load

* refactor: remove redundand fallback for blockNumber

* chore: fix text copy

* refactor: updated BondOption design

* style: minor spacing and color adjustments

* chore: rename "direct staking" => "delegated staking"

* chore: add separator after recommended validator in bittensor validator selection

* chore: ran pnpm changeset

* fix: nom pool staking warning copy

* fix: canary build icon names

* chore: updated copy to "featured"

---------

Co-authored-by: Chid Gilovitz <[email protected]>
  • Loading branch information
UrbanWill and chidg authored Dec 4, 2024
1 parent 6b49bf8 commit 122f828
Show file tree
Hide file tree
Showing 90 changed files with 2,412 additions and 492 deletions.
5 changes: 5 additions & 0 deletions .changeset/flat-wombats-breathe.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@talismn/balances": patch
---

rename "direct staking" => "delegated staking"
5 changes: 5 additions & 0 deletions .changeset/loud-maps-pay.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@talismn/util": patch
---

added formatTokenDecimals util fn
5 changes: 4 additions & 1 deletion apps/extension/.env.sample
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
# TEST_MNEMONIC=blarg blarg blarg blarg blarg blarg blarg blarg blarg blarg blarg blarg

# for dev only, Coingecko api settings
# # with a paid api key, use url https://pro-api.coingecko.com
# # with a paid api key, use url https://pro-api.coingecko.com
# COINGECKO_API_URL=https://api.coingecko.com
# # with a paid api key, use header name x-cg-pro-api-key
# COINGECKO_API_KEY_NAME=x-cg-demo-api-key
Expand All @@ -25,3 +25,6 @@
# Talisman core dev team setup for tx analysis :
# BLOWFISH_API_KEY=<PASSWORD_FOR_DEVMODE>
# BLOWFISH_QA_API_KEY=<PASSWORD_FOR_QA_BUILDS> # optional : only used for canary, ci & qa builds

# TAOSTATS_BASE_PATH=https://taostats-api-proxy.talismn.workers.dev
# TAOSTATS_API_KEY=<YOUR_OWN_API_KEY>
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import { classNames } from "@talismn/util"
import React, { MouseEvent, TouchEvent, useEffect, useRef, useState } from "react"

type ScrollContainerDraggableHorizontalProps = {
children?: React.ReactNode
className?: string
}

export const ScrollContainerDraggableHorizontal = ({
children,
className,
}: ScrollContainerDraggableHorizontalProps) => {
const containerRef = useRef<HTMLDivElement>(null)

// State to track dragging
const [isDragging, setIsDragging] = useState<boolean>(false)
const [startPosition, setStartPosition] = useState<number>(0)
const [scrollLeft, setScrollLeft] = useState<number>(0)

// State to track if there's more content to the left or right
const [more, setMore] = useState<{ left: boolean; right: boolean }>({
left: false,
right: false,
})

useEffect(() => {
const scrollable = containerRef.current
if (!scrollable) return

const handleDetectScroll = () => {
setMore({
left: scrollable.scrollLeft > 0,
right: scrollable.scrollWidth - scrollable.scrollLeft > scrollable.clientWidth,
})
}

// Attach event listeners
scrollable.addEventListener("scroll", handleDetectScroll)
window.addEventListener("resize", handleDetectScroll)

// Initial detection
handleDetectScroll()

// Fix for initial load when scrollWidth might not be calculated yet
setTimeout(handleDetectScroll, 50)

// Cleanup
return () => {
scrollable.removeEventListener("scroll", handleDetectScroll)
window.removeEventListener("resize", handleDetectScroll)
}
}, [])

const handleDragStart = (e: MouseEvent<HTMLDivElement> | TouchEvent<HTMLDivElement>) => {
e.preventDefault()
setIsDragging(true)
containerRef.current?.classList.add("cursor-grabbing")
const pageX = "touches" in e ? e.touches[0].pageX : e.pageX
setStartPosition(pageX)
setScrollLeft(containerRef.current?.scrollLeft || 0)
}

const handleDragEnd = () => {
setIsDragging(false)
containerRef.current?.classList.remove("cursor-grabbing")
}

const handleDragMove = (e: MouseEvent<HTMLDivElement> | TouchEvent<HTMLDivElement>) => {
if (!isDragging) return
e.preventDefault()
const pageX = "touches" in e ? e.touches[0].pageX : e.pageX
const distance = pageX - startPosition
if (containerRef.current) {
containerRef.current.scrollLeft = scrollLeft - distance
}
}

return (
<div className="relative z-0 overflow-hidden">
{/* eslint-disable-next-line jsx-a11y/no-static-element-interactions */}
<div
ref={containerRef}
className={classNames(
"no-scrollbar flex cursor-grab select-none overflow-x-auto",
className,
)}
onMouseDown={handleDragStart}
onMouseUp={handleDragEnd}
onMouseLeave={handleDragEnd}
onMouseMove={handleDragMove}
onTouchStart={handleDragStart}
onTouchEnd={handleDragEnd}
onTouchMove={handleDragMove}
>
{children}
</div>
{/* Left gradient overlay */}
<div
className={`pointer-events-none absolute left-0 top-0 h-full w-12 bg-gradient-to-r from-black to-transparent transition-opacity duration-300 ${
more.left ? "opacity-100" : "opacity-0"
}`}
></div>
{/* Right gradient overlay */}
<div
className={`pointer-events-none absolute right-0 top-0 h-full w-12 bg-gradient-to-l from-black to-transparent transition-opacity duration-300 ${
more.right ? "opacity-100" : "opacity-0"
}`}
></div>
</div>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ import { BuyTokensModal } from "@ui/domains/Asset/Buy/BuyTokensModal"
import { CopyAddressModal } from "@ui/domains/CopyAddress"
import { GetStartedModals } from "@ui/domains/Portfolio/GetStarted/GetStartedModals"
import { MigratePasswordModal } from "@ui/domains/Settings/MigratePassword/MigratePasswordModal"
import { NomPoolBondModal } from "@ui/domains/Staking/NomPoolBond/NomPoolBondModal"
import { NomPoolUnbondModal } from "@ui/domains/Staking/NomPoolUnbond/NomPoolUnbondModal"
import { BondModal } from "@ui/domains/Staking/Bond/BondModal"
import { NomPoolWithdrawModal } from "@ui/domains/Staking/NomPoolWithdraw/NomPoolWithdrawModal"
import { UnbondModal } from "@ui/domains/Staking/Unbond/UnbondModal"
import { ExplorerNetworkPickerModal } from "@ui/domains/ViewOnExplorer"

import DashboardNotifications from "."
Expand Down Expand Up @@ -49,8 +49,8 @@ export const DashboardNotificationsAndModals = () => {
<ExplorerNetworkPickerModal />
<MigratePasswordModal />
<OnboardingToast />
<NomPoolBondModal />
<NomPoolUnbondModal />
<BondModal />
<UnbondModal />
<NomPoolWithdrawModal />
<GetStartedModals />
</Suspense>
Expand Down
8 changes: 4 additions & 4 deletions apps/extension/src/ui/apps/popup/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ import { AccountRemoveModal } from "@ui/domains/Account/AccountRemoveModal"
import { AccountRenameModal } from "@ui/domains/Account/AccountRenameModal"
import { CopyAddressModal } from "@ui/domains/CopyAddress"
import { DatabaseErrorAlert } from "@ui/domains/Settings/DatabaseErrorAlert"
import { NomPoolBondModal } from "@ui/domains/Staking/NomPoolBond/NomPoolBondModal"
import { NomPoolUnbondModal } from "@ui/domains/Staking/NomPoolUnbond/NomPoolUnbondModal"
import { BondModal } from "@ui/domains/Staking/Bond/BondModal"
import { NomPoolWithdrawModal } from "@ui/domains/Staking/NomPoolWithdraw/NomPoolWithdrawModal"
import { UnbondModal } from "@ui/domains/Staking/Unbond/UnbondModal"
import { ExplorerNetworkPickerModal } from "@ui/domains/ViewOnExplorer"
import { useLoginCheck } from "@ui/hooks/useLoginCheck"

Expand Down Expand Up @@ -91,8 +91,8 @@ const Popup = () => {
<ExplorerNetworkPickerModal />
<BackupWarningDrawer />
<LedgerPolkadotUpgradeAlertDrawer />
<NomPoolBondModal />
<NomPoolUnbondModal />
<BondModal />
<UnbondModal />
<NomPoolWithdrawModal />
</Suspense>
{/* Render outside of suspense or it will never show in case of migration error */}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ import { TokenLogo } from "@ui/domains/Asset/TokenLogo"
import Tokens from "@ui/domains/Asset/Tokens"
import { AssetBalanceCellValue } from "@ui/domains/Portfolio/AssetBalanceCellValue"
import { NoTokensMessage } from "@ui/domains/Portfolio/NoTokensMessage"
import { NomPoolBondButton } from "@ui/domains/Staking/NomPoolBond/NomPoolBondButton"
import { NomPoolUnbondButton } from "@ui/domains/Staking/NomPoolUnbond/NomPoolUnbondButton"
import { BondButton } from "@ui/domains/Staking/Bond/BondButton"
import { useNomPoolStakingStatus } from "@ui/domains/Staking/hooks/nomPools/useNomPoolStakingStatus"
import { NomPoolWithdrawButton } from "@ui/domains/Staking/NomPoolWithdraw/NomPoolWithdrawButton"
import { useNomPoolStakingStatus } from "@ui/domains/Staking/shared/useNomPoolStakingStatus"
import { UnbondButton } from "@ui/domains/Staking/Unbond/UnbondButton"
import { BalancesStatus } from "@ui/hooks/useBalancesStatus"
import { useSelectedCurrency } from "@ui/state"

Expand All @@ -35,11 +35,15 @@ const AssetState = ({
description,
render,
address,
isLoading,
locked,
}: {
title: string
description?: string
render: boolean
address?: Address
isLoading?: boolean
locked?: boolean
}) => {
if (!render) return null
return (
Expand All @@ -55,6 +59,9 @@ const AssetState = ({
</div>
)}
{/* show description below title when address is not set */}
{isLoading && !description && locked && (
<div className="bg-grey-700 rounded-xs h-[1.6rem] w-80 animate-pulse" />
)}
{description && !address && (
<div className="flex-shrink-0 truncate text-sm">{description}</div>
)}
Expand Down Expand Up @@ -123,7 +130,7 @@ const ChainTokenBalances = ({ chainId, balances }: AssetRowProps) => {
/>
</div>
<div className="flex items-center justify-end gap-2">
{tokenId && <NomPoolBondButton tokenId={tokenId} balances={balances} />}
{tokenId && <BondButton tokenId={tokenId} balances={balances} />}
<AssetBalanceCellValue
render
tokens={summary.availableTokens}
Expand Down Expand Up @@ -252,7 +259,14 @@ const ChainTokenBalancesDetailRow = ({
className={classNames("bg-grey-850 grid grid-cols-[40%_30%_30%]", isLastRow && "rounded-b")}
>
<div>
<AssetState title={row.title} description={row.description} render address={row.address} />
<AssetState
title={row.title}
description={row.description}
render
address={row.address}
isLoading={row.isLoading}
locked={row.locked}
/>
</div>
{!row.locked && <div></div>}
<div>
Expand All @@ -263,7 +277,9 @@ const ChainTokenBalancesDetailRow = ({
symbol={symbol}
locked={row.locked}
balancesStatus={status}
className={classNames(status.status === "fetching" && "animate-pulse transition-opacity")}
className={classNames(
(status.status === "fetching" || row.isLoading) && "animate-pulse transition-opacity",
)}
/>
</div>
{!!row.locked && row.meta && tokenId && (
Expand Down Expand Up @@ -305,12 +321,12 @@ const LockedExtra: FC<{
[accountStatus?.canWithdrawIn, rowMeta.unbonding],
)

if (!rowAddress || !accountStatus) return null
if (!rowAddress) return null

return (
<div className="flex h-[6.6rem] flex-col items-end justify-center gap-2 whitespace-nowrap p-8 text-right">
{rowMeta.unbonding ? (
accountStatus.canWithdraw ? (
accountStatus?.canWithdraw ? (
<NomPoolWithdrawButton tokenId={tokenId} address={rowAddress} variant="large" />
) : (
<>
Expand All @@ -327,8 +343,13 @@ const LockedExtra: FC<{
)}
</>
)
) : accountStatus.canUnstake ? (
<NomPoolUnbondButton tokenId={tokenId} address={rowAddress} variant="large" />
) : accountStatus?.canUnstake || tokenId === "bittensor-substrate-native" ? (
<UnbondButton
tokenId={tokenId}
address={rowAddress}
variant="large"
poolId={rowMeta.poolId}
/>
) : null}
</div>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,16 @@ import { Fiat } from "@ui/domains/Asset/Fiat"
import { TokenLogo } from "@ui/domains/Asset/TokenLogo"
import Tokens from "@ui/domains/Asset/Tokens"
import { useCopyAddressModal } from "@ui/domains/CopyAddress"
import { NomPoolBondButton } from "@ui/domains/Staking/NomPoolBond/NomPoolBondButton"
import { NomPoolUnbondButton } from "@ui/domains/Staking/NomPoolUnbond/NomPoolUnbondButton"
import { usePortfolioNavigation } from "@ui/domains/Portfolio/usePortfolioNavigation"
import { BondButton } from "@ui/domains/Staking/Bond/BondButton"
import { useNomPoolStakingStatus } from "@ui/domains/Staking/hooks/nomPools/useNomPoolStakingStatus"
import { NomPoolWithdrawButton } from "@ui/domains/Staking/NomPoolWithdraw/NomPoolWithdrawButton"
import { useNomPoolStakingStatus } from "@ui/domains/Staking/shared/useNomPoolStakingStatus"
import { UnbondButton } from "@ui/domains/Staking/Unbond/UnbondButton"
import { useAnalytics } from "@ui/hooks/useAnalytics"
import { BalancesStatus } from "@ui/hooks/useBalancesStatus"
import { useFeatureFlag, useSelectedCurrency } from "@ui/state"

import { StaleBalancesIcon } from "../StaleBalancesIcon"
import { usePortfolioNavigation } from "../usePortfolioNavigation"
import { CopyAddressButton } from "./CopyAddressIconButton"
import { PortfolioAccount } from "./PortfolioAccount"
import { SendFundsButton } from "./SendFundsIconButton"
Expand Down Expand Up @@ -76,7 +76,7 @@ const ChainTokenBalances = ({ chainId, balances }: AssetRowProps) => {
{tokenId && (
<div className="size-[3.8rem] shrink-0 empty:hidden">
<Suspense fallback={<SuspenseTracker name="StakeButton" />}>
<NomPoolBondButton tokenId={tokenId} balances={balances} />
<BondButton tokenId={tokenId} balances={balances} />
</Suspense>
</div>
)}
Expand Down Expand Up @@ -211,7 +211,7 @@ const ChainTokenBalancesDetailRow = ({
tokenId={tokenId}
address={row.address}
rowMeta={row.meta}
isLoading={status.status === "fetching"}
isLoading={status.status === "fetching" || !!row.isLoading}
/>
)}
</div>
Expand All @@ -220,6 +220,9 @@ const ChainTokenBalancesDetailRow = ({
<PortfolioAccount address={row.address} />
</div>
)}
{row.isLoading && !row.description && row.locked && (
<div className="bg-grey-700 rounded-xs h-[1.6rem] max-w-48 animate-pulse" />
)}
{!row.address && row.description && (
<div className="overflow-hidden text-ellipsis whitespace-nowrap text-xs">
{row.description}
Expand Down Expand Up @@ -279,12 +282,12 @@ const LockedExtra: FC<{
[accountStatus?.canWithdrawIn, rowMeta.unbonding],
)

if (!rowAddress || !accountStatus) return null
if (!rowAddress) return null

return (
<>
{rowMeta.unbonding ? (
accountStatus.canWithdraw ? (
accountStatus?.canWithdraw ? (
<NomPoolWithdrawButton tokenId={tokenId} address={rowAddress} variant="small" />
) : (
<Tooltip>
Expand All @@ -305,8 +308,13 @@ const LockedExtra: FC<{
</Tooltip>
)
) : //eslint-disable-next-line @typescript-eslint/no-explicit-any
accountStatus.canUnstake ? (
<NomPoolUnbondButton tokenId={tokenId} address={rowAddress} variant="small" />
accountStatus?.canUnstake || tokenId === "bittensor-substrate-native" ? (
<UnbondButton
tokenId={tokenId}
address={rowAddress}
variant="small"
poolId={rowMeta.poolId}
/>
) : null}
</>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ import {
import urlJoin from "url-join"

import { SuspenseTracker } from "@talisman/components/SuspenseTracker"
import { useNomPoolBondModal } from "@ui/domains/Staking/NomPoolBond/useNomPoolBondModal"
import { useNomPoolStakingStatus } from "@ui/domains/Staking/shared/useNomPoolStakingStatus"
import { useBondModal } from "@ui/domains/Staking/Bond/useBondModal"
import { useNomPoolStakingStatus } from "@ui/domains/Staking/hooks/nomPools/useNomPoolStakingStatus"
import { useViewOnExplorer } from "@ui/domains/ViewOnExplorer"
import { useAnalytics } from "@ui/hooks/useAnalytics"
import { useToken } from "@ui/state"
Expand Down Expand Up @@ -54,7 +54,7 @@ const StakeMenuItem: FC<{ tokenId: string }> = ({ tokenId }) => {
const { t } = useTranslation()
const { genericEvent } = useAnalytics()

const { open } = useNomPoolBondModal()
const { open } = useBondModal()
const { data: stakingStatus } = useNomPoolStakingStatus(tokenId)

const openArgs = useMemo<Parameters<typeof open>[0] | undefined>(() => {
Expand Down
Loading

0 comments on commit 122f828

Please sign in to comment.