Repository: deriv-com/champion-trader
Pull request #72: Aum / GRWT-5204 / stake proposal stream
Author: login: aum-deriv
Created: 2025-03-11T12:32:49Z | Merged: 2025-03-17T10:44:38Z

## Summary by Sourcery

Subscribes to a stream of stake proposals based on the selected trade parameters and updates the UI with the received data. This includes fetching proposal data, handling loading and error states, and updating the payout values for the trade buttons.

New Features:
- Introduces a hook to subscribe to a stream of stake proposals based on trade parameters.
- Updates the trade buttons with payout information received from the proposal stream.
- Adds a new API endpoint for streaming proposal data.
- Implements error handling for the proposal stream, updating the UI to reflect error states and reconnection attempts.
- Adds contract details to the proposal response


https://github.com/user-attachments/assets/073f1804-2367-441e-8f96-9ee637567e5f

## Summary by Sourcery

Subscribes to a stream of stake proposals based on the selected trade parameters and updates the UI with the received data, including payout values for the trade buttons. It also handles loading and error states, and includes contract details in the proposal response.

New Features:
- Introduces a hook to subscribe to a stream of stake proposals based on trade parameters.
- Updates the trade buttons with payout information received from the proposal stream.
- Adds a new API endpoint for streaming proposal data.
- Implements error handling for the proposal stream, updating the UI to reflect error states and reconnection attempts.
- Adds contract details to the proposal response


Diff:
diff --git a/src/api/services/proposal/proposal-sse.ts b/src/api/services/proposal/proposal-sse.ts
index 96ccc79a..f4de0426 100644
--- a/src/api/services/proposal/proposal-sse.ts
+++ b/src/api/services/proposal/proposal-sse.ts
@@ -20,12 +20,16 @@ export const subscribeToProposalStream = (
             stream: "proposal",
             ...Object.entries(params).reduce(
                 (acc, [key, value]) => {
-                    acc[key] = value.toString();
+                    // Only include defined values in the parameters
+                    if (value !== undefined) {
+                        acc[key] = value.toString();
+                    }
                     return acc;
                 },
                 {} as Record<string, string>
             ),
         },
+        customPath: "/v1/trading/proposal/stream", // Use the new API endpoint path
         onMessage: callbacks.onData,
         onError: callbacks.onError,
     });
diff --git a/src/api/services/proposal/types.ts b/src/api/services/proposal/types.ts
index d88751b0..c4f95691 100644
--- a/src/api/services/proposal/types.ts
+++ b/src/api/services/proposal/types.ts
@@ -2,21 +2,41 @@
  * Proposal request parameters
  */
 export interface ProposalRequest {
-    instrument_id: string;
-    trade_type: string;
-    duration: string;
-    amount: number;
-    currency: string;
+    product_id: string; // e.g., "rise_fall"
+    instrument_id: string; // e.g., "frxUSDJPY"
+    duration: number; // e.g., 900
+    duration_unit: string; // e.g., "seconds"
+    allow_equals?: boolean; // e.g., false
+    stake: string; // e.g., "2.00"
+    account_uuid?: string; // e.g., "9f8c1b23-4e2a-47ad-92c2-b1e5d2a7e65f"
+}
+
+/**
+ * Contract details in the proposal response
+ */
+export interface ContractDetails {
+    payout: string;
+    stake: string;
+    probability: number;
+    pricing_spot: string;
+    barrier: string;
+    allow_equals: boolean;
+    contract_expiry_time: number;
+}
+
+/**
+ * Proposal variant (rise or fall)
+ */
+export interface ProposalVariant {
+    variant: string; // e.g., "rise" or "fall"
+    contract_details: ContractDetails;
 }
 
 /**
  * Proposal data from SSE stream
  */
 export interface ProposalData {
-    id: string;
-    price: number;
-    payout: number;
-    spot: number;
-    spot_time: number;
-    // Add other proposal properties as needed
+    data: {
+        variants: ProposalVariant[];
+    };
 }
diff --git a/src/components/Stake/StakeController.tsx b/src/components/Stake/StakeController.tsx
index a7a2b81e..4b1d9d6d 100644
--- a/src/components/Stake/StakeController.tsx
+++ b/src/components/Stake/StakeController.tsx
@@ -47,7 +47,7 @@ export const StakeController: React.FC<StakeControllerProps> = () => {
         const initialStates: ButtonStates = {};
         tradeTypeConfigs[trade_type].buttons.forEach((button) => {
             initialStates[button.actionName] = {
-                loading: true,
+                loading: false, // Set to false since we're using payouts from tradeStore
                 error: null,
                 payout: 0,
                 reconnecting: false,
@@ -148,10 +148,17 @@ export const StakeController: React.FC<StakeControllerProps> = () => {
         }
 
         const amount = parseStakeAmount(value);
+        const minStake = config?.data.validations.stake
+            ? parseFloat(config.data.validations.stake.min)
+            : getStakeConfig().min;
+        const maxStake = config?.data.validations.stake
+            ? parseFloat(config.data.validations.stake.max)
+            : payouts.max;
+
         const validation = validateStake({
             amount,
-            minStake: getStakeConfig().min,
-            maxStake: payouts.max,
+            minStake,
+            maxStake,
             currency,
         });
 
@@ -169,10 +176,17 @@ export const StakeController: React.FC<StakeControllerProps> = () => {
         }
 
         const amount = parseStakeAmount(value);
+        const minStake = config?.data.validations.stake
+            ? parseFloat(config.data.validations.stake.min)
+            : getStakeConfig().min;
+        const maxStake = config?.data.validations.stake
+            ? parseFloat(config.data.validations.stake.max)
+            : payouts.max;
+
         const validation = validateStake({
             amount,
-            minStake: getStakeConfig().min,
-            maxStake: payouts.max,
+            minStake,
+            maxStake,
             currency,
         });
 
@@ -184,8 +198,10 @@ export const StakeController: React.FC<StakeControllerProps> = () => {
     const preventExceedingMax = (value: string) => {
         if (error && errorMessage?.includes("maximum")) {
             const newAmount = value ? parseStakeAmount(value) : 0;
-            const maxAmount = parseStakeAmount(payouts.max.toString());
-            return newAmount > maxAmount;
+            const maxStake = config?.data.validations.stake
+                ? parseFloat(config.data.validations.stake.max)
+                : payouts.max;
+            return newAmount > maxStake;
         }
         return false;
     };
@@ -236,11 +252,11 @@ export const StakeController: React.FC<StakeControllerProps> = () => {
                     maxPayout={payouts.max}
                     payoutValues={payouts.values}
                     isDesktop={isLandscape}
-                    loading={Object.values(buttonStates).some((state) => state.loading)}
-                    loadingStates={Object.keys(buttonStates).reduce(
+                    loading={false} // Set to false since we're using payouts from tradeStore
+                    loadingStates={Object.keys(tradeTypeConfigs[trade_type].buttons).reduce(
                         (acc, key) => ({
                             ...acc,
-                            [key]: buttonStates[key].loading,
+                            [key]: false, // Set all loading states to false
                         }),
                         {}
                     )}
diff --git a/src/components/Stake/StakeField.tsx b/src/components/Stake/StakeField.tsx
index cfa0bdc0..abca62c0 100644
--- a/src/components/Stake/StakeField.tsx
+++ b/src/components/Stake/StakeField.tsx
@@ -1,21 +1,47 @@
 import React from "react";
 import TradeParam from "@/components/TradeFields/TradeParam";
 import { Tooltip } from "@/components/ui/tooltip";
-import { useStakeField } from "./hooks/useStakeField";
 import { cn } from "@/lib/utils";
 import { useOrientationStore } from "@/stores/orientationStore";
 import { DesktopTradeFieldCard } from "@/components/ui/desktop-trade-field-card";
 import { MobileTradeFieldCard } from "@/components/ui/mobile-trade-field-card";
+import { useStakeField } from "./hooks/useStakeField";
 
 interface StakeFieldProps {
     className?: string;
+    // Value props
+    stake: string;
+    setStake: (value: string) => void;
+    // Config props
+    productConfig: any;
+    currency: string;
+    // Optional UI state props
+    isConfigLoading?: boolean;
+    // Optional handlers that can be overridden
+    onIncrement?: () => void;
+    onDecrement?: () => void;
+    onMobileClick?: () => void;
+    // Error handler callback
+    handleError?: (hasError: boolean, errorMessage: string | null) => void;
 }
 
-export const StakeField: React.FC<StakeFieldProps> = ({ className }) => {
+export const StakeField: React.FC<StakeFieldProps> = ({
+    className,
+    stake,
+    setStake,
+    productConfig,
+    currency,
+    isConfigLoading = false,
+    onIncrement,
+    onDecrement,
+    onMobileClick,
+    handleError,
+}) => {
     const { isLandscape } = useOrientationStore();
+
+    // Use the hook to get all the state and handlers
     const {
-        stake,
-        currency,
+        isStakeSelected,
         error,
         errorMessage,
         localValue,
@@ -23,13 +49,21 @@ export const StakeField: React.FC<StakeFieldProps> = ({ className }) => {
         containerRef,
         handleSelect,
         handleChange,
-        handleIncrement,
-        handleDecrement,
-        handleMobileClick,
-        isConfigLoading,
-        isStakeSelected,
+        defaultHandleIncrement,
+        defaultHandleDecrement,
+        defaultHandleMobileClick,
+    } = useStakeField({
+        stake,
+        setStake,
         productConfig,
-    } = useStakeField();
+        currency,
+        handleError,
+    });
+
+    // Use provided handlers or defaults
+    const handleIncrement = onIncrement || defaultHandleIncrement;
+    const handleDecrement = onDecrement || defaultHandleDecrement;
+    const handleMobileClick = onMobileClick || defaultHandleMobileClick;
 
     if (isConfigLoading) {
         return (
diff --git a/src/components/Stake/__tests__/StakeField.test.tsx b/src/components/Stake/__tests__/StakeField.test.tsx
index 7d4ee84e..c8e437d5 100644
--- a/src/components/Stake/__tests__/StakeField.test.tsx
+++ b/src/components/Stake/__tests__/StakeField.test.tsx
@@ -67,6 +67,8 @@ describe("StakeField", () => {
     const mockShowTooltip = jest.fn();
     const mockHideTooltip = jest.fn();
     const mockSetBottomSheet = jest.fn();
+    const mockSetStake = jest.fn();
+    const mockHandleError = jest.fn();
 
     beforeEach(() => {
         jest.clearAllMocks();
@@ -78,7 +80,7 @@ describe("StakeField", () => {
         });
         (useTradeStore as jest.MockedFunction<typeof useTradeStore>).mockReturnValue({
             stake: "100",
-            setStake: jest.fn(),
+            setStake: mockSetStake,
             isConfigLoading: false,
             productConfig: {
                 data: {
@@ -105,24 +107,42 @@ describe("StakeField", () => {
         });
     });
 
+    // Helper function to render with default props
+    const renderWithProps = (props = {}) => {
+        const defaultProps = {
+            stake: "100",
+            setStake: mockSetStake,
+            productConfig: {
+                data: {
+                    validations: {
+                        stake: {
+                            min: "1",
+                            max: "50000",
+                        },
+                    },
+                },
+            },
+            currency: "USD",
+            handleError: mockHandleError,
+            ...props,
+        };
+        return render(<StakeField {...defaultProps} />);
+    };
+
     describe("Loading State", () => {
         it("should show skeleton loader when config is loading", () => {
-            (useTradeStore as jest.MockedFunction<typeof useTradeStore>).mockReturnValue({
-                stake: "100",
-                setStake: jest.fn(),
+            renderWithProps({
                 isConfigLoading: true,
                 productConfig: null,
             });
 
-            render(<StakeField />);
-
             const skeleton = screen.getByTestId("stake-field-skeleton");
             expect(skeleton).toBeInTheDocument();
             expect(screen.queryByTestId("trade-param")).not.toBeInTheDocument();
         });
 
         it("should show stake value when not loading", () => {
-            render(<StakeField />);
+            renderWithProps();
 
             const param = screen.getByTestId("trade-param");
             expect(param).toBeInTheDocument();
@@ -134,15 +154,10 @@ describe("StakeField", () => {
 
     describe("Error State", () => {
         it("should display N/A when productConfig is null", () => {
-            (useTradeStore as jest.MockedFunction<typeof useTradeStore>).mockReturnValue({
-                stake: "100",
-                setStake: jest.fn(),
-                isConfigLoading: false,
+            renderWithProps({
                 productConfig: null,
             });
 
-            render(<StakeField />);
-
             const param = screen.getByTestId("trade-param");
             expect(param).toHaveAttribute("data-value", "N/A");
             expect(param.className).toContain("opacity-50");
@@ -150,15 +165,10 @@ describe("StakeField", () => {
         });
 
         it("should not trigger click actions when productConfig is null", () => {
-            (useTradeStore as jest.MockedFunction<typeof useTradeStore>).mockReturnValue({
-                stake: "100",
-                setStake: jest.fn(),
-                isConfigLoading: false,
+            renderWithProps({
                 productConfig: null,
             });
 
-            render(<StakeField />);
-
             fireEvent.click(screen.getByTestId("trade-param"));
             expect(mockSetBottomSheet).not.toHaveBeenCalled();
         });
@@ -169,15 +179,11 @@ describe("StakeField", () => {
             ).mockReturnValue({
                 isLandscape: true,
             });
-            (useTradeStore as jest.MockedFunction<typeof useTradeStore>).mockReturnValue({
-                stake: "100",
-                setStake: jest.fn(),
-                isConfigLoading: false,
+
+            renderWithProps({
                 productConfig: null,
             });
 
-            render(<StakeField />);
-
             expect(screen.queryByLabelText("Increase stake")).not.toBeInTheDocument();
             expect(screen.queryByLabelText("Decrease stake")).not.toBeInTheDocument();
             expect(screen.getByText("N/A")).toBeInTheDocument();
@@ -194,7 +200,7 @@ describe("StakeField", () => {
         });
 
         it("should open bottom sheet when clicked", () => {
-            render(<StakeField />);
+            renderWithProps();
 
             fireEvent.click(screen.getByTestId("trade-param"));
 
@@ -202,7 +208,7 @@ describe("StakeField", () => {
         });
 
         it("should wrap in mobile trade field card", () => {
-            render(<StakeField />);
+            renderWithProps();
 
             expect(screen.getByTestId("mobile-trade-field-card")).toBeInTheDocument();
             expect(screen.queryByTestId("desktop-trade-field-card")).not.toBeInTheDocument();
@@ -219,7 +225,7 @@ describe("StakeField", () => {
         });
 
         it("should show input field and increment/decrement buttons", () => {
-            render(<StakeField />);
+            renderWithProps();
 
             expect(screen.getByLabelText("Stake amount")).toBeInTheDocument();
             expect(screen.getByLabelText("Decrease stake")).toBeInTheDocument();
@@ -227,7 +233,7 @@ describe("StakeField", () => {
         });
 
         it("should update isSelected state when clicked", () => {
-            render(<StakeField />);
+            renderWithProps();
 
             const container = screen.getByTestId("desktop-trade-field-card")
                 .firstChild as HTMLElement;
@@ -242,24 +248,7 @@ describe("StakeField", () => {
         });
 
         it("should handle increment/decrement with validation", () => {
-            const mockSetStake = jest.fn();
-            (useTradeStore as jest.MockedFunction<typeof useTradeStore>).mockReturnValue({
-                stake: "100",
-                setStake: mockSetStake,
-                isConfigLoading: false,
-                productConfig: {
-                    data: {
-                        validations: {
-                            stake: {
-                                min: "1",
-                                max: "50000",
-                            },
-                        },
-                    },
-                },
-            });
-
-            render(<StakeField />);
+            renderWithProps();
 
             // Test increment (step = 1)
             fireEvent.click(screen.getByLabelText("Increase stake"));
@@ -271,7 +260,7 @@ describe("StakeField", () => {
         });
 
         it("should show error state and tooltip when validation fails", () => {
-            render(<StakeField />);
+            renderWithProps();
 
             const input = screen.getByLabelText("Stake amount");
             fireEvent.change(input, { target: { value: "" } }); // Empty value triggers error
@@ -291,4 +280,267 @@ describe("StakeField", () => {
             expect(mockHideTooltip).toHaveBeenCalled();
         });
     });
+
+    describe("Error Callback", () => {
+        beforeEach(() => {
+            (
+                useOrientationStore as jest.MockedFunction<typeof useOrientationStore>
+            ).mockReturnValue({
+                isLandscape: true,
+            });
+        });
+
+        it("should call handleError with true and error message when value is empty", () => {
+            // Create a fresh mock for this test
+            const localMockHandleError = jest.fn();
+
+            // Set landscape mode for input field
+            (
+                useOrientationStore as jest.MockedFunction<typeof useOrientationStore>
+            ).mockReturnValue({
+                isLandscape: true,
+            });
+
+            // Render with our local mock
+            render(
+                <StakeField
+                    stake="100"
+                    setStake={mockSetStake}
+                    productConfig={{
+                        data: {
+                            validations: {
+                                stake: {
+                                    min: "1",
+                                    max: "50000",
+                                },
+                            },
+                        },
+                    }}
+                    currency="USD"
+                    handleError={localMockHandleError}
+                />
+            );
+
+            // Get the input field
+            const input = screen.getByLabelText("Stake amount");
+
+            // Create a proper change event
+            const changeEvent = {
+                target: {
+                    value: "",
+                    selectionStart: 0,
+                },
+            };
+
+            // Directly trigger the empty value validation
+            fireEvent.change(input, changeEvent);
+
+            // Verify our local mock was called with the correct parameters
+            expect(localMockHandleError).toHaveBeenCalledWith(true, "Please enter an amount");
+        });
+
+        it("should call handleError with true and error message when value is below minimum", () => {
+            renderWithProps({
+                productConfig: {
+                    data: {
+                        validations: {
+                            stake: {
+                                min: "10",
+                                max: "50000",
+                            },
+                        },
+                    },
+                },
+            });
+
+            // Clear mock calls from initial render
+            mockHandleError.mockClear();
+
+            // Trigger below minimum error
+            const input = screen.getByLabelText("Stake amount");
+            fireEvent.change(input, { target: { value: "5" } });
+
+            // Verify callback was called with correct params
+            expect(mockHandleError).toHaveBeenCalledWith(
+                true,
+                expect.stringContaining("Minimum stake")
+            );
+        });
+
+        it("should call handleError with true and error message when value is above maximum", () => {
+            // Clear mock calls before rendering
+            mockHandleError.mockClear();
+
+            renderWithProps({
+                productConfig: {
+                    data: {
+                        validations: {
+                            stake: {
+                                min: "1",
+                                max: "1000",
+                            },
+                        },
+                    },
+                },
+            });
+
+            // Trigger above maximum error
+            const input = screen.getByLabelText("Stake amount");
+            fireEvent.change(input, { target: { value: "2000" } });
+
+            // Verify callback was called with correct params - match the actual error message format
+            expect(mockHandleError).toHaveBeenCalledWith(
+                true,
+                expect.stringContaining("Minimum stake of 1 USD and maximum stake of 1000 USD")
+            );
+        });
+
+        it("should call handleError with false and null when value is valid", () => {
+            // Clear mock calls before rendering
+            mockHandleError.mockClear();
+
+            // Render with a starting value that's different from what we'll enter
+            renderWithProps({
+                stake: "50",
+            });
+
+            // Enter valid value that's different from the initial value
+            const input = screen.getByLabelText("Stake amount");
+            fireEvent.change(input, { target: { value: "100" } });
+
+            // Verify callback was called with correct params
+            expect(mockHandleError).toHaveBeenCalledWith(false, null);
+        });
+
+        it("should call handleError during increment/decrement operations", () => {
+            // Clear mock calls before rendering
+            mockHandleError.mockClear();
+
+            renderWithProps();
+
+            // Test increment - clear mock first to ensure we only capture the increment call
+            mockHandleError.mockClear();
+            fireEvent.click(screen.getByLabelText("Increase stake"));
+            expect(mockHandleError).toHaveBeenCalledWith(false, null);
+
+            // Test decrement - clear mock first to ensure we only capture the decrement call
+            mockHandleError.mockClear();
+            fireEvent.click(screen.getByLabelText("Decrease stake"));
+            expect(mockHandleError).toHaveBeenCalledWith(false, null);
+        });
+
+        it("should not throw errors when handleError is not provided", () => {
+            // Create props without handleError
+            const testProps = {
+                stake: "100",
+                setStake: mockSetStake,
+                productConfig: {
+                    data: {
+                        validations: {
+                            stake: {
+                                min: "1",
+                                max: "50000",
+                            },
+                        },
+                    },
+                },
+                currency: "USD",
+            };
+
+            // Render without handleError prop
+            render(<StakeField {...testProps} />);
+
+            // This should not throw an error
+            const input = screen.getByLabelText("Stake amount");
+            expect(() => {
+                fireEvent.change(input, { target: { value: "" } });
+            }).not.toThrow();
+        });
+    });
+
+    describe("Handler Props", () => {
+        it("should apply className when provided", () => {
+            renderWithProps({
+                className: "custom-class",
+            });
+
+            // In portrait mode, check TradeParam
+            expect(screen.getByTestId("trade-param").className).toContain("custom-class");
+
+            // In landscape mode, check container
+            (
+                useOrientationStore as jest.MockedFunction<typeof useOrientationStore>
+            ).mockReturnValue({
+                isLandscape: true,
+            });
+
+            renderWithProps({
+                className: "custom-class",
+            });
+
+            const container = screen.getByTestId("desktop-trade-field-card")
+                .firstChild as HTMLElement;
+            expect(container.className).toContain("custom-class");
+        });
+
+        it("should use custom onIncrement handler when provided", () => {
+            const mockIncrement = jest.fn();
+
+            (
+                useOrientationStore as jest.MockedFunction<typeof useOrientationStore>
+            ).mockReturnValue({
+                isLandscape: true,
+            });
+
+            renderWithProps({
+                onIncrement: mockIncrement,
+            });
+
+            // Click increment button
+            fireEvent.click(screen.getByLabelText("Increase stake"));
+
+            // Verify custom handler was called
+            expect(mockIncrement).toHaveBeenCalled();
+            // Default handler should not be called
+            expect(mockSetStake).not.toHaveBeenCalled();
+        });
+
+        it("should use custom onDecrement handler when provided", () => {
+            const mockDecrement = jest.fn();
+
+            (
+                useOrientationStore as jest.MockedFunction<typeof useOrientationStore>
+            ).mockReturnValue({
+                isLandscape: true,
+            });
+
+            renderWithProps({
+                onDecrement: mockDecrement,
+            });
+
+            // Click decrement button
+            fireEvent.click(screen.getByLabelText("Decrease stake"));
+
+            // Verify custom handler was called
+            expect(mockDecrement).toHaveBeenCalled();
+            // Default handler should not be called
+            expect(mockSetStake).not.toHaveBeenCalled();
+        });
+
+        it("should use custom onMobileClick handler when provided", () => {
+            const mockMobileClick = jest.fn();
+
+            renderWithProps({
+                onMobileClick: mockMobileClick,
+            });
+
+            // Click mobile card
+            fireEvent.click(screen.getByTestId("trade-param"));
+
+            // Verify custom handler was called
+            expect(mockMobileClick).toHaveBeenCalled();
+            // Default handler should not be called
+            expect(mockSetBottomSheet).not.toHaveBeenCalled();
+        });
+    });
 });
diff --git a/src/components/Stake/hooks/useStakeField.ts b/src/components/Stake/hooks/useStakeField.ts
index 4296bd17..e4cb41a7 100644
--- a/src/components/Stake/hooks/useStakeField.ts
+++ b/src/components/Stake/hooks/useStakeField.ts
@@ -1,17 +1,49 @@
 import { useState, useRef, useEffect } from "react";
-import { useTradeStore } from "@/stores/tradeStore";
-import { useClientStore } from "@/stores/clientStore";
+import { validateStake } from "../utils/validation";
 import { incrementStake, decrementStake, parseStakeAmount } from "@/utils/stake";
-import { getStakeConfig } from "@/adapters/stake-config-adapter";
 import { useBottomSheetStore } from "@/stores/bottomSheetStore";
 import { useTooltipStore } from "@/stores/tooltipStore";
-import { validateStake } from "../utils/validation";
 
-export const useStakeField = () => {
-    const { stake, setStake, isConfigLoading, productConfig } = useTradeStore();
-    const { currency } = useClientStore();
+interface UseStakeFieldParams {
+    stake: string;
+    setStake: (value: string) => void;
+    productConfig: any;
+    currency: string;
+    handleError?: (hasError: boolean, errorMessage: string | null) => void;
+}
+
+interface UseStakeFieldResult {
+    // State
+    isStakeSelected: boolean;
+    error: boolean;
+    errorMessage?: string;
+    localValue: string;
+    inputRef: React.RefObject<HTMLInputElement>;
+    containerRef: React.RefObject<HTMLDivElement>;
+
+    // Handlers
+    handleSelect: (selected: boolean) => void;
+    handleChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
+    defaultHandleIncrement: () => void;
+    defaultHandleDecrement: () => void;
+    defaultHandleMobileClick: () => void;
+    showError: (message: string) => void;
+    validateAndUpdateStake: (value: string) => boolean;
+}
+
+export const useStakeField = ({
+    stake,
+    setStake,
+    productConfig,
+    currency,
+    handleError,
+}: UseStakeFieldParams): UseStakeFieldResult => {
+    // isConfigLoading is used in the component but not in the hook
+    // We're keeping it in the interface for API consistency
     const { setBottomSheet } = useBottomSheetStore();
     const { showTooltip, hideTooltip } = useTooltipStore();
+
+    // Internal state
     const [isStakeSelected, setIsStakeSelected] = useState(false);
     const [error, setError] = useState(false);
     const [errorMessage, setErrorMessage] = useState<string>();
@@ -19,6 +51,7 @@ export const useStakeField = () => {
     const inputRef = useRef<HTMLInputElement>(null);
     const containerRef = useRef<HTMLDivElement>(null);
 
+    // Update local value when stake prop changes
     useEffect(() => {
         setLocalValue(stake);
     }, [stake]);
@@ -34,22 +67,35 @@ export const useStakeField = () => {
         if (!productConfig) return false;
 
         const amount = parseStakeAmount(value || "0");
+
+        // Use values from productConfig
+        const minStake = parseFloat(productConfig.data.validations.stake.min);
+        const maxStake = parseFloat(productConfig.data.validations.stake.max);
+
         const validation = validateStake({
             amount,
-            minStake: getStakeConfig().min,
-            maxStake: getStakeConfig().max,
+            minStake,
+            maxStake,
             currency,
         });
 
         setError(validation.error);
         setErrorMessage(validation.message);
+
+        // Call handleError callback if provided
+        if (handleError) {
+            handleError(validation.error, validation.error ? validation.message || null : null);
+        }
+
         if (validation.error && validation.message) {
             showError(validation.message);
         }
+
         return !validation.error;
     };
 
-    const handleIncrement = () => {
+    // Default handlers that can be overridden by props
+    const defaultHandleIncrement = () => {
         if (!productConfig) return;
 
         const newValue = incrementStake(stake || "0");
@@ -59,7 +105,7 @@ export const useStakeField = () => {
         }
     };
 
-    const handleDecrement = () => {
+    const defaultHandleDecrement = () => {
         if (!productConfig) return;
 
         const newValue = decrementStake(stake || "0");
@@ -69,6 +115,11 @@ export const useStakeField = () => {
         }
     };
 
+    const defaultHandleMobileClick = () => {
+        if (!productConfig) return;
+        setBottomSheet(true, "stake", "400px");
+    };
+
     const handleSelect = (selected: boolean) => {
         if (!productConfig) return;
 
@@ -122,6 +173,12 @@ export const useStakeField = () => {
             setErrorMessage(message);
             showError(message);
             setStake("");
+
+            // Call handleError callback if provided
+            if (handleError) {
+                handleError(true, message);
+            }
+
             return;
         }
 
@@ -142,26 +199,22 @@ export const useStakeField = () => {
         }
     };
 
-    const handleMobileClick = () => {
-        if (!productConfig) return;
-        setBottomSheet(true, "stake", "400px");
-    };
-
     return {
-        stake,
-        currency,
+        // State
         isStakeSelected,
-        isConfigLoading,
         error,
         errorMessage,
         localValue,
         inputRef,
         containerRef,
+
+        // Handlers
         handleSelect,
         handleChange,
-        handleIncrement,
-        handleDecrement,
-        handleMobileClick,
-        productConfig,
+        defaultHandleIncrement,
+        defaultHandleDecrement,
+        defaultHandleMobileClick,
+        showError,
+        validateAndUpdateStake,
     };
 };
diff --git a/src/hooks/product/useProductConfig.ts b/src/hooks/product/useProductConfig.ts
index 3a424fb2..e518ea50 100644
--- a/src/hooks/product/useProductConfig.ts
+++ b/src/hooks/product/useProductConfig.ts
@@ -12,36 +12,6 @@ import {
     adaptDefaultStake,
 } from "@/adapters/stake-config-adapter";
 
-// Default configuration to use as fallback
-const DEFAULT_CONFIG: ProductConfigResponse = {
-    data: {
-        defaults: {
-            product_id: "rise_fall",
-            duration: 60,
-            duration_unit: "seconds",
-            allow_equals: true,
-            stake: 10,
-            variants: ["rise", "fall"],
-        },
-        validations: {
-            durations: {
-                supported_units: ["ticks", "seconds", "days"],
-                ticks: { min: 1, max: 10 },
-                seconds: { min: 15, max: 86400 },
-                days: { min: 1, max: 365 },
-            },
-            stake: {
-                min: "1.00",
-                max: "50000.00",
-            },
-            payout: {
-                min: "1.00",
-                max: "50000.00",
-            },
-        },
-    },
-};
-
 interface ProductConfigParams {
     product_id: string;
     instrument_id: string;
@@ -56,7 +26,7 @@ export const useProductConfig = () => {
         setDuration,
         setStake,
         allowEquals,
-        toggleAllowEquals,
+        setAllowEquals,
         configCache,
         setConfigCache,
     } = useTradeStore();
@@ -82,20 +52,12 @@ export const useProductConfig = () => {
 
             // Set allow equals if different from current value
             if (allowEquals !== config.data.defaults.allow_equals) {
-                toggleAllowEquals();
+                setAllowEquals(config.data.defaults.allow_equals);
             }
         },
-        [setDuration, setStake, allowEquals, toggleAllowEquals]
+        [setDuration, setStake, allowEquals]
     );
 
-    // Apply fallback configuration
-    const applyFallbackConfig = useCallback(() => {
-        console.warn("Using fallback product configuration");
-        setProductConfig(DEFAULT_CONFIG);
-        setConfigLoading(false);
-        applyConfig(DEFAULT_CONFIG);
-    }, [applyConfig, setConfigLoading, setProductConfig]);
-
     // Handle successful product config fetch
     const handleConfigSuccess = useCallback(
         (config: ProductConfigResponse, product_id: string, instrument_id: string) => {
@@ -126,9 +88,9 @@ export const useProductConfig = () => {
             });
 
             // Apply fallback config
-            applyFallbackConfig();
+            // applyFallbackConfig();
         },
-        [applyFallbackConfig, setConfigError, setConfigLoading, toast]
+        [setConfigError, setConfigLoading, toast]
     );
 
     // Use mutation hook for product config
@@ -179,7 +141,6 @@ export const useProductConfig = () => {
     return {
         fetchProductConfig,
         resetProductConfig,
-        applyFallbackConfig,
         loading,
         error,
     };
diff --git a/src/hooks/proposal/useProposal.ts b/src/hooks/proposal/useProposal.ts
index dffbbbe8..228bf12c 100644
--- a/src/hooks/proposal/useProposal.ts
+++ b/src/hooks/proposal/useProposal.ts
@@ -1,22 +1,81 @@
 import { useSSESubscription } from "@/api/hooks";
 import { subscribeToProposalStream } from "@/api/services/proposal/proposal-sse";
 import { ProposalRequest, ProposalData } from "@/api/services/proposal/types";
+import { useTradeStore } from "@/stores/tradeStore";
+import { useClientStore } from "@/stores/clientStore";
+import { useMemo } from "react";
+
+/**
+ * Helper function to parse duration string into value and unit
+ * @param durationString Duration string from tradeStore (e.g., "5 minutes")
+ * @returns Tuple of [value, unit]
+ */
+const parseDuration = (durationString: string): [number, string] => {
+    const match = durationString.match(/(\d+)\s+(\w+)/);
+    if (match) {
+        const value = parseInt(match[1], 10);
+        const unit = match[2].toLowerCase();
+        return [value, unit];
+    }
+    return [0, ""];
+};
 
 /**
  * Hook for subscribing to proposal stream
- * @param params Proposal request parameters
  * @param options Additional options for the subscription
- * @returns Subscription result with proposal data
+ * @returns Subscription result with proposal data containing variants for rise and fall
  */
-export const useProposalStream = (
-    params: ProposalRequest,
-    options?: {
-        enabled?: boolean;
-    }
-) => {
-    return useSSESubscription<ProposalData>(
-        (onData: (data: ProposalData) => void, onError: (error: any) => void) =>
-            subscribeToProposalStream(params, { onData, onError }),
-        [params, options?.enabled]
+export const useProposalStream = (options?: { enabled?: boolean }) => {
+    // Get parameters directly from tradeStore
+    const { trade_type, instrument, stake, duration, allowEquals, productConfig } = useTradeStore();
+
+    // Get account_uuid from clientStore
+    const { account_uuid } = useClientStore();
+
+    // Parse duration into value and unit
+    const [durationValue, durationUnit] = parseDuration(duration);
+
+    // Default enabled to true if not provided, and ensure productConfig is available
+    const isEnabled =
+        (options?.enabled !== undefined ? options.enabled : true) &&
+        Boolean(productConfig?.data) &&
+        !!durationValue &&
+        !!durationUnit &&
+        !!stake;
+
+    // Create proposal parameters from store values
+    const proposalParams = useMemo<ProposalRequest>(
+        () => ({
+            product_id: trade_type,
+            instrument_id: instrument,
+            duration: durationValue,
+            duration_unit: durationUnit,
+            allow_equals: allowEquals,
+            stake: stake,
+            ...(account_uuid ? { account_uuid } : {}),
+        }),
+        [trade_type, instrument, durationValue, durationUnit, allowEquals, stake, account_uuid]
+    );
+
+    // ALWAYS call useSSESubscription, but conditionally create the subscription
+    const { data, error } = useSSESubscription<ProposalData>(
+        (onData: (data: ProposalData) => void, onError: (error: any) => void) => {
+            // Only create subscription if enabled
+            if (!isEnabled) {
+                return () => {}; // Return no-op cleanup function
+            }
+
+            // Use the current proposal parameters
+            return subscribeToProposalStream(proposalParams, { onData, onError });
+        },
+        // Include all parameters in the dependency array
+        [isEnabled, proposalParams]
     );
+
+    // If not enabled, return null data and error
+    if (!isEnabled) {
+        return { data: null, error: null };
+    }
+
+    return { data, error };
 };
diff --git a/src/screens/TradePage/components/TradeFormController.tsx b/src/screens/TradePage/components/TradeFormController.tsx
index e2f2eb13..52480de0 100644
--- a/src/screens/TradePage/components/TradeFormController.tsx
+++ b/src/screens/TradePage/components/TradeFormController.tsx
@@ -6,12 +6,14 @@ import { TradeButton } from "@/components/TradeButton";
 import { ResponsiveTradeParamLayout } from "@/components/ui/responsive-trade-param-layout";
 import { useTradeStore } from "@/stores/tradeStore";
 import { tradeTypeConfigs } from "@/config/tradeTypes";
-// import { useTradeActions } from "@/hooks/useTradeActions";
 import { useClientStore } from "@/stores/clientStore";
 import { HowToTrade } from "@/components/HowToTrade";
 import { TradeNotification } from "@/components/ui/trade-notification";
 import { AccountSwitcher } from "@/components/AccountSwitcher";
 import { useProductConfig } from "@/hooks/product/useProductConfig";
+import { useProposalStream } from "@/hooks/proposal/useProposal";
+import { validateStake } from "@/components/Stake/utils/validation";
+import { parseStakeAmount } from "@/utils/stake";
 
 // Lazy load components
 const DurationField = lazy(() =>
@@ -38,15 +40,94 @@ interface TradeFormControllerProps {
 
 interface ButtonState {
     loading: boolean;
-    error: Event | null;
+    error: Event | { error: string } | null;
     payout: number;
     reconnecting?: boolean;
+    validationError?: string | null;
 }
 
+interface ValidationResult {
+    error: boolean;
+    message?: string;
+}
+
+// Validate payout
+const validatePayout = (
+    payout: number,
+    minPayout: number,
+    maxPayout: number,
+    currency: string
+): ValidationResult => {
+    if (payout < minPayout) {
+        return {
+            error: true,
+            message: `Minimum payout is ${minPayout} ${currency}`,
+        };
+    }
+
+    if (payout > maxPayout) {
+        return {
+            error: true,
+            message: `Maximum payout is ${maxPayout} ${currency}`,
+        };
+    }
+
+    return { error: false };
+};
+
 type ButtonStates = Record<string, ButtonState>;
 
+// Validate trade parameters (stake and payout)
+const validateTradeParameters = (
+    buttonState: ButtonState | undefined,
+    stake: string,
+    productConfig: any,
+    currency: string
+): { isValid: boolean; errorMessage: string | null } => {
+    console.log({ stake });
+    if (!productConfig?.data) {
+        return { isValid: true, errorMessage: null }; // Default to valid if no config
+    }
+
+    const stakeValidation = productConfig.data.validations.stake;
+    const payoutValidation = productConfig.data.validations.payout;
+
+    if (!buttonState) {
+        return { isValid: false, errorMessage: "Button state not available" };
+    }
+
+    // Parse stake value
+    const stakeValue = parseStakeAmount(stake || "0");
+
+    // Validate stake
+    const stakeResult = validateStake({
+        amount: stakeValue,
+        minStake: parseFloat(stakeValidation.min),
+        maxStake: parseFloat(stakeValidation.max),
+        currency,
+    });
+
+    if (stakeResult.error) {
+        return { isValid: false, errorMessage: stakeResult.message || null };
+    }
+
+    // Validate payout
+    const payoutResult = validatePayout(
+        buttonState.payout,
+        parseFloat(payoutValidation.min),
+        parseFloat(payoutValidation.max),
+        currency
+    );
+
+    if (payoutResult.error) {
+        return { isValid: false, errorMessage: payoutResult.message || null };
+    }
+
+    return { isValid: true, errorMessage: null };
+};
+
 export const TradeFormController: React.FC<TradeFormControllerProps> = ({ isLandscape }) => {
-    const { trade_type, instrument } = useTradeStore();
+    const { trade_type, instrument, productConfig, setPayouts, stake, setStake } = useTradeStore();
     const { fetchProductConfig } = useProductConfig();
     const { setSidebar } = useMainLayoutStore();
     const { toast, hideToast } = useToastStore();
@@ -54,10 +135,18 @@ export const TradeFormController: React.FC<TradeFormControllerProps> = ({ isLand
     // const tradeActions = useTradeActions()
     const config = tradeTypeConfigs[trade_type];
 
+    // Track stake validation errors separately to persist them across payout updates
+    const [stakeValidationError, setStakeValidationError] = useState<string | null>(null);
+
+    // Parse duration into value and unit
+
+    // Subscribe to proposal stream at the top level of the component
+    const { data: proposalData, error: proposalError } = useProposalStream();
+
     const [buttonStates, setButtonStates] = useState<ButtonStates>(() => {
         // Initialize states for all buttons in the current trade type
         const initialStates: ButtonStates = {};
-        tradeTypeConfigs[trade_type].buttons.forEach((button) => {
+        config.buttons.forEach((button: any) => {
             initialStates[button.actionName] = {
                 loading: true,
                 error: null,
@@ -70,101 +159,28 @@ export const TradeFormController: React.FC<TradeFormControllerProps> = ({ isLand
 
     // Fetch product config when trade_type changes
     useEffect(() => {
-        fetchProductConfig(trade_type, instrument);
+        if (trade_type && instrument) {
+            fetchProductConfig(trade_type, instrument);
+        }
     }, [trade_type, instrument]);
 
-    // useEffect(() => {
-    //   // Create SSE connections for each button's contract type
-    //   const cleanupFunctions = tradeTypeConfigs[trade_type].buttons.map(
-    //     (button) => {
-    //       return createSSEConnection({
-    //         params: {
-    //           action: "contract_price",
-    //           duration: formatDuration(Number(apiDurationValue), apiDurationType),
-    //           trade_type: button.contractType,
-    //           instrument: "R_100",
-    //           currency: currency,
-    //           payout: stake,
-    //           strike: stake,
-    //         },
-    //         headers: token ? { Authorization: `Bearer ${token}` } : undefined,
-    //         onMessage: (priceData) => {
-    //           // Update button state for this specific button
-    //           setButtonStates((prev) => ({
-    //             ...prev,
-    //             [button.actionName]: {
-    //               loading: false,
-    //               error: null,
-    //               payout: Number(priceData.price),
-    //               reconnecting: false,
-    //             },
-    //           }))
-
-    //           // Update payouts in store
-    //           const payoutValue = Number(priceData.price)
-
-    //           // Create a map of button action names to their payout values
-    //           const payoutValues = Object.keys(buttonStates).reduce(
-    //             (acc, key) => {
-    //               acc[key] =
-    //                 key === button.actionName
-    //                   ? payoutValue
-    //                   : buttonStates[key]?.payout || 0
-    //               return acc
-    //             },
-    //             {} as Record<string, number>
-    //           )
-
-    //           setPayouts({
-    //             max: 50000,
-    //             values: payoutValues,
-    //           })
-    //         },
-    //         onError: (error) => {
-    //           // Update only this button's state on error
-    //           setButtonStates((prev) => ({
-    //             ...prev,
-    //             [button.actionName]: {
-    //               ...prev[button.actionName],
-    //               loading: false,
-    //               error,
-    //               reconnecting: true,
-    //             },
-    //           }))
-    //         },
-    //         onOpen: () => {
-    //           // Reset error and reconnecting state on successful connection
-    //           setButtonStates((prev) => ({
-    //             ...prev,
-    //             [button.actionName]: {
-    //               ...prev[button.actionName],
-    //               error: null,
-    //               reconnecting: false,
-    //             },
-    //           }))
-    //         },
-    //       })
-    //     }
-    //   )
-
-    //   return () => {
-    //     cleanupFunctions.forEach((cleanup) => cleanup())
-    //   }
-    // }, [duration, stake, currency, token])
-
-    // Reset loading states when duration or trade type changes
+    // Reset button states when trade type changes
     useEffect(() => {
-        const initialStates: ButtonStates = {};
-        tradeTypeConfigs[trade_type].buttons.forEach((button) => {
-            initialStates[button.actionName] = {
-                loading: false,
-                error: null,
-                payout: buttonStates[button.actionName]?.payout || 0,
-                reconnecting: false,
-            };
+        setButtonStates((prevStates) => {
+            const initialStates: ButtonStates = {};
+            config.buttons.forEach((button: any) => {
+                initialStates[button.actionName] = {
+                    loading: false,
+                    error: null,
+                    payout: prevStates[button.actionName]?.payout || 0,
+                    reconnecting: false,
+                    // Preserve any validation errors when trade type changes
+                    validationError: prevStates[button.actionName]?.validationError || null,
+                };
+            });
+            return initialStates;
         });
-        setButtonStates(initialStates);
-    }, [trade_type]);
+    }, [trade_type, config.buttons]);
 
     // Preload components based on metadata
     useEffect(() => {
@@ -182,6 +198,271 @@ export const TradeFormController: React.FC<TradeFormControllerProps> = ({ isLand
         }
     }, [trade_type, config]);
 
+    // Process proposal data and update button states
+    useEffect(() => {
+        if (!productConfig?.data) return;
+
+        // Set initial loading state for buttons when parameters change
+        if (!proposalData && !proposalError) {
+            setButtonStates((prevStates) => {
+                // Create a new state object based on previous state
+                const initialLoadingStates: ButtonStates = {};
+                config.buttons.forEach((button: any) => {
+                    // Preserve existing payout and validationError values from previous state
+                    initialLoadingStates[button.actionName] = {
+                        loading: true,
+                        error: null,
+                        payout: prevStates[button.actionName]?.payout || 0,
+                        reconnecting: false,
+                        // Also preserve any existing validation errors
+                        validationError: prevStates[button.actionName]?.validationError || null,
+                    };
+                });
+                return initialLoadingStates;
+            });
+            return;
+        }
+
+        // Update button states when data is received
+        if (proposalData) {
+            const { variants } = proposalData.data;
+
+            setButtonStates((prevStates) => {
+                // Create updated button states
+                const updatedButtonStates: ButtonStates = {};
+
+                // Map variants to buttons
+                config.buttons.forEach((button: any) => {
+                    // Find the matching variant for this button
+                    const variantType = button.actionName === "buy_rise" ? "rise" : "fall";
+                    const variant = variants.find((v) => v.variant === variantType);
+                    const payout = variant ? Number(variant.contract_details.payout) : 0;
+
+                    // Preserve stake validation error if it exists
+                    updatedButtonStates[button.actionName] = {
+                        loading: false,
+                        error: null,
+                        payout: payout || prevStates[button.actionName]?.payout,
+                        reconnecting: false,
+                        validationError: stakeValidationError, // Use the separate stake validation state
+                    };
+
+                    // If no stake error, check payout validation
+                    if (!stakeValidationError && productConfig?.data) {
+                        const payoutValidation = productConfig.data.validations.payout;
+                        const payoutResult = validatePayout(
+                            payout,
+                            parseFloat(payoutValidation.min),
+                            parseFloat(payoutValidation.max),
+                            currency
+                        );
+
+                        if (payoutResult.error) {
+                            updatedButtonStates[button.actionName].validationError =
+                                payoutResult.message || null;
+                        }
+                    }
+                });
+
+                return updatedButtonStates;
+            });
+
+            // Update payouts in store
+            const payoutValues = config.buttons.reduce(
+                (acc, button) => {
+                    const variantType = button.actionName === "buy_rise" ? "rise" : "fall";
+                    const variant = variants.find((v) => v.variant === variantType);
+                    acc[button.actionName] = variant ? Number(variant.contract_details.payout) : 0;
+                    return acc;
+                },
+                {} as Record<string, number>
+            );
+
+            // Set payouts in store
+            setPayouts({
+                max: productConfig?.data.validations.payout.max
+                    ? Number(productConfig.data.validations.payout.max)
+                    : 50000,
+                values: payoutValues,
+            });
+        }
+
+        // Handle errors
+        if (proposalError) {
+            // Update all buttons to show error state
+            setButtonStates((prevStates) => {
+                const errorButtonStates = { ...prevStates };
+                Object.keys(errorButtonStates).forEach((key) => {
+                    errorButtonStates[key] = {
+                        ...errorButtonStates[key],
+                        loading: false,
+                        error:
+                            proposalError instanceof Error
+                                ? { error: proposalError.message }
+                                : proposalError,
+                        reconnecting: true,
+                        // Preserve stake validation errors
+                        validationError: stakeValidationError,
+                    };
+                });
+                return errorButtonStates;
+            });
+        }
+    }, [
+        proposalData,
+        proposalError,
+        trade_type,
+        config.buttons,
+        productConfig,
+        stakeValidationError,
+        currency,
+    ]);
+
+    // Validate all trade parameters (stake and payout) for all buttons
+    const validateAllTradeParameters = () => {
+        if (!productConfig?.data) return;
+
+        const payoutValidation = productConfig.data.validations.payout;
+        const stakeValidation = productConfig.data.validations.stake;
+
+        // First validate stake if needed
+        if (!stakeValidationError && stake) {
+            const stakeValue = parseStakeAmount(stake || "0");
+            const stakeResult = validateStake({
+                amount: stakeValue,
+                minStake: parseFloat(stakeValidation.min),
+                maxStake: parseFloat(stakeValidation.max),
+                currency,
+            });
+
+            // If stake is invalid, update stake validation error
+            if (stakeResult.error) {
+                setStakeValidationError(stakeResult.message || null);
+
+                // Update button states with stake error
+                setButtonStates((prevStates) => {
+                    const updatedStates = { ...prevStates };
+                    Object.keys(updatedStates).forEach((buttonActionName) => {
+                        updatedStates[buttonActionName] = {
+                            ...updatedStates[buttonActionName],
+                            validationError: stakeResult.message || null,
+                        };
+                    });
+                    return updatedStates;
+                });
+
+                return; // Exit early if stake is invalid
+            }
+        }
+
+        // Then validate payout for each button
+        setButtonStates((prevStates) => {
+            const updatedStates = { ...prevStates };
+
+            // Check each button
+            Object.keys(updatedStates).forEach((buttonActionName) => {
+                const buttonState = updatedStates[buttonActionName];
+
+                // If there's a stake validation error, keep it
+                if (stakeValidationError) {
+                    updatedStates[buttonActionName] = {
+                        ...buttonState,
+                        validationError: stakeValidationError,
+                    };
+                    return;
+                }
+
+                // Otherwise, validate payout
+                const payoutResult = validatePayout(
+                    buttonState.payout,
+                    parseFloat(payoutValidation.min),
+                    parseFloat(payoutValidation.max),
+                    currency
+                );
+
+                if (payoutResult.error) {
+                    updatedStates[buttonActionName] = {
+                        ...buttonState,
+                        validationError: payoutResult.message || null,
+                    };
+                } else {
+                    // Clear validation error if both stake and payout are valid
+                    updatedStates[buttonActionName] = {
+                        ...buttonState,
+                        validationError: null,
+                    };
+                }
+            });
+
+            return updatedStates;
+        });
+    };
+
+    // Handle stake validation errors
+    const handleStakeError = (hasError: boolean, errorMessage: string | null) => {
+        // Update the separate stake validation state
+        setStakeValidationError(hasError ? errorMessage : null);
+
+        // Also update button states
+        setButtonStates((prevStates) => {
+            const updatedStates = { ...prevStates };
+
+            // Update all buttons with the stake validation error
+            Object.keys(updatedStates).forEach((buttonActionName) => {
+                updatedStates[buttonActionName] = {
+                    ...updatedStates[buttonActionName],
+                    validationError: hasError ? errorMessage : null,
+                };
+            });
+
+            return updatedStates;
+        });
+
+        // If stake is now valid, re-validate payout
+        if (!hasError) {
+            // Use setTimeout to ensure state updates have completed
+            setTimeout(() => validateAllTradeParameters(), 0);
+        }
+    };
+
+    // Update payout validation errors when productConfig or currency changes
+    useEffect(() => {
+        if (!productConfig?.data) {
+            return;
+        }
+
+        const payoutValidation = productConfig.data.validations.payout;
+
+        setButtonStates((prevStates) => {
+            const updatedStates = { ...prevStates };
+
+            // Only validate payout for each button
+            Object.keys(updatedStates).forEach((buttonActionName) => {
+                const buttonState = updatedStates[buttonActionName];
+
+                // Skip if there's already a stake validation error
+                if (buttonState.validationError) return;
+
+                // Validate payout
+                const payoutResult = validatePayout(
+                    buttonState.payout,
+                    parseFloat(payoutValidation.min),
+                    parseFloat(payoutValidation.max),
+                    currency
+                );
+
+                if (payoutResult.error) {
+                    updatedStates[buttonActionName] = {
+                        ...buttonState,
+                        validationError: payoutResult.message || null,
+                    };
+                }
+            });
+
+            return updatedStates;
+        });
+    }, [productConfig, currency]); // Only run for productConfig and currency changes
+
     return (
         <div
             id="trade-section"
@@ -238,7 +519,15 @@ export const TradeFormController: React.FC<TradeFormControllerProps> = ({ isLand
                             )}
                             {config.fields.stake && (
                                 <Suspense fallback={<div>Loading stake field...</div>}>
-                                    <StakeField className="w-full" />
+                                    <StakeField
+                                        className="w-full"
+                                        stake={stake}
+                                        setStake={setStake}
+                                        productConfig={productConfig}
+                                        currency={currency}
+                                        isConfigLoading={!productConfig}
+                                        handleError={handleStakeError}
+                                    />
                                 </Suspense>
                             )}
                         </div>
@@ -256,13 +545,19 @@ export const TradeFormController: React.FC<TradeFormControllerProps> = ({ isLand
                                         buttonStates[button.actionName]?.loading
                                             ? "Loading..."
                                             : `${
-                                                  // added for demo proposes will change it to 0 once api is connected
-                                                  buttonStates[button.actionName]?.payout || 10
+                                                  buttonStates[button.actionName]?.payout ||
+                                                  (productConfig?.data.validations.payout.max
+                                                      ? Number(
+                                                            productConfig.data.validations.payout
+                                                                .max
+                                                        )
+                                                      : 0)
                                               } ${currency}`
                                     }
                                     title_position={button.position}
                                     disabled={
-                                        buttonStates[button.actionName]?.loading
+                                        buttonStates[button.actionName]?.loading ||
+                                        Boolean(buttonStates[button.actionName]?.validationError)
                                         // Commenting it as api is not working we'll enable it once api is working
                                         // buttonStates[button.actionName]?.error !== null
                                     }
@@ -271,7 +566,15 @@ export const TradeFormController: React.FC<TradeFormControllerProps> = ({ isLand
                                         // Commenting it as api is not working we'll enable it once api is working
                                         // buttonStates[button.actionName]?.reconnecting
                                     }
-                                    error={buttonStates[button.actionName]?.error}
+                                    error={
+                                        buttonStates[button.actionName]?.validationError
+                                            ? {
+                                                  error:
+                                                      buttonStates[button.actionName]
+                                                          ?.validationError || "",
+                                              }
+                                            : buttonStates[button.actionName]?.error
+                                    }
                                     onClick={() => {
                                         if (!isLoggedIn) return;
                                         // Comment out actual API call but keep the success flow
@@ -318,7 +621,14 @@ export const TradeFormController: React.FC<TradeFormControllerProps> = ({ isLand
                                 )}
                                 {config.fields.stake && (
                                     <Suspense fallback={<div>Loading stake field...</div>}>
-                                        <StakeField />
+                                        <StakeField
+                                            stake={stake}
+                                            setStake={setStake}
+                                            productConfig={productConfig}
+                                            currency={currency}
+                                            isConfigLoading={!productConfig}
+                                            handleError={handleStakeError}
+                                        />
                                     </Suspense>
                                 )}
                             </ResponsiveTradeParamLayout>
@@ -343,12 +653,19 @@ export const TradeFormController: React.FC<TradeFormControllerProps> = ({ isLand
                                         buttonStates[button.actionName]?.loading
                                             ? "Loading..."
                                             : `${
-                                                  buttonStates[button.actionName]?.payout || 10
+                                                  buttonStates[button.actionName]?.payout ||
+                                                  (productConfig?.data.validations.payout.max
+                                                      ? Number(
+                                                            productConfig.data.validations.payout
+                                                                .max
+                                                        )
+                                                      : 0)
                                               } ${currency}`
                                     }
                                     title_position={button.position}
                                     disabled={
-                                        buttonStates[button.actionName]?.loading
+                                        buttonStates[button.actionName]?.loading ||
+                                        Boolean(buttonStates[button.actionName]?.validationError)
                                         // ||
                                         // Commenting it as api is not working we'll enable it once api is working
                                         // buttonStates[button.actionName]?.error !== null
@@ -359,7 +676,15 @@ export const TradeFormController: React.FC<TradeFormControllerProps> = ({ isLand
                                         // Commenting it as api is not working we'll enable it once api is working
                                         // buttonStates[button.actionName]?.reconnecting
                                     }
-                                    error={buttonStates[button.actionName]?.error}
+                                    error={
+                                        buttonStates[button.actionName]?.validationError
+                                            ? {
+                                                  error:
+                                                      buttonStates[button.actionName]
+                                                          ?.validationError || "",
+                                              }
+                                            : buttonStates[button.actionName]?.error
+                                    }
                                     onClick={() => {
                                         if (!isLoggedIn) return;
                                         // Comment out actual API call but keep the success flow
diff --git a/src/stores/tradeStore.ts b/src/stores/tradeStore.ts
index bee2a0a4..b85d4e43 100644
--- a/src/stores/tradeStore.ts
+++ b/src/stores/tradeStore.ts
@@ -105,8 +105,8 @@ interface TradeState {
 
 export const useTradeStore = create<TradeState>((set) => ({
     // Trade State
-    stake: "10",
-    duration: "5 minutes",
+    stake: "",
+    duration: "",
     allowEquals: false,
     trade_type: "rise_fall", // Default to rise_fall trade type
     tradeTypeDisplayName: "", // Initialize with empty string
diff --git a/src/utils/__tests__/stake.test.ts b/src/utils/__tests__/stake.test.ts
index fd6389b5..2a3c3103 100644
--- a/src/utils/__tests__/stake.test.ts
+++ b/src/utils/__tests__/stake.test.ts
@@ -1,4 +1,4 @@
-import { parseStakeAmount, validateStakeAmount, incrementStake, decrementStake } from "../stake";
+import { parseStakeAmount, incrementStake, decrementStake } from "../stake";
 import { getStakeConfig } from "@/adapters/stake-config-adapter";
 
 // Mock the stake-config-adapter
@@ -37,39 +37,6 @@ describe("stake utils", () => {
         });
     });
 
-    describe("validateStakeAmount", () => {
-        it("validates amount within range", () => {
-            expect(validateStakeAmount(50)).toBe(true);
-            expect(validateStakeAmount(1)).toBe(true);
-            expect(validateStakeAmount(100)).toBe(true);
-        });
-
-        it("invalidates amount below minimum", () => {
-            expect(validateStakeAmount(0)).toBe(false);
-            expect(validateStakeAmount(-1)).toBe(false);
-        });
-
-        it("invalidates amount above maximum", () => {
-            expect(validateStakeAmount(101)).toBe(false);
-            expect(validateStakeAmount(1000)).toBe(false);
-        });
-
-        it("uses config from adapter", () => {
-            (getStakeConfig as jest.Mock).mockReturnValue({
-                min: 5,
-                max: 50,
-                step: 1,
-            });
-
-            expect(validateStakeAmount(4)).toBe(false);
-            expect(validateStakeAmount(5)).toBe(true);
-            expect(validateStakeAmount(50)).toBe(true);
-            expect(validateStakeAmount(51)).toBe(false);
-
-            expect(getStakeConfig).toHaveBeenCalled();
-        });
-    });
-
     describe("incrementStake", () => {
         it("increments stake by step amount", () => {
             expect(incrementStake("50")).toBe("51");
diff --git a/src/utils/stake.ts b/src/utils/stake.ts
index 87fd1958..0bc27561 100644
--- a/src/utils/stake.ts
+++ b/src/utils/stake.ts
@@ -5,11 +5,6 @@ export const parseStakeAmount = (stake: string): number => {
     return Number(stake.includes(" ") ? stake.split(" ")[0] : stake);
 };
 
-export const validateStakeAmount = (amount: number): boolean => {
-    const config = getStakeConfig();
-    return amount >= config.min && amount <= config.max;
-};
-
 export const incrementStake = (currentStake: string): string => {
     const amount = parseStakeAmount(currentStake);
     const config = getStakeConfig();


Reviews:
user: login: devloai[bot]
body: I've completed a review of the stake proposal stream implementation. The code introduces a well-structured SSE connection for real-time proposal data, following the project's patterns. I've identified several areas for improvement including type inconsistencies, potential bugs in state management, and opportunities to enhance error handling and reconnection logic. Overall, the implementation is solid but would benefit from addressing these issues before merging. Feel free to provide any feedback or instructions for future reviews or tasks related to this repository.
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-11T12:34:35Z
user: login: sourcery-ai[bot]
body: Hey @aum-deriv - I've reviewed your changes - here's some feedback:

**Overall Comments**:
- Consider adding a cancellation mechanism to the `useProposalStream` hook to prevent memory leaks when the component unmounts.
- The `parseDuration` function could be improved by using a more robust regular expression or a dedicated library for parsing duration strings.

<details>
<summary>Here's what I looked at during the review</summary>

- 🟢 **General issues**: all looks good
- 🟢 **Security**: all looks good
- 🟢 **Testing**: all looks good
- 🟡 **Complexity**: 1 issue found
- 🟢 **Documentation**: all looks good
</details>

***

<details>
<summary>Sourcery is free for open source - if you like our reviews please consider sharing them ✨</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20got%20an%20instant%20code%20review%20from%20%40SourceryAI%2C%20and%20it%20was%20brilliant%21%20It%27s%20free%20for%20open%20source%20and%20has%20a%20free%20trial%20for%20private%20code.%20Check%20it%20out%20https%3A//sourcery.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20got%20an%20instant%20code%20review%20from%20%40SourceryAI%2C%20and%20it%20was%20brilliant%21%20It%27s%20free%20for%20open%20source%20and%20has%20a%20free%20trial%20for%20private%20code.%20Check%20it%20out%20https%3A//sourcery.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https://sourcery.ai)
- [Facebook](https://www.facebook.com/sharer/sharer.php?u=https://sourcery.ai)

</details>

<sub>
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
</sub>
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-14T08:09:24Z
user: login: vinu-deriv
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-17T10:15:57Z
user: login: shafin-deriv
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-17T10:17:16Z
user: login: shafin-deriv
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-17T10:17:48Z
user: login: aum-deriv
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-17T10:36:19Z
user: login: shafin-deriv
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-17T10:38:14Z

Review comments:
diff_hunk: @@ -0,0 +1,103 @@
+import { useState, useEffect } from "react";
+import { createSSEConnection } from "@/services/api/sse/createSSEConnection";
+import { ProposalStreamResponse } from "@/services/api/sse/types";
+
+/**
+ * Parameters for the useProposalStream hook
+ */
+interface UseProposalStreamParams {
+    /** Product ID (e.g., "rise_fall") */
+    product_id: string;
+    /** Instrument ID (e.g., "frxUSDJPY") */
+    instrument_id: string;
+    /** Duration value */
+    duration: number;
+    /** Duration unit (e.g., "seconds", "minutes") */
+    duration_unit: string;
+    /** Whether equals option is allowed */
+    allow_equals: boolean;
+    /** Stake amount */
path: src/hooks/useProposalStream.ts
user: login: devloai[bot]
body: The type for `allow_equals` in the interface is a boolean, but it's being converted to a string in the params on line 70. This could cause type confusion. Consider either making the interface parameter a string or handling the conversion more explicitly in the hook.
created_at: 2025-03-11T12:34:35Z
author_association: NONE
diff_hunk: @@ -0,0 +1,103 @@
+import { useState, useEffect } from "react";
+import { createSSEConnection } from "@/services/api/sse/createSSEConnection";
+import { ProposalStreamResponse } from "@/services/api/sse/types";
+
+/**
+ * Parameters for the useProposalStream hook
+ */
+interface UseProposalStreamParams {
+    /** Product ID (e.g., "rise_fall") */
+    product_id: string;
+    /** Instrument ID (e.g., "frxUSDJPY") */
+    instrument_id: string;
+    /** Duration value */
+    duration: number;
+    /** Duration unit (e.g., "seconds", "minutes") */
+    duration_unit: string;
+    /** Whether equals option is allowed */
+    allow_equals: boolean;
+    /** Stake amount */
+    stake: string;
+    /** Authentication token (optional) */
+    token?: string | null;
+    /** Account UUID (optional) */
+    account_uuid?: string;
+}
+
+/**
+ * Hook for connecting to the proposal stream API via SSE
+ *
+ * This hook establishes a Server-Sent Events connection to the proposal stream endpoint
+ * and provides real-time updates for trade proposals based on the provided parameters.
+ *
+ * @param params - Parameters for the proposal stream
+ * @returns Object containing data, loading state, and error state
+ *
+ * @example
+ * ```tsx
+ * const { data, loading, error } = useProposalStream({
+ *   product_id: "rise_fall",
+ *   instrument_id: "frxUSDJPY",
+ *   duration: 900,
+ *   duration_unit: "seconds",
+ *   allow_equals: false,
+ *   stake: "2.00",
+ *   token: authToken
+ * });
+ * ```
+ */
+export const useProposalStream = (params: UseProposalStreamParams) => {
+    const [data, setData] = useState<ProposalStreamResponse | null>(null);
+    const [loading, setLoading] = useState<boolean>(true);
+    const [error, setError] = useState<Error | null>(null);
+
+    useEffect(() => {
+        // Skip connection if essential parameters are missing
+        if (!params.product_id || !params.instrument_id || !params.stake) {
+            setLoading(false);
+            return;
+        }
+
+        setLoading(true);
+        setError(null);
+
+        const cleanup = createSSEConnection<ProposalStreamResponse>({
path: src/hooks/useProposalStream.ts
user: login: devloai[bot]
body: When a connection is established, `loading` state isn't explicitly reset if there are no messages received. Consider adding a timeout to set `loading` to false after a certain period if no message is received, to prevent the UI from showing a loading state indefinitely.
created_at: 2025-03-11T12:34:35Z
author_association: NONE
diff_hunk: @@ -58,7 +57,7 @@ export const createSSEConnection = (options: SSEOptions) => {
                     jsonString = dataLine.replace("data: ", "").trim();
                 }
 
-                const data = JSON.parse(jsonString) as ContractPriceResponse;
+                const data = JSON.parse(jsonString) as T;
path: src/services/api/sse/createSSEConnection.ts
user: login: devloai[bot]
body: The generic type `T` is correctly applied to the function signature, but there's no runtime type validation here. Consider adding a validation step or schema checking to ensure the parsed data matches the expected type structure, especially for critical data like contract proposals.
created_at: 2025-03-11T12:34:35Z
author_association: NONE
diff_hunk: @@ -154,12 +170,81 @@ export const TradeFormController: React.FC<TradeFormControllerProps> = ({ isLand
     //   }
     // }, [duration, stake, currency, token])
 
-    // Reset loading states when duration or trade type changes
+    // Update button states based on proposal stream data
+    useEffect(() => {
+        if (data) {
path: src/screens/TradePage/components/TradeFormController.tsx
user: login: devloai[bot]
body: This effect doesn't properly handle the case where the `variants` array might not include all possible button types. Consider adding a fallback that sets error states for buttons that don't have corresponding variants in the response data.
created_at: 2025-03-11T12:34:35Z
author_association: NONE
diff_hunk: @@ -0,0 +1,185 @@
+import { renderHook, act } from "@testing-library/react-hooks";
+import { useProposalStream } from "../useProposalStream";
+import { createSSEConnection } from "@/services/api/sse/createSSEConnection";
+import { ProposalStreamResponse } from "@/services/api/sse/types";
+
+// Mock the createSSEConnection function
+jest.mock("@/services/api/sse/createSSEConnection", () => ({
+    createSSEConnection: jest.fn(),
+}));
+
+describe("useProposalStream", () => {
+    // Mock cleanup function
+    const mockCleanup = jest.fn();
+
+    // Mock response data
+    const mockResponseData: ProposalStreamResponse = {
+        data: {
+            pricing_tick_id: "17112223333000",
+            variants: [
+                {
+                    variant: "rise",
+                    contract_details: {
+                        payout: "2.30",
+                        stake: "2.00",
+                        probability: 0.984,
+                        pricing_spot: "1.2345",
+                        barrier: "1.2345",
+                        allow_equals: false,
+                        contract_expiry_time: 1741669445330,
+                    },
+                },
+                {
+                    variant: "fall",
+                    contract_details: {
+                        payout: "2.30",
+                        stake: "2.00",
+                        probability: 0.984,
+                        pricing_spot: "1.2345",
+                        barrier: "1.2345",
+                        allow_equals: false,
+                        contract_expiry_time: 1741669445330,
+                    },
+                },
+            ],
+        },
+    };
+
+    // Default hook parameters
+    const defaultParams = {
+        product_id: "rise_fall",
+        instrument_id: "frxUSDJPY",
+        duration: 900,
+        duration_unit: "seconds",
+        allow_equals: false,
+        stake: "2.00",
+        token: "mock-token",
+    };
+
+    beforeEach(() => {
+        jest.clearAllMocks();
+        (createSSEConnection as jest.Mock).mockReturnValue(mockCleanup);
+    });
+
path: src/hooks/__tests__/useProposalStream.test.ts
user: login: devloai[bot]
body: The test verifies that the hook is called with correct parameters, but it doesn't test the automatic reconnection behavior when the network fails. Consider adding a test that simulates a network failure and checks if the connection is reestablished correctly.
created_at: 2025-03-11T12:34:35Z
author_association: NONE
diff_hunk: @@ -154,12 +170,81 @@ export const TradeFormController: React.FC<TradeFormControllerProps> = ({ isLand
     //   }
     // }, [duration, stake, currency, token])
 
-    // Reset loading states when duration or trade type changes
+    // Update button states based on proposal stream data
+    useEffect(() => {
+        if (data) {
+            const newButtonStates: ButtonStates = {};
+
+            // Map variants to buttons
+            data.data.variants.forEach((variant) => {
+                // Find the corresponding button for this variant
+                const button = tradeTypeConfigs[trade_type].buttons.find(
+                    (btn) =>
+                        (variant.variant === "rise" && btn.actionName === "buy_rise") ||
+                        (variant.variant === "fall" && btn.actionName === "buy_fall")
+                );
+
+                if (button) {
+                    newButtonStates[button.actionName] = {
+                        loading: false,
+                        error: null,
+                        payout: Number(variant.contract_details.payout),
+                        reconnecting: false,
+                    };
+                }
+            });
+
+            // Update button states with new data
+            setButtonStates((prev) => ({
+                ...prev,
+                ...newButtonStates,
+            }));
+        }
+    }, [data, trade_type]);
+
+    // Update loading states based on proposal stream loading state
+    useEffect(() => {
+        if (loading) {
+            // Set loading state for all buttons without referencing previous state directly
+            setButtonStates((prev) => {
+                const newState = { ...prev };
path: src/screens/TradePage/components/TradeFormController.tsx
user: login: devloai[bot]
body: You're creating a new state object with `newState = { ...prev }` but then modifying it directly in the forEach loop. This mutative approach could lead to subtle bugs. Consider using a more immutable pattern:

```typescript
setButtonStates((prev) => {
    const newState = {};
    tradeTypeConfigs[trade_type].buttons.forEach((button) => {
        newState[button.actionName] = {
            ...prev[button.actionName],
            loading: false,
            error: {
                error: error instanceof Error ? error.message : String(error),
            } as WebSocketError,
        };
    });
    return { ...prev, ...newState };
});
```
created_at: 2025-03-11T12:34:35Z
author_association: NONE
diff_hunk: @@ -27,3 +27,34 @@ export interface ContractPriceResponse {
     account: null;
     transaction: null;
 }
+
path: src/services/api/sse/types.ts
user: login: devloai[bot]
body: The `pricing_tick_id` field appears as required in the response type, but it's marked as optional in the request type on line 9. This mismatch could cause confusion. Consider either making it consistent in both places or documenting when/why it might be optional in the request but always present in the response.
created_at: 2025-03-11T12:34:35Z
author_association: NONE
diff_hunk: @@ -0,0 +1,103 @@
+import { useState, useEffect } from "react";
+import { createSSEConnection } from "@/services/api/sse/createSSEConnection";
+import { ProposalStreamResponse } from "@/services/api/sse/types";
+
+/**
+ * Parameters for the useProposalStream hook
+ */
+interface UseProposalStreamParams {
+    /** Product ID (e.g., "rise_fall") */
+    product_id: string;
+    /** Instrument ID (e.g., "frxUSDJPY") */
+    instrument_id: string;
+    /** Duration value */
+    duration: number;
+    /** Duration unit (e.g., "seconds", "minutes") */
+    duration_unit: string;
+    /** Whether equals option is allowed */
+    allow_equals: boolean;
+    /** Stake amount */
+    stake: string;
+    /** Authentication token (optional) */
+    token?: string | null;
+    /** Account UUID (optional) */
+    account_uuid?: string;
+}
+
+/**
+ * Hook for connecting to the proposal stream API via SSE
+ *
+ * This hook establishes a Server-Sent Events connection to the proposal stream endpoint
+ * and provides real-time updates for trade proposals based on the provided parameters.
+ *
+ * @param params - Parameters for the proposal stream
+ * @returns Object containing data, loading state, and error state
+ *
+ * @example
+ * ```tsx
+ * const { data, loading, error } = useProposalStream({
+ *   product_id: "rise_fall",
+ *   instrument_id: "frxUSDJPY",
+ *   duration: 900,
+ *   duration_unit: "seconds",
+ *   allow_equals: false,
+ *   stake: "2.00",
+ *   token: authToken
+ * });
+ * ```
+ */
+export const useProposalStream = (params: UseProposalStreamParams) => {
+    const [data, setData] = useState<ProposalStreamResponse | null>(null);
+    const [loading, setLoading] = useState<boolean>(true);
+    const [error, setError] = useState<Error | null>(null);
+
+    useEffect(() => {
+        // Skip connection if essential parameters are missing
+        if (!params.product_id || !params.instrument_id || !params.stake) {
path: src/hooks/useProposalStream.ts
user: login: devloai[bot]
body: The hook doesn't handle reconnection attempts for network issues. The `createSSEConnection` function has reconnection logic built in, but the state in this hook doesn't reflect reconnection attempts. Consider adding a reconnecting state that users of this hook can display to users.
created_at: 2025-03-11T12:34:35Z
author_association: NONE
diff_hunk: @@ -154,17 +192,19 @@ export const TradeFormController: React.FC<TradeFormControllerProps> = ({ isLand
 
     // Reset loading states when duration or trade type changes
     useEffect(() => {
-        const initialStates: ButtonStates = {};
-        tradeTypeConfigs[trade_type].buttons.forEach((button) => {
-            initialStates[button.actionName] = {
-                loading: false,
-                error: null,
-                payout: buttonStates[button.actionName]?.payout || 0,
-                reconnecting: false,
-            };
+        setButtonStates((prevStates) => {
+            const initialStates: ButtonStates = {};
+            config.buttons.forEach((button: any) => {
+                initialStates[button.actionName] = {
+                    loading: false,
+                    error: null,
+                    payout: prevStates[button.actionName]?.payout || 0,
+                    reconnecting: false,
+                };
+            });
path: src/screens/TradePage/components/TradeFormController.tsx
user: login: sourcery-ai[bot]
body: **issue (complexity):** Consider extracting the button state update logic into a helper function to avoid duplicated loops over `config.buttons` in multiple `useEffect` blocks, improving code maintainability and readability by centralizing state management..

Consider extracting and centralizing the button state update logic into a helper or custom hook so that you can remove the duplicated loops over `config.buttons` in multiple `useEffect` blocks. For example, you could define a helper that returns the initial or updated states and then call it from your effects.

**Create a helper:**  
```tsx
const getButtonStates = (
  buttons: any[],
  prevStates: ButtonStates = {},
  overrides: Partial<ButtonState> = {}
): ButtonStates =>
  buttons.reduce((acc, button) => {
    acc[button.actionName] = {
      loading: overrides.loading ?? false,
      error: null,
      payout: prevStates[button.actionName]?.payout || 0,
      reconnecting: false,
      ...overrides,
    };
    return acc;
  }, {} as ButtonStates);
```

**Usage in your effects:**  
```tsx
// In the trade_type update effect:
useEffect(() => {
  setButtonStates(getButtonStates(config.buttons, buttonStates));
}, [trade_type, config.buttons]);

// In the proposal update effect:
useEffect(() => {
  if (!isLoggedIn || !account_uuid) return;

  if (!proposalData && !proposalError) {
    setButtonStates(getButtonStates(config.buttons, buttonStates, { loading: true }));
    return;
  }

  if (proposalData) {
    const updatedStates: ButtonStates = {};
    config.buttons.forEach((button: any) => {
      const variantType = button.actionName === "buy_rise" ? "rise" : "fall";
      const variant = proposalData.data.variants.find(
        (v: any) => v.variant === variantType
      );
      updatedStates[button.actionName] = {
        loading: false,
        error: null,
        payout: variant ? Number(variant.contract_details.payout) : 0,
        reconnecting: false,
      };
    });
    setButtonStates(updatedStates);
  }

  if (proposalError) {
    setButtonStates((prevStates) =>
      getButtonStates(config.buttons, prevStates, {
        loading: false,
        reconnecting: true,
        error: proposalError instanceof Error ? { error: proposalError.message } : proposalError,
      })
    );
  }
}, [proposalData, proposalError, trade_type, isLoggedIn, account_uuid, config.buttons]);
```

This refactoring reduces duplication and helps isolate the button state logic, making the code more maintainable while keeping all features intact.
created_at: 2025-03-14T08:09:24Z
author_association: NONE
diff_hunk: @@ -182,6 +222,74 @@
         }
     }, [trade_type, config]);
 
+    // Process proposal data and update button states
+    useEffect(() => {
+        if (!isLoggedIn || !account_uuid) return;
path: src/screens/TradePage/components/TradeFormController.tsx
user: login: sourcery-ai[bot]
body: **suggestion (code-quality):** Use block braces for ifs, whiles, etc. ([`use-braces`](https://docs.sourcery.ai/Reference/Rules-and-In-Line-Suggestions/TypeScript/Default-Rules/use-braces))

```suggestion
        if (!isLoggedIn || !account_uuid) {
```

<br/><details><summary>Explanation</summary>It is recommended to always use braces and create explicit statement blocks.

Using the allowed syntax to just write a single statement can lead to very confusing
situations, especially where subsequently a developer might add another statement
while forgetting to add the braces (meaning that this wouldn't be included in the condition).
</details>
created_at: 2025-03-14T08:09:24Z
author_association: NONE
diff_hunk: @@ -182,6 +222,74 @@
         }
     }, [trade_type, config]);
 
+    // Process proposal data and update button states
+    useEffect(() => {
+        if (!isLoggedIn || !account_uuid) return;
+
+        // Set initial loading state for buttons when parameters change
+        if (!proposalData && !proposalError) {
+            setButtonStates((prevStates) => {
+                const initialLoadingStates: ButtonStates = {};
+                config.buttons.forEach((button: any) => {
+                    initialLoadingStates[button.actionName] = {
+                        loading: true,
+                        error: null,
+                        payout: prevStates[button.actionName]?.payout || 0,
+                        reconnecting: false,
+                    };
+                });
+                return initialLoadingStates;
+            });
+            return;
+        }
+
+        // Update button states when data is received
+        if (proposalData) {
+            const variants = proposalData.data.variants;
path: src/screens/TradePage/components/TradeFormController.tsx
user: login: sourcery-ai[bot]
body: **suggestion (code-quality):** Prefer object destructuring when accessing and using properties. ([`use-object-destructuring`](https://docs.sourcery.ai/Reference/Rules-and-In-Line-Suggestions/TypeScript/Default-Rules/use-object-destructuring))

```suggestion
            const {variants} = proposalData.data;
```

<br/><details><summary>Explanation</summary>Object destructuring can often remove an unnecessary temporary reference, as well as making your code more succinct.

From the [Airbnb Javascript Style Guide](https://airbnb.io/javascript/#destructuring--object)
</details>
created_at: 2025-03-14T08:09:24Z
author_association: NONE
diff_hunk: @@ -1,35 +1,209 @@
-import React from "react";
+import React, { useState, useRef, useEffect } from "react";
 import TradeParam from "@/components/TradeFields/TradeParam";
 import { Tooltip } from "@/components/ui/tooltip";
-import { useStakeField } from "./hooks/useStakeField";
 import { cn } from "@/lib/utils";
 import { useOrientationStore } from "@/stores/orientationStore";
 import { DesktopTradeFieldCard } from "@/components/ui/desktop-trade-field-card";
 import { MobileTradeFieldCard } from "@/components/ui/mobile-trade-field-card";
+import { validateStake } from "./utils/validation";
+import { incrementStake, decrementStake, parseStakeAmount } from "@/utils/stake";
+import { useBottomSheetStore } from "@/stores/bottomSheetStore";
+import { useTooltipStore } from "@/stores/tooltipStore";
 
 interface StakeFieldProps {
     className?: string;
+    // Value props
+    stake: string;
+    setStake: (value: string) => void;
+    // Config props
+    productConfig: any;
+    currency: string;
+    // Optional UI state props
+    isConfigLoading?: boolean;
+    // Optional handlers that can be overridden
+    onIncrement?: () => void;
+    onDecrement?: () => void;
+    onMobileClick?: () => void;
+    // Error handler callback
+    handleError?: (hasError: boolean, errorMessage: string | null) => void;
 }
 
-export const StakeField: React.FC<StakeFieldProps> = ({ className }) => {
+export const StakeField: React.FC<StakeFieldProps> = ({
+    className,
+    stake,
+    setStake,
+    productConfig,
+    currency,
+    isConfigLoading = false,
+    onIncrement,
+    onDecrement,
+    onMobileClick,
+    handleError,
+}) => {
     const { isLandscape } = useOrientationStore();
-    const {
-        stake,
-        currency,
-        error,
-        errorMessage,
-        localValue,
-        inputRef,
-        containerRef,
-        handleSelect,
-        handleChange,
-        handleIncrement,
-        handleDecrement,
-        handleMobileClick,
-        isConfigLoading,
-        isStakeSelected,
-        productConfig,
-    } = useStakeField();
+    const { setBottomSheet } = useBottomSheetStore();
+    const { showTooltip, hideTooltip } = useTooltipStore();
+
+    // Internal state
+    const [isStakeSelected, setIsStakeSelected] = useState(false);
+    const [error, setError] = useState(false);
+    const [errorMessage, setErrorMessage] = useState<string>();
+    const [localValue, setLocalValue] = useState(stake);
+    const inputRef = useRef<HTMLInputElement>(null);
+    const containerRef = useRef<HTMLDivElement>(null);
+
+    // Update local value when stake prop changes
+    useEffect(() => {
+        setLocalValue(stake);
+    }, [stake]);
+
+    const showError = (message: string) => {
+        if (containerRef.current) {
+            const rect = containerRef.current.getBoundingClientRect();
+            showTooltip(message, { x: rect.left - 8, y: rect.top + rect.height / 2 }, "error");
+        }
+    };
+
+    const validateAndUpdateStake = (value: string) => {
+        if (!productConfig) return false;
+
+        const amount = parseStakeAmount(value || "0");
+
+        // Use values from productConfig
+        const minStake = parseFloat(productConfig.data.validations.stake.min);
+        const maxStake = parseFloat(productConfig.data.validations.stake.max);
+
+        const validation = validateStake({
+            amount,
+            minStake,
+            maxStake,
+            currency,
+        });
+
+        setError(validation.error);
+        setErrorMessage(validation.message);
+
+        // Call handleError callback if provided
+        if (handleError) {
+            handleError(validation.error, validation.error ? validation.message || null : null);
+        }
+
+        if (validation.error && validation.message) {
+            showError(validation.message);
+        }
+
+        return !validation.error;
+    };
+
+    // Default handlers that can be overridden by props
+    const defaultHandleIncrement = () => {
+        if (!productConfig) return;
+
+        const newValue = incrementStake(stake || "0");
+        if (validateAndUpdateStake(newValue)) {
+            setStake(newValue);
+            hideTooltip();
+        }
+    };
+
+    const defaultHandleDecrement = () => {
+        if (!productConfig) return;
+
+        const newValue = decrementStake(stake || "0");
+        if (validateAndUpdateStake(newValue)) {
+            setStake(newValue);
+            hideTooltip();
+        }
+    };
+
+    const defaultHandleMobileClick = () => {
+        if (!productConfig) return;
+        setBottomSheet(true, "stake", "400px");
+    };
+
+    // Use provided handlers or defaults
+    const handleIncrement = onIncrement || defaultHandleIncrement;
+    const handleDecrement = onDecrement || defaultHandleDecrement;
+    const handleMobileClick = onMobileClick || defaultHandleMobileClick;
+
+    const handleSelect = (selected: boolean) => {
+        if (!productConfig) return;
+
+        setIsStakeSelected(selected);
+
+        // Show error tooltip if there's an error
+        if (error && errorMessage) {
+            showError(errorMessage);
+        }
+    };
+
+    const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
path: src/components/Stake/StakeField.tsx
user: login: shafin-deriv
body: @aum-deriv since the file is getting bigger, any chance we can take these validations out of this file and take it from any util function or custom hook (depending on the use case)? Just to keep the component readable.
created_at: 2025-03-17T10:17:15Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,35 +1,209 @@
-import React from "react";
+import React, { useState, useRef, useEffect } from "react";
 import TradeParam from "@/components/TradeFields/TradeParam";
 import { Tooltip } from "@/components/ui/tooltip";
-import { useStakeField } from "./hooks/useStakeField";
 import { cn } from "@/lib/utils";
 import { useOrientationStore } from "@/stores/orientationStore";
 import { DesktopTradeFieldCard } from "@/components/ui/desktop-trade-field-card";
 import { MobileTradeFieldCard } from "@/components/ui/mobile-trade-field-card";
+import { validateStake } from "./utils/validation";
+import { incrementStake, decrementStake, parseStakeAmount } from "@/utils/stake";
+import { useBottomSheetStore } from "@/stores/bottomSheetStore";
+import { useTooltipStore } from "@/stores/tooltipStore";
 
 interface StakeFieldProps {
     className?: string;
+    // Value props
+    stake: string;
+    setStake: (value: string) => void;
+    // Config props
+    productConfig: any;
+    currency: string;
+    // Optional UI state props
+    isConfigLoading?: boolean;
+    // Optional handlers that can be overridden
+    onIncrement?: () => void;
+    onDecrement?: () => void;
+    onMobileClick?: () => void;
+    // Error handler callback
+    handleError?: (hasError: boolean, errorMessage: string | null) => void;
 }
 
-export const StakeField: React.FC<StakeFieldProps> = ({ className }) => {
+export const StakeField: React.FC<StakeFieldProps> = ({
+    className,
+    stake,
+    setStake,
+    productConfig,
+    currency,
+    isConfigLoading = false,
+    onIncrement,
+    onDecrement,
+    onMobileClick,
+    handleError,
+}) => {
     const { isLandscape } = useOrientationStore();
-    const {
-        stake,
-        currency,
-        error,
-        errorMessage,
-        localValue,
-        inputRef,
-        containerRef,
-        handleSelect,
-        handleChange,
-        handleIncrement,
-        handleDecrement,
-        handleMobileClick,
-        isConfigLoading,
-        isStakeSelected,
-        productConfig,
-    } = useStakeField();
+    const { setBottomSheet } = useBottomSheetStore();
+    const { showTooltip, hideTooltip } = useTooltipStore();
+
+    // Internal state
+    const [isStakeSelected, setIsStakeSelected] = useState(false);
+    const [error, setError] = useState(false);
+    const [errorMessage, setErrorMessage] = useState<string>();
+    const [localValue, setLocalValue] = useState(stake);
+    const inputRef = useRef<HTMLInputElement>(null);
+    const containerRef = useRef<HTMLDivElement>(null);
+
+    // Update local value when stake prop changes
+    useEffect(() => {
+        setLocalValue(stake);
+    }, [stake]);
+
+    const showError = (message: string) => {
+        if (containerRef.current) {
+            const rect = containerRef.current.getBoundingClientRect();
+            showTooltip(message, { x: rect.left - 8, y: rect.top + rect.height / 2 }, "error");
+        }
+    };
+
+    const validateAndUpdateStake = (value: string) => {
path: src/components/Stake/StakeField.tsx
user: login: shafin-deriv
body: Same comment as the handleChange 🙏 
created_at: 2025-03-17T10:17:48Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,35 +1,209 @@
-import React from "react";
+import React, { useState, useRef, useEffect } from "react";
 import TradeParam from "@/components/TradeFields/TradeParam";
 import { Tooltip } from "@/components/ui/tooltip";
-import { useStakeField } from "./hooks/useStakeField";
 import { cn } from "@/lib/utils";
 import { useOrientationStore } from "@/stores/orientationStore";
 import { DesktopTradeFieldCard } from "@/components/ui/desktop-trade-field-card";
 import { MobileTradeFieldCard } from "@/components/ui/mobile-trade-field-card";
+import { validateStake } from "./utils/validation";
+import { incrementStake, decrementStake, parseStakeAmount } from "@/utils/stake";
+import { useBottomSheetStore } from "@/stores/bottomSheetStore";
+import { useTooltipStore } from "@/stores/tooltipStore";
 
 interface StakeFieldProps {
     className?: string;
+    // Value props
+    stake: string;
+    setStake: (value: string) => void;
+    // Config props
+    productConfig: any;
+    currency: string;
+    // Optional UI state props
+    isConfigLoading?: boolean;
+    // Optional handlers that can be overridden
+    onIncrement?: () => void;
+    onDecrement?: () => void;
+    onMobileClick?: () => void;
+    // Error handler callback
+    handleError?: (hasError: boolean, errorMessage: string | null) => void;
 }
 
-export const StakeField: React.FC<StakeFieldProps> = ({ className }) => {
+export const StakeField: React.FC<StakeFieldProps> = ({
+    className,
+    stake,
+    setStake,
+    productConfig,
+    currency,
+    isConfigLoading = false,
+    onIncrement,
+    onDecrement,
+    onMobileClick,
+    handleError,
+}) => {
     const { isLandscape } = useOrientationStore();
-    const {
-        stake,
-        currency,
-        error,
-        errorMessage,
-        localValue,
-        inputRef,
-        containerRef,
-        handleSelect,
-        handleChange,
-        handleIncrement,
-        handleDecrement,
-        handleMobileClick,
-        isConfigLoading,
-        isStakeSelected,
-        productConfig,
-    } = useStakeField();
+    const { setBottomSheet } = useBottomSheetStore();
+    const { showTooltip, hideTooltip } = useTooltipStore();
+
+    // Internal state
+    const [isStakeSelected, setIsStakeSelected] = useState(false);
+    const [error, setError] = useState(false);
+    const [errorMessage, setErrorMessage] = useState<string>();
+    const [localValue, setLocalValue] = useState(stake);
+    const inputRef = useRef<HTMLInputElement>(null);
+    const containerRef = useRef<HTMLDivElement>(null);
+
+    // Update local value when stake prop changes
+    useEffect(() => {
+        setLocalValue(stake);
+    }, [stake]);
+
+    const showError = (message: string) => {
+        if (containerRef.current) {
+            const rect = containerRef.current.getBoundingClientRect();
+            showTooltip(message, { x: rect.left - 8, y: rect.top + rect.height / 2 }, "error");
+        }
+    };
+
+    const validateAndUpdateStake = (value: string) => {
+        if (!productConfig) return false;
+
+        const amount = parseStakeAmount(value || "0");
+
+        // Use values from productConfig
+        const minStake = parseFloat(productConfig.data.validations.stake.min);
+        const maxStake = parseFloat(productConfig.data.validations.stake.max);
+
+        const validation = validateStake({
+            amount,
+            minStake,
+            maxStake,
+            currency,
+        });
+
+        setError(validation.error);
+        setErrorMessage(validation.message);
+
+        // Call handleError callback if provided
+        if (handleError) {
+            handleError(validation.error, validation.error ? validation.message || null : null);
+        }
+
+        if (validation.error && validation.message) {
+            showError(validation.message);
+        }
+
+        return !validation.error;
+    };
+
+    // Default handlers that can be overridden by props
+    const defaultHandleIncrement = () => {
+        if (!productConfig) return;
+
+        const newValue = incrementStake(stake || "0");
+        if (validateAndUpdateStake(newValue)) {
+            setStake(newValue);
+            hideTooltip();
+        }
+    };
+
+    const defaultHandleDecrement = () => {
+        if (!productConfig) return;
+
+        const newValue = decrementStake(stake || "0");
+        if (validateAndUpdateStake(newValue)) {
+            setStake(newValue);
+            hideTooltip();
+        }
+    };
+
+    const defaultHandleMobileClick = () => {
+        if (!productConfig) return;
+        setBottomSheet(true, "stake", "400px");
+    };
+
+    // Use provided handlers or defaults
+    const handleIncrement = onIncrement || defaultHandleIncrement;
+    const handleDecrement = onDecrement || defaultHandleDecrement;
+    const handleMobileClick = onMobileClick || defaultHandleMobileClick;
+
+    const handleSelect = (selected: boolean) => {
+        if (!productConfig) return;
+
+        setIsStakeSelected(selected);
+
+        // Show error tooltip if there's an error
+        if (error && errorMessage) {
+            showError(errorMessage);
+        }
+    };
+
+    const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
path: src/components/Stake/StakeField.tsx
user: login: aum-deriv
body: @shafin-deriv I have moved all the logic for this component to the unused hook `useStakeField`.
created_at: 2025-03-17T10:36:19Z
author_association: CONTRIBUTOR

PR comments:
user: login: sourcery-ai[bot]
created_at: 2025-03-11T12:32:54Z
body: <!-- Generated by sourcery-ai[bot]: start review_guide -->

## Reviewer's Guide by Sourcery

This pull request implements a stream of stake proposals based on the selected trade parameters and updates the UI with the received data. It introduces a new hook, `useProposalStream`, to manage the proposal subscription and updates the trade buttons with payout information from the proposal stream. The pull request also includes contract details in the proposal response and refactors stake validation logic.

#### Sequence diagram for proposal stream subscription

```mermaid
sequenceDiagram
    participant User
    participant TradeFormController
    participant useProposalStream
    participant subscribeToProposalStream
    participant Server

    User->>TradeFormController: Selects trade parameters
    TradeFormController->>useProposalStream: Passes proposalParams, isLoggedIn, account_uuid
    useProposalStream->>subscribeToProposalStream: Subscribes to proposal stream with params
    activate Server
    Server-->>subscribeToProposalStream: Sends proposal data
    deactivate Server
    subscribeToProposalStream-->>useProposalStream: Returns proposal data
    useProposalStream-->>TradeFormController: Updates proposalData state
    TradeFormController->>TradeFormController: Updates button states with payout information
```

### File-Level Changes

| Change | Details | Files |
| ------ | ------- | ----- |
| Implements a hook to subscribe to a stream of stake proposals based on trade parameters. | <ul><li>Introduces `useProposalStream` hook to manage the proposal subscription.</li><li>Adds `ProposalRequest`, `ProposalData`, `ContractDetails`, and `ProposalVariant` interfaces for proposal data types.</li><li>Updates `subscribeToProposalStream` to use a new API endpoint and include defined values in the parameters.</li><li>Modifies `TradeFormController` to use the `useProposalStream` hook and update button states based on the received proposal data.</li></ul> | `src/screens/TradePage/components/TradeFormController.tsx`<br/>`src/api/services/proposal/types.ts`<br/>`src/hooks/proposal/useProposal.ts`<br/>`src/api/services/proposal/proposal-sse.ts` |
| Updates the trade buttons with payout information received from the proposal stream. | <ul><li>Modifies `TradeFormController` to update button states with payout information from the proposal data.</li><li>Handles loading and error states for the trade buttons based on the proposal stream status.</li><li>Uses payout values from `productConfig` when proposal data is not available.</li></ul> | `src/screens/TradePage/components/TradeFormController.tsx` |
| Adds contract details to the proposal response. | <ul><li>Introduces `ContractDetails` interface to represent contract details in the proposal response.</li><li>Adds `contract_details` property to the `ProposalVariant` interface.</li></ul> | `src/api/services/proposal/types.ts` |
| Refactors stake validation logic. | <ul><li>Removes `validateStakeAmount` function.</li><li>Updates `useStakeField` hook to use stake values directly from `productConfig`.</li></ul> | `src/utils/__tests__/stake.test.ts`<br/>`src/components/Stake/hooks/useStakeField.ts`<br/>`src/utils/stake.ts` |

---

<details>
<summary>Tips and commands</summary>

#### Interacting with Sourcery

- **Trigger a new review:** Comment `@sourcery-ai review` on the pull request.
- **Continue discussions:** Reply directly to Sourcery's review comments.
- **Generate a GitHub issue from a review comment:** Ask Sourcery to create an
  issue from a review comment by replying to it. You can also reply to a
  review comment with `@sourcery-ai issue` to create an issue from it.
- **Generate a pull request title:** Write `@sourcery-ai` anywhere in the pull
  request title to generate a title at any time. You can also comment
  `@sourcery-ai title` on the pull request to (re-)generate the title at any time.
- **Generate a pull request summary:** Write `@sourcery-ai summary` anywhere in
  the pull request body to generate a PR summary at any time exactly where you
  want it. You can also comment `@sourcery-ai summary` on the pull request to
  (re-)generate the summary at any time.
- **Generate reviewer's guide:** Comment `@sourcery-ai guide` on the pull
  request to (re-)generate the reviewer's guide at any time.
- **Resolve all Sourcery comments:** Comment `@sourcery-ai resolve` on the
  pull request to resolve all Sourcery comments. Useful if you've already
  addressed all the comments and don't want to see them anymore.
- **Dismiss all Sourcery reviews:** Comment `@sourcery-ai dismiss` on the pull
  request to dismiss all existing Sourcery reviews. Especially useful if you
  want to start fresh with a new review - don't forget to comment
  `@sourcery-ai review` to trigger a new review!
- **Generate a plan of action for an issue:** Comment `@sourcery-ai plan` on
  an issue to generate a plan of action for it.

#### Customizing Your Experience

Access your [dashboard](https://app.sourcery.ai) to:
- Enable or disable review features such as the Sourcery-generated pull request
  summary, the reviewer's guide, and others.
- Change the review language.
- Add, remove or edit custom review instructions.
- Adjust other review settings.

#### Getting Help

- [Contact our support team](mailto:support@sourcery.ai) for questions or feedback.
- Visit our [documentation](https://docs.sourcery.ai) for detailed guides and information.
- Keep in touch with the Sourcery team by following us on [X/Twitter](https://x.com/SourceryAI), [LinkedIn](https://www.linkedin.com/company/sourcery-ai/) or [GitHub](https://github.com/sourcery-ai).

</details>

<!-- Generated by sourcery-ai[bot]: end review_guide -->
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-03-11T12:33:05Z
body: <h1>Dependency Review</h1>
✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.<h2>OpenSSF Scorecard</h2>
<table><tr><th>Package</th><th>Version</th><th>Score</th><th>Details</th></tr>
</table><h2>Scanned Manifest Files</h2>


<!-- dependency-review-pr-comment-marker -->
author_association: NONE
user: login: cloudflare-workers-and-pages[bot]
created_at: 2025-03-11T12:33:09Z
body: ## Deploying champion-trader with &nbsp;<a href="https://pages.dev"><img alt="Cloudflare Pages" src="https://user-images.githubusercontent.com/23264/106598434-9e719e00-654f-11eb-9e59-6167043cfa01.png" width="16"></a> &nbsp;Cloudflare Pages

<table><tr><td><strong>Latest commit:</strong> </td><td>
<code>8a55f22</code>
</td></tr>
<tr><td><strong>Status:</strong></td><td>&nbsp;✅&nbsp; Deploy successful!</td></tr>
<tr><td><strong>Preview URL:</strong></td><td>
<a href='https://8fa3ec07.champion-trader.pages.dev'>https://8fa3ec07.champion-trader.pages.dev</a>
</td></tr>
<tr><td><strong>Branch Preview URL:</strong></td><td>
<a href='https://aum-grwt-5204-stake-proposal.champion-trader.pages.dev'>https://aum-grwt-5204-stake-proposal.champion-trader.pages.dev</a>
</td></tr>
</table>

[View logs](https://dash.cloudflare.com/a88ddb4a494e6f543194cd549214286d/pages/view/champion-trader/8fa3ec07-5db1-4920-aac5-7b89d603e68c)

author_association: NONE
user: login: devloai[bot]
created_at: 2025-03-12T10:27:43Z
body: Unable to perform a code review. You have run out of credits 😔
author_association: NONE
user: login: aum-deriv
created_at: 2025-03-14T08:03:04Z
body: @sourcery-ai summary
author_association: CONTRIBUTOR
user: login: aum-deriv
created_at: 2025-03-17T10:08:19Z
body: @sourcery summary
author_association: CONTRIBUTOR