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,46 +1,45 @@
import { useEffect } from "react"
import { useAccount } from "wagmi"
import { formatTokenAmount } from "@/utils/atpFormatters"
import { useStakingAssetTokenDetails } from "@/hooks/stakingRegistry"
import { useStakerBalance, useMoveFundsBackToATP } from "@/hooks/staker"
import { useStakerBalance } from "@/hooks/staker"
import { TooltipIcon } from "@/components/Tooltip"
import { useStakeableAmount, type ATPData } from "@/hooks"
import { useQueryClient } from "@tanstack/react-query"
import { Icon } from "@/components/Icon"
import { type ATPData } from "@/hooks"
import { useTransactionCart } from "@/contexts/TransactionCartContext"
import { buildMoveFundsBackToATPEntry } from "@/utils/actionCart"

interface ATPDetailsStakerBalanceProps {
atp: ATPData
}

/**
* Displays staker contract balance and provides button to move funds back to ATP
* Only the operator can move funds back to vault
* Compact inline layout
* Displays staker contract balance and a button to queue a move-funds-back-to-ATP
* transaction in the cart. Only the operator can submit it on-chain.
*
* Compact inline layout.
*/
export const ATPDetailsStakerBalance = ({ atp }: ATPDetailsStakerBalanceProps) => {
const { address: connectedAddress } = useAccount()
const { balance, isLoading: isLoadingBalance, refetch } = useStakerBalance({ stakerAddress: atp.staker })
const { balance, isLoading: isLoadingBalance } = useStakerBalance({ stakerAddress: atp.staker })
const { symbol, decimals, isLoading: isLoadingTokenDetails } = useStakingAssetTokenDetails()
const { moveFunds, isPending, isConfirming, isSuccess } = useMoveFundsBackToATP(atp.staker!)
const { refetch: refetchStakeableAmount } = useStakeableAmount(atp)
const queryClient = useQueryClient()
const { addTransaction, checkStepGroupInQueue, openCart } = useTransactionCart()

const isLoading = isLoadingBalance || isLoadingTokenDetails
const isProcessing = isPending || isConfirming
const hasBalance = balance > 0n
const isOperator = connectedAddress?.toLowerCase() === atp.operator?.toLowerCase()

// Refetch balance after successful move
useEffect(() => {
if (isSuccess) {
refetch()
refetchStakeableAmount()
const entry = atp.staker
? buildMoveFundsBackToATPEntry({ stakerAddress: atp.staker, atpAddress: atp.atpAddress })
: undefined

// Invalidate multiple stakeable amounts and refetch
queryClient.invalidateQueries({
queryKey: ['readContracts']
})
}
}, [isSuccess, refetch])
const isQueued = !!entry && !!entry.metadata?.stepType && !!entry.metadata?.stepGroupIdentifier &&
checkStepGroupInQueue(entry.metadata.stepType, entry.metadata.stepGroupIdentifier)

const handleAddToBatch = () => {
if (!entry) return
addTransaction(entry, { preventDuplicate: true })
openCart()
}

return (
<div className="bg-parchment/5 border border-parchment/20 p-3 flex items-center justify-between">
Expand All @@ -59,13 +58,24 @@ export const ATPDetailsStakerBalance = ({ atp }: ATPDetailsStakerBalanceProps) =
</div>
{hasBalance && (
<div className="flex items-center gap-2">
<button
onClick={moveFunds}
disabled={isProcessing || !isOperator}
className="px-3 py-1.5 bg-chartreuse text-ink font-oracle-standard text-xs uppercase tracking-wider hover:bg-chartreuse/90 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
>
{isPending ? 'Moving...' : isConfirming ? 'Confirming...' : 'Move to Vault'}
</button>
{isQueued ? (
<button
onClick={openCart}
disabled={!isOperator}
className="px-3 py-1.5 bg-chartreuse/20 border border-chartreuse/40 text-chartreuse font-oracle-standard text-xs uppercase tracking-wider hover:bg-chartreuse/30 disabled:opacity-50 disabled:cursor-not-allowed transition-colors flex items-center gap-1"
>
<Icon name="shoppingCart" size="sm" />
In Batch
</button>
) : (
<button
onClick={handleAddToBatch}
disabled={!isOperator || !entry}
className="px-3 py-1.5 bg-chartreuse text-ink font-oracle-standard text-xs uppercase tracking-wider hover:bg-chartreuse/90 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
>
Add to Batch
</button>
)}
{!isOperator && (
<TooltipIcon
content="Only the operator can move funds to vault"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ import { useState, useMemo, useEffect } from "react"
import type { Address } from "viem"
import { useAtpRegistryData, useStakerImplementations } from "@/hooks/atpRegistry"
import { useStakerImplementation as useStakerImplementationFromStaker } from "@/hooks/staker/useStakerImplementation"
import { useUpgradeStaker } from "@/hooks/atp"
import { Icon } from "@/components/Icon"
import { TooltipIcon } from "@/components/Tooltip"
import { useTransactionCart } from "@/contexts/TransactionCartContext"
import { buildUpgradeStakerEntry } from "@/utils/actionCart"
import {
getVersionByImplementation,
getImplementationDescription,
Expand All @@ -30,12 +31,12 @@ interface ATPDetailsStakerManagementProps {
export const ATPDetailsStakerManagement = ({ atp }: ATPDetailsStakerManagementProps) => {
const [selectedVersion, setSelectedVersion] = useState<bigint | null>(null)
const [isOpen, setIsOpen] = useState(false)
const { addTransaction, checkStepGroupInQueue, openCart } = useTransactionCart()

// Get current implementation
const {
implementation: currentImplementation,
isLoading: isLoadingImplementation,
refetch: refetchImplementation
} = useStakerImplementationFromStaker(atp.staker as Address)

// Get available versions
Expand All @@ -44,9 +45,6 @@ export const ATPDetailsStakerManagement = ({ atp }: ATPDetailsStakerManagementPr
})
const { implementations, isLoading: isLoadingImplementations } = useStakerImplementations(stakerVersions, atp.registry)

// Staker operations
const upgradeStakerHook = useUpgradeStaker(atp.atpAddress as Address)

// Get current version number
const currentVersion = useMemo(() => {
return getVersionByImplementation(currentImplementation, implementations)
Expand All @@ -67,24 +65,23 @@ export const ATPDetailsStakerManagement = ({ atp }: ATPDetailsStakerManagementPr
}
}, [selectedVersion, currentVersion, stakerVersions, isLoadingImplementation])

// Refetch implementation after successful upgrade
useEffect(() => {
if (upgradeStakerHook.isSuccess) {
refetchImplementation()
}
}, [upgradeStakerHook.isSuccess, refetchImplementation])
const upgradeEntry = useMemo(() => {
if (!selectedVersion) return undefined
return buildUpgradeStakerEntry({ atpAddress: atp.atpAddress as Address, version: selectedVersion })
}, [selectedVersion, atp.atpAddress])

const isUpgradeQueued = !!upgradeEntry && !!upgradeEntry.metadata?.stepType &&
!!upgradeEntry.metadata?.stepGroupIdentifier &&
checkStepGroupInQueue(upgradeEntry.metadata.stepType, upgradeEntry.metadata.stepGroupIdentifier)

const handleVersionChange = (value: string) => {
setSelectedVersion(BigInt(value))
}

const handleUpgrade = async () => {
if (!selectedVersion) return
try {
await upgradeStakerHook.upgradeStaker(selectedVersion)
} catch (error) {
console.error('Failed to upgrade staker:', error)
}
const handleUpgrade = () => {
if (!upgradeEntry) return
addTransaction(upgradeEntry, { preventDuplicate: true })
openCart()
}

const isLoading = isLoadingImplementation || isLoadingImplementations
Expand Down Expand Up @@ -211,7 +208,6 @@ export const ATPDetailsStakerManagement = ({ atp }: ATPDetailsStakerManagementPr
<Select
value={selectedVersion?.toString() || ""}
onValueChange={handleVersionChange}
disabled={upgradeStakerHook.isPending || upgradeStakerHook.isConfirming}
>
<SelectTrigger>
<SelectValue />
Expand All @@ -233,31 +229,24 @@ export const ATPDetailsStakerManagement = ({ atp }: ATPDetailsStakerManagementPr
</Select>
</div>
{canUpgrade && (
<button
onClick={handleUpgrade}
disabled={upgradeStakerHook.isPending || upgradeStakerHook.isConfirming}
className="bg-chartreuse text-ink py-2 px-3 font-oracle-standard font-bold text-xs uppercase tracking-wider hover:bg-chartreuse/90 transition-all disabled:opacity-50 disabled:cursor-not-allowed whitespace-nowrap"
>
{upgradeStakerHook.isPending
? "Confirming..."
: upgradeStakerHook.isConfirming
? "Upgrading..."
: "Upgrade"}
</button>
isUpgradeQueued ? (
<button
onClick={openCart}
className="bg-chartreuse/20 border border-chartreuse/40 text-chartreuse py-2 px-3 font-oracle-standard font-bold text-xs uppercase tracking-wider hover:bg-chartreuse/30 transition-all whitespace-nowrap flex items-center gap-1"
>
<Icon name="shoppingCart" size="sm" />
In Batch
</button>
) : (
<button
onClick={handleUpgrade}
className="bg-chartreuse text-ink py-2 px-3 font-oracle-standard font-bold text-xs uppercase tracking-wider hover:bg-chartreuse/90 transition-all whitespace-nowrap"
>
Add to Batch
</button>
)
)}
</div>

{upgradeStakerHook.error && (
<div className="text-xs text-vermillion">
{upgradeStakerHook.error.message.includes('rejected') ? 'Transaction cancelled' : 'Upgrade failed'}
</div>
)}

{upgradeStakerHook.isSuccess && (
<div className="text-xs text-chartreuse">
Successfully upgraded to v{selectedVersion?.toString()}
</div>
)}
</div>
</>
)}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,35 +1,37 @@
import { useState, useMemo, useEffect } from "react"
import { useState, useMemo } from "react"
import { Icon } from "@/components/Icon"
import { useAtpRegistryData, useStakerImplementations } from "@/hooks/atpRegistry"
import { useStakerImplementation as useStakerImplementationFromStaker } from "@/hooks/staker/useStakerImplementation"
import { useUpgradeStaker } from "@/hooks/atp"
import { AddressDisplay } from "@/components/AddressDisplay"
import { TooltipIcon } from "@/components/Tooltip"
import { useTransactionCart } from "@/contexts/TransactionCartContext"
import { buildUpgradeStakerEntry } from "@/utils/actionCart"
import { getVersionByImplementation, getImplementationDescription } from "@/utils/stakerVersion"
import type { ATPData } from "@/hooks/atp"
import type { Address } from "viem"

interface ATPDetailsTechnicalInfoProps {
atp: ATPData
// Kept for source-compatibility; cart execution drives refetch globally.
onUpgradeSuccess?: () => void
}

/**
* Component displaying technical details of a Token Vault position
* Shows vault address, and staker information if staker contract exists
*/
export const ATPDetailsTechnicalInfo = ({ atp, onUpgradeSuccess }: ATPDetailsTechnicalInfoProps) => {
export const ATPDetailsTechnicalInfo = ({ atp }: ATPDetailsTechnicalInfoProps) => {
const [isTechnicalDetailsExpanded, setIsTechnicalDetailsExpanded] = useState(true)
const { addTransaction, checkStepGroupInQueue, openCart } = useTransactionCart()

const { implementation: stakerImplementation, isLoading: isLoadingImplementation, refetch } = useStakerImplementationFromStaker(
const { implementation: stakerImplementation, isLoading: isLoadingImplementation } = useStakerImplementationFromStaker(
atp.staker as Address
)

const { stakerVersions } = useAtpRegistryData({
registryAddress: atp.registry
})
const { implementations, isLoading: isLoadingImplementations } = useStakerImplementations(stakerVersions, atp.registry)
const upgradeStakerHook = useUpgradeStaker(atp.atpAddress as Address)

const stakerVersion = useMemo(() => {
return getVersionByImplementation(stakerImplementation, implementations)
Expand All @@ -47,23 +49,22 @@ export const ATPDetailsTechnicalInfo = ({ atp, onUpgradeSuccess }: ATPDetailsTec
return getImplementationDescription(stakerImplementation, stakerVersion!)
}, [stakerImplementation, stakerVersion])

useEffect(() => {
if (upgradeStakerHook.isSuccess) {
refetch()
onUpgradeSuccess?.()
}
}, [upgradeStakerHook.isSuccess, refetch, onUpgradeSuccess])

const isOnLatestVersion = stakerVersion !== null && latestVersion !== null && stakerVersion === latestVersion
const isLoadingVersion = isLoadingImplementation || isLoadingImplementations

const handleUpgrade = async () => {
if (!latestVersion) return
try {
await upgradeStakerHook.upgradeStaker(latestVersion)
} catch (error) {
console.error('Failed to upgrade staker:', error)
}
const upgradeEntry = useMemo(() => {
if (!latestVersion) return undefined
return buildUpgradeStakerEntry({ atpAddress: atp.atpAddress as Address, version: latestVersion })
}, [latestVersion, atp.atpAddress])

const isUpgradeQueued = !!upgradeEntry && !!upgradeEntry.metadata?.stepType &&
!!upgradeEntry.metadata?.stepGroupIdentifier &&
checkStepGroupInQueue(upgradeEntry.metadata.stepType, upgradeEntry.metadata.stepGroupIdentifier)

const handleUpgrade = () => {
if (!upgradeEntry) return
addTransaction(upgradeEntry, { preventDuplicate: true })
openCart()
}

return (
Expand Down Expand Up @@ -130,30 +131,26 @@ export const ATPDetailsTechnicalInfo = ({ atp, onUpgradeSuccess }: ATPDetailsTec
</>
) : (
<>
<button
onClick={handleUpgrade}
disabled={upgradeStakerHook.isPending || upgradeStakerHook.isConfirming}
className="bg-chartreuse text-ink py-2 px-3 font-oracle-standard font-bold text-xs uppercase tracking-wider hover:bg-parchment hover:text-ink transition-all disabled:opacity-50 disabled:cursor-not-allowed"
>
{upgradeStakerHook.isPending
? "Confirm in Wallet..."
: upgradeStakerHook.isConfirming
? "Upgrading..."
: `Upgrade to Latest`}
</button>
{isUpgradeQueued ? (
<button
onClick={openCart}
className="bg-chartreuse/20 border border-chartreuse/40 text-chartreuse py-2 px-3 font-oracle-standard font-bold text-xs uppercase tracking-wider hover:bg-chartreuse/30 transition-all flex items-center gap-1"
>
<Icon name="shoppingCart" size="sm" />
In Batch
</button>
) : (
<button
onClick={handleUpgrade}
disabled={!upgradeEntry}
className="bg-chartreuse text-ink py-2 px-3 font-oracle-standard font-bold text-xs uppercase tracking-wider hover:bg-parchment hover:text-ink transition-all disabled:opacity-50 disabled:cursor-not-allowed"
>
Upgrade to Latest
</button>
)}
<div className="text-xs text-parchment/60 mt-1">{currentDescription}</div>
</>
)}
{upgradeStakerHook.error && (
<div className="text-xs text-vermillion mt-1">
{upgradeStakerHook.error.message.includes('rejected') ? 'Transaction cancelled' : 'Upgrade failed'}
</div>
)}
{upgradeStakerHook.isSuccess && (
<div className="text-xs text-chartreuse mt-1">
Successfully upgraded to latest version
</div>
)}
</div>
</>
)}
Expand Down
Loading
Loading