diff --git a/public/images/chain/crab.png b/public/images/chain/crab.png new file mode 100644 index 0000000..c0de690 Binary files /dev/null and b/public/images/chain/crab.png differ diff --git a/public/images/chain/darwinia.png b/public/images/chain/darwinia.png new file mode 100644 index 0000000..874eb29 Binary files /dev/null and b/public/images/chain/darwinia.png differ diff --git a/public/manifest.json b/public/manifest.json new file mode 100644 index 0000000..0529cd8 --- /dev/null +++ b/public/manifest.json @@ -0,0 +1,5 @@ +{ + "name": "Collactor Staking - Darwinia", + "description": "Collactor staking of Darwinia and Crab network", + "icons": [{ "src": "icon.svg", "sizes": "any" }] +} diff --git a/src/app/globals.css b/src/app/globals.css index 8a4b86b..a07478f 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -2,6 +2,17 @@ @tailwind components; @tailwind utilities; +@layer base { + ::-webkit-scrollbar { + width: 6px; + } + ::-webkit-scrollbar-thumb { + border-radius: 10px; + box-shadow: inset 0 0 5x rgba(0, 0, 0, 0.2); + background: hsla(0, 0%, 100%, 0.4); + } +} + .app-header { height: 70px; position: fixed; diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 2b21707..39dbce9 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -3,13 +3,13 @@ import "./globals.css"; import { JetBrains_Mono } from "next/font/google"; import Footer from "@/components/footer"; import Header from "@/components/header"; -import WrongChainAlert from "@/components/wrong-chain-alert"; const fontJetBrainsMono = JetBrains_Mono({ subsets: ["latin", "latin-ext"] }); export const metadata = { - title: "Darwinia Staking", - description: "Darwinia and Crab network staking app", + title: "Collactor Staking - Darwinia", + description: "Collactor staking of Darwinia and Crab network", + manifest: "/manifest.json", }; export default function RootLayout({ children }: { children: React.ReactNode }) { @@ -25,8 +25,6 @@ export default function RootLayout({ children }: { children: React.ReactNode }) <Footer className="app-footer" /> </StakingProvider> </ApiProvider> - - <WrongChainAlert /> </RainbowProvider> </AppProvider> </body> diff --git a/src/components/balance-input.tsx b/src/components/balance-input.tsx index 9e808f0..9f8be37 100644 --- a/src/components/balance-input.tsx +++ b/src/components/balance-input.tsx @@ -1,22 +1,18 @@ -import { formatBlanace, prettyNumber } from "@/utils"; +import { formatBlanace } from "@/utils"; import Image from "next/image"; import InputLabel from "./input-label"; import { parseUnits } from "viem"; import { useEffect, useRef, useState } from "react"; -type PowerChanges = "more" | "less"; - export default function BalanceInput({ isReset, balance, symbol, decimals, logoPath, - power, label, boldLabel, className, - powerChanges = "more", onChange = () => undefined, }: { isReset?: boolean; @@ -24,8 +20,6 @@ export default function BalanceInput({ symbol: string; decimals: number; logoPath?: string; - power?: bigint; - powerChanges?: PowerChanges; label?: string; boldLabel?: boolean; className?: string; @@ -49,7 +43,7 @@ export default function BalanceInput({ }`} > <input - placeholder={`Balance: ${formatBlanace(balance, decimals, { keepZero: false, precision: 4 })}`} + placeholder={`Balance: ${formatBlanace(balance, decimals, { keepZero: false, precision: decimals })}`} className="h-full w-[72%] bg-transparent text-sm font-light focus-visible:outline-none" onChange={(e) => { const _hasError = Number.isNaN(Number(e.target.value)); @@ -66,15 +60,6 @@ export default function BalanceInput({ <span className="text-sm font-light text-white">{symbol}</span> </div> </div> - {power !== undefined && <ExtraPower power={power} powerChanges={powerChanges} />} </div> ); } - -export function ExtraPower({ power, powerChanges = "more" }: { power: bigint; powerChanges?: PowerChanges }) { - return ( - <span className="text-xs font-bold text-primary">{`${powerChanges === "more" ? "+" : "-"}${prettyNumber( - power - )} Power`}</span> - ); -} diff --git a/src/components/bond-more-deposit-modal.tsx b/src/components/bond-more-deposit-modal.tsx index 072d232..0fa565d 100644 --- a/src/components/bond-more-deposit-modal.tsx +++ b/src/components/bond-more-deposit-modal.tsx @@ -1,40 +1,24 @@ -import { Key, useCallback, useMemo, useState } from "react"; +import { Key, useCallback, useState } from "react"; import Modal from "./modal"; import CheckboxGroup from "./checkbox-group"; -import { commissionWeightedPower, formatBlanace, getChainConfig, notifyTransaction } from "@/utils"; -import { ExtraPower } from "./balance-input"; +import { formatBlanace, getChainConfig, notifyTransaction } from "@/utils"; import { useApp, useStaking } from "@/hooks"; import { notification } from "./notification"; import { writeContract, waitForTransaction } from "@wagmi/core"; -import { ChainID } from "@/types"; export default function BondMoreDepositModal({ - commission, isOpen, onClose = () => undefined, }: { - commission: string; isOpen: boolean; onClose?: () => void; }) { - const { deposits, calcExtraPower } = useStaking(); + const { deposits } = useStaking(); const { activeChain } = useApp(); const [checkedDeposits, setCheckedDeposits] = useState<number[]>([]); const [busy, setBusy] = useState(false); - const extraPower = useMemo( - () => - commissionWeightedPower( - calcExtraPower( - deposits.filter(({ id }) => checkedDeposits.includes(id)).reduce((acc, cur) => acc + cur.value, 0n), - 0n - ), - commission - ), - [deposits, commission, checkedDeposits, calcExtraPower] - ); - const availableDeposits = deposits.filter(({ inUse }) => !inUse); const { nativeToken } = getChainConfig(activeChain); @@ -43,14 +27,9 @@ export default function BondMoreDepositModal({ const { contract, explorer } = getChainConfig(activeChain); try { - const abi = - activeChain === ChainID.CRAB - ? (await import("@/config/abi/staking-v2.json")).default - : (await import(`@/config/abi/${contract.staking.abiFile}`)).default; - const { hash } = await writeContract({ address: contract.staking.address, - abi, + abi: (await import(`@/config/abi/${contract.staking.abiFile}`)).default, functionName: "stake", args: [0n, 0n, checkedDeposits], }); @@ -100,10 +79,6 @@ export default function BondMoreDepositModal({ onChange={setCheckedDeposits as (values: Key[]) => void} className="max-h-80 overflow-y-auto" /> - - <div className="h-[1px] bg-white/20" /> - - <ExtraPower power={extraPower} /> </> ) : ( <span className="text-xs font-light text-white">No more deposits to bond</span> diff --git a/src/components/bond-more-kton-modal.tsx b/src/components/bond-more-kton-modal.tsx index ef7f894..29a50ca 100644 --- a/src/components/bond-more-kton-modal.tsx +++ b/src/components/bond-more-kton-modal.tsx @@ -1,29 +1,25 @@ -import { commissionWeightedPower, getChainConfig, notifyTransaction } from "@/utils"; +import { getChainConfig, notifyTransaction } from "@/utils"; import BondMoreTokenModal from "./bond-more-token-modal"; -import { useApp, useStaking } from "@/hooks"; +import { useApp } from "@/hooks"; import { useAccount, useBalance } from "wagmi"; import { useCallback, useState } from "react"; import { notification } from "./notification"; import { writeContract, waitForTransaction } from "@wagmi/core"; -import { ChainID } from "@/types"; export default function BondMoreKtonModal({ - commission, isOpen, onClose = () => undefined, }: { - commission: string; isOpen: boolean; onClose?: () => void; }) { const { activeChain } = useApp(); const { address } = useAccount(); - const { calcExtraPower } = useStaking(); const [inputAmount, setInputAmount] = useState(0n); const [busy, setBusy] = useState(false); - const { ktonToken, contract, explorer } = getChainConfig(activeChain); + const { ktonToken } = getChainConfig(activeChain); const { data: ktonBalance } = useBalance({ address, token: ktonToken?.address, watch: true }); const handleBond = useCallback(async () => { @@ -34,14 +30,9 @@ export default function BondMoreKtonModal({ const { contract, explorer } = getChainConfig(activeChain); try { - const abi = - activeChain === ChainID.CRAB - ? (await import("@/config/abi/staking-v2.json")).default - : (await import(`@/config/abi/${contract.staking.abiFile}`)).default; - const { hash } = await writeContract({ address: contract.staking.address, - abi, + abi: (await import(`@/config/abi/${contract.staking.abiFile}`)).default, functionName: "stake", args: [0n, inputAmount, []], }); @@ -67,7 +58,6 @@ export default function BondMoreKtonModal({ isOpen={isOpen} symbol={ktonToken.symbol} decimals={ktonToken.decimals} - power={commissionWeightedPower(calcExtraPower(0n, inputAmount), commission)} balance={ktonBalance?.value || 0n} busy={busy} disabled={inputAmount <= 0n} diff --git a/src/components/bond-more-ring-modal.tsx b/src/components/bond-more-ring-modal.tsx index f891b0f..9ba3327 100644 --- a/src/components/bond-more-ring-modal.tsx +++ b/src/components/bond-more-ring-modal.tsx @@ -1,25 +1,21 @@ -import { commissionWeightedPower, getChainConfig, notifyTransaction } from "@/utils"; +import { getChainConfig, notifyTransaction } from "@/utils"; import BondMoreTokenModal from "./bond-more-token-modal"; import { useAccount, useBalance } from "wagmi"; -import { useApp, useStaking } from "@/hooks"; +import { useApp } from "@/hooks"; import { useCallback, useState } from "react"; import { notification } from "./notification"; import { writeContract, waitForTransaction } from "@wagmi/core"; -import { ChainID } from "@/types"; export default function BondMoreRingModal({ - commission, isOpen, onClose = () => undefined, }: { - commission: string; isOpen: boolean; onClose?: () => void; }) { const { activeChain } = useApp(); const { address } = useAccount(); const { data: ringBalance } = useBalance({ address, watch: true }); - const { calcExtraPower } = useStaking(); const [inputAmount, setInputAmount] = useState(0n); const [busy, setBusy] = useState(false); @@ -34,14 +30,9 @@ export default function BondMoreRingModal({ const { contract, explorer } = getChainConfig(activeChain); try { - const abi = - activeChain === ChainID.CRAB - ? (await import("@/config/abi/staking-v2.json")).default - : (await import(`@/config/abi/${contract.staking.abiFile}`)).default; - const { hash } = await writeContract({ address: contract.staking.address, - abi, + abi: (await import(`@/config/abi/${contract.staking.abiFile}`)).default, functionName: "stake", args: [inputAmount, 0n, []], }); @@ -65,7 +56,6 @@ export default function BondMoreRingModal({ isOpen={isOpen} symbol={nativeToken.symbol} decimals={nativeToken.decimals} - power={commissionWeightedPower(calcExtraPower(inputAmount, 0n), commission)} balance={ringBalance?.value || 0n} busy={busy} disabled={inputAmount <= 0n} diff --git a/src/components/bond-more-token-modal.tsx b/src/components/bond-more-token-modal.tsx index daf0da5..8407633 100644 --- a/src/components/bond-more-token-modal.tsx +++ b/src/components/bond-more-token-modal.tsx @@ -6,7 +6,6 @@ export default function BondMoreTokenModal({ symbol, decimals, balance, - power, busy, disabled, isReset, @@ -19,7 +18,6 @@ export default function BondMoreTokenModal({ symbol: string; decimals: number; balance: bigint; - power: bigint; busy?: boolean; disabled?: boolean; isReset?: boolean; @@ -28,29 +26,44 @@ export default function BondMoreTokenModal({ onClose?: () => void; onChange?: (amount: bigint) => void; }) { + const isKton = symbol.endsWith("KTON"); + return ( <Modal title={`Bond More ${symbol}`} isOpen={isOpen} onCancel={onCancel} onClose={onClose} - onOk={onBond} + onOk={isKton ? undefined : onBond} maskClosable={false} okText="Bond" className="lg:w-[25rem]" busy={busy} disabled={disabled} > - <BalanceInput - label="Amount" - boldLabel - decimals={decimals} - symbol={symbol} - balance={balance} - power={power} - isReset={isReset} - onChange={onChange} - /> + {isKton ? ( + <div className="flex flex-col gap-small text-xs font-bold lg:text-sm lg:font-light"> + <span className="text-white">{`Please stake ${symbol} in`}</span> + <a + href="https://kton-staking.darwinia.network/" + rel="noopener noreferrer" + target="_blank" + className="text-primary underline transition-opacity hover:opacity-80" + > + https://kton-staking.darwinia.network + </a> + </div> + ) : ( + <BalanceInput + label="Amount" + boldLabel + decimals={decimals} + symbol={symbol} + balance={balance} + isReset={isReset} + onChange={onChange} + /> + )} </Modal> ); } diff --git a/src/components/collator-select-modal.tsx b/src/components/collator-select-modal.tsx index 9213825..d42c7f4 100644 --- a/src/components/collator-select-modal.tsx +++ b/src/components/collator-select-modal.tsx @@ -67,37 +67,34 @@ const columns: ColumnType<DataSource>[] = [ key: "power", dataIndex: "power", title: ( - <div className="inline-flex flex-col text-xs font-bold text-white"> - <span>Total-staked</span> - <div className="inline-flex items-center gap-small"> - <span>(Power)</span> - <Tooltip - content={ - <div className="inline-block text-xs font-light text-white"> - {`The Collator's total-staked power is a dynamic value, inversely proportional to the commission set by - the Collator. Higher commission results in lower total-staked power and vice versa. `} - <a - rel="noopener noreferrer" - target="_blank" - className="text-primary hover:underline" - href="https://github.com/darwinia-network/DIPs/blob/main/DIPs/dip-1.md" - > - Learn More - </a> - </div> - } - enabledSafePolygon - contentClassName="w-80" - > - <Image - width={15} - height={14} - alt="Info" - src="/images/help.svg" - className="opacity-60 transition-opacity hover:opacity-100" - /> - </Tooltip> - </div> + <div className="inline-flex items-center gap-small"> + <span className="text-xs font-bold text-white">Total-staked</span> + <Tooltip + content={ + <div className="inline-block text-xs font-light text-white"> + {`The Collator's total-staked is a dynamic value, inversely proportional to the commission set by + the Collator. Higher commission results in lower total-staked and vice versa. `} + <a + rel="noopener noreferrer" + target="_blank" + className="text-primary hover:underline" + href="https://github.com/darwinia-network/DIPs/blob/main/DIPs/dip-1.md" + > + Learn More + </a> + </div> + } + enabledSafePolygon + contentClassName="w-80" + > + <Image + width={15} + height={14} + alt="Info" + src="/images/help.svg" + className="opacity-60 transition-opacity hover:opacity-100" + /> + </Tooltip> </div> ), render: (row) => <span>{prettyNumber(row.power)}</span>, diff --git a/src/components/collator-selector.tsx b/src/components/collator-selector.tsx index fbf23d2..46cac11 100644 --- a/src/components/collator-selector.tsx +++ b/src/components/collator-selector.tsx @@ -24,7 +24,7 @@ export default function CollatorSelector({ collator, onSelect }: Props) { return ( <> {collator ? ( - <div className="flex items-center gap-middle border border-primary px-large py-middle transition-opacity"> + <div className="flex h-10 items-center gap-middle border border-white px-large transition-opacity"> <Collator collator={collator} /> <button className="shrink-0 transition-transform hover:scale-105 active:scale-95" @@ -35,7 +35,7 @@ export default function CollatorSelector({ collator, onSelect }: Props) { </div> ) : ( <button - className="border border-primary py-middle text-sm font-bold text-white transition-opacity hover:opacity-80 active:opacity-60" + className="h-10 border border-white text-sm font-light text-white transition-opacity hover:opacity-80 active:opacity-60" onClick={() => setIsOpen(true)} > Select a collator @@ -52,13 +52,10 @@ function Collator({ collator }: { collator: string }) { return ( <> - <Jazzicon address={collator} size={30} /> - <div className="flex min-w-0 flex-col gap-small"> - <span className="text-sm font-bold text-white"> - {accountName === collator ? toShortAdrress(collator) : accountName} - </span> - <span className="break-words text-xs font-light text-white">{collator}</span> - </div> + <Jazzicon address={collator} size={22} /> + <span className="truncate text-sm font-bold text-white"> + {accountName === collator ? toShortAdrress(collator) : accountName} + </span> </> ); } diff --git a/src/components/custom-rpc/add-rpc-modal.tsx b/src/components/custom-rpc/add-rpc-modal.tsx deleted file mode 100644 index 7f8cde3..0000000 --- a/src/components/custom-rpc/add-rpc-modal.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import { RpcMeta } from "@/types"; -import { ChangeEventHandler, useState } from "react"; -import Modal from "../modal"; - -export default function AddRpcModal({ - isOpen, - onClose = () => undefined, - onSave = () => undefined, -}: { - isOpen: boolean; - onClose?: () => void; - onSave?: (rpcMeta: RpcMeta) => void; -}) { - const [rpcMeta, setRpcMeta] = useState<RpcMeta>(); - - return ( - <Modal title="Custom Endpoint" isOpen={isOpen} onClose={onClose} className="lg:w-[560px]"> - <div className="flex flex-col gap-5"> - <Input - label="Endpoint Name (Optional)" - onChange={(e) => setRpcMeta((prev) => ({ name: e.target.value, url: prev?.url || "" }))} - /> - <Input - label="Endpoint URL" - onChange={(e) => setRpcMeta((prev) => ({ name: prev?.name, url: e.target.value }))} - /> - - <div className="h-[1px] w-full bg-white/20" /> - - <button - className="flex h-10 w-full items-center justify-center bg-primary transition-opacity hover:opacity-80 active:opacity-60 disabled:cursor-not-allowed disabled:opacity-60" - onClick={() => { - rpcMeta && onSave(rpcMeta); - }} - disabled={!rpcMeta?.url} - > - <span>Save</span> - </button> - </div> - </Modal> - ); -} - -function Input({ label, onChange }: { label: string; onChange: ChangeEventHandler<HTMLInputElement> }) { - return ( - <div className="flex flex-col gap-middle"> - <span className="text-xs font-bold text-white">{label}</span> - <input - type="text" - onChange={onChange} - className="h-10 border border-white/50 bg-transparent px-2 text-sm transition-colors hover:border-white focus-visible:border-white focus-visible:outline-none" - /> - </div> - ); -} diff --git a/src/components/custom-rpc/index.tsx b/src/components/custom-rpc/index.tsx deleted file mode 100644 index 2a101fe..0000000 --- a/src/components/custom-rpc/index.tsx +++ /dev/null @@ -1,57 +0,0 @@ -"use client"; - -import { - useFloating, - offset, - useTransitionStyles, - useClick, - useDismiss, - useInteractions, - FloatingPortal, -} from "@floating-ui/react"; -import Image from "next/image"; -import { useState } from "react"; -import RpcSelector from "./rpc-selector"; - -export default function CustomRpc() { - const [isOpen, setIsOpen] = useState(false); - - const { refs, context, floatingStyles } = useFloating({ - open: isOpen, - onOpenChange: setIsOpen, - placement: "bottom-end", - middleware: [offset(10)], - }); - - const { styles, isMounted } = useTransitionStyles(context, { - initial: { transform: "translateY(-20px)", opacity: 0 }, - open: { transform: "translateY(0)", opacity: 1 }, - close: { transform: "translateY(-20px)", opacity: 0 }, - }); - - const click = useClick(context); - const dismiss = useDismiss(context); - const { getReferenceProps, getFloatingProps } = useInteractions([click, dismiss]); - - return ( - <> - <button - className="inline-flex h-9 w-9 items-center justify-center border border-primary text-sm font-light text-white transition-opacity hover:opacity-80 active:opacity-60" - ref={refs.setReference} - {...getReferenceProps()} - > - <Image width={16} height={16} alt="Custom rpc" src="/images/setting.svg" /> - </button> - - {isMounted && ( - <FloatingPortal> - <div style={floatingStyles} ref={refs.setFloating} {...getFloatingProps()} className="z-20"> - <div style={styles}> - <RpcSelector onClose={() => setIsOpen(false)} /> - </div> - </div> - </FloatingPortal> - )} - </> - ); -} diff --git a/src/components/custom-rpc/rpc-selector.tsx b/src/components/custom-rpc/rpc-selector.tsx deleted file mode 100644 index f251dd6..0000000 --- a/src/components/custom-rpc/rpc-selector.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import { useApp } from "@/hooks"; -import { RpcMeta } from "@/types"; -import { useState } from "react"; -import AddRpcModal from "./add-rpc-modal"; - -export default function RpcSelector({ onClose = () => undefined }: { onClose?: () => void }) { - const [isOpen, setIsOpen] = useState(false); - const { rpcMetas, setRpcMetas } = useApp(); - - return ( - <> - <div className="flex w-60 flex-col gap-middle border border-primary bg-component p-5"> - <div className="max-h-[25vh] overflow-y-auto"> - <div className="flex flex-col gap-middle"> - {rpcMetas.map((rpcMeta) => ( - <RpcItem key={rpcMeta.url} rpcMeta={rpcMeta} onClose={onClose} /> - ))} - </div> - </div> - - <div className="flex h-4 items-center gap-middle"> - <div className="h-[1px] w-full bg-white/20" /> - <span className="text-xs font-bold text-white">or</span> - <div className="h-[1px] w-full bg-white/20" /> - </div> - - <button - className="flex w-full items-center justify-center border border-primary bg-transparent py-2 transition-opacity hover:opacity-80 active:opacity-60" - onClick={() => setIsOpen(true)} - > - <span className="text-xs font-bold text-white">Add Custom Endpoint</span> - </button> - </div> - - <AddRpcModal - isOpen={isOpen} - onClose={() => setIsOpen(false)} - onSave={(value) => { - setRpcMetas((prev) => (prev.some(({ url }) => url === value.url) ? prev : [...prev, value])); - setIsOpen(false); - }} - /> - </> - ); -} - -function RpcItem({ rpcMeta, onClose }: { rpcMeta: RpcMeta; onClose: () => void }) { - const { activeRpc, setActiveRpc } = useApp(); - - return ( - <div - className={`flex gap-middle p-small transition hover:cursor-pointer hover:opacity-80 active:opacity-60 ${ - activeRpc.url === rpcMeta.url ? "bg-primary/20" : "bg-white/20" - }`} - onClick={() => { - setActiveRpc(rpcMeta); - onClose(); - }} - > - <div - className={`w-[3px] shrink-0 transition-colors ${activeRpc.url === rpcMeta.url ? "bg-primary" : "bg-white/20"}`} - /> - <span className="break-all text-xs font-bold">via {rpcMeta.name ?? rpcMeta.url}</span> - </div> - ); -} diff --git a/src/components/do-deposit.tsx b/src/components/do-deposit.tsx index ff755d1..1abc033 100644 --- a/src/components/do-deposit.tsx +++ b/src/components/do-deposit.tsx @@ -36,11 +36,9 @@ export default function DoDeposit() { setBusy(true); try { - const contractAbi = (await import(`@/config/abi/${chainConfig.contract.deposit.abiFile}`)).default; - const { hash } = await writeContract({ address: chainConfig.contract.deposit.address, - abi: contractAbi, + abi: (await import(`@/config/abi/${chainConfig.contract.deposit.abiFile}`)).default, functionName: "lock", args: [depositRing, depositTerm], }); diff --git a/src/components/do-stake.tsx b/src/components/do-stake.tsx index 706995b..08a19e8 100644 --- a/src/components/do-stake.tsx +++ b/src/components/do-stake.tsx @@ -1,8 +1,8 @@ -import { commissionWeightedPower, getChainConfig, notifyTransaction } from "@/utils"; +import { getChainConfig, notifyTransaction } from "@/utils"; import ActiveDepositSelector from "./active-deposit-selector"; import CollatorSelector from "./collator-selector"; -import BalanceInput, { ExtraPower } from "./balance-input"; -import { useCallback, useEffect, useMemo, useState } from "react"; +import BalanceInput from "./balance-input"; +import { useCallback, useEffect, useState } from "react"; import { useApp, useStaking } from "@/hooks"; import { useAccount, useBalance, usePublicClient, useWalletClient } from "wagmi"; import EnsureMatchNetworkButton from "./ensure-match-network-button"; @@ -11,14 +11,7 @@ import { ChainID } from "@/types"; import { notification } from "./notification"; export default function DoStake() { - const { - deposits, - nominatorCollators, - collatorCommission, - isNominatorCollatorsLoading, - calcExtraPower, - updateNominatorCollators, - } = useStaking(); + const { nominatorCollators, isNominatorCollatorsLoading, updateNominatorCollators } = useStaking(); const [delegateCollator, setDelegateCollator] = useState<string | undefined>(undefined); const [delegateRing, setDelegateRing] = useState(0n); const [delegateKton, setDelegateKton] = useState(0n); @@ -26,37 +19,12 @@ export default function DoStake() { const [busy, setBusy] = useState(false); const { activeChain } = useApp(); - const { nativeToken, ktonToken, explorer } = getChainConfig(activeChain); + const { nativeToken, explorer } = getChainConfig(activeChain); const publicClient = usePublicClient(); const { data: walletClient } = useWalletClient(); const { address } = useAccount(); const { data: ringBalance } = useBalance({ address, watch: true }); - const { data: ktonBalance } = useBalance({ address, watch: true, token: ktonToken?.address }); - - const commission = useMemo(() => { - return (delegateCollator && collatorCommission[delegateCollator]) || "0.00%"; - }, [delegateCollator, collatorCommission]); - - const ringExtraPower = useMemo( - () => commissionWeightedPower(calcExtraPower(delegateRing, 0n), commission), - [commission, delegateRing, calcExtraPower] - ); - const ktonExtraPower = useMemo( - () => commissionWeightedPower(calcExtraPower(0n, delegateKton), commission), - [commission, delegateKton, calcExtraPower] - ); - const depositsExtraPower = useMemo( - () => - commissionWeightedPower( - calcExtraPower( - deposits.filter(({ id }) => delegateDeposits.includes(id)).reduce((acc, cur) => acc + cur.value, 0n), - 0n - ), - commission - ), - [delegateDeposits, commission, deposits, calcExtraPower] - ); const handleStake = useCallback(async () => { if (delegateCollator && walletClient) { @@ -114,9 +82,6 @@ export default function DoStake() { <div className="h-[1px] bg-white/20" /> - {/* collator */} - <CollatorSelector collator={delegateCollator} onSelect={setDelegateCollator} /> - <div className="flex flex-col gap-middle lg:flex-row"> {/* ring */} <BalanceInput @@ -124,32 +89,19 @@ export default function DoStake() { symbol={nativeToken.symbol} logoPath={nativeToken.logoPath} decimals={nativeToken.decimals} - power={ringExtraPower} className="lg:flex-1" onChange={setDelegateRing} isReset={delegateRing <= 0} /> - {/* kton */} - {ktonToken && ( - <> - <BalanceInput - balance={ktonBalance?.value || 0n} - symbol={ktonToken.symbol} - logoPath={ktonToken.logoPath} - decimals={ktonToken.decimals} - power={ktonExtraPower} - className="lg:flex-1" - onChange={setDelegateKton} - isReset={delegateKton <= 0} - /> - </> - )} - {/* active deposit */} <div className="flex flex-col gap-middle lg:flex-1"> <ActiveDepositSelector checkedDeposits={delegateDeposits} onChange={setDelegateDeposits} /> - <ExtraPower power={depositsExtraPower} /> + </div> + + {/* collator */} + <div className="flex flex-col gap-middle lg:flex-1"> + <CollatorSelector collator={delegateCollator} onSelect={setDelegateCollator} /> </div> </div> diff --git a/src/components/ensure-match-network-button.tsx b/src/components/ensure-match-network-button.tsx index 8a093ef..a2082a0 100644 --- a/src/components/ensure-match-network-button.tsx +++ b/src/components/ensure-match-network-button.tsx @@ -34,7 +34,6 @@ export default forwardRef< >(function EnsureMatchNetworkButton({ children, onClick, kind = "opacity", disabled, busy, ...rest }, ref) { const { activeChain } = useApp(); const { chain } = useNetwork(); - const { switchNetwork } = useSwitchNetwork(); const isMatch = useMemo(() => chain?.id === activeChain, [chain?.id, activeChain]); @@ -103,15 +102,7 @@ export default forwardRef< <div ref={refs.setFloating} style={floatingStyles} {...getFloatingProps()} className="z-30"> <FloatingArrow ref={arrowRef} style={styles} context={context} fill="#FF0083" /> <div style={styles} className="w-[70vw] border border-primary bg-component p-middle lg:w-64"> - <p className="text-xs font-light text-white"> - You are connected to the Wrong Chain.{" "} - <span - className="text-primary transition-opacity hover:cursor-pointer hover:opacity-80" - onClick={() => switchNetwork && switchNetwork(activeChain)} - > - Switch network - </span> - </p> + <p className="text-xs font-light text-white">You are connected to the Wrong Chain.</p> </div> </div> </FloatingPortal> diff --git a/src/components/header/chain-switch.tsx b/src/components/header/chain-switch.tsx new file mode 100644 index 0000000..9a16256 --- /dev/null +++ b/src/components/header/chain-switch.tsx @@ -0,0 +1,114 @@ +"use client"; + +import { useApp } from "@/hooks"; +import { getChainConfig, getChainConfigs } from "@/utils"; +import { + FloatingPortal, + offset, + size, + useClick, + useDismiss, + useFloating, + useInteractions, + useTransitionStyles, +} from "@floating-ui/react"; +import Image from "next/image"; +import { useEffect, useState } from "react"; +import { useNetwork, useSwitchNetwork } from "wagmi"; + +const chainOptions = getChainConfigs(); + +export default function ChainSwitch() { + const { chain } = useNetwork(); + const { switchNetwork } = useSwitchNetwork(); + const { setActiveChain } = useApp(); + const [currentChain, setCurrentChain] = useState(chainOptions.find((option) => option.chainId === chain?.id)); + useEffect(() => { + const c = chainOptions.find((option) => option.chainId === chain?.id); + setCurrentChain(c); + c && setActiveChain(c.chainId); + }, [chain?.id, setActiveChain]); + + const [isOpen, setIsOpen] = useState(false); + const { refs, context, floatingStyles } = useFloating({ + open: isOpen, + onOpenChange: setIsOpen, + middleware: [ + offset(10), + size({ + apply({ rects, elements }) { + Object.assign(elements.floating.style, { width: `${rects.reference.width}px` }); + }, + }), + ], + }); + const { styles, isMounted } = useTransitionStyles(context, { + initial: { transform: "translateY(-20px)", opacity: 0 }, + open: { transform: "translateY(0)", opacity: 1 }, + close: { transform: "translateY(-20px)", opacity: 0 }, + }); + const click = useClick(context); + const dismiss = useDismiss(context); + const { getReferenceProps, getFloatingProps } = useInteractions([click, dismiss]); + + return chain ? ( + <> + <button + className={`flex h-10 items-center justify-center gap-middle border border-primary px-large transition-opacity hover:opacity-80 lg:h-9 ${ + currentChain ? "border-primary" : "border-orange-400" + }`} + ref={refs.setReference} + {...getReferenceProps()} + > + {currentChain ? ( + <> + <Image alt="Chain logo" width={24} height={24} src={getChainLogo(currentChain.logo)} /> + <span className="text-sm font-light">{currentChain.name}</span> + <Image + src="/images/caret-down.svg" + alt="Account profiles icon" + width={16} + height={16} + className="transition-transform duration-300" + style={{ transform: isOpen ? "rotateX(180deg)" : "rotateX(0)" }} + /> + </> + ) : ( + <div className="flex items-center gap-middle"> + <Image src="/images/status/warn.svg" alt="Account profiles icon" width={20} height={20} /> + <span className="text-sm font-light text-orange-300">Wrong Chain</span> + </div> + )} + </button> + + {isMounted && ( + <FloatingPortal> + <div style={floatingStyles} ref={refs.setFloating} {...getFloatingProps()} className="z-20"> + <div style={styles} className="flex flex-col gap-[2px] border border-primary bg-app-black"> + {chainOptions.map((option) => ( + <button + key={option.chainId} + className={`flex items-center gap-middle p-middle transition-colors ${ + option.chainId === chain?.id ? "bg-white/10" : "hover:bg-white/10" + }`} + disabled={option.chainId === chain?.id} + onClick={() => { + switchNetwork?.(option.chainId); + setIsOpen(false); + }} + > + <Image alt="Chain logo" width={24} height={24} src={getChainLogo(option.logo)} /> + <span className="text-sm font-light">{getChainConfig(option.chainId).name}</span> + </button> + ))} + </div> + </div> + </FloatingPortal> + )} + </> + ) : null; +} + +function getChainLogo(fileName: string) { + return `/images/chain/${fileName}`; +} diff --git a/src/components/header/index.tsx b/src/components/header/index.tsx index 8844c2c..5e4bff4 100644 --- a/src/components/header/index.tsx +++ b/src/components/header/index.tsx @@ -1,8 +1,7 @@ import dynamic from "next/dynamic"; -import NetworkSelector from "./network-selector"; import Image from "next/image"; -import CustomRpc from "../custom-rpc"; +const ChainSwitch = dynamic(() => import("./chain-switch"), { ssr: false }); const User = dynamic(() => import("./user"), { ssr: false }); export default function Header({ className }: { className: string }) { @@ -10,19 +9,10 @@ export default function Header({ className }: { className: string }) { <div className={`${className} z-20 flex items-center bg-app-black px-large`}> <div className="container mx-auto flex items-center justify-between"> <Image width={156} height={18} alt="Logo" src="/images/logo.png" className="hidden lg:block" /> - <div className="lg:hidden"> - <User /> - </div> - <div className="lg:hidden"> - <NetworkSelector /> - </div> - <div className="hidden items-center gap-10 lg:flex"> - <NetworkSelector /> - <div className="flex items-center gap-middle"> - <User /> - <CustomRpc /> - </div> + <div className="flex w-full items-center justify-between gap-1 lg:w-fit lg:gap-middle"> + <ChainSwitch /> + <User /> </div> </div> </div> diff --git a/src/components/header/network-selector.tsx b/src/components/header/network-selector.tsx deleted file mode 100644 index a866eb9..0000000 --- a/src/components/header/network-selector.tsx +++ /dev/null @@ -1,64 +0,0 @@ -"use client"; - -import { useApp } from "@/hooks"; -import Selector from "../selector"; -import { getChainConfig, getChainConfigs } from "@/utils"; -import { useState } from "react"; -import ActionButton from "./action-button"; - -const chainConfigs = getChainConfigs(); - -export default function NetworkSelector() { - const [isOpen, setIsOpen] = useState(false); - const { activeChain, setActiveChain } = useApp(); - - const chainConfig = getChainConfig(activeChain); - - return ( - <> - {/* mobile */} - <div className="lg:hidden"> - <Selector - label={<span className="text-sm font-light">{chainConfig.name}</span>} - menuClassName="border border-primary p-large flex flex-col items-start gap-large bg-app-black" - isOpen={isOpen} - setIsOpen={setIsOpen} - > - {chainConfigs.map(({ name, chainId }) => ( - <ActionButton - key={chainId} - disabled={chainId === activeChain} - onClick={() => { - setActiveChain(chainId); - setIsOpen(false); - }} - > - {name} - </ActionButton> - ))} - </Selector> - </div> - - {/* pc */} - <div className="hidden lg:block"> - <div className="relative flex items-center gap-10"> - {chainConfigs.map(({ name, chainId }) => ( - <button - key={chainId} - onClick={() => setActiveChain(chainId)} - className={`border-b-[2px] pb-1 transition duration-300 hover:opacity-80 ${ - chainId === activeChain ? "border-b-primary" : "border-b-transparent" - }`} - > - <span - className={`text-sm text-white transition-all ${chainId === activeChain ? "font-bold" : "font-light"}`} - > - {name} - </span> - </button> - ))} - </div> - </div> - </> - ); -} diff --git a/src/components/power.tsx b/src/components/latest-rewards.tsx similarity index 67% rename from src/components/power.tsx rename to src/components/latest-rewards.tsx index 692eaa3..bdb57f8 100644 --- a/src/components/power.tsx +++ b/src/components/latest-rewards.tsx @@ -1,12 +1,11 @@ import { GET_LATEST_STAKING_REWARDS } from "@/config"; -import { useApp, useStaking } from "@/hooks"; -import { commissionWeightedPower, formatBlanace, getChainConfig, prettyNumber } from "@/utils"; +import { useApp } from "@/hooks"; +import { formatBlanace, getChainConfig } from "@/utils"; import { formatDistanceStrict } from "date-fns"; -import Image from "next/image"; import { getAddress } from "viem"; import { useAccount } from "wagmi"; import CountLoading from "./count-loading"; -import { useMemo, useRef } from "react"; +import { useRef } from "react"; import { CSSTransition } from "react-transition-group"; import { useQuery } from "@apollo/client"; @@ -34,18 +33,8 @@ interface QueryResult { stakingRecord: StakingRecord | null; } -export default function Power() { +export default function LatestRewards() { const loadingRef = useRef<HTMLDivElement>(null); - const { - power, - nominatorCollators, - collatorCommission, - isNominatorCollatorsInitialized, - isCollatorCommissionInitialized, - isLedgersInitialized, - isRingPoolInitialized, - isKtonPoolInitialized, - } = useStaking(); const { activeChain } = useApp(); const { address } = useAccount(); const { data: rewardData, loading: rewardLoading } = useQuery<QueryResult, QueryVariables>( @@ -55,37 +44,10 @@ export default function Power() { } ); - const thePower = useMemo(() => { - const isCollator = - address && Object.keys(collatorCommission).some((addr) => addr.toLowerCase() === address.toLowerCase()) - ? true - : false; - const collator = isCollator ? address : address ? nominatorCollators[address]?.at(0) : undefined; - const commission = collator ? collatorCommission[collator] : undefined; - return commissionWeightedPower(power, commission ?? "0.00%"); - }, [address, power, collatorCommission, nominatorCollators]); - const chainConfig = getChainConfig(activeChain); return ( <div className="flex flex-1 flex-col gap-5 bg-primary p-5"> - {/* power */} - <div className="flex items-center justify-between"> - <div className="flex items-center gap-middle"> - <Image alt="Icon of Power" src="/images/power.svg" width={30} height={42} /> - <span className="text-3xl font-bold text-white">Power</span> - </div> - {isLedgersInitialized && - isRingPoolInitialized && - isKtonPoolInitialized && - isNominatorCollatorsInitialized && - isCollatorCommissionInitialized ? ( - <span className="text-3xl font-bold text-white">{prettyNumber(thePower)}</span> - ) : ( - <CountLoading color="white" size="large" /> - )} - </div> - {/* reward records */} <div className="flex flex-col gap-middle bg-component p-5"> <span className="text-sm font-bold text-white">Latest Staking Rewards</span> diff --git a/src/components/login.tsx b/src/components/login.tsx index ff6d4e4..ac52d53 100644 --- a/src/components/login.tsx +++ b/src/components/login.tsx @@ -21,7 +21,7 @@ export default function Login() { Connect Wallet </button> <p className="text-center text-xs font-light text-[#FFFDFD]"> - Connect wallet to participate in staking and deposit in Darwinia. + Connect wallet to participate in collactor staking and deposit in Darwinia. </p> </> ); diff --git a/src/components/manage-collator-modal.tsx b/src/components/manage-collator-modal.tsx index 69b09fa..b75b4fa 100644 --- a/src/components/manage-collator-modal.tsx +++ b/src/components/manage-collator-modal.tsx @@ -76,11 +76,9 @@ export default function ManageCollator({ setBusy(true); try { - const contractAbi = (await import(`@/config/abi/${contract.staking.abiFile}`)).default; - const { hash } = await writeContract({ address: contract.staking.address, - abi: contractAbi, + abi: (await import(`@/config/abi/${contract.staking.abiFile}`)).default, functionName: "collect", args: [commissionValue], }); @@ -105,11 +103,9 @@ export default function ManageCollator({ setBusy(true); try { - const contractAbi = (await import(`@/config/abi/${contract.staking.abiFile}`)).default; - const { hash } = await writeContract({ address: contract.staking.address, - abi: contractAbi, + abi: (await import(`@/config/abi/${contract.staking.abiFile}`)).default, functionName: "chill", args: [], }); diff --git a/src/components/records-bonded-tokens.tsx b/src/components/records-bonded-tokens.tsx index e2e6df6..626fd9a 100644 --- a/src/components/records-bonded-tokens.tsx +++ b/src/components/records-bonded-tokens.tsx @@ -1,5 +1,5 @@ import { useApp } from "@/hooks"; -import { ChainID, StakingRecordsDataSource } from "@/types"; +import { StakingRecordsDataSource } from "@/types"; import { formatBlanace, getChainConfig, notifyTransaction } from "@/utils"; import UnbondingTokenTooltip from "./unbonding-token-tooltip"; import UnbondingDepositTooltip from "./unbonding-deposit-tooltip"; @@ -35,17 +35,11 @@ export default function RecordsBondedTokens({ row }: { row: StakingRecordsDataSo const { contract, explorer } = getChainConfig(activeChain); try { - const abi = - activeChain === ChainID.CRAB - ? (await import("@/config/abi/staking-v2.json")).default - : (await import(`@/config/abi/${contract.staking.abiFile}`)).default; - const args = activeChain === ChainID.CRAB ? [ring, depositIds] : [ring, kton, depositIds]; - const { hash } = await writeContract({ address: contract.staking.address, - abi, + abi: (await import(`@/config/abi/${contract.staking.abiFile}`)).default, functionName: "restake", - args, + args: [ring, depositIds], }); const receipt = await waitForTransaction({ hash }); @@ -130,8 +124,8 @@ export default function RecordsBondedTokens({ row }: { row: StakingRecordsDataSo </span> {row.collator.length > 0 && ( <> - <BondMoreRing commission={row.commission} /> - <UnbondRing commission={row.commission} /> + <BondMoreRing /> + <UnbondRing /> </> )} </div> @@ -159,8 +153,8 @@ export default function RecordsBondedTokens({ row }: { row: StakingRecordsDataSo </span> {row.collator.length > 0 && ( <> - <BondMoreDeposit commission={row.commission} /> - <UnbondDeposit commission={row.commission} /> + <BondMoreDeposit /> + <UnbondDeposit /> </> )} </div> @@ -187,8 +181,8 @@ export default function RecordsBondedTokens({ row }: { row: StakingRecordsDataSo </span> {row.collator.length > 0 && ( <> - <BondMoreKton commission={row.commission} /> - <UnbondKton commission={row.commission} /> + <BondMoreKton /> + <UnbondKton /> </> )} </div> @@ -196,62 +190,62 @@ export default function RecordsBondedTokens({ row }: { row: StakingRecordsDataSo ); } -function BondMoreRing({ commission }: { commission: string }) { +function BondMoreRing() { const [isOpen, setIsOpen] = useState(false); return ( <> <ChangeBondButton action="bond" onClick={() => setIsOpen(true)} /> - <BondMoreRingModal commission={commission} isOpen={isOpen} onClose={() => setIsOpen(false)} /> + <BondMoreRingModal isOpen={isOpen} onClose={() => setIsOpen(false)} /> </> ); } -function BondMoreKton({ commission }: { commission: string }) { +function BondMoreKton() { const [isOpen, setIsOpen] = useState(false); return ( <> <ChangeBondButton action="bond" onClick={() => setIsOpen(true)} /> - <BondMoreKtonModal commission={commission} isOpen={isOpen} onClose={() => setIsOpen(false)} /> + <BondMoreKtonModal isOpen={isOpen} onClose={() => setIsOpen(false)} /> </> ); } -function BondMoreDeposit({ commission }: { commission: string }) { +function BondMoreDeposit() { const [isOpen, setIsOpen] = useState(false); return ( <> <ChangeBondButton action="bond" onClick={() => setIsOpen(true)} /> - <BondMoreDepositModal commission={commission} isOpen={isOpen} onClose={() => setIsOpen(false)} /> + <BondMoreDepositModal isOpen={isOpen} onClose={() => setIsOpen(false)} /> </> ); } -function UnbondRing({ commission }: { commission: string }) { +function UnbondRing() { const [isOpen, setIsOpen] = useState(false); return ( <> <ChangeBondButton action="unbond" onClick={() => setIsOpen(true)} /> - <UnbondRingModal commission={commission} isOpen={isOpen} onClose={() => setIsOpen(false)} /> + <UnbondRingModal isOpen={isOpen} onClose={() => setIsOpen(false)} /> </> ); } -function UnbondKton({ commission }: { commission: string }) { +function UnbondKton() { const [isOpen, setIsOpen] = useState(false); return ( <> <ChangeBondButton action="unbond" onClick={() => setIsOpen(true)} /> - <UnbondKtonModal commission={commission} isOpen={isOpen} onClose={() => setIsOpen(false)} /> + <UnbondKtonModal isOpen={isOpen} onClose={() => setIsOpen(false)} /> </> ); } -function UnbondDeposit({ commission }: { commission: string }) { +function UnbondDeposit() { const [isOpen, setIsOpen] = useState(false); return ( <> <ChangeBondButton action="unbond" onClick={() => setIsOpen(true)} /> - <UnbondDepositModal commission={commission} isOpen={isOpen} onClose={() => setIsOpen(false)} /> + <UnbondDepositModal isOpen={isOpen} onClose={() => setIsOpen(false)} /> </> ); } diff --git a/src/components/records-select-collator.tsx b/src/components/records-select-collator.tsx index 2d59519..0ceb871 100644 --- a/src/components/records-select-collator.tsx +++ b/src/components/records-select-collator.tsx @@ -19,11 +19,9 @@ export default function RecordsSelectCollator({ text }: { text: string }) { const chainConfig = getChainConfig(activeChain); try { - const contractAbi = (await import(`@/config/abi/${chainConfig.contract.staking.abiFile}`)).default; - const { hash } = await writeContract({ address: chainConfig.contract.staking.address, - abi: contractAbi, + abi: (await import(`@/config/abi/${chainConfig.contract.staking.abiFile}`)).default, functionName: "nominate", args: [collator], }); diff --git a/src/components/reserved-in-staking.tsx b/src/components/reserved-in-staking.tsx index a83df62..f1d2062 100644 --- a/src/components/reserved-in-staking.tsx +++ b/src/components/reserved-in-staking.tsx @@ -4,10 +4,10 @@ import Image from "next/image"; import CountLoading from "./count-loading"; export default function ReservedInStaking() { - const { stakedRing, stakedKton, stakedDeposit, isLedgersInitialized } = useStaking(); + const { stakedRing, stakedDeposit, isLedgersInitialized } = useStaking(); const { activeChain } = useApp(); - const { nativeToken, ktonToken } = getChainConfig(activeChain); + const { nativeToken } = getChainConfig(activeChain); return ( <div className="flex flex-col gap-5 bg-component p-5 lg:w-[32%] lg:shrink-0"> @@ -22,16 +22,6 @@ export default function ReservedInStaking() { inDeposit={stakedDeposit} isNative /> - <div className="h-[1px] bg-white/20" /> - {ktonToken && ( - <Token - symbol={ktonToken.symbol} - decimals={ktonToken.decimals} - logoPath={ktonToken.logoPath} - bonded={stakedKton} - loading={!isLedgersInitialized} - /> - )} </div> ); } diff --git a/src/components/selector.tsx b/src/components/selector.tsx index 7b3d024..4642d4f 100644 --- a/src/components/selector.tsx +++ b/src/components/selector.tsx @@ -89,7 +89,7 @@ export const Button = forwardRef<HTMLButtonElement, ButtonHTMLAttributes<HTMLBut {...rest} type="button" ref={ref} - className={`flex h-10 items-center border border-primary px-large text-sm font-light text-white transition-opacity hover:opacity-80 active:opacity-60 ${className}`} + className={`flex h-10 items-center border border-primary px-large text-sm font-light text-white transition-opacity hover:opacity-80 ${className}`} > {children} </button> diff --git a/src/components/staking-dashboard.tsx b/src/components/staking-dashboard.tsx index 16a42ea..02d6d77 100644 --- a/src/components/staking-dashboard.tsx +++ b/src/components/staking-dashboard.tsx @@ -2,7 +2,7 @@ import { redirect } from "next/navigation"; import { useAccount } from "wagmi"; -import Power from "./power"; +import LatestRewards from "./latest-rewards"; import ReservedInStaking from "./reserved-in-staking"; import StakingDepositTabs from "./staking-deposit-tabs"; @@ -16,7 +16,7 @@ export default function StakingDashboard() { return ( <> <div className="flex flex-col gap-5 lg:flex-row"> - <Power /> + <LatestRewards /> <ReservedInStaking /> </div> diff --git a/src/components/staking-records.tsx b/src/components/staking-records.tsx index 9883b75..d1b94a7 100644 --- a/src/components/staking-records.tsx +++ b/src/components/staking-records.tsx @@ -39,7 +39,7 @@ export default function StakingRecords() { { key: "collator", dataIndex: "collator", - width: "22%", + width: "30%", title: <span>Collator</span>, render: (row) => { if (row.collator) { @@ -74,43 +74,9 @@ export default function StakingRecords() { return <RecordsSelectCollator text="Select a collator" />; }, }, - { - key: "stakedPower", - dataIndex: "stakedPower", - title: <span>Your staked (Power)</span>, - render: (row) => { - if (row.collator) { - return <span className="truncate">{prettyNumber(row.stakedPower)}</span>; - } - - return ( - <div className="flex items-center gap-middle"> - <span className="truncate text-white/50">{prettyNumber(row.stakedPower)}</span> - <Tooltip - content={ - <span className="text-xs font-light text-white"> - The power is not working yet, You can delegate a collator to complete staking. - </span> - } - className="w-fit" - contentClassName="w-64" - > - <Image - alt="Collator tooltip" - width={16} - height={16} - src="/images/help.svg" - className="transition-transform hover:scale-105" - /> - </Tooltip> - </div> - ); - }, - }, { key: "bondedTokens", dataIndex: "bondedTokens", - width: "32%", title: <span>Your bonded tokens</span>, render: (row) => <RecordsBondedTokens row={row} />, }, diff --git a/src/components/unbond-all-staked.tsx b/src/components/unbond-all-staked.tsx index 1797f2e..28268da 100644 --- a/src/components/unbond-all-staked.tsx +++ b/src/components/unbond-all-staked.tsx @@ -4,7 +4,6 @@ import { useCallback, useState } from "react"; import { writeContract, waitForTransaction } from "@wagmi/core"; import { notification } from "./notification"; import { getChainConfig, notifyTransaction } from "@/utils"; -import { ChainID } from "@/types"; export default function UnbondAllStaked() { const { stakedRing, stakedKton, stakedDeposits } = useStaking(); @@ -16,14 +15,9 @@ export default function UnbondAllStaked() { setBusy(true); try { - const abi = - activeChain === ChainID.CRAB - ? (await import("@/config/abi/staking-v2.json")).default - : (await import(`@/config/abi/${contract.staking.abiFile}`)).default; - const { hash } = await writeContract({ address: contract.staking.address, - abi, + abi: (await import(`@/config/abi/${contract.staking.abiFile}`)).default, functionName: "unstake", args: [stakedRing, stakedKton, stakedDeposits], }); diff --git a/src/components/unbond-deposit-modal.tsx b/src/components/unbond-deposit-modal.tsx index 2d08852..082b229 100644 --- a/src/components/unbond-deposit-modal.tsx +++ b/src/components/unbond-deposit-modal.tsx @@ -1,40 +1,24 @@ -import { Key, useCallback, useMemo, useState } from "react"; +import { Key, useCallback, useState } from "react"; import Modal from "./modal"; import CheckboxGroup from "./checkbox-group"; -import { commissionWeightedPower, formatBlanace, getChainConfig, notifyTransaction } from "@/utils"; -import { ExtraPower } from "./balance-input"; +import { formatBlanace, getChainConfig, notifyTransaction } from "@/utils"; import { useApp, useStaking } from "@/hooks"; import { notification } from "./notification"; import { writeContract, waitForTransaction } from "@wagmi/core"; -import { ChainID } from "@/types"; export default function UnbondDepositModal({ - commission, isOpen, onClose = () => undefined, }: { - commission: string; isOpen: boolean; onClose?: () => void; }) { - const { deposits, stakedDeposits, calcExtraPower } = useStaking(); + const { deposits, stakedDeposits } = useStaking(); const { activeChain } = useApp(); const [checkedDeposits, setCheckedDeposits] = useState<number[]>([]); const [busy, setBusy] = useState(false); - const extraPower = useMemo( - () => - commissionWeightedPower( - calcExtraPower( - deposits.filter(({ id }) => checkedDeposits.includes(id)).reduce((acc, cur) => acc + cur.value, 0n), - 0n - ), - commission - ), - [deposits, commission, checkedDeposits, calcExtraPower] - ); - const availableDeposits = deposits.filter(({ id }) => stakedDeposits.includes(id)); const { nativeToken } = getChainConfig(activeChain); @@ -43,14 +27,9 @@ export default function UnbondDepositModal({ const { contract, explorer } = getChainConfig(activeChain); try { - const abi = - activeChain === ChainID.CRAB - ? (await import("@/config/abi/staking-v2.json")).default - : (await import(`@/config/abi/${contract.staking.abiFile}`)).default; - const { hash } = await writeContract({ address: contract.staking.address, - abi, + abi: (await import(`@/config/abi/${contract.staking.abiFile}`)).default, functionName: "unstake", args: [0n, 0n, checkedDeposits], }); @@ -100,10 +79,6 @@ export default function UnbondDepositModal({ onChange={setCheckedDeposits as (values: Key[]) => void} className="max-h-80 overflow-y-auto" /> - - <div className="h-[1px] bg-white/20" /> - - <ExtraPower power={extraPower} powerChanges="less" /> </> ) : ( <span className="text-xs font-light text-white">No deposits to unbond</span> diff --git a/src/components/unbond-kton-modal.tsx b/src/components/unbond-kton-modal.tsx index 5cd43f5..e05df15 100644 --- a/src/components/unbond-kton-modal.tsx +++ b/src/components/unbond-kton-modal.tsx @@ -1,27 +1,24 @@ -import { commissionWeightedPower, formatBlanace, getChainConfig, notifyTransaction } from "@/utils"; +import { formatBlanace, getChainConfig, notifyTransaction } from "@/utils"; import UnbondTokenModal from "./unbond-token-modal"; import { useApp, useStaking } from "@/hooks"; import { useCallback, useState } from "react"; import { notification } from "./notification"; import { writeContract, waitForTransaction } from "@wagmi/core"; -import { ChainID } from "@/types"; export default function UnbondKtonModal({ - commission, isOpen, onClose = () => undefined, }: { - commission: string; isOpen: boolean; onClose?: () => void; }) { const { activeChain } = useApp(); - const { stakedKton, calcExtraPower } = useStaking(); + const { stakedKton } = useStaking(); const [inputAmount, setInputAmount] = useState(0n); const [busy, setBusy] = useState(false); - const { ktonToken, contract, explorer } = getChainConfig(activeChain); + const { ktonToken } = getChainConfig(activeChain); const handleUnbond = useCallback(async () => { if (stakedKton < inputAmount) { @@ -36,14 +33,9 @@ export default function UnbondKtonModal({ const { contract, explorer } = getChainConfig(activeChain); try { - const abi = - activeChain === ChainID.CRAB - ? (await import("@/config/abi/staking-v2.json")).default - : (await import(`@/config/abi/${contract.staking.abiFile}`)).default; - const { hash } = await writeContract({ address: contract.staking.address, - abi, + abi: (await import(`@/config/abi/${contract.staking.abiFile}`)).default, functionName: "unstake", args: [0n, inputAmount, []], }); @@ -69,7 +61,6 @@ export default function UnbondKtonModal({ isOpen={isOpen} symbol={ktonToken.symbol} decimals={ktonToken.decimals} - power={commissionWeightedPower(calcExtraPower(0n, inputAmount), commission)} balance={stakedKton} busy={busy} disabled={inputAmount <= 0n} diff --git a/src/components/unbond-ring-modal.tsx b/src/components/unbond-ring-modal.tsx index 9ecde7a..dc5cdae 100644 --- a/src/components/unbond-ring-modal.tsx +++ b/src/components/unbond-ring-modal.tsx @@ -1,22 +1,19 @@ -import { commissionWeightedPower, formatBlanace, getChainConfig, notifyTransaction } from "@/utils"; +import { formatBlanace, getChainConfig, notifyTransaction } from "@/utils"; import UnbondTokenModal from "./unbond-token-modal"; import { useApp, useStaking } from "@/hooks"; import { useCallback, useState } from "react"; import { notification } from "./notification"; import { writeContract, waitForTransaction } from "@wagmi/core"; -import { ChainID } from "@/types"; export default function UnbondRingModal({ - commission, isOpen, onClose = () => undefined, }: { - commission: string; isOpen: boolean; onClose?: () => void; }) { const { activeChain } = useApp(); - const { stakedRing, calcExtraPower } = useStaking(); + const { stakedRing } = useStaking(); const [inputAmount, setInputAmount] = useState(0n); const [busy, setBusy] = useState(false); @@ -36,14 +33,9 @@ export default function UnbondRingModal({ const { contract, explorer } = getChainConfig(activeChain); try { - const abi = - activeChain === ChainID.CRAB - ? (await import("@/config/abi/staking-v2.json")).default - : (await import(`@/config/abi/${contract.staking.abiFile}`)).default; - const { hash } = await writeContract({ address: contract.staking.address, - abi, + abi: (await import(`@/config/abi/${contract.staking.abiFile}`)).default, functionName: "unstake", args: [inputAmount, 0n, []], }); @@ -68,7 +60,6 @@ export default function UnbondRingModal({ isOpen={isOpen} symbol={nativeToken.symbol} decimals={nativeToken.decimals} - power={commissionWeightedPower(calcExtraPower(inputAmount, 0n), commission)} balance={stakedRing} busy={busy} disabled={inputAmount <= 0n} diff --git a/src/components/unbond-token-modal.tsx b/src/components/unbond-token-modal.tsx index 53468b0..bb934f5 100644 --- a/src/components/unbond-token-modal.tsx +++ b/src/components/unbond-token-modal.tsx @@ -6,7 +6,6 @@ export default function UnbondTokenModal({ symbol, decimals, balance, - power, busy, disabled, isReset, @@ -19,7 +18,6 @@ export default function UnbondTokenModal({ symbol: string; decimals: number; balance: bigint; - power: bigint; busy?: boolean; disabled?: boolean; isReset?: boolean; @@ -28,6 +26,8 @@ export default function UnbondTokenModal({ onClose?: () => void; onChange?: (amount: bigint) => void; }) { + const isKton = symbol.endsWith("KTON"); + return ( <Modal title={`Unbond ${symbol}`} @@ -42,7 +42,12 @@ export default function UnbondTokenModal({ disabled={disabled} > <> - <p className="text-xs font-light text-white">This unbonding process will take 14 days to complete.</p> + <p className={`text-xs font-light text-white ${isKton ? "line-through" : ""}`}> + This unbonding process will take 14 days to complete. + </p> + {isKton && ( + <p className="text-xs font-light text-white">{`There is no longer a 14-day period for unbonding ${symbol}.`}</p> + )} <div className="h-[1px] bg-white/20" /> <BalanceInput label="Amount" @@ -50,9 +55,7 @@ export default function UnbondTokenModal({ decimals={decimals} symbol={symbol} balance={balance} - power={power} isReset={isReset} - powerChanges="less" onChange={onChange} /> </> diff --git a/src/components/unbonding-token-tooltip.tsx b/src/components/unbonding-token-tooltip.tsx index b743b13..d4ce139 100644 --- a/src/components/unbonding-token-tooltip.tsx +++ b/src/components/unbonding-token-tooltip.tsx @@ -56,12 +56,14 @@ function UnbondingToken({ unbondings, token, onCancelUnbonding, onRelease }: Pro {`#${index + 1} ${formatBlanace(amount, token.decimals, { keepZero: false })} ${ token.symbol } is unbonding and will be released in ${formatDistanceStrict(expiredTimestamp, Date.now())}. `} - <EnsureMatchNetworkButton - className="font-bold text-primary" - onClick={() => onCancelUnbonding(isKton ? 0n : amount, isKton ? amount : 0n, [])} - > - Cancel Unbonding - </EnsureMatchNetworkButton> + {isKton ? null : ( + <EnsureMatchNetworkButton + className="font-bold text-primary" + onClick={() => onCancelUnbonding(isKton ? 0n : amount, isKton ? amount : 0n, [])} + > + Cancel Unbonding + </EnsureMatchNetworkButton> + )} </p> ))} </div> diff --git a/src/components/undelegate-modal.tsx b/src/components/undelegate-modal.tsx index 789f32c..3c0b425 100644 --- a/src/components/undelegate-modal.tsx +++ b/src/components/undelegate-modal.tsx @@ -21,11 +21,9 @@ export default function UndelegateModal({ isOpen, onClose = () => undefined }: P setBusy(true); try { - const contractAbi = (await import(`@/config/abi/${chainConfig.contract.staking.abiFile}`)).default; - const { hash } = await writeContract({ address: chainConfig.contract.staking.address, - abi: contractAbi, + abi: (await import(`@/config/abi/${chainConfig.contract.staking.abiFile}`)).default, functionName: "chill", args: [], }); diff --git a/src/config/abi/staking-v2.json b/src/config/abi/staking-v2.json deleted file mode 100644 index 9dbec17..0000000 --- a/src/config/abi/staking-v2.json +++ /dev/null @@ -1,167 +0,0 @@ -[ - { - "inputs": [], - "name": "chill", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "claim", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint32", - "name": "commission", - "type": "uint32" - } - ], - "name": "collect", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "target", - "type": "address" - } - ], - "name": "nominate", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "who", - "type": "address" - } - ], - "name": "payout", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "ringAmount", - "type": "uint256" - }, - { - "internalType": "uint16[]", - "name": "depositIds", - "type": "uint16[]" - } - ], - "name": "restake", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "ringAmount", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "ktonAmount", - "type": "uint256" - }, - { - "internalType": "uint16[]", - "name": "depositIds", - "type": "uint16[]" - } - ], - "name": "stake", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "ringAmount", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "ktonAmount", - "type": "uint256" - }, - { - "internalType": "uint16[]", - "name": "depositIds", - "type": "uint16[]" - } - ], - "name": "unstake", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "nonpayable", - "type": "function" - } -] diff --git a/src/config/abi/staking.json b/src/config/abi/staking.json index a6d10b4..9dbec17 100644 --- a/src/config/abi/staking.json +++ b/src/config/abi/staking.json @@ -5,7 +5,7 @@ "outputs": [ { "internalType": "bool", - "name": "result", + "name": "", "type": "bool" } ], @@ -18,7 +18,7 @@ "outputs": [ { "internalType": "bool", - "name": "result", + "name": "", "type": "bool" } ], @@ -37,7 +37,7 @@ "outputs": [ { "internalType": "bool", - "name": "result", + "name": "", "type": "bool" } ], @@ -56,7 +56,7 @@ "outputs": [ { "internalType": "bool", - "name": "result", + "name": "", "type": "bool" } ], @@ -66,26 +66,40 @@ { "inputs": [ { - "internalType": "uint256", - "name": "ring_amount", - "type": "uint256" - }, + "internalType": "address", + "name": "who", + "type": "address" + } + ], + "name": "payout", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ { "internalType": "uint256", - "name": "kton_amount", + "name": "ringAmount", "type": "uint256" }, { - "internalType": "uint8[]", - "name": "deposits", - "type": "uint8[]" + "internalType": "uint16[]", + "name": "depositIds", + "type": "uint16[]" } ], "name": "restake", "outputs": [ { "internalType": "bool", - "name": "result", + "name": "", "type": "bool" } ], @@ -96,25 +110,25 @@ "inputs": [ { "internalType": "uint256", - "name": "ring_amount", + "name": "ringAmount", "type": "uint256" }, { "internalType": "uint256", - "name": "kton_amount", + "name": "ktonAmount", "type": "uint256" }, { - "internalType": "uint8[]", - "name": "deposits", - "type": "uint8[]" + "internalType": "uint16[]", + "name": "depositIds", + "type": "uint16[]" } ], "name": "stake", "outputs": [ { "internalType": "bool", - "name": "result", + "name": "", "type": "bool" } ], @@ -125,25 +139,25 @@ "inputs": [ { "internalType": "uint256", - "name": "ring_amount", + "name": "ringAmount", "type": "uint256" }, { "internalType": "uint256", - "name": "kton_amount", + "name": "ktonAmount", "type": "uint256" }, { - "internalType": "uint8[]", - "name": "deposits", - "type": "uint8[]" + "internalType": "uint16[]", + "name": "depositIds", + "type": "uint16[]" } ], "name": "unstake", "outputs": [ { "internalType": "bool", - "name": "result", + "name": "", "type": "bool" } ], diff --git a/src/config/chains/crab.ts b/src/config/chains/crab.ts index b179c66..3301000 100644 --- a/src/config/chains/crab.ts +++ b/src/config/chains/crab.ts @@ -20,10 +20,6 @@ export const crabChainConfig: ChainConfig = { name: "Dwellir", url: "https://darwiniacrab-rpc.dwellir.com", }, - // { - // name: "OnFinality", - // url: "https://crab.api.onfinality.io/public-rpc", - // }, ], nativeToken: { symbol: "CRAB", @@ -54,4 +50,5 @@ export const crabChainConfig: ChainConfig = { https: "https://crab-rpc.darwinia.network", }, }, + logo: "crab.png", }; diff --git a/src/config/chains/darwinia.ts b/src/config/chains/darwinia.ts index c8810bb..85ffd57 100644 --- a/src/config/chains/darwinia.ts +++ b/src/config/chains/darwinia.ts @@ -52,4 +52,5 @@ export const darwiniaChainConfig: ChainConfig = { https: "https://rpc.darwinia.network", }, }, + logo: "darwinia.png", }; diff --git a/src/providers/rainbow-provider.tsx b/src/providers/rainbow-provider.tsx index abc1260..190759a 100644 --- a/src/providers/rainbow-provider.tsx +++ b/src/providers/rainbow-provider.tsx @@ -2,51 +2,46 @@ import "@rainbow-me/rainbowkit/styles.css"; import { getDefaultWallets, RainbowKitProvider, connectorsForWallets, darkTheme } from "@rainbow-me/rainbowkit"; -import { trustWallet, imTokenWallet, okxWallet, talismanWallet, safeWallet } from "@rainbow-me/rainbowkit/wallets"; +import { okxWallet, talismanWallet, safeWallet, rabbyWallet } from "@rainbow-me/rainbowkit/wallets"; import { configureChains, createConfig, WagmiConfig } from "wagmi"; import { publicProvider } from "wagmi/providers/public"; import { APP_NAME_CONF } from "@/config"; import { getChainConfigs } from "@/utils"; -import { PropsWithChildren, useEffect, useState } from "react"; -import { useApp } from "@/hooks"; +import { PropsWithChildren } from "react"; const projectId = process.env.NEXT_PUBLIC_WALLET_CONNECT_ID || ""; - -export function RainbowProvider({ children }: PropsWithChildren<unknown>) { - const [mounted, setMounted] = useState(true); // temporarity set to true - const { activeChain, activeRpc } = useApp(); - - const { chains, publicClient } = configureChains( - getChainConfigs().map(({ chainId, name, nativeToken, explorer }) => ({ - id: chainId, - name, - network: name.toLowerCase().split(" ").join("-"), - nativeCurrency: { - name: nativeToken.symbol, - symbol: nativeToken.symbol, - decimals: nativeToken.decimals, +const { chains, publicClient } = configureChains( + getChainConfigs().map(({ chainId, name, nativeToken, explorer, rpcMetas }) => ({ + id: chainId, + name, + network: name.toLowerCase().split(" ").join("-"), + nativeCurrency: { + name: nativeToken.symbol, + symbol: nativeToken.symbol, + decimals: nativeToken.decimals, + }, + rpcUrls: { + default: { + http: rpcMetas.filter(({ url }) => url.startsWith("http")).map(({ url }) => url), + webSocket: rpcMetas.filter(({ url }) => url.startsWith("ws")).map(({ url }) => url), }, - rpcUrls: { - default: { - http: activeRpc.url.startsWith("http") ? [activeRpc.url] : [], - webSocket: activeRpc.url.startsWith("ws") ? [activeRpc.url] : [], - }, - public: { - http: activeRpc.url.startsWith("http") ? [activeRpc.url] : [], - webSocket: activeRpc.url.startsWith("ws") ? [activeRpc.url] : [], - }, + public: { + http: rpcMetas.filter(({ url }) => url.startsWith("http")).map(({ url }) => url), + webSocket: rpcMetas.filter(({ url }) => url.startsWith("ws")).map(({ url }) => url), }, - blockExplorers: { - default: { - url: explorer.url, - name: explorer.name, - }, + }, + blockExplorers: { + default: { + url: explorer.url, + name: explorer.name, }, - })), - [publicProvider()] - ); + }, + })), + [publicProvider()] +); +export function RainbowProvider({ children }: PropsWithChildren<unknown>) { const { wallets } = getDefaultWallets({ appName: APP_NAME_CONF, projectId, @@ -58,10 +53,9 @@ export function RainbowProvider({ children }: PropsWithChildren<unknown>) { { groupName: "More", wallets: [ - talismanWallet({ chains }), okxWallet({ projectId, chains }), - imTokenWallet({ projectId, chains }), - trustWallet({ projectId, chains }), + rabbyWallet({ chains }), + talismanWallet({ chains }), safeWallet({ chains }), ], }, @@ -73,17 +67,14 @@ export function RainbowProvider({ children }: PropsWithChildren<unknown>) { publicClient, }); - useEffect(() => setMounted(true), []); - return ( <WagmiConfig config={wagmiConfig}> <RainbowKitProvider theme={darkTheme({ borderRadius: "none", accentColor: "#FF0083" })} chains={chains} appInfo={{ appName: APP_NAME_CONF }} - initialChain={activeChain} > - {mounted && children} + {children} </RainbowKitProvider> </WagmiConfig> ); diff --git a/src/types/chain.ts b/src/types/chain.ts index e347722..141bf6f 100644 --- a/src/types/chain.ts +++ b/src/types/chain.ts @@ -51,5 +51,6 @@ export interface ChainConfig { }; secondsPerBlock: number; substrate: Substrate; + logo: string; // File name isTestNet?: boolean; } diff --git a/vercel.json b/vercel.json index 4a91e26..418a8a0 100644 --- a/vercel.json +++ b/vercel.json @@ -11,10 +11,6 @@ "key": "Referrer-Policy", "value": "origin-when-cross-origin" }, - { - "key": "X-Frame-Options", - "value": "SAMEORIGIN" - }, { "key": "Strict-Transport-Security", "value": "max-age=2592000" @@ -22,10 +18,5 @@ ] } ], - "rewrites": [ - { - "source": "/(.*)", - "destination": "/index.html" - } - ] + "rewrites": [{ "source": "/(.*)", "destination": "/index.html" }] }