From a9bc9469cc69b71bee300388d8216e8f59b90092 Mon Sep 17 00:00:00 2001 From: fr1j0 Date: Sat, 4 Oct 2025 01:01:50 +0300 Subject: [PATCH 01/99] feat: add referral link generation feature with analytics tracking --- src/ProtectedLayout.tsx | 9 ++ src/components/ReferralLinkGenerator.tsx | 105 +++++++++++++ src/components/nav/nav/Navbar.tsx | 1 + src/components/nav/nav/Navi.desktop.tsx | 5 + src/components/nav/nav/Navi.mobile.tsx | 7 + src/constants/analytics-events.ts | 22 +++ src/pages/Referral.tsx | 181 +++++++++++++++++++++++ src/utils/referral.ts | 63 ++++++++ 8 files changed, 393 insertions(+) create mode 100644 src/components/ReferralLinkGenerator.tsx create mode 100644 src/pages/Referral.tsx create mode 100644 src/utils/referral.ts diff --git a/src/ProtectedLayout.tsx b/src/ProtectedLayout.tsx index 09581e3b1..630ae118b 100644 --- a/src/ProtectedLayout.tsx +++ b/src/ProtectedLayout.tsx @@ -8,6 +8,7 @@ import Explorer from "./pages/Explorer"; import Field from "./pages/Field"; import { Market as MarketPage } from "./pages/Market"; import Overview from "./pages/Overview"; +import Referral from "./pages/Referral"; import Silo from "./pages/Silo"; import SiloToken from "./pages/SiloToken"; import Swap from "./pages/Swap"; @@ -60,6 +61,14 @@ export default function ProtectedLayout() { } /> + + + + } + /> +
Connect your wallet to generate a referral link
+ + ); + } + + const encodedRef = encodeReferralAddress(address); + const referralUrl = `${window.location.origin}/field?ref=${encodedRef}`; + + // Calculate total sown beans from plots + const totalSownBeans = farmerField.plots.reduce((total, plot) => { + // Each plot represents pods sown. To get beans sown, we use the initial sown amount + // which is stored in the plot data + return total + (plot.pods?.toNumber() || 0); + }, 0); + + // For now, we'll use the totalPods as a proxy. In production, you'd want to query + // the subgraph for the actual sownBeans value from farmer.field.sownBeans + const isEligible = totalSownBeans >= MIN_SOWN_BEANS; + + const handleCopy = () => { + navigator.clipboard.writeText(referralUrl); + toast.success("Referral link copied to clipboard!"); + + trackSimpleEvent(ANALYTICS_EVENTS.REFERRAL.LINK_COPIED, { + address, + is_eligible: isEligible, + total_sown_beans: totalSownBeans, + }); + }; + + const handleGenerateClick = () => { + trackSimpleEvent(ANALYTICS_EVENTS.REFERRAL.LINK_GENERATED, { + address, + is_eligible: isEligible, + total_sown_beans: totalSownBeans, + }); + }; + + return ( +
+
+
Your Referral Link
+
+ Share your link to earn 1% bonus Pods when others sow using it +
+
+ + {!isEligible && ( + + You need to sow at least {formatter.number(MIN_SOWN_BEANS)} Beans before your referral link will work. +
+ Current: {formatter.number(totalSownBeans)} Beans sown. +
+ )} + +
+
+ + +
+ + {isEligible && ( +
+ ✓ Your referral link is active! You'll earn 1% bonus Pods when someone sows using your link. +
+ )} +
+ +
+
How it works:
+
    +
  • Share your referral link with others
  • +
  • When they sow Beans using your link, you both earn bonus Pods
  • +
  • You receive 1% of the Pods they earn as a referral bonus
  • +
  • They get their full Pod allocation plus the referral bonus
  • +
+
+
+ ); +} diff --git a/src/components/nav/nav/Navbar.tsx b/src/components/nav/nav/Navbar.tsx index bd09404f1..64647d10a 100644 --- a/src/components/nav/nav/Navbar.tsx +++ b/src/components/nav/nav/Navbar.tsx @@ -331,6 +331,7 @@ export const navLinks = { silo: "/silo", field: "/field", swap: "/swap", + referral: "/referral", 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 c4ffabfc3..f82cce26f 100644 --- a/src/components/nav/nav/Navi.desktop.tsx +++ b/src/components/nav/nav/Navi.desktop.tsx @@ -100,6 +100,11 @@ const AppNavi = () => { Swap + + + Referral + + Pod Market diff --git a/src/components/nav/nav/Navi.mobile.tsx b/src/components/nav/nav/Navi.mobile.tsx index 2dd4a2c12..7581cce54 100644 --- a/src/components/nav/nav/Navi.mobile.tsx +++ b/src/components/nav/nav/Navi.mobile.tsx @@ -168,6 +168,13 @@ function MobileNavContent({ learnOpen, setLearnOpen, unmount, close }: IMobileNa > Swap + + Referral + { + trackSimpleEvent(ANALYTICS_EVENTS.REFERRAL.PAGE_VIEWED); + }, []); + + return ( + +
+ {/* Hero Section */} +
+

Pinto Referral Program

+

+ Earn rewards by referring new farmers to Pinto. Share your referral link and earn 1% of the Pods your + referrals sow. +

+
+ + {/* Main Referral Card */} + + + + + {/* Stats Section - Coming Soon */} + +

Your Referral Stats

+
+

Referral stats and leaderboard coming soon!

+

+ Track your earned Pods and see how you rank among top referrers. +

+
+
+ + {/* How It Works */} + +

How It Works

+
+
+
+ 1 +
+
+

Qualify as a Referrer

+

Sow at least 1,000 Beans in the Field to unlock your referral link.

+
+
+ +
+
+ 2 +
+
+

Share Your Link

+

Copy your unique referral link and share it with friends, on social media, or anywhere else.

+
+
+ +
+
+ 3 +
+
+

Earn Rewards

+

+ When someone uses your link and sows Beans, you earn 1% of the Pods they receive as a referral bonus. +

+
+
+ +
+
+ 4 +
+
+

Get Credited

+

+ Referral rewards are automatically credited to your wallet address when your referral completes their + sow transaction. +

+
+
+
+
+ + {/* Example Section */} + +

Example

+
+
+

Scenario:

+

+ Alice has sown 5,000 Beans in the Field. She shares her referral link with Bob. +

+
+ +
+

What Happens:

+
    +
  • Bob clicks Alice's referral link and visits Pinto
  • +
  • Bob connects his wallet and sows 1,000 Beans
  • +
  • Bob receives 1,000 Pods (assuming 1:1 weather)
  • +
  • Alice automatically receives 10 Pods (1% of 1,000 Pods) as a referral bonus
  • +
+
+ +
+

Result:

+

+ Alice earns passive Pods every time someone uses her referral link to sow Beans. The more people she + refers, the more she earns! +

+
+
+
+ + {/* Requirements Card */} + +

Requirements & FAQs

+
+
+

Why do I need to sow 1,000 Beans first?

+

+ This requirement ensures that referrers are genuine participants in the Pinto ecosystem and helps + prevent spam or gaming of the referral system. +

+
+ +
+

+ Is there a limit to how many people I can refer? +

+

No! You can refer as many people as you'd like. There is no cap on referral earnings.

+
+ +
+

When do I receive my referral rewards?

+

+ Referral rewards are credited immediately when your referral completes their sow transaction. The Pods + are sent directly to your wallet address. +

+
+ +
+

Can I refer myself?

+

+ No, self-referrals are not allowed. Referral links are tracked by wallet address, so using your own link + won't generate rewards. +

+
+ +
+

What if I lose my referral link?

+

+ Don't worry! You can always come back to this page while connected with your wallet to retrieve your + referral link. It's permanently associated with your wallet address. +

+
+
+
+ + {/* Call to Action */} + +
+

Ready to Start Earning?

+

+ Connect your wallet and start sharing your referral link today. Help grow the Pinto community and earn + passive rewards! +

+
+
+
+
+ ); +} diff --git a/src/utils/referral.ts b/src/utils/referral.ts new file mode 100644 index 000000000..1ae7ce0b3 --- /dev/null +++ b/src/utils/referral.ts @@ -0,0 +1,63 @@ +import type { Address } from "viem"; +import { isAddress, zeroAddress } from "viem"; + +/** + * Encode an Ethereum address to base64 for URL shortening + * Treats the address as raw bytes (20 bytes) and encodes to base64 + * + * @param address - Ethereum address (0x prefixed hex string) + * @returns Base64 encoded string (28 characters) + */ +export function encodeReferralAddress(address: Address): string { + // Remove 0x prefix + const hex = address.slice(2); + + // Convert hex string to byte array + const hexPairs = hex.match(/.{1,2}/g) || []; + const bytes = new Uint8Array(hexPairs.map((byte) => Number.parseInt(byte, 16))); + + // Convert bytes to base64 + const base64 = btoa(String.fromCharCode(...bytes)); + + return base64; +} + +/** + * Decode a base64 referral code back to an Ethereum address + * + * @param encoded - Base64 encoded referral code + * @returns Ethereum address or null if invalid + */ +export function decodeReferralAddress(encoded: string): Address | null { + try { + // Decode base64 to bytes + const decoded = atob(encoded); + const bytes = new Uint8Array(decoded.length); + for (let i = 0; i < decoded.length; i++) { + bytes[i] = decoded.charCodeAt(i); + } + + // Convert bytes to hex string + const hex = `0x${Array.from(bytes) + .map((b) => b.toString(16).padStart(2, "0")) + .join("")}`; + + // Validate it's a proper address + if (!isAddress(hex)) return null; + if (hex === zeroAddress) return null; + + return hex as Address; + } catch { + return null; + } +} + +/** + * Check if a referral code is valid (can be decoded to a valid address) + * + * @param code - Base64 encoded referral code + * @returns true if valid, false otherwise + */ +export function isValidReferralCode(code: string): boolean { + return decodeReferralAddress(code) !== null; +} From 619d3774d04d2ba413bd74a74696af1067714592 Mon Sep 17 00:00:00 2001 From: fr1j0 Date: Sat, 4 Oct 2025 01:08:53 +0300 Subject: [PATCH 02/99] feat: add referral rewards info to Swap rewards tooltip --- src/constants/meta.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/constants/meta.ts b/src/constants/meta.ts index db4a6cf8d..4986a172d 100644 --- a/src/constants/meta.ts +++ b/src/constants/meta.ts @@ -25,6 +25,7 @@ const slugs = [ "PINTO", "sPinto", "tractor", + "referral", ] as const; const nestedSlugs = ["PINTOUSDC", "PINTOcbBTC", "PINTOWSOL", "PINTOWETH", "PINTOcbETH", "PINTO"] as const; @@ -133,6 +134,11 @@ const PINTO_META: Record = { description: "View and manage your collection of Pinto NFTs.", url: "https://pinto.money/collectionsoon", }, + referral: { + title: "Referral | Pinto", + description: "Share Pinto and earn rewards through referrals.", + url: "https://pinto.money/referral", + }, }; export default PINTO_META; From 5e4bb9b7267e16436dbb86be7b002358c3d9d1d1 Mon Sep 17 00:00:00 2001 From: fr1j0 Date: Sat, 4 Oct 2025 02:07:50 +0300 Subject: [PATCH 03/99] feat: enhance referral link UX with real-time copy feedback and improved analytics tracking --- src/components/ReferralLinkGenerator.tsx | 32 ++++++++++-- src/constants/analytics-events.ts | 1 + src/pages/Referral.tsx | 64 +++++++----------------- 3 files changed, 47 insertions(+), 50 deletions(-) diff --git a/src/components/ReferralLinkGenerator.tsx b/src/components/ReferralLinkGenerator.tsx index 8ffc59175..c0a1fa9d4 100644 --- a/src/components/ReferralLinkGenerator.tsx +++ b/src/components/ReferralLinkGenerator.tsx @@ -7,7 +7,7 @@ import { useFarmerField } from "@/state/useFarmerField"; import { trackSimpleEvent } from "@/utils/analytics"; import { formatter } from "@/utils/format"; import { encodeReferralAddress } from "@/utils/referral"; -import { CopyIcon } from "@radix-ui/react-icons"; +import { CopyIcon, Share1Icon } from "@radix-ui/react-icons"; import { toast } from "sonner"; import { useAccount } from "wagmi"; @@ -58,6 +58,19 @@ export function ReferralLinkGenerator() { }); }; + const handleTwitterShare = () => { + const tweetText = + "🌱 I'm farming on @PintoProtocol and earning passive rewards!\n\nJoin me and we both earn bonus Pods when you sow Beans 🫘\n\nStart farming today:"; + const twitterUrl = `https://twitter.com/intent/tweet?text=${encodeURIComponent(tweetText)}&url=${encodeURIComponent(referralUrl)}`; + window.open(twitterUrl, "_blank", "noopener,noreferrer"); + + trackSimpleEvent(ANALYTICS_EVENTS.REFERRAL.TWITTER_SHARE, { + address, + is_eligible: isEligible, + total_sown_beans: totalSownBeans, + }); + }; + return (
@@ -85,9 +98,20 @@ export function ReferralLinkGenerator() {
{isEligible && ( -
- ✓ Your referral link is active! You'll earn 1% bonus Pods when someone sows using your link. -
+ <> +
+ ✓ Your referral link is active! You'll earn 1% bonus Pods when someone sows using your link. +
+ + {/* Twitter Share Button */} +
+
Share on Social
+ +
+ )}
diff --git a/src/constants/analytics-events.ts b/src/constants/analytics-events.ts index fb49e1696..c6042b1a2 100644 --- a/src/constants/analytics-events.ts +++ b/src/constants/analytics-events.ts @@ -158,6 +158,7 @@ const REFERRAL_EVENTS = { LINK_GENERATED: "referral_link_generated", LINK_COPIED: "referral_link_copied", LINK_SHARED: "referral_link_shared", + TWITTER_SHARE: "referral_twitter_share", // Validation ELIGIBILITY_CHECKED: "referral_eligibility_checked", diff --git a/src/pages/Referral.tsx b/src/pages/Referral.tsx index f687e35bb..8f3065b74 100644 --- a/src/pages/Referral.tsx +++ b/src/pages/Referral.tsx @@ -22,21 +22,24 @@ export default function Referral() {

- {/* Main Referral Card */} - - - - - {/* Stats Section - Coming Soon */} - -

Your Referral Stats

-
-

Referral stats and leaderboard coming soon!

-

- Track your earned Pods and see how you rank among top referrers. -

-
-
+ {/* Main Referral Cards - Two Column Layout */} +
+ {/* Your Referral Link */} + + + + + {/* Your Referral Stats */} + +

Your Referral Stats

+
+

Referral stats and leaderboard coming soon!

+

+ Track your earned Pods and see how you rank among top referrers. +

+
+
+
{/* How It Works */} @@ -89,37 +92,6 @@ export default function Referral() { - {/* Example Section */} - -

Example

-
-
-

Scenario:

-

- Alice has sown 5,000 Beans in the Field. She shares her referral link with Bob. -

-
- -
-

What Happens:

-
    -
  • Bob clicks Alice's referral link and visits Pinto
  • -
  • Bob connects his wallet and sows 1,000 Beans
  • -
  • Bob receives 1,000 Pods (assuming 1:1 weather)
  • -
  • Alice automatically receives 10 Pods (1% of 1,000 Pods) as a referral bonus
  • -
-
- -
-

Result:

-

- Alice earns passive Pods every time someone uses her referral link to sow Beans. The more people she - refers, the more she earns! -

-
-
-
- {/* Requirements Card */}

Requirements & FAQs

From db9241e15371877e31696d185fc5d5dc26ece1d7 Mon Sep 17 00:00:00 2001 From: fr1j0 Date: Sat, 4 Oct 2025 02:34:56 +0300 Subject: [PATCH 04/99] feat: improve referral page styling and add progress bar MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Update page layout to use consistent design system (variant="lg" container) - Replace all typography with Pinto design system classes - Add Separator component for consistent visual hierarchy - Replace text-based warning with visual horizontal progress bar - Show qualification progress with animated gradient bar - Display remaining beans needed to unlock referral link - Update all color classes to use design system tokens - Improve mobile responsiveness with responsive padding 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- src/components/ReferralLinkGenerator.tsx | 35 ++-- src/pages/Referral.tsx | 241 ++++++++++++----------- 2 files changed, 150 insertions(+), 126 deletions(-) diff --git a/src/components/ReferralLinkGenerator.tsx b/src/components/ReferralLinkGenerator.tsx index c0a1fa9d4..f5139a2d1 100644 --- a/src/components/ReferralLinkGenerator.tsx +++ b/src/components/ReferralLinkGenerator.tsx @@ -1,7 +1,6 @@ import { Button } from "@/components/ui/Button"; import { Card } from "@/components/ui/Card"; import { Input } from "@/components/ui/Input"; -import Warning from "@/components/ui/Warning"; import { ANALYTICS_EVENTS } from "@/constants/analytics-events"; import { useFarmerField } from "@/state/useFarmerField"; import { trackSimpleEvent } from "@/utils/analytics"; @@ -71,21 +70,35 @@ export function ReferralLinkGenerator() { }); }; + const progressPercentage = Math.min((totalSownBeans / MIN_SOWN_BEANS) * 100, 100); + return (
Your Referral Link
-
+
Share your link to earn 1% bonus Pods when others sow using it
{!isEligible && ( - - You need to sow at least {formatter.number(MIN_SOWN_BEANS)} Beans before your referral link will work. -
- Current: {formatter.number(totalSownBeans)} Beans sown. -
+
+
+
Qualification Progress
+
+ {formatter.number(totalSownBeans)} / {formatter.number(MIN_SOWN_BEANS)} Beans +
+
+
+
+
+
+ Sow {formatter.number(MIN_SOWN_BEANS - totalSownBeans)} more Beans to unlock your referral link +
+
)}
@@ -99,13 +112,13 @@ export function ReferralLinkGenerator() { {isEligible && ( <> -
+
✓ Your referral link is active! You'll earn 1% bonus Pods when someone sows using your link.
{/* Twitter Share Button */}
-
Share on Social
+
Share on Social
-
How it works:
-
    +
    How it works:
    +
    • Share your referral link with others
    • When they sow Beans using your link, you both earn bonus Pods
    • You receive 1% of the Pods they earn as a referral bonus
    • diff --git a/src/pages/Referral.tsx b/src/pages/Referral.tsx index 8f3065b74..70026f30d 100644 --- a/src/pages/Referral.tsx +++ b/src/pages/Referral.tsx @@ -1,5 +1,6 @@ import PageContainer from "@/components/ui/PageContainer"; import { Card } from "@/components/ui/Card"; +import { Separator } from "@/components/ui/Separator"; import { ReferralLinkGenerator } from "@/components/ReferralLinkGenerator"; import { ANALYTICS_EVENTS } from "@/constants/analytics-events"; import { trackSimpleEvent } from "@/utils/analytics"; @@ -11,142 +12,152 @@ export default function Referral() { }, []); return ( - -
      - {/* Hero Section */} -
      -

      Pinto Referral Program

      -

      - Earn rewards by referring new farmers to Pinto. Share your referral link and earn 1% of the Pods your - referrals sow. -

      -
      + +
      +
      + {/* Hero Section */} +
      +
      Referral Program
      +
      + Earn rewards by referring new farmers to Pinto. Share your referral link and earn 1% of the Pods your + referrals sow. +
      +
      + - {/* Main Referral Cards - Two Column Layout */} -
      - {/* Your Referral Link */} - - - + {/* Main Referral Cards - Two Column Layout */} +
      + {/* Your Referral Link */} + + + - {/* Your Referral Stats */} - -

      Your Referral Stats

      -
      -

      Referral stats and leaderboard coming soon!

      -

      - Track your earned Pods and see how you rank among top referrers. -

      -
      -
      -
      + {/* Your Referral Stats */} + +
      Your Referral Stats
      +
      +
      Referral stats and leaderboard coming soon!
      +
      + Track your earned Pods and see how you rank among top referrers. +
      +
      +
      +
      - {/* How It Works */} - -

      How It Works

      -
      -
      -
      - 1 + {/* How It Works */} + +
      How It Works
      +
      +
      +
      + 1 +
      +
      +
      Qualify as a Referrer
      +
      + Sow at least 1,000 Beans in the Field to unlock your referral link. +
      +
      -
      -

      Qualify as a Referrer

      -

      Sow at least 1,000 Beans in the Field to unlock your referral link.

      + +
      +
      + 2 +
      +
      +
      Share Your Link
      +
      + Copy your unique referral link and share it with friends, on social media, or anywhere else. +
      +
      -
      -
      -
      - 2 +
      +
      + 3 +
      +
      +
      Earn Rewards
      +
      + When someone uses your link and sows Beans, you earn 1% of the Pods they receive as a referral + bonus. +
      +
      -
      -

      Share Your Link

      -

      Copy your unique referral link and share it with friends, on social media, or anywhere else.

      + +
      +
      + 4 +
      +
      +
      Get Credited
      +
      + Referral rewards are automatically credited to your wallet address when your referral completes + their sow transaction. +
      +
      + -
      -
      - 3 -
      + {/* Requirements Card */} + +
      Requirements & FAQs
      +
      -

      Earn Rewards

      -

      - When someone uses your link and sows Beans, you earn 1% of the Pods they receive as a referral bonus. -

      +
      Why do I need to sow 1,000 Beans first?
      +
      + This requirement ensures that referrers are genuine participants in the Pinto ecosystem and helps + prevent spam or gaming of the referral system. +
      -
      -
      -
      - 4 -
      -

      Get Credited

      -

      - Referral rewards are automatically credited to your wallet address when your referral completes their - sow transaction. -

      +
      + Is there a limit to how many people I can refer? +
      +
      + No! You can refer as many people as you'd like. There is no cap on referral earnings. +
      -
      -
      - - - {/* Requirements Card */} - -

      Requirements & FAQs

      -
      -
      -

      Why do I need to sow 1,000 Beans first?

      -

      - This requirement ensures that referrers are genuine participants in the Pinto ecosystem and helps - prevent spam or gaming of the referral system. -

      -
      -
      -

      - Is there a limit to how many people I can refer? -

      -

      No! You can refer as many people as you'd like. There is no cap on referral earnings.

      -
      +
      +
      When do I receive my referral rewards?
      +
      + Referral rewards are credited immediately when your referral completes their sow transaction. The Pods + are sent directly to your wallet address. +
      +
      -
      -

      When do I receive my referral rewards?

      -

      - Referral rewards are credited immediately when your referral completes their sow transaction. The Pods - are sent directly to your wallet address. -

      -
      +
      +
      Can I refer myself?
      +
      + No, self-referrals are not allowed. Referral links are tracked by wallet address, so using your own + link won't generate rewards. +
      +
      -
      -

      Can I refer myself?

      -

      - No, self-referrals are not allowed. Referral links are tracked by wallet address, so using your own link - won't generate rewards. -

      +
      +
      What if I lose my referral link?
      +
      + Don't worry! You can always come back to this page while connected with your wallet to retrieve your + referral link. It's permanently associated with your wallet address. +
      +
      + -
      -

      What if I lose my referral link?

      -

      - Don't worry! You can always come back to this page while connected with your wallet to retrieve your - referral link. It's permanently associated with your wallet address. -

      + {/* Call to Action */} + +
      +
      Ready to Start Earning?
      +
      + Connect your wallet and start sharing your referral link today. Help grow the Pinto community and earn + passive rewards! +
      -
      - - - {/* Call to Action */} - -
      -

      Ready to Start Earning?

      -

      - Connect your wallet and start sharing your referral link today. Help grow the Pinto community and earn - passive rewards! -

      -
      -
      + +
      ); From a7f7a370f3ece40ab600914ce6312445ca8a2834 Mon Sep 17 00:00:00 2001 From: fr1j0 Date: Sat, 4 Oct 2025 03:06:19 +0300 Subject: [PATCH 05/99] fix: improve referral link input width and layout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add min-w-0 to input to allow proper flexbox shrinking - Add text-sm to input for better URL readability - Add whitespace-nowrap to Copy button to prevent text wrapping - Ensure input takes maximum available space in flex container 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- src/components/ReferralLinkGenerator.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/ReferralLinkGenerator.tsx b/src/components/ReferralLinkGenerator.tsx index f5139a2d1..d917ccc09 100644 --- a/src/components/ReferralLinkGenerator.tsx +++ b/src/components/ReferralLinkGenerator.tsx @@ -103,8 +103,8 @@ export function ReferralLinkGenerator() {
      - - From 6926830c6ebc88b4445ccb54672328d90068433e Mon Sep 17 00:00:00 2001 From: fr1j0 Date: Sat, 4 Oct 2025 03:08:39 +0300 Subject: [PATCH 06/99] refactor: remove redundant section and rebrand beans to pinto MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove "How it works" section from ReferralLinkGenerator (redundant with main page) - Replace all instances of "Beans" with "Pinto" for brand consistency - Update progress bar labels to show "Pinto" instead of "Beans" - Update Twitter share text to reference "Pinto" - Update FAQ text to use "Pinto" terminology 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- src/components/ReferralLinkGenerator.tsx | 16 +++------------- src/pages/Referral.tsx | 6 +++--- 2 files changed, 6 insertions(+), 16 deletions(-) diff --git a/src/components/ReferralLinkGenerator.tsx b/src/components/ReferralLinkGenerator.tsx index d917ccc09..cecbfe7bd 100644 --- a/src/components/ReferralLinkGenerator.tsx +++ b/src/components/ReferralLinkGenerator.tsx @@ -59,7 +59,7 @@ export function ReferralLinkGenerator() { const handleTwitterShare = () => { const tweetText = - "🌱 I'm farming on @PintoProtocol and earning passive rewards!\n\nJoin me and we both earn bonus Pods when you sow Beans 🫘\n\nStart farming today:"; + "🌱 I'm farming on @PintoProtocol and earning passive rewards!\n\nJoin me and we both earn bonus Pods when you sow Pinto 🫘\n\nStart farming today:"; const twitterUrl = `https://twitter.com/intent/tweet?text=${encodeURIComponent(tweetText)}&url=${encodeURIComponent(referralUrl)}`; window.open(twitterUrl, "_blank", "noopener,noreferrer"); @@ -86,7 +86,7 @@ export function ReferralLinkGenerator() {
      Qualification Progress
      - {formatter.number(totalSownBeans)} / {formatter.number(MIN_SOWN_BEANS)} Beans + {formatter.number(totalSownBeans)} / {formatter.number(MIN_SOWN_BEANS)} Pinto
      @@ -96,7 +96,7 @@ export function ReferralLinkGenerator() { />
      - Sow {formatter.number(MIN_SOWN_BEANS - totalSownBeans)} more Beans to unlock your referral link + Sow {formatter.number(MIN_SOWN_BEANS - totalSownBeans)} more Pinto to unlock your referral link
      )} @@ -127,16 +127,6 @@ export function ReferralLinkGenerator() { )}
      - -
      -
      How it works:
      -
        -
      • Share your referral link with others
      • -
      • When they sow Beans using your link, you both earn bonus Pods
      • -
      • You receive 1% of the Pods they earn as a referral bonus
      • -
      • They get their full Pod allocation plus the referral bonus
      • -
      -
      ); } diff --git a/src/pages/Referral.tsx b/src/pages/Referral.tsx index 70026f30d..d3058f9d1 100644 --- a/src/pages/Referral.tsx +++ b/src/pages/Referral.tsx @@ -55,7 +55,7 @@ export default function Referral() {
      Qualify as a Referrer
      - Sow at least 1,000 Beans in the Field to unlock your referral link. + Sow at least 1,000 Pinto in the Field to unlock your referral link.
      @@ -79,7 +79,7 @@ export default function Referral() {
      Earn Rewards
      - When someone uses your link and sows Beans, you earn 1% of the Pods they receive as a referral + When someone uses your link and sows Pinto, you earn 1% of the Pods they receive as a referral bonus.
      @@ -105,7 +105,7 @@ export default function Referral() {
      Requirements & FAQs
      -
      Why do I need to sow 1,000 Beans first?
      +
      Why do I need to sow 1,000 Pinto first?
      This requirement ensures that referrers are genuine participants in the Pinto ecosystem and helps prevent spam or gaming of the referral system. From a2d756255bbd4ae092abc5e8a25e4dcdd3133593 Mon Sep 17 00:00:00 2001 From: fr1j0 Date: Sat, 4 Oct 2025 03:09:33 +0300 Subject: [PATCH 07/99] feat: separate leaderboard and improve referral stats display MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Split "Your Referral Stats" and "Referral Leaderboard" into separate cards - Move leaderboard section above "How It Works" component - Update referral stats to show specific metrics: - Total Pods Earned (placeholder: 0) - Successful Referrals (placeholder: 0) - Improve visual hierarchy with dedicated sections - Add better spacing and layout for stats display 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- src/pages/Referral.tsx | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/src/pages/Referral.tsx b/src/pages/Referral.tsx index d3058f9d1..ca4ff659f 100644 --- a/src/pages/Referral.tsx +++ b/src/pages/Referral.tsx @@ -35,15 +35,30 @@ export default function Referral() { {/* Your Referral Stats */}
      Your Referral Stats
      -
      -
      Referral stats and leaderboard coming soon!
      -
      - Track your earned Pods and see how you rank among top referrers. +
      +
      +
      Total Pods Earned
      +
      0
      +
      +
      +
      Successful Referrals
      +
      0
      + {/* Leaderboard */} + +
      Referral Leaderboard
      +
      +
      Leaderboard coming soon!
      +
      + See how you rank among top referrers in the Pinto community. +
      +
      +
      + {/* How It Works */}
      How It Works
      From 94551f1c6a60495c68d69f197afc0114d7cc9101 Mon Sep 17 00:00:00 2001 From: fr1j0 Date: Sat, 4 Oct 2025 03:11:55 +0300 Subject: [PATCH 08/99] fix: update referral bonus to 10% and clarify only referrer earns MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Update all instances of 1% to 10% referral bonus - Clarify that only the referrer earns the bonus, not both parties - Update Twitter share text to reflect accurate mechanics - Update hero description and all "How It Works" copy - Remove misleading "you both earn" language 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- src/components/ReferralLinkGenerator.tsx | 6 +++--- src/pages/Referral.tsx | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/ReferralLinkGenerator.tsx b/src/components/ReferralLinkGenerator.tsx index cecbfe7bd..abd438004 100644 --- a/src/components/ReferralLinkGenerator.tsx +++ b/src/components/ReferralLinkGenerator.tsx @@ -59,7 +59,7 @@ export function ReferralLinkGenerator() { const handleTwitterShare = () => { const tweetText = - "🌱 I'm farming on @PintoProtocol and earning passive rewards!\n\nJoin me and we both earn bonus Pods when you sow Pinto 🫘\n\nStart farming today:"; + "🌱 I'm farming on @PintoProtocol and earning passive rewards!\n\nJoin me and I'll earn bonus Pods when you sow Pinto 🫘\n\nStart farming today:"; const twitterUrl = `https://twitter.com/intent/tweet?text=${encodeURIComponent(tweetText)}&url=${encodeURIComponent(referralUrl)}`; window.open(twitterUrl, "_blank", "noopener,noreferrer"); @@ -77,7 +77,7 @@ export function ReferralLinkGenerator() {
      Your Referral Link
      - Share your link to earn 1% bonus Pods when others sow using it + Share your link to earn 10% bonus Pods when others sow using it
      @@ -113,7 +113,7 @@ export function ReferralLinkGenerator() { {isEligible && ( <>
      - ✓ Your referral link is active! You'll earn 1% bonus Pods when someone sows using your link. + ✓ Your referral link is active! You'll earn 10% bonus Pods when someone sows using your link.
      {/* Twitter Share Button */} diff --git a/src/pages/Referral.tsx b/src/pages/Referral.tsx index ca4ff659f..70fa605e1 100644 --- a/src/pages/Referral.tsx +++ b/src/pages/Referral.tsx @@ -19,7 +19,7 @@ export default function Referral() {
      Referral Program
      - Earn rewards by referring new farmers to Pinto. Share your referral link and earn 1% of the Pods your + Earn rewards by referring new farmers to Pinto. Share your referral link and earn 10% of the Pods your referrals sow.
      @@ -94,7 +94,7 @@ export default function Referral() {
      Earn Rewards
      - When someone uses your link and sows Pinto, you earn 1% of the Pods they receive as a referral + When someone uses your link and sows Pinto, you earn 10% of the Pods they receive as a referral bonus.
      From a049d72812fd4a926dcea7839d41f804c9f5058a Mon Sep 17 00:00:00 2001 From: fr1j0 Date: Sat, 4 Oct 2025 03:13:25 +0300 Subject: [PATCH 09/99] refactor: capitalize Sow, remove CTA, and improve stats layout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Capitalize all instances of "sow/sows" to "Sow/Sows" for brand consistency - Remove "Ready to Start Earning?" CTA card (redundant) - Change stats layout from 2 rows to 2 columns in 1 row (grid-cols-2) - Update all copy across both referral components 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- src/components/ReferralLinkGenerator.tsx | 6 +++--- src/pages/Referral.tsx | 23 ++++++----------------- 2 files changed, 9 insertions(+), 20 deletions(-) diff --git a/src/components/ReferralLinkGenerator.tsx b/src/components/ReferralLinkGenerator.tsx index abd438004..6ffde32d8 100644 --- a/src/components/ReferralLinkGenerator.tsx +++ b/src/components/ReferralLinkGenerator.tsx @@ -59,7 +59,7 @@ export function ReferralLinkGenerator() { const handleTwitterShare = () => { const tweetText = - "🌱 I'm farming on @PintoProtocol and earning passive rewards!\n\nJoin me and I'll earn bonus Pods when you sow Pinto 🫘\n\nStart farming today:"; + "🌱 I'm farming on @PintoProtocol and earning passive rewards!\n\nJoin me and I'll earn bonus Pods when you Sow Pinto 🫘\n\nStart farming today:"; const twitterUrl = `https://twitter.com/intent/tweet?text=${encodeURIComponent(tweetText)}&url=${encodeURIComponent(referralUrl)}`; window.open(twitterUrl, "_blank", "noopener,noreferrer"); @@ -77,7 +77,7 @@ export function ReferralLinkGenerator() {
      Your Referral Link
      - Share your link to earn 10% bonus Pods when others sow using it + Share your link to earn 10% bonus Pods when others Sow using it
      @@ -113,7 +113,7 @@ export function ReferralLinkGenerator() { {isEligible && ( <>
      - ✓ Your referral link is active! You'll earn 10% bonus Pods when someone sows using your link. + ✓ Your referral link is active! You'll earn 10% bonus Pods when someone Sows using your link.
      {/* Twitter Share Button */} diff --git a/src/pages/Referral.tsx b/src/pages/Referral.tsx index 70fa605e1..7fbd10a4b 100644 --- a/src/pages/Referral.tsx +++ b/src/pages/Referral.tsx @@ -20,7 +20,7 @@ export default function Referral() {
      Referral Program
      Earn rewards by referring new farmers to Pinto. Share your referral link and earn 10% of the Pods your - referrals sow. + referrals Sow.
      @@ -35,7 +35,7 @@ export default function Referral() { {/* Your Referral Stats */}
      Your Referral Stats
      -
      +
      Total Pods Earned
      0
      @@ -94,7 +94,7 @@ export default function Referral() {
      Earn Rewards
      - When someone uses your link and sows Pinto, you earn 10% of the Pods they receive as a referral + When someone uses your link and Sows Pinto, you earn 10% of the Pods they receive as a referral bonus.
      @@ -108,7 +108,7 @@ export default function Referral() {
      Get Credited
      Referral rewards are automatically credited to your wallet address when your referral completes - their sow transaction. + their Sow transaction.
      @@ -120,7 +120,7 @@ export default function Referral() {
      Requirements & FAQs
      -
      Why do I need to sow 1,000 Pinto first?
      +
      Why do I need to Sow 1,000 Pinto first?
      This requirement ensures that referrers are genuine participants in the Pinto ecosystem and helps prevent spam or gaming of the referral system. @@ -139,7 +139,7 @@ export default function Referral() {
      When do I receive my referral rewards?
      - Referral rewards are credited immediately when your referral completes their sow transaction. The Pods + Referral rewards are credited immediately when your referral completes their Sow transaction. The Pods are sent directly to your wallet address.
      @@ -161,17 +161,6 @@ export default function Referral() {
      - - {/* Call to Action */} - -
      -
      Ready to Start Earning?
      -
      - Connect your wallet and start sharing your referral link today. Help grow the Pinto community and earn - passive rewards! -
      -
      -
      From b32e59c16c5ce2e3472fff4fad5b081d34e25b4d Mon Sep 17 00:00:00 2001 From: fr1j0 Date: Sat, 4 Oct 2025 03:15:08 +0300 Subject: [PATCH 10/99] feat: improve referral link UX with disabled state MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Move referral link input to always be visible at top - Disable input and copy button when threshold not met (grayed out) - Reorder UI: link input first, then qualification progress, then active state - Better visual hierarchy showing link is locked until qualified - Input adapts to full width with flex-1 min-w-0 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- src/components/ReferralLinkGenerator.tsx | 55 ++++++++++++++---------- 1 file changed, 33 insertions(+), 22 deletions(-) diff --git a/src/components/ReferralLinkGenerator.tsx b/src/components/ReferralLinkGenerator.tsx index 6ffde32d8..8a387c377 100644 --- a/src/components/ReferralLinkGenerator.tsx +++ b/src/components/ReferralLinkGenerator.tsx @@ -81,35 +81,46 @@ export function ReferralLinkGenerator() {
      - {!isEligible && ( -
      -
      -
      Qualification Progress
      -
      - {formatter.number(totalSownBeans)} / {formatter.number(MIN_SOWN_BEANS)} Pinto -
      -
      -
      -
      -
      -
      - Sow {formatter.number(MIN_SOWN_BEANS - totalSownBeans)} more Pinto to unlock your referral link -
      -
      - )} -
      - -
      + {!isEligible && ( +
      +
      +
      Qualification Progress
      +
      + {formatter.number(totalSownBeans)} / {formatter.number(MIN_SOWN_BEANS)} Pinto +
      +
      +
      +
      +
      +
      + Sow {formatter.number(MIN_SOWN_BEANS - totalSownBeans)} more Pinto to unlock your referral link +
      +
      + )} + {isEligible && ( <>
      From 6dde85868be200ae3770b21b977cee1664578d96 Mon Sep 17 00:00:00 2001 From: fr1j0 Date: Sat, 4 Oct 2025 03:16:43 +0300 Subject: [PATCH 11/99] feat: reorder navigation - move Referral after Pod Market MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Reorder navigation items in home tab - New order: Overview, Silo, Field, Swap, Pod Market, Referral, sPinto, Collection - Referral now appears after Pod Market and before Collection 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- src/components/nav/nav/Navi.desktop.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/nav/nav/Navi.desktop.tsx b/src/components/nav/nav/Navi.desktop.tsx index f82cce26f..da6f6eec1 100644 --- a/src/components/nav/nav/Navi.desktop.tsx +++ b/src/components/nav/nav/Navi.desktop.tsx @@ -101,13 +101,13 @@ const AppNavi = () => { - - Referral + + Pod Market - - Pod Market + + Referral From ea9872e162346722a0a9933341779493ba5ea561 Mon Sep 17 00:00:00 2001 From: fr1j0 Date: Sat, 4 Oct 2025 03:18:28 +0300 Subject: [PATCH 12/99] chore: apply Biome formatting to referral components MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- src/components/ReferralLinkGenerator.tsx | 7 +------ src/pages/Referral.tsx | 4 ++-- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/src/components/ReferralLinkGenerator.tsx b/src/components/ReferralLinkGenerator.tsx index 8a387c377..f1de6a888 100644 --- a/src/components/ReferralLinkGenerator.tsx +++ b/src/components/ReferralLinkGenerator.tsx @@ -90,12 +90,7 @@ export function ReferralLinkGenerator() { className="flex-1 min-w-0 text-sm" onClick={isEligible ? handleGenerateClick : undefined} /> - diff --git a/src/pages/Referral.tsx b/src/pages/Referral.tsx index 7fbd10a4b..f7e7ae101 100644 --- a/src/pages/Referral.tsx +++ b/src/pages/Referral.tsx @@ -1,7 +1,7 @@ -import PageContainer from "@/components/ui/PageContainer"; +import { ReferralLinkGenerator } from "@/components/ReferralLinkGenerator"; import { Card } from "@/components/ui/Card"; +import PageContainer from "@/components/ui/PageContainer"; import { Separator } from "@/components/ui/Separator"; -import { ReferralLinkGenerator } from "@/components/ReferralLinkGenerator"; import { ANALYTICS_EVENTS } from "@/constants/analytics-events"; import { trackSimpleEvent } from "@/utils/analytics"; import { useEffect } from "react"; From 5c06e1c85cb6c905626e276bdba23569898db22b Mon Sep 17 00:00:00 2001 From: fr1j0 Date: Sat, 4 Oct 2025 03:19:35 +0300 Subject: [PATCH 13/99] feat: reorder qualification progress to appear above link input MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Improves UX by showing users the progress they need to make before displaying the locked referral link input. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- src/components/ReferralLinkGenerator.tsx | 28 ++++++++++++------------ 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/components/ReferralLinkGenerator.tsx b/src/components/ReferralLinkGenerator.tsx index f1de6a888..2056ca3dc 100644 --- a/src/components/ReferralLinkGenerator.tsx +++ b/src/components/ReferralLinkGenerator.tsx @@ -82,20 +82,6 @@ export function ReferralLinkGenerator() {
      -
      - - -
      - {!isEligible && (
      @@ -116,6 +102,20 @@ export function ReferralLinkGenerator() {
      )} +
      + + +
      + {isEligible && ( <>
      From 36c851f9d9a5fb02098ff1de9c79708f0f0a435d Mon Sep 17 00:00:00 2001 From: burr Date: Wed, 12 Nov 2025 14:45:42 +0900 Subject: [PATCH 14/99] feat: add wsteth + pintowsteth --- src/constants/tokens.ts | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/constants/tokens.ts b/src/constants/tokens.ts index 1c62fe63d..dc955c84c 100644 --- a/src/constants/tokens.ts +++ b/src/constants/tokens.ts @@ -174,8 +174,39 @@ export const WSOL_TOKEN: ChainLookup = { }, } as const; +export const WSTETH_TOKEN: ChainLookup = { + [base.id]: { + chainId: base.id, + name: "Wrapped stETH", + symbol: "WSTETH", + address: "0xc1CBa3fCea344f92D9239c08C0568f6F2F0ee452", + decimals: 18, + displayDecimals: 2, + isLPUnderlying: true, + isCompositeLPWhitelisted: false, + logoURI: "https://assets.coingecko.com/coins/images/18834/thumb/wstETH.png?1696518295", + color: "#00A3FF", + }, +}; + // -------------------- LP TOKENS -------------------- +export const PINTO_WSTETH_TOKEN: ChainLookup = { + [base.id]: { + chainId: base.id, + name: "PINTOWSTETH LP", + symbol: "PINTOWSTETH", + address: "0xbd2d86B89353e0d441A3CC3d939A48f17CCDDff5", + decimals: 18, + displayDecimals: 6, // show 6 decimal places for this token + isLP: true, + isWhitelisted: false, + logoURI: pintoWsolIcon, + color: "#00A3FF", + tokens: [MAIN_TOKEN[base.id].address, WSTETH_TOKEN[base.id].address], + }, +} as const; + export const PINTO_WSOL_TOKEN: ChainLookup = { [base.id]: { chainId: base.id, From 47fc67a538d19343568742f0ae09857c90d0a4b2 Mon Sep 17 00:00:00 2001 From: burr Date: Wed, 12 Nov 2025 14:52:50 +0900 Subject: [PATCH 15/99] feat: add wsteth to silo page --- src/constants/tokens.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/constants/tokens.ts b/src/constants/tokens.ts index dc955c84c..b2a2088eb 100644 --- a/src/constants/tokens.ts +++ b/src/constants/tokens.ts @@ -183,7 +183,7 @@ export const WSTETH_TOKEN: ChainLookup = { decimals: 18, displayDecimals: 2, isLPUnderlying: true, - isCompositeLPWhitelisted: false, + isCompositeLPWhitelisted: true, logoURI: "https://assets.coingecko.com/coins/images/18834/thumb/wstETH.png?1696518295", color: "#00A3FF", }, @@ -200,7 +200,7 @@ export const PINTO_WSTETH_TOKEN: ChainLookup = { decimals: 18, displayDecimals: 6, // show 6 decimal places for this token isLP: true, - isWhitelisted: false, + isWhitelisted: true, logoURI: pintoWsolIcon, color: "#00A3FF", tokens: [MAIN_TOKEN[base.id].address, WSTETH_TOKEN[base.id].address], @@ -398,6 +398,7 @@ export const LP_TOKENS: ChainLookup = { PINTO_CBETH_TOKEN[base.id], PINTO_CBBTC_TOKEN[base.id], PINTO_WSOL_TOKEN[base.id], + PINTO_WSTETH_TOKEN[base.id], ], } as const; @@ -430,6 +431,7 @@ export const UNDERLYING_TOKENS: ChainLookup = { CBETH_TOKEN[base.id], CBBTC_TOKEN[base.id], WSOL_TOKEN[base.id], + WSTETH_TOKEN[base.id], ], } as const; From e41517674acaea7f4e7be78c97d4c46a336cd26a Mon Sep 17 00:00:00 2001 From: burr Date: Thu, 13 Nov 2025 13:01:33 +0800 Subject: [PATCH 16/99] feat: add wsteth --- src/assets/tokens/wstETH.svg | 11 +++++++++++ src/components/nav/PriceButton.tsx | 23 ++++++++++++++++++----- src/constants/tokens.ts | 28 ++++++++++++++++++++++------ src/pages/Swap.tsx | 1 - src/state/useTokenData.ts | 9 +++++---- src/utils/token.ts | 6 +++++- src/utils/types.ts | 20 ++++++++++++++++++++ 7 files changed, 81 insertions(+), 17 deletions(-) create mode 100644 src/assets/tokens/wstETH.svg diff --git a/src/assets/tokens/wstETH.svg b/src/assets/tokens/wstETH.svg new file mode 100644 index 000000000..552ceaa09 --- /dev/null +++ b/src/assets/tokens/wstETH.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/components/nav/PriceButton.tsx b/src/components/nav/PriceButton.tsx index be58800ba..6b2204bd0 100644 --- a/src/components/nav/PriceButton.tsx +++ b/src/components/nav/PriceButton.tsx @@ -12,6 +12,7 @@ import { PoolData, usePriceData, useTwaDeltaBLPQuery, useTwaDeltaBQuery } from " import useTokenData from "@/state/useTokenData"; import { withTracking } from "@/utils/analytics"; import { formatter } from "@/utils/format"; +import { stringEq } from "@/utils/string"; import { getTokenIndex } from "@/utils/token"; import { Token } from "@/utils/types"; import { cn } from "@/utils/utils"; @@ -19,7 +20,7 @@ import { HTMLAttributes, memo, useMemo, useState } from "react"; import { Link } from "react-router-dom"; import { useChainId } from "wagmi"; import { renderAnnouncement } from "../AnnouncementBanner"; -import { InlineCenterSpan, Row } from "../Container"; +import { InlineCenterSpan } from "../Container"; import { ExternalLinkIcon, ForwardArrowIcon } from "../Icons"; import TooltipSimple from "../TooltipSimple"; import { Card, CardContent, CardFooter, CardHeader } from "../ui/Card"; @@ -301,14 +302,18 @@ const PoolGroup = ({ if (props.showPrices) { return ( <> - + ); } return ( { const backingTokenIndex = pool.tokens.findIndex((token: Token) => !token.isMain); + + if (backingTokenIndex === -1) { + return null; + } + const tokenToShow = priceData.tokenPrices.get(pool.tokens[backingTokenIndex]); - if (!tokenToShow) return null; + + if (!tokenToShow) { + return null; + } return (
      @@ -375,7 +388,7 @@ const PoolCard = ({ pool, chainId, priceData, expandAll, useTwa, twaDeltaBMap }: const token0BalanceUsd = pool.balances[0].mul(token0Price ?? TokenValue.ZERO) || 0n; const token1BalanceUsd = pool.balances[1].mul(token1Price ?? TokenValue.ZERO) || 0n; const mainTokenIndex = pool.tokens.findIndex((token: Token) => token.isMain); - const deltaBar = pool.balances[mainTokenIndex].gt(0) + const deltaBar = pool.balances[mainTokenIndex]?.gt(0) ? Number(pool.deltaB.abs().div(pool.balances[mainTokenIndex]).mul(100).toHuman()).toFixed(2) : "0"; diff --git a/src/constants/tokens.ts b/src/constants/tokens.ts index b2a2088eb..6620950c4 100644 --- a/src/constants/tokens.ts +++ b/src/constants/tokens.ts @@ -11,6 +11,7 @@ import spectrasPintoYTIcon from "@/assets/tokens/SPECTRA-sPINTO-YT.png"; import wsolIcon from "@/assets/tokens/WSOL.png"; import crsPintoIcon from "@/assets/tokens/crsPINTO.png"; import sPintoIcon from "@/assets/tokens/sPINTO.png"; +import wstETHIcon from "@/assets/tokens/wstETH.svg"; import { Token, Token3PIntegration } from "@/utils/types"; import { ChainLookup } from "@/utils/types.generic"; import { arbitrum, base } from "viem/chains"; @@ -177,18 +178,25 @@ export const WSOL_TOKEN: ChainLookup = { export const WSTETH_TOKEN: ChainLookup = { [base.id]: { chainId: base.id, - name: "Wrapped stETH", - symbol: "WSTETH", + name: "Wrapped liquid staked Ether 2.0", + symbol: "wstETH", address: "0xc1CBa3fCea344f92D9239c08C0568f6F2F0ee452", decimals: 18, displayDecimals: 2, isLPUnderlying: true, isCompositeLPWhitelisted: true, - logoURI: "https://assets.coingecko.com/coins/images/18834/thumb/wstETH.png?1696518295", + logoURI: wstETHIcon, color: "#00A3FF", }, }; +const defaultLPTokenIndicies = { + tokenIndicies: { + main: 0, + pair: 1, + }, +} as const; + // -------------------- LP TOKENS -------------------- export const PINTO_WSTETH_TOKEN: ChainLookup = { @@ -196,14 +204,15 @@ export const PINTO_WSTETH_TOKEN: ChainLookup = { chainId: base.id, name: "PINTOWSTETH LP", symbol: "PINTOWSTETH", - address: "0xbd2d86B89353e0d441A3CC3d939A48f17CCDDff5", + address: "0x1957F43834d4e538cE99378FB26059579cf9bEe1", // temp address decimals: 18, - displayDecimals: 6, // show 6 decimal places for this token + displayDecimals: 2, isLP: true, isWhitelisted: true, logoURI: pintoWsolIcon, color: "#00A3FF", tokens: [MAIN_TOKEN[base.id].address, WSTETH_TOKEN[base.id].address], + ...defaultLPTokenIndicies, }, } as const; @@ -220,6 +229,7 @@ export const PINTO_WSOL_TOKEN: ChainLookup = { logoURI: pintoWsolIcon, color: "#9945FF", tokens: [MAIN_TOKEN[base.id].address, WSOL_TOKEN[base.id].address], + ...defaultLPTokenIndicies, }, } as const; @@ -236,6 +246,7 @@ export const PINTO_WETH_TOKEN: ChainLookup = { logoURI: pintoWethIcon, color: "#8C8C8C", tokens: [MAIN_TOKEN[base.id].address, WETH_TOKEN[base.id].address], + ...defaultLPTokenIndicies, }, } as const; @@ -252,6 +263,7 @@ export const PINTO_CBETH_TOKEN: ChainLookup = { logoURI: pintoCbethIcon, color: "#0052FF", tokens: [MAIN_TOKEN[base.id].address, CBETH_TOKEN[base.id].address], + ...defaultLPTokenIndicies, }, } as const; @@ -268,6 +280,7 @@ export const PINTO_USDC_TOKEN: ChainLookup = { logoURI: pintoUsdcIcon, color: "#2775CA", tokens: [MAIN_TOKEN[base.id].address, USDC_TOKEN[base.id].address], + ...defaultLPTokenIndicies, }, } as const; @@ -284,6 +297,7 @@ export const PINTO_CBBTC_TOKEN: ChainLookup = { logoURI: pintoCbbtcIcon, color: "#F7931A", tokens: [MAIN_TOKEN[base.id].address, CBBTC_TOKEN[base.id].address], + ...defaultLPTokenIndicies, }, } as const; @@ -479,7 +493,7 @@ export const tokens: { [chainId: number]: Token[] } = { export const PINTO = MAIN_TOKEN[defaultChain]; // Native Chain Token -export const ETH = NATIVE_TOKEN[defaultChain][1]; +export const ETH = NATIVE_TOKEN[defaultChain]; // Well LP Tokens export const PINTOWETH = LP_TOKENS[defaultChain][0]; @@ -487,6 +501,7 @@ export const PINTOCBETH = LP_TOKENS[defaultChain][1]; export const PINTOUSDC = LP_TOKENS[defaultChain][2]; export const PINTOCBBTC = LP_TOKENS[defaultChain][3]; export const PINTOWSOL = LP_TOKENS[defaultChain][4]; +export const PINTOWSTETH = LP_TOKENS[defaultChain][5]; // Underlying Tokens export const WETH = UNDERLYING_TOKENS[defaultChain][0]; @@ -494,3 +509,4 @@ export const USDC = UNDERLYING_TOKENS[defaultChain][1]; export const CBETH = UNDERLYING_TOKENS[defaultChain][2]; export const CBBTC = UNDERLYING_TOKENS[defaultChain][3]; export const WSOL = UNDERLYING_TOKENS[defaultChain][4]; +export const WSTETH = UNDERLYING_TOKENS[defaultChain][5]; diff --git a/src/pages/Swap.tsx b/src/pages/Swap.tsx index 7b7fb4028..f3f0b90c3 100644 --- a/src/pages/Swap.tsx +++ b/src/pages/Swap.tsx @@ -1,4 +1,3 @@ -import { TokenValue } from "@/classes/TokenValue"; import { ComboInputField } from "@/components/ComboInputField"; import DestinationBalanceSelect from "@/components/DestinationBalanceSelect"; import { UpDownArrowsIcon } from "@/components/Icons"; diff --git a/src/state/useTokenData.ts b/src/state/useTokenData.ts index cbf94bd6f..b2b24fc97 100644 --- a/src/state/useTokenData.ts +++ b/src/state/useTokenData.ts @@ -1,6 +1,7 @@ import { LP_TOKENS, MAIN_TOKEN, NATIVE_TOKEN, S_MAIN_TOKEN, WETH_TOKEN, tokens } from "@/constants/tokens"; import { useChainConstant, useResolvedChainId } from "@/utils/chain"; -import { Token } from "@/utils/types"; +import { isLPToken } from "@/utils/token"; +import { LPToken, Token } from "@/utils/types"; import { useMemo } from "react"; export function useWhitelistedTokens() { @@ -35,19 +36,19 @@ export default function useTokenData() { const wrappedNativeToken = useChainConstant(WETH_TOKEN); return useMemo(() => { - const lpTokens: Token[] = []; + const lpTokens: LPToken[] = []; const preferredTokens: Token[] = []; const whitelistedTokens: Token[] = []; const deWhitelistedTokens: Token[] = []; - const siloWrappedToken3p = tokens[chainId].find((token) => token.is3PSiloWrapped) as Token; + const siloWrappedToken3p = tokens[chainId].find((token) => token.is3PSiloWrapped); if (!siloWrappedToken3p) { throw new Error("3p wrapped native token not found"); } for (const token of tokens[chainId]) { - if (token.isLP) { + if (isLPToken(token)) { lpTokens.push(token); } if (!token.isNative && !token.isLP) { diff --git a/src/utils/token.ts b/src/utils/token.ts index 5f563b3e9..afb0d9ee0 100644 --- a/src/utils/token.ts +++ b/src/utils/token.ts @@ -3,7 +3,7 @@ import { PINTO, tokens } from "@/constants/tokens"; import { Address } from "viem"; import { resolveChainId } from "./chain"; import { stringEq } from "./string"; -import { AddressMap, InternalToken, SiloTokenData, Token, TokenDepositData } from "./types"; +import { AddressMap, InternalToken, LPToken, SiloTokenData, Token, TokenDepositData } from "./types"; import { ChainLookup } from "./types.generic"; import { exists } from "./utils"; @@ -47,6 +47,10 @@ export const tokensEqual = (a: TokenIsh | undefined | null, b: TokenIsh | undefi return stringEq(a.address, b.address) && stringEq(a.symbol, b.symbol); }; +export const isLPToken = (token: Token): token is LPToken => { + return !!token.tokens && !!token.tokenIndicies && !!token.isLP; +}; + /** * Sort tokens for tables, with configurable sorting modes: * diff --git a/src/utils/types.ts b/src/utils/types.ts index 8e65bcec4..5b9836481 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -3,6 +3,7 @@ import { SeasonalChartData } from "@/components/charts/SeasonalChart"; import { PlotSource } from "@/generated/gql/pintostalk/graphql"; import { ProtocolIntegration } from "@/state/integrations/types"; import { APYWindow } from "@/state/seasonal/queries/useSeasonalAPY"; +import { Prettify } from "@/utils/types.generic"; import { QueryKey, UseQueryOptions } from "@tanstack/react-query"; import { DateTime } from "luxon"; import { ReactNode } from "react"; @@ -20,6 +21,16 @@ export enum FarmToMode { INTERNAL = "1", } +interface LPIndicies { + main: number; + pair: number; +} + +interface LPTokenIsh { + tokens: Address[]; + tokenIndicies: LPIndicies; +} + export interface Token { name: string; symbol: string; @@ -77,8 +88,17 @@ export interface Token { * Description of the token (If Applicable). */ description?: string; + /** + * The Token indexes of the main & pair token for LPs + */ + tokenIndicies?: { + main: number; + pair: number; + }; } +export type LPToken = Prettify & LPTokenIsh & { isLP: true }>; + export interface Token3PIntegration extends Token { integration: ProtocolIntegration; } From 2c0267630d5f9009b76f7551efbddee0892f9539 Mon Sep 17 00:00:00 2001 From: burr Date: Tue, 18 Nov 2025 16:47:23 +0900 Subject: [PATCH 17/99] feat: fix dev page keys + update pintowsteth address --- src/components/DevPage.tsx | 9 ++++++--- src/components/nav/PriceButton.tsx | 2 +- src/constants/tokens.ts | 2 +- src/state/usePriceData.ts | 4 ++-- 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/components/DevPage.tsx b/src/components/DevPage.tsx index 0e5b637e9..a722981e0 100644 --- a/src/components/DevPage.tsx +++ b/src/components/DevPage.tsx @@ -1180,8 +1180,8 @@ const ViewFunctionCaller = () => { className="h-10 px-3 py-2 text-sm border rounded-md bg-white" > - {viewFunctions.map((fn) => ( - @@ -1204,7 +1204,10 @@ const ViewFunctionCaller = () => {
      Function Parameters:
      {selectedFunctionObj.inputs.map((input, idx) => ( -
      +
      diff --git a/src/components/nav/PriceButton.tsx b/src/components/nav/PriceButton.tsx index 6b2204bd0..c9d8678e5 100644 --- a/src/components/nav/PriceButton.tsx +++ b/src/components/nav/PriceButton.tsx @@ -39,7 +39,7 @@ function PriceButtonPanel() { const { data: twaDeltaBMap } = useTwaDeltaBLPQuery(); const whitelistedPools = useMemo(() => { - return priceData.pools.filter((pool) => pool.pool.isWhitelisted); + return priceData.pools.filter((pool) => pool.pool && pool.pool.isWhitelisted); }, [priceData.pools]); const mainTokenBalances = useMemo(() => { diff --git a/src/constants/tokens.ts b/src/constants/tokens.ts index 6620950c4..7723801d4 100644 --- a/src/constants/tokens.ts +++ b/src/constants/tokens.ts @@ -204,7 +204,7 @@ export const PINTO_WSTETH_TOKEN: ChainLookup = { chainId: base.id, name: "PINTOWSTETH LP", symbol: "PINTOWSTETH", - address: "0x1957F43834d4e538cE99378FB26059579cf9bEe1", // temp address + address: "0x3e1155480Fce43686793dd18E43104A01abCBC92", // temp address decimals: 18, displayDecimals: 2, isLP: true, diff --git a/src/state/usePriceData.ts b/src/state/usePriceData.ts index c0b1267e1..12a6e5bb5 100644 --- a/src/state/usePriceData.ts +++ b/src/state/usePriceData.ts @@ -70,7 +70,7 @@ const useSelectWellPriceData = () => { const pool = tokenMap[getTokenIndex(data.pool)]; const tokens = data.tokens.map((token) => tokenMap[getTokenIndex(token)]); - const balances = tokens.map((token, index) => TokenValue.fromBigInt(data.balances[index], token.decimals)); + const balances = tokens.map((token, index) => TokenValue.fromBigInt(data.balances[index], token?.decimals ?? 0)); return { pool, @@ -247,7 +247,7 @@ export function usePriceData() { const combined = [...price.ps, ...deWhitelistedWellsQuery.data]; for (const wellPriceData of combined) { const poolData = selectWellPriceData(wellPriceData); - if (poolData) { + if (poolData && poolData.pool) { pools.push(poolData); } } From 880ec075aa5bfe5f8faa179bfa4890023c39be78 Mon Sep 17 00:00:00 2001 From: burr Date: Tue, 18 Nov 2025 22:57:23 +0900 Subject: [PATCH 18/99] feat: update to use multicall instead of advpipe on silo convert cache --- src/lib/siloConvert/SiloConvert.cache.ts | 190 ++++++++++++++++++++++- 1 file changed, 188 insertions(+), 2 deletions(-) diff --git a/src/lib/siloConvert/SiloConvert.cache.ts b/src/lib/siloConvert/SiloConvert.cache.ts index e7614ff31..cb6f76750 100644 --- a/src/lib/siloConvert/SiloConvert.cache.ts +++ b/src/lib/siloConvert/SiloConvert.cache.ts @@ -1,5 +1,6 @@ import { Clipboard } from "@/classes/Clipboard"; import { TV } from "@/classes/TokenValue"; +import { diamondABI } from "@/constants/abi/diamondABI"; import { diamondPriceABI } from "@/constants/abi/diamondPriceABI"; import { abiSnippets } from "@/constants/abiSnippets"; import { MAIN_TOKEN } from "@/constants/tokens"; @@ -13,13 +14,22 @@ import { beanstalkPriceAddress } from "@/generated/contractHooks"; import { AdvancedPipeWorkflow } from "@/lib/farm/workflow"; import { SiloConvertContext } from "@/lib/siloConvert/types"; import { ExchangeWell } from "@/lib/well/ExchangeWell"; -import { PoolData } from "@/state/usePriceData"; +import { BasePoolData, PoolData } from "@/state/usePriceData"; import { resolveChainId } from "@/utils/chain"; +import { stringEq } from "@/utils/string"; import { getChainToken, getChainTokenMap, getTokenIndex } from "@/utils/token"; import { tokensEqual } from "@/utils/token"; import { AdvancedPipeCall, Token } from "@/utils/types"; import { AddressLookup, HashString } from "@/utils/types.generic"; -import { Address, decodeFunctionResult, encodeFunctionData } from "viem"; +import { + Address, + ContractFunctionParameters, + MulticallResponse, + MulticallReturnType, + decodeFunctionResult, + encodeFunctionData, +} from "viem"; +import { multicall } from "viem/actions"; /** * Extended pool data for the SiloConvertCache. @@ -92,6 +102,11 @@ export class SiloConvertPriceCache { /** The last time the cache was updated. */ lastUpdateTimestamp: number = 0; + /** + * A set of well addresses that failed to return any form of price data from the price / diamond contract + */ + private invalidWells: Set = new Set(); + constructor(context: SiloConvertContext) { this.context = context; this.lp2Pair = getLpTokensToPairs(context.chainId); @@ -292,6 +307,134 @@ export class SiloConvertPriceCache { return advPipe; } + async fetchMulticall() { + const client = this.context.wagmiConfig.getClient({ chainId: this.context.chainId }); + const tokenMap = getChainTokenMap(this.context.chainId); + + const erroredWells: Set = new Set(); + + const calls = this.getMultiCallPriceContracts(); + + const { priceCalls, dewhitelistedWellLPCalls, lpPairTokenPriceCalls } = calls; + + const datas = await multicall(client, { + contracts: [...priceCalls, ...dewhitelistedWellLPCalls, ...lpPairTokenPriceCalls], + allowFailure: true, + }); + + const [priceData, ...remaining] = datas; + const dewhitelistedLPData = remaining.slice(0, dewhitelistedWellLPCalls.length) as MulticallReturnType< + typeof dewhitelistedWellLPCalls, + true + >; + const lpPairTokenPriceData = remaining.slice(dewhitelistedWellLPCalls.length, remaining.length); + + const priceResult = priceData.result; + + if (!priceResult || typeof priceResult !== "object" || !("ps" in priceResult)) { + throw new Error("[SiloConvertCache/fetchMulticall] Price data error"); + } + + const map: AddressLookup = {}; + + const dwLPs = Object.values(this.dewhitelistedLP); + + const dwLPData = dewhitelistedLPData + .map((d, i) => { + if (d.result && typeof d.result === "object") { + return d.result; + } + + console.debug( + `[SiloConvertCache/fetchMulticall] Error decoding dewhitelisted LP data for ${dwLPs[i]?.address}. Adding to erroredWells set.`, + ); + const erroredWell = Object.values(this.dewhitelistedLP)[i]?.address; + erroredWells.add(Object.values(this.dewhitelistedLP)[i]?.address); + return undefined; + }) + .filter((d) => d !== undefined); + + const reducedPs = priceResult.ps.reduce>>((prev, curr) => { + prev[getTokenIndex(curr.pool)] = { + ...curr, + tokens: [curr.tokens[0], curr.tokens[1]] satisfies Address[], + balances: [curr.balances[0], curr.balances[1]] satisfies bigint[], + }; + return prev; + }, {}); + + for (const [index, [lpTokenIndex, pairToken]] of Object.entries(this.lp2Pair).entries()) { + const data = lpPairTokenPriceData[index]; + const result = data.result; + + if (!result || typeof result !== "bigint") { + continue; + } + + let poolResult = reducedPs[getTokenIndex(lpTokenIndex)]; + + if (!poolResult) { + const mayPoolResult = dwLPData.find((d) => stringEq(d.pool, lpTokenIndex)); + if (mayPoolResult) { + poolResult = { + ...mayPoolResult, + tokens: [mayPoolResult.tokens[0], mayPoolResult.tokens[1]] satisfies Address[], + balances: [mayPoolResult.balances[0], mayPoolResult.balances[1]] satisfies bigint[], + }; + } else { + erroredWells.add(lpTokenIndex); + console.debug( + `[SiloConvertCache/fetchMulticall] No pool result found for ${lpTokenIndex}. Adding to erroredWells set.`, + ); + continue; + } + } + + const wellTokens = poolResult.tokens.map((t) => getChainToken(this.context.chainId, t)); + if (!wellTokens.length) { + erroredWells.add(lpTokenIndex); + console.debug( + `[SiloConvertCache/fetchMulticall] No well tokens found with address: ${lpTokenIndex}. Adding to erroredWells set.`, + ); + continue; + } + + const pairPrice = TV.fromBigInt(result, pairToken.decimals); + const poolPrice = TV.fromBigInt(poolResult.price, 6); + + const pairData = { + token: pairToken, + index: wellTokens[0].isMain ? 1 : 0, + price: pairPrice, + }; + + map[lpTokenIndex] = { + pool: tokenMap[lpTokenIndex], + price: TV.fromBigInt(poolResult.price, 6), + pair: pairData, + tokens: wellTokens, + liquidity: TV.fromBigInt(poolResult.liquidity, 6), + lpUsd: TV.fromBigInt(poolResult.lpUsd, 6), + lpBdv: TV.fromBigInt(poolResult.lpBdv, 6), + deltaB: TV.fromBigInt(poolResult.deltaB, 6), + balances: wellTokens.map((t, i) => TV.fromBigInt(poolResult.balances[i], t.decimals)), + prices: wellTokens.map((t) => (t.isMain ? poolPrice : pairData.price)), + }; + } + + if (!dewhitelistedLPData.length || !lpPairTokenPriceData.length) { + throw new Error("No dewhitelistedLPData or lpPairTokenPriceData found"); + } + + return { + deltaB: TV.fromBigInt(priceResult.deltaB, 6), + price: TV.fromBigInt(priceResult.price, 6), + liquidity: TV.fromBigInt(priceResult.liquidity, 6), + pools: map, + erroredWells, + }; + } + /** * Fetches the relevant pool data from on chain */ @@ -302,6 +445,8 @@ export class SiloConvertPriceCache { const advPipe = this.constructPriceAdvPipe(); + const others = await this.fetchMulticall(); + // Fetch price contract data & price oracle data const advPipeResult = await advPipe.readStatic(); @@ -359,6 +504,47 @@ export class SiloConvertPriceCache { pools: map, }; } + + /** + * + * constructs the contracts for the price multicall + */ + private getMultiCallPriceContracts() { + const priceAddress = beanstalkPriceAddress[resolveChainId(this.context.chainId)]; + + // Put this in an array so TS doesn't complain about the type mismatch when not in an array + const priceCalls: ContractFunctionParameters[] = [ + { + address: priceAddress, + abi: diamondPriceABI, + functionName: "price", + args: [], + }, + ]; + const dewhitelistedWellLPCalls: ContractFunctionParameters[] = + Object.values(this.dewhitelistedLP).map((dwlp) => { + return { + address: priceAddress, + abi: diamondPriceABI, + functionName: "getWell", + args: [dwlp.address], + }; + }); + const lpPairTokenPriceCalls = Object.values(this.lp2Pair).map((data) => { + return { + address: this.context.diamond, + abi: diamondABI, + functionName: "getTokenUsdPrice", + args: [data.address], + } as const; + }); + + return { + priceCalls, + dewhitelistedWellLPCalls, + lpPairTokenPriceCalls, + } as const; + } } /** From 9f01c005166215c00daaa51bee2196fe0c99543e Mon Sep 17 00:00:00 2001 From: burr Date: Tue, 18 Nov 2025 23:05:34 +0900 Subject: [PATCH 19/99] feat: add invalid well swap strategizer --- src/lib/siloConvert/SiloConvert.cache.ts | 11 ++++++- .../siloConvert/siloConvert.strategizer.ts | 30 +++++++++++-------- 2 files changed, 27 insertions(+), 14 deletions(-) diff --git a/src/lib/siloConvert/SiloConvert.cache.ts b/src/lib/siloConvert/SiloConvert.cache.ts index cb6f76750..7544f3a31 100644 --- a/src/lib/siloConvert/SiloConvert.cache.ts +++ b/src/lib/siloConvert/SiloConvert.cache.ts @@ -186,6 +186,14 @@ export class SiloConvertPriceCache { return well.pair.price; } + getIsWellAvailable(well: Address) { + if (this.invalidWells.has(getTokenIndex(well))) { + return false; + } + + return true; + } + /** * Returns the Extended Well data for a given Well address. */ @@ -211,7 +219,8 @@ export class SiloConvertPriceCache { console.debug("[SiloConvertCache/update] updating cache..."); const diff = Date.now() - this.lastUpdateTimestamp; if (force || this.lastUpdateTimestamp === 0 || diff > REFETCH_INTERVAL) { - const priceResult = await this.fetch(); + const priceResult = await this.fetchMulticall(); + this.invalidWells = priceResult.erroredWells; this.priceStruct = priceResult; this.lastUpdateTimestamp = Date.now(); console.debug("[PipelineConvert/Cache/update]: ", this); diff --git a/src/lib/siloConvert/siloConvert.strategizer.ts b/src/lib/siloConvert/siloConvert.strategizer.ts index 8b1e98791..78efdd538 100644 --- a/src/lib/siloConvert/siloConvert.strategizer.ts +++ b/src/lib/siloConvert/siloConvert.strategizer.ts @@ -137,19 +137,23 @@ export class Strategizer { const sourceWell = source.isLP ? this.cache.getWell(source.address) : undefined; // if SourceWell exists, target must be main due to validation above. - if (sourceWell && !this.maxConvertQuoter.isAggDisabledToken(source)) { - routes.push({ - source, - target, - strategies: [ - { - strategy: new LP2MainPipeline(sourceWell, target, this.context), - amount: amountIn, - }, - ], - convertType: "LP2MainPipeline", - }); - } + // if ( + // sourceWell && + // !this.maxConvertQuoter.isAggDisabledToken(source) && + // this.cache.getIsWellAvailable(source.address) + // ) { + // routes.push({ + // source, + // target, + // strategies: [ + // { + // strategy: new LP2MainPipeline(sourceWell, target, this.context), + // amount: amountIn, + // }, + // ], + // convertType: "LP2MainPipeline", + // }); + // } return routes; }, "strategizeLPAndMain"); From aeabe091af5d93dd457c234b3e1135b910e57823 Mon Sep 17 00:00:00 2001 From: burr Date: Tue, 18 Nov 2025 23:32:22 +0900 Subject: [PATCH 20/99] feat: update silo convert --- src/lib/siloConvert/SiloConvert.cache.ts | 81 ++----------------- src/lib/siloConvert/SiloConvert.ts | 4 + .../siloConvert/siloConvert.strategizer.ts | 34 ++++---- 3 files changed, 28 insertions(+), 91 deletions(-) diff --git a/src/lib/siloConvert/SiloConvert.cache.ts b/src/lib/siloConvert/SiloConvert.cache.ts index 7544f3a31..27fc91184 100644 --- a/src/lib/siloConvert/SiloConvert.cache.ts +++ b/src/lib/siloConvert/SiloConvert.cache.ts @@ -24,7 +24,6 @@ import { AddressLookup, HashString } from "@/utils/types.generic"; import { Address, ContractFunctionParameters, - MulticallResponse, MulticallReturnType, decodeFunctionResult, encodeFunctionData, @@ -260,15 +259,19 @@ export class SiloConvertPriceCache { } getPriceCallStructs(): AdvancedPipeCall[] { + const availWells = Object.values(this.dewhitelistedLP).filter((tk) => this.getIsWellAvailable(tk.address)); + return [ encodePrice(this.context.chainId), - ...Object.values(this.dewhitelistedLP).map((tk) => encodeGetWell(this.context.chainId, tk.address)), + ...availWells.map((tk) => encodeGetWell(this.context.chainId, tk.address)), ]; } decodePriceCallResults(results: HashString[]) { + const availWells = Object.values(this.dewhitelistedLP).filter((tk) => this.getIsWellAvailable(tk.address)); + // +1 for the price call - const expectedLength = Object.keys(this.dewhitelistedLP).length + 1; + const expectedLength = availWells.length + 1; if (results.length < expectedLength) { throw new Error(`Cannot decode price call results. Expected ${expectedLength} results but got ${results.length}`); @@ -279,7 +282,7 @@ export class SiloConvertPriceCache { priceResult.pools = { ...priceResult.pools }; - Object.values(this.dewhitelistedLP).forEach((tk, idx) => { + availWells.forEach((tk, idx) => { const res = decodeGetWell(dewhitelistedLPResults[idx]); priceResult.pools[getTokenIndex(tk.address)] = res; }); @@ -444,76 +447,6 @@ export class SiloConvertPriceCache { }; } - /** - * Fetches the relevant pool data from on chain - */ - async fetch(): Promise { - console.debug("[SiloConvertCache/fetch] fetching price data..."); - const tokenMap = getChainTokenMap(this.context.chainId); - const mainToken = MAIN_TOKEN[resolveChainId(this.context.chainId)]; - - const advPipe = this.constructPriceAdvPipe(); - - const others = await this.fetchMulticall(); - - // Fetch price contract data & price oracle data - - const advPipeResult = await advPipe.readStatic(); - const sliceIdx = Object.keys(this.dewhitelistedLP).length + 1; - const priceFragments = advPipeResult.slice(0, sliceIdx); - const tokenUsdData = advPipeResult.slice(sliceIdx, advPipeResult.length); - - const priceResult = this.decodePriceCallResults(priceFragments); - - const map: AddressLookup = {}; - - for (const [index, [lpTokenIndex, pairToken]] of Object.entries(this.lp2Pair).entries()) { - const pairPriceBigInt = decodeFunctionResult({ - abi: abiSnippets.price.getTokenUsdPrice, - functionName: "getTokenUsdPrice", - data: tokenUsdData[index], - }); - - const poolResult = priceResult.pools[lpTokenIndex]; - const wellTokens = poolResult?.tokens.map((t) => getChainToken(this.context.chainId, t)); - - if (!poolResult) { - throw new Error(`Pool result not found for ${lpTokenIndex}`); - } - if (!wellTokens.length) { - throw new Error(`No well tokens found with address: ${lpTokenIndex}`); - } - - const poolPrice = TV.fromBigInt(poolResult.price, mainToken.decimals); - - const pairData = { - token: pairToken, - index: wellTokens[0].isMain ? 1 : 0, - price: TV.fromBigInt(pairPriceBigInt, mainToken.decimals), - }; - - map[lpTokenIndex] = { - pool: tokenMap[lpTokenIndex], - price: poolPrice, - pair: pairData, - tokens: wellTokens, - liquidity: TV.fromBigInt(poolResult.liquidity, 6), - lpUsd: TV.fromBigInt(poolResult.lpUsd, mainToken.decimals), - lpBdv: TV.fromBigInt(poolResult.lpBdv, mainToken.decimals), - deltaB: TV.fromBigInt(poolResult.deltaB, mainToken.decimals), - balances: wellTokens.map((t, i) => TV.fromBigInt(poolResult.balances[i], t.decimals)), - prices: wellTokens.map((t) => (t.isMain ? poolPrice : pairData.price)), - }; - } - - return { - deltaB: TV.fromBigInt(priceResult.deltaB, 6), - price: TV.fromBigInt(priceResult.price, 6), - liquidity: TV.fromBigInt(priceResult.liquidity, 6), - pools: map, - }; - } - /** * * constructs the contracts for the price multicall diff --git a/src/lib/siloConvert/SiloConvert.ts b/src/lib/siloConvert/SiloConvert.ts index 9173560c8..a5968fddc 100644 --- a/src/lib/siloConvert/SiloConvert.ts +++ b/src/lib/siloConvert/SiloConvert.ts @@ -397,6 +397,10 @@ export class SiloConvert { // price result is the last element in the static call result const priceResult = staticCallResult.pop(); + console.log({ + priceResult, + }); + const decodedConvertResults = decodeConvertResults(staticCallResult, route.convertType); const decodedPriceCalls = priceResult ? AdvancedPipeWorkflow.decodeResult(priceResult) : undefined; diff --git a/src/lib/siloConvert/siloConvert.strategizer.ts b/src/lib/siloConvert/siloConvert.strategizer.ts index 78efdd538..b14b59237 100644 --- a/src/lib/siloConvert/siloConvert.strategizer.ts +++ b/src/lib/siloConvert/siloConvert.strategizer.ts @@ -137,23 +137,23 @@ export class Strategizer { const sourceWell = source.isLP ? this.cache.getWell(source.address) : undefined; // if SourceWell exists, target must be main due to validation above. - // if ( - // sourceWell && - // !this.maxConvertQuoter.isAggDisabledToken(source) && - // this.cache.getIsWellAvailable(source.address) - // ) { - // routes.push({ - // source, - // target, - // strategies: [ - // { - // strategy: new LP2MainPipeline(sourceWell, target, this.context), - // amount: amountIn, - // }, - // ], - // convertType: "LP2MainPipeline", - // }); - // } + if ( + sourceWell && + !this.maxConvertQuoter.isAggDisabledToken(source) && + this.cache.getIsWellAvailable(source.address) + ) { + routes.push({ + source, + target, + strategies: [ + { + strategy: new LP2MainPipeline(sourceWell, target, this.context), + amount: amountIn, + }, + ], + convertType: "LP2MainPipeline", + }); + } return routes; }, "strategizeLPAndMain"); From b9d3d0ab21cc303b77a31f806a1c9417f01e3fc7 Mon Sep 17 00:00:00 2001 From: burr Date: Wed, 19 Nov 2025 22:35:13 +0900 Subject: [PATCH 21/99] bug: fix swaps div by zero bug --- src/constants/slots.ts | 2 + src/hooks/swap/useSwapSummary.ts | 10 ++++- src/lib/Swap/price-cache.ts | 70 +++++++++++++++++--------------- 3 files changed, 47 insertions(+), 35 deletions(-) diff --git a/src/constants/slots.ts b/src/constants/slots.ts index 246c4d612..5453ebcb2 100644 --- a/src/constants/slots.ts +++ b/src/constants/slots.ts @@ -6,6 +6,7 @@ import { USDC_TOKEN, WETH_TOKEN, WSOL_TOKEN, + WSTETH_TOKEN, } from "@/constants/tokens"; import { AddressMap } from "@/utils/types"; import { ChainLookup } from "@/utils/types.generic"; @@ -15,6 +16,7 @@ export const addressAllowanceSlotMap: ChainLookup> = { [base.id]: { [MAIN_TOKEN[base.id].address.toLowerCase()]: 1, [WETH_TOKEN[base.id].address.toLowerCase()]: 4, + [WSTETH_TOKEN[base.id].address.toLowerCase()]: 2, [CBETH_TOKEN[base.id].address.toLowerCase()]: 52, [USDC_TOKEN[base.id].address.toLowerCase()]: 10, [CBBTC_TOKEN[base.id].address.toLowerCase()]: 10, diff --git a/src/hooks/swap/useSwapSummary.ts b/src/hooks/swap/useSwapSummary.ts index b77ace324..7bb10dc12 100644 --- a/src/hooks/swap/useSwapSummary.ts +++ b/src/hooks/swap/useSwapSummary.ts @@ -60,7 +60,13 @@ export default function useSwapSummary(quote: BeanSwapNodeQuote | undefined): Sw if (node instanceof WellSyncSwapNode) { addLiquidityRoute = route; - addLiquiditySlippage = quote.usdIn.sub(quote.usdOut).div(quote.usdIn).mul(100).toNumber(); + const usdOut = quote.usdOut.eq(0) ? TV.ONE : quote.usdOut; + const usdIn = quote.usdIn.eq(0) ? TV.ONE : quote.usdIn; + if (usdIn.isZero || usdOut.isZero) { + addLiquiditySlippage = undefined; + } else { + addLiquiditySlippage = usdIn.sub(usdOut).div(usdIn).mul(100).toNumber(); + } routes.push(route); } else { if (node instanceof ZeroXSwapNode) { @@ -69,7 +75,7 @@ export default function useSwapSummary(quote: BeanSwapNodeQuote | undefined): Sw if (fee?.feeToken) { const feeTokenUSD = tokenPrices.get(fee.feeToken); - if (feeTokenUSD) { + if (feeTokenUSD && !node.usdIn.isZero && !node.usdIn.isZero) { const feeUSD = fee.fee.mul(feeTokenUSD.instant); const feePct = feeUSD.div(node.usdIn).mul(100).toNumber(); route.exchangeFee = feeUSD.toNumber(); diff --git a/src/lib/Swap/price-cache.ts b/src/lib/Swap/price-cache.ts index 9c98ec773..48bf8f194 100644 --- a/src/lib/Swap/price-cache.ts +++ b/src/lib/Swap/price-cache.ts @@ -8,7 +8,7 @@ import { resolveChainId } from "@/utils/chain"; import { getTokenIndex, tokensEqual } from "@/utils/token"; import { Token } from "@/utils/types"; import { multicall } from "@wagmi/core"; -import { ContractFunctionParameters } from "viem"; +import { ContractFunctionParameters, MulticallReturnType } from "viem"; import { readContract } from "viem/actions"; const TWO_MINS = 1000 * 60 * 2; @@ -77,21 +77,21 @@ export class SwapPriceCache { const diff = now - this.#lastFetchedTimestamp; if (force || diff > TWO_MINS) { - const { priceMap, wellPriceMap } = await this.#fetchPrices(); + const { priceMap, wellPriceMap } = await this.fetchPrices(); this.#priceMap = priceMap; this.#wellPriceMap = wellPriceMap; this.#lastFetchedTimestamp = now; } } - async #fetchPrices() { - const { tokens, contracts, price: priceContract } = this.#buildMulticall(); + private async fetchPrices() { + const { tokens, contracts, price: priceContract } = this.buildMulticall(); const [prices, redemptionForOneMainToken] = await Promise.all([ // all token prices multicall(this.#context.config, { contracts: [...contracts, priceContract], - allowFailure: false, + allowFailure: true, }), // silo wrapped token redemption for 1 main token readContract(this.#context.config.getClient({ chainId: this.#context.chainId }), { @@ -100,35 +100,47 @@ export class SwapPriceCache { functionName: "previewRedeem", args: [BigInt(10 ** this.#context.siloWrappedToken.decimals)], }), + // multicall(this.#context.config, { + // contracts: this.tokens + // }) ]); const priceMap = new Map(); const wellPriceMap = new Map(); - for (const [index, price] of prices.entries()) { - const token = tokens[index]; + // price result is the last element in the prices array + const priceResult = prices[prices.length - 1]; + + const tokenPrices = [...prices].slice(0, contracts.length); + + // set the token prices into the map + for (const [index, token] of tokens.entries()) { + const price = tokenPrices[index]; + const result = price.result; + + if (result && typeof result === "bigint") { + priceMap.set(token, TV.fromBigInt(result, 6)); - if (typeof price === "bigint") { if (token.isWrappedNative) { - priceMap.set(this.#context.native, TV.fromBlockchain(price, 6)); + priceMap.set(this.#context.native, TV.fromBlockchain(result, 6)); } - const mainTokenPrice = TV.fromBlockchain(price, 6); - priceMap.set(token, mainTokenPrice); - priceMap.set(this.#context.siloWrappedToken, mainTokenPrice); - } else if (price.price) { - priceMap.set(this.#context.mainToken, TV.fromBlockchain(price.price, 6)); - - price.ps.forEach((pool) => { - const well = this.#context.tokenMap[getTokenIndex(pool.pool)]; - if (well) { - wellPriceMap.set(well, TV.fromBlockchain(pool.price, 6)); - priceMap.set(well, TV.fromBlockchain(pool.lpUsd, 6)); - } - }); } } - this.#updatePriceMapWithSiloWrappedToken(redemptionForOneMainToken, priceMap); + if (priceResult.result && typeof priceResult.result === "object") { + const result = priceResult.result; + priceMap.set(this.#context.mainToken, TV.fromBlockchain(result.price, 6)); + + result.ps.forEach((pool) => { + const well = this.#context.tokenMap[getTokenIndex(pool.pool)]; + if (well) { + wellPriceMap.set(well, TV.fromBlockchain(pool.price, 6)); + priceMap.set(well, TV.fromBlockchain(pool.lpUsd, 6)); + } + }); + } + + this.updatePriceMapWithSiloWrappedToken(redemptionForOneMainToken, priceMap); return { priceMap, @@ -136,7 +148,7 @@ export class SwapPriceCache { }; } - #buildMulticall() { + private buildMulticall() { const tokens = Object.keys(this.#context.underlying2LP).map((token) => { return this.#context.tokenMap[getTokenIndex(token)]; }); @@ -156,22 +168,14 @@ export class SwapPriceCache { args: [], }; - const siloWrappedExchangeRate: ContractFunctionParameters = { - address: this.#context.siloWrappedToken.address, - abi: siloedPintoABI, - functionName: "previewRedeem", - args: [BigInt(10 ** this.#context.siloWrappedToken.decimals)], - }; - return { tokens, contracts: contracts, - siloWrappedExchangeRate, price, }; } - #updatePriceMapWithSiloWrappedToken(amount: bigint, priceMap: Map): void { + private updatePriceMapWithSiloWrappedToken(amount: bigint, priceMap: Map): void { const baseAmount = TV.fromHuman(1, this.#context.mainToken.decimals); const redemptionAmount = TV.fromBigInt(amount, this.#context.mainToken.decimals); const mainTokenUSD = priceMap.get(this.#context.mainToken); From d3d4606c9052c87fbb72cc23a1072b84e11e005b Mon Sep 17 00:00:00 2001 From: burr Date: Wed, 19 Nov 2025 22:48:56 +0900 Subject: [PATCH 22/99] feat: remove unnecessary comment --- src/lib/Swap/price-cache.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/lib/Swap/price-cache.ts b/src/lib/Swap/price-cache.ts index 48bf8f194..ef8f7a649 100644 --- a/src/lib/Swap/price-cache.ts +++ b/src/lib/Swap/price-cache.ts @@ -100,9 +100,6 @@ export class SwapPriceCache { functionName: "previewRedeem", args: [BigInt(10 ** this.#context.siloWrappedToken.decimals)], }), - // multicall(this.#context.config, { - // contracts: this.tokens - // }) ]); const priceMap = new Map(); From a326ccfee87cdbe55e19eb49224c499401ba3a0b Mon Sep 17 00:00:00 2001 From: feyyazcigim Date: Sat, 22 Nov 2025 17:36:22 +0300 Subject: [PATCH 23/99] Change referral layout --- src/components/HowToCard.tsx | 57 ++++++++ src/components/ReferralLinkGenerator.tsx | 174 ++++++++++++----------- src/components/ReferralStatsCard.tsx | 53 +++++++ src/pages/Referral.tsx | 25 ++-- 4 files changed, 213 insertions(+), 96 deletions(-) create mode 100644 src/components/HowToCard.tsx create mode 100644 src/components/ReferralStatsCard.tsx diff --git a/src/components/HowToCard.tsx b/src/components/HowToCard.tsx new file mode 100644 index 000000000..324be4c96 --- /dev/null +++ b/src/components/HowToCard.tsx @@ -0,0 +1,57 @@ +export function HowToCard() { + return ( +
      +
      How It Works
      +
      +
      +
      + 1 +
      +
      +
      Qualify as a Referrer
      +
      + Sow at least 1,000 Pinto in the Field to unlock your referral link. +
      +
      +
      + +
      +
      + 2 +
      +
      +
      Share Your Link
      +
      + Copy your unique referral link and share it with friends, on social media, or anywhere else. +
      +
      +
      + +
      +
      + 3 +
      +
      +
      Earn Rewards
      +
      + When someone uses your link and Sows Pinto, you earn 10% of the Pods they receive as a referral bonus. +
      +
      +
      + +
      +
      + 4 +
      +
      +
      Get Credited
      +
      + Referral rewards are automatically credited to your wallet address when your referral completes their Sow + transaction. +
      +
      +
      +
      +
      + ); +} diff --git a/src/components/ReferralLinkGenerator.tsx b/src/components/ReferralLinkGenerator.tsx index 2056ca3dc..c70d67173 100644 --- a/src/components/ReferralLinkGenerator.tsx +++ b/src/components/ReferralLinkGenerator.tsx @@ -1,63 +1,53 @@ import { Button } from "@/components/ui/Button"; -import { Card } from "@/components/ui/Card"; import { Input } from "@/components/ui/Input"; import { ANALYTICS_EVENTS } from "@/constants/analytics-events"; -import { useFarmerField } from "@/state/useFarmerField"; import { trackSimpleEvent } from "@/utils/analytics"; -import { formatter } from "@/utils/format"; +import { truncateHex } from "@/utils/format"; import { encodeReferralAddress } from "@/utils/referral"; -import { CopyIcon, Share1Icon } from "@radix-ui/react-icons"; +import { CopyIcon, Share1Icon, ChatBubbleIcon, BarChartIcon } from "@radix-ui/react-icons"; import { toast } from "sonner"; import { useAccount } from "wagmi"; -const MIN_SOWN_BEANS = 1000; - export function ReferralLinkGenerator() { const { address } = useAccount(); - const farmerField = useFarmerField(); if (!address) { return ( - -
      Connect your wallet to generate a referral link
      -
      +
      Connect your wallet to access referral features
      ); } - const encodedRef = encodeReferralAddress(address); - const referralUrl = `${window.location.origin}/field?ref=${encodedRef}`; + const referralCode = encodeReferralAddress(address); + const referralUrl = `${window.location.origin}/field?ref=${referralCode}`; + const podDestinationAddress = address; - // Calculate total sown beans from plots - const totalSownBeans = farmerField.plots.reduce((total, plot) => { - // Each plot represents pods sown. To get beans sown, we use the initial sown amount - // which is stored in the plot data - return total + (plot.pods?.toNumber() || 0); - }, 0); + const handleCopyCode = () => { + navigator.clipboard.writeText(referralCode); + toast.success("Referral code copied to clipboard!"); - // For now, we'll use the totalPods as a proxy. In production, you'd want to query - // the subgraph for the actual sownBeans value from farmer.field.sownBeans - const isEligible = totalSownBeans >= MIN_SOWN_BEANS; + trackSimpleEvent(ANALYTICS_EVENTS.REFERRAL.LINK_COPIED, { + address, + type: "code", + }); + }; - const handleCopy = () => { + const handleCopyLink = () => { navigator.clipboard.writeText(referralUrl); toast.success("Referral link copied to clipboard!"); trackSimpleEvent(ANALYTICS_EVENTS.REFERRAL.LINK_COPIED, { address, - is_eligible: isEligible, - total_sown_beans: totalSownBeans, + type: "link", }); }; - const handleGenerateClick = () => { - trackSimpleEvent(ANALYTICS_EVENTS.REFERRAL.LINK_GENERATED, { - address, - is_eligible: isEligible, - total_sown_beans: totalSownBeans, - }); + const handleChangeAddress = () => { + console.log("Change pod destination address clicked"); + toast.info("Change address functionality coming soon!"); }; const handleTwitterShare = () => { + console.log("Twitter/X share clicked"); const tweetText = "🌱 I'm farming on @PintoProtocol and earning passive rewards!\n\nJoin me and I'll earn bonus Pods when you Sow Pinto 🫘\n\nStart farming today:"; const twitterUrl = `https://twitter.com/intent/tweet?text=${encodeURIComponent(tweetText)}&url=${encodeURIComponent(referralUrl)}`; @@ -65,73 +55,93 @@ export function ReferralLinkGenerator() { trackSimpleEvent(ANALYTICS_EVENTS.REFERRAL.TWITTER_SHARE, { address, - is_eligible: isEligible, - total_sown_beans: totalSownBeans, }); }; - const progressPercentage = Math.min((totalSownBeans / MIN_SOWN_BEANS) * 100, 100); + const handleTelegramShare = () => { + console.log("Telegram share clicked"); + toast.info("Telegram share functionality coming soon!"); + }; + + const handleQRCode = () => { + console.log("QR code clicked"); + toast.info("QR code functionality coming soon!"); + }; return (
      -
      -
      Your Referral Link
      -
      - Share your link to earn 10% bonus Pods when others Sow using it +
      Invite via
      + +
      + {/* Referral Code */} +
      + +
      + + +
      -
      -
      - {!isEligible && ( -
      -
      -
      Qualification Progress
      -
      - {formatter.number(totalSownBeans)} / {formatter.number(MIN_SOWN_BEANS)} Pinto -
      -
      -
      -
      -
      -
      - Sow {formatter.number(MIN_SOWN_BEANS - totalSownBeans)} more Pinto to unlock your referral link -
      + {/* Referral Link */} +
      + +
      + +
      - )} - -
      - -
      - {isEligible && ( - <> -
      - ✓ Your referral link is active! You'll earn 10% bonus Pods when someone Sows using your link. + {/* Pod Destination Address and Share via - Row Layout */} +
      + {/* Pod Destination Address */} +
      + +
      + {truncateHex(podDestinationAddress, 6, 4)} +
      +
      - {/* Twitter Share Button */} -
      -
      Share on Social
      - + +
      - - )} +
      +
      ); diff --git a/src/components/ReferralStatsCard.tsx b/src/components/ReferralStatsCard.tsx new file mode 100644 index 000000000..d0d58a30d --- /dev/null +++ b/src/components/ReferralStatsCard.tsx @@ -0,0 +1,53 @@ +export interface StatItem { + label: string; + value: string | number; + description?: string; +} + +interface ReferralStatsCardProps { + stats?: StatItem[]; +} + +const defaultStats: StatItem[] = [ + { + label: "Total Pods Earned", + value: 0, + description: "Pods earned from referrals", + }, + { + label: "Total successful referrals", + value: 0, + description: "Number of users who used your link", + }, + { + label: "Referral Ranking", + value: "-", + description: "Your rank among all referrers", + }, + { + label: "Total Pods created from referrals", + value: 0, + description: "Total Pods your referrals have earned", + }, + { + label: "Total Pinto Sown from referrals", + value: 0, + description: "Total Pinto your referrals have sown", + }, +]; + +export function ReferralStatsCard({ stats = defaultStats }: ReferralStatsCardProps) { + return ( +
      +
      Your Referral Stats
      +
      + {stats.map((stat) => ( +
      +
      {stat.label}
      +
      {stat.value}
      +
      + ))} +
      +
      + ); +} diff --git a/src/pages/Referral.tsx b/src/pages/Referral.tsx index f7e7ae101..b55b3eb85 100644 --- a/src/pages/Referral.tsx +++ b/src/pages/Referral.tsx @@ -1,4 +1,6 @@ +import { HowToCard } from "@/components/HowToCard"; import { ReferralLinkGenerator } from "@/components/ReferralLinkGenerator"; +import { ReferralStatsCard } from "@/components/ReferralStatsCard"; import { Card } from "@/components/ui/Card"; import PageContainer from "@/components/ui/PageContainer"; import { Separator } from "@/components/ui/Separator"; @@ -27,27 +29,22 @@ export default function Referral() { {/* Main Referral Cards - Two Column Layout */}
      - {/* Your Referral Link */} + {/* Invite via */} - {/* Your Referral Stats */} - -
      Your Referral Stats
      -
      -
      -
      Total Pods Earned
      -
      0
      -
      -
      -
      Successful Referrals
      -
      0
      -
      -
      + {/* How to */} + +
      + {/* Your Referral Stats - Standalone Section */} + + + + {/* Leaderboard */}
      Referral Leaderboard
      From 05577185503c4b3457d8bf33651d88b15e29585d Mon Sep 17 00:00:00 2001 From: burr Date: Mon, 24 Nov 2025 23:02:33 +0900 Subject: [PATCH 24/99] feat: add new token icons --- src/assets/tokens/PINTO_wstETH.png | Bin 0 -> 21117 bytes src/assets/tokens/wstETH.png | Bin 0 -> 17422 bytes src/assets/tokens/wstETH.svg | 11 ----------- 3 files changed, 11 deletions(-) create mode 100644 src/assets/tokens/PINTO_wstETH.png create mode 100644 src/assets/tokens/wstETH.png delete mode 100644 src/assets/tokens/wstETH.svg diff --git a/src/assets/tokens/PINTO_wstETH.png b/src/assets/tokens/PINTO_wstETH.png new file mode 100644 index 0000000000000000000000000000000000000000..2e4402765b424df44becb9a6c956048ed2ea8435 GIT binary patch literal 21117 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
      =z3(*dw;1m$|EkMA-#5??si_2vM<*bfj-lIb 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 UuZ)MHp8x;=07*qoM6N<$f`}))oB#j- literal 0 HcmV?d00001 diff --git a/src/assets/tokens/wstETH.png b/src/assets/tokens/wstETH.png new file mode 100644 index 0000000000000000000000000000000000000000..f7772f6dc32e0f1b7b48d3c93f8cf0d4e97cb0aa GIT binary patch literal 17422 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 literal 0 HcmV?d00001 diff --git a/src/assets/tokens/wstETH.svg b/src/assets/tokens/wstETH.svg deleted file mode 100644 index 552ceaa09..000000000 --- a/src/assets/tokens/wstETH.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - From df925cdb64ac1dd9ea0c052b5452743279cf63b5 Mon Sep 17 00:00:00 2001 From: burr Date: Mon, 24 Nov 2025 23:59:38 +0900 Subject: [PATCH 25/99] feat: update silo convert quoteing --- src/constants/tokens.ts | 7 +- src/lib/siloConvert/SiloConvert.ts | 81 +++++++++++-------- src/lib/siloConvert/siloConvert.swapQuoter.ts | 2 +- 3 files changed, 52 insertions(+), 38 deletions(-) diff --git a/src/constants/tokens.ts b/src/constants/tokens.ts index 7723801d4..82686f977 100644 --- a/src/constants/tokens.ts +++ b/src/constants/tokens.ts @@ -5,13 +5,14 @@ import pintoWethIcon from "@/assets/tokens/PINTO_WETH.png"; import pintoWsolIcon from "@/assets/tokens/PINTO_WSOL.png"; import pintoCbbtcIcon from "@/assets/tokens/PINTO_cbBTC.png"; import pintoCbethIcon from "@/assets/tokens/PINTO_cbETH.png"; +import pintoWstethIcon from "@/assets/tokens/PINTO_wstETH.png"; import spectrasPintoLPIcon from "@/assets/tokens/SPECTRA-sPINTO-LP.png"; import spectrasPintoPTIcon from "@/assets/tokens/SPECTRA-sPINTO-PT.png"; import spectrasPintoYTIcon from "@/assets/tokens/SPECTRA-sPINTO-YT.png"; import wsolIcon from "@/assets/tokens/WSOL.png"; import crsPintoIcon from "@/assets/tokens/crsPINTO.png"; import sPintoIcon from "@/assets/tokens/sPINTO.png"; -import wstETHIcon from "@/assets/tokens/wstETH.svg"; +import wstETHIcon from "@/assets/tokens/wstETH.png"; import { Token, Token3PIntegration } from "@/utils/types"; import { ChainLookup } from "@/utils/types.generic"; import { arbitrum, base } from "viem/chains"; @@ -204,12 +205,12 @@ export const PINTO_WSTETH_TOKEN: ChainLookup = { chainId: base.id, name: "PINTOWSTETH LP", symbol: "PINTOWSTETH", - address: "0x3e1155480Fce43686793dd18E43104A01abCBC92", // temp address + address: "0x3e1155245FF9a6a019Bc35827e801c6ED2CE91b9", // temp address decimals: 18, displayDecimals: 2, isLP: true, isWhitelisted: true, - logoURI: pintoWsolIcon, + logoURI: pintoWstethIcon, color: "#00A3FF", tokens: [MAIN_TOKEN[base.id].address, WSTETH_TOKEN[base.id].address], ...defaultLPTokenIndicies, diff --git a/src/lib/siloConvert/SiloConvert.ts b/src/lib/siloConvert/SiloConvert.ts index a5968fddc..48f8594a5 100644 --- a/src/lib/siloConvert/SiloConvert.ts +++ b/src/lib/siloConvert/SiloConvert.ts @@ -295,28 +295,43 @@ export class SiloConvert { throw e; }); - const simulationsRawResults = await Promise.all( - quotedRoutes.map((route) => - route.workflow - .simulate({ - account: this.context.account, - after: this.priceCache.constructPriceAdvPipe({ noTokenPrices: true }), - }) - .catch((e) => { - logError("[SiloConvert/quote] FAILED to simulate routes : ", e); - throw new SimulationError("quote", e instanceof Error ? e.message : "Unknown error", { - routes, - quotedRoutes, - }); - }) - .then((r) => { - console.debug("[SiloConvert/quote] simulated route!: ", route, r); - return r; - }), - ), - ); + const runSimulate = (getPrices: boolean, warn?: boolean) => { + return Promise.all( + quotedRoutes.map((route) => + route.workflow + .simulate({ + account: this.context.account, + after: getPrices ? this.priceCache.constructPriceAdvPipe({ noTokenPrices: true }) : undefined, + }) + .catch((e) => { + logError("[SiloConvert/quote] FAILED to simulate routes : ", e, warn); + throw new SimulationError("quote", e instanceof Error ? e.message : "Unknown error", { + routes, + quotedRoutes, + }); + }) + .then((r) => { + console.debug("[SiloConvert/quote] simulated route!: ", route, r); + return r; + }), + ), + ); + }; + + let didSucceedWithPrices = true; + + const simulationsRawResults = await runSimulate(true, true).catch((e) => { + didSucceedWithPrices = false; + logError("[SiloConvert/quote] RETRYING to simulate routes w/o prices: ", e, true); + return runSimulate(false, false).catch((e) => { + throw new SimulationError("quote", e instanceof Error ? e.message : "Unknown error", { + routes, + quotedRoutes, + }); + }); + }); - console.debug("[SiloConvert/quote] post simulation results: ", { + console.log("[SiloConvert/quote] post simulation results: ", { quotedRoutes, simulationsRawResults, }); @@ -333,7 +348,7 @@ export class SiloConvert { let decoded: ReturnType; try { - decoded = this.decodeRouteAndPriceResults(staticCallResult, route.route); + decoded = this.decodeRouteAndPriceResults(staticCallResult, route.route, didSucceedWithPrices); } catch (e) { logError("[SiloConvert/quote] FAILED to decode route and price results: ", e); throw new ConversionQuotationError("Failed to decode route and price results", { @@ -389,24 +404,22 @@ export class SiloConvert { private decodeRouteAndPriceResults( rawResponse: HashString[], route: SiloConvertRoute, + priceCallSuccess: boolean, ): Pick, "results" | "reducedResults" | "postPriceData"> { const mainToken = getChainConstant(this.context.chainId, MAIN_TOKEN); try { const staticCallResult = [...rawResponse]; // price result is the last element in the static call result - const priceResult = staticCallResult.pop(); - - console.log({ - priceResult, - }); + const priceResult = !priceCallSuccess ? undefined : staticCallResult.pop(); const decodedConvertResults = decodeConvertResults(staticCallResult, route.convertType); const decodedPriceCalls = priceResult ? AdvancedPipeWorkflow.decodeResult(priceResult) : undefined; - const postPriceData = decodedPriceCalls?.length - ? this.priceCache.decodePriceCallResults([...decodedPriceCalls]) - : undefined; + const postPriceData = + decodedPriceCalls?.length && priceCallSuccess + ? this.priceCache.decodePriceCallResults([...decodedPriceCalls]) + : undefined; return { postPriceData, @@ -462,12 +475,12 @@ export class SiloConvert { } } -function logError(prefix: string, e: unknown) { +function logError(prefix: string, e: unknown, warn?: boolean) { if (e instanceof SiloConvertError) { - console.error(prefix, e.toLogObject()); + console[warn ? "warn" : "error"](prefix, e.toLogObject()); } else if (e instanceof Error) { - console.error(prefix, e.message); + console[warn ? "warn" : "error"](prefix, e.message); } else { - console.error("[SiloConvert] Unknown error: ", e); + console[warn ? "warn" : "error"]("[SiloConvert] Unknown error: ", e); } } diff --git a/src/lib/siloConvert/siloConvert.swapQuoter.ts b/src/lib/siloConvert/siloConvert.swapQuoter.ts index 3e722c120..86f047851 100644 --- a/src/lib/siloConvert/siloConvert.swapQuoter.ts +++ b/src/lib/siloConvert/siloConvert.swapQuoter.ts @@ -81,7 +81,7 @@ export class SiloConvertSwapQuoter { const fee = quote.fees?.zeroExFee; - if (fee) { + if (fee && !usdIn.isZero && !usdOut.isZero) { const feeToken = stringEq(fee.token, sellToken.address) ? sellToken : buyToken; const feeAmount = TV.fromBlockchain(fee.amount, feeToken.decimals); const feeTokenUSD = tokensEqual(feeToken, sellToken) ? sellTokenUSD : buyTokenUSD; From 649332e305c017434f708cdb44253e8d5ded8ad9 Mon Sep 17 00:00:00 2001 From: PintoPirate <189064953+PintoPirate@users.noreply.github.com> Date: Wed, 26 Nov 2025 00:44:51 -0500 Subject: [PATCH 26/99] Wsteth market performance wip --- .../charts/MarketPerformanceChart.tsx | 2 +- .../queries/useSeasonalMarketPerformance.ts | 75 ++++++++++++------- 2 files changed, 47 insertions(+), 30 deletions(-) diff --git a/src/components/charts/MarketPerformanceChart.tsx b/src/components/charts/MarketPerformanceChart.tsx index 75c2c8abe..9fd25e7d6 100644 --- a/src/components/charts/MarketPerformanceChart.tsx +++ b/src/components/charts/MarketPerformanceChart.tsx @@ -165,7 +165,7 @@ const MarketPerformanceChart = ({ season, size, className }: MarketPerformanceCh const chartData: LineChartData[] = []; const tokens: (Token | undefined)[] = []; const chartStrokeGradients: StrokeGradientFunction[] = []; - for (const token of ["NET", "WETH", "cbETH", "cbBTC", "WSOL"]) { + for (const token of ["NET", "WETH", "cbETH", "wstETH", "cbBTC", "WSOL"]) { for (let i = 0; i < allData[token].length; i++) { chartData[i] ??= { timestamp: allData[token][i].timestamp, diff --git a/src/state/seasonal/queries/useSeasonalMarketPerformance.ts b/src/state/seasonal/queries/useSeasonalMarketPerformance.ts index 75ba5d270..f5b408c5b 100644 --- a/src/state/seasonal/queries/useSeasonalMarketPerformance.ts +++ b/src/state/seasonal/queries/useSeasonalMarketPerformance.ts @@ -120,8 +120,18 @@ export function useMarketPerformanceCalc( const responseData = useMemo(() => { const result: SeasonalMarketPerformanceChartData = {}; if (seasonalData) { - for (let i = 0; i < seasonalData.length; ++i) { - const season = seasonalData[i]; + const test = [ + ...seasonalData, + { + ...seasonalData[0], + percentChange: [...(seasonalData[0].percentChange as string[]), "0.01"], + totalPercentChange: seasonalData[0].totalPercentChange, + usdChange: [...(seasonalData[0].usdChange as string[]), "12345"], + totalUsdChange: seasonalData[0].totalUsdChange, + }, + ]; + for (let i = 0; i < test.length; ++i) { + const season = test[i]; if (chartType !== SMPChartType.TOKEN_PRICES) { if (season.season <= (startSeasons.NET ?? 0)) { continue; @@ -149,6 +159,10 @@ export function useMarketPerformanceCalc( }); } + if (i === test.length - 1) { + console.log("abc", season, test[i - 1]); + } + let tokenIdx = 0; for (const token of season.silo.allWhitelistedTokens) { // Skip Pinto token @@ -167,35 +181,38 @@ export function useMarketPerformanceCalc( } if (chartType !== SMPChartType.TOKEN_PRICES) { - result[symbol] ??= [ - { - season: season.season - 1, - value: 0, - timestamp: new Date((Number(season.timestamp) - 60 * 60) * 1000), - }, - ]; - const arr = result[symbol]; + const seasonValue = season[CHART_FIELDS[chartType % 2][0]][tokenIdx] ?? null; + if (!!seasonValue) { + result[symbol] ??= [ + { + season: season.season - 1, + value: 0, + timestamp: new Date((Number(season.timestamp) - 60 * 60) * 1000), + }, + ]; + const arr = result[symbol]; - const value = - chartType < SMPChartType.USD_CUMULATIVE - ? Number(season[CHART_FIELDS[chartType % 2][0]][tokenIdx]) - : accumulator(chartType)( - arr[arr.length - 1].value, - Number(season[CHART_FIELDS[chartType % 2][0]][tokenIdx]), - ); - arr.push({ - season: season.season, - value, - timestamp: new Date(Number(season.timestamp) * 1000), - }); + const value = + chartType < SMPChartType.USD_CUMULATIVE + ? Number(seasonValue) + : accumulator(chartType)(arr[arr.length - 1].value, Number(seasonValue)); + arr.push({ + season: season.season, + value, + timestamp: new Date(Number(season.timestamp) * 1000), + }); + } } else { - result[symbol] ??= []; - result[symbol].push({ - season: season.season, - // biome-ignore lint/style/noNonNullAssertion: can't be null given only valid=true is retrieved from sg. - value: Number(season.thisSeasonTokenUsdPrices![tokenIdx]), - timestamp: new Date(Number(season.timestamp) * 1000), - }); + // biome-ignore lint/style/noNonNullAssertion: can't be null given only valid=true is retrieved from sg. + const seasonValue = season.thisSeasonTokenUsdPrices![tokenIdx] ?? null; + if (!!seasonValue) { + result[symbol] ??= []; + result[symbol].push({ + season: season.season, + value: Number(seasonValue), + timestamp: new Date(Number(season.timestamp) * 1000), + }); + } } ++tokenIdx; } From b79231d17de586d1b2385d5522fee99f0582292f Mon Sep 17 00:00:00 2001 From: PintoPirate <189064953+PintoPirate@users.noreply.github.com> Date: Thu, 27 Nov 2025 01:14:36 -0500 Subject: [PATCH 27/99] Arranges newly appearing datapoints --- src/components/charts/LineChart.tsx | 12 +++++-- .../charts/MarketPerformanceChart.tsx | 34 ++++++++++++++---- .../queries/useSeasonalMarketPerformance.ts | 35 ++++++++++++------- 3 files changed, 59 insertions(+), 22 deletions(-) diff --git a/src/components/charts/LineChart.tsx b/src/components/charts/LineChart.tsx index 75430e6a8..3dc32f676 100644 --- a/src/components/charts/LineChart.tsx +++ b/src/components/charts/LineChart.tsx @@ -18,7 +18,7 @@ import { LineChartHorizontalReferenceLine, plugins } from "./chartHelpers"; Chart.register(LineController, LineElement, LinearScale, LogarithmicScale, CategoryScale, PointElement, Filler); export type LineChartData = { - values: number[]; + values: Array; } & Record; export type MakeGradientFunction = ( @@ -96,8 +96,14 @@ const LineChart = React.memo( const [yTickMin, yTickMax] = useMemo(() => { // Otherwise calculate based on data - const maxData = data.reduce((acc, next) => Math.max(acc, ...next.values), Number.MIN_SAFE_INTEGER); - const minData = data.reduce((acc, next) => Math.min(acc, ...next.values), Number.MAX_SAFE_INTEGER); + const maxData = data.reduce( + (acc, next) => Math.max(acc, ...next.values.filter((v): v is number => v !== null)), + Number.MIN_SAFE_INTEGER, + ); + const minData = data.reduce( + (acc, next) => Math.min(acc, ...next.values.filter((v): v is number => v !== null)), + Number.MAX_SAFE_INTEGER, + ); const maxTick = maxData === minData && maxData === 0 ? 1 : maxData; let minTick = minData - (maxData - minData) * 0.1; diff --git a/src/components/charts/MarketPerformanceChart.tsx b/src/components/charts/MarketPerformanceChart.tsx index 9fd25e7d6..8fffdcbf6 100644 --- a/src/components/charts/MarketPerformanceChart.tsx +++ b/src/components/charts/MarketPerformanceChart.tsx @@ -165,24 +165,41 @@ const MarketPerformanceChart = ({ season, size, className }: MarketPerformanceCh const chartData: LineChartData[] = []; const tokens: (Token | undefined)[] = []; const chartStrokeGradients: StrokeGradientFunction[] = []; - for (const token of ["NET", "WETH", "cbETH", "wstETH", "cbBTC", "WSOL"]) { + const allTokens = ["NET", "WETH", "cbETH", "wstETH", "cbBTC", "WSOL"]; + const tokensPresent: string[] = []; + for (const token of allTokens) { + if (allData[token].length > 0) { + tokensPresent.push(token); + } + } + for (const token of tokensPresent) { + const missingDatapoints = allData.NET.length - allData[token].length; for (let i = 0; i < allData[token].length; i++) { - chartData[i] ??= { + chartData[i + missingDatapoints] ??= { timestamp: allData[token][i].timestamp, values: [], }; if (dataType !== DataType.PRICE) { - chartData[i].values.push(allData[token][i].value); + chartData[i + missingDatapoints].values.push(allData[token][i].value); } else { - chartData[i].values.push( + chartData[i + missingDatapoints].values.push( transformValue(allData[token][i].value, minValues[token], maxValues[token], priceTransformRanges[token]), ); } } + for (let i = 0; i < missingDatapoints; i++) { + // Datapoint was missing for this token but present for other tokens + chartData[i].values.push(null); + } const tokenObj = tokenConfig.find((t) => t.symbol === token); tokens.push(tokenObj); chartStrokeGradients.push(gradientFunctions.solid(tokenObj?.color ?? "green")); } + console.log("abc cd", { + chartData, + tokens, + chartStrokeGradients, + }); return { chartData, tokens, @@ -287,10 +304,12 @@ const MarketPerformanceChart = ({ season, size, className }: MarketPerformanceCh
      - Season {allData.NET[displayIndex].season} + {/* TODO(pp): revisit this ?. it only crashes on hovering at the end */} + Season {allData.NET[displayIndex]?.season ?? 12345643}
      - {formatDate(allData.NET[displayIndex].timestamp)} + {/* TODO(pp): revisit this &&, ?. it only crashes on hovering at the end */} + {allData.NET[displayIndex]?.timestamp && formatDate(allData.NET[displayIndex]?.timestamp)}
      @@ -324,7 +343,8 @@ const MarketPerformanceChart = ({ season, size, className }: MarketPerformanceCh
      {tokenSymbol === "NET" && "Total: "}

      - {displayValueFormatter(allData[tokenSymbol][displayIndex].value)} + {/* TODO(pp): revisit this ?. */} + {displayValueFormatter(allData[tokenSymbol][displayIndex]?.value)}

      {idx < Object.keys(allData).length - 1 && ( diff --git a/src/state/seasonal/queries/useSeasonalMarketPerformance.ts b/src/state/seasonal/queries/useSeasonalMarketPerformance.ts index f5b408c5b..5f33b65cc 100644 --- a/src/state/seasonal/queries/useSeasonalMarketPerformance.ts +++ b/src/state/seasonal/queries/useSeasonalMarketPerformance.ts @@ -120,16 +120,30 @@ export function useMarketPerformanceCalc( const responseData = useMemo(() => { const result: SeasonalMarketPerformanceChartData = {}; if (seasonalData) { + /// TODO(pp): replace test with seasonalData when done testing this mock data const test = [ - ...seasonalData, + ...seasonalData.slice(0, -1), { - ...seasonalData[0], - percentChange: [...(seasonalData[0].percentChange as string[]), "0.01"], - totalPercentChange: seasonalData[0].totalPercentChange, - usdChange: [...(seasonalData[0].usdChange as string[]), "12345"], - totalUsdChange: seasonalData[0].totalUsdChange, + ...seasonalData[seasonalData.length - 1], + season: seasonalData[seasonalData.length - 1].season, + silo: { + ...seasonalData[seasonalData.length - 1].silo, + allWhitelistedTokens: [ + ...seasonalData[seasonalData.length - 1].silo.allWhitelistedTokens, + "0x3e1155245ff9a6a019bc35827e801c6ed2ce91b9", + ], + }, + percentChange: [...(seasonalData[seasonalData.length - 1].percentChange as string[]), "0.01"], + totalPercentChange: seasonalData[seasonalData.length - 1].totalPercentChange, + usdChange: [...(seasonalData[seasonalData.length - 1].usdChange as string[]), "12345"], + totalUsdChange: seasonalData[seasonalData.length - 1].totalUsdChange, + thisSeasonTokenUsdPrices: [ + ...(seasonalData[seasonalData.length - 1].thisSeasonTokenUsdPrices as string[]), + "3000", + ], }, ]; + /// for (let i = 0; i < test.length; ++i) { const season = test[i]; if (chartType !== SMPChartType.TOKEN_PRICES) { @@ -159,10 +173,6 @@ export function useMarketPerformanceCalc( }); } - if (i === test.length - 1) { - console.log("abc", season, test[i - 1]); - } - let tokenIdx = 0; for (const token of season.silo.allWhitelistedTokens) { // Skip Pinto token @@ -181,7 +191,7 @@ export function useMarketPerformanceCalc( } if (chartType !== SMPChartType.TOKEN_PRICES) { - const seasonValue = season[CHART_FIELDS[chartType % 2][0]][tokenIdx] ?? null; + const seasonValue = season[CHART_FIELDS[chartType % 2][0]][tokenIdx]; if (!!seasonValue) { result[symbol] ??= [ { @@ -204,7 +214,7 @@ export function useMarketPerformanceCalc( } } else { // biome-ignore lint/style/noNonNullAssertion: can't be null given only valid=true is retrieved from sg. - const seasonValue = season.thisSeasonTokenUsdPrices![tokenIdx] ?? null; + const seasonValue = season.thisSeasonTokenUsdPrices![tokenIdx]; if (!!seasonValue) { result[symbol] ??= []; result[symbol].push({ @@ -220,6 +230,7 @@ export function useMarketPerformanceCalc( } return result; }, [seasonalData, chartType, startSeasons, mainToken.address, lpToUnderlyingMap]); + return responseData; } From a03c2219119c815fd9a6e61405a2f34b21ce0386 Mon Sep 17 00:00:00 2001 From: PintoPirate <189064953+PintoPirate@users.noreply.github.com> Date: Fri, 28 Nov 2025 13:38:53 -0500 Subject: [PATCH 28/99] Fix incorrect selected value display --- .../charts/MarketPerformanceChart.tsx | 11 +++++----- .../queries/useSeasonalMarketPerformance.ts | 21 ++++++++++++++++++- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/src/components/charts/MarketPerformanceChart.tsx b/src/components/charts/MarketPerformanceChart.tsx index 8fffdcbf6..b04d99b91 100644 --- a/src/components/charts/MarketPerformanceChart.tsx +++ b/src/components/charts/MarketPerformanceChart.tsx @@ -304,12 +304,10 @@ const MarketPerformanceChart = ({ season, size, className }: MarketPerformanceCh
      - {/* TODO(pp): revisit this ?. it only crashes on hovering at the end */} - Season {allData.NET[displayIndex]?.season ?? 12345643} + Season {allData.NET[displayIndex].season}
      - {/* TODO(pp): revisit this &&, ?. it only crashes on hovering at the end */} - {allData.NET[displayIndex]?.timestamp && formatDate(allData.NET[displayIndex]?.timestamp)} + {formatDate(allData.NET[displayIndex].timestamp)}
      @@ -317,6 +315,8 @@ const MarketPerformanceChart = ({ season, size, className }: MarketPerformanceCh {chartDataset.tokens.map((token, idx) => { const tokenSymbol = token?.symbol ?? "NET"; const isNetToken = tokenSymbol === "NET"; + const missingDatapoints = allData.NET.length - allData[tokenSymbol].length; + const value = allData[tokenSymbol][displayIndex - missingDatapoints]?.value; return (
      {tokenSymbol === "NET" && "Total: "}

      - {/* TODO(pp): revisit this ?. */} - {displayValueFormatter(allData[tokenSymbol][displayIndex]?.value)} + {!!value ? displayValueFormatter(value) : "-"}

      {idx < Object.keys(allData).length - 1 && ( diff --git a/src/state/seasonal/queries/useSeasonalMarketPerformance.ts b/src/state/seasonal/queries/useSeasonalMarketPerformance.ts index 5f33b65cc..e6350d86c 100644 --- a/src/state/seasonal/queries/useSeasonalMarketPerformance.ts +++ b/src/state/seasonal/queries/useSeasonalMarketPerformance.ts @@ -122,7 +122,26 @@ export function useMarketPerformanceCalc( if (seasonalData) { /// TODO(pp): replace test with seasonalData when done testing this mock data const test = [ - ...seasonalData.slice(0, -1), + ...seasonalData.slice(0, -2), + { + ...seasonalData[seasonalData.length - 2], + season: seasonalData[seasonalData.length - 2].season, + silo: { + ...seasonalData[seasonalData.length - 2].silo, + allWhitelistedTokens: [ + ...seasonalData[seasonalData.length - 2].silo.allWhitelistedTokens, + "0x3e1155245ff9a6a019bc35827e801c6ed2ce91b9", + ], + }, + percentChange: [...(seasonalData[seasonalData.length - 2].percentChange as string[]), "0.09"], + totalPercentChange: seasonalData[seasonalData.length - 2].totalPercentChange, + usdChange: [...(seasonalData[seasonalData.length - 2].usdChange as string[]), "10000"], + totalUsdChange: seasonalData[seasonalData.length - 2].totalUsdChange, + thisSeasonTokenUsdPrices: [ + ...(seasonalData[seasonalData.length - 2].thisSeasonTokenUsdPrices as string[]), + "2900", + ], + }, { ...seasonalData[seasonalData.length - 1], season: seasonalData[seasonalData.length - 1].season, From 97423bb03eef31026ba90ea4b6e46efcb9ff3edb Mon Sep 17 00:00:00 2001 From: PintoPirate <189064953+PintoPirate@users.noreply.github.com> Date: Fri, 28 Nov 2025 13:50:57 -0500 Subject: [PATCH 29/99] Proper undefined check --- .../charts/MarketPerformanceChart.tsx | 2 +- .../queries/useSeasonalMarketPerformance.ts | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/components/charts/MarketPerformanceChart.tsx b/src/components/charts/MarketPerformanceChart.tsx index b04d99b91..c77d4e628 100644 --- a/src/components/charts/MarketPerformanceChart.tsx +++ b/src/components/charts/MarketPerformanceChart.tsx @@ -343,7 +343,7 @@ const MarketPerformanceChart = ({ season, size, className }: MarketPerformanceCh
      {tokenSymbol === "NET" && "Total: "}

      - {!!value ? displayValueFormatter(value) : "-"} + {typeof value === "number" ? displayValueFormatter(value) : "-"}

      {idx < Object.keys(allData).length - 1 && ( diff --git a/src/state/seasonal/queries/useSeasonalMarketPerformance.ts b/src/state/seasonal/queries/useSeasonalMarketPerformance.ts index e6350d86c..45a02f8d3 100644 --- a/src/state/seasonal/queries/useSeasonalMarketPerformance.ts +++ b/src/state/seasonal/queries/useSeasonalMarketPerformance.ts @@ -123,6 +123,25 @@ export function useMarketPerformanceCalc( /// TODO(pp): replace test with seasonalData when done testing this mock data const test = [ ...seasonalData.slice(0, -2), + { + ...seasonalData[seasonalData.length - 3], + season: seasonalData[seasonalData.length - 3].season, + silo: { + ...seasonalData[seasonalData.length - 3].silo, + allWhitelistedTokens: [ + ...seasonalData[seasonalData.length - 3].silo.allWhitelistedTokens, + "0x3e1155245ff9a6a019bc35827e801c6ed2ce91b9", + ], + }, + percentChange: [...(seasonalData[seasonalData.length - 3].percentChange as string[]), "-0.02"], + totalPercentChange: seasonalData[seasonalData.length - 3].totalPercentChange, + usdChange: [...(seasonalData[seasonalData.length - 3].usdChange as string[]), "-500"], + totalUsdChange: seasonalData[seasonalData.length - 3].totalUsdChange, + thisSeasonTokenUsdPrices: [ + ...(seasonalData[seasonalData.length - 3].thisSeasonTokenUsdPrices as string[]), + "2700", + ], + }, { ...seasonalData[seasonalData.length - 2], season: seasonalData[seasonalData.length - 2].season, From 9156f56ff3296695855b3b4774ce53294d61c3dc Mon Sep 17 00:00:00 2001 From: PintoPirate <189064953+PintoPirate@users.noreply.github.com> Date: Fri, 28 Nov 2025 13:59:50 -0500 Subject: [PATCH 30/99] Wsteth added to big chart --- src/state/useChartSetupData.ts | 45 +++++++++++++++++++++++++++++++++- src/state/useSeasonsData.ts | 14 +++++++++++ 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/src/state/useChartSetupData.ts b/src/state/useChartSetupData.ts index 6f5412a4c..5ff6bab94 100644 --- a/src/state/useChartSetupData.ts +++ b/src/state/useChartSetupData.ts @@ -2,7 +2,7 @@ import pintoExchangeLogo from "@/assets/misc/pinto-exchange-logo.svg"; import podIcon from "@/assets/protocol/Pod.png"; import stalkIcon from "@/assets/protocol/Stalk.png"; import { TokenValue } from "@/classes/TokenValue"; -import { CBBTC_TOKEN, CBETH_TOKEN, WETH_TOKEN, WSOL_TOKEN } from "@/constants/tokens"; +import { CBBTC_TOKEN, CBETH_TOKEN, WETH_TOKEN, WSOL_TOKEN, WSTETH_TOKEN } from "@/constants/tokens"; import { chartFormatters, formatNum, formatPct, formatUSD } from "@/utils/format"; import { Token } from "@/utils/types"; import { useMemo } from "react"; @@ -1137,6 +1137,15 @@ const createMarketCharts = (mainToken: Token): ChartSetupBase[] => { valueAxis: "ethPrice", formatter: usdFormatter, }), + marketEntry({ + id: "marketPriceWsteth", + name: "wstETH Price", + icon: WSTETH_TOKEN[mainToken.chainId].logoURI, + tooltipTitle: "wstEth Price", + description: "wstEth Price", + valueAxis: "ethPrice", + formatter: usdFormatter, + }), marketEntry({ id: "marketPriceCbbtc", name: "cbBTC Price", @@ -1180,6 +1189,15 @@ const createMarketCharts = (mainToken: Token): ChartSetupBase[] => { formatter: usdFormatter, inputOptions: "SEASON", }), + marketEntry({ + id: "marketCumulativeWstethUsd", + name: "Protocol Cumulative cbETH Value Change (USD)", + icon: WSTETH_TOKEN[mainToken.chainId].logoURI, + tooltipTitle: "Market: Cumulative wstETH Value Change", + description: "Change of wstETH liquidity USD value since a selectable starting season.", + formatter: usdFormatter, + inputOptions: "SEASON", + }), marketEntry({ id: "marketCumulativeCbbtcUsd", name: "Protocol Cumulative cbBTC Value Change (USD)", @@ -1222,6 +1240,14 @@ const createMarketCharts = (mainToken: Token): ChartSetupBase[] => { description: "Change of cbETH liquidity USD value by season.", formatter: usdFormatter, }), + marketEntry({ + id: "marketSeasonalWstethUsd", + name: "Protocol Seasonal wstETH Value Change (USD)", + icon: WSTETH_TOKEN[mainToken.chainId].logoURI, + tooltipTitle: "Market: Seasonal wstETH Value Change", + description: "Change of wstETH liquidity USD value by season.", + formatter: usdFormatter, + }), marketEntry({ id: "marketSeasonalCbbtcUsd", name: "Protocol Seasonal cbBTC Value Change (USD)", @@ -1265,6 +1291,15 @@ const createMarketCharts = (mainToken: Token): ChartSetupBase[] => { formatter: chartFormatters.percentFormatter(4), inputOptions: "SEASON", }), + marketEntry({ + id: "marketCumulativeWstethPercent", + name: "Protocol Cumulative wstETH Value Change (%)", + icon: WSTETH_TOKEN[mainToken.chainId].logoURI, + tooltipTitle: "Market: Cumulative wstETH Value Change", + description: "Percentage change of wstETH liquidity value since a selectable starting season.", + formatter: chartFormatters.percentFormatter(4), + inputOptions: "SEASON", + }), marketEntry({ id: "marketCumulativeCbbtcPercent", name: "Protocol Cumulative cbBTC Value Change (%)", @@ -1307,6 +1342,14 @@ const createMarketCharts = (mainToken: Token): ChartSetupBase[] => { description: "Percentage change of cbETH liquidity value by season.", formatter: chartFormatters.percentFormatter(4), }), + marketEntry({ + id: "marketSeasonalWstethPercent", + name: "Protocol Seasonal wstETH Value Change (%)", + icon: WSTETH_TOKEN[mainToken.chainId].logoURI, + tooltipTitle: "Market: Seasonal wstETH Value Change", + description: "Percentage change of wstETH liquidity value by season.", + formatter: chartFormatters.percentFormatter(4), + }), marketEntry({ id: "marketSeasonalCbbtcPercent", name: "Protocol Seasonal cbBTC Value Change (%)", diff --git a/src/state/useSeasonsData.ts b/src/state/useSeasonsData.ts index 7841ed50a..f7de85b34 100644 --- a/src/state/useSeasonsData.ts +++ b/src/state/useSeasonsData.ts @@ -130,26 +130,31 @@ export interface SeasonsTableData { inflowFieldDeltaVolume: number; marketPriceWeth: number; marketPriceCbeth: number; + marketPriceWsteth: number; marketPriceCbbtc: number; marketPriceWsol: number; marketCumulativeNonPintoUsd: number; marketCumulativeWethUsd: number; marketCumulativeCbethUsd: number; + marketCumulativeWstethUsd: number; marketCumulativeCbbtcUsd: number; marketCumulativeWsolUsd: number; marketSeasonalNonPintoUsd: number; marketSeasonalWethUsd: number; marketSeasonalCbethUsd: number; + marketSeasonalWstethUsd: number; marketSeasonalCbbtcUsd: number; marketSeasonalWsolUsd: number; marketCumulativeNonPintoPercent: number; marketCumulativeWethPercent: number; marketCumulativeCbethPercent: number; + marketCumulativeWstethPercent: number; marketCumulativeCbbtcPercent: number; marketCumulativeWsolPercent: number; marketSeasonalNonPintoPercent: number; marketSeasonalWethPercent: number; marketSeasonalCbethPercent: number; + marketSeasonalWstethPercent: number; marketSeasonalCbbtcPercent: number; marketSeasonalWsolPercent: number; } @@ -641,6 +646,7 @@ export default function useSeasonsData( if (marketPerformanceData && idx + syncOffset < countSubgraphSeasons) { allData.marketPriceWeth = marketPrices.WETH[marketPrices.WETH.length - 1 - idx - syncOffset].value; allData.marketPriceCbeth = marketPrices.cbETH[marketPrices.cbETH.length - 1 - idx - syncOffset].value; + allData.marketPriceWsteth = marketPrices.wstETH[marketPrices.wstETH.length - 1 - idx - syncOffset].value; allData.marketPriceCbbtc = marketPrices.cbBTC[marketPrices.cbBTC.length - 1 - idx - syncOffset].value; allData.marketPriceWsol = marketPrices.WSOL[marketPrices.WSOL.length - 1 - idx - syncOffset].value; allData.marketCumulativeNonPintoUsd = @@ -649,6 +655,8 @@ export default function useSeasonsData( marketUsdCumulative.WETH?.[marketUsdCumulative.WETH.length - 1 - idx - syncOffset]?.value; allData.marketCumulativeCbethUsd = marketUsdCumulative.cbETH?.[marketUsdCumulative.cbETH.length - 1 - idx - syncOffset]?.value; + allData.marketCumulativeWstethUsd = + marketUsdCumulative.wstETH?.[marketUsdCumulative.wstETH.length - 1 - idx - syncOffset]?.value; allData.marketCumulativeCbbtcUsd = marketUsdCumulative.cbBTC?.[marketUsdCumulative.cbBTC.length - 1 - idx - syncOffset]?.value; allData.marketCumulativeWsolUsd = @@ -659,6 +667,8 @@ export default function useSeasonsData( marketUsdSeasonal.WETH[marketUsdSeasonal.WETH.length - 1 - idx - syncOffset]?.value; allData.marketSeasonalCbethUsd = marketUsdSeasonal.cbETH[marketUsdSeasonal.cbETH.length - 1 - idx - syncOffset]?.value; + allData.marketSeasonalWstethUsd = + marketUsdSeasonal.wstETH[marketUsdSeasonal.wstETH.length - 1 - idx - syncOffset]?.value; allData.marketSeasonalCbbtcUsd = marketUsdSeasonal.cbBTC[marketUsdSeasonal.cbBTC.length - 1 - idx - syncOffset]?.value; allData.marketSeasonalWsolUsd = @@ -669,6 +679,8 @@ export default function useSeasonsData( marketPercentCumulative.WETH?.[marketPercentCumulative.WETH.length - 1 - idx - syncOffset]?.value; allData.marketCumulativeCbethPercent = marketPercentCumulative.cbETH?.[marketPercentCumulative.cbETH.length - 1 - idx - syncOffset]?.value; + allData.marketCumulativeWstethPercent = + marketPercentCumulative.wstETH?.[marketPercentCumulative.wstETH.length - 1 - idx - syncOffset]?.value; allData.marketCumulativeCbbtcPercent = marketPercentCumulative.cbBTC?.[marketPercentCumulative.cbBTC.length - 1 - idx - syncOffset]?.value; allData.marketCumulativeWsolPercent = @@ -679,6 +691,8 @@ export default function useSeasonsData( marketPercentSeasonal.WETH[marketPercentSeasonal.WETH.length - 1 - idx - syncOffset]?.value; allData.marketSeasonalCbethPercent = marketPercentSeasonal.cbETH[marketPercentSeasonal.cbETH.length - 1 - idx - syncOffset]?.value; + allData.marketSeasonalWstethPercent = + marketPercentSeasonal.wstETH[marketPercentSeasonal.wstETH.length - 1 - idx - syncOffset]?.value; allData.marketSeasonalCbbtcPercent = marketPercentSeasonal.cbBTC[marketPercentSeasonal.cbBTC.length - 1 - idx - syncOffset]?.value; allData.marketSeasonalWsolPercent = From 75e61aec22722b9c35b849121a2c5a197d4760a0 Mon Sep 17 00:00:00 2001 From: PintoPirate <189064953+PintoPirate@users.noreply.github.com> Date: Fri, 28 Nov 2025 14:41:34 -0500 Subject: [PATCH 31/99] Correct sync offset --- .../queries/useSeasonalMarketPerformance.ts | 7 -- src/state/useSeasonsData.ts | 84 +++++++++---------- 2 files changed, 40 insertions(+), 51 deletions(-) diff --git a/src/state/seasonal/queries/useSeasonalMarketPerformance.ts b/src/state/seasonal/queries/useSeasonalMarketPerformance.ts index 45a02f8d3..9a09367dd 100644 --- a/src/state/seasonal/queries/useSeasonalMarketPerformance.ts +++ b/src/state/seasonal/queries/useSeasonalMarketPerformance.ts @@ -1,11 +1,4 @@ import { subgraphs } from "@/constants/subgraph"; -import { - PINTO_CBBTC_TOKEN, - PINTO_CBETH_TOKEN, - PINTO_USDC_TOKEN, - PINTO_WETH_TOKEN, - PINTO_WSOL_TOKEN, -} from "@/constants/tokens"; import { BeanstalkSeasonalMarketPerformanceDocument, BeanstalkSeasonalMarketPerformanceQuery, diff --git a/src/state/useSeasonsData.ts b/src/state/useSeasonsData.ts index f7de85b34..561d1f738 100644 --- a/src/state/useSeasonsData.ts +++ b/src/state/useSeasonsData.ts @@ -445,7 +445,8 @@ export default function useSeasonsData( 18, ).toNumber(); allData.deltaBeans = TokenValue.fromBlockchain(currStalkSeasons.deltaBeans, tokenData.mainToken.decimals); - allData.price = TokenValue.fromHuman(currStalkSeasons.price, 4); + // No sync offset needed for price + allData.price = TokenValue.fromHuman(stalkResults.seasons[idx].price, 4); allData.raining = currStalkSeasons.raining; allData.rewardBeans = TokenValue.fromHuman(currStalkSeasons.rewardBeans, 2); allData.deltaPodDemand = TokenValue.fromBlockchain(currFieldHourlySnapshots.deltaPodDemand, 18); @@ -508,14 +509,17 @@ export default function useSeasonsData( if (beanData && idx + syncOffset < countSubgraphSeasons) { const beanHourly = beanResults[idx + syncOffset].beanHourlySnapshot; allData.crosses = beanHourly.crosses; - allData.marketCap = Number(beanHourly.marketCap); allData.supply = TokenValue.fromBlockchain(beanHourly.supply, tokenData.mainToken.decimals); allData.supplyInPegLP = TokenValue.fromBlockchain(beanHourly.supply, tokenData.mainToken.decimals); - allData.instDeltaB = TokenValue.fromHuman(beanHourly.instDeltaB, tokenData.mainToken.decimals); - allData.instPrice = TokenValue.fromHuman(beanHourly.instPrice, tokenData.mainToken.decimals); - allData.l2sr = TokenValue.fromHuman(beanHourly.l2sr * 100, 2); - allData.twaDeltaB = TokenValue.fromHuman(beanHourly.twaDeltaB, 2); - allData.twaPrice = TokenValue.fromHuman(beanHourly.twaPrice, 4); + + // No sync offset needed for some of these + const beanHourlyNoSync = beanResults[idx].beanHourlySnapshot; + allData.marketCap = Number(beanHourlyNoSync.marketCap); + allData.l2sr = TokenValue.fromHuman(beanHourlyNoSync.l2sr * 100, 2); + allData.instPrice = TokenValue.fromHuman(beanHourlyNoSync.instPrice, tokenData.mainToken.decimals); + allData.instDeltaB = TokenValue.fromHuman(beanHourlyNoSync.instDeltaB, tokenData.mainToken.decimals); + allData.twaDeltaB = TokenValue.fromHuman(beanHourlyNoSync.twaDeltaB, 2); + allData.twaPrice = TokenValue.fromHuman(beanHourlyNoSync.twaPrice, 4); if (!allData.season) { const season = beanResults[idx].season; @@ -643,60 +647,52 @@ export default function useSeasonsData( } } - if (marketPerformanceData && idx + syncOffset < countSubgraphSeasons) { + if (marketPerformanceData && idx < countSubgraphSeasons) { allData.marketPriceWeth = marketPrices.WETH[marketPrices.WETH.length - 1 - idx - syncOffset].value; - allData.marketPriceCbeth = marketPrices.cbETH[marketPrices.cbETH.length - 1 - idx - syncOffset].value; - allData.marketPriceWsteth = marketPrices.wstETH[marketPrices.wstETH.length - 1 - idx - syncOffset].value; - allData.marketPriceCbbtc = marketPrices.cbBTC[marketPrices.cbBTC.length - 1 - idx - syncOffset].value; - allData.marketPriceWsol = marketPrices.WSOL[marketPrices.WSOL.length - 1 - idx - syncOffset].value; + allData.marketPriceCbeth = marketPrices.cbETH[marketPrices.cbETH.length - 1 - idx].value; + allData.marketPriceWsteth = marketPrices.wstETH[marketPrices.wstETH.length - 1 - idx]?.value; + allData.marketPriceCbbtc = marketPrices.cbBTC[marketPrices.cbBTC.length - 1 - idx].value; + allData.marketPriceWsol = marketPrices.WSOL[marketPrices.WSOL.length - 1 - idx].value; allData.marketCumulativeNonPintoUsd = - marketUsdCumulative.NET?.[marketUsdCumulative.NET.length - 1 - idx - syncOffset]?.value; - allData.marketCumulativeWethUsd = - marketUsdCumulative.WETH?.[marketUsdCumulative.WETH.length - 1 - idx - syncOffset]?.value; + marketUsdCumulative.NET?.[marketUsdCumulative.NET.length - 1 - idx]?.value; + allData.marketCumulativeWethUsd = marketUsdCumulative.WETH?.[marketUsdCumulative.WETH.length - 1 - idx]?.value; allData.marketCumulativeCbethUsd = - marketUsdCumulative.cbETH?.[marketUsdCumulative.cbETH.length - 1 - idx - syncOffset]?.value; + marketUsdCumulative.cbETH?.[marketUsdCumulative.cbETH.length - 1 - idx]?.value; allData.marketCumulativeWstethUsd = - marketUsdCumulative.wstETH?.[marketUsdCumulative.wstETH.length - 1 - idx - syncOffset]?.value; + marketUsdCumulative.wstETH?.[marketUsdCumulative.wstETH.length - 1 - idx]?.value; allData.marketCumulativeCbbtcUsd = - marketUsdCumulative.cbBTC?.[marketUsdCumulative.cbBTC.length - 1 - idx - syncOffset]?.value; - allData.marketCumulativeWsolUsd = - marketUsdCumulative.WSOL?.[marketUsdCumulative.WSOL.length - 1 - idx - syncOffset]?.value; - allData.marketSeasonalNonPintoUsd = - marketUsdSeasonal.NET[marketUsdSeasonal.NET.length - 1 - idx - syncOffset]?.value; - allData.marketSeasonalWethUsd = - marketUsdSeasonal.WETH[marketUsdSeasonal.WETH.length - 1 - idx - syncOffset]?.value; - allData.marketSeasonalCbethUsd = - marketUsdSeasonal.cbETH[marketUsdSeasonal.cbETH.length - 1 - idx - syncOffset]?.value; - allData.marketSeasonalWstethUsd = - marketUsdSeasonal.wstETH[marketUsdSeasonal.wstETH.length - 1 - idx - syncOffset]?.value; - allData.marketSeasonalCbbtcUsd = - marketUsdSeasonal.cbBTC[marketUsdSeasonal.cbBTC.length - 1 - idx - syncOffset]?.value; - allData.marketSeasonalWsolUsd = - marketUsdSeasonal.WSOL[marketUsdSeasonal.WSOL.length - 1 - idx - syncOffset]?.value; + marketUsdCumulative.cbBTC?.[marketUsdCumulative.cbBTC.length - 1 - idx]?.value; + allData.marketCumulativeWsolUsd = marketUsdCumulative.WSOL?.[marketUsdCumulative.WSOL.length - 1 - idx]?.value; + allData.marketSeasonalNonPintoUsd = marketUsdSeasonal.NET[marketUsdSeasonal.NET.length - 1 - idx]?.value; + allData.marketSeasonalWethUsd = marketUsdSeasonal.WETH[marketUsdSeasonal.WETH.length - 1 - idx]?.value; + allData.marketSeasonalCbethUsd = marketUsdSeasonal.cbETH[marketUsdSeasonal.cbETH.length - 1 - idx]?.value; + allData.marketSeasonalWstethUsd = marketUsdSeasonal.wstETH[marketUsdSeasonal.wstETH.length - 1 - idx]?.value; + allData.marketSeasonalCbbtcUsd = marketUsdSeasonal.cbBTC[marketUsdSeasonal.cbBTC.length - 1 - idx]?.value; + allData.marketSeasonalWsolUsd = marketUsdSeasonal.WSOL[marketUsdSeasonal.WSOL.length - 1 - idx]?.value; allData.marketCumulativeNonPintoPercent = - marketPercentCumulative.NET?.[marketPercentCumulative.NET.length - 1 - idx - syncOffset]?.value; + marketPercentCumulative.NET?.[marketPercentCumulative.NET.length - 1 - idx]?.value; allData.marketCumulativeWethPercent = - marketPercentCumulative.WETH?.[marketPercentCumulative.WETH.length - 1 - idx - syncOffset]?.value; + marketPercentCumulative.WETH?.[marketPercentCumulative.WETH.length - 1 - idx]?.value; allData.marketCumulativeCbethPercent = - marketPercentCumulative.cbETH?.[marketPercentCumulative.cbETH.length - 1 - idx - syncOffset]?.value; + marketPercentCumulative.cbETH?.[marketPercentCumulative.cbETH.length - 1 - idx]?.value; allData.marketCumulativeWstethPercent = - marketPercentCumulative.wstETH?.[marketPercentCumulative.wstETH.length - 1 - idx - syncOffset]?.value; + marketPercentCumulative.wstETH?.[marketPercentCumulative.wstETH.length - 1 - idx]?.value; allData.marketCumulativeCbbtcPercent = - marketPercentCumulative.cbBTC?.[marketPercentCumulative.cbBTC.length - 1 - idx - syncOffset]?.value; + marketPercentCumulative.cbBTC?.[marketPercentCumulative.cbBTC.length - 1 - idx]?.value; allData.marketCumulativeWsolPercent = - marketPercentCumulative.WSOL?.[marketPercentCumulative.WSOL.length - 1 - idx - syncOffset]?.value; + marketPercentCumulative.WSOL?.[marketPercentCumulative.WSOL.length - 1 - idx]?.value; allData.marketSeasonalNonPintoPercent = - marketPercentSeasonal.NET[marketPercentSeasonal.NET.length - 1 - idx - syncOffset]?.value; + marketPercentSeasonal.NET[marketPercentSeasonal.NET.length - 1 - idx]?.value; allData.marketSeasonalWethPercent = - marketPercentSeasonal.WETH[marketPercentSeasonal.WETH.length - 1 - idx - syncOffset]?.value; + marketPercentSeasonal.WETH[marketPercentSeasonal.WETH.length - 1 - idx]?.value; allData.marketSeasonalCbethPercent = - marketPercentSeasonal.cbETH[marketPercentSeasonal.cbETH.length - 1 - idx - syncOffset]?.value; + marketPercentSeasonal.cbETH[marketPercentSeasonal.cbETH.length - 1 - idx]?.value; allData.marketSeasonalWstethPercent = - marketPercentSeasonal.wstETH[marketPercentSeasonal.wstETH.length - 1 - idx - syncOffset]?.value; + marketPercentSeasonal.wstETH[marketPercentSeasonal.wstETH.length - 1 - idx]?.value; allData.marketSeasonalCbbtcPercent = - marketPercentSeasonal.cbBTC[marketPercentSeasonal.cbBTC.length - 1 - idx - syncOffset]?.value; + marketPercentSeasonal.cbBTC[marketPercentSeasonal.cbBTC.length - 1 - idx]?.value; allData.marketSeasonalWsolPercent = - marketPercentSeasonal.WSOL[marketPercentSeasonal.WSOL.length - 1 - idx - syncOffset]?.value; + marketPercentSeasonal.WSOL[marketPercentSeasonal.WSOL.length - 1 - idx]?.value; if (!allData.season) { const season = marketPerformanceResults[marketPerformanceResults.length - 1 - idx].season; From 91c107d7c13dea5d31778792b7914275c5c3de86 Mon Sep 17 00:00:00 2001 From: PintoPirate <189064953+PintoPirate@users.noreply.github.com> Date: Sat, 29 Nov 2025 01:05:52 -0500 Subject: [PATCH 32/99] minimal allowed starting cumulative season to wsteth deployment --- src/components/nav/ChartSelectPanel.tsx | 20 +++++++++------ src/state/useChartSetupData.ts | 33 +++++++++++++------------ 2 files changed, 30 insertions(+), 23 deletions(-) diff --git a/src/components/nav/ChartSelectPanel.tsx b/src/components/nav/ChartSelectPanel.tsx index c9dc7660a..bf33e2142 100644 --- a/src/components/nav/ChartSelectPanel.tsx +++ b/src/components/nav/ChartSelectPanel.tsx @@ -6,10 +6,10 @@ import { useDebouncedEffect } from "@/utils/useDebounce"; import { cn } from "@/utils/utils"; import { useAtom } from "jotai"; import { isEqual } from "lodash"; -import { memo, useCallback, useEffect, useMemo, useRef, useState } from "react"; +import { memo, useCallback, useEffect, useMemo, useState } from "react"; import { renderAnnouncement } from "../AnnouncementBanner"; import { ChevronDownIcon, SearchIcon } from "../Icons"; -import { MIN_ADV_SEASON, chartSeasonInputsAtom, selectedChartsAtom } from "../charts/AdvancedChart"; +import { chartSeasonInputsAtom, selectedChartsAtom } from "../charts/AdvancedChart"; import { Input } from "../ui/Input"; import { ScrollArea } from "../ui/ScrollArea"; import { Separator } from "../ui/Separator"; @@ -105,10 +105,11 @@ const ChartSelectPanel = memo(() => { } else { // When selecting, initialize season input to min value const chartData = chartSetupData.find((chart) => chart.index === selection); - if (chartData && chartData.inputOptions === "SEASON") { + const opts = chartData?.inputOptions; + if (opts && opts.type === "SEASON") { setInternalSeasonInputs((prev) => ({ ...prev, - [chartData.id]: MIN_ADV_SEASON, + [chartData.id]: opts.minSeason, })); } selectedItems.push(selection); @@ -159,7 +160,12 @@ const ChartSelectPanel = memo(() => { () => { const clampedInputs = {}; for (const chartId in rawSeasonInputs) { - clampedInputs[chartId] = Math.max(MIN_ADV_SEASON, Math.min(currentSeason, rawSeasonInputs[chartId])); + const chartData = chartSetupData.find((chart) => chart.id === chartId); + clampedInputs[chartId] = Math.max( + // biome-ignore lint/style/noNonNullAssertion: Season input cannot change for chart that didn't configure seasons. + chartData!.inputOptions!.minSeason, + Math.min(currentSeason, rawSeasonInputs[chartId]), + ); } setInternalSeasonInputs(clampedInputs); }, @@ -230,7 +236,7 @@ const ChartSelectPanel = memo(() => {
      {data.shortDescription}
      - {isSelected && data.inputOptions === "SEASON" && ( + {isSelected && data.inputOptions?.type === "SEASON" && (
      e.stopPropagation()}>
      Available tokens: PINTO, WETH, USDC, cbBTC, cbETH, wstETH. You can use either token symbols or addresses. + Decimal precision is dependent on the token. (i.e "1" is 1 token)
      Date: Tue, 13 Jan 2026 18:50:44 -0500 Subject: [PATCH 83/99] Update wstETH token configuration in DevPage --- src/components/DevPage.tsx | 217 +++++++++++++++++++++++++++++++++++++ 1 file changed, 217 insertions(+) diff --git a/src/components/DevPage.tsx b/src/components/DevPage.tsx index 606f1d769..443b27cb4 100644 --- a/src/components/DevPage.tsx +++ b/src/components/DevPage.tsx @@ -167,6 +167,7 @@ export default function DevPage() { const [selectedPercent, setSelectedPercent] = useState(10); const [blockSkipAmount, setBlockSkipAmount] = useState("6"); // default to 6 blocks because the morning auction updates every 6 blocks (12 seconds on eth, 2 seconds on base, 12/2 = 6) + const [sunriseCount, setSunriseCount] = useState("1"); // number of times to call sunrise const [mockAddress, setMockAddress] = useAtom(mockAddressAtom); @@ -382,6 +383,25 @@ export default function DevPage() { } }; + const callSunriseN = async () => { + try { + const count = parseInt(sunriseCount); + if (Number.isNaN(count) || count < 1) { + toast.error("Please enter a valid number of seasons"); + return; + } + + setLoading("callSunriseN"); + await executeTask("callSunriseN", { n: count }); + toast.success(`Called sunrise ${count} time${count > 1 ? "s" : ""}`); + setLoading(null); + } catch (error) { + console.error("Failed to call sunrise N times:", error); + toast.error("Failed to call sunrise multiple times"); + setLoading(null); + } + }; + const handleQuickMint = async () => { if (!address) { toast.error("No wallet connected"); @@ -584,6 +604,18 @@ export default function DevPage() { Mint Me ETH/USDC/Pinto
      +
      + setSunriseCount(e.target.value)} + className="h-10 w-48" + /> + +
      + + {/* SiloToken Gauge Data section */} +
      ); @@ -2246,3 +2281,185 @@ function FarmerSiloDeposits() { ); } + +// SiloToken Gauge Data Component +function SiloTokenGaugeData() { + const publicClient = usePublicClient(); + const protocolAddress = useProtocolAddress(); + const tokenData = useTokenData(); + const [loading, setLoading] = useState(false); + const [tokenSettings, setTokenSettings] = useState>(new Map()); + + const siloTokens = useMemo(() => { + if (!tokenData) return []; + return tokenData.whitelistedTokens; + }, [tokenData]); + + const fetchTokenSettings = async () => { + if (!publicClient || !protocolAddress || siloTokens.length === 0) return; + + setLoading(true); + try { + const settingsMap = new Map(); + + for (const token of siloTokens) { + try { + const settings = await publicClient.readContract({ + address: protocolAddress, + abi: diamondABI, + functionName: "tokenSettings", + args: [token.address], + }); + settingsMap.set(token.address, settings); + } catch (error) { + console.error(`Failed to fetch settings for ${token.symbol}:`, error); + } + } + + setTokenSettings(settingsMap); + } catch (error) { + console.error("Error fetching token settings:", error); + toast.error("Failed to fetch token settings"); + } finally { + setLoading(false); + } + }; + + useEffect(() => { + fetchTokenSettings(); + }, [publicClient, protocolAddress, siloTokens.length]); + + const formatBigInt = (value: any, decimals = 0) => { + if (!value) return "0"; + try { + const num = BigInt(value); + if (decimals > 0) { + const divisor = BigInt(10 ** decimals); + const wholePart = num / divisor; + const fractionalPart = num % divisor; + return `${wholePart}.${fractionalPart.toString().padStart(decimals, "0")}`; + } + return num.toString(); + } catch { + return String(value); + } + }; + + const formatBytes = (value: any) => { + if (!value) return "0x0"; + return String(value); + }; + + return ( + +

      SiloToken Gauge Data

      + +
      +
      + Displaying token settings for {siloTokens.length} silo token{siloTokens.length !== 1 ? "s" : ""} +
      + +
      + + {loading && tokenSettings.size === 0 ? ( +
      +
      +
      + Loading token settings... +
      +
      + ) : tokenSettings.size === 0 ? ( +
      No token settings available
      + ) : ( +
      + {siloTokens.map((token) => { + const settings = tokenSettings.get(token.address); + if (!settings) return null; + + return ( +
      +
      + {token.symbol} +
      +
      {token.symbol}
      +
      {token.address}
      +
      +
      + +
      +
      +
      + Selector: + {formatBytes(settings.selector)} +
      +
      + Stalk Earned Per Season: + {formatBigInt(settings.stalkEarnedPerSeason, 6)} +
      +
      + Stalk Issued Per BDV: + {formatBigInt(settings.stalkIssuedPerBdv, 16)} +
      +
      + Milestone Season: + {formatBigInt(settings.milestoneSeason)} +
      +
      + Milestone Stem: + {formatBigInt(settings.milestoneStem)} +
      +
      + +
      +
      + Encode Type: + {formatBytes(settings.encodeType)} +
      +
      + Delta Stalk Earned Per Season: + {formatBigInt(settings.deltaStalkEarnedPerSeason, 6)} +
      +
      + Gauge Points: + {formatBigInt(settings.gaugePoints, 18)} +
      +
      + Optimal % Deposited BDV: + {formatBigInt(settings.optimalPercentDepositedBdv, 6)}% +
      +
      +
      + +
      +
      Implementations:
      +
      +
      +
      Gauge Point Implementation
      +
      +
      Target: {settings.gaugePointImplementation?.target || "N/A"}
      +
      Selector: {formatBytes(settings.gaugePointImplementation?.selector)}
      +
      Encode: {formatBytes(settings.gaugePointImplementation?.encodeType)}
      +
      +
      +
      +
      Liquidity Weight Implementation
      +
      +
      + Target: {settings.liquidityWeightImplementation?.target || "N/A"} +
      +
      Selector: {formatBytes(settings.liquidityWeightImplementation?.selector)}
      +
      Encode: {formatBytes(settings.liquidityWeightImplementation?.encodeType)}
      +
      +
      +
      +
      +
      + ); + })} +
      + )} + + ); +} From 5a26ac90c5d6a8516974e8f461ec198a7c24b529 Mon Sep 17 00:00:00 2001 From: feyyazcigim Date: Wed, 14 Jan 2026 15:16:14 +0300 Subject: [PATCH 84/99] change wsteth pint lp address --- src/constants/tokens.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/constants/tokens.ts b/src/constants/tokens.ts index 82686f977..4e137332b 100644 --- a/src/constants/tokens.ts +++ b/src/constants/tokens.ts @@ -205,7 +205,7 @@ export const PINTO_WSTETH_TOKEN: ChainLookup = { chainId: base.id, name: "PINTOWSTETH LP", symbol: "PINTOWSTETH", - address: "0x3e1155245FF9a6a019Bc35827e801c6ED2CE91b9", // temp address + address: "0x3e1155480Fce43686793dd18E43104A01abCBC92", decimals: 18, displayDecimals: 2, isLP: true, From 6cabedf94265928e0766b3e0bf962f1602c71b24 Mon Sep 17 00:00:00 2001 From: feyyazcigim Date: Thu, 15 Jan 2026 01:46:15 +0300 Subject: [PATCH 85/99] Revert token address --- src/constants/tokens.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/constants/tokens.ts b/src/constants/tokens.ts index 4e137332b..82686f977 100644 --- a/src/constants/tokens.ts +++ b/src/constants/tokens.ts @@ -205,7 +205,7 @@ export const PINTO_WSTETH_TOKEN: ChainLookup = { chainId: base.id, name: "PINTOWSTETH LP", symbol: "PINTOWSTETH", - address: "0x3e1155480Fce43686793dd18E43104A01abCBC92", + address: "0x3e1155245FF9a6a019Bc35827e801c6ED2CE91b9", // temp address decimals: 18, displayDecimals: 2, isLP: true, From 05057e54e265c72dbeb103f22a6fc186888670fb Mon Sep 17 00:00:00 2001 From: fr1j0 Date: Wed, 14 Jan 2026 21:44:12 -0500 Subject: [PATCH 86/99] Convert referral link generator to standalone page with proper routing --- src/components/ReferralLinkGenerator.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/components/ReferralLinkGenerator.tsx b/src/components/ReferralLinkGenerator.tsx index 55857373c..4ea6b6043 100644 --- a/src/components/ReferralLinkGenerator.tsx +++ b/src/components/ReferralLinkGenerator.tsx @@ -27,7 +27,7 @@ export function ReferralLinkGenerator({ onChangeAddress }: ReferralLinkGenerator const referralCode = address ? encodeReferralAddress(address) : ""; const referralUrl = `${window.location.origin}/field?ref=${encodeURIComponent(referralCode)}`; - const podDestinationAddress = delegateAddress === ZERO_ADDRESS_HEX ? address : delegateAddress || address; + const podDestinationAddress = delegateAddress === ZERO_ADDRESS_HEX ? address : delegateAddress; const handleCopyCode = () => { if (!isWalletConnected) return; @@ -165,13 +165,15 @@ export function ReferralLinkGenerator({ onChangeAddress }: ReferralLinkGenerator > {truncateHex(podDestinationAddress, 6, 4)} - {podDestinationAddress === address && (Delegated)} + {podDestinationAddress === delegateAddress && ( + (Delegated) + )} ) : ( "-" )} - {podDestinationAddress !== address && ( + {podDestinationAddress !== delegateAddress && ( From f94e2e8fe743c3d4b575fb8f4313f3268f1adf91 Mon Sep 17 00:00:00 2001 From: feyyazcigim Date: Thu, 15 Jan 2026 19:26:16 +0300 Subject: [PATCH 87/99] refactor: enhance referral experience with updated text and links --- src/components/HowToCard.tsx | 10 +++++++++- src/components/ReferralLinkGenerator.tsx | 6 +++--- src/components/SowRequirementCard.tsx | 2 +- src/components/ui/StepItem.tsx | 4 +++- 4 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/components/HowToCard.tsx b/src/components/HowToCard.tsx index 271de0361..f67bfe2fe 100644 --- a/src/components/HowToCard.tsx +++ b/src/components/HowToCard.tsx @@ -1,9 +1,17 @@ +import { Link } from "react-router-dom"; import { StepItem } from "./ui/StepItem"; const steps = [ { title: "Qualify as a Referrer", - description: "Sow at least 1,000 Pinto in the Field to unlock your referral link.", + description: ( + <> + Sow at least 1,000 Pinto in the Field to unlock your referral link.{" "} + + Sow now + + + ), }, { title: "Share Your Link", diff --git a/src/components/ReferralLinkGenerator.tsx b/src/components/ReferralLinkGenerator.tsx index 4ea6b6043..523cd54ef 100644 --- a/src/components/ReferralLinkGenerator.tsx +++ b/src/components/ReferralLinkGenerator.tsx @@ -58,7 +58,7 @@ export function ReferralLinkGenerator({ onChangeAddress }: ReferralLinkGenerator console.log("Twitter/X share clicked"); const tweetText = - "🌱 I'm farming on @PintoProtocol and earning passive rewards!\n\nJoin me and I'll earn bonus Pods when you Sow Pinto 🫘\n\nStart farming today:"; + "I'm farming on @pintodotmoney to contributing to the Pinto economy! 🌱\n\nStart farming today and get a 5% bonus:"; const twitterUrl = `https://twitter.com/intent/tweet?text=${encodeURIComponent(tweetText)}&url=${encodeURIComponent(referralUrl)}`; window.open(twitterUrl, "_blank", "noopener,noreferrer"); @@ -72,7 +72,7 @@ export function ReferralLinkGenerator({ onChangeAddress }: ReferralLinkGenerator console.log("Telegram share clicked"); const shareText = - "🌱 I'm farming on Pinto Protocol and earning passive rewards! Join me and I'll earn bonus Pods when you Sow Pinto 🫘 Start farming today"; + "\nI'm farming on @pintodotmoney to contributing to the Pinto economy! 🌱 Start farming today and get a 5% bonus."; const telegramUrl = `https://t.me/share/url?url=${encodeURIComponent(referralUrl)}&text=${encodeURIComponent(shareText)}`; window.open(telegramUrl, "_blank", "noopener,noreferrer"); @@ -84,7 +84,7 @@ export function ReferralLinkGenerator({ onChangeAddress }: ReferralLinkGenerator return (
      -
      Invite via
      +
      Your Referral Link
      {/* Only show referral code/link if user meets requirement */} diff --git a/src/components/SowRequirementCard.tsx b/src/components/SowRequirementCard.tsx index 125466d2a..ed0265546 100644 --- a/src/components/SowRequirementCard.tsx +++ b/src/components/SowRequirementCard.tsx @@ -22,7 +22,7 @@ export function SowRequirementCard({
      {disabled ? "Connect your wallet to access referral features" - : "You currently do not meet the criteria to refer farmers"} + : "You have not Sown enough Pinto to be eligible to refer farmers"}
      diff --git a/src/components/ui/StepItem.tsx b/src/components/ui/StepItem.tsx index dbece71e6..25183718c 100644 --- a/src/components/ui/StepItem.tsx +++ b/src/components/ui/StepItem.tsx @@ -1,7 +1,9 @@ +import type { ReactNode } from "react"; + interface StepItemProps { stepNumber: number; title: string; - description: string; + description: string | ReactNode; } export function StepItem({ stepNumber, title, description }: StepItemProps) { From eac755b9bf7e4aa41127adf88600e8e80a4c71ac Mon Sep 17 00:00:00 2001 From: fr1j0 Date: Thu, 15 Jan 2026 12:19:31 -0500 Subject: [PATCH 88/99] Update sow blueprint referral contract address --- src/constants/address.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/constants/address.ts b/src/constants/address.ts index 25ffd787e..e103dd152 100644 --- a/src/constants/address.ts +++ b/src/constants/address.ts @@ -19,7 +19,7 @@ export const SILO_HELPERS_ADDRESS: HashString = "0xE145082A7C5EDd1767f8148A6c29a export const SOW_BLUEPRINT_V0_ADDRESS: HashString = "0xbb0a41927895F8ca2b4ECCc659ba158735fCF28B"; -export const SOW_BLUEPRINT_REFERRAL_V0_ADDRESS: HashString = "0xCa9fdD6653c851014Ef8aBE7Bd7ec8A8B02c41d0"; // Placeholder - update after deployment +export const SOW_BLUEPRINT_REFERRAL_V0_ADDRESS: HashString = "0x3AC78708a055cF494cBaF50b1EC82f01D1f4A0D3"; // Placeholder - update after deployment export const CONVERT_UP_BLUEPRINT_V0_ADDRESS: HashString = "0x5167Ae1fF37bE08D9cc9188C7e64DB228B6F06ca"; From 3c34d91f33d93e941cf548d372fb7c9ff0273d82 Mon Sep 17 00:00:00 2001 From: fr1j0 Date: Thu, 15 Jan 2026 12:49:10 -0500 Subject: [PATCH 89/99] Update SOW Blueprint referral address in constants --- src/constants/address.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/constants/address.ts b/src/constants/address.ts index e103dd152..43d1e0797 100644 --- a/src/constants/address.ts +++ b/src/constants/address.ts @@ -19,7 +19,7 @@ export const SILO_HELPERS_ADDRESS: HashString = "0xE145082A7C5EDd1767f8148A6c29a export const SOW_BLUEPRINT_V0_ADDRESS: HashString = "0xbb0a41927895F8ca2b4ECCc659ba158735fCF28B"; -export const SOW_BLUEPRINT_REFERRAL_V0_ADDRESS: HashString = "0x3AC78708a055cF494cBaF50b1EC82f01D1f4A0D3"; // Placeholder - update after deployment +export const SOW_BLUEPRINT_REFERRAL_V0_ADDRESS: HashString = "0xD9DF9C4C0160401702de1771AAAaD886e2375F65"; // Placeholder - update after deployment export const CONVERT_UP_BLUEPRINT_V0_ADDRESS: HashString = "0x5167Ae1fF37bE08D9cc9188C7e64DB228B6F06ca"; From a36784ddf382b7b406addac6e010d5becb1c242f Mon Sep 17 00:00:00 2001 From: feyyazcigim Date: Thu, 15 Jan 2026 23:24:23 +0300 Subject: [PATCH 90/99] refactor(referral): streamline leaderboard data and refine UX --- src/components/ReferralLeaderboard.tsx | 3 --- src/components/ReferralStatsCard.tsx | 9 +++++--- .../Tractor/form/SowOrderV0Fields.tsx | 6 ++--- src/generated/gql/pintostalk/gql.ts | 6 ++--- src/generated/gql/pintostalk/graphql.ts | 2 +- src/pages/Referral.tsx | 8 +++---- src/pages/field/actions/Sow.tsx | 23 ++++++++++--------- .../referral/ReferralLeaderboard.graphql | 2 +- src/state/referral/useReferralLeaderboard.ts | 13 ++++++----- 9 files changed, 37 insertions(+), 35 deletions(-) diff --git a/src/components/ReferralLeaderboard.tsx b/src/components/ReferralLeaderboard.tsx index 9eba3c651..a03052ac7 100644 --- a/src/components/ReferralLeaderboard.tsx +++ b/src/components/ReferralLeaderboard.tsx @@ -105,7 +105,6 @@ export default function ReferralLeaderboard() {
      Referral Leaderboard
      -

      Top referrers ranked by Pods earned

      @@ -152,7 +151,6 @@ export default function ReferralLeaderboard() {
      Referral Leaderboard
      -

      Top referrers ranked by Pods earned

      @@ -187,7 +185,6 @@ export default function ReferralLeaderboard() {
      Referral Leaderboard
      {(isLoading || isRetrying) && } -

      Top referrers ranked by Pods earned

      {error && allData.length > 0 && (
      diff --git a/src/components/ReferralStatsCard.tsx b/src/components/ReferralStatsCard.tsx index ce85420b0..3fb5ee70f 100644 --- a/src/components/ReferralStatsCard.tsx +++ b/src/components/ReferralStatsCard.tsx @@ -4,9 +4,10 @@ import { formatter } from "@/utils/format"; export function ReferralStatsCard() { const { data, isLoading } = useFarmerReferralData(); - const { data: leaderboardData, userRank, isLoading: isLeaderboardLoading } = useReferralLeaderboard(); + const { userRank, isLoading: isLeaderboardLoading } = useReferralLeaderboard(); - if (isLoading || isLeaderboardLoading) { + // Only show loading on initial load (when there's no data yet) + if ((isLoading && !data) || (isLeaderboardLoading && !userRank)) { return (
      Your Referral Stats
      @@ -17,6 +18,8 @@ export function ReferralStatsCard() { ); } + const hasEarnedPods = data && data.totalPodsEarned.gt(0); + const statsData = [ { label: "Total Pods Earned", @@ -30,7 +33,7 @@ export function ReferralStatsCard() { }, { label: "Referral Ranking", - value: userRank && leaderboardData ? `#${userRank}/${leaderboardData.length}` : "-", + value: hasEarnedPods && userRank ? `#${userRank}` : "#N/A", description: "Your rank among all referrers", }, ]; diff --git a/src/components/Tractor/form/SowOrderV0Fields.tsx b/src/components/Tractor/form/SowOrderV0Fields.tsx index af7431548..5262f7cad 100644 --- a/src/components/Tractor/form/SowOrderV0Fields.tsx +++ b/src/components/Tractor/form/SowOrderV0Fields.tsx @@ -426,7 +426,7 @@ SowOrderV0Fields.Temperature = function Temperature() { }, [maxTemperature, temperature.max]); const minTemp = useMemo(() => Math.max(0, currentTempValue - 100), [currentTempValue]); - const maxTemp = useMemo(() => currentTempValue + 100, [currentTempValue]); + const maxTemp = useMemo(() => currentTempValue + 300, [currentTempValue]); // Set default value to current temperature only on initial mount useEffect(() => { @@ -456,7 +456,7 @@ SowOrderV0Fields.Temperature = function Temperature() { outlined {...handlers} isError={!!fieldState.error} - containerClassName="w-full max-w-[30rem]" + containerClassName="w-full max-w-[8rem]" endIcon={
      %
      } /> @@ -568,7 +568,7 @@ SowOrderV0Fields.PodDisplay = function PodDisplay({ onClick={onOpenReferralPopover} className="pinto-sm-light text-pinto-green-4 underline cursor-pointer hover:text-pinto-green-3" > - Use a referral code and gain 10% more pods! + Have a referral code? )} diff --git a/src/generated/gql/pintostalk/gql.ts b/src/generated/gql/pintostalk/gql.ts index bd81f7224..508c651f8 100644 --- a/src/generated/gql/pintostalk/gql.ts +++ b/src/generated/gql/pintostalk/gql.ts @@ -30,7 +30,7 @@ type Documents = { "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, "fragment PodOrder on PodOrder {\n id\n farmer {\n id\n }\n historyID\n pricingType\n pricePerPod\n pricingFunction\n maxPlaceInLine\n minFillAmount\n beanAmount\n podAmountFilled\n beanAmountFilled\n status\n createdAt\n updatedAt\n creationHash\n}": typeof types.PodOrderFragmentDoc, "query FarmerReferral($id: ID!) {\n farmer(id: $id) {\n id\n totalReferralRewardPodsReceived\n refereeCount\n }\n}": typeof types.FarmerReferralDocument, - "query ReferralLeaderboard($first: Int!, $skip: Int!, $block: Block_height) {\n farmers(\n first: $first\n skip: $skip\n orderBy: totalReferralRewardPodsReceived\n orderDirection: desc\n where: {refereeCount_gte: 0}\n block: $block\n ) {\n id\n refereeCount\n totalReferralRewardPodsReceived\n }\n}": typeof types.ReferralLeaderboardDocument, + "query ReferralLeaderboard($first: Int!, $skip: Int!, $block: Block_height) {\n farmers(\n first: $first\n skip: $skip\n orderBy: totalReferralRewardPodsReceived\n orderDirection: desc\n where: {totalReferralRewardPodsReceived_gt: \"0\"}\n block: $block\n ) {\n id\n refereeCount\n totalReferralRewardPodsReceived\n }\n}": typeof types.ReferralLeaderboardDocument, "query FarmerSeasonalSilo($from: Int, $to: Int, $account: String) {\n siloHourlySnapshots(\n where: {silo: $account, season_gte: $from, season_lte: $to}\n first: 1000\n orderBy: season\n orderDirection: asc\n ) {\n id\n season\n createdAt\n plantedBeans\n stalk\n germinatingStalk\n depositedBDV\n }\n}": typeof types.FarmerSeasonalSiloDocument, "query FarmerSeasonalSiloAssetToken($from: Int, $to: Int, $siloAsset: String) {\n siloAssetHourlySnapshots(\n where: {siloAsset: $siloAsset, season_gte: $from, season_lte: $to}\n first: 1000\n orderBy: season\n orderDirection: asc\n ) {\n id\n season\n depositedAmount\n depositedBDV\n deltaDepositedBDV\n deltaDepositedAmount\n createdAt\n }\n}": typeof types.FarmerSeasonalSiloAssetTokenDocument, "query BeanstalkSeasonalSiloActiveFarmers($from: Int, $to: Int, $silo: String) {\n siloHourlySnapshots(\n where: {season_gte: $from, season_lte: $to, silo: $silo, stalk_gt: 0}\n first: 1000\n orderBy: season\n orderDirection: desc\n ) {\n id\n season\n activeFarmers\n }\n}": typeof types.BeanstalkSeasonalSiloActiveFarmersDocument, @@ -58,7 +58,7 @@ const documents: Documents = { "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, "fragment PodOrder on PodOrder {\n id\n farmer {\n id\n }\n historyID\n pricingType\n pricePerPod\n pricingFunction\n maxPlaceInLine\n minFillAmount\n beanAmount\n podAmountFilled\n beanAmountFilled\n status\n createdAt\n updatedAt\n creationHash\n}": types.PodOrderFragmentDoc, "query FarmerReferral($id: ID!) {\n farmer(id: $id) {\n id\n totalReferralRewardPodsReceived\n refereeCount\n }\n}": types.FarmerReferralDocument, - "query ReferralLeaderboard($first: Int!, $skip: Int!, $block: Block_height) {\n farmers(\n first: $first\n skip: $skip\n orderBy: totalReferralRewardPodsReceived\n orderDirection: desc\n where: {refereeCount_gte: 0}\n block: $block\n ) {\n id\n refereeCount\n totalReferralRewardPodsReceived\n }\n}": types.ReferralLeaderboardDocument, + "query ReferralLeaderboard($first: Int!, $skip: Int!, $block: Block_height) {\n farmers(\n first: $first\n skip: $skip\n orderBy: totalReferralRewardPodsReceived\n orderDirection: desc\n where: {totalReferralRewardPodsReceived_gt: \"0\"}\n block: $block\n ) {\n id\n refereeCount\n totalReferralRewardPodsReceived\n }\n}": types.ReferralLeaderboardDocument, "query FarmerSeasonalSilo($from: Int, $to: Int, $account: String) {\n siloHourlySnapshots(\n where: {silo: $account, season_gte: $from, season_lte: $to}\n first: 1000\n orderBy: season\n orderDirection: asc\n ) {\n id\n season\n createdAt\n plantedBeans\n stalk\n germinatingStalk\n depositedBDV\n }\n}": types.FarmerSeasonalSiloDocument, "query FarmerSeasonalSiloAssetToken($from: Int, $to: Int, $siloAsset: String) {\n siloAssetHourlySnapshots(\n where: {siloAsset: $siloAsset, season_gte: $from, season_lte: $to}\n first: 1000\n orderBy: season\n orderDirection: asc\n ) {\n id\n season\n depositedAmount\n depositedBDV\n deltaDepositedBDV\n deltaDepositedAmount\n createdAt\n }\n}": types.FarmerSeasonalSiloAssetTokenDocument, "query BeanstalkSeasonalSiloActiveFarmers($from: Int, $to: Int, $silo: String) {\n siloHourlySnapshots(\n where: {season_gte: $from, season_lte: $to, silo: $silo, stalk_gt: 0}\n first: 1000\n orderBy: season\n orderDirection: desc\n ) {\n id\n season\n activeFarmers\n }\n}": types.BeanstalkSeasonalSiloActiveFarmersDocument, @@ -151,7 +151,7 @@ export function graphql(source: "query FarmerReferral($id: ID!) {\n farmer(id: /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "query ReferralLeaderboard($first: Int!, $skip: Int!, $block: Block_height) {\n farmers(\n first: $first\n skip: $skip\n orderBy: totalReferralRewardPodsReceived\n orderDirection: desc\n where: {refereeCount_gte: 0}\n block: $block\n ) {\n id\n refereeCount\n totalReferralRewardPodsReceived\n }\n}"): (typeof documents)["query ReferralLeaderboard($first: Int!, $skip: Int!, $block: Block_height) {\n farmers(\n first: $first\n skip: $skip\n orderBy: totalReferralRewardPodsReceived\n orderDirection: desc\n where: {refereeCount_gte: 0}\n block: $block\n ) {\n id\n refereeCount\n totalReferralRewardPodsReceived\n }\n}"]; +export function graphql(source: "query ReferralLeaderboard($first: Int!, $skip: Int!, $block: Block_height) {\n farmers(\n first: $first\n skip: $skip\n orderBy: totalReferralRewardPodsReceived\n orderDirection: desc\n where: {totalReferralRewardPodsReceived_gt: \"0\"}\n block: $block\n ) {\n id\n refereeCount\n totalReferralRewardPodsReceived\n }\n}"): (typeof documents)["query ReferralLeaderboard($first: Int!, $skip: Int!, $block: Block_height) {\n farmers(\n first: $first\n skip: $skip\n orderBy: totalReferralRewardPodsReceived\n orderDirection: desc\n where: {totalReferralRewardPodsReceived_gt: \"0\"}\n block: $block\n ) {\n id\n refereeCount\n totalReferralRewardPodsReceived\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 fbf6b32ae..78aa7d75a 100644 --- a/src/generated/gql/pintostalk/graphql.ts +++ b/src/generated/gql/pintostalk/graphql.ts @@ -14244,7 +14244,7 @@ export const AllPodListingsDocument = {"kind":"Document","definitions":[{"kind": export const AllPodOrdersDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"AllPodOrders"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"first"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}},"defaultValue":{"kind":"IntValue","value":"1000"}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"status"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"MarketStatus"}},"defaultValue":{"kind":"EnumValue","value":"ACTIVE"}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"skip"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}},"defaultValue":{"kind":"IntValue","value":"0"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"podOrders"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"first"},"value":{"kind":"Variable","name":{"kind":"Name","value":"first"}}},{"kind":"Argument","name":{"kind":"Name","value":"skip"},"value":{"kind":"Variable","name":{"kind":"Name","value":"skip"}}},{"kind":"Argument","name":{"kind":"Name","value":"orderBy"},"value":{"kind":"EnumValue","value":"createdAt"}},{"kind":"Argument","name":{"kind":"Name","value":"orderDirection"},"value":{"kind":"EnumValue","value":"desc"}},{"kind":"Argument","name":{"kind":"Name","value":"where"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"status"},"value":{"kind":"Variable","name":{"kind":"Name","value":"status"}}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"PodOrder"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PodOrder"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"PodOrder"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"farmer"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}},{"kind":"Field","name":{"kind":"Name","value":"historyID"}},{"kind":"Field","name":{"kind":"Name","value":"pricingType"}},{"kind":"Field","name":{"kind":"Name","value":"pricePerPod"}},{"kind":"Field","name":{"kind":"Name","value":"pricingFunction"}},{"kind":"Field","name":{"kind":"Name","value":"maxPlaceInLine"}},{"kind":"Field","name":{"kind":"Name","value":"minFillAmount"}},{"kind":"Field","name":{"kind":"Name","value":"beanAmount"}},{"kind":"Field","name":{"kind":"Name","value":"podAmountFilled"}},{"kind":"Field","name":{"kind":"Name","value":"beanAmountFilled"}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"creationHash"}}]}}]} as unknown as DocumentNode; export const FarmerMarketActivityDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"FarmerMarketActivity"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"first"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}},"defaultValue":{"kind":"IntValue","value":"1000"}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"account"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"listings_createdAt_gt"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"BigInt"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"orders_createdAt_gt"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"BigInt"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"fill_createdAt_gt"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"BigInt"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"podListings"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"first"},"value":{"kind":"Variable","name":{"kind":"Name","value":"first"}}},{"kind":"Argument","name":{"kind":"Name","value":"where"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"farmer"},"value":{"kind":"Variable","name":{"kind":"Name","value":"account"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"createdAt_gt"},"value":{"kind":"Variable","name":{"kind":"Name","value":"listings_createdAt_gt"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"status_not"},"value":{"kind":"EnumValue","value":"FILLED_PARTIAL"}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"PodListing"}}]}},{"kind":"Field","name":{"kind":"Name","value":"podOrders"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"first"},"value":{"kind":"Variable","name":{"kind":"Name","value":"first"}}},{"kind":"Argument","name":{"kind":"Name","value":"orderBy"},"value":{"kind":"EnumValue","value":"createdAt"}},{"kind":"Argument","name":{"kind":"Name","value":"orderDirection"},"value":{"kind":"EnumValue","value":"desc"}},{"kind":"Argument","name":{"kind":"Name","value":"where"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"farmer"},"value":{"kind":"Variable","name":{"kind":"Name","value":"account"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"createdAt_gt"},"value":{"kind":"Variable","name":{"kind":"Name","value":"orders_createdAt_gt"}}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"PodOrder"}}]}},{"kind":"Field","name":{"kind":"Name","value":"podFills"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"first"},"value":{"kind":"Variable","name":{"kind":"Name","value":"first"}}},{"kind":"Argument","name":{"kind":"Name","value":"where"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"and"},"value":{"kind":"ListValue","values":[{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"createdAt_gt"},"value":{"kind":"Variable","name":{"kind":"Name","value":"fill_createdAt_gt"}}}]},{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"or"},"value":{"kind":"ListValue","values":[{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"fromFarmer"},"value":{"kind":"Variable","name":{"kind":"Name","value":"account"}}}]},{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"toFarmer"},"value":{"kind":"Variable","name":{"kind":"Name","value":"account"}}}]}]}}]}]}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"PodFill"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PodListing"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"PodListing"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"farmer"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}},{"kind":"Field","name":{"kind":"Name","value":"historyID"}},{"kind":"Field","name":{"kind":"Name","value":"index"}},{"kind":"Field","name":{"kind":"Name","value":"start"}},{"kind":"Field","name":{"kind":"Name","value":"mode"}},{"kind":"Field","name":{"kind":"Name","value":"pricingType"}},{"kind":"Field","name":{"kind":"Name","value":"pricePerPod"}},{"kind":"Field","name":{"kind":"Name","value":"pricingFunction"}},{"kind":"Field","name":{"kind":"Name","value":"maxHarvestableIndex"}},{"kind":"Field","name":{"kind":"Name","value":"minFillAmount"}},{"kind":"Field","name":{"kind":"Name","value":"originalIndex"}},{"kind":"Field","name":{"kind":"Name","value":"originalPlaceInLine"}},{"kind":"Field","name":{"kind":"Name","value":"originalAmount"}},{"kind":"Field","name":{"kind":"Name","value":"filled"}},{"kind":"Field","name":{"kind":"Name","value":"amount"}},{"kind":"Field","name":{"kind":"Name","value":"remainingAmount"}},{"kind":"Field","name":{"kind":"Name","value":"filledAmount"}},{"kind":"Field","name":{"kind":"Name","value":"fill"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"placeInLine"}}]}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"creationHash"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PodOrder"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"PodOrder"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"farmer"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}},{"kind":"Field","name":{"kind":"Name","value":"historyID"}},{"kind":"Field","name":{"kind":"Name","value":"pricingType"}},{"kind":"Field","name":{"kind":"Name","value":"pricePerPod"}},{"kind":"Field","name":{"kind":"Name","value":"pricingFunction"}},{"kind":"Field","name":{"kind":"Name","value":"maxPlaceInLine"}},{"kind":"Field","name":{"kind":"Name","value":"minFillAmount"}},{"kind":"Field","name":{"kind":"Name","value":"beanAmount"}},{"kind":"Field","name":{"kind":"Name","value":"podAmountFilled"}},{"kind":"Field","name":{"kind":"Name","value":"beanAmountFilled"}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"creationHash"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PodFill"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"PodFill"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"placeInLine"}},{"kind":"Field","name":{"kind":"Name","value":"amount"}},{"kind":"Field","name":{"kind":"Name","value":"index"}},{"kind":"Field","name":{"kind":"Name","value":"start"}},{"kind":"Field","name":{"kind":"Name","value":"costInBeans"}},{"kind":"Field","name":{"kind":"Name","value":"fromFarmer"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}},{"kind":"Field","name":{"kind":"Name","value":"toFarmer"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}},{"kind":"Field","name":{"kind":"Name","value":"listing"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"originalAmount"}}]}},{"kind":"Field","name":{"kind":"Name","value":"order"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"beanAmount"}}]}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}}]} as unknown as DocumentNode; 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":"refereeCount_gte"},"value":{"kind":"IntValue","value":"0"}}]}},{"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; +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; export const FarmerSeasonalSiloDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"FarmerSeasonalSilo"},"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"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"account"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"siloHourlySnapshots"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"where"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"silo"},"value":{"kind":"Variable","name":{"kind":"Name","value":"account"}}},{"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"}}}]}},{"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":"asc"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"season"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"plantedBeans"}},{"kind":"Field","name":{"kind":"Name","value":"stalk"}},{"kind":"Field","name":{"kind":"Name","value":"germinatingStalk"}},{"kind":"Field","name":{"kind":"Name","value":"depositedBDV"}}]}}]}}]} as unknown as DocumentNode; export const FarmerSeasonalSiloAssetTokenDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"FarmerSeasonalSiloAssetToken"},"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"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"siloAsset"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"siloAssetHourlySnapshots"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"where"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"siloAsset"},"value":{"kind":"Variable","name":{"kind":"Name","value":"siloAsset"}}},{"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"}}}]}},{"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":"asc"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"season"}},{"kind":"Field","name":{"kind":"Name","value":"depositedAmount"}},{"kind":"Field","name":{"kind":"Name","value":"depositedBDV"}},{"kind":"Field","name":{"kind":"Name","value":"deltaDepositedBDV"}},{"kind":"Field","name":{"kind":"Name","value":"deltaDepositedAmount"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}}]}}]} as unknown as DocumentNode; export const BeanstalkSeasonalSiloActiveFarmersDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"BeanstalkSeasonalSiloActiveFarmers"},"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"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"silo"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"siloHourlySnapshots"},"arguments":[{"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"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"silo"},"value":{"kind":"Variable","name":{"kind":"Name","value":"silo"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"stalk_gt"},"value":{"kind":"IntValue","value":"0"}}]}},{"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"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"season"}},{"kind":"Field","name":{"kind":"Name","value":"activeFarmers"}}]}}]}}]} as unknown as DocumentNode; diff --git a/src/pages/Referral.tsx b/src/pages/Referral.tsx index a5d68e322..873d80226 100644 --- a/src/pages/Referral.tsx +++ b/src/pages/Referral.tsx @@ -27,10 +27,10 @@ export default function Referral() { {/* Main Referral Cards - Two Column Layout */} -
      +
      {/* Invite via - with overlay pattern like Field page */} -
      - +
      + setIsDelegateModalOpen(true)} /> @@ -53,7 +53,7 @@ export default function Referral() {
      {/* How to */} - +
      diff --git a/src/pages/field/actions/Sow.tsx b/src/pages/field/actions/Sow.tsx index 67f162303..60166ea40 100644 --- a/src/pages/field/actions/Sow.tsx +++ b/src/pages/field/actions/Sow.tsx @@ -485,6 +485,17 @@ function Sow({ isMorning, onShowOrder }: SowProps) { disableClamping={true} /> + {!hasReferralCode && ( + + + + )}
      Use Silo Deposits
      @@ -569,23 +580,13 @@ function Sow({ isMorning, onShowOrder }: SowProps) {
      {!hasSoil && Your usable balance is 0.00 because there is no Soil available.} - {hasReferralCode && bonusPods.gt(0) ? ( + {hasReferralCode && bonusPods.gt(0) && (
      You gained 10% more Pods due to using a referral link!
      - ) : ( -
      - -
      )} {!tokenIn.isMain && swapSummary?.swap && ( diff --git a/src/queries/beanstalk/referral/ReferralLeaderboard.graphql b/src/queries/beanstalk/referral/ReferralLeaderboard.graphql index bb13969a0..de02752fd 100644 --- a/src/queries/beanstalk/referral/ReferralLeaderboard.graphql +++ b/src/queries/beanstalk/referral/ReferralLeaderboard.graphql @@ -4,7 +4,7 @@ query ReferralLeaderboard($first: Int!, $skip: Int!, $block: Block_height) { skip: $skip orderBy: totalReferralRewardPodsReceived orderDirection: desc - where: { refereeCount_gte: 0 } + where: { totalReferralRewardPodsReceived_gt: "0" } block: $block ) { id diff --git a/src/state/referral/useReferralLeaderboard.ts b/src/state/referral/useReferralLeaderboard.ts index 0a59d793f..9be3854a7 100644 --- a/src/state/referral/useReferralLeaderboard.ts +++ b/src/state/referral/useReferralLeaderboard.ts @@ -91,14 +91,14 @@ export interface UseReferralLeaderboardReturn { export function useReferralLeaderboard(): UseReferralLeaderboardReturn { const chainId = useChainId(); const { address: userAddress } = useAccount(); - const { data: latestBlock, isLoading: isBlockLoading } = useLatestBlock(); + const { data: latestBlock } = useLatestBlock(); - // Create stable query key that includes block height when available + // Use block height for pagination consistency but not in query key + // This prevents unnecessary refetches while maintaining data consistency const blockHeight = latestBlock?.number ? Number(latestBlock.number) : null; - const queryKey = ["referralLeaderboard", chainId.toString(), blockHeight?.toString() || "latest"]; const query = useQuery({ - queryKey, + queryKey: ["referralLeaderboard", chainId.toString()], queryFn: async () => { const initialVars: FarmersLeaderboardVariables = { first: 1000, @@ -119,9 +119,10 @@ export function useReferralLeaderboard(): UseReferralLeaderboardReturn { ); }, select: selectLeaderboardEntries, - enabled: !!chainId && !isBlockLoading, + enabled: !!chainId, staleTime: 5 * 60 * 1000, // 5 minutes gcTime: 10 * 60 * 1000, // 10 minutes + refetchInterval: 5 * 60 * 1000, // Refetch every 5 minutes }); // Calculate user's rank in the leaderboard @@ -150,7 +151,7 @@ export function useReferralLeaderboard(): UseReferralLeaderboardReturn { return { data: query.data || [], - isLoading: query.isLoading || isBlockLoading, + isLoading: query.isLoading, error: query.error, refetch: query.refetch, userRank, From ec14adbaef3bbe8b22fe53ac7b3795e0784f5f68 Mon Sep 17 00:00:00 2001 From: fr1j0 Date: Thu, 15 Jan 2026 16:05:22 -0500 Subject: [PATCH 91/99] Optimize deposit sorting and combining for blueprint publishing - Skip sortDeposits if deposits are already sorted on-chain - Check getSortedDeposits and compare with actual deposit order - Only add sortDeposits call when stems don't match or have combine calls - Fix single-deposit groups from being "combined" (filter > 1 instead of > 0) - Skip sortDeposits entirely for accounts with only germinating deposits This optimization saves gas by avoiding unnecessary sortDeposits calls when deposits are already in the correct order on-chain. Co-Authored-By: Claude Sonnet 4.5 --- src/lib/claim/depositUtils.ts | 47 +++++++++++++++++++++++++++++------ 1 file changed, 40 insertions(+), 7 deletions(-) diff --git a/src/lib/claim/depositUtils.ts b/src/lib/claim/depositUtils.ts index 068ef81cb..c6080e1a1 100644 --- a/src/lib/claim/depositUtils.ts +++ b/src/lib/claim/depositUtils.ts @@ -90,7 +90,7 @@ export function generateCombineAndL2LCallData(farmerDeposits: Map depositData.deposits.length >= PROCESS_SINGLE_TOKEN_ONLY_THRESHOLD, ); - + if (highVolumeToken) { console.debug("Processing single high-volume token:", { name: highVolumeToken[0].name, @@ -467,9 +467,40 @@ export async function generateBatchSortDepositsCallData( } if (!USE_SIMULATION_METHOD) { - // Add a pipe call with a call to sortDeposits - const sortDepositsCall = encodeSortDepositsCall(account); - callData.push(sortDepositsCall); + // Check if deposits need sorting by comparing current order + const hasNonGerminatingDeposits = Array.from(farmerDeposits.values()).some((data) => + data.deposits.some((d) => !d.isGerminating), + ); + + let needsSorting = false; + + // Only check if we have combine calls or non-germinating deposits + if (callData.length > 0 || hasNonGerminatingDeposits) { + // Check if any token's deposits are not already sorted in descending order + tokenLoop: for (const [token, depositData] of farmerDeposits.entries()) { + const nonGerminatingDeposits = depositData.deposits.filter((d) => !d.isGerminating); + + if (nonGerminatingDeposits.length === 0) continue; + + // Get stems and check if they're already in descending order + const stems = nonGerminatingDeposits.map((d) => d.stem.toBigInt()); + + // Check if stems are sorted descending - break at first unsorted pair + for (let i = 1; i < stems.length; i++) { + if (stems[i] > stems[i - 1]) { + // Not in descending order + needsSorting = true; + break tokenLoop; + } + } + } + } + + if (needsSorting) { + // Add a pipe call with a call to sortDeposits + const sortDepositsCall = encodeSortDepositsCall(account); + callData.push(sortDepositsCall); + } } console.debug(`Generated ${callData.length} total calls (combines + sort deposits)`); @@ -493,7 +524,9 @@ export function createSmartGroups(deposits: DepositData[], targetGroups: number // Only slice if we have more than MAX_DEPOSITS const slicedDeposits = validDeposits.length > MAX_DEPOSITS ? validDeposits.slice(-MAX_DEPOSITS) : validDeposits; - if (slicedDeposits.length === 0) return []; + if (slicedDeposits.length === 0) { + return []; + } // Calculate ratio differences between adjacent deposits const ratioDiffs = slicedDeposits.slice(1).map((deposit, i) => ({ @@ -548,8 +581,8 @@ export function encodeGroupCombineCalls( token: Token, deposits: DepositData[], ): `0x${string}`[] { - // Exclude groups with only one deposit, since they are already alone - const groupsToEncode = validGroups.filter((group) => group.deposits.length > 0); + // Exclude groups with only one deposit, since they don't need combining + const groupsToEncode = validGroups.filter((group) => group.deposits.length > 1); return groupsToEncode.map((group) => { // Get selected deposits for this group From 173f2820000b8a0d25a5daf693df3b1a28c0ed35 Mon Sep 17 00:00:00 2001 From: fr1j0 Date: Thu, 15 Jan 2026 16:36:16 -0500 Subject: [PATCH 92/99] Optimize deposit sorting with stable sort and early termination --- src/lib/Tractor/sowOrder/tractor-sow.ts | 203 ++++++++++++------------ src/lib/claim/depositUtils.ts | 9 +- 2 files changed, 105 insertions(+), 107 deletions(-) diff --git a/src/lib/Tractor/sowOrder/tractor-sow.ts b/src/lib/Tractor/sowOrder/tractor-sow.ts index ddd9ea7c3..8ca12a31e 100644 --- a/src/lib/Tractor/sowOrder/tractor-sow.ts +++ b/src/lib/Tractor/sowOrder/tractor-sow.ts @@ -7,7 +7,9 @@ import { diamondABI } from "@/constants/abi/diamondABI"; import { SILO_HELPERS_ADDRESS, SOW_BLUEPRINT_REFERRAL_V0_ADDRESS, + SOW_BLUEPRINT_REFERRAL_V0_SELECTOR, SOW_BLUEPRINT_V0_ADDRESS, + SOW_BLUEPRINT_V0_SELECTOR, TRACTOR_HELPERS_ADDRESS, } from "@/constants/address"; import { TIME_TO_BLOCKS } from "@/constants/blocks"; @@ -85,12 +87,12 @@ export async function createSowTractorData({ referralAddress?: `0x${string}`; // Optional referral address }): Promise { // Add more detailed debug logs - console.debug("tokenStrategy received:", tokenStrategy); - console.debug("tokenStrategy.type:", tokenStrategy.type); - console.debug( - "tokenStrategy.address:", - tokenStrategy.type === "SPECIFIC_TOKEN" ? tokenStrategy.addresses?.[0] : "N/A", - ); + // console.debug("tokenStrategy received:", tokenStrategy); + // console.debug("tokenStrategy.type:", tokenStrategy.type); + // console.debug( + // "tokenStrategy.address:", + // tokenStrategy.type === "SPECIFIC_TOKEN" ? tokenStrategy.addresses?.[0] : "N/A", + // ); // Convert inputs to appropriate types const totalAmount = sanitizeNumericInputValue(totalAmountToSow, 6).tv.toBigInt(); @@ -108,13 +110,13 @@ export async function createSowTractorData({ // Get source token indices based on strategy let sourceTokenIndices: number[]; if (tokenStrategy.type === "LOWEST_SEEDS") { - console.debug("Using LOWEST_SEEDS strategy"); + // console.debug("Using LOWEST_SEEDS strategy"); sourceTokenIndices = [255]; } else if (tokenStrategy.type === "LOWEST_PRICE") { - console.debug("Using LOWEST_PRICE strategy"); + // console.debug("Using LOWEST_PRICE strategy"); sourceTokenIndices = [254]; } else if (tokenStrategy.type === "SPECIFIC_TOKEN") { - console.debug("Using SPECIFIC_TOKEN strategy with address:", tokenStrategy.addresses?.[0]); + // console.debug("Using SPECIFIC_TOKEN strategy with address:", tokenStrategy.addresses?.[0]); const indexes = await getTokenIndexesFromTractorTokenStrategy(publicClient, tokenStrategy); console.debug("Got token index:", indexes); sourceTokenIndices = indexes; @@ -124,7 +126,7 @@ export async function createSowTractorData({ } // Log the final indices - console.debug("Final sourceTokenIndices:", sourceTokenIndices); + // console.debug("Final sourceTokenIndices:", sourceTokenIndices); // Create the SowBlueprintStruct const sowBlueprintStruct = { @@ -148,25 +150,25 @@ export async function createSowTractorData({ }, }; - console.debug("Struct before encoding:", { - sowParams: { - sourceTokenIndices: sowBlueprintStruct.sowParams.sourceTokenIndices, - sowAmounts: { - totalAmountToSow: sowBlueprintStruct.sowParams.sowAmounts.totalAmountToSow.toString(), - minAmountToSowPerSeason: sowBlueprintStruct.sowParams.sowAmounts.minAmountToSowPerSeason.toString(), - maxAmountToSowPerSeason: sowBlueprintStruct.sowParams.sowAmounts.maxAmountToSowPerSeason.toString(), - }, - minTemp: sowBlueprintStruct.sowParams.minTemp.toString(), - maxPodlineLength: sowBlueprintStruct.sowParams.maxPodlineLength.toString(), - maxGrownStalkPerBdv: sowBlueprintStruct.sowParams.maxGrownStalkPerBdv.toString(), - runBlocksAfterSunrise: sowBlueprintStruct.sowParams.runBlocksAfterSunrise.toString(), - }, - opParams: { - whitelistedOperators: sowBlueprintStruct.opParams.whitelistedOperators, - tipAddress: sowBlueprintStruct.opParams.tipAddress, - operatorTipAmount: sowBlueprintStruct.opParams.operatorTipAmount.toString(), - }, - }); + // console.debug("Struct before encoding:", { + // sowParams: { + // sourceTokenIndices: sowBlueprintStruct.sowParams.sourceTokenIndices, + // sowAmounts: { + // totalAmountToSow: sowBlueprintStruct.sowParams.sowAmounts.totalAmountToSow.toString(), + // minAmountToSowPerSeason: sowBlueprintStruct.sowParams.sowAmounts.minAmountToSowPerSeason.toString(), + // maxAmountToSowPerSeason: sowBlueprintStruct.sowParams.sowAmounts.maxAmountToSowPerSeason.toString(), + // }, + // minTemp: sowBlueprintStruct.sowParams.minTemp.toString(), + // maxPodlineLength: sowBlueprintStruct.sowParams.maxPodlineLength.toString(), + // maxGrownStalkPerBdv: sowBlueprintStruct.sowParams.maxGrownStalkPerBdv.toString(), + // runBlocksAfterSunrise: sowBlueprintStruct.sowParams.runBlocksAfterSunrise.toString(), + // }, + // opParams: { + // whitelistedOperators: sowBlueprintStruct.opParams.whitelistedOperators, + // tipAddress: sowBlueprintStruct.opParams.tipAddress, + // operatorTipAmount: sowBlueprintStruct.opParams.operatorTipAmount.toString(), + // }, + // }); // Encode the blueprint call - use referral version if referralAddress is provided const blueprintAddress = referralAddress ? SOW_BLUEPRINT_REFERRAL_V0_ADDRESS : SOW_BLUEPRINT_V0_ADDRESS; @@ -188,11 +190,11 @@ export async function createSowTractorData({ args: [sowBlueprintStruct], }); - console.debug("Using blueprint:", referralAddress ? "sowBlueprintReferral" : "sowBlueprintv0"); - console.debug("Blueprint address:", blueprintAddress); - if (referralAddress) { - console.debug("Referral address:", referralAddress); - } + // console.debug("Using blueprint:", referralAddress ? "sowBlueprintReferral" : "sowBlueprintv0"); + // console.debug("Blueprint address:", blueprintAddress); + // if (referralAddress) { + // console.debug("Referral address:", referralAddress); + // } // Step 1: Wrap the blueprint call in an advancedPipe call const pipeCall = encodeFunctionData({ @@ -227,7 +229,7 @@ export async function createSowTractorData({ let depositOptimizationCalls: `0x${string}`[] | undefined; if (farmerDeposits && userAddress && protocolAddress) { - console.debug("Generating deposit optimization calls for user transaction"); + // console.debug("Generating deposit optimization calls for user transaction"); try { depositOptimizationCalls = await generateBatchSortDepositsCallData( @@ -237,16 +239,16 @@ export async function createSowTractorData({ protocolAddress, ); - console.debug(`Generated ${depositOptimizationCalls.length} deposit optimization calls for user transaction`); + // console.debug(`Generated ${depositOptimizationCalls.length} deposit optimization calls for user transaction`); } catch (error) { console.warn("Failed to generate deposit optimization calls:", error); // Continue without optimization calls - don't fail the entire transaction } } - console.debug("Raw sowBlueprintv0 call:", sowBlueprintCall); - console.debug("advancedPipe call:", pipeCall); - console.debug("Final blueprint data:", advFarmCall); + // console.debug("Raw sowBlueprintv0 call:", sowBlueprintCall); + // console.debug("advancedPipe call:", pipeCall); + // console.debug("Final blueprint data:", advFarmCall); return { data: advFarmCall, @@ -265,7 +267,7 @@ export function handleDecodeSowV0BlueprintFromAdvancedPipe( chainId: number, ): SowBlueprintData | null { if (!calls?.length) { - console.debug("[Tractor/handleDecodeBlueprintFromAdvancedPipe] No calls provided. Returning null."); + // console.debug("[Tractor/handleDecodeBlueprintFromAdvancedPipe] No calls provided. Returning null."); return null; } @@ -292,7 +294,7 @@ export function handleDecodeSowReferralBlueprintFromAdvancedPipe( chainId: number, ): { blueprintData: SowBlueprintData; referralAddress: `0x${string}` } | null { if (!calls?.length) { - console.debug("[Tractor/handleDecodeSowReferralBlueprintFromAdvancedPipe] No calls provided. Returning null."); + // console.debug("[Tractor/handleDecodeSowReferralBlueprintFromAdvancedPipe] No calls provided. Returning null."); return null; } @@ -334,7 +336,7 @@ export function handleDecodeSowReferralBlueprintFromAdvancedPipe( export function transformSowRequisitionEvent(params: unknown | null, chainId: number) { try { if (!shallowCheckIsSowParams(params)) { - console.debug("[Tractor/transformSowRequisitionEvent] Invalid parameters"); + // console.debug("[Tractor/transformSowRequisitionEvent] Invalid parameters"); return null; } @@ -387,22 +389,19 @@ export function decodeSowTractorData( const calls = decodeEncodedTractorDataToAdvancedPipeCalls(encodedData, "sowV0"); if (calls?.length) { - // First try to decode as referral blueprint - try { - const referralData = handleDecodeSowReferralBlueprintFromAdvancedPipe(calls, chainId); - if (referralData) { - console.debug("Decoded as referral blueprint"); - return referralData; - } - } catch (e) { - console.debug("Not a referral blueprint, trying v0:", e); - } + const callData = calls[0].callData; - // Fall back to v0 blueprint - const v0Data = handleDecodeSowV0BlueprintFromAdvancedPipe(calls, chainId); - if (v0Data) { - console.debug("Decoded as v0 blueprint"); - return v0Data; + // Check the function selector (first 4 bytes) to determine which ABI to use + const selector = callData.slice(0, 10) as `0x${string}`; + + if (selector === SOW_BLUEPRINT_REFERRAL_V0_SELECTOR) { + // Use referral blueprint ABI + return handleDecodeSowReferralBlueprintFromAdvancedPipe(calls, chainId); + } else if (selector === SOW_BLUEPRINT_V0_SELECTOR) { + // Use v0 blueprint ABI + return handleDecodeSowV0BlueprintFromAdvancedPipe(calls, chainId); + } else { + console.warn(`Unknown blueprint selector: ${selector}`); } } } catch (e) { @@ -446,7 +445,7 @@ export async function loadOrderbookData( let currentPodLine = TokenValue.ZERO; // Fetch SowOrderComplete events to identify completed orders - console.debug("[TRACTOR/loadOrderbookData] Fetching..."); + // console.debug("[TRACTOR/loadOrderbookData] Fetching..."); const [ podIndexResult, @@ -488,7 +487,7 @@ export async function loadOrderbookData( // Pod line is podIndex - harvestableIndex currentPodLine = TokenValue.fromBlockchain(podIndexResult - harvestableIndexResult, 6); } else { - console.error("[TRACTOR/loadOrderbookData] Failed to get current pod line"); + // console.error("[TRACTOR/loadOrderbookData] Failed to get current pod line"); // Continue with zero if we can't get the current pod line } @@ -540,18 +539,18 @@ export async function loadOrderbookData( // Sort requisitions by temperature requisitionsWithTemperature.sort((a, b) => Number(a.temperature - b.temperature)); - console.debug("[TRACTOR/loadOrderbookData] initial fetch results: ", { - raw: { - podIndexResult, - harvestableIndexResult, - completedOrders, - activeRequisitions, - requisitionsWithTemperature, - }, - currentPodLine: currentPodLine.toHuman(), - activeRequisitions: activeRequisitions?.length, - completedOrders: completedOrders.size, - }); + // console.debug("[TRACTOR/loadOrderbookData] initial fetch results: ", { + // raw: { + // podIndexResult, + // harvestableIndexResult, + // completedOrders, + // activeRequisitions, + // requisitionsWithTemperature, + // }, + // currentPodLine: currentPodLine.toHuman(), + // activeRequisitions: activeRequisitions?.length, + // completedOrders: completedOrders.size, + // }); // Track used withdrawal plans per publisher for allocation priority const publisherWithdrawalPlans: { [publisher: string]: any[] } = {}; @@ -562,17 +561,17 @@ export async function loadOrderbookData( return true; }); - console.debug("\nProcessing orderbook data:"); + // console.debug("\nProcessing orderbook data:"); for (let i = 0; i < requisitionsWithTemperature.length; i++) { const { requisition, decodedData, referralAddress } = requisitionsWithTemperature[i]; const publisher = requisition.requisition.blueprint.publisher; - console.debug(`\n--- Processing Order #${i + 1} ---`); - if (decodedData) { - console.debug(`Temperature: ${decodedData.minTempAsString}%`); - } - console.debug(`Publisher: ${publisher}`); + // console.debug(`\n--- Processing Order #${i + 1} ---`); + // if (decodedData) { + // console.debug(`Temperature: ${decodedData.minTempAsString}%`); + // } + // console.debug(`Publisher: ${publisher}`); try { // Determine which blueprint contract to query based on the blueprint address @@ -593,7 +592,7 @@ export async function loadOrderbookData( ? TokenValue.fromBlockchain(decodedData.sowAmounts.totalAmountToSow, 6) : TokenValue.fromBlockchain(pintosLeft, 6); - console.debug(`Pintos Left to Sow: ${finalPintosLeft.toHuman()}`); + // console.debug(`Pintos Left to Sow: ${finalPintosLeft.toHuman()}`); // Handle withdrawal plan calculation with temperature priority let withdrawalPlan: WithdrawalPlan | null = null; @@ -602,7 +601,7 @@ export async function loadOrderbookData( if (decodedData) { // Get existing withdrawal plans for this publisher const existingPlans = publisherWithdrawalPlans[publisher] || []; - console.debug("Existing plans for publisher:", existingPlans.length); + // console.debug("Existing plans for publisher:", existingPlans.length); let combinedExistingPlan = null; @@ -619,11 +618,11 @@ export async function loadOrderbookData( combinedExistingPlan = combinedPlan; - console.debug("Combined existing plans for publisher:", publisher); - console.debug( - "Total available PINTO in combined plan:", - TokenValue.fromBlockchain(combinedPlan.totalAvailableBeans, 6).toHuman(), - ); + // console.debug("Combined existing plans for publisher:", publisher); + // console.debug( + // "Total available PINTO in combined plan:", + // TokenValue.fromBlockchain(combinedPlan.totalAvailableBeans, 6).toHuman(), + // ); } catch (error) { console.error("Failed to combine withdrawal plans:", error); combinedExistingPlan = null; @@ -665,7 +664,7 @@ export async function loadOrderbookData( ], }); - console.debug("Got updated withdrawal plan excluding existing orders"); + // console.debug("Got updated withdrawal plan excluding existing orders"); totalAvailablePinto = TokenValue.fromBlockchain(withdrawalPlan.totalAvailableBeans, 6); // Add this plan to the list of existing plans for future orders @@ -679,7 +678,7 @@ export async function loadOrderbookData( // console.error("Failed to get updated withdrawal plan:", error); // If the error is "No beans available", set the plan to empty if (error instanceof Error && error.message?.includes("No beans available")) { - console.debug("No beans available for this order, setting available PINTO to 0"); + // console.debug("No beans available for this order, setting available PINTO to 0"); withdrawalPlan = { sourceTokens: [] as readonly `0x${string}`[], stems: [] as readonly (readonly bigint[])[], @@ -694,16 +693,16 @@ export async function loadOrderbookData( // Calculate how much PINTO this order can use const currentlySowable = TokenValue.min(finalPintosLeft, totalAvailablePinto); - console.debug(`Total available PINTO: ${totalAvailablePinto.toHuman()}`); - console.debug(`Currently sowable: ${currentlySowable.toHuman()}`); + // console.debug(`Total available PINTO: ${totalAvailablePinto.toHuman()}`); + // console.debug(`Currently sowable: ${currentlySowable.toHuman()}`); // Calculate amountSowableNextSeason as the greater of currentlySowable and minAmountToSowPerSeason let amountSowableNextSeason = currentlySowable; if (decodedData && decodedData.sowAmounts.maxAmountToSowPerSeason) { const maxAmountToSowPerSeason = TokenValue.fromBlockchain(decodedData.sowAmounts.maxAmountToSowPerSeason, 6); amountSowableNextSeason = TokenValue.min(currentlySowable, maxAmountToSowPerSeason); - console.debug(`Min amount to sow per season: ${maxAmountToSowPerSeason.toHuman()}`); - console.debug(`Amount sowable next season: ${amountSowableNextSeason.toHuman()}`); + // console.debug(`Min amount to sow per season: ${maxAmountToSowPerSeason.toHuman()}`); + // console.debug(`Amount sowable next season: ${amountSowableNextSeason.toHuman()}`); } if (!withdrawalPlan) { @@ -776,22 +775,22 @@ export async function loadOrderbookData( if (soilAmount) { availableSoil = TokenValue.fromBlockchain(soilAmount, 6); - console.debug(`\nCurrent soil from latest Soil event: ${availableSoil.toHuman()}`); + // console.debug(`\nCurrent soil from latest Soil event: ${availableSoil.toHuman()}`); } } else { - console.debug(`No Soil events found, falling back to estimation`); + // console.debug(`No Soil events found, falling back to estimation`); } // If we couldn't get soil from events, fall back to estimate if (availableSoil.eq(0)) { availableSoil = orderbookData.reduce((total, entry) => total.add(entry.currentlySowable), TokenValue.ZERO); - console.debug(`\nEstimated soil from orderbook data: ${availableSoil.toHuman()}`); + // console.debug(`\nEstimated soil from orderbook data: ${availableSoil.toHuman()}`); } } catch (error) { - console.error("Failed to get soil from on chain:", error); + // console.error("Failed to get soil from on chain:", error); // Fall back to estimating soil as the sum of all currentlySowable values availableSoil = orderbookData.reduce((total, entry) => total.add(entry.currentlySowable), TokenValue.ZERO); - console.debug(`\nFalling back to estimated soil: ${availableSoil.toHuman()}`); + // console.debug(`\nFalling back to estimated soil: ${availableSoil.toHuman()}`); } // Sort orderbook entries by operator tip amount (highest first) @@ -808,7 +807,7 @@ export async function loadOrderbookData( // Sort by tip amount (highest first) orderbookDataWithTips.sort((a, b) => Number(b.tipAmount - a.tipAmount)); - console.debug("\nAllocating available soil based on operator tip priority:"); + // console.debug("\nAllocating available soil based on operator tip priority:"); // Track remaining soil as we allocate it let remainingSoil = availableSoil; @@ -819,13 +818,13 @@ export async function loadOrderbookData( const tipAmountFormatted = TokenValue.fromBlockchain(tipAmount, 6).toHuman(); const orderTemp = decodedData ? parseFloat(decodedData.minTempAsString) : 0; - console.debug( - `Order #${i + 1} - Tip: ${tipAmountFormatted}, Temp: ${orderTemp}%, Requested: ${entry.amountSowableNextSeason.toHuman()}`, - ); + // console.debug( + // `Order #${i + 1} - Tip: ${tipAmountFormatted}, Temp: ${orderTemp}%, Requested: ${entry.amountSowableNextSeason.toHuman()}`, + // ); // Skip orders with temperature higher than the max temperature (if provided) if (maxTemperature !== undefined && orderTemp > maxTemperature) { - console.debug(` Temperature too high (max: ${maxTemperature}%), allocated: 0`); + // console.debug(` Temperature too high (max: ${maxTemperature}%), allocated: 0`); entry.amountSowableNextSeasonConsideringAvailableSoil = TokenValue.ZERO; continue; } @@ -833,7 +832,7 @@ export async function loadOrderbookData( // If no soil left, set to zero if (remainingSoil.lte(0)) { entry.amountSowableNextSeasonConsideringAvailableSoil = TokenValue.ZERO; - console.debug(` No soil remaining, allocated: 0`); + // console.debug(` No soil remaining, allocated: 0`); continue; } @@ -844,7 +843,7 @@ export async function loadOrderbookData( // Subtract from remaining soil remainingSoil = remainingSoil.sub(allocatedAmount); - console.debug(` Allocated: ${allocatedAmount.toHuman()}, Remaining soil: ${remainingSoil.toHuman()}`); + // console.debug(` Allocated: ${allocatedAmount.toHuman()}, Remaining soil: ${remainingSoil.toHuman()}`); } return orderbookData; diff --git a/src/lib/claim/depositUtils.ts b/src/lib/claim/depositUtils.ts index c6080e1a1..ee0e22d15 100644 --- a/src/lib/claim/depositUtils.ts +++ b/src/lib/claim/depositUtils.ts @@ -476,19 +476,18 @@ export async function generateBatchSortDepositsCallData( // Only check if we have combine calls or non-germinating deposits if (callData.length > 0 || hasNonGerminatingDeposits) { - // Check if any token's deposits are not already sorted in descending order + // Check if any token's deposits are not already sorted in ascending order tokenLoop: for (const [token, depositData] of farmerDeposits.entries()) { const nonGerminatingDeposits = depositData.deposits.filter((d) => !d.isGerminating); if (nonGerminatingDeposits.length === 0) continue; - // Get stems and check if they're already in descending order + // Get stems and check if they're already in ascending order const stems = nonGerminatingDeposits.map((d) => d.stem.toBigInt()); - // Check if stems are sorted descending - break at first unsorted pair + // Check if stems are sorted ascending - break at first unsorted pair for (let i = 1; i < stems.length; i++) { - if (stems[i] > stems[i - 1]) { - // Not in descending order + if (stems[i] < stems[i - 1]) { needsSorting = true; break tokenLoop; } From 65d680dbb604634af6fa47b7e64f099109c05d73 Mon Sep 17 00:00:00 2001 From: fr1j0 Date: Thu, 15 Jan 2026 16:40:49 -0500 Subject: [PATCH 93/99] Optimize deposit sorting with stable sort and early termination --- src/lib/claim/depositUtils.ts | 67 ++++------------------------------- 1 file changed, 7 insertions(+), 60 deletions(-) diff --git a/src/lib/claim/depositUtils.ts b/src/lib/claim/depositUtils.ts index ee0e22d15..923812fdf 100644 --- a/src/lib/claim/depositUtils.ts +++ b/src/lib/claim/depositUtils.ts @@ -12,12 +12,12 @@ import { PublicClient, decodeAbiParameters, encodeFunctionData } from "viem"; // Constants for deposit management const MIN_DEPOSITS_FOR_COMBINING = 25; // Minimum deposits to trigger combining logic -const MIN_DEPOSITS_FOR_ELIGIBILITY = 20; // Combine down to this many deposits -const PROCESS_SINGLE_TOKEN_ONLY_THRESHOLD = 200; // If a single token has more than this many deposits, process it alone -const LARGE_DEPOSITS_THRESHOLD = 100; // If a single token has more than this many deposits, process it along with not more than the next variable's worth of tokens at time -const MAX_TOKENS_WITH_LARGE_DEPOSITS = 3; // Maximum number of tokens to process when large deposits are present -const MAX_TOP_DEPOSITS = 10; // Maximum number of deposits to L2L update in regular Claim -const MIN_BDV_THRESHOLD = TokenValue.ONE; // Minimum BDV difference threshold for regular updates, this filters out "dust" updates that are not worth L2L'ing +// const MIN_DEPOSITS_FOR_ELIGIBILITY = 20; // Combine down to this many deposits +// const PROCESS_SINGLE_TOKEN_ONLY_THRESHOLD = 200; // If a single token has more than this many deposits, process it alone +// const LARGE_DEPOSITS_THRESHOLD = 100; // If a single token has more than this many deposits, process it along with not more than the next variable's worth of tokens at time +// const MAX_TOKENS_WITH_LARGE_DEPOSITS = 3; // Maximum number of tokens to process when large deposits are present +// const MAX_TOP_DEPOSITS = 10; // Maximum number of deposits to L2L update in regular Claim +// const MIN_BDV_THRESHOLD = TokenValue.ONE; // Minimum BDV difference threshold for regular updates, this filters out "dust" updates that are not worth L2L'ing const USE_SIMULATION_METHOD = false; // Turning this off for now because it fails due to a complex simulation issue, instead it gets sorted deposits within txn @@ -84,7 +84,7 @@ export function generateCombineAndL2LCallData(farmerDeposits: Map 0 && stemCount < 1000) { // Sanity check on the count - console.debug(`Decoding ${stemCount} sorted deposits`); // Start position for stem array elements (after the length field) const stemStartPos = stemCountPosition + 64; @@ -186,7 +185,6 @@ function decodeSortedDepositsResult( amounts.push(amountValue); } - console.debug(`Successfully decoded ${stems.length} stems and ${amounts.length} amounts`); return { stems, amounts }; } else { console.error(`Amount count (${amountCount}) doesn't match stem count (${stemCount})`); @@ -247,8 +245,6 @@ export async function simulateAndPrepareFarmCalls( farmerDeposits: Map, sender?: `0x${string}`, ): Promise<`0x${string}`[] | null> { - console.debug(`Simulating and preparing farm calls for ${token.symbol}`); - try { // Create a call to getSortedDeposits from the TractorHelpers contract const getSortedDepositsCall = encodeFunctionData({ @@ -277,18 +273,14 @@ export async function simulateAndPrepareFarmCalls( // Generate convert calls with smart limits using our utility function const combineCalls = generateCombineAndL2LCallData(farmerDeposits); if (combineCalls.length > 0) { - console.debug(`Adding ${combineCalls.length} combine/L2L calls to execute before sort deposits`); farmCalls.push(...combineCalls); } else { - console.debug("No combine/L2L calls needed"); } } else { - console.debug("No farmer deposits provided, skipping combine/L2L calls"); } // Add the pipe call last so we can capture its result farmCalls.push(pipeCall); - console.debug(`Total farm calls to execute in simulation: ${farmCalls.length}`); // Simulate the farm call const simulationResult = await publicClient.simulateContract({ @@ -299,9 +291,6 @@ export async function simulateAndPrepareFarmCalls( account: address, }); - console.debug("Simulation completed successfully"); - console.debug("Number of results:", simulationResult.result?.length || 0); - // The getSortedDeposits result will be the last item in the results array const sortDepositsResult = simulationResult.result?.[simulationResult.result.length - 1]; @@ -310,8 +299,6 @@ export async function simulateAndPrepareFarmCalls( return null; } - console.debug(`Sort deposits result length: ${(sortDepositsResult as `0x${string}`).length}`); - // Decode the result data const decodedResult = decodeSortedDepositsResult(sortDepositsResult as `0x${string}`); @@ -320,8 +307,6 @@ export async function simulateAndPrepareFarmCalls( return null; } - console.debug(`Successfully decoded ${decodedResult.stems.length} stems and amounts`); - // Extract the combine/L2L calls from the simulation result // Note: We want all calls except the last one (the pipe call) const combineCalls: `0x${string}`[] = []; @@ -329,14 +314,11 @@ export async function simulateAndPrepareFarmCalls( // Only add combine calls if we have them in the simulation if (farmCalls.length > 1) { combineCalls.push(...farmCalls.slice(0, -1)); - console.debug(`Extracted ${combineCalls.length} combine/L2L calls`); } // Convert the sorted stems to deposit IDs and reverse them for proper sorting order const reversedDepositIds = createReversedDepositIds(token.address, decodedResult.stems); - console.debug(`Generated ${reversedDepositIds.length} deposit IDs from sorted stems`); - // For the actual transaction, we need to use the real sender address in the call // Use address for simulation, but zero address for transaction to avoid "tx from field is set" error const effectiveAddress = sender || address; @@ -351,9 +333,6 @@ export async function simulateAndPrepareFarmCalls( // Prepare the final farm calls: combine/L2L calls followed by updateSortedDepositIds const finalFarmCalls = [...combineCalls, updateSortedIdsCall]; - // Log details about the operations - console.debug(`Final farm calls: ${finalFarmCalls.length} total (${combineCalls.length} combine + 1 update)`); - return finalFarmCalls; } catch (error) { console.error(`Error simulating and preparing farm calls for ${token.symbol}:`, error); @@ -418,19 +397,14 @@ export async function generateBatchSortDepositsCallData( protocolAddress: `0x${string}`, sender?: `0x${string}`, ): Promise<`0x${string}`[]> { - console.debug(`Generating batch sort deposits call data for ${farmerDeposits.size} tokens`); - const callData: `0x${string}`[] = []; // Process each token in the farmer's deposits for (const [token, depositData] of farmerDeposits.entries()) { if (!depositData.deposits.length) { - console.debug(`Skipping ${token.symbol} - no deposits`); continue; } - console.debug(`Processing ${token.symbol} with ${depositData.deposits.length} deposits`); - try { // Create a map with only this token's deposits const singleTokenMap = new Map(); @@ -459,7 +433,6 @@ export async function generateBatchSortDepositsCallData( if (farmCalls && farmCalls.length > 0) { // Include all calls from simulateAndPrepareFarmCalls including combines callData.push(...farmCalls); - console.debug(`Added ${farmCalls.length} calls for ${token.symbol} (includes combine operations)`); } } catch (error) { console.error(`Error processing ${token.symbol}:`, error); @@ -502,7 +475,6 @@ export async function generateBatchSortDepositsCallData( } } - console.debug(`Generated ${callData.length} total calls (combines + sort deposits)`); return callData; } @@ -613,21 +585,9 @@ export function encodeClaimRewardCombineCalls( token: Token, targetGroups: number = 20, ): `0x${string}`[] { - console.debug("Processing deposits for", token.symbol, ":", { - depositCount: deposits.length, - }); - // Use our existing smart grouping logic const groups = createSmartGroups(deposits, targetGroups); - console.debug("Created groups for", token.symbol, ":", { - groupCount: groups.length, - groups: groups.map((g) => ({ - id: g.id, - depositCount: g.deposits.length, - })), - }); - // Sort groups by average Stalk/BDV ratio const groupsWithRatio = groups.map((group) => { const groupDeposits = group.deposits @@ -652,14 +612,6 @@ export function encodeClaimRewardCombineCalls( .sort((a, b) => b.stalkPerBdv.sub(a.stalkPerBdv).toNumber()) .map(({ id, deposits, stalkPerBdv }) => ({ id, deposits, stalkPerBdv })); - console.debug("Sorted groups by Stalk/BDV ratio for", token.symbol, ":", { - sortedGroups: sortedGroups.map((g) => ({ - id: g.id, - depositCount: g.deposits.length, - stalkPerBdv: g.stalkPerBdv.toHuman(), - })), - }); - // Use our existing encode function with sorted groups - strip stalkPerBdv before passing const result = encodeGroupCombineCalls( sortedGroups.map(({ id, deposits }) => ({ id, deposits })), @@ -667,10 +619,5 @@ export function encodeClaimRewardCombineCalls( deposits, ); - console.debug("Final encoded calls for", token.symbol, ":", { - groupCount: sortedGroups.length, - encodedCallCount: result.length, - }); - return result; } From 13603d3b6baea659c4fb9cabc1d0c08f44ee82fe Mon Sep 17 00:00:00 2001 From: feyyazcigim Date: Fri, 16 Jan 2026 02:09:08 +0300 Subject: [PATCH 94/99] refactor(ui): standardize temperature slider bounds and update styling --- src/components/Tractor/form/SowOrderV0Fields.tsx | 12 ++++++++++-- src/pages/field/actions/Sow.tsx | 2 +- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/components/Tractor/form/SowOrderV0Fields.tsx b/src/components/Tractor/form/SowOrderV0Fields.tsx index 5262f7cad..f993012ae 100644 --- a/src/components/Tractor/form/SowOrderV0Fields.tsx +++ b/src/components/Tractor/form/SowOrderV0Fields.tsx @@ -40,6 +40,11 @@ const sharedInputProps = { pattern: "[0-9]*.?[0-9]*", } as const; +const TEMPERATURE_SLIDER_BOUNDS = { + MIN_OFFSET: 100, + MAX_OFFSET: 300, +} as const; + export const TOOLTIP_COPY = { tokenStrategy: "The source token(s) to use for the Sow Order.", totalAmount: "The total amount of PINTO to Sow in this order.", @@ -425,8 +430,11 @@ SowOrderV0Fields.Temperature = function Temperature() { return Math.floor(temperature.max?.toNumber() || 0); }, [maxTemperature, temperature.max]); - const minTemp = useMemo(() => Math.max(0, currentTempValue - 100), [currentTempValue]); - const maxTemp = useMemo(() => currentTempValue + 300, [currentTempValue]); + const minTemp = useMemo( + () => Math.max(0, currentTempValue - TEMPERATURE_SLIDER_BOUNDS.MIN_OFFSET), + [currentTempValue], + ); + const maxTemp = useMemo(() => currentTempValue + TEMPERATURE_SLIDER_BOUNDS.MAX_OFFSET, [currentTempValue]); // Set default value to current temperature only on initial mount useEffect(() => { diff --git a/src/pages/field/actions/Sow.tsx b/src/pages/field/actions/Sow.tsx index 60166ea40..c9dc1dd94 100644 --- a/src/pages/field/actions/Sow.tsx +++ b/src/pages/field/actions/Sow.tsx @@ -490,7 +490,7 @@ function Sow({ isMorning, onShowOrder }: SowProps) { From 114b623515d9021abf48909e32f6a6cdfdb5fa80 Mon Sep 17 00:00:00 2001 From: feyyazcigim Date: Fri, 16 Jan 2026 02:55:38 +0300 Subject: [PATCH 95/99] feature: add address name generator for referral leaderboard --- .../{ => referral}/DelegateReferralModal.tsx | 0 .../{ => referral}/ReferralLeaderboard.tsx | 29 +-- .../referral/ReferralLeaderboardRow.tsx | 39 +++ .../{ => referral}/ReferralLinkGenerator.tsx | 0 .../{ => referral}/ReferralStatsCard.tsx | 0 src/components/referral/index.ts | 5 + src/hooks/useAddressName.ts | 28 +++ src/pages/Referral.tsx | 10 +- src/utils/addressNameGenerator.ts | 222 ++++++++++++++++++ 9 files changed, 308 insertions(+), 25 deletions(-) rename src/components/{ => referral}/DelegateReferralModal.tsx (100%) rename src/components/{ => referral}/ReferralLeaderboard.tsx (88%) create mode 100644 src/components/referral/ReferralLeaderboardRow.tsx rename src/components/{ => referral}/ReferralLinkGenerator.tsx (100%) rename src/components/{ => referral}/ReferralStatsCard.tsx (100%) create mode 100644 src/components/referral/index.ts create mode 100644 src/hooks/useAddressName.ts create mode 100644 src/utils/addressNameGenerator.ts diff --git a/src/components/DelegateReferralModal.tsx b/src/components/referral/DelegateReferralModal.tsx similarity index 100% rename from src/components/DelegateReferralModal.tsx rename to src/components/referral/DelegateReferralModal.tsx diff --git a/src/components/ReferralLeaderboard.tsx b/src/components/referral/ReferralLeaderboard.tsx similarity index 88% rename from src/components/ReferralLeaderboard.tsx rename to src/components/referral/ReferralLeaderboard.tsx index a03052ac7..b5af8ac87 100644 --- a/src/components/ReferralLeaderboard.tsx +++ b/src/components/referral/ReferralLeaderboard.tsx @@ -1,14 +1,11 @@ -import podIcon from "@/assets/protocol/Pod.png"; import FrameAnimator from "@/components/LoadingSpinner"; import { Button } from "@/components/ui/Button"; import { Card, CardContent, CardHeader } from "@/components/ui/Card"; -import IconImage from "@/components/ui/IconImage"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/Table"; import { useReferralLeaderboard } from "@/state/referral"; -import { formatter, truncateHex } from "@/utils/format"; -import { cn } from "@/utils/utils"; import { useCallback, useMemo, useState } from "react"; import { useAccount } from "wagmi"; +import { ReferralLeaderboardRow } from "./ReferralLeaderboardRow"; /** * Shows top referrers ranked by Pods earned in a paginated table. @@ -157,7 +154,7 @@ export default function ReferralLeaderboard() { No. - Address + Farmer Pods Earned Referrals @@ -207,28 +204,18 @@ export default function ReferralLeaderboard() { No. - Address + Farmer Pods Earned Referrals {paginationState.displayData?.map((entry) => ( - - {entry.rank} - - {truncateHex(entry.address, 6, 4)} - - -
      - - {formatter.noDec(entry.podsEarned)} -
      -
      - {entry.totalSuccessfulReferrals} -
      + ))}
      diff --git a/src/components/referral/ReferralLeaderboardRow.tsx b/src/components/referral/ReferralLeaderboardRow.tsx new file mode 100644 index 000000000..5dca19fad --- /dev/null +++ b/src/components/referral/ReferralLeaderboardRow.tsx @@ -0,0 +1,39 @@ +import podIcon from "@/assets/protocol/Pod.png"; +import IconImage from "@/components/ui/IconImage"; +import { TableCell, TableRow } from "@/components/ui/Table"; +import { useAddressName } from "@/hooks/useAddressName"; +import { formatter } from "@/utils/format"; + +/** + * Individual row component for the leaderboard table + * Displays generated name instead of address for privacy + */ +interface LeaderboardRowProps { + entry: { + address: string; + rank: number; + podsEarned: any; + totalSuccessfulReferrals: number; + }; + isCurrentUser: boolean; +} + +export function ReferralLeaderboardRow({ entry, isCurrentUser }: LeaderboardRowProps) { + const generatedName = useAddressName(entry.address); + + return ( + + {entry.rank} + +
      {generatedName}
      +
      + +
      + + {formatter.noDec(entry.podsEarned)} +
      +
      + {entry.totalSuccessfulReferrals} +
      + ); +} diff --git a/src/components/ReferralLinkGenerator.tsx b/src/components/referral/ReferralLinkGenerator.tsx similarity index 100% rename from src/components/ReferralLinkGenerator.tsx rename to src/components/referral/ReferralLinkGenerator.tsx diff --git a/src/components/ReferralStatsCard.tsx b/src/components/referral/ReferralStatsCard.tsx similarity index 100% rename from src/components/ReferralStatsCard.tsx rename to src/components/referral/ReferralStatsCard.tsx diff --git a/src/components/referral/index.ts b/src/components/referral/index.ts new file mode 100644 index 000000000..f7e78a962 --- /dev/null +++ b/src/components/referral/index.ts @@ -0,0 +1,5 @@ +export { default as ReferralLeaderboard } from "./ReferralLeaderboard"; +export { ReferralLeaderboardRow } from "./ReferralLeaderboardRow"; +export { ReferralLinkGenerator } from "./ReferralLinkGenerator"; +export { ReferralStatsCard } from "./ReferralStatsCard"; +export { DelegateReferralModal } from "./DelegateReferralModal"; diff --git a/src/hooks/useAddressName.ts b/src/hooks/useAddressName.ts new file mode 100644 index 000000000..f42d40ca0 --- /dev/null +++ b/src/hooks/useAddressName.ts @@ -0,0 +1,28 @@ +import { useMemo } from "react"; +import { generateAddressName } from "../utils/addressNameGenerator"; + +/** + * Hook that generates and memoizes a human-readable name from an Ethereum address + * + * The generated name is deterministic and will always be the same for a given address. + * Uses memoization to prevent unnecessary recalculations when the component re-renders. + * + * @param address - Ethereum address (0x-prefixed hex string) or undefined + * @returns Generated name string in format "Word1-Word2-Word3-Word4" + * + * @example + * const name = useAddressName('0x113ad340...'); + * // Returns "Scrappy-Turkey-Picker-Haystack" + * + * @example + * const name = useAddressName(undefined); + * // Returns "Unknown-Unknown-Unknown-Unknown" + */ +export function useAddressName(address: string | undefined): string { + return useMemo(() => { + if (!address) { + return "Unknown-Unknown-Unknown-Unknown"; + } + return generateAddressName(address); + }, [address]); +} diff --git a/src/pages/Referral.tsx b/src/pages/Referral.tsx index 873d80226..54ac714de 100644 --- a/src/pages/Referral.tsx +++ b/src/pages/Referral.tsx @@ -1,8 +1,10 @@ -import { DelegateReferralModal } from "@/components/DelegateReferralModal"; import { HowToCard } from "@/components/HowToCard"; -import ReferralLeaderboard from "@/components/ReferralLeaderboard"; -import { ReferralLinkGenerator } from "@/components/ReferralLinkGenerator"; -import { ReferralStatsCard } from "@/components/ReferralStatsCard"; +import { + DelegateReferralModal, + ReferralLeaderboard, + ReferralLinkGenerator, + ReferralStatsCard, +} from "@/components/referral"; import { Card } from "@/components/ui/Card"; import PageContainer from "@/components/ui/PageContainer"; import { Separator } from "@/components/ui/Separator"; diff --git a/src/utils/addressNameGenerator.ts b/src/utils/addressNameGenerator.ts new file mode 100644 index 000000000..03c42792e --- /dev/null +++ b/src/utils/addressNameGenerator.ts @@ -0,0 +1,222 @@ +/** + * Address Name Generator + * + * Generates deterministic, human-readable names from Ethereum addresses + * for privacy-friendly display on the referral leaderboard. + */ + +// List 1: Color/Quality words (25 entries, 0-indexed) +export const COLOR_QUALITY_WORDS = [ + "golden", // 0 + "silver", // 1 + "rusty", // 2 + "muddy", // 3 + "dusty", // 4 + "shiny", // 5 + "crusty", // 6 + "mighty", // 7 + "tiny", // 8 + "hefty", // 9 + "ancient", // 10 + "humble", // 11 + "noble", // 12 + "wild", // 13 + "gentle", // 14 + "sturdy", // 15 + "lazy", // 16 + "busy", // 17 + "lucky", // 18 + "scrappy", // 19 + "happy", // 20 + "grumpy", // 21 + "sleepy", // 22 + "zippy", // 23 + "chunky", // 24 +] as const; + +// List 2: Animal/Creature words (25 entries, 0-indexed) +export const ANIMAL_CREATURE_WORDS = [ + "chicken", // 0 + "pig", // 1 + "cow", // 2 + "goat", // 3 + "sheep", // 4 + "horse", // 5 + "rooster", // 6 + "duck", // 7 + "turkey", // 8 + "mule", // 9 + "ox", // 10 + "llama", // 11 + "donkey", // 12 + "rabbit", // 13 + "hen", // 14 + "ram", // 15 + "bull", // 16 + "calf", // 17 + "chick", // 18 + "pony", // 19 + "foal", // 20 + "lamb", // 21 + "piglet", // 22 + "colt", // 23 + "mare", // 24 +] as const; + +// List 3: Action/State/Role words (25 entries, 0-indexed) +export const ACTION_STATE_ROLE_WORDS = [ + "plowing", // 0 + "farmer", // 1 + "milking", // 2 + "rancher", // 3 + "reaping", // 4 + "herder", // 5 + "grazing", // 6 + "keeper", // 7 + "sowing", // 8 + "tender", // 9 + "hauling", // 10 + "picker", // 11 + "feeding", // 12 + "grower", // 13 + "wrangling", // 14 + "hand", // 15 + "tilling", // 16 + "boss", // 17 + "harvesting", // 18 + "yokel", // 19 + "mowing", // 20 + "pruning", // 21 + "foreman", // 22 + "planter", // 23 + "shepherd", // 24 +] as const; + +// List 4: Place/Thing words (25 entries, 0-indexed) +export const PLACE_THING_WORDS = [ + "barn", // 0 + "field", // 1 + "pasture", // 2 + "silo", // 3 + "coop", // 4 + "stable", // 5 + "meadow", // 6 + "ranch", // 7 + "acres", // 8 + "creek", // 9 + "shed", // 10 + "mill", // 11 + "fence", // 12 + "trough", // 13 + "haystack", // 14 + "orchard", // 15 + "garden", // 16 + "prairie", // 17 + "valley", // 18 + "hillside", // 19 + "farmhouse", // 20 + "corral", // 21 + "paddock", // 22 + "grove", // 23 + "homestead", // 24 +] as const; + +/** + * Validates if a string is a valid Ethereum address format + * + * @param address - The address string to validate + * @returns true if valid, false otherwise + */ +export function isValidAddress(address: string): boolean { + // Check if address exists and is a string + if (!address || typeof address !== "string") { + return false; + } + + // Check for 0x prefix and at least 4 bytes (8 hex chars) after prefix + // Valid hex pattern: 0x followed by at least 8 hexadecimal characters + const hexPattern = /^0x[0-9a-fA-F]{8,}$/; + return hexPattern.test(address); +} + +/** + * Extracts the first 4 bytes from an Ethereum address and calculates word indices + * + * @param address - Valid Ethereum address (0x-prefixed) + * @returns Array of 4 indices (0-24) for word selection + */ +function extractByteIndices(address: string): [number, number, number, number] { + // Extract first 4 bytes (8 hex characters) after 0x prefix + // Positions 2-9 in the string (0x[byte1][byte2][byte3][byte4]...) + const hexBytes = address.slice(2, 10); + + // Extract individual byte pairs + const byte1 = hexBytes.slice(0, 2); // chars 0-1 + const byte2 = hexBytes.slice(2, 4); // chars 2-3 + const byte3 = hexBytes.slice(4, 6); // chars 4-5 + const byte4 = hexBytes.slice(6, 8); // chars 6-7 + + // Convert hex to decimal and apply modulo 25 to get indices 0-24 + const index1 = parseInt(byte1, 16) % 25; + const index2 = parseInt(byte2, 16) % 25; + const index3 = parseInt(byte3, 16) % 25; + const index4 = parseInt(byte4, 16) % 25; + + return [index1, index2, index3, index4]; +} + +/** + * Formats an array of words into Title Case hyphenated string + * + * @param words - Array of lowercase words + * @returns Hyphenated string with each word in Title Case + */ +function formatTitleCase(words: string[]): string { + return words.map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join("-"); +} + +/** + * Selects words from the four word lists using the provided indices + * + * @param indices - Array of 4 indices (0-24) + * @returns Array of 4 selected words + */ +function selectWords(indices: [number, number, number, number]): [string, string, string, string] { + const word1 = COLOR_QUALITY_WORDS[indices[0]]; + const word2 = ANIMAL_CREATURE_WORDS[indices[1]]; + const word3 = ACTION_STATE_ROLE_WORDS[indices[2]]; + const word4 = PLACE_THING_WORDS[indices[3]]; + + return [word1, word2, word3, word4]; +} + +/** + * Generates a deterministic four-word name from an Ethereum address + * + * The name is generated by: + * 1. Extracting the first 4 bytes (8 hex chars) after the 0x prefix + * 2. Converting each byte to decimal and applying modulo 25 + * 3. Using the results as indices to select words from 4 predefined lists + * 4. Formatting the words in Title Case joined with hyphens + * + * @param address - Ethereum address (0x-prefixed hex string) + * @returns Four-word hyphenated name in Title Case, or "Unknown-Unknown-Unknown-Unknown" for invalid input + * + * @example + * generateAddressName('0x113ad340...') // Returns "Scrappy-Turkey-Picker-Haystack" + */ +export function generateAddressName(address: string): string { + // Validate input + if (!isValidAddress(address)) { + return "Unknown-Unknown-Unknown-Unknown"; + } + + // Extract byte indices (0-24) from first 4 bytes of address + const indices = extractByteIndices(address); + + // Select words from lists using calculated indices + const words = selectWords(indices); + + // Format in Title Case with hyphens + return formatTitleCase(words); +} From 600b03033f712be2bf7a7849b584551d0a954476 Mon Sep 17 00:00:00 2001 From: fr1j0 Date: Thu, 15 Jan 2026 20:08:56 -0500 Subject: [PATCH 96/99] Refactor deposit sorting optimization and add SOW referral blueprint decoder --- src/components/Tractor/Plow.tsx | 58 ++++++-- src/components/Tractor/SoilOrderbook.tsx | 20 +-- .../Tractor/Sow/SowOrderEstimatedTipPaid.tsx | 16 ++- .../Tractor/TractorRequisitionsTable.tsx | 9 +- .../TractorFarmerMixedOrders.utils.ts | 18 ++- .../farmer-orders/TractorOrdersPanel.tsx | 15 +- src/lib/Tractor/blueprint-decoders/index.ts | 10 +- .../sow-referral-decoder.ts | 34 +++++ .../requisitions/tractor-requisition.ts | 16 ++- src/lib/Tractor/sowOrder/tractor-sow.ts | 128 ++++++++++++++---- src/pages/field/FieldActivity.tsx | 8 +- src/pages/field/TractorOrdersChart.tsx | 8 +- src/state/protocol/field/field.updater.ts | 13 +- src/utils/math.ts | 13 +- 14 files changed, 283 insertions(+), 83 deletions(-) create mode 100644 src/lib/Tractor/blueprint-decoders/sow-referral-decoder.ts diff --git a/src/components/Tractor/Plow.tsx b/src/components/Tractor/Plow.tsx index 7a3524cba..e9fbe3ddc 100644 --- a/src/components/Tractor/Plow.tsx +++ b/src/components/Tractor/Plow.tsx @@ -7,7 +7,7 @@ import useTokenData from "@/state/useTokenData"; import { formatter } from "@/utils/format"; import { Token } from "@/utils/types"; import { useCallback, useMemo, useState } from "react"; -import { BaseOrderType, ColumnConfig, ExecuteOrdersTab } from "./ExecuteOrdersTab"; +import { ColumnConfig, ExecuteOrdersTab } from "./ExecuteOrdersTab"; import { PlowDetails } from "./PlowDetails"; const BASESCAN_URL = "https://basescan.org/address/"; @@ -92,24 +92,54 @@ export function Plow() { (orders: RequisitionEvent[]): RequisitionEvent[] => { const currentTemperature = temperatures.scaled; - return orders.filter((req) => { - // Skip cancelled requisitions - if (req.isCancelled) return false; + return orders + .filter((req) => { + // Skip cancelled requisitions + if (req.isCancelled) { + // console.log("[Plow/filterOrders] Skipping cancelled order:", req.requisition.blueprintHash); + return false; + } - // Skip requisitions with invalid data or non-positive tip - if (!req.decodedData || !req.decodedData.operatorParams) return false; - const tipAmount = req.decodedData.operatorParams.operatorTipAmount; + // Skip requisitions with invalid data or non-positive tip + if (!req.decodedData) { + // console.log("[Plow/filterOrders] Skipping order with no decodedData:", req.requisition.blueprintHash); + return false; + } - // Skip requisitions with temperature requirements higher than current temperature - if (currentTemperature && req.decodedData.minTemp) { - const reqMinTemp = TokenValue.fromBlockchain(req.decodedData.minTemp, 6); - if (reqMinTemp.gt(currentTemperature)) { + if (!req.decodedData.operatorParams) { + // console.log("[Plow/filterOrders] Skipping order with no operatorParams:", { + // hash: req.requisition.blueprintHash, + // decodedData: req.decodedData, + // }); return false; } - } - return tipAmount > 0n; - }); + const tipAmount = req.decodedData.operatorParams.operatorTipAmount; + + // Skip requisitions with temperature requirements higher than current temperature + if (currentTemperature && req.decodedData.minTemp) { + const reqMinTemp = TokenValue.fromBlockchain(req.decodedData.minTemp, 6); + if (reqMinTemp.gt(currentTemperature)) { + return false; + } + } + + const passes = tipAmount > 0n; + // console.log("[Plow/filterOrders] Order filter result:", { + // hash: req.requisition.blueprintHash, + // passes, + // tipAmount: tipAmount.toString(), + // }); + return passes; + }) + .map((req, idx, arr) => { + // if (idx === arr.length - 1) { + // console.log("[Plow/filterOrders] AFTER filtering:", { + // totalPassed: arr.length, + // }); + // } + return req; + }); }, [temperatures.scaled], ); diff --git a/src/components/Tractor/SoilOrderbook.tsx b/src/components/Tractor/SoilOrderbook.tsx index b558d49c9..e2f9e7bd1 100644 --- a/src/components/Tractor/SoilOrderbook.tsx +++ b/src/components/Tractor/SoilOrderbook.tsx @@ -8,7 +8,7 @@ import { Switch } from "@/components/ui/Switch"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/Table"; import { PINTO } from "@/constants/tokens"; import { useGetTractorTokenStrategyWithBlueprint } from "@/hooks/tractor/useGetTractorTokenStrategy"; -import { OrderbookEntry, SowBlueprintData, decodeSowTractorData } from "@/lib/Tractor"; +import { OrderbookEntry, SowBlueprintData, decodeSowTractorData, unwrapSowBlueprintData } from "@/lib/Tractor"; import { Blueprint } from "@/lib/Tractor/types"; import { useTractorSowOrderbook } from "@/state/tractor/useTractorSowOrders"; import useCachedLatestBlockQuery from "@/state/useCachedLatestBlockQuery"; @@ -131,7 +131,7 @@ export function SoilOrderbookContent({ return; } - const d = decodeSowTractorData(selectedOrder.requisition.blueprint.data); + const d = unwrapSowBlueprintData(decodeSowTractorData(selectedOrder.requisition.blueprint.data)); if (!d) return; setDecodedData({ type: "sow", @@ -152,8 +152,8 @@ export function SoilOrderbookContent({ } else if (sortBy === "tip") { sorted = [...requisitions].sort((a, b) => { try { - const dataA = decodeSowTractorData(a.requisition.blueprint.data); - const dataB = decodeSowTractorData(b.requisition.blueprint.data); + const dataA = unwrapSowBlueprintData(decodeSowTractorData(a.requisition.blueprint.data)); + const dataB = unwrapSowBlueprintData(decodeSowTractorData(b.requisition.blueprint.data)); if (!dataA || !dataB) return 0; const tipA = BigInt(dataA.operatorParams.operatorTipAmount); const tipB = BigInt(dataB.operatorParams.operatorTipAmount); @@ -175,7 +175,7 @@ export function SoilOrderbookContent({ let matchesTemperatureFilter = true; if (!showAboveCurrentTemp) { try { - const data = decodeSowTractorData(req.requisition.blueprint.data); + const data = unwrapSowBlueprintData(decodeSowTractorData(req.requisition.blueprint.data)); if (data) { const reqTemp = parseFloat(data.minTempAsString); matchesTemperatureFilter = reqTemp < temperature.max.toNumber(); @@ -202,7 +202,7 @@ export function SoilOrderbookContent({ if (sortedReqs.length > 0) { sortedReqs.forEach((req, idx) => { try { - const data = decodeSowTractorData(req.requisition.blueprint.data); + const data = unwrapSowBlueprintData(decodeSowTractorData(req.requisition.blueprint.data)); if (data) { // Parse the percentage string to just get the number const tempValue = parseFloat(data.minTempAsString); @@ -217,8 +217,10 @@ export function SoilOrderbookContent({ // Insert at the position where the first requisition temperature is GREATER than max temperature while ( insertIndex < sortedReqs.length && - parseFloat(decodeSowTractorData(sortedReqs[insertIndex].requisition.blueprint.data)?.minTempAsString || "0") <= - maxTemp + parseFloat( + unwrapSowBlueprintData(decodeSowTractorData(sortedReqs[insertIndex].requisition.blueprint.data)) + ?.minTempAsString || "0", + ) <= maxTemp ) { insertIndex++; } @@ -296,7 +298,7 @@ export function SoilOrderbookContent({ const renderRequisitionRow = (req, index) => { let decodedData: SowBlueprintData | null = null; try { - decodedData = decodeSowTractorData(req.requisition.blueprint.data); + decodedData = unwrapSowBlueprintData(decodeSowTractorData(req.requisition.blueprint.data)); } catch (error) { console.error("Failed to decode data for requisition:", error); } diff --git a/src/components/Tractor/Sow/SowOrderEstimatedTipPaid.tsx b/src/components/Tractor/Sow/SowOrderEstimatedTipPaid.tsx index 9d877a1bc..5e79a5f09 100644 --- a/src/components/Tractor/Sow/SowOrderEstimatedTipPaid.tsx +++ b/src/components/Tractor/Sow/SowOrderEstimatedTipPaid.tsx @@ -60,8 +60,16 @@ export const SowOrderEstimatedTipPaid = ({ averageTipPaid, operatorTipPreset }: let maxTimes: TV; // Check if we have all required data for accurate calculation - if (!cultivationFactor || !initialSoil || isCultivationLoading || isInitialSoilLoading || !pintoPrice) { - // Fallback to simple division while loading or if data unavailable + // Also check if initialSoil is effectively zero (≤ 0) + if ( + !cultivationFactor || + !initialSoil || + initialSoil.lte(0) || + isCultivationLoading || + isInitialSoilLoading || + !pintoPrice + ) { + // Fallback to simple division: total / minSoil maxTimes = min.gt(0) ? total.div(min) : TV.ZERO; } else { // Calculate initial value: initialSoil * INITIAL_CULTIVATION_FACTOR / cultivationFactor @@ -78,10 +86,12 @@ export const SowOrderEstimatedTipPaid = ({ averageTipPaid, operatorTipPreset }: maxTimes = TV.fromHuman(maxExecutions, mainToken.decimals); } - return { + const result = { min: minTimes.mul(tip), max: maxTimes.mul(tip), }; + + return result; }, [ operatorTip, customOperatorTip, diff --git a/src/components/Tractor/TractorRequisitionsTable.tsx b/src/components/Tractor/TractorRequisitionsTable.tsx index 68b57ae27..3c1438d2f 100644 --- a/src/components/Tractor/TractorRequisitionsTable.tsx +++ b/src/components/Tractor/TractorRequisitionsTable.tsx @@ -3,7 +3,12 @@ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@ import { diamondABI } from "@/constants/abi/diamondABI"; import { useProtocolAddress } from "@/hooks/pinto/useProtocolAddress"; import useTransaction from "@/hooks/useTransaction"; -import { TractorRequisitionEvent as RequisitionEvent, SowBlueprintData, decodeSowTractorData } from "@/lib/Tractor"; +import { + TractorRequisitionEvent as RequisitionEvent, + SowBlueprintData, + decodeSowTractorData, + unwrapSowBlueprintData, +} from "@/lib/Tractor"; import useTractorPublishedRequisitions from "@/state/tractor/useTractorPublishedRequisitions"; import { useEffect } from "react"; import { toast } from "sonner"; @@ -108,7 +113,7 @@ export function TractorRequisitionsTable({ refreshTrigger = 0 }: TractorRequisit } | null = null; try { - const decoded = decodeSowTractorData(req.requisition.blueprint.data); + const decoded = unwrapSowBlueprintData(decodeSowTractorData(req.requisition.blueprint.data)); if (decoded) { decodedData = { minTempAsString: decoded.minTempAsString, diff --git a/src/components/Tractor/farmer-orders/TractorFarmerMixedOrders.utils.ts b/src/components/Tractor/farmer-orders/TractorFarmerMixedOrders.utils.ts index 20de7715b..571a00297 100644 --- a/src/components/Tractor/farmer-orders/TractorFarmerMixedOrders.utils.ts +++ b/src/components/Tractor/farmer-orders/TractorFarmerMixedOrders.utils.ts @@ -19,7 +19,23 @@ export function transformSowOrderToUnified( throw new Error("Missing decoded data for Sow order"); } - const data = req.decodedData; + // Handle both unwrapped and wrapped referral formats + let data: SowBlueprintData; + if ("blueprintData" in req.decodedData && typeof req.decodedData.blueprintData === "object") { + // Wrapped referral format: { blueprintData, referralAddress } + console.warn("[TractorFarmerMixedOrders] Received wrapped referral format, extracting blueprintData"); + data = (req.decodedData as any).blueprintData; + } else { + // Regular SowBlueprintData format + data = req.decodedData; + } + + // Additional safety check + if (!data.sowAmounts) { + console.error("[TractorFarmerMixedOrders] Invalid decoded data structure:", req.decodedData); + throw new Error("Invalid decoded data structure - missing sowAmounts"); + } + const totalAmount = TokenValue.fromBlockchain(data.sowAmounts.totalAmountToSow, 6); // Calculate progress from executions diff --git a/src/components/Tractor/farmer-orders/TractorOrdersPanel.tsx b/src/components/Tractor/farmer-orders/TractorOrdersPanel.tsx index 79b352122..196fd4c05 100644 --- a/src/components/Tractor/farmer-orders/TractorOrdersPanel.tsx +++ b/src/components/Tractor/farmer-orders/TractorOrdersPanel.tsx @@ -111,10 +111,21 @@ function TractorOrdersPanelGeneric({ sowOrders .filter((req) => stringEq(req.requisition.blueprint.publisher, address)) .forEach((req) => { - const decodedData = ORDER_TYPE_REGISTRY.sow.decodeData(req.requisition.blueprint.data); - if (!decodedData) { + const decodedResult = ORDER_TYPE_REGISTRY.sow.decodeData(req.requisition.blueprint.data); + if (!decodedResult) { return; } + + // Handle both unwrapped and wrapped referral formats + let decodedData: any; + if ("blueprintData" in decodedResult && typeof decodedResult.blueprintData === "object") { + // Wrapped referral format: { blueprintData, referralAddress } + decodedData = decodedResult.blueprintData; + } else { + // Regular SowBlueprintData format + decodedData = decodedResult; + } + const reqWithDecodedData = { ...req, decodedData }; const orderExecutions = executionsByHash?.[req.requisition.blueprintHash] || []; diff --git a/src/lib/Tractor/blueprint-decoders/index.ts b/src/lib/Tractor/blueprint-decoders/index.ts index a33a90ea0..01faf3198 100644 --- a/src/lib/Tractor/blueprint-decoders/index.ts +++ b/src/lib/Tractor/blueprint-decoders/index.ts @@ -1,10 +1,13 @@ -import { diamondABI } from "@/constants/abi/diamondABI"; -import { CONVERT_UP_BLUEPRINT_V0_SELECTOR, SOW_BLUEPRINT_V0_SELECTOR } from "@/constants/address"; -import { decodeFunctionData } from "viem"; +import { + CONVERT_UP_BLUEPRINT_V0_SELECTOR, + SOW_BLUEPRINT_REFERRAL_V0_SELECTOR, + SOW_BLUEPRINT_V0_SELECTOR, +} from "@/constants/address"; import { extractTractorBlueprintCall } from "../requisitions/tractor-requisition"; import { convertUpBlueprintDecoder } from "./convert-up-decoder"; import { genericBlueprintDecoder } from "./generic-decoder"; import { sowBlueprintDecoder } from "./sow-decoder"; +import { sowBlueprintReferralDecoder } from "./sow-referral-decoder"; export interface BlueprintDecoder { selector: string; @@ -21,6 +24,7 @@ export interface DecodedBlueprintResult { export const BLUEPRINT_REGISTRY: Record = { [SOW_BLUEPRINT_V0_SELECTOR]: sowBlueprintDecoder, + [SOW_BLUEPRINT_REFERRAL_V0_SELECTOR]: sowBlueprintReferralDecoder, [CONVERT_UP_BLUEPRINT_V0_SELECTOR]: convertUpBlueprintDecoder, } as const; diff --git a/src/lib/Tractor/blueprint-decoders/sow-referral-decoder.ts b/src/lib/Tractor/blueprint-decoders/sow-referral-decoder.ts new file mode 100644 index 000000000..c09f4ed90 --- /dev/null +++ b/src/lib/Tractor/blueprint-decoders/sow-referral-decoder.ts @@ -0,0 +1,34 @@ +import { sowBlueprintReferralV0ABI } from "@/constants/abi/SowBlueprintReferralV0ABI"; +import { SOW_BLUEPRINT_REFERRAL_V0_SELECTOR } from "@/constants/address"; +import { decodeFunctionData } from "viem"; +import type { BlueprintDecoder, DecodedBlueprintResult } from "./index"; + +export const sowBlueprintReferralDecoder: BlueprintDecoder = { + selector: SOW_BLUEPRINT_REFERRAL_V0_SELECTOR, + abi: sowBlueprintReferralV0ABI, + functionName: "sowBlueprintReferral", + decode: (callData: string): DecodedBlueprintResult | null => { + try { + const decoded = decodeFunctionData({ + abi: sowBlueprintReferralV0ABI, + data: callData as `0x${string}`, + }); + + if (decoded.functionName === "sowBlueprintReferral" && decoded.args[0]) { + const referralStruct = decoded.args[0]; + + if (typeof referralStruct === "object" && referralStruct !== null && "params" in referralStruct) { + return { + type: "sow", + functionName: "sowBlueprintReferral", + params: referralStruct.params, + }; + } + } + } catch (error) { + console.error("Error decoding sowBlueprintReferral:", error); + } + + return null; + }, +}; diff --git a/src/lib/Tractor/requisitions/tractor-requisition.ts b/src/lib/Tractor/requisitions/tractor-requisition.ts index 4da7e629b..283347b83 100644 --- a/src/lib/Tractor/requisitions/tractor-requisition.ts +++ b/src/lib/Tractor/requisitions/tractor-requisition.ts @@ -1,4 +1,5 @@ import { TV, TokenValue } from "@/classes/TokenValue"; +import { sowBlueprintReferralV0ABI } from "@/constants/abi/SowBlueprintReferralV0ABI"; import { sowBlueprintv0ABI } from "@/constants/abi/SowBlueprintv0ABI"; import { convertUpBlueprintV0ABI } from "@/constants/abi/convertUpBlueprintV0ABI"; import { diamondABI } from "@/constants/abi/diamondABI"; @@ -152,7 +153,7 @@ type SelectRequisitionTypeArgs = { data: Awaited>; }; -const combinedABI = [...sowBlueprintv0ABI, ...convertUpBlueprintV0ABI] as const; +const combinedABI = [...sowBlueprintv0ABI, ...sowBlueprintReferralV0ABI, ...convertUpBlueprintV0ABI] as const; type BaseDecodedTractorRequisition = { type: RequisitionType; @@ -175,6 +176,17 @@ const blueprintTransformerLookup = { transformer: transformSowRequisitionEvent, type: "sowBlueprintv0", }, + sowBlueprintReferral: { + transformer: (args: any, chainId: number) => { + // The referral blueprint wraps the params in a referral struct + // Extract the params and pass to the same transformer + if (typeof args === "object" && args !== null && "params" in args) { + return transformSowRequisitionEvent(args.params, chainId); + } + return null; + }, + type: "sowBlueprintv0", + }, convertUpBlueprint: { transformer: transformConvertUpRequisitionEvent, type: "convertUpBlueprint", @@ -189,7 +201,6 @@ export const decodeTractorBlueprint = ( const pipeCalls = decodeEncodedTractorDataToAdvancedPipeCalls(encodedData, "decodeTractorBlueprint"); if (!pipeCalls?.length) { - console.debug("[Tractor/decodeTractorBlueprint] No pipe calls provided. Returning null."); return null; } @@ -283,7 +294,6 @@ export const getSelectRequisitionType = (requisitionsType: MayArray { - const decodedResult = decodeSowTractorData(requisition.requisition.blueprint.data); - - // Handle both v0 and referral blueprint formats - let blueprintData: SowBlueprintData | null = null; - let referralAddress: `0x${string}` | undefined; - - if (decodedResult) { - if ("blueprintData" in decodedResult) { - // Referral blueprint - blueprintData = decodedResult.blueprintData; - referralAddress = decodedResult.referralAddress; - } else { - // Regular v0 blueprint - blueprintData = decodedResult; + const requisitionsWithTemperature = activeRequisitions + .map((requisition) => { + const decodedResult = decodeSowTractorData(requisition.requisition.blueprint.data); + + // Handle both v0 and referral blueprint formats + let blueprintData: SowBlueprintData | null = null; + let referralAddress: `0x${string}` | undefined; + + if (decodedResult) { + if ("blueprintData" in decodedResult) { + // Referral blueprint + blueprintData = decodedResult.blueprintData; + referralAddress = decodedResult.referralAddress; + } else { + // Regular v0 blueprint + blueprintData = decodedResult; + } } - } - return { - requisition, - temperature: blueprintData?.minTemp || 0n, - decodedData: blueprintData, - referralAddress, - }; - }); + return { + requisition, + temperature: blueprintData?.minTemp || 0n, + decodedData: blueprintData, + referralAddress, + }; + }) + .filter((item) => { + // Filter out requisitions that failed to decode + if (!item.decodedData) { + console.warn( + `Skipping requisition ${item.requisition.requisition.blueprintHash} - failed to decode blueprint data`, + ); + return false; + } + return true; + }); // Sort requisitions by temperature requisitionsWithTemperature.sort((a, b) => Number(a.temperature - b.temperature)); @@ -574,9 +608,23 @@ export async function loadOrderbookData( // console.debug(`Publisher: ${publisher}`); try { - // Determine which blueprint contract to query based on the blueprint address - const blueprintAddress = requisition.requisition.blueprint.operator; - const isReferralBlueprint = blueprintAddress.toLowerCase() === SOW_BLUEPRINT_REFERRAL_V0_ADDRESS.toLowerCase(); + // Determine blueprint address from the selector in the encoded data + const blueprintData = requisition.requisition.blueprint.data; + let blueprintAddress: `0x${string}` = SOW_BLUEPRINT_V0_ADDRESS; + let isReferralBlueprint = false; + + try { + const calls = decodeEncodedTractorDataToAdvancedPipeCalls(blueprintData, "sowV0"); + if (calls?.length) { + const selector = calls[0].callData.slice(0, 10) as `0x${string}`; + if (selector === SOW_BLUEPRINT_REFERRAL_V0_SELECTOR) { + blueprintAddress = SOW_BLUEPRINT_REFERRAL_V0_ADDRESS; + isReferralBlueprint = true; + } + } + } catch (error) { + console.warn("Failed to determine blueprint address, defaulting to v0:", error); + } // Get pintos left to sow from the appropriate contract const pintosLeft = await publicClient.readContract({ @@ -698,7 +746,7 @@ export async function loadOrderbookData( // Calculate amountSowableNextSeason as the greater of currentlySowable and minAmountToSowPerSeason let amountSowableNextSeason = currentlySowable; - if (decodedData && decodedData.sowAmounts.maxAmountToSowPerSeason) { + if (decodedData?.sowAmounts.maxAmountToSowPerSeason) { const maxAmountToSowPerSeason = TokenValue.fromBlockchain(decodedData.sowAmounts.maxAmountToSowPerSeason, 6); amountSowableNextSeason = TokenValue.min(currentlySowable, maxAmountToSowPerSeason); // console.debug(`Min amount to sow per season: ${maxAmountToSowPerSeason.toHuman()}`); @@ -795,8 +843,28 @@ export async function loadOrderbookData( // Sort orderbook entries by operator tip amount (highest first) const orderbookDataWithTips = orderbookData.map((entry) => { - const decodedData = decodeSowTractorData(entry.requisition.blueprint.data); - const tipAmount = decodedData?.operatorParams.operatorTipAmount || 0n; + const decodedResult = decodeSowTractorData(entry.requisition.blueprint.data); + + // Handle both v0 and referral blueprint formats + let decodedData: SowBlueprintData | null = null; + if (decodedResult) { + if ("blueprintData" in decodedResult) { + // Referral blueprint format + decodedData = decodedResult.blueprintData; + } else { + // Regular v0 blueprint format + decodedData = decodedResult; + } + } + + console.debug("[TRACTOR/loadOrderbookData] Decoded data:", { + hash: entry.requisition.blueprintHash, + hasData: !!decodedData, + hasOperatorParams: !!decodedData?.operatorParams, + tipAmount: decodedData?.operatorParams?.operatorTipAmount?.toString(), + }); + + const tipAmount = decodedData?.operatorParams?.operatorTipAmount || 0n; return { decodedData, entry, diff --git a/src/pages/field/FieldActivity.tsx b/src/pages/field/FieldActivity.tsx index ef4c4eebf..3be642063 100644 --- a/src/pages/field/FieldActivity.tsx +++ b/src/pages/field/FieldActivity.tsx @@ -6,7 +6,7 @@ import { SoilOrderbookDialog } from "@/components/Tractor/SoilOrderbook"; import { Button } from "@/components/ui/Button"; import IconImage from "@/components/ui/IconImage"; import { Skeleton } from "@/components/ui/Skeleton"; -import { OrderbookEntry, SowBlueprintData, decodeSowTractorData } from "@/lib/Tractor"; +import { OrderbookEntry, SowBlueprintData, decodeSowTractorData, unwrapSowBlueprintData } from "@/lib/Tractor"; import useFieldSowEvents from "@/state/events/useFieldSowEvents"; import { useTractorSowOrderbook } from "@/state/tractor/useTractorSowOrders"; import { useTemperature } from "@/state/useFieldData"; @@ -451,7 +451,8 @@ const FieldActivityNoDataDisplay = () => ( const getOrderTemperature = (order: OrderbookEntry): number => { // Try to decode the data to get the temperature if (order.requisition && order.requisition.blueprint && order.requisition.blueprint.data) { - const decodedData = decodeSowTractorData(order.requisition.blueprint.data); + const decoded = decodeSowTractorData(order.requisition.blueprint.data); + const decodedData = unwrapSowBlueprintData(decoded); if (decodedData && decodedData.minTempAsString) { return parseFloat(decodedData.minTempAsString); } @@ -463,7 +464,8 @@ const getOrderTemperature = (order: OrderbookEntry): number => { // Helper function to get the decoded tractor data const getDecodedTractorData = (order: OrderbookEntry): SowBlueprintData | null => { if (order.requisition && order.requisition.blueprint && order.requisition.blueprint.data) { - return decodeSowTractorData(order.requisition.blueprint.data); + const decoded = decodeSowTractorData(order.requisition.blueprint.data); + return unwrapSowBlueprintData(decoded); } return null; }; diff --git a/src/pages/field/TractorOrdersChart.tsx b/src/pages/field/TractorOrdersChart.tsx index 4ea0edd14..f25f87054 100644 --- a/src/pages/field/TractorOrdersChart.tsx +++ b/src/pages/field/TractorOrdersChart.tsx @@ -5,7 +5,7 @@ import TextSkeleton from "@/components/TextSkeleton"; import BarChart from "@/components/charts/BarChart"; import { Button } from "@/components/ui/Button"; import { Card } from "@/components/ui/Card"; -import { OrderbookEntry, decodeSowTractorData } from "@/lib/Tractor"; +import { OrderbookEntry, decodeSowTractorData, unwrapSowBlueprintData } from "@/lib/Tractor"; import { useTractorSowOrderbook } from "@/state/tractor/useTractorSowOrders"; import { formatter, numberAbbr } from "@/utils/format"; import { useDebounceValue } from "@/utils/useDebounce"; @@ -72,7 +72,7 @@ const TractorOrdersChart = React.memo(({ className, variant = "default" }: Tract const filteredOrders = selectedTemperature !== null ? orders.filter((order) => { - const decodedData = decodeSowTractorData(order.requisition.blueprint.data); + const decodedData = unwrapSowBlueprintData(decodeSowTractorData(order.requisition.blueprint.data)); if (!decodedData?.minTempAsString) return false; const orderTemp = parseFloat(decodedData.minTempAsString); return Math.abs(orderTemp - selectedTemperature) < 0.01; // Handle floating point precision @@ -363,7 +363,7 @@ const processOrdersByTemperature = (orders: OrderbookEntry[]): ProcessedOrderDat const groupedData = new Map(); for (const order of orders) { - const decodedData = decodeSowTractorData(order.requisition.blueprint.data); + const decodedData = unwrapSowBlueprintData(decodeSowTractorData(order.requisition.blueprint.data)); console.log("[processOrdersByTemperature] Decoded data for order:", decodedData); if (!decodedData?.minTempAsString) { @@ -408,7 +408,7 @@ const processOrdersByTip = (orders: OrderbookEntry[]): ProcessedOrderData[] => { const groupedData = new Map(); for (const order of orders) { - const decodedData = decodeSowTractorData(order.requisition.blueprint.data); + const decodedData = unwrapSowBlueprintData(decodeSowTractorData(order.requisition.blueprint.data)); if (!decodedData?.operatorParams?.operatorTipAmountAsString) continue; const tipAmount = TokenValue.fromHuman(decodedData.operatorParams.operatorTipAmountAsString, 6); diff --git a/src/state/protocol/field/field.updater.ts b/src/state/protocol/field/field.updater.ts index 2ff107b44..5a5897f43 100644 --- a/src/state/protocol/field/field.updater.ts +++ b/src/state/protocol/field/field.updater.ts @@ -125,10 +125,11 @@ const useUpdateInitialSoil = () => { const query = useQuery({ queryKey: _queryKey, queryFn: async () => { - return request(subgraphs[chainId].beanstalk, FieldIssuedSoilDocument, { + const result = await request(subgraphs[chainId].beanstalk, FieldIssuedSoilDocument, { season: season, field_contains_nocase: diamond, }); + return result; }, enabled: !!season && season > 0 && !SG_FETCH_DISABLED, ...settings.query, @@ -138,12 +139,14 @@ const useUpdateInitialSoil = () => { // biome-ignore lint/correctness/useExhaustiveDependencies: useEffect(() => { - if (!exists(query?.data)) return; - - console.debug("[protocol/field/useUpdateInitialSoil]: data", query.data); + if (!exists(query?.data)) { + return; + } setInitialSoil((prev) => { - const newSoil = TV.fromBlockchain(query.data.fieldHourlySnapshots[0]?.issuedSoil || 0, SOIL_DECIMALS); + const rawIssuedSoil = query.data.fieldHourlySnapshots[0]?.issuedSoil || 0; + const newSoil = TV.fromBlockchain(rawIssuedSoil, SOIL_DECIMALS); + // return old value if it hasn't changed. Prevents new object reference of TokenValue. if (prev.initialSoil.eq(newSoil)) return prev; // otherwise, return the new value diff --git a/src/utils/math.ts b/src/utils/math.ts index bdd638b07..77bf38676 100644 --- a/src/utils/math.ts +++ b/src/utils/math.ts @@ -34,8 +34,11 @@ export function solveArithmeticSeriesForN( ): number { // Edge case: if delta is zero, fallback to simple division if (delta.eq(0)) { - if (initialValue.eq(0)) return 0; - return Number(totalAmount.div(initialValue).toHuman()); + if (initialValue.eq(0)) { + return 0; + } + const result = Number(totalAmount.div(initialValue).toHuman()); + return result; } // Edge case: if initialValue is zero and delta is positive @@ -47,7 +50,9 @@ export function solveArithmeticSeriesForN( const term = totalAmount.mul(8).div(delta); const discriminant = TokenValue.ONE.add(term); - if (discriminant.lt(0)) return 0; + if (discriminant.lt(0)) { + return 0; + } const sqrtDiscriminant = Math.sqrt(Number(discriminant.toHuman())); const n = (1 + sqrtDiscriminant) / 2; @@ -69,7 +74,7 @@ export function solveArithmeticSeriesForN( // Handle negative discriminant (shouldn't happen in practice for valid inputs) if (discriminant.lt(0)) { - console.warn("Negative discriminant in arithmetic series calculation, returning 0"); + console.warn("[solveArithmeticSeriesForN] Negative discriminant in arithmetic series calculation, returning 0"); return 0; } From f16d57174565e6cbd6f082e57fa9f81f505b02c2 Mon Sep 17 00:00:00 2001 From: fr1j0 Date: Thu, 15 Jan 2026 20:50:15 -0500 Subject: [PATCH 97/99] omit 0 sow events (i.e from bonus referrals) --- src/state/events/useFieldSowEvents.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/state/events/useFieldSowEvents.ts b/src/state/events/useFieldSowEvents.ts index 4b573e2df..2ae3bb3d9 100644 --- a/src/state/events/useFieldSowEvents.ts +++ b/src/state/events/useFieldSowEvents.ts @@ -116,6 +116,11 @@ const selectFieldSowEvents = ( const beans = args.beans || BigInt(0); // PINTO amount in beans const pods = args.pods || BigInt(0); + // Skip events where 0 beans were sown + if (beans === BigInt(0)) { + continue; + } + // Calculate timestamp using block number difference and 2-second block time // Base has 2 second blocks const blockDiff = latestBlockNumber - Number(blockNumber); From cb1fbff3d0355a475a7c765e5ae09ee62333acb3 Mon Sep 17 00:00:00 2001 From: fr1j0 Date: Thu, 15 Jan 2026 22:20:15 -0500 Subject: [PATCH 98/99] fix: reduce referral bonus pods from 10% to 5% Updated the bonus pods calculation to match the UI text that was already showing "5% more Pods" Co-Authored-By: Claude Sonnet 4.5 --- src/pages/field/actions/Sow.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pages/field/actions/Sow.tsx b/src/pages/field/actions/Sow.tsx index c9dc1dd94..d4bd66f36 100644 --- a/src/pages/field/actions/Sow.tsx +++ b/src/pages/field/actions/Sow.tsx @@ -195,7 +195,7 @@ function Sow({ isMorning, onShowOrder }: SowProps) { const bonusPods = useMemo(() => { if (!referralAddress || !pods || pods.lte(0)) return TV.ZERO; - return pods.mul(0.1); + return pods.mul(0.05); }, [referralAddress, pods]); const hasReferralCode = Boolean(referralAddress); @@ -583,8 +583,8 @@ function Sow({ isMorning, onShowOrder }: SowProps) { {hasReferralCode && bonusPods.gt(0) && (
      - You gained 10% more Pods due to - using a referral link! + You gained 5% more Pods due to using + a referral link!
      )} From 79fdb6d2955844108d0c0c42119a933adc00b3c2 Mon Sep 17 00:00:00 2001 From: feyyazcigim Date: Fri, 16 Jan 2026 18:27:34 +0300 Subject: [PATCH 99/99] chore(plow): clean up unused map in order filtering --- src/components/Tractor/Plow.tsx | 87 ++++++++++++++++----------------- 1 file changed, 43 insertions(+), 44 deletions(-) diff --git a/src/components/Tractor/Plow.tsx b/src/components/Tractor/Plow.tsx index e9fbe3ddc..f20046fd7 100644 --- a/src/components/Tractor/Plow.tsx +++ b/src/components/Tractor/Plow.tsx @@ -92,54 +92,53 @@ export function Plow() { (orders: RequisitionEvent[]): RequisitionEvent[] => { const currentTemperature = temperatures.scaled; - return orders - .filter((req) => { - // Skip cancelled requisitions - if (req.isCancelled) { - // console.log("[Plow/filterOrders] Skipping cancelled order:", req.requisition.blueprintHash); - return false; - } + return orders.filter((req) => { + // Skip cancelled requisitions + if (req.isCancelled) { + // console.log("[Plow/filterOrders] Skipping cancelled order:", req.requisition.blueprintHash); + return false; + } + + // Skip requisitions with invalid data or non-positive tip + if (!req.decodedData) { + // console.log("[Plow/filterOrders] Skipping order with no decodedData:", req.requisition.blueprintHash); + return false; + } + + if (!req.decodedData.operatorParams) { + // console.log("[Plow/filterOrders] Skipping order with no operatorParams:", { + // hash: req.requisition.blueprintHash, + // decodedData: req.decodedData, + // }); + return false; + } - // Skip requisitions with invalid data or non-positive tip - if (!req.decodedData) { - // console.log("[Plow/filterOrders] Skipping order with no decodedData:", req.requisition.blueprintHash); - return false; - } + const tipAmount = req.decodedData.operatorParams.operatorTipAmount; - if (!req.decodedData.operatorParams) { - // console.log("[Plow/filterOrders] Skipping order with no operatorParams:", { - // hash: req.requisition.blueprintHash, - // decodedData: req.decodedData, - // }); + // Skip requisitions with temperature requirements higher than current temperature + if (currentTemperature && req.decodedData.minTemp) { + const reqMinTemp = TokenValue.fromBlockchain(req.decodedData.minTemp, 6); + if (reqMinTemp.gt(currentTemperature)) { return false; } - - const tipAmount = req.decodedData.operatorParams.operatorTipAmount; - - // Skip requisitions with temperature requirements higher than current temperature - if (currentTemperature && req.decodedData.minTemp) { - const reqMinTemp = TokenValue.fromBlockchain(req.decodedData.minTemp, 6); - if (reqMinTemp.gt(currentTemperature)) { - return false; - } - } - - const passes = tipAmount > 0n; - // console.log("[Plow/filterOrders] Order filter result:", { - // hash: req.requisition.blueprintHash, - // passes, - // tipAmount: tipAmount.toString(), - // }); - return passes; - }) - .map((req, idx, arr) => { - // if (idx === arr.length - 1) { - // console.log("[Plow/filterOrders] AFTER filtering:", { - // totalPassed: arr.length, - // }); - // } - return req; - }); + } + + const passes = tipAmount > 0n; + // console.log("[Plow/filterOrders] Order filter result:", { + // hash: req.requisition.blueprintHash, + // passes, + // tipAmount: tipAmount.toString(), + // }); + return passes; + }); + // .map((req, idx, arr) => { + // if (idx === arr.length - 1) { + // console.log("[Plow/filterOrders] AFTER filtering:", { + // totalPassed: arr.length, + // }); + // } + // return req; + // }); }, [temperatures.scaled], );