Skip to content
This repository has been archived by the owner on Nov 28, 2024. It is now read-only.

Commit

Permalink
Add kton staking migration logic and instruction in koi testnet (#20)
Browse files Browse the repository at this point in the history
* Add kton migrate in koi testnet

* Add instruction

* Remove useless change

* Rename files

* Fix lint issues

* Update src/config/v2migrate.ts

* Update src/config/v2migrate.ts

* Update src/config/v2migrate.ts

---------

Co-authored-by: Gege Li <[email protected]>
Co-authored-by: fisher <[email protected]>
  • Loading branch information
3 people authored Aug 20, 2024
1 parent a787209 commit 041fa70
Show file tree
Hide file tree
Showing 18 changed files with 429 additions and 79 deletions.
Binary file added public/images/chains/koi.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
129 changes: 74 additions & 55 deletions src/app/(defi)/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,13 @@ import { motion, AnimatePresence } from 'framer-motion';

import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { Separator } from '@/components/ui/separator';
import { menuItems } from '@/config/menu';
import { menuItemsV1, menuItemsV2 } from '@/config/menu';
import KTONActionLoading from '@/components/kton-action-loading';
import ClaimLoading from '@/components/claim-loading';
import { cn } from '@/lib/utils';
import { useChain } from '@/hooks/useChain';
import MigrationPopover from '@/components/migration-popover';
import { v2ChainId } from '@/config/v2migrate';

const Stake = dynamic(() => import('@/components/stake'), {
ssr: false,
Expand All @@ -23,15 +26,25 @@ const Claim = dynamic(() => import('@/components/claim'), {
ssr: false,
loading: () => <ClaimLoading />
});
const Migrate = dynamic(() => import('@/components/migrate'), {
ssr: false,
loading: () => <ClaimLoading />
})

const transactionActiveClassName = 'opacity-50 cursor-default pointer-events-none';
const defaultValue = menuItems[0]?.key;

const DefiTabs = () => {
const pathname = usePathname();
const router = useRouter();
const searchParams = useSearchParams();


const { activeChain } = useChain();
const isV2 = v2ChainId.includes(activeChain.id);
const menuItems = isV2 ? menuItemsV2 : menuItemsV1;

const defaultValue = menuItems[0]?.key;

const [isTransactionActive, setIsTransactionActive] = useState(false);

const value = searchParams.get('type') || defaultValue;
Expand All @@ -43,60 +56,66 @@ const DefiTabs = () => {
};

return (
<Tabs defaultValue={defaultValue} className="w-full" value={value}>
<TabsList className="flex h-6 items-center justify-start gap-5 self-stretch bg-transparent">
{menuItems.map((item) => (
<TabsTrigger
asChild
value={item.key}
key={item.key}
className="line-clamp-6 bg-transparent p-0 text-[0.875rem] font-bold text-white data-[state=active]:bg-transparent data-[state=active]:text-primary data-[state=active]:shadow-none"
>
<span
className={cn(
'transition-colors',
'hover:text-primary/80',
'active:text-primary/60',
'cursor-pointer',
isTransactionActive ? transactionActiveClassName : ''
)}
onClick={() => handleClick(item.key)}
<>
<Tabs defaultValue={defaultValue} className="w-full" value={value}>
<TabsList className="flex h-6 items-center justify-start gap-5 self-stretch bg-transparent">
{menuItems.map((item) => (
<TabsTrigger
asChild
value={item.key}
key={item.key}
className="line-clamp-6 bg-transparent p-0 text-[0.875rem] font-bold text-white data-[state=active]:bg-transparent data-[state=active]:text-primary data-[state=active]:shadow-none"
>
{item.label}
</span>
</TabsTrigger>
))}
</TabsList>
<Separator className="my-5 bg-white/20" />
<AnimatePresence>
<Suspense fallback={<div>Loading...</div>}>
{menuItems.map(
(item) =>
value === item.key && (
<TabsContent key={item.key} value={item.key} className="mt-0">
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.5, ease: 'easeIn' }}
className="flex flex-col gap-5"
>
{value === 'stake' && (
<Stake onTransactionActiveChange={setIsTransactionActive} />
)}
{value === 'unstake' && (
<UnStakes onTransactionActiveChange={setIsTransactionActive} />
)}
{value === 'claim' && (
<Claim onTransactionActiveChange={setIsTransactionActive} />
)}
</motion.div>
</TabsContent>
)
)}
</Suspense>
</AnimatePresence>
</Tabs>
<span
className={cn(
'transition-colors',
'hover:text-primary/80',
'active:text-primary/60',
'cursor-pointer',
isTransactionActive ? transactionActiveClassName : ''
)}
onClick={() => handleClick(item.key)}
>
{item.label}
</span>
</TabsTrigger>
))}
</TabsList>
<Separator className="my-5 bg-white/20" />
<AnimatePresence>
<Suspense fallback={<div>Loading...</div>}>
{menuItems.map(
(item) =>
value === item.key && (
<TabsContent key={item.key} value={item.key} className="mt-0">
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.5, ease: 'easeIn' }}
className="flex flex-col gap-5"
>
{value === 'stake' && (
<Stake onTransactionActiveChange={setIsTransactionActive} />
)}
{value === 'unstake' && (
<UnStakes onTransactionActiveChange={setIsTransactionActive} />
)}
{value === 'claim' && (
<Claim onTransactionActiveChange={setIsTransactionActive} />
)}
{value === 'migrate' && (
<Migrate onTransactionActiveChange={setIsTransactionActive} />
)}
</motion.div>
</TabsContent>
)
)}
</Suspense>
</AnimatePresence>
</Tabs>
{isV2 && <MigrationPopover />}
</>
);
};

Expand Down
6 changes: 2 additions & 4 deletions src/components/claim.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,9 @@ import { useClaim } from '@/hooks/useClaim';
import { abi } from '@/config/abi/KTONStakingRewards';
import { useBigIntContractQuery } from '@/hooks/useBigIntContractQuery';
import { useClaimState } from '@/hooks/useClaimState';
import { ActionProps } from '@/types/action';

type ClaimProps = {
onTransactionActiveChange?: (isTransaction: boolean) => void;
};
const Claim = ({ onTransactionActiveChange }: ClaimProps) => {
const Claim = ({ onTransactionActiveChange }: ActionProps) => {
const { address, isConnected } = useAccount();
const { isSupportedChainId, activeChain } = useChain();

Expand Down
2 changes: 1 addition & 1 deletion src/components/loading/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,4 @@
transform: scale(0.5);
opacity: 0.3;
}
}
}
112 changes: 112 additions & 0 deletions src/components/migrate.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import { useAccount } from "wagmi";
import { useEffect } from "react";

import { useBigIntContractQuery } from "@/hooks/useBigIntContractQuery";
import { useChain } from "@/hooks/useChain";
import { ActionProps } from "@/types/action";
import { abi } from '@/config/abi/KTONStakingRewards';
import { usePoolAmount } from "@/hooks/usePoolAmount";
import { useMigrate } from "@/hooks/useMigrate";
import { useMigrateState } from "@/hooks/useMigrateState";
import { formatNumericValue } from "@/utils";
import { Button } from '@/components/ui/button';
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';

import Loading from "./loading";

const newPoolButtonText = "Stake in new pool";
const newPoolUrl = "https://kton-staking-v2.darwinia.network"

const Migrate = ({ onTransactionActiveChange }: ActionProps) => {
const { address, isConnected } = useAccount();
const { isSupportedChainId, activeChain } = useChain();
const { refetch: refetchPoolAmount } = usePoolAmount();

const { value, formatted, isLoadingOrRefetching, refetch: refetchBalance } = useBigIntContractQuery({
contractAddress: activeChain.stakingContractAddress,
abi,
functionName: 'balanceOf',
args: [address!],
forceEnabled: isSupportedChainId
});

const { migrate, isMigrating, isTransactionConfirming } = useMigrate({
ownerAddress: address!,
onSuccess() {
refetchBalance();
refetchPoolAmount();
}
});

const { buttonText, isButtonDisabled, isFetching } = useMigrateState({
isConnected,
isSupportedChainId,
isMigrating,
isTransactionConfirming,
isLoadingOrRefetching,
value
});

const balanceAmount = formatNumericValue(formatted);

const openInNewTab = (url: string) => {
window.open(url, '_blank', 'noopener,noreferrer');
}

useEffect(() => {
const isActive = isMigrating || isTransactionConfirming;
onTransactionActiveChange && onTransactionActiveChange(isActive);
}, [isMigrating, isTransactionConfirming, onTransactionActiveChange]);

return (
<div>
<div className="flex h-[4.5rem] items-center justify-center gap-3 self-stretch rounded-[0.3125rem] bg-[#1A1D1F]">
{isLoadingOrRefetching ? (
<Loading className="ml-2 gap-1" itemClassName="size-2" />
) : (
<TooltipProvider>
<Tooltip delayDuration={0}>
<TooltipTrigger asChild>
<div>
<span className=" text-[1.5rem] font-bold leading-normal text-white">
{balanceAmount?.integerPart}
</span>
{balanceAmount?.decimalPart ? (
<span className=" text-[1.5rem] font-bold leading-normal text-white/50">
.{balanceAmount?.decimalPart}
</span>
) : null}
</div>
</TooltipTrigger>
<TooltipContent asChild>
<span>{balanceAmount?.originalFormatNumberWithThousandsSeparator}</span>
</TooltipContent>
</Tooltip>
</TooltipProvider>
)}
<span className=" text-[1.5rem] font-bold leading-normal text-white">
{activeChain?.ktonToken.symbol}
</span>
</div>
<Button
disabled={isButtonDisabled}
isLoading={isMigrating || isTransactionConfirming}
type="button"
onClick={migrate}
className="mt-5 w-full rounded-[0.3125rem] text-[0.875rem] text-white"
>
{isFetching ? <span className="animate-pulse"> {buttonText}</span> : buttonText}
</Button>
<Button
type="button"
onClick={() => openInNewTab(newPoolUrl)}
className="mt-5 w-full rounded-[0.3125rem] text-[0.875rem] text-white"
>
{newPoolButtonText}
</Button>
</div>
);

}

export default Migrate;
34 changes: 34 additions & 0 deletions src/components/migration-popover.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
"use client";
import { useState } from 'react';

import { cn } from '@/lib/utils';

import { Button } from './ui/button';

const title = "We are migrating";
const instruction = "Please migrate to new KTON Pool and get your gKTON reward. To migrate, please follow steps below:"
const stepContents = [`Step 1:Click \"Unstake\" button to unstake your KTON`, `Step 2:Click \"Stake in new pool\" button to stake your KTON`];
const buttonText = "Start Migration";

export default function MigrationPopover() {
const [open, setOpen] = useState(true);

return (
open && <div className="absolute h-[360px] w-[480px] flex justify-center items-center rounded-[20px] bg-[#242A2E]">
<div className='h-full w-full px-[20px] font-mono'>
<h1 className='text-center font-extrabold py-[15px]'>{title}</h1>
<div className='h-[1px] bg-[rgba(255,255,255,0.1)]'></div>
<div className='py-[20px]'>
<p className='text-sm py-[10px]'>{instruction}</p>
{stepContents.map((content, index) => {
return <p className='text-center text-sm py-[10px] bg-[rgba(18,22,25)] mb-[10px]' key={index} style={{wordSpacing: '-3px', letterSpacing: '-0.5px'}}>{content}</p>
})}
</div>
<div className='h-[1px] bg-[rgba(255,255,255,0.1)]'></div>
<Button onClick={() => setOpen(false)} type="submit"
className={cn('font-extrabold mt-5 w-full rounded-[0.3125rem] text-[0.875rem] text-white')}
>{buttonText}</Button>
</div>
</div>
);
}
6 changes: 2 additions & 4 deletions src/components/stake.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,14 @@ import { useTokenAllowanceAndApprove } from '@/hooks/useTokenAllowanceAndApprove
import { useStake } from '@/hooks/useStake';
import { usePoolAmount } from '@/hooks/usePoolAmount';
import { useStakeState } from '@/hooks/useStakeState';
import { ActionProps } from '@/types/action';

import AmountInputForm from './amount-input-form';
import KTONBalance from './kton-balance';

import type { Form, SubmitData } from './amount-input-form';

type StakeProps = {
onTransactionActiveChange?: (isTransaction: boolean) => void;
};
const Stake = ({ onTransactionActiveChange }: StakeProps) => {
const Stake = ({ onTransactionActiveChange }: ActionProps) => {
const formRef: MutableRefObject<Form | null> = useRef(null);
const amountRef: MutableRefObject<bigint> = useRef(0n);
const [amount, setAmount] = useState<bigint>(0n);
Expand Down
18 changes: 8 additions & 10 deletions src/components/unstake.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,24 @@
'use client';
import { useAccount } from 'wagmi';
import { parseEther } from 'viem';
import { MutableRefObject, useCallback, useEffect, useRef, useState } from 'react';
import { parseEther } from 'viem';
import { useAccount } from 'wagmi';

import { cn } from '@/lib/utils';
import { Button } from '@/components/ui/button';
import { useChain } from '@/hooks/useChain';
import { useUnStake } from '@/hooks/useUnStake';
import { usePoolAmount } from '@/hooks/usePoolAmount';
import { abi } from '@/config/abi/KTONStakingRewards';
import { useBigIntContractQuery } from '@/hooks/useBigIntContractQuery';
import { useChain } from '@/hooks/useChain';
import { usePoolAmount } from '@/hooks/usePoolAmount';
import { useUnStake } from '@/hooks/useUnStake';
import { useUnStakeState } from '@/hooks/useUnstakeState';
import { cn } from '@/lib/utils';
import { ActionProps } from '@/types/action';

import AmountInputForm from './amount-input-form';
import KTONBalance from './kton-balance';

import type { Form, SubmitData } from './amount-input-form';

type UnStakeProps = {
onTransactionActiveChange?: (isTransaction: boolean) => void;
};
const UnStake = ({ onTransactionActiveChange }: UnStakeProps) => {
const UnStake = ({ onTransactionActiveChange }: ActionProps) => {
const formRef: MutableRefObject<Form | null> = useRef(null);
const [amount, setAmount] = useState<bigint>(0n);
const { address, isConnected } = useAccount();
Expand Down
1 change: 1 addition & 0 deletions src/config/chains/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './crab';
export * from './darwinia';
export * from './koi';
Loading

0 comments on commit 041fa70

Please sign in to comment.