From f51ba49435ca86f6d66840e491d1a064bad3aa4d Mon Sep 17 00:00:00 2001 From: Zintarh Date: Thu, 10 Jul 2025 11:41:05 +0100 Subject: [PATCH 1/7] feat: cancel pool logic --- src/app/hooks/useCancelPool.ts | 49 ++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 src/app/hooks/useCancelPool.ts diff --git a/src/app/hooks/useCancelPool.ts b/src/app/hooks/useCancelPool.ts new file mode 100644 index 0000000..98dbc10 --- /dev/null +++ b/src/app/hooks/useCancelPool.ts @@ -0,0 +1,49 @@ +import { ActionType } from "@/lib/types"; +import { myProvider } from "@/lib/utils"; +import { PREDIFI_CONTRACT_ADDRESS } from "@/static"; +import { useAccount } from "@starknet-react/core"; +import React, { useState } from "react"; +import toast from "react-hot-toast"; +import { cairo, CallData } from "starknet"; + +export default function useCancelPool(poolId: string) { + const { account } = useAccount(); + + const [cancelstatus, setCancelStatus] = useState("idle"); + + const cancelPool = async () => { + if (!account) { + return; + } + try { + setCancelStatus("pending"); + const result = await account.execute({ + contractAddress: PREDIFI_CONTRACT_ADDRESS, + entrypoint: "cancel_pool", + calldata: CallData.compile({ + pool_id: cairo.uint256(poolId), + }), + }); + + const status = await myProvider.waitForTransaction( + result.transaction_hash + ); + + if (status.isSuccess()) { + toast.success("Success! pool cancelled successfully."); + setCancelStatus("success"); + } + } catch (err) { + console.log(err); + setCancelStatus("error"); + toast.error("Error cancelling pool ."); + } finally { + setCancelStatus("idle"); + } + }; + + return { + cancelstatus, + cancelPool, + }; +} From be7ac9555a46d53c3ca1ae5665da5fe4a5d0ed2d Mon Sep 17 00:00:00 2001 From: Zintarh Date: Thu, 10 Jul 2025 11:42:00 +0100 Subject: [PATCH 2/7] feat: action type --- src/lib/types.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/types.ts b/src/lib/types.ts index 1dbc6a8..24c53e0 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -78,3 +78,4 @@ export type PoolDetails = { poolType: string; status: string; }; +export type ActionType = "idle" | "pending" | "success" | "error"; From 634e3973a32ca1542016ce09bf3cbcdd2a7e8e03 Mon Sep 17 00:00:00 2001 From: Zintarh Date: Thu, 10 Jul 2025 11:42:51 +0100 Subject: [PATCH 3/7] feat: implement cancel pool --- .../[id]/components/poolPrediction.tsx | 59 +++++++++++++------ src/app/dashboard/pool-market/[id]/page.tsx | 37 ++++++------ src/contexts/pool-creation-context.tsx | 3 +- 3 files changed, 61 insertions(+), 38 deletions(-) diff --git a/src/app/dashboard/pool-market/[id]/components/poolPrediction.tsx b/src/app/dashboard/pool-market/[id]/components/poolPrediction.tsx index c63ac9e..21caaef 100644 --- a/src/app/dashboard/pool-market/[id]/components/poolPrediction.tsx +++ b/src/app/dashboard/pool-market/[id]/components/poolPrediction.tsx @@ -1,20 +1,34 @@ -import { Button } from '@/components/ui/button'; -import { Input } from '@/components/ui/input'; -import Image from 'next/image'; -import React, { useState } from 'react'; +import useCancelPool from "@/app/hooks/useCancelPool"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { useAccount } from "@starknet-react/core"; +import Image from "next/image"; +import React, { useState } from "react"; interface Prediction { - name: string + name: string; + creator: string; + poolId: string; predictions: { options: string; odds: string; }[]; } -export default function PoolPrediction({ predictions, name }: Prediction) { - const [stake, setStake] = useState('0'); - const [selectedOption, setSelectedOption] = useState('Option 1'); - const [selectedOdds, setSelectedOdds] = useState('1.17'); +export default function PoolPrediction({ + predictions, + name, + creator, + poolId +}: Prediction) { + const [stake, setStake] = useState("0"); + const [selectedOption, setSelectedOption] = useState("Option 1"); + const [selectedOdds, setSelectedOdds] = useState("1.17"); + const { address } = useAccount(); + const creatorAddress = creator as `0x${string}`; + + const { cancelPool, cancelstatus } = useCancelPool(poolId); + return (
@@ -23,8 +37,8 @@ export default function PoolPrediction({ predictions, name }: Prediction) { ))} - {/* Stake input */} +
Stake @@ -54,7 +68,6 @@ export default function PoolPrediction({ predictions, name }: Prediction) {
- {/* Stake Preview */}

Preview

@@ -82,11 +95,23 @@ export default function PoolPrediction({ predictions, name }: Prediction) {
- + {address && creatorAddress === address && ( + + )} + + {!address && ( + + )} +
- {' '} + {" "} Once confirmed, this bet cannot be changed.
diff --git a/src/app/dashboard/pool-market/[id]/page.tsx b/src/app/dashboard/pool-market/[id]/page.tsx index 99ab7ac..f9144d1 100644 --- a/src/app/dashboard/pool-market/[id]/page.tsx +++ b/src/app/dashboard/pool-market/[id]/page.tsx @@ -4,10 +4,7 @@ import { useParams } from "next/navigation"; import { useContractFetch } from "@/app/hooks/useBlockchain"; import { PREDIFI_ABI } from "@/app/abi/predifi_abi"; import { GET_POOL } from "@/constants/functionNames"; -import { - PoolCardDetails, - PoolDescription, -} from "./components/poolDetails"; +import { PoolCardDetails, PoolDescription } from "./components/poolDetails"; import PoolPrediction from "./components/poolPrediction"; import SocialsShare from "./components/socialsShare"; import Comments from "@/components/ui/comments"; @@ -100,7 +97,6 @@ export default function Market() { ); } - return (
@@ -111,7 +107,6 @@ export default function Market() {
- {/* Section 1 */} {readIsLoading ? ( ) : readError ? ( @@ -212,19 +207,23 @@ export default function Market() {
- + {poolDetails && ( + + )}
); diff --git a/src/contexts/pool-creation-context.tsx b/src/contexts/pool-creation-context.tsx index 6388750..a96b903 100644 --- a/src/contexts/pool-creation-context.tsx +++ b/src/contexts/pool-creation-context.tsx @@ -57,7 +57,7 @@ export function PoolCreationProvider({ children }: { children: ReactNode }) { } try { setIsCreatingPool(true); - toast("heeeyyy"); + const result = await account.execute({ contractAddress: PREDIFI_CONTRACT_ADDRESS, entrypoint: "create_pool", @@ -86,7 +86,6 @@ export function PoolCreationProvider({ children }: { children: ReactNode }) { result.transaction_hash ); - console.log(status); if (status.isSuccess()) { toast.success("Success! 🎉 Your pool has been created."); setIsComplete(true); From fd77451bff70b8013c0e94381fb83fda081f4038 Mon Sep 17 00:00:00 2001 From: Zintarh Date: Tue, 29 Jul 2025 08:59:44 +0100 Subject: [PATCH 4/7] fix: ux fix --- .../[id]/components/CancelPool.tsx | 27 ++++++++ .../[id]/components/poolPrediction.tsx | 65 ++++--------------- src/app/hooks/useCancelPool.ts | 1 + 3 files changed, 39 insertions(+), 54 deletions(-) create mode 100644 src/app/dashboard/pool-market/[id]/components/CancelPool.tsx diff --git a/src/app/dashboard/pool-market/[id]/components/CancelPool.tsx b/src/app/dashboard/pool-market/[id]/components/CancelPool.tsx new file mode 100644 index 0000000..4744983 --- /dev/null +++ b/src/app/dashboard/pool-market/[id]/components/CancelPool.tsx @@ -0,0 +1,27 @@ +import useCancelPool from '@/app/hooks/useCancelPool'; +import { Button } from '@/components/ui/button'; +import { useAccount } from '@starknet-react/core'; + +interface CancelPoolProps { + poolId: string; + creator: string; +} + +export default function CancelPool({ poolId, creator }: CancelPoolProps) { + const { address } = useAccount(); + const creatorAddress = creator as `0x${string}`; + const { cancelPool, cancelstatus } = useCancelPool(poolId); + + if (!address || creatorAddress !== address) { + return null; + } + + return ( + + ); +} diff --git a/src/app/dashboard/pool-market/[id]/components/poolPrediction.tsx b/src/app/dashboard/pool-market/[id]/components/poolPrediction.tsx index cedb6d0..fa60d02 100644 --- a/src/app/dashboard/pool-market/[id]/components/poolPrediction.tsx +++ b/src/app/dashboard/pool-market/[id]/components/poolPrediction.tsx @@ -1,18 +1,10 @@ -<<<<<<< HEAD -import useCancelPool from "@/app/hooks/useCancelPool"; -import { Button } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; -import { useAccount } from "@starknet-react/core"; -import Image from "next/image"; -import React, { useState } from "react"; -======= import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { truncate } from '@/lib/utils'; import Image from 'next/image'; import React, { useState } from 'react'; import { FaSpinner } from 'react-icons/fa'; ->>>>>>> 55325bae59988d2b94495668222e4934c581a2a4 +import CancelPool from './CancelPool'; interface Prediction { name: string; @@ -28,37 +20,23 @@ interface Prediction { isParticipationLoading?: boolean; } -<<<<<<< HEAD -export default function PoolPrediction({ - predictions, - name, - creator, - poolId -}: Prediction) { - const [stake, setStake] = useState("0"); - const [selectedOption, setSelectedOption] = useState("Option 1"); - const [selectedOdds, setSelectedOdds] = useState("1.17"); - const { address } = useAccount(); - const creatorAddress = creator as `0x${string}`; - - const { cancelPool, cancelstatus } = useCancelPool(poolId); - -======= -export default function PoolPrediction({ predictions, name, isConnected, address, hasParticipatedAlready, isParticipationLoading }: Prediction) { +export default function PoolPrediction({ predictions, name, creator, poolId, isConnected, address, hasParticipatedAlready, isParticipationLoading }: Prediction) { const [stake, setStake] = useState('0'); const [selectedOption, setSelectedOption] = useState('Option 1'); const [selectedOdds, setSelectedOdds] = useState('1.17'); ->>>>>>> 55325bae59988d2b94495668222e4934c581a2a4 return (
-

Select Prediction

+
+

Select Prediction

+ +
{predictions.map((prediction, index) => ( ))} - + {/* Stake input */}
Stake @@ -88,6 +66,7 @@ export default function PoolPrediction({ predictions, name, isConnected, address
+ {/* Stake Preview */}

Preview

@@ -114,27 +93,6 @@ export default function PoolPrediction({ predictions, name, isConnected, address {+selectedOdds * +stake} strk
-<<<<<<< HEAD -
- {address && creatorAddress === address && ( - - )} - - {!address && ( - - )} - -
- {" "} - Once confirmed, this bet cannot be changed. -======= { isParticipationLoading ?
->>>>>>> 55325bae59988d2b94495668222e4934c581a2a4
: hasParticipatedAlready ? @@ -195,4 +152,4 @@ export function SelectPrediction({
{odds}
); -} +} \ No newline at end of file diff --git a/src/app/hooks/useCancelPool.ts b/src/app/hooks/useCancelPool.ts index 98dbc10..455564e 100644 --- a/src/app/hooks/useCancelPool.ts +++ b/src/app/hooks/useCancelPool.ts @@ -13,6 +13,7 @@ export default function useCancelPool(poolId: string) { const cancelPool = async () => { if (!account) { + toast.error("Account not connected!"); return; } try { From b2ed3deb81b6dcce3ddb528a9ef13f85f5154d8a Mon Sep 17 00:00:00 2001 From: Zintarh Date: Wed, 30 Jul 2025 09:17:15 +0100 Subject: [PATCH 5/7] feat: wallet balance --- src/app/hooks/useStakePool.ts | 58 +++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 src/app/hooks/useStakePool.ts diff --git a/src/app/hooks/useStakePool.ts b/src/app/hooks/useStakePool.ts new file mode 100644 index 0000000..ef6a2d8 --- /dev/null +++ b/src/app/hooks/useStakePool.ts @@ -0,0 +1,58 @@ +import { ActionType } from "@/lib/types"; +import { myProvider } from "@/lib/utils"; +import { PREDIFI_CONTRACT_ADDRESS } from "@/static"; +import { useAccount } from "@starknet-react/core"; +import { useState } from "react"; +import toast from "react-hot-toast"; +import { cairo, CallData } from "starknet"; + +export default function useStakePool(poolId: string) { + const { account } = useAccount(); + + const [stakeStatus, setStakeStatus] = useState("idle"); + + const stakeOnPool = async (amount: string) => { + if (!account) { + toast.error("Account not connected!"); + return; + } + + if (!amount || parseFloat(amount) <= 0) { + toast.error("Please enter a valid stake amount!"); + return; + } + + try { + setStakeStatus("pending"); + + const result = await account.execute({ + contractAddress: PREDIFI_CONTRACT_ADDRESS, + entrypoint: "stake", + calldata: CallData.compile({ + pool_id: cairo.uint256(poolId), + amount: cairo.uint256(amount), + }), + }); + + const status = await myProvider.waitForTransaction( + result.transaction_hash + ); + + if (status.isSuccess()) { + toast.success(`Success! Staked ${amount} STRK on the pool.`); + setStakeStatus("success"); + } + } catch (err) { + console.log(err); + setStakeStatus("error"); + toast.error("Error staking on pool. Please try again."); + } finally { + setStakeStatus("idle"); + } + }; + + return { + stakeStatus, + stakeOnPool, + }; +} From 8288c45d6f65165f4fc2a599c87e95a73df55cfe Mon Sep 17 00:00:00 2001 From: Zintarh Date: Wed, 30 Jul 2025 09:18:06 +0100 Subject: [PATCH 6/7] feat: integrate staking on pool --- src/app/hooks/useWalletBalance.ts | 68 +++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 src/app/hooks/useWalletBalance.ts diff --git a/src/app/hooks/useWalletBalance.ts b/src/app/hooks/useWalletBalance.ts new file mode 100644 index 0000000..1256454 --- /dev/null +++ b/src/app/hooks/useWalletBalance.ts @@ -0,0 +1,68 @@ +import { useState, useEffect } from 'react'; +import { useBalance } from '@starknet-react/core'; +import { constants } from 'starknet'; + +interface UseWalletBalanceProps { + address?: string; + enabled?: boolean; +} + +interface UseWalletBalanceReturn { + balance: number; + balanceFormatted: string; + isLoading: boolean; + error: Error | null; + refetch: () => void; +} + +export default function useWalletBalance({ + address, + enabled = true +}: UseWalletBalanceProps): UseWalletBalanceReturn { + const [balance, setBalance] = useState(0); + const [error, setError] = useState(null); + + // STRK token address (mainnet) + const STRK_TOKEN_ADDRESS = '0x04718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d'; + + const { + data: balanceData, + isLoading, + error: balanceError, + refetch + } = useBalance({ + address: address as `0x${string}`, + token: STRK_TOKEN_ADDRESS, + watch: true, + }); + + useEffect(() => { + if (balanceData?.value) { + try { + // Convert from wei to STRK (18 decimals) + const balanceInStrk = Number(balanceData.value) / 1e18; + setBalance(balanceInStrk); + setError(null); + } catch (err) { + setError(err as Error); + setBalance(0); + } + } else if (balanceError) { + setError(balanceError); + setBalance(0); + } + }, [balanceData, balanceError]); + + const balanceFormatted = balance.toLocaleString('en-US', { + minimumFractionDigits: 2, + maximumFractionDigits: 6 + }); + + return { + balance, + balanceFormatted, + isLoading, + error, + refetch: refetch || (() => {}), + }; +} From 5088314b44ff348c65c0d27f06e92dca34a2ed9a Mon Sep 17 00:00:00 2001 From: Zintarh Date: Wed, 30 Jul 2025 09:30:28 +0100 Subject: [PATCH 7/7] feat: implement staking --- .../pool-market/[id]/components/StakePool.tsx | 228 ++++++++++++++++++ src/app/dashboard/pool-market/[id]/page.tsx | 96 +++++--- 2 files changed, 293 insertions(+), 31 deletions(-) create mode 100644 src/app/dashboard/pool-market/[id]/components/StakePool.tsx diff --git a/src/app/dashboard/pool-market/[id]/components/StakePool.tsx b/src/app/dashboard/pool-market/[id]/components/StakePool.tsx new file mode 100644 index 0000000..41a6d6e --- /dev/null +++ b/src/app/dashboard/pool-market/[id]/components/StakePool.tsx @@ -0,0 +1,228 @@ +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { truncate } from '@/lib/utils'; +import React, { useState } from 'react'; +import { FaSpinner } from 'react-icons/fa'; +import useStakePool from '@/app/hooks/useStakePool'; +import useWalletBalance from '@/app/hooks/useWalletBalance'; +import ConnectWalletModal from '@/components/ConnectWalletModal'; + +interface StakePoolProps { + poolId: string; + isConnected?: boolean; + address?: string; + onStakeSuccess?: () => void; +} + +export default function StakePool({ + poolId, + isConnected, + address, + onStakeSuccess +}: StakePoolProps) { + const [stakeAmount, setStakeAmount] = useState(''); + const [showConnectModal, setShowConnectModal] = useState(false); + const { stakeStatus, stakeOnPool } = useStakePool(poolId); + + // Get wallet balance using the dedicated hook + const { balance: walletBalance, isLoading: balanceLoading } = useWalletBalance({ + address, + enabled: isConnected, + }); + + const handleInputChange = (e: React.ChangeEvent) => { + const value = e.target.value; + + // Allow empty string + if (value === '') { + setStakeAmount(''); + return; + } + + // Only allow valid number patterns (digits and single decimal point) + const numberRegex = /^\d*\.?\d*$/; + + if (numberRegex.test(value)) { + // Prevent multiple decimal points + const decimalCount = (value.match(/\./g) || []).length; + if (decimalCount <= 1) { + // Convert to number to check if it's positive + const numValue = parseFloat(value); + if (isNaN(numValue) || numValue >= 0) { + setStakeAmount(value); + } + } + } + }; + + const handleStake = async () => { + await stakeOnPool(stakeAmount); + if (onStakeSuccess) { + onStakeSuccess(); + } + // Clear the input after successful stake + setStakeAmount(''); + }; + + const isLoading = stakeStatus === 'pending'; + const stakeAmountNum = parseFloat(stakeAmount) || 0; + const isValidAmount = stakeAmount && stakeAmountNum > 0; + const isMinimumStake = stakeAmountNum >= 10; + const hasSufficientBalance = stakeAmountNum <= walletBalance; + const canStake = isValidAmount && isMinimumStake && hasSufficientBalance && isConnected; + + return ( +
+

+ Stake tokens on this pool without voting on a specific outcome. + Your stake will be distributed proportionally based on the final results. +

+ +
+
+ +
+ + {balanceLoading ? 'Loading...' : `${walletBalance.toFixed(2)} STRK`} + +
+
+ +
+
+
+
+
+ +
+ Enter amount to stake +
+
+
+
+ STRK +
+
+ Token +
+
+
+
+
+ +
+ {['10', '50', '100', '500'].map((amount) => ( + + ))} +
+
+ + {isConnected && stakeAmount && ( +
+ {!isMinimumStake && ( +
+ ⚠️ + Minimum stake is 10 STRK +
+ )} + {!hasSufficientBalance && ( +
+ ⚠️ + Insufficient balance. You have {walletBalance.toFixed(2)} STRK +
+ )} + {isValidAmount && isMinimumStake && hasSufficientBalance && ( +
+ + Ready to stake +
+ )} +
+ )} + + {/* Wallet Balance Display */} + {isConnected && ( +
+
+ Wallet Balance: + + {balanceLoading ? 'Loading...' : `${walletBalance.toFixed(2)} STRK`} + +
+
+ )} + + {/* Stake Button */} + {isLoading ? ( + + ) : !isConnected ? ( + + ) : !isValidAmount ? ( + + ) : !isMinimumStake ? ( + + ) : !hasSufficientBalance ? ( + + ) : ( + + )} + +
+ Stakes are locked until pool settlement +
+ + setShowConnectModal(false)} + /> +
+ ); +} diff --git a/src/app/dashboard/pool-market/[id]/page.tsx b/src/app/dashboard/pool-market/[id]/page.tsx index 190beef..d47c066 100644 --- a/src/app/dashboard/pool-market/[id]/page.tsx +++ b/src/app/dashboard/pool-market/[id]/page.tsx @@ -6,6 +6,7 @@ import { PREDIFI_ABI } from "@/app/abi/predifi_abi"; import { GET_POOL } from "@/constants/functionNames"; import { PoolCardDetails, PoolDescription } from "./components/poolDetails"; import PoolPrediction from "./components/poolPrediction"; +import StakePool from "./components/StakePool"; import SocialsShare from "./components/socialsShare"; import Comments from "@/components/ui/comments"; import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs"; @@ -36,11 +37,14 @@ export default function Market() { readData: pool, } = useContractFetch(PREDIFI_ABI, GET_POOL, [poolId]); - const { data: hasUserAlreadyParticipated, isLoading: isUserParticipatedLoading } = useGetUserParticiaptionInPool({ + const { + data: hasUserAlreadyParticipated, + isLoading: isUserParticipatedLoading, + } = useGetUserParticiaptionInPool({ enabled: isConnected, userAddress: address, - poolId: poolId - }) + poolId: poolId, + }); const [poolDetails, setPoolDetails] = useState(null); @@ -114,8 +118,8 @@ export default function Market() {
-
-
+
+
{readIsLoading ? ( ) : readError ? ( @@ -152,11 +156,37 @@ export default function Market() { {poolDetails?.option2} {poolDetails?.totalStakeOption2}
-
- Total stake distribution:{" "} - - {poolDetails?.totalBetAmountStrk} - +
+
+ Total stake distribution:{" "} + + {poolDetails?.totalBetAmountStrk} STRK + +
+ {poolDetails && ( + + + + + + Stake + + + + Stake on Pool + + { + // Optionally refresh pool data after successful stake + window.location.reload(); + }} + /> + + + )}
)} @@ -216,27 +246,31 @@ export default function Market() {
- {poolDetails && ( - - )} +
+ {poolDetails && ( + + )} + + +
);