+
+
+ BOTBATTLES ARENA
+
+
+
+
+ {!isConnected ? (
+
+
+ Connect Your Wallet
+
+
+ Connect your StarkNet wallet to start placing bets on AI agents.
+
+
+
+ ) : (
+
+
+
+
+ Place Your Bets
+
+
+ {agents.map((agent) => (
+
setSelectedAgent(agent)}
+ >
+
+
+
+
+
+ {agent.name}
+
+
+
66
+ ? "bg-green-400"
+ : agent.performance > 33
+ ? "bg-yellow-400"
+ : "bg-red-400"
+ } rounded-full`}
+ style={{ width: `${agent.performance}%` }}
+ />
+
+
+
+ ))}
+
+
+ {selectedAgent && (
+
+
+ Betting on: {selectedAgent.name}
+
+
+
+
+
+ setBetAmount(e.target.value)}
+ min="0.001"
+ step="0.001"
+ className={`
+ bg-gray-700 border-2 border-gray-500
+ font-pixel text-white px-3 py-2 rounded
+ focus:border-green-400 focus:ring-1 focus:ring-green-400
+ focus:outline-none
+ hover:border-gray-400
+ shadow-[0_2px_4px_rgba(0,0,0,0.25)]
+ transition-all
+ w-full
+ `}
+ />
+
+
+
+
+ )}
+
+
+
+
+
+
+ Transaction History
+
+
+ {transactions.length > 0 ? (
+ transactions.map((tx) => (
+
+
+ {tx.status === "confirmed" ? (
+
+ ) : tx.status === "failed" ? (
+
+ ) : (
+
+ )}
+
+
+
+ {tx.message}
+
+
+
+ {tx.status === "pending"
+ ? "Processing..."
+ : tx.status === "confirmed"
+ ? "Confirmed"
+ : "Failed"}
+
+
+ {new Date(tx.timestamp).toLocaleTimeString()}
+
+
+ {tx.status === "confirmed" && (
+
+ Tx: {tx.hash.substring(0, 10)}...
+ {tx.hash.substring(tx.hash.length - 6)}
+
+ )}
+
+
+
+ ))
+ ) : (
+
+ No transactions yet. Place a bet to get started!
+
+ )}
+
+
+
+
+
+ Wallet Info
+
+
+
+ Address:{" "}
+ {address}
+
+
+ Network: StarkNet
+
+
+
+
+
+ )}
+
+ );
+}
diff --git a/frontend/src/components/starknet-provider.tsx b/frontend/src/components/starknet-provider.tsx
index 108b46c..4c42ffa 100644
--- a/frontend/src/components/starknet-provider.tsx
+++ b/frontend/src/components/starknet-provider.tsx
@@ -1,7 +1,7 @@
"use client";
import React from "react";
-import { sepolia} from "@starknet-react/chains";
+import { sepolia } from "@starknet-react/chains";
import {
StarknetConfig,
argent,
diff --git a/frontend/src/components/ui/button.tsx b/frontend/src/components/ui/button.tsx
index a2df8dc..2adaf00 100644
--- a/frontend/src/components/ui/button.tsx
+++ b/frontend/src/components/ui/button.tsx
@@ -1,8 +1,8 @@
-import * as React from "react"
-import { Slot } from "@radix-ui/react-slot"
-import { cva, type VariantProps } from "class-variance-authority"
+import * as React from "react";
+import { Slot } from "@radix-ui/react-slot";
+import { cva, type VariantProps } from "class-variance-authority";
-import { cn } from "@/lib/utils"
+import { cn } from "@/lib/utils";
const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
@@ -32,8 +32,8 @@ const buttonVariants = cva(
variant: "default",
size: "default",
},
- }
-)
+ },
+);
function Button({
className,
@@ -43,9 +43,9 @@ function Button({
...props
}: React.ComponentProps<"button"> &
VariantProps
& {
- asChild?: boolean
+ asChild?: boolean;
}) {
- const Comp = asChild ? Slot : "button"
+ const Comp = asChild ? Slot : "button";
return (
- )
+ );
}
-export { Button, buttonVariants }
+export { Button, buttonVariants };
diff --git a/frontend/src/components/ui/dialog.tsx b/frontend/src/components/ui/dialog.tsx
index 7d7a9d3..7d7783f 100644
--- a/frontend/src/components/ui/dialog.tsx
+++ b/frontend/src/components/ui/dialog.tsx
@@ -1,33 +1,33 @@
-"use client"
+"use client";
-import * as React from "react"
-import * as DialogPrimitive from "@radix-ui/react-dialog"
-import { XIcon } from "lucide-react"
+import * as React from "react";
+import * as DialogPrimitive from "@radix-ui/react-dialog";
+import { XIcon } from "lucide-react";
-import { cn } from "@/lib/utils"
+import { cn } from "@/lib/utils";
function Dialog({
...props
}: React.ComponentProps) {
- return
+ return ;
}
function DialogTrigger({
...props
}: React.ComponentProps) {
- return
+ return ;
}
function DialogPortal({
...props
}: React.ComponentProps) {
- return
+ return ;
}
function DialogClose({
...props
}: React.ComponentProps) {
- return
+ return ;
}
function DialogOverlay({
@@ -39,11 +39,11 @@ function DialogOverlay({
data-slot="dialog-overlay"
className={cn(
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
- className
+ className,
)}
{...props}
/>
- )
+ );
}
function DialogContent({
@@ -58,7 +58,7 @@ function DialogContent({
data-slot="dialog-content"
className={cn(
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg",
- className
+ className,
)}
{...props}
>
@@ -69,7 +69,7 @@ function DialogContent({
- )
+ );
}
function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
@@ -79,7 +79,7 @@ function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
className={cn("flex flex-col gap-2 text-center sm:text-left", className)}
{...props}
/>
- )
+ );
}
function DialogFooter({ className, ...props }: React.ComponentProps<"div">) {
@@ -88,11 +88,11 @@ function DialogFooter({ className, ...props }: React.ComponentProps<"div">) {
data-slot="dialog-footer"
className={cn(
"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
- className
+ className,
)}
{...props}
/>
- )
+ );
}
function DialogTitle({
@@ -105,7 +105,7 @@ function DialogTitle({
className={cn("text-lg leading-none font-semibold", className)}
{...props}
/>
- )
+ );
}
function DialogDescription({
@@ -118,7 +118,7 @@ function DialogDescription({
className={cn("text-muted-foreground text-sm", className)}
{...props}
/>
- )
+ );
}
export {
@@ -132,4 +132,4 @@ export {
DialogPortal,
DialogTitle,
DialogTrigger,
-}
+};
diff --git a/frontend/src/components/ui/dropdown-menu.tsx b/frontend/src/components/ui/dropdown-menu.tsx
index ec51e9c..1fc1f4e 100644
--- a/frontend/src/components/ui/dropdown-menu.tsx
+++ b/frontend/src/components/ui/dropdown-menu.tsx
@@ -1,15 +1,15 @@
-"use client"
+"use client";
-import * as React from "react"
-import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
-import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react"
+import * as React from "react";
+import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
+import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react";
-import { cn } from "@/lib/utils"
+import { cn } from "@/lib/utils";
function DropdownMenu({
...props
}: React.ComponentProps) {
- return
+ return ;
}
function DropdownMenuPortal({
@@ -17,7 +17,7 @@ function DropdownMenuPortal({
}: React.ComponentProps) {
return (
- )
+ );
}
function DropdownMenuTrigger({
@@ -28,7 +28,7 @@ function DropdownMenuTrigger({
data-slot="dropdown-menu-trigger"
{...props}
/>
- )
+ );
}
function DropdownMenuContent({
@@ -43,12 +43,12 @@ function DropdownMenuContent({
sideOffset={sideOffset}
className={cn(
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md",
- className
+ className,
)}
{...props}
/>
- )
+ );
}
function DropdownMenuGroup({
@@ -56,7 +56,7 @@ function DropdownMenuGroup({
}: React.ComponentProps) {
return (
- )
+ );
}
function DropdownMenuItem({
@@ -65,8 +65,8 @@ function DropdownMenuItem({
variant = "default",
...props
}: React.ComponentProps & {
- inset?: boolean
- variant?: "default" | "destructive"
+ inset?: boolean;
+ variant?: "default" | "destructive";
}) {
return (
- )
+ );
}
function DropdownMenuCheckboxItem({
@@ -93,7 +93,7 @@ function DropdownMenuCheckboxItem({
data-slot="dropdown-menu-checkbox-item"
className={cn(
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
- className
+ className,
)}
checked={checked}
{...props}
@@ -105,7 +105,7 @@ function DropdownMenuCheckboxItem({
{children}
- )
+ );
}
function DropdownMenuRadioGroup({
@@ -116,7 +116,7 @@ function DropdownMenuRadioGroup({
data-slot="dropdown-menu-radio-group"
{...props}
/>
- )
+ );
}
function DropdownMenuRadioItem({
@@ -129,7 +129,7 @@ function DropdownMenuRadioItem({
data-slot="dropdown-menu-radio-item"
className={cn(
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
- className
+ className,
)}
{...props}
>
@@ -140,7 +140,7 @@ function DropdownMenuRadioItem({
{children}
- )
+ );
}
function DropdownMenuLabel({
@@ -148,7 +148,7 @@ function DropdownMenuLabel({
inset,
...props
}: React.ComponentProps & {
- inset?: boolean
+ inset?: boolean;
}) {
return (
- )
+ );
}
function DropdownMenuSeparator({
@@ -173,7 +173,7 @@ function DropdownMenuSeparator({
className={cn("bg-border -mx-1 my-1 h-px", className)}
{...props}
/>
- )
+ );
}
function DropdownMenuShortcut({
@@ -185,17 +185,17 @@ function DropdownMenuShortcut({
data-slot="dropdown-menu-shortcut"
className={cn(
"text-muted-foreground ml-auto text-xs tracking-widest",
- className
+ className,
)}
{...props}
/>
- )
+ );
}
function DropdownMenuSub({
...props
}: React.ComponentProps) {
- return
+ return ;
}
function DropdownMenuSubTrigger({
@@ -204,7 +204,7 @@ function DropdownMenuSubTrigger({
children,
...props
}: React.ComponentProps & {
- inset?: boolean
+ inset?: boolean;
}) {
return (
{children}
- )
+ );
}
function DropdownMenuSubContent({
@@ -231,11 +231,11 @@ function DropdownMenuSubContent({
data-slot="dropdown-menu-sub-content"
className={cn(
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-lg",
- className
+ className,
)}
{...props}
/>
- )
+ );
}
export {
@@ -254,4 +254,4 @@ export {
DropdownMenuSub,
DropdownMenuSubTrigger,
DropdownMenuSubContent,
-}
+};
diff --git a/frontend/src/components/wallet-connect-button.tsx b/frontend/src/components/wallet-connect-button.tsx
index 4d8a3e8..55450ea 100644
--- a/frontend/src/components/wallet-connect-button.tsx
+++ b/frontend/src/components/wallet-connect-button.tsx
@@ -1,151 +1,151 @@
-"use client";
-
-import { useState, useEffect, useRef } from "react";
-import {
- useAccount,
- useConnect,
- useDisconnect,
- type Connector,
-} from "@starknet-react/core";
-import { Wallet } from "lucide-react";
-// import { Button } from "@/components/ui/button";
-import { WalletDropdown } from "./wallet-dropdown";
-import { truncateAddress } from "@/utils/wallet";
-import WalletConnectModal from "./wallet-connect-modal";
-
-export function WalletConnectButton() {
- const [isModalOpen, setIsModalOpen] = useState(false);
- const [isDropdownOpen, setIsDropdownOpen] = useState(false);
- const [isConnecting, setIsConnecting] = useState(false);
- const [connectionError, setConnectionError] = useState(null);
- const dropdownTimeoutRef = useRef(null);
-
- const { address, isConnected } = useAccount();
- const { connect, connectors } = useConnect();
- const { disconnect } = useDisconnect();
-
- const handleConnect = () => {
- if (isConnected) {
- setIsDropdownOpen(!isDropdownOpen);
- } else {
- setIsModalOpen(true);
- setConnectionError(null);
- }
- };
-
- const handleDisconnect = () => {
- disconnect();
- setIsDropdownOpen(false);
- localStorage.removeItem("lastUsedConnector");
- };
-
- const handleConnectorSelect = async (connector: Connector) => {
- if (!connector) return;
-
- setIsConnecting(true);
- setConnectionError(null);
-
- try {
- await connect({ connector });
-
- if (connector.id) {
- localStorage.setItem("lastUsedConnector", connector.id);
- }
-
- // Only close modal after successful connection
- setIsModalOpen(false);
- } catch (error) {
- const errorMessage =
- error instanceof Error ? error.message : "Failed to connect wallet";
- console.error("Wallet connection error:", errorMessage);
- setConnectionError(errorMessage);
- } finally {
- setIsConnecting(false);
- }
- };
-
- // Handle dropdown hover behavior
- const handleMouseEnter = () => {
- if (isConnected) {
- if (dropdownTimeoutRef.current) {
- clearTimeout(dropdownTimeoutRef.current);
- dropdownTimeoutRef.current = null;
- }
- setIsDropdownOpen(true);
- }
- };
-
- const handleMouseLeave = () => {
- if (dropdownTimeoutRef.current) {
- clearTimeout(dropdownTimeoutRef.current);
- }
-
- dropdownTimeoutRef.current = setTimeout(() => {
- setIsDropdownOpen(false);
- }, 300);
- };
-
- // Listen for wallet_disconnected events
- useEffect(() => {
- const handleWalletDisconnected = () => {
- setIsDropdownOpen(false);
- };
-
- window.addEventListener("wallet_disconnected", handleWalletDisconnected);
-
- return () => {
- window.removeEventListener(
- "wallet_disconnected",
- handleWalletDisconnected
- );
-
- if (dropdownTimeoutRef.current) {
- clearTimeout(dropdownTimeoutRef.current);
- }
- };
- }, []);
-
- return (
-
- {/* Connect Button */}
-
-
- {/* Wallet Connect Modal */}
- {
- if (!isConnecting) {
- setIsModalOpen(false);
- setConnectionError(null);
- }
- }}
- onSelect={async (walletId) => {
- const connector = connectors.find((c) => c.id === walletId);
- if (connector) {
- await handleConnectorSelect(connector);
- }
- }}
- />
-
- {/* Wallet Dropdown */}
- {isConnected && address && (
- setIsDropdownOpen(false)}
- address={address}
- onDisconnect={handleDisconnect}
- />
- )}
-
- );
-}
+"use client";
+
+import { useState, useEffect, useRef } from "react";
+import {
+ useAccount,
+ useConnect,
+ useDisconnect,
+ type Connector,
+} from "@starknet-react/core";
+import { Wallet } from "lucide-react";
+// import { Button } from "@/components/ui/button";
+import { WalletDropdown } from "./wallet-dropdown";
+import { truncateAddress } from "@/utils/wallet";
+import WalletConnectModal from "./wallet-connect-modal";
+
+export function WalletConnectButton() {
+ const [isModalOpen, setIsModalOpen] = useState(false);
+ const [isDropdownOpen, setIsDropdownOpen] = useState(false);
+ const [isConnecting, setIsConnecting] = useState(false);
+ const [connectionError, setConnectionError] = useState(null);
+ const dropdownTimeoutRef = useRef(null);
+
+ const { address, isConnected } = useAccount();
+ const { connect, connectors } = useConnect();
+ const { disconnect } = useDisconnect();
+
+ const handleConnect = () => {
+ if (isConnected) {
+ setIsDropdownOpen(!isDropdownOpen);
+ } else {
+ setIsModalOpen(true);
+ setConnectionError(null);
+ }
+ };
+
+ const handleDisconnect = () => {
+ disconnect();
+ setIsDropdownOpen(false);
+ localStorage.removeItem("lastUsedConnector");
+ };
+
+ const handleConnectorSelect = async (connector: Connector) => {
+ if (!connector) return;
+
+ setIsConnecting(true);
+ setConnectionError(null);
+
+ try {
+ await connect({ connector });
+
+ if (connector.id) {
+ localStorage.setItem("lastUsedConnector", connector.id);
+ }
+
+ // Only close modal after successful connection
+ setIsModalOpen(false);
+ } catch (error) {
+ const errorMessage =
+ error instanceof Error ? error.message : "Failed to connect wallet";
+ console.error("Wallet connection error:", errorMessage);
+ setConnectionError(errorMessage);
+ } finally {
+ setIsConnecting(false);
+ }
+ };
+
+ // Handle dropdown hover behavior
+ const handleMouseEnter = () => {
+ if (isConnected) {
+ if (dropdownTimeoutRef.current) {
+ clearTimeout(dropdownTimeoutRef.current);
+ dropdownTimeoutRef.current = null;
+ }
+ setIsDropdownOpen(true);
+ }
+ };
+
+ const handleMouseLeave = () => {
+ if (dropdownTimeoutRef.current) {
+ clearTimeout(dropdownTimeoutRef.current);
+ }
+
+ dropdownTimeoutRef.current = setTimeout(() => {
+ setIsDropdownOpen(false);
+ }, 300);
+ };
+
+ // Listen for wallet_disconnected events
+ useEffect(() => {
+ const handleWalletDisconnected = () => {
+ setIsDropdownOpen(false);
+ };
+
+ window.addEventListener("wallet_disconnected", handleWalletDisconnected);
+
+ return () => {
+ window.removeEventListener(
+ "wallet_disconnected",
+ handleWalletDisconnected,
+ );
+
+ if (dropdownTimeoutRef.current) {
+ clearTimeout(dropdownTimeoutRef.current);
+ }
+ };
+ }, []);
+
+ return (
+
+ {/* Connect Button */}
+
+
+ {/* Wallet Connect Modal */}
+ {
+ if (!isConnecting) {
+ setIsModalOpen(false);
+ setConnectionError(null);
+ }
+ }}
+ onSelect={async (walletId) => {
+ const connector = connectors.find((c) => c.id === walletId);
+ if (connector) {
+ await handleConnectorSelect(connector);
+ }
+ }}
+ />
+
+ {/* Wallet Dropdown */}
+ {isConnected && address && (
+ setIsDropdownOpen(false)}
+ address={address}
+ onDisconnect={handleDisconnect}
+ />
+ )}
+
+ );
+}
diff --git a/frontend/src/components/wallet-connect-modal.tsx b/frontend/src/components/wallet-connect-modal.tsx
index e215fc3..878824e 100644
--- a/frontend/src/components/wallet-connect-modal.tsx
+++ b/frontend/src/components/wallet-connect-modal.tsx
@@ -24,16 +24,15 @@ export default function WalletConnectModal({
onSelect, // Added this prop
}: WalletConnectModalProps) {
const [selectedWallet, setSelectedWallet] = useState(null);
- const { connectors } = useWalletContext();
+ const { connectors } = useWalletContext();
const handleSelect = (walletId: string) => {
setSelectedWallet(walletId);
};
-
// helper to get icon source
function getIconSource(
- icon: string | { dark: string; light: string }
+ icon: string | { dark: string; light: string },
): string {
if (typeof icon === "string") {
return icon;
diff --git a/frontend/src/components/wallet-dropdown.tsx b/frontend/src/components/wallet-dropdown.tsx
index 84bff46..2d333cd 100644
--- a/frontend/src/components/wallet-dropdown.tsx
+++ b/frontend/src/components/wallet-dropdown.tsx
@@ -1,101 +1,101 @@
-"use client";
-
-import { useRef, useEffect } from "react";
-import { LogOut, ExternalLink, Copy, CheckCircle2 } from "lucide-react";
-import { useState } from "react";
-import Link from "next/link";
-import { truncateAddress } from "@/utils/wallet";
-
-interface WalletDropdownProps {
- isOpen: boolean;
- onClose: () => void;
- address: string;
- onDisconnect: () => void;
-}
-
-export function WalletDropdown({
- isOpen,
- onClose,
- address,
- onDisconnect,
-}: WalletDropdownProps) {
- const dropdownRef = useRef(null);
- const [copied, setCopied] = useState(false);
-
- // Close dropdown when clicking outside
- useEffect(() => {
- const handleClickOutside = (event: MouseEvent) => {
- if (
- dropdownRef.current &&
- !dropdownRef.current.contains(event.target as Node)
- ) {
- onClose();
- }
- };
-
- if (isOpen) {
- document.addEventListener("mousedown", handleClickOutside);
- }
-
- return () => {
- document.removeEventListener("mousedown", handleClickOutside);
- };
- }, [isOpen, onClose]);
-
- const copyToClipboard = () => {
- navigator.clipboard.writeText(address);
- setCopied(true);
- setTimeout(() => setCopied(false), 2000);
- };
-
- if (!isOpen) return null;
-
- const explorerUrl = `https://voyager.online/contract/${address}`;
-
- return (
-
-
-
- Connected Wallet
-
-
-
- {truncateAddress(address)}
-
-
-
-
-
-
-
- View on Explorer
-
-
-
-
- );
-}
+"use client";
+
+import { useRef, useEffect } from "react";
+import { LogOut, ExternalLink, Copy, CheckCircle2 } from "lucide-react";
+import { useState } from "react";
+import Link from "next/link";
+import { truncateAddress } from "@/utils/wallet";
+
+interface WalletDropdownProps {
+ isOpen: boolean;
+ onClose: () => void;
+ address: string;
+ onDisconnect: () => void;
+}
+
+export function WalletDropdown({
+ isOpen,
+ onClose,
+ address,
+ onDisconnect,
+}: WalletDropdownProps) {
+ const dropdownRef = useRef(null);
+ const [copied, setCopied] = useState(false);
+
+ // Close dropdown when clicking outside
+ useEffect(() => {
+ const handleClickOutside = (event: MouseEvent) => {
+ if (
+ dropdownRef.current &&
+ !dropdownRef.current.contains(event.target as Node)
+ ) {
+ onClose();
+ }
+ };
+
+ if (isOpen) {
+ document.addEventListener("mousedown", handleClickOutside);
+ }
+
+ return () => {
+ document.removeEventListener("mousedown", handleClickOutside);
+ };
+ }, [isOpen, onClose]);
+
+ const copyToClipboard = () => {
+ navigator.clipboard.writeText(address);
+ setCopied(true);
+ setTimeout(() => setCopied(false), 2000);
+ };
+
+ if (!isOpen) return null;
+
+ const explorerUrl = `https://voyager.online/contract/${address}`;
+
+ return (
+
+
+
+ Connected Wallet
+
+
+
+ {truncateAddress(address)}
+
+
+
+
+
+
+
+ View on Explorer
+
+
+
+
+ );
+}
diff --git a/frontend/src/components/wallet-provider.tsx b/frontend/src/components/wallet-provider.tsx
index f6df863..4a68095 100644
--- a/frontend/src/components/wallet-provider.tsx
+++ b/frontend/src/components/wallet-provider.tsx
@@ -12,7 +12,7 @@ import {
useAccount,
useDisconnect,
Connector,
- ConnectVariables
+ ConnectVariables,
} from "@starknet-react/core";
interface WalletContextProps {
@@ -44,7 +44,7 @@ export const WalletProvider: React.FC<{ children: ReactNode }> = ({
(connector: Connector) => {
connect({ connector });
},
- [connect]
+ [connect],
);
// Save wallet address to localStorage when connected
@@ -71,7 +71,6 @@ export const WalletProvider: React.FC<{ children: ReactNode }> = ({
);
};
-
export const useWalletContext = () => {
const ctx = useContext(WalletContext);
if (!ctx) {
diff --git a/frontend/src/hooks/use-betting.tsx b/frontend/src/hooks/use-betting.tsx
index a81537e..25c90c5 100644
--- a/frontend/src/hooks/use-betting.tsx
+++ b/frontend/src/hooks/use-betting.tsx
@@ -1,82 +1,82 @@
-"use client";
-
-import { useState, useCallback } from "react";
-import { useAccount } from "@starknet-react/core";
-import { Transaction } from "../types/betting";
-import { placeBet as placeBetOnChain } from "../lib/starknet";
-
-export function useBetting() {
- const { address } = useAccount();
- const [transactions, setTransactions] = useState([]);
- const [isLoading, setIsLoading] = useState(false);
-
- const placeBet = useCallback(
- async (agentId: number, amount: string) => {
- if (!address) {
- throw new Error("Wallet not connected");
- }
-
- setIsLoading(true);
-
- try {
- // Create a pending transaction record
- const pendingTx: Transaction = {
- hash: "pending-" + Date.now(),
- status: "pending",
- message: `Placing bet of ${amount} STRK on agent #${agentId}...`,
- timestamp: Date.now(),
- };
-
- setTransactions((prev) => [pendingTx, ...prev]);
-
- // Call the StarkNet contract
- const result = await placeBetOnChain(agentId, amount);
-
- // Update the transaction status
- setTransactions((prev) =>
- prev.map((tx) =>
- tx.hash === pendingTx.hash
- ? {
- hash: result.transactionHash,
- status: "confirmed",
- message: `Successfully placed bet of ${amount} STRK on agent #${agentId}`,
- timestamp: Date.now(),
- }
- : tx
- )
- );
-
- return result;
- } catch (error) {
- console.error("Transaction failed:", error);
-
- // Update the transaction status to failed
- setTransactions((prev) =>
- prev.map((tx) =>
- tx.hash === "pending-" + Date.now().toString().slice(0, -3)
- ? {
- hash: "failed-" + Date.now(),
- status: "failed",
- message: `Failed to place bet: ${
- error instanceof Error ? error.message : "Unknown error"
- }`,
- timestamp: Date.now(),
- }
- : tx
- )
- );
-
- throw error;
- } finally {
- setIsLoading(false);
- }
- },
- [address]
- );
-
- return {
- transactions,
- isLoading,
- placeBet,
- };
-}
+"use client";
+
+import { useState, useCallback } from "react";
+import { useAccount } from "@starknet-react/core";
+import { Transaction } from "../types/betting";
+import { placeBet as placeBetOnChain } from "../lib/starknet";
+
+export function useBetting() {
+ const { address } = useAccount();
+ const [transactions, setTransactions] = useState([]);
+ const [isLoading, setIsLoading] = useState(false);
+
+ const placeBet = useCallback(
+ async (agentId: number, amount: string) => {
+ if (!address) {
+ throw new Error("Wallet not connected");
+ }
+
+ setIsLoading(true);
+
+ try {
+ // Create a pending transaction record
+ const pendingTx: Transaction = {
+ hash: "pending-" + Date.now(),
+ status: "pending",
+ message: `Placing bet of ${amount} STRK on agent #${agentId}...`,
+ timestamp: Date.now(),
+ };
+
+ setTransactions((prev) => [pendingTx, ...prev]);
+
+ // Call the StarkNet contract
+ const result = await placeBetOnChain(agentId, amount);
+
+ // Update the transaction status
+ setTransactions((prev) =>
+ prev.map((tx) =>
+ tx.hash === pendingTx.hash
+ ? {
+ hash: result.transactionHash,
+ status: "confirmed",
+ message: `Successfully placed bet of ${amount} STRK on agent #${agentId}`,
+ timestamp: Date.now(),
+ }
+ : tx,
+ ),
+ );
+
+ return result;
+ } catch (error) {
+ console.error("Transaction failed:", error);
+
+ // Update the transaction status to failed
+ setTransactions((prev) =>
+ prev.map((tx) =>
+ tx.hash === "pending-" + Date.now().toString().slice(0, -3)
+ ? {
+ hash: "failed-" + Date.now(),
+ status: "failed",
+ message: `Failed to place bet: ${
+ error instanceof Error ? error.message : "Unknown error"
+ }`,
+ timestamp: Date.now(),
+ }
+ : tx,
+ ),
+ );
+
+ throw error;
+ } finally {
+ setIsLoading(false);
+ }
+ },
+ [address],
+ );
+
+ return {
+ transactions,
+ isLoading,
+ placeBet,
+ };
+}
diff --git a/frontend/src/hooks/use-starknet-connect.tsx b/frontend/src/hooks/use-starknet-connect.tsx
index 81c7bd3..84c1550 100644
--- a/frontend/src/hooks/use-starknet-connect.tsx
+++ b/frontend/src/hooks/use-starknet-connect.tsx
@@ -1,51 +1,51 @@
-"use client";
-
-import { useCallback, useMemo } from "react";
-import {
- argent,
- braavos,
- useInjectedConnectors,
- voyager,
- publicProvider,
-} from "@starknet-react/core";
-import { mainnet, sepolia } from "@starknet-react/chains";
-
-/**
- * Hook to get Starknet connectors
- */
-export function useStarknetConnectors() {
- return useInjectedConnectors({
- recommended: [argent(), braavos()],
- includeRecommended: "onlyIfNoConnectors",
- order: "random",
- });
-}
-
-/**
- * Hook to get the complete Starknet configuration
- * Includes error handling for disconnection events
- */
-export function useStarknetConfig() {
- const { connectors } = useStarknetConnectors();
-
- // Handle wallet disconnection errors
- const handleDisconnectError = useCallback((error: Error) => {
- console.warn("Starknet disconnect error:", error);
- // Dispatch a custom event that our components can listen for
- window.dispatchEvent(new CustomEvent("wallet_disconnected"));
- // Return true to indicate the error was handled
- return true;
- }, []);
-
- return useMemo(
- () => ({
- chains: [mainnet, sepolia],
- provider: publicProvider(),
- connectors,
- explorer: voyager,
- autoConnect: true,
- onDisconnectError: handleDisconnectError,
- }),
- [connectors, handleDisconnectError]
- );
-}
+"use client";
+
+import { useCallback, useMemo } from "react";
+import {
+ argent,
+ braavos,
+ useInjectedConnectors,
+ voyager,
+ publicProvider,
+} from "@starknet-react/core";
+import { mainnet, sepolia } from "@starknet-react/chains";
+
+/**
+ * Hook to get Starknet connectors
+ */
+export function useStarknetConnectors() {
+ return useInjectedConnectors({
+ recommended: [argent(), braavos()],
+ includeRecommended: "onlyIfNoConnectors",
+ order: "random",
+ });
+}
+
+/**
+ * Hook to get the complete Starknet configuration
+ * Includes error handling for disconnection events
+ */
+export function useStarknetConfig() {
+ const { connectors } = useStarknetConnectors();
+
+ // Handle wallet disconnection errors
+ const handleDisconnectError = useCallback((error: Error) => {
+ console.warn("Starknet disconnect error:", error);
+ // Dispatch a custom event that our components can listen for
+ window.dispatchEvent(new CustomEvent("wallet_disconnected"));
+ // Return true to indicate the error was handled
+ return true;
+ }, []);
+
+ return useMemo(
+ () => ({
+ chains: [mainnet, sepolia],
+ provider: publicProvider(),
+ connectors,
+ explorer: voyager,
+ autoConnect: true,
+ onDisconnectError: handleDisconnectError,
+ }),
+ [connectors, handleDisconnectError],
+ );
+}
diff --git a/frontend/src/hooks/use-wallet-connection.tsx b/frontend/src/hooks/use-wallet-connection.tsx
index b609cb3..978194b 100644
--- a/frontend/src/hooks/use-wallet-connection.tsx
+++ b/frontend/src/hooks/use-wallet-connection.tsx
@@ -1,109 +1,109 @@
-"use client";
-
-import { useState, useCallback, useEffect } from "react";
-import {
- useAccount,
- useConnect,
- useDisconnect,
- type Connector,
-} from "@starknet-react/core";
-
-/**
- * Custom hook for managing wallet connection state and operations
- */
-export function useWalletConnection() {
- const [isConnecting, setIsConnecting] = useState(false);
- const [connectionError, setConnectionError] = useState(null);
- const { address, isConnected, isConnecting: isReconnecting } = useAccount();
- const { connect, connectors } = useConnect();
- const { disconnect } = useDisconnect();
-
- // Connect to wallet with error handling
- const connectWallet = useCallback(
- async (connector: Connector) => {
- if (!connector) return Promise.reject(new Error("No connector provided"));
-
- setIsConnecting(true);
- setConnectionError(null);
-
- try {
- await connect({ connector });
-
- // Save the last used connector for auto-connect
- if (connector.id) {
- localStorage.setItem("lastUsedConnector", connector.id);
- }
- return Promise.resolve();
- } catch (error) {
- const errorMessage =
- error instanceof Error ? error.message : "Failed to connect wallet";
- console.error("Wallet connection error:", errorMessage);
- setConnectionError(errorMessage);
- return Promise.reject(error);
- } finally {
- setIsConnecting(false);
- }
- },
- [connect]
- );
-
- // Disconnect wallet
- const disconnectWallet = useCallback(() => {
- disconnect();
- localStorage.removeItem("lastUsedConnector");
- }, [disconnect]);
-
- // Auto-connect on component mount
- const autoConnect = useCallback(() => {
- if (
- !isConnected &&
- !isReconnecting &&
- connectors &&
- connectors.length > 0
- ) {
- const lastConnector = localStorage.getItem("lastUsedConnector");
-
- if (lastConnector) {
- const connector = connectors.find((c) => c.id === lastConnector);
- if (connector) {
- connectWallet(connector).catch(() => {
- // Silent fail for auto-connect
- });
- }
- }
- }
- }, [connectWallet, connectors, isConnected, isReconnecting]);
-
- // Listen for wallet_disconnected events
- useEffect(() => {
- const handleWalletDisconnected = () => {
- // Clear any connection state
- localStorage.removeItem("lastUsedConnector");
- };
-
- window.addEventListener("wallet_disconnected", handleWalletDisconnected);
-
- return () => {
- window.removeEventListener(
- "wallet_disconnected",
- handleWalletDisconnected
- );
- };
- }, []);
-
- // Run auto-connect on component mount
- useEffect(() => {
- autoConnect();
- }, [autoConnect]);
-
- return {
- address,
- isConnected,
- isConnecting,
- connectionError,
- connectors,
- connectWallet,
- disconnectWallet,
- autoConnect,
- };
-}
+"use client";
+
+import { useState, useCallback, useEffect } from "react";
+import {
+ useAccount,
+ useConnect,
+ useDisconnect,
+ type Connector,
+} from "@starknet-react/core";
+
+/**
+ * Custom hook for managing wallet connection state and operations
+ */
+export function useWalletConnection() {
+ const [isConnecting, setIsConnecting] = useState(false);
+ const [connectionError, setConnectionError] = useState(null);
+ const { address, isConnected, isConnecting: isReconnecting } = useAccount();
+ const { connect, connectors } = useConnect();
+ const { disconnect } = useDisconnect();
+
+ // Connect to wallet with error handling
+ const connectWallet = useCallback(
+ async (connector: Connector) => {
+ if (!connector) return Promise.reject(new Error("No connector provided"));
+
+ setIsConnecting(true);
+ setConnectionError(null);
+
+ try {
+ await connect({ connector });
+
+ // Save the last used connector for auto-connect
+ if (connector.id) {
+ localStorage.setItem("lastUsedConnector", connector.id);
+ }
+ return Promise.resolve();
+ } catch (error) {
+ const errorMessage =
+ error instanceof Error ? error.message : "Failed to connect wallet";
+ console.error("Wallet connection error:", errorMessage);
+ setConnectionError(errorMessage);
+ return Promise.reject(error);
+ } finally {
+ setIsConnecting(false);
+ }
+ },
+ [connect],
+ );
+
+ // Disconnect wallet
+ const disconnectWallet = useCallback(() => {
+ disconnect();
+ localStorage.removeItem("lastUsedConnector");
+ }, [disconnect]);
+
+ // Auto-connect on component mount
+ const autoConnect = useCallback(() => {
+ if (
+ !isConnected &&
+ !isReconnecting &&
+ connectors &&
+ connectors.length > 0
+ ) {
+ const lastConnector = localStorage.getItem("lastUsedConnector");
+
+ if (lastConnector) {
+ const connector = connectors.find((c) => c.id === lastConnector);
+ if (connector) {
+ connectWallet(connector).catch(() => {
+ // Silent fail for auto-connect
+ });
+ }
+ }
+ }
+ }, [connectWallet, connectors, isConnected, isReconnecting]);
+
+ // Listen for wallet_disconnected events
+ useEffect(() => {
+ const handleWalletDisconnected = () => {
+ // Clear any connection state
+ localStorage.removeItem("lastUsedConnector");
+ };
+
+ window.addEventListener("wallet_disconnected", handleWalletDisconnected);
+
+ return () => {
+ window.removeEventListener(
+ "wallet_disconnected",
+ handleWalletDisconnected,
+ );
+ };
+ }, []);
+
+ // Run auto-connect on component mount
+ useEffect(() => {
+ autoConnect();
+ }, [autoConnect]);
+
+ return {
+ address,
+ isConnected,
+ isConnecting,
+ connectionError,
+ connectors,
+ connectWallet,
+ disconnectWallet,
+ autoConnect,
+ };
+}
diff --git a/frontend/src/lib/starknet.ts b/frontend/src/lib/starknet.ts
index d898e04..4a979de 100644
--- a/frontend/src/lib/starknet.ts
+++ b/frontend/src/lib/starknet.ts
@@ -1,82 +1,82 @@
-import { Agent } from "../types/betting";
-
-// Mock agents data
-const mockAgents: Agent[] = [
- {
- id: 1,
- name: "Mark Cuban",
- image: "/bet-image1.jpg",
- performance: 75,
- },
- {
- id: 2,
- name: "Batman",
- image: "/bet-image3.jpg",
- performance: 45,
- },
- {
- id: 3,
- name: "Literally a fish",
- image: "/bet-image2.jpg",
- performance: 90,
- },
-];
-
-// Get list of available agents
-export async function getAgents(): Promise {
- // In a real implementation, this would fetch agents from a backend or smart contract
- return new Promise((resolve) => {
- setTimeout(() => {
- resolve(mockAgents);
- }, 800);
- });
-}
-
-// Place a bet on an agent
-export async function placeBet(
- agentId: number,
- amount: string
-): Promise<{ transactionHash: string }> {
- // In a real implementation, this would call the StarkNet contract
-
- // This is where you would implement the actual StarkNet contract call
- // Example implementation (commented out as it requires actual contract):
- /*
- const contractAddress = "0x123..."; // Your contract address
- const contract = new Contract(abi, contractAddress, provider);
-
- // Convert amount to uint256
- const amountUint256 = uint256.bnToUint256(ethers.utils.parseEther(amount));
-
- // Prepare calldata
- const calldata = CallData.compile({
- agentId: agentId,
- amount: amountUint256
- });
-
- // Execute transaction
- const tx = await contract.invoke("placeBet", calldata);
-
- return {
- transactionHash: tx.transaction_hash
- };
- */
-
- // For now, we'll simulate a transaction with a delay
- return new Promise((resolve, reject) => {
- // Simulate transaction delay
- setTimeout(() => {
- // 90% chance of success
- if (Math.random() > 0.1) {
- resolve({
- transactionHash:
- "0x" +
- Math.random().toString(16).substring(2) +
- Math.random().toString(16).substring(2),
- });
- } else {
- reject(new Error("Transaction rejected by the network"));
- }
- }, 2000);
- });
-}
+import { Agent } from "../types/betting";
+
+// Mock agents data
+const mockAgents: Agent[] = [
+ {
+ id: 1,
+ name: "Mark Cuban",
+ image: "/bet-image1.jpg",
+ performance: 75,
+ },
+ {
+ id: 2,
+ name: "Batman",
+ image: "/bet-image3.jpg",
+ performance: 45,
+ },
+ {
+ id: 3,
+ name: "Literally a fish",
+ image: "/bet-image2.jpg",
+ performance: 90,
+ },
+];
+
+// Get list of available agents
+export async function getAgents(): Promise {
+ // In a real implementation, this would fetch agents from a backend or smart contract
+ return new Promise((resolve) => {
+ setTimeout(() => {
+ resolve(mockAgents);
+ }, 800);
+ });
+}
+
+// Place a bet on an agent
+export async function placeBet(
+ agentId: number,
+ amount: string,
+): Promise<{ transactionHash: string }> {
+ // In a real implementation, this would call the StarkNet contract
+
+ // This is where you would implement the actual StarkNet contract call
+ // Example implementation (commented out as it requires actual contract):
+ /*
+ const contractAddress = "0x123..."; // Your contract address
+ const contract = new Contract(abi, contractAddress, provider);
+
+ // Convert amount to uint256
+ const amountUint256 = uint256.bnToUint256(ethers.utils.parseEther(amount));
+
+ // Prepare calldata
+ const calldata = CallData.compile({
+ agentId: agentId,
+ amount: amountUint256
+ });
+
+ // Execute transaction
+ const tx = await contract.invoke("placeBet", calldata);
+
+ return {
+ transactionHash: tx.transaction_hash
+ };
+ */
+
+ // For now, we'll simulate a transaction with a delay
+ return new Promise((resolve, reject) => {
+ // Simulate transaction delay
+ setTimeout(() => {
+ // 90% chance of success
+ if (Math.random() > 0.1) {
+ resolve({
+ transactionHash:
+ "0x" +
+ Math.random().toString(16).substring(2) +
+ Math.random().toString(16).substring(2),
+ });
+ } else {
+ reject(new Error("Transaction rejected by the network"));
+ }
+ }, 2000);
+ });
+}
diff --git a/frontend/src/lib/utils.ts b/frontend/src/lib/utils.ts
index bd0c391..a5ef193 100644
--- a/frontend/src/lib/utils.ts
+++ b/frontend/src/lib/utils.ts
@@ -1,6 +1,6 @@
-import { clsx, type ClassValue } from "clsx"
-import { twMerge } from "tailwind-merge"
+import { clsx, type ClassValue } from "clsx";
+import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
- return twMerge(clsx(inputs))
+ return twMerge(clsx(inputs));
}
diff --git a/frontend/src/motion/background-particles.tsx b/frontend/src/motion/background-particles.tsx
index 83700af..9f34c88 100644
--- a/frontend/src/motion/background-particles.tsx
+++ b/frontend/src/motion/background-particles.tsx
@@ -19,4 +19,3 @@ const AnimatedBackground = () => {
);
};
export default AnimatedBackground;
-
diff --git a/frontend/src/motion/floating-particle.tsx b/frontend/src/motion/floating-particle.tsx
index 1b94c3c..715249d 100644
--- a/frontend/src/motion/floating-particle.tsx
+++ b/frontend/src/motion/floating-particle.tsx
@@ -16,4 +16,4 @@ export function FloatingParticles({ count = 50 }: { count?: number }) {
))}
);
-}
\ No newline at end of file
+}
diff --git a/frontend/src/types/betting.ts b/frontend/src/types/betting.ts
index ceef481..924a199 100644
--- a/frontend/src/types/betting.ts
+++ b/frontend/src/types/betting.ts
@@ -1,13 +1,13 @@
-export type Agent = {
- id: number;
- name: string;
- image: string;
- performance: number; // 0-100 scale for the colored bar
-};
-
-export type Transaction = {
- hash: string;
- status: "pending" | "confirmed" | "failed";
- message: string;
- timestamp: number;
-};
+export type Agent = {
+ id: number;
+ name: string;
+ image: string;
+ performance: number;
+};
+
+export type Transaction = {
+ hash: string;
+ status: "pending" | "confirmed" | "failed";
+ message: string;
+ timestamp: number;
+};
diff --git a/frontend/src/utils/wallet.ts b/frontend/src/utils/wallet.ts
index bbbdf7d..bcbec9d 100644
--- a/frontend/src/utils/wallet.ts
+++ b/frontend/src/utils/wallet.ts
@@ -1,13 +1,13 @@
-/**
- * Truncates an address for display purposes
- * @param address The full address to truncate
- * @returns The truncated address (e.g. 0x1234...5678)
- */
-export function truncateAddress(address: string): string {
- if (!address) return "";
- if (address.length <= 10) return address;
-
- return `${address.substring(0, 6)}...${address.substring(
- address.length - 4
- )}`;
-}
+/**
+ * Truncates an address for display purposes
+ * @param address The full address to truncate
+ * @returns The truncated address (e.g. 0x1234...5678)
+ */
+export function truncateAddress(address: string): string {
+ if (!address) return "";
+ if (address.length <= 10) return address;
+
+ return `${address.substring(0, 6)}...${address.substring(
+ address.length - 4,
+ )}`;
+}