profile data
This commit is contained in:
parent
145b7946f8
commit
dd303efd01
|
|
@ -2,6 +2,9 @@ import type { NextConfig } from "next";
|
|||
|
||||
const nextConfig: NextConfig = {
|
||||
/* config options here */
|
||||
images: {
|
||||
domains: ["pbs.twimg.com","vps.playpoolstudios.com"], // ✅ add Twitter's image domain
|
||||
},
|
||||
};
|
||||
|
||||
export default nextConfig;
|
||||
|
|
|
|||
|
|
@ -52,3 +52,45 @@ body {
|
|||
background-size: 50px 50px;
|
||||
animation: scrollGrid 2s linear infinite;
|
||||
}
|
||||
|
||||
/* Slide in from the top */
|
||||
@keyframes slide-down {
|
||||
0% {
|
||||
transform: translateY(-100%);
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
transform: translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Slide out to the top */
|
||||
@keyframes slide-up {
|
||||
0% {
|
||||
transform: translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
transform: translateY(-100%);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
@keyframes slide-down {
|
||||
0% {
|
||||
transform: translateY(-100%);
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
transform: translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-enter {
|
||||
animation: slide-down 0.2s ease-out;
|
||||
}
|
||||
|
||||
.modal-exit {
|
||||
animation: slide-up 0.2s ease-in;
|
||||
}
|
||||
|
|
@ -8,33 +8,36 @@ import { toSolanaWalletConnectors } from "@privy-io/react-auth/solana";
|
|||
import { Toaster } from "sonner";
|
||||
|
||||
export default function Home() {
|
||||
|
||||
return (
|
||||
|
||||
<div className="bg-[rgb(22,22,22)]">
|
||||
<PrivyProvider
|
||||
appId="cm8spd7l600lfe4am1phq9qq8"
|
||||
clientId="client-WY5i4HS6T7JP44iKMQUyZXwftzwKLFvEsGvMtFY1znXSj"
|
||||
config={{
|
||||
// Customize Privy's appearance in your app
|
||||
appearance: {
|
||||
theme: 'dark',
|
||||
accentColor: '#f89016',
|
||||
logo: 'https://your-logo-url'
|
||||
},
|
||||
// Create embedded wallets for users who don't have a wallet
|
||||
embeddedWallets: {
|
||||
createOnLogin: 'users-without-wallets'
|
||||
},
|
||||
externalWallets:{
|
||||
solana:{connectors:toSolanaWalletConnectors()}
|
||||
}
|
||||
appId="cm8spd7l600lfe4am1phq9qq8"
|
||||
clientId="client-WY5i4HS6T7JP44iKMQUyZXwftzwKLFvEsGvMtFY1znXSj"
|
||||
config={{
|
||||
// Customize Privy's appearance in your app
|
||||
appearance: {
|
||||
theme: 'dark',
|
||||
accentColor: '#f89016',
|
||||
logo: 'https://i.postimg.cc/3xymQbkZ/Logo-no-BG-4.png'
|
||||
},
|
||||
// Create embedded wallets for users who don't have a wallet
|
||||
embeddedWallets: {
|
||||
createOnLogin: 'users-without-wallets'
|
||||
},
|
||||
externalWallets: {
|
||||
solana: { connectors: toSolanaWalletConnectors() }
|
||||
}
|
||||
|
||||
}}
|
||||
>
|
||||
<Toaster position="top-right" richColors />
|
||||
<Header/>
|
||||
<HeroSection/>
|
||||
<Footer/>
|
||||
}}
|
||||
>
|
||||
<>
|
||||
<Toaster position="top-right" richColors />
|
||||
<Header />
|
||||
<HeroSection />
|
||||
<Footer />
|
||||
</>
|
||||
</PrivyProvider>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { usePrivy, useSolanaWallets } from "@privy-io/react-auth";
|
||||
import { toast } from "sonner";
|
||||
import { games } from "../data/games";
|
||||
|
|
@ -8,7 +8,7 @@ import { PriceSelection } from "./PriceSelection";
|
|||
import { GameSelection } from "./GameSelection";
|
||||
import { createBet } from "@/shared/solana_helpers";
|
||||
import { Game } from "@/types/Game";
|
||||
import { EXPLORER_TX_TEMPLATE } from "@/data/shared";
|
||||
import { EXPLORER_TX_TEMPLATE } from "@/data/shared";
|
||||
|
||||
interface GameModalProps {
|
||||
isOpen: boolean;
|
||||
|
|
@ -17,19 +17,39 @@ interface GameModalProps {
|
|||
|
||||
export default function GameModal({ isOpen, onClose }: GameModalProps) {
|
||||
const { wallets } = useSolanaWallets();
|
||||
const { authenticated } = usePrivy();
|
||||
const { authenticated, user } = usePrivy();
|
||||
|
||||
const [selectedGame, setSelectedGame] = useState<Game | null>(null);
|
||||
const [selectedPrice, setSelectedPrice] = useState<number | null>(null);
|
||||
const [isProcessing, setIsProcessing] = useState(false);
|
||||
|
||||
const [shouldRender, setShouldRender] = useState(isOpen);
|
||||
const [animationClass, setAnimationClass] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
setShouldRender(true);
|
||||
setAnimationClass("modal-enter");
|
||||
} else {
|
||||
setAnimationClass("modal-exit");
|
||||
const timer = setTimeout(() => setShouldRender(false), 200);
|
||||
return () => clearTimeout(timer);
|
||||
}
|
||||
}, [isOpen]);
|
||||
|
||||
const handleCreateGame = async () => {
|
||||
if (!authenticated) {
|
||||
toast.error("Please log in with Privy.");
|
||||
return;
|
||||
}
|
||||
|
||||
const wallet = wallets[0];
|
||||
let wallet = wallets[0];
|
||||
wallets.forEach((_wallet) => {
|
||||
if (_wallet.type === "solana") {
|
||||
wallet = _wallet;
|
||||
}
|
||||
});
|
||||
|
||||
if (!wallet) {
|
||||
toast.error("Please connect your wallet.");
|
||||
return;
|
||||
|
|
@ -42,15 +62,16 @@ export default function GameModal({ isOpen, onClose }: GameModalProps) {
|
|||
|
||||
setIsProcessing(true);
|
||||
try {
|
||||
const tx = await createBet(wallet, selectedPrice, selectedGame);
|
||||
const tx = await createBet(wallet, user?.id ?? "", selectedPrice, selectedGame);
|
||||
const url = EXPLORER_TX_TEMPLATE.replace("{address}", tx);
|
||||
toast.success(`Bet created successfully!`, {
|
||||
action: {
|
||||
label: "View TX",
|
||||
onClick: () => window.open(url, "_blank"),
|
||||
},
|
||||
});
|
||||
|
||||
if (tx.length > 5) {
|
||||
toast.success(`Bet created successfully!`, {
|
||||
action: {
|
||||
label: "View TX",
|
||||
onClick: () => window.open(url, "_blank"),
|
||||
},
|
||||
});
|
||||
}
|
||||
onClose();
|
||||
} catch (error) {
|
||||
console.error("Error creating bet:", error);
|
||||
|
|
@ -58,20 +79,39 @@ export default function GameModal({ isOpen, onClose }: GameModalProps) {
|
|||
} finally {
|
||||
setIsProcessing(false);
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
||||
if (!isOpen) return null;
|
||||
if (!shouldRender) return null;
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 bg-black/70 flex justify-center items-start pt-10 z-50" onClick={onClose}>
|
||||
<div className="bg-[rgb(30,30,30)] text-white w-full max-w-lg p-6 rounded-2xl shadow-lg" onClick={(e) => e.stopPropagation()}>
|
||||
<div
|
||||
className="fixed inset-0 bg-black/70 flex justify-center items-start pt-10 z-50"
|
||||
onClick={onClose}
|
||||
>
|
||||
<div
|
||||
className={`bg-[rgb(30,30,30)] text-white w-full max-w-lg p-6 rounded-2xl shadow-lg ${animationClass}`}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
{isProcessing ? (
|
||||
<div className="flex flex-col items-center">
|
||||
<svg className="animate-spin h-8 w-8 text-blue-400 mb-4" viewBox="0 0 24 24" fill="none">
|
||||
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
|
||||
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v8H4z"></path>
|
||||
<svg
|
||||
className="animate-spin h-8 w-8 text-blue-400 mb-4"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
>
|
||||
<circle
|
||||
className="opacity-25"
|
||||
cx="12"
|
||||
cy="12"
|
||||
r="10"
|
||||
stroke="currentColor"
|
||||
strokeWidth="4"
|
||||
></circle>
|
||||
<path
|
||||
className="opacity-75"
|
||||
fill="currentColor"
|
||||
d="M4 12a8 8 0 018-8v8H4z"
|
||||
></path>
|
||||
</svg>
|
||||
<h3 className="text-lg font-semibold">Processing...</h3>
|
||||
<p className="text-gray-400 text-sm mt-2">Creating your bet, please wait...</p>
|
||||
|
|
@ -79,9 +119,15 @@ export default function GameModal({ isOpen, onClose }: GameModalProps) {
|
|||
) : (
|
||||
<>
|
||||
<h2 className="text-xl font-bold mb-4">Create Game</h2>
|
||||
<GameSelection games={games} selectedGame={selectedGame} onSelect={setSelectedGame} />
|
||||
<PriceSelection selectedPrice={selectedPrice} onSelect={setSelectedPrice} />
|
||||
|
||||
<GameSelection
|
||||
games={games}
|
||||
selectedGame={selectedGame}
|
||||
onSelect={setSelectedGame}
|
||||
/>
|
||||
<PriceSelection
|
||||
selectedPrice={selectedPrice}
|
||||
onSelect={setSelectedPrice}
|
||||
/>
|
||||
<button
|
||||
className="mt-6 w-full py-2 rounded-xl font-semibold bg-[rgb(248,144,22)] text-black hover:bg-white hover:scale-105"
|
||||
onClick={handleCreateGame}
|
||||
|
|
|
|||
|
|
@ -78,8 +78,7 @@ export default function Header() {
|
|||
</div>
|
||||
|
||||
{/* Mobile-only view (Logo & Login) */}
|
||||
<div className="flex md:hidden items-center gap-4">
|
||||
{/* Hamburger Button */}
|
||||
<div className="flex md:hidden items-center ">
|
||||
<PrivyButton></PrivyButton>
|
||||
|
||||
<button
|
||||
|
|
|
|||
|
|
@ -1,41 +1,62 @@
|
|||
import { useEffect, useState } from "react";
|
||||
|
||||
interface HowItWorksModalProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
}
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export function HowItWorksModal({ isOpen, onClose }: HowItWorksModalProps) {
|
||||
if (!isOpen) return null;
|
||||
export function HowItWorksModal({ isOpen, onClose }: HowItWorksModalProps) {
|
||||
const [shouldRender, setShouldRender] = useState(isOpen);
|
||||
const [animationClass, setAnimationClass] = useState("");
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 bg-black/70 flex justify-center items-start pt-10 z-50" onClick={onClose}>
|
||||
<div
|
||||
className="bg-[rgb(30,30,30)] text-white w-full max-w-lg p-6 rounded-2xl shadow-lg transform transition-transform duration-300 animate-slide-down"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<h2 className="text-xl font-bold mb-4">How It Works</h2>
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
setShouldRender(true);
|
||||
setAnimationClass("modal-enter");
|
||||
} else {
|
||||
setAnimationClass("modal-exit");
|
||||
// Wait for animation to finish before unmounting
|
||||
const timer = setTimeout(() => setShouldRender(false), 200); // match animation duration
|
||||
return () => clearTimeout(timer);
|
||||
}
|
||||
}, [isOpen]);
|
||||
|
||||
<div className="space-y-4">
|
||||
{[
|
||||
{ step: "Connect Your Wallet", desc: "Start by linking your wallet securely." },
|
||||
{ step: "Create or Join Game", desc: "Pick a game and set a wager, or join an existing match." },
|
||||
{ step: "Place Your Bet", desc: "Confirm your wager and get ready to play." },
|
||||
{ step: "Claim Your Winnings", desc: "Win the game and collect your rewards instantly!" },
|
||||
].map(({ step, desc }, index) => (
|
||||
<div key={index}>
|
||||
<h3 className="text-[rgb(248,144,22)] font-bold text-lg">{index + 1}. {step}</h3>
|
||||
<p className="text-xs text-gray-400 font-mono">{desc}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
if (!shouldRender) return null;
|
||||
|
||||
<button
|
||||
className="mt-6 w-full bg-[rgb(248,144,22)] text-black font-semibold py-2 rounded-xl transition hover:bg-white hover:scale-105"
|
||||
onClick={onClose}
|
||||
>
|
||||
Okay
|
||||
</button>
|
||||
return (
|
||||
<div
|
||||
className="fixed inset-0 bg-black/70 flex justify-center items-start pt-10 z-50"
|
||||
onClick={onClose}
|
||||
>
|
||||
<div
|
||||
className={`bg-[rgb(30,30,30)] text-white w-full max-w-lg p-6 rounded-2xl shadow-lg transform transition-transform duration-300 ${animationClass}`}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<h2 className="text-xl font-bold mb-4">How It Works</h2>
|
||||
|
||||
<div className="space-y-4">
|
||||
{[
|
||||
{ step: "Connect Your Wallet", desc: "Start by linking your wallet securely." },
|
||||
{ step: "Create or Join Game", desc: "Pick a game and set a wager, or join an existing match." },
|
||||
{ step: "Place Your Bet", desc: "Confirm your wager and get ready to play." },
|
||||
{ step: "Claim Your Winnings", desc: "Win the game and collect your rewards instantly!" },
|
||||
].map(({ step, desc }, index) => (
|
||||
<div key={index}>
|
||||
<h3 className="text-[rgb(248,144,22)] font-bold text-lg">
|
||||
{index + 1}. {step}
|
||||
</h3>
|
||||
<p className="text-xs text-gray-400 font-mono">{desc}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
<button
|
||||
className="mt-6 w-full bg-[rgb(248,144,22)] text-black font-semibold py-2 rounded-xl transition hover:bg-white hover:scale-105"
|
||||
onClick={onClose}
|
||||
>
|
||||
Okay
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,16 +4,33 @@ import Image from "next/image";
|
|||
import { games } from "../data/games";
|
||||
import { useSolanaWallets } from "@privy-io/react-auth";
|
||||
import { fetchOpenBets } from "@/shared/solana_helpers";
|
||||
import {Bet} from "../types/Bet";
|
||||
import { Bet } from "../types/Bet";
|
||||
import { fetchUserById } from "@/shared/data_fetcher";
|
||||
export default function YourGames() {
|
||||
const { wallets, ready } = useSolanaWallets();
|
||||
const [myBets, setMyBets] = useState<Bet[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
// Function to fetch open bets
|
||||
const updateBets= async ()=>{
|
||||
const bets:Bet[] = await fetchOpenBets(wallets[0]);
|
||||
setMyBets(bets.filter((bet) => bet.owner !== wallets[0].address));
|
||||
const updateBets = async () => {
|
||||
let wallet = wallets[0];
|
||||
wallets.forEach((_wallet) => {
|
||||
if (wallet.type === "solana") {
|
||||
wallet = _wallet;
|
||||
}
|
||||
});
|
||||
const bets: Bet[] = await fetchOpenBets(wallet);
|
||||
const filteredBets = (bets.filter((bet) => bet.owner !== wallet.address));
|
||||
const enrichedBets = await Promise.all(
|
||||
filteredBets.map(async (bet) => {
|
||||
const ownerProfile = await fetchUserById(bet.owner_id);
|
||||
return {
|
||||
...bet,
|
||||
ownerProfile, // contains {username, x_profile_url, etc.}
|
||||
};
|
||||
})
|
||||
);
|
||||
setMyBets(enrichedBets);
|
||||
setLoading(false);
|
||||
console.log(`Got ${bets.length} bets`);
|
||||
}
|
||||
|
|
@ -33,41 +50,68 @@ export default function YourGames() {
|
|||
{loading ? (
|
||||
<p className="text-gray-400">Loading Open games...</p>
|
||||
) :
|
||||
myBets.length === 0 ? <></> :(
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{myBets.map((bet) => {
|
||||
console.log(`Finding game for the id ${bet.id}`)
|
||||
const game = games.find((g) => g.id === bet.id); // Match game
|
||||
myBets.length === 0 ? <></> : (
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{myBets.map((bet) => {
|
||||
console.log(`Finding game for the id ${bet.id}`)
|
||||
const game = games.find((g) => g.id === bet.id); // Match game
|
||||
|
||||
if (!game) return null; // Skip unmatched bets
|
||||
if (!game) return null; // Skip unmatched bets
|
||||
return (
|
||||
<div
|
||||
key={bet.id + bet.owner}
|
||||
className="relative group bg-[rgb(30,30,30)] rounded-2xl p-2 w-50 overflow-hidden cursor-pointer transition-all duration-300"
|
||||
>
|
||||
{/* Game Thumbnail */}
|
||||
<div className="relative w-full h-40 overflow-hidden rounded-xl">
|
||||
<Image
|
||||
src={game.thumbnail}
|
||||
alt={game.name}
|
||||
layout="fill"
|
||||
objectFit="cover"
|
||||
className="transition-all duration-300 group-hover:brightness-50"
|
||||
/>
|
||||
|
||||
return (
|
||||
<div
|
||||
key={bet.id + bet.owner}
|
||||
className="relative group bg-[rgb(30,30,30)] rounded-2xl p-2 w-50 overflow-hidden cursor-pointer transition-all duration-300"
|
||||
>
|
||||
{/* Game Thumbnail */}
|
||||
<div className="relative w-full h-40 overflow-hidden rounded-xl">
|
||||
<Image
|
||||
src={game.thumbnail}
|
||||
alt={game.name}
|
||||
layout="fill"
|
||||
objectFit="cover"
|
||||
className="transition-transform duration-300 group-hover:scale-110"
|
||||
/>
|
||||
{/* Join Overlay */}
|
||||
<div className="absolute inset-0 flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity duration-300">
|
||||
<span className="text-white text-xl font-bold">Join</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Game Info */}
|
||||
<div className="mt-4 px-3 text-left">
|
||||
<h3 className="text-lg font-semibold text-white py-2">{game.name}</h3>
|
||||
|
||||
<div className="flex justify-between text-xs font-mono py-1">
|
||||
<p className="text-gray-400">Wager</p>
|
||||
<p className="text-gray-400">Prize</p>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between text-xs font-mono py-1">
|
||||
<p className="text-white">{bet.wager} SOL</p>
|
||||
<p className="text-white">{(bet.wager * 2).toFixed(2)} SOL</p>
|
||||
</div>
|
||||
|
||||
{/* User Info */}
|
||||
{bet.ownerProfile && (
|
||||
<div className="flex items-center gap-2 mt-4">
|
||||
<Image
|
||||
src={bet.ownerProfile.x_profile_url || `https://vps.playpoolstudios.com/duelfi/profile_pics/${bet.ownerProfile.id}.jpg`}
|
||||
alt={bet.ownerProfile.username}
|
||||
width={24}
|
||||
height={24}
|
||||
className="rounded-full"
|
||||
/>
|
||||
<p className="text-white text-sm font-mono">{bet.ownerProfile.username}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Game Info */}
|
||||
<div className="mt-4 px-3 text-left">
|
||||
<h3 className="text-lg font-semibold text-white py-2">{game.name}</h3>
|
||||
<p className="text-xs text-gray-400 font-mono py-1">Wager</p>
|
||||
<p className="text-xs text-white font-mono py-1">{bet.wager} SOL</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,53 +2,106 @@
|
|||
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
import { usePrivy, useSolanaWallets } from "@privy-io/react-auth";
|
||||
import { Connection, PublicKey } from "@solana/web3.js"; // Solana Web3.js imports
|
||||
import { Connection, PublicKey } from "@solana/web3.js";
|
||||
import { toast } from "sonner";
|
||||
|
||||
import "react-toastify/dist/ReactToastify.css";
|
||||
import { CLUSTER_URL } from "@/data/shared";
|
||||
import { useFundWallet } from "@privy-io/react-auth/solana";
|
||||
|
||||
export default function PrivyButton() {
|
||||
const { login, logout, user } = usePrivy();
|
||||
const { login, logout, user, linkTwitter, unlinkTwitter } = usePrivy();
|
||||
const { fundWallet} = useFundWallet();
|
||||
const { wallets } = useSolanaWallets();
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
const [solWallet, setSolWallet] = useState("");
|
||||
const [solBalance, setSolBalance] = useState("--");
|
||||
|
||||
const [username, setUsername] = useState("Tester");
|
||||
const [bio, setBio] = useState("");
|
||||
const [avatar, setAvatar] = useState<string | null>(null);
|
||||
|
||||
const [isUsernameClaimModalOpen, setIsUsernameClaimModalOpen] = useState(false);
|
||||
const [newUsername, setNewUsername] = useState("");
|
||||
|
||||
const modalRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const fetchSolBalance = async () => {
|
||||
|
||||
wallets.forEach((wallet)=>{
|
||||
console.log(wallet.address + " : " + wallet.type);
|
||||
if(wallet.type == "solana"){
|
||||
const updateSolWallet = ()=>{
|
||||
wallets.forEach((wallet) => {
|
||||
if (wallet.type === "solana") {
|
||||
setSolWallet(wallet.address);
|
||||
}
|
||||
})
|
||||
if (solWallet!="") {
|
||||
const walletAddress = solWallet; // Access the Solana wallet address
|
||||
});
|
||||
}
|
||||
const fetchSolBalance = async () => {
|
||||
updateSolWallet();
|
||||
|
||||
if (solWallet !== "") {
|
||||
try {
|
||||
const connection = new Connection(CLUSTER_URL, "confirmed");
|
||||
const publicKey = new PublicKey(walletAddress);
|
||||
|
||||
const publicKey = new PublicKey(solWallet);
|
||||
const balance = await connection.getBalance(publicKey);
|
||||
setSolBalance((balance / 1e9).toFixed(2)); // Convert lamports to SOL (1 SOL = 1e9 lamports)
|
||||
setSolBalance((balance / 1e9).toFixed(2));
|
||||
} catch (error) {
|
||||
console.error("Failed to get balance:", error);
|
||||
setSolBalance("Error");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const saveProfileChanges = async () => {
|
||||
if (!user) {
|
||||
toast.error("User not found!");
|
||||
return;
|
||||
}
|
||||
|
||||
const updateUrl = `https://vps.playpoolstudios.com/duelfi/api/update_profile.php?id=${user.id}&username=${username}&bio=${bio}`;
|
||||
|
||||
try {
|
||||
const response = await fetch(updateUrl);
|
||||
const data = await response.text();
|
||||
|
||||
if (data == "0") {
|
||||
toast.success("Profile updated successfully!");
|
||||
} else {
|
||||
toast.error("Failed to update profile.");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error updating profile:", error);
|
||||
toast.error("An error occurred while updating the profile.");
|
||||
}
|
||||
};
|
||||
|
||||
const fetchUserData = async () => {
|
||||
if (user) {
|
||||
const apiUrl = `https://vps.playpoolstudios.com/duelfi/api/get_user_by_id.php?id=${user?.id}`;
|
||||
try {
|
||||
const response = await fetch(apiUrl);
|
||||
const data = await response.json();
|
||||
if (data === 0) {
|
||||
setIsUsernameClaimModalOpen(true); // Show modal if user isn't registered
|
||||
} else {
|
||||
setUsername(data.username || "Tester");
|
||||
setBio(data.bio || "");
|
||||
|
||||
// Check if the user has a profile picture URL and update in database
|
||||
const customProfileUrl = `https://vps.playpoolstudios.com/duelfi/profile_pics/${user.id}.jpg`;
|
||||
const profilePictureUrl = user?.twitter?.profilePictureUrl ?? customProfileUrl;
|
||||
if (profilePictureUrl) {
|
||||
const updatePicUrlApi = `https://vps.playpoolstudios.com/duelfi/api/update_x_pic_url.php?id=${user?.id}&url=${profilePictureUrl}`;
|
||||
await fetch(updatePicUrlApi);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch user data:", error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
|
||||
if (wallets) {
|
||||
fetchSolBalance();
|
||||
}
|
||||
}, [user, wallets]); // Added wallets to dependency array
|
||||
}, [user, wallets]);
|
||||
|
||||
// Function to validate if the address is a valid Solana address (base58)
|
||||
|
||||
// Close modal when clicking outside
|
||||
useEffect(() => {
|
||||
function handleClickOutside(event: MouseEvent) {
|
||||
if (modalRef.current && !modalRef.current.contains(event.target as Node)) {
|
||||
|
|
@ -63,52 +116,207 @@ export default function PrivyButton() {
|
|||
return () => document.removeEventListener("mousedown", handleClickOutside);
|
||||
}, [isModalOpen]);
|
||||
|
||||
// Fetch user data on login
|
||||
useEffect(() => {
|
||||
if (user) {
|
||||
fetchUserData();
|
||||
}
|
||||
}, [user]);
|
||||
|
||||
const customLogin = () => {
|
||||
login({ walletChainType: "solana-only" });
|
||||
};
|
||||
|
||||
const copyToClipboard = async () => {
|
||||
const walletAddress = solWallet ?? ""; // Use an empty string if wallet address is undefined or null
|
||||
if (walletAddress) {
|
||||
await navigator.clipboard.writeText(walletAddress);
|
||||
toast.success("Wallet address copied!"); // Toast notification for successful copy
|
||||
if (solWallet) {
|
||||
await navigator.clipboard.writeText(solWallet);
|
||||
toast.success("Wallet address copied!");
|
||||
}
|
||||
};
|
||||
|
||||
const handleAvatarChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const file = e.target.files?.[0];
|
||||
if (file) {
|
||||
const reader = new FileReader();
|
||||
reader.onloadend = async () => {
|
||||
|
||||
try {
|
||||
// Ensure the user is authenticated and the privy_id is available
|
||||
if (!user?.id) {
|
||||
toast.error('No Privy ID found!');
|
||||
return;
|
||||
}
|
||||
|
||||
// Prepare the form data
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
formData.append('privy_id', user.id); // Append the privy_id
|
||||
|
||||
// Upload the avatar image to your server
|
||||
const uploadResponse = await fetch('https://vps.playpoolstudios.com/duelfi/api/upload_profile_picture.php', {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
});
|
||||
|
||||
const uploadData = await uploadResponse.json();
|
||||
console.log(uploadData);
|
||||
if (uploadData.success) {
|
||||
// Get the image URL from the response (assuming it returns the image path)
|
||||
const imageUrl = uploadData.imageUrl;
|
||||
|
||||
// Update the avatar state and database
|
||||
setAvatar(imageUrl);
|
||||
const updatePicUrlApi = `https://vps.playpoolstudios.com/duelfi/api/update_x_pic_url.php?id=${user?.id}&url=${imageUrl}`;
|
||||
await fetch(updatePicUrlApi);
|
||||
|
||||
toast.success('Profile picture uploaded successfully!');
|
||||
} else {
|
||||
|
||||
toast.error('Failed to upload profile picture!');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error uploading avatar:", error);
|
||||
toast.error("An error occurred while uploading the profile picture.");
|
||||
}
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const handleUsernameClaim = async () => {
|
||||
if (newUsername.trim()) {
|
||||
const apiUrl = `https://vps.playpoolstudios.com/duelfi/api/register.php?id=${user?.id}&username=${newUsername}`;
|
||||
try {
|
||||
const response = await fetch(apiUrl);
|
||||
const data = await response.text();
|
||||
if (data == "0") {
|
||||
toast.success("Username claimed successfully!");
|
||||
setIsUsernameClaimModalOpen(false);
|
||||
} else {
|
||||
toast.error("Failed to claim username.");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error claiming username:", error);
|
||||
toast.error("An error occurred while claiming your username.");
|
||||
}
|
||||
} else {
|
||||
toast.error("Username cannot be empty!");
|
||||
}
|
||||
};
|
||||
|
||||
const twitterProfilePic: string = user?.twitter?.profilePictureUrl ?? "";
|
||||
const customProfileUrl = `https://vps.playpoolstudios.com/duelfi/profile_pics/${user?.id}.jpg`;
|
||||
|
||||
return (
|
||||
<>
|
||||
{user ? (
|
||||
<button
|
||||
onClick={() =>{setIsModalOpen(true); fetchSolBalance();}}
|
||||
onClick={() => {
|
||||
setIsModalOpen(true);
|
||||
fetchSolBalance();
|
||||
fetchUserData();
|
||||
}}
|
||||
className="bg-[rgb(248,144,22)] hover:bg-[rgb(248,200,100)] text-black px-6 py-3 rounded-md transition duration-300 hover:scale-105"
|
||||
>
|
||||
<span className="font-bold">Connected</span>
|
||||
<p className="text-xs font-mono text-gray-700">
|
||||
{solWallet?.slice(0, 6)}...
|
||||
{solWallet?.slice(-4)}
|
||||
{solWallet?.slice(0, 6)}...{solWallet?.slice(-4)}
|
||||
</p>
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
onClick={customLogin}
|
||||
className="bg-[rgb(248,144,22)] hover:bg-[rgb(248,200,100)] text-black font-bold px-6 py-3 rounded-md transition duration-300 hover:scale-105"
|
||||
className="bg-[rgb(248,144,22)] hover:bg-[rgb(248,200,100)] text-black font-bold px-6 py-3 rounded-md transition duration-300 hover:scale-115 w-30 mx-3"
|
||||
>
|
||||
Sign In
|
||||
</button>
|
||||
)}
|
||||
|
||||
{/* Modal */}
|
||||
{isModalOpen && user ? (
|
||||
{isModalOpen && user && (
|
||||
<div className="fixed inset-0 bg-black/70 flex justify-center items-start pt-10 z-50">
|
||||
<div
|
||||
ref={modalRef}
|
||||
className="bg-[rgb(30,30,30)] text-white w-full max-w-md p-6 rounded-2xl shadow-lg transform transition-transform duration-300 animate-slide-down"
|
||||
className="bg-[rgb(30,30,30)] text-white w-full max-w-2xl p-6 rounded-2xl shadow-lg transform transition-transform duration-300 animate-slide-down space-y-6"
|
||||
>
|
||||
<h2 className="text-xl font-bold mb-4">Account Info</h2>
|
||||
<h2 className="text-2xl font-bold mb-2">Your Profile</h2>
|
||||
|
||||
{/* Wallet Address with Copy Button */}
|
||||
<div className="mb-4">
|
||||
<p className="text-gray-400 text-xs font-mono">Connected Wallet</p>
|
||||
{/* Avatar + Link Twitter Row */}
|
||||
<div className="flex items-center justify-between gap-4 flex-wrap">
|
||||
<div className="flex items-center gap-4">
|
||||
<img
|
||||
src={user?.twitter ? (twitterProfilePic) : (avatar ?? customProfileUrl)}
|
||||
alt=""
|
||||
className="w-16 h-16 rounded-full border border-gray-500 object-cover"
|
||||
/>
|
||||
{(!user.twitter) ? (<label className="bg-gray-800 hover:bg-gray-700 text-white text-sm px-4 py-2 rounded-md cursor-pointer">
|
||||
Change
|
||||
<input
|
||||
type="file"
|
||||
accept="image/*"
|
||||
onChange={handleAvatarChange}
|
||||
className="hidden"
|
||||
/>
|
||||
</label>) : (<></>)}
|
||||
</div>
|
||||
<div>
|
||||
{user.twitter ? (
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm text-gray-300 font-mono">
|
||||
Connected as {user.twitter.username}
|
||||
</span>
|
||||
<button
|
||||
onClick={() => unlinkTwitter(user.twitter?.subject ?? "")}
|
||||
className="bg-black text-white px-4 py-2 rounded-md text-sm hover:bg-gray-900"
|
||||
>
|
||||
Unlink
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<button
|
||||
onClick={linkTwitter}
|
||||
className="bg-black text-white px-4 py-2 rounded-md text-sm hover:bg-gray-900"
|
||||
>
|
||||
Link X (Twitter)
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{/* Username */}
|
||||
<div>
|
||||
<label className="block text-xs text-gray-400 mb-1">Username</label>
|
||||
<input
|
||||
type="text"
|
||||
value={username}
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
className="w-full bg-gray-800 text-white p-2 rounded-md"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Bio */}
|
||||
<div>
|
||||
<label className="block text-xs text-gray-400 mb-1">Bio</label>
|
||||
<textarea
|
||||
value={bio}
|
||||
onChange={(e) => setBio(e.target.value)}
|
||||
className="w-full bg-gray-800 text-white p-2 rounded-md"
|
||||
rows={2}
|
||||
/>
|
||||
</div>
|
||||
{/* Save Button */}
|
||||
<button
|
||||
className="bg-green-500 hover:bg-green-600 text-white px-4 py-2 rounded-md font-semibold"
|
||||
onClick={saveProfileChanges}
|
||||
>
|
||||
Save Changes
|
||||
</button>
|
||||
|
||||
{/* Divider */}
|
||||
<hr className="border-gray-600" />
|
||||
|
||||
{/* Wallet Info */}
|
||||
<div>
|
||||
<p className="text-gray-400 text-xs font-mono mb-1">Connected Wallet</p>
|
||||
<div className="flex items-center justify-between bg-gray-800 p-2 rounded-md">
|
||||
<p className="font-mono text-sm truncate">{solWallet}</p>
|
||||
<button
|
||||
|
|
@ -118,37 +326,18 @@ export default function PrivyButton() {
|
|||
📋
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<p className="text-gray-400 text-xs font-mono mt-4">SOL Balance</p>
|
||||
<p className="text-sm mb-3">{solBalance} SOL</p>
|
||||
|
||||
<button
|
||||
onClick={()=>{fundWallet(solWallet, {})}}
|
||||
className="w-full bg-purple-600 hover:bg-purple-700 text-white font-semibold py-2 rounded-xl transition"
|
||||
>
|
||||
Fund Wallet
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* SOL Balance */}
|
||||
<div className="mb-4">
|
||||
<p className="text-gray-400 text-xs font-mono">SOL Balance</p>
|
||||
<p className="text-sm">{solBalance} SOL</p>
|
||||
</div>
|
||||
|
||||
{/* Socials */}
|
||||
{(user?.discord?.username || user?.email?.address) && (
|
||||
<div className="mb-4">
|
||||
<p className="text-gray-400 text-xs font-mono">Socials</p>
|
||||
<div className="flex gap-2">
|
||||
{user?.discord?.username && (
|
||||
<a
|
||||
href={`https://discord.com/users/${user?.discord?.username}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-blue-400 text-sm hover:underline"
|
||||
>
|
||||
Discord
|
||||
</a>
|
||||
)}
|
||||
{user?.email?.address && (
|
||||
<span className="text-gray-400 text-sm">{user?.email?.address}</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Sign Out Button */}
|
||||
<button
|
||||
className="mt-6 w-full bg-[rgb(248,144,22)] text-black font-semibold py-2 rounded-xl transition hover:bg-white"
|
||||
onClick={() => {
|
||||
|
|
@ -160,24 +349,30 @@ export default function PrivyButton() {
|
|||
</button>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
)}
|
||||
|
||||
{/* Username Claim Modal */}
|
||||
{isUsernameClaimModalOpen && (
|
||||
<div className="fixed inset-0 bg-black/70 flex justify-center items-center z-50">
|
||||
<div className="bg-[rgb(30,30,30)] text-white w-full max-w-lg p-6 rounded-2xl shadow-lg space-y-6">
|
||||
<h2 className="text-2xl font-bold">Claim Your Username</h2>
|
||||
<input
|
||||
type="text"
|
||||
value={newUsername}
|
||||
onChange={(e) => setNewUsername(e.target.value)}
|
||||
className="w-full bg-[rgb(10,10,10)] text-white p-2 rounded-md"
|
||||
placeholder="Enter your new username"
|
||||
/>
|
||||
<button
|
||||
onClick={handleUsernameClaim}
|
||||
className="w-full bg-[rgb(248,144,22)] hover:bg-orange-400 text-white px-4 py-2 rounded-md transition duration-500 hover:scale-105"
|
||||
>
|
||||
Claim
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Animation */}
|
||||
<style jsx>{`
|
||||
@keyframes slide-down {
|
||||
from {
|
||||
transform: translateY(-100%);
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
transform: translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
.animate-slide-down {
|
||||
animation: slide-down 0.3s ease-out;
|
||||
}
|
||||
`}</style>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,8 +18,14 @@ export default function YourGames() {
|
|||
|
||||
// Fetch bets
|
||||
const updateBets = async () => {
|
||||
const bets: Bet[] = await fetchOpenBets(wallets[0]);
|
||||
setMyBets(bets.filter((bet) => bet.owner === wallets[0].address));
|
||||
let wallet = wallets[0];
|
||||
wallets.forEach((_wallet) => {
|
||||
if (wallet.type === "solana") {
|
||||
wallet = _wallet;
|
||||
}
|
||||
});
|
||||
const bets: Bet[] = await fetchOpenBets(wallet);
|
||||
setMyBets(bets.filter((bet) => bet.owner === wallet.address));
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
|
|
@ -34,8 +40,14 @@ export default function YourGames() {
|
|||
const handleCloseBet = async () => {
|
||||
if (!selectedBet) return;
|
||||
setIsProcessing(true);
|
||||
let wallet = wallets[0];
|
||||
wallets.forEach((_wallet) => {
|
||||
if (wallet.type === "solana") {
|
||||
wallet = _wallet;
|
||||
}
|
||||
});
|
||||
try {
|
||||
const tx = await closeBet(wallets[0], selectedBet.id);
|
||||
const tx = await closeBet(wallet, selectedBet.id);
|
||||
const url = EXPLORER_TX_TEMPLATE.replace("{address}", tx);
|
||||
|
||||
toast.success(`Closed the bet successfully!`, {
|
||||
|
|
|
|||
559
src/idl/bets.ts
559
src/idl/bets.ts
|
|
@ -5,295 +5,310 @@
|
|||
* IDL can be found at `target/idl/bets.json`.
|
||||
*/
|
||||
export type Bets = {
|
||||
"address": "HxsDuhD7wcPxcMsrYdteMYxkffuwff8HoxhZ7NuFtM37",
|
||||
"metadata": {
|
||||
"name": "bets",
|
||||
"version": "0.1.0",
|
||||
"spec": "0.1.0",
|
||||
"description": "Created with Anchor"
|
||||
"address": "JAf3ZkQ469okXAzA6BKJeKBb9ZkCtZanULaUsapskoyn",
|
||||
"metadata": {
|
||||
"name": "bets",
|
||||
"version": "0.1.0",
|
||||
"spec": "0.1.0",
|
||||
"description": "Created with Anchor"
|
||||
},
|
||||
"instructions": [
|
||||
{
|
||||
"name": "closeBet",
|
||||
"discriminator": [
|
||||
185,
|
||||
206,
|
||||
13,
|
||||
184,
|
||||
176,
|
||||
108,
|
||||
140,
|
||||
107
|
||||
],
|
||||
"accounts": [
|
||||
{
|
||||
"name": "betsList",
|
||||
"writable": true
|
||||
},
|
||||
{
|
||||
"name": "betVault",
|
||||
"writable": true
|
||||
},
|
||||
{
|
||||
"name": "winner",
|
||||
"writable": true
|
||||
},
|
||||
{
|
||||
"name": "payer",
|
||||
"writable": true,
|
||||
"signer": true
|
||||
},
|
||||
{
|
||||
"name": "systemProgram",
|
||||
"address": "11111111111111111111111111111111"
|
||||
}
|
||||
],
|
||||
"args": [
|
||||
{
|
||||
"name": "winner",
|
||||
"type": "pubkey"
|
||||
}
|
||||
]
|
||||
},
|
||||
"instructions": [
|
||||
{
|
||||
"name": "closeBet",
|
||||
"discriminator": [
|
||||
185,
|
||||
206,
|
||||
13,
|
||||
184,
|
||||
176,
|
||||
108,
|
||||
140,
|
||||
107
|
||||
],
|
||||
"accounts": [
|
||||
{
|
||||
"name": "betsList",
|
||||
"writable": true
|
||||
},
|
||||
{
|
||||
"name": "betVault",
|
||||
"writable": true
|
||||
},
|
||||
{
|
||||
"name": "winner",
|
||||
"writable": true
|
||||
},
|
||||
{
|
||||
"name": "payer",
|
||||
"writable": true,
|
||||
"signer": true
|
||||
},
|
||||
{
|
||||
"name": "systemProgram",
|
||||
"address": "11111111111111111111111111111111"
|
||||
{
|
||||
"name": "createBet",
|
||||
"discriminator": [
|
||||
197,
|
||||
42,
|
||||
153,
|
||||
2,
|
||||
59,
|
||||
63,
|
||||
143,
|
||||
246
|
||||
],
|
||||
"accounts": [
|
||||
{
|
||||
"name": "payer",
|
||||
"writable": true,
|
||||
"signer": true
|
||||
},
|
||||
{
|
||||
"name": "betsList",
|
||||
"writable": true
|
||||
},
|
||||
{
|
||||
"name": "betVault",
|
||||
"writable": true,
|
||||
"pda": {
|
||||
"seeds": [
|
||||
{
|
||||
"kind": "const",
|
||||
"value": [
|
||||
98,
|
||||
101,
|
||||
116,
|
||||
95,
|
||||
118,
|
||||
97,
|
||||
117,
|
||||
108,
|
||||
116
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "account",
|
||||
"path": "payer"
|
||||
},
|
||||
{
|
||||
"kind": "arg",
|
||||
"path": "gameId"
|
||||
},
|
||||
{
|
||||
"kind": "arg",
|
||||
"path": "nonce"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"args": [
|
||||
},
|
||||
{
|
||||
"name": "systemProgram",
|
||||
"address": "11111111111111111111111111111111"
|
||||
}
|
||||
],
|
||||
"args": [
|
||||
{
|
||||
"name": "wager",
|
||||
"type": "u64"
|
||||
},
|
||||
{
|
||||
"name": "userId",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "gameId",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "nonce",
|
||||
"type": "u64"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "initialize",
|
||||
"discriminator": [
|
||||
175,
|
||||
175,
|
||||
109,
|
||||
31,
|
||||
13,
|
||||
152,
|
||||
155,
|
||||
237
|
||||
],
|
||||
"accounts": [
|
||||
{
|
||||
"name": "betsList",
|
||||
"writable": true,
|
||||
"pda": {
|
||||
"seeds": [
|
||||
{
|
||||
"kind": "const",
|
||||
"value": [
|
||||
98,
|
||||
101,
|
||||
116,
|
||||
115,
|
||||
95,
|
||||
108,
|
||||
105,
|
||||
115,
|
||||
116
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "payer",
|
||||
"writable": true,
|
||||
"signer": true
|
||||
},
|
||||
{
|
||||
"name": "systemProgram",
|
||||
"address": "11111111111111111111111111111111"
|
||||
}
|
||||
],
|
||||
"args": []
|
||||
},
|
||||
{
|
||||
"name": "joinBet",
|
||||
"discriminator": [
|
||||
69,
|
||||
116,
|
||||
82,
|
||||
26,
|
||||
144,
|
||||
192,
|
||||
58,
|
||||
238
|
||||
],
|
||||
"accounts": [
|
||||
{
|
||||
"name": "betVault",
|
||||
"writable": true
|
||||
},
|
||||
{
|
||||
"name": "payer",
|
||||
"writable": true,
|
||||
"signer": true
|
||||
},
|
||||
{
|
||||
"name": "systemProgram",
|
||||
"address": "11111111111111111111111111111111"
|
||||
}
|
||||
],
|
||||
"args": [
|
||||
{
|
||||
"name": "userId",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "gameId",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"accounts": [
|
||||
{
|
||||
"name": "betVault",
|
||||
"discriminator": [
|
||||
103,
|
||||
78,
|
||||
21,
|
||||
234,
|
||||
18,
|
||||
250,
|
||||
230,
|
||||
209
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "betsList",
|
||||
"discriminator": [
|
||||
231,
|
||||
234,
|
||||
50,
|
||||
58,
|
||||
81,
|
||||
179,
|
||||
239,
|
||||
117
|
||||
]
|
||||
}
|
||||
],
|
||||
"errors": [
|
||||
{
|
||||
"code": 6000,
|
||||
"name": "betNotFilled",
|
||||
"msg": "Bet is not filled yet!"
|
||||
}
|
||||
],
|
||||
"types": [
|
||||
{
|
||||
"name": "betVault",
|
||||
"type": {
|
||||
"kind": "struct",
|
||||
"fields": [
|
||||
{
|
||||
"name": "winner",
|
||||
"name": "gameId",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "owner",
|
||||
"type": "pubkey"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "createBet",
|
||||
"discriminator": [
|
||||
197,
|
||||
42,
|
||||
153,
|
||||
2,
|
||||
59,
|
||||
63,
|
||||
143,
|
||||
246
|
||||
],
|
||||
"accounts": [
|
||||
{
|
||||
"name": "payer",
|
||||
"writable": true,
|
||||
"signer": true
|
||||
},
|
||||
{
|
||||
"name": "betsList",
|
||||
"writable": true
|
||||
"name": "ownerId",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "betVault",
|
||||
"writable": true,
|
||||
"pda": {
|
||||
"seeds": [
|
||||
{
|
||||
"kind": "const",
|
||||
"value": [
|
||||
98,
|
||||
101,
|
||||
116,
|
||||
95,
|
||||
118,
|
||||
97,
|
||||
117,
|
||||
108,
|
||||
116
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "account",
|
||||
"path": "payer"
|
||||
},
|
||||
{
|
||||
"kind": "arg",
|
||||
"path": "gameId"
|
||||
},
|
||||
{
|
||||
"kind": "arg",
|
||||
"path": "nonce"
|
||||
}
|
||||
]
|
||||
}
|
||||
"name": "joiner",
|
||||
"type": "pubkey"
|
||||
},
|
||||
{
|
||||
"name": "systemProgram",
|
||||
"address": "11111111111111111111111111111111"
|
||||
}
|
||||
],
|
||||
"args": [
|
||||
"name": "joinerId",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "wager",
|
||||
"type": "u64"
|
||||
},
|
||||
{
|
||||
"name": "gameId",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "nonce",
|
||||
"type": "u64"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "initialize",
|
||||
"discriminator": [
|
||||
175,
|
||||
175,
|
||||
109,
|
||||
31,
|
||||
13,
|
||||
152,
|
||||
155,
|
||||
237
|
||||
],
|
||||
"accounts": [
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "betsList",
|
||||
"type": {
|
||||
"kind": "struct",
|
||||
"fields": [
|
||||
{
|
||||
"name": "betsList",
|
||||
"writable": true,
|
||||
"pda": {
|
||||
"seeds": [
|
||||
{
|
||||
"kind": "const",
|
||||
"value": [
|
||||
98,
|
||||
101,
|
||||
116,
|
||||
115,
|
||||
95,
|
||||
108,
|
||||
105,
|
||||
115,
|
||||
116
|
||||
]
|
||||
}
|
||||
]
|
||||
"name": "bets",
|
||||
"type": {
|
||||
"vec": "pubkey"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "payer",
|
||||
"writable": true,
|
||||
"signer": true
|
||||
},
|
||||
{
|
||||
"name": "systemProgram",
|
||||
"address": "11111111111111111111111111111111"
|
||||
}
|
||||
],
|
||||
"args": []
|
||||
},
|
||||
{
|
||||
"name": "joinBet",
|
||||
"discriminator": [
|
||||
69,
|
||||
116,
|
||||
82,
|
||||
26,
|
||||
144,
|
||||
192,
|
||||
58,
|
||||
238
|
||||
],
|
||||
"accounts": [
|
||||
{
|
||||
"name": "betVault",
|
||||
"writable": true
|
||||
},
|
||||
{
|
||||
"name": "payer",
|
||||
"writable": true,
|
||||
"signer": true
|
||||
},
|
||||
{
|
||||
"name": "systemProgram",
|
||||
"address": "11111111111111111111111111111111"
|
||||
}
|
||||
],
|
||||
"args": [
|
||||
{
|
||||
"name": "gameId",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"accounts": [
|
||||
{
|
||||
"name": "betVault",
|
||||
"discriminator": [
|
||||
103,
|
||||
78,
|
||||
21,
|
||||
234,
|
||||
18,
|
||||
250,
|
||||
230,
|
||||
209
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "betsList",
|
||||
"discriminator": [
|
||||
231,
|
||||
234,
|
||||
50,
|
||||
58,
|
||||
81,
|
||||
179,
|
||||
239,
|
||||
117
|
||||
]
|
||||
}
|
||||
],
|
||||
"errors": [
|
||||
{
|
||||
"code": 6000,
|
||||
"name": "customError",
|
||||
"msg": "Custom error message"
|
||||
}
|
||||
],
|
||||
"types": [
|
||||
{
|
||||
"name": "betVault",
|
||||
"type": {
|
||||
"kind": "struct",
|
||||
"fields": [
|
||||
{
|
||||
"name": "gameId",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "owner",
|
||||
"type": "pubkey"
|
||||
},
|
||||
{
|
||||
"name": "joiner",
|
||||
"type": "pubkey"
|
||||
},
|
||||
{
|
||||
"name": "wager",
|
||||
"type": "u64"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "betsList",
|
||||
"type": {
|
||||
"kind": "struct",
|
||||
"fields": [
|
||||
{
|
||||
"name": "bets",
|
||||
"type": {
|
||||
"vec": "pubkey"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"constants": [
|
||||
{
|
||||
"name": "seed",
|
||||
"type": "string",
|
||||
"value": "\"anchor\""
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
}
|
||||
],
|
||||
"constants": [
|
||||
{
|
||||
"name": "seed",
|
||||
"type": "string",
|
||||
"value": "\"anchor\""
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,292 +1,308 @@
|
|||
{
|
||||
"address": "HxsDuhD7wcPxcMsrYdteMYxkffuwff8HoxhZ7NuFtM37",
|
||||
"metadata": {
|
||||
"name": "bets",
|
||||
"version": "0.1.0",
|
||||
"spec": "0.1.0",
|
||||
"description": "Created with Anchor"
|
||||
"address": "JAf3ZkQ469okXAzA6BKJeKBb9ZkCtZanULaUsapskoyn",
|
||||
"metadata": {
|
||||
"name": "bets",
|
||||
"version": "0.1.0",
|
||||
"spec": "0.1.0",
|
||||
"description": "Created with Anchor"
|
||||
},
|
||||
"instructions": [
|
||||
{
|
||||
"name": "close_bet",
|
||||
"discriminator": [
|
||||
185,
|
||||
206,
|
||||
13,
|
||||
184,
|
||||
176,
|
||||
108,
|
||||
140,
|
||||
107
|
||||
],
|
||||
"accounts": [
|
||||
{
|
||||
"name": "bets_list",
|
||||
"writable": true
|
||||
},
|
||||
{
|
||||
"name": "bet_vault",
|
||||
"writable": true
|
||||
},
|
||||
{
|
||||
"name": "winner",
|
||||
"writable": true
|
||||
},
|
||||
{
|
||||
"name": "payer",
|
||||
"writable": true,
|
||||
"signer": true
|
||||
},
|
||||
{
|
||||
"name": "system_program",
|
||||
"address": "11111111111111111111111111111111"
|
||||
}
|
||||
],
|
||||
"args": [
|
||||
{
|
||||
"name": "winner",
|
||||
"type": "pubkey"
|
||||
}
|
||||
]
|
||||
},
|
||||
"instructions": [
|
||||
{
|
||||
"name": "close_bet",
|
||||
"discriminator": [
|
||||
185,
|
||||
206,
|
||||
13,
|
||||
184,
|
||||
176,
|
||||
108,
|
||||
140,
|
||||
107
|
||||
],
|
||||
"accounts": [
|
||||
{
|
||||
"name": "bets_list",
|
||||
"writable": true
|
||||
},
|
||||
{
|
||||
"name": "bet_vault",
|
||||
"writable": true
|
||||
},
|
||||
{
|
||||
"name": "winner",
|
||||
"writable": true
|
||||
},
|
||||
{
|
||||
"name": "payer",
|
||||
"writable": true,
|
||||
"signer": true
|
||||
},
|
||||
{
|
||||
"name": "system_program",
|
||||
"address": "11111111111111111111111111111111"
|
||||
{
|
||||
"name": "create_bet",
|
||||
"discriminator": [
|
||||
197,
|
||||
42,
|
||||
153,
|
||||
2,
|
||||
59,
|
||||
63,
|
||||
143,
|
||||
246
|
||||
],
|
||||
"accounts": [
|
||||
{
|
||||
"name": "payer",
|
||||
"writable": true,
|
||||
"signer": true
|
||||
},
|
||||
{
|
||||
"name": "bets_list",
|
||||
"writable": true
|
||||
},
|
||||
{
|
||||
"name": "bet_vault",
|
||||
"writable": true,
|
||||
"pda": {
|
||||
"seeds": [
|
||||
{
|
||||
"kind": "const",
|
||||
"value": [
|
||||
98,
|
||||
101,
|
||||
116,
|
||||
95,
|
||||
118,
|
||||
97,
|
||||
117,
|
||||
108,
|
||||
116
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "account",
|
||||
"path": "payer"
|
||||
},
|
||||
{
|
||||
"kind": "arg",
|
||||
"path": "game_id"
|
||||
},
|
||||
{
|
||||
"kind": "arg",
|
||||
"path": "_nonce"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"args": [
|
||||
},
|
||||
{
|
||||
"name": "system_program",
|
||||
"address": "11111111111111111111111111111111"
|
||||
}
|
||||
],
|
||||
"args": [
|
||||
{
|
||||
"name": "wager",
|
||||
"type": "u64"
|
||||
},
|
||||
{
|
||||
"name": "user_id",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "game_id",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "nonce",
|
||||
"type": "u64"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "initialize",
|
||||
"discriminator": [
|
||||
175,
|
||||
175,
|
||||
109,
|
||||
31,
|
||||
13,
|
||||
152,
|
||||
155,
|
||||
237
|
||||
],
|
||||
"accounts": [
|
||||
{
|
||||
"name": "bets_list",
|
||||
"writable": true,
|
||||
"pda": {
|
||||
"seeds": [
|
||||
{
|
||||
"kind": "const",
|
||||
"value": [
|
||||
98,
|
||||
101,
|
||||
116,
|
||||
115,
|
||||
95,
|
||||
108,
|
||||
105,
|
||||
115,
|
||||
116
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "payer",
|
||||
"writable": true,
|
||||
"signer": true
|
||||
},
|
||||
{
|
||||
"name": "system_program",
|
||||
"address": "11111111111111111111111111111111"
|
||||
}
|
||||
],
|
||||
"args": []
|
||||
},
|
||||
{
|
||||
"name": "join_bet",
|
||||
"discriminator": [
|
||||
69,
|
||||
116,
|
||||
82,
|
||||
26,
|
||||
144,
|
||||
192,
|
||||
58,
|
||||
238
|
||||
],
|
||||
"accounts": [
|
||||
{
|
||||
"name": "bet_vault",
|
||||
"writable": true
|
||||
},
|
||||
{
|
||||
"name": "payer",
|
||||
"writable": true,
|
||||
"signer": true
|
||||
},
|
||||
{
|
||||
"name": "system_program",
|
||||
"address": "11111111111111111111111111111111"
|
||||
}
|
||||
],
|
||||
"args": [
|
||||
{
|
||||
"name": "user_id",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "game_id",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"accounts": [
|
||||
{
|
||||
"name": "BetVault",
|
||||
"discriminator": [
|
||||
103,
|
||||
78,
|
||||
21,
|
||||
234,
|
||||
18,
|
||||
250,
|
||||
230,
|
||||
209
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "BetsList",
|
||||
"discriminator": [
|
||||
231,
|
||||
234,
|
||||
50,
|
||||
58,
|
||||
81,
|
||||
179,
|
||||
239,
|
||||
117
|
||||
]
|
||||
}
|
||||
],
|
||||
"errors": [
|
||||
{
|
||||
"code": 6000,
|
||||
"name": "BetNotFilled",
|
||||
"msg": "Bet is not filled yet!"
|
||||
}
|
||||
],
|
||||
"types": [
|
||||
{
|
||||
"name": "BetVault",
|
||||
"type": {
|
||||
"kind": "struct",
|
||||
"fields": [
|
||||
{
|
||||
"name": "winner",
|
||||
"name": "game_id",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "owner",
|
||||
"type": "pubkey"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "create_bet",
|
||||
"discriminator": [
|
||||
197,
|
||||
42,
|
||||
153,
|
||||
2,
|
||||
59,
|
||||
63,
|
||||
143,
|
||||
246
|
||||
],
|
||||
"accounts": [
|
||||
{
|
||||
"name": "payer",
|
||||
"writable": true,
|
||||
"signer": true
|
||||
},
|
||||
{
|
||||
"name": "bets_list",
|
||||
"writable": true
|
||||
"name": "owner_id",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "bet_vault",
|
||||
"writable": true,
|
||||
"pda": {
|
||||
"seeds": [
|
||||
{
|
||||
"kind": "const",
|
||||
"value": [
|
||||
98,
|
||||
101,
|
||||
116,
|
||||
95,
|
||||
118,
|
||||
97,
|
||||
117,
|
||||
108,
|
||||
116
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "account",
|
||||
"path": "payer"
|
||||
},
|
||||
{
|
||||
"kind": "arg",
|
||||
"path": "game_id"
|
||||
},
|
||||
{
|
||||
"kind": "arg",
|
||||
"path": "_nonce"
|
||||
}
|
||||
]
|
||||
}
|
||||
"name": "joiner",
|
||||
"type": "pubkey"
|
||||
},
|
||||
{
|
||||
"name": "system_program",
|
||||
"address": "11111111111111111111111111111111"
|
||||
}
|
||||
],
|
||||
"args": [
|
||||
"name": "joiner_id",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "wager",
|
||||
"type": "u64"
|
||||
},
|
||||
{
|
||||
"name": "game_id",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "nonce",
|
||||
"type": "u64"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "initialize",
|
||||
"discriminator": [
|
||||
175,
|
||||
175,
|
||||
109,
|
||||
31,
|
||||
13,
|
||||
152,
|
||||
155,
|
||||
237
|
||||
],
|
||||
"accounts": [
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "BetsList",
|
||||
"type": {
|
||||
"kind": "struct",
|
||||
"fields": [
|
||||
{
|
||||
"name": "bets_list",
|
||||
"writable": true,
|
||||
"pda": {
|
||||
"seeds": [
|
||||
{
|
||||
"kind": "const",
|
||||
"value": [
|
||||
98,
|
||||
101,
|
||||
116,
|
||||
115,
|
||||
95,
|
||||
108,
|
||||
105,
|
||||
115,
|
||||
116
|
||||
]
|
||||
}
|
||||
]
|
||||
"name": "bets",
|
||||
"type": {
|
||||
"vec": "pubkey"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "payer",
|
||||
"writable": true,
|
||||
"signer": true
|
||||
},
|
||||
{
|
||||
"name": "system_program",
|
||||
"address": "11111111111111111111111111111111"
|
||||
}
|
||||
],
|
||||
"args": []
|
||||
},
|
||||
{
|
||||
"name": "join_bet",
|
||||
"discriminator": [
|
||||
69,
|
||||
116,
|
||||
82,
|
||||
26,
|
||||
144,
|
||||
192,
|
||||
58,
|
||||
238
|
||||
],
|
||||
"accounts": [
|
||||
{
|
||||
"name": "bet_vault",
|
||||
"writable": true
|
||||
},
|
||||
{
|
||||
"name": "payer",
|
||||
"writable": true,
|
||||
"signer": true
|
||||
},
|
||||
{
|
||||
"name": "system_program",
|
||||
"address": "11111111111111111111111111111111"
|
||||
}
|
||||
],
|
||||
"args": [
|
||||
{
|
||||
"name": "game_id",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"accounts": [
|
||||
{
|
||||
"name": "BetVault",
|
||||
"discriminator": [
|
||||
103,
|
||||
78,
|
||||
21,
|
||||
234,
|
||||
18,
|
||||
250,
|
||||
230,
|
||||
209
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "BetsList",
|
||||
"discriminator": [
|
||||
231,
|
||||
234,
|
||||
50,
|
||||
58,
|
||||
81,
|
||||
179,
|
||||
239,
|
||||
117
|
||||
]
|
||||
}
|
||||
],
|
||||
"errors": [
|
||||
{
|
||||
"code": 6000,
|
||||
"name": "CustomError",
|
||||
"msg": "Custom error message"
|
||||
}
|
||||
],
|
||||
"types": [
|
||||
{
|
||||
"name": "BetVault",
|
||||
"type": {
|
||||
"kind": "struct",
|
||||
"fields": [
|
||||
{
|
||||
"name": "game_id",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "owner",
|
||||
"type": "pubkey"
|
||||
},
|
||||
{
|
||||
"name": "joiner",
|
||||
"type": "pubkey"
|
||||
},
|
||||
{
|
||||
"name": "wager",
|
||||
"type": "u64"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "BetsList",
|
||||
"type": {
|
||||
"kind": "struct",
|
||||
"fields": [
|
||||
{
|
||||
"name": "bets",
|
||||
"type": {
|
||||
"vec": "pubkey"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"constants": [
|
||||
{
|
||||
"name": "SEED",
|
||||
"type": "string",
|
||||
"value": "\"anchor\""
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"constants": [
|
||||
{
|
||||
"name": "SEED",
|
||||
"type": "string",
|
||||
"value": "\"anchor\""
|
||||
}
|
||||
]
|
||||
}
|
||||
6
src/shared/data_fetcher.ts
Normal file
6
src/shared/data_fetcher.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
export async function fetchUserById(id: string) {
|
||||
const res = await fetch(`https://vps.playpoolstudios.com/duelfi/api/get_user_by_id.php?id=${id}`);
|
||||
if (!res.ok) return null;
|
||||
return await res.json();
|
||||
}
|
||||
|
||||
|
|
@ -8,7 +8,6 @@ import { Bet } from "@/types/Bet";
|
|||
import { toast } from "sonner";
|
||||
import { Game } from "@/types/Game";
|
||||
|
||||
|
||||
export const fetchOpenBets = async (wallets: ConnectedSolanaWallet): Promise<Bet[]> => {
|
||||
try {
|
||||
if (!wallets) return [];
|
||||
|
|
@ -39,7 +38,9 @@ export const fetchOpenBets = async (wallets: ConnectedSolanaWallet): Promise<Bet
|
|||
return {
|
||||
id: betAcc.gameId,
|
||||
owner: betAcc.owner.toBase58(),
|
||||
owner_id:betAcc.ownerId,
|
||||
joiner: betAcc.joiner ? betAcc.joiner.toBase58() : "Open",
|
||||
joiner_id:betAcc.joinerId,
|
||||
wager: betAcc.wager.toNumber() / LAMPORTS_PER_SOL
|
||||
};
|
||||
})
|
||||
|
|
@ -120,7 +121,7 @@ export const fetchOpenBets = async (wallets: ConnectedSolanaWallet): Promise<Bet
|
|||
}
|
||||
|
||||
|
||||
export async function createBet(wallets:ConnectedSolanaWallet,selectedPrice:number,selectedGame:Game):Promise<string>{
|
||||
export async function createBet(wallets:ConnectedSolanaWallet, uid:string,selectedPrice:number,selectedGame:Game):Promise<string>{
|
||||
const connection = new Connection(CLUSTER_URL);
|
||||
const wallet = {
|
||||
publicKey: new PublicKey(wallets.address),
|
||||
|
|
@ -134,7 +135,7 @@ export async function createBet(wallets:ConnectedSolanaWallet,selectedPrice:numb
|
|||
const program = new Program<Bets>(idl, provider);
|
||||
|
||||
try {
|
||||
const nonce = 1;
|
||||
const nonce = getRandomInt(100000000);
|
||||
const [bet_list_pda] = await PublicKey.findProgramAddress(
|
||||
[Buffer.from("bets_list")],
|
||||
program.programId
|
||||
|
|
@ -145,7 +146,7 @@ export async function createBet(wallets:ConnectedSolanaWallet,selectedPrice:numb
|
|||
|
||||
// Create transaction
|
||||
const tx = await program.methods
|
||||
.createBet(new BN(selectedPrice * 1000000000), selectedGame.id, new BN(nonce))
|
||||
.createBet(new BN(selectedPrice * 1000000000),uid, selectedGame.id, new BN(nonce))
|
||||
.accounts({
|
||||
betsList: bet_list_pda,
|
||||
})
|
||||
|
|
@ -177,3 +178,7 @@ export async function createBet(wallets:ConnectedSolanaWallet,selectedPrice:numb
|
|||
|
||||
return "";
|
||||
}
|
||||
|
||||
function getRandomInt(max:number):number {
|
||||
return Math.floor(Math.random() * max);
|
||||
}
|
||||
|
|
@ -1,6 +1,14 @@
|
|||
export interface Bet {
|
||||
id: string;
|
||||
owner: string;
|
||||
owner_id:string;
|
||||
joiner: string;
|
||||
joiner_id:string;
|
||||
wager: number;
|
||||
ownerProfile?: {
|
||||
id: string;
|
||||
username: string;
|
||||
bio: string;
|
||||
x_profile_url: string;
|
||||
};
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user