Merge remote-tracking branch 'origin/dev'

This commit is contained in:
Sewmina 2025-06-27 23:33:04 +00:00
commit 818b00dc55
28 changed files with 2459 additions and 70 deletions

205
package-lock.json generated
View File

@ -12,6 +12,7 @@
"@coral-xyz/anchor": "^0.31.0",
"@fontsource/manrope": "^5.2.5",
"@privy-io/react-auth": "^2.7.2",
"@solana/spl-token": "^0.4.13",
"@solana/web3.js": "^1.98.0",
"axios": "^1.8.4",
"bs58": "^6.0.0",
@ -2305,6 +2306,193 @@
"node": ">=5.10"
}
},
"node_modules/@solana/buffer-layout-utils": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/@solana/buffer-layout-utils/-/buffer-layout-utils-0.2.0.tgz",
"integrity": "sha512-szG4sxgJGktbuZYDg2FfNmkMi0DYQoVjN2h7ta1W1hPrwzarcFLBq9UpX1UjNXsNpT9dn+chgprtWGioUAr4/g==",
"license": "Apache-2.0",
"dependencies": {
"@solana/buffer-layout": "^4.0.0",
"@solana/web3.js": "^1.32.0",
"bigint-buffer": "^1.1.5",
"bignumber.js": "^9.0.1"
},
"engines": {
"node": ">= 10"
}
},
"node_modules/@solana/codecs": {
"version": "2.0.0-rc.1",
"resolved": "https://registry.npmjs.org/@solana/codecs/-/codecs-2.0.0-rc.1.tgz",
"integrity": "sha512-qxoR7VybNJixV51L0G1RD2boZTcxmwUWnKCaJJExQ5qNKwbpSyDdWfFJfM5JhGyKe9DnPVOZB+JHWXnpbZBqrQ==",
"license": "MIT",
"dependencies": {
"@solana/codecs-core": "2.0.0-rc.1",
"@solana/codecs-data-structures": "2.0.0-rc.1",
"@solana/codecs-numbers": "2.0.0-rc.1",
"@solana/codecs-strings": "2.0.0-rc.1",
"@solana/options": "2.0.0-rc.1"
},
"peerDependencies": {
"typescript": ">=5"
}
},
"node_modules/@solana/codecs-core": {
"version": "2.0.0-rc.1",
"resolved": "https://registry.npmjs.org/@solana/codecs-core/-/codecs-core-2.0.0-rc.1.tgz",
"integrity": "sha512-bauxqMfSs8EHD0JKESaNmNuNvkvHSuN3bbWAF5RjOfDu2PugxHrvRebmYauvSumZ3cTfQ4HJJX6PG5rN852qyQ==",
"license": "MIT",
"dependencies": {
"@solana/errors": "2.0.0-rc.1"
},
"peerDependencies": {
"typescript": ">=5"
}
},
"node_modules/@solana/codecs-data-structures": {
"version": "2.0.0-rc.1",
"resolved": "https://registry.npmjs.org/@solana/codecs-data-structures/-/codecs-data-structures-2.0.0-rc.1.tgz",
"integrity": "sha512-rinCv0RrAVJ9rE/rmaibWJQxMwC5lSaORSZuwjopSUE6T0nb/MVg6Z1siNCXhh/HFTOg0l8bNvZHgBcN/yvXog==",
"license": "MIT",
"dependencies": {
"@solana/codecs-core": "2.0.0-rc.1",
"@solana/codecs-numbers": "2.0.0-rc.1",
"@solana/errors": "2.0.0-rc.1"
},
"peerDependencies": {
"typescript": ">=5"
}
},
"node_modules/@solana/codecs-numbers": {
"version": "2.0.0-rc.1",
"resolved": "https://registry.npmjs.org/@solana/codecs-numbers/-/codecs-numbers-2.0.0-rc.1.tgz",
"integrity": "sha512-J5i5mOkvukXn8E3Z7sGIPxsThRCgSdgTWJDQeZvucQ9PT6Y3HiVXJ0pcWiOWAoQ3RX8e/f4I3IC+wE6pZiJzDQ==",
"license": "MIT",
"dependencies": {
"@solana/codecs-core": "2.0.0-rc.1",
"@solana/errors": "2.0.0-rc.1"
},
"peerDependencies": {
"typescript": ">=5"
}
},
"node_modules/@solana/codecs-strings": {
"version": "2.0.0-rc.1",
"resolved": "https://registry.npmjs.org/@solana/codecs-strings/-/codecs-strings-2.0.0-rc.1.tgz",
"integrity": "sha512-9/wPhw8TbGRTt6mHC4Zz1RqOnuPTqq1Nb4EyuvpZ39GW6O2t2Q7Q0XxiB3+BdoEjwA2XgPw6e2iRfvYgqty44g==",
"license": "MIT",
"dependencies": {
"@solana/codecs-core": "2.0.0-rc.1",
"@solana/codecs-numbers": "2.0.0-rc.1",
"@solana/errors": "2.0.0-rc.1"
},
"peerDependencies": {
"fastestsmallesttextencoderdecoder": "^1.0.22",
"typescript": ">=5"
}
},
"node_modules/@solana/errors": {
"version": "2.0.0-rc.1",
"resolved": "https://registry.npmjs.org/@solana/errors/-/errors-2.0.0-rc.1.tgz",
"integrity": "sha512-ejNvQ2oJ7+bcFAYWj225lyRkHnixuAeb7RQCixm+5mH4n1IA4Qya/9Bmfy5RAAHQzxK43clu3kZmL5eF9VGtYQ==",
"license": "MIT",
"dependencies": {
"chalk": "^5.3.0",
"commander": "^12.1.0"
},
"bin": {
"errors": "bin/cli.mjs"
},
"peerDependencies": {
"typescript": ">=5"
}
},
"node_modules/@solana/errors/node_modules/chalk": {
"version": "5.4.1",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz",
"integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==",
"license": "MIT",
"engines": {
"node": "^12.17.0 || ^14.13 || >=16.0.0"
},
"funding": {
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
"node_modules/@solana/errors/node_modules/commander": {
"version": "12.1.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz",
"integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==",
"license": "MIT",
"engines": {
"node": ">=18"
}
},
"node_modules/@solana/options": {
"version": "2.0.0-rc.1",
"resolved": "https://registry.npmjs.org/@solana/options/-/options-2.0.0-rc.1.tgz",
"integrity": "sha512-mLUcR9mZ3qfHlmMnREdIFPf9dpMc/Bl66tLSOOWxw4ml5xMT2ohFn7WGqoKcu/UHkT9CrC6+amEdqCNvUqI7AA==",
"license": "MIT",
"dependencies": {
"@solana/codecs-core": "2.0.0-rc.1",
"@solana/codecs-data-structures": "2.0.0-rc.1",
"@solana/codecs-numbers": "2.0.0-rc.1",
"@solana/codecs-strings": "2.0.0-rc.1",
"@solana/errors": "2.0.0-rc.1"
},
"peerDependencies": {
"typescript": ">=5"
}
},
"node_modules/@solana/spl-token": {
"version": "0.4.13",
"resolved": "https://registry.npmjs.org/@solana/spl-token/-/spl-token-0.4.13.tgz",
"integrity": "sha512-cite/pYWQZZVvLbg5lsodSovbetK/eA24gaR0eeUeMuBAMNrT8XFCwaygKy0N2WSg3gSyjjNpIeAGBAKZaY/1w==",
"license": "Apache-2.0",
"dependencies": {
"@solana/buffer-layout": "^4.0.0",
"@solana/buffer-layout-utils": "^0.2.0",
"@solana/spl-token-group": "^0.0.7",
"@solana/spl-token-metadata": "^0.1.6",
"buffer": "^6.0.3"
},
"engines": {
"node": ">=16"
},
"peerDependencies": {
"@solana/web3.js": "^1.95.5"
}
},
"node_modules/@solana/spl-token-group": {
"version": "0.0.7",
"resolved": "https://registry.npmjs.org/@solana/spl-token-group/-/spl-token-group-0.0.7.tgz",
"integrity": "sha512-V1N/iX7Cr7H0uazWUT2uk27TMqlqedpXHRqqAbVO2gvmJyT0E0ummMEAVQeXZ05ZhQ/xF39DLSdBp90XebWEug==",
"license": "Apache-2.0",
"dependencies": {
"@solana/codecs": "2.0.0-rc.1"
},
"engines": {
"node": ">=16"
},
"peerDependencies": {
"@solana/web3.js": "^1.95.3"
}
},
"node_modules/@solana/spl-token-metadata": {
"version": "0.1.6",
"resolved": "https://registry.npmjs.org/@solana/spl-token-metadata/-/spl-token-metadata-0.1.6.tgz",
"integrity": "sha512-7sMt1rsm/zQOQcUWllQX9mD2O6KhSAtY1hFR2hfFwgqfFWzSY9E9GDvFVNYUI1F0iQKcm6HmePU9QbKRXTEBiA==",
"license": "Apache-2.0",
"dependencies": {
"@solana/codecs": "2.0.0-rc.1"
},
"engines": {
"node": ">=16"
},
"peerDependencies": {
"@solana/web3.js": "^1.95.3"
}
},
"node_modules/@solana/wallet-adapter-base": {
"version": "0.9.23",
"resolved": "https://registry.npmjs.org/@solana/wallet-adapter-base/-/wallet-adapter-base-0.9.23.tgz",
@ -4309,6 +4497,15 @@
"node": ">= 10.0.0"
}
},
"node_modules/bignumber.js": {
"version": "9.3.0",
"resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.0.tgz",
"integrity": "sha512-EM7aMFTXbptt/wZdMlBv2t8IViwQL+h6SLHosp8Yf0dqJMTnY6iL32opnAB6kAdL0SZPuvcAzFr31o0c/R3/RA==",
"license": "MIT",
"engines": {
"node": "*"
}
},
"node_modules/bindings": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
@ -6060,6 +6257,13 @@
"integrity": "sha512-wpYMUmFu5f00Sm0cj2pfivpmawLZ0NKdviQ4w9zJeR8JVtOpOxHmLaJuj0vxvGqMJQWyP/COUkF75/57OKyRag==",
"license": "MIT"
},
"node_modules/fastestsmallesttextencoderdecoder": {
"version": "1.0.22",
"resolved": "https://registry.npmjs.org/fastestsmallesttextencoderdecoder/-/fastestsmallesttextencoderdecoder-1.0.22.tgz",
"integrity": "sha512-Pb8d48e+oIuY4MaM64Cd7OW1gt4nxCHs7/ddPPZ/Ic3sg8yVGM7O9wDvZ7us6ScaUupzM+pfBolwtYhN1IxBIw==",
"license": "CC0-1.0",
"peer": true
},
"node_modules/fastq": {
"version": "1.19.1",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz",
@ -10034,7 +10238,6 @@
"version": "5.8.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz",
"integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==",
"devOptional": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",

View File

@ -13,6 +13,7 @@
"@coral-xyz/anchor": "^0.31.0",
"@fontsource/manrope": "^5.2.5",
"@privy-io/react-auth": "^2.7.2",
"@solana/spl-token": "^0.4.13",
"@solana/web3.js": "^1.98.0",
"axios": "^1.8.4",
"bs58": "^6.0.0",

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -8,8 +8,11 @@ import { PriceSelection } from "./PriceSelection";
import { GameSelection } from "./GameSelection";
import { createBet } from "@/shared/solana_helpers";
import { Game } from "@/types/Game";
import { Currency } from "@/types/Currency";
import { getDefaultCurrency } from "@/data/currencies";
import { connection, EXPLORER_TX_TEMPLATE } from "@/data/shared";
import { CONFIRMATION_THRESHOLD } from "@/shared/constants";
interface GameModalProps {
isOpen: boolean;
onClose: () => void;
@ -21,6 +24,7 @@ export default function GameModal({ isOpen, onClose }: GameModalProps) {
const [selectedGame, setSelectedGame] = useState<Game | null>(null);
const [selectedPrice, setSelectedPrice] = useState<number | null>(null);
const [selectedCurrency, setSelectedCurrency] = useState<Currency>(getDefaultCurrency());
const [isProcessing, setIsProcessing] = useState(false);
const [shouldRender, setShouldRender] = useState(isOpen);
@ -64,7 +68,7 @@ export default function GameModal({ isOpen, onClose }: GameModalProps) {
toast.loading("Creating Game");
try {
//const ownerProfile = await fetchUserById(user?.id ?? "");
const tx = await createBet(wallet, user?.id ?? "", selectedPrice, selectedGame.id, false);
const tx = await createBet(wallet, user?.id ?? "", selectedPrice, selectedGame.id, false, selectedCurrency.tokenAddress);
const url = EXPLORER_TX_TEMPLATE.replace("{address}", tx);
if (tx.length > 5) {
connection.confirmTransaction(tx, CONFIRMATION_THRESHOLD).finally(()=>{
@ -140,6 +144,8 @@ export default function GameModal({ isOpen, onClose }: GameModalProps) {
<PriceSelection
selectedPrice={selectedPrice}
onSelect={setSelectedPrice}
selectedCurrency={selectedCurrency}
onCurrencySelect={setSelectedCurrency}
/>
<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"

View File

@ -14,6 +14,7 @@ import { ConnectedSolanaWallet, usePrivy, useSolanaWallets } from "@privy-io/rea
import { RematchModal } from "./RematchModal";
import { API_URL, CLUSTER_URL } from '../data/shared';
import { clusterApiUrl } from "@solana/web3.js";
import { getCurrencyByMint } from "@/data/currencies";
export default function HeroSection() {
const [isModalOpen, setIsModalOpen] = useState(false);
@ -100,13 +101,15 @@ export default function HeroSection() {
setRematchTxError(false);
try {
const currency = getCurrencyByMint(lastActiveBet.currency ?? "SOL");
// Step 1: Create new bet
const tx = await createBet(
solWallet,
user?.id ?? "",
lastActiveBet.wager,
lastActiveBet.id,
true
true,
currency?.tokenAddress ?? "11111111111111111111111111111111"
);
console.log("Rematch created. Transaction ID:", tx);

View File

@ -2,7 +2,7 @@ import { useEffect, useState } from "react";
import Image from "next/image";
import { games } from "../data/games";
import { usePrivy, useSolanaWallets } from "@privy-io/react-auth";
import { joinBet } from "@/shared/solana_helpers";
import { joinBet, getVaultByAddress } from "@/shared/solana_helpers";
import { Bet } from "../types/Bet";
import { fetchUserById } from "@/shared/data_fetcher";
import { toast } from "sonner";
@ -77,14 +77,29 @@ export default function YourGames({ bets }: GameModalProps) {
console.log("No user found, showing all bets");
}
// Enrich bets with currency information and owner profiles
const enrichedBets = await Promise.all(
filteredBets.map(async (bet) => {
const ownerProfile = await fetchUserById(bet.owner_id);
return {
...bet,
ownerProfile,
};
// Fetch currency information if not already present
let enrichedBet = { ...bet, ownerProfile };
if (!bet.currency && wallets && wallets.length > 0) {
try {
const solanaWallet = wallets.find(wallet => wallet.type === "solana");
if (solanaWallet) {
const vaultBet = await getVaultByAddress(solanaWallet, bet.address);
if (vaultBet && vaultBet.currency) {
enrichedBet = { ...enrichedBet, currency: vaultBet.currency };
}
}
} catch (error) {
console.error("Error fetching currency for bet:", error);
enrichedBet = { ...enrichedBet, currency: "SOL" }; // Default fallback
}
}
return enrichedBet;
})
);
@ -149,8 +164,8 @@ export default function YourGames({ bets }: GameModalProps) {
</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 * WAGER_PRIZE_MULT).toFixed(3)} SOL</p>
<p className="text-white">{bet.wager} {bet.currency}</p>
<p className="text-white text-right">{(bet.wager * 2 * WAGER_PRIZE_MULT).toFixed(3)} {bet.currency}</p>
</div>
{bet.ownerProfile && (
@ -248,11 +263,11 @@ export default function YourGames({ bets }: GameModalProps) {
)}
<div className="flex justify-between items-center">
<span className="text-gray-400">Entry:</span>
<span className="font-bold">{selectedBet.wager} SOL</span>
<span className="font-bold">{selectedBet.wager} {selectedBet.currency}</span>
</div>
<div className="flex justify-between items-center">
<span className="text-gray-400">Prize:</span>
<span className="font-bold">{(selectedBet.wager * 2 * WAGER_PRIZE_MULT).toFixed(3)} SOL</span>
<span className="font-bold">{(selectedBet.wager * 2 * WAGER_PRIZE_MULT).toFixed(3)} {selectedBet.currency}</span>
</div>
</div>
</div>

View File

@ -1,13 +1,27 @@
import { useState, useEffect } from "react";
import { Currency } from "../types/Currency";
import { currencies, getDefaultCurrency } from "../data/currencies";
import { getTokenBalance } from "../shared/solana_helpers";
import { useSolanaWallets } from "@privy-io/react-auth";
interface PriceSelectionProps {
selectedPrice: number | null;
onSelect: (price: number) => void;
selectedCurrency?: Currency;
onCurrencySelect?: (currency: Currency) => void;
}
export function PriceSelection({ selectedPrice, onSelect }: PriceSelectionProps) {
const presets = [0.01,0.05, 0.1, 0.2, 0.5, 1.0];
export function PriceSelection({
selectedPrice,
onSelect,
selectedCurrency = getDefaultCurrency(),
onCurrencySelect
}: PriceSelectionProps) {
const { wallets } = useSolanaWallets();
const [inputValue, setInputValue] = useState<string>("");
const [balance, setBalance] = useState<number | null>(null);
const [isLoadingBalance, setIsLoadingBalance] = useState(false);
const MIN_AMOUNT = 0.01;
useEffect(() => {
@ -16,6 +30,34 @@ export function PriceSelection({ selectedPrice, onSelect }: PriceSelectionProps)
}
}, [selectedPrice]);
useEffect(() => {
const fetchBalance = async () => {
if (!wallets || wallets.length === 0) {
setBalance(null);
return;
}
const solanaWallet = wallets.find(wallet => wallet.type === "solana");
if (!solanaWallet) {
setBalance(null);
return;
}
setIsLoadingBalance(true);
try {
const tokenBalance = await getTokenBalance(solanaWallet.address, selectedCurrency);
setBalance(tokenBalance);
} catch (error) {
console.error("Error fetching balance:", error);
setBalance(null);
} finally {
setIsLoadingBalance(false);
}
};
fetchBalance();
}, [wallets, selectedCurrency]);
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value;
setInputValue(value);
@ -30,16 +72,46 @@ export function PriceSelection({ selectedPrice, onSelect }: PriceSelectionProps)
setInputValue(value.toString());
};
const handleCurrencyChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
const selectedSymbol = e.target.value;
const currency = currencies.find(c => c.symbol === selectedSymbol);
if (currency && onCurrencySelect) {
onCurrencySelect(currency);
}
};
const formatBalance = (balance: number | null) => {
if (balance === null) return "Loading...";
if (balance === 0) return "0";
return balance.toFixed(4);
};
return (
<div className="mb-6 space-y-2">
<label className="block text-sm text-gray-300 font-mono mb-1">Select Currency</label>
<select
value={selectedCurrency.symbol}
onChange={handleCurrencyChange}
className="bg-[rgb(10,10,10)] border border-gray-700 text-[rgb(248,144,22)] font-mono px-2 py-1 rounded-lg w-full cursor-pointer"
>
<option>SOL</option>
{currencies.map((currency) => (
<option key={currency.symbol} value={currency.symbol}>
{currency.symbol} - {currency.name}
</option>
))}
</select>
<label className="block text-sm text-gray-300 font-mono mb-1">Entry Price (SOL)</label>
<div className="flex items-center justify-between text-sm text-gray-400 font-mono">
<span>Wallet Balance:</span>
<span className={`${isLoadingBalance ? 'text-gray-500' : 'text-[rgb(248,144,22)]'}`}>
{formatBalance(balance)} {selectedCurrency.symbol}
</span>
</div>
<label className="block text-sm text-gray-300 font-mono mb-1">
Entry Price ({selectedCurrency.symbol})
</label>
<div className="flex items-center gap-2">
<input
type="number"
@ -47,7 +119,7 @@ export function PriceSelection({ selectedPrice, onSelect }: PriceSelectionProps)
value={inputValue}
onChange={handleInputChange}
className="bg-[rgb(10,10,10)] border border-gray-700 text-[rgb(248,144,22)] font-mono px-2 py-1 rounded-lg flex-1 appearance-none no-spinner"
placeholder="e.g. 0.5"
placeholder={`e.g. 0.5`}
/>
</div>
<div className="flex items-center gap-2">
@ -60,7 +132,7 @@ export function PriceSelection({ selectedPrice, onSelect }: PriceSelectionProps)
</div>
<div className="flex flex-wrap gap-1.5 mt-2">
{presets.map((price) => (
{selectedCurrency.price_presets.map((price: number) => (
<button
key={price}
onClick={() => handlePresetClick(price)}

View File

@ -4,7 +4,7 @@ import Image from "next/image";
import { games } from "../data/games";
import { FiTrash } from "react-icons/fi";
import { usePrivy, useSolanaWallets } from "@privy-io/react-auth";
import { closeBet } from "@/shared/solana_helpers";
import { closeBet, getVaultByAddress } from "@/shared/solana_helpers";
import { Bet } from "../types/Bet";
import { toast } from "sonner";
import { connection, EXPLORER_TX_TEMPLATE } from "@/data/shared";
@ -29,8 +29,35 @@ export default function YourGames({bets}:GameModalProps) {
wallet = _wallet;
}
});
setMyBets(bets.filter((bet) => bet.owner === wallet.address));
// Filter bets owned by the current user
const userBets = bets.filter((bet) => bet.owner === wallet.address);
// Enrich bets with currency information
const enrichedBets = await Promise.all(
userBets.map(async (bet) => {
// Fetch currency information if not already present
let enrichedBet = { ...bet };
if (!bet.currency && wallets && wallets.length > 0) {
try {
const solanaWallet = wallets.find(wallet => wallet.type === "solana");
if (solanaWallet) {
const vaultBet = await getVaultByAddress(solanaWallet, bet.address);
if (vaultBet && vaultBet.currency) {
enrichedBet = { ...enrichedBet, currency: vaultBet.currency };
}
}
} catch (error) {
console.error("Error fetching currency for bet:", error);
enrichedBet = { ...enrichedBet, currency: "SOL" }; // Default fallback
}
}
return enrichedBet;
})
);
setMyBets(enrichedBets);
}
setLoading(false);
};
@ -113,7 +140,7 @@ export default function YourGames({bets}:GameModalProps) {
<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">Entry</p>
<p className="text-xs text-white font-mono py-1">{bet.wager} SOL</p>
<p className="text-xs text-white font-mono py-1">{bet.wager} {bet.currency}</p>
</div>
{/* Close Button */}

43
src/data/currencies.ts Normal file
View File

@ -0,0 +1,43 @@
import { Currency } from '../types/Currency';
const OLD_TOKEN_PROGRAM_ID = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA";
const NEW_TOKEN_PROGRAM_ID = "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb";
export const currencies: Currency[] = [
{
name: "Solana",
symbol: "SOL",
tokenAddress: "11111111111111111111111111111111", // Native SOL
tokenProgram: NEW_TOKEN_PROGRAM_ID,
decimals: 9,
price_presets:[0.01, 0.05, 0.1, 0.5, 1]
},
{
name: "Tether USD",
symbol: "USDT",
tokenAddress: "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB",
tokenProgram: OLD_TOKEN_PROGRAM_ID,
decimals: 6,
price_presets:[0.5, 1, 5, 10, 20]
},
{
name: "DuelFi",
symbol: "DUELFI",
tokenAddress: "FiQBQdASK3AJVZfE7Nc5KwHvQiTd8YepsHAexopPF1eS",
tokenProgram: OLD_TOKEN_PROGRAM_ID,
decimals: 9,
price_presets:[100, 500, 1000, 5000, 10000]
}
];
export const getCurrencyBySymbol = (symbol: string): Currency | undefined => {
return currencies.find(currency => currency.symbol === symbol);
};
export const getCurrencyByMint = (mint: string): Currency | undefined => {
return currencies.find(currency => currency.tokenAddress === mint);
};
export const getDefaultCurrency = (): Currency => {
return currencies[0]; // SOL as default
};

View File

@ -27,7 +27,7 @@ export const games = [
name:"Bubble Blast",
entryFee: "0.05 ETH",
thumbnail: "/duelfiassets/Bubble Blast Game Cover Illustration.png",
isAvailable: false
isAvailable: true
}
];

View File

@ -1,10 +1,11 @@
import { Connection } from "@solana/web3.js";
import { clusterApiUrl, Connection } from "@solana/web3.js";
// Replace this URL with your dedicated RPC endpoint
// You can get one from providers like QuickNode, Alchemy, Helius, or GenesysGo
// export const CLUSTER_URL = "https://tiniest-cold-darkness.solana-mainnet.quiknode.pro/72332d636ff78d498b880bd8fdc3eb646c827da8/";
// export const CLUSTER_URL = "https://go.getblock.io/908837801b534ae7a6f0869fc44cc567";
export const CLUSTER_URL = "https://solana-mainnet.core.chainstack.com/c54e14eef17693283a0323efcc4ce731";
export const CLUSTER_URL_DEV= clusterApiUrl("devnet");
// export const CLUSTER_URL = "https://mainnet.helius-rpc.com/?api-key=72332d63-6ff7-4d49-8b88-0bd8fdc3eb64";
// export const CLUSTER_URL = clusterApiUrl("devnet");
export const EXPLORER_ADDRESS_TEMPLATE = "https://explorer.solana.com/address/{address}";
@ -16,3 +17,6 @@ export const API_BASE_URL = "/api/";
export const VALIDATOR_URL = "https://validator.duelfi.io/";
export const VALIDATOR_URL_DEV = "https://validatordev.duelfi.io/";
export const TOKEN_PROGRAM_ID = "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb";
export const TOKEN_PROGRAM_ID_OLD = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA";

View File

@ -28,7 +28,25 @@ export type Bets = {
"accounts": [
{
"name": "betsList",
"writable": true
"writable": true,
"pda": {
"seeds": [
{
"kind": "const",
"value": [
98,
101,
116,
115,
95,
108,
105,
115,
116
]
}
]
}
},
{
"name": "payer",
@ -84,6 +102,218 @@ export type Bets = {
}
]
},
{
"name": "closeBetToken",
"discriminator": [
253,
179,
157,
65,
93,
13,
142,
130
],
"accounts": [
{
"name": "betsList",
"writable": true
},
{
"name": "betVault",
"writable": true,
"pda": {
"seeds": [
{
"kind": "const",
"value": [
98,
101,
116,
95,
118,
97,
117,
108,
116
]
},
{
"kind": "arg",
"path": "owner"
},
{
"kind": "arg",
"path": "gameId"
},
{
"kind": "arg",
"path": "nonce"
}
]
}
},
{
"name": "winner",
"writable": true
},
{
"name": "payer",
"writable": true,
"signer": true
},
{
"name": "tokenMint"
},
{
"name": "tokenVault",
"writable": true,
"pda": {
"seeds": [
{
"kind": "account",
"path": "betVault"
},
{
"kind": "account",
"path": "tokenProgram"
},
{
"kind": "account",
"path": "tokenMint"
}
],
"program": {
"kind": "const",
"value": [
140,
151,
37,
143,
78,
36,
137,
241,
187,
61,
16,
41,
20,
142,
13,
131,
11,
90,
19,
153,
218,
255,
16,
132,
4,
142,
123,
216,
219,
233,
248,
89
]
}
}
},
{
"name": "winnerTokenAccount",
"writable": true,
"pda": {
"seeds": [
{
"kind": "arg",
"path": "winner"
},
{
"kind": "account",
"path": "tokenProgram"
},
{
"kind": "account",
"path": "tokenMint"
}
],
"program": {
"kind": "const",
"value": [
140,
151,
37,
143,
78,
36,
137,
241,
187,
61,
16,
41,
20,
142,
13,
131,
11,
90,
19,
153,
218,
255,
16,
132,
4,
142,
123,
216,
219,
233,
248,
89
]
}
}
},
{
"name": "systemProgram",
"address": "11111111111111111111111111111111"
},
{
"name": "tokenProgram"
},
{
"name": "associatedTokenProgram",
"address": "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"
}
],
"args": [
{
"name": "winner",
"type": "pubkey"
},
{
"name": "userid",
"type": "string"
},
{
"name": "owner",
"type": "pubkey"
},
{
"name": "gameId",
"type": "string"
},
{
"name": "nonce",
"type": "u64"
}
]
},
{
"name": "createBet",
"discriminator": [
@ -164,6 +394,210 @@ export type Bets = {
}
]
},
{
"name": "createBetToken",
"discriminator": [
112,
150,
197,
85,
168,
49,
140,
199
],
"accounts": [
{
"name": "payer",
"writable": true,
"signer": true
},
{
"name": "tokenMint"
},
{
"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"
}
]
}
},
{
"name": "payerTokenAccount",
"writable": true,
"pda": {
"seeds": [
{
"kind": "account",
"path": "payer"
},
{
"kind": "account",
"path": "tokenProgram"
},
{
"kind": "account",
"path": "tokenMint"
}
],
"program": {
"kind": "const",
"value": [
140,
151,
37,
143,
78,
36,
137,
241,
187,
61,
16,
41,
20,
142,
13,
131,
11,
90,
19,
153,
218,
255,
16,
132,
4,
142,
123,
216,
219,
233,
248,
89
]
}
}
},
{
"name": "tokenVault",
"writable": true,
"pda": {
"seeds": [
{
"kind": "account",
"path": "betVault"
},
{
"kind": "account",
"path": "tokenProgram"
},
{
"kind": "account",
"path": "tokenMint"
}
],
"program": {
"kind": "const",
"value": [
140,
151,
37,
143,
78,
36,
137,
241,
187,
61,
16,
41,
20,
142,
13,
131,
11,
90,
19,
153,
218,
255,
16,
132,
4,
142,
123,
216,
219,
233,
248,
89
]
}
}
},
{
"name": "systemProgram",
"address": "11111111111111111111111111111111"
},
{
"name": "tokenProgram"
},
{
"name": "associatedTokenProgram",
"address": "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"
}
],
"args": [
{
"name": "wager",
"type": "u64"
},
{
"name": "userId",
"type": "string"
},
{
"name": "gameId",
"type": "string"
},
{
"name": "nonce",
"type": "u64"
}
]
},
{
"name": "deductFees",
"discriminator": [
@ -218,6 +652,340 @@ export type Bets = {
}
]
},
{
"name": "deductFeesToken",
"discriminator": [
92,
93,
60,
121,
30,
164,
148,
135
],
"accounts": [
{
"name": "betsList",
"writable": true
},
{
"name": "betVault",
"writable": true,
"pda": {
"seeds": [
{
"kind": "const",
"value": [
98,
101,
116,
95,
118,
97,
117,
108,
116
]
},
{
"kind": "arg",
"path": "owner"
},
{
"kind": "arg",
"path": "gameId"
},
{
"kind": "arg",
"path": "nonce"
}
]
}
},
{
"name": "feeWallet",
"writable": true
},
{
"name": "payer",
"writable": true,
"signer": true
},
{
"name": "ownerReferrer",
"writable": true
},
{
"name": "joinerReferrer",
"writable": true
},
{
"name": "tokenMint"
},
{
"name": "tokenVault",
"writable": true,
"pda": {
"seeds": [
{
"kind": "account",
"path": "betVault"
},
{
"kind": "account",
"path": "tokenProgram"
},
{
"kind": "account",
"path": "tokenMint"
}
],
"program": {
"kind": "const",
"value": [
140,
151,
37,
143,
78,
36,
137,
241,
187,
61,
16,
41,
20,
142,
13,
131,
11,
90,
19,
153,
218,
255,
16,
132,
4,
142,
123,
216,
219,
233,
248,
89
]
}
}
},
{
"name": "feeWalletTokenAccount",
"writable": true,
"pda": {
"seeds": [
{
"kind": "account",
"path": "feeWallet"
},
{
"kind": "account",
"path": "tokenProgram"
},
{
"kind": "account",
"path": "tokenMint"
}
],
"program": {
"kind": "const",
"value": [
140,
151,
37,
143,
78,
36,
137,
241,
187,
61,
16,
41,
20,
142,
13,
131,
11,
90,
19,
153,
218,
255,
16,
132,
4,
142,
123,
216,
219,
233,
248,
89
]
}
}
},
{
"name": "ownerReferrerTokenAccount",
"writable": true,
"pda": {
"seeds": [
{
"kind": "account",
"path": "ownerReferrer"
},
{
"kind": "account",
"path": "tokenProgram"
},
{
"kind": "account",
"path": "tokenMint"
}
],
"program": {
"kind": "const",
"value": [
140,
151,
37,
143,
78,
36,
137,
241,
187,
61,
16,
41,
20,
142,
13,
131,
11,
90,
19,
153,
218,
255,
16,
132,
4,
142,
123,
216,
219,
233,
248,
89
]
}
}
},
{
"name": "joinerReferrerTokenAccount",
"writable": true,
"pda": {
"seeds": [
{
"kind": "account",
"path": "joinerReferrer"
},
{
"kind": "account",
"path": "tokenProgram"
},
{
"kind": "account",
"path": "tokenMint"
}
],
"program": {
"kind": "const",
"value": [
140,
151,
37,
143,
78,
36,
137,
241,
187,
61,
16,
41,
20,
142,
13,
131,
11,
90,
19,
153,
218,
255,
16,
132,
4,
142,
123,
216,
219,
233,
248,
89
]
}
}
},
{
"name": "systemProgram",
"address": "11111111111111111111111111111111"
},
{
"name": "tokenProgram"
},
{
"name": "associatedTokenProgram",
"address": "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"
}
],
"args": [
{
"name": "winner",
"type": "pubkey"
},
{
"name": "userid",
"type": "string"
},
{
"name": "owner",
"type": "pubkey"
},
{
"name": "gameId",
"type": "string"
},
{
"name": "nonce",
"type": "u64"
}
]
},
{
"name": "initialize",
"discriminator": [
@ -303,6 +1071,168 @@ export type Bets = {
}
]
},
{
"name": "joinBetToken",
"discriminator": [
166,
253,
62,
118,
163,
87,
166,
204
],
"accounts": [
{
"name": "betVault",
"writable": true
},
{
"name": "payer",
"writable": true,
"signer": true
},
{
"name": "tokenMint"
},
{
"name": "payerTokenAccount",
"writable": true,
"pda": {
"seeds": [
{
"kind": "account",
"path": "payer"
},
{
"kind": "account",
"path": "tokenProgram"
},
{
"kind": "account",
"path": "tokenMint"
}
],
"program": {
"kind": "const",
"value": [
140,
151,
37,
143,
78,
36,
137,
241,
187,
61,
16,
41,
20,
142,
13,
131,
11,
90,
19,
153,
218,
255,
16,
132,
4,
142,
123,
216,
219,
233,
248,
89
]
}
}
},
{
"name": "tokenVault",
"writable": true,
"pda": {
"seeds": [
{
"kind": "account",
"path": "betVault"
},
{
"kind": "account",
"path": "tokenProgram"
},
{
"kind": "account",
"path": "tokenMint"
}
],
"program": {
"kind": "const",
"value": [
140,
151,
37,
143,
78,
36,
137,
241,
187,
61,
16,
41,
20,
142,
13,
131,
11,
90,
19,
153,
218,
255,
16,
132,
4,
142,
123,
216,
219,
233,
248,
89
]
}
}
},
{
"name": "systemProgram",
"address": "11111111111111111111111111111111"
},
{
"name": "tokenProgram"
},
{
"name": "associatedTokenProgram",
"address": "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"
}
],
"args": [
{
"name": "userId",
"type": "string"
},
{
"name": "gameId",
"type": "string"
}
]
},
{
"name": "refundBet",
"discriminator": [
@ -387,6 +1317,10 @@ export type Bets = {
"type": {
"kind": "struct",
"fields": [
{
"name": "nonce",
"type": "u64"
},
{
"name": "gameId",
"type": "string"
@ -407,6 +1341,10 @@ export type Bets = {
"name": "joinerId",
"type": "string"
},
{
"name": "tokenMint",
"type": "pubkey"
},
{
"name": "wager",
"type": "u64"

View File

@ -22,7 +22,25 @@
"accounts": [
{
"name": "bets_list",
"writable": true
"writable": true,
"pda": {
"seeds": [
{
"kind": "const",
"value": [
98,
101,
116,
115,
95,
108,
105,
115,
116
]
}
]
}
},
{
"name": "payer",
@ -78,6 +96,218 @@
}
]
},
{
"name": "close_bet_token",
"discriminator": [
253,
179,
157,
65,
93,
13,
142,
130
],
"accounts": [
{
"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": "arg",
"path": "owner"
},
{
"kind": "arg",
"path": "game_id"
},
{
"kind": "arg",
"path": "nonce"
}
]
}
},
{
"name": "winner",
"writable": true
},
{
"name": "payer",
"writable": true,
"signer": true
},
{
"name": "token_mint"
},
{
"name": "token_vault",
"writable": true,
"pda": {
"seeds": [
{
"kind": "account",
"path": "bet_vault"
},
{
"kind": "account",
"path": "token_program"
},
{
"kind": "account",
"path": "token_mint"
}
],
"program": {
"kind": "const",
"value": [
140,
151,
37,
143,
78,
36,
137,
241,
187,
61,
16,
41,
20,
142,
13,
131,
11,
90,
19,
153,
218,
255,
16,
132,
4,
142,
123,
216,
219,
233,
248,
89
]
}
}
},
{
"name": "winner_token_account",
"writable": true,
"pda": {
"seeds": [
{
"kind": "arg",
"path": "winner"
},
{
"kind": "account",
"path": "token_program"
},
{
"kind": "account",
"path": "token_mint"
}
],
"program": {
"kind": "const",
"value": [
140,
151,
37,
143,
78,
36,
137,
241,
187,
61,
16,
41,
20,
142,
13,
131,
11,
90,
19,
153,
218,
255,
16,
132,
4,
142,
123,
216,
219,
233,
248,
89
]
}
}
},
{
"name": "system_program",
"address": "11111111111111111111111111111111"
},
{
"name": "token_program"
},
{
"name": "associated_token_program",
"address": "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"
}
],
"args": [
{
"name": "winner",
"type": "pubkey"
},
{
"name": "userid",
"type": "string"
},
{
"name": "owner",
"type": "pubkey"
},
{
"name": "game_id",
"type": "string"
},
{
"name": "nonce",
"type": "u64"
}
]
},
{
"name": "create_bet",
"discriminator": [
@ -158,6 +388,210 @@
}
]
},
{
"name": "create_bet_token",
"discriminator": [
112,
150,
197,
85,
168,
49,
140,
199
],
"accounts": [
{
"name": "payer",
"writable": true,
"signer": true
},
{
"name": "token_mint"
},
{
"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"
}
]
}
},
{
"name": "payer_token_account",
"writable": true,
"pda": {
"seeds": [
{
"kind": "account",
"path": "payer"
},
{
"kind": "account",
"path": "token_program"
},
{
"kind": "account",
"path": "token_mint"
}
],
"program": {
"kind": "const",
"value": [
140,
151,
37,
143,
78,
36,
137,
241,
187,
61,
16,
41,
20,
142,
13,
131,
11,
90,
19,
153,
218,
255,
16,
132,
4,
142,
123,
216,
219,
233,
248,
89
]
}
}
},
{
"name": "token_vault",
"writable": true,
"pda": {
"seeds": [
{
"kind": "account",
"path": "bet_vault"
},
{
"kind": "account",
"path": "token_program"
},
{
"kind": "account",
"path": "token_mint"
}
],
"program": {
"kind": "const",
"value": [
140,
151,
37,
143,
78,
36,
137,
241,
187,
61,
16,
41,
20,
142,
13,
131,
11,
90,
19,
153,
218,
255,
16,
132,
4,
142,
123,
216,
219,
233,
248,
89
]
}
}
},
{
"name": "system_program",
"address": "11111111111111111111111111111111"
},
{
"name": "token_program"
},
{
"name": "associated_token_program",
"address": "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"
}
],
"args": [
{
"name": "wager",
"type": "u64"
},
{
"name": "user_id",
"type": "string"
},
{
"name": "game_id",
"type": "string"
},
{
"name": "nonce",
"type": "u64"
}
]
},
{
"name": "deduct_fees",
"discriminator": [
@ -212,6 +646,340 @@
}
]
},
{
"name": "deduct_fees_token",
"discriminator": [
92,
93,
60,
121,
30,
164,
148,
135
],
"accounts": [
{
"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": "arg",
"path": "owner"
},
{
"kind": "arg",
"path": "game_id"
},
{
"kind": "arg",
"path": "nonce"
}
]
}
},
{
"name": "fee_wallet",
"writable": true
},
{
"name": "payer",
"writable": true,
"signer": true
},
{
"name": "owner_referrer",
"writable": true
},
{
"name": "joiner_referrer",
"writable": true
},
{
"name": "token_mint"
},
{
"name": "token_vault",
"writable": true,
"pda": {
"seeds": [
{
"kind": "account",
"path": "bet_vault"
},
{
"kind": "account",
"path": "token_program"
},
{
"kind": "account",
"path": "token_mint"
}
],
"program": {
"kind": "const",
"value": [
140,
151,
37,
143,
78,
36,
137,
241,
187,
61,
16,
41,
20,
142,
13,
131,
11,
90,
19,
153,
218,
255,
16,
132,
4,
142,
123,
216,
219,
233,
248,
89
]
}
}
},
{
"name": "fee_wallet_token_account",
"writable": true,
"pda": {
"seeds": [
{
"kind": "account",
"path": "fee_wallet"
},
{
"kind": "account",
"path": "token_program"
},
{
"kind": "account",
"path": "token_mint"
}
],
"program": {
"kind": "const",
"value": [
140,
151,
37,
143,
78,
36,
137,
241,
187,
61,
16,
41,
20,
142,
13,
131,
11,
90,
19,
153,
218,
255,
16,
132,
4,
142,
123,
216,
219,
233,
248,
89
]
}
}
},
{
"name": "owner_referrer_token_account",
"writable": true,
"pda": {
"seeds": [
{
"kind": "account",
"path": "owner_referrer"
},
{
"kind": "account",
"path": "token_program"
},
{
"kind": "account",
"path": "token_mint"
}
],
"program": {
"kind": "const",
"value": [
140,
151,
37,
143,
78,
36,
137,
241,
187,
61,
16,
41,
20,
142,
13,
131,
11,
90,
19,
153,
218,
255,
16,
132,
4,
142,
123,
216,
219,
233,
248,
89
]
}
}
},
{
"name": "joiner_referrer_token_account",
"writable": true,
"pda": {
"seeds": [
{
"kind": "account",
"path": "joiner_referrer"
},
{
"kind": "account",
"path": "token_program"
},
{
"kind": "account",
"path": "token_mint"
}
],
"program": {
"kind": "const",
"value": [
140,
151,
37,
143,
78,
36,
137,
241,
187,
61,
16,
41,
20,
142,
13,
131,
11,
90,
19,
153,
218,
255,
16,
132,
4,
142,
123,
216,
219,
233,
248,
89
]
}
}
},
{
"name": "system_program",
"address": "11111111111111111111111111111111"
},
{
"name": "token_program"
},
{
"name": "associated_token_program",
"address": "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"
}
],
"args": [
{
"name": "winner",
"type": "pubkey"
},
{
"name": "userid",
"type": "string"
},
{
"name": "owner",
"type": "pubkey"
},
{
"name": "game_id",
"type": "string"
},
{
"name": "nonce",
"type": "u64"
}
]
},
{
"name": "initialize",
"discriminator": [
@ -297,6 +1065,168 @@
}
]
},
{
"name": "join_bet_token",
"discriminator": [
166,
253,
62,
118,
163,
87,
166,
204
],
"accounts": [
{
"name": "bet_vault",
"writable": true
},
{
"name": "payer",
"writable": true,
"signer": true
},
{
"name": "token_mint"
},
{
"name": "payer_token_account",
"writable": true,
"pda": {
"seeds": [
{
"kind": "account",
"path": "payer"
},
{
"kind": "account",
"path": "token_program"
},
{
"kind": "account",
"path": "token_mint"
}
],
"program": {
"kind": "const",
"value": [
140,
151,
37,
143,
78,
36,
137,
241,
187,
61,
16,
41,
20,
142,
13,
131,
11,
90,
19,
153,
218,
255,
16,
132,
4,
142,
123,
216,
219,
233,
248,
89
]
}
}
},
{
"name": "token_vault",
"writable": true,
"pda": {
"seeds": [
{
"kind": "account",
"path": "bet_vault"
},
{
"kind": "account",
"path": "token_program"
},
{
"kind": "account",
"path": "token_mint"
}
],
"program": {
"kind": "const",
"value": [
140,
151,
37,
143,
78,
36,
137,
241,
187,
61,
16,
41,
20,
142,
13,
131,
11,
90,
19,
153,
218,
255,
16,
132,
4,
142,
123,
216,
219,
233,
248,
89
]
}
}
},
{
"name": "system_program",
"address": "11111111111111111111111111111111"
},
{
"name": "token_program"
},
{
"name": "associated_token_program",
"address": "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"
}
],
"args": [
{
"name": "user_id",
"type": "string"
},
{
"name": "game_id",
"type": "string"
}
]
},
{
"name": "refund_bet",
"discriminator": [
@ -381,6 +1311,10 @@
"type": {
"kind": "struct",
"fields": [
{
"name": "nonce",
"type": "u64"
},
{
"name": "game_id",
"type": "string"
@ -401,6 +1335,10 @@
"name": "joiner_id",
"type": "string"
},
{
"name": "token_mint",
"type": "pubkey"
},
{
"name": "wager",
"type": "u64"

View File

@ -19,10 +19,10 @@ export async function showNewGameNotification(username:string, game:string, wage
}
}
export async function add_new_activity(type:string, owner_id:string, joiner_id:string, game:string, amount:number ){
export async function add_new_activity(type:string, owner_id:string, joiner_id:string, game:string, amount:number, currency_symbol:string ){
try{
const isDevnet = CLUSTER_URL === clusterApiUrl("devnet");
const url = `${API_URL}add_activity.php?type=${type}&owner_id=${owner_id}&joiner_id=${joiner_id}&game=${game}&amount=${amount}&devnet=${isDevnet ? "1" : "0"}`;
const url = `${API_URL}add_activity.php?type=${type}&owner_id=${owner_id}&joiner_id=${joiner_id}&game=${game}&amount=${amount}&currency_symbol=${currency_symbol}&devnet=${isDevnet ? "1" : "0"}`;
console.log(url);
await fetch(url);
}catch(error){

View File

@ -3,11 +3,15 @@ import { Bets } from "@/idl/bets";
import { AnchorProvider, BN, Program } from "@coral-xyz/anchor";
import { ConnectedSolanaWallet } from "@privy-io/react-auth";
import { Connection, LAMPORTS_PER_SOL, PublicKey } from "@solana/web3.js";
import { getAssociatedTokenAddress } from "@solana/spl-token";
import idl from "../idl/bets_idl.json";
import { Bet } from "@/types/Bet";
import { toast } from "sonner";
import { CONFIRMATION_THRESHOLD } from "./constants";
import { add_new_activity } from "./data_fetcher";
import { Currency } from "@/types/Currency";
import { getCurrencyByMint } from "@/data/currencies";
export async function fetchOpenBets(): Promise<Bet[]> {
const response = await fetch(`${VALIDATOR_URL}fetchBets`);
@ -42,6 +46,18 @@ export async function getVaultByAddress(wallets: ConnectedSolanaWallet, address:
// Extract required bet data
const betAcc = await program.account.betVault.fetch(address);
// console.log(betAcc.gameId);
// Determine currency symbol from tokenMint
let currencySymbol = "SOL"; // Default to SOL
let lamp_per_sol = LAMPORTS_PER_SOL;
if (betAcc.tokenMint.toString() !== "So11111111111111111111111111111111111111112") {
const currency = getCurrencyByMint(betAcc.tokenMint.toString());
if (currency) {
currencySymbol = currency.symbol;
lamp_per_sol = Math.pow(10, currency.decimals);
}
}
return {
address: address.toString(),
id: betAcc.gameId,
@ -49,7 +65,8 @@ export async function getVaultByAddress(wallets: ConnectedSolanaWallet, address:
owner_id: betAcc.ownerId,
joiner: betAcc.joiner ? betAcc.joiner.toBase58() : "Open",
joiner_id: betAcc.joinerId,
wager: betAcc.wager.toNumber() / LAMPORTS_PER_SOL
wager: betAcc.wager.toNumber() / lamp_per_sol,
currency: currencySymbol
};
} catch (error) {
console.error("Error fetching open bets:", error);
@ -95,14 +112,23 @@ export async function closeBet(wallets: ConnectedSolanaWallet, uid:string, betI
const winner = new PublicKey(wallets.address);
const chosenBetVaultAcc = await program.account.betVault.fetch(chosenBet);
// Execute the closeBet transaction
const tx = await program.methods
.closeBet(winner, uid)
let txId="";
let currency_symbol = "SOL";
if(chosenBetVaultAcc.tokenMint.toString() !== "11111111111111111111111111111111"){
const currency = getCurrencyByMint(chosenBetVaultAcc.tokenMint.toString());
if(!currency){
toast.error("Currency not found");
return "";
}
currency_symbol = currency.symbol;
const tokenProgramID = new PublicKey(currency.tokenProgram);
const tx = await program.methods
.closeBetToken(winner, uid, chosenBetVaultAcc.owner, game_id, chosenBetVaultAcc.nonce)
.accounts({
betVault: chosenBet,
betsList: bet_list_pda,
winner: winner
winner: winner,
tokenMint: chosenBetVaultAcc.tokenMint,
tokenProgram:tokenProgramID
})
.transaction();
tx.feePayer = new PublicKey(wallets.address);
@ -112,8 +138,27 @@ export async function closeBet(wallets: ConnectedSolanaWallet, uid:string, betI
const signedTx = await wallet.signTransaction(tx);
// Send transaction// Replace with correct RPC endpoint
const txId = await connection.sendRawTransaction(signedTx.serialize());
add_new_activity("close", uid, chosenBetVaultAcc.joinerId, game_id, chosenBetVaultAcc.wager.toNumber() / LAMPORTS_PER_SOL);
txId = await connection.sendRawTransaction(signedTx.serialize());
}else{
// Execute the closeBet transaction
const tx = await program.methods
.closeBet(winner, uid)
.accounts({
betVault: chosenBet,
betsList: bet_list_pda,
winner: winner
})
.transaction();
tx.feePayer = new PublicKey(wallets.address);
tx.recentBlockhash = (await connection.getLatestBlockhash()).blockhash;
// Sign transaction with Privy
const signedTx = await wallet.signTransaction(tx);
// Send transaction// Replace with correct RPC endpoint
txId = await connection.sendRawTransaction(signedTx.serialize());
}
add_new_activity("close", uid, chosenBetVaultAcc.joinerId, game_id, chosenBetVaultAcc.wager.toNumber() / LAMPORTS_PER_SOL, currency_symbol);
console.log(`Transaction: ${txId}`);
return txId;
} catch (error) {
@ -122,7 +167,7 @@ export async function closeBet(wallets: ConnectedSolanaWallet, uid:string, betI
return "";
}
export async function createBet(wallets: ConnectedSolanaWallet, uid: string, selectedPrice: number, selectedGame: string, get_account_address: boolean): Promise<string> {
export async function createBet(wallets: ConnectedSolanaWallet, uid: string, selectedPrice: number, selectedGame: string, get_account_address: boolean, tokenMint: string): Promise<string> {
const wallet = {
publicKey: new PublicKey(wallets.address),
@ -144,8 +189,37 @@ export async function createBet(wallets: ConnectedSolanaWallet, uid: string, sel
console.log(`bets list : ${bet_list_pda}`);
// Create transaction
const tx = await program.methods
const tokenMintPubkey = new PublicKey(tokenMint);
let txId = "";
let currency_symbol = "SOL";
if(tokenMint!== "11111111111111111111111111111111"){
const currency = getCurrencyByMint(tokenMint);
if(!currency){
toast.error("Currency not found");
return "";
}
currency_symbol = currency.symbol;
const tokenProgramID = new PublicKey(currency.tokenProgram);
const tx = await program.methods
.createBetToken(new BN(selectedPrice * Math.pow(10, currency.decimals)), uid, selectedGame, new BN(nonce))
.accounts({
betsList: bet_list_pda,
tokenMint: tokenMintPubkey,
tokenProgram:tokenProgramID
})
.transaction(); // Get transaction object instead of RPC call
tx.feePayer = new PublicKey(wallets.address);
tx.recentBlockhash = (await connection.getLatestBlockhash()).blockhash;
// Sign transaction with Privy
const signedTx = await wallet.signTransaction(tx);
// Send transaction
txId = await connection.sendRawTransaction(signedTx.serialize());
}else{
const tx = await program.methods
.createBet(new BN(selectedPrice * 1000000000), uid, selectedGame, new BN(nonce))
.accounts({
betsList: bet_list_pda,
@ -159,10 +233,13 @@ export async function createBet(wallets: ConnectedSolanaWallet, uid: string, sel
const signedTx = await wallet.signTransaction(tx);
// Send transaction
const txId = await connection.sendRawTransaction(signedTx.serialize());
txId = await connection.sendRawTransaction(signedTx.serialize());
}
// Create transaction
console.log(`Transaction sent: ${txId}`);
console.log(`Transaction confirmed: ${txId}`);
add_new_activity("create", uid, "", selectedGame, selectedPrice);
add_new_activity("create", uid, "", selectedGame, selectedPrice, currency_symbol);
if (get_account_address) {
const gameIdBytes = Buffer.from(selectedGame); // selectedGame is a string (game_id)
const nonceBytes = new BN(nonce).toArrayLike(Buffer, "le", 8); // _nonce.to_le_bytes()
@ -210,32 +287,57 @@ export async function joinBet(wallets: ConnectedSolanaWallet, uid: string, gameI
const program = new Program<Bets>(idl, provider);
try {
const [bet_list_pda] = await PublicKey.findProgramAddress(
[Buffer.from("bets_list")],
program.programId
);
const connection = new Connection(CLUSTER_URL, CONFIRMATION_THRESHOLD);
const betVaultPubkey = new PublicKey(address);
console.log(`bets list : ${bet_list_pda}`);
// Create transaction
const tx = await program.methods
.joinBet(uid, gameId)
.accounts({
betVault: betVaultPubkey,
})
.transaction(); // Get transaction object instead of RPC call
tx.feePayer = new PublicKey(wallets.address);
tx.recentBlockhash = (await connection.getLatestBlockhash()).blockhash;
// Sign transaction with Privy
const signedTx = await wallet.signTransaction(tx);
const chosenBetVaultAcc = await program.account.betVault.fetch(betVaultPubkey);
// Send transaction// Replace with correct RPC endpoint
const txId = await connection.sendRawTransaction(signedTx.serialize());
add_new_activity("join", "",uid, gameId, chosenBetVaultAcc.wager.toNumber() / LAMPORTS_PER_SOL);
let txId = "";
let currency_symbol = "SOL";
if(chosenBetVaultAcc.tokenMint.toString() !== "11111111111111111111111111111111"){
//Token bet
// Create transaction
const currency = getCurrencyByMint(chosenBetVaultAcc.tokenMint.toString());
if(!currency){
toast.error("Currency not found");
return "";
}
currency_symbol = currency.symbol;
const tokenProgramID = new PublicKey(currency.tokenProgram);
const tx = await program.methods
.joinBetToken(uid, gameId)
.accounts({
betVault:betVaultPubkey,
tokenMint: chosenBetVaultAcc.tokenMint,
tokenProgram:tokenProgramID
})
.transaction(); // Get transaction object instead of RPC call
tx.feePayer = new PublicKey(wallets.address);
tx.recentBlockhash = (await connection.getLatestBlockhash()).blockhash;
// Sign transaction with Privy
const signedTx = await wallet.signTransaction(tx);
// Send transaction// Replace with correct RPC endpoint
txId = await connection.sendRawTransaction(signedTx.serialize());
}else{
const tx = await program.methods
.joinBet(uid, gameId)
.accounts({
betVault: betVaultPubkey,
})
.transaction(); // Get transaction object instead of RPC call
tx.feePayer = new PublicKey(wallets.address);
tx.recentBlockhash = (await connection.getLatestBlockhash()).blockhash;
// Sign transaction with Privy
const signedTx = await wallet.signTransaction(tx);
// Send transaction// Replace with correct RPC endpoint
txId = await connection.sendRawTransaction(signedTx.serialize());
}
add_new_activity("join", "",uid, gameId, chosenBetVaultAcc.wager.toNumber() / LAMPORTS_PER_SOL, currency_symbol);
console.log(`Transaction ID: ${txId}`);
return txId;
} catch (error: unknown) {
@ -252,4 +354,31 @@ export async function joinBet(wallets: ConnectedSolanaWallet, uid: string, gameI
function getRandomInt(max: number): number {
return Math.floor(Math.random() * max);
}
export async function getTokenBalance(walletAddress: string, currency: Currency): Promise<number> {
try {
const connection = new Connection(CLUSTER_URL, "confirmed");
const publicKey = new PublicKey(walletAddress);
// For native SOL
if (currency.tokenAddress === "So11111111111111111111111111111111111111112") {
const balance = await connection.getBalance(publicKey);
return balance / Math.pow(10, currency.decimals);
}
// For SPL tokens
const tokenMint = new PublicKey(currency.tokenAddress);
const tokenAccount = await getAssociatedTokenAddress(tokenMint, publicKey, true, new PublicKey(currency.tokenProgram));
console.log(`token vault for ${currency.name}:${walletAddress} : ${tokenAccount}`);
try {
const tokenBalance = await connection.getTokenAccountBalance(tokenAccount);
return tokenBalance.value.uiAmount || 0;
} catch {
return 0;
}
} catch (error) {
console.error("Error fetching token balance:", error);
return 0;
}
}

View File

@ -6,6 +6,7 @@ export interface Bet {
joiner: string;
joiner_id:string;
wager: number;
currency?: string; // Currency symbol (SOL, USDT, DUELFI, etc.)
ownerProfile?: {
id: string;
username: string;

9
src/types/Currency.ts Normal file
View File

@ -0,0 +1,9 @@
export interface Currency {
name: string;
symbol: string;
tokenAddress: string;
decimals: number;
price_presets: number[];
tokenProgram: string;
logo?: string;
}