diff --git a/src/ProtectedLayout.tsx b/src/ProtectedLayout.tsx index 630ae118b..ff8170241 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() { } /> + + + + } + /> + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/components/BeanstalkStatField.tsx b/src/components/BeanstalkStatField.tsx new file mode 100644 index 000000000..167bd396e --- /dev/null +++ b/src/components/BeanstalkStatField.tsx @@ -0,0 +1,67 @@ +import TextSkeleton from "@/components/TextSkeleton"; +import { Button } from "@/components/ui/Button"; +import { ReactNode } from "react"; + +interface BeanstalkStatFieldAction { + label: string; + onClick?: () => 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/ComboInputField.tsx b/src/components/ComboInputField.tsx index dfdc9f271..75041aba1 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 new file mode 100644 index 000000000..4d0d7a688 --- /dev/null +++ b/src/components/FertilizerCard.tsx @@ -0,0 +1,67 @@ +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 */} +
+
+ {sprouts} Sprouts + Humidity: {humidity} +
+
+ {formatter.number(Number(maxBalance))} bsFERT - ID {formatter.number(Number(fertId))} +
+
+ + {/* 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 9ddd76671..1e5528e42 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/components/PodLineGraph.tsx b/src/components/PodLineGraph.tsx index 39d64a50b..d7a9671e1 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 @@ -604,7 +637,7 @@ export default function PodLineGraph({ return (
- + {open && ( + + {children} + )} - > - {open && {children}} - + {open ? " Read less" : " Read more"} diff --git a/src/components/Tractor/form/SowOrderV0Fields.tsx b/src/components/Tractor/form/SowOrderV0Fields.tsx index 0d6e7d64e..b210b0fb0 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, diff --git a/src/components/nav/nav/Navbar.tsx b/src/components/nav/nav/Navbar.tsx index 39d9c9a8d..aa4729fbe 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 da6f6eec1..4a6072f81 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 7581cce54..c57cad248 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/context/BeanstalkMarketContext.tsx b/src/context/BeanstalkMarketContext.tsx new file mode 100644 index 000000000..c4fb08a51 --- /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 15b52a97a..ac1c4528d 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 c5cb777ab..59c075b42 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/exchange/graphql.ts b/src/generated/gql/exchange/graphql.ts index 964297858..bbcf894a0 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 508c651f8..f64db3c39 100644 --- a/src/generated/gql/pintostalk/gql.ts +++ b/src/generated/gql/pintostalk/gql.ts @@ -22,9 +22,9 @@ 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 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 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, $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, @@ -50,9 +50,9 @@ 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 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 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, $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, @@ -119,15 +119,15 @@ 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. */ -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 78aa7d75a..1ff4fd753 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; }>; @@ -14105,6 +14119,7 @@ export type AllPodListingsQueryVariables = Exact<{ status?: InputMaybe; maxHarvestableIndex: Scalars['BigInt']['input']; skip?: InputMaybe; + podMarketplace?: InputMaybe; }>; @@ -14114,6 +14129,7 @@ export type AllPodOrdersQueryVariables = Exact<{ first?: InputMaybe; status?: InputMaybe; skip?: InputMaybe; + podMarketplace?: InputMaybe; }>; @@ -14239,9 +14255,9 @@ 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 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 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"}},{"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/hooks/useAllFertilizerIds.ts b/src/hooks/useAllFertilizerIds.ts new file mode 100644 index 000000000..5be3443ed --- /dev/null +++ b/src/hooks/useAllFertilizerIds.ts @@ -0,0 +1,133 @@ +import { abiSnippets } from "@/constants/abiSnippets"; +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 fetches all active fertilizer IDs from the BarnPayback contract. + * + * Strategy: + * 1. Call fert() → get fertFirst, fertLast, activeFertilizer + * 2. If activeFertilizer == 0 && fertFirst == 0 → no fertilizers exist, bail out + * 3. Collect unique IDs from TransferSingle/TransferBatch events on the BarnPayback contract + */ + +const DEPLOYMENT_BLOCK = 42040733n; +const BARN_PAYBACK = BARN_PAYBACK_ADDRESS as `0x${string}`; + +const transferSingleEvent = parseAbiItem( + "event TransferSingle(address indexed operator, address indexed from, address indexed to, uint256 id, uint256 value)", +); + +const transferBatchEvent = parseAbiItem( + "event TransferBatch(address indexed operator, address indexed from, address indexed to, uint256[] ids, uint256[] values)", +); + +export interface FertData { + fertilizedIndex: bigint; + unfertilizedIndex: bigint; + fertilizedPaidIndex: bigint; + leftoverBeans: bigint; + activeFertilizer: bigint; + fertFirst: bigint; + fertLast: bigint; + bpf: bigint; +} + +/** Fetch unique fertilizer IDs from event logs */ +async function fetchFertilizerIds(publicClient: PublicClient): Promise { + const uniqueIds = new Set(); + + // Scan TransferSingle events + const singleLogs = await publicClient.getLogs({ + address: BARN_PAYBACK, + event: transferSingleEvent, + fromBlock: DEPLOYMENT_BLOCK, + toBlock: "latest", + }); + + for (const log of singleLogs) { + if (log.args.id !== undefined) { + uniqueIds.add(log.args.id); + } + } + + // Scan TransferBatch events + const batchLogs = await publicClient.getLogs({ + address: BARN_PAYBACK, + event: transferBatchEvent, + fromBlock: DEPLOYMENT_BLOCK, + toBlock: "latest", + }); + + for (const log of batchLogs) { + if (log.args.ids) { + for (const id of log.args.ids) { + uniqueIds.add(id); + } + } + } + + 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]); + + // If no active fertilizer, return empty without error + const fertIds = !hasActiveFert ? [] : idsQuery.data ?? []; + + return { + fertilizerIds: fertIds, + fertData, + isLoading: fertQuery.isLoading || (hasActiveFert && idsQuery.isLoading), + isError: fertQuery.isError || (hasActiveFert && idsQuery.isError), + refetch, + }; +} diff --git a/src/pages/Beanstalk.tsx b/src/pages/Beanstalk.tsx new file mode 100644 index 000000000..597bcb827 --- /dev/null +++ b/src/pages/Beanstalk.tsx @@ -0,0 +1,56 @@ +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"; + +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. A portion + of new Pinto mints go towards repaying these obligations across 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/Market.tsx b/src/pages/Market.tsx index 229afc2c6..ec2a9c295 100644 --- a/src/pages/Market.tsx +++ b/src/pages/Market.tsx @@ -10,9 +10,12 @@ 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 { 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"; @@ -21,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"; @@ -213,12 +216,50 @@ export function Market() { const hoverInfoRef = useRef(null); const lastPositionSideRef = useRef<{ isRight: boolean; isAbove: boolean } | null>(null); const navigate = useNavigate(); - const { data, isLoaded } = useAllMarket(); + 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); @@ -257,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); @@ -273,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) { @@ -317,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(() => { @@ -519,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 @@ -599,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( @@ -779,7 +820,7 @@ export function Market() { setIsCrosshairFrozen(false); setContextMenu(null); setIsContextMenuClosing(false); - navigate(path, { state }); + navigate(buildMarketPath(path), { state }); // Reset flag after navigation setTimeout(() => { @@ -787,7 +828,7 @@ export function Market() { }, 100); }, 200); // Match fade-out animation duration }, - [navigate], + [navigate, buildMarketPath], ); const contextMenuOptions = useMemo(() => { @@ -860,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 { @@ -871,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 } @@ -905,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 { @@ -916,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 } @@ -967,166 +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. + + <> +
+

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. - - - -
-
-
- {!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/Transfer.tsx b/src/pages/Transfer.tsx index 57117cf28..4206fd51b 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/beanstalk/components/BeanstalkFertilizerSection.tsx b/src/pages/beanstalk/components/BeanstalkFertilizerSection.tsx new file mode 100644 index 000000000..74ccbd886 --- /dev/null +++ b/src/pages/beanstalk/components/BeanstalkFertilizerSection.tsx @@ -0,0 +1,68 @@ +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; + onSend?: () => void; +} + +/** + * 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 +
+
+ )} +
+
+ ); +}; + +export default BeanstalkFertilizerSection; diff --git a/src/pages/beanstalk/components/BeanstalkGlobalCard.tsx b/src/pages/beanstalk/components/BeanstalkGlobalCard.tsx new file mode 100644 index 000000000..30e791a8c --- /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 000000000..112b8e823 --- /dev/null +++ b/src/pages/beanstalk/components/BeanstalkObligationsCard.tsx @@ -0,0 +1,177 @@ +import { Button } from "@/components/ui/Button"; +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"; +import { useAccount, useChainId } 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 chainId = useChainId(); + const navigate = useNavigate(); + const { silo, pods, fertilizer, isLoading, isError, refetch } = useFarmerBeanstalkRepayment(); + const globalStats = useBeanstalkGlobalStats(); + + const isConnected = !!account.address; + const showDisabled = !isConnected || isError; + + // Total bsFERT token count from per-ID ERC1155 balances + const totalBsFert = useMemo(() => { + let total = 0n; + for (const detail of fertilizer.perIdData.values()) { + total += detail.balance; + } + return total; + }, [fertilizer.perIdData]); + + // Transaction hook for claim operations + const { writeWithEstimateGas, setSubmitting } = useTransaction({ + successCallback: () => { + refetch(); + }, + successMessage: "Claim successful", + errorMessage: "Claim failed", + }); + + // Silo Claim — Claim earned urBDV from SiloPayback contract directly + const handleClaimSilo = useCallback(async () => { + if (!account.address || silo.earned.isZero) return; + + try { + setSubmitting(true); + + // 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) + }); + } catch (error) { + console.error("Silo claim error:", error); + setSubmitting(false); + } + }, [account.address, silo.earned, writeWithEstimateGas, setSubmitting]); + + // Pods Harvest — Harvest harvestable pods from repayment field (fieldId=1) + const handleHarvestPods = useCallback(async () => { + if (!account.address) return; + + const harvestablePlotIndices = pods.plots.filter((p) => p.harvestablePods?.gt(0)).map((p) => p.index.toBigInt()); + + if (harvestablePlotIndices.length === 0) return; + + try { + setSubmitting(true); + + await writeWithEstimateGas({ + address: beanstalkAddress[chainId as keyof typeof beanstalkAddress], + abi: beanstalkAbi, + functionName: "harvest", + args: [1n, harvestablePlotIndices, 1], // fieldId=1, plot indices, EXTERNAL + }); + } catch (error) { + console.error("Pods harvest error:", error); + setSubmitting(false); + } + }, [account.address, pods.plots, writeWithEstimateGas, setSubmitting, chainId]); + + // Fertilizer Rinse — Rinse fertilized sprouts from BarnPayback contract directly + const handleRinseFert = useCallback(async () => { + if (!account.address) return; + + if (!fertilizer.fertilizerIds || fertilizer.fertilizerIds.length === 0) { + console.warn("Cannot rinse fertilizer: No fertilizer IDs available."); + return; + } + + try { + setSubmitting(true); + + await writeWithEstimateGas({ + address: BARN_PAYBACK_ADDRESS, + abi: abiSnippets.barnPayback, + functionName: "claimFertilized", + args: [fertilizer.fertilizerIds, 1], + }); + } catch (error) { + console.error("Fertilizer rinse error:", error); + setSubmitting(false); + } + }, [account.address, fertilizer.fertilizerIds, writeWithEstimateGas, setSubmitting]); + + const handleSendSilo = () => { + navigate("/transfer/beanstalk-silo"); + }; + + const handleSendPods = () => { + navigate("/transfer/beanstalk-pods"); + }; + + const handleMarketPods = () => { + navigate("/market/pods/buy/fill?beanstalk=true"); + }; + + const handleSendFertilizer = () => { + navigate("/transfer/beanstalk-fertilizer"); + }; + + 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 000000000..6e207290a --- /dev/null +++ b/src/pages/beanstalk/components/BeanstalkPodsSection.tsx @@ -0,0 +1,67 @@ +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; + harvestableIndex: TokenValue; + podIndex: TokenValue; + isLoading: boolean; + disabled?: boolean; + onHarvest?: () => void; + onSend?: () => void; + onMarket?: () => void; +} + +/** + * Section component displaying pods from the repayment field (fieldId=1) + * Shows PodLineGraph visualization + */ +const BeanstalkPodsSection: React.FC = ({ + plots, + totalPods, + harvestableIndex, + podIndex, + isLoading, + disabled = false, + onHarvest, + onSend, + onMarket, +}) => { + const hasPlots = plots.length > 0; + const hasPods = !totalPods.isZero; + const hasHarvestablePods = plots.some((p) => p.harvestablePods?.gt(0)); + 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 000000000..1464b156e --- /dev/null +++ b/src/pages/beanstalk/components/BeanstalkSiloSection.tsx @@ -0,0 +1,68 @@ +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; + totalDistributed: TokenValue; + isLoading: boolean; + disabled?: boolean; + onClaim?: () => void; + onSend?: () => void; +} + +/** + * Section component displaying urBDV token balance for Silo Payback + * Shows total balance as primary value, earned (claimable) as secondary + */ +const BeanstalkSiloSection: React.FC = ({ + balance, + earned, + totalDistributed, + isLoading, + disabled = false, + onClaim, + onSend, +}) => { + 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 + ) : ( +
+
+ {formatter.number(balance, { minDecimals: 2, maxDecimals: 2 })} + + Beanstalk Silo Tokens ({sharePercent}%) + +
+
+ {formatter.number(earned, { minDecimals: 2, maxDecimals: 2 })} Earned +
+
+ )} +
+
+ ); +}; + +export default BeanstalkSiloSection; diff --git a/src/pages/market/MarketModeSelect.tsx b/src/pages/market/MarketModeSelect.tsx index fd03cd506..3e2556300 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 35d750e2f..7e68f948d 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 8022c7370..c0a95398b 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 032308f97..4af2a7535 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 65f80fd86..98aa5b560 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 5cb237f8f..dbd1fbdf6 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 b00a9239f..fbcde708d 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 a75aa2af9..b21b86956 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/TransferActions.tsx b/src/pages/transfer/TransferActions.tsx index c4ad9c69a..fea40e4d6 100644 --- a/src/pages/transfer/TransferActions.tsx +++ b/src/pages/transfer/TransferActions.tsx @@ -3,10 +3,12 @@ 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"; import { formatter } from "@/utils/format"; +import { useMemo } from "react"; import { Link } from "react-router-dom"; export default function TransferActions() { @@ -15,6 +17,16 @@ export default function TransferActions() { const farmerBalance = useFarmerBalances(); const farmerSilo = useFarmerSilo(); 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) => @@ -25,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 (
@@ -75,6 +93,39 @@ export default function TransferActions() {
+ + +
); } diff --git a/src/pages/transfer/actions/TransferAll.tsx b/src/pages/transfer/actions/TransferAll.tsx index 6f166f11b..6878f1433 100644 --- a/src/pages/transfer/actions/TransferAll.tsx +++ b/src/pages/transfer/actions/TransferAll.tsx @@ -1,12 +1,15 @@ import FlowForm from "@/components/FormFlow"; +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 { useFarmerBalances } from "@/state/useFarmerBalances"; +import { useFarmerBeanstalkRepayment } from "@/state/useFarmerBeanstalkRepayment"; import { useFarmerField } from "@/state/useFarmerField"; import { useFarmerSilo } from "@/state/useFarmerSilo"; import { FarmFromMode, FarmToMode } from "@/utils/types"; import { useQueryClient } from "@tanstack/react-query"; -import { useEffect, useState } from "react"; +import { useEffect, useMemo, useState } from "react"; import { useNavigate } from "react-router-dom"; import { toast } from "sonner"; import { type Address, encodeFunctionData } from "viem"; @@ -35,6 +38,16 @@ export default function TransferAll() { const hasPlots = farmerField.plots.length > 0; + const repayment = useFarmerBeanstalkRepayment(); + const hasBeanstalkSilo = repayment.silo.balance.gt(0); + const hasBeanstalkPods = repayment.pods.plots.length > 0; + const hasBeanstalkFert = useMemo(() => { + for (const detail of repayment.fertilizer.perIdData.values()) { + if (detail.balance > 0n) return true; + } + return false; + }, [repayment.fertilizer.perIdData]); + const queryClient = useQueryClient(); const navigate = useNavigate(); @@ -43,19 +56,11 @@ export default function TransferAll() { }, [destination]); const { writeWithEstimateGas, setSubmitting } = useTransaction({ - successCallback: () => { - for (const queryKey of farmerSilo.queryKeys) { - queryClient.invalidateQueries({ queryKey }); - } - farmerBalances.refetch(); - farmerField.refetch(); - navigate("/transfer"); - }, successMessage: "Transfer success", errorMessage: "Transfer failed", }); - function onSubmit() { + async function onSubmit() { try { setSubmitting(true); toast.loading("Transferring..."); @@ -93,9 +98,8 @@ export default function TransferAll() { farmData.push(depositTransferCall); } - // Plot Transfers + // Plot Transfers (fieldId=0) if (hasPlots) { - // todo: add support for more than one plot line const fieldId = BigInt(0); const ids: bigint[] = []; const starts: bigint[] = []; @@ -108,24 +112,82 @@ export default function TransferAll() { 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); + } + + // Beanstalk Repayment Pods (fieldId=1) + if (hasBeanstalkPods) { + const fieldId = BigInt(1); + const ids: bigint[] = []; + const starts: bigint[] = []; + const ends: bigint[] = []; + for (const plotData of repayment.pods.plots) { + ids.push(plotData.index.toBigInt()); + starts.push(BigInt(0)); + ends.push(plotData.pods.toBigInt()); + } + const plotTransferCall = encodeFunctionData({ + abi: beanstalkAbi, + functionName: "transferPlots", + args: [account.address, destination as Address, fieldId, ids, starts, ends], }); farmData.push(plotTransferCall); } - return writeWithEstimateGas({ - address: beanstalkAddress[chainId as keyof typeof beanstalkAddress], - abi: beanstalkAbi, - functionName: "farm", - args: [farmData], - }); + // Execute farm() with all batched calls (skip if nothing to batch) + if (farmData.length > 0) { + await writeWithEstimateGas({ + address: beanstalkAddress[chainId as keyof typeof beanstalkAddress], + abi: beanstalkAbi, + functionName: "farm", + args: [farmData], + }); + } + + // Beanstalk Repayment Fertilizer — direct ERC1155 transfer (not via farm) + // BarnPayback is a separate contract; calling via farm() makes diamond the msg.sender + // which requires the user to have approved diamond. Direct call avoids this. + if (hasBeanstalkFert) { + const fertIds: bigint[] = []; + const fertValues: bigint[] = []; + for (const [idStr, detail] of repayment.fertilizer.perIdData) { + if (detail.balance > 0n) { + fertIds.push(BigInt(idStr)); + fertValues.push(detail.balance); + } + } + if (fertIds.length > 0) { + toast.loading("Transferring bsFERT..."); + await writeWithEstimateGas({ + address: BARN_PAYBACK_ADDRESS as Address, + abi: abiSnippets.barnPayback, + functionName: "safeBatchTransferFrom", + args: [account.address, destination as Address, fertIds, fertValues, "0x"], + }); + } + } + + // urBDV transfer is a separate contract call (not via farm()) + if (hasBeanstalkSilo) { + toast.loading("Transferring urBDV..."); + await writeWithEstimateGas({ + address: SILO_PAYBACK_ADDRESS as Address, + abi: abiSnippets.siloPayback, + functionName: "transfer", + args: [destination as Address, repayment.silo.balance.toBigInt()], + }); + } + + // All transfers complete — invalidate caches and navigate + for (const queryKey of farmerSilo.queryKeys) { + queryClient.invalidateQueries({ queryKey }); + } + farmerBalances.refetch(); + farmerField.refetch(); + repayment.refetch(); + navigate("/transfer"); } catch (e) { console.error(e); toast.dismiss(); diff --git a/src/pages/transfer/actions/TransferBeanstalkFertilizer.tsx b/src/pages/transfer/actions/TransferBeanstalkFertilizer.tsx new file mode 100644 index 000000000..4a78b9ca5 --- /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 000000000..cbd934c5e --- /dev/null +++ b/src/pages/transfer/actions/TransferBeanstalkPods.tsx @@ -0,0 +1,125 @@ +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"; + +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 Beanstalk Plots"; + default: + return "Confirm send"; + } + }; + + const enableNextStep = () => { + switch (step) { + case 1: + return transferData.length > 0 && !!destination && transferNotice; + 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, starts, 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 ? ( + + ) : ( + + )} + + ); +} diff --git a/src/pages/transfer/actions/TransferBeanstalkSilo.tsx b/src/pages/transfer/actions/TransferBeanstalkSilo.tsx new file mode 100644 index 000000000..3ae20257c --- /dev/null +++ b/src/pages/transfer/actions/TransferBeanstalkSilo.tsx @@ -0,0 +1,92 @@ +import { TokenValue } from "@/classes/TokenValue"; +import FlowForm from "@/components/FormFlow"; +import { abiSnippets } from "@/constants/abiSnippets"; +import { SILO_PAYBACK_ADDRESS } from "@/constants/address"; +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 } from "viem"; +import { useAccount } 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 navigate = useNavigate(); + + const [step, setStep] = useState(1); + const [destination, setDestination] = useState(); + const [amount, setAmount] = useState(""); + const [transferNotice, setTransferNotice] = useState(false); + + const repayment = useFarmerBeanstalkRepayment(); + + useEffect(() => { + setTransferNotice(false); + }, [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; + + // urBDV is an ERC20 token on SiloPayback contract — direct transfer to recipient wallet + return writeWithEstimateGas({ + address: SILO_PAYBACK_ADDRESS as Address, + abi: abiSnippets.siloPayback, + functionName: "transfer", + args: [destination as Address, parsedAmount.toBigInt()], + }); + } catch (e) { + console.error("Transfer Beanstalk Silo failed", e); + toast.dismiss(); + toast.error("Transfer failed"); + } + } + + const numericAmount = Number(amount) || 0; + + return ( + 0} + onSubmit={onSubmit} + stepDescription={stepDescription} + > + {step === 1 ? ( + + ) : ( + + )} + + ); +} diff --git a/src/pages/transfer/actions/TransferPods.tsx b/src/pages/transfer/actions/TransferPods.tsx index 541985559..ae8cfa530 100644 --- a/src/pages/transfer/actions/TransferPods.tsx +++ b/src/pages/transfer/actions/TransferPods.tsx @@ -10,7 +10,6 @@ import { type Address, encodeFunctionData } from "viem"; import { useAccount, useChainId } from "wagmi"; import FinalStep from "./pods/FinalStep"; import StepOne from "./pods/StepOne"; -import StepTwo from "./pods/StepTwo"; export interface PodTransferData { id: TokenValue; @@ -37,9 +36,7 @@ export default function TransferPods() { const stepDescription = () => { switch (step) { case 1: - return "Select Plots"; - case 2: - return "Specify amount and address"; + return "Select Pinto Plots"; default: return "Confirm send"; } @@ -48,15 +45,7 @@ export default function TransferPods() { 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; + return transferData.length > 0 && !!destination && transferNotice; default: return true; } @@ -121,16 +110,14 @@ export default function TransferPods() { {step === 1 ? ( - - ) : step === 2 ? ( - ({ token, deposit })); const harvestableIndex = useHarvestableIndex(); + const repayment = useFarmerBeanstalkRepayment(); const hasBalance = balancesToSend .reduce((total, balanceToSend) => total.add(balanceToSend.balance.internal), TokenValue.ZERO) @@ -34,6 +35,17 @@ export default function FinalStep({ destination }: StepTwoProps) { const hasDeposits = depositsToSend.reduce((total, depositToSend) => total + depositToSend.deposit.deposits.length, 0) > 0; + const hasBeanstalkSilo = repayment.silo.balance.gt(0); + const hasBeanstalkPods = repayment.pods.plots.length > 0; + const totalBsFert = useMemo(() => { + let total = 0n; + for (const detail of repayment.fertilizer.perIdData.values()) { + total += detail.balance; + } + return total; + }, [repayment.fertilizer.perIdData]); + const hasBeanstalkFert = totalBsFert > 0n; + return (
@@ -71,6 +83,32 @@ export default function FinalStep({ destination }: StepTwoProps) {
)} + {hasBeanstalkSilo && ( +
+ +
+ {formatter.twoDec(repayment.silo.balance)} urBDV +
+
+ )} + {hasBeanstalkPods && ( +
+ +
+ {formatter.twoDec(repayment.pods.totalPods)} + Plot + Pods +
+
+ )} + {hasBeanstalkFert && ( +
+ +
+ {formatter.number(Number(totalBsFert))} bsFERT +
+
+ )}
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 000000000..2a3e41099 --- /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 000000000..4b607825e --- /dev/null +++ b/src/pages/transfer/actions/beanstalk-fertilizer/StepOne.tsx @@ -0,0 +1,229 @@ +import AddressInputField from "@/components/AddressInputField"; +import FertilizerCard from "@/components/FertilizerCard"; +import PintoAssetTransferNotice from "@/components/PintoAssetTransferNotice"; +import { Button } from "@/components/ui/Button"; +import { Label } from "@/components/ui/Label"; +import { useFarmerBeanstalkRepayment } from "@/state/useFarmerBeanstalkRepayment"; +import { formatter } from "@/utils/format"; +import { AnimatePresence, motion } from "framer-motion"; +import { Dispatch, SetStateAction, useCallback, 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 perIdData = repayment.fertilizer.perIdData; + + // Get actual balance per fertilizer ID from on-chain data + const getFertilizerBalance = useCallback( + (fertId: bigint): bigint => { + return perIdData.get(fertId.toString())?.balance ?? 0n; + }, + [perIdData], + ); + + // 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; + }); + + // Track which fertilizers are selected (have amount > 0) + const [selected, setSelected] = useState>(() => { + const initial = new Set(); + for (const item of selectedIds) { + if (item.value > 0n) { + initial.add(item.id.toString()); + } + } + return initial; + }); + + const toggleSelection = useCallback( + (fertId: bigint) => { + const idStr = fertId.toString(); + const balance = getFertilizerBalance(fertId); + 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 to full balance when selecting + const defaultAmount = balance > 0n ? balance : 1n; + setAmounts((prevAmounts) => ({ ...prevAmounts, [idStr]: defaultAmount.toString() })); + setSelectedIds((prev) => [...prev, { id: fertId, value: defaultAmount }]); + } + return newSet; + }); + }, + [setSelectedIds, getFertilizerBalance], + ); + + const handleAmountChange = useCallback( + (fertId: bigint, value: string, maxBalance: bigint) => { + const idStr = fertId.toString(); + + // Validate against max balance + let numValue = value === "" ? 0n : BigInt(Math.max(0, Math.floor(Number(value)))); + if (numValue > maxBalance) { + numValue = maxBalance; + } + + const displayValue = numValue === 0n ? "" : numValue.toString(); + setAmounts((prev) => ({ ...prev, [idStr]: displayValue })); + + // 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 }]; + }); + }, + [setSelectedIds], + ); + + const selectAll = useCallback(() => { + const newAmounts: Record = {}; + const newSelectedIds: FertilizerTransferItem[] = []; + const newSelected = new Set(); + + for (const fertId of fertilizerIds) { + const idStr = fertId.toString(); + const balance = getFertilizerBalance(fertId); + const amount = balance > 0n ? balance : 1n; + newAmounts[idStr] = amount.toString(); + newSelectedIds.push({ id: fertId, value: amount }); + newSelected.add(idStr); + } + + setAmounts(newAmounts); + setSelectedIds(newSelectedIds); + setSelected(newSelected); + }, [fertilizerIds, setSelectedIds, getFertilizerBalance]); + + if (fertilizerIds.length === 0) { + return ( +
+
No Beanstalk Repayment Fertilizer found.
+
+ ); + } + + return ( + +
+ +
+ + + +
+ {fertilizerIds.map((fertId) => { + const idStr = fertId.toString(); + const amount = amounts[idStr] || ""; + const isSelected = selected.has(idStr); + const detail = perIdData.get(idStr); + const maxBalance = detail?.balance ?? 0n; + // Format sprouts: raw value is balance * remainingBpf (no decimals on balance, 6 decimals on bpf) + const sproutsRaw = detail?.sprouts ?? 0n; + const sprouts = formatter.number(Number(sproutsRaw) / 1e6); + const humidity = detail?.humidity !== undefined ? `${formatter.number(detail.humidity)}%` : "—"; + + return ( + + ); + })} +
+
+ + + + + + {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 000000000..4136bdd72 --- /dev/null +++ b/src/pages/transfer/actions/beanstalk-pods/FinalStep.tsx @@ -0,0 +1,62 @@ +import podIcon from "@/assets/protocol/Pod.png"; +import AddressLink from "@/components/AddressLink"; +import { Label } from "@/components/ui/Label"; +import { useFarmerBeanstalkRepayment } from "@/state/useFarmerBeanstalkRepayment"; +import { formatter } from "@/utils/format"; +import { computeSummaryRange } from "@/utils/podTransferUtils"; +import { useMemo } from "react"; +import { PodTransferData } from "../TransferBeanstalkPods"; + +interface FinalStepProps { + destination: string | undefined; + transferData: PodTransferData[]; +} + +export default function FinalStep({ destination, transferData }: FinalStepProps) { + const harvestableIndex = useFarmerBeanstalkRepayment().pods.harvestableIndex; + + 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 ( +
+
+ +
+
+
+ {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 new file mode 100644 index 000000000..856c2cd2a --- /dev/null +++ b/src/pages/transfer/actions/beanstalk-pods/StepOne.tsx @@ -0,0 +1,220 @@ +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 { useFarmerBeanstalkRepayment } from "@/state/useFarmerBeanstalkRepayment"; +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 "../TransferBeanstalkPods"; + +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, + destination, + setDestination, + transferNotice, + setTransferNotice, +}: StepOneProps) { + const repaymentPods = useFarmerBeanstalkRepayment().pods; + const { plots, harvestableIndex, podIndex } = repaymentPods; + + const [selectedPlots, setSelectedPlots] = useState([]); + const [podRange, setPodRange] = useState<[number, number]>([0, 0]); + + const mountedRef = useRef(false); + + // Restore selection from existing transferData on mount + useEffect(() => { + 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]); + } + // 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( + (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; + } + + const newPlots = [...selectedPlots]; + for (const plotToAdd of plotsInGroup) { + if (!selectedSet.has(plotToAdd.index.toHuman())) { + newPlots.push(plotToAdd); + } + } + handlePlotSelection(newPlots); + }, + [plots, selectedPlots, handlePlotSelection], + ); + + const handlePodRangeChange = useCallback( + (value: number[]) => { + const newRange: [number, number] = [value[0], value[1]]; + setPodRange(newRange); + setTransferData(computeTransferData(selectedPlots, newRange)); + }, + [selectedPlots, setTransferData], + ); + + return ( +
+
+ + + + {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])}

+
+
+
+ )} + + + + + + {destination && ( + + + + )} + + +
+ ); +} 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 000000000..86237f983 --- /dev/null +++ b/src/pages/transfer/actions/beanstalk-pods/StepTwo.tsx @@ -0,0 +1,39 @@ +import AddressInputField from "@/components/AddressInputField"; +import PintoAssetTransferNotice from "@/components/PintoAssetTransferNotice"; +import { Label } from "@/components/ui/Label"; +import { AnimatePresence, motion } from "framer-motion"; +import { Dispatch, SetStateAction } from "react"; + +interface StepTwoProps { + destination: string | undefined; + setDestination: Dispatch>; + transferNotice: boolean; + setTransferNotice: Dispatch>; +} + +export default function StepTwo({ destination, setDestination, transferNotice, setTransferNotice }: StepTwoProps) { + return ( +
+
+ + + + {destination && ( + + + + )} + +
+
+ ); +} 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 000000000..83d2a8703 --- /dev/null +++ b/src/pages/transfer/actions/beanstalk-silo/FinalStep.tsx @@ -0,0 +1,25 @@ +import AddressLink from "@/components/AddressLink"; +import { Label } from "@/components/ui/Label"; + +interface FinalStepProps { + amount: string; + destination: string | undefined; +} + +export default function FinalStep({ amount, destination }: FinalStepProps) { + return ( +
+
+ +
+ {amount} + urBDV +
+
+
+ + +
+
+ ); +} 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 000000000..64219a347 --- /dev/null +++ b/src/pages/transfer/actions/beanstalk-silo/StepOne.tsx @@ -0,0 +1,81 @@ +import AddressInputField from "@/components/AddressInputField"; +import { ComboInputField } from "@/components/ComboInputField"; +import PintoAssetTransferNotice from "@/components/PintoAssetTransferNotice"; +import { Label } from "@/components/ui/Label"; +import { useFarmerBeanstalkRepayment } from "@/state/useFarmerBeanstalkRepayment"; +import { AnimatePresence, motion } from "framer-motion"; +import { Dispatch, SetStateAction } from "react"; + +interface StepOneProps { + amount: string; + setAmount: 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({ + amount, + setAmount, + destination, + setDestination, + transferNotice, + setTransferNotice, +}: StepOneProps) { + const repayment = useFarmerBeanstalkRepayment(); + const maxAmount = repayment.silo.balance; + + return ( + + + + + + + + + + + {destination && ( + + + + )} + + + + ); +} diff --git a/src/pages/transfer/actions/pods/FinalStep.tsx b/src/pages/transfer/actions/pods/FinalStep.tsx index e6a18bb51..6a25d4149 100644 --- a/src/pages/transfer/actions/pods/FinalStep.tsx +++ b/src/pages/transfer/actions/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 "../TransferPods"; 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/pods/StepOne.tsx b/src/pages/transfer/actions/pods/StepOne.tsx index 5e08ea3c0..b139df48e 100644 --- a/src/pages/transfer/actions/pods/StepOne.tsx +++ b/src/pages/transfer/actions/pods/StepOne.tsx @@ -1,93 +1,230 @@ -import { TokenValue } from "@/classes/TokenValue"; -import PlotsTable from "@/components/PlotsTable"; -import { Button } from "@/components/ui/Button"; +import AddressInputField from "@/components/AddressInputField"; +import PintoAssetTransferNotice from "@/components/PintoAssetTransferNotice"; +import PodLineGraph from "@/components/PodLineGraph"; import { Label } from "@/components/ui/Label"; -import { ToggleGroup } from "@/components/ui/ToggleGroup"; +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 { 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>; } -export default function StepOne({ transferData, setTransferData }: StepOneProps) { - const [selected, setSelected] = useState(); +function sortPlotsByIndex(plots: Plot[]): Plot[] { + return [...plots].sort((a, b) => a.index.sub(b.index).toNumber()); +} + +export default function StepOne({ + transferData, + setTransferData, + destination, + setDestination, + transferNotice, + setTransferNotice, +}: StepOneProps) { 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])}

+
+
+
+ )} + + + + + + {destination && ( + + + + )} + + +
); } diff --git a/src/pages/transfer/actions/pods/StepTwo.tsx b/src/pages/transfer/actions/pods/StepTwo.tsx index 1f8c07d78..86237f983 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/queries/beanstalk/podmarket/AllMarketActivity.graphql b/src/queries/beanstalk/podmarket/AllMarketActivity.graphql index cd5672688..0c14f2a16 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/queries/beanstalk/podmarket/AllPodListings.graphql b/src/queries/beanstalk/podmarket/AllPodListings.graphql index bb2884cb3..32898b9c4 100644 --- a/src/queries/beanstalk/podmarket/AllPodListings.graphql +++ b/src/queries/beanstalk/podmarket/AllPodListings.graphql @@ -5,6 +5,7 @@ query AllPodListings( $status: MarketStatus = ACTIVE $maxHarvestableIndex: BigInt! $skip: Int = 0 + $podMarketplace: String ) { podListings( first: $first @@ -13,6 +14,7 @@ query AllPodListings( status: $status maxHarvestableIndex_gt: $maxHarvestableIndex remainingAmount_gt: "100000" # = 0.10 Pods. hides dust pods. + podMarketplace: $podMarketplace } orderBy: index # index of the listed plot orderDirection: asc # start from earliest listings diff --git a/src/queries/beanstalk/podmarket/AllPodOrders.graphql b/src/queries/beanstalk/podmarket/AllPodOrders.graphql index 24e785528..6eb7f22ef 100644 --- a/src/queries/beanstalk/podmarket/AllPodOrders.graphql +++ b/src/queries/beanstalk/podmarket/AllPodOrders.graphql @@ -4,13 +4,14 @@ query AllPodOrders( $first: Int = 1000 $status: MarketStatus = ACTIVE $skip: Int = 0 + $podMarketplace: String ) { podOrders( first: $first skip: $skip orderBy: createdAt orderDirection: desc - where: { status: $status } + where: { status: $status, podMarketplace: $podMarketplace } ) { ...PodOrder } diff --git a/src/state/market/useAllMarket.ts b/src/state/market/useAllMarket.ts index f09bab3f0..8499c9c96 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); } diff --git a/src/state/market/usePodListings.ts b/src/state/market/usePodListings.ts index 8c0c7d5a0..97b52a865 100644 --- a/src/state/market/usePodListings.ts +++ b/src/state/market/usePodListings.ts @@ -10,19 +10,22 @@ import { useChainId } from "wagmi"; import { useHarvestableIndex } from "../useFieldData"; import { useQueryKeys } from "../useQueryKeys"; -export default function usePodListings() { +export default function usePodListings(podMarketplaceId?: string) { const chainId = useChainId(); const harvestableIndex = useHarvestableIndex(); const { allPodListings: queryKey } = useQueryKeys({ chainId, harvestableIndex }); const podListings = useQuery({ - queryKey: queryKey, - queryFn: async () => - 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 c874da161..1db4ec904 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 { diff --git a/src/state/useBeanstalkGlobalStats.ts b/src/state/useBeanstalkGlobalStats.ts new file mode 100644 index 000000000..922ea475a --- /dev/null +++ b/src/state/useBeanstalkGlobalStats.ts @@ -0,0 +1,141 @@ +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 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; + +/** + * Interface for the global Beanstalk statistics data + */ +export interface BeanstalkGlobalStatsData { + totalUrBdvDistributed: TokenValue; + totalPodsInRepaymentField: TokenValue; + totalUnfertilizedSprouts: TokenValue; + totalPintoPaidOut: TokenValue; + siloRemaining: TokenValue; + barnRemaining: TokenValue; + isLoading: boolean; + isError: boolean; + refetch: () => Promise; +} + +// Field ID for the Beanstalk repayment field +const BEANSTALK_REPAYMENT_FIELD_ID = 1n; + +/** + * Hook for fetching global Beanstalk repayment statistics + * + * This hook fetches protocol-wide statistics: + * - Silo Payback: siloRemaining() and totalDistributed() from Silo_Payback contract + * - Total pods in the repayment field (fieldId=1) + * - 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 + * + * @returns BeanstalkGlobalStatsData with all global statistics + */ +export function useBeanstalkGlobalStats(): BeanstalkGlobalStatsData { + const protocolAddress = useProtocolAddress(); + + // Query for available global statistics (only totalPods exists in protocol) + const globalQuery = useReadContracts({ + contracts: [ + { + address: protocolAddress, + abi: fieldGlobalAbi, + functionName: "totalPods", + args: [BEANSTALK_REPAYMENT_FIELD_ID], + }, + // 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", + }, + // Total Pinto received by Silo Payback contract + { + address: SILO_PAYBACK_ADDRESS, + abi: abiSnippets.siloPayback, + functionName: "totalReceived", + }, + ], + allowFailure: true, + query: { + ...defaultQuerySettingsMedium, // 5 minutes staleTime for global stats + }, + }); + + // 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; + 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: 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), + }; + }, [globalQuery.data, globalQuery.isError]); + + // 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 000000000..9ef3ec359 --- /dev/null +++ b/src/state/useFarmerBeanstalkRepayment.ts @@ -0,0 +1,407 @@ +import { TokenValue } from "@/classes/TokenValue"; +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"; +import { Plot } from "@/utils/types"; +import { useCallback, useMemo } from "react"; +import { toHex } from "viem"; +import { useAccount, useReadContracts } from "wagmi"; + +/** + * Interface for silo payback data + */ +interface SiloPaybackData { + balance: TokenValue; // getBalanceCombined(account) - total urBDV balance + earned: TokenValue; // earned(account) - earned but unclaimed + totalDistributed: TokenValue; // totalDistributed() - total distributed + totalReceived: TokenValue; // totalReceived() - total received +} + +/** + * Interface for pods data from repayment field (fieldId=1) + */ +interface PodsData { + plots: Plot[]; + totalPods: TokenValue; + harvestableIndex: TokenValue; + podIndex: TokenValue; +} + +/** + * Per-ID fertilizer detail + */ +export interface FertilizerIdDetail { + balance: bigint; // bsFERT balance for this ID + sprouts: bigint; // unfertilized beans remaining (amount * max(0, id - currentBpf)) + humidity: number; // humidity percentage, e.g. 500 means 500% +} + +/** + * Interface for fertilizer data + */ +interface FertilizerData { + balance: TokenValue; // Total bsFERT balance + fertilized: TokenValue; // balanceOfFertilized(account, ids) + unfertilized: TokenValue; // balanceOfUnfertilized(account, ids) + fertilizerIds: bigint[]; // Fertilizer IDs owned by the user + perIdData: Map; // Per-ID balance, sprouts, humidity +} + +/** + * 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; + +/** + * Hook for fetching farmer-specific Beanstalk repayment data + * + * This hook fetches: + * - Silo payback data from Silo_Payback contract (earned, balance, totalDistributed, totalReceived) + * - 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 { + const account = useAccount(); + const protocolAddress = useProtocolAddress(); + const farmerAddress = account.address ?? ZERO_ADDRESS; + + // Query for pods data from repayment field (fieldId=1) + // These are the only functions that exist in the protocol + 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 + }, + { + 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 + }, + { + address: protocolAddress, + abi: beanstalkAbi, + functionName: "podIndex", + args: [1n], // fieldId=1 for repayment field + }, + ], + allowFailure: true, + query: { + // Always fetch - will use ZERO_ADDRESS if wallet not connected + ...defaultQuerySettings, // 20 minutes staleTime + }, + }); + + // --- Phase 1: Get all global fertilizer IDs from linked list --- + const { + fertilizerIds: allFertilizerIds, + fertData, + 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 and build per-ID balance map --- + const { userOwnedIds, perIdBalances } = useMemo(() => { + if (!balanceChecks.data) return { userOwnedIds: [] as bigint[], perIdBalances: new Map() }; + const owned: bigint[] = []; + const balances = new Map(); + for (let i = 0; i < allFertilizerIds.length; i++) { + const result = balanceChecks.data?.[i]; + if (result?.status === "success" && (result.result as bigint) > 0n) { + const id = allFertilizerIds[i]; + owned.push(id); + balances.set(id.toString(), result.result as bigint); + } + } + return { userOwnedIds: owned, perIdBalances: balances }; + }, [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, userOwnedIds], + }, + { + address: BARN_PAYBACK_ADDRESS, + abi: abiSnippets.barnPayback, + functionName: "balanceOfUnfertilized", + args: [farmerAddress, userOwnedIds], + }, + ], + allowFailure: true, + query: { + ...defaultQuerySettings, + enabled: fertQueryEnabled, + }, + }); + + // 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(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 — defaults to ZERO on error + const podsData = useMemo((): PodsData => { + if (podsQuery.isError) { + return { + plots: [], + totalPods: TokenValue.ZERO, + harvestableIndex: TokenValue.ZERO, + podIndex: TokenValue.ZERO, + }; + } + + 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 podIndexResult = podsQuery.data?.[3]?.result as bigint | undefined; + + const harvestableIndex = TokenValue.fromBigInt(harvestableIndexResult ?? 0n, PODS.decimals); + let podIndex = TokenValue.fromBigInt(podIndexResult ?? 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(), + idHex: toHex(`${plotData.index}${plotData.pods}`), + index, + pods, + harvestedPods: TokenValue.ZERO, + harvestablePods, + unharvestablePods, + }; + }); + + // If podIndex is 0 (e.g. beanstalk repayment field has no native pod index), + // derive it from the last plot's end position + if (podIndex.isZero && plots.length > 0) { + const lastPlot = plots[plots.length - 1]; + podIndex = lastPlot.index.add(lastPlot.pods); + } + + return { + plots, + totalPods: TokenValue.fromBlockchain(totalPodsResult ?? 0n, PODS.decimals), + harvestableIndex, + podIndex, + }; + }, [podsQuery.data, podsQuery.isError]); + + // Process Barn Payback (bsFERT) data — defaults to ZERO on error + const fertilizerData = useMemo((): FertilizerData => { + // Build per-ID detail map with balance, sprouts, humidity + const perIdData = new Map(); + const currentBpf = fertData?.bpf ?? 0n; + + for (const id of userOwnedIds) { + const idStr = id.toString(); + const balance = perIdBalances.get(idStr) ?? 0n; + // Sprouts (unfertilized beans) = balance * max(0, id - currentBpf) + const remainingBpf = id > currentBpf ? id - currentBpf : 0n; + const sprouts = balance * remainingBpf; + // Humidity: fertId = endBpf = (1 + humidity/100) * 1e6 + // humidity% = (id / 1e6 - 1) * 100 + const humidity = (Number(id) / 1e6 - 1) * 100; + perIdData.set(idStr, { balance, sprouts, humidity }); + } + + if (fertilizerQuery.isError) { + return { + balance: TokenValue.ZERO, + fertilized: TokenValue.ZERO, + unfertilized: TokenValue.ZERO, + fertilizerIds: userOwnedIds, + perIdData, + }; + } + + const fertilizedResult = fertilizerQuery.data?.[0]?.result; + const unfertilizedResult = fertilizerQuery.data?.[1]?.result; + + 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); + + return { + balance, + fertilized, + unfertilized, + fertilizerIds: userOwnedIds, + perIdData, + }; + }, [fertilizerQuery.data, fertilizerQuery.isError, userOwnedIds, perIdBalances, fertData]); + + // Refetch all queries + const refetch = useCallback(async () => { + 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 + // 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); + + return useMemo( + () => ({ + silo: siloData, + pods: podsData, + fertilizer: fertilizerData, + isLoading, + isError, + refetch, + }), + [siloData, podsData, fertilizerData, isLoading, isError, refetch], + ); +} diff --git a/src/utils/podTransferUtils.ts b/src/utils/podTransferUtils.ts new file mode 100644 index 000000000..ebbff1758 --- /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), + }; +}