diff --git a/apps/app/components/ClaimKhalaAssets.tsx b/apps/app/components/ClaimKhalaAssets.tsx index f0b83c7c..dd21aae4 100644 --- a/apps/app/components/ClaimKhalaAssets.tsx +++ b/apps/app/components/ClaimKhalaAssets.tsx @@ -1,10 +1,14 @@ import khalaClaimerAbi from '@/assets/khala_claimer_abi' +import Property from '@/components/Property' +import SwitchChainButton from '@/components/SwitchChainButton' import { khalaAssetsApi, khalaClaimerAddress, useClaimStatus, useKhalaAssetsQuery, } from '@/hooks/khalaAssets' +import {useSharePrice} from '@/hooks/staking' +import {useAutoSwitchChain} from '@/hooks/useAutoSwitchChain' import {walletDialogOpenAtom} from '@/store/ui' import {CheckCircleOutline, ContentCopy} from '@mui/icons-material' import {LoadingButton} from '@mui/lab' @@ -14,8 +18,10 @@ import { Divider, Link, Paper, - Skeleton, Stack, + Step, + StepLabel, + Stepper, TextField, Typography, } from '@mui/material' @@ -32,12 +38,23 @@ import NextLink from 'next/link' import {useSnackbar} from 'notistack' import {useEffect, useMemo, useState} from 'react' import type {Hex} from 'viem' -import {mainnet, sepolia} from 'viem/chains' import {useAccount, useWaitForTransactionReceipt, useWriteContract} from 'wagmi' -import Property from './Property' -import SwitchChainButton from './SwitchChainButton' -const targetChain = process.env.VERCEL_ENV === 'production' ? mainnet : sepolia +const Steps = () => { + return ( + + + Sign with Khala account + + + Connect Ethereum wallet + + + Claim PHA on Ethereum + + + ) +} export const CheckKhalaAssets = ({ onCheck, @@ -57,10 +74,9 @@ export const CheckKhalaAssets = ({ } return ( <> - - - + ) } @@ -86,12 +102,12 @@ export const CheckKhalaAssets = ({ const ClaimKhalaAssets = () => { const {enqueueSnackbar} = useSnackbar() const setWalletDialogOpen = useSetAtom(walletDialogOpenAtom) - const [loading, setLoading] = useState(false) + const [isSigning, setIsSigning] = useState(false) const [checkAddress, setCheckAddress] = useState( undefined, ) - const [selected, setSelected] = useState(false) - const [accepted, setAccepted] = useState(false) + useAutoSwitchChain() + const sharePrice = useSharePrice() const {address: ethAddress, chain: ethChain} = useAccount() const [polkadotAccount] = useAtom(polkadotAccountAtom) const address = checkAddress ?? polkadotAccount?.address @@ -104,50 +120,35 @@ const ClaimKhalaAssets = () => { const h160 = u8aToHex(publicKey).slice(0, 42) as Hex return h160 }, [address]) - const {claimed, logs} = useClaimStatus(h160Address) + const {claimed, log, refetch} = useClaimStatus(h160Address) const tx = useMemo(() => { - if ( - ethChain?.blockExplorers?.default == null || - logs?.[0]?.transactionHash == null - ) { + if (ethChain?.blockExplorers?.default == null || log == null) { return undefined } - const txHash = logs[0].transactionHash + const txHash = log.transactionHash return { - url: `https://${ethChain.blockExplorers.default.url}/tx/${txHash}`, + url: `${ethChain.blockExplorers.default.url}/tx/${txHash}`, hash: txHash, trimmedHash: trimAddress(txHash, 6, 6), } - }, [ethChain, logs]) - const total = useMemo(() => { - return data && Decimal.add(data.free, data.staked) - }, [data]) + }, [ethChain, log]) - const {data: hash, writeContract} = useWriteContract() - const { - isLoading: isConfirming, - isSuccess: isConfirmed, - isError, - } = useWaitForTransactionReceipt({hash}) + const {data: hash, writeContract, isPending, reset} = useWriteContract() + const claimResult = useWaitForTransactionReceipt({hash}) - useEffect(() => { - if (isConfirmed) { - setLoading(false) - enqueueSnackbar('Claimed successfully', {variant: 'success'}) - } - }, [isConfirmed, enqueueSnackbar]) + const isLoading = isPending || claimResult.isLoading || isSigning useEffect(() => { - if (isError) { - setLoading(false) - enqueueSnackbar('Failed to claim', {variant: 'error'}) + if (claimResult.data?.status === 'success') { + enqueueSnackbar('Claimed successfully', {variant: 'success'}) + reset() + refetch() } - }, [isError, enqueueSnackbar]) + }, [claimResult.data?.status, enqueueSnackbar, reset, refetch]) const handleSubmit = async (e: React.FormEvent) => { e.preventDefault() - if (!ethAddress) { return } @@ -156,9 +157,9 @@ const ClaimKhalaAssets = () => { if (signRaw == null || polkadotAccount == null || h160Address == null) { return } - setLoading(true) let polkadotSignature: Hex try { + setIsSigning(true) const {signature} = await signRaw({ address: polkadotAccount.address, data: stringToHex(`Khala Asset Receiver: ${receiver}`), @@ -166,7 +167,7 @@ const ClaimKhalaAssets = () => { }) polkadotSignature = signature } catch (e) { - setLoading(false) + setIsSigning(false) return } @@ -187,8 +188,9 @@ const ClaimKhalaAssets = () => { args: [h160, BigInt(free), BigInt(staked), receiver, signature], }) } catch (e) { - setLoading(false) return enqueueSnackbar('Failed to claim', {variant: 'error'}) + } finally { + setIsSigning(false) } } @@ -200,12 +202,30 @@ const ClaimKhalaAssets = () => { } } + const rewards = useMemo(() => { + if (sharePrice == null || data?.staked == null) { + return + } + return new Decimal(sharePrice.toString()) + .div(1e18) + .minus(1) + .mul(data.staked) + }, [sharePrice, data?.staked]) + + const total = useMemo(() => { + if (data == null || rewards == null) { + return + } + return Decimal.add(data.free, data.staked).add(rewards) + }, [data, rewards]) + if (address == null) { return ( - + Connect wallet to claim ) : ( <> + { Claim diff --git a/apps/app/components/Staking/Stake.tsx b/apps/app/components/Staking/Stake.tsx index 282dd130..499de0d3 100644 --- a/apps/app/components/Staking/Stake.tsx +++ b/apps/app/components/Staking/Stake.tsx @@ -247,14 +247,23 @@ const Stake = () => { balance, ]) - const dailyReward = useMemo(() => { - if (!isStake || rewardRate == null || totalAssets == null) { + const dailyRewards = useMemo(() => { + if ( + !isStake || + rewardRate == null || + totalAssets == null || + totalAssets === 0n || + amount == null + ) { return null } - return Decimal.mul(rewardRate.toString(), 24 * 60 * 60).div( - totalAssets.toString(), - ) - }, [isStake, rewardRate, totalAssets]) + return Decimal.mul(rewardRate.toString(), 24 * 60 * 60) + .mul(amount.toString()) + .div((totalAssets + amount).toString()) + .toDP(0) + .div(1e18) + .toString() + }, [isStake, rewardRate, totalAssets, amount]) return ( @@ -400,8 +409,18 @@ const Stake = () => { /> - + + {isStake && ( + + {dailyRewards != null ? toCurrency(dailyRewards) : '-'} + + )} {shares != null ? toCurrency(formatUnits(shares, 18)) : '-'} diff --git a/apps/app/components/Staking/Unstake.tsx b/apps/app/components/Staking/Unstake.tsx index 71d32ede..61f1e790 100644 --- a/apps/app/components/Staking/Unstake.tsx +++ b/apps/app/components/Staking/Unstake.tsx @@ -151,7 +151,7 @@ const Unstake = () => { }, [unlockRequests?.length]) return ( - + Unlock Requests {maxUnlockRequests != null && unlockRequests != null && ( diff --git a/apps/app/components/Staking/index.tsx b/apps/app/components/Staking/index.tsx index 6f2c4adf..c0e231e7 100644 --- a/apps/app/components/Staking/index.tsx +++ b/apps/app/components/Staking/index.tsx @@ -50,6 +50,12 @@ const Staking = () => { + + + {assets != null ? toCurrency(formatUnits(assets, 18)) : '-'} + + + {totalAssets != null @@ -57,18 +63,13 @@ const Staking = () => { : '-'} - - - {assets != null ? toCurrency(formatUnits(assets, 18)) : '-'} - - - + - + diff --git a/apps/app/components/TopBar/index.tsx b/apps/app/components/TopBar/index.tsx index bf9de4df..a495ba3e 100644 --- a/apps/app/components/TopBar/index.tsx +++ b/apps/app/components/TopBar/index.tsx @@ -238,7 +238,7 @@ const TopBar: FC = () => { - Claim Khala assets + Claim Khala Assets {isPolkadot && } { } export const useClaimStatus = (address?: Hex) => { - const {data: claimed} = useReadContract({ + const {data: claimed, refetch} = useReadContract({ address: khalaClaimerAddress, abi: khalaClaimerAbi, functionName: 'claimed', args: address && [address], - query: {enabled: Boolean(address)}, + query: { + enabled: Boolean(address), + refetchInterval: (query) => (query.state.data ? false : 3000), + }, }) const publicClient = usePublicClient() - const {data: logs} = useQuery({ + const {data: log} = useQuery({ queryKey: ['khala-claim-logs', address, publicClient?.chain.id], queryFn: async () => { - const logs = await publicClient?.getLogs({ + if (publicClient == null) { + return + } + const event = khalaClaimerAbi.find( + (item) => item.type === 'event' && item.name === 'Claimed', + ) + if (event == null) { + return + } + const logs = await publicClient.getLogs({ address: khalaClaimerAddress, - event: khalaClaimerAbi.find( - (item) => item.type === 'event' && item.name === 'Claimed', - ), + event, args: {user: address}, + fromBlock: 0n, }) - return logs + return logs[0] }, - enabled: claimed === true, + enabled: claimed === true && publicClient != null && address != null, }) return { claimed, - logs, + log, + refetch, } } diff --git a/apps/app/pages/khala-assets.tsx b/apps/app/pages/khala-assets.tsx index 9f291112..d0e6d807 100644 --- a/apps/app/pages/khala-assets.tsx +++ b/apps/app/pages/khala-assets.tsx @@ -11,11 +11,39 @@ const Page = ({ return ( Claim Khala Assets - + Claim Khala Assets - + + + + FAQ + + How to claim with multisig account? + + + Due to the multisig account's inability to verify signatures, + please contact us on Discord, and we will assist you manually with + the claim. + + + How to claim with Ledger or other hardware wallets? + + + We are currently testing the Ledger hardware wallet, you can claim + it anytime once it's ready. + + + I can't connect my Khala wallet and Ethereum wallet at the same + time. + + + We are preparing a new version to facilitate claiming with + different devices. + + + )