Skip to content

Commit

Permalink
🔧 Update validation
Browse files Browse the repository at this point in the history
  • Loading branch information
DonFungible committed Feb 13, 2025
1 parent 31f8487 commit 9240d73
Show file tree
Hide file tree
Showing 11 changed files with 108 additions and 37 deletions.
8 changes: 8 additions & 0 deletions src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,14 @@
}
}

@layer base {
input[type='number']::-webkit-inner-spin-button,
input[type='number']::-webkit-outer-spin-button {
-webkit-appearance: none;
margin: 0;
}
}

textarea:focus,
input:focus {
outline: none;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ export default function ActivityTable(props: { delegatorEvmAddr: Address }) {
const { chain } = useAccount()
const [sorting, setSorting] = useState<SortingState>([])
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([])
const explorerUrl = process.env.NEXT_PUBLIC_TESTNET_EXPLORER_URL

const [{ pageIndex, pageSize }, setPagination] = useState({
pageIndex: 0,
Expand Down Expand Up @@ -119,7 +118,7 @@ export default function ActivityTable(props: { delegatorEvmAddr: Address }) {
<TableCell className="text-center">{operation.block_height}</TableCell>
<TableCell className="text-center">
<Link
href={`${explorerUrl}/tx/${operation.tx_hash}`}
href={`${process.env.NEXT_PUBLIC_EXPLORER_URL}/tx/${operation.tx_hash}`}
className="flex flex-row justify-center gap-2"
>
{truncateAddress(operation.tx_hash)}
Expand Down
2 changes: 1 addition & 1 deletion src/components/HeaderWithSortArrows.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export default function HeaderWithSortArrows<T>({

return (
<div className={cn('flex flex-row', className)}>
<p className="text-center lg:text-xl">{header}</p>
<div className="text-center lg:text-xl">{header}</div>
<ArrowUp
className={cn(
'my-auto h-4 w-4 stroke-1 hover:cursor-pointer hover:stroke-2 active:stroke-2',
Expand Down
2 changes: 1 addition & 1 deletion src/components/WagmiProviderWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export const storyMainnet = defineChain({
},
rpcUrls: {
default: {
http: [process.env.NEXT_PUBLIC_CHAIN_RPC_URL],
http: ['https://mainnet.storyrpc.io'],
},
},
blockExplorers: {
Expand Down
6 changes: 1 addition & 5 deletions src/components/buttons/ViewTransaction.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,8 @@ export type ViewTransactionProps = {
}

export default function ViewTransaction(props: ViewTransactionProps) {
const { chain } = useAccount()

const explorerUrl = process.env.NEXT_PUBLIC_EXPLORER_URL

return (
<Link href={`${explorerUrl}/tx/${props.txHash}`} target="_blank">
<Link href={`${process.env.NEXT_PUBLIC_EXPLORER_URL}/tx/${props.txHash}`} target="_blank">
<p className="font-medium text-blue-600 hover:underline dark:text-blue-500">View transaction</p>
</Link>
)
Expand Down
54 changes: 36 additions & 18 deletions src/components/forms/StakeForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,14 @@ import { useAllValidators } from '@/lib/services/hooks/useAllValidators'
import { useIsSmallDevice } from '@/lib/services/hooks/useIsSmallDevice'
import { getValidator } from '@/lib/services/api/validatorApi'
import { STAKING_PERIODS } from '@/lib/constants'
import { useNetworkStakingParams } from '@/lib/services/hooks/useNetworkStakingParams'
import { useDelegatorPeriodDelegations } from '@/lib/services/hooks/useDelegatorPeriodDelegations'

const createFormSchema = ({
minStakeAmount,
balance,
}: {
minStakeAmount: bigint | undefined
minStakeAmount: string | undefined
balance: bigint | undefined
}) =>
z.object({
Expand All @@ -40,21 +42,28 @@ const createFormSchema = ({
(value): value is string => {
if (!balance) return false
const amount = parseFloat(value)
// return !isNaN(amount) && amount >= parseFloat(formatEther(minStakeAmount))
return !isNaN(amount)
return amount <= parseFloat(formatEther(balance))
},
{
message: `Must input a valid amount ${minStakeAmount ? `(minimum: ${formatEther(minStakeAmount)})` : ''}`,
message: 'Insufficient balance',
}
)
.refine(
(value): value is string => {
if (!balance) return false
if (!balance || !minStakeAmount) return false
const amount = parseFloat(value)
return amount <= parseFloat(formatEther(balance))
return amount >= parseFloat(minStakeAmount)
},
{
message: 'Insufficient balance',
message: `Amount below minimum stake amount ${minStakeAmount ? `(${minStakeAmount})` : ''}`,
}
)
.refine(
(value): value is string => {
return !!value
},
{
message: 'Enter an amount',
}
),
validator: z.string(),
Expand All @@ -65,8 +74,9 @@ const createFormSchema = ({

export function StakeForm(props: { validator?: Validator; isFlexible?: boolean }) {
const { writeContractAsync: ipTokenStake, isPending: isWaitingForWalletConfirmation } = useWriteIpTokenStakeStake()
const { data: minStakeAmount } = useReadIpTokenStakeMinStakeAmount()
console.log({ minStakeAmount })
const { data: stakingParams } = useNetworkStakingParams()
const minStakeAmount = stakingParams?.params.minDelegationEth

const [stakeTxHash, setStakeTxHash] = useState<Hex | undefined>(undefined)
const { address, chainId } = useAccount()
const { data: balance, refetch: refetchBalance } = useBalance({
Expand All @@ -76,6 +86,9 @@ export function StakeForm(props: { validator?: Validator; isFlexible?: boolean }
validatorAddr: props.validator?.operator_address || zeroAddress,
delegatorAddr: address || zeroAddress,
})
const { refetch: refetchDelegatorPeriodDelegations } = useDelegatorPeriodDelegations({
delegatorAddr: address || zeroAddress,
})

const txnReceipt = useWaitForTransactionReceipt({
hash: stakeTxHash || '0x',
Expand All @@ -98,14 +111,16 @@ export function StakeForm(props: { validator?: Validator; isFlexible?: boolean }
useEffect(() => {
refetchDelegatorStake()
refetchBalance()
refetchDelegatorPeriodDelegations()
}, [txnReceipt.isSuccess])

async function onSubmit(values: z.infer<typeof formSchema>) {
if (props.validator?.operator_address !== undefined) {
form.setValue('validator', props.validator.operator_address)
}
const { stakeAmount, stakingPeriod } = values
console.log(props.validator.operator_address)

const { stakeAmount, stakingPeriod } = values
let cmpPubkey: Hex = '0x'
if (props.validator) {
cmpPubkey = `0x${base64ToHex(props.validator.consensus_pubkey.value)}`
Expand Down Expand Up @@ -137,10 +152,10 @@ export function StakeForm(props: { validator?: Validator; isFlexible?: boolean }
let buttonText
if (chainId?.toString() != process.env.NEXT_PUBLIC_CHAIN_ID) {
buttonText = 'Wrong network. Switch to Story'
} else if (balance && minStakeAmount && balance.value < minStakeAmount) {
} else if (balance && minStakeAmount && balance.value < parseEther(minStakeAmount)) {
buttonText = 'Not enough IP'
} else if (minStakeAmount && parseEther(form.watch('stakeAmount')) < minStakeAmount) {
buttonText = `Minimum ${formatEther(minStakeAmount)} IP`
} else if (minStakeAmount && parseEther(form.watch('stakeAmount')) < parseEther(minStakeAmount)) {
buttonText = `Minimum ${minStakeAmount} IP`
} else if (balance && parseEther(form.watch('stakeAmount')) > balance.value) {
buttonText = `Exceeds balance`
} else if (isWaitingForWalletConfirmation) {
Expand Down Expand Up @@ -174,7 +189,7 @@ export function StakeForm(props: { validator?: Validator; isFlexible?: boolean }
{props.validator && minStakeAmount && (
<section className="flex flex-col">
<p className="font-semibold">Minimum Stake Amount</p>
<p className="text-primary-outline">{formatEther(minStakeAmount) + ' IP'} </p>
<p className="text-primary-outline">{minStakeAmount + ' IP'} </p>
</section>
)}
{props.validator && (
Expand Down Expand Up @@ -217,11 +232,13 @@ export function StakeForm(props: { validator?: Validator; isFlexible?: boolean }
) : (
STAKING_PERIODS[process.env.NEXT_PUBLIC_CHAIN_ID].map(
(period: StakingPeriodMultiplierInfo) => (
<SelectItem key={period.value} value={period.value} className="text-white">
<SelectItem
key={period.value}
value={period.value}
className="text-white flex flex-row justify-between w-full"
>
<div className="flex flex-row items-center gap-2">
<span className="font-medium">
{period.label} ({period.multiplier} rewards)
</span>
<span className="font-medium">{period.label}</span>
<span className="text-sm text-gray-400">- {period.description}</span>
</div>
</SelectItem>
Expand Down Expand Up @@ -266,6 +283,7 @@ export function StakeForm(props: { validator?: Validator; isFlexible?: boolean }
className="border-none bg-black font-normal text-white placeholder-gray-500 outline-none placeholder:text-white placeholder:opacity-50 focus:border-0 focus:border-none focus:outline-none focus:ring-0 focus:ring-transparent"
placeholder="Enter amount..."
{...field}
type="number"
/>
<div className="flex items-center space-x-2">
<span className="ml-2 text-white text-opacity-50">IP</span>
Expand Down
16 changes: 8 additions & 8 deletions src/lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,15 @@ export const feeWei = parseEther(feeEther)

export const STAKING_PERIODS: Record<string, StakingPeriodMultiplierInfo[]> = {
1315: [
{ value: '0', label: 'Flexible', multiplier: '1.0x', description: 'Unstake anytime' },
{ value: '1', label: '90 Days', multiplier: '1.051x', description: 'Lock for 90 days' },
{ value: '2', label: '360 Days', multiplier: '1.16x', description: 'Lock for 360 days' },
{ value: '3', label: '540 Days', multiplier: '1.34x', description: 'Lock for 540 days' },
{ value: '0', label: 'Flexible (Unstake anytime)', multiplier: '1.0x', description: '1.0x rewards' },
{ value: '1', label: 'Lock for 90 Days', multiplier: '1.051x', description: '1.051x rewards' },
{ value: '2', label: 'Lock for 360 Days', multiplier: '1.16x', description: '1.16x rewards' },
{ value: '3', label: 'Lock for 540 Days', multiplier: '1.34x', description: '1.34x rewards' },
] as const,
1514: [
{ value: '0', label: 'Flexible', multiplier: '1.0x', description: 'Unstake anytime' },
{ value: '1', label: '90 Days', multiplier: '1.1x', description: 'Lock for 90 days' },
{ value: '2', label: '360 Days', multiplier: '1.5x', description: 'Lock for 360 days' },
{ value: '3', label: '540 Days', multiplier: '2.0x', description: 'Lock for 540 days' },
{ value: '0', label: 'Flexible (Unstake anytime)', multiplier: '1.0x', description: '1.0x rewards' },
{ value: '1', label: 'Lock for 90 Days', multiplier: '1.1x', description: '1.1x rewards' },
{ value: '2', label: 'Lock for 360 Days', multiplier: '1.5x', description: '1.5x rewards' },
{ value: '3', label: 'Lock for 540 Days', multiplier: '2.0x', description: '2x rewards' },
] as const,
}
1 change: 0 additions & 1 deletion src/lib/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ const envVariables = z.object({
NEXT_PUBLIC_STAKING_API_URL: z.string().url(),
NEXT_PUBLIC_SINGULARITY_BLOCK_HEIGHT: z.string().transform((val) => parseInt(val)),
NEXT_PUBLIC_CHAIN_ID: z.string(),
NEXT_PUBLIC_CHAIN_RPC_URL: z.string(),
})

envVariables.parse(process.env)
Expand Down
20 changes: 19 additions & 1 deletion src/lib/services/api/networkApi.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import {
EvmOperation,
GetAprApiResponse,
GetAprResponse,
GetEvmOperationsApiResponse,
GetEvmOperationsParams,
GetEvmOperationsResponse,
GetNetworkHealthApiResponse,
GetNetworkHealthResponse,
GetNetworkStakingParamsApiResponse,
GetNetworkStakingParamsResponse,
GetStakingPoolApiResponse,
GetStakingPoolResponse,
GetTokenTotalSupplyApiResponse,
Expand All @@ -15,6 +16,7 @@ import {
} from '@/lib/types/networkApiTypes'

import { stakingDataAxios } from '.'
import { formatEther } from 'viem'

export async function getApr(): Promise<GetAprResponse> {
const response = await stakingDataAxios.get<GetAprApiResponse>(`estimated_apr`)
Expand Down Expand Up @@ -64,3 +66,19 @@ export async function getStakingPool(): Promise<GetStakingPoolResponse> {
totalStaked: totalStaked.toString(),
}
}

export async function getNetworkStakingParams(): Promise<GetNetworkStakingParamsResponse> {
const response = await stakingDataAxios.get<GetNetworkStakingParamsApiResponse>('/staking/params')
if (response.status !== 200) {
throw new Error(`Failed to get network staking params: ${response.status}`)
}
const msg = response.data.msg

return {
...msg,
params: {
...msg.params,
minDelegationEth: formatEther(BigInt(msg.params.min_delegation), 'gwei'),
},
}
}
12 changes: 12 additions & 0 deletions src/lib/services/hooks/useNetworkStakingParams.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
'use client'

import { GetNetworkStakingParamsResponse } from './../../types/networkApiTypes'
import { useQuery } from '@tanstack/react-query'
import { getNetworkStakingParams } from '../api/networkApi'

export function useNetworkStakingParams() {
return useQuery<GetNetworkStakingParamsResponse>({
queryKey: ['stakingParams'],
queryFn: () => getNetworkStakingParams(),
})
}
21 changes: 21 additions & 0 deletions src/lib/types/networkApiTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,24 @@ export type GetStakingPoolApiResponse = {
msg: GetStakingPoolResponse
error: string
}

export type GetNetworkStakingParamsResponse = {
params: {
unbonding_time: string
max_validators: number
max_entries: number
historical_entries: number
bond_denom: string
min_commision_rate: string
min_delegation: string
periods: { period_type: number; duration: string; rewards_multiplier: string }[]
token_types: { token_type: number; rewards_multiplier: string }[]
singularity_height: string
minDelegationEth: string
}
}
export type GetNetworkStakingParamsApiResponse = {
code: number
error: string
msg: GetNetworkStakingParamsResponse
}

0 comments on commit 9240d73

Please sign in to comment.