From 2367e441d52d31ebe5b42cfd2e4d49512593c8cb Mon Sep 17 00:00:00 2001 From: "claude[bot]" <41898282+claude[bot]@users.noreply.github.com> Date: Thu, 22 Jan 2026 19:29:48 +0000 Subject: [PATCH 01/22] fix: update bonus pods from 10% to 5% in sow order form Fixes #404 Co-authored-by: fr1jo --- src/components/Tractor/form/SowOrderV0Fields.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Tractor/form/SowOrderV0Fields.tsx b/src/components/Tractor/form/SowOrderV0Fields.tsx index 0d6e7d64..b210b0fb 100644 --- a/src/components/Tractor/form/SowOrderV0Fields.tsx +++ b/src/components/Tractor/form/SowOrderV0Fields.tsx @@ -496,7 +496,7 @@ SowOrderV0Fields.MorningAuction = function MorningAuction() { // TODO: ADD REFERRAL CODE VALIDATOR! -const BONUS_MULTIPLIER = 0.1; +const BONUS_MULTIPLIER = 0.05; SowOrderV0Fields.PodDisplay = function PodDisplay({ onOpenReferralPopover, From 2c18f541332703f97af423b13f484b1f9a5ec58c Mon Sep 17 00:00:00 2001 From: feyyazcigim Date: Wed, 4 Feb 2026 04:22:25 +0300 Subject: [PATCH 02/22] feat: implement dashboard for legacy beanstalk holder obligations --- src/ProtectedLayout.tsx | 9 + src/components/BeanstalkStatField.tsx | 64 ++++ src/components/nav/nav/Navbar.tsx | 1 + src/components/nav/nav/Navi.desktop.tsx | 3 + src/components/nav/nav/Navi.mobile.tsx | 3 + src/components/ui/Button.tsx | 3 +- src/constants/meta.ts | 6 + src/pages/Beanstalk.tsx | 53 +++ .../components/BeanstalkFertilizerSection.tsx | 39 +++ .../components/BeanstalkGlobalCard.tsx | 66 ++++ .../components/BeanstalkObligationsCard.tsx | 47 +++ .../components/BeanstalkPodsSection.tsx | 53 +++ .../components/BeanstalkSiloSection.tsx | 39 +++ src/state/useBeanstalkGlobalStats.ts | 153 +++++++++ src/state/useFarmerBeanstalkRepayment.ts | 313 ++++++++++++++++++ 15 files changed, 851 insertions(+), 1 deletion(-) create mode 100644 src/components/BeanstalkStatField.tsx create mode 100644 src/pages/Beanstalk.tsx create mode 100644 src/pages/beanstalk/components/BeanstalkFertilizerSection.tsx create mode 100644 src/pages/beanstalk/components/BeanstalkGlobalCard.tsx create mode 100644 src/pages/beanstalk/components/BeanstalkObligationsCard.tsx create mode 100644 src/pages/beanstalk/components/BeanstalkPodsSection.tsx create mode 100644 src/pages/beanstalk/components/BeanstalkSiloSection.tsx create mode 100644 src/state/useBeanstalkGlobalStats.ts create mode 100644 src/state/useFarmerBeanstalkRepayment.ts diff --git a/src/ProtectedLayout.tsx b/src/ProtectedLayout.tsx index 630ae118..ff817024 100644 --- a/src/ProtectedLayout.tsx +++ b/src/ProtectedLayout.tsx @@ -2,6 +2,7 @@ import { isDev } from "@/utils/utils"; import { Navigate, Route, Routes } from "react-router-dom"; import DevPage from "./components/DevPage"; import PageMetaWrapper from "./components/PageMetaWrapper"; +import Beanstalk from "./pages/Beanstalk"; import Collection from "./pages/Collection"; import Error404 from "./pages/Error404"; import Explorer from "./pages/Explorer"; @@ -69,6 +70,14 @@ export default function ProtectedLayout() { } /> + + + + } + /> void; + disabled?: boolean; +} + +interface BeanstalkStatFieldProps { + title: string; + value: ReactNode; + isLoading?: boolean; + disabled?: boolean; + actions?: BeanstalkStatFieldAction[]; + children?: ReactNode; +} + +/** + * Reusable stat field component with title, value, and optional action buttons + * Used in Beanstalk obligations and global stats cards + */ +const BeanstalkStatField: React.FC = ({ + title, + value, + isLoading = false, + disabled = false, + actions, + children, +}) => { + return ( +
+
+
{title}
+ {actions && actions.length > 0 && ( +
+ {actions.map((action) => ( + + ))} +
+ )} +
+ {children ? ( + children + ) : ( + +
{disabled ? N/A : value}
+
+ )} +
+ ); +}; + +export default BeanstalkStatField; diff --git a/src/components/nav/nav/Navbar.tsx b/src/components/nav/nav/Navbar.tsx index 39d9c9a8..aa4729fb 100644 --- a/src/components/nav/nav/Navbar.tsx +++ b/src/components/nav/nav/Navbar.tsx @@ -332,6 +332,7 @@ export const navLinks = { field: "/field", swap: "/swap", referral: "/referral", + beanstalk: "/beanstalk", sPinto: "/sPinto", collection: "/collection", podmarket: "/market/pods", diff --git a/src/components/nav/nav/Navi.desktop.tsx b/src/components/nav/nav/Navi.desktop.tsx index da6f6eec..4a6072f8 100644 --- a/src/components/nav/nav/Navi.desktop.tsx +++ b/src/components/nav/nav/Navi.desktop.tsx @@ -110,6 +110,9 @@ const AppNavi = () => { Referral + + Beanstalk + sPinto diff --git a/src/components/nav/nav/Navi.mobile.tsx b/src/components/nav/nav/Navi.mobile.tsx index 7581cce5..c57cad24 100644 --- a/src/components/nav/nav/Navi.mobile.tsx +++ b/src/components/nav/nav/Navi.mobile.tsx @@ -175,6 +175,9 @@ function MobileNavContent({ learnOpen, setLearnOpen, unmount, close }: IMobileNa > Referral + + Beanstalk + = { description: "Share Pinto and earn rewards through referrals.", url: "https://pinto.money/referral", }, + beanstalk: { + title: "Beanstalk Obligations | Pinto", + description: "View your legacy Beanstalk holder obligations including Silo Payback, Pods, and Fertilizer.", + url: "https://pinto.money/beanstalk", + }, }; export default PINTO_META; diff --git a/src/pages/Beanstalk.tsx b/src/pages/Beanstalk.tsx new file mode 100644 index 00000000..d05cc05a --- /dev/null +++ b/src/pages/Beanstalk.tsx @@ -0,0 +1,53 @@ +import ReadMoreAccordion from "@/components/ReadMoreAccordion"; +import { Card } from "@/components/ui/Card"; +import PageContainer from "@/components/ui/PageContainer"; +import { Separator } from "@/components/ui/Separator"; +import BeanstalkGlobalCard from "./beanstalk/components/BeanstalkGlobalCard"; +import BeanstalkObligationsCard from "./beanstalk/components/BeanstalkObligationsCard"; + +const Beanstalk = () => { + return ( + +
+
+ {/* Hero Section */} +
+
Beanstalk Obligations
+
+ Beanstalk Debt issued by Pinto. + + Beanstalk participants at the time of Pinto launch were issued assets based on their holdings. When + Pinto exceeds 1 Billion in supply, 3% of mints go towards these between Beanstalk Silo Tokens, Pods, and + Fertilizer.{" "} + + Learn more + + +
+
+ + + {/* Main Cards - Two Column Layout */} +
+ {/* Left Panel - Obligations Card */} + + + + + {/* Right Panel - Global Stats Card */} + + + +
+
+
+
+ ); +}; + +export default Beanstalk; diff --git a/src/pages/beanstalk/components/BeanstalkFertilizerSection.tsx b/src/pages/beanstalk/components/BeanstalkFertilizerSection.tsx new file mode 100644 index 00000000..6ab7e299 --- /dev/null +++ b/src/pages/beanstalk/components/BeanstalkFertilizerSection.tsx @@ -0,0 +1,39 @@ +import { TokenValue } from "@/classes/TokenValue"; +import BeanstalkStatField from "@/components/BeanstalkStatField"; +import { formatter } from "@/utils/format"; + +interface BeanstalkFertilizerSectionProps { + balance: TokenValue; + isLoading: boolean; + disabled?: boolean; + onRinse?: () => void; + onSend?: () => void; +} + +/** + * Section component displaying fertilizer balance + */ +const BeanstalkFertilizerSection: React.FC = ({ + balance, + isLoading, + disabled = false, + onRinse, + onSend, +}) => { + const hasBalance = !balance.isZero; + + return ( + + ); +}; + +export default BeanstalkFertilizerSection; diff --git a/src/pages/beanstalk/components/BeanstalkGlobalCard.tsx b/src/pages/beanstalk/components/BeanstalkGlobalCard.tsx new file mode 100644 index 00000000..30e791a8 --- /dev/null +++ b/src/pages/beanstalk/components/BeanstalkGlobalCard.tsx @@ -0,0 +1,66 @@ +import BeanstalkStatField from "@/components/BeanstalkStatField"; +import { Button } from "@/components/ui/Button"; +import { useBeanstalkGlobalStats } from "@/state/useBeanstalkGlobalStats"; +import { formatter } from "@/utils/format"; + +/** + * Component displaying global Beanstalk repayment statistics + * Shows total urBDV distributed, total pods in repayment field, + * total unfertilized sprouts, and total Pinto paid out + * Shows N/A values when data cannot be loaded + */ +const BeanstalkGlobalCard: React.FC = () => { + const { + totalUrBdvDistributed, + totalPodsInRepaymentField, + totalUnfertilizedSprouts, + totalPintoPaidOut, + isLoading, + isError, + refetch, + } = useBeanstalkGlobalStats(); + + const formatValue = (value: typeof totalUrBdvDistributed) => { + return formatter.number(value, { minDecimals: 2, maxDecimals: 2 }); + }; + + return ( +
+ {isError && ( +
+ +
+ )} +
+ + + + +
+
+ ); +}; + +export default BeanstalkGlobalCard; diff --git a/src/pages/beanstalk/components/BeanstalkObligationsCard.tsx b/src/pages/beanstalk/components/BeanstalkObligationsCard.tsx new file mode 100644 index 00000000..2bc0ad65 --- /dev/null +++ b/src/pages/beanstalk/components/BeanstalkObligationsCard.tsx @@ -0,0 +1,47 @@ +import { Button } from "@/components/ui/Button"; +import { useFarmerBeanstalkRepayment } from "@/state/useFarmerBeanstalkRepayment"; +import { useAccount } from "wagmi"; +import BeanstalkFertilizerSection from "./BeanstalkFertilizerSection"; +import BeanstalkPodsSection from "./BeanstalkPodsSection"; +import BeanstalkSiloSection from "./BeanstalkSiloSection"; + +/** + * Container component for displaying user's Beanstalk obligations + * Shows Silo Payback (urBDV), Pods from repayment field, and Fertilizer data + * Shows N/A values when wallet is not connected or data cannot be loaded + */ +const BeanstalkObligationsCard: React.FC = () => { + const account = useAccount(); + const { silo, pods, fertilizer, isLoading, isError, refetch } = useFarmerBeanstalkRepayment(); + + const isConnected = !!account.address; + const showDisabled = !isConnected || isError; + + return ( +
+ {isConnected && isError && ( +
+ +
+ )} +
+ + + +
+
+ ); +}; + +export default BeanstalkObligationsCard; diff --git a/src/pages/beanstalk/components/BeanstalkPodsSection.tsx b/src/pages/beanstalk/components/BeanstalkPodsSection.tsx new file mode 100644 index 00000000..e26a2f5d --- /dev/null +++ b/src/pages/beanstalk/components/BeanstalkPodsSection.tsx @@ -0,0 +1,53 @@ +import { TokenValue } from "@/classes/TokenValue"; +import BeanstalkStatField from "@/components/BeanstalkStatField"; +import PodLineGraph from "@/components/PodLineGraph"; +import TextSkeleton from "@/components/TextSkeleton"; +import { Plot } from "@/utils/types"; + +interface BeanstalkPodsSectionProps { + plots: Plot[]; + totalPods: TokenValue; + isLoading: boolean; + disabled?: boolean; + onHarvest?: () => void; + onSend?: () => void; +} + +/** + * Section component displaying pods from the repayment field (fieldId=1) + * Shows PodLineGraph visualization + */ +const BeanstalkPodsSection: React.FC = ({ + plots, + totalPods, + isLoading, + disabled = false, + onHarvest, + onSend, +}) => { + const hasPlots = plots.length > 0; + const hasPods = !totalPods.isZero; + const showDisabledGraph = disabled || !hasPlots; + + return ( + + {isLoading ? ( + + ) : ( +
+ +
+ )} +
+ ); +}; + +export default BeanstalkPodsSection; diff --git a/src/pages/beanstalk/components/BeanstalkSiloSection.tsx b/src/pages/beanstalk/components/BeanstalkSiloSection.tsx new file mode 100644 index 00000000..c24ca4dd --- /dev/null +++ b/src/pages/beanstalk/components/BeanstalkSiloSection.tsx @@ -0,0 +1,39 @@ +import { TokenValue } from "@/classes/TokenValue"; +import BeanstalkStatField from "@/components/BeanstalkStatField"; +import { formatter } from "@/utils/format"; + +interface BeanstalkSiloSectionProps { + balance: TokenValue; + isLoading: boolean; + disabled?: boolean; + onClaim?: () => void; + onSend?: () => void; +} + +/** + * Section component displaying urBDV token balance for Silo Payback + */ +const BeanstalkSiloSection: React.FC = ({ + balance, + isLoading, + disabled = false, + onClaim, + onSend, +}) => { + const hasBalance = !balance.isZero; + + return ( + + ); +}; + +export default BeanstalkSiloSection; diff --git a/src/state/useBeanstalkGlobalStats.ts b/src/state/useBeanstalkGlobalStats.ts new file mode 100644 index 00000000..315d47db --- /dev/null +++ b/src/state/useBeanstalkGlobalStats.ts @@ -0,0 +1,153 @@ +import { TokenValue } from "@/classes/TokenValue"; +import { PODS } from "@/constants/internalTokens"; +import { defaultQuerySettingsMedium } from "@/constants/query"; +import { useProtocolAddress } from "@/hooks/pinto/useProtocolAddress"; +import { useCallback, useMemo } from "react"; +import { useReadContracts } from "wagmi"; + +/** + * ABI snippets for Silo Payback contract global functions + */ +const siloPaybackGlobalAbi = [ + { + inputs: [], + name: "totalUrBdvDistributed", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "totalPintoPaidOut", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, +] as const; + +/** + * ABI snippets for Field contract global functions + */ +const fieldGlobalAbi = [ + { + inputs: [{ internalType: "uint256", name: "fieldId", type: "uint256" }], + name: "totalPods", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, +] as const; + +/** + * ABI snippets for Barn Payback contract global functions + */ +const barnPaybackGlobalAbi = [ + { + inputs: [], + name: "totalUnfertilizedSprouts", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, +] as const; + +/** + * Interface for the global Beanstalk statistics data + */ +export interface BeanstalkGlobalStatsData { + totalUrBdvDistributed: TokenValue; + totalPodsInRepaymentField: TokenValue; + totalUnfertilizedSprouts: TokenValue; + totalPintoPaidOut: TokenValue; + isLoading: boolean; + isError: boolean; + refetch: () => Promise; +} + +// Token decimals for urBDV (same as BEAN - 6 decimals) +const URBDV_DECIMALS = 6; +// Token decimals for Pinto (6 decimals) +const PINTO_DECIMALS = 6; +// Token decimals for sprouts +const SPROUTS_DECIMALS = 6; + +/** + * Hook for fetching global Beanstalk repayment statistics + * + * This hook fetches protocol-wide statistics: + * - Total urBDV distributed across all holders + * - Total pods in the repayment field (fieldId=1) + * - Total unfertilized sprouts + * - Total Pinto paid out to holders + * + * Uses a 5-minute stale time for more frequent updates of global stats + * + * @returns BeanstalkGlobalStatsData with all global statistics + */ +export function useBeanstalkGlobalStats(): BeanstalkGlobalStatsData { + const protocolAddress = useProtocolAddress(); + + // Query for all global statistics + const globalQuery = useReadContracts({ + contracts: [ + { + address: protocolAddress, + abi: siloPaybackGlobalAbi, + functionName: "totalUrBdvDistributed", + args: [], + }, + { + address: protocolAddress, + abi: fieldGlobalAbi, + functionName: "totalPods", + args: [1n], // fieldId=1 for repayment field + }, + { + address: protocolAddress, + abi: barnPaybackGlobalAbi, + functionName: "totalUnfertilizedSprouts", + args: [], + }, + { + address: protocolAddress, + abi: siloPaybackGlobalAbi, + functionName: "totalPintoPaidOut", + args: [], + }, + ], + allowFailure: true, + query: { + ...defaultQuerySettingsMedium, // 5 minutes staleTime for global stats + }, + }); + + // Process global data + const globalData = useMemo(() => { + const totalUrBdvDistributed = globalQuery.data?.[0]?.result; + const totalPodsInRepaymentField = globalQuery.data?.[1]?.result; + const totalUnfertilizedSprouts = globalQuery.data?.[2]?.result; + const totalPintoPaidOut = globalQuery.data?.[3]?.result; + + return { + totalUrBdvDistributed: TokenValue.fromBlockchain(totalUrBdvDistributed ?? 0n, URBDV_DECIMALS), + totalPodsInRepaymentField: TokenValue.fromBlockchain(totalPodsInRepaymentField ?? 0n, PODS.decimals), + totalUnfertilizedSprouts: TokenValue.fromBlockchain(totalUnfertilizedSprouts ?? 0n, SPROUTS_DECIMALS), + totalPintoPaidOut: TokenValue.fromBlockchain(totalPintoPaidOut ?? 0n, PINTO_DECIMALS), + }; + }, [globalQuery.data]); + + // Refetch function + const refetch = useCallback(async () => { + await globalQuery.refetch(); + }, [globalQuery.refetch]); + + return useMemo( + () => ({ + ...globalData, + isLoading: globalQuery.isLoading, + isError: globalQuery.isError, + refetch, + }), + [globalData, globalQuery.isLoading, globalQuery.isError, refetch], + ); +} diff --git a/src/state/useFarmerBeanstalkRepayment.ts b/src/state/useFarmerBeanstalkRepayment.ts new file mode 100644 index 00000000..8c92ff52 --- /dev/null +++ b/src/state/useFarmerBeanstalkRepayment.ts @@ -0,0 +1,313 @@ +import { TokenValue } from "@/classes/TokenValue"; +import { ZERO_ADDRESS } from "@/constants/address"; +import { PODS } from "@/constants/internalTokens"; +import { defaultQuerySettings } from "@/constants/query"; +import { beanstalkAbi } from "@/generated/contractHooks"; +import { useProtocolAddress } from "@/hooks/pinto/useProtocolAddress"; +import { Plot } from "@/utils/types"; +import { useCallback, useMemo } from "react"; +import { toHex } from "viem"; +import { useAccount, useReadContracts } from "wagmi"; + +/** + * ABI snippets for Silo Payback contract functions + * These are the functions needed to fetch urBDV token data for legacy Beanstalk holders + */ +const siloPaybackAbi = [ + { + inputs: [{ internalType: "address", name: "account", type: "address" }], + name: "balanceOfUrBdv", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "account", type: "address" }], + name: "earnedUrBdv", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "account", type: "address" }], + name: "totalDistributedToAccount", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "account", type: "address" }], + name: "totalReceivedByAccount", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, +] as const; + +/** + * ABI snippets for Barn Payback contract functions + * These are the functions needed to fetch fertilizer and sprouts data + */ +const barnPaybackAbi = [ + { + inputs: [{ internalType: "address", name: "account", type: "address" }], + name: "balanceOfFertilizer", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "account", type: "address" }], + name: "balanceOfSprouts", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "account", type: "address" }], + name: "balanceOfFertilized", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "humidity", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, +] as const; + +/** + * Interface for silo payback data + */ +interface SiloPaybackData { + balance: TokenValue; + earned: TokenValue; + totalDistributed: TokenValue; + totalReceived: TokenValue; +} + +/** + * Interface for pods data from repayment field (fieldId=1) + */ +interface PodsData { + plots: Plot[]; + totalPods: TokenValue; +} + +/** + * Interface for fertilizer data + */ +interface FertilizerData { + balance: TokenValue; + sprouts: TokenValue; + fertilized: TokenValue; + humidity: TokenValue; +} + +/** + * Interface for the complete farmer Beanstalk repayment data + */ +export interface FarmerBeanstalkRepaymentData { + silo: SiloPaybackData; + pods: PodsData; + fertilizer: FertilizerData; + isLoading: boolean; + isError: boolean; + refetch: () => Promise; +} + +// Token decimals for urBDV (same as BEAN - 6 decimals) +const URBDV_DECIMALS = 6; +// Token decimals for fertilizer amounts +const FERTILIZER_DECIMALS = 6; +// Humidity is typically represented as a percentage with 2 decimal places +const HUMIDITY_DECIMALS = 2; + +/** + * Hook for fetching farmer-specific Beanstalk repayment data + * + * This hook fetches: + * - Silo payback data (urBDV balance, earned, distributed, received) + * - Pods data from repayment field (fieldId=1) + * - Fertilizer data (balance, sprouts, fertilized, humidity) + * + * @returns FarmerBeanstalkRepaymentData with all farmer obligations data + */ +export function useFarmerBeanstalkRepayment(): FarmerBeanstalkRepaymentData { + const account = useAccount(); + const protocolAddress = useProtocolAddress(); + const farmerAddress = account.address ?? ZERO_ADDRESS; + + // Query for silo payback data + const siloQuery = useReadContracts({ + contracts: [ + { + address: protocolAddress, + abi: siloPaybackAbi, + functionName: "balanceOfUrBdv", + args: [farmerAddress], + }, + { + address: protocolAddress, + abi: siloPaybackAbi, + functionName: "earnedUrBdv", + args: [farmerAddress], + }, + { + address: protocolAddress, + abi: siloPaybackAbi, + functionName: "totalDistributedToAccount", + args: [farmerAddress], + }, + { + address: protocolAddress, + abi: siloPaybackAbi, + functionName: "totalReceivedByAccount", + args: [farmerAddress], + }, + ], + allowFailure: true, + query: { + enabled: !!account.address, + ...defaultQuerySettings, // 20 minutes staleTime + }, + }); + + // Query for pods data from repayment field (fieldId=1) + const podsQuery = useReadContracts({ + contracts: [ + { + address: protocolAddress, + abi: beanstalkAbi, + functionName: "getPlotsFromAccount", + args: [farmerAddress, 1n], // fieldId=1 for repayment field + }, + { + address: protocolAddress, + abi: beanstalkAbi, + functionName: "balanceOfPods", + args: [farmerAddress, 1n], // fieldId=1 for repayment field + }, + ], + allowFailure: true, + query: { + enabled: !!account.address, + ...defaultQuerySettings, // 20 minutes staleTime + }, + }); + + // Query for fertilizer data + const fertilizerQuery = useReadContracts({ + contracts: [ + { + address: protocolAddress, + abi: barnPaybackAbi, + functionName: "balanceOfFertilizer", + args: [farmerAddress], + }, + { + address: protocolAddress, + abi: barnPaybackAbi, + functionName: "balanceOfSprouts", + args: [farmerAddress], + }, + { + address: protocolAddress, + abi: barnPaybackAbi, + functionName: "balanceOfFertilized", + args: [farmerAddress], + }, + { + address: protocolAddress, + abi: barnPaybackAbi, + functionName: "humidity", + args: [], + }, + ], + allowFailure: true, + query: { + enabled: !!account.address, + ...defaultQuerySettings, // 20 minutes staleTime + }, + }); + + // Process silo data + const siloData = useMemo((): SiloPaybackData => { + const balance = siloQuery.data?.[0]?.result; + const earned = siloQuery.data?.[1]?.result; + const totalDistributed = siloQuery.data?.[2]?.result; + const totalReceived = siloQuery.data?.[3]?.result; + + return { + balance: TokenValue.fromBlockchain(balance ?? 0n, URBDV_DECIMALS), + earned: TokenValue.fromBlockchain(earned ?? 0n, URBDV_DECIMALS), + totalDistributed: TokenValue.fromBlockchain(totalDistributed ?? 0n, URBDV_DECIMALS), + totalReceived: TokenValue.fromBlockchain(totalReceived ?? 0n, URBDV_DECIMALS), + }; + }, [siloQuery.data]); + + // Process pods data + const podsData = useMemo((): PodsData => { + const plotsResult = podsQuery.data?.[0]?.result as readonly { index: bigint; pods: bigint }[] | undefined; + const totalPodsResult = podsQuery.data?.[1]?.result; + + const plots: Plot[] = (plotsResult ?? []).map((plotData) => { + const index = TokenValue.fromBigInt(plotData.index, PODS.decimals); + const pods = TokenValue.fromBigInt(plotData.pods, PODS.decimals); + + return { + id: index.toHuman(), + idHex: toHex(`${plotData.index}${plotData.pods}`), + index, + pods, + harvestedPods: TokenValue.ZERO, + harvestablePods: TokenValue.ZERO, + unharvestablePods: pods, + }; + }); + + return { + plots, + totalPods: TokenValue.fromBlockchain(totalPodsResult ?? 0n, PODS.decimals), + }; + }, [podsQuery.data]); + + // Process fertilizer data + const fertilizerData = useMemo((): FertilizerData => { + const balance = fertilizerQuery.data?.[0]?.result; + const sprouts = fertilizerQuery.data?.[1]?.result; + const fertilized = fertilizerQuery.data?.[2]?.result; + const humidity = fertilizerQuery.data?.[3]?.result; + + return { + balance: TokenValue.fromBlockchain(balance ?? 0n, FERTILIZER_DECIMALS), + sprouts: TokenValue.fromBlockchain(sprouts ?? 0n, FERTILIZER_DECIMALS), + fertilized: TokenValue.fromBlockchain(fertilized ?? 0n, FERTILIZER_DECIMALS), + humidity: TokenValue.fromBlockchain(humidity ?? 0n, HUMIDITY_DECIMALS), + }; + }, [fertilizerQuery.data]); + + // Refetch all queries + const refetch = useCallback(async () => { + await Promise.all([siloQuery.refetch(), podsQuery.refetch(), fertilizerQuery.refetch()]); + }, [siloQuery.refetch, podsQuery.refetch, fertilizerQuery.refetch]); + + // Combined loading and error states + const isLoading = siloQuery.isLoading || podsQuery.isLoading || fertilizerQuery.isLoading; + const isError = siloQuery.isError || podsQuery.isError || fertilizerQuery.isError; + + return useMemo( + () => ({ + silo: siloData, + pods: podsData, + fertilizer: fertilizerData, + isLoading, + isError, + refetch, + }), + [siloData, podsData, fertilizerData, isLoading, isError, refetch], + ); +} From b6d1d7578a872d766680ba3aa5641162b8f968f6 Mon Sep 17 00:00:00 2001 From: feyyazcigim Date: Wed, 4 Feb 2026 05:55:26 +0300 Subject: [PATCH 03/22] chore: remove non-existent contract calls and add subgraph TODOs --- src/state/useBeanstalkGlobalStats.ts | 97 ++++----- src/state/useFarmerBeanstalkRepayment.ts | 256 ++++++++--------------- 2 files changed, 125 insertions(+), 228 deletions(-) diff --git a/src/state/useBeanstalkGlobalStats.ts b/src/state/useBeanstalkGlobalStats.ts index 315d47db..0c825214 100644 --- a/src/state/useBeanstalkGlobalStats.ts +++ b/src/state/useBeanstalkGlobalStats.ts @@ -7,23 +7,24 @@ import { useReadContracts } from "wagmi"; /** * ABI snippets for Silo Payback contract global functions + * NOTE: These functions don't exist in the protocol yet - will be indexed from subgraph later */ -const siloPaybackGlobalAbi = [ - { - inputs: [], - name: "totalUrBdvDistributed", - outputs: [{ internalType: "uint256", name: "", type: "uint256" }], - stateMutability: "view", - type: "function", - }, - { - inputs: [], - name: "totalPintoPaidOut", - outputs: [{ internalType: "uint256", name: "", type: "uint256" }], - stateMutability: "view", - type: "function", - }, -] as const; +// const siloPaybackGlobalAbi = [ +// { +// inputs: [], +// name: "totalUrBdvDistributed", +// outputs: [{ internalType: "uint256", name: "", type: "uint256" }], +// stateMutability: "view", +// type: "function", +// }, +// { +// inputs: [], +// name: "totalPintoPaidOut", +// outputs: [{ internalType: "uint256", name: "", type: "uint256" }], +// stateMutability: "view", +// type: "function", +// }, +// ] as const; /** * ABI snippets for Field contract global functions @@ -40,16 +41,17 @@ const fieldGlobalAbi = [ /** * ABI snippets for Barn Payback contract global functions + * NOTE: This function doesn't exist in the protocol yet - will be indexed from subgraph later */ -const barnPaybackGlobalAbi = [ - { - inputs: [], - name: "totalUnfertilizedSprouts", - outputs: [{ internalType: "uint256", name: "", type: "uint256" }], - stateMutability: "view", - type: "function", - }, -] as const; +// const barnPaybackGlobalAbi = [ +// { +// inputs: [], +// name: "totalUnfertilizedSprouts", +// outputs: [{ internalType: "uint256", name: "", type: "uint256" }], +// stateMutability: "view", +// type: "function", +// }, +// ] as const; /** * Interface for the global Beanstalk statistics data @@ -67,7 +69,7 @@ export interface BeanstalkGlobalStatsData { // Token decimals for urBDV (same as BEAN - 6 decimals) const URBDV_DECIMALS = 6; // Token decimals for Pinto (6 decimals) -const PINTO_DECIMALS = 6; +// const PINTO_DECIMALS = 6; // Token decimals for sprouts const SPROUTS_DECIMALS = 6; @@ -75,10 +77,10 @@ const SPROUTS_DECIMALS = 6; * Hook for fetching global Beanstalk repayment statistics * * This hook fetches protocol-wide statistics: - * - Total urBDV distributed across all holders + * - Total urBDV distributed across all holders (TODO: from subgraph) * - Total pods in the repayment field (fieldId=1) - * - Total unfertilized sprouts - * - Total Pinto paid out to holders + * - Total unfertilized sprouts (TODO: from subgraph) + * - Total Pinto paid out to holders (TODO: from subgraph) * * Uses a 5-minute stale time for more frequent updates of global stats * @@ -87,33 +89,20 @@ const SPROUTS_DECIMALS = 6; export function useBeanstalkGlobalStats(): BeanstalkGlobalStatsData { const protocolAddress = useProtocolAddress(); - // Query for all global statistics + // Query for available global statistics (only totalPods exists in protocol) const globalQuery = useReadContracts({ contracts: [ - { - address: protocolAddress, - abi: siloPaybackGlobalAbi, - functionName: "totalUrBdvDistributed", - args: [], - }, { address: protocolAddress, abi: fieldGlobalAbi, functionName: "totalPods", args: [1n], // fieldId=1 for repayment field }, - { - address: protocolAddress, - abi: barnPaybackGlobalAbi, - functionName: "totalUnfertilizedSprouts", - args: [], - }, - { - address: protocolAddress, - abi: siloPaybackGlobalAbi, - functionName: "totalPintoPaidOut", - args: [], - }, + // TODO: These functions don't exist in the protocol yet + // Will be indexed from subgraph later: + // - totalUrBdvDistributed + // - totalUnfertilizedSprouts + // - totalPintoPaidOut ], allowFailure: true, query: { @@ -123,16 +112,14 @@ export function useBeanstalkGlobalStats(): BeanstalkGlobalStatsData { // Process global data const globalData = useMemo(() => { - const totalUrBdvDistributed = globalQuery.data?.[0]?.result; - const totalPodsInRepaymentField = globalQuery.data?.[1]?.result; - const totalUnfertilizedSprouts = globalQuery.data?.[2]?.result; - const totalPintoPaidOut = globalQuery.data?.[3]?.result; + const totalPodsInRepaymentField = globalQuery.data?.[0]?.result; return { - totalUrBdvDistributed: TokenValue.fromBlockchain(totalUrBdvDistributed ?? 0n, URBDV_DECIMALS), + // TODO: These will come from subgraph later + totalUrBdvDistributed: TokenValue.fromBlockchain(0n, URBDV_DECIMALS), totalPodsInRepaymentField: TokenValue.fromBlockchain(totalPodsInRepaymentField ?? 0n, PODS.decimals), - totalUnfertilizedSprouts: TokenValue.fromBlockchain(totalUnfertilizedSprouts ?? 0n, SPROUTS_DECIMALS), - totalPintoPaidOut: TokenValue.fromBlockchain(totalPintoPaidOut ?? 0n, PINTO_DECIMALS), + totalUnfertilizedSprouts: TokenValue.fromBlockchain(0n, SPROUTS_DECIMALS), + totalPintoPaidOut: TokenValue.fromBlockchain(0n, URBDV_DECIMALS), }; }, [globalQuery.data]); diff --git a/src/state/useFarmerBeanstalkRepayment.ts b/src/state/useFarmerBeanstalkRepayment.ts index 8c92ff52..28bc670f 100644 --- a/src/state/useFarmerBeanstalkRepayment.ts +++ b/src/state/useFarmerBeanstalkRepayment.ts @@ -11,82 +11,79 @@ import { useAccount, useReadContracts } from "wagmi"; /** * ABI snippets for Silo Payback contract functions - * These are the functions needed to fetch urBDV token data for legacy Beanstalk holders + * NOTE: These functions don't exist in the protocol yet - will be indexed from subgraph later */ -const siloPaybackAbi = [ - { - inputs: [{ internalType: "address", name: "account", type: "address" }], - name: "balanceOfUrBdv", - outputs: [{ internalType: "uint256", name: "", type: "uint256" }], - stateMutability: "view", - type: "function", - }, - { - inputs: [{ internalType: "address", name: "account", type: "address" }], - name: "earnedUrBdv", - outputs: [{ internalType: "uint256", name: "", type: "uint256" }], - stateMutability: "view", - type: "function", - }, - { - inputs: [{ internalType: "address", name: "account", type: "address" }], - name: "totalDistributedToAccount", - outputs: [{ internalType: "uint256", name: "", type: "uint256" }], - stateMutability: "view", - type: "function", - }, - { - inputs: [{ internalType: "address", name: "account", type: "address" }], - name: "totalReceivedByAccount", - outputs: [{ internalType: "uint256", name: "", type: "uint256" }], - stateMutability: "view", - type: "function", - }, -] as const; +// const siloPaybackAbi = [ +// { +// inputs: [{ internalType: "address", name: "account", type: "address" }], +// name: "balanceOfUrBdv", +// outputs: [{ internalType: "uint256", name: "", type: "uint256" }], +// stateMutability: "view", +// type: "function", +// }, +// { +// inputs: [{ internalType: "address", name: "account", type: "address" }], +// name: "earnedUrBdv", +// outputs: [{ internalType: "uint256", name: "", type: "uint256" }], +// stateMutability: "view", +// type: "function", +// }, +// { +// inputs: [{ internalType: "address", name: "account", type: "address" }], +// name: "totalDistributedToAccount", +// outputs: [{ internalType: "uint256", name: "", type: "uint256" }], +// stateMutability: "view", +// type: "function", +// }, +// { +// inputs: [{ internalType: "address", name: "account", type: "address" }], +// name: "totalReceivedByAccount", +// outputs: [{ internalType: "uint256", name: "", type: "uint256" }], +// stateMutability: "view", +// type: "function", +// }, +// ] as const; /** * ABI snippets for Barn Payback contract functions - * These are the functions needed to fetch fertilizer and sprouts data + * NOTE: These functions don't exist in the protocol yet - will be indexed from subgraph later */ -const barnPaybackAbi = [ - { - inputs: [{ internalType: "address", name: "account", type: "address" }], - name: "balanceOfFertilizer", - outputs: [{ internalType: "uint256", name: "", type: "uint256" }], - stateMutability: "view", - type: "function", - }, - { - inputs: [{ internalType: "address", name: "account", type: "address" }], - name: "balanceOfSprouts", - outputs: [{ internalType: "uint256", name: "", type: "uint256" }], - stateMutability: "view", - type: "function", - }, - { - inputs: [{ internalType: "address", name: "account", type: "address" }], - name: "balanceOfFertilized", - outputs: [{ internalType: "uint256", name: "", type: "uint256" }], - stateMutability: "view", - type: "function", - }, - { - inputs: [], - name: "humidity", - outputs: [{ internalType: "uint256", name: "", type: "uint256" }], - stateMutability: "view", - type: "function", - }, -] as const; +// const barnPaybackAbi = [ +// { +// inputs: [{ internalType: "address", name: "account", type: "address" }], +// name: "balanceOfFertilizer", +// outputs: [{ internalType: "uint256", name: "", type: "uint256" }], +// stateMutability: "view", +// type: "function", +// }, +// { +// inputs: [{ internalType: "address", name: "account", type: "address" }], +// name: "balanceOfSprouts", +// outputs: [{ internalType: "uint256", name: "", type: "uint256" }], +// stateMutability: "view", +// type: "function", +// }, +// { +// inputs: [{ internalType: "address", name: "account", type: "address" }], +// name: "balanceOfFertilized", +// outputs: [{ internalType: "uint256", name: "", type: "uint256" }], +// stateMutability: "view", +// type: "function", +// }, +// { +// inputs: [], +// name: "humidity", +// outputs: [{ internalType: "uint256", name: "", type: "uint256" }], +// stateMutability: "view", +// type: "function", +// }, +// ] as const; /** * Interface for silo payback data */ interface SiloPaybackData { balance: TokenValue; - earned: TokenValue; - totalDistributed: TokenValue; - totalReceived: TokenValue; } /** @@ -102,9 +99,6 @@ interface PodsData { */ interface FertilizerData { balance: TokenValue; - sprouts: TokenValue; - fertilized: TokenValue; - humidity: TokenValue; } /** @@ -123,16 +117,14 @@ export interface FarmerBeanstalkRepaymentData { const URBDV_DECIMALS = 6; // Token decimals for fertilizer amounts const FERTILIZER_DECIMALS = 6; -// Humidity is typically represented as a percentage with 2 decimal places -const HUMIDITY_DECIMALS = 2; /** * Hook for fetching farmer-specific Beanstalk repayment data * * This hook fetches: - * - Silo payback data (urBDV balance, earned, distributed, received) - * - Pods data from repayment field (fieldId=1) - * - Fertilizer data (balance, sprouts, fertilized, humidity) + * - Silo payback data (TODO: from subgraph - urBDV balance) + * - Pods data from repayment field (fieldId=1) - from on-chain + * - Fertilizer data (TODO: from subgraph) * * @returns FarmerBeanstalkRepaymentData with all farmer obligations data */ @@ -141,42 +133,8 @@ export function useFarmerBeanstalkRepayment(): FarmerBeanstalkRepaymentData { const protocolAddress = useProtocolAddress(); const farmerAddress = account.address ?? ZERO_ADDRESS; - // Query for silo payback data - const siloQuery = useReadContracts({ - contracts: [ - { - address: protocolAddress, - abi: siloPaybackAbi, - functionName: "balanceOfUrBdv", - args: [farmerAddress], - }, - { - address: protocolAddress, - abi: siloPaybackAbi, - functionName: "earnedUrBdv", - args: [farmerAddress], - }, - { - address: protocolAddress, - abi: siloPaybackAbi, - functionName: "totalDistributedToAccount", - args: [farmerAddress], - }, - { - address: protocolAddress, - abi: siloPaybackAbi, - functionName: "totalReceivedByAccount", - args: [farmerAddress], - }, - ], - allowFailure: true, - query: { - enabled: !!account.address, - ...defaultQuerySettings, // 20 minutes staleTime - }, - }); - // Query for pods data from repayment field (fieldId=1) + // These are the only functions that exist in the protocol const podsQuery = useReadContracts({ contracts: [ { @@ -194,62 +152,20 @@ export function useFarmerBeanstalkRepayment(): FarmerBeanstalkRepaymentData { ], allowFailure: true, query: { - enabled: !!account.address, - ...defaultQuerySettings, // 20 minutes staleTime - }, - }); - - // Query for fertilizer data - const fertilizerQuery = useReadContracts({ - contracts: [ - { - address: protocolAddress, - abi: barnPaybackAbi, - functionName: "balanceOfFertilizer", - args: [farmerAddress], - }, - { - address: protocolAddress, - abi: barnPaybackAbi, - functionName: "balanceOfSprouts", - args: [farmerAddress], - }, - { - address: protocolAddress, - abi: barnPaybackAbi, - functionName: "balanceOfFertilized", - args: [farmerAddress], - }, - { - address: protocolAddress, - abi: barnPaybackAbi, - functionName: "humidity", - args: [], - }, - ], - allowFailure: true, - query: { - enabled: !!account.address, + // Always fetch - will use ZERO_ADDRESS if wallet not connected ...defaultQuerySettings, // 20 minutes staleTime }, }); - // Process silo data + // TODO: Silo payback data will come from subgraph + // Functions don't exist in protocol: balanceOfUrBdv, earnedUrBdv, totalDistributedToAccount, totalReceivedByAccount const siloData = useMemo((): SiloPaybackData => { - const balance = siloQuery.data?.[0]?.result; - const earned = siloQuery.data?.[1]?.result; - const totalDistributed = siloQuery.data?.[2]?.result; - const totalReceived = siloQuery.data?.[3]?.result; - return { - balance: TokenValue.fromBlockchain(balance ?? 0n, URBDV_DECIMALS), - earned: TokenValue.fromBlockchain(earned ?? 0n, URBDV_DECIMALS), - totalDistributed: TokenValue.fromBlockchain(totalDistributed ?? 0n, URBDV_DECIMALS), - totalReceived: TokenValue.fromBlockchain(totalReceived ?? 0n, URBDV_DECIMALS), + balance: TokenValue.fromBlockchain(0n, URBDV_DECIMALS), }; - }, [siloQuery.data]); + }, []); - // Process pods data + // Process pods data - these functions exist in protocol const podsData = useMemo((): PodsData => { const plotsResult = podsQuery.data?.[0]?.result as readonly { index: bigint; pods: bigint }[] | undefined; const totalPodsResult = podsQuery.data?.[1]?.result; @@ -275,29 +191,23 @@ export function useFarmerBeanstalkRepayment(): FarmerBeanstalkRepaymentData { }; }, [podsQuery.data]); - // Process fertilizer data + // TODO: Fertilizer data will come from subgraph + // Functions don't exist in protocol: balanceOfFertilizer, balanceOfSprouts, balanceOfFertilized + // humidity() exists as getCurrentHumidity() but not needed for now const fertilizerData = useMemo((): FertilizerData => { - const balance = fertilizerQuery.data?.[0]?.result; - const sprouts = fertilizerQuery.data?.[1]?.result; - const fertilized = fertilizerQuery.data?.[2]?.result; - const humidity = fertilizerQuery.data?.[3]?.result; - return { - balance: TokenValue.fromBlockchain(balance ?? 0n, FERTILIZER_DECIMALS), - sprouts: TokenValue.fromBlockchain(sprouts ?? 0n, FERTILIZER_DECIMALS), - fertilized: TokenValue.fromBlockchain(fertilized ?? 0n, FERTILIZER_DECIMALS), - humidity: TokenValue.fromBlockchain(humidity ?? 0n, HUMIDITY_DECIMALS), + balance: TokenValue.fromBlockchain(0n, FERTILIZER_DECIMALS), }; - }, [fertilizerQuery.data]); + }, []); - // Refetch all queries + // Refetch pods query (only one that works) const refetch = useCallback(async () => { - await Promise.all([siloQuery.refetch(), podsQuery.refetch(), fertilizerQuery.refetch()]); - }, [siloQuery.refetch, podsQuery.refetch, fertilizerQuery.refetch]); + await podsQuery.refetch(); + }, [podsQuery.refetch]); - // Combined loading and error states - const isLoading = siloQuery.isLoading || podsQuery.isLoading || fertilizerQuery.isLoading; - const isError = siloQuery.isError || podsQuery.isError || fertilizerQuery.isError; + // Loading and error states only from pods query + const isLoading = podsQuery.isLoading; + const isError = podsQuery.isError; return useMemo( () => ({ From 01f94e7d5d7196d8eaaa1452f9fcc21b7f2c80fc Mon Sep 17 00:00:00 2001 From: "claude[bot]" <41898282+claude[bot]@users.noreply.github.com> Date: Fri, 6 Feb 2026 12:47:03 +0000 Subject: [PATCH 04/22] refactor: extract repayment field ID to constant Extract hardcoded field ID value (1n) to BEANSTALK_REPAYMENT_FIELD_ID constant for better code maintainability and readability. Co-authored-by: frijo Co-Authored-By: Claude Sonnet 4.5 --- src/state/useBeanstalkGlobalStats.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/state/useBeanstalkGlobalStats.ts b/src/state/useBeanstalkGlobalStats.ts index 0c825214..a29ef48e 100644 --- a/src/state/useBeanstalkGlobalStats.ts +++ b/src/state/useBeanstalkGlobalStats.ts @@ -72,6 +72,8 @@ const URBDV_DECIMALS = 6; // const PINTO_DECIMALS = 6; // Token decimals for sprouts const SPROUTS_DECIMALS = 6; +// Field ID for the Beanstalk repayment field +const BEANSTALK_REPAYMENT_FIELD_ID = 1n; /** * Hook for fetching global Beanstalk repayment statistics @@ -96,7 +98,7 @@ export function useBeanstalkGlobalStats(): BeanstalkGlobalStatsData { address: protocolAddress, abi: fieldGlobalAbi, functionName: "totalPods", - args: [1n], // fieldId=1 for repayment field + args: [BEANSTALK_REPAYMENT_FIELD_ID], }, // TODO: These functions don't exist in the protocol yet // Will be indexed from subgraph later: From 01c3a9df99f23574573297f969f5a28c37a008ac Mon Sep 17 00:00:00 2001 From: "claude[bot]" <41898282+claude[bot]@users.noreply.github.com> Date: Fri, 6 Feb 2026 13:19:21 +0000 Subject: [PATCH 05/22] refactor: replace anchor tag with Link component in Beanstalk page Co-authored-by: frijo Co-Authored-By: Claude Sonnet 4.5 --- src/pages/Beanstalk.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/pages/Beanstalk.tsx b/src/pages/Beanstalk.tsx index d05cc05a..7fe23357 100644 --- a/src/pages/Beanstalk.tsx +++ b/src/pages/Beanstalk.tsx @@ -2,6 +2,7 @@ import ReadMoreAccordion from "@/components/ReadMoreAccordion"; import { Card } from "@/components/ui/Card"; import PageContainer from "@/components/ui/PageContainer"; import { Separator } from "@/components/ui/Separator"; +import { Link } from "react-router-dom"; import BeanstalkGlobalCard from "./beanstalk/components/BeanstalkGlobalCard"; import BeanstalkObligationsCard from "./beanstalk/components/BeanstalkObligationsCard"; @@ -19,14 +20,14 @@ const Beanstalk = () => { Beanstalk participants at the time of Pinto launch were issued assets based on their holdings. When Pinto exceeds 1 Billion in supply, 3% of mints go towards these between Beanstalk Silo Tokens, Pods, and Fertilizer.{" "} - Learn more - + From eaf5465160ed01c346888fbdab4295bf5557a2cf Mon Sep 17 00:00:00 2001 From: "claude[bot]" <41898282+claude[bot]@users.noreply.github.com> Date: Fri, 6 Feb 2026 15:41:37 +0000 Subject: [PATCH 06/22] refactor: extract urBDV and Sprouts to internal token definitions - Add URBDV and SPROUTS token definitions to internalTokens.ts - Remove hardcoded URBDV_DECIMALS and SPROUTS_DECIMALS constants - Update useBeanstalkGlobalStats to use token instances for decimals Co-authored-by: frijo --- src/constants/internalTokens.ts | 16 ++++++++++++++++ src/state/useBeanstalkGlobalStats.ts | 14 ++++---------- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/src/constants/internalTokens.ts b/src/constants/internalTokens.ts index 954e7e41..b9a9ceb1 100644 --- a/src/constants/internalTokens.ts +++ b/src/constants/internalTokens.ts @@ -27,3 +27,19 @@ export const PODS: InternalToken = { displayDecimals: 2, logoURI: podLogo, }; + +export const URBDV: InternalToken = { + name: "Unripe BDV", + symbol: "urBDV", + decimals: 6, + displayDecimals: 2, + logoURI: "", // TODO: Add logo if needed +}; + +export const SPROUTS: InternalToken = { + name: "Sprouts", + symbol: "SPROUTS", + decimals: 6, + displayDecimals: 2, + logoURI: "", // TODO: Add logo if needed +}; diff --git a/src/state/useBeanstalkGlobalStats.ts b/src/state/useBeanstalkGlobalStats.ts index a29ef48e..4a3518fb 100644 --- a/src/state/useBeanstalkGlobalStats.ts +++ b/src/state/useBeanstalkGlobalStats.ts @@ -1,5 +1,5 @@ import { TokenValue } from "@/classes/TokenValue"; -import { PODS } from "@/constants/internalTokens"; +import { PODS, SPROUTS, URBDV } from "@/constants/internalTokens"; import { defaultQuerySettingsMedium } from "@/constants/query"; import { useProtocolAddress } from "@/hooks/pinto/useProtocolAddress"; import { useCallback, useMemo } from "react"; @@ -66,12 +66,6 @@ export interface BeanstalkGlobalStatsData { refetch: () => Promise; } -// Token decimals for urBDV (same as BEAN - 6 decimals) -const URBDV_DECIMALS = 6; -// Token decimals for Pinto (6 decimals) -// const PINTO_DECIMALS = 6; -// Token decimals for sprouts -const SPROUTS_DECIMALS = 6; // Field ID for the Beanstalk repayment field const BEANSTALK_REPAYMENT_FIELD_ID = 1n; @@ -118,10 +112,10 @@ export function useBeanstalkGlobalStats(): BeanstalkGlobalStatsData { return { // TODO: These will come from subgraph later - totalUrBdvDistributed: TokenValue.fromBlockchain(0n, URBDV_DECIMALS), + totalUrBdvDistributed: TokenValue.fromBlockchain(0n, URBDV.decimals), totalPodsInRepaymentField: TokenValue.fromBlockchain(totalPodsInRepaymentField ?? 0n, PODS.decimals), - totalUnfertilizedSprouts: TokenValue.fromBlockchain(0n, SPROUTS_DECIMALS), - totalPintoPaidOut: TokenValue.fromBlockchain(0n, URBDV_DECIMALS), + totalUnfertilizedSprouts: TokenValue.fromBlockchain(0n, SPROUTS.decimals), + totalPintoPaidOut: TokenValue.fromBlockchain(0n, URBDV.decimals), }; }, [globalQuery.data]); From 4877ee8b5bb0a232a88b2465cd684f60651ad1f3 Mon Sep 17 00:00:00 2001 From: feyyazcigim Date: Wed, 11 Feb 2026 03:52:04 +0300 Subject: [PATCH 07/22] feat(transfer): add consolidated pod range selection and summary view --- src/pages/transfer/actions/TransferPods.tsx | 12 +- src/pages/transfer/actions/pods/FinalStep.tsx | 52 ++-- src/pages/transfer/actions/pods/StepOne.tsx | 223 +++++++++++++----- src/pages/transfer/actions/pods/StepTwo.tsx | 125 +--------- src/utils/podTransferUtils.ts | 98 ++++++++ 5 files changed, 292 insertions(+), 218 deletions(-) create mode 100644 src/utils/podTransferUtils.ts diff --git a/src/pages/transfer/actions/TransferPods.tsx b/src/pages/transfer/actions/TransferPods.tsx index 54198555..3537ccd7 100644 --- a/src/pages/transfer/actions/TransferPods.tsx +++ b/src/pages/transfer/actions/TransferPods.tsx @@ -39,7 +39,7 @@ export default function TransferPods() { case 1: return "Select Plots"; case 2: - return "Specify amount and address"; + return "Enter address"; default: return "Confirm send"; } @@ -50,13 +50,7 @@ export default function TransferPods() { case 1: return transferData.length > 0; case 2: - if (!!destination && transferNotice) { - if (transferData.length === 1) { - return transferData[0].end.gt(transferData[0].start); - } - return true; - } - return false; + return !!destination && transferNotice; default: return true; } @@ -131,8 +125,6 @@ export default function TransferPods() { ) : step === 2 ? ( { + if (transferData.length === 0) return null; + return computeSummaryRange(transferData, harvestableIndex); + }, [transferData, harvestableIndex]); + + if (!destination || !summary) { return null; } + const { totalPods, placeInLineStart, placeInLineEnd } = summary; + const isSinglePlot = transferData.length === 1; + return (
- {transferData.map((transfer) => { - const placeInLine = transfer.id.sub(harvestableIndex); - const podAmount = transfer.end.sub(transfer.start); - - return ( -
-
- {formatter.number(podAmount)} - Plot - Pods -
-
- @ - {formatter.number(placeInLine.add(transfer.start))} in Line -
-
- ); - })} +
+
+ {formatter.number(totalPods)} + Plot + Pods +
+
+ {isSinglePlot ? ( + <> + @ + {formatter.number(placeInLineStart)} in Line + + ) : ( + + between {formatter.number(placeInLineStart)} - {formatter.number(placeInLineEnd)} in Line + + )} +
+
diff --git a/src/pages/transfer/actions/pods/StepOne.tsx b/src/pages/transfer/actions/pods/StepOne.tsx index 5e08ea3c..9568a1ce 100644 --- a/src/pages/transfer/actions/pods/StepOne.tsx +++ b/src/pages/transfer/actions/pods/StepOne.tsx @@ -1,11 +1,11 @@ -import { TokenValue } from "@/classes/TokenValue"; -import PlotsTable from "@/components/PlotsTable"; -import { Button } from "@/components/ui/Button"; -import { Label } from "@/components/ui/Label"; -import { ToggleGroup } from "@/components/ui/ToggleGroup"; +import PodLineGraph from "@/components/PodLineGraph"; +import { MultiSlider } from "@/components/ui/Slider"; import { useFarmerField } from "@/state/useFarmerField"; +import { useHarvestableIndex } from "@/state/useFieldData"; +import { formatter } from "@/utils/format"; +import { computeTransferData, offsetToAbsoluteIndex } from "@/utils/podTransferUtils"; import { Plot } from "@/utils/types"; -import { Dispatch, SetStateAction, useCallback, useEffect, useState } from "react"; +import { Dispatch, SetStateAction, useCallback, useEffect, useMemo, useRef, useState } from "react"; import { PodTransferData } from "../TransferPods"; interface StepOneProps { @@ -13,81 +13,176 @@ interface StepOneProps { setTransferData: Dispatch>; } +function sortPlotsByIndex(plots: Plot[]): Plot[] { + return [...plots].sort((a, b) => a.index.sub(b.index).toNumber()); +} + export default function StepOne({ transferData, setTransferData }: StepOneProps) { - const [selected, setSelected] = useState(); const { plots } = useFarmerField(); + const harvestableIndex = useHarvestableIndex(); + + const [selectedPlots, setSelectedPlots] = useState([]); + const [podRange, setPodRange] = useState<[number, number]>([0, 0]); + const mountedRef = useRef(false); + + // Restore selection from existing transferData on mount useEffect(() => { - const _newPlots: string[] = []; - for (const data of transferData) { - const _plot = plots.find((plot) => plot.index.eq(data.id)); - if (_plot) { - _newPlots.push(_plot.index.toHuman()); - } + if (mountedRef.current) return; + mountedRef.current = true; + if (transferData.length === 0) return; + const restoredPlots = transferData + .map((data) => plots.find((p) => p.index.eq(data.id))) + .filter((p): p is Plot => p !== undefined); + if (restoredPlots.length > 0) { + const sorted = sortPlotsByIndex(restoredPlots); + setSelectedPlots(sorted); + const total = sorted.reduce((sum, p) => sum + p.pods.toNumber(), 0); + setPodRange([0, total]); } - setSelected(_newPlots); + // Only run on mount + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); + // Total pods across selected plots + const totalPods = useMemo(() => { + return selectedPlots.reduce((sum, p) => sum + p.pods.toNumber(), 0); + }, [selectedPlots]); + + // Derived amount from slider range — no separate state needed + const amount = podRange[1] - podRange[0]; + + // Memoize selectedPlotIndices to avoid new array ref each render + const selectedPlotIndices = useMemo(() => selectedPlots.map((p) => p.index.toHuman()), [selectedPlots]); + + // Position info — plots are already sorted, use first/last directly + const positionInfo = useMemo(() => { + if (selectedPlots.length === 0) return null; + const first = selectedPlots[0]; + const last = selectedPlots[selectedPlots.length - 1]; + return { + start: first.index.sub(harvestableIndex), + end: last.index.add(last.pods).sub(harvestableIndex), + }; + }, [selectedPlots, harvestableIndex]); + + // Compute selectedPodRange for PodLineGraph (absolute indices) + const selectedPodRange = useMemo(() => { + if (selectedPlots.length === 0) return undefined; + return { + start: offsetToAbsoluteIndex(podRange[0], selectedPlots), + end: offsetToAbsoluteIndex(podRange[1], selectedPlots), + }; + }, [selectedPlots, podRange]); + + // Handle plot selection changes: sort, reset slider, update transferData const handlePlotSelection = useCallback( - (value: string[]) => { - // Update selected plots - setSelected(value); - - // Get selected plots data - const selectedPlots = value - .map((plotIndex) => { - const plot = plots.find((p) => p.index.toHuman() === plotIndex); - return plot; - }) - .filter((plot): plot is Plot => plot !== undefined && !plot.fullyHarvested); - - // If no valid plots selected, clear transfer data - if (selectedPlots.length === 0) { + (newPlots: Plot[]) => { + const sorted = sortPlotsByIndex(newPlots); + setSelectedPlots(sorted); + + if (sorted.length > 0) { + const newTotal = sorted.reduce((sum, p) => sum + p.pods.toNumber(), 0); + setPodRange([0, newTotal]); + setTransferData(computeTransferData(sorted, [0, newTotal])); + } else { + setPodRange([0, 0]); setTransferData([]); + } + }, + [setTransferData], + ); + + // Toggle logic: if all in group selected → deselect, else add + const handlePlotGroupSelect = useCallback( + (plotIndices: string[]) => { + const groupSet = new Set(plotIndices); + const plotsInGroup = plots.filter((p) => groupSet.has(p.index.toHuman())); + if (plotsInGroup.length === 0) return; + + const selectedSet = new Set(selectedPlots.map((p) => p.index.toHuman())); + const allSelected = plotIndices.every((idx) => selectedSet.has(idx)); + + if (allSelected) { + handlePlotSelection(selectedPlots.filter((p) => !groupSet.has(p.index.toHuman()))); return; } - // Create plot transfer data - const transferData = selectedPlots.map((plot) => { - return { - id: plot.index, - start: TokenValue.ZERO, - end: plot.pods, - }; - }); - - // Update transfer data - setTransferData(transferData); + const newPlots = [...selectedPlots]; + for (const plotToAdd of plotsInGroup) { + if (!selectedSet.has(plotToAdd.index.toHuman())) { + newPlots.push(plotToAdd); + } + } + handlePlotSelection(newPlots); }, - [plots, setTransferData], + [plots, selectedPlots, handlePlotSelection], ); - const selectAllPlots = useCallback(() => { - const plotIndexes = plots.map((plot) => plot.index.toHuman()); - handlePlotSelection(plotIndexes); - }, [plots, handlePlotSelection]); + // Slider change handler + const handlePodRangeChange = useCallback( + (value: number[]) => { + const newRange: [number, number] = [value[0], value[1]]; + setPodRange(newRange); + setTransferData(computeTransferData(selectedPlots, newRange)); + }, + [selectedPlots, setTransferData], + ); return ( - <> -
- -
-
- - - - +
+ {/* Pod Line Graph Visualization */} +
+ + + {/* Position in Line Display */} + {positionInfo && ( +
+

+ {positionInfo.start.toHuman("short")} - {positionInfo.end.toHuman("short")} +

+
+ )}
- + + {/* Total Pods Summary */} + {totalPods > 0 && ( +
+

Total Pods to send:

+

{formatter.noDec(amount)} Pods

+
+ )} + + {/* MultiSlider for pod range selection */} + {selectedPlots.length > 0 && ( +
+
+

Select Pods

+
+

{formatter.noDec(podRange[0])}

+
+ {totalPods > 0 && ( + + )} +
+

{formatter.noDec(podRange[1])}

+
+
+
+ )} +
); } diff --git a/src/pages/transfer/actions/pods/StepTwo.tsx b/src/pages/transfer/actions/pods/StepTwo.tsx index 1f8c07d7..86237f98 100644 --- a/src/pages/transfer/actions/pods/StepTwo.tsx +++ b/src/pages/transfer/actions/pods/StepTwo.tsx @@ -1,135 +1,19 @@ -import { TokenValue } from "@/classes/TokenValue"; import AddressInputField from "@/components/AddressInputField"; -import { ComboInputField } from "@/components/ComboInputField"; import PintoAssetTransferNotice from "@/components/PintoAssetTransferNotice"; -import PodRangeSelector from "@/components/PodRangeSelector"; import { Label } from "@/components/ui/Label"; -import { PODS } from "@/constants/internalTokens"; -import { useFarmerField } from "@/state/useFarmerField"; -import { Plot } from "@/utils/types"; import { AnimatePresence, motion } from "framer-motion"; -import { Dispatch, SetStateAction, useCallback, useEffect, useState } from "react"; -import { PodTransferData } from "../TransferPods"; +import { Dispatch, SetStateAction } from "react"; interface StepTwoProps { - transferData: PodTransferData[]; - setTransferData: Dispatch>; destination: string | undefined; setDestination: Dispatch>; transferNotice: boolean; setTransferNotice: Dispatch>; } -export default function StepTwo({ - transferData, - setTransferData, - destination, - setDestination, - transferNotice, - setTransferNotice, -}: StepTwoProps) { - const { plots } = useFarmerField(); - const [selectedPlots, setSelectedPlots] = useState([]); - const [amount, setAmount] = useState("0"); - const [range, setRange] = useState<[TokenValue, TokenValue]>([TokenValue.ZERO, TokenValue.ZERO]); - - useEffect(() => { - const _newPlots: Plot[] = []; - for (const data of transferData) { - const _plot = plots.find((plot) => plot.index.eq(data.id)); - if (_plot) { - _newPlots.push(_plot); - } - } - setSelectedPlots(_newPlots); - }, []); - - useEffect(() => { - if (selectedPlots.length === 1) { - const plot = selectedPlots[0]; - setRange([plot.index, plot.index.add(plot.pods)]); - } - }, [selectedPlots]); - - const handleRangeChange = useCallback( - (newRange: TokenValue[]) => { - if (selectedPlots.length === 0 || selectedPlots.length > 1) return; - const plot = selectedPlots[0]; - const newStart = newRange[0]; - const newEnd = newRange[1]; - - const relativeStart = newStart.sub(plot.index); - const relativeEnd = newEnd.sub(plot.index); - - const newData = [ - { - id: plot.index, - start: relativeStart, - end: relativeEnd, - }, - ]; - - const newAmount = newEnd.sub(newStart).toHuman(); - - const batchUpdate = () => { - setRange([newStart, newEnd]); - setTransferData(newData); - setAmount(newAmount); - }; - batchUpdate(); - }, - [selectedPlots, setTransferData], - ); - - const handleAmountChange = useCallback( - (value: string) => { - const newAmount = value; - - if (selectedPlots.length === 0) return; - - const plot = selectedPlots[0]; - const amountValue = TokenValue.fromHuman(newAmount || "0", PODS.decimals); - const newEnd = plot.index.add(amountValue); - - const newStart = plot.index; - const relativeStart = newStart.sub(plot.index); - const relativeEnd = newEnd.sub(plot.index); - - const newData = [ - { - id: plot.index, - start: relativeStart, - end: relativeEnd, - }, - ]; - - const batchUpdate = () => { - setAmount(newAmount); - if (selectedPlots.length === 1) { - setRange([newStart, newEnd]); - setTransferData(newData); - } - }; - batchUpdate(); - }, - [selectedPlots, setTransferData], - ); - +export default function StepTwo({ destination, setDestination, transferNotice, setTransferNotice }: StepTwoProps) { return (
-
- - 1} - altText={selectedPlots.length > 1 ? "Balance:" : "Plot Balance:"} - /> -
@@ -148,11 +32,8 @@ export default function StepTwo({ /> )} - {" "} +
- {selectedPlots.length === 1 && ( - - )}
); } diff --git a/src/utils/podTransferUtils.ts b/src/utils/podTransferUtils.ts new file mode 100644 index 00000000..ebbff175 --- /dev/null +++ b/src/utils/podTransferUtils.ts @@ -0,0 +1,98 @@ +import { TokenValue } from "@/classes/TokenValue"; +import { PODS } from "@/constants/internalTokens"; +import { PodTransferData } from "@/pages/transfer/actions/TransferPods"; +import { Plot } from "@/utils/types"; + +/** + * Converts a cumulative offset range [rangeStart, rangeEnd] across sorted plots + * into per-plot PodTransferData records with relative start/end values. + * + * Adapted from CreateListing's `listingData` useMemo logic. + * + * @param selectedPlots - Plots sorted by index + * @param podRange - [rangeStart, rangeEnd] cumulative offset (0 to totalPods) + * @returns PodTransferData[] with one entry per intersecting plot + */ +export function computeTransferData(selectedPlots: Plot[], podRange: [number, number]): PodTransferData[] { + const result: PodTransferData[] = []; + let cumulativeStart = 0; + + for (const plot of selectedPlots) { + const plotPods = plot.pods.toNumber(); + const cumulativeEnd = cumulativeStart + plotPods; + + // Check if this plot intersects with the selected range + if (podRange[1] > cumulativeStart && podRange[0] < cumulativeEnd) { + const startInPlot = Math.max(0, podRange[0] - cumulativeStart); + const endInPlot = Math.min(plotPods, podRange[1] - cumulativeStart); + + if (endInPlot > startInPlot) { + result.push({ + id: plot.index, + start: TokenValue.fromHuman(startInPlot, PODS.decimals), + end: TokenValue.fromHuman(endInPlot, PODS.decimals), + }); + } + } + + cumulativeStart = cumulativeEnd; + } + + return result; +} + +/** + * Converts a cumulative offset (relative to sorted plots) into an absolute + * TokenValue index on the pod line. + * + * Adapted from CreateListing's `selectedPodRange` useMemo logic. + * + * @param offset - Cumulative offset (0 to totalPods) + * @param sortedPlots - Plots sorted by index + * @returns Absolute TokenValue index on the pod line + */ +export function offsetToAbsoluteIndex(offset: number, sortedPlots: Plot[]): TokenValue { + let remainingOffset = offset; + + for (const plot of sortedPlots) { + const plotPods = plot.pods.toNumber(); + if (remainingOffset <= plotPods) { + return plot.index.add(TokenValue.fromHuman(remainingOffset, PODS.decimals)); + } + remainingOffset -= plotPods; + } + + // Fallback: offset exceeds total pods, clamp to end of last plot + const lastPlot = sortedPlots[sortedPlots.length - 1]; + return lastPlot.index.add(lastPlot.pods); +} + +/** + * Computes a consolidated summary range from transfer data records. + * + * - totalPods: sum of (end - start) across all records + * - placeInLineStart: first record's (id + start) - harvestableIndex + * - placeInLineEnd: last record's (id + end) - harvestableIndex + * + * @param transferData - Array of PodTransferData (must have at least one entry) + * @param harvestableIndex - Current harvestable index on the pod line + * @returns { totalPods, placeInLineStart, placeInLineEnd } + */ +export function computeSummaryRange( + transferData: PodTransferData[], + harvestableIndex: TokenValue, +): { totalPods: TokenValue; placeInLineStart: TokenValue; placeInLineEnd: TokenValue } { + const totalPods = transferData.reduce((sum, record) => sum.add(record.end.sub(record.start)), TokenValue.ZERO); + + const first = transferData[0]; + const last = transferData[transferData.length - 1]; + + const rangeStart = first.id.add(first.start); + const rangeEnd = last.id.add(last.end); + + return { + totalPods, + placeInLineStart: rangeStart.sub(harvestableIndex), + placeInLineEnd: rangeEnd.sub(harvestableIndex), + }; +} From 9368d259a329f94104a50fee92bd2c969fe5725b Mon Sep 17 00:00:00 2001 From: feyyazcigim Date: Wed, 11 Feb 2026 04:24:18 +0300 Subject: [PATCH 08/22] feat(market): support marketplace filtering and update plot metadata --- src/generated/gql/exchange/graphql.ts | 94 +++---------------- src/generated/gql/pintostalk/gql.ts | 6 +- src/generated/gql/pintostalk/graphql.ts | 16 +++- src/pages/Market.tsx | 11 ++- .../podmarket/AllMarketActivity.graphql | 13 ++- src/state/market/useAllMarket.ts | 11 ++- 6 files changed, 59 insertions(+), 92 deletions(-) diff --git a/src/generated/gql/exchange/graphql.ts b/src/generated/gql/exchange/graphql.ts index 96429785..bbcf894a 100644 --- a/src/generated/gql/exchange/graphql.ts +++ b/src/generated/gql/exchange/graphql.ts @@ -29,44 +29,6 @@ export type Scalars = { Timestamp: { input: any; output: any; } }; -export type Account = { - __typename?: 'Account'; - id: Scalars['Bytes']['output']; - trades: Array; -}; - - -export type AccountTradesArgs = { - first?: InputMaybe; - orderBy?: InputMaybe; - orderDirection?: InputMaybe; - skip?: InputMaybe; - where?: InputMaybe; -}; - -export type AccountFilter = { - /** Filter for the block changed event. */ - _change_block?: InputMaybe; - and?: InputMaybe>>; - id?: InputMaybe; - id_contains?: InputMaybe; - id_gt?: InputMaybe; - id_gte?: InputMaybe; - id_in?: InputMaybe>; - id_lt?: InputMaybe; - id_lte?: InputMaybe; - id_not?: InputMaybe; - id_not_contains?: InputMaybe; - id_not_in?: InputMaybe>; - or?: InputMaybe>>; - trades_?: InputMaybe; -}; - -export enum AccountOrderBy { - id = 'id', - trades = 'trades' -} - export enum AggregationInterval { day = 'day', hour = 'hour' @@ -1284,6 +1246,7 @@ export type ConvertCandidateFilter = { export enum ConvertCandidateOrderBy { addLiquidityTrade = 'addLiquidityTrade', + addLiquidityTrade__account = 'addLiquidityTrade__account', addLiquidityTrade__blockNumber = 'addLiquidityTrade__blockNumber', addLiquidityTrade__hash = 'addLiquidityTrade__hash', addLiquidityTrade__id = 'addLiquidityTrade__id', @@ -1298,6 +1261,7 @@ export enum ConvertCandidateOrderBy { addLiquidityTrade__transferVolumeUSD = 'addLiquidityTrade__transferVolumeUSD', id = 'id', removeLiquidityTrade = 'removeLiquidityTrade', + removeLiquidityTrade__account = 'removeLiquidityTrade__account', removeLiquidityTrade__blockNumber = 'removeLiquidityTrade__blockNumber', removeLiquidityTrade__hash = 'removeLiquidityTrade__hash', removeLiquidityTrade__id = 'removeLiquidityTrade__id', @@ -1402,8 +1366,6 @@ export type Query = { __typename?: 'Query'; /** Access to subgraph metadata */ _meta?: Maybe; - account?: Maybe; - accounts: Array; aquifer?: Maybe; aquifers: Array; beanstalk?: Maybe; @@ -1444,24 +1406,6 @@ export type QueryMetaArgs = { }; -export type QueryAccountArgs = { - block?: InputMaybe; - id: Scalars['ID']['input']; - subgraphError?: SubgraphErrorPolicy; -}; - - -export type QueryAccountsArgs = { - block?: InputMaybe; - first?: InputMaybe; - orderBy?: InputMaybe; - orderDirection?: InputMaybe; - skip?: InputMaybe; - subgraphError?: SubgraphErrorPolicy; - where?: InputMaybe; -}; - - export type QueryAquiferArgs = { block?: InputMaybe; id: Scalars['ID']['input']; @@ -1953,7 +1897,7 @@ export enum TokenOrderBy { export type Trade = { __typename?: 'Trade'; /** Account that sent this transaction */ - account: Account; + account: Scalars['Bytes']['output']; /** Well.reserves after this event */ afterReserves: Array; /** Well.tokenRates before this event */ @@ -2015,27 +1959,16 @@ export enum TradeType { export type TradeFilter = { /** Filter for the block changed event. */ _change_block?: InputMaybe; - account?: InputMaybe; - account_?: InputMaybe; - account_contains?: InputMaybe; - account_contains_nocase?: InputMaybe; - account_ends_with?: InputMaybe; - account_ends_with_nocase?: InputMaybe; - account_gt?: InputMaybe; - account_gte?: InputMaybe; - account_in?: InputMaybe>; - account_lt?: InputMaybe; - account_lte?: InputMaybe; - account_not?: InputMaybe; - account_not_contains?: InputMaybe; - account_not_contains_nocase?: InputMaybe; - account_not_ends_with?: InputMaybe; - account_not_ends_with_nocase?: InputMaybe; - account_not_in?: InputMaybe>; - account_not_starts_with?: InputMaybe; - account_not_starts_with_nocase?: InputMaybe; - account_starts_with?: InputMaybe; - account_starts_with_nocase?: InputMaybe; + account?: InputMaybe; + account_contains?: InputMaybe; + account_gt?: InputMaybe; + account_gte?: InputMaybe; + account_in?: InputMaybe>; + account_lt?: InputMaybe; + account_lte?: InputMaybe; + account_not?: InputMaybe; + account_not_contains?: InputMaybe; + account_not_in?: InputMaybe>; afterReserves?: InputMaybe>; afterReserves_contains?: InputMaybe>; afterReserves_contains_nocase?: InputMaybe>; @@ -2255,7 +2188,6 @@ export type TradeFilter = { export enum TradeOrderBy { account = 'account', - account__id = 'account__id', afterReserves = 'afterReserves', afterTokenRates = 'afterTokenRates', beforeReserves = 'beforeReserves', diff --git a/src/generated/gql/pintostalk/gql.ts b/src/generated/gql/pintostalk/gql.ts index 508c651f..ec851937 100644 --- a/src/generated/gql/pintostalk/gql.ts +++ b/src/generated/gql/pintostalk/gql.ts @@ -22,7 +22,7 @@ type Documents = { "query BeanstalkSeasonsTable($from: Int, $to: Int) {\n seasons(\n first: 1000\n orderBy: season\n orderDirection: desc\n where: {season_gte: $from, season_lte: $to}\n ) {\n id\n sunriseBlock\n rewardBeans\n price\n deltaBeans\n raining\n season\n }\n fieldHourlySnapshots(\n first: 1000\n orderBy: season\n orderDirection: desc\n where: {field: \"0xd1a0d188e861ed9d15773a2f3574a2e94134ba8f\", season_gte: $from, season_lte: $to}\n ) {\n id\n caseId\n issuedSoil\n deltaSownBeans\n sownBeans\n deltaPodDemand\n blocksToSoldOutSoil\n podRate\n temperature\n deltaTemperature\n season\n }\n siloHourlySnapshots(\n first: 1000\n orderBy: season\n orderDirection: desc\n where: {silo: \"0xd1a0d188e861ed9d15773a2f3574a2e94134ba8f\", season_gte: $from, season_lte: $to}\n ) {\n id\n beanToMaxLpGpPerBdvRatio\n deltaBeanToMaxLpGpPerBdvRatio\n season\n }\n}": typeof types.BeanstalkSeasonsTableDocument, "query SiloSnapshots($first: Int!, $id: Bytes!) {\n siloHourlySnapshots(\n first: $first\n orderBy: season\n orderDirection: desc\n where: {silo_: {id: $id}}\n ) {\n beanToMaxLpGpPerBdvRatio\n deltaBeanMints\n season\n }\n}": typeof types.SiloSnapshotsDocument, "query SiloYields {\n siloYields(\n orderBy: season\n orderDirection: desc\n where: {emaWindow: ROLLING_30_DAY}\n first: 1\n ) {\n beansPerSeasonEMA\n beta\n createdAt\n season\n id\n u\n whitelistedTokens\n emaWindow\n tokenAPYS {\n beanAPY\n stalkAPY\n season\n createdAt\n token\n }\n }\n}": typeof types.SiloYieldsDocument, - "query AllMarketActivity($first: Int = 1000, $listings_createdAt_gt: BigInt, $orders_createdAt_gt: BigInt, $fill_createdAt_gt: BigInt) {\n podListings(\n first: $first\n where: {createdAt_gt: $listings_createdAt_gt, status_not: FILLED_PARTIAL}\n ) {\n ...PodListing\n }\n podOrders(\n first: $first\n orderBy: createdAt\n orderDirection: desc\n where: {createdAt_gt: $orders_createdAt_gt}\n ) {\n ...PodOrder\n }\n podFills(first: $first, where: {createdAt_gt: $fill_createdAt_gt}) {\n ...PodFill\n }\n}": typeof types.AllMarketActivityDocument, + "query AllMarketActivity($first: Int = 1000, $listings_createdAt_gt: BigInt, $orders_createdAt_gt: BigInt, $fill_createdAt_gt: BigInt, $listings_podMarketplace: String, $orders_podMarketplace: String) {\n podListings(\n first: $first\n where: {createdAt_gt: $listings_createdAt_gt, status_not: FILLED_PARTIAL, podMarketplace: $listings_podMarketplace}\n ) {\n ...PodListing\n }\n podOrders(\n first: $first\n orderBy: createdAt\n orderDirection: desc\n where: {createdAt_gt: $orders_createdAt_gt, podMarketplace: $orders_podMarketplace}\n ) {\n ...PodOrder\n }\n podFills(first: $first, where: {createdAt_gt: $fill_createdAt_gt}) {\n ...PodFill\n }\n}": typeof types.AllMarketActivityDocument, "query AllPodListings($first: Int = 1000, $status: MarketStatus = ACTIVE, $maxHarvestableIndex: BigInt!, $skip: Int = 0) {\n podListings(\n first: $first\n skip: $skip\n where: {status: $status, maxHarvestableIndex_gt: $maxHarvestableIndex, remainingAmount_gt: \"100000\"}\n orderBy: index\n orderDirection: asc\n ) {\n ...PodListing\n }\n}": typeof types.AllPodListingsDocument, "query AllPodOrders($first: Int = 1000, $status: MarketStatus = ACTIVE, $skip: Int = 0) {\n podOrders(\n first: $first\n skip: $skip\n orderBy: createdAt\n orderDirection: desc\n where: {status: $status}\n ) {\n ...PodOrder\n }\n}": typeof types.AllPodOrdersDocument, "query FarmerMarketActivity($first: Int = 1000, $account: String!, $listings_createdAt_gt: BigInt, $orders_createdAt_gt: BigInt, $fill_createdAt_gt: BigInt) {\n podListings(\n first: $first\n where: {farmer: $account, createdAt_gt: $listings_createdAt_gt, status_not: FILLED_PARTIAL}\n ) {\n ...PodListing\n }\n podOrders(\n first: $first\n orderBy: createdAt\n orderDirection: desc\n where: {farmer: $account, createdAt_gt: $orders_createdAt_gt}\n ) {\n ...PodOrder\n }\n podFills(\n first: $first\n where: {and: [{createdAt_gt: $fill_createdAt_gt}, {or: [{fromFarmer: $account}, {toFarmer: $account}]}]}\n ) {\n ...PodFill\n }\n}": typeof types.FarmerMarketActivityDocument, @@ -50,7 +50,7 @@ const documents: Documents = { "query BeanstalkSeasonsTable($from: Int, $to: Int) {\n seasons(\n first: 1000\n orderBy: season\n orderDirection: desc\n where: {season_gte: $from, season_lte: $to}\n ) {\n id\n sunriseBlock\n rewardBeans\n price\n deltaBeans\n raining\n season\n }\n fieldHourlySnapshots(\n first: 1000\n orderBy: season\n orderDirection: desc\n where: {field: \"0xd1a0d188e861ed9d15773a2f3574a2e94134ba8f\", season_gte: $from, season_lte: $to}\n ) {\n id\n caseId\n issuedSoil\n deltaSownBeans\n sownBeans\n deltaPodDemand\n blocksToSoldOutSoil\n podRate\n temperature\n deltaTemperature\n season\n }\n siloHourlySnapshots(\n first: 1000\n orderBy: season\n orderDirection: desc\n where: {silo: \"0xd1a0d188e861ed9d15773a2f3574a2e94134ba8f\", season_gte: $from, season_lte: $to}\n ) {\n id\n beanToMaxLpGpPerBdvRatio\n deltaBeanToMaxLpGpPerBdvRatio\n season\n }\n}": types.BeanstalkSeasonsTableDocument, "query SiloSnapshots($first: Int!, $id: Bytes!) {\n siloHourlySnapshots(\n first: $first\n orderBy: season\n orderDirection: desc\n where: {silo_: {id: $id}}\n ) {\n beanToMaxLpGpPerBdvRatio\n deltaBeanMints\n season\n }\n}": types.SiloSnapshotsDocument, "query SiloYields {\n siloYields(\n orderBy: season\n orderDirection: desc\n where: {emaWindow: ROLLING_30_DAY}\n first: 1\n ) {\n beansPerSeasonEMA\n beta\n createdAt\n season\n id\n u\n whitelistedTokens\n emaWindow\n tokenAPYS {\n beanAPY\n stalkAPY\n season\n createdAt\n token\n }\n }\n}": types.SiloYieldsDocument, - "query AllMarketActivity($first: Int = 1000, $listings_createdAt_gt: BigInt, $orders_createdAt_gt: BigInt, $fill_createdAt_gt: BigInt) {\n podListings(\n first: $first\n where: {createdAt_gt: $listings_createdAt_gt, status_not: FILLED_PARTIAL}\n ) {\n ...PodListing\n }\n podOrders(\n first: $first\n orderBy: createdAt\n orderDirection: desc\n where: {createdAt_gt: $orders_createdAt_gt}\n ) {\n ...PodOrder\n }\n podFills(first: $first, where: {createdAt_gt: $fill_createdAt_gt}) {\n ...PodFill\n }\n}": types.AllMarketActivityDocument, + "query AllMarketActivity($first: Int = 1000, $listings_createdAt_gt: BigInt, $orders_createdAt_gt: BigInt, $fill_createdAt_gt: BigInt, $listings_podMarketplace: String, $orders_podMarketplace: String) {\n podListings(\n first: $first\n where: {createdAt_gt: $listings_createdAt_gt, status_not: FILLED_PARTIAL, podMarketplace: $listings_podMarketplace}\n ) {\n ...PodListing\n }\n podOrders(\n first: $first\n orderBy: createdAt\n orderDirection: desc\n where: {createdAt_gt: $orders_createdAt_gt, podMarketplace: $orders_podMarketplace}\n ) {\n ...PodOrder\n }\n podFills(first: $first, where: {createdAt_gt: $fill_createdAt_gt}) {\n ...PodFill\n }\n}": types.AllMarketActivityDocument, "query AllPodListings($first: Int = 1000, $status: MarketStatus = ACTIVE, $maxHarvestableIndex: BigInt!, $skip: Int = 0) {\n podListings(\n first: $first\n skip: $skip\n where: {status: $status, maxHarvestableIndex_gt: $maxHarvestableIndex, remainingAmount_gt: \"100000\"}\n orderBy: index\n orderDirection: asc\n ) {\n ...PodListing\n }\n}": types.AllPodListingsDocument, "query AllPodOrders($first: Int = 1000, $status: MarketStatus = ACTIVE, $skip: Int = 0) {\n podOrders(\n first: $first\n skip: $skip\n orderBy: createdAt\n orderDirection: desc\n where: {status: $status}\n ) {\n ...PodOrder\n }\n}": types.AllPodOrdersDocument, "query FarmerMarketActivity($first: Int = 1000, $account: String!, $listings_createdAt_gt: BigInt, $orders_createdAt_gt: BigInt, $fill_createdAt_gt: BigInt) {\n podListings(\n first: $first\n where: {farmer: $account, createdAt_gt: $listings_createdAt_gt, status_not: FILLED_PARTIAL}\n ) {\n ...PodListing\n }\n podOrders(\n first: $first\n orderBy: createdAt\n orderDirection: desc\n where: {farmer: $account, createdAt_gt: $orders_createdAt_gt}\n ) {\n ...PodOrder\n }\n podFills(\n first: $first\n where: {and: [{createdAt_gt: $fill_createdAt_gt}, {or: [{fromFarmer: $account}, {toFarmer: $account}]}]}\n ) {\n ...PodFill\n }\n}": types.FarmerMarketActivityDocument, @@ -119,7 +119,7 @@ export function graphql(source: "query SiloYields {\n siloYields(\n orderBy: /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "query AllMarketActivity($first: Int = 1000, $listings_createdAt_gt: BigInt, $orders_createdAt_gt: BigInt, $fill_createdAt_gt: BigInt) {\n podListings(\n first: $first\n where: {createdAt_gt: $listings_createdAt_gt, status_not: FILLED_PARTIAL}\n ) {\n ...PodListing\n }\n podOrders(\n first: $first\n orderBy: createdAt\n orderDirection: desc\n where: {createdAt_gt: $orders_createdAt_gt}\n ) {\n ...PodOrder\n }\n podFills(first: $first, where: {createdAt_gt: $fill_createdAt_gt}) {\n ...PodFill\n }\n}"): (typeof documents)["query AllMarketActivity($first: Int = 1000, $listings_createdAt_gt: BigInt, $orders_createdAt_gt: BigInt, $fill_createdAt_gt: BigInt) {\n podListings(\n first: $first\n where: {createdAt_gt: $listings_createdAt_gt, status_not: FILLED_PARTIAL}\n ) {\n ...PodListing\n }\n podOrders(\n first: $first\n orderBy: createdAt\n orderDirection: desc\n where: {createdAt_gt: $orders_createdAt_gt}\n ) {\n ...PodOrder\n }\n podFills(first: $first, where: {createdAt_gt: $fill_createdAt_gt}) {\n ...PodFill\n }\n}"]; +export function graphql(source: "query AllMarketActivity($first: Int = 1000, $listings_createdAt_gt: BigInt, $orders_createdAt_gt: BigInt, $fill_createdAt_gt: BigInt, $listings_podMarketplace: String, $orders_podMarketplace: String) {\n podListings(\n first: $first\n where: {createdAt_gt: $listings_createdAt_gt, status_not: FILLED_PARTIAL, podMarketplace: $listings_podMarketplace}\n ) {\n ...PodListing\n }\n podOrders(\n first: $first\n orderBy: createdAt\n orderDirection: desc\n where: {createdAt_gt: $orders_createdAt_gt, podMarketplace: $orders_podMarketplace}\n ) {\n ...PodOrder\n }\n podFills(first: $first, where: {createdAt_gt: $fill_createdAt_gt}) {\n ...PodFill\n }\n}"): (typeof documents)["query AllMarketActivity($first: Int = 1000, $listings_createdAt_gt: BigInt, $orders_createdAt_gt: BigInt, $fill_createdAt_gt: BigInt, $listings_podMarketplace: String, $orders_podMarketplace: String) {\n podListings(\n first: $first\n where: {createdAt_gt: $listings_createdAt_gt, status_not: FILLED_PARTIAL, podMarketplace: $listings_podMarketplace}\n ) {\n ...PodListing\n }\n podOrders(\n first: $first\n orderBy: createdAt\n orderDirection: desc\n where: {createdAt_gt: $orders_createdAt_gt, podMarketplace: $orders_podMarketplace}\n ) {\n ...PodOrder\n }\n podFills(first: $first, where: {createdAt_gt: $fill_createdAt_gt}) {\n ...PodFill\n }\n}"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ diff --git a/src/generated/gql/pintostalk/graphql.ts b/src/generated/gql/pintostalk/graphql.ts index 78aa7d75..98e8681f 100644 --- a/src/generated/gql/pintostalk/graphql.ts +++ b/src/generated/gql/pintostalk/graphql.ts @@ -3413,6 +3413,8 @@ export type Plot = { __typename?: 'Plot'; /** Number of beans spent for each pod, whether through sowing or on the marketplace */ beansPerPod: Scalars['BigInt']['output']; + /** Block number of the most recent plot combination, null if never combined */ + combinedAtBlock?: Maybe; /** Timestamp of entity creation (not sown) */ createdAt: Scalars['BigInt']['output']; /** Transaction hash of when this plot entity was created (not sown) */ @@ -3496,6 +3498,14 @@ export type PlotFilter = { beansPerPod_lte?: InputMaybe; beansPerPod_not?: InputMaybe; beansPerPod_not_in?: InputMaybe>; + combinedAtBlock?: InputMaybe; + combinedAtBlock_gt?: InputMaybe; + combinedAtBlock_gte?: InputMaybe; + combinedAtBlock_in?: InputMaybe>; + combinedAtBlock_lt?: InputMaybe; + combinedAtBlock_lte?: InputMaybe; + combinedAtBlock_not?: InputMaybe; + combinedAtBlock_not_in?: InputMaybe>; createdAt?: InputMaybe; createdAt_gt?: InputMaybe; createdAt_gte?: InputMaybe; @@ -3811,6 +3821,7 @@ export type PlotFilter = { export enum PlotOrderBy { beansPerPod = 'beansPerPod', + combinedAtBlock = 'combinedAtBlock', createdAt = 'createdAt', creationHash = 'creationHash', farmer = 'farmer', @@ -5140,6 +5151,7 @@ export enum PodListingOrderBy { originalPlaceInLine = 'originalPlaceInLine', plot = 'plot', plot__beansPerPod = 'plot__beansPerPod', + plot__combinedAtBlock = 'plot__combinedAtBlock', plot__createdAt = 'plot__createdAt', plot__creationHash = 'plot__creationHash', plot__fieldId = 'plot__fieldId', @@ -14095,6 +14107,8 @@ export type AllMarketActivityQueryVariables = Exact<{ listings_createdAt_gt?: InputMaybe; orders_createdAt_gt?: InputMaybe; fill_createdAt_gt?: InputMaybe; + listings_podMarketplace?: InputMaybe; + orders_podMarketplace?: InputMaybe; }>; @@ -14239,7 +14253,7 @@ export const FieldSnapshotsDocument = {"kind":"Document","definitions":[{"kind": export const BeanstalkSeasonsTableDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"BeanstalkSeasonsTable"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"from"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"to"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"seasons"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"first"},"value":{"kind":"IntValue","value":"1000"}},{"kind":"Argument","name":{"kind":"Name","value":"orderBy"},"value":{"kind":"EnumValue","value":"season"}},{"kind":"Argument","name":{"kind":"Name","value":"orderDirection"},"value":{"kind":"EnumValue","value":"desc"}},{"kind":"Argument","name":{"kind":"Name","value":"where"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"season_gte"},"value":{"kind":"Variable","name":{"kind":"Name","value":"from"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"season_lte"},"value":{"kind":"Variable","name":{"kind":"Name","value":"to"}}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"sunriseBlock"}},{"kind":"Field","name":{"kind":"Name","value":"rewardBeans"}},{"kind":"Field","name":{"kind":"Name","value":"price"}},{"kind":"Field","name":{"kind":"Name","value":"deltaBeans"}},{"kind":"Field","name":{"kind":"Name","value":"raining"}},{"kind":"Field","name":{"kind":"Name","value":"season"}}]}},{"kind":"Field","name":{"kind":"Name","value":"fieldHourlySnapshots"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"first"},"value":{"kind":"IntValue","value":"1000"}},{"kind":"Argument","name":{"kind":"Name","value":"orderBy"},"value":{"kind":"EnumValue","value":"season"}},{"kind":"Argument","name":{"kind":"Name","value":"orderDirection"},"value":{"kind":"EnumValue","value":"desc"}},{"kind":"Argument","name":{"kind":"Name","value":"where"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"field"},"value":{"kind":"StringValue","value":"0xd1a0d188e861ed9d15773a2f3574a2e94134ba8f","block":false}},{"kind":"ObjectField","name":{"kind":"Name","value":"season_gte"},"value":{"kind":"Variable","name":{"kind":"Name","value":"from"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"season_lte"},"value":{"kind":"Variable","name":{"kind":"Name","value":"to"}}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"caseId"}},{"kind":"Field","name":{"kind":"Name","value":"issuedSoil"}},{"kind":"Field","name":{"kind":"Name","value":"deltaSownBeans"}},{"kind":"Field","name":{"kind":"Name","value":"sownBeans"}},{"kind":"Field","name":{"kind":"Name","value":"deltaPodDemand"}},{"kind":"Field","name":{"kind":"Name","value":"blocksToSoldOutSoil"}},{"kind":"Field","name":{"kind":"Name","value":"podRate"}},{"kind":"Field","name":{"kind":"Name","value":"temperature"}},{"kind":"Field","name":{"kind":"Name","value":"deltaTemperature"}},{"kind":"Field","name":{"kind":"Name","value":"season"}}]}},{"kind":"Field","name":{"kind":"Name","value":"siloHourlySnapshots"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"first"},"value":{"kind":"IntValue","value":"1000"}},{"kind":"Argument","name":{"kind":"Name","value":"orderBy"},"value":{"kind":"EnumValue","value":"season"}},{"kind":"Argument","name":{"kind":"Name","value":"orderDirection"},"value":{"kind":"EnumValue","value":"desc"}},{"kind":"Argument","name":{"kind":"Name","value":"where"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"silo"},"value":{"kind":"StringValue","value":"0xd1a0d188e861ed9d15773a2f3574a2e94134ba8f","block":false}},{"kind":"ObjectField","name":{"kind":"Name","value":"season_gte"},"value":{"kind":"Variable","name":{"kind":"Name","value":"from"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"season_lte"},"value":{"kind":"Variable","name":{"kind":"Name","value":"to"}}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"beanToMaxLpGpPerBdvRatio"}},{"kind":"Field","name":{"kind":"Name","value":"deltaBeanToMaxLpGpPerBdvRatio"}},{"kind":"Field","name":{"kind":"Name","value":"season"}}]}}]}}]} as unknown as DocumentNode; export const SiloSnapshotsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"SiloSnapshots"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"first"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Bytes"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"siloHourlySnapshots"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"first"},"value":{"kind":"Variable","name":{"kind":"Name","value":"first"}}},{"kind":"Argument","name":{"kind":"Name","value":"orderBy"},"value":{"kind":"EnumValue","value":"season"}},{"kind":"Argument","name":{"kind":"Name","value":"orderDirection"},"value":{"kind":"EnumValue","value":"desc"}},{"kind":"Argument","name":{"kind":"Name","value":"where"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"silo_"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}]}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"beanToMaxLpGpPerBdvRatio"}},{"kind":"Field","name":{"kind":"Name","value":"deltaBeanMints"}},{"kind":"Field","name":{"kind":"Name","value":"season"}}]}}]}}]} as unknown as DocumentNode; export const SiloYieldsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"SiloYields"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"siloYields"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"orderBy"},"value":{"kind":"EnumValue","value":"season"}},{"kind":"Argument","name":{"kind":"Name","value":"orderDirection"},"value":{"kind":"EnumValue","value":"desc"}},{"kind":"Argument","name":{"kind":"Name","value":"where"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"emaWindow"},"value":{"kind":"EnumValue","value":"ROLLING_30_DAY"}}]}},{"kind":"Argument","name":{"kind":"Name","value":"first"},"value":{"kind":"IntValue","value":"1"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"beansPerSeasonEMA"}},{"kind":"Field","name":{"kind":"Name","value":"beta"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"season"}},{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"u"}},{"kind":"Field","name":{"kind":"Name","value":"whitelistedTokens"}},{"kind":"Field","name":{"kind":"Name","value":"emaWindow"}},{"kind":"Field","name":{"kind":"Name","value":"tokenAPYS"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"beanAPY"}},{"kind":"Field","name":{"kind":"Name","value":"stalkAPY"}},{"kind":"Field","name":{"kind":"Name","value":"season"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"token"}}]}}]}}]}}]} as unknown as DocumentNode; -export const AllMarketActivityDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"AllMarketActivity"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"first"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}},"defaultValue":{"kind":"IntValue","value":"1000"}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"listings_createdAt_gt"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"BigInt"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"orders_createdAt_gt"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"BigInt"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"fill_createdAt_gt"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"BigInt"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"podListings"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"first"},"value":{"kind":"Variable","name":{"kind":"Name","value":"first"}}},{"kind":"Argument","name":{"kind":"Name","value":"where"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"createdAt_gt"},"value":{"kind":"Variable","name":{"kind":"Name","value":"listings_createdAt_gt"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"status_not"},"value":{"kind":"EnumValue","value":"FILLED_PARTIAL"}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"PodListing"}}]}},{"kind":"Field","name":{"kind":"Name","value":"podOrders"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"first"},"value":{"kind":"Variable","name":{"kind":"Name","value":"first"}}},{"kind":"Argument","name":{"kind":"Name","value":"orderBy"},"value":{"kind":"EnumValue","value":"createdAt"}},{"kind":"Argument","name":{"kind":"Name","value":"orderDirection"},"value":{"kind":"EnumValue","value":"desc"}},{"kind":"Argument","name":{"kind":"Name","value":"where"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"createdAt_gt"},"value":{"kind":"Variable","name":{"kind":"Name","value":"orders_createdAt_gt"}}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"PodOrder"}}]}},{"kind":"Field","name":{"kind":"Name","value":"podFills"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"first"},"value":{"kind":"Variable","name":{"kind":"Name","value":"first"}}},{"kind":"Argument","name":{"kind":"Name","value":"where"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"createdAt_gt"},"value":{"kind":"Variable","name":{"kind":"Name","value":"fill_createdAt_gt"}}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"PodFill"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PodListing"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"PodListing"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"farmer"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}},{"kind":"Field","name":{"kind":"Name","value":"historyID"}},{"kind":"Field","name":{"kind":"Name","value":"index"}},{"kind":"Field","name":{"kind":"Name","value":"start"}},{"kind":"Field","name":{"kind":"Name","value":"mode"}},{"kind":"Field","name":{"kind":"Name","value":"pricingType"}},{"kind":"Field","name":{"kind":"Name","value":"pricePerPod"}},{"kind":"Field","name":{"kind":"Name","value":"pricingFunction"}},{"kind":"Field","name":{"kind":"Name","value":"maxHarvestableIndex"}},{"kind":"Field","name":{"kind":"Name","value":"minFillAmount"}},{"kind":"Field","name":{"kind":"Name","value":"originalIndex"}},{"kind":"Field","name":{"kind":"Name","value":"originalPlaceInLine"}},{"kind":"Field","name":{"kind":"Name","value":"originalAmount"}},{"kind":"Field","name":{"kind":"Name","value":"filled"}},{"kind":"Field","name":{"kind":"Name","value":"amount"}},{"kind":"Field","name":{"kind":"Name","value":"remainingAmount"}},{"kind":"Field","name":{"kind":"Name","value":"filledAmount"}},{"kind":"Field","name":{"kind":"Name","value":"fill"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"placeInLine"}}]}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"creationHash"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PodOrder"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"PodOrder"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"farmer"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}},{"kind":"Field","name":{"kind":"Name","value":"historyID"}},{"kind":"Field","name":{"kind":"Name","value":"pricingType"}},{"kind":"Field","name":{"kind":"Name","value":"pricePerPod"}},{"kind":"Field","name":{"kind":"Name","value":"pricingFunction"}},{"kind":"Field","name":{"kind":"Name","value":"maxPlaceInLine"}},{"kind":"Field","name":{"kind":"Name","value":"minFillAmount"}},{"kind":"Field","name":{"kind":"Name","value":"beanAmount"}},{"kind":"Field","name":{"kind":"Name","value":"podAmountFilled"}},{"kind":"Field","name":{"kind":"Name","value":"beanAmountFilled"}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"creationHash"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PodFill"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"PodFill"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"placeInLine"}},{"kind":"Field","name":{"kind":"Name","value":"amount"}},{"kind":"Field","name":{"kind":"Name","value":"index"}},{"kind":"Field","name":{"kind":"Name","value":"start"}},{"kind":"Field","name":{"kind":"Name","value":"costInBeans"}},{"kind":"Field","name":{"kind":"Name","value":"fromFarmer"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}},{"kind":"Field","name":{"kind":"Name","value":"toFarmer"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}},{"kind":"Field","name":{"kind":"Name","value":"listing"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"originalAmount"}}]}},{"kind":"Field","name":{"kind":"Name","value":"order"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"beanAmount"}}]}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}}]} as unknown as DocumentNode; +export const AllMarketActivityDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"AllMarketActivity"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"first"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}},"defaultValue":{"kind":"IntValue","value":"1000"}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"listings_createdAt_gt"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"BigInt"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"orders_createdAt_gt"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"BigInt"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"fill_createdAt_gt"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"BigInt"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"listings_podMarketplace"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"orders_podMarketplace"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"podListings"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"first"},"value":{"kind":"Variable","name":{"kind":"Name","value":"first"}}},{"kind":"Argument","name":{"kind":"Name","value":"where"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"createdAt_gt"},"value":{"kind":"Variable","name":{"kind":"Name","value":"listings_createdAt_gt"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"status_not"},"value":{"kind":"EnumValue","value":"FILLED_PARTIAL"}},{"kind":"ObjectField","name":{"kind":"Name","value":"podMarketplace"},"value":{"kind":"Variable","name":{"kind":"Name","value":"listings_podMarketplace"}}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"PodListing"}}]}},{"kind":"Field","name":{"kind":"Name","value":"podOrders"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"first"},"value":{"kind":"Variable","name":{"kind":"Name","value":"first"}}},{"kind":"Argument","name":{"kind":"Name","value":"orderBy"},"value":{"kind":"EnumValue","value":"createdAt"}},{"kind":"Argument","name":{"kind":"Name","value":"orderDirection"},"value":{"kind":"EnumValue","value":"desc"}},{"kind":"Argument","name":{"kind":"Name","value":"where"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"createdAt_gt"},"value":{"kind":"Variable","name":{"kind":"Name","value":"orders_createdAt_gt"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"podMarketplace"},"value":{"kind":"Variable","name":{"kind":"Name","value":"orders_podMarketplace"}}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"PodOrder"}}]}},{"kind":"Field","name":{"kind":"Name","value":"podFills"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"first"},"value":{"kind":"Variable","name":{"kind":"Name","value":"first"}}},{"kind":"Argument","name":{"kind":"Name","value":"where"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"createdAt_gt"},"value":{"kind":"Variable","name":{"kind":"Name","value":"fill_createdAt_gt"}}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"PodFill"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PodListing"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"PodListing"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"farmer"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}},{"kind":"Field","name":{"kind":"Name","value":"historyID"}},{"kind":"Field","name":{"kind":"Name","value":"index"}},{"kind":"Field","name":{"kind":"Name","value":"start"}},{"kind":"Field","name":{"kind":"Name","value":"mode"}},{"kind":"Field","name":{"kind":"Name","value":"pricingType"}},{"kind":"Field","name":{"kind":"Name","value":"pricePerPod"}},{"kind":"Field","name":{"kind":"Name","value":"pricingFunction"}},{"kind":"Field","name":{"kind":"Name","value":"maxHarvestableIndex"}},{"kind":"Field","name":{"kind":"Name","value":"minFillAmount"}},{"kind":"Field","name":{"kind":"Name","value":"originalIndex"}},{"kind":"Field","name":{"kind":"Name","value":"originalPlaceInLine"}},{"kind":"Field","name":{"kind":"Name","value":"originalAmount"}},{"kind":"Field","name":{"kind":"Name","value":"filled"}},{"kind":"Field","name":{"kind":"Name","value":"amount"}},{"kind":"Field","name":{"kind":"Name","value":"remainingAmount"}},{"kind":"Field","name":{"kind":"Name","value":"filledAmount"}},{"kind":"Field","name":{"kind":"Name","value":"fill"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"placeInLine"}}]}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"creationHash"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PodOrder"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"PodOrder"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"farmer"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}},{"kind":"Field","name":{"kind":"Name","value":"historyID"}},{"kind":"Field","name":{"kind":"Name","value":"pricingType"}},{"kind":"Field","name":{"kind":"Name","value":"pricePerPod"}},{"kind":"Field","name":{"kind":"Name","value":"pricingFunction"}},{"kind":"Field","name":{"kind":"Name","value":"maxPlaceInLine"}},{"kind":"Field","name":{"kind":"Name","value":"minFillAmount"}},{"kind":"Field","name":{"kind":"Name","value":"beanAmount"}},{"kind":"Field","name":{"kind":"Name","value":"podAmountFilled"}},{"kind":"Field","name":{"kind":"Name","value":"beanAmountFilled"}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"creationHash"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PodFill"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"PodFill"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"placeInLine"}},{"kind":"Field","name":{"kind":"Name","value":"amount"}},{"kind":"Field","name":{"kind":"Name","value":"index"}},{"kind":"Field","name":{"kind":"Name","value":"start"}},{"kind":"Field","name":{"kind":"Name","value":"costInBeans"}},{"kind":"Field","name":{"kind":"Name","value":"fromFarmer"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}},{"kind":"Field","name":{"kind":"Name","value":"toFarmer"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}},{"kind":"Field","name":{"kind":"Name","value":"listing"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"originalAmount"}}]}},{"kind":"Field","name":{"kind":"Name","value":"order"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"beanAmount"}}]}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}}]} as unknown as DocumentNode; export const AllPodListingsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"AllPodListings"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"first"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}},"defaultValue":{"kind":"IntValue","value":"1000"}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"status"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"MarketStatus"}},"defaultValue":{"kind":"EnumValue","value":"ACTIVE"}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"maxHarvestableIndex"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"BigInt"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"skip"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}},"defaultValue":{"kind":"IntValue","value":"0"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"podListings"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"first"},"value":{"kind":"Variable","name":{"kind":"Name","value":"first"}}},{"kind":"Argument","name":{"kind":"Name","value":"skip"},"value":{"kind":"Variable","name":{"kind":"Name","value":"skip"}}},{"kind":"Argument","name":{"kind":"Name","value":"where"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"status"},"value":{"kind":"Variable","name":{"kind":"Name","value":"status"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"maxHarvestableIndex_gt"},"value":{"kind":"Variable","name":{"kind":"Name","value":"maxHarvestableIndex"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"remainingAmount_gt"},"value":{"kind":"StringValue","value":"100000","block":false}}]}},{"kind":"Argument","name":{"kind":"Name","value":"orderBy"},"value":{"kind":"EnumValue","value":"index"}},{"kind":"Argument","name":{"kind":"Name","value":"orderDirection"},"value":{"kind":"EnumValue","value":"asc"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"PodListing"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PodListing"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"PodListing"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"farmer"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}},{"kind":"Field","name":{"kind":"Name","value":"historyID"}},{"kind":"Field","name":{"kind":"Name","value":"index"}},{"kind":"Field","name":{"kind":"Name","value":"start"}},{"kind":"Field","name":{"kind":"Name","value":"mode"}},{"kind":"Field","name":{"kind":"Name","value":"pricingType"}},{"kind":"Field","name":{"kind":"Name","value":"pricePerPod"}},{"kind":"Field","name":{"kind":"Name","value":"pricingFunction"}},{"kind":"Field","name":{"kind":"Name","value":"maxHarvestableIndex"}},{"kind":"Field","name":{"kind":"Name","value":"minFillAmount"}},{"kind":"Field","name":{"kind":"Name","value":"originalIndex"}},{"kind":"Field","name":{"kind":"Name","value":"originalPlaceInLine"}},{"kind":"Field","name":{"kind":"Name","value":"originalAmount"}},{"kind":"Field","name":{"kind":"Name","value":"filled"}},{"kind":"Field","name":{"kind":"Name","value":"amount"}},{"kind":"Field","name":{"kind":"Name","value":"remainingAmount"}},{"kind":"Field","name":{"kind":"Name","value":"filledAmount"}},{"kind":"Field","name":{"kind":"Name","value":"fill"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"placeInLine"}}]}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"creationHash"}}]}}]} as unknown as DocumentNode; export const AllPodOrdersDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"AllPodOrders"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"first"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}},"defaultValue":{"kind":"IntValue","value":"1000"}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"status"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"MarketStatus"}},"defaultValue":{"kind":"EnumValue","value":"ACTIVE"}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"skip"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}},"defaultValue":{"kind":"IntValue","value":"0"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"podOrders"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"first"},"value":{"kind":"Variable","name":{"kind":"Name","value":"first"}}},{"kind":"Argument","name":{"kind":"Name","value":"skip"},"value":{"kind":"Variable","name":{"kind":"Name","value":"skip"}}},{"kind":"Argument","name":{"kind":"Name","value":"orderBy"},"value":{"kind":"EnumValue","value":"createdAt"}},{"kind":"Argument","name":{"kind":"Name","value":"orderDirection"},"value":{"kind":"EnumValue","value":"desc"}},{"kind":"Argument","name":{"kind":"Name","value":"where"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"status"},"value":{"kind":"Variable","name":{"kind":"Name","value":"status"}}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"PodOrder"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PodOrder"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"PodOrder"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"farmer"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}},{"kind":"Field","name":{"kind":"Name","value":"historyID"}},{"kind":"Field","name":{"kind":"Name","value":"pricingType"}},{"kind":"Field","name":{"kind":"Name","value":"pricePerPod"}},{"kind":"Field","name":{"kind":"Name","value":"pricingFunction"}},{"kind":"Field","name":{"kind":"Name","value":"maxPlaceInLine"}},{"kind":"Field","name":{"kind":"Name","value":"minFillAmount"}},{"kind":"Field","name":{"kind":"Name","value":"beanAmount"}},{"kind":"Field","name":{"kind":"Name","value":"podAmountFilled"}},{"kind":"Field","name":{"kind":"Name","value":"beanAmountFilled"}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"creationHash"}}]}}]} as unknown as DocumentNode; export const FarmerMarketActivityDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"FarmerMarketActivity"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"first"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}},"defaultValue":{"kind":"IntValue","value":"1000"}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"account"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"listings_createdAt_gt"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"BigInt"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"orders_createdAt_gt"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"BigInt"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"fill_createdAt_gt"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"BigInt"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"podListings"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"first"},"value":{"kind":"Variable","name":{"kind":"Name","value":"first"}}},{"kind":"Argument","name":{"kind":"Name","value":"where"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"farmer"},"value":{"kind":"Variable","name":{"kind":"Name","value":"account"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"createdAt_gt"},"value":{"kind":"Variable","name":{"kind":"Name","value":"listings_createdAt_gt"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"status_not"},"value":{"kind":"EnumValue","value":"FILLED_PARTIAL"}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"PodListing"}}]}},{"kind":"Field","name":{"kind":"Name","value":"podOrders"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"first"},"value":{"kind":"Variable","name":{"kind":"Name","value":"first"}}},{"kind":"Argument","name":{"kind":"Name","value":"orderBy"},"value":{"kind":"EnumValue","value":"createdAt"}},{"kind":"Argument","name":{"kind":"Name","value":"orderDirection"},"value":{"kind":"EnumValue","value":"desc"}},{"kind":"Argument","name":{"kind":"Name","value":"where"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"farmer"},"value":{"kind":"Variable","name":{"kind":"Name","value":"account"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"createdAt_gt"},"value":{"kind":"Variable","name":{"kind":"Name","value":"orders_createdAt_gt"}}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"PodOrder"}}]}},{"kind":"Field","name":{"kind":"Name","value":"podFills"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"first"},"value":{"kind":"Variable","name":{"kind":"Name","value":"first"}}},{"kind":"Argument","name":{"kind":"Name","value":"where"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"and"},"value":{"kind":"ListValue","values":[{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"createdAt_gt"},"value":{"kind":"Variable","name":{"kind":"Name","value":"fill_createdAt_gt"}}}]},{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"or"},"value":{"kind":"ListValue","values":[{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"fromFarmer"},"value":{"kind":"Variable","name":{"kind":"Name","value":"account"}}}]},{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"toFarmer"},"value":{"kind":"Variable","name":{"kind":"Name","value":"account"}}}]}]}}]}]}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"PodFill"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PodListing"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"PodListing"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"farmer"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}},{"kind":"Field","name":{"kind":"Name","value":"historyID"}},{"kind":"Field","name":{"kind":"Name","value":"index"}},{"kind":"Field","name":{"kind":"Name","value":"start"}},{"kind":"Field","name":{"kind":"Name","value":"mode"}},{"kind":"Field","name":{"kind":"Name","value":"pricingType"}},{"kind":"Field","name":{"kind":"Name","value":"pricePerPod"}},{"kind":"Field","name":{"kind":"Name","value":"pricingFunction"}},{"kind":"Field","name":{"kind":"Name","value":"maxHarvestableIndex"}},{"kind":"Field","name":{"kind":"Name","value":"minFillAmount"}},{"kind":"Field","name":{"kind":"Name","value":"originalIndex"}},{"kind":"Field","name":{"kind":"Name","value":"originalPlaceInLine"}},{"kind":"Field","name":{"kind":"Name","value":"originalAmount"}},{"kind":"Field","name":{"kind":"Name","value":"filled"}},{"kind":"Field","name":{"kind":"Name","value":"amount"}},{"kind":"Field","name":{"kind":"Name","value":"remainingAmount"}},{"kind":"Field","name":{"kind":"Name","value":"filledAmount"}},{"kind":"Field","name":{"kind":"Name","value":"fill"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"placeInLine"}}]}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"creationHash"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PodOrder"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"PodOrder"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"farmer"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}},{"kind":"Field","name":{"kind":"Name","value":"historyID"}},{"kind":"Field","name":{"kind":"Name","value":"pricingType"}},{"kind":"Field","name":{"kind":"Name","value":"pricePerPod"}},{"kind":"Field","name":{"kind":"Name","value":"pricingFunction"}},{"kind":"Field","name":{"kind":"Name","value":"maxPlaceInLine"}},{"kind":"Field","name":{"kind":"Name","value":"minFillAmount"}},{"kind":"Field","name":{"kind":"Name","value":"beanAmount"}},{"kind":"Field","name":{"kind":"Name","value":"podAmountFilled"}},{"kind":"Field","name":{"kind":"Name","value":"beanAmountFilled"}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"creationHash"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PodFill"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"PodFill"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"placeInLine"}},{"kind":"Field","name":{"kind":"Name","value":"amount"}},{"kind":"Field","name":{"kind":"Name","value":"index"}},{"kind":"Field","name":{"kind":"Name","value":"start"}},{"kind":"Field","name":{"kind":"Name","value":"costInBeans"}},{"kind":"Field","name":{"kind":"Name","value":"fromFarmer"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}},{"kind":"Field","name":{"kind":"Name","value":"toFarmer"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}},{"kind":"Field","name":{"kind":"Name","value":"listing"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"originalAmount"}}]}},{"kind":"Field","name":{"kind":"Name","value":"order"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"beanAmount"}}]}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}}]} as unknown as DocumentNode; diff --git a/src/pages/Market.tsx b/src/pages/Market.tsx index 229afc2c..7bfbe64d 100644 --- a/src/pages/Market.tsx +++ b/src/pages/Market.tsx @@ -10,6 +10,7 @@ import ReadMoreAccordion from "@/components/ReadMoreAccordion"; import ScatterChart, { PointClickPayload, PointHoverPayload, ScatterChartRef } from "@/components/charts/ScatterChart"; import { Card } from "@/components/ui/Card"; import { Separator } from "@/components/ui/Separator"; +import { Switch } from "@/components/ui/Switch"; import { ANALYTICS_EVENTS } from "@/constants/analytics-events"; import useNavHeight from "@/hooks/display/useNavHeight"; import { useAllMarket } from "@/state/market/useAllMarket"; @@ -213,7 +214,9 @@ export function Market() { const hoverInfoRef = useRef(null); const lastPositionSideRef = useRef<{ isRight: boolean; isAbove: boolean } | null>(null); const navigate = useNavigate(); - const { data, isLoaded } = useAllMarket(); + const [isBeanstalkMarketplace, setIsBeanstalkMarketplace] = useState(false); + const podMarketplaceId = isBeanstalkMarketplace ? "1" : undefined; + const { data, isLoaded } = useAllMarket(podMarketplaceId); const podLine = usePodLine(); const podLineAsNumber = podLine.toNumber() / MILLION; // Chart rounds X max to nearest 10 (not ceil), so we need to match that for validation @@ -1009,7 +1012,11 @@ export function Market() {
-
+
+ Toggle Beanstalk Marketplace + +
+
{!isLoaded && (
diff --git a/src/queries/beanstalk/podmarket/AllMarketActivity.graphql b/src/queries/beanstalk/podmarket/AllMarketActivity.graphql index cd567268..0c14f2a1 100644 --- a/src/queries/beanstalk/podmarket/AllMarketActivity.graphql +++ b/src/queries/beanstalk/podmarket/AllMarketActivity.graphql @@ -7,10 +7,16 @@ query AllMarketActivity( $listings_createdAt_gt: BigInt $orders_createdAt_gt: BigInt $fill_createdAt_gt: BigInt + $listings_podMarketplace: String + $orders_podMarketplace: String ) { podListings( first: $first - where: { createdAt_gt: $listings_createdAt_gt, status_not: FILLED_PARTIAL } + where: { + createdAt_gt: $listings_createdAt_gt + status_not: FILLED_PARTIAL + podMarketplace: $listings_podMarketplace + } ) { ...PodListing } @@ -19,7 +25,10 @@ query AllMarketActivity( first: $first orderBy: createdAt orderDirection: desc - where: { createdAt_gt: $orders_createdAt_gt } + where: { + createdAt_gt: $orders_createdAt_gt + podMarketplace: $orders_podMarketplace + } ) { ...PodOrder } diff --git a/src/state/market/useAllMarket.ts b/src/state/market/useAllMarket.ts index f09bab3f..8499c9c9 100644 --- a/src/state/market/useAllMarket.ts +++ b/src/state/market/useAllMarket.ts @@ -2,25 +2,30 @@ import { subgraphs } from "@/constants/subgraph"; import { AllMarketActivityDocument } from "@/generated/gql/pintostalk/graphql"; import { useQuery } from "@tanstack/react-query"; import request from "graphql-request"; +import { useMemo } from "react"; import { useChainId } from "wagmi"; import { useQueryKeys } from "../useQueryKeys"; import { useMarketEntities } from "./useMarketEntities"; -export function useAllMarket() { +export function useAllMarket(podMarketplaceId?: string) { const chainId = useChainId(); const { allMarket: queryKey } = useQueryKeys({ chainId }); + const marketQueryKey = useMemo(() => [...queryKey, { podMarketplaceId }], [queryKey, podMarketplaceId]); + const { data, isFetching } = useQuery({ - queryKey, + queryKey: marketQueryKey, queryFn: async () => request(subgraphs[chainId].beanstalk, AllMarketActivityDocument, { listings_createdAt_gt: 0, orders_createdAt_gt: 0, fill_createdAt_gt: 0, first: 1000, + listings_podMarketplace: podMarketplaceId ?? "0", + orders_podMarketplace: podMarketplaceId ?? "0", }), }); - return useMarketEntities(data, isFetching, queryKey); + return useMarketEntities(data, isFetching, marketQueryKey); } From 699bb76eed2cfb7a6cc6f6245c340a0680436266 Mon Sep 17 00:00:00 2001 From: feyyazcigim Date: Fri, 13 Feb 2026 16:18:03 +0300 Subject: [PATCH 09/22] feat(transfer): add silo, pod, and fertilizer transfer flows --- src/constants/abiSnippets.ts | 87 +++++++ src/constants/address.ts | 4 + src/constants/internalTokens.ts | 8 + src/pages/Transfer.tsx | 11 +- src/pages/transfer/TransferActions.tsx | 35 +++ .../actions/TransferBeanstalkFertilizer.tsx | 115 +++++++++ .../actions/TransferBeanstalkPods.tsx | 145 ++++++++++++ .../actions/TransferBeanstalkSilo.tsx | 113 +++++++++ .../beanstalk-fertilizer/FinalStep.tsx | 39 +++ .../actions/beanstalk-fertilizer/StepOne.tsx | 161 +++++++++++++ .../actions/beanstalk-pods/FinalStep.tsx | 54 +++++ .../actions/beanstalk-pods/StepOne.tsx | 126 ++++++++++ .../actions/beanstalk-pods/StepTwo.tsx | 158 ++++++++++++ .../actions/beanstalk-silo/FinalStep.tsx | 33 +++ .../actions/beanstalk-silo/StepOne.tsx | 90 +++++++ src/state/useBeanstalkGlobalStats.ts | 92 ++++--- src/state/useFarmerBeanstalkRepayment.ts | 224 ++++++++++-------- 17 files changed, 1351 insertions(+), 144 deletions(-) create mode 100644 src/pages/transfer/actions/TransferBeanstalkFertilizer.tsx create mode 100644 src/pages/transfer/actions/TransferBeanstalkPods.tsx create mode 100644 src/pages/transfer/actions/TransferBeanstalkSilo.tsx create mode 100644 src/pages/transfer/actions/beanstalk-fertilizer/FinalStep.tsx create mode 100644 src/pages/transfer/actions/beanstalk-fertilizer/StepOne.tsx create mode 100644 src/pages/transfer/actions/beanstalk-pods/FinalStep.tsx create mode 100644 src/pages/transfer/actions/beanstalk-pods/StepOne.tsx create mode 100644 src/pages/transfer/actions/beanstalk-pods/StepTwo.tsx create mode 100644 src/pages/transfer/actions/beanstalk-silo/FinalStep.tsx create mode 100644 src/pages/transfer/actions/beanstalk-silo/StepOne.tsx diff --git a/src/constants/abiSnippets.ts b/src/constants/abiSnippets.ts index c294ff9a..13083b5c 100644 --- a/src/constants/abiSnippets.ts +++ b/src/constants/abiSnippets.ts @@ -372,6 +372,91 @@ const erc721Enum = [ }, ] as const; +const siloPayback = [ + { + inputs: [{ internalType: "address", name: "account", type: "address" }], + name: "earned", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "account", type: "address" }], + name: "getBalanceCombined", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "siloRemaining", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "totalDistributed", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "totalReceived", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, +] as const; + +const barnPayback = [ + { + inputs: [ + { internalType: "address", name: "account", type: "address" }, + { internalType: "uint256", name: "id", type: "uint256" }, + ], + name: "balanceOf", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "account", type: "address" }, + { internalType: "uint256[]", name: "ids", type: "uint256[]" }, + ], + name: "balanceOfFertilized", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "account", type: "address" }, + { internalType: "uint256[]", name: "ids", type: "uint256[]" }, + ], + name: "balanceOfUnfertilized", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "barnRemaining", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "fert", + outputs: [{ internalType: "address", name: "", type: "address" }], + stateMutability: "view", + type: "function", + }, +] as const; + export const abiSnippets = { advancedPipe, advancedFarm, @@ -399,4 +484,6 @@ export const abiSnippets = { convert, }, erc721Enum, + siloPayback, + barnPayback, } as const; diff --git a/src/constants/address.ts b/src/constants/address.ts index 43d1e079..dd81e757 100644 --- a/src/constants/address.ts +++ b/src/constants/address.ts @@ -40,3 +40,7 @@ export const WELL_FUNCTION_ADDRESSES: ChainLookup<{ } as const; export const NFT_COLLECTION_1_CONTRACT: HashString = "0xBea5e200a087529AA3B62c460040aE94E3651b76"; + +// TODO: Replace with actual deployed contract addresses +export const SILO_PAYBACK_ADDRESS: HashString = "0x0000000000000000000000000000000000000000"; // urBDV contract address +export const BARN_PAYBACK_ADDRESS: HashString = "0x0000000000000000000000000000000000000000"; // bsFERT contract address diff --git a/src/constants/internalTokens.ts b/src/constants/internalTokens.ts index b9a9ceb1..89283811 100644 --- a/src/constants/internalTokens.ts +++ b/src/constants/internalTokens.ts @@ -36,6 +36,14 @@ export const URBDV: InternalToken = { logoURI: "", // TODO: Add logo if needed }; +export const BSFERT: InternalToken = { + name: "Beanstalk Payback Fertilizer", + symbol: "bsFERT", + decimals: 0, + displayDecimals: 0, + logoURI: "", // TODO: Add bsFERT icon +}; + export const SPROUTS: InternalToken = { name: "Sprouts", symbol: "SPROUTS", diff --git a/src/pages/Transfer.tsx b/src/pages/Transfer.tsx index 57117cf2..4206fd51 100644 --- a/src/pages/Transfer.tsx +++ b/src/pages/Transfer.tsx @@ -4,6 +4,9 @@ import { useParams } from "react-router-dom"; import { useChainId, useConfig } from "wagmi"; import TransferActions from "./transfer/TransferActions"; import TransferAll from "./transfer/actions/TransferAll"; +import TransferBeanstalkFertilizer from "./transfer/actions/TransferBeanstalkFertilizer"; +import TransferBeanstalkPods from "./transfer/actions/TransferBeanstalkPods"; +import TransferBeanstalkSilo from "./transfer/actions/TransferBeanstalkSilo"; import TransferDeposits from "./transfer/actions/TransferDeposits"; import TransferFarmBalance from "./transfer/actions/TransferFarmBalance"; import TransferPods from "./transfer/actions/TransferPods"; @@ -23,7 +26,13 @@ function Transfer() {
{`Move tokens to a different address${currentChain ? ` on ${currentChain.name}.` : "."}`}
- {mode === "all" ? ( + {mode === "beanstalk-silo" ? ( + + ) : mode === "beanstalk-pods" ? ( + + ) : mode === "beanstalk-fertilizer" ? ( + + ) : mode === "all" ? ( ) : mode === "farmbalance" ? ( diff --git a/src/pages/transfer/TransferActions.tsx b/src/pages/transfer/TransferActions.tsx index c4ad9c69..54ad0a3a 100644 --- a/src/pages/transfer/TransferActions.tsx +++ b/src/pages/transfer/TransferActions.tsx @@ -3,6 +3,7 @@ import { TokenValue } from "@/classes/TokenValue"; import { Button } from "@/components/ui/Button"; import IconImage from "@/components/ui/IconImage"; import { useFarmerBalances } from "@/state/useFarmerBalances"; +import { useFarmerBeanstalkRepayment } from "@/state/useFarmerBeanstalkRepayment"; import { useFarmerField } from "@/state/useFarmerField"; import { useFarmerSilo } from "@/state/useFarmerSilo"; import { usePriceData } from "@/state/usePriceData"; @@ -15,6 +16,7 @@ export default function TransferActions() { const farmerBalance = useFarmerBalances(); const farmerSilo = useFarmerSilo(); const farmerField = useFarmerField(); + const repayment = useFarmerBeanstalkRepayment(); const totalInternalBalance = Array.from(farmerBalance.balances).reduce( (total: TokenValue, tokenBalance) => @@ -75,6 +77,39 @@ export default function TransferActions() {
+ + +
); } diff --git a/src/pages/transfer/actions/TransferBeanstalkFertilizer.tsx b/src/pages/transfer/actions/TransferBeanstalkFertilizer.tsx new file mode 100644 index 00000000..4a78b9ca --- /dev/null +++ b/src/pages/transfer/actions/TransferBeanstalkFertilizer.tsx @@ -0,0 +1,115 @@ +import FlowForm from "@/components/FormFlow"; +import { BARN_PAYBACK_ADDRESS } from "@/constants/address"; +import { beanstalkAbi, beanstalkAddress } from "@/generated/contractHooks"; +import useTransaction from "@/hooks/useTransaction"; +import { useFarmerBeanstalkRepayment } from "@/state/useFarmerBeanstalkRepayment"; +import { useEffect, useState } from "react"; +import { useNavigate } from "react-router-dom"; +import { toast } from "sonner"; +import { type Address, encodeFunctionData } from "viem"; +import { useAccount, useChainId } from "wagmi"; +import FinalStep from "./beanstalk-fertilizer/FinalStep"; +import StepOne from "./beanstalk-fertilizer/StepOne"; + +export interface FertilizerTransferItem { + id: bigint; + value: bigint; +} + +export default function TransferBeanstalkFertilizer() { + const account = useAccount(); + const chainId = useChainId(); + const navigate = useNavigate(); + + const [step, setStep] = useState(1); + const [destination, setDestination] = useState(); + const [selectedIds, setSelectedIds] = useState([]); + const [transferNotice, setTransferNotice] = useState(false); + + const repayment = useFarmerBeanstalkRepayment(); + + useEffect(() => { + setTransferNotice(false); + }, [destination]); + + const stepDescription = step === 1 ? "Select Fertilizer IDs and recipient" : "Confirm send"; + + const enableNextStep = + step === 1 + ? selectedIds.length > 0 && selectedIds.every((item) => item.value > 0n) && !!destination && transferNotice + : true; + + const { writeWithEstimateGas, setSubmitting } = useTransaction({ + successCallback: () => { + repayment.refetch(); + navigate("/transfer"); + }, + successMessage: "Transfer success", + errorMessage: "Transfer failed", + }); + + function onSubmit() { + setSubmitting(true); + toast.loading("Transferring..."); + try { + if (!account.address || !destination) return; + + const farmData: `0x${string}`[] = []; + + if (selectedIds.length === 1) { + // Single ID: use transferERC1155 + const transferCall = encodeFunctionData({ + abi: beanstalkAbi, + functionName: "transferERC1155", + args: [BARN_PAYBACK_ADDRESS as Address, destination as Address, selectedIds[0].id, selectedIds[0].value], + }); + farmData.push(transferCall); + } else { + // Multiple IDs: use batchTransferERC1155 + const ids = selectedIds.map((item) => item.id); + const values = selectedIds.map((item) => item.value); + const batchTransferCall = encodeFunctionData({ + abi: beanstalkAbi, + functionName: "batchTransferERC1155", + args: [BARN_PAYBACK_ADDRESS as Address, destination as Address, ids, values], + }); + farmData.push(batchTransferCall); + } + + return writeWithEstimateGas({ + address: beanstalkAddress[chainId as keyof typeof beanstalkAddress], + abi: beanstalkAbi, + functionName: "farm", + args: [farmData], + }); + } catch (e) { + console.error("Transfer beanstalk fertilizer failed", e); + toast.dismiss(); + toast.error("Transfer failed"); + } + } + + return ( + + {step === 1 ? ( + + ) : ( + + )} + + ); +} diff --git a/src/pages/transfer/actions/TransferBeanstalkPods.tsx b/src/pages/transfer/actions/TransferBeanstalkPods.tsx new file mode 100644 index 00000000..e2e60b60 --- /dev/null +++ b/src/pages/transfer/actions/TransferBeanstalkPods.tsx @@ -0,0 +1,145 @@ +import { TokenValue } from "@/classes/TokenValue"; +import FlowForm from "@/components/FormFlow"; +import { beanstalkAbi, beanstalkAddress } from "@/generated/contractHooks"; +import useTransaction from "@/hooks/useTransaction"; +import { useFarmerBeanstalkRepayment } from "@/state/useFarmerBeanstalkRepayment"; +import { useEffect, useState } from "react"; +import { useNavigate } from "react-router-dom"; +import { toast } from "sonner"; +import { type Address, encodeFunctionData } from "viem"; +import { useAccount, useChainId } from "wagmi"; +import FinalStep from "./beanstalk-pods/FinalStep"; +import StepOne from "./beanstalk-pods/StepOne"; +import StepTwo from "./beanstalk-pods/StepTwo"; + +export interface PodTransferData { + id: TokenValue; + start: TokenValue; + end: TokenValue; +} + +export default function TransferBeanstalkPods() { + const account = useAccount(); + const chainId = useChainId(); + const navigate = useNavigate(); + + const [step, setStep] = useState(1); + const [destination, setDestination] = useState(); + const [transferData, setTransferData] = useState([]); + const [transferNotice, setTransferNotice] = useState(false); + + const repayment = useFarmerBeanstalkRepayment(); + + useEffect(() => { + setTransferNotice(false); + }, [destination]); + + const stepDescription = () => { + switch (step) { + case 1: + return "Select Plots"; + case 2: + return "Specify amount and address"; + default: + return "Confirm send"; + } + }; + + const enableNextStep = () => { + switch (step) { + case 1: + return transferData.length > 0; + case 2: + if (!!destination && transferNotice) { + if (transferData.length === 1) { + return transferData[0].end.gt(transferData[0].start); + } + return true; + } + return false; + default: + return true; + } + }; + + const { writeWithEstimateGas, setSubmitting } = useTransaction({ + successCallback: () => { + repayment.refetch(); + navigate("/transfer"); + }, + successMessage: "Transfer success", + errorMessage: "Transfer failed", + }); + + function onSubmit() { + setSubmitting(true); + toast.loading("Transferring..."); + try { + if (!account.address || !destination) return; + + const farmData: `0x${string}`[] = []; + + // Plot Transfers — fieldId=1 for Beanstalk Repayment Field + const fieldId = BigInt(1); + const ids: bigint[] = []; + const starts: bigint[] = []; + const ends: bigint[] = []; + for (const plotData of transferData) { + ids.push(plotData.id.toBigInt()); + starts.push(plotData.start.toBigInt()); + ends.push(plotData.end.toBigInt()); + } + const plotTransferCall = encodeFunctionData({ + abi: beanstalkAbi, + functionName: "transferPlots", + args: [ + account.address, + destination as Address, + fieldId, + ids, //plot ids + starts, // starts + ends, // ends + ], + }); + farmData.push(plotTransferCall); + + return writeWithEstimateGas({ + address: beanstalkAddress[chainId as keyof typeof beanstalkAddress], + abi: beanstalkAbi, + functionName: "farm", + args: [farmData], + }); + } catch (e) { + console.error("Transfer beanstalk pods failed", e); + toast.dismiss(); + toast.error("Transfer failed"); + } + } + + return ( + + {step === 1 ? ( + + ) : step === 2 ? ( + + ) : ( + + )} + + ); +} diff --git a/src/pages/transfer/actions/TransferBeanstalkSilo.tsx b/src/pages/transfer/actions/TransferBeanstalkSilo.tsx new file mode 100644 index 00000000..57b7743a --- /dev/null +++ b/src/pages/transfer/actions/TransferBeanstalkSilo.tsx @@ -0,0 +1,113 @@ +import { TokenValue } from "@/classes/TokenValue"; +import FlowForm from "@/components/FormFlow"; +import { SILO_PAYBACK_ADDRESS } from "@/constants/address"; +import { beanstalkAbi, beanstalkAddress } from "@/generated/contractHooks"; +import useTransaction from "@/hooks/useTransaction"; +import { useFarmerBeanstalkRepayment } from "@/state/useFarmerBeanstalkRepayment"; +import { FarmFromMode, FarmToMode } from "@/utils/types"; +import { useEffect, useState } from "react"; +import { useNavigate } from "react-router-dom"; +import { toast } from "sonner"; +import { type Address, encodeFunctionData } from "viem"; +import { useAccount, useChainId } from "wagmi"; +import FinalStep from "./beanstalk-silo/FinalStep"; +import StepOne from "./beanstalk-silo/StepOne"; + +const URBDV_DECIMALS = 6; + +export default function TransferBeanstalkSilo() { + const account = useAccount(); + const chainId = useChainId(); + const navigate = useNavigate(); + + const [step, setStep] = useState(1); + const [destination, setDestination] = useState(); + const [amount, setAmount] = useState(""); + const [balanceTo, setBalanceTo] = useState(undefined); + const [transferNotice, setTransferNotice] = useState(false); + + const repayment = useFarmerBeanstalkRepayment(); + + useEffect(() => { + setTransferNotice(false); + }, [balanceTo, destination]); + + const stepDescription = step === 1 ? "Specify amount and recipient address" : "Confirm send"; + + const { writeWithEstimateGas, setSubmitting } = useTransaction({ + successCallback: () => { + repayment.refetch(); + navigate("/transfer"); + }, + successMessage: "Transfer success", + errorMessage: "Transfer failed", + }); + + function onSubmit() { + try { + setSubmitting(true); + toast.loading("Transferring..."); + + if (!account.address || !destination || !amount) return; + + const parsedAmount = TokenValue.fromHuman(amount, URBDV_DECIMALS); + if (parsedAmount.eq(0)) return; + + const farmData: `0x${string}`[] = []; + + const transferCall = encodeFunctionData({ + abi: beanstalkAbi, + functionName: "transferToken", + args: [ + SILO_PAYBACK_ADDRESS, + destination as Address, + parsedAmount.toBigInt(), + Number(FarmFromMode.INTERNAL), + Number(balanceTo), + ], + }); + farmData.push(transferCall); + + return writeWithEstimateGas({ + address: beanstalkAddress[chainId as keyof typeof beanstalkAddress], + abi: beanstalkAbi, + functionName: "farm", + args: [farmData], + }); + } catch (e) { + console.error("Transfer Beanstalk Silo failed", e); + toast.dismiss(); + toast.error("Transfer failed"); + } + } + + const numericAmount = Number(amount) || 0; + + return ( + 0 && !!balanceTo && (balanceTo === FarmToMode.INTERNAL ? transferNotice : true) + } + onSubmit={onSubmit} + stepDescription={stepDescription} + > + {step === 1 ? ( + + ) : ( + + )} + + ); +} diff --git a/src/pages/transfer/actions/beanstalk-fertilizer/FinalStep.tsx b/src/pages/transfer/actions/beanstalk-fertilizer/FinalStep.tsx new file mode 100644 index 00000000..2a3e4109 --- /dev/null +++ b/src/pages/transfer/actions/beanstalk-fertilizer/FinalStep.tsx @@ -0,0 +1,39 @@ +import AddressLink from "@/components/AddressLink"; +import { Label } from "@/components/ui/Label"; +import { formatter } from "@/utils/format"; +import { type FertilizerTransferItem } from "../TransferBeanstalkFertilizer"; + +interface FinalStepProps { + destination: string | undefined; + selectedIds: FertilizerTransferItem[]; +} + +export default function FinalStep({ destination, selectedIds }: FinalStepProps) { + if (!destination || selectedIds.length === 0) { + return null; + } + + return ( +
+
+ +
+ {selectedIds.map((item) => ( +
+ {formatter.number(Number(item.value))} + bsFERT + ID {formatter.number(Number(item.id))} +
+ ))} +
+
+
+ + +
+
+ ); +} diff --git a/src/pages/transfer/actions/beanstalk-fertilizer/StepOne.tsx b/src/pages/transfer/actions/beanstalk-fertilizer/StepOne.tsx new file mode 100644 index 00000000..48ad9d06 --- /dev/null +++ b/src/pages/transfer/actions/beanstalk-fertilizer/StepOne.tsx @@ -0,0 +1,161 @@ +import AddressInputField from "@/components/AddressInputField"; +import CheckmarkCircle from "@/components/CheckmarkCircle"; +import PintoAssetTransferNotice from "@/components/PintoAssetTransferNotice"; +import { Button } from "@/components/ui/Button"; +import { Label } from "@/components/ui/Label"; +import { Table, TableBody, TableCell, TableRow } from "@/components/ui/Table"; +import { ToggleGroup, ToggleGroupItem } from "@/components/ui/ToggleGroup"; +import { useFarmerBeanstalkRepayment } from "@/state/useFarmerBeanstalkRepayment"; +import { formatter } from "@/utils/format"; +import { AnimatePresence, motion } from "framer-motion"; +import { Dispatch, SetStateAction, useCallback, useEffect, useState } from "react"; +import { type FertilizerTransferItem } from "../TransferBeanstalkFertilizer"; + +interface StepOneProps { + selectedIds: FertilizerTransferItem[]; + setSelectedIds: Dispatch>; + destination: string | undefined; + setDestination: Dispatch>; + transferNotice: boolean; + setTransferNotice: Dispatch>; +} + +const variants = { + hidden: { + opacity: 0, + transition: { opacity: { duration: 0.2 } }, + }, + visible: { + opacity: 1, + transition: { opacity: { duration: 0.2 } }, + }, + exit: { + opacity: 0, + transition: { opacity: { duration: 0.2 } }, + }, +}; + +export default function StepOne({ + selectedIds, + setSelectedIds, + destination, + setDestination, + transferNotice, + setTransferNotice, +}: StepOneProps) { + const repayment = useFarmerBeanstalkRepayment(); + const fertilizerIds = repayment.fertilizer.fertilizerIds; + + const [selected, setSelected] = useState(() => selectedIds.map((item) => item.id.toString())); + + useEffect(() => { + const restored: string[] = []; + for (const item of selectedIds) { + if (fertilizerIds.includes(item.id)) { + restored.push(item.id.toString()); + } + } + setSelected(restored); + }, []); + + const handleSelection = useCallback( + (value: string[]) => { + setSelected(value); + + const items: FertilizerTransferItem[] = value + .map((idStr) => { + const id = BigInt(idStr); + // For now, transfer the full balance (value=1 per ERC-1155 token unit) + // The actual balance per ID would come from on-chain data + return { id, value: 1n }; + }) + .filter((item) => fertilizerIds.includes(item.id)); + + setSelectedIds(items); + }, + [fertilizerIds, setSelectedIds], + ); + + const selectAll = useCallback(() => { + const allIds = fertilizerIds.map((id) => id.toString()); + handleSelection(allIds); + }, [fertilizerIds, handleSelection]); + + if (fertilizerIds.length === 0) { + return ( +
+
No Beanstalk Repayment Fertilizer found.
+
+ ); + } + + return ( + +
+ +
+ + + + + + + {fertilizerIds.map((fertId) => ( + + + +
+ +
+
+ Fertilizer ID {formatter.number(Number(fertId))} — 1 bsFERT +
+
+
+
+
+
+ ))} +
+
+
+
+ + + + + + {destination && ( + + + + )} + + +
+ ); +} diff --git a/src/pages/transfer/actions/beanstalk-pods/FinalStep.tsx b/src/pages/transfer/actions/beanstalk-pods/FinalStep.tsx new file mode 100644 index 00000000..f45e127b --- /dev/null +++ b/src/pages/transfer/actions/beanstalk-pods/FinalStep.tsx @@ -0,0 +1,54 @@ +import podIcon from "@/assets/protocol/Pod.png"; +import AddressLink from "@/components/AddressLink"; +import { Label } from "@/components/ui/Label"; +import { useHarvestableIndex } from "@/state/useFieldData"; +import { formatter } from "@/utils/format"; +import { PodTransferData } from "../TransferBeanstalkPods"; + +interface FinalStepProps { + destination: string | undefined; + transferData: PodTransferData[]; +} + +export default function FinalStep({ destination, transferData }: FinalStepProps) { + const harvestableIndex = useHarvestableIndex(); + + if (!destination || transferData.length === 0) { + return null; + } + + return ( +
+
+ +
+ {transferData.map((transfer) => { + const placeInLine = transfer.id.sub(harvestableIndex); + const podAmount = transfer.end.sub(transfer.start); + + return ( +
+
+ {formatter.number(podAmount)} + Plot + Pods +
+
+ @ + {formatter.number(placeInLine.add(transfer.start))} in Line +
+
+ ); + })} +
+
+
+ + +
+
+ ); +} diff --git a/src/pages/transfer/actions/beanstalk-pods/StepOne.tsx b/src/pages/transfer/actions/beanstalk-pods/StepOne.tsx new file mode 100644 index 00000000..794e2f7b --- /dev/null +++ b/src/pages/transfer/actions/beanstalk-pods/StepOne.tsx @@ -0,0 +1,126 @@ +import podIcon from "@/assets/protocol/Pod.png"; +import { TokenValue } from "@/classes/TokenValue"; +import CheckmarkCircle from "@/components/CheckmarkCircle"; +import { Button } from "@/components/ui/Button"; +import { Label } from "@/components/ui/Label"; +import { Table, TableBody, TableCell, TableRow } from "@/components/ui/Table"; +import { ToggleGroup, ToggleGroupItem } from "@/components/ui/ToggleGroup"; +import { useFarmerBeanstalkRepayment } from "@/state/useFarmerBeanstalkRepayment"; +import { formatter } from "@/utils/format"; +import { Plot } from "@/utils/types"; +import { Dispatch, SetStateAction, useCallback, useEffect, useState } from "react"; +import { PodTransferData } from "../TransferBeanstalkPods"; + +interface StepOneProps { + transferData: PodTransferData[]; + setTransferData: Dispatch>; +} + +export default function StepOne({ transferData, setTransferData }: StepOneProps) { + const [selected, setSelected] = useState(); + const { plots } = useFarmerBeanstalkRepayment().pods; + + useEffect(() => { + const _newPlots: string[] = []; + for (const data of transferData) { + const _plot = plots.find((plot) => plot.index.eq(data.id)); + if (_plot) { + _newPlots.push(_plot.index.toHuman()); + } + } + setSelected(_newPlots); + }, []); + + const handlePlotSelection = useCallback( + (value: string[]) => { + // Update selected plots + setSelected(value); + + // Get selected plots data + const selectedPlots = value + .map((plotIndex) => { + const plot = plots.find((p) => p.index.toHuman() === plotIndex); + return plot; + }) + .filter((plot): plot is Plot => plot !== undefined && !plot.fullyHarvested); + + // If no valid plots selected, clear transfer data + if (selectedPlots.length === 0) { + setTransferData([]); + return; + } + + // Create plot transfer data + const transferData = selectedPlots.map((plot) => { + return { + id: plot.index, + start: TokenValue.ZERO, + end: plot.pods, + }; + }); + + // Update transfer data + setTransferData(transferData); + }, + [plots, setTransferData], + ); + + const selectAllPlots = useCallback(() => { + const plotIndexes = plots.map((plot) => plot.index.toHuman()); + handlePlotSelection(plotIndexes); + }, [plots, handlePlotSelection]); + + return ( + <> +
+ +
+
+ + + + + {plots + .filter((plot) => plot.unharvestablePods?.gt(0)) + .map((plot) => ( + + + +
+ +
+ Pods +
+ {formatter.number(plot.unharvestablePods ?? plot.pods, { + minValue: 0.01, + })}{" "} + Pods +
+
+
+
+
+
+ ))} +
+
+
+
+ + ); +} diff --git a/src/pages/transfer/actions/beanstalk-pods/StepTwo.tsx b/src/pages/transfer/actions/beanstalk-pods/StepTwo.tsx new file mode 100644 index 00000000..797b529b --- /dev/null +++ b/src/pages/transfer/actions/beanstalk-pods/StepTwo.tsx @@ -0,0 +1,158 @@ +import { TokenValue } from "@/classes/TokenValue"; +import AddressInputField from "@/components/AddressInputField"; +import { ComboInputField } from "@/components/ComboInputField"; +import PintoAssetTransferNotice from "@/components/PintoAssetTransferNotice"; +import PodRangeSelector from "@/components/PodRangeSelector"; +import { Label } from "@/components/ui/Label"; +import { PODS } from "@/constants/internalTokens"; +import { useFarmerBeanstalkRepayment } from "@/state/useFarmerBeanstalkRepayment"; +import { Plot } from "@/utils/types"; +import { AnimatePresence, motion } from "framer-motion"; +import { Dispatch, SetStateAction, useCallback, useEffect, useState } from "react"; +import { PodTransferData } from "../TransferBeanstalkPods"; + +interface StepTwoProps { + transferData: PodTransferData[]; + setTransferData: Dispatch>; + destination: string | undefined; + setDestination: Dispatch>; + transferNotice: boolean; + setTransferNotice: Dispatch>; +} + +export default function StepTwo({ + transferData, + setTransferData, + destination, + setDestination, + transferNotice, + setTransferNotice, +}: StepTwoProps) { + const { plots } = useFarmerBeanstalkRepayment().pods; + const [selectedPlots, setSelectedPlots] = useState([]); + const [amount, setAmount] = useState("0"); + const [range, setRange] = useState<[TokenValue, TokenValue]>([TokenValue.ZERO, TokenValue.ZERO]); + + useEffect(() => { + const _newPlots: Plot[] = []; + for (const data of transferData) { + const _plot = plots.find((plot) => plot.index.eq(data.id)); + if (_plot) { + _newPlots.push(_plot); + } + } + setSelectedPlots(_newPlots); + }, []); + + useEffect(() => { + if (selectedPlots.length === 1) { + const plot = selectedPlots[0]; + setRange([plot.index, plot.index.add(plot.pods)]); + } + }, [selectedPlots]); + + const handleRangeChange = useCallback( + (newRange: TokenValue[]) => { + if (selectedPlots.length === 0 || selectedPlots.length > 1) return; + const plot = selectedPlots[0]; + const newStart = newRange[0]; + const newEnd = newRange[1]; + + const relativeStart = newStart.sub(plot.index); + const relativeEnd = newEnd.sub(plot.index); + + const newData = [ + { + id: plot.index, + start: relativeStart, + end: relativeEnd, + }, + ]; + + const newAmount = newEnd.sub(newStart).toHuman(); + + const batchUpdate = () => { + setRange([newStart, newEnd]); + setTransferData(newData); + setAmount(newAmount); + }; + batchUpdate(); + }, + [selectedPlots, setTransferData], + ); + + const handleAmountChange = useCallback( + (value: string) => { + const newAmount = value; + + if (selectedPlots.length === 0) return; + + const plot = selectedPlots[0]; + const amountValue = TokenValue.fromHuman(newAmount || "0", PODS.decimals); + const newEnd = plot.index.add(amountValue); + + const newStart = plot.index; + const relativeStart = newStart.sub(plot.index); + const relativeEnd = newEnd.sub(plot.index); + + const newData = [ + { + id: plot.index, + start: relativeStart, + end: relativeEnd, + }, + ]; + + const batchUpdate = () => { + setAmount(newAmount); + if (selectedPlots.length === 1) { + setRange([newStart, newEnd]); + setTransferData(newData); + } + }; + batchUpdate(); + }, + [selectedPlots, setTransferData], + ); + + return ( +
+
+ + 1} + altText={selectedPlots.length > 1 ? "Balance:" : "Plot Balance:"} + /> +
+
+ + + + {destination && ( + + + + )} + {" "} +
+ {selectedPlots.length === 1 && ( + + )} +
+ ); +} diff --git a/src/pages/transfer/actions/beanstalk-silo/FinalStep.tsx b/src/pages/transfer/actions/beanstalk-silo/FinalStep.tsx new file mode 100644 index 00000000..80ba03d7 --- /dev/null +++ b/src/pages/transfer/actions/beanstalk-silo/FinalStep.tsx @@ -0,0 +1,33 @@ +import AddressLink from "@/components/AddressLink"; +import { Label } from "@/components/ui/Label"; +import { FarmToMode } from "@/utils/types"; + +interface FinalStepProps { + amount: string; + destination: string | undefined; + balanceTo: FarmToMode | undefined; +} + +export default function FinalStep({ amount, destination, balanceTo }: FinalStepProps) { + return ( +
+
+ +
+ {amount} + urBDV +
+
+
+ + +
+
+ +
+ {balanceTo === FarmToMode.EXTERNAL ? "External Wallet" : "Farm Wallet"} +
+
+
+ ); +} diff --git a/src/pages/transfer/actions/beanstalk-silo/StepOne.tsx b/src/pages/transfer/actions/beanstalk-silo/StepOne.tsx new file mode 100644 index 00000000..abf3582e --- /dev/null +++ b/src/pages/transfer/actions/beanstalk-silo/StepOne.tsx @@ -0,0 +1,90 @@ +import AddressInputField from "@/components/AddressInputField"; +import { ComboInputField } from "@/components/ComboInputField"; +import DestinationBalanceSelect from "@/components/DestinationBalanceSelect"; +import PintoAssetTransferNotice from "@/components/PintoAssetTransferNotice"; +import { Label } from "@/components/ui/Label"; +import { useFarmerBeanstalkRepayment } from "@/state/useFarmerBeanstalkRepayment"; +import { FarmToMode } from "@/utils/types"; +import { AnimatePresence, motion } from "framer-motion"; +import { Dispatch, SetStateAction } from "react"; + +interface StepOneProps { + amount: string; + setAmount: Dispatch>; + destination: string | undefined; + setDestination: Dispatch>; + balanceTo: FarmToMode | undefined; + setBalanceTo: Dispatch>; + transferNotice: boolean; + setTransferNotice: Dispatch>; +} + +const variants = { + hidden: { + opacity: 0, + transition: { opacity: { duration: 0.2 } }, + }, + visible: { + opacity: 1, + transition: { opacity: { duration: 0.2 } }, + }, + exit: { + opacity: 0, + transition: { opacity: { duration: 0.2 } }, + }, +}; + +export default function StepOne({ + amount, + setAmount, + destination, + setDestination, + balanceTo, + setBalanceTo, + transferNotice, + setTransferNotice, +}: StepOneProps) { + const repayment = useFarmerBeanstalkRepayment(); + const maxAmount = repayment.silo.balance; + + return ( + + + + + + + + + + + {destination && ( + + + + )} + + + + + + + + + ); +} diff --git a/src/state/useBeanstalkGlobalStats.ts b/src/state/useBeanstalkGlobalStats.ts index 4a3518fb..45db6383 100644 --- a/src/state/useBeanstalkGlobalStats.ts +++ b/src/state/useBeanstalkGlobalStats.ts @@ -1,31 +1,12 @@ import { TokenValue } from "@/classes/TokenValue"; +import { abiSnippets } from "@/constants/abiSnippets"; +import { BARN_PAYBACK_ADDRESS, SILO_PAYBACK_ADDRESS } from "@/constants/address"; import { PODS, SPROUTS, URBDV } from "@/constants/internalTokens"; import { defaultQuerySettingsMedium } from "@/constants/query"; import { useProtocolAddress } from "@/hooks/pinto/useProtocolAddress"; import { useCallback, useMemo } from "react"; import { useReadContracts } from "wagmi"; -/** - * ABI snippets for Silo Payback contract global functions - * NOTE: These functions don't exist in the protocol yet - will be indexed from subgraph later - */ -// const siloPaybackGlobalAbi = [ -// { -// inputs: [], -// name: "totalUrBdvDistributed", -// outputs: [{ internalType: "uint256", name: "", type: "uint256" }], -// stateMutability: "view", -// type: "function", -// }, -// { -// inputs: [], -// name: "totalPintoPaidOut", -// outputs: [{ internalType: "uint256", name: "", type: "uint256" }], -// stateMutability: "view", -// type: "function", -// }, -// ] as const; - /** * ABI snippets for Field contract global functions */ @@ -39,20 +20,6 @@ const fieldGlobalAbi = [ }, ] as const; -/** - * ABI snippets for Barn Payback contract global functions - * NOTE: This function doesn't exist in the protocol yet - will be indexed from subgraph later - */ -// const barnPaybackGlobalAbi = [ -// { -// inputs: [], -// name: "totalUnfertilizedSprouts", -// outputs: [{ internalType: "uint256", name: "", type: "uint256" }], -// stateMutability: "view", -// type: "function", -// }, -// ] as const; - /** * Interface for the global Beanstalk statistics data */ @@ -61,6 +28,8 @@ export interface BeanstalkGlobalStatsData { totalPodsInRepaymentField: TokenValue; totalUnfertilizedSprouts: TokenValue; totalPintoPaidOut: TokenValue; + siloRemaining: TokenValue; + barnRemaining: TokenValue; isLoading: boolean; isError: boolean; refetch: () => Promise; @@ -73,10 +42,10 @@ const BEANSTALK_REPAYMENT_FIELD_ID = 1n; * Hook for fetching global Beanstalk repayment statistics * * This hook fetches protocol-wide statistics: - * - Total urBDV distributed across all holders (TODO: from subgraph) + * - Silo Payback: siloRemaining() and totalDistributed() from Silo_Payback contract * - Total pods in the repayment field (fieldId=1) - * - Total unfertilized sprouts (TODO: from subgraph) - * - Total Pinto paid out to holders (TODO: from subgraph) + * - Barn Payback: barnRemaining() from Barn_Payback contract + * - Total Pinto paid out to holders (TODO: calculated total) * * Uses a 5-minute stale time for more frequent updates of global stats * @@ -94,11 +63,23 @@ export function useBeanstalkGlobalStats(): BeanstalkGlobalStatsData { functionName: "totalPods", args: [BEANSTALK_REPAYMENT_FIELD_ID], }, - // TODO: These functions don't exist in the protocol yet - // Will be indexed from subgraph later: - // - totalUrBdvDistributed - // - totalUnfertilizedSprouts - // - totalPintoPaidOut + // Silo Payback global stats + { + address: SILO_PAYBACK_ADDRESS, + abi: abiSnippets.siloPayback, + functionName: "siloRemaining", + }, + { + address: SILO_PAYBACK_ADDRESS, + abi: abiSnippets.siloPayback, + functionName: "totalDistributed", + }, + // Barn Payback global stats + { + address: BARN_PAYBACK_ADDRESS, + abi: abiSnippets.barnPayback, + functionName: "barnRemaining", + }, ], allowFailure: true, query: { @@ -106,18 +87,33 @@ export function useBeanstalkGlobalStats(): BeanstalkGlobalStatsData { }, }); - // Process global data + // Process global data — defaults to ZERO on error const globalData = useMemo(() => { + if (globalQuery.isError) { + return { + totalUrBdvDistributed: TokenValue.ZERO, + totalPodsInRepaymentField: TokenValue.ZERO, + totalUnfertilizedSprouts: TokenValue.ZERO, + totalPintoPaidOut: TokenValue.ZERO, + siloRemaining: TokenValue.ZERO, + barnRemaining: TokenValue.ZERO, + }; + } + const totalPodsInRepaymentField = globalQuery.data?.[0]?.result; + const siloRemainingResult = globalQuery.data?.[1]?.result; + const totalDistributedResult = globalQuery.data?.[2]?.result; + const barnRemainingResult = globalQuery.data?.[3]?.result; return { - // TODO: These will come from subgraph later - totalUrBdvDistributed: TokenValue.fromBlockchain(0n, URBDV.decimals), + totalUrBdvDistributed: TokenValue.fromBlockchain(totalDistributedResult ?? 0n, URBDV.decimals), totalPodsInRepaymentField: TokenValue.fromBlockchain(totalPodsInRepaymentField ?? 0n, PODS.decimals), - totalUnfertilizedSprouts: TokenValue.fromBlockchain(0n, SPROUTS.decimals), + totalUnfertilizedSprouts: TokenValue.fromBlockchain(barnRemainingResult ?? 0n, SPROUTS.decimals), totalPintoPaidOut: TokenValue.fromBlockchain(0n, URBDV.decimals), + siloRemaining: TokenValue.fromBlockchain(siloRemainingResult ?? 0n, URBDV.decimals), + barnRemaining: TokenValue.fromBlockchain(barnRemainingResult ?? 0n, SPROUTS.decimals), }; - }, [globalQuery.data]); + }, [globalQuery.data, globalQuery.isError]); // Refetch function const refetch = useCallback(async () => { diff --git a/src/state/useFarmerBeanstalkRepayment.ts b/src/state/useFarmerBeanstalkRepayment.ts index 28bc670f..f11dc1be 100644 --- a/src/state/useFarmerBeanstalkRepayment.ts +++ b/src/state/useFarmerBeanstalkRepayment.ts @@ -1,6 +1,7 @@ import { TokenValue } from "@/classes/TokenValue"; -import { ZERO_ADDRESS } from "@/constants/address"; -import { PODS } from "@/constants/internalTokens"; +import { abiSnippets } from "@/constants/abiSnippets"; +import { BARN_PAYBACK_ADDRESS, SILO_PAYBACK_ADDRESS, ZERO_ADDRESS } from "@/constants/address"; +import { BSFERT, PODS } from "@/constants/internalTokens"; import { defaultQuerySettings } from "@/constants/query"; import { beanstalkAbi } from "@/generated/contractHooks"; import { useProtocolAddress } from "@/hooks/pinto/useProtocolAddress"; @@ -9,81 +10,14 @@ import { useCallback, useMemo } from "react"; import { toHex } from "viem"; import { useAccount, useReadContracts } from "wagmi"; -/** - * ABI snippets for Silo Payback contract functions - * NOTE: These functions don't exist in the protocol yet - will be indexed from subgraph later - */ -// const siloPaybackAbi = [ -// { -// inputs: [{ internalType: "address", name: "account", type: "address" }], -// name: "balanceOfUrBdv", -// outputs: [{ internalType: "uint256", name: "", type: "uint256" }], -// stateMutability: "view", -// type: "function", -// }, -// { -// inputs: [{ internalType: "address", name: "account", type: "address" }], -// name: "earnedUrBdv", -// outputs: [{ internalType: "uint256", name: "", type: "uint256" }], -// stateMutability: "view", -// type: "function", -// }, -// { -// inputs: [{ internalType: "address", name: "account", type: "address" }], -// name: "totalDistributedToAccount", -// outputs: [{ internalType: "uint256", name: "", type: "uint256" }], -// stateMutability: "view", -// type: "function", -// }, -// { -// inputs: [{ internalType: "address", name: "account", type: "address" }], -// name: "totalReceivedByAccount", -// outputs: [{ internalType: "uint256", name: "", type: "uint256" }], -// stateMutability: "view", -// type: "function", -// }, -// ] as const; - -/** - * ABI snippets for Barn Payback contract functions - * NOTE: These functions don't exist in the protocol yet - will be indexed from subgraph later - */ -// const barnPaybackAbi = [ -// { -// inputs: [{ internalType: "address", name: "account", type: "address" }], -// name: "balanceOfFertilizer", -// outputs: [{ internalType: "uint256", name: "", type: "uint256" }], -// stateMutability: "view", -// type: "function", -// }, -// { -// inputs: [{ internalType: "address", name: "account", type: "address" }], -// name: "balanceOfSprouts", -// outputs: [{ internalType: "uint256", name: "", type: "uint256" }], -// stateMutability: "view", -// type: "function", -// }, -// { -// inputs: [{ internalType: "address", name: "account", type: "address" }], -// name: "balanceOfFertilized", -// outputs: [{ internalType: "uint256", name: "", type: "uint256" }], -// stateMutability: "view", -// type: "function", -// }, -// { -// inputs: [], -// name: "humidity", -// outputs: [{ internalType: "uint256", name: "", type: "uint256" }], -// stateMutability: "view", -// type: "function", -// }, -// ] as const; - /** * Interface for silo payback data */ interface SiloPaybackData { - balance: TokenValue; + balance: TokenValue; // getBalanceCombined(account) - total urBDV balance + earned: TokenValue; // earned(account) - earned but unclaimed + totalDistributed: TokenValue; // totalDistributed() - total distributed + totalReceived: TokenValue; // totalReceived() - total received } /** @@ -98,7 +32,10 @@ interface PodsData { * Interface for fertilizer data */ interface FertilizerData { - balance: TokenValue; + balance: TokenValue; // Total bsFERT balance + fertilized: TokenValue; // balanceOfFertilized(account, ids) + unfertilized: TokenValue; // balanceOfUnfertilized(account, ids) + fertilizerIds: bigint[]; // Fertilizer IDs owned by the user } /** @@ -115,16 +52,14 @@ export interface FarmerBeanstalkRepaymentData { // Token decimals for urBDV (same as BEAN - 6 decimals) const URBDV_DECIMALS = 6; -// Token decimals for fertilizer amounts -const FERTILIZER_DECIMALS = 6; /** * Hook for fetching farmer-specific Beanstalk repayment data * * This hook fetches: - * - Silo payback data (TODO: from subgraph - urBDV balance) + * - Silo payback data from Silo_Payback contract (earned, balance, totalDistributed, totalReceived) * - Pods data from repayment field (fieldId=1) - from on-chain - * - Fertilizer data (TODO: from subgraph) + * - Fertilizer data from Barn_Payback contract (fertilized, unfertilized balances) * * @returns FarmerBeanstalkRepaymentData with all farmer obligations data */ @@ -157,16 +92,97 @@ export function useFarmerBeanstalkRepayment(): FarmerBeanstalkRepaymentData { }, }); - // TODO: Silo payback data will come from subgraph - // Functions don't exist in protocol: balanceOfUrBdv, earnedUrBdv, totalDistributedToAccount, totalReceivedByAccount + // Query for Barn Payback (bsFERT) data from Barn_Payback contract + // balanceOfFertilized and balanceOfUnfertilized require fertilizer IDs. + // We pass empty arrays initially; these will return 0 until IDs are populated. + const fertilizerIds: bigint[] = []; + + const fertilizerQuery = useReadContracts({ + contracts: [ + { + address: BARN_PAYBACK_ADDRESS, + abi: abiSnippets.barnPayback, + functionName: "balanceOfFertilized", + args: [farmerAddress, fertilizerIds], + }, + { + address: BARN_PAYBACK_ADDRESS, + abi: abiSnippets.barnPayback, + functionName: "balanceOfUnfertilized", + args: [farmerAddress, fertilizerIds], + }, + ], + allowFailure: true, + query: { + ...defaultQuerySettings, + }, + }); + + // Query for Silo Payback data from Silo_Payback contract + const siloQuery = useReadContracts({ + contracts: [ + { + address: SILO_PAYBACK_ADDRESS, + abi: abiSnippets.siloPayback, + functionName: "earned", + args: [farmerAddress], + }, + { + address: SILO_PAYBACK_ADDRESS, + abi: abiSnippets.siloPayback, + functionName: "getBalanceCombined", + args: [farmerAddress], + }, + { + address: SILO_PAYBACK_ADDRESS, + abi: abiSnippets.siloPayback, + functionName: "totalDistributed", + }, + { + address: SILO_PAYBACK_ADDRESS, + abi: abiSnippets.siloPayback, + functionName: "totalReceived", + }, + ], + allowFailure: true, + query: { + ...defaultQuerySettings, + }, + }); + + // Process Silo Payback data — defaults to ZERO on error const siloData = useMemo((): SiloPaybackData => { + if (siloQuery.isError) { + return { + balance: TokenValue.ZERO, + earned: TokenValue.ZERO, + totalDistributed: TokenValue.ZERO, + totalReceived: TokenValue.ZERO, + }; + } + + const earnedResult = siloQuery.data?.[0]?.result; + const balanceResult = siloQuery.data?.[1]?.result; + const totalDistributedResult = siloQuery.data?.[2]?.result; + const totalReceivedResult = siloQuery.data?.[3]?.result; + return { - balance: TokenValue.fromBlockchain(0n, URBDV_DECIMALS), + balance: TokenValue.fromBlockchain(balanceResult ?? 0n, URBDV_DECIMALS), + earned: TokenValue.fromBlockchain(earnedResult ?? 0n, URBDV_DECIMALS), + totalDistributed: TokenValue.fromBlockchain(totalDistributedResult ?? 0n, URBDV_DECIMALS), + totalReceived: TokenValue.fromBlockchain(totalReceivedResult ?? 0n, URBDV_DECIMALS), }; - }, []); + }, [siloQuery.data, siloQuery.isError]); - // Process pods data - these functions exist in protocol + // Process pods data — defaults to ZERO on error const podsData = useMemo((): PodsData => { + if (podsQuery.isError) { + return { + plots: [], + totalPods: TokenValue.ZERO, + }; + } + const plotsResult = podsQuery.data?.[0]?.result as readonly { index: bigint; pods: bigint }[] | undefined; const totalPodsResult = podsQuery.data?.[1]?.result; @@ -189,25 +205,43 @@ export function useFarmerBeanstalkRepayment(): FarmerBeanstalkRepaymentData { plots, totalPods: TokenValue.fromBlockchain(totalPodsResult ?? 0n, PODS.decimals), }; - }, [podsQuery.data]); + }, [podsQuery.data, podsQuery.isError]); - // TODO: Fertilizer data will come from subgraph - // Functions don't exist in protocol: balanceOfFertilizer, balanceOfSprouts, balanceOfFertilized - // humidity() exists as getCurrentHumidity() but not needed for now + // Process Barn Payback (bsFERT) data — defaults to ZERO on error const fertilizerData = useMemo((): FertilizerData => { + if (fertilizerQuery.isError) { + return { + balance: TokenValue.ZERO, + fertilized: TokenValue.ZERO, + unfertilized: TokenValue.ZERO, + fertilizerIds, + }; + } + + const fertilizedResult = fertilizerQuery.data?.[0]?.result; + const unfertilizedResult = fertilizerQuery.data?.[1]?.result; + + const fertilized = TokenValue.fromBlockchain(fertilizedResult ?? 0n, BSFERT.decimals); + const unfertilized = TokenValue.fromBlockchain(unfertilizedResult ?? 0n, BSFERT.decimals); + // Total balance is the sum of fertilized + unfertilized + const balance = fertilized.add(unfertilized); + return { - balance: TokenValue.fromBlockchain(0n, FERTILIZER_DECIMALS), + balance, + fertilized, + unfertilized, + fertilizerIds, }; - }, []); + }, [fertilizerQuery.data, fertilizerQuery.isError, fertilizerIds]); - // Refetch pods query (only one that works) + // Refetch all queries const refetch = useCallback(async () => { - await podsQuery.refetch(); - }, [podsQuery.refetch]); + await Promise.all([siloQuery.refetch(), podsQuery.refetch(), fertilizerQuery.refetch()]); + }, [siloQuery.refetch, podsQuery.refetch, fertilizerQuery.refetch]); - // Loading and error states only from pods query - const isLoading = podsQuery.isLoading; - const isError = podsQuery.isError; + // Loading and error states from all queries + const isLoading = siloQuery.isLoading || podsQuery.isLoading || fertilizerQuery.isLoading; + const isError = siloQuery.isError || podsQuery.isError || fertilizerQuery.isError; return useMemo( () => ({ From fa1c8d412b4b26f446256ecde312bcfea9fed381 Mon Sep 17 00:00:00 2001 From: feyyazcigim Date: Fri, 13 Feb 2026 18:36:51 +0300 Subject: [PATCH 10/22] feat(transfer): update pod and fertilizer selection and transfer flows --- src/assets/protocol/Fertilizer.svg | 24 ++ src/components/FertilizerCard.tsx | 64 +++++ src/components/PintoAssetTransferNotice.tsx | 21 +- .../actions/TransferBeanstalkPods.tsx | 21 +- .../actions/beanstalk-fertilizer/StepOne.tsx | 186 +++++++++----- .../actions/beanstalk-pods/FinalStep.tsx | 52 ++-- .../actions/beanstalk-pods/StepOne.tsx | 242 +++++++++++------- .../actions/beanstalk-pods/StepTwo.tsx | 125 +-------- src/state/useFarmerBeanstalkRepayment.ts | 2 +- 9 files changed, 416 insertions(+), 321 deletions(-) create mode 100644 src/assets/protocol/Fertilizer.svg create mode 100644 src/components/FertilizerCard.tsx diff --git a/src/assets/protocol/Fertilizer.svg b/src/assets/protocol/Fertilizer.svg new file mode 100644 index 00000000..14d969f1 --- /dev/null +++ b/src/assets/protocol/Fertilizer.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/components/FertilizerCard.tsx b/src/components/FertilizerCard.tsx new file mode 100644 index 00000000..f1dc2117 --- /dev/null +++ b/src/components/FertilizerCard.tsx @@ -0,0 +1,64 @@ +import fertilizerIcon from "@/assets/protocol/Fertilizer.svg"; +import CheckmarkCircle from "@/components/CheckmarkCircle"; +import IconImage from "@/components/ui/IconImage"; +import { Input } from "@/components/ui/Input"; +import { formatter } from "@/utils/format"; + +interface FertilizerCardProps { + fertId: bigint; + amount: string; + isSelected: boolean; + maxBalance: bigint; + sprouts: string; + humidity: string; + onToggleSelection: (fertId: bigint) => void; + onAmountChange: (fertId: bigint, value: string, maxBalance: bigint) => void; +} + +export default function FertilizerCard({ + fertId, + amount, + isSelected, + maxBalance, + sprouts, + humidity, + onToggleSelection, + onAmountChange, +}: FertilizerCardProps) { + return ( +
+ {/* Checkbox */} +
onToggleSelection(fertId)}> + +
+ + {/* Fertilizer icon */} +
+ +
+ + {/* Fertilizer info */} +
+
{formatter.number(Number(fertId))} FERTILIZER
+
+ Sprouts: {sprouts} · Humidity: {humidity} +
+
+ + {/* Amount input */} +
+ onAmountChange(fertId, e.target.value, maxBalance)} + outlined={true} + containerClassName="border border-pinto-green-4 focus-within:border-pinto-green-4" + min="0" + max={maxBalance.toString()} + step="1" + /> +
+
+ ); +} diff --git a/src/components/PintoAssetTransferNotice.tsx b/src/components/PintoAssetTransferNotice.tsx index 9ddd7667..1e5528e4 100644 --- a/src/components/PintoAssetTransferNotice.tsx +++ b/src/components/PintoAssetTransferNotice.tsx @@ -76,9 +76,26 @@ export default function PintoAssetTransferNotice({ animate={{ opacity: 1, y: 0 }} exit={{ opacity: 0, y: -20 }} transition={{ duration: 0.3, ease: "easeInOut" }} - className="text-2xl" + className="flex flex-col gap-8" > - Note: be sure recipient address is intended. +

Note: be sure recipient address is intended.

+
+ { + if (checked !== "indeterminate") { + setTransferNotice(checked); + } else { + setTransferNotice(false); + } + }} + /> + +
)} diff --git a/src/pages/transfer/actions/TransferBeanstalkPods.tsx b/src/pages/transfer/actions/TransferBeanstalkPods.tsx index e2e60b60..e5af15a9 100644 --- a/src/pages/transfer/actions/TransferBeanstalkPods.tsx +++ b/src/pages/transfer/actions/TransferBeanstalkPods.tsx @@ -39,7 +39,7 @@ export default function TransferBeanstalkPods() { case 1: return "Select Plots"; case 2: - return "Specify amount and address"; + return "Enter address"; default: return "Confirm send"; } @@ -50,13 +50,7 @@ export default function TransferBeanstalkPods() { case 1: return transferData.length > 0; case 2: - if (!!destination && transferNotice) { - if (transferData.length === 1) { - return transferData[0].end.gt(transferData[0].start); - } - return true; - } - return false; + return !!destination && transferNotice; default: return true; } @@ -92,14 +86,7 @@ export default function TransferBeanstalkPods() { const plotTransferCall = encodeFunctionData({ abi: beanstalkAbi, functionName: "transferPlots", - args: [ - account.address, - destination as Address, - fieldId, - ids, //plot ids - starts, // starts - ends, // ends - ], + args: [account.address, destination as Address, fieldId, ids, starts, ends], }); farmData.push(plotTransferCall); @@ -130,8 +117,6 @@ export default function TransferBeanstalkPods() { ) : step === 2 ? ( (() => selectedIds.map((item) => item.id.toString())); + // TODO: Get actual balance per fertilizer ID from contract + // For now, mock balance of 1 bsFERT per ID + const getFertilizerBalance = useCallback((fertId: bigint): bigint => { + // Mock: Each fertilizer ID has 1 bsFERT + return 1n; + }, []); + + // Local state for amount inputs per fertilizer ID + const [amounts, setAmounts] = useState>(() => { + const initial: Record = {}; + for (const item of selectedIds) { + initial[item.id.toString()] = item.value.toString(); + } + return initial; + }); - useEffect(() => { - const restored: string[] = []; + // Track which fertilizers are selected (have amount > 0) + const [selected, setSelected] = useState>(() => { + const initial = new Set(); for (const item of selectedIds) { - if (fertilizerIds.includes(item.id)) { - restored.push(item.id.toString()); + if (item.value > 0n) { + initial.add(item.id.toString()); } } - setSelected(restored); - }, []); + return initial; + }); + + const toggleSelection = useCallback( + (fertId: bigint) => { + const idStr = fertId.toString(); + setSelected((prev) => { + const newSet = new Set(prev); + if (newSet.has(idStr)) { + newSet.delete(idStr); + // Clear amount when deselecting + setAmounts((prevAmounts) => ({ ...prevAmounts, [idStr]: "" })); + setSelectedIds((prev) => prev.filter((item) => item.id !== fertId)); + } else { + newSet.add(idStr); + // Set default amount of 1 when selecting + setAmounts((prevAmounts) => ({ ...prevAmounts, [idStr]: "1" })); + setSelectedIds((prev) => [...prev, { id: fertId, value: 1n }]); + } + return newSet; + }); + }, + [setSelectedIds], + ); - const handleSelection = useCallback( - (value: string[]) => { - setSelected(value); + const handleAmountChange = useCallback( + (fertId: bigint, value: string, maxBalance: bigint) => { + const idStr = fertId.toString(); - const items: FertilizerTransferItem[] = value - .map((idStr) => { - const id = BigInt(idStr); - // For now, transfer the full balance (value=1 per ERC-1155 token unit) - // The actual balance per ID would come from on-chain data - return { id, value: 1n }; - }) - .filter((item) => fertilizerIds.includes(item.id)); + // Validate against max balance + let numValue = value === "" ? 0n : BigInt(value); + if (numValue > maxBalance) { + numValue = maxBalance; + } + + const displayValue = numValue === 0n ? "" : numValue.toString(); + setAmounts((prev) => ({ ...prev, [idStr]: displayValue })); - setSelectedIds(items); + // Update selectedIds with the new amount + setSelectedIds((prev) => { + const existing = prev.find((item) => item.id === fertId); + if (numValue === 0n) { + // Remove if amount is 0 + setSelected((prevSelected) => { + const newSet = new Set(prevSelected); + newSet.delete(idStr); + return newSet; + }); + return prev.filter((item) => item.id !== fertId); + } + // Auto-select if amount is entered + setSelected((prevSelected) => new Set(prevSelected).add(idStr)); + if (existing) { + // Update existing + return prev.map((item) => (item.id === fertId ? { ...item, value: numValue } : item)); + } + // Add new + return [...prev, { id: fertId, value: numValue }]; + }); }, - [fertilizerIds, setSelectedIds], + [setSelectedIds], ); const selectAll = useCallback(() => { - const allIds = fertilizerIds.map((id) => id.toString()); - handleSelection(allIds); - }, [fertilizerIds, handleSelection]); + const newAmounts: Record = {}; + const newSelectedIds: FertilizerTransferItem[] = []; + const newSelected = new Set(); + + for (const fertId of fertilizerIds) { + const idStr = fertId.toString(); + // For now, default to 1 bsFERT per ID when selecting all + // TODO: Use actual balance per ID from on-chain data + newAmounts[idStr] = "1"; + newSelectedIds.push({ id: fertId, value: 1n }); + newSelected.add(idStr); + } + + setAmounts(newAmounts); + setSelectedIds(newSelectedIds); + setSelected(newSelected); + }, [fertilizerIds, setSelectedIds]); if (fertilizerIds.length === 0) { return ( @@ -96,44 +169,37 @@ export default function StepOne({ className="font-[340] sm:pr-0 text-[1rem] sm:text-[1.25rem] text-pinto-green-4 bg-transparent hover:underline hover:bg-transparent" onClick={selectAll} > - Select all Fertilizer + Select all
- - - - - {fertilizerIds.map((fertId) => ( - - - -
- -
-
- Fertilizer ID {formatter.number(Number(fertId))} — 1 bsFERT -
-
-
-
-
-
- ))} -
-
-
+ +
+ {fertilizerIds.map((fertId) => { + const idStr = fertId.toString(); + const amount = amounts[idStr] || ""; + const isSelected = selected.has(idStr); + const maxBalance = getFertilizerBalance(fertId); + // TODO: Replace with actual sprouts and humidity data from on-chain + const sprouts = "407,287"; + const humidity = "500%"; + + return ( + + ); + })} +
diff --git a/src/pages/transfer/actions/beanstalk-pods/FinalStep.tsx b/src/pages/transfer/actions/beanstalk-pods/FinalStep.tsx index f45e127b..1e6b7d1b 100644 --- a/src/pages/transfer/actions/beanstalk-pods/FinalStep.tsx +++ b/src/pages/transfer/actions/beanstalk-pods/FinalStep.tsx @@ -3,6 +3,8 @@ import AddressLink from "@/components/AddressLink"; import { Label } from "@/components/ui/Label"; import { useHarvestableIndex } from "@/state/useFieldData"; import { formatter } from "@/utils/format"; +import { computeSummaryRange } from "@/utils/podTransferUtils"; +import { useMemo } from "react"; import { PodTransferData } from "../TransferBeanstalkPods"; interface FinalStepProps { @@ -13,36 +15,42 @@ interface FinalStepProps { export default function FinalStep({ destination, transferData }: FinalStepProps) { const harvestableIndex = useHarvestableIndex(); - if (!destination || transferData.length === 0) { + const summary = useMemo(() => { + if (transferData.length === 0) return null; + return computeSummaryRange(transferData, harvestableIndex); + }, [transferData, harvestableIndex]); + + if (!destination || !summary) { return null; } + const { totalPods, placeInLineStart, placeInLineEnd } = summary; + const isSinglePlot = transferData.length === 1; + return (
- {transferData.map((transfer) => { - const placeInLine = transfer.id.sub(harvestableIndex); - const podAmount = transfer.end.sub(transfer.start); - - return ( -
-
- {formatter.number(podAmount)} - Plot - Pods -
-
- @ - {formatter.number(placeInLine.add(transfer.start))} in Line -
-
- ); - })} +
+
+ {formatter.number(totalPods)} + Plot + Pods +
+
+ {isSinglePlot ? ( + <> + @ + {formatter.number(placeInLineStart)} in Line + + ) : ( + + between {formatter.number(placeInLineStart)} - {formatter.number(placeInLineEnd)} in Line + + )} +
+
diff --git a/src/pages/transfer/actions/beanstalk-pods/StepOne.tsx b/src/pages/transfer/actions/beanstalk-pods/StepOne.tsx index 794e2f7b..6c898322 100644 --- a/src/pages/transfer/actions/beanstalk-pods/StepOne.tsx +++ b/src/pages/transfer/actions/beanstalk-pods/StepOne.tsx @@ -1,14 +1,11 @@ -import podIcon from "@/assets/protocol/Pod.png"; -import { TokenValue } from "@/classes/TokenValue"; -import CheckmarkCircle from "@/components/CheckmarkCircle"; -import { Button } from "@/components/ui/Button"; -import { Label } from "@/components/ui/Label"; -import { Table, TableBody, TableCell, TableRow } from "@/components/ui/Table"; -import { ToggleGroup, ToggleGroupItem } from "@/components/ui/ToggleGroup"; +import PodLineGraph from "@/components/PodLineGraph"; +import { MultiSlider } from "@/components/ui/Slider"; import { useFarmerBeanstalkRepayment } from "@/state/useFarmerBeanstalkRepayment"; +import { useHarvestableIndex } from "@/state/useFieldData"; import { formatter } from "@/utils/format"; +import { computeTransferData, offsetToAbsoluteIndex } from "@/utils/podTransferUtils"; import { Plot } from "@/utils/types"; -import { Dispatch, SetStateAction, useCallback, useEffect, useState } from "react"; +import { Dispatch, SetStateAction, useCallback, useEffect, useMemo, useRef, useState } from "react"; import { PodTransferData } from "../TransferBeanstalkPods"; interface StepOneProps { @@ -16,111 +13,164 @@ interface StepOneProps { setTransferData: Dispatch>; } +function sortPlotsByIndex(plots: Plot[]): Plot[] { + return [...plots].sort((a, b) => a.index.sub(b.index).toNumber()); +} + export default function StepOne({ transferData, setTransferData }: StepOneProps) { - const [selected, setSelected] = useState(); const { plots } = useFarmerBeanstalkRepayment().pods; + const harvestableIndex = useHarvestableIndex(); + + const [selectedPlots, setSelectedPlots] = useState([]); + const [podRange, setPodRange] = useState<[number, number]>([0, 0]); + const mountedRef = useRef(false); + + // Restore selection from existing transferData on mount useEffect(() => { - const _newPlots: string[] = []; - for (const data of transferData) { - const _plot = plots.find((plot) => plot.index.eq(data.id)); - if (_plot) { - _newPlots.push(_plot.index.toHuman()); - } + if (mountedRef.current) return; + mountedRef.current = true; + if (transferData.length === 0) return; + const restoredPlots = transferData + .map((data) => plots.find((p) => p.index.eq(data.id))) + .filter((p): p is Plot => p !== undefined); + if (restoredPlots.length > 0) { + const sorted = sortPlotsByIndex(restoredPlots); + setSelectedPlots(sorted); + const total = sorted.reduce((sum, p) => sum + p.pods.toNumber(), 0); + setPodRange([0, total]); } - setSelected(_newPlots); + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); + // Total pods across selected plots + const totalPods = useMemo(() => { + return selectedPlots.reduce((sum, p) => sum + p.pods.toNumber(), 0); + }, [selectedPlots]); + + const amount = podRange[1] - podRange[0]; + + const selectedPlotIndices = useMemo(() => selectedPlots.map((p) => p.index.toHuman()), [selectedPlots]); + + const positionInfo = useMemo(() => { + if (selectedPlots.length === 0) return null; + const first = selectedPlots[0]; + const last = selectedPlots[selectedPlots.length - 1]; + return { + start: first.index.sub(harvestableIndex), + end: last.index.add(last.pods).sub(harvestableIndex), + }; + }, [selectedPlots, harvestableIndex]); + + const selectedPodRange = useMemo(() => { + if (selectedPlots.length === 0) return undefined; + return { + start: offsetToAbsoluteIndex(podRange[0], selectedPlots), + end: offsetToAbsoluteIndex(podRange[1], selectedPlots), + }; + }, [selectedPlots, podRange]); + const handlePlotSelection = useCallback( - (value: string[]) => { - // Update selected plots - setSelected(value); - - // Get selected plots data - const selectedPlots = value - .map((plotIndex) => { - const plot = plots.find((p) => p.index.toHuman() === plotIndex); - return plot; - }) - .filter((plot): plot is Plot => plot !== undefined && !plot.fullyHarvested); - - // If no valid plots selected, clear transfer data - if (selectedPlots.length === 0) { + (newPlots: Plot[]) => { + const sorted = sortPlotsByIndex(newPlots); + setSelectedPlots(sorted); + + if (sorted.length > 0) { + const newTotal = sorted.reduce((sum, p) => sum + p.pods.toNumber(), 0); + setPodRange([0, newTotal]); + setTransferData(computeTransferData(sorted, [0, newTotal])); + } else { + setPodRange([0, 0]); setTransferData([]); + } + }, + [setTransferData], + ); + + const handlePlotGroupSelect = useCallback( + (plotIndices: string[]) => { + const groupSet = new Set(plotIndices); + const plotsInGroup = plots.filter((p) => groupSet.has(p.index.toHuman())); + if (plotsInGroup.length === 0) return; + + const selectedSet = new Set(selectedPlots.map((p) => p.index.toHuman())); + const allSelected = plotIndices.every((idx) => selectedSet.has(idx)); + + if (allSelected) { + handlePlotSelection(selectedPlots.filter((p) => !groupSet.has(p.index.toHuman()))); return; } - // Create plot transfer data - const transferData = selectedPlots.map((plot) => { - return { - id: plot.index, - start: TokenValue.ZERO, - end: plot.pods, - }; - }); - - // Update transfer data - setTransferData(transferData); + const newPlots = [...selectedPlots]; + for (const plotToAdd of plotsInGroup) { + if (!selectedSet.has(plotToAdd.index.toHuman())) { + newPlots.push(plotToAdd); + } + } + handlePlotSelection(newPlots); }, - [plots, setTransferData], + [plots, selectedPlots, handlePlotSelection], ); - const selectAllPlots = useCallback(() => { - const plotIndexes = plots.map((plot) => plot.index.toHuman()); - handlePlotSelection(plotIndexes); - }, [plots, handlePlotSelection]); + const handlePodRangeChange = useCallback( + (value: number[]) => { + const newRange: [number, number] = [value[0], value[1]]; + setPodRange(newRange); + setTransferData(computeTransferData(selectedPlots, newRange)); + }, + [selectedPlots, setTransferData], + ); return ( - <> -
- -
-
- - - - - {plots - .filter((plot) => plot.unharvestablePods?.gt(0)) - .map((plot) => ( - - - -
- -
- Pods -
- {formatter.number(plot.unharvestablePods ?? plot.pods, { - minValue: 0.01, - })}{" "} - Pods -
-
-
-
-
-
- ))} -
-
-
+
+
+ + + {positionInfo && ( +
+

+ {positionInfo.start.toHuman("short")} - {positionInfo.end.toHuman("short")} +

+
+ )}
- + + {totalPods > 0 && ( +
+

Total Pods to send:

+

{formatter.noDec(amount)} Pods

+
+ )} + + {selectedPlots.length > 0 && ( +
+
+

Select Pods

+
+

{formatter.noDec(podRange[0])}

+
+ {totalPods > 0 && ( + + )} +
+

{formatter.noDec(podRange[1])}

+
+
+
+ )} +
); } diff --git a/src/pages/transfer/actions/beanstalk-pods/StepTwo.tsx b/src/pages/transfer/actions/beanstalk-pods/StepTwo.tsx index 797b529b..86237f98 100644 --- a/src/pages/transfer/actions/beanstalk-pods/StepTwo.tsx +++ b/src/pages/transfer/actions/beanstalk-pods/StepTwo.tsx @@ -1,135 +1,19 @@ -import { TokenValue } from "@/classes/TokenValue"; import AddressInputField from "@/components/AddressInputField"; -import { ComboInputField } from "@/components/ComboInputField"; import PintoAssetTransferNotice from "@/components/PintoAssetTransferNotice"; -import PodRangeSelector from "@/components/PodRangeSelector"; import { Label } from "@/components/ui/Label"; -import { PODS } from "@/constants/internalTokens"; -import { useFarmerBeanstalkRepayment } from "@/state/useFarmerBeanstalkRepayment"; -import { Plot } from "@/utils/types"; import { AnimatePresence, motion } from "framer-motion"; -import { Dispatch, SetStateAction, useCallback, useEffect, useState } from "react"; -import { PodTransferData } from "../TransferBeanstalkPods"; +import { Dispatch, SetStateAction } from "react"; interface StepTwoProps { - transferData: PodTransferData[]; - setTransferData: Dispatch>; destination: string | undefined; setDestination: Dispatch>; transferNotice: boolean; setTransferNotice: Dispatch>; } -export default function StepTwo({ - transferData, - setTransferData, - destination, - setDestination, - transferNotice, - setTransferNotice, -}: StepTwoProps) { - const { plots } = useFarmerBeanstalkRepayment().pods; - const [selectedPlots, setSelectedPlots] = useState([]); - const [amount, setAmount] = useState("0"); - const [range, setRange] = useState<[TokenValue, TokenValue]>([TokenValue.ZERO, TokenValue.ZERO]); - - useEffect(() => { - const _newPlots: Plot[] = []; - for (const data of transferData) { - const _plot = plots.find((plot) => plot.index.eq(data.id)); - if (_plot) { - _newPlots.push(_plot); - } - } - setSelectedPlots(_newPlots); - }, []); - - useEffect(() => { - if (selectedPlots.length === 1) { - const plot = selectedPlots[0]; - setRange([plot.index, plot.index.add(plot.pods)]); - } - }, [selectedPlots]); - - const handleRangeChange = useCallback( - (newRange: TokenValue[]) => { - if (selectedPlots.length === 0 || selectedPlots.length > 1) return; - const plot = selectedPlots[0]; - const newStart = newRange[0]; - const newEnd = newRange[1]; - - const relativeStart = newStart.sub(plot.index); - const relativeEnd = newEnd.sub(plot.index); - - const newData = [ - { - id: plot.index, - start: relativeStart, - end: relativeEnd, - }, - ]; - - const newAmount = newEnd.sub(newStart).toHuman(); - - const batchUpdate = () => { - setRange([newStart, newEnd]); - setTransferData(newData); - setAmount(newAmount); - }; - batchUpdate(); - }, - [selectedPlots, setTransferData], - ); - - const handleAmountChange = useCallback( - (value: string) => { - const newAmount = value; - - if (selectedPlots.length === 0) return; - - const plot = selectedPlots[0]; - const amountValue = TokenValue.fromHuman(newAmount || "0", PODS.decimals); - const newEnd = plot.index.add(amountValue); - - const newStart = plot.index; - const relativeStart = newStart.sub(plot.index); - const relativeEnd = newEnd.sub(plot.index); - - const newData = [ - { - id: plot.index, - start: relativeStart, - end: relativeEnd, - }, - ]; - - const batchUpdate = () => { - setAmount(newAmount); - if (selectedPlots.length === 1) { - setRange([newStart, newEnd]); - setTransferData(newData); - } - }; - batchUpdate(); - }, - [selectedPlots, setTransferData], - ); - +export default function StepTwo({ destination, setDestination, transferNotice, setTransferNotice }: StepTwoProps) { return (
-
- - 1} - altText={selectedPlots.length > 1 ? "Balance:" : "Plot Balance:"} - /> -
@@ -148,11 +32,8 @@ export default function StepTwo({ /> )} - {" "} +
- {selectedPlots.length === 1 && ( - - )}
); } diff --git a/src/state/useFarmerBeanstalkRepayment.ts b/src/state/useFarmerBeanstalkRepayment.ts index f11dc1be..7661281d 100644 --- a/src/state/useFarmerBeanstalkRepayment.ts +++ b/src/state/useFarmerBeanstalkRepayment.ts @@ -94,7 +94,7 @@ export function useFarmerBeanstalkRepayment(): FarmerBeanstalkRepaymentData { // Query for Barn Payback (bsFERT) data from Barn_Payback contract // balanceOfFertilized and balanceOfUnfertilized require fertilizer IDs. - // We pass empty arrays initially; these will return 0 until IDs are populated. + // TODO: Fetch actual fertilizer IDs owned by the user from the contract const fertilizerIds: bigint[] = []; const fertilizerQuery = useReadContracts({ From d81b93d494233c3659a2d8f65dd8cdb056c12675 Mon Sep 17 00:00:00 2001 From: feyyazcigim Date: Fri, 13 Feb 2026 20:09:07 +0300 Subject: [PATCH 11/22] feat: implement beanstalk obligation repayment and claim functionality --- src/constants/abiSnippets.ts | 63 +++++++++ src/constants/address.ts | 7 +- src/hooks/useAllFertilizerIds.ts | 86 ++++++++++++ src/pages/Beanstalk.tsx | 4 +- .../components/BeanstalkFertilizerSection.tsx | 2 +- .../components/BeanstalkObligationsCard.tsx | 116 ++++++++++++++++- .../components/BeanstalkPodsSection.tsx | 2 +- .../components/BeanstalkSiloSection.tsx | 2 +- src/state/useBeanstalkGlobalStats.ts | 11 +- src/state/useFarmerBeanstalkRepayment.ts | 123 +++++++++++++++--- 10 files changed, 391 insertions(+), 25 deletions(-) create mode 100644 src/hooks/useAllFertilizerIds.ts diff --git a/src/constants/abiSnippets.ts b/src/constants/abiSnippets.ts index 13083b5c..8c7d901e 100644 --- a/src/constants/abiSnippets.ts +++ b/src/constants/abiSnippets.ts @@ -408,6 +408,16 @@ const siloPayback = [ stateMutability: "view", type: "function", }, + { + inputs: [ + { internalType: "address", name: "recipient", type: "address" }, + { internalType: "enum LibTransfer.To", name: "toMode", type: "uint8" }, + ], + name: "claim", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, ] as const; const barnPayback = [ @@ -455,6 +465,57 @@ const barnPayback = [ stateMutability: "view", type: "function", }, + { + inputs: [ + { internalType: "uint256[]", name: "ids", type: "uint256[]" }, + { internalType: "enum LibTransfer.To", name: "mode", type: "uint8" }, + ], + name: "claimFertilized", + outputs: [], + stateMutability: "payable", + type: "function", + }, +] as const; + +const contractPayback = [ + { + inputs: [], + name: "totalDistributed", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "totalReceived", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, +] as const; + +const fertilizerLinkedList = [ + { + inputs: [], + name: "getFirst", + outputs: [{ internalType: "uint128", name: "", type: "uint128" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "getLast", + outputs: [{ internalType: "uint128", name: "", type: "uint128" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "uint128", name: "id", type: "uint128" }], + name: "getNext", + outputs: [{ internalType: "uint128", name: "", type: "uint128" }], + stateMutability: "view", + type: "function", + }, ] as const; export const abiSnippets = { @@ -486,4 +547,6 @@ export const abiSnippets = { erc721Enum, siloPayback, barnPayback, + contractPayback, + fertilizerLinkedList, } as const; diff --git a/src/constants/address.ts b/src/constants/address.ts index dd81e757..eeb70471 100644 --- a/src/constants/address.ts +++ b/src/constants/address.ts @@ -41,6 +41,7 @@ export const WELL_FUNCTION_ADDRESSES: ChainLookup<{ export const NFT_COLLECTION_1_CONTRACT: HashString = "0xBea5e200a087529AA3B62c460040aE94E3651b76"; -// TODO: Replace with actual deployed contract addresses -export const SILO_PAYBACK_ADDRESS: HashString = "0x0000000000000000000000000000000000000000"; // urBDV contract address -export const BARN_PAYBACK_ADDRESS: HashString = "0x0000000000000000000000000000000000000000"; // bsFERT contract address +// Deployed contract addresses for Beanstalk Shipments +export const SILO_PAYBACK_ADDRESS: HashString = "0x525C94754C51946a7a3B72580Ce0DF36922E1E64"; +export const BARN_PAYBACK_ADDRESS: HashString = "0x68bDbb0402a3Ca89C7C4af8e41C021635102d158"; +export const CONTRACT_PAYBACK_ADDRESS: HashString = "0xbA941Af3292c49b585f4EC5C8164c1dfc893EEdC"; diff --git a/src/hooks/useAllFertilizerIds.ts b/src/hooks/useAllFertilizerIds.ts new file mode 100644 index 00000000..ebc4761b --- /dev/null +++ b/src/hooks/useAllFertilizerIds.ts @@ -0,0 +1,86 @@ +import { abiSnippets } from "@/constants/abiSnippets"; +import { useProtocolAddress } from "@/hooks/pinto/useProtocolAddress"; +import { useCallback, useEffect, useState } from "react"; +import { usePublicClient } from "wagmi"; + +const MAX_ITERATIONS = 500; + +export interface UseAllFertilizerIdsReturn { + fertilizerIds: bigint[]; + isLoading: boolean; + isError: boolean; + refetch: () => Promise; +} + +/** + * Hook that traverses the Beanstalk Diamond contract's linked list + * to collect all global fertilizer IDs. + * + * Uses getFirst() to get the first ID, then getNext(id) to traverse + * until getNext returns 0 or MAX_ITERATIONS are reached. + * + * Does NOT require wallet connection — this is global data. + */ +export function useAllFertilizerIds(): UseAllFertilizerIdsReturn { + const publicClient = usePublicClient(); + const protocolAddress = useProtocolAddress(); + + const [fertilizerIds, setFertilizerIds] = useState([]); + const [isLoading, setIsLoading] = useState(true); + const [isError, setIsError] = useState(false); + + const fetchIds = useCallback(async () => { + if (!publicClient || !protocolAddress) { + setIsLoading(false); + return; + } + + try { + setIsLoading(true); + setIsError(false); + + const firstId = await publicClient.readContract({ + address: protocolAddress, + abi: abiSnippets.fertilizerLinkedList, + functionName: "getFirst", + }); + + if (!firstId || firstId === 0n) { + setFertilizerIds([]); + setIsLoading(false); + return; + } + + const ids: bigint[] = [BigInt(firstId)]; + let currentId = firstId; + + for (let i = 0; i < MAX_ITERATIONS; i++) { + const nextId = await publicClient.readContract({ + address: protocolAddress, + abi: abiSnippets.fertilizerLinkedList, + functionName: "getNext", + args: [currentId], + }); + + if (!nextId || nextId === 0n) break; + + ids.push(BigInt(nextId)); + currentId = nextId; + } + + setFertilizerIds(ids); + } catch (error) { + console.error("[useAllFertilizerIds] Linked list traverse failed:", error); + setFertilizerIds([]); + setIsError(true); + } finally { + setIsLoading(false); + } + }, [publicClient, protocolAddress]); + + useEffect(() => { + fetchIds(); + }, [fetchIds]); + + return { fertilizerIds, isLoading, isError, refetch: fetchIds }; +} diff --git a/src/pages/Beanstalk.tsx b/src/pages/Beanstalk.tsx index 7fe23357..74b91a74 100644 --- a/src/pages/Beanstalk.tsx +++ b/src/pages/Beanstalk.tsx @@ -17,8 +17,8 @@ const Beanstalk = () => {
Beanstalk Debt issued by Pinto. - Beanstalk participants at the time of Pinto launch were issued assets based on their holdings. When - Pinto exceeds 1 Billion in supply, 3% of mints go towards these between Beanstalk Silo Tokens, Pods, and + Beanstalk participants at the time of Pinto launch were issued assets based on their holdings. A portion + of new Pinto mints go towards repaying these obligations across Beanstalk Silo Tokens, Pods, and Fertilizer.{" "} = ({ disabled={disabled} actions={[ { label: "Rinse", onClick: onRinse, disabled: !hasBalance }, - { label: "Send", onClick: onSend }, + { label: "Send", onClick: onSend, disabled: !hasBalance }, ]} /> ); diff --git a/src/pages/beanstalk/components/BeanstalkObligationsCard.tsx b/src/pages/beanstalk/components/BeanstalkObligationsCard.tsx index 2bc0ad65..db9fe51f 100644 --- a/src/pages/beanstalk/components/BeanstalkObligationsCard.tsx +++ b/src/pages/beanstalk/components/BeanstalkObligationsCard.tsx @@ -1,5 +1,12 @@ import { Button } from "@/components/ui/Button"; +import { abiSnippets } from "@/constants/abiSnippets"; +import { SILO_PAYBACK_ADDRESS } from "@/constants/address"; +import { beanstalkAbi, beanstalkAddress } from "@/generated/contractHooks"; +import useTransaction from "@/hooks/useTransaction"; import { useFarmerBeanstalkRepayment } from "@/state/useFarmerBeanstalkRepayment"; +import { useCallback } from "react"; +import { useNavigate } from "react-router-dom"; +import { encodeFunctionData } from "viem"; import { useAccount } from "wagmi"; import BeanstalkFertilizerSection from "./BeanstalkFertilizerSection"; import BeanstalkPodsSection from "./BeanstalkPodsSection"; @@ -12,11 +19,108 @@ import BeanstalkSiloSection from "./BeanstalkSiloSection"; */ const BeanstalkObligationsCard: React.FC = () => { const account = useAccount(); + const navigate = useNavigate(); const { silo, pods, fertilizer, isLoading, isError, refetch } = useFarmerBeanstalkRepayment(); const isConnected = !!account.address; const showDisabled = !isConnected || isError; + // Transaction hook for claim operations + const { writeWithEstimateGas, setSubmitting } = useTransaction({ + onSuccess: () => { + refetch(); + }, + successMessage: "Claim successful", + errorMessage: "Claim failed", + }); + + // Silo Claim — Claim earned urBDV from SiloPayback + const handleClaimSilo = useCallback(async () => { + if (!account.address || silo.earned.isZero) return; + + try { + setSubmitting(true); + + // Encode claim(address recipient, enum LibTransfer.To toMode) call for SiloPayback contract + // toMode: 0 = INTERNAL (to internal balance), 1 = EXTERNAL (to wallet), 2 = INTERNAL_TOLERANT + const claimData = encodeFunctionData({ + abi: abiSnippets.siloPayback, + functionName: "claim", + args: [account.address, 1], // Claim to wallet (EXTERNAL) + }); + + await writeWithEstimateGas({ + address: beanstalkAddress[account.chainId ?? 8453], + abi: beanstalkAbi, + functionName: "farm", + args: [[claimData]], + }); + } catch (error) { + console.error("Silo claim error:", error); + setSubmitting(false); + } + }, [account.address, account.chainId, silo.earned, writeWithEstimateGas, setSubmitting]); + + // Pods Harvest — Navigate to field harvest page + const handleHarvestPods = useCallback(() => { + // Navigate to field page with harvest action for fieldId=1 + navigate("/field?action=harvest&fieldId=1"); + }, [navigate]); + + // Fertilizer Rinse — Rinse fertilized sprouts + const handleRinseFert = useCallback(async () => { + if (!account.address || fertilizer.fertilized.isZero) return; + + // Check if we have fertilizer IDs + if (!fertilizer.fertilizerIds || fertilizer.fertilizerIds.length === 0) { + console.warn( + "Cannot rinse fertilizer: No fertilizer IDs available. Fertilizer ID enumeration not yet implemented.", + ); + return; + } + + try { + setSubmitting(true); + + // Encode claimFertilized(uint256[] ids, enum LibTransfer.To mode) call for BarnPayback contract + // mode: 0 = INTERNAL (to internal balance), 1 = EXTERNAL (to wallet), 2 = INTERNAL_TOLERANT + const claimFertilizedData = encodeFunctionData({ + abi: abiSnippets.barnPayback, + functionName: "claimFertilized", + args: [fertilizer.fertilizerIds, 1], // Claim to wallet (EXTERNAL) + }); + + await writeWithEstimateGas({ + address: beanstalkAddress[account.chainId ?? 8453], + abi: beanstalkAbi, + functionName: "farm", + args: [[claimFertilizedData]], + }); + } catch (error) { + console.error("Fertilizer rinse error:", error); + setSubmitting(false); + } + }, [ + account.address, + account.chainId, + fertilizer.fertilized, + fertilizer.fertilizerIds, + writeWithEstimateGas, + setSubmitting, + ]); + + const handleSendSilo = () => { + navigate("/transfer/beanstalk-silo"); + }; + + const handleSendPods = () => { + navigate("/transfer/beanstalk-pods"); + }; + + const handleSendFertilizer = () => { + navigate("/transfer/beanstalk-fertilizer"); + }; + return (
{isConnected && isError && ( @@ -27,17 +131,27 @@ const BeanstalkObligationsCard: React.FC = () => {
)}
- +
diff --git a/src/pages/beanstalk/components/BeanstalkPodsSection.tsx b/src/pages/beanstalk/components/BeanstalkPodsSection.tsx index e26a2f5d..d721509f 100644 --- a/src/pages/beanstalk/components/BeanstalkPodsSection.tsx +++ b/src/pages/beanstalk/components/BeanstalkPodsSection.tsx @@ -36,7 +36,7 @@ const BeanstalkPodsSection: React.FC = ({ disabled={disabled} actions={[ { label: "Harvest", onClick: onHarvest, disabled: !hasPods }, - { label: "Send", onClick: onSend }, + { label: "Send", onClick: onSend, disabled: !hasPods }, ]} > {isLoading ? ( diff --git a/src/pages/beanstalk/components/BeanstalkSiloSection.tsx b/src/pages/beanstalk/components/BeanstalkSiloSection.tsx index c24ca4dd..07b041c6 100644 --- a/src/pages/beanstalk/components/BeanstalkSiloSection.tsx +++ b/src/pages/beanstalk/components/BeanstalkSiloSection.tsx @@ -30,7 +30,7 @@ const BeanstalkSiloSection: React.FC = ({ disabled={disabled} actions={[ { label: "Claim", onClick: onClaim, disabled: !hasBalance }, - { label: "Send", onClick: onSend }, + { label: "Send", onClick: onSend, disabled: !hasBalance }, ]} /> ); diff --git a/src/state/useBeanstalkGlobalStats.ts b/src/state/useBeanstalkGlobalStats.ts index 45db6383..922ea475 100644 --- a/src/state/useBeanstalkGlobalStats.ts +++ b/src/state/useBeanstalkGlobalStats.ts @@ -80,6 +80,12 @@ export function useBeanstalkGlobalStats(): BeanstalkGlobalStatsData { abi: abiSnippets.barnPayback, functionName: "barnRemaining", }, + // Total Pinto received by Silo Payback contract + { + address: SILO_PAYBACK_ADDRESS, + abi: abiSnippets.siloPayback, + functionName: "totalReceived", + }, ], allowFailure: true, query: { @@ -104,12 +110,15 @@ export function useBeanstalkGlobalStats(): BeanstalkGlobalStatsData { const siloRemainingResult = globalQuery.data?.[1]?.result; const totalDistributedResult = globalQuery.data?.[2]?.result; const barnRemainingResult = globalQuery.data?.[3]?.result; + const totalReceivedResult = globalQuery.data?.[4]?.result; return { totalUrBdvDistributed: TokenValue.fromBlockchain(totalDistributedResult ?? 0n, URBDV.decimals), totalPodsInRepaymentField: TokenValue.fromBlockchain(totalPodsInRepaymentField ?? 0n, PODS.decimals), totalUnfertilizedSprouts: TokenValue.fromBlockchain(barnRemainingResult ?? 0n, SPROUTS.decimals), - totalPintoPaidOut: TokenValue.fromBlockchain(0n, URBDV.decimals), + // totalPintoPaidOut: Use totalReceived for now (will be > 0 once shipments start) + // Alternative: Could sum totalDistributed as a proxy for "issued" amount + totalPintoPaidOut: TokenValue.fromBlockchain(totalReceivedResult ?? 0n, URBDV.decimals), siloRemaining: TokenValue.fromBlockchain(siloRemainingResult ?? 0n, URBDV.decimals), barnRemaining: TokenValue.fromBlockchain(barnRemainingResult ?? 0n, SPROUTS.decimals), }; diff --git a/src/state/useFarmerBeanstalkRepayment.ts b/src/state/useFarmerBeanstalkRepayment.ts index 7661281d..82e9f0c3 100644 --- a/src/state/useFarmerBeanstalkRepayment.ts +++ b/src/state/useFarmerBeanstalkRepayment.ts @@ -5,6 +5,7 @@ import { BSFERT, PODS } from "@/constants/internalTokens"; import { defaultQuerySettings } from "@/constants/query"; import { beanstalkAbi } from "@/generated/contractHooks"; import { useProtocolAddress } from "@/hooks/pinto/useProtocolAddress"; +import { useAllFertilizerIds } from "@/hooks/useAllFertilizerIds"; import { Plot } from "@/utils/types"; import { useCallback, useMemo } from "react"; import { toHex } from "viem"; @@ -61,6 +62,12 @@ const URBDV_DECIMALS = 6; * - Pods data from repayment field (fieldId=1) - from on-chain * - Fertilizer data from Barn_Payback contract (fertilized, unfertilized balances) * + * Fertilizer flow (4 phases): + * 1. useAllFertilizerIds() — get all global fertilizer IDs from linked list + * 2. multicall balanceOf(user, id) for each global ID + * 3. filter IDs where balance > 0 (userOwnedIds) + * 4. balanceOfFertilized + balanceOfUnfertilized for userOwnedIds + * * @returns FarmerBeanstalkRepaymentData with all farmer obligations data */ export function useFarmerBeanstalkRepayment(): FarmerBeanstalkRepaymentData { @@ -84,6 +91,20 @@ export function useFarmerBeanstalkRepayment(): FarmerBeanstalkRepaymentData { functionName: "balanceOfPods", args: [farmerAddress, 1n], // fieldId=1 for repayment field }, + { + address: protocolAddress, + abi: [ + { + inputs: [{ name: "fieldId", type: "uint256" }], + name: "getHarvestableIndex", + outputs: [{ name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + ] as const, + functionName: "getHarvestableIndex", + args: [1n], // fieldId=1 for repayment field + }, ], allowFailure: true, query: { @@ -92,29 +113,65 @@ export function useFarmerBeanstalkRepayment(): FarmerBeanstalkRepaymentData { }, }); - // Query for Barn Payback (bsFERT) data from Barn_Payback contract - // balanceOfFertilized and balanceOfUnfertilized require fertilizer IDs. - // TODO: Fetch actual fertilizer IDs owned by the user from the contract - const fertilizerIds: bigint[] = []; + // --- Phase 1: Get all global fertilizer IDs from linked list --- + const { + fertilizerIds: allFertilizerIds, + isLoading: fertIdsLoading, + isError: fertIdsError, + refetch: refetchFertIds, + } = useAllFertilizerIds(); + + // --- Phase 2: Multicall balanceOf(user, id) for each global ID --- + const balanceChecksEnabled = allFertilizerIds.length > 0 && farmerAddress !== ZERO_ADDRESS; + const balanceCheckContracts = useMemo( + () => + allFertilizerIds.map((id) => ({ + address: BARN_PAYBACK_ADDRESS, + abi: abiSnippets.barnPayback, + functionName: "balanceOf" as const, + args: [farmerAddress, id] as const, + })), + [allFertilizerIds, farmerAddress], + ); + const balanceChecks = useReadContracts({ + contracts: balanceChecksEnabled ? balanceCheckContracts : [], + allowFailure: true, + query: { + ...defaultQuerySettings, + enabled: balanceChecksEnabled, + }, + }); + + // --- Phase 3: Filter IDs where balance > 0 --- + const userOwnedIds = useMemo(() => { + if (!balanceChecks.data) return []; + return allFertilizerIds.filter((_, i) => { + const result = balanceChecks.data?.[i]; + return result?.status === "success" && (result.result as bigint) > 0n; + }); + }, [balanceChecks.data, allFertilizerIds]); + // --- Phase 4: balanceOfFertilized + balanceOfUnfertilized for userOwnedIds --- + const fertQueryEnabled = userOwnedIds.length > 0; const fertilizerQuery = useReadContracts({ contracts: [ { address: BARN_PAYBACK_ADDRESS, abi: abiSnippets.barnPayback, functionName: "balanceOfFertilized", - args: [farmerAddress, fertilizerIds], + args: [farmerAddress, userOwnedIds], }, { address: BARN_PAYBACK_ADDRESS, abi: abiSnippets.barnPayback, functionName: "balanceOfUnfertilized", - args: [farmerAddress, fertilizerIds], + args: [farmerAddress, userOwnedIds], }, ], allowFailure: true, query: { ...defaultQuerySettings, + enabled: fertQueryEnabled, }, }); @@ -185,10 +242,27 @@ export function useFarmerBeanstalkRepayment(): FarmerBeanstalkRepaymentData { const plotsResult = podsQuery.data?.[0]?.result as readonly { index: bigint; pods: bigint }[] | undefined; const totalPodsResult = podsQuery.data?.[1]?.result; + const harvestableIndexResult = podsQuery.data?.[2]?.result as bigint | undefined; + + const harvestableIndex = TokenValue.fromBigInt(harvestableIndexResult ?? 0n, PODS.decimals); const plots: Plot[] = (plotsResult ?? []).map((plotData) => { const index = TokenValue.fromBigInt(plotData.index, PODS.decimals); const pods = TokenValue.fromBigInt(plotData.pods, PODS.decimals); + const endIndex = index.add(pods); + + let harvestablePods = TokenValue.ZERO; + let unharvestablePods = pods; + + if (harvestableIndex.gt(index)) { + if (harvestableIndex.gte(endIndex)) { + harvestablePods = pods; + unharvestablePods = TokenValue.ZERO; + } else { + harvestablePods = harvestableIndex.sub(index); + unharvestablePods = pods.sub(harvestablePods); + } + } return { id: index.toHuman(), @@ -196,8 +270,8 @@ export function useFarmerBeanstalkRepayment(): FarmerBeanstalkRepaymentData { index, pods, harvestedPods: TokenValue.ZERO, - harvestablePods: TokenValue.ZERO, - unharvestablePods: pods, + harvestablePods, + unharvestablePods, }; }); @@ -214,7 +288,7 @@ export function useFarmerBeanstalkRepayment(): FarmerBeanstalkRepaymentData { balance: TokenValue.ZERO, fertilized: TokenValue.ZERO, unfertilized: TokenValue.ZERO, - fertilizerIds, + fertilizerIds: userOwnedIds, }; } @@ -230,18 +304,37 @@ export function useFarmerBeanstalkRepayment(): FarmerBeanstalkRepaymentData { balance, fertilized, unfertilized, - fertilizerIds, + fertilizerIds: userOwnedIds, }; - }, [fertilizerQuery.data, fertilizerQuery.isError, fertilizerIds]); + }, [fertilizerQuery.data, fertilizerQuery.isError, userOwnedIds]); // Refetch all queries const refetch = useCallback(async () => { - await Promise.all([siloQuery.refetch(), podsQuery.refetch(), fertilizerQuery.refetch()]); - }, [siloQuery.refetch, podsQuery.refetch, fertilizerQuery.refetch]); + await Promise.all([ + siloQuery.refetch(), + podsQuery.refetch(), + refetchFertIds(), + balanceChecks.refetch(), + fertilizerQuery.refetch(), + ]); + }, [siloQuery.refetch, podsQuery.refetch, refetchFertIds, balanceChecks.refetch, fertilizerQuery.refetch]); // Loading and error states from all queries - const isLoading = siloQuery.isLoading || podsQuery.isLoading || fertilizerQuery.isLoading; - const isError = siloQuery.isError || podsQuery.isError || fertilizerQuery.isError; + // Only count isError from queries that are actually enabled, + // disabled queries can report isError spuriously + const isLoading = + siloQuery.isLoading || + podsQuery.isLoading || + fertIdsLoading || + (balanceChecksEnabled && balanceChecks.isLoading) || + (fertQueryEnabled && fertilizerQuery.isLoading); + const isError = + siloQuery.isError || + podsQuery.isError || + fertIdsError || + (balanceChecksEnabled && balanceChecks.isError) || + (fertQueryEnabled && fertilizerQuery.isError); + (balanceChecksEnabled && balanceChecks.isError) || (fertQueryEnabled && fertilizerQuery.isError); return useMemo( () => ({ From 1f8ac96c994061b2167b76a001c3db2eec7ea179 Mon Sep 17 00:00:00 2001 From: feyyazcigim Date: Fri, 13 Feb 2026 21:53:21 +0300 Subject: [PATCH 12/22] feat: enhance beanstalk repayment data fetching and transfer flows Refactor repayment logic to use event scanning for fertilizer IDs and direct contract interactions for urBDV and bsFERT transfers. This update also improves the UI with dynamic pod line grouping, detailed earned balance displays, and comprehensive batch transfer support. --- src/components/ComboInputField.tsx | 14 +- src/components/FertilizerCard.tsx | 5 +- src/components/PodLineGraph.tsx | 49 ++++- src/constants/abiSnippets.ts | 81 +++++--- src/hooks/useAllFertilizerIds.ts | 182 +++++++++++------- .../components/BeanstalkObligationsCard.tsx | 55 ++---- .../components/BeanstalkPodsSection.tsx | 12 +- .../components/BeanstalkSiloSection.tsx | 27 ++- src/pages/transfer/TransferActions.tsx | 24 ++- src/pages/transfer/actions/TransferAll.tsx | 116 +++++++++-- .../actions/TransferBeanstalkSilo.tsx | 43 ++--- src/pages/transfer/actions/all/FinalStep.tsx | 42 +++- .../actions/beanstalk-fertilizer/StepOne.tsx | 50 ++--- .../actions/beanstalk-pods/FinalStep.tsx | 4 +- .../actions/beanstalk-pods/StepOne.tsx | 8 +- .../actions/beanstalk-silo/FinalStep.tsx | 10 +- .../actions/beanstalk-silo/StepOne.tsx | 15 +- src/state/useFarmerBeanstalkRepayment.ts | 72 ++++++- 18 files changed, 544 insertions(+), 265 deletions(-) diff --git a/src/components/ComboInputField.tsx b/src/components/ComboInputField.tsx index dfdc9f27..75041aba 100644 --- a/src/components/ComboInputField.tsx +++ b/src/components/ComboInputField.tsx @@ -105,6 +105,7 @@ export interface ComboInputProps extends InputHTMLAttributes { // Additional info display showAdditionalInfo?: boolean; + hideUsdValue?: boolean; } function ComboInputField({ @@ -146,6 +147,7 @@ function ComboInputField({ enableSlider, sliderMarkers, showAdditionalInfo = true, + hideUsdValue = false, }: ComboInputProps) { const tokenData = useTokenData(); const { balances } = useFarmerBalances(); @@ -230,8 +232,9 @@ function ComboInputField({ } // If customMaxAmount is provided and greater than 0, use the minimum of base balance and customMaxAmount + // If base balance is 0 (no token selected or no farmer balance), use customMaxAmount directly if (customMaxAmount?.gt(0)) { - return TokenValue.min(baseBalance, customMaxAmount); + return baseBalance.gt(0) ? TokenValue.min(baseBalance, customMaxAmount) : customMaxAmount; } // Otherwise use base balance @@ -256,7 +259,12 @@ function ComboInputField({ return tokenAndBalanceMap.get(selectedToken) ?? TokenValue.ZERO; } // Always use farmerTokenBalance for display, not maxAmount (which may be limited by customMaxAmount) - return getFarmerBalanceByMode(farmerTokenBalance, balanceFrom); + const farmerBalance = getFarmerBalanceByMode(farmerTokenBalance, balanceFrom); + // If farmer balance is 0 and customMaxAmount is provided, show customMaxAmount as the balance + if (farmerBalance.eq(0) && customMaxAmount?.gt(0)) { + return customMaxAmount; + } + return farmerBalance; }, [mode, selectedPlots, tokenAndBalanceMap, selectedToken, farmerTokenBalance, balanceFrom, getFarmerBalanceByMode]); /** @@ -630,7 +638,7 @@ function ComboInputField({ {!disableInlineBalance && (
- {shouldShowAdditionalInfo() && mode !== "plots" ? ( + {shouldShowAdditionalInfo() && mode !== "plots" && !hideUsdValue ? ( {formatter.usd(inputValue)} diff --git a/src/components/FertilizerCard.tsx b/src/components/FertilizerCard.tsx index f1dc2117..59e11261 100644 --- a/src/components/FertilizerCard.tsx +++ b/src/components/FertilizerCard.tsx @@ -39,7 +39,10 @@ export default function FertilizerCard({ {/* Fertilizer info */}
-
{formatter.number(Number(fertId))} FERTILIZER
+
+ {formatter.number(Number(maxBalance))} bsFERT + ID {formatter.number(Number(fertId))} +
Sprouts: {sprouts} · Humidity: {humidity}
diff --git a/src/components/PodLineGraph.tsx b/src/components/PodLineGraph.tsx index 39d64a50..ccf5093a 100644 --- a/src/components/PodLineGraph.tsx +++ b/src/components/PodLineGraph.tsx @@ -53,14 +53,32 @@ interface PodLineGraphProps { label?: string; /** Optional: specify label type */ labelType?: "title" | "label"; + /** Optional: override harvestable index (for non-default fields like repayment field) */ + customHarvestableIndex?: TokenValue; + /** Optional: override pod index (for non-default fields like repayment field) */ + customPodIndex?: TokenValue; } /** * Groups nearby plots for visual display while keeping each plot individually interactive */ -function combinePlots(plots: Plot[], harvestableIndex: TokenValue, selectedIndices: Set): CombinedPlot[] { +function combinePlots( + plots: Plot[], + harvestableIndex: TokenValue, + selectedIndices: Set, + podLine: TokenValue, +): CombinedPlot[] { if (plots.length === 0) return []; + // Dynamically scale the grouping gap based on pod line size + const podLineNum = podLine.toNumber(); + let maxGap = MAX_GAP_TO_COMBINE; + if (podLineNum > 200_000_000) { + maxGap = TokenValue.fromHuman("50000000", PODS.decimals); // 50M gap for large pod lines + } else if (podLineNum > 50_000_000) { + maxGap = TokenValue.fromHuman("10000000", PODS.decimals); // 10M gap for medium pod lines + } + // Sort plots by index const sortedPlots = [...plots].sort((a, b) => a.index.sub(b.index).toNumber()); @@ -78,7 +96,7 @@ function combinePlots(plots: Plot[], harvestableIndex: TokenValue, selectedIndic const gap = nextPlot.index.sub(plot.index.add(plot.pods)); // If gap is small enough, continue grouping - if (gap.lt(MAX_GAP_TO_COMBINE)) { + if (gap.lt(maxGap)) { continue; } } @@ -116,9 +134,19 @@ function combinePlots(plots: Plot[], harvestableIndex: TokenValue, selectedIndic */ function generateAxisLabels(min: number, max: number): number[] { const labels: number[] = []; - const start = Math.floor(min / AXIS_INTERVAL) * AXIS_INTERVAL; + const range = max - min; + + // Dynamically choose interval based on range size + let interval = AXIS_INTERVAL; // default 10M + if (range > 200_000_000) { + interval = 100_000_000; // 100M intervals for large ranges + } else if (range > 50_000_000) { + interval = 50_000_000; // 50M intervals for medium ranges + } - for (let value = start; value <= max; value += AXIS_INTERVAL) { + const start = Math.floor(min / interval) * interval; + + for (let value = start; value <= max; value += interval) { if (value >= min) { labels.push(value); } @@ -191,10 +219,15 @@ export default function PodLineGraph({ className, label = "My Pods In Line", labelType = "label", + customHarvestableIndex, + customPodIndex, }: PodLineGraphProps) { const farmerField = useFarmerField(); - const harvestableIndex = useHarvestableIndex(); - const podIndex = usePodIndex(); + const defaultHarvestableIndex = useHarvestableIndex(); + const defaultPodIndex = usePodIndex(); + + const harvestableIndex = customHarvestableIndex ?? defaultHarvestableIndex; + const podIndex = customPodIndex ?? defaultPodIndex; const [hoveredPlotIndex, setHoveredPlotIndex] = useState(null); const [tooltipData, setTooltipData] = useState<{ @@ -253,8 +286,8 @@ export default function PodLineGraph({ // Combine plots for visualization const combinedPlots = useMemo( - () => combinePlots(plots, harvestableIndex, selectedSet), - [plots, harvestableIndex, selectedSet], + () => combinePlots(plots, harvestableIndex, selectedSet, podLine), + [plots, harvestableIndex, selectedSet, podLine], ); // Separate harvested and unharvested plots diff --git a/src/constants/abiSnippets.ts b/src/constants/abiSnippets.ts index 8c7d901e..a8cf1e99 100644 --- a/src/constants/abiSnippets.ts +++ b/src/constants/abiSnippets.ts @@ -418,6 +418,16 @@ const siloPayback = [ stateMutability: "nonpayable", type: "function", }, + { + inputs: [ + { internalType: "address", name: "to", type: "address" }, + { internalType: "uint256", name: "value", type: "uint256" }, + ], + name: "transfer", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "nonpayable", + type: "function", + }, ] as const; const barnPayback = [ @@ -437,7 +447,7 @@ const barnPayback = [ { internalType: "uint256[]", name: "ids", type: "uint256[]" }, ], name: "balanceOfFertilized", - outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + outputs: [{ internalType: "uint256", name: "beans", type: "uint256" }], stateMutability: "view", type: "function", }, @@ -447,7 +457,7 @@ const barnPayback = [ { internalType: "uint256[]", name: "ids", type: "uint256[]" }, ], name: "balanceOfUnfertilized", - outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + outputs: [{ internalType: "uint256", name: "beans", type: "uint256" }], stateMutability: "view", type: "function", }, @@ -461,58 +471,70 @@ const barnPayback = [ { inputs: [], name: "fert", - outputs: [{ internalType: "address", name: "", type: "address" }], + outputs: [ + { internalType: "uint128", name: "fertilizedIndex", type: "uint128" }, + { internalType: "uint128", name: "unfertilizedIndex", type: "uint128" }, + { internalType: "uint128", name: "fertilizedPaidIndex", type: "uint128" }, + { internalType: "uint128", name: "leftoverBeans", type: "uint128" }, + { internalType: "uint128", name: "activeFertilizer", type: "uint128" }, + { internalType: "uint128", name: "fertFirst", type: "uint128" }, + { internalType: "uint128", name: "fertLast", type: "uint128" }, + { internalType: "uint128", name: "bpf", type: "uint128" }, + ], stateMutability: "view", type: "function", }, { inputs: [ - { internalType: "uint256[]", name: "ids", type: "uint256[]" }, - { internalType: "enum LibTransfer.To", name: "mode", type: "uint8" }, + { internalType: "address", name: "account", type: "address" }, + { internalType: "uint256", name: "id", type: "uint256" }, ], - name: "claimFertilized", - outputs: [], - stateMutability: "payable", + name: "lastBalanceOf", + outputs: [ + { + components: [ + { internalType: "uint128", name: "amount", type: "uint128" }, + { internalType: "uint128", name: "lastBpf", type: "uint128" }, + ], + internalType: "struct BeanstalkFertilizer.Balance", + name: "", + type: "tuple", + }, + ], + stateMutability: "view", type: "function", }, -] as const; - -const contractPayback = [ { - inputs: [], - name: "totalDistributed", - outputs: [{ internalType: "uint256", name: "", type: "uint256" }], - stateMutability: "view", + inputs: [ + { internalType: "uint256[]", name: "ids", type: "uint256[]" }, + { internalType: "uint8", name: "mode", type: "uint8" }, + ], + name: "claimFertilized", + outputs: [], + stateMutability: "nonpayable", type: "function", }, { inputs: [], - name: "totalReceived", - outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + name: "totalUnfertilizedBeans", + outputs: [{ internalType: "uint256", name: "beans", type: "uint256" }], stateMutability: "view", type: "function", }, ] as const; -const fertilizerLinkedList = [ +const contractPayback = [ { inputs: [], - name: "getFirst", - outputs: [{ internalType: "uint128", name: "", type: "uint128" }], + name: "totalDistributed", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], stateMutability: "view", type: "function", }, { inputs: [], - name: "getLast", - outputs: [{ internalType: "uint128", name: "", type: "uint128" }], - stateMutability: "view", - type: "function", - }, - { - inputs: [{ internalType: "uint128", name: "id", type: "uint128" }], - name: "getNext", - outputs: [{ internalType: "uint128", name: "", type: "uint128" }], + name: "totalReceived", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], stateMutability: "view", type: "function", }, @@ -548,5 +570,4 @@ export const abiSnippets = { siloPayback, barnPayback, contractPayback, - fertilizerLinkedList, } as const; diff --git a/src/hooks/useAllFertilizerIds.ts b/src/hooks/useAllFertilizerIds.ts index ebc4761b..52e3a10a 100644 --- a/src/hooks/useAllFertilizerIds.ts +++ b/src/hooks/useAllFertilizerIds.ts @@ -1,86 +1,136 @@ import { abiSnippets } from "@/constants/abiSnippets"; -import { useProtocolAddress } from "@/hooks/pinto/useProtocolAddress"; -import { useCallback, useEffect, useState } from "react"; -import { usePublicClient } from "wagmi"; - -const MAX_ITERATIONS = 500; - -export interface UseAllFertilizerIdsReturn { - fertilizerIds: bigint[]; - isLoading: boolean; - isError: boolean; - refetch: () => Promise; -} +import { BARN_PAYBACK_ADDRESS } from "@/constants/address"; +import { useQuery } from "@tanstack/react-query"; +import { useCallback, useMemo } from "react"; +import { PublicClient, parseAbiItem } from "viem"; +import { usePublicClient, useReadContract } from "wagmi"; /** - * Hook that traverses the Beanstalk Diamond contract's linked list - * to collect all global fertilizer IDs. + * BarnPayback kontratından tüm aktif fertilizer ID'lerini çeken hook. * - * Uses getFirst() to get the first ID, then getNext(id) to traverse - * until getNext returns 0 or MAX_ITERATIONS are reached. + * Strateji: + * 1. fert() çağır → fertFirst, fertLast, activeFertilizer al + * 2. activeFertilizer == 0 && fertFirst == 0 ise → hiç fertilizer yok, çık + * 3. BarnPayback kontratının TransferSingle/TransferBatch eventlerinden benzersiz ID'leri topla * - * Does NOT require wallet connection — this is global data. + * NOT: Diamond'da getFirst()/getNext() yok. + * Linked list BarnPayback'in kendi içinde ama getNext() public getter olarak expose edilmemiş. + * Bu yüzden event scan kullanıyoruz. */ -export function useAllFertilizerIds(): UseAllFertilizerIdsReturn { - const publicClient = usePublicClient(); - const protocolAddress = useProtocolAddress(); - const [fertilizerIds, setFertilizerIds] = useState([]); - const [isLoading, setIsLoading] = useState(true); - const [isError, setIsError] = useState(false); +const BARN_PAYBACK = BARN_PAYBACK_ADDRESS as `0x${string}`; - const fetchIds = useCallback(async () => { - if (!publicClient || !protocolAddress) { - setIsLoading(false); - return; - } +const transferSingleEvent = parseAbiItem( + "event TransferSingle(address indexed operator, address indexed from, address indexed to, uint256 id, uint256 value)", +); - try { - setIsLoading(true); - setIsError(false); +const transferBatchEvent = parseAbiItem( + "event TransferBatch(address indexed operator, address indexed from, address indexed to, uint256[] ids, uint256[] values)", +); - const firstId = await publicClient.readContract({ - address: protocolAddress, - abi: abiSnippets.fertilizerLinkedList, - functionName: "getFirst", - }); +export interface FertData { + fertilizedIndex: bigint; + unfertilizedIndex: bigint; + fertilizedPaidIndex: bigint; + leftoverBeans: bigint; + activeFertilizer: bigint; + fertFirst: bigint; + fertLast: bigint; + bpf: bigint; +} - if (!firstId || firstId === 0n) { - setFertilizerIds([]); - setIsLoading(false); - return; - } +/** Fetch unique fertilizer IDs from event logs */ +async function fetchFertilizerIds(publicClient: PublicClient): Promise { + const uniqueIds = new Set(); - const ids: bigint[] = [BigInt(firstId)]; - let currentId = firstId; + // Scan TransferSingle events + const singleLogs = await publicClient.getLogs({ + address: BARN_PAYBACK, + event: transferSingleEvent, + fromBlock: "earliest", + toBlock: "latest", + }); - for (let i = 0; i < MAX_ITERATIONS; i++) { - const nextId = await publicClient.readContract({ - address: protocolAddress, - abi: abiSnippets.fertilizerLinkedList, - functionName: "getNext", - args: [currentId], - }); + for (const log of singleLogs) { + if (log.args.id !== undefined) { + uniqueIds.add(log.args.id); + } + } - if (!nextId || nextId === 0n) break; + // Scan TransferBatch events + const batchLogs = await publicClient.getLogs({ + address: BARN_PAYBACK, + event: transferBatchEvent, + fromBlock: "earliest", + toBlock: "latest", + }); - ids.push(BigInt(nextId)); - currentId = nextId; + for (const log of batchLogs) { + if (log.args.ids) { + for (const id of log.args.ids) { + uniqueIds.add(id); } - - setFertilizerIds(ids); - } catch (error) { - console.error("[useAllFertilizerIds] Linked list traverse failed:", error); - setFertilizerIds([]); - setIsError(true); - } finally { - setIsLoading(false); } - }, [publicClient, protocolAddress]); + } + + return Array.from(uniqueIds).sort((a, b) => (a < b ? -1 : a > b ? 1 : 0)); +} + +export function useAllFertilizerIds() { + const publicClient = usePublicClient(); + + // Step 1: fert() call — get contract state + const fertQuery = useReadContract({ + address: BARN_PAYBACK, + abi: abiSnippets.barnPayback, + functionName: "fert", + query: { + staleTime: 5 * 60 * 1000, + }, + }); + + // Parse fert() results + const fertData = useMemo((): FertData | null => { + if (!fertQuery.data) return null; + const result = fertQuery.data as readonly [bigint, bigint, bigint, bigint, bigint, bigint, bigint, bigint]; + return { + fertilizedIndex: result[0], + unfertilizedIndex: result[1], + fertilizedPaidIndex: result[2], + leftoverBeans: result[3], + activeFertilizer: result[4], + fertFirst: result[5], + fertLast: result[6], + bpf: result[7], + }; + }, [fertQuery.data]); + + const hasActiveFert = fertData ? !(fertData.activeFertilizer === 0n && fertData.fertFirst === 0n) : false; + + // Step 2: Scan event logs — cached via React Query (staleTime: 1 hour) + const idsQuery = useQuery({ + queryKey: ["beanstalk", "fertilizerIds", "eventScan"], + queryFn: () => fetchFertilizerIds(publicClient as PublicClient), + enabled: !!publicClient && !!fertData && hasActiveFert, + staleTime: 60 * 60 * 1000, // 1 hour — these IDs rarely change + gcTime: 60 * 60 * 1000, // keep in cache 1 hour after last use + refetchOnMount: false, + refetchOnWindowFocus: false, + }); + + const refetch = useCallback(async () => { + await fertQuery.refetch(); + await idsQuery.refetch(); + }, [fertQuery, idsQuery]); - useEffect(() => { - fetchIds(); - }, [fetchIds]); + // If no active fertilizer, return empty without error + const fertIds = !hasActiveFert ? [] : idsQuery.data ?? []; - return { fertilizerIds, isLoading, isError, refetch: fetchIds }; + return { + fertilizerIds: fertIds, + fertData, + isLoading: fertQuery.isLoading || (hasActiveFert && idsQuery.isLoading), + isError: fertQuery.isError || (hasActiveFert && idsQuery.isError), + refetch, + }; } diff --git a/src/pages/beanstalk/components/BeanstalkObligationsCard.tsx b/src/pages/beanstalk/components/BeanstalkObligationsCard.tsx index db9fe51f..7821540b 100644 --- a/src/pages/beanstalk/components/BeanstalkObligationsCard.tsx +++ b/src/pages/beanstalk/components/BeanstalkObligationsCard.tsx @@ -1,12 +1,10 @@ import { Button } from "@/components/ui/Button"; import { abiSnippets } from "@/constants/abiSnippets"; -import { SILO_PAYBACK_ADDRESS } from "@/constants/address"; -import { beanstalkAbi, beanstalkAddress } from "@/generated/contractHooks"; +import { BARN_PAYBACK_ADDRESS, SILO_PAYBACK_ADDRESS } from "@/constants/address"; import useTransaction from "@/hooks/useTransaction"; import { useFarmerBeanstalkRepayment } from "@/state/useFarmerBeanstalkRepayment"; import { useCallback } from "react"; import { useNavigate } from "react-router-dom"; -import { encodeFunctionData } from "viem"; import { useAccount } from "wagmi"; import BeanstalkFertilizerSection from "./BeanstalkFertilizerSection"; import BeanstalkPodsSection from "./BeanstalkPodsSection"; @@ -34,32 +32,26 @@ const BeanstalkObligationsCard: React.FC = () => { errorMessage: "Claim failed", }); - // Silo Claim — Claim earned urBDV from SiloPayback + // Silo Claim — Claim earned urBDV from SiloPayback contract directly const handleClaimSilo = useCallback(async () => { if (!account.address || silo.earned.isZero) return; try { setSubmitting(true); - // Encode claim(address recipient, enum LibTransfer.To toMode) call for SiloPayback contract - // toMode: 0 = INTERNAL (to internal balance), 1 = EXTERNAL (to wallet), 2 = INTERNAL_TOLERANT - const claimData = encodeFunctionData({ + // Call claim(address recipient, enum LibTransfer.To toMode) directly on SiloPayback contract + // toMode: 0 = INTERNAL, 1 = EXTERNAL (to wallet), 2 = INTERNAL_TOLERANT + await writeWithEstimateGas({ + address: SILO_PAYBACK_ADDRESS, abi: abiSnippets.siloPayback, functionName: "claim", args: [account.address, 1], // Claim to wallet (EXTERNAL) }); - - await writeWithEstimateGas({ - address: beanstalkAddress[account.chainId ?? 8453], - abi: beanstalkAbi, - functionName: "farm", - args: [[claimData]], - }); } catch (error) { console.error("Silo claim error:", error); setSubmitting(false); } - }, [account.address, account.chainId, silo.earned, writeWithEstimateGas, setSubmitting]); + }, [account.address, silo.earned, writeWithEstimateGas, setSubmitting]); // Pods Harvest — Navigate to field harvest page const handleHarvestPods = useCallback(() => { @@ -67,47 +59,31 @@ const BeanstalkObligationsCard: React.FC = () => { navigate("/field?action=harvest&fieldId=1"); }, [navigate]); - // Fertilizer Rinse — Rinse fertilized sprouts + // Fertilizer Rinse — Rinse fertilized sprouts from BarnPayback contract directly const handleRinseFert = useCallback(async () => { if (!account.address || fertilizer.fertilized.isZero) return; - // Check if we have fertilizer IDs if (!fertilizer.fertilizerIds || fertilizer.fertilizerIds.length === 0) { - console.warn( - "Cannot rinse fertilizer: No fertilizer IDs available. Fertilizer ID enumeration not yet implemented.", - ); + console.warn("Cannot rinse fertilizer: No fertilizer IDs available."); return; } try { setSubmitting(true); - // Encode claimFertilized(uint256[] ids, enum LibTransfer.To mode) call for BarnPayback contract - // mode: 0 = INTERNAL (to internal balance), 1 = EXTERNAL (to wallet), 2 = INTERNAL_TOLERANT - const claimFertilizedData = encodeFunctionData({ + // Call claimFertilized(uint256[] ids, uint8 mode) directly on BarnPayback contract + // mode: 0 = INTERNAL, 1 = EXTERNAL (to wallet), 2 = INTERNAL_TOLERANT + await writeWithEstimateGas({ + address: BARN_PAYBACK_ADDRESS, abi: abiSnippets.barnPayback, functionName: "claimFertilized", args: [fertilizer.fertilizerIds, 1], // Claim to wallet (EXTERNAL) }); - - await writeWithEstimateGas({ - address: beanstalkAddress[account.chainId ?? 8453], - abi: beanstalkAbi, - functionName: "farm", - args: [[claimFertilizedData]], - }); } catch (error) { console.error("Fertilizer rinse error:", error); setSubmitting(false); } - }, [ - account.address, - account.chainId, - fertilizer.fertilized, - fertilizer.fertilizerIds, - writeWithEstimateGas, - setSubmitting, - ]); + }, [account.address, fertilizer.fertilized, fertilizer.fertilizerIds, writeWithEstimateGas, setSubmitting]); const handleSendSilo = () => { navigate("/transfer/beanstalk-silo"); @@ -133,6 +109,7 @@ const BeanstalkObligationsCard: React.FC = () => {
{ void; @@ -20,6 +22,8 @@ interface BeanstalkPodsSectionProps { const BeanstalkPodsSection: React.FC = ({ plots, totalPods, + harvestableIndex, + podIndex, isLoading, disabled = false, onHarvest, @@ -43,7 +47,13 @@ const BeanstalkPodsSection: React.FC = ({ ) : (
- +
)} diff --git a/src/pages/beanstalk/components/BeanstalkSiloSection.tsx b/src/pages/beanstalk/components/BeanstalkSiloSection.tsx index 07b041c6..ac6415bd 100644 --- a/src/pages/beanstalk/components/BeanstalkSiloSection.tsx +++ b/src/pages/beanstalk/components/BeanstalkSiloSection.tsx @@ -1,9 +1,11 @@ import { TokenValue } from "@/classes/TokenValue"; import BeanstalkStatField from "@/components/BeanstalkStatField"; +import TextSkeleton from "@/components/TextSkeleton"; import { formatter } from "@/utils/format"; interface BeanstalkSiloSectionProps { balance: TokenValue; + earned: TokenValue; isLoading: boolean; disabled?: boolean; onClaim?: () => void; @@ -12,27 +14,46 @@ interface BeanstalkSiloSectionProps { /** * Section component displaying urBDV token balance for Silo Payback + * Shows earned (claimable) as primary value, total urBDV balance as secondary */ const BeanstalkSiloSection: React.FC = ({ balance, + earned, isLoading, disabled = false, onClaim, onSend, }) => { const hasBalance = !balance.isZero; + const hasEarned = !earned.isZero; return ( + > + + {disabled ? ( + N/A + ) : ( +
+
+ {formatter.number(earned, { minDecimals: 2, maxDecimals: 2 })} + earned +
+
+ {formatter.number(balance, { minDecimals: 2, maxDecimals: 2 })} urBDV total +
+
+ )} +
+
); }; diff --git a/src/pages/transfer/TransferActions.tsx b/src/pages/transfer/TransferActions.tsx index 54ad0a3a..fea40e4d 100644 --- a/src/pages/transfer/TransferActions.tsx +++ b/src/pages/transfer/TransferActions.tsx @@ -8,6 +8,7 @@ import { useFarmerField } from "@/state/useFarmerField"; import { useFarmerSilo } from "@/state/useFarmerSilo"; import { usePriceData } from "@/state/usePriceData"; import { formatter } from "@/utils/format"; +import { useMemo } from "react"; import { Link } from "react-router-dom"; export default function TransferActions() { @@ -18,6 +19,15 @@ export default function TransferActions() { const farmerField = useFarmerField(); const repayment = useFarmerBeanstalkRepayment(); + // Compute total bsFERT token count from per-ID balances + const totalBsFert = useMemo(() => { + let total = 0n; + for (const detail of repayment.fertilizer.perIdData.values()) { + total += detail.balance; + } + return total; + }, [repayment.fertilizer.perIdData]); + const totalInternalBalance = Array.from(farmerBalance.balances).reduce( (total: TokenValue, tokenBalance) => total.add( @@ -27,7 +37,13 @@ export default function TransferActions() { TokenValue.ZERO, ); - const disableSendAll = totalInternalBalance.eq(0) && farmerSilo.depositsUSD.eq(0) && farmerField.totalPods.eq(0); + const disableSendAll = + totalInternalBalance.eq(0) && + farmerSilo.depositsUSD.eq(0) && + farmerField.totalPods.eq(0) && + repayment.silo.balance.eq(0) && + repayment.pods.totalPods.eq(0) && + totalBsFert === 0n; return (
@@ -84,17 +100,17 @@ export default function TransferActions() { > Beanstalk Repayment Silo Tokens - {formatter.usd(repayment.silo.balance)} + {`${formatter.twoDec(repayment.silo.balance)} urBDV`} @@ -54,7 +55,9 @@ const BeanstalkStatField: React.FC = ({ children ) : ( -
{disabled ? N/A : value}
+
+ {disabled ? N/A : value} +
)}
diff --git a/src/components/ReadMoreAccordion.tsx b/src/components/ReadMoreAccordion.tsx index 4a804605..b2b046cd 100644 --- a/src/components/ReadMoreAccordion.tsx +++ b/src/components/ReadMoreAccordion.tsx @@ -1,5 +1,5 @@ import { cn } from "@/utils/utils"; -import { motion } from "framer-motion"; +import { AnimatePresence, motion } from "framer-motion"; import { useState } from "react"; import { Col } from "./Container"; @@ -26,20 +26,21 @@ export default function ReadMoreAccordion({ if (inline) { return ( - + {open && ( + + {children} + )} - > - {open && {children}} - + {open ? " Read less" : " Read more"} diff --git a/src/pages/Beanstalk.tsx b/src/pages/Beanstalk.tsx index 74b91a74..597bcb82 100644 --- a/src/pages/Beanstalk.tsx +++ b/src/pages/Beanstalk.tsx @@ -16,7 +16,9 @@ const Beanstalk = () => {
Beanstalk Obligations
Beanstalk Debt issued by Pinto. - +
+ + Beanstalk participants at the time of Pinto launch were issued assets based on their holdings. A portion of new Pinto mints go towards repaying these obligations across Beanstalk Silo Tokens, Pods, and Fertilizer.{" "} @@ -28,13 +30,13 @@ const Beanstalk = () => { > Learn more - -
+ +
{/* Main Cards - Two Column Layout */} -
+
{/* Left Panel - Obligations Card */} diff --git a/src/pages/Market.tsx b/src/pages/Market.tsx index 7bfbe64d..b9cecdc4 100644 --- a/src/pages/Market.tsx +++ b/src/pages/Market.tsx @@ -998,15 +998,17 @@ export function Market() { Buy and sell Pods on the open market.
- - The Pod Market is a decentralized marketplace where users can trade Pods, which are protocol-native debt - instruments that represent future Pinto tokens. When you buy Pods, you're essentially purchasing the right - to redeem them for Pinto tokens at a fixed rate when they become harvestable. The market operates on a - first-in-first-out (FIFO) basis, meaning the oldest Pods become harvestable first. You can place buy - orders to acquire Pods at a specific price, or create listings to sell your existing Pods to other users. - The scatter chart above visualizes all active orders and listings, showing their place in line and price - per Pod. This allows you to see market depth and make informed trading decisions based on current market - conditions and your investment strategy. + + + The Pod Market is a decentralized marketplace where users can trade Pods, which are protocol-native debt + instruments that represent future Pinto tokens. When you buy Pods, you're essentially purchasing the + right to redeem them for Pinto tokens at a fixed rate when they become harvestable. The market operates + on a first-in-first-out (FIFO) basis, meaning the oldest Pods become harvestable first. You can place + buy orders to acquire Pods at a specific price, or create listings to sell your existing Pods to other + users. The scatter chart above visualizes all active orders and listings, showing their place in line + and price per Pod. This allows you to see market depth and make informed trading decisions based on + current market conditions and your investment strategy. + diff --git a/src/pages/beanstalk/components/BeanstalkFertilizerSection.tsx b/src/pages/beanstalk/components/BeanstalkFertilizerSection.tsx index 6acad9ef..74ccbd88 100644 --- a/src/pages/beanstalk/components/BeanstalkFertilizerSection.tsx +++ b/src/pages/beanstalk/components/BeanstalkFertilizerSection.tsx @@ -1,8 +1,13 @@ +import { TokenValue } from "@/classes/TokenValue"; import BeanstalkStatField from "@/components/BeanstalkStatField"; +import TextSkeleton from "@/components/TextSkeleton"; import { formatter } from "@/utils/format"; interface BeanstalkFertilizerSectionProps { tokenCount: bigint; + fertilized: TokenValue; + unfertilized: TokenValue; + totalUnfertilizedSprouts: TokenValue; isLoading: boolean; disabled?: boolean; onRinse?: () => void; @@ -11,27 +16,52 @@ interface BeanstalkFertilizerSectionProps { /** * Section component displaying fertilizer token count (bsFERT ERC1155 balance) + * Shows unfertilized sprouts as primary value, fertilized (rinsable) as secondary */ const BeanstalkFertilizerSection: React.FC = ({ tokenCount, + fertilized, + unfertilized, + totalUnfertilizedSprouts, isLoading, disabled = false, onRinse, onSend, }) => { const hasBalance = tokenCount > 0n; + const hasRinsable = !fertilized.isZero; + + const sharePercent = totalUnfertilizedSprouts.gt(0) + ? ((unfertilized.toNumber() / totalUnfertilizedSprouts.toNumber()) * 100).toFixed(2) + : "0.00"; return ( + > + + {disabled ? ( + N/A + ) : ( +
+
+ {formatter.number(unfertilized, { minDecimals: 2, maxDecimals: 2 })} + Sprouts ({sharePercent}%) +
+
+ {formatter.number(fertilized, { minDecimals: 2, maxDecimals: 2 })} Fertilized +
+
+ )} +
+
); }; diff --git a/src/pages/beanstalk/components/BeanstalkObligationsCard.tsx b/src/pages/beanstalk/components/BeanstalkObligationsCard.tsx index cf6eb8d1..c596a240 100644 --- a/src/pages/beanstalk/components/BeanstalkObligationsCard.tsx +++ b/src/pages/beanstalk/components/BeanstalkObligationsCard.tsx @@ -3,6 +3,7 @@ import { abiSnippets } from "@/constants/abiSnippets"; import { BARN_PAYBACK_ADDRESS, SILO_PAYBACK_ADDRESS } from "@/constants/address"; import { beanstalkAbi, beanstalkAddress } from "@/generated/contractHooks"; import useTransaction from "@/hooks/useTransaction"; +import { useBeanstalkGlobalStats } from "@/state/useBeanstalkGlobalStats"; import { useFarmerBeanstalkRepayment } from "@/state/useFarmerBeanstalkRepayment"; import { useCallback, useMemo } from "react"; import { useNavigate } from "react-router-dom"; @@ -21,6 +22,7 @@ const BeanstalkObligationsCard: React.FC = () => { const chainId = useChainId(); const navigate = useNavigate(); const { silo, pods, fertilizer, isLoading, isError, refetch } = useFarmerBeanstalkRepayment(); + const globalStats = useBeanstalkGlobalStats(); const isConnected = !!account.address; const showDisabled = !isConnected || isError; @@ -119,6 +121,10 @@ const BeanstalkObligationsCard: React.FC = () => { navigate("/transfer/beanstalk-pods"); }; + const handleMarketPods = () => { + navigate("/market/pods/buy/fill"); + }; + const handleSendFertilizer = () => { navigate("/transfer/beanstalk-fertilizer"); }; @@ -136,6 +142,7 @@ const BeanstalkObligationsCard: React.FC = () => { { disabled={showDisabled} onHarvest={handleHarvestPods} onSend={handleSendPods} + onMarket={handleMarketPods} /> void; onSend?: () => void; + onMarket?: () => void; } /** @@ -28,6 +29,7 @@ const BeanstalkPodsSection: React.FC = ({ disabled = false, onHarvest, onSend, + onMarket, }) => { const hasPlots = plots.length > 0; const hasPods = !totalPods.isZero; @@ -41,6 +43,7 @@ const BeanstalkPodsSection: React.FC = ({ disabled={disabled} actions={[ { label: "Harvest", onClick: onHarvest, disabled: !hasHarvestablePods }, + { label: "Buy/Sell", onClick: onMarket }, { label: "Send", onClick: onSend, disabled: !hasPods }, ]} > @@ -50,7 +53,7 @@ const BeanstalkPodsSection: React.FC = ({
void; @@ -14,11 +15,12 @@ interface BeanstalkSiloSectionProps { /** * Section component displaying urBDV token balance for Silo Payback - * Shows earned (claimable) as primary value, total urBDV balance as secondary + * Shows total balance as primary value, earned (claimable) as secondary */ const BeanstalkSiloSection: React.FC = ({ balance, earned, + totalDistributed, isLoading, disabled = false, onClaim, @@ -27,6 +29,10 @@ const BeanstalkSiloSection: React.FC = ({ const hasBalance = !balance.isZero; const hasEarned = !earned.isZero; + const sharePercent = totalDistributed.gt(0) + ? ((balance.toNumber() / totalDistributed.toNumber()) * 100).toFixed(2) + : "0.00"; + return ( = ({ > {disabled ? ( - N/A + N/A ) : (
-
- {formatter.number(earned, { minDecimals: 2, maxDecimals: 2 })} - earned +
+ {formatter.number(balance, { minDecimals: 2, maxDecimals: 2 })} + + Beanstalk Silo Tokens ({sharePercent}%) +
-
- {formatter.number(balance, { minDecimals: 2, maxDecimals: 2 })} urBDV total +
+ {formatter.number(earned, { minDecimals: 2, maxDecimals: 2 })} Earned
)} diff --git a/src/pages/transfer/actions/TransferBeanstalkPods.tsx b/src/pages/transfer/actions/TransferBeanstalkPods.tsx index e5af15a9..cbd934c5 100644 --- a/src/pages/transfer/actions/TransferBeanstalkPods.tsx +++ b/src/pages/transfer/actions/TransferBeanstalkPods.tsx @@ -10,7 +10,6 @@ import { type Address, encodeFunctionData } from "viem"; import { useAccount, useChainId } from "wagmi"; import FinalStep from "./beanstalk-pods/FinalStep"; import StepOne from "./beanstalk-pods/StepOne"; -import StepTwo from "./beanstalk-pods/StepTwo"; export interface PodTransferData { id: TokenValue; @@ -37,9 +36,7 @@ export default function TransferBeanstalkPods() { const stepDescription = () => { switch (step) { case 1: - return "Select Plots"; - case 2: - return "Enter address"; + return "Select Beanstalk Plots"; default: return "Confirm send"; } @@ -48,9 +45,7 @@ export default function TransferBeanstalkPods() { const enableNextStep = () => { switch (step) { case 1: - return transferData.length > 0; - case 2: - return !!destination && transferNotice; + return transferData.length > 0 && !!destination && transferNotice; default: return true; } @@ -107,16 +102,16 @@ export default function TransferBeanstalkPods() { {step === 1 ? ( - - ) : step === 2 ? ( - { switch (step) { case 1: - return "Select Plots"; - case 2: - return "Enter address"; + return "Select Pinto Plots"; default: return "Confirm send"; } @@ -48,9 +45,7 @@ export default function TransferPods() { const enableNextStep = () => { switch (step) { case 1: - return transferData.length > 0; - case 2: - return !!destination && transferNotice; + return transferData.length > 0 && !!destination && transferNotice; default: return true; } @@ -115,16 +110,16 @@ export default function TransferPods() { {step === 1 ? ( - - ) : step === 2 ? ( - >; + destination: string | undefined; + setDestination: Dispatch>; + transferNotice: boolean; + setTransferNotice: Dispatch>; } function sortPlotsByIndex(plots: Plot[]): Plot[] { return [...plots].sort((a, b) => a.index.sub(b.index).toNumber()); } -export default function StepOne({ transferData, setTransferData }: StepOneProps) { +export default function StepOne({ + transferData, + setTransferData, + destination, + setDestination, + transferNotice, + setTransferNotice, +}: StepOneProps) { const repaymentPods = useFarmerBeanstalkRepayment().pods; const { plots, harvestableIndex, podIndex } = repaymentPods; @@ -121,15 +136,16 @@ export default function StepOne({ transferData, setTransferData }: StepOneProps) ); return ( -
+
+ @@ -173,6 +189,32 @@ export default function StepOne({ transferData, setTransferData }: StepOneProps)
)} + + + + + + {destination && ( + + + + )} + +
); } diff --git a/src/pages/transfer/actions/pods/StepOne.tsx b/src/pages/transfer/actions/pods/StepOne.tsx index 9568a1ce..17041383 100644 --- a/src/pages/transfer/actions/pods/StepOne.tsx +++ b/src/pages/transfer/actions/pods/StepOne.tsx @@ -1,23 +1,38 @@ +import AddressInputField from "@/components/AddressInputField"; +import PintoAssetTransferNotice from "@/components/PintoAssetTransferNotice"; import PodLineGraph from "@/components/PodLineGraph"; +import { Label } from "@/components/ui/Label"; import { MultiSlider } from "@/components/ui/Slider"; import { useFarmerField } from "@/state/useFarmerField"; import { useHarvestableIndex } from "@/state/useFieldData"; import { formatter } from "@/utils/format"; import { computeTransferData, offsetToAbsoluteIndex } from "@/utils/podTransferUtils"; import { Plot } from "@/utils/types"; +import { AnimatePresence, motion } from "framer-motion"; import { Dispatch, SetStateAction, useCallback, useEffect, useMemo, useRef, useState } from "react"; import { PodTransferData } from "../TransferPods"; interface StepOneProps { transferData: PodTransferData[]; setTransferData: Dispatch>; + destination: string | undefined; + setDestination: Dispatch>; + transferNotice: boolean; + setTransferNotice: Dispatch>; } function sortPlotsByIndex(plots: Plot[]): Plot[] { return [...plots].sort((a, b) => a.index.sub(b.index).toNumber()); } -export default function StepOne({ transferData, setTransferData }: StepOneProps) { +export default function StepOne({ + transferData, + setTransferData, + destination, + setDestination, + transferNotice, + setTransferNotice, +}: StepOneProps) { const { plots } = useFarmerField(); const harvestableIndex = useHarvestableIndex(); @@ -130,13 +145,14 @@ export default function StepOne({ transferData, setTransferData }: StepOneProps) ); return ( -
+
{/* Pod Line Graph Visualization */}
+ @@ -183,6 +199,32 @@ export default function StepOne({ transferData, setTransferData }: StepOneProps)
)} + + + + + + {destination && ( + + + + )} + +
); } diff --git a/src/state/useFarmerBeanstalkRepayment.ts b/src/state/useFarmerBeanstalkRepayment.ts index 9656c45f..9ef3ec35 100644 --- a/src/state/useFarmerBeanstalkRepayment.ts +++ b/src/state/useFarmerBeanstalkRepayment.ts @@ -3,6 +3,7 @@ import { abiSnippets } from "@/constants/abiSnippets"; import { BARN_PAYBACK_ADDRESS, SILO_PAYBACK_ADDRESS, ZERO_ADDRESS } from "@/constants/address"; import { BSFERT, PODS } from "@/constants/internalTokens"; import { defaultQuerySettings } from "@/constants/query"; +import { PINTO } from "@/constants/tokens"; import { beanstalkAbi } from "@/generated/contractHooks"; import { useProtocolAddress } from "@/hooks/pinto/useProtocolAddress"; import { useAllFertilizerIds } from "@/hooks/useAllFertilizerIds"; @@ -351,8 +352,8 @@ export function useFarmerBeanstalkRepayment(): FarmerBeanstalkRepaymentData { const fertilizedResult = fertilizerQuery.data?.[0]?.result; const unfertilizedResult = fertilizerQuery.data?.[1]?.result; - const fertilized = TokenValue.fromBlockchain(fertilizedResult ?? 0n, BSFERT.decimals); - const unfertilized = TokenValue.fromBlockchain(unfertilizedResult ?? 0n, BSFERT.decimals); + const fertilized = TokenValue.fromBlockchain(fertilizedResult ?? 0n, PINTO.decimals); + const unfertilized = TokenValue.fromBlockchain(unfertilizedResult ?? 0n, PINTO.decimals); // Total balance is the sum of fertilized + unfertilized const balance = fertilized.add(unfertilized); From c4b1b6f15c8d0701a44bc56a1ac476ece7d6c986 Mon Sep 17 00:00:00 2001 From: feyyazcigim Date: Mon, 16 Feb 2026 13:47:57 +0300 Subject: [PATCH 18/22] feat: integrate beanstalk marketplace toggle and field-specific logic --- src/context/BeanstalkMarketContext.tsx | 17 + src/encoders/createPodOrder.ts | 3 +- src/encoders/fillPodListing.ts | 3 +- src/generated/gql/pintostalk/gql.ts | 12 +- src/generated/gql/pintostalk/graphql.ts | 6 +- src/pages/Market.tsx | 404 ++++++++++-------- .../components/BeanstalkObligationsCard.tsx | 2 +- src/pages/market/MarketModeSelect.tsx | 22 +- src/pages/market/PodListingsTable.tsx | 4 +- src/pages/market/PodOrdersTable.tsx | 4 +- src/pages/market/actions/CancelListing.tsx | 6 +- src/pages/market/actions/CancelOrder.tsx | 6 +- src/pages/market/actions/CreateListing.tsx | 67 ++- src/pages/market/actions/CreateOrder.tsx | 14 +- src/pages/market/actions/FillListing.tsx | 33 +- src/pages/market/actions/FillOrder.tsx | 46 +- .../actions/beanstalk-pods/StepOne.tsx | 10 + .../podmarket/AllPodListings.graphql | 2 + .../beanstalk/podmarket/AllPodOrders.graphql | 3 +- src/state/market/usePodListings.ts | 13 +- src/state/market/usePodOrders.ts | 13 +- 21 files changed, 434 insertions(+), 256 deletions(-) create mode 100644 src/context/BeanstalkMarketContext.tsx diff --git a/src/context/BeanstalkMarketContext.tsx b/src/context/BeanstalkMarketContext.tsx new file mode 100644 index 00000000..c4fb08a5 --- /dev/null +++ b/src/context/BeanstalkMarketContext.tsx @@ -0,0 +1,17 @@ +import { createContext, useContext } from "react"; + +export interface BeanstalkMarketContextValue { + isBeanstalkMarketplace: boolean; + fieldId: bigint; + podMarketplaceId: string | undefined; +} + +export const BeanstalkMarketContext = createContext({ + isBeanstalkMarketplace: false, + fieldId: 0n, + podMarketplaceId: undefined, +}); + +export function useBeanstalkMarket() { + return useContext(BeanstalkMarketContext); +} diff --git a/src/encoders/createPodOrder.ts b/src/encoders/createPodOrder.ts index 15b52a97..ac1c4528 100644 --- a/src/encoders/createPodOrder.ts +++ b/src/encoders/createPodOrder.ts @@ -12,6 +12,7 @@ export default function createPodOrder( minFill?: TokenValue, balanceFrom?: FarmFromMode, clipboard?: `0x${string}`, + fieldId: bigint = 0n, ) { if (!amount || !pricePerPod || !maxPlaceInLine || !minFill || !balanceFrom) { return { @@ -26,7 +27,7 @@ export default function createPodOrder( args: [ { orderer: account, - fieldId: 0n, + fieldId: fieldId, pricePerPod, maxPlaceInLine: maxPlaceInLine.toBigInt(), minFillAmount: minFill.toBigInt(), diff --git a/src/encoders/fillPodListing.ts b/src/encoders/fillPodListing.ts index c5cb777a..59c075b4 100644 --- a/src/encoders/fillPodListing.ts +++ b/src/encoders/fillPodListing.ts @@ -16,6 +16,7 @@ export default function fillPodListing( amountIn?: TokenValue, // amountIn balanceFrom?: FarmFromMode, // fromMode clipboard?: `0x${string}`, + fieldId: bigint = 0n, // fieldId ) { if ( account === undefined || @@ -41,7 +42,7 @@ export default function fillPodListing( args: [ { lister: account, // account - fieldId: 0n, + fieldId: fieldId, index: index.toBigInt(), // index start: start.toBigInt(), // start podAmount: amount.toBigInt(), // amount diff --git a/src/generated/gql/pintostalk/gql.ts b/src/generated/gql/pintostalk/gql.ts index ec851937..f64db3c3 100644 --- a/src/generated/gql/pintostalk/gql.ts +++ b/src/generated/gql/pintostalk/gql.ts @@ -23,8 +23,8 @@ type Documents = { "query SiloSnapshots($first: Int!, $id: Bytes!) {\n siloHourlySnapshots(\n first: $first\n orderBy: season\n orderDirection: desc\n where: {silo_: {id: $id}}\n ) {\n beanToMaxLpGpPerBdvRatio\n deltaBeanMints\n season\n }\n}": typeof types.SiloSnapshotsDocument, "query SiloYields {\n siloYields(\n orderBy: season\n orderDirection: desc\n where: {emaWindow: ROLLING_30_DAY}\n first: 1\n ) {\n beansPerSeasonEMA\n beta\n createdAt\n season\n id\n u\n whitelistedTokens\n emaWindow\n tokenAPYS {\n beanAPY\n stalkAPY\n season\n createdAt\n token\n }\n }\n}": typeof types.SiloYieldsDocument, "query AllMarketActivity($first: Int = 1000, $listings_createdAt_gt: BigInt, $orders_createdAt_gt: BigInt, $fill_createdAt_gt: BigInt, $listings_podMarketplace: String, $orders_podMarketplace: String) {\n podListings(\n first: $first\n where: {createdAt_gt: $listings_createdAt_gt, status_not: FILLED_PARTIAL, podMarketplace: $listings_podMarketplace}\n ) {\n ...PodListing\n }\n podOrders(\n first: $first\n orderBy: createdAt\n orderDirection: desc\n where: {createdAt_gt: $orders_createdAt_gt, podMarketplace: $orders_podMarketplace}\n ) {\n ...PodOrder\n }\n podFills(first: $first, where: {createdAt_gt: $fill_createdAt_gt}) {\n ...PodFill\n }\n}": typeof types.AllMarketActivityDocument, - "query AllPodListings($first: Int = 1000, $status: MarketStatus = ACTIVE, $maxHarvestableIndex: BigInt!, $skip: Int = 0) {\n podListings(\n first: $first\n skip: $skip\n where: {status: $status, maxHarvestableIndex_gt: $maxHarvestableIndex, remainingAmount_gt: \"100000\"}\n orderBy: index\n orderDirection: asc\n ) {\n ...PodListing\n }\n}": typeof types.AllPodListingsDocument, - "query AllPodOrders($first: Int = 1000, $status: MarketStatus = ACTIVE, $skip: Int = 0) {\n podOrders(\n first: $first\n skip: $skip\n orderBy: createdAt\n orderDirection: desc\n where: {status: $status}\n ) {\n ...PodOrder\n }\n}": typeof types.AllPodOrdersDocument, + "query AllPodListings($first: Int = 1000, $status: MarketStatus = ACTIVE, $maxHarvestableIndex: BigInt!, $skip: Int = 0, $podMarketplace: String) {\n podListings(\n first: $first\n skip: $skip\n where: {status: $status, maxHarvestableIndex_gt: $maxHarvestableIndex, remainingAmount_gt: \"100000\", podMarketplace: $podMarketplace}\n orderBy: index\n orderDirection: asc\n ) {\n ...PodListing\n }\n}": typeof types.AllPodListingsDocument, + "query AllPodOrders($first: Int = 1000, $status: MarketStatus = ACTIVE, $skip: Int = 0, $podMarketplace: String) {\n podOrders(\n first: $first\n skip: $skip\n orderBy: createdAt\n orderDirection: desc\n where: {status: $status, podMarketplace: $podMarketplace}\n ) {\n ...PodOrder\n }\n}": typeof types.AllPodOrdersDocument, "query FarmerMarketActivity($first: Int = 1000, $account: String!, $listings_createdAt_gt: BigInt, $orders_createdAt_gt: BigInt, $fill_createdAt_gt: BigInt) {\n podListings(\n first: $first\n where: {farmer: $account, createdAt_gt: $listings_createdAt_gt, status_not: FILLED_PARTIAL}\n ) {\n ...PodListing\n }\n podOrders(\n first: $first\n orderBy: createdAt\n orderDirection: desc\n where: {farmer: $account, createdAt_gt: $orders_createdAt_gt}\n ) {\n ...PodOrder\n }\n podFills(\n first: $first\n where: {and: [{createdAt_gt: $fill_createdAt_gt}, {or: [{fromFarmer: $account}, {toFarmer: $account}]}]}\n ) {\n ...PodFill\n }\n}": typeof types.FarmerMarketActivityDocument, "fragment PodFill on PodFill {\n id\n placeInLine\n amount\n index\n start\n costInBeans\n fromFarmer {\n id\n }\n toFarmer {\n id\n }\n listing {\n id\n originalAmount\n }\n order {\n id\n beanAmount\n }\n createdAt\n}": typeof types.PodFillFragmentDoc, "fragment PodListing on PodListing {\n id\n farmer {\n id\n }\n historyID\n index\n start\n mode\n pricingType\n pricePerPod\n pricingFunction\n maxHarvestableIndex\n minFillAmount\n originalIndex\n originalPlaceInLine\n originalAmount\n filled\n amount\n remainingAmount\n filledAmount\n fill {\n placeInLine\n }\n status\n createdAt\n updatedAt\n creationHash\n}": typeof types.PodListingFragmentDoc, @@ -51,8 +51,8 @@ const documents: Documents = { "query SiloSnapshots($first: Int!, $id: Bytes!) {\n siloHourlySnapshots(\n first: $first\n orderBy: season\n orderDirection: desc\n where: {silo_: {id: $id}}\n ) {\n beanToMaxLpGpPerBdvRatio\n deltaBeanMints\n season\n }\n}": types.SiloSnapshotsDocument, "query SiloYields {\n siloYields(\n orderBy: season\n orderDirection: desc\n where: {emaWindow: ROLLING_30_DAY}\n first: 1\n ) {\n beansPerSeasonEMA\n beta\n createdAt\n season\n id\n u\n whitelistedTokens\n emaWindow\n tokenAPYS {\n beanAPY\n stalkAPY\n season\n createdAt\n token\n }\n }\n}": types.SiloYieldsDocument, "query AllMarketActivity($first: Int = 1000, $listings_createdAt_gt: BigInt, $orders_createdAt_gt: BigInt, $fill_createdAt_gt: BigInt, $listings_podMarketplace: String, $orders_podMarketplace: String) {\n podListings(\n first: $first\n where: {createdAt_gt: $listings_createdAt_gt, status_not: FILLED_PARTIAL, podMarketplace: $listings_podMarketplace}\n ) {\n ...PodListing\n }\n podOrders(\n first: $first\n orderBy: createdAt\n orderDirection: desc\n where: {createdAt_gt: $orders_createdAt_gt, podMarketplace: $orders_podMarketplace}\n ) {\n ...PodOrder\n }\n podFills(first: $first, where: {createdAt_gt: $fill_createdAt_gt}) {\n ...PodFill\n }\n}": types.AllMarketActivityDocument, - "query AllPodListings($first: Int = 1000, $status: MarketStatus = ACTIVE, $maxHarvestableIndex: BigInt!, $skip: Int = 0) {\n podListings(\n first: $first\n skip: $skip\n where: {status: $status, maxHarvestableIndex_gt: $maxHarvestableIndex, remainingAmount_gt: \"100000\"}\n orderBy: index\n orderDirection: asc\n ) {\n ...PodListing\n }\n}": types.AllPodListingsDocument, - "query AllPodOrders($first: Int = 1000, $status: MarketStatus = ACTIVE, $skip: Int = 0) {\n podOrders(\n first: $first\n skip: $skip\n orderBy: createdAt\n orderDirection: desc\n where: {status: $status}\n ) {\n ...PodOrder\n }\n}": types.AllPodOrdersDocument, + "query AllPodListings($first: Int = 1000, $status: MarketStatus = ACTIVE, $maxHarvestableIndex: BigInt!, $skip: Int = 0, $podMarketplace: String) {\n podListings(\n first: $first\n skip: $skip\n where: {status: $status, maxHarvestableIndex_gt: $maxHarvestableIndex, remainingAmount_gt: \"100000\", podMarketplace: $podMarketplace}\n orderBy: index\n orderDirection: asc\n ) {\n ...PodListing\n }\n}": types.AllPodListingsDocument, + "query AllPodOrders($first: Int = 1000, $status: MarketStatus = ACTIVE, $skip: Int = 0, $podMarketplace: String) {\n podOrders(\n first: $first\n skip: $skip\n orderBy: createdAt\n orderDirection: desc\n where: {status: $status, podMarketplace: $podMarketplace}\n ) {\n ...PodOrder\n }\n}": types.AllPodOrdersDocument, "query FarmerMarketActivity($first: Int = 1000, $account: String!, $listings_createdAt_gt: BigInt, $orders_createdAt_gt: BigInt, $fill_createdAt_gt: BigInt) {\n podListings(\n first: $first\n where: {farmer: $account, createdAt_gt: $listings_createdAt_gt, status_not: FILLED_PARTIAL}\n ) {\n ...PodListing\n }\n podOrders(\n first: $first\n orderBy: createdAt\n orderDirection: desc\n where: {farmer: $account, createdAt_gt: $orders_createdAt_gt}\n ) {\n ...PodOrder\n }\n podFills(\n first: $first\n where: {and: [{createdAt_gt: $fill_createdAt_gt}, {or: [{fromFarmer: $account}, {toFarmer: $account}]}]}\n ) {\n ...PodFill\n }\n}": types.FarmerMarketActivityDocument, "fragment PodFill on PodFill {\n id\n placeInLine\n amount\n index\n start\n costInBeans\n fromFarmer {\n id\n }\n toFarmer {\n id\n }\n listing {\n id\n originalAmount\n }\n order {\n id\n beanAmount\n }\n createdAt\n}": types.PodFillFragmentDoc, "fragment PodListing on PodListing {\n id\n farmer {\n id\n }\n historyID\n index\n start\n mode\n pricingType\n pricePerPod\n pricingFunction\n maxHarvestableIndex\n minFillAmount\n originalIndex\n originalPlaceInLine\n originalAmount\n filled\n amount\n remainingAmount\n filledAmount\n fill {\n placeInLine\n }\n status\n createdAt\n updatedAt\n creationHash\n}": types.PodListingFragmentDoc, @@ -123,11 +123,11 @@ export function graphql(source: "query AllMarketActivity($first: Int = 1000, $li /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "query AllPodListings($first: Int = 1000, $status: MarketStatus = ACTIVE, $maxHarvestableIndex: BigInt!, $skip: Int = 0) {\n podListings(\n first: $first\n skip: $skip\n where: {status: $status, maxHarvestableIndex_gt: $maxHarvestableIndex, remainingAmount_gt: \"100000\"}\n orderBy: index\n orderDirection: asc\n ) {\n ...PodListing\n }\n}"): (typeof documents)["query AllPodListings($first: Int = 1000, $status: MarketStatus = ACTIVE, $maxHarvestableIndex: BigInt!, $skip: Int = 0) {\n podListings(\n first: $first\n skip: $skip\n where: {status: $status, maxHarvestableIndex_gt: $maxHarvestableIndex, remainingAmount_gt: \"100000\"}\n orderBy: index\n orderDirection: asc\n ) {\n ...PodListing\n }\n}"]; +export function graphql(source: "query AllPodListings($first: Int = 1000, $status: MarketStatus = ACTIVE, $maxHarvestableIndex: BigInt!, $skip: Int = 0, $podMarketplace: String) {\n podListings(\n first: $first\n skip: $skip\n where: {status: $status, maxHarvestableIndex_gt: $maxHarvestableIndex, remainingAmount_gt: \"100000\", podMarketplace: $podMarketplace}\n orderBy: index\n orderDirection: asc\n ) {\n ...PodListing\n }\n}"): (typeof documents)["query AllPodListings($first: Int = 1000, $status: MarketStatus = ACTIVE, $maxHarvestableIndex: BigInt!, $skip: Int = 0, $podMarketplace: String) {\n podListings(\n first: $first\n skip: $skip\n where: {status: $status, maxHarvestableIndex_gt: $maxHarvestableIndex, remainingAmount_gt: \"100000\", podMarketplace: $podMarketplace}\n orderBy: index\n orderDirection: asc\n ) {\n ...PodListing\n }\n}"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "query AllPodOrders($first: Int = 1000, $status: MarketStatus = ACTIVE, $skip: Int = 0) {\n podOrders(\n first: $first\n skip: $skip\n orderBy: createdAt\n orderDirection: desc\n where: {status: $status}\n ) {\n ...PodOrder\n }\n}"): (typeof documents)["query AllPodOrders($first: Int = 1000, $status: MarketStatus = ACTIVE, $skip: Int = 0) {\n podOrders(\n first: $first\n skip: $skip\n orderBy: createdAt\n orderDirection: desc\n where: {status: $status}\n ) {\n ...PodOrder\n }\n}"]; +export function graphql(source: "query AllPodOrders($first: Int = 1000, $status: MarketStatus = ACTIVE, $skip: Int = 0, $podMarketplace: String) {\n podOrders(\n first: $first\n skip: $skip\n orderBy: createdAt\n orderDirection: desc\n where: {status: $status, podMarketplace: $podMarketplace}\n ) {\n ...PodOrder\n }\n}"): (typeof documents)["query AllPodOrders($first: Int = 1000, $status: MarketStatus = ACTIVE, $skip: Int = 0, $podMarketplace: String) {\n podOrders(\n first: $first\n skip: $skip\n orderBy: createdAt\n orderDirection: desc\n where: {status: $status, podMarketplace: $podMarketplace}\n ) {\n ...PodOrder\n }\n}"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ diff --git a/src/generated/gql/pintostalk/graphql.ts b/src/generated/gql/pintostalk/graphql.ts index 98e8681f..1ff4fd75 100644 --- a/src/generated/gql/pintostalk/graphql.ts +++ b/src/generated/gql/pintostalk/graphql.ts @@ -14119,6 +14119,7 @@ export type AllPodListingsQueryVariables = Exact<{ status?: InputMaybe; maxHarvestableIndex: Scalars['BigInt']['input']; skip?: InputMaybe; + podMarketplace?: InputMaybe; }>; @@ -14128,6 +14129,7 @@ export type AllPodOrdersQueryVariables = Exact<{ first?: InputMaybe; status?: InputMaybe; skip?: InputMaybe; + podMarketplace?: InputMaybe; }>; @@ -14254,8 +14256,8 @@ export const BeanstalkSeasonsTableDocument = {"kind":"Document","definitions":[{ export const SiloSnapshotsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"SiloSnapshots"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"first"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Bytes"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"siloHourlySnapshots"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"first"},"value":{"kind":"Variable","name":{"kind":"Name","value":"first"}}},{"kind":"Argument","name":{"kind":"Name","value":"orderBy"},"value":{"kind":"EnumValue","value":"season"}},{"kind":"Argument","name":{"kind":"Name","value":"orderDirection"},"value":{"kind":"EnumValue","value":"desc"}},{"kind":"Argument","name":{"kind":"Name","value":"where"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"silo_"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}]}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"beanToMaxLpGpPerBdvRatio"}},{"kind":"Field","name":{"kind":"Name","value":"deltaBeanMints"}},{"kind":"Field","name":{"kind":"Name","value":"season"}}]}}]}}]} as unknown as DocumentNode; export const SiloYieldsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"SiloYields"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"siloYields"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"orderBy"},"value":{"kind":"EnumValue","value":"season"}},{"kind":"Argument","name":{"kind":"Name","value":"orderDirection"},"value":{"kind":"EnumValue","value":"desc"}},{"kind":"Argument","name":{"kind":"Name","value":"where"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"emaWindow"},"value":{"kind":"EnumValue","value":"ROLLING_30_DAY"}}]}},{"kind":"Argument","name":{"kind":"Name","value":"first"},"value":{"kind":"IntValue","value":"1"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"beansPerSeasonEMA"}},{"kind":"Field","name":{"kind":"Name","value":"beta"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"season"}},{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"u"}},{"kind":"Field","name":{"kind":"Name","value":"whitelistedTokens"}},{"kind":"Field","name":{"kind":"Name","value":"emaWindow"}},{"kind":"Field","name":{"kind":"Name","value":"tokenAPYS"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"beanAPY"}},{"kind":"Field","name":{"kind":"Name","value":"stalkAPY"}},{"kind":"Field","name":{"kind":"Name","value":"season"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"token"}}]}}]}}]}}]} as unknown as DocumentNode; export const AllMarketActivityDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"AllMarketActivity"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"first"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}},"defaultValue":{"kind":"IntValue","value":"1000"}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"listings_createdAt_gt"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"BigInt"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"orders_createdAt_gt"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"BigInt"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"fill_createdAt_gt"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"BigInt"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"listings_podMarketplace"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"orders_podMarketplace"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"podListings"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"first"},"value":{"kind":"Variable","name":{"kind":"Name","value":"first"}}},{"kind":"Argument","name":{"kind":"Name","value":"where"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"createdAt_gt"},"value":{"kind":"Variable","name":{"kind":"Name","value":"listings_createdAt_gt"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"status_not"},"value":{"kind":"EnumValue","value":"FILLED_PARTIAL"}},{"kind":"ObjectField","name":{"kind":"Name","value":"podMarketplace"},"value":{"kind":"Variable","name":{"kind":"Name","value":"listings_podMarketplace"}}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"PodListing"}}]}},{"kind":"Field","name":{"kind":"Name","value":"podOrders"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"first"},"value":{"kind":"Variable","name":{"kind":"Name","value":"first"}}},{"kind":"Argument","name":{"kind":"Name","value":"orderBy"},"value":{"kind":"EnumValue","value":"createdAt"}},{"kind":"Argument","name":{"kind":"Name","value":"orderDirection"},"value":{"kind":"EnumValue","value":"desc"}},{"kind":"Argument","name":{"kind":"Name","value":"where"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"createdAt_gt"},"value":{"kind":"Variable","name":{"kind":"Name","value":"orders_createdAt_gt"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"podMarketplace"},"value":{"kind":"Variable","name":{"kind":"Name","value":"orders_podMarketplace"}}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"PodOrder"}}]}},{"kind":"Field","name":{"kind":"Name","value":"podFills"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"first"},"value":{"kind":"Variable","name":{"kind":"Name","value":"first"}}},{"kind":"Argument","name":{"kind":"Name","value":"where"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"createdAt_gt"},"value":{"kind":"Variable","name":{"kind":"Name","value":"fill_createdAt_gt"}}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"PodFill"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PodListing"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"PodListing"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"farmer"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}},{"kind":"Field","name":{"kind":"Name","value":"historyID"}},{"kind":"Field","name":{"kind":"Name","value":"index"}},{"kind":"Field","name":{"kind":"Name","value":"start"}},{"kind":"Field","name":{"kind":"Name","value":"mode"}},{"kind":"Field","name":{"kind":"Name","value":"pricingType"}},{"kind":"Field","name":{"kind":"Name","value":"pricePerPod"}},{"kind":"Field","name":{"kind":"Name","value":"pricingFunction"}},{"kind":"Field","name":{"kind":"Name","value":"maxHarvestableIndex"}},{"kind":"Field","name":{"kind":"Name","value":"minFillAmount"}},{"kind":"Field","name":{"kind":"Name","value":"originalIndex"}},{"kind":"Field","name":{"kind":"Name","value":"originalPlaceInLine"}},{"kind":"Field","name":{"kind":"Name","value":"originalAmount"}},{"kind":"Field","name":{"kind":"Name","value":"filled"}},{"kind":"Field","name":{"kind":"Name","value":"amount"}},{"kind":"Field","name":{"kind":"Name","value":"remainingAmount"}},{"kind":"Field","name":{"kind":"Name","value":"filledAmount"}},{"kind":"Field","name":{"kind":"Name","value":"fill"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"placeInLine"}}]}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"creationHash"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PodOrder"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"PodOrder"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"farmer"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}},{"kind":"Field","name":{"kind":"Name","value":"historyID"}},{"kind":"Field","name":{"kind":"Name","value":"pricingType"}},{"kind":"Field","name":{"kind":"Name","value":"pricePerPod"}},{"kind":"Field","name":{"kind":"Name","value":"pricingFunction"}},{"kind":"Field","name":{"kind":"Name","value":"maxPlaceInLine"}},{"kind":"Field","name":{"kind":"Name","value":"minFillAmount"}},{"kind":"Field","name":{"kind":"Name","value":"beanAmount"}},{"kind":"Field","name":{"kind":"Name","value":"podAmountFilled"}},{"kind":"Field","name":{"kind":"Name","value":"beanAmountFilled"}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"creationHash"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PodFill"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"PodFill"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"placeInLine"}},{"kind":"Field","name":{"kind":"Name","value":"amount"}},{"kind":"Field","name":{"kind":"Name","value":"index"}},{"kind":"Field","name":{"kind":"Name","value":"start"}},{"kind":"Field","name":{"kind":"Name","value":"costInBeans"}},{"kind":"Field","name":{"kind":"Name","value":"fromFarmer"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}},{"kind":"Field","name":{"kind":"Name","value":"toFarmer"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}},{"kind":"Field","name":{"kind":"Name","value":"listing"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"originalAmount"}}]}},{"kind":"Field","name":{"kind":"Name","value":"order"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"beanAmount"}}]}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}}]} as unknown as DocumentNode; -export const AllPodListingsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"AllPodListings"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"first"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}},"defaultValue":{"kind":"IntValue","value":"1000"}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"status"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"MarketStatus"}},"defaultValue":{"kind":"EnumValue","value":"ACTIVE"}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"maxHarvestableIndex"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"BigInt"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"skip"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}},"defaultValue":{"kind":"IntValue","value":"0"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"podListings"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"first"},"value":{"kind":"Variable","name":{"kind":"Name","value":"first"}}},{"kind":"Argument","name":{"kind":"Name","value":"skip"},"value":{"kind":"Variable","name":{"kind":"Name","value":"skip"}}},{"kind":"Argument","name":{"kind":"Name","value":"where"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"status"},"value":{"kind":"Variable","name":{"kind":"Name","value":"status"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"maxHarvestableIndex_gt"},"value":{"kind":"Variable","name":{"kind":"Name","value":"maxHarvestableIndex"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"remainingAmount_gt"},"value":{"kind":"StringValue","value":"100000","block":false}}]}},{"kind":"Argument","name":{"kind":"Name","value":"orderBy"},"value":{"kind":"EnumValue","value":"index"}},{"kind":"Argument","name":{"kind":"Name","value":"orderDirection"},"value":{"kind":"EnumValue","value":"asc"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"PodListing"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PodListing"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"PodListing"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"farmer"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}},{"kind":"Field","name":{"kind":"Name","value":"historyID"}},{"kind":"Field","name":{"kind":"Name","value":"index"}},{"kind":"Field","name":{"kind":"Name","value":"start"}},{"kind":"Field","name":{"kind":"Name","value":"mode"}},{"kind":"Field","name":{"kind":"Name","value":"pricingType"}},{"kind":"Field","name":{"kind":"Name","value":"pricePerPod"}},{"kind":"Field","name":{"kind":"Name","value":"pricingFunction"}},{"kind":"Field","name":{"kind":"Name","value":"maxHarvestableIndex"}},{"kind":"Field","name":{"kind":"Name","value":"minFillAmount"}},{"kind":"Field","name":{"kind":"Name","value":"originalIndex"}},{"kind":"Field","name":{"kind":"Name","value":"originalPlaceInLine"}},{"kind":"Field","name":{"kind":"Name","value":"originalAmount"}},{"kind":"Field","name":{"kind":"Name","value":"filled"}},{"kind":"Field","name":{"kind":"Name","value":"amount"}},{"kind":"Field","name":{"kind":"Name","value":"remainingAmount"}},{"kind":"Field","name":{"kind":"Name","value":"filledAmount"}},{"kind":"Field","name":{"kind":"Name","value":"fill"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"placeInLine"}}]}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"creationHash"}}]}}]} as unknown as DocumentNode; -export const AllPodOrdersDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"AllPodOrders"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"first"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}},"defaultValue":{"kind":"IntValue","value":"1000"}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"status"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"MarketStatus"}},"defaultValue":{"kind":"EnumValue","value":"ACTIVE"}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"skip"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}},"defaultValue":{"kind":"IntValue","value":"0"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"podOrders"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"first"},"value":{"kind":"Variable","name":{"kind":"Name","value":"first"}}},{"kind":"Argument","name":{"kind":"Name","value":"skip"},"value":{"kind":"Variable","name":{"kind":"Name","value":"skip"}}},{"kind":"Argument","name":{"kind":"Name","value":"orderBy"},"value":{"kind":"EnumValue","value":"createdAt"}},{"kind":"Argument","name":{"kind":"Name","value":"orderDirection"},"value":{"kind":"EnumValue","value":"desc"}},{"kind":"Argument","name":{"kind":"Name","value":"where"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"status"},"value":{"kind":"Variable","name":{"kind":"Name","value":"status"}}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"PodOrder"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PodOrder"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"PodOrder"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"farmer"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}},{"kind":"Field","name":{"kind":"Name","value":"historyID"}},{"kind":"Field","name":{"kind":"Name","value":"pricingType"}},{"kind":"Field","name":{"kind":"Name","value":"pricePerPod"}},{"kind":"Field","name":{"kind":"Name","value":"pricingFunction"}},{"kind":"Field","name":{"kind":"Name","value":"maxPlaceInLine"}},{"kind":"Field","name":{"kind":"Name","value":"minFillAmount"}},{"kind":"Field","name":{"kind":"Name","value":"beanAmount"}},{"kind":"Field","name":{"kind":"Name","value":"podAmountFilled"}},{"kind":"Field","name":{"kind":"Name","value":"beanAmountFilled"}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"creationHash"}}]}}]} as unknown as DocumentNode; +export const AllPodListingsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"AllPodListings"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"first"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}},"defaultValue":{"kind":"IntValue","value":"1000"}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"status"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"MarketStatus"}},"defaultValue":{"kind":"EnumValue","value":"ACTIVE"}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"maxHarvestableIndex"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"BigInt"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"skip"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}},"defaultValue":{"kind":"IntValue","value":"0"}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"podMarketplace"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"podListings"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"first"},"value":{"kind":"Variable","name":{"kind":"Name","value":"first"}}},{"kind":"Argument","name":{"kind":"Name","value":"skip"},"value":{"kind":"Variable","name":{"kind":"Name","value":"skip"}}},{"kind":"Argument","name":{"kind":"Name","value":"where"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"status"},"value":{"kind":"Variable","name":{"kind":"Name","value":"status"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"maxHarvestableIndex_gt"},"value":{"kind":"Variable","name":{"kind":"Name","value":"maxHarvestableIndex"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"remainingAmount_gt"},"value":{"kind":"StringValue","value":"100000","block":false}},{"kind":"ObjectField","name":{"kind":"Name","value":"podMarketplace"},"value":{"kind":"Variable","name":{"kind":"Name","value":"podMarketplace"}}}]}},{"kind":"Argument","name":{"kind":"Name","value":"orderBy"},"value":{"kind":"EnumValue","value":"index"}},{"kind":"Argument","name":{"kind":"Name","value":"orderDirection"},"value":{"kind":"EnumValue","value":"asc"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"PodListing"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PodListing"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"PodListing"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"farmer"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}},{"kind":"Field","name":{"kind":"Name","value":"historyID"}},{"kind":"Field","name":{"kind":"Name","value":"index"}},{"kind":"Field","name":{"kind":"Name","value":"start"}},{"kind":"Field","name":{"kind":"Name","value":"mode"}},{"kind":"Field","name":{"kind":"Name","value":"pricingType"}},{"kind":"Field","name":{"kind":"Name","value":"pricePerPod"}},{"kind":"Field","name":{"kind":"Name","value":"pricingFunction"}},{"kind":"Field","name":{"kind":"Name","value":"maxHarvestableIndex"}},{"kind":"Field","name":{"kind":"Name","value":"minFillAmount"}},{"kind":"Field","name":{"kind":"Name","value":"originalIndex"}},{"kind":"Field","name":{"kind":"Name","value":"originalPlaceInLine"}},{"kind":"Field","name":{"kind":"Name","value":"originalAmount"}},{"kind":"Field","name":{"kind":"Name","value":"filled"}},{"kind":"Field","name":{"kind":"Name","value":"amount"}},{"kind":"Field","name":{"kind":"Name","value":"remainingAmount"}},{"kind":"Field","name":{"kind":"Name","value":"filledAmount"}},{"kind":"Field","name":{"kind":"Name","value":"fill"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"placeInLine"}}]}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"creationHash"}}]}}]} as unknown as DocumentNode; +export const AllPodOrdersDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"AllPodOrders"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"first"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}},"defaultValue":{"kind":"IntValue","value":"1000"}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"status"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"MarketStatus"}},"defaultValue":{"kind":"EnumValue","value":"ACTIVE"}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"skip"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}},"defaultValue":{"kind":"IntValue","value":"0"}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"podMarketplace"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"podOrders"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"first"},"value":{"kind":"Variable","name":{"kind":"Name","value":"first"}}},{"kind":"Argument","name":{"kind":"Name","value":"skip"},"value":{"kind":"Variable","name":{"kind":"Name","value":"skip"}}},{"kind":"Argument","name":{"kind":"Name","value":"orderBy"},"value":{"kind":"EnumValue","value":"createdAt"}},{"kind":"Argument","name":{"kind":"Name","value":"orderDirection"},"value":{"kind":"EnumValue","value":"desc"}},{"kind":"Argument","name":{"kind":"Name","value":"where"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"status"},"value":{"kind":"Variable","name":{"kind":"Name","value":"status"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"podMarketplace"},"value":{"kind":"Variable","name":{"kind":"Name","value":"podMarketplace"}}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"PodOrder"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PodOrder"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"PodOrder"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"farmer"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}},{"kind":"Field","name":{"kind":"Name","value":"historyID"}},{"kind":"Field","name":{"kind":"Name","value":"pricingType"}},{"kind":"Field","name":{"kind":"Name","value":"pricePerPod"}},{"kind":"Field","name":{"kind":"Name","value":"pricingFunction"}},{"kind":"Field","name":{"kind":"Name","value":"maxPlaceInLine"}},{"kind":"Field","name":{"kind":"Name","value":"minFillAmount"}},{"kind":"Field","name":{"kind":"Name","value":"beanAmount"}},{"kind":"Field","name":{"kind":"Name","value":"podAmountFilled"}},{"kind":"Field","name":{"kind":"Name","value":"beanAmountFilled"}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"creationHash"}}]}}]} as unknown as DocumentNode; export const FarmerMarketActivityDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"FarmerMarketActivity"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"first"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}},"defaultValue":{"kind":"IntValue","value":"1000"}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"account"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"listings_createdAt_gt"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"BigInt"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"orders_createdAt_gt"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"BigInt"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"fill_createdAt_gt"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"BigInt"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"podListings"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"first"},"value":{"kind":"Variable","name":{"kind":"Name","value":"first"}}},{"kind":"Argument","name":{"kind":"Name","value":"where"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"farmer"},"value":{"kind":"Variable","name":{"kind":"Name","value":"account"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"createdAt_gt"},"value":{"kind":"Variable","name":{"kind":"Name","value":"listings_createdAt_gt"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"status_not"},"value":{"kind":"EnumValue","value":"FILLED_PARTIAL"}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"PodListing"}}]}},{"kind":"Field","name":{"kind":"Name","value":"podOrders"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"first"},"value":{"kind":"Variable","name":{"kind":"Name","value":"first"}}},{"kind":"Argument","name":{"kind":"Name","value":"orderBy"},"value":{"kind":"EnumValue","value":"createdAt"}},{"kind":"Argument","name":{"kind":"Name","value":"orderDirection"},"value":{"kind":"EnumValue","value":"desc"}},{"kind":"Argument","name":{"kind":"Name","value":"where"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"farmer"},"value":{"kind":"Variable","name":{"kind":"Name","value":"account"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"createdAt_gt"},"value":{"kind":"Variable","name":{"kind":"Name","value":"orders_createdAt_gt"}}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"PodOrder"}}]}},{"kind":"Field","name":{"kind":"Name","value":"podFills"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"first"},"value":{"kind":"Variable","name":{"kind":"Name","value":"first"}}},{"kind":"Argument","name":{"kind":"Name","value":"where"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"and"},"value":{"kind":"ListValue","values":[{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"createdAt_gt"},"value":{"kind":"Variable","name":{"kind":"Name","value":"fill_createdAt_gt"}}}]},{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"or"},"value":{"kind":"ListValue","values":[{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"fromFarmer"},"value":{"kind":"Variable","name":{"kind":"Name","value":"account"}}}]},{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"toFarmer"},"value":{"kind":"Variable","name":{"kind":"Name","value":"account"}}}]}]}}]}]}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"PodFill"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PodListing"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"PodListing"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"farmer"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}},{"kind":"Field","name":{"kind":"Name","value":"historyID"}},{"kind":"Field","name":{"kind":"Name","value":"index"}},{"kind":"Field","name":{"kind":"Name","value":"start"}},{"kind":"Field","name":{"kind":"Name","value":"mode"}},{"kind":"Field","name":{"kind":"Name","value":"pricingType"}},{"kind":"Field","name":{"kind":"Name","value":"pricePerPod"}},{"kind":"Field","name":{"kind":"Name","value":"pricingFunction"}},{"kind":"Field","name":{"kind":"Name","value":"maxHarvestableIndex"}},{"kind":"Field","name":{"kind":"Name","value":"minFillAmount"}},{"kind":"Field","name":{"kind":"Name","value":"originalIndex"}},{"kind":"Field","name":{"kind":"Name","value":"originalPlaceInLine"}},{"kind":"Field","name":{"kind":"Name","value":"originalAmount"}},{"kind":"Field","name":{"kind":"Name","value":"filled"}},{"kind":"Field","name":{"kind":"Name","value":"amount"}},{"kind":"Field","name":{"kind":"Name","value":"remainingAmount"}},{"kind":"Field","name":{"kind":"Name","value":"filledAmount"}},{"kind":"Field","name":{"kind":"Name","value":"fill"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"placeInLine"}}]}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"creationHash"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PodOrder"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"PodOrder"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"farmer"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}},{"kind":"Field","name":{"kind":"Name","value":"historyID"}},{"kind":"Field","name":{"kind":"Name","value":"pricingType"}},{"kind":"Field","name":{"kind":"Name","value":"pricePerPod"}},{"kind":"Field","name":{"kind":"Name","value":"pricingFunction"}},{"kind":"Field","name":{"kind":"Name","value":"maxPlaceInLine"}},{"kind":"Field","name":{"kind":"Name","value":"minFillAmount"}},{"kind":"Field","name":{"kind":"Name","value":"beanAmount"}},{"kind":"Field","name":{"kind":"Name","value":"podAmountFilled"}},{"kind":"Field","name":{"kind":"Name","value":"beanAmountFilled"}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"creationHash"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PodFill"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"PodFill"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"placeInLine"}},{"kind":"Field","name":{"kind":"Name","value":"amount"}},{"kind":"Field","name":{"kind":"Name","value":"index"}},{"kind":"Field","name":{"kind":"Name","value":"start"}},{"kind":"Field","name":{"kind":"Name","value":"costInBeans"}},{"kind":"Field","name":{"kind":"Name","value":"fromFarmer"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}},{"kind":"Field","name":{"kind":"Name","value":"toFarmer"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}},{"kind":"Field","name":{"kind":"Name","value":"listing"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"originalAmount"}}]}},{"kind":"Field","name":{"kind":"Name","value":"order"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"beanAmount"}}]}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}}]} as unknown as DocumentNode; export const FarmerReferralDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"FarmerReferral"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"farmer"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"totalReferralRewardPodsReceived"}},{"kind":"Field","name":{"kind":"Name","value":"refereeCount"}}]}}]}}]} as unknown as DocumentNode; export const ReferralLeaderboardDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"ReferralLeaderboard"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"first"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"skip"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"block"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Block_height"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"farmers"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"first"},"value":{"kind":"Variable","name":{"kind":"Name","value":"first"}}},{"kind":"Argument","name":{"kind":"Name","value":"skip"},"value":{"kind":"Variable","name":{"kind":"Name","value":"skip"}}},{"kind":"Argument","name":{"kind":"Name","value":"orderBy"},"value":{"kind":"EnumValue","value":"totalReferralRewardPodsReceived"}},{"kind":"Argument","name":{"kind":"Name","value":"orderDirection"},"value":{"kind":"EnumValue","value":"desc"}},{"kind":"Argument","name":{"kind":"Name","value":"where"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"totalReferralRewardPodsReceived_gt"},"value":{"kind":"StringValue","value":"0","block":false}}]}},{"kind":"Argument","name":{"kind":"Name","value":"block"},"value":{"kind":"Variable","name":{"kind":"Name","value":"block"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"refereeCount"}},{"kind":"Field","name":{"kind":"Name","value":"totalReferralRewardPodsReceived"}}]}}]}}]} as unknown as DocumentNode; diff --git a/src/pages/Market.tsx b/src/pages/Market.tsx index b9cecdc4..ec2a9c29 100644 --- a/src/pages/Market.tsx +++ b/src/pages/Market.tsx @@ -12,8 +12,10 @@ import { Card } from "@/components/ui/Card"; import { Separator } from "@/components/ui/Separator"; import { Switch } from "@/components/ui/Switch"; import { ANALYTICS_EVENTS } from "@/constants/analytics-events"; +import { BeanstalkMarketContext } from "@/context/BeanstalkMarketContext"; import useNavHeight from "@/hooks/display/useNavHeight"; import { useAllMarket } from "@/state/market/useAllMarket"; +import { useFarmerBeanstalkRepayment } from "@/state/useFarmerBeanstalkRepayment"; import { useHarvestableIndex, usePodLine } from "@/state/useFieldData"; import { trackSimpleEvent } from "@/utils/analytics"; import { calculatePodScore } from "@/utils/podScore"; @@ -22,7 +24,7 @@ import { exists } from "@/utils/utils"; import { ActiveElement, ChartEvent, PointStyle, TooltipOptions } from "chart.js"; import { Chart } from "chart.js"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; -import { useNavigate, useParams } from "react-router-dom"; +import { useNavigate, useParams, useSearchParams } from "react-router-dom"; import { AllActivityTable } from "./market/AllActivityTable"; import { FarmerActivityTable } from "./market/FarmerActivityTable"; import MarketModeSelect from "./market/MarketModeSelect"; @@ -214,14 +216,50 @@ export function Market() { const hoverInfoRef = useRef(null); const lastPositionSideRef = useRef<{ isRight: boolean; isAbove: boolean } | null>(null); const navigate = useNavigate(); - const [isBeanstalkMarketplace, setIsBeanstalkMarketplace] = useState(false); + const [searchParams, setSearchParams] = useSearchParams(); + const isBeanstalkMarketplace = searchParams.get("beanstalk") === "true"; + const fieldId = isBeanstalkMarketplace ? 1n : 0n; const podMarketplaceId = isBeanstalkMarketplace ? "1" : undefined; + + const handleToggleBeanstalk = useCallback( + (checked: boolean) => { + setSearchParams( + (prev) => { + const next = new URLSearchParams(prev); + if (checked) { + next.set("beanstalk", "true"); + } else { + next.delete("beanstalk"); + } + return next; + }, + { replace: true }, + ); + }, + [setSearchParams], + ); + const buildMarketPath = useCallback( + (path: string) => { + if (isBeanstalkMarketplace) { + return `${path}?beanstalk=true`; + } + return path; + }, + [isBeanstalkMarketplace], + ); + const { data, isLoaded } = useAllMarket(podMarketplaceId); const podLine = usePodLine(); - const podLineAsNumber = podLine.toNumber() / MILLION; + const harvestableIndex = useHarvestableIndex(); + const repayment = useFarmerBeanstalkRepayment(); + + // Toggle durumuna göre pod line ve harvestable index seçimi + const activePodLine = isBeanstalkMarketplace ? repayment.pods.podIndex.sub(repayment.pods.harvestableIndex) : podLine; + const activeHarvestableIndex = isBeanstalkMarketplace ? repayment.pods.harvestableIndex : harvestableIndex; + + const podLineAsNumber = activePodLine.toNumber() / MILLION; // Chart rounds X max to nearest 10 (not ceil), so we need to match that for validation const chartXMax = Math.round((podLineAsNumber / 10) * 10); - const harvestableIndex = useHarvestableIndex(); const navHeight = useNavHeight(); const [mounted, setMounted] = useState(false); @@ -260,7 +298,7 @@ export function Market() { if (!selectedPlotData) return []; return selectedPlotData.listingData.map((data) => { - const placeInLine = data.index.sub(harvestableIndex).toNumber(); + const placeInLine = data.index.sub(activeHarvestableIndex).toNumber(); const placeInLineMillions = placeInLine / MILLION; const podScore = calculatePodScore(selectedPlotData.pricePerPod, placeInLineMillions); @@ -276,11 +314,11 @@ export function Market() { }; }); }, - [harvestableIndex], + [activeHarvestableIndex], ); const scatterChartData: MarketScatterChartData[] = useMemo(() => { - const baseData = shapeScatterChartData(data || [], harvestableIndex); + const baseData = shapeScatterChartData(data || [], activeHarvestableIndex); // Add selected plots dataset if available if (!selectedPlotData || selectedPlotData.listingData.length === 0 || selectedPlotData.pricePerPod <= 0) { @@ -320,7 +358,7 @@ export function Market() { }; return [...baseData, selectedPlotsDataset]; - }, [data, harvestableIndex, selectedPlotData, transformSelectedPlotsToChartPoints]); + }, [data, activeHarvestableIndex, selectedPlotData, transformSelectedPlotsToChartPoints]); // Calculate highlighted event IDs (filtered by FillListing parameters) const highlightedEventIds = useMemo(() => { @@ -522,13 +560,13 @@ export function Market() { useEffect(() => { if (!mode) { // No mode specified (e.g. /market/pods), redirect to buy/fill - navigate("/market/pods/buy/fill", { replace: true }); + navigate(buildMarketPath("/market/pods/buy/fill"), { replace: true }); } else if (mode === "buy" && !id) { - navigate("/market/pods/buy/fill", { replace: true }); + navigate(buildMarketPath("/market/pods/buy/fill"), { replace: true }); } else if (mode === "sell" && !id) { - navigate("/market/pods/sell/create", { replace: true }); + navigate(buildMarketPath("/market/pods/sell/create"), { replace: true }); } - }, [id, mode, navigate]); + }, [id, mode, navigate, buildMarketPath]); // Clear preview plots and unfreeze chart when route changes away from create listing // Also unfreeze when switching between listing/selling pages @@ -602,14 +640,14 @@ export function Market() { if (selection === TABLE_SLUGS[1]) { setIsNavigating(true); - navigate(`/market/pods/buy/fill`); + navigate(buildMarketPath(`/market/pods/buy/fill`)); } else if (selection === TABLE_SLUGS[2]) { setIsNavigating(true); - navigate(`/market/pods/sell/fill`); + navigate(buildMarketPath(`/market/pods/sell/fill`)); } handleChangeTab(selection); }, - [navigate, tab], + [navigate, tab, buildMarketPath], ); const handleSecondaryTabClick = useCallback( @@ -782,7 +820,7 @@ export function Market() { setIsCrosshairFrozen(false); setContextMenu(null); setIsContextMenuClosing(false); - navigate(path, { state }); + navigate(buildMarketPath(path), { state }); // Reset flag after navigation setTimeout(() => { @@ -790,7 +828,7 @@ export function Market() { }, 100); }, 200); // Match fade-out animation duration }, - [navigate], + [navigate, buildMarketPath], ); const contextMenuOptions = useMemo(() => { @@ -863,7 +901,7 @@ export function Market() { // Switch to buy mode and fill action if (mode !== "buy" || id !== "fill") { setIsNavigating(true); - navigate("/market/pods/buy/fill"); + navigate(buildMarketPath("/market/pods/buy/fill")); } handleChangeTab(TABLE_SLUGS[1]); // Switch to listings tab } else { @@ -874,7 +912,7 @@ export function Market() { // Switch to sell mode and fill action if (mode !== "sell" || id !== "fill") { setIsNavigating(true); - navigate("/market/pods/sell/fill"); + navigate(buildMarketPath("/market/pods/sell/fill")); } handleChangeTab(TABLE_SLUGS[2]); // Switch to orders tab } @@ -908,7 +946,7 @@ export function Market() { // Switch to buy mode and fill action if (mode !== "buy" || id !== "fill") { setIsNavigating(true); - navigate("/market/pods/buy/fill"); + navigate(buildMarketPath("/market/pods/buy/fill")); } handleChangeTab(TABLE_SLUGS[1]); // Switch to listings tab } else { @@ -919,7 +957,7 @@ export function Market() { // Switch to sell mode and fill action if (mode !== "sell" || id !== "fill") { setIsNavigating(true); - navigate("/market/pods/sell/fill"); + navigate(buildMarketPath("/market/pods/sell/fill")); } handleChangeTab(TABLE_SLUGS[2]); // Switch to orders tab } @@ -970,172 +1008,200 @@ export function Market() { }); // Navigate to CreateListing with plot indices (not full Plot objects to avoid serialization issues) - navigate("/market/pods/sell/create", { + navigate(buildMarketPath("/market/pods/sell/create"), { state: { selectedPlotIndices: plotIndices }, }); }, - [navigate], + [navigate, buildMarketPath], ); // Default to buy/fill when no mode is selected const viewMode = mode || "buy"; const viewAction = id || (viewMode === "buy" ? "fill" : "create"); + const contextValue = useMemo( + () => ({ + isBeanstalkMarketplace, + fieldId, + podMarketplaceId, + }), + [isBeanstalkMarketplace, fieldId, podMarketplaceId], + ); + return ( - <> -
-

Your screen size is too small to access the Pod Market.

-

- If you're on Desktop, zoom out on your browser to access the Pod Market. -

-
-
-
- -
-
Market
-
- Buy and sell Pods on the open market. -
-
- - - The Pod Market is a decentralized marketplace where users can trade Pods, which are protocol-native debt - instruments that represent future Pinto tokens. When you buy Pods, you're essentially purchasing the - right to redeem them for Pinto tokens at a fixed rate when they become harvestable. The market operates - on a first-in-first-out (FIFO) basis, meaning the oldest Pods become harvestable first. You can place - buy orders to acquire Pods at a specific price, or create listings to sell your existing Pods to other - users. The scatter chart above visualizes all active orders and listings, showing their place in line - and price per Pod. This allows you to see market depth and make informed trading decisions based on - current market conditions and your investment strategy. - - - - -
-
-
- Toggle Beanstalk Marketplace - + + <> +
+

Your screen size is too small to access the Pod Market.

+

+ If you're on Desktop, zoom out on your browser to access the Pod Market. +

+
+
+
+ +
+
Market
+
+ Buy and sell Pods on the open market. +
-
- {!isLoaded && ( -
- + + + The Pod Market is a decentralized marketplace where users can trade Pods, which are protocol-native + debt instruments that represent future Pinto tokens. When you buy Pods, you're essentially purchasing + the right to redeem them for Pinto tokens at a fixed rate when they become harvestable. The market + operates on a first-in-first-out (FIFO) basis, meaning the oldest Pods become harvestable first. You + can place buy orders to acquire Pods at a specific price, or create listings to sell your existing + Pods to other users. The scatter chart above visualizes all active orders and listings, showing their + place in line and price per Pod. This allows you to see market depth and make informed trading + decisions based on current market conditions and your investment strategy. + + + + +
+
+
+ Toggle Beanstalk Marketplace + +
+
+ {!isLoaded && ( +
+ +
+ )} + + + {/* Gradient Legend - positioned in top-right corner */} +
+
- )} - - - {/* Gradient Legend - positioned in top-right corner */} -
- +
+
+ +
+
+ {TABLE_SLUGS.map((s, idx) => ( +

+ {TABLE_LABELS[idx]} +

+ ))} +
+ +
+ {tab === TABLE_SLUGS[0] && } + {tab === TABLE_SLUGS[1] && } + {tab === TABLE_SLUGS[2] && } + {tab === TABLE_SLUGS[3] && }
-
- -
-
- {TABLE_SLUGS.map((s, idx) => ( -

- {TABLE_LABELS[idx]} -

- ))} -
- -
- {tab === TABLE_SLUGS[0] && } - {tab === TABLE_SLUGS[1] && } - {tab === TABLE_SLUGS[2] && } - {tab === TABLE_SLUGS[3] && } -
-
-
- -
- -
- {viewMode === "buy" && viewAction === "create" && } - {viewMode === "buy" && viewAction === "fill" && ( - - )} - {viewMode === "sell" && viewAction === "create" && ( - - )} - {viewMode === "sell" && viewAction === "fill" && ( - - )} +
+ +
+ +
+ {viewMode === "buy" && viewAction === "create" && ( + + )} + {viewMode === "buy" && viewAction === "fill" && ( + + )} + {viewMode === "sell" && viewAction === "create" && ( + + )} + {viewMode === "sell" && viewAction === "fill" && ( + + )} +
-
- + +
-
- - {/* Hover info - rendered once, updated via direct DOM manipulation for performance */} -
-
- Price per Pod: 0.000 -
-
- Place in line: 0.0M + + {/* Hover info - rendered once, updated via direct DOM manipulation for performance */} +
+
+ Price per Pod: 0.000 +
+
+ Place in line: 0.0M +
-
- - {contextMenu && ( - { - // Only unfreeze if NOT navigating (closed via Escape/click outside/scroll) - // If navigating, handleUnfreezeAndNavigate already handles unfreeze - if (!isNavigatingRef.current) { - // Trigger closing animation - setIsContextMenuClosing(true); - - // Wait for animation to complete before unfreezing - setTimeout(() => { - if (isCrosshairFrozen) { - chartRef.current?.unfreeze(); - setIsCrosshairFrozen(false); - } - setContextMenu(null); - setIsContextMenuClosing(false); - }, 200); // Match fade-out animation duration - } - }} - /> - )} - + + {contextMenu && ( + { + // Only unfreeze if NOT navigating (closed via Escape/click outside/scroll) + // If navigating, handleUnfreezeAndNavigate already handles unfreeze + if (!isNavigatingRef.current) { + // Trigger closing animation + setIsContextMenuClosing(true); + + // Wait for animation to complete before unfreezing + setTimeout(() => { + if (isCrosshairFrozen) { + chartRef.current?.unfreeze(); + setIsCrosshairFrozen(false); + } + setContextMenu(null); + setIsContextMenuClosing(false); + }, 200); // Match fade-out animation duration + } + }} + /> + )} + + ); } diff --git a/src/pages/beanstalk/components/BeanstalkObligationsCard.tsx b/src/pages/beanstalk/components/BeanstalkObligationsCard.tsx index c596a240..112b8e82 100644 --- a/src/pages/beanstalk/components/BeanstalkObligationsCard.tsx +++ b/src/pages/beanstalk/components/BeanstalkObligationsCard.tsx @@ -122,7 +122,7 @@ const BeanstalkObligationsCard: React.FC = () => { }; const handleMarketPods = () => { - navigate("/market/pods/buy/fill"); + navigate("/market/pods/buy/fill?beanstalk=true"); }; const handleSendFertilizer = () => { diff --git a/src/pages/market/MarketModeSelect.tsx b/src/pages/market/MarketModeSelect.tsx index fd03cd50..3e255630 100644 --- a/src/pages/market/MarketModeSelect.tsx +++ b/src/pages/market/MarketModeSelect.tsx @@ -3,7 +3,7 @@ import { Tabs, TabsList, TabsTrigger } from "@/components/ui/Tabs"; import { ANALYTICS_EVENTS } from "@/constants/analytics-events"; import { trackSimpleEvent } from "@/utils/analytics"; import { useCallback, useMemo } from "react"; -import { useNavigate, useParams } from "react-router-dom"; +import { useNavigate, useParams, useSearchParams } from "react-router-dom"; type MarketMode = "buy" | "sell"; type MarketAction = "create" | "fill"; @@ -34,6 +34,16 @@ const ACTION_LABELS: Record> = { export default function MarketModeSelect({ onMainSelectionChange, onSecondarySelectionChange }: MarketModeSelectProps) { const { mode, id } = useParams(); const navigate = useNavigate(); + const [searchParams] = useSearchParams(); + + // Helper to preserve current search params (e.g. ?beanstalk=true) during navigation + const buildPath = useCallback( + (path: string) => { + const params = searchParams.toString(); + return params ? `${path}?${params}` : path; + }, + [searchParams], + ); // Derive current state from URL params const { mainTab, secondaryTab, secondaryTabValue } = useMemo(() => { @@ -62,10 +72,10 @@ export default function MarketModeSelect({ onMainSelectionChange, onSecondarySel secondary_tab: secondaryTab, }); - navigate(`/market/pods/${newMode}/${defaultAction}`); + navigate(buildPath(`/market/pods/${newMode}/${defaultAction}`)); onMainSelectionChange?.(v); }, - [navigate, onMainSelectionChange, mainTab, secondaryTab], + [navigate, onMainSelectionChange, mainTab, secondaryTab, buildPath], ); const handleSecondaryChange = useCallback( @@ -79,13 +89,13 @@ export default function MarketModeSelect({ onMainSelectionChange, onSecondarySel }); if (v === "create") { - navigate(`/market/pods/${currentMode}/create`); + navigate(buildPath(`/market/pods/${currentMode}/create`)); } else if (v === "fill") { - navigate(`/market/pods/${currentMode}/fill`); + navigate(buildPath(`/market/pods/${currentMode}/fill`)); } onSecondarySelectionChange?.(v); }, - [mainTab, navigate, onSecondarySelectionChange, secondaryTab], + [mainTab, navigate, onSecondarySelectionChange, secondaryTab, buildPath], ); return ( diff --git a/src/pages/market/PodListingsTable.tsx b/src/pages/market/PodListingsTable.tsx index 35d750e2..7e68f948 100644 --- a/src/pages/market/PodListingsTable.tsx +++ b/src/pages/market/PodListingsTable.tsx @@ -7,6 +7,7 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/com import IconImage from "@/components/ui/IconImage"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/Table"; import { PODS } from "@/constants/internalTokens"; +import { useBeanstalkMarket } from "@/context/BeanstalkMarketContext"; import usePodListings from "@/state/market/usePodListings"; import { useHarvestableIndex } from "@/state/useFieldData"; import useTokenData from "@/state/useTokenData"; @@ -17,8 +18,9 @@ import { useNavigate, useParams } from "react-router-dom"; export function PodListingsTable() { const { id: selectedListing } = useParams(); const BEAN = useTokenData().mainToken; + const { podMarketplaceId } = useBeanstalkMarket(); - const podListingsQuery = usePodListings(); + const podListingsQuery = usePodListings(podMarketplaceId); const podListings = podListingsQuery.data?.podListings; const harvestableIndex = useHarvestableIndex(); diff --git a/src/pages/market/PodOrdersTable.tsx b/src/pages/market/PodOrdersTable.tsx index 8022c737..c0a95398 100644 --- a/src/pages/market/PodOrdersTable.tsx +++ b/src/pages/market/PodOrdersTable.tsx @@ -7,6 +7,7 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/com import IconImage from "@/components/ui/IconImage"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/Table"; import { PODS } from "@/constants/internalTokens"; +import { useBeanstalkMarket } from "@/context/BeanstalkMarketContext"; import usePodOrders from "@/state/market/usePodOrders"; import useTokenData from "@/state/useTokenData"; import { formatter } from "@/utils/format"; @@ -16,7 +17,8 @@ import { useNavigate, useParams } from "react-router-dom"; export function PodOrdersTable() { const { id: selectedOrder } = useParams(); const BEAN = useTokenData().mainToken; - const podOrdersQuery = usePodOrders(); + const { podMarketplaceId } = useBeanstalkMarket(); + const podOrdersQuery = usePodOrders(podMarketplaceId); const podOrders = podOrdersQuery.data?.podOrders; const filteredOrders: NonNullable["data"]>["podOrders"] = []; diff --git a/src/pages/market/actions/CancelListing.tsx b/src/pages/market/actions/CancelListing.tsx index 032308f9..4af2a753 100644 --- a/src/pages/market/actions/CancelListing.tsx +++ b/src/pages/market/actions/CancelListing.tsx @@ -1,6 +1,7 @@ import { TokenValue } from "@/classes/TokenValue"; import SmartSubmitButton from "@/components/SmartSubmitButton"; import { PODS } from "@/constants/internalTokens"; +import { useBeanstalkMarket } from "@/context/BeanstalkMarketContext"; import { beanstalkAbi } from "@/generated/contractHooks"; import { AllPodListingsQuery } from "@/generated/gql/pintostalk/graphql"; import { useProtocolAddress } from "@/hooks/pinto/useProtocolAddress"; @@ -21,6 +22,7 @@ export default function CancelListing({ listing }: CancelListingProps) { const diamondAddress = useProtocolAddress(); const account = useAccount(); const harvestableIndex = useHarvestableIndex(); + const { fieldId } = useBeanstalkMarket(); const navigate = useNavigate(); const queryClient = useQueryClient(); @@ -50,7 +52,7 @@ export default function CancelListing({ listing }: CancelListingProps) { abi: beanstalkAbi, functionName: "cancelPodListing", args: [ - 0n, // fieldId + fieldId, // fieldId TokenValue.fromBlockchain(listing.index, PODS.decimals).toBigInt(), // index ], }); @@ -62,7 +64,7 @@ export default function CancelListing({ listing }: CancelListingProps) { } finally { setSubmitting(false); } - }, [listing, diamondAddress, setSubmitting, writeWithEstimateGas]); + }, [listing, diamondAddress, fieldId, setSubmitting, writeWithEstimateGas]); return ( <> diff --git a/src/pages/market/actions/CancelOrder.tsx b/src/pages/market/actions/CancelOrder.tsx index 65f80fd8..98aa5b56 100644 --- a/src/pages/market/actions/CancelOrder.tsx +++ b/src/pages/market/actions/CancelOrder.tsx @@ -4,6 +4,7 @@ import FarmBalanceToggle from "@/components/FarmBalanceToggle"; import SmartSubmitButton from "@/components/SmartSubmitButton"; import { Separator } from "@/components/ui/Separator"; import { PODS } from "@/constants/internalTokens"; +import { useBeanstalkMarket } from "@/context/BeanstalkMarketContext"; import { beanstalkAbi } from "@/generated/contractHooks"; import { AllPodOrdersQuery } from "@/generated/gql/pintostalk/graphql"; import { useProtocolAddress } from "@/hooks/pinto/useProtocolAddress"; @@ -29,6 +30,7 @@ export default function CancelOrder({ order }: CancelOrderProps) { const diamondAddress = useProtocolAddress(); const { queryKeys: balanceQKs } = useFarmerBalances(); const account = useAccount(); + const { fieldId } = useBeanstalkMarket(); const navigate = useNavigate(); const [mode, toFarm, setMode] = useFarmTogglePreference(); @@ -71,7 +73,7 @@ export default function CancelOrder({ order }: CancelOrderProps) { args: [ { orderer: account.address, // account - fieldId: 0n, // fieldId + fieldId: fieldId, // fieldId pricePerPod, // pricePerPod maxPlaceInLine, // maxPlaceInLine minFillAmount, // minFillAmount @@ -87,7 +89,7 @@ export default function CancelOrder({ order }: CancelOrderProps) { } finally { setSubmitting(false); } - }, [order, diamondAddress, account, toFarm, mainToken, setSubmitting, writeWithEstimateGas]); + }, [order, diamondAddress, account, fieldId, toFarm, mainToken, setSubmitting, writeWithEstimateGas]); return ( <> diff --git a/src/pages/market/actions/CreateListing.tsx b/src/pages/market/actions/CreateListing.tsx index 5cb237f8..dbd1fbdf 100644 --- a/src/pages/market/actions/CreateListing.tsx +++ b/src/pages/market/actions/CreateListing.tsx @@ -11,9 +11,11 @@ import { MultiSlider, Slider } from "@/components/ui/Slider"; import { Switch } from "@/components/ui/Switch"; import { ANALYTICS_EVENTS } from "@/constants/analytics-events"; import { PODS } from "@/constants/internalTokens"; +import { useBeanstalkMarket } from "@/context/BeanstalkMarketContext"; import { beanstalkAbi } from "@/generated/contractHooks"; import { useProtocolAddress } from "@/hooks/pinto/useProtocolAddress"; import useTransaction from "@/hooks/useTransaction"; +import { useFarmerBeanstalkRepayment } from "@/state/useFarmerBeanstalkRepayment"; import { useFarmerField } from "@/state/useFarmerField"; import { useHarvestableIndex, usePodIndex } from "@/state/useFieldData"; import { useQueryKeys } from "@/state/useQueryKeys"; @@ -89,12 +91,24 @@ export default function CreateListing({ onSelectionChange }: CreateListingProps const navigate = useNavigate(); const location = useLocation(); const farmerField = useFarmerField(); + const { isBeanstalkMarketplace, fieldId } = useBeanstalkMarket(); + const repayment = useFarmerBeanstalkRepayment(); const queryClient = useQueryClient(); const { allPodListings, allMarket, farmerMarket } = useQueryKeys({ account, harvestableIndex }); const allQK = useMemo(() => [allPodListings, allMarket, farmerMarket], [allPodListings, allMarket, farmerMarket]); - const userPlots = useMemo(() => farmerField?.plots || [], [farmerField?.plots]); + const userPlots = useMemo(() => { + if (isBeanstalkMarketplace) { + return repayment.pods.plots; + } + return farmerField?.plots || []; + }, [isBeanstalkMarketplace, farmerField?.plots, repayment.pods.plots]); + + const activeHarvestableIndex = useMemo( + () => (isBeanstalkMarketplace ? repayment.pods.harvestableIndex : harvestableIndex), + [isBeanstalkMarketplace, repayment.pods.harvestableIndex, harvestableIndex], + ); const [plot, setPlot] = useState([]); const [amount, setAmount] = useState(0); @@ -107,13 +121,18 @@ export default function CreateListing({ onSelectionChange }: CreateListingProps const [successAmount, setSuccessAmount] = useState(null); const [successPrice, setSuccessPrice] = useState(null); const podIndex = usePodIndex(); - const maxExpiration = Number.parseInt(podIndex.toHuman()) - Number.parseInt(harvestableIndex.toHuman()) || 0; + const activePodIndex = useMemo( + () => (isBeanstalkMarketplace ? repayment.pods.podIndex : podIndex), + [isBeanstalkMarketplace, repayment.pods.podIndex, podIndex], + ); + const maxExpiration = + Number.parseInt(activePodIndex.toHuman()) - Number.parseInt(activeHarvestableIndex.toHuman()) || 0; const [expiresIn, setExpiresIn] = useState(null); const selectedExpiresIn = expiresIn ?? maxExpiration; const minFill = TokenValue.fromHuman(0.1, PODS.decimals); const [showAdvancedSettings, setShowAdvancedSettings] = useState(false); - const plotPosition = plot.length > 0 ? plot[0].index.sub(harvestableIndex) : TV.ZERO; + const plotPosition = plot.length > 0 ? plot[0].index.sub(activeHarvestableIndex) : TV.ZERO; // Helper: Find nearest plot within 10% tolerance (for context menu prefill) const findNearestPlot = useCallback( @@ -125,7 +144,7 @@ export default function CreateListing({ onSelectionChange }: CreateListingProps let minDistance = Infinity; for (const p of userPlots) { - const plotPos = Number(p.index.sub(harvestableIndex).toBigInt()); + const plotPos = Number(p.index.sub(activeHarvestableIndex).toBigInt()); const distance = Math.abs(plotPos - targetPosition); const tolerance = targetPosition * 0.1; // 10% tolerance @@ -137,19 +156,19 @@ export default function CreateListing({ onSelectionChange }: CreateListingProps return nearestPlot; }, - [userPlots, harvestableIndex], + [userPlots, activeHarvestableIndex], ); // Calculate max pods based on selected plots OR all farmer plots const maxPodAmount = useMemo(() => { - const plotsToUse = plot.length > 0 ? plot : farmerField.plots; + const plotsToUse = plot.length > 0 ? plot : userPlots; if (plotsToUse.length === 0) return 0; return plotsToUse.reduce((sum, p) => sum + p.pods.toNumber(), 0); - }, [plot, farmerField.plots]); + }, [plot, userPlots]); // Calculate position range in line const positionInfo = useMemo(() => { - const plotsToUse = plot.length > 0 ? plot : farmerField.plots; + const plotsToUse = plot.length > 0 ? plot : userPlots; if (plotsToUse.length === 0) return null; const minIndex = plotsToUse.reduce((min, p) => (p.index.lt(min) ? p.index : min), plotsToUse[0].index); @@ -159,10 +178,10 @@ export default function CreateListing({ onSelectionChange }: CreateListingProps }, plotsToUse[0].index); return { - start: minIndex.sub(harvestableIndex), - end: maxIndex.sub(harvestableIndex), + start: minIndex.sub(activeHarvestableIndex), + end: maxIndex.sub(activeHarvestableIndex), }; - }, [plot, farmerField.plots, harvestableIndex]); + }, [plot, userPlots, activeHarvestableIndex]); // Calculate selected pod range for PodLineGraph partial selection const selectedPodRange = useMemo(() => { @@ -243,7 +262,7 @@ export default function CreateListing({ onSelectionChange }: CreateListingProps const scores = listingData .map((data) => { - const placeInLine = data.index.sub(harvestableIndex).toNumber(); + const placeInLine = data.index.sub(activeHarvestableIndex).toNumber(); // Use placeInLine in millions for consistent scaling return calculatePodScore(pricePerPod, placeInLine / MILLION); }) @@ -255,7 +274,7 @@ export default function CreateListing({ onSelectionChange }: CreateListingProps const max = Math.max(...scores); return { min, max, isSingle: scores.length === 1 || min === max }; - }, [listingData, pricePerPod, harvestableIndex]); + }, [listingData, pricePerPod, activeHarvestableIndex]); // Notify parent component of selection changes useEffect(() => { @@ -324,7 +343,7 @@ export default function CreateListing({ onSelectionChange }: CreateListingProps } // Find matching plots from farmer's field using string comparison - const validPlots = farmerField.plots.filter((p) => selectedPlotIndices.includes(p.index.toHuman())); + const validPlots = userPlots.filter((p) => selectedPlotIndices.includes(p.index.toHuman())); if (validPlots.length > 0) { // Mark this selection as processed @@ -348,7 +367,7 @@ export default function CreateListing({ onSelectionChange }: CreateListingProps // Clean up location state to prevent re-selection on re-mount window.history.replaceState({}, document.title); } - }, [location.state, farmerField.plots, sortPlotsByIndex]); + }, [location.state, userPlots, sortPlotsByIndex]); // Prefill from context menu click - always update when new values arrive useEffect(() => { @@ -402,7 +421,7 @@ export default function CreateListing({ onSelectionChange }: CreateListingProps prefillExpiresIn, maxExpiration, findNearestPlot, - harvestableIndex, + activeHarvestableIndex, navigate, location.pathname, sortPlotsByIndex, @@ -468,7 +487,7 @@ export default function CreateListing({ onSelectionChange }: CreateListingProps const handlePlotGroupSelect = useCallback( (plotIndices: string[]) => { - const plotsInGroup = farmerField.plots.filter((p) => plotIndices.includes(p.index.toHuman())); + const plotsInGroup = userPlots.filter((p) => plotIndices.includes(p.index.toHuman())); if (plotsInGroup.length === 0) return; const allSelected = plotIndices.every((index) => plot.some((p) => p.index.toHuman() === index)); @@ -491,7 +510,7 @@ export default function CreateListing({ onSelectionChange }: CreateListingProps handlePlotSelection(newPlots); } }, - [farmerField.plots, plot, handlePlotSelection], + [userPlots, plot, handlePlotSelection], ); // reset form and invalidate pod listing query @@ -538,7 +557,7 @@ export default function CreateListing({ onSelectionChange }: CreateListingProps // pricePerPod should be encoded as uint24 with 6 decimals (0.5 * 1_000_000 = 500000) const encodedPricePerPod = pricePerPod ? Math.floor(pricePerPod * PRICE_PER_POD_CONFIG.DECIMAL_MULTIPLIER) : 0; const _expiresIn = TokenValue.fromHuman(selectedExpiresIn, PODS.decimals); - const maxHarvestableIndex = _expiresIn.add(harvestableIndex); + const maxHarvestableIndex = _expiresIn.add(activeHarvestableIndex); try { setSubmitting(true); toast.loading(`Creating ${listingData.length} Listing${listingData.length > 1 ? "s" : ""}...`); @@ -549,7 +568,7 @@ export default function CreateListing({ onSelectionChange }: CreateListingProps for (const data of listingData) { const listingArgs = { lister: account, - fieldId: 0n, + fieldId: fieldId, index: data.index.toBigInt(), start: data.start.toBigInt(), podAmount: data.amount.toBigInt(), @@ -588,7 +607,8 @@ export default function CreateListing({ onSelectionChange }: CreateListingProps pricePerPod, selectedExpiresIn, balanceTo, - harvestableIndex, + activeHarvestableIndex, + fieldId, minFill, plot, listingData, @@ -615,6 +635,9 @@ export default function CreateListing({ onSelectionChange }: CreateListingProps selectedPodRange={selectedPodRange} label="My Pods In Line" onPlotGroupSelect={handlePlotGroupSelect} + plots={userPlots} + customHarvestableIndex={activeHarvestableIndex} + customPodIndex={activePodIndex} /> {/* Position in Line Display (below graph) */} @@ -757,7 +780,7 @@ export default function CreateListing({ onSelectionChange }: CreateListingProps podAmount={amount} listingData={listingData} pricePerPod={pricePerPod} - harvestableIndex={harvestableIndex} + harvestableIndex={activeHarvestableIndex} /> )} (undefined); const [pricePerPod, setPricePerPod] = useState(PRICE_PER_POD_CONFIG.MIN); @@ -388,6 +394,7 @@ export default function CreateOrder() { minFill, fromMode, orderClipboard?.clipboard, + fieldId, ); advFarm.push(orderCallStruct); @@ -427,6 +434,7 @@ export default function CreateOrder() { tokenIn.symbol, podsOut, amountIn, + fieldId, ]); const swapDataNotReady = (shouldSwap && (!swapData || !swapBuild)) || !!swapQuery.error; @@ -438,8 +446,8 @@ export default function CreateOrder() { // Calculate orderRangeEnd for PodLineGraph overlay const orderRangeEnd = useMemo(() => { if (!maxPlaceInLine) return undefined; - return harvestableIndex.add(TokenValue.fromHuman(maxPlaceInLine.toString(), PODS.decimals)); - }, [maxPlaceInLine, harvestableIndex]); + return activeHarvestableIndex.add(TokenValue.fromHuman(maxPlaceInLine.toString(), PODS.decimals)); + }, [maxPlaceInLine, activeHarvestableIndex]); return (
diff --git a/src/pages/market/actions/FillListing.tsx b/src/pages/market/actions/FillListing.tsx index b00a9239..fbcde708 100644 --- a/src/pages/market/actions/FillListing.tsx +++ b/src/pages/market/actions/FillListing.tsx @@ -16,6 +16,7 @@ import { Separator } from "@/components/ui/Separator"; import { Slider } from "@/components/ui/Slider"; import { ANALYTICS_EVENTS } from "@/constants/analytics-events"; import { PODS } from "@/constants/internalTokens"; +import { useBeanstalkMarket } from "@/context/BeanstalkMarketContext"; import fillPodListing from "@/encoders/fillPodListing"; import { beanstalkAbi } from "@/generated/contractHooks"; import { useProtocolAddress } from "@/hooks/pinto/useProtocolAddress"; @@ -30,6 +31,7 @@ import useTransaction from "@/hooks/useTransaction"; import usePriceImpactSummary from "@/hooks/wells/usePriceImpactSummary"; import usePodListings from "@/state/market/usePodListings"; import { useFarmerBalances } from "@/state/useFarmerBalances"; +import { useFarmerBeanstalkRepayment } from "@/state/useFarmerBeanstalkRepayment"; import { useFarmerPlotsQuery } from "@/state/useFarmerField"; import { useHarvestableIndex, usePodIndex } from "@/state/useFieldData"; import { useQueryKeys } from "@/state/useQueryKeys"; @@ -96,6 +98,8 @@ export default function FillListing({ selectedListingId, selectedPlaceInLine, on const account = useAccount(); const farmerBalances = useFarmerBalances(); const harvestableIndex = useHarvestableIndex(); + const { podMarketplaceId, fieldId, isBeanstalkMarketplace } = useBeanstalkMarket(); + const repayment = useFarmerBeanstalkRepayment(); const navigate = useNavigate(); const [searchParams] = useSearchParams(); // Use prop if provided, otherwise fall back to URL param @@ -119,7 +123,7 @@ export default function FillListing({ selectedListingId, selectedPlaceInLine, on filterLP: true, }); - const podListings = usePodListings(); + const podListings = usePodListings(podMarketplaceId); const allListings = podListings.data; const [didSetPreferred, setDidSetPreferred] = useState(false); @@ -139,7 +143,9 @@ export default function FillListing({ selectedListingId, selectedPlaceInLine, on // Place in line state const podIndex = usePodIndex(); - const maxPlace = Number.parseInt(podIndex.toHuman()) - Number.parseInt(harvestableIndex.toHuman()) || 0; + const activeHarvestableIndex = isBeanstalkMarketplace ? repayment.pods.harvestableIndex : harvestableIndex; + const activePodIndex = isBeanstalkMarketplace ? repayment.pods.podIndex : podIndex; + const maxPlace = Number.parseInt(activePodIndex.toHuman()) - Number.parseInt(activeHarvestableIndex.toHuman()) || 0; const [maxPlaceInLine, setMaxPlaceInLine] = useState(undefined); const [hasInitializedPlace, setHasInitializedPlace] = useState(false); @@ -213,12 +219,12 @@ export default function FillListing({ selectedListingId, selectedPlaceInLine, on if (Number.isNaN(placeInLine) || placeInLine <= 0) { // Fallback to calculating from listing index if URL value is invalid const listingIndex = TokenValue.fromBlockchain(listing.index, PODS.decimals); - placeInLine = listingIndex.sub(harvestableIndex).toNumber(); + placeInLine = listingIndex.sub(activeHarvestableIndex).toNumber(); } } else { // Calculate listing's place in line from index (fallback for direct URL access) const listingIndex = TokenValue.fromBlockchain(listing.index, PODS.decimals); - placeInLine = listingIndex.sub(harvestableIndex).toNumber(); + placeInLine = listingIndex.sub(activeHarvestableIndex).toNumber(); } // Set max place in line to the place in line plus one (to include the current plot) @@ -226,7 +232,7 @@ export default function FillListing({ selectedListingId, selectedPlaceInLine, on const maxPlaceValue = Math.min(maxPlace, Math.max(0, placeInLine + 1)); setMaxPlaceInLine(maxPlaceValue); setHasInitializedPlace(true); // Mark as initialized to prevent default value override - }, [listingId, allListings, maxPlace, mainToken.decimals, harvestableIndex, placeInLineFromUrl]); + }, [listingId, allListings, maxPlace, mainToken.decimals, activeHarvestableIndex, placeInLineFromUrl]); // Token selection handler with tracking const handleTokenSelection = useCallback( @@ -325,7 +331,7 @@ export default function FillListing({ selectedListingId, selectedPlaceInLine, on // Calculate place in line boundary for filtering const maxPlaceIndex = maxPlaceInLine - ? harvestableIndex.add(TokenValue.fromHuman(maxPlaceInLine.toString(), PODS.decimals)) + ? activeHarvestableIndex.add(TokenValue.fromHuman(maxPlaceInLine.toString(), PODS.decimals)) : undefined; // Determine eligible listings (shown as green on graph) @@ -349,13 +355,13 @@ export default function FillListing({ selectedListingId, selectedPlaceInLine, on // Calculate range overlay for visual feedback on graph const overlay = maxPlaceInLine ? { - start: harvestableIndex, - end: harvestableIndex.add(TokenValue.fromHuman(maxPlaceInLine.toString(), PODS.decimals)), + start: activeHarvestableIndex, + end: activeHarvestableIndex.add(TokenValue.fromHuman(maxPlaceInLine.toString(), PODS.decimals)), } : undefined; return { listingPlots: plots, eligibleListingIds: eligible, rangeOverlay: overlay }; - }, [allListings, maxPricePerPod, maxPlaceInLine, mainToken.decimals, harvestableIndex]); + }, [allListings, maxPricePerPod, maxPlaceInLine, mainToken.decimals, activeHarvestableIndex]); // Notify parent component when filter values change for chart highlighting useEffect(() => { @@ -468,7 +474,7 @@ export default function FillListing({ selectedListingId, selectedPlaceInLine, on const { listing, beanAmount } = listingsToFill[0]; const listingPrice = TokenValue.fromBlockchain(listing.pricePerPod, mainToken.decimals); const podsFromListing = beanAmount.div(listingPrice); - const listingPlace = TokenValue.fromBlockchain(listing.index, PODS.decimals).sub(harvestableIndex); + const listingPlace = TokenValue.fromBlockchain(listing.index, PODS.decimals).sub(activeHarvestableIndex); return { avgPricePerPod: listingPrice, @@ -485,7 +491,7 @@ export default function FillListing({ selectedListingId, selectedPlaceInLine, on for (const { listing, beanAmount } of listingsToFill) { const listingPrice = TokenValue.fromBlockchain(listing.pricePerPod, mainToken.decimals); const podsFromListing = beanAmount.div(listingPrice); - const listingPlace = TokenValue.fromBlockchain(listing.index, PODS.decimals).sub(harvestableIndex); + const listingPlace = TokenValue.fromBlockchain(listing.index, PODS.decimals).sub(activeHarvestableIndex); const pods = podsFromListing.toNumber(); totalValue += listingPrice.toNumber() * pods; @@ -501,7 +507,7 @@ export default function FillListing({ selectedListingId, selectedPlaceInLine, on avgPlaceInLine: TokenValue.fromHuman(avgPlaceInLine, PODS.decimals), totalPods, }; - }, [listingsToFill, mainToken.decimals, harvestableIndex]); + }, [listingsToFill, mainToken.decimals, activeHarvestableIndex]); // Calculate total tokens needed to fill eligible listings const totalMainTokensToFill = useMemo(() => { @@ -574,7 +580,7 @@ export default function FillListing({ selectedListingId, selectedPlaceInLine, on args: [ { lister: listing.farmer.id as Address, - fieldId: 0n, + fieldId: fieldId, index: TokenValue.fromBlockchain(listing.index, PODS.decimals).toBigInt(), start: TokenValue.fromBlockchain(listing.start, PODS.decimals).toBigInt(), podAmount: TokenValue.fromBlockchain(listing.amount, PODS.decimals).toBigInt(), @@ -628,6 +634,7 @@ export default function FillListing({ selectedListingId, selectedPlaceInLine, on beanAmount, FarmFromMode.INTERNAL, clipboard, + fieldId, ); advFarm.push(fillCall); diff --git a/src/pages/market/actions/FillOrder.tsx b/src/pages/market/actions/FillOrder.tsx index a75aa2af..b21b8695 100644 --- a/src/pages/market/actions/FillOrder.tsx +++ b/src/pages/market/actions/FillOrder.tsx @@ -10,12 +10,14 @@ import { Separator } from "@/components/ui/Separator"; import { MultiSlider } from "@/components/ui/Slider"; import { ANALYTICS_EVENTS } from "@/constants/analytics-events"; import { PODS } from "@/constants/internalTokens"; +import { useBeanstalkMarket } from "@/context/BeanstalkMarketContext"; import { beanstalkAbi } from "@/generated/contractHooks"; import { useProtocolAddress } from "@/hooks/pinto/useProtocolAddress"; import { useFarmTogglePreference } from "@/hooks/useFarmTogglePreference"; import useTransaction from "@/hooks/useTransaction"; import usePodOrders from "@/state/market/usePodOrders"; import { useFarmerBalances } from "@/state/useFarmerBalances"; +import { useFarmerBeanstalkRepayment } from "@/state/useFarmerBeanstalkRepayment"; import { useFarmerField, useFarmerPlotsQuery } from "@/state/useFarmerField"; import { useHarvestableIndex, usePodIndex } from "@/state/useFieldData"; import { useQueryKeys } from "@/state/useQueryKeys"; @@ -32,7 +34,6 @@ import { useAccount } from "wagmi"; import CancelOrder from "./CancelOrder"; // Constants -const FIELD_ID = 0n; const MIN_PODS_THRESHOLD = 1; // Minimum pods required for order eligibility // Helper Functions @@ -84,6 +85,7 @@ export default function FillOrder({ selectedOrderId }: FillOrderProps) { const podLine = podIndex.sub(harvestableIndex); const navigate = useNavigate(); const [searchParams] = useSearchParams(); + const { isBeanstalkMarketplace, fieldId, podMarketplaceId } = useBeanstalkMarket(); // Use prop if provided, otherwise fall back to URL param const orderId = selectedOrderId || searchParams.get("orderId"); @@ -118,9 +120,22 @@ export default function FillOrder({ selectedOrderId }: FillOrderProps) { prevTotalCapacityRef.current = -1; // Reset to allow re-triggering range update }, [selectedOrderIds]); - const podOrders = usePodOrders(); + const podOrders = usePodOrders(podMarketplaceId); const allOrders = podOrders.data; const farmerField = useFarmerField(); + const repayment = useFarmerBeanstalkRepayment(); + + const userPlots = useMemo(() => { + if (isBeanstalkMarketplace) { + return repayment.pods.plots; + } + return farmerField?.plots || []; + }, [isBeanstalkMarketplace, farmerField?.plots, repayment.pods.plots]); + + const activeHarvestableIndex = useMemo( + () => (isBeanstalkMarketplace ? repayment.pods.harvestableIndex : harvestableIndex), + [isBeanstalkMarketplace, repayment.pods.harvestableIndex, harvestableIndex], + ); const { selectedOrders, orderPositions, totalCapacity } = useMemo(() => { if (!allOrders?.podOrders) return { selectedOrders: [], orderPositions: [], totalCapacity: 0 }; @@ -195,7 +210,7 @@ export default function FillOrder({ selectedOrderId }: FillOrderProps) { if (!allOrders?.podOrders) return []; // Get farmer's frontmost pod position (lowest index) - const farmerPlots = farmerField.plots; + const farmerPlots = userPlots; const farmerFrontmostPodIndex = farmerPlots.length > 0 ? farmerPlots.reduce((min, plot) => (plot.index.lt(min) ? plot.index : min), farmerPlots[0].index) @@ -208,17 +223,19 @@ export default function FillOrder({ selectedOrderId }: FillOrderProps) { } // Check if farmer has pods that can fill this order - // Order's maxPlaceInLine + harvestableIndex must be >= farmer's frontmost pod index + // Order's maxPlaceInLine + activeHarvestableIndex must be >= farmer's frontmost pod index if (!farmerFrontmostPodIndex) { return false; // No pods available } - const orderMaxPlaceIndex = harvestableIndex.add(TokenValue.fromBlockchain(order.maxPlaceInLine, PODS.decimals)); + const orderMaxPlaceIndex = activeHarvestableIndex.add( + TokenValue.fromBlockchain(order.maxPlaceInLine, PODS.decimals), + ); // Farmer's pod must be at or before the order's maxPlaceInLine position return farmerFrontmostPodIndex.lte(orderMaxPlaceIndex); }); - }, [allOrders?.podOrders, mainToken.decimals, podLine, farmerField.plots, harvestableIndex]); + }, [allOrders?.podOrders, mainToken.decimals, podLine, userPlots, activeHarvestableIndex]); useEffect(() => { if (totalCapacity !== prevTotalCapacityRef.current) { @@ -246,7 +263,7 @@ export default function FillOrder({ selectedOrderId }: FillOrderProps) { return eligibleOrders.map((order) => { const orderMaxPlace = TokenValue.fromBlockchain(order.maxPlaceInLine, PODS.decimals); - const markerIndex = harvestableIndex.add(orderMaxPlace); + const markerIndex = activeHarvestableIndex.add(orderMaxPlace); return { index: markerIndex, @@ -255,7 +272,7 @@ export default function FillOrder({ selectedOrderId }: FillOrderProps) { id: order.id, } as Plot; }); - }, [eligibleOrders, harvestableIndex]); + }, [eligibleOrders, activeHarvestableIndex]); const plotsForGraph = useMemo(() => { return orderMarkers; @@ -294,7 +311,7 @@ export default function FillOrder({ selectedOrderId }: FillOrderProps) { }); const onSubmit = useCallback(async () => { - if (ordersToFill.length === 0 || !account || farmerField.plots.length === 0) { + if (ordersToFill.length === 0 || !account || userPlots.length === 0) { return; } @@ -324,7 +341,7 @@ export default function FillOrder({ selectedOrderId }: FillOrderProps) { toast.loading(`Filling ${ordersToFill.length} Order${ordersToFill.length !== 1 ? "s" : ""}...`); // Sort farmer plots by index to use them in order (only sort once) - const sortedPlots = [...farmerField.plots].sort((a, b) => a.index.sub(b.index).toNumber()); + const sortedPlots = [...userPlots].sort((a, b) => a.index.sub(b.index).toNumber()); if (sortedPlots.length === 0) { throw new Error("No pods available to fill orders"); @@ -340,7 +357,7 @@ export default function FillOrder({ selectedOrderId }: FillOrderProps) { for (const { order: orderToFill, amount: fillAmount } of ordersToFill) { let remainingAmount = fillAmount; - const orderMaxPlaceIndex = harvestableIndex.add( + const orderMaxPlaceIndex = activeHarvestableIndex.add( TokenValue.fromBlockchain(orderToFill.maxPlaceInLine, PODS.decimals), ); @@ -373,7 +390,7 @@ export default function FillOrder({ selectedOrderId }: FillOrderProps) { // Create fillPodOrder call for this order with pod allocation from current plot const fillOrderArgs = { orderer: orderToFill.farmer.id as Address, - fieldId: FIELD_ID, + fieldId: fieldId, maxPlaceInLine: BigInt(orderToFill.maxPlaceInLine), pricePerPod: Number(orderToFill.pricePerPod), minFillAmount: BigInt(orderToFill.minFillAmount), @@ -430,13 +447,14 @@ export default function FillOrder({ selectedOrderId }: FillOrderProps) { }, [ ordersToFill, account, - farmerField.plots, + userPlots, writeWithEstimateGas, setSubmitting, diamondAddress, amount, weightedAvgPricePerPod, - harvestableIndex, + activeHarvestableIndex, + fieldId, ]); const isOwnOrder = useMemo(() => { diff --git a/src/pages/transfer/actions/beanstalk-pods/StepOne.tsx b/src/pages/transfer/actions/beanstalk-pods/StepOne.tsx index bbc11af1..73f9cfb1 100644 --- a/src/pages/transfer/actions/beanstalk-pods/StepOne.tsx +++ b/src/pages/transfer/actions/beanstalk-pods/StepOne.tsx @@ -1,6 +1,7 @@ import AddressInputField from "@/components/AddressInputField"; import PintoAssetTransferNotice from "@/components/PintoAssetTransferNotice"; import PodLineGraph from "@/components/PodLineGraph"; +import { Button } from "@/components/ui/Button"; import { Label } from "@/components/ui/Label"; import { MultiSlider } from "@/components/ui/Slider"; import { useFarmerBeanstalkRepayment } from "@/state/useFarmerBeanstalkRepayment"; @@ -9,6 +10,7 @@ import { computeTransferData, offsetToAbsoluteIndex } from "@/utils/podTransferU import { Plot } from "@/utils/types"; import { AnimatePresence, motion } from "framer-motion"; import { Dispatch, SetStateAction, useCallback, useEffect, useMemo, useRef, useState } from "react"; +import { Link } from "react-router-dom"; import { PodTransferData } from "../TransferBeanstalkPods"; interface StepOneProps { @@ -137,6 +139,14 @@ export default function StepOne({ return (
+
+ + +
- request(subgraphs[chainId].beanstalk, AllPodListingsDocument, { + queryKey: [...queryKey, { podMarketplaceId }], + queryFn: async () => { + const variables: { maxHarvestableIndex: string; skip: number; podMarketplace?: string } = { maxHarvestableIndex: harvestableIndex.toBigInt().toString(), skip: 0, - }), + }; + if (podMarketplaceId) variables.podMarketplace = podMarketplaceId; + return request(subgraphs[chainId].beanstalk, AllPodListingsDocument, variables); + }, enabled: harvestableIndex.gt(0), }); diff --git a/src/state/market/usePodOrders.ts b/src/state/market/usePodOrders.ts index c874da16..1db4ec90 100644 --- a/src/state/market/usePodOrders.ts +++ b/src/state/market/usePodOrders.ts @@ -5,17 +5,18 @@ import request from "graphql-request"; import { useChainId } from "wagmi"; import { useQueryKeys } from "../useQueryKeys"; -export default function usePodOrders() { +export default function usePodOrders(podMarketplaceId?: string) { const chainId = useChainId(); const { allPodOrders: queryKey } = useQueryKeys({ chainId }); const podOrders = useQuery({ - queryKey: queryKey, - queryFn: async () => - request(subgraphs[chainId].beanstalk, AllPodOrdersDocument, { - skip: 0, - }), + queryKey: [...queryKey, { podMarketplaceId }], + queryFn: async () => { + const variables: { skip: number; podMarketplace?: string } = { skip: 0 }; + if (podMarketplaceId) variables.podMarketplace = podMarketplaceId; + return request(subgraphs[chainId].beanstalk, AllPodOrdersDocument, variables); + }, }); return { From 674b768ca893aea1e17153572def59758f7fda55 Mon Sep 17 00:00:00 2001 From: feyyazcigim Date: Mon, 16 Feb 2026 20:00:14 +0300 Subject: [PATCH 19/22] chore: remove unecessary buttons from beanstalk pods send page --- src/pages/transfer/actions/beanstalk-pods/StepOne.tsx | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/pages/transfer/actions/beanstalk-pods/StepOne.tsx b/src/pages/transfer/actions/beanstalk-pods/StepOne.tsx index 73f9cfb1..bbc11af1 100644 --- a/src/pages/transfer/actions/beanstalk-pods/StepOne.tsx +++ b/src/pages/transfer/actions/beanstalk-pods/StepOne.tsx @@ -1,7 +1,6 @@ import AddressInputField from "@/components/AddressInputField"; import PintoAssetTransferNotice from "@/components/PintoAssetTransferNotice"; import PodLineGraph from "@/components/PodLineGraph"; -import { Button } from "@/components/ui/Button"; import { Label } from "@/components/ui/Label"; import { MultiSlider } from "@/components/ui/Slider"; import { useFarmerBeanstalkRepayment } from "@/state/useFarmerBeanstalkRepayment"; @@ -10,7 +9,6 @@ import { computeTransferData, offsetToAbsoluteIndex } from "@/utils/podTransferU import { Plot } from "@/utils/types"; import { AnimatePresence, motion } from "framer-motion"; import { Dispatch, SetStateAction, useCallback, useEffect, useMemo, useRef, useState } from "react"; -import { Link } from "react-router-dom"; import { PodTransferData } from "../TransferBeanstalkPods"; interface StepOneProps { @@ -139,14 +137,6 @@ export default function StepOne({ return (
-
- - -
Date: Mon, 16 Feb 2026 20:07:35 +0300 Subject: [PATCH 20/22] chore: update fertilizer text order --- src/components/FertilizerCard.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/FertilizerCard.tsx b/src/components/FertilizerCard.tsx index 59e11261..4d0d7a68 100644 --- a/src/components/FertilizerCard.tsx +++ b/src/components/FertilizerCard.tsx @@ -40,11 +40,11 @@ export default function FertilizerCard({ {/* Fertilizer info */}
- {formatter.number(Number(maxBalance))} bsFERT - ID {formatter.number(Number(fertId))} + {sprouts} Sprouts + Humidity: {humidity}
- Sprouts: {sprouts} · Humidity: {humidity} + {formatter.number(Number(maxBalance))} bsFERT - ID {formatter.number(Number(fertId))}
From 62ef0b120fe6b60e30204da2df7221d9308b4dad Mon Sep 17 00:00:00 2001 From: feyyazcigim Date: Mon, 23 Feb 2026 06:23:37 +0300 Subject: [PATCH 21/22] feat(silo): introduce automated claim presets and order management --- src/__tests/conditionSection.test.ts | 36 ++ .../AutomateClaim/AutomateClaimContext.tsx | 42 ++ .../AutomateClaimExecutionHistory.tsx | 107 +++++ .../AutomateClaimVisualization.tsx | 93 +++++ .../AutomateClaim/ConditionSection.tsx | 149 +++++++ .../AutomateClaim/SpecifyConditionsDialog.tsx | 334 +++++++++++++++ src/components/Tractor/AutomateClaim/index.ts | 5 + .../Tractor/HighlightedCallData.tsx | 50 ++- .../FarmerTractorAutomateClaimOrderCard.tsx | 110 +++++ .../TractorFarmerMixedOrders.types.ts | 25 +- .../TractorFarmerMixedOrders.utils.ts | 62 ++- .../TractorFarmerOrderTypeRegistry.tsx | 77 +++- .../farmer-orders/TractorOrdersPanel.tsx | 49 ++- .../form/schema/automateClaim.schema.ts | 127 ++++++ src/components/Tractor/types.ts | 12 +- .../abi/AutomateClaimBlueprintABI.ts | 135 ++++++ src/constants/abiConfig.ts | 12 + src/constants/address.ts | 4 + src/hooks/tractor/useAutomateClaimOrder.ts | 147 +++++++ .../automate-claim-decoder.ts | 30 ++ src/lib/Tractor/blueprint-decoders/index.ts | 7 +- src/lib/Tractor/claimOrder/index.ts | 2 + .../Tractor/claimOrder/tractor-claim-types.ts | 119 ++++++ src/lib/Tractor/claimOrder/tractor-claim.ts | 388 ++++++++++++++++++ src/lib/Tractor/core/constants.ts | 2 + src/lib/Tractor/core/shared-tractor-types.ts | 4 +- src/lib/Tractor/index.ts | 1 + .../requisitions/tractor-requisition.ts | 32 +- src/pages/Silo.tsx | 16 +- src/pages/overview/FarmerOverview.tsx | 2 +- src/pages/silo/RewardsClaim.tsx | 71 ++-- src/pages/silo/SiloConvertUpContent.tsx | 6 +- .../tractor/useTractorAutomateClaimOrders.ts | 106 +++++ src/state/tractor/useTractorClaimOrders.ts | 8 + 34 files changed, 2317 insertions(+), 53 deletions(-) create mode 100644 src/__tests/conditionSection.test.ts create mode 100644 src/components/Tractor/AutomateClaim/AutomateClaimContext.tsx create mode 100644 src/components/Tractor/AutomateClaim/AutomateClaimExecutionHistory.tsx create mode 100644 src/components/Tractor/AutomateClaim/AutomateClaimVisualization.tsx create mode 100644 src/components/Tractor/AutomateClaim/ConditionSection.tsx create mode 100644 src/components/Tractor/AutomateClaim/SpecifyConditionsDialog.tsx create mode 100644 src/components/Tractor/AutomateClaim/index.ts create mode 100644 src/components/Tractor/farmer-orders/FarmerTractorAutomateClaimOrderCard.tsx create mode 100644 src/components/Tractor/form/schema/automateClaim.schema.ts create mode 100644 src/constants/abi/AutomateClaimBlueprintABI.ts create mode 100644 src/hooks/tractor/useAutomateClaimOrder.ts create mode 100644 src/lib/Tractor/blueprint-decoders/automate-claim-decoder.ts create mode 100644 src/lib/Tractor/claimOrder/index.ts create mode 100644 src/lib/Tractor/claimOrder/tractor-claim-types.ts create mode 100644 src/lib/Tractor/claimOrder/tractor-claim.ts create mode 100644 src/state/tractor/useTractorAutomateClaimOrders.ts create mode 100644 src/state/tractor/useTractorClaimOrders.ts diff --git a/src/__tests/conditionSection.test.ts b/src/__tests/conditionSection.test.ts new file mode 100644 index 00000000..fc57a292 --- /dev/null +++ b/src/__tests/conditionSection.test.ts @@ -0,0 +1,36 @@ +import { CLAIM_PRESETS } from "@/lib/Tractor/claimOrder/tractor-claim"; +import { describe, expect, it } from "vitest"; + +/** + * Unit tests for ConditionSection preset configuration and display logic. + * Validates Requirements 3.1-3.10, 6.2-6.5 + */ +describe("ConditionSection — Preset Configuration", () => { + it("should have correct preset values for Mow (Req 3.1-3.3)", () => { + expect(CLAIM_PRESETS.mow.high.value).toBe(50); + expect(CLAIM_PRESETS.mow.medium.value).toBe(100); + expect(CLAIM_PRESETS.mow.low.value).toBe(1000); + }); + + it("should have correct preset values for Plant (Req 3.4-3.6)", () => { + expect(CLAIM_PRESETS.plant.high.value).toBe(10); + expect(CLAIM_PRESETS.plant.medium.value).toBe(50); + expect(CLAIM_PRESETS.plant.low.value).toBe(500); + }); + + it("should have correct preset values for Harvest (Req 3.7-3.9)", () => { + expect(CLAIM_PRESETS.harvest.high.value).toBe(10); + expect(CLAIM_PRESETS.harvest.medium.value).toBe(50); + expect(CLAIM_PRESETS.harvest.low.value).toBe(500); + }); + + it("should have exactly 3 preset levels (high, medium, low) per operation", () => { + for (const op of ["mow", "plant", "harvest"] as const) { + const keys = Object.keys(CLAIM_PRESETS[op]); + expect(keys).toContain("high"); + expect(keys).toContain("medium"); + expect(keys).toContain("low"); + expect(keys).toHaveLength(3); + } + }); +}); diff --git a/src/components/Tractor/AutomateClaim/AutomateClaimContext.tsx b/src/components/Tractor/AutomateClaim/AutomateClaimContext.tsx new file mode 100644 index 00000000..3af09c2b --- /dev/null +++ b/src/components/Tractor/AutomateClaim/AutomateClaimContext.tsx @@ -0,0 +1,42 @@ +import { createContext, useCallback, useContext, useMemo, useState } from "react"; + +// ──────────────────────────────────────────────────────────────────────────────── +// Context shape +// ──────────────────────────────────────────────────────────────────────────────── + +interface AutomateClaimContextValue { + /** Whether the SpecifyConditionsDialog is open. */ + isOpen: boolean; + /** Toggle or set the dialog open state. */ + setIsOpen: (open: boolean) => void; +} + +const AutomateClaimContext = createContext(null); + +// ──────────────────────────────────────────────────────────────────────────────── +// Provider +// ──────────────────────────────────────────────────────────────────────────────── + +export function AutomateClaimProvider({ children }: { children: React.ReactNode }) { + const [isOpen, setIsOpenRaw] = useState(false); + + const setIsOpen = useCallback((open: boolean) => { + setIsOpenRaw(open); + }, []); + + const value = useMemo(() => ({ isOpen, setIsOpen }), [isOpen, setIsOpen]); + + return {children}; +} + +// ──────────────────────────────────────────────────────────────────────────────── +// Hook +// ──────────────────────────────────────────────────────────────────────────────── + +export function useAutomateClaimContext(): AutomateClaimContextValue { + const ctx = useContext(AutomateClaimContext); + if (!ctx) { + throw new Error("useAutomateClaimContext must be used within an AutomateClaimProvider"); + } + return ctx; +} diff --git a/src/components/Tractor/AutomateClaim/AutomateClaimExecutionHistory.tsx b/src/components/Tractor/AutomateClaim/AutomateClaimExecutionHistory.tsx new file mode 100644 index 00000000..bd2bc31d --- /dev/null +++ b/src/components/Tractor/AutomateClaim/AutomateClaimExecutionHistory.tsx @@ -0,0 +1,107 @@ +import { TV } from "@/classes/TokenValue"; +import { formatter } from "@/utils/format"; +import { format } from "date-fns"; +import { useMemo } from "react"; +import { AutomateClaimOrderData, ExecutionHistoryProps } from "../types"; + +// Helper function to shorten addresses +function shortenAddress(address: string): string { + return `${address.substring(0, 6)}...${address.substring(address.length - 4)}`; +} + +// Helper function to format dates with time +const formatDate = (timestamp?: number) => { + if (!timestamp) return "Unknown"; + return format(new Date(timestamp), "MM/dd/yyyy h:mm a"); +}; + +export function AutomateClaimExecutionHistory({ executionHistory, orderData }: ExecutionHistoryProps) { + if (orderData.type !== "automateClaim") { + throw new Error("AutomateClaimExecutionHistory requires automateClaim order data"); + } + + const claimData = orderData as AutomateClaimOrderData; + + const enabledOps = useMemo(() => { + const ops: string[] = []; + if (claimData.mowEnabled) ops.push("Mow"); + if (claimData.plantEnabled) ops.push("Plant"); + if (claimData.harvestEnabled) ops.push("Harvest"); + return ops; + }, [claimData.mowEnabled, claimData.plantEnabled, claimData.harvestEnabled]); + + const totalTipsPaid = useMemo(() => { + const tipAmount = claimData.operatorTip ? TV.fromHuman(claimData.operatorTip, 6) : TV.ZERO; + return tipAmount.mul(executionHistory.length); + }, [claimData.operatorTip, executionHistory.length]); + + const sortedExecutions = useMemo( + () => + [...executionHistory].sort((a, b) => { + if (a.timestamp && b.timestamp) { + return b.timestamp - a.timestamp; + } + return b.blockNumber - a.blockNumber; + }), + [executionHistory], + ); + + if (executionHistory.length === 0) { + return
No executions yet
; + } + + return ( +
+ {/* Summary Section */} +
+
+ Total Executions + {executionHistory.length} +
+
+ Enabled Operations + {enabledOps.join(", ") || "None"} +
+
+ Total Tips Paid + {formatter.number(totalTipsPaid)} PINTO +
+
+ + {/* Execution Table */} +
+ + + + + + + + + + + {sortedExecutions.map((execution, index) => ( + + + + + + + ))} + +
ExecutionOperatorDate & TimeActions
#{executionHistory.length - index}{shortenAddress(execution.operator)} + {execution.timestamp ? formatDate(execution.timestamp) : `Block ${execution.blockNumber}`} + + + View Transaction + +
+
+
+ ); +} diff --git a/src/components/Tractor/AutomateClaim/AutomateClaimVisualization.tsx b/src/components/Tractor/AutomateClaim/AutomateClaimVisualization.tsx new file mode 100644 index 00000000..edd23fea --- /dev/null +++ b/src/components/Tractor/AutomateClaim/AutomateClaimVisualization.tsx @@ -0,0 +1,93 @@ +import { OrderVisualization } from "@/components/OrderVisualization"; +import { useMainToken } from "@/state/useTokenData"; +import { CheckCircledIcon, CrossCircledIcon } from "@radix-ui/react-icons"; +import { AutomateClaimOrderData, TractorOrderVisualizationProps } from "../types"; + +/** + * Visualizes decoded AutomateClaim order data in the Tractor orders panel. + * Displays enabled operations (Mow, Plant, Harvest) and operator tip configuration. + */ +export function AutomateClaimVisualization({ orderData, className }: TractorOrderVisualizationProps) { + if (orderData.type !== "automateClaim") { + throw new Error("AutomateClaimVisualization requires automateClaim order data"); + } + + const claimData = orderData as AutomateClaimOrderData; + const mainToken = useMainToken(); + + const operations = [ + { label: "Mow", enabled: claimData.mowEnabled }, + { label: "Plant", enabled: claimData.plantEnabled }, + { label: "Harvest", enabled: claimData.harvestEnabled }, + ]; + + const enabledOps = operations.filter((op) => op.enabled); + + return ( +
+
+
+
+ +
+ {/* Automate Claim Section */} +
+ + + {enabledOps.length} operation{enabledOps.length !== 1 ? "s" : ""} enabled + + ), + }, + ]} + size="sm" + /> + ({ + text: ( + + {op.enabled ? ( + + ) : ( + + )} + {op.label} + {op.enabled ? "enabled" : "disabled"} + + ), + }))} + size="sm" + /> + +
+ + {/* Tip Section */} +
+ + + {claimData.operatorTip} PINTO + PINTO + + ), + }, + { type: "context", content: "per execution to Operator" }, + ]} + size="sm" + /> + +
+
+
+ ); +} diff --git a/src/components/Tractor/AutomateClaim/ConditionSection.tsx b/src/components/Tractor/AutomateClaim/ConditionSection.tsx new file mode 100644 index 00000000..0f37f545 --- /dev/null +++ b/src/components/Tractor/AutomateClaim/ConditionSection.tsx @@ -0,0 +1,149 @@ +import { Col, Row } from "@/components/Container"; +import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ui/Accordion"; +import type { ClaimFrequencyPreset } from "@/lib/Tractor/claimOrder/tractor-claim-types"; +import { cn } from "@/utils/utils"; +import { CheckIcon } from "@radix-ui/react-icons"; + +// ──────────────────────────────────────────────────────────────────────────────── +// Types +// ──────────────────────────────────────────────────────────────────────────────── + +export interface ConditionSectionProps { + /** Section title shown in the accordion header (e.g. "Mow my Silo"). */ + title: string; + /** Whether a preset has been selected (shows green checkmark when true). */ + enabled: boolean; + /** Currently selected preset, or null if none selected. */ + preset: ClaimFrequencyPreset | null; + /** Value for the custom numeric input field. */ + customValue: string; + /** Callback when a preset button is clicked. */ + onPresetChange: (preset: ClaimFrequencyPreset | null) => void; + /** Callback when the custom input value changes. */ + onCustomValueChange: (value: string) => void; + /** Unit label shown inside the custom input (e.g. "Stalk" or "PINTO"). */ + unit?: string; +} + +// ──────────────────────────────────────────────────────────────────────────────── +// Preset button metadata +// ──────────────────────────────────────────────────────────────────────────────── + +interface PresetMeta { + key: ClaimFrequencyPreset; + label: string; +} + +const PRESET_BUTTONS: PresetMeta[] = [ + { key: "high", label: "Aggressively" }, + { key: "medium", label: "Medium-Aggressively" }, + { key: "low", label: "Not-Aggressively" }, + { key: "custom", label: "Custom" }, +]; + +// ──────────────────────────────────────────────────────────────────────────────── +// Component +// ──────────────────────────────────────────────────────────────────────────────── + +export const ConditionSection = ({ + title, + enabled, + preset, + customValue, + onPresetChange, + onCustomValueChange, + unit, +}: ConditionSectionProps) => { + return ( + + + {/* Header: title + green checkmark + chevron */} + + + {title} + {enabled && } + + + + {/* Expanded content: preset buttons + optional custom input */} + + + {/* Preset buttons grid */} +
+ {PRESET_BUTTONS.map((btn) => { + const isSelected = preset === btn.key; + + // When "custom" is selected, show inline input instead of button + if (btn.key === "custom" && isSelected) { + const normalized = customValue.trim().replace(",", "."); + const num = Number(normalized); + const isValid = normalized !== "" && !Number.isNaN(num) && num > 0; + + return ( +
+ onCustomValueChange(e.target.value)} + className={cn( + "w-full text-center pinto-xs bg-transparent outline-none", + isValid + ? "text-pinto-green-4 placeholder:text-pinto-green-4/50" + : "text-pinto-gray-4 placeholder:text-pinto-gray-3", + )} + /> +
+ ); + } + + return ( + onPresetChange(isSelected ? null : btn.key)} + /> + ); + })} +
+ +
+
+
+ ); +}; + +// ──────────────────────────────────────────────────────────────────────────────── +// Internal: Preset Button +// ──────────────────────────────────────────────────────────────────────────────── + +const PresetButton = ({ + label, + selected, + onClick, +}: { + label: string; + selected: boolean; + onClick: () => void; +}) => ( + +); diff --git a/src/components/Tractor/AutomateClaim/SpecifyConditionsDialog.tsx b/src/components/Tractor/AutomateClaim/SpecifyConditionsDialog.tsx new file mode 100644 index 00000000..71543596 --- /dev/null +++ b/src/components/Tractor/AutomateClaim/SpecifyConditionsDialog.tsx @@ -0,0 +1,334 @@ +import { Col, Row } from "@/components/Container"; +import { Form } from "@/components/Form"; +import ReviewTractorOrderDialog from "@/components/ReviewTractorOrderDialog"; +import TooltipSimple from "@/components/TooltipSimple"; +import { Button } from "@/components/ui/Button"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogOverlay, + DialogPortal, + DialogTitle, +} from "@/components/ui/Dialog"; +import IconImage from "@/components/ui/IconImage"; +import { Separator } from "@/components/ui/Separator"; +import { useAutomateClaimOrder } from "@/hooks/tractor/useAutomateClaimOrder"; +import { estimatedTotalTipRange } from "@/lib/Tractor/claimOrder"; +import type { ClaimFrequencyPreset } from "@/lib/Tractor/claimOrder/tractor-claim-types"; +import useTractorOperatorAverageTipPaid from "@/state/tractor/useTractorOperatorAverageTipPaid"; +import { useFarmerSilo } from "@/state/useFarmerSilo"; +import { useMainToken } from "@/state/useTokenData"; +import { formatter } from "@/utils/format"; +import { useCallback, useMemo, useState } from "react"; +import { useWatch } from "react-hook-form"; +import { toast } from "sonner"; +import { + OperatorTipFormField, + type TractorOperatorTipStrategy, + getTractorOperatorTipAmountFromPreset, +} from "../form/fields/sharedFields"; +import { defaultAutomateClaimValues, useAutomateClaimForm } from "../form/schema/automateClaim.schema"; +import { ConditionSection } from "./ConditionSection"; + +// ──────────────────────────────────────────────────────────────────────────────── +// Preset display configs +// ──────────────────────────────────────────────────────────────────────────────── + +// ──────────────────────────────────────────────────────────────────────────────── +// Props +// ──────────────────────────────────────────────────────────────────────────────── + +export interface SpecifyConditionsDialogProps { + open: boolean; + onOpenChange: (open: boolean) => void; +} + +// ──────────────────────────────────────────────────────────────────────────────── +// Component +// ──────────────────────────────────────────────────────────────────────────────── + +export const SpecifyConditionsDialog = ({ open, onOpenChange }: SpecifyConditionsDialogProps) => { + const mainToken = useMainToken(); + const farmerSilo = useFarmerSilo(); + const { data: averageTipPaid = 0.15 } = useTractorOperatorAverageTipPaid(); + + // Form state + const { form } = useAutomateClaimForm(); + + // Operator tip preset state + const [operatorTipPreset, setOperatorTipPreset] = useState("Normal"); + + // Blueprint creation state + const { state, orderData, isLoading, handleCreateBlueprint } = useAutomateClaimOrder(); + const [showReviewDialog, setShowReviewDialog] = useState(false); + + // Watch form values for condition sections + const [ + mowPreset, + plantPreset, + harvestPreset, + mowCustomValue, + plantCustomValue, + harvestCustomValue, + customOperatorTip, + ] = useWatch({ + control: form.control, + name: [ + "mowPreset", + "plantPreset", + "harvestPreset", + "mowCustomValue", + "plantCustomValue", + "harvestCustomValue", + "customOperatorTip", + ], + }); + + // Derived: which operations are enabled (a preset has been selected) + const mowEnabled = mowPreset !== null; + const plantEnabled = plantPreset !== null; + const harvestEnabled = harvestPreset !== null; + + // ── Preset change handlers ────────────────────────────────────────────────── + + const handleMowPresetChange = useCallback( + (preset: ClaimFrequencyPreset | null) => { + form.setValue("mowPreset", preset, { shouldValidate: true }); + form.setValue("mowEnabled", preset !== null, { shouldValidate: true }); + }, + [form], + ); + + const handlePlantPresetChange = useCallback( + (preset: ClaimFrequencyPreset | null) => { + form.setValue("plantPreset", preset, { shouldValidate: true }); + form.setValue("plantEnabled", preset !== null, { shouldValidate: true }); + }, + [form], + ); + + const handleHarvestPresetChange = useCallback( + (preset: ClaimFrequencyPreset | null) => { + form.setValue("harvestPreset", preset, { shouldValidate: true }); + form.setValue("harvestEnabled", preset !== null, { shouldValidate: true }); + }, + [form], + ); + + // ── Submit handler ─────────────────────────────────────────────────────────── + + const handleSubmit = useCallback( + async (e: React.MouseEvent) => { + e.preventDefault(); + e.stopPropagation(); + + // Validate the form first + const isValid = await form.trigger(); + if (!isValid) return; + + try { + await handleCreateBlueprint(form, averageTipPaid, operatorTipPreset, farmerSilo.deposits, { + onSuccess: () => { + setShowReviewDialog(true); + }, + onFailure: () => { + toast.error("Failed to create Automate Claim blueprint"); + }, + }); + } catch (error) { + console.error("[SpecifyConditionsDialog] Error creating blueprint:", error); + toast.error("Failed to create Automate Claim blueprint"); + } + }, + [form, handleCreateBlueprint, averageTipPaid, operatorTipPreset, farmerSilo.deposits], + ); + + // ── Estimated tip range ───────────────────────────────────────────────────── + + const tipRange = useMemo(() => { + const enabledCount = (mowEnabled ? 1 : 0) + (plantEnabled ? 1 : 0) + (harvestEnabled ? 1 : 0); + + if (enabledCount === 0) { + return { min: 0n, max: 0n }; + } + + const tipTV = getTractorOperatorTipAmountFromPreset( + operatorTipPreset, + averageTipPaid, + customOperatorTip, + mainToken.decimals, + ); + + if (!tipTV) { + return { min: 0n, max: 0n }; + } + + return estimatedTotalTipRange(tipTV.toBigInt(), enabledCount); + }, [ + mowEnabled, + plantEnabled, + harvestEnabled, + operatorTipPreset, + averageTipPaid, + customOperatorTip, + mainToken.decimals, + ]); + + // Check if submit should be disabled + const hasAnyEnabled = mowEnabled || plantEnabled || harvestEnabled; + + // Custom presets require a valid positive numeric value + const isValidCustomValue = (v: string) => { + const normalized = v.trim().replace(",", "."); + if (!normalized) return false; + const num = Number(normalized); + return !Number.isNaN(num) && num > 0; + }; + + const hasInvalidCustom = + (mowPreset === "custom" && !isValidCustomValue(mowCustomValue)) || + (plantPreset === "custom" && !isValidCustomValue(plantCustomValue)) || + (harvestPreset === "custom" && !isValidCustomValue(harvestCustomValue)); + + if (!open) return null; + + return ( + <> + {/* modal={false} to prevent operator tip popover from blocking input events */} + + + + + + Specify conditions + + Configure which Silo claim operations to automate and their execution thresholds. + + + +
+ + {/* Conditions area */} + + + + {/* Condition Sections */} + + form.setValue("mowCustomValue", v)} + unit="Stalk" + /> + form.setValue("plantCustomValue", v)} + unit="PINTO" + /> + form.setValue("harvestCustomValue", v)} + unit="PINTO" + /> + + + + {/* Fixed footer: always pinned to bottom */} + + + + + + + + + + + + +
+
+
+ + {/* Review → Sign → Publish dialog */} + {showReviewDialog && state && orderData && ( + { + form.reset({ ...defaultAutomateClaimValues }); + setShowReviewDialog(false); + onOpenChange(false); + }} + orderData={{ + type: "automateClaim" as const, + ...orderData, + }} + encodedData={state.encodedData} + operatorPasteInstrs={state.operatorPasteInstructions} + blueprint={state.blueprint} + depositOptimizationCalls={state.depositOptimizationCalls} + /> + )} + + ); +}; + +// ──────────────────────────────────────────────────────────────────────────────── +// Internal: Estimated Total Tip Range display +// ──────────────────────────────────────────────────────────────────────────────── + +const EstimatedTotalTipRange = ({ + tipRange, + mainToken, +}: { + tipRange: { min: bigint; max: bigint }; + mainToken: ReturnType; +}) => { + const formatTip = (value: bigint) => { + const human = Number(value) / 10 ** mainToken.decimals; + return formatter.number(human.toString(), { minDecimals: 2, maxDecimals: 3 }); + }; + + return ( + + +
Estimated Total Tip
+ +
+ + + {formatTip(tipRange.min)} - {formatTip(tipRange.max)} + +
+ ); +}; diff --git a/src/components/Tractor/AutomateClaim/index.ts b/src/components/Tractor/AutomateClaim/index.ts new file mode 100644 index 00000000..ca3a9a8f --- /dev/null +++ b/src/components/Tractor/AutomateClaim/index.ts @@ -0,0 +1,5 @@ +export { AutomateClaimProvider, useAutomateClaimContext } from "./AutomateClaimContext"; +export { SpecifyConditionsDialog } from "./SpecifyConditionsDialog"; +export { ConditionSection } from "./ConditionSection"; +export { AutomateClaimVisualization } from "./AutomateClaimVisualization"; +export { AutomateClaimExecutionHistory } from "./AutomateClaimExecutionHistory"; diff --git a/src/components/Tractor/HighlightedCallData.tsx b/src/components/Tractor/HighlightedCallData.tsx index 7052342b..ba9bc665 100644 --- a/src/components/Tractor/HighlightedCallData.tsx +++ b/src/components/Tractor/HighlightedCallData.tsx @@ -74,6 +74,52 @@ function ConvertUpBlueprintDisplay({ params }: { params: any }) { ); } +function AutomateClaimBlueprintDisplay({ params }: { params: any }) { + return ( +
+
Function: automateClaimBlueprint
+
+
minMowAmount: {params.claimParams.minMowAmount.toString()}
+
minPlantAmount: {params.claimParams.minPlantAmount.toString()}
+
+ fieldHarvestConfigs: + {params.claimParams.fieldHarvestConfigs.length > 0 ? ( +
+ {params.claimParams.fieldHarvestConfigs.map((config: any, index: number) => ( +
+ [{index}] fieldId: {config.fieldId.toString()}, minHarvestAmount: {config.minHarvestAmount.toString()} +
+ ))} +
+ ) : ( + [] + )} +
+
minRinseAmount: {params.claimParams.minRinseAmount.toString()}
+
minUnripeClaimAmount: {params.claimParams.minUnripeClaimAmount.toString()}
+
+ operatorTips: +
+
mowTipAmount: {params.opParams.mowTipAmount.toString()}
+
plantTipAmount: {params.opParams.plantTipAmount.toString()}
+
harvestTipAmount: {params.opParams.harvestTipAmount.toString()}
+
rinseTipAmount: {params.opParams.rinseTipAmount.toString()}
+
unripeClaimTipAmount: {params.opParams.unripeClaimTipAmount.toString()}
+
+
+
+ operatorParams: +
+
operatorTipAmount: {params.opParams.baseOpParams.operatorTipAmount.toString()}
+
tipAddress: {params.opParams.baseOpParams.tipAddress}
+
whitelistedOperators: [{params.opParams.baseOpParams.whitelistedOperators.join(", ")}]
+
+
+
+
+ ); +} + function GenericParameterDisplay({ selector, data, @@ -147,7 +193,7 @@ function RequisitionDataDisplay({ } } -const knownBlueprintTypes = new Set(["sow", "convertUp"]); +const knownBlueprintTypes = new Set(["sow", "convertUp", "automateClaim"]); export function HighlightedCallData({ blueprintData, @@ -176,6 +222,8 @@ export function HighlightedCallData({ return ; case "convertUp": return ; + case "automateClaim": + return ; case "generic": return ( ; + executions?: PublisherTractorExecution[]; + onOrderClick: (req: RequisitionEvent) => void; + onCancelClick: (req: RequisitionEvent, e: React.MouseEvent) => void; + isSubmitting?: boolean; + isConfirming?: boolean; +} + +const FarmerTractorAutomateClaimOrderCard = ({ + req, + executions, + onOrderClick, + onCancelClick, + isSubmitting = false, + isConfirming = false, +}: FarmerTractorAutomateClaimOrderCardProps) => { + if (req.requisitionType !== "automateClaimBlueprint" || !req.decodedData) return null; + + const transformed = transformAutomateClaimRequisitionEvent(req.decodedData); + if (!transformed) return null; + + const MAX_UINT256 = 2n ** 256n - 1n; + const mowEnabled = transformed.claimParams.minMowAmount !== MAX_UINT256; + const plantEnabled = transformed.claimParams.minPlantAmount !== MAX_UINT256; + const harvestEnabled = transformed.claimParams.fieldHarvestConfigs.length > 0; + + const enabledOps = [mowEnabled && "Mow", plantEnabled && "Plant", harvestEnabled && "Harvest"].filter(Boolean); + + const operatorTip = TokenValue.fromBlockchain(transformed.operatorParams.operatorTipAmount, 6); + + const blueprintExecutions = executions || []; + const executionCount = blueprintExecutions.length; + const publishDate = req.timestamp ? format(new Date(req.timestamp), "dd MMM yyyy") : "Unknown"; + + return ( + + onOrderClick(req)} + > +
+
+ + {enabledOps.join(", ")} + + ), + }, + ]} + /> + +
+ + ({ + text: `${op} enabled`, + }))} + /> +
+
+ + +
+ + Published {publishDate} +
+ + + + Executed {executionCount} time{executionCount !== 1 ? "s" : ""} + + + + + +
+ + ); +}; + +export default FarmerTractorAutomateClaimOrderCard; diff --git a/src/components/Tractor/farmer-orders/TractorFarmerMixedOrders.types.ts b/src/components/Tractor/farmer-orders/TractorFarmerMixedOrders.types.ts index 34730c8e..141998d5 100644 --- a/src/components/Tractor/farmer-orders/TractorFarmerMixedOrders.types.ts +++ b/src/components/Tractor/farmer-orders/TractorFarmerMixedOrders.types.ts @@ -1,4 +1,5 @@ import { PublisherTractorExecution, SowBlueprintData, TractorRequisitionEvent } from "@/lib/Tractor"; +import { AutomateClaimBlueprintStruct } from "@/lib/Tractor/claimOrder/tractor-claim-types"; import { ConvertUpOrderbookEntry } from "@/lib/Tractor/convertUp/tractor-convert-up-types"; import { OrderType } from "./TractorFarmerOrderTypeRegistry"; @@ -14,13 +15,16 @@ export interface UnifiedTractorOrder { isComplete?: boolean; // Order-specific data (discriminated union) - orderData: SowOrderData | ConvertUpOrderData; + orderData: SowOrderData | ConvertUpOrderData | AutomateClaimOrderData; // Executions executions?: PublisherTractorExecution[]; // Raw data for dialogs - requisition: ConvertUpOrderbookEntry | TractorRequisitionEvent; + requisition: + | ConvertUpOrderbookEntry + | TractorRequisitionEvent + | TractorRequisitionEvent; } // Sow-specific order data @@ -50,6 +54,16 @@ export interface ConvertUpOrderData { strategy: string; } +// AutomateClaim-specific order data +export interface AutomateClaimOrderData { + type: "automateClaim"; + mowEnabled: boolean; + plantEnabled: boolean; + harvestEnabled: boolean; + operatorTip: string; + percentComplete: number; +} + // Type guards export function isSowOrder(order: UnifiedTractorOrder): order is UnifiedTractorOrder & { orderData: SowOrderData; @@ -64,6 +78,13 @@ export function isConvertUpOrder( return order.orderData.type === "convertUp" && order.requisition.requisitionType === "convertUpBlueprint"; } +export function isAutomateClaimOrder(order: UnifiedTractorOrder): order is UnifiedTractorOrder & { + orderData: AutomateClaimOrderData; + requisition: TractorRequisitionEvent; +} { + return order.orderData.type === "automateClaim" && order.requisition.requisitionType === "automateClaimBlueprint"; +} + // Sorting options for mixed orders export type MixedOrderSortBy = "newest" | "oldest" | "type" | "operatorTip" | "percentComplete" | "publisher"; diff --git a/src/components/Tractor/farmer-orders/TractorFarmerMixedOrders.utils.ts b/src/components/Tractor/farmer-orders/TractorFarmerMixedOrders.utils.ts index 571a0029..869926d1 100644 --- a/src/components/Tractor/farmer-orders/TractorFarmerMixedOrders.utils.ts +++ b/src/components/Tractor/farmer-orders/TractorFarmerMixedOrders.utils.ts @@ -1,8 +1,11 @@ import { TokenValue } from "@/classes/TokenValue"; import { PublisherTractorExecution, SowBlueprintData, TractorRequisitionEvent } from "@/lib/Tractor"; +import { transformAutomateClaimRequisitionEvent } from "@/lib/Tractor/claimOrder"; +import { AutomateClaimBlueprintStruct } from "@/lib/Tractor/claimOrder/tractor-claim-types"; import { ConvertUpOrderbookEntry } from "@/lib/Tractor/convertUp/tractor-convert-up-types"; import { getTokenNameByIndex } from "@/utils/token"; import { + AutomateClaimOrderData, ConvertUpOrderData, MixedOrderFilters, MixedOrderSortBy, @@ -144,6 +147,50 @@ export function transformConvertUpOrderToUnified( }; } +// Transform AutomateClaim order to unified format +export function transformAutomateClaimOrderToUnified( + req: TractorRequisitionEvent, + executions: PublisherTractorExecution[] = [], +): UnifiedTractorOrder { + if (!req.decodedData) { + throw new Error("Missing decoded data for AutomateClaim order"); + } + + const transformed = transformAutomateClaimRequisitionEvent(req.decodedData); + if (!transformed) { + throw new Error("Failed to transform AutomateClaim data"); + } + + const MAX_UINT256 = 2n ** 256n - 1n; + const mowEnabled = transformed.claimParams.minMowAmount !== MAX_UINT256; + const plantEnabled = transformed.claimParams.minPlantAmount !== MAX_UINT256; + const harvestEnabled = transformed.claimParams.fieldHarvestConfigs.length > 0; + + const operatorTip = TokenValue.fromBlockchain(transformed.operatorParams.operatorTipAmount, 6).toHuman(); + + const automateClaimOrderData: AutomateClaimOrderData = { + type: "automateClaim", + mowEnabled, + plantEnabled, + harvestEnabled, + operatorTip, + percentComplete: 0, + }; + + return { + id: req.requisition.blueprintHash, + type: "automateClaim", + timestamp: req.timestamp, + blockNumber: req.blockNumber, + publisher: req.requisition.blueprint.publisher, + isCancelled: req.isCancelled, + isComplete: false, + orderData: automateClaimOrderData, + executions, + requisition: req, + }; +} + // Sort unified orders export function sortUnifiedOrders(orders: UnifiedTractorOrder[], sortBy: MixedOrderSortBy): UnifiedTractorOrder[] { const sortedOrders = [...orders]; @@ -213,6 +260,13 @@ export function filterUnifiedOrders(orders: UnifiedTractorOrder[], filters: Mixe // Format order summary for display export function getOrderSummary(order: UnifiedTractorOrder): string { + if (order.type === "automateClaim") { + const data = order.orderData as AutomateClaimOrderData; + const ops = [data.mowEnabled && "Mow", data.plantEnabled && "Plant", data.harvestEnabled && "Harvest"].filter( + Boolean, + ); + return `Automate Claim • ${ops.join(", ")} • Active`; + } const typeLabel = order.type === "sow" ? "Sow" : "Convert Up"; const amount = order.type === "sow" @@ -223,7 +277,7 @@ export function getOrderSummary(order: UnifiedTractorOrder): string { } // Get order type badge info -export function getOrderTypeBadge(orderType: "sow" | "convertUp") { +export function getOrderTypeBadge(orderType: "sow" | "convertUp" | "automateClaim") { switch (orderType) { case "sow": return { @@ -237,5 +291,11 @@ export function getOrderTypeBadge(orderType: "sow" | "convertUp") { className: "bg-green-100 text-green-800 border-green-200", icon: "⬆️", }; + case "automateClaim": + return { + label: "Automate Claim", + className: "bg-purple-100 text-purple-800 border-purple-200", + icon: "🔄", + }; } } diff --git a/src/components/Tractor/farmer-orders/TractorFarmerOrderTypeRegistry.tsx b/src/components/Tractor/farmer-orders/TractorFarmerOrderTypeRegistry.tsx index c9aa62ed..afbee0b7 100644 --- a/src/components/Tractor/farmer-orders/TractorFarmerOrderTypeRegistry.tsx +++ b/src/components/Tractor/farmer-orders/TractorFarmerOrderTypeRegistry.tsx @@ -7,12 +7,13 @@ import { SowBlueprintData, decodeSowTractorData, } from "@/lib/Tractor"; +import { decodeAutomateClaimBlueprint, transformAutomateClaimRequisitionEvent } from "@/lib/Tractor/claimOrder"; +import { AutomateClaimBlueprintStruct } from "@/lib/Tractor/claimOrder/tractor-claim-types"; import { decodeConvertUpTractorOrder } from "@/lib/Tractor/convertUp/tractor-convert-up"; import { ConvertUpOrderbookEntry } from "@/lib/Tractor/convertUp/tractor-convert-up-types"; import { prepareRequisitionEventForTxn } from "@/lib/Tractor/utils"; -import React from "react"; import { base } from "viem/chains"; -import { Col } from "../../Container"; +import { AutomateClaimExecutionHistory, AutomateClaimVisualization } from "../AutomateClaim"; import ConvertUpExecutionHistory from "../executions/ConvertUpExecutionHistory"; import SowExecutionHistory from "../executions/SowExecutionHistory"; import { OrderTypeConfig, TractorOrderData } from "../types"; @@ -77,6 +78,30 @@ const ConvertUpOrderDescription = ({ isViewOnly }: { isViewOnly: boolean }) => { ); }; +// Automate Claim Order Description +const AutomateClaimDescription = ({ isViewOnly }: { isViewOnly: boolean }) => { + if (isViewOnly) { + return ( + + This is your active Automate Claim Order. It allows an Operator to execute Silo claim operations (Mow, Plant, + Harvest) for you when the conditions are met. + + ); + } + + return ( + + + An Automate Claim Order allows you to pay an Operator to execute Silo claim operations (Mow, Plant, Harvest) for + you automatically. + + + This allows you to interact with the Pinto protocol autonomously when the conditions of your Order are met. + + + ); +}; + // Transform functions for order data const transformSowOrderData = ( req: RequisitionEvent, @@ -125,6 +150,23 @@ const transformConvertUpOrderData = ( }; }; +const transformAutomateClaimOrderData = (req: RequisitionEvent): TractorOrderData => { + if (!req.decodedData) throw new Error("Missing decoded data for AutomateClaim order"); + + const transformed = transformAutomateClaimRequisitionEvent(req.decodedData); + if (!transformed) throw new Error("Failed to transform AutomateClaim data"); + + const MAX_UINT256 = 2n ** 256n - 1n; + + return { + type: "automateClaim", + mowEnabled: transformed.claimParams.minMowAmount !== MAX_UINT256, + plantEnabled: transformed.claimParams.minPlantAmount !== MAX_UINT256, + harvestEnabled: transformed.claimParams.fieldHarvestConfigs.length > 0, + operatorTip: transformed.operatorParams.operatorTipAmountAsString, + }; +}; + // Unified transform function with overloads for type safety function transformOrderData( req: RequisitionEvent, @@ -132,7 +174,11 @@ function transformOrderData( ): TractorOrderData; function transformOrderData(req: ConvertUpOrderbookEntry, getStrategyProps: GetStrategyProps): TractorOrderData; function transformOrderData( - req: RequisitionEvent | ConvertUpOrderbookEntry, + req: RequisitionEvent, + getStrategyProps: GetStrategyProps, +): TractorOrderData; +function transformOrderData( + req: RequisitionEvent | ConvertUpOrderbookEntry | RequisitionEvent, getStrategyProps: GetStrategyProps, ): TractorOrderData { if (isSowRequest(req)) { @@ -141,6 +187,9 @@ function transformOrderData( if (isConvertUpRequest(req)) { return transformConvertUpOrderData(req, getStrategyProps); } + if (isAutomateClaimRequest(req)) { + return transformAutomateClaimOrderData(req); + } throw new Error("Unknown request type for order transformation"); } @@ -166,16 +215,21 @@ function isConvertUpRequest(req: any): req is ConvertUpOrderbookEntry { return req && "orderInfo" in req && "totalAvailableBdv" in req; } +function isAutomateClaimRequest(req: any): req is RequisitionEvent { + return req && "requisitionType" in req && req.requisitionType === "automateClaimBlueprint"; +} + // Type for the strategy props hook type GetStrategyProps = ReturnType; // Type for decoded data type DecodedSowData = ReturnType; type DecodedConvertUpData = ReturnType; +type DecodedAutomateClaimData = ReturnType; // Extended OrderTypeConfig interface with only the used fields export interface ExtendedOrderTypeConfig extends OrderTypeConfig { - decodeData: (blueprintData: `0x${string}`) => DecodedSowData | DecodedConvertUpData | null; + decodeData: (blueprintData: `0x${string}`) => DecodedSowData | DecodedConvertUpData | DecodedAutomateClaimData | null; prepareForCancellation?: typeof prepareForCancellation; transformOrderData: typeof transformOrderData; } @@ -211,6 +265,21 @@ export const ORDER_TYPE_REGISTRY = { prepareForCancellation: prepareForCancellation, transformOrderData: transformOrderData, }, + + automateClaim: { + // UI Components + visualization: AutomateClaimVisualization, + executionHistory: AutomateClaimExecutionHistory, + + // Metadata + title: "Review Automate Claim Order", + description: (isViewOnly: boolean) => , + + // Data Handling + decodeData: decodeAutomateClaimBlueprint, + prepareForCancellation: prepareForCancellation, + transformOrderData: transformOrderData, + }, } as const satisfies Record; // Helper function to get order configuration by type diff --git a/src/components/Tractor/farmer-orders/TractorOrdersPanel.tsx b/src/components/Tractor/farmer-orders/TractorOrdersPanel.tsx index 4ee22483..11b55b9b 100644 --- a/src/components/Tractor/farmer-orders/TractorOrdersPanel.tsx +++ b/src/components/Tractor/farmer-orders/TractorOrdersPanel.tsx @@ -10,6 +10,7 @@ import useTransaction from "@/hooks/useTransaction"; import { PublisherTractorExecution } from "@/lib/Tractor"; import type { Blueprint } from "@/lib/Tractor"; import { queryKeys } from "@/state/queryKeys"; +import { useTractorAutomateClaimOrderbook } from "@/state/tractor/useTractorAutomateClaimOrders"; import { useTractorConvertUpOrderbook } from "@/state/tractor/useTractorConvertUpOrders"; import usePublisherTractorExecutions from "@/state/tractor/useTractorExecutions"; import useTractorOperatorAverageTipPaid from "@/state/tractor/useTractorOperatorAverageTipPaid"; @@ -24,12 +25,14 @@ import { toast } from "sonner"; import { useAccount } from "wagmi"; import ModifyConvertUpOrderDialog from "../ModifyConvertUpOrderDialog"; import ModifyTractorOrderDialog from "../ModifySowOrderDialog"; +import FarmerTractorAutomateClaimOrderCard from "./FarmerTractorAutomateClaimOrderCard"; import FarmerTractorConvertUpOrderCard from "./FarmerTractorConvertUpOrderCard"; import FarmerTractorSowOrderCard from "./FarmerTractorSowOrderCard"; import { MixedOrderFilters, MixedOrderSortBy, UnifiedTractorOrder, + isAutomateClaimOrder, isConvertUpOrder, isSowOrder, } from "./TractorFarmerMixedOrders.types"; @@ -37,6 +40,7 @@ import { filterUnifiedOrders, getOrderTypeBadge, sortUnifiedOrders, + transformAutomateClaimOrderToUnified, transformConvertUpOrderToUnified, transformSowOrderToUnified, } from "./TractorFarmerMixedOrders.utils"; @@ -50,7 +54,7 @@ interface TractorOrdersPanelProps { initialFilters?: Partial; } -const ORDER_TYPES: OrderType[] = ["sow", "convertUp"]; +const ORDER_TYPES: OrderType[] = ["sow", "convertUp", "automateClaim"]; function TractorOrdersPanelGeneric({ orderTypes: _orderTypes = ORDER_TYPES, @@ -96,6 +100,12 @@ function TractorOrdersPanelGeneric({ enabled: !!address && filters.orderTypes.includes("convertUp"), }); + const { data: automateClaimOrders, ...automateClaimOrdersQuery } = useTractorAutomateClaimOrderbook({ + address, + filterOutCompleted: false, + enabled: !!address && filters.orderTypes.includes("automateClaim"), + }); + const executionsByHash = useMemo(() => { const byHash = executions?.reduce>((acc, curr) => { if (curr.blueprintHash in acc) acc[curr.blueprintHash].push(curr); @@ -152,10 +162,27 @@ function TractorOrdersPanelGeneric({ }); } + // Process AutomateClaim orders + if (filters.orderTypes.includes("automateClaim") && automateClaimOrders) { + automateClaimOrders + .filter((req) => stringEq(req.requisition.blueprint.publisher, address)) + .forEach((req) => { + const decodedData = + req.decodedData ?? ORDER_TYPE_REGISTRY.automateClaim.decodeData(req.requisition.blueprint.data); + if (!decodedData) { + return; + } + const reqWithDecodedData = { ...req, decodedData }; + const orderExecutions = executionsByHash?.[req.requisition.blueprintHash] || []; + + unified.push(transformAutomateClaimOrderToUnified(reqWithDecodedData, orderExecutions)); + }); + } + // Apply filters and sorting const filtered = filterUnifiedOrders(unified, filters); return sortUnifiedOrders(filtered, currentSortBy); - }, [sowOrders, convertUpOrders, executions, address, filters, currentSortBy, executionsByHash]); + }, [sowOrders, convertUpOrders, automateClaimOrders, executions, address, filters, currentSortBy, executionsByHash]); // Loading and error states const dataHasLoaded = address ? Boolean(executions !== undefined) : true; @@ -163,9 +190,11 @@ function TractorOrdersPanelGeneric({ executionsQuery.isLoading || (filters.orderTypes.includes("sow") && sowOrdersQuery.isLoading) || (filters.orderTypes.includes("convertUp") && convertUpOrdersQuery.isLoading) || + (filters.orderTypes.includes("automateClaim") && automateClaimOrdersQuery.isLoading) || !dataHasLoaded; - const error = executionsQuery.error || sowOrdersQuery.error || convertUpOrdersQuery.error; + const error = + executionsQuery.error || sowOrdersQuery.error || convertUpOrdersQuery.error || automateClaimOrdersQuery.error; const [lastRefetchedCounter, setLastRefetchedCounter] = useState(0); @@ -180,6 +209,9 @@ function TractorOrdersPanelGeneric({ if (filters.orderTypes.includes("convertUp")) { convertUpOrdersQuery.refetch(); } + if (filters.orderTypes.includes("automateClaim")) { + automateClaimOrdersQuery.refetch(); + } } }, [refreshData, dataHasLoaded, filters.orderTypes]); @@ -316,6 +348,17 @@ function TractorOrdersPanelGeneric({ isConfirming={isConfirming} /> )} + + {isAutomateClaimOrder(order) && ( + handleOrderClick(order)} + onCancelClick={(_, e) => handleCancelOrder(order, e)} + isSubmitting={submitting} + isConfirming={isConfirming} + /> + )}
); diff --git a/src/components/Tractor/form/schema/automateClaim.schema.ts b/src/components/Tractor/form/schema/automateClaim.schema.ts new file mode 100644 index 00000000..b36eed42 --- /dev/null +++ b/src/components/Tractor/form/schema/automateClaim.schema.ts @@ -0,0 +1,127 @@ +import { zodResolver } from "@hookform/resolvers/zod"; +import { useCallback } from "react"; +import { useForm } from "react-hook-form"; +import { z } from "zod"; + +// ──────────────────────────────────────────────────────────────────────────────── +// SCHEMA +// ──────────────────────────────────────────────────────────────────────────────── + +const claimFrequencyPreset = z.enum(["high", "medium", "low", "custom"]).nullable().default(null); + +export const automateClaimSchemaErrors = { + noOperationEnabled: "At least one operation (Mow, Plant, or Harvest) must be enabled", + customValueRequired: "A positive numeric value is required when Custom is selected", +} as const; + +export const automateClaimSchema = z + .object({ + mowEnabled: z.boolean().default(false), + mowPreset: claimFrequencyPreset, + mowCustomValue: z.string().default(""), + + plantEnabled: z.boolean().default(false), + plantPreset: claimFrequencyPreset, + plantCustomValue: z.string().default(""), + + harvestEnabled: z.boolean().default(false), + harvestPreset: claimFrequencyPreset, + harvestCustomValue: z.string().default(""), + + operatorTipPreset: z.enum(["Custom", "Low", "Normal", "High"] as const).default("Normal"), + customOperatorTip: z.string().default(""), + }) + .superRefine((data, ctx) => { + // At least one operation must be enabled (Req 4.1) + if (!data.mowEnabled && !data.plantEnabled && !data.harvestEnabled) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: automateClaimSchemaErrors.noOperationEnabled, + path: ["mowEnabled"], + }); + } + + // Custom preset validation — positive numeric value required (Req 4.2, 4.3) + const operations = [ + { enabled: data.mowEnabled, preset: data.mowPreset, customValue: data.mowCustomValue, field: "mowCustomValue" }, + { + enabled: data.plantEnabled, + preset: data.plantPreset, + customValue: data.plantCustomValue, + field: "plantCustomValue", + }, + { + enabled: data.harvestEnabled, + preset: data.harvestPreset, + customValue: data.harvestCustomValue, + field: "harvestCustomValue", + }, + ] as const; + + for (const op of operations) { + if (op.enabled && op.preset === "custom") { + const trimmed = op.customValue.trim(); + const num = Number(trimmed); + if (!trimmed || Number.isNaN(num) || num <= 0) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: automateClaimSchemaErrors.customValueRequired, + path: [op.field], + }); + } + } + } + }); + +// ──────────────────────────────────────────────────────────────────────────────── +// TYPES +// ──────────────────────────────────────────────────────────────────────────────── + +export type AutomateClaimFormValues = z.infer; + +// ──────────────────────────────────────────────────────────────────────────────── +// DEFAULT VALUES +// ──────────────────────────────────────────────────────────────────────────────── + +export const defaultAutomateClaimValues: AutomateClaimFormValues = { + mowEnabled: false, + mowPreset: null, + mowCustomValue: "", + + plantEnabled: false, + plantPreset: null, + plantCustomValue: "", + + harvestEnabled: false, + harvestPreset: null, + harvestCustomValue: "", + + operatorTipPreset: "Normal", + customOperatorTip: "", +}; + +// ──────────────────────────────────────────────────────────────────────────────── +// FORM HOOK +// ──────────────────────────────────────────────────────────────────────────────── + +export type IAutomateClaimForm = { + form: ReturnType>; + prefillValues: (values: Partial) => void; +}; + +export const useAutomateClaimForm = (): IAutomateClaimForm => { + const form = useForm({ + resolver: zodResolver(automateClaimSchema), + defaultValues: { ...defaultAutomateClaimValues }, + mode: "onChange", + }); + + const prefillValues = useCallback( + (values: Partial) => { + form.reset(values, { keepDirty: true }); + }, + [form.reset], + ); + + return { form, prefillValues } as const; +}; diff --git a/src/components/Tractor/types.ts b/src/components/Tractor/types.ts index 9eea42fb..9c187ed4 100644 --- a/src/components/Tractor/types.ts +++ b/src/components/Tractor/types.ts @@ -6,7 +6,7 @@ import { TimeScaleSelect } from "./form/fields/sharedFields"; // Base interfaces for all tractor orders export interface BaseTractorOrderData { operatorTip: string; - type: "sow" | "convertUp"; + type: "sow" | "convertUp" | "automateClaim"; } // Sow-specific order data @@ -42,8 +42,16 @@ export interface ConvertUpOrderData extends BaseTractorOrderData { tokenStrategy: TractorTokenStrategyUnion; } +// AutomateClaim-specific order data +export interface AutomateClaimOrderData extends BaseTractorOrderData { + type: "automateClaim"; + mowEnabled: boolean; + plantEnabled: boolean; + harvestEnabled: boolean; +} + // Union type for all order data -export type TractorOrderData = SowOrderData | ConvertUpOrderData; +export type TractorOrderData = SowOrderData | ConvertUpOrderData | AutomateClaimOrderData; // Generic execution event interface export interface BaseExecutionEvent { diff --git a/src/constants/abi/AutomateClaimBlueprintABI.ts b/src/constants/abi/AutomateClaimBlueprintABI.ts new file mode 100644 index 00000000..d40ea3ec --- /dev/null +++ b/src/constants/abi/AutomateClaimBlueprintABI.ts @@ -0,0 +1,135 @@ +export const automateClaimBlueprintABI = [ + { + inputs: [ + { + components: [ + { + components: [ + { + internalType: "uint256", + name: "minMowAmount", + type: "uint256", + }, + { + internalType: "int256", + name: "minTwaDeltaB", + type: "int256", + }, + { + internalType: "uint256", + name: "minPlantAmount", + type: "uint256", + }, + { + components: [ + { + internalType: "uint256", + name: "fieldId", + type: "uint256", + }, + { + internalType: "uint256", + name: "minHarvestAmount", + type: "uint256", + }, + ], + internalType: "struct AutomateClaimBlueprint.FieldHarvestConfig[]", + name: "fieldHarvestConfigs", + type: "tuple[]", + }, + { + internalType: "uint256", + name: "minRinseAmount", + type: "uint256", + }, + { + internalType: "uint256", + name: "minUnripeClaimAmount", + type: "uint256", + }, + { + internalType: "uint256[]", + name: "sourceTokenIndices", + type: "uint256[]", + }, + { + internalType: "uint256", + name: "maxGrownStalkPerBdv", + type: "uint256", + }, + { + internalType: "uint256", + name: "slippageRatio", + type: "uint256", + }, + ], + internalType: "struct AutomateClaimBlueprint.AutomateClaimParams", + name: "claimParams", + type: "tuple", + }, + { + components: [ + { + components: [ + { + internalType: "address[]", + name: "whitelistedOperators", + type: "address[]", + }, + { + internalType: "address", + name: "tipAddress", + type: "address", + }, + { + internalType: "uint256", + name: "operatorTipAmount", + type: "uint256", + }, + ], + internalType: "struct AutomateClaimBlueprint.OperatorParams", + name: "baseOpParams", + type: "tuple", + }, + { + internalType: "uint256", + name: "mowTipAmount", + type: "uint256", + }, + { + internalType: "uint256", + name: "plantTipAmount", + type: "uint256", + }, + { + internalType: "uint256", + name: "harvestTipAmount", + type: "uint256", + }, + { + internalType: "uint256", + name: "rinseTipAmount", + type: "uint256", + }, + { + internalType: "uint256", + name: "unripeClaimTipAmount", + type: "uint256", + }, + ], + internalType: "struct AutomateClaimBlueprint.OperatorParamsExtended", + name: "opParams", + type: "tuple", + }, + ], + internalType: "struct AutomateClaimBlueprint.AutomateClaimBlueprintStruct", + name: "params", + type: "tuple", + }, + ], + name: "automateClaimBlueprint", + outputs: [], + stateMutability: "payable", + type: "function", + }, +] as const; diff --git a/src/constants/abiConfig.ts b/src/constants/abiConfig.ts index d41dea2b..2ae19efe 100644 --- a/src/constants/abiConfig.ts +++ b/src/constants/abiConfig.ts @@ -1,5 +1,6 @@ import { Abi } from "viem"; import { arbitrum, base, foundry, localhost, mainnet } from "wagmi/chains"; +import { automateClaimBlueprintABI } from "./abi/AutomateClaimBlueprintABI"; import { pipelineABI } from "./abi/PipelineABI"; import { siloHelpersABI } from "./abi/SiloHelpersABI"; import { sowBlueprintv0ABI } from "./abi/SowBlueprintv0ABI"; @@ -9,6 +10,7 @@ import { depotABI } from "./abi/depotABI"; import { diamondABI } from "./abi/diamondABI"; import { diamondPriceABI } from "./abi/diamondPriceABI"; import { + AUTOMATE_CLAIM_BLUEPRINT_ADDRESS, CONVERT_UP_BLUEPRINT_V0_ADDRESS, DIAMOND_PRICE_ADDRESS, PIPELINE_ADDRESS, @@ -84,6 +86,12 @@ const ADDRESSES_LOOKUP = { [TESTNET_CHAIN_ID]: CONVERT_UP_BLUEPRINT_V0_ADDRESS, [foundry.id]: CONVERT_UP_BLUEPRINT_V0_ADDRESS, }, + automateClaimBlueprint: { + [base.id]: AUTOMATE_CLAIM_BLUEPRINT_ADDRESS, + [localhost.id]: AUTOMATE_CLAIM_BLUEPRINT_ADDRESS, + [TESTNET_CHAIN_ID]: AUTOMATE_CLAIM_BLUEPRINT_ADDRESS, + [foundry.id]: AUTOMATE_CLAIM_BLUEPRINT_ADDRESS, + }, } as const; const ABI_CONFIG = { @@ -119,6 +127,10 @@ const ABI_CONFIG = { abi: convertUpBlueprintV0ABI as Abi, address: ADDRESSES_LOOKUP.convertUpBlueprint, }, + automateClaimBlueprint: { + abi: automateClaimBlueprintABI as Abi, + address: ADDRESSES_LOOKUP.automateClaimBlueprint, + }, } as const; export default ABI_CONFIG; diff --git a/src/constants/address.ts b/src/constants/address.ts index eeb70471..1e367475 100644 --- a/src/constants/address.ts +++ b/src/constants/address.ts @@ -23,12 +23,16 @@ export const SOW_BLUEPRINT_REFERRAL_V0_ADDRESS: HashString = "0xD9DF9C4C01604017 export const CONVERT_UP_BLUEPRINT_V0_ADDRESS: HashString = "0x5167Ae1fF37bE08D9cc9188C7e64DB228B6F06ca"; +export const AUTOMATE_CLAIM_BLUEPRINT_ADDRESS: HashString = "0x0000000000000000000000000000000000000000"; // Placeholder - update after deployment + export const SOW_BLUEPRINT_V0_SELECTOR = "0x3ca8e1b2" as const; export const SOW_BLUEPRINT_REFERRAL_V0_SELECTOR = "0xd3936ead" as const; // Placeholder - update after deployment export const CONVERT_UP_BLUEPRINT_V0_SELECTOR = "0x0fd2f3ed" as const; +export const AUTOMATE_CLAIM_BLUEPRINT_SELECTOR = "0xf2a6fd1a" as const; + export const WELL_FUNCTION_ADDRESSES: ChainLookup<{ stable2: HashString; cp2: HashString; diff --git a/src/hooks/tractor/useAutomateClaimOrder.ts b/src/hooks/tractor/useAutomateClaimOrder.ts new file mode 100644 index 00000000..183bcd72 --- /dev/null +++ b/src/hooks/tractor/useAutomateClaimOrder.ts @@ -0,0 +1,147 @@ +import { TV } from "@/classes/TokenValue"; +import { + TractorOperatorTipStrategy, + getTractorOperatorTipAmountFromPreset, +} from "@/components/Tractor/form/fields/sharedFields"; +import { type AutomateClaimFormValues } from "@/components/Tractor/form/schema/automateClaim.schema"; +import { useProtocolAddress } from "@/hooks/pinto/useProtocolAddress"; +import { Blueprint, createBlueprintFromBlock } from "@/lib/Tractor"; +import { createAutomateClaimTractorData } from "@/lib/Tractor/claimOrder"; +import { useFarmerSilo } from "@/state/useFarmerSilo"; +import { useCallback, useState } from "react"; +import { useForm } from "react-hook-form"; +import { useAccount, usePublicClient } from "wagmi"; + +// ──────────────────────────────────────────────────────────────────────────────── +// TYPES +// ──────────────────────────────────────────────────────────────────────────────── + +export type AutomateClaimOrderState = { + blueprint: Blueprint; + encodedData: `0x${string}`; + operatorPasteInstructions: `0x${string}`[]; + depositOptimizationCalls: `0x${string}`[]; +}; + +export type AutomateClaimOrderData = { + mowEnabled: boolean; + plantEnabled: boolean; + harvestEnabled: boolean; + operatorTip: string; +}; + +// ──────────────────────────────────────────────────────────────────────────────── +// HOOK +// ──────────────────────────────────────────────────────────────────────────────── + +export const useAutomateClaimOrder = () => { + const client = usePublicClient(); + const { address } = useAccount(); + const protocolAddress = useProtocolAddress(); + + const [state, setState] = useState(undefined); + const [orderData, setOrderData] = useState(undefined); + const [isLoading, setIsLoading] = useState(false); + + const handleCreateBlueprint = useCallback( + async ( + form: ReturnType>, + averageTipPaid: number, + operatorTipPreset: TractorOperatorTipStrategy, + deposits?: ReturnType["deposits"], + options?: { + onFailure?: () => void; + onSuccess?: () => void; + }, + ) => { + if (!client) { + throw new Error("No public client available."); + } + if (!address) { + throw new Error("Signer not found."); + } + + setIsLoading(true); + + try { + const formData = form.getValues(); + console.debug("[useAutomateClaimOrder] Creating blueprint with form data:", formData); + + // Resolve operator tip amount from preset + const tipTV = getTractorOperatorTipAmountFromPreset( + operatorTipPreset, + averageTipPaid, + formData.customOperatorTip, + 6, // PINTO decimals + ); + + if (!tipTV) { + throw new Error("Unable to resolve operator tip amount."); + } + + const tipPerExecution = tipTV.toBigInt(); + console.debug("[useAutomateClaimOrder] Tip per execution:", tipPerExecution.toString()); + + // Create tractor data and fetch latest block in parallel + const [tractorData, block] = await Promise.all([ + createAutomateClaimTractorData({ + formValues: formData, + tipPerExecution, + whitelistedOperators: [], + publicClient: client, + farmerDeposits: deposits, + userAddress: address, + protocolAddress, + }).catch((e) => { + console.error("[useAutomateClaimOrder] Error creating tractor data:", e); + throw e; + }), + client.getBlock({ blockTag: "latest" }), + ]); + + console.debug("[useAutomateClaimOrder] Tractor data created:", tractorData); + + // Create the blueprint (Req 5.2) + const blueprint = createBlueprintFromBlock({ + block, + publisher: address, + data: tractorData.data, + operatorPasteInstrs: tractorData.operatorPasteInstrs, + maxNonce: TV.MAX_UINT256.toBigInt(), + }); + + console.debug("[useAutomateClaimOrder] Blueprint created:", blueprint); + + // Set order data for display in ReviewTractorOrderDialog + setOrderData({ + mowEnabled: formData.mowEnabled, + plantEnabled: formData.plantEnabled, + harvestEnabled: formData.harvestEnabled, + operatorTip: tipTV.toHuman(), + }); + + // Set state for the blueprint and encoded data + setState({ + blueprint, + encodedData: tractorData.data, + operatorPasteInstructions: tractorData.operatorPasteInstrs, + depositOptimizationCalls: tractorData.depositOptimizationCalls || [], + }); + + options?.onSuccess?.(); + } catch (_) { + options?.onFailure?.(); + } finally { + setIsLoading(false); + } + }, + [client, address, protocolAddress], + ); + + return { + state, + orderData, + isLoading, + handleCreateBlueprint, + } as const; +}; diff --git a/src/lib/Tractor/blueprint-decoders/automate-claim-decoder.ts b/src/lib/Tractor/blueprint-decoders/automate-claim-decoder.ts new file mode 100644 index 00000000..31f2a070 --- /dev/null +++ b/src/lib/Tractor/blueprint-decoders/automate-claim-decoder.ts @@ -0,0 +1,30 @@ +import { automateClaimBlueprintABI } from "@/constants/abi/AutomateClaimBlueprintABI"; +import { AUTOMATE_CLAIM_BLUEPRINT_SELECTOR } from "@/constants/address"; +import { decodeFunctionData } from "viem"; +import type { BlueprintDecoder, DecodedBlueprintResult } from "./index"; + +export const automateClaimBlueprintDecoder: BlueprintDecoder = { + selector: AUTOMATE_CLAIM_BLUEPRINT_SELECTOR, + abi: automateClaimBlueprintABI, + functionName: "automateClaimBlueprint", + decode: (callData: string): DecodedBlueprintResult | null => { + try { + const decoded = decodeFunctionData({ + abi: automateClaimBlueprintABI, + data: callData as `0x${string}`, + }); + + if (decoded.functionName === "automateClaimBlueprint" && decoded.args[0]) { + return { + type: "automateClaim", + functionName: "automateClaimBlueprint", + params: decoded.args[0], + }; + } + } catch (error) { + console.error("Error decoding automateClaimBlueprint:", error); + } + + return null; + }, +}; diff --git a/src/lib/Tractor/blueprint-decoders/index.ts b/src/lib/Tractor/blueprint-decoders/index.ts index 01faf319..f230a02f 100644 --- a/src/lib/Tractor/blueprint-decoders/index.ts +++ b/src/lib/Tractor/blueprint-decoders/index.ts @@ -1,9 +1,11 @@ import { + AUTOMATE_CLAIM_BLUEPRINT_SELECTOR, CONVERT_UP_BLUEPRINT_V0_SELECTOR, SOW_BLUEPRINT_REFERRAL_V0_SELECTOR, SOW_BLUEPRINT_V0_SELECTOR, } from "@/constants/address"; import { extractTractorBlueprintCall } from "../requisitions/tractor-requisition"; +import { automateClaimBlueprintDecoder } from "./automate-claim-decoder"; import { convertUpBlueprintDecoder } from "./convert-up-decoder"; import { genericBlueprintDecoder } from "./generic-decoder"; import { sowBlueprintDecoder } from "./sow-decoder"; @@ -17,7 +19,7 @@ export interface BlueprintDecoder { } export interface DecodedBlueprintResult { - type: "sow" | "convertUp" | "generic"; + type: "sow" | "convertUp" | "automateClaim" | "generic"; functionName: string; params: any; } @@ -26,6 +28,7 @@ export const BLUEPRINT_REGISTRY: Record = { [SOW_BLUEPRINT_V0_SELECTOR]: sowBlueprintDecoder, [SOW_BLUEPRINT_REFERRAL_V0_SELECTOR]: sowBlueprintReferralDecoder, [CONVERT_UP_BLUEPRINT_V0_SELECTOR]: convertUpBlueprintDecoder, + [AUTOMATE_CLAIM_BLUEPRINT_SELECTOR]: automateClaimBlueprintDecoder, } as const; export function getBlueprintDecoder(selector: string): BlueprintDecoder | null { @@ -71,4 +74,4 @@ export function decodeBlueprintCallData(callData: string): DecodedBlueprintResul return genericResult; } -export type BlueprintType = "sow" | "convertUp" | "auto"; +export type BlueprintType = "sow" | "convertUp" | "automateClaim" | "auto"; diff --git a/src/lib/Tractor/claimOrder/index.ts b/src/lib/Tractor/claimOrder/index.ts new file mode 100644 index 00000000..d334add8 --- /dev/null +++ b/src/lib/Tractor/claimOrder/index.ts @@ -0,0 +1,2 @@ +export * from "./tractor-claim"; +export * from "./tractor-claim-types"; diff --git a/src/lib/Tractor/claimOrder/tractor-claim-types.ts b/src/lib/Tractor/claimOrder/tractor-claim-types.ts new file mode 100644 index 00000000..01f0c0e0 --- /dev/null +++ b/src/lib/Tractor/claimOrder/tractor-claim-types.ts @@ -0,0 +1,119 @@ +// ──────────────────────────────────────────────────────────────────────────────── +// AutomateClaim Blueprint — TypeScript Type Definitions +// ──────────────────────────────────────────────────────────────────────────────── + +/** + * Frequency preset for claim operation thresholds. + * - "high": frequent execution (low thresholds) + * - "medium": moderate execution frequency + * - "low": infrequent execution (high thresholds) + * - "custom": user-defined threshold value + */ +export type ClaimFrequencyPreset = "high" | "medium" | "low" | "custom"; + +// ──────────────────────────────────────────────────────────────────────────────── +// Contract Struct Types (match ABI exactly) +// ──────────────────────────────────────────────────────────────────────────────── + +/** + * Per-field harvest configuration. + * Maps to `AutomateClaimBlueprint.FieldHarvestConfig` in the contract. + */ +export interface FieldHarvestConfig { + fieldId: bigint; + minHarvestAmount: bigint; +} + +/** + * Claim parameters for the AutomateClaimBlueprint contract. + * Maps to `AutomateClaimBlueprint.AutomateClaimParams` in the contract. + * + * Disabled operations use MAX_UINT256 for their min amounts. + * Harvest is disabled by passing an empty `fieldHarvestConfigs` array. + */ +export interface AutomateClaimParams { + minMowAmount: bigint; + minTwaDeltaB: bigint; + minPlantAmount: bigint; + fieldHarvestConfigs: FieldHarvestConfig[]; + minRinseAmount: bigint; + minUnripeClaimAmount: bigint; + sourceTokenIndices: readonly number[]; + maxGrownStalkPerBdv: bigint; + slippageRatio: bigint; +} + +/** + * Extended operator parameters with per-operation tip amounts. + * Maps to `AutomateClaimBlueprint.OperatorParamsExtended` in the contract. + */ +export interface OperatorParamsExtended { + baseOpParams: { + whitelistedOperators: readonly `0x${string}`[]; + tipAddress: `0x${string}`; + operatorTipAmount: bigint; + }; + mowTipAmount: bigint; + plantTipAmount: bigint; + harvestTipAmount: bigint; + rinseTipAmount: bigint; + unripeClaimTipAmount: bigint; +} + +/** + * Top-level struct passed to the `automateClaimBlueprint` contract function. + * Maps to `AutomateClaimBlueprint.AutomateClaimBlueprintStruct` in the contract. + */ +export interface AutomateClaimBlueprintStruct { + claimParams: AutomateClaimParams; + opParams: OperatorParamsExtended; +} + +// ──────────────────────────────────────────────────────────────────────────────── +// Decoded Blueprint Data (for display / visualization) +// ──────────────────────────────────────────────────────────────────────────────── + +/** + * Decoded and display-ready representation of an AutomateClaim blueprint. + * Includes both raw bigint values and human-readable string representations. + */ +export interface AutomateClaimBlueprintData { + claimParams: { + minMowAmount: bigint; + minMowAmountAsString: string; + minTwaDeltaB: bigint; + minTwaDeltaBAsString: string; + minPlantAmount: bigint; + minPlantAmountAsString: string; + fieldHarvestConfigs: Array<{ + fieldId: bigint; + minHarvestAmount: bigint; + minHarvestAmountAsString: string; + }>; + minRinseAmount: bigint; + minRinseAmountAsString: string; + minUnripeClaimAmount: bigint; + minUnripeClaimAmountAsString: string; + sourceTokenIndices: readonly number[]; + maxGrownStalkPerBdv: bigint; + maxGrownStalkPerBdvAsString: string; + slippageRatio: bigint; + slippageRatioAsString: string; + }; + operatorParams: { + whitelistedOperators: readonly `0x${string}`[]; + tipAddress: `0x${string}`; + operatorTipAmount: bigint; + operatorTipAmountAsString: string; + mowTipAmount: bigint; + mowTipAmountAsString: string; + plantTipAmount: bigint; + plantTipAmountAsString: string; + harvestTipAmount: bigint; + harvestTipAmountAsString: string; + rinseTipAmount: bigint; + rinseTipAmountAsString: string; + unripeClaimTipAmount: bigint; + unripeClaimTipAmountAsString: string; + }; +} diff --git a/src/lib/Tractor/claimOrder/tractor-claim.ts b/src/lib/Tractor/claimOrder/tractor-claim.ts new file mode 100644 index 00000000..ef2853a3 --- /dev/null +++ b/src/lib/Tractor/claimOrder/tractor-claim.ts @@ -0,0 +1,388 @@ +import { TokenValue } from "@/classes/TokenValue"; +import { automateClaimBlueprintABI } from "@/constants/abi/AutomateClaimBlueprintABI"; +import { AUTOMATE_CLAIM_BLUEPRINT_ADDRESS } from "@/constants/address"; +import { useFarmerSilo } from "@/state/useFarmerSilo"; +import { AdvancedPipeCall } from "@/utils/types"; +import { PublicClient, decodeFunctionData, encodeFunctionData, maxUint256 } from "viem"; +import { base } from "viem/chains"; +import { + CreateTractorDataReturnType, + decodeEncodedTractorDataToAdvancedPipeCalls, + encodeTractorAndOptimizeDeposits, +} from "../core"; +import { + AutomateClaimBlueprintData, + AutomateClaimBlueprintStruct, + ClaimFrequencyPreset, + FieldHarvestConfig, +} from "./tractor-claim-types"; + +// ──────────────────────────────────────────────────────────────────────────────── +// PRESET VALUES +// ──────────────────────────────────────────────────────────────────────────────── + +export const CLAIM_PRESETS = { + mow: { + high: { value: 50, scaled: 50n * 10_000_000_000n }, + medium: { value: 100, scaled: 100n * 10_000_000_000n }, + low: { value: 1000, scaled: 1000n * 10_000_000_000n }, + }, + plant: { + high: { value: 10, scaled: 10_000_000n }, + medium: { value: 50, scaled: 50_000_000n }, + low: { value: 500, scaled: 500_000_000n }, + }, + harvest: { + high: { value: 10, scaled: 10_000_000n }, + medium: { value: 50, scaled: 50_000_000n }, + low: { value: 500, scaled: 500_000_000n }, + }, +} as const; + +// Default minTwaDeltaB: 10 PINTO × 1e6 +const DEFAULT_MIN_TWA_DELTA_B = 10_000_000n; + +// ──────────────────────────────────────────────────────────────────────────────── +// HELPERS +// ──────────────────────────────────────────────────────────────────────────────── + +/** + * Resolve a preset or custom value to a scaled bigint for a given operation. + * - Mow: value × 1e10 (stalk decimals) + * - Plant/Harvest: value × 1e6 (PINTO decimals) + */ +export function resolvePresetValue( + operation: "mow" | "plant" | "harvest", + preset: ClaimFrequencyPreset | null, + customValue: string, +): bigint { + if (preset && preset !== "custom") { + return CLAIM_PRESETS[operation][preset].scaled; + } + + // Custom value — scale based on operation type + const numericValue = parseFloat(customValue); + if (Number.isNaN(numericValue) || numericValue <= 0) { + throw new Error(`Invalid custom value for ${operation}: ${customValue}`); + } + + if (operation === "mow") { + // Stalk: value × 1e10 + return BigInt(Math.round(numericValue * 1e10)); + } + // PINTO: value × 1e6 + return BigInt(Math.round(numericValue * 1e6)); +} + +/** + * Calculate per-operation tip amounts based on which operations are enabled. + * Disabled operations get 0 tip. Rinse and UnripeClaim are always 0. + */ +export function calculateTips( + formValues: { mowEnabled: boolean; plantEnabled: boolean; harvestEnabled: boolean }, + tipPerExecution: bigint, +) { + return { + mowTipAmount: formValues.mowEnabled ? tipPerExecution : 0n, + plantTipAmount: formValues.plantEnabled ? tipPerExecution : 0n, + harvestTipAmount: formValues.harvestEnabled ? tipPerExecution : 0n, + rinseTipAmount: 0n, + unripeClaimTipAmount: 0n, + }; +} + +/** + * Estimate the total tip range for a given tip amount and number of enabled operations. + */ +export function estimatedTotalTipRange(tipPerExecution: bigint, enabledOpsCount: number) { + return { + min: tipPerExecution * 1n, + max: tipPerExecution * BigInt(enabledOpsCount), + }; +} + +// ──────────────────────────────────────────────────────────────────────────────── +// STRUCT BUILDER +// ──────────────────────────────────────────────────────────────────────────────── + +export interface AutomateClaimFormValues { + mowEnabled: boolean; + mowPreset: ClaimFrequencyPreset | null; + mowCustomValue: string; + + plantEnabled: boolean; + plantPreset: ClaimFrequencyPreset | null; + plantCustomValue: string; + + harvestEnabled: boolean; + harvestPreset: ClaimFrequencyPreset | null; + harvestCustomValue: string; + + // Advanced defaults + sourceTokenIndices?: number[]; + maxGrownStalkPerBdv?: string; + slippageRatio?: string; +} + +/** + * Build the AutomateClaimBlueprintStruct from form values. + */ +export function buildAutomateClaimStruct( + formValues: AutomateClaimFormValues, + tipPerExecution: bigint, + whitelistedOperators: readonly `0x${string}`[], +): AutomateClaimBlueprintStruct { + // Mow + const minMowAmount = formValues.mowEnabled + ? resolvePresetValue("mow", formValues.mowPreset, formValues.mowCustomValue) + : maxUint256; + + // Plant + const minPlantAmount = formValues.plantEnabled + ? resolvePresetValue("plant", formValues.plantPreset, formValues.plantCustomValue) + : maxUint256; + + // Harvest + const fieldHarvestConfigs: FieldHarvestConfig[] = formValues.harvestEnabled + ? [ + { + fieldId: 0n, + minHarvestAmount: resolvePresetValue("harvest", formValues.harvestPreset, formValues.harvestCustomValue), + }, + { + fieldId: 1n, + minHarvestAmount: resolvePresetValue("harvest", formValues.harvestPreset, formValues.harvestCustomValue), + }, + ] + : []; + + // Tips + const tips = calculateTips(formValues, tipPerExecution); + + const sourceTokenIndices = formValues.sourceTokenIndices ?? [255]; + const maxGrownStalkPerBdv = formValues.maxGrownStalkPerBdv + ? BigInt(Math.round(parseFloat(formValues.maxGrownStalkPerBdv) * 1e6)) + : BigInt(1e18); + const slippageRatio = formValues.slippageRatio + ? BigInt(Math.round(parseFloat(formValues.slippageRatio) * 1e18)) + : BigInt(1e18); + + return { + claimParams: { + minMowAmount, + minTwaDeltaB: DEFAULT_MIN_TWA_DELTA_B, + minPlantAmount, + fieldHarvestConfigs, + minRinseAmount: maxUint256, + minUnripeClaimAmount: maxUint256, + sourceTokenIndices, + maxGrownStalkPerBdv, + slippageRatio, + }, + opParams: { + baseOpParams: { + whitelistedOperators, + tipAddress: "0x0000000000000000000000000000000000000000" as `0x${string}`, + operatorTipAmount: tipPerExecution, + }, + ...tips, + }, + }; +} + +// ──────────────────────────────────────────────────────────────────────────────── +// CREATE ORDER (ENCODING) +// ──────────────────────────────────────────────────────────────────────────────── + +/** + * Creates encoded tractor data from form values. + * Follows the advancedFarm(advancedPipe(automateClaimBlueprint(...))) wrapping pattern. + */ +export async function createAutomateClaimTractorData({ + formValues, + tipPerExecution, + whitelistedOperators, + publicClient, + farmerDeposits, + userAddress, + protocolAddress, +}: { + formValues: AutomateClaimFormValues; + tipPerExecution: bigint; + whitelistedOperators: readonly `0x${string}`[]; + publicClient: PublicClient; + farmerDeposits?: ReturnType["deposits"]; + userAddress: `0x${string}`; + protocolAddress: `0x${string}`; +}): Promise { + // 1. Build struct from form values + const struct = buildAutomateClaimStruct(formValues, tipPerExecution, whitelistedOperators); + + // 2. Encode the automateClaimBlueprint function call + // Convert sourceTokenIndices from number[] to bigint[] for ABI encoding (uint256[]) + const abiStruct = { + ...struct, + claimParams: { + ...struct.claimParams, + sourceTokenIndices: struct.claimParams.sourceTokenIndices.map((i) => BigInt(i)), + }, + }; + + const blueprintCall = encodeFunctionData({ + abi: automateClaimBlueprintABI, + functionName: "automateClaimBlueprint", + args: [abiStruct], + }); + + // 3. Create advancedPipe struct targeting the blueprint contract + const advPipeStruct: AdvancedPipeCall = { + target: AUTOMATE_CLAIM_BLUEPRINT_ADDRESS, + callData: blueprintCall, + clipboard: "0x0000" as `0x${string}`, + }; + + // 4. Wrap with encodeTractorAndOptimizeDeposits + const { data, depositOptimizationCalls } = await encodeTractorAndOptimizeDeposits( + { client: publicClient, protocolAddress, farmerAddress: userAddress }, + advPipeStruct, + farmerDeposits, + ); + + return { + data, + operatorPasteInstrs: [], + rawCall: blueprintCall, + depositOptimizationCalls, + }; +} + +// ──────────────────────────────────────────────────────────────────────────────── +// DECODE BLUEPRINT DATA +// ──────────────────────────────────────────────────────────────────────────────── + +/** + * Decode an automateClaimBlueprint call from advancedPipe calls. + * Extracts the inner function call and decodes using the ABI. + */ +export function decodeAutomateClaimBlueprintFromAdvancedPipe( + calls: readonly AdvancedPipeCall[] | undefined, +): AutomateClaimBlueprintStruct | null { + if (!calls?.length) { + console.debug("[Tractor/decodeAutomateClaimBlueprintFromAdvancedPipe] No calls provided. Returning null."); + return null; + } + + const callData = calls[0].callData; + + try { + const decoded = decodeFunctionData({ + abi: automateClaimBlueprintABI, + data: callData, + }); + + const params = decoded.args?.[0]; + if (!params) return null; + + return params as unknown as AutomateClaimBlueprintStruct; + } catch (error) { + console.error("[Tractor/decodeAutomateClaimBlueprintFromAdvancedPipe] Failed to decode:", error); + return null; + } +} + +/** + * Decode encoded tractor data (advancedFarm → advancedPipe → automateClaimBlueprint). + * Entry point for decoding a full encoded blueprint. + */ +export function decodeAutomateClaimBlueprint(encodedData: `0x${string}`): AutomateClaimBlueprintStruct | null { + try { + console.debug("[Tractor/decodeAutomateClaimBlueprint] Attempting to decode, data length:", encodedData.length); + const pipeCalls = decodeEncodedTractorDataToAdvancedPipeCalls(encodedData, "automateClaim"); + console.debug("[Tractor/decodeAutomateClaimBlueprint] Pipe calls result:", pipeCalls?.length ?? "undefined"); + + if (pipeCalls?.length) { + const result = decodeAutomateClaimBlueprintFromAdvancedPipe(pipeCalls); + console.debug("[Tractor/decodeAutomateClaimBlueprint] Decode result:", result ? "success" : "null"); + return result; + } + } catch (e) { + console.error("[Tractor/decodeAutomateClaimBlueprint] Failed to decode:", e); + } + + return null; +} + +// ──────────────────────────────────────────────────────────────────────────────── +// TRANSFORM (raw decoded → display-ready data) +// ──────────────────────────────────────────────────────────────────────────────── + +/** + * Transform raw decoded AutomateClaimBlueprintStruct into display-ready + * AutomateClaimBlueprintData with string representations. + */ +export function transformAutomateClaimRequisitionEvent( + params: unknown | null, + _chainId: number = base.id, +): AutomateClaimBlueprintData | null { + try { + if (!params || typeof params !== "object") { + console.debug("[Tractor/transformAutomateClaimRequisitionEvent] Invalid params."); + return null; + } + + const struct = params as AutomateClaimBlueprintStruct; + + if (!struct.claimParams || !struct.opParams) { + console.debug("[Tractor/transformAutomateClaimRequisitionEvent] Missing claimParams or opParams."); + return null; + } + + const cp = struct.claimParams; + const op = struct.opParams; + + return { + claimParams: { + minMowAmount: cp.minMowAmount, + minMowAmountAsString: TokenValue.fromBlockchain(cp.minMowAmount, 10).toHuman(), + minTwaDeltaB: cp.minTwaDeltaB, + minTwaDeltaBAsString: TokenValue.fromBlockchain(cp.minTwaDeltaB, 6).toHuman(), + minPlantAmount: cp.minPlantAmount, + minPlantAmountAsString: TokenValue.fromBlockchain(cp.minPlantAmount, 6).toHuman(), + fieldHarvestConfigs: cp.fieldHarvestConfigs.map((config) => ({ + fieldId: config.fieldId, + minHarvestAmount: config.minHarvestAmount, + minHarvestAmountAsString: TokenValue.fromBlockchain(config.minHarvestAmount, 6).toHuman(), + })), + minRinseAmount: cp.minRinseAmount, + minRinseAmountAsString: TokenValue.fromBlockchain(cp.minRinseAmount, 6).toHuman(), + minUnripeClaimAmount: cp.minUnripeClaimAmount, + minUnripeClaimAmountAsString: TokenValue.fromBlockchain(cp.minUnripeClaimAmount, 6).toHuman(), + sourceTokenIndices: cp.sourceTokenIndices, + maxGrownStalkPerBdv: cp.maxGrownStalkPerBdv, + maxGrownStalkPerBdvAsString: TokenValue.fromBlockchain(cp.maxGrownStalkPerBdv, 6).toHuman(), + slippageRatio: cp.slippageRatio, + slippageRatioAsString: TokenValue.fromBlockchain(cp.slippageRatio, 18).toHuman(), + }, + operatorParams: { + whitelistedOperators: op.baseOpParams.whitelistedOperators, + tipAddress: op.baseOpParams.tipAddress, + operatorTipAmount: op.baseOpParams.operatorTipAmount, + operatorTipAmountAsString: TokenValue.fromBlockchain(op.baseOpParams.operatorTipAmount, 6).toHuman(), + mowTipAmount: op.mowTipAmount, + mowTipAmountAsString: TokenValue.fromBlockchain(op.mowTipAmount, 6).toHuman(), + plantTipAmount: op.plantTipAmount, + plantTipAmountAsString: TokenValue.fromBlockchain(op.plantTipAmount, 6).toHuman(), + harvestTipAmount: op.harvestTipAmount, + harvestTipAmountAsString: TokenValue.fromBlockchain(op.harvestTipAmount, 6).toHuman(), + rinseTipAmount: op.rinseTipAmount, + rinseTipAmountAsString: TokenValue.fromBlockchain(op.rinseTipAmount, 6).toHuman(), + unripeClaimTipAmount: op.unripeClaimTipAmount, + unripeClaimTipAmountAsString: TokenValue.fromBlockchain(op.unripeClaimTipAmount, 6).toHuman(), + }, + }; + } catch (e) { + console.debug("[Tractor/transformAutomateClaimRequisitionEvent] Failed to transform:", e); + } + + return null; +} diff --git a/src/lib/Tractor/core/constants.ts b/src/lib/Tractor/core/constants.ts index f74cae17..dd8b5a5a 100644 --- a/src/lib/Tractor/core/constants.ts +++ b/src/lib/Tractor/core/constants.ts @@ -7,6 +7,7 @@ import { LowStalkDepositsMode } from "./shared-tractor-types"; // Block number at which Tractor was deployed - use this as starting point for event queries export const TRACTOR_DEPLOYMENT_BLOCK = 28930876n; export const TRACTOR_DEPLOYMENT_BLOCK_CONVERT_UP = 37073327n; +export const TRACTOR_DEPLOYMENT_BLOCK_AUTOMATE_CLAIM = 41417660n; export const TRACTOR_TOKEN_STRATEGY_INDICIES = { LOWEST_PRICE: 254, @@ -16,6 +17,7 @@ export const TRACTOR_TOKEN_STRATEGY_INDICIES = { export const TRACTOR_DEPLOYMENT_BLOCKS_BY_TYPE = { sowBlueprintv0: TRACTOR_DEPLOYMENT_BLOCK, convertUpBlueprint: TRACTOR_DEPLOYMENT_BLOCK_CONVERT_UP, + automateClaimBlueprint: TRACTOR_DEPLOYMENT_BLOCK_AUTOMATE_CLAIM, } as const; export const LOW_STALK_DEPOSIT_MODES_TO_LABELS = { diff --git a/src/lib/Tractor/core/shared-tractor-types.ts b/src/lib/Tractor/core/shared-tractor-types.ts index 5e2c155b..5362efc4 100644 --- a/src/lib/Tractor/core/shared-tractor-types.ts +++ b/src/lib/Tractor/core/shared-tractor-types.ts @@ -1,9 +1,9 @@ import { TV } from "@/classes/TokenValue"; import { Address } from "viem"; -export type RequisitionType = "sowBlueprintv0" | "convertUpBlueprint" | "unknown"; +export type RequisitionType = "sowBlueprintv0" | "convertUpBlueprint" | "automateClaimBlueprint" | "unknown"; -export type TractorBlueprintType = "sow" | "convertUp"; +export type TractorBlueprintType = "sow" | "convertUp" | "automateClaim"; export interface CreateTractorDataReturnType { data: `0x${string}`; diff --git a/src/lib/Tractor/index.ts b/src/lib/Tractor/index.ts index 5eb37cce..0396ce32 100644 --- a/src/lib/Tractor/index.ts +++ b/src/lib/Tractor/index.ts @@ -2,6 +2,7 @@ import TractorAPI from "./api"; export * from "./types"; export * from "./convertUp"; +export * from "./claimOrder"; export * from "./blueprint"; export * from "./sowOrder"; export * from "./utils"; diff --git a/src/lib/Tractor/requisitions/tractor-requisition.ts b/src/lib/Tractor/requisitions/tractor-requisition.ts index 283347b8..35f4f0ad 100644 --- a/src/lib/Tractor/requisitions/tractor-requisition.ts +++ b/src/lib/Tractor/requisitions/tractor-requisition.ts @@ -1,4 +1,5 @@ import { TV, TokenValue } from "@/classes/TokenValue"; +import { automateClaimBlueprintABI } from "@/constants/abi/AutomateClaimBlueprintABI"; import { sowBlueprintReferralV0ABI } from "@/constants/abi/SowBlueprintReferralV0ABI"; import { sowBlueprintv0ABI } from "@/constants/abi/SowBlueprintv0ABI"; import { convertUpBlueprintV0ABI } from "@/constants/abi/convertUpBlueprintV0ABI"; @@ -14,6 +15,8 @@ import { SignableMessage, decodeFunctionData } from "viem"; import { PublicClient } from "viem"; import { base } from "viem/chains"; import { decodeBlueprintCallData } from "../blueprint-decoders"; +import { transformAutomateClaimRequisitionEvent } from "../claimOrder/tractor-claim"; +import { AutomateClaimBlueprintData, AutomateClaimBlueprintStruct } from "../claimOrder/tractor-claim-types"; import { ConvertUpBlueprintStruct } from "../convertUp"; import { Requisition, @@ -153,7 +156,12 @@ type SelectRequisitionTypeArgs = { data: Awaited>; }; -const combinedABI = [...sowBlueprintv0ABI, ...sowBlueprintReferralV0ABI, ...convertUpBlueprintV0ABI] as const; +const combinedABI = [ + ...sowBlueprintv0ABI, + ...sowBlueprintReferralV0ABI, + ...convertUpBlueprintV0ABI, + ...automateClaimBlueprintABI, +] as const; type BaseDecodedTractorRequisition = { type: RequisitionType; @@ -169,7 +177,12 @@ type DecodedConvertUpRequisition = BaseDecodedTractorRequisition & { data: NonNullable>; }; -type DecodedTractorRequisition = DecodedSowRequisition | DecodedConvertUpRequisition; +type DecodedAutomateClaimRequisition = BaseDecodedTractorRequisition & { + type: "automateClaimBlueprint"; + data: NonNullable>; +}; + +type DecodedTractorRequisition = DecodedSowRequisition | DecodedConvertUpRequisition | DecodedAutomateClaimRequisition; const blueprintTransformerLookup = { sowBlueprintv0: { @@ -191,6 +204,10 @@ const blueprintTransformerLookup = { transformer: transformConvertUpRequisitionEvent, type: "convertUpBlueprint", }, + automateClaimBlueprint: { + transformer: transformAutomateClaimRequisitionEvent, + type: "automateClaimBlueprint", + }, } as const; export const decodeTractorBlueprint = ( @@ -279,9 +296,11 @@ export const getSelectRequisitionType = (requisitionsType: MayArray[]; convertUpBlueprint: TractorRequisitionEvent>[]; + automateClaimBlueprint: TractorRequisitionEvent[]; } = { sowBlueprintv0: [], convertUpBlueprint: [], + automateClaimBlueprint: [], }; for (const event of publishEvents) { @@ -330,6 +349,15 @@ export const getSelectRequisitionType = (requisitionsType: MayArray
Deposit Whitelist
-
- These are Deposits which are currently incentivized by Pinto. +
+
+ These are Deposits which are currently incentivized by Pinto. +
+ setShowAutomateClaimDialog(true)} + > + Automate your Silo with Tractor 🚜 +
@@ -180,6 +190,8 @@ function Silo() {
+ + ); } diff --git a/src/pages/overview/FarmerOverview.tsx b/src/pages/overview/FarmerOverview.tsx index 2a8b3cba..9a4ae628 100644 --- a/src/pages/overview/FarmerOverview.tsx +++ b/src/pages/overview/FarmerOverview.tsx @@ -569,4 +569,4 @@ const Overview = () => { export default Overview; -const ORDER_TYPES: OrderType[] = ["sow", "convertUp"] as const; +const ORDER_TYPES: OrderType[] = ["sow", "convertUp", "automateClaim"] as const; diff --git a/src/pages/silo/RewardsClaim.tsx b/src/pages/silo/RewardsClaim.tsx index 4cb0dd0d..26a25462 100644 --- a/src/pages/silo/RewardsClaim.tsx +++ b/src/pages/silo/RewardsClaim.tsx @@ -1,4 +1,5 @@ import { TokenValue } from "@/classes/TokenValue"; +import { SpecifyConditionsDialog } from "@/components/Tractor/AutomateClaim"; import { Button } from "@/components/ui/Button"; import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/Card"; import { Label } from "@/components/ui/Label"; @@ -8,6 +9,7 @@ import { useFarmerSilo } from "@/state/useFarmerSilo"; import { useSiloData } from "@/state/useSiloData"; import useTokenData from "@/state/useTokenData"; import { useQueryClient } from "@tanstack/react-query"; +import { useState } from "react"; import { toast } from "sonner"; import { encodeFunctionData } from "viem"; import { useAccount } from "wagmi"; @@ -17,6 +19,7 @@ function RewardsClaim() { const chainId = useChainId(); const account = useAccount(); const data = useFarmerSilo(); + const [showAutomateClaimDialog, setShowAutomateClaimDialog] = useState(false); const siloData = useSiloData(); const { mainToken: BEAN, whitelistedTokens: SILO_WHITELIST } = useTokenData(); const stalkRewards = siloData.tokenData.get(BEAN)?.rewards.stalk; @@ -68,35 +71,45 @@ function RewardsClaim() { } return ( - - - Rewards - Claim your Silo Rewards - - -
- -
{data.earnedBeansBalance.toHuman("short")}
-
-
- -
{data.earnedBeansBalance.mul(stalkRewards ?? TokenValue.ZERO).toHuman("short")}
-
-
- -
{data.earnedBeansBalance.mul(seedsRewards ?? TokenValue.ZERO).toHuman("short")}
-
-
- -
{grownStalk.toHuman("short")}
-
-
- - - -
+ <> + + + Rewards + Claim your Silo Rewards + + +
+ +
{data.earnedBeansBalance.toHuman("short")}
+
+
+ +
{data.earnedBeansBalance.mul(stalkRewards ?? TokenValue.ZERO).toHuman("short")}
+
+
+ +
{data.earnedBeansBalance.mul(seedsRewards ?? TokenValue.ZERO).toHuman("short")}
+
+
+ +
{grownStalk.toHuman("short")}
+
+
+ + + setShowAutomateClaimDialog(true)} + > + Automate with Tractor + + +
+ + + ); } diff --git a/src/pages/silo/SiloConvertUpContent.tsx b/src/pages/silo/SiloConvertUpContent.tsx index f398b776..c8db5efc 100644 --- a/src/pages/silo/SiloConvertUpContent.tsx +++ b/src/pages/silo/SiloConvertUpContent.tsx @@ -5,7 +5,7 @@ import ConvertUpOrderForm from "@/components/Tractor/ConvertUpOrderForm"; import ConvertUpOrderbookDialog from "@/components/Tractor/ConvertUpOrderbook"; import ConvertUpTractorOrderBookChart from "@/components/Tractor/ConvertUpTractorOrderBookChart"; import ConvertUpTractorOrders from "@/components/Tractor/ConvertUpTractorOrders"; -import { TractorConvertUpOrdersPanel } from "@/components/Tractor/farmer-orders/TractorOrdersPanel"; +import TractorOrdersPanelGeneric from "@/components/Tractor/farmer-orders/TractorOrdersPanel"; import CompactSeasonalLineChart from "@/components/charts/CompactSeasonalLineChart"; import { tabToSeasonalLookback } from "@/components/charts/SeasonalChart"; import TimeTabsSelector, { TimeTab } from "@/components/charts/TimeTabs"; @@ -31,6 +31,8 @@ import SiloConvertUpStats from "./SiloConvertUpStats"; const mobileOnlyActions = ["orders"] as const; const actions = [...mobileOnlyActions, "tractor"] as const; +const SILO_ORDER_TYPES = ["convertUp", "automateClaim"] as const; + // Pull this out to minimize re-renders const useConvertUpTractorActiveTab = () => { const isMobile = useIsMobile(); @@ -116,7 +118,7 @@ export const SiloConvertUpContent = () => { setOpen(true)} /> - +
diff --git a/src/state/tractor/useTractorAutomateClaimOrders.ts b/src/state/tractor/useTractorAutomateClaimOrders.ts new file mode 100644 index 00000000..404bdfbf --- /dev/null +++ b/src/state/tractor/useTractorAutomateClaimOrders.ts @@ -0,0 +1,106 @@ +import { defaultQuerySettingsMedium } from "@/constants/query"; +import { useProtocolAddress } from "@/hooks/pinto/useProtocolAddress"; +import { TractorRequisitionData, TractorRequisitionEvent } from "@/lib/Tractor"; +import { decodeBlueprintCallData } from "@/lib/Tractor/blueprint-decoders"; +import { decodeAutomateClaimBlueprint } from "@/lib/Tractor/claimOrder"; +import { AutomateClaimBlueprintStruct } from "@/lib/Tractor/claimOrder/tractor-claim-types"; +import { TRACTOR_DEPLOYMENT_BLOCK } from "@/lib/Tractor/core"; +import { fetchTractorEvents } from "@/lib/Tractor/events/tractor-events"; +import { stringEq } from "@/utils/string"; +import { useQuery } from "@tanstack/react-query"; +import { useCallback } from "react"; +import { useChainId, usePublicClient } from "wagmi"; + +interface UseTractorAutomateClaimOrderbookOptions { + address?: `0x${string}`; + filterOutCompleted?: boolean; + enabled?: boolean; +} + +export function useTractorAutomateClaimOrderbook({ + address, + enabled = true, +}: UseTractorAutomateClaimOrderbookOptions = {}) { + const chainId = useChainId(); + const client = usePublicClient({ chainId }); + const diamond = useProtocolAddress(); + + const query = useQuery({ + queryKey: ["tractor", "automateClaimOrders", address, chainId], + queryFn: async (): Promise[]> => { + if (!client || !diamond) return []; + + // Use the general deployment block to catch all events + const { publishEvents, cancelledHashes } = await fetchTractorEvents(client, diamond, TRACTOR_DEPLOYMENT_BLOCK); + + console.debug("[useTractorAutomateClaimOrderbook] Total publish events:", publishEvents.length); + + const automateClaimOrders: TractorRequisitionEvent[] = []; + + for (const event of publishEvents) { + const requisition = event.args?.requisition as TractorRequisitionData; + if (!requisition?.blueprint || !requisition?.blueprintHash || !requisition?.signature) { + continue; + } + + // Filter by publisher if address is provided + if (address && !stringEq(requisition.blueprint.publisher, address)) { + continue; + } + + const blueprintData = requisition.blueprint.data; + if (!blueprintData) continue; + + // First try the blueprint-decoders system to check if this is an automateClaim type + const blueprintDecoded = decodeBlueprintCallData(blueprintData); + + // Try to decode as automateClaimBlueprint using the direct decoder + const decodedData = decodeAutomateClaimBlueprint(blueprintData); + + // Accept if either decoder identifies this as automateClaim + if (!decodedData && blueprintDecoded?.type !== "automateClaim") { + continue; + } + + console.debug("[useTractorAutomateClaimOrderbook] Found automateClaim order:", { + hash: requisition.blueprintHash, + publisher: requisition.blueprint.publisher, + decodedData: !!decodedData, + blueprintDecodedType: blueprintDecoded?.type, + }); + + const isCancelled = cancelledHashes.has(requisition.blueprintHash); + + // If direct decode worked, use it. Otherwise try to extract from blueprint-decoders result + let finalDecodedData = decodedData; + if (!finalDecodedData && blueprintDecoded?.type === "automateClaim" && blueprintDecoded.params) { + // The blueprint-decoders system decoded the params directly from the inner call + finalDecodedData = blueprintDecoded.params as AutomateClaimBlueprintStruct; + } + + if (!finalDecodedData) continue; + + automateClaimOrders.push({ + requisition, + blockNumber: Number(event.blockNumber ?? 0), + timestamp: undefined, + isCancelled, + requisitionType: "automateClaimBlueprint", + decodedData: finalDecodedData, + }); + } + + console.debug("[useTractorAutomateClaimOrderbook] Found orders:", automateClaimOrders.length); + + return automateClaimOrders; + }, + enabled: !!client && !!diamond && enabled, + ...defaultQuerySettingsMedium, + }); + + const refetch = useCallback(async () => { + return query.refetch(); + }, [query]); + + return { ...query, refetch } as const; +} diff --git a/src/state/tractor/useTractorClaimOrders.ts b/src/state/tractor/useTractorClaimOrders.ts new file mode 100644 index 00000000..ae7b25d1 --- /dev/null +++ b/src/state/tractor/useTractorClaimOrders.ts @@ -0,0 +1,8 @@ +/** + * Re-export of automate claim orderbook hook. + * + * The design doc lists this module path (`useTractorClaimOrders`) as the + * canonical location for fetching & decoding automate-claim orders. + * The actual implementation lives in `useTractorAutomateClaimOrders.ts`. + */ +export { useTractorAutomateClaimOrderbook } from "./useTractorAutomateClaimOrders"; From 1f1421610b7f8ce4d8c164397a945e2bf9aee278 Mon Sep 17 00:00:00 2001 From: feyyazcigim Date: Wed, 25 Feb 2026 01:23:44 +0300 Subject: [PATCH 22/22] refactor: fix automated order indext and refactor implementation --- src/__tests/conditionSection.test.ts | 36 ---------- src/components/ReviewTractorOrderDialog.tsx | 30 ++++---- .../AutomateClaim/ConditionSection.tsx | 5 +- .../AutomateClaim/SpecifyConditionsDialog.tsx | 17 ++--- src/components/Tractor/AutomateClaim/index.ts | 1 - .../FarmerTractorAutomateClaimOrderCard.tsx | 34 +++++---- .../TractorFarmerMixedOrders.types.ts | 10 +-- .../TractorFarmerMixedOrders.utils.ts | 18 +++-- .../TractorFarmerOrderTypeRegistry.tsx | 12 ++-- .../farmer-orders/TractorOrdersPanel.tsx | 3 +- src/constants/address.ts | 2 +- src/hooks/tractor/useAutomateClaimOrder.ts | 8 +-- .../claimOrder/automate-claim-helpers.ts | 70 +++++++++++++++++++ src/lib/Tractor/claimOrder/index.ts | 1 + src/lib/Tractor/claimOrder/tractor-claim.ts | 17 +---- src/state/queryKeys.ts | 6 ++ .../tractor/useTractorAutomateClaimOrders.ts | 55 ++++++--------- src/state/tractor/useTractorClaimOrders.ts | 8 --- 18 files changed, 169 insertions(+), 164 deletions(-) delete mode 100644 src/__tests/conditionSection.test.ts create mode 100644 src/lib/Tractor/claimOrder/automate-claim-helpers.ts delete mode 100644 src/state/tractor/useTractorClaimOrders.ts diff --git a/src/__tests/conditionSection.test.ts b/src/__tests/conditionSection.test.ts deleted file mode 100644 index fc57a292..00000000 --- a/src/__tests/conditionSection.test.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { CLAIM_PRESETS } from "@/lib/Tractor/claimOrder/tractor-claim"; -import { describe, expect, it } from "vitest"; - -/** - * Unit tests for ConditionSection preset configuration and display logic. - * Validates Requirements 3.1-3.10, 6.2-6.5 - */ -describe("ConditionSection — Preset Configuration", () => { - it("should have correct preset values for Mow (Req 3.1-3.3)", () => { - expect(CLAIM_PRESETS.mow.high.value).toBe(50); - expect(CLAIM_PRESETS.mow.medium.value).toBe(100); - expect(CLAIM_PRESETS.mow.low.value).toBe(1000); - }); - - it("should have correct preset values for Plant (Req 3.4-3.6)", () => { - expect(CLAIM_PRESETS.plant.high.value).toBe(10); - expect(CLAIM_PRESETS.plant.medium.value).toBe(50); - expect(CLAIM_PRESETS.plant.low.value).toBe(500); - }); - - it("should have correct preset values for Harvest (Req 3.7-3.9)", () => { - expect(CLAIM_PRESETS.harvest.high.value).toBe(10); - expect(CLAIM_PRESETS.harvest.medium.value).toBe(50); - expect(CLAIM_PRESETS.harvest.low.value).toBe(500); - }); - - it("should have exactly 3 preset levels (high, medium, low) per operation", () => { - for (const op of ["mow", "plant", "harvest"] as const) { - const keys = Object.keys(CLAIM_PRESETS[op]); - expect(keys).toContain("high"); - expect(keys).toContain("medium"); - expect(keys).toContain("low"); - expect(keys).toHaveLength(3); - } - }); -}); diff --git a/src/components/ReviewTractorOrderDialog.tsx b/src/components/ReviewTractorOrderDialog.tsx index 5918443e..437ec20e 100644 --- a/src/components/ReviewTractorOrderDialog.tsx +++ b/src/components/ReviewTractorOrderDialog.tsx @@ -4,13 +4,15 @@ import { useProtocolAddress } from "@/hooks/pinto/useProtocolAddress"; import useSignTractorBlueprint from "@/hooks/tractor/useSignTractorBlueprint"; import useTransaction from "@/hooks/useTransaction"; import { Blueprint, PublisherTractorExecution, Requisition, useGetBlueprintHash } from "@/lib/Tractor"; +import { queryKeys } from "@/state/queryKeys"; import { cn } from "@/utils/utils"; import { CheckIcon } from "@radix-ui/react-icons"; +import { useQueryClient } from "@tanstack/react-query"; import { useMemo, useState } from "react"; import { useNavigate } from "react-router-dom"; import { toast } from "sonner"; import { encodeFunctionData } from "viem"; -import { useAccount } from "wagmi"; +import { useAccount, usePublicClient } from "wagmi"; import { Col, Row } from "./Container"; import LoadingSpinner from "./LoadingSpinner"; import { HighlightedCallData } from "./Tractor/HighlightedCallData"; @@ -64,6 +66,8 @@ export default function ReviewTractorOrderDialog({ const [decodeAbi, setDecodeAbi] = useState(false); const protocolAddress = useProtocolAddress(); const navigate = useNavigate(); + const queryClient = useQueryClient(); + const publicClient = usePublicClient(); // Get order type configuration from registry // Memoize the order config to avoid re-rendering the component on every render @@ -114,6 +118,8 @@ export default function ReviewTractorOrderDialog({ try { setSubmitting(true); + let txHash: `0x${string}` | undefined; + // Check if we need to include deposit optimization calls if (depositOptimizationCalls && depositOptimizationCalls.length > 0) { console.debug(`Publishing requisition with ${depositOptimizationCalls.length} deposit optimization calls`); @@ -130,7 +136,7 @@ export default function ReviewTractorOrderDialog({ const farmCalls = [...depositOptimizationCalls, publishRequisitionCall]; // Execute as farm call - await writeWithEstimateGas({ + txHash = await writeWithEstimateGas({ address: protocolAddress, abi: diamondABI, functionName: "farm", @@ -140,7 +146,7 @@ export default function ReviewTractorOrderDialog({ console.debug("Publishing requisition without deposit optimization"); // Call publish requisition directly (like before) - await writeWithEstimateGas({ + txHash = await writeWithEstimateGas({ address: protocolAddress, abi: diamondABI, functionName: "publishRequisition", @@ -148,11 +154,13 @@ export default function ReviewTractorOrderDialog({ }); } - // Success handling - toast.success("Order published successfully"); + // Wait for tx confirmation before refreshing data + if (txHash && publicClient) { + await publicClient.waitForTransactionReceipt({ hash: txHash, confirmations: 1 }); + } - // Close the dialog - onOpenChange(false); + // Invalidate all tractor queries to refresh order lists + queryClient.invalidateQueries({ queryKey: [queryKeys.base.tractor] }); // Navigate to the Field page with tractor tab active if (orderData.type === "sow") { @@ -160,14 +168,10 @@ export default function ReviewTractorOrderDialog({ } // Call the parent success callback to refresh data - if (onSuccess) { - onSuccess(); - } + onSuccess?.(); // Call the onOrderPublished callback if provided - if (onOrderPublished) { - onOrderPublished(); - } + onOrderPublished?.(); } catch (error) { console.error("Error publishing requisition:", error); } finally { diff --git a/src/components/Tractor/AutomateClaim/ConditionSection.tsx b/src/components/Tractor/AutomateClaim/ConditionSection.tsx index 0f37f545..50e51142 100644 --- a/src/components/Tractor/AutomateClaim/ConditionSection.tsx +++ b/src/components/Tractor/AutomateClaim/ConditionSection.tsx @@ -1,5 +1,6 @@ import { Col, Row } from "@/components/Container"; import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ui/Accordion"; +import { isValidCustomValue } from "@/lib/Tractor/claimOrder/automate-claim-helpers"; import type { ClaimFrequencyPreset } from "@/lib/Tractor/claimOrder/tractor-claim-types"; import { cn } from "@/utils/utils"; import { CheckIcon } from "@radix-ui/react-icons"; @@ -75,9 +76,7 @@ export const ConditionSection = ({ // When "custom" is selected, show inline input instead of button if (btn.key === "custom" && isSelected) { - const normalized = customValue.trim().replace(",", "."); - const num = Number(normalized); - const isValid = normalized !== "" && !Number.isNaN(num) && num > 0; + const isValid = isValidCustomValue(customValue); return (
{ - const normalized = v.trim().replace(",", "."); - if (!normalized) return false; - const num = Number(normalized); - return !Number.isNaN(num) && num > 0; - }; - const hasInvalidCustom = (mowPreset === "custom" && !isValidCustomValue(mowCustomValue)) || (plantPreset === "custom" && !isValidCustomValue(plantCustomValue)) || @@ -200,7 +196,7 @@ export const SpecifyConditionsDialog = ({ open, onOpenChange }: SpecifyCondition - + Specify conditions @@ -215,7 +211,7 @@ export const SpecifyConditionsDialog = ({ open, onOpenChange }: SpecifyCondition {/* Condition Sections */} - + { + queryClient.invalidateQueries({ queryKey: [queryKeys.base.tractor] }); form.reset({ ...defaultAutomateClaimValues }); setShowReviewDialog(false); onOpenChange(false); diff --git a/src/components/Tractor/AutomateClaim/index.ts b/src/components/Tractor/AutomateClaim/index.ts index ca3a9a8f..04eabd7a 100644 --- a/src/components/Tractor/AutomateClaim/index.ts +++ b/src/components/Tractor/AutomateClaim/index.ts @@ -1,4 +1,3 @@ -export { AutomateClaimProvider, useAutomateClaimContext } from "./AutomateClaimContext"; export { SpecifyConditionsDialog } from "./SpecifyConditionsDialog"; export { ConditionSection } from "./ConditionSection"; export { AutomateClaimVisualization } from "./AutomateClaimVisualization"; diff --git a/src/components/Tractor/farmer-orders/FarmerTractorAutomateClaimOrderCard.tsx b/src/components/Tractor/farmer-orders/FarmerTractorAutomateClaimOrderCard.tsx index c03adaa8..d09210e8 100644 --- a/src/components/Tractor/farmer-orders/FarmerTractorAutomateClaimOrderCard.tsx +++ b/src/components/Tractor/farmer-orders/FarmerTractorAutomateClaimOrderCard.tsx @@ -7,7 +7,12 @@ import { Card } from "@/components/ui/Card"; import IconImage from "@/components/ui/IconImage"; import { TractorRequisitionEvent as RequisitionEvent } from "@/lib/Tractor"; import { PublisherTractorExecution } from "@/lib/Tractor"; -import { transformAutomateClaimRequisitionEvent } from "@/lib/Tractor/claimOrder"; +import { + getClaimOpConditionLabel, + getEnabledClaimOpLabels, + getEnabledClaimOps, + transformAutomateClaimRequisitionEvent, +} from "@/lib/Tractor/claimOrder"; import { AutomateClaimBlueprintStruct } from "@/lib/Tractor/claimOrder/tractor-claim-types"; import { formatter } from "@/utils/format"; import { CalendarIcon, ClockIcon, Cross1Icon } from "@radix-ui/react-icons"; @@ -35,15 +40,24 @@ const FarmerTractorAutomateClaimOrderCard = ({ const transformed = transformAutomateClaimRequisitionEvent(req.decodedData); if (!transformed) return null; - const MAX_UINT256 = 2n ** 256n - 1n; - const mowEnabled = transformed.claimParams.minMowAmount !== MAX_UINT256; - const plantEnabled = transformed.claimParams.minPlantAmount !== MAX_UINT256; - const harvestEnabled = transformed.claimParams.fieldHarvestConfigs.length > 0; - - const enabledOps = [mowEnabled && "Mow", plantEnabled && "Plant", harvestEnabled && "Harvest"].filter(Boolean); + const { mowEnabled, plantEnabled, harvestEnabled } = getEnabledClaimOps(transformed); + const enabledOps = getEnabledClaimOpLabels({ mowEnabled, plantEnabled, harvestEnabled }); const operatorTip = TokenValue.fromBlockchain(transformed.operatorParams.operatorTipAmount, 6); + // Build condition labels with threshold details + const conditions: { text: string }[] = []; + if (mowEnabled) { + conditions.push({ text: getClaimOpConditionLabel("mow", true, transformed.claimParams.minMowAmount) }); + } + if (plantEnabled) { + conditions.push({ text: getClaimOpConditionLabel("plant", true, transformed.claimParams.minPlantAmount) }); + } + if (harvestEnabled) { + const harvestThreshold = transformed.claimParams.fieldHarvestConfigs[0]?.minHarvestAmount ?? 0n; + conditions.push({ text: getClaimOpConditionLabel("harvest", true, harvestThreshold) }); + } + const blueprintExecutions = executions || []; const executionCount = blueprintExecutions.length; const publishDate = req.timestamp ? format(new Date(req.timestamp), "dd MMM yyyy") : "Unknown"; @@ -72,11 +86,7 @@ const FarmerTractorAutomateClaimOrderCard = ({
- ({ - text: `${op} enabled`, - }))} - /> +
diff --git a/src/components/Tractor/farmer-orders/TractorFarmerMixedOrders.types.ts b/src/components/Tractor/farmer-orders/TractorFarmerMixedOrders.types.ts index 141998d5..86f17f03 100644 --- a/src/components/Tractor/farmer-orders/TractorFarmerMixedOrders.types.ts +++ b/src/components/Tractor/farmer-orders/TractorFarmerMixedOrders.types.ts @@ -1,3 +1,4 @@ +import { AutomateClaimOrderData as BaseAutomateClaimOrderData } from "@/components/Tractor/types"; import { PublisherTractorExecution, SowBlueprintData, TractorRequisitionEvent } from "@/lib/Tractor"; import { AutomateClaimBlueprintStruct } from "@/lib/Tractor/claimOrder/tractor-claim-types"; import { ConvertUpOrderbookEntry } from "@/lib/Tractor/convertUp/tractor-convert-up-types"; @@ -54,13 +55,8 @@ export interface ConvertUpOrderData { strategy: string; } -// AutomateClaim-specific order data -export interface AutomateClaimOrderData { - type: "automateClaim"; - mowEnabled: boolean; - plantEnabled: boolean; - harvestEnabled: boolean; - operatorTip: string; +// AutomateClaim-specific order data (extends base with percentComplete for unified order tracking) +export interface AutomateClaimOrderData extends BaseAutomateClaimOrderData { percentComplete: number; } diff --git a/src/components/Tractor/farmer-orders/TractorFarmerMixedOrders.utils.ts b/src/components/Tractor/farmer-orders/TractorFarmerMixedOrders.utils.ts index 869926d1..5b5ca30f 100644 --- a/src/components/Tractor/farmer-orders/TractorFarmerMixedOrders.utils.ts +++ b/src/components/Tractor/farmer-orders/TractorFarmerMixedOrders.utils.ts @@ -1,6 +1,10 @@ import { TokenValue } from "@/classes/TokenValue"; import { PublisherTractorExecution, SowBlueprintData, TractorRequisitionEvent } from "@/lib/Tractor"; -import { transformAutomateClaimRequisitionEvent } from "@/lib/Tractor/claimOrder"; +import { + getEnabledClaimOpLabels, + getEnabledClaimOps, + transformAutomateClaimRequisitionEvent, +} from "@/lib/Tractor/claimOrder"; import { AutomateClaimBlueprintStruct } from "@/lib/Tractor/claimOrder/tractor-claim-types"; import { ConvertUpOrderbookEntry } from "@/lib/Tractor/convertUp/tractor-convert-up-types"; import { getTokenNameByIndex } from "@/utils/token"; @@ -161,11 +165,7 @@ export function transformAutomateClaimOrderToUnified( throw new Error("Failed to transform AutomateClaim data"); } - const MAX_UINT256 = 2n ** 256n - 1n; - const mowEnabled = transformed.claimParams.minMowAmount !== MAX_UINT256; - const plantEnabled = transformed.claimParams.minPlantAmount !== MAX_UINT256; - const harvestEnabled = transformed.claimParams.fieldHarvestConfigs.length > 0; - + const { mowEnabled, plantEnabled, harvestEnabled } = getEnabledClaimOps(transformed); const operatorTip = TokenValue.fromBlockchain(transformed.operatorParams.operatorTipAmount, 6).toHuman(); const automateClaimOrderData: AutomateClaimOrderData = { @@ -262,9 +262,7 @@ export function filterUnifiedOrders(orders: UnifiedTractorOrder[], filters: Mixe export function getOrderSummary(order: UnifiedTractorOrder): string { if (order.type === "automateClaim") { const data = order.orderData as AutomateClaimOrderData; - const ops = [data.mowEnabled && "Mow", data.plantEnabled && "Plant", data.harvestEnabled && "Harvest"].filter( - Boolean, - ); + const ops = getEnabledClaimOpLabels(data); return `Automate Claim • ${ops.join(", ")} • Active`; } const typeLabel = order.type === "sow" ? "Sow" : "Convert Up"; @@ -294,7 +292,7 @@ export function getOrderTypeBadge(orderType: "sow" | "convertUp" | "automateClai case "automateClaim": return { label: "Automate Claim", - className: "bg-purple-100 text-purple-800 border-purple-200", + className: "bg-green-100 text-green-800 border-green-200", icon: "🔄", }; } diff --git a/src/components/Tractor/farmer-orders/TractorFarmerOrderTypeRegistry.tsx b/src/components/Tractor/farmer-orders/TractorFarmerOrderTypeRegistry.tsx index afbee0b7..c9f7d750 100644 --- a/src/components/Tractor/farmer-orders/TractorFarmerOrderTypeRegistry.tsx +++ b/src/components/Tractor/farmer-orders/TractorFarmerOrderTypeRegistry.tsx @@ -7,7 +7,11 @@ import { SowBlueprintData, decodeSowTractorData, } from "@/lib/Tractor"; -import { decodeAutomateClaimBlueprint, transformAutomateClaimRequisitionEvent } from "@/lib/Tractor/claimOrder"; +import { + decodeAutomateClaimBlueprint, + getEnabledClaimOps, + transformAutomateClaimRequisitionEvent, +} from "@/lib/Tractor/claimOrder"; import { AutomateClaimBlueprintStruct } from "@/lib/Tractor/claimOrder/tractor-claim-types"; import { decodeConvertUpTractorOrder } from "@/lib/Tractor/convertUp/tractor-convert-up"; import { ConvertUpOrderbookEntry } from "@/lib/Tractor/convertUp/tractor-convert-up-types"; @@ -156,13 +160,9 @@ const transformAutomateClaimOrderData = (req: RequisitionEvent 0, + ...getEnabledClaimOps(transformed), operatorTip: transformed.operatorParams.operatorTipAmountAsString, }; }; diff --git a/src/components/Tractor/farmer-orders/TractorOrdersPanel.tsx b/src/components/Tractor/farmer-orders/TractorOrdersPanel.tsx index 11b55b9b..5d66cc7a 100644 --- a/src/components/Tractor/farmer-orders/TractorOrdersPanel.tsx +++ b/src/components/Tractor/farmer-orders/TractorOrdersPanel.tsx @@ -102,7 +102,6 @@ function TractorOrdersPanelGeneric({ const { data: automateClaimOrders, ...automateClaimOrdersQuery } = useTractorAutomateClaimOrderbook({ address, - filterOutCompleted: false, enabled: !!address && filters.orderTypes.includes("automateClaim"), }); @@ -221,7 +220,7 @@ function TractorOrdersPanelGeneric({ errorMessage: "Failed to cancel order", successCallback: useCallback(() => { // Invalidate tractor-related queries to refresh order data - queryClient.invalidateQueries({ queryKey: queryKeys.base.tractor }); + queryClient.invalidateQueries({ queryKey: [queryKeys.base.tractor] }); }, [queryClient]), }); diff --git a/src/constants/address.ts b/src/constants/address.ts index 1e367475..1764f53c 100644 --- a/src/constants/address.ts +++ b/src/constants/address.ts @@ -23,7 +23,7 @@ export const SOW_BLUEPRINT_REFERRAL_V0_ADDRESS: HashString = "0xD9DF9C4C01604017 export const CONVERT_UP_BLUEPRINT_V0_ADDRESS: HashString = "0x5167Ae1fF37bE08D9cc9188C7e64DB228B6F06ca"; -export const AUTOMATE_CLAIM_BLUEPRINT_ADDRESS: HashString = "0x0000000000000000000000000000000000000000"; // Placeholder - update after deployment +export const AUTOMATE_CLAIM_BLUEPRINT_ADDRESS: HashString = "0xe02919865F7ea6F342F09538BE801ee4a14884CD"; // Placeholder - update after deployment export const SOW_BLUEPRINT_V0_SELECTOR = "0x3ca8e1b2" as const; diff --git a/src/hooks/tractor/useAutomateClaimOrder.ts b/src/hooks/tractor/useAutomateClaimOrder.ts index 183bcd72..0c59b47b 100644 --- a/src/hooks/tractor/useAutomateClaimOrder.ts +++ b/src/hooks/tractor/useAutomateClaimOrder.ts @@ -65,7 +65,6 @@ export const useAutomateClaimOrder = () => { try { const formData = form.getValues(); - console.debug("[useAutomateClaimOrder] Creating blueprint with form data:", formData); // Resolve operator tip amount from preset const tipTV = getTractorOperatorTipAmountFromPreset( @@ -80,7 +79,6 @@ export const useAutomateClaimOrder = () => { } const tipPerExecution = tipTV.toBigInt(); - console.debug("[useAutomateClaimOrder] Tip per execution:", tipPerExecution.toString()); // Create tractor data and fetch latest block in parallel const [tractorData, block] = await Promise.all([ @@ -99,9 +97,7 @@ export const useAutomateClaimOrder = () => { client.getBlock({ blockTag: "latest" }), ]); - console.debug("[useAutomateClaimOrder] Tractor data created:", tractorData); - - // Create the blueprint (Req 5.2) + // Create the blueprint const blueprint = createBlueprintFromBlock({ block, publisher: address, @@ -110,8 +106,6 @@ export const useAutomateClaimOrder = () => { maxNonce: TV.MAX_UINT256.toBigInt(), }); - console.debug("[useAutomateClaimOrder] Blueprint created:", blueprint); - // Set order data for display in ReviewTractorOrderDialog setOrderData({ mowEnabled: formData.mowEnabled, diff --git a/src/lib/Tractor/claimOrder/automate-claim-helpers.ts b/src/lib/Tractor/claimOrder/automate-claim-helpers.ts new file mode 100644 index 00000000..ba823959 --- /dev/null +++ b/src/lib/Tractor/claimOrder/automate-claim-helpers.ts @@ -0,0 +1,70 @@ +import { maxUint256 } from "viem"; +import { CLAIM_PRESETS } from "./tractor-claim"; +import type { AutomateClaimBlueprintData } from "./tractor-claim-types"; + +/** + * Determine which claim operations are enabled from decoded blueprint data. + * Centralizes the MAX_UINT256 comparison logic used across multiple components. + */ +export function getEnabledClaimOps(transformed: AutomateClaimBlueprintData) { + const mowEnabled = transformed.claimParams.minMowAmount !== maxUint256; + const plantEnabled = transformed.claimParams.minPlantAmount !== maxUint256; + const harvestEnabled = transformed.claimParams.fieldHarvestConfigs.length > 0; + + return { mowEnabled, plantEnabled, harvestEnabled }; +} + +/** + * Get a list of enabled operation labels (e.g. ["Mow", "Plant"]). + */ +export function getEnabledClaimOpLabels(ops: { + mowEnabled: boolean; + plantEnabled: boolean; + harvestEnabled: boolean; +}): string[] { + return [ops.mowEnabled && "Mow", ops.plantEnabled && "Plant", ops.harvestEnabled && "Harvest"].filter( + Boolean, + ) as string[]; +} + +/** + * Get a descriptive condition label for a claim operation based on its threshold. + * Matches the threshold against known presets, or shows the custom value. + */ +export function getClaimOpConditionLabel( + op: "mow" | "plant" | "harvest", + enabled: boolean, + thresholdBigInt: bigint, +): string { + if (!enabled) return ""; + + const presets = CLAIM_PRESETS[op]; + const unit = op === "mow" ? "Stalk" : "PINTO"; + const opLabel = op === "mow" ? "Mow" : op === "plant" ? "Plant" : "Harvest"; + + if (thresholdBigInt === presets.high.scaled) { + return `${opLabel}: Aggressive (≥ ${presets.high.value} ${unit})`; + } + if (thresholdBigInt === presets.medium.scaled) { + return `${opLabel}: Moderate (≥ ${presets.medium.value} ${unit})`; + } + if (thresholdBigInt === presets.low.scaled) { + return `${opLabel}: Conservative (≥ ${presets.low.value} ${unit})`; + } + + // Custom value + const decimals = op === "mow" ? 10 : 6; + const humanValue = Number(thresholdBigInt) / 10 ** decimals; + return `${opLabel}: Custom (≥ ${humanValue} ${unit})`; +} + +/** + * Validate a custom numeric input value (used in ConditionSection and SpecifyConditionsDialog). + * Returns true if the value is a positive number. + */ +export function isValidCustomValue(v: string): boolean { + const normalized = v.trim().replace(",", "."); + if (!normalized) return false; + const num = Number(normalized); + return !Number.isNaN(num) && num > 0; +} diff --git a/src/lib/Tractor/claimOrder/index.ts b/src/lib/Tractor/claimOrder/index.ts index d334add8..d4a8b8f9 100644 --- a/src/lib/Tractor/claimOrder/index.ts +++ b/src/lib/Tractor/claimOrder/index.ts @@ -1,2 +1,3 @@ export * from "./tractor-claim"; export * from "./tractor-claim-types"; +export * from "./automate-claim-helpers"; diff --git a/src/lib/Tractor/claimOrder/tractor-claim.ts b/src/lib/Tractor/claimOrder/tractor-claim.ts index ef2853a3..7a4d41b1 100644 --- a/src/lib/Tractor/claimOrder/tractor-claim.ts +++ b/src/lib/Tractor/claimOrder/tractor-claim.ts @@ -267,17 +267,12 @@ export async function createAutomateClaimTractorData({ export function decodeAutomateClaimBlueprintFromAdvancedPipe( calls: readonly AdvancedPipeCall[] | undefined, ): AutomateClaimBlueprintStruct | null { - if (!calls?.length) { - console.debug("[Tractor/decodeAutomateClaimBlueprintFromAdvancedPipe] No calls provided. Returning null."); - return null; - } - - const callData = calls[0].callData; + if (!calls?.length) return null; try { const decoded = decodeFunctionData({ abi: automateClaimBlueprintABI, - data: callData, + data: calls[0].callData, }); const params = decoded.args?.[0]; @@ -296,14 +291,10 @@ export function decodeAutomateClaimBlueprintFromAdvancedPipe( */ export function decodeAutomateClaimBlueprint(encodedData: `0x${string}`): AutomateClaimBlueprintStruct | null { try { - console.debug("[Tractor/decodeAutomateClaimBlueprint] Attempting to decode, data length:", encodedData.length); const pipeCalls = decodeEncodedTractorDataToAdvancedPipeCalls(encodedData, "automateClaim"); - console.debug("[Tractor/decodeAutomateClaimBlueprint] Pipe calls result:", pipeCalls?.length ?? "undefined"); if (pipeCalls?.length) { - const result = decodeAutomateClaimBlueprintFromAdvancedPipe(pipeCalls); - console.debug("[Tractor/decodeAutomateClaimBlueprint] Decode result:", result ? "success" : "null"); - return result; + return decodeAutomateClaimBlueprintFromAdvancedPipe(pipeCalls); } } catch (e) { console.error("[Tractor/decodeAutomateClaimBlueprint] Failed to decode:", e); @@ -326,14 +317,12 @@ export function transformAutomateClaimRequisitionEvent( ): AutomateClaimBlueprintData | null { try { if (!params || typeof params !== "object") { - console.debug("[Tractor/transformAutomateClaimRequisitionEvent] Invalid params."); return null; } const struct = params as AutomateClaimBlueprintStruct; if (!struct.claimParams || !struct.opParams) { - console.debug("[Tractor/transformAutomateClaimRequisitionEvent] Missing claimParams or opParams."); return null; } diff --git a/src/state/queryKeys.ts b/src/state/queryKeys.ts index 85846e6b..72eaa78e 100644 --- a/src/state/queryKeys.ts +++ b/src/state/queryKeys.ts @@ -87,6 +87,12 @@ const tractorQueryKeys = { publisher ?? "no-publisher", lastUpdatedBlock?.toString() ?? "0", ], + automateClaimOrders: (address?: string, chainId?: number) => [ + BASE_QKS.tractor, + "automateClaimOrders", + address ?? "no-address", + chainId?.toString() ?? "0", + ], ...convertUpQK, } as const; diff --git a/src/state/tractor/useTractorAutomateClaimOrders.ts b/src/state/tractor/useTractorAutomateClaimOrders.ts index 404bdfbf..fe87f6c7 100644 --- a/src/state/tractor/useTractorAutomateClaimOrders.ts +++ b/src/state/tractor/useTractorAutomateClaimOrders.ts @@ -6,6 +6,7 @@ import { decodeAutomateClaimBlueprint } from "@/lib/Tractor/claimOrder"; import { AutomateClaimBlueprintStruct } from "@/lib/Tractor/claimOrder/tractor-claim-types"; import { TRACTOR_DEPLOYMENT_BLOCK } from "@/lib/Tractor/core"; import { fetchTractorEvents } from "@/lib/Tractor/events/tractor-events"; +import { queryKeys } from "@/state/queryKeys"; import { stringEq } from "@/utils/string"; import { useQuery } from "@tanstack/react-query"; import { useCallback } from "react"; @@ -13,7 +14,6 @@ import { useChainId, usePublicClient } from "wagmi"; interface UseTractorAutomateClaimOrderbookOptions { address?: `0x${string}`; - filterOutCompleted?: boolean; enabled?: boolean; } @@ -26,14 +26,16 @@ export function useTractorAutomateClaimOrderbook({ const diamond = useProtocolAddress(); const query = useQuery({ - queryKey: ["tractor", "automateClaimOrders", address, chainId], + queryKey: queryKeys.tractor.automateClaimOrders(address, chainId), queryFn: async (): Promise[]> => { if (!client || !diamond) return []; - // Use the general deployment block to catch all events const { publishEvents, cancelledHashes } = await fetchTractorEvents(client, diamond, TRACTOR_DEPLOYMENT_BLOCK); - console.debug("[useTractorAutomateClaimOrderbook] Total publish events:", publishEvents.length); + // Get latest block for timestamp approximation + const latestBlock = await client.getBlock({ blockTag: "latest" }); + const latestTimestamp = Number(latestBlock.timestamp); + const latestBlockNumber = Number(latestBlock.number); const automateClaimOrders: TractorRequisitionEvent[] = []; @@ -51,47 +53,32 @@ export function useTractorAutomateClaimOrderbook({ const blueprintData = requisition.blueprint.data; if (!blueprintData) continue; - // First try the blueprint-decoders system to check if this is an automateClaim type - const blueprintDecoded = decodeBlueprintCallData(blueprintData); + // Try direct decode first, fall back to blueprint-decoders system + let decodedData = decodeAutomateClaimBlueprint(blueprintData); - // Try to decode as automateClaimBlueprint using the direct decoder - const decodedData = decodeAutomateClaimBlueprint(blueprintData); - - // Accept if either decoder identifies this as automateClaim - if (!decodedData && blueprintDecoded?.type !== "automateClaim") { - continue; + if (!decodedData) { + const blueprintDecoded = decodeBlueprintCallData(blueprintData); + if (blueprintDecoded?.type === "automateClaim" && blueprintDecoded.params) { + decodedData = blueprintDecoded.params as AutomateClaimBlueprintStruct; + } } - console.debug("[useTractorAutomateClaimOrderbook] Found automateClaim order:", { - hash: requisition.blueprintHash, - publisher: requisition.blueprint.publisher, - decodedData: !!decodedData, - blueprintDecodedType: blueprintDecoded?.type, - }); + if (!decodedData) continue; - const isCancelled = cancelledHashes.has(requisition.blueprintHash); - - // If direct decode worked, use it. Otherwise try to extract from blueprint-decoders result - let finalDecodedData = decodedData; - if (!finalDecodedData && blueprintDecoded?.type === "automateClaim" && blueprintDecoded.params) { - // The blueprint-decoders system decoded the params directly from the inner call - finalDecodedData = blueprintDecoded.params as AutomateClaimBlueprintStruct; - } - - if (!finalDecodedData) continue; + // Approximate timestamp from block number difference (~2s per block on Base) + const eventBlockNumber = Number(event.blockNumber ?? 0); + const timestamp = latestTimestamp * 1000 - (latestBlockNumber - eventBlockNumber) * 2000; automateClaimOrders.push({ requisition, - blockNumber: Number(event.blockNumber ?? 0), - timestamp: undefined, - isCancelled, + blockNumber: eventBlockNumber, + timestamp, + isCancelled: cancelledHashes.has(requisition.blueprintHash), requisitionType: "automateClaimBlueprint", - decodedData: finalDecodedData, + decodedData, }); } - console.debug("[useTractorAutomateClaimOrderbook] Found orders:", automateClaimOrders.length); - return automateClaimOrders; }, enabled: !!client && !!diamond && enabled, diff --git a/src/state/tractor/useTractorClaimOrders.ts b/src/state/tractor/useTractorClaimOrders.ts deleted file mode 100644 index ae7b25d1..00000000 --- a/src/state/tractor/useTractorClaimOrders.ts +++ /dev/null @@ -1,8 +0,0 @@ -/** - * Re-export of automate claim orderbook hook. - * - * The design doc lists this module path (`useTractorClaimOrders`) as the - * canonical location for fetching & decoding automate-claim orders. - * The actual implementation lives in `useTractorAutomateClaimOrders.ts`. - */ -export { useTractorAutomateClaimOrderbook } from "./useTractorAutomateClaimOrders";