Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
"use client"

import { FC } from "react"
import { useApp } from "@/context/app.context"

import ActionTabs from "./_components/action-tabs"
import ConnectWallet from "./_components/connect-wallet"
import BalanceHeader from "./_components/balance-header"
import AmountInput from "./_components/amount-input"
import QuickAmounts from "./_components/quick-amounts"
Expand Down
90 changes: 69 additions & 21 deletions src/app/(root)/farms/[farmId]/_hooks/use-farm-operations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import type { InterestAccount } from "@interest-protocol/farms"
import { formatNumberWithSuffix } from "@/utils/format"
import { parseInputAmount } from "../../farms.utils"
import { POW_9 } from "../../farms.const"
import { suiClient } from "@/lib/sui-client"

interface UseFarmOperationsProps {
farmId: string
Expand All @@ -24,20 +25,30 @@ export const useFarmOperations = ({
farmId,
stakeCoinType,
rewardCoinType,
account,
tokenSymbol = "tokens",
rewardSymbol = "SUI",
rewardDecimals = 9,
onSuccess,
onSuccess
}: UseFarmOperationsProps) => {
const { address } = useApp()
const { address, wallet } = useApp()
const { executeTransaction } = useTransaction()
const [isStaking, setIsStaking] = useState(false)
const [isHarvesting, setIsHarvesting] = useState(false)
const [isUnstaking, setIsUnstaking] = useState(false)

const getAccountWithHighestStake = async (): Promise<InterestAccount | undefined> => {
if (!address) return undefined

const allAccounts = await farmsSdk.getAccounts(address)
const farmAccounts = allAccounts.filter((acc) => acc.farm === farmId)

return farmAccounts.sort((a, b) =>
Number(b.stakeBalance - a.stakeBalance)
)[0]
}

const stake = async (amount: string) => {
if (!address) {
if (!address || !wallet) {
toast.error("Please connect your wallet")
return
}
Expand All @@ -47,6 +58,19 @@ export const useFarmOperations = ({
const amountBigInt = parseInputAmount(amount)
if (amountBigInt <= 0n) {
toast.error("Invalid stake amount")
setIsStaking(false)
return
}

const balance = await suiClient.getBalance({
owner: address,
coinType: stakeCoinType,
})

if (amountBigInt > BigInt(balance.totalBalance)) {
const available = Number(balance.totalBalance) / Number(POW_9)
toast.error(`Insufficient balance. Available: ${formatNumberWithSuffix(available)} ${tokenSymbol}`)
setIsStaking(false)
return
}

Expand All @@ -55,8 +79,7 @@ export const useFarmOperations = ({
type: stakeCoinType,
})

const freshAccounts = await farmsSdk.getAccounts(address)
const existingAccount = freshAccounts.find((acc) => acc.farm === farmId)
const existingAccount = await getAccountWithHighestStake()

if (existingAccount) {
const { tx } = await farmsSdk.stake({
Expand All @@ -69,6 +92,7 @@ export const useFarmOperations = ({

const amountInTokens = Number(amountBigInt) / Number(POW_9)
toast.success(`Staked ${formatNumberWithSuffix(amountInTokens)} ${tokenSymbol}`)

onSuccess?.()
return
}
Expand All @@ -92,48 +116,62 @@ export const useFarmOperations = ({

const amountInTokens = Number(amountBigInt) / Number(POW_9)
toast.success(`Created account and staked ${formatNumberWithSuffix(amountInTokens)} ${tokenSymbol}`)

onSuccess?.()

} catch (error) {
console.error("Staking error:", error)

if (error instanceof Error) {
if (error.message.includes("Rejected") || error.message.includes("User rejected")) {
const errorMsg = error.message.toLowerCase()

if (errorMsg.includes("rejected") || errorMsg.includes("user rejected")) {
toast.error("Transaction rejected by user")
} else if (error.message.includes("Insufficient")) {
} else if (errorMsg.includes("insufficient") || errorMsg.includes("balance")) {
toast.error("Insufficient balance")
} else {
toast.error(`Failed to stake: ${error.message}`)
const shortMsg = error.message.split('\n')[0].slice(0, 100)
toast.error(`Failed to stake: ${shortMsg}`)
}
} else {
toast.error(`Failed to stake ${tokenSymbol}`)
}

} finally {
setIsStaking(false)
}
}

const harvest = async () => {
if (!address || !account) {
const accountToUse = await getAccountWithHighestStake()

if (!address || !accountToUse) {
toast.error("No farm account found")
return
}

setIsHarvesting(true)
try {
const rewards = await farmsSdk.pendingRewards(account.objectId)
const rewards = await farmsSdk.pendingRewards(accountToUse.objectId)
const rewardsAmount = Number(rewards[0].amount) / Math.pow(10, rewardDecimals)

if (rewardsAmount === 0) {
toast.error("No rewards to harvest")
setIsHarvesting(false)
return
}

const { tx, rewardCoin } = await farmsSdk.harvest({
farm: farmId,
account: account.objectId,
account: accountToUse.objectId,
rewardType: rewardCoinType,
})

tx.transferObjects([rewardCoin], address)
await executeTransaction(tx)

toast.success(`Harvested ${formatNumberWithSuffix(rewardsAmount)} ${rewardSymbol} rewards`)

onSuccess?.()
} catch (error) {
console.error("Harvest error:", error)
Expand All @@ -144,7 +182,9 @@ export const useFarmOperations = ({
}

const unstake = async (amount: string) => {
if (!address || !account) {
const accountToUse = await getAccountWithHighestStake()

if (!address || !accountToUse) {
toast.error("No farm account found")
return
}
Expand All @@ -154,23 +194,30 @@ export const useFarmOperations = ({
const amountBigInt = parseInputAmount(amount)
if (amountBigInt <= 0n) {
toast.error("Invalid unstake amount")
setIsUnstaking(false)
return
}

if (amountBigInt > accountToUse.stakeBalance) {
const available = Number(accountToUse.stakeBalance) / Number(POW_9)
toast.error(`Insufficient staked balance. Available: ${formatNumberWithSuffix(available)} ${tokenSymbol}`)
setIsUnstaking(false)
return
}

const isMaxWithdrawal = amountBigInt === account.stakeBalance
const isMaxWithdrawal = amountBigInt === accountToUse.stakeBalance

const rewards = await farmsSdk.pendingRewards(account.objectId);
const rewards = await farmsSdk.pendingRewards(accountToUse.objectId)
const rewardsAmount = Number(rewards[0].amount) / Math.pow(10, rewardDecimals)
const hasRewards = rewardsAmount > 0n
const hasRewards = rewardsAmount > 0

const tx = new Transaction();
const tx = new Transaction()

// harvest rewards if available and withdrawing max amount
if (hasRewards && isMaxWithdrawal) {
const { rewardCoin } = await farmsSdk.harvest({
tx,
farm: farmId,
account: account.objectId,
account: accountToUse.objectId,
rewardType: rewardCoinType,
})

Expand All @@ -180,7 +227,7 @@ export const useFarmOperations = ({
const { unstakeCoin } = await farmsSdk.unstake({
tx,
farm: farmId,
account: account.objectId,
account: accountToUse.objectId,
amount: amountBigInt,
})

Expand All @@ -197,6 +244,7 @@ export const useFarmOperations = ({
}

onSuccess?.()

} catch (error) {
console.error("Unstake error:", error)
toast.error(`Failed to unstake ${tokenSymbol}`)
Expand All @@ -213,4 +261,4 @@ export const useFarmOperations = ({
isHarvesting,
isUnstaking,
}
}
}
14 changes: 10 additions & 4 deletions src/app/(root)/farms/_hooks/use-farms/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,10 @@ export const useFarms = () => {
if (abortControllerRef.current) {
abortControllerRef.current.abort()
}
abortControllerRef.current = new AbortController()

abortControllerRef.current = new AbortController()
setIsLoading(true)

try {
const farms = FARMS[env.NEXT_PUBLIC_DEFAULT_NETWORK as Network]
const farmIds = Object.values(farms).map((farm) => farm.objectId)
Expand All @@ -43,12 +44,17 @@ export const useFarms = () => {

if (address && isConnected) {
const allAccounts = await farmsSdk.getAccounts(address)

if (!isMountedRef.current) return

farmsWithAccountsData = farmsData.map((farm) => {
const account = allAccounts.find((acc) => acc.farm === farm.objectId)
return { farm, account }
const farmAccounts = allAccounts.filter((acc) => acc.farm === farm.objectId)

const primaryAccount = farmAccounts.sort((a, b) =>
Number(b.stakeBalance - a.stakeBalance)
)[0]

return { farm, account: primaryAccount }
})
}

Expand Down