diff --git a/apps/api/package.json b/apps/api/package.json
index bf317e56..e88a5c0e 100644
--- a/apps/api/package.json
+++ b/apps/api/package.json
@@ -12,9 +12,9 @@
"clean": "rm -rf .turbo && rm -rf node_modules && rm -rf dist"
},
"dependencies": {
- "@coral-xyz/anchor": "^0.27.0",
- "@solana/spl-token": "^0.3.8",
- "@solana/web3.js": "^1.73.5",
+ "@coral-xyz/anchor": "^0.31.1",
+ "@solana/spl-token": "^0.4.13",
+ "@solana/web3.js": "^1.95.4",
"apicache": "^1.6.3",
"cors": "^2.8.5",
"dotenv": "^16.0.3",
diff --git a/apps/explorer/.env b/apps/explorer/.env
new file mode 100644
index 00000000..20c068f8
--- /dev/null
+++ b/apps/explorer/.env
@@ -0,0 +1,5 @@
+# Web3 connection
+VITE_RPC_ENDPOINT="https://mainnet.helius-rpc.com/?api-key=373105b2-df11-4381-bea4-e9aecdda396e"
+VITE_GAMBA_API_ENDPOINT="api.gamba.so"
+VITE_HELIUS_API_KEY="373105b2-df11-4381-bea4-e9aecdda396e"
+
diff --git a/apps/explorer/package.json b/apps/explorer/package.json
index 66033b99..14ec2e26 100644
--- a/apps/explorer/package.json
+++ b/apps/explorer/package.json
@@ -8,7 +8,7 @@
"clean": "rm -rf .turbo && rm -rf node_modules && rm -rf dist"
},
"dependencies": {
- "@coral-xyz/anchor": "^0.27.0",
+ "@coral-xyz/anchor": "^0.31.1",
"@preact/signals-react": "^1.3.8",
"@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-navigation-menu": "^1.1.4",
@@ -18,7 +18,7 @@
"@radix-ui/react-tooltip": "^1.0.7",
"@radix-ui/themes": "^1.1.2",
"@solana/spl-token": "^0.3.8",
- "@solana/wallet-adapter-react": "^0.15.35",
+ "@solana/wallet-adapter-react": "^0.15.39",
"@solana/wallet-adapter-react-ui": "^0.9.34",
"@solana/wallet-adapter-wallets": "^0.19.18",
"@solana/web3.js": "^1.98.2",
@@ -46,8 +46,8 @@
"zustand": "^4.4.1"
},
"devDependencies": {
- "@types/react": "^18.2.22",
- "@types/react-dom": "^18.0.11",
+ "@types/react": "^18.2.13",
+ "@types/react-dom": "^18.2.4",
"@typescript-eslint/eslint-plugin": "^6.10.0",
"@typescript-eslint/parser": "^6.10.0",
"@vitejs/plugin-react": "^3.1.0",
diff --git a/apps/explorer/src/index.tsx b/apps/explorer/src/index.tsx
index fde658b4..0abc7797 100644
--- a/apps/explorer/src/index.tsx
+++ b/apps/explorer/src/index.tsx
@@ -23,7 +23,8 @@ function Root() {
],
[],
)
-
+
+
return (
diff --git a/apps/platform/.env b/apps/platform/.env
new file mode 100644
index 00000000..1a54dad7
--- /dev/null
+++ b/apps/platform/.env
@@ -0,0 +1,4 @@
+# Web3 connection
+VITE_RPC_ENDPOINT="https://devnet.helius-rpc.com/?api-key=7b05747c-b100-4159-ba5f-c85e8c8d3997"
+# VITE_HELIUS_API_KEY=""
+# VITE_REAL_PLAYS_DISABLED=true
\ No newline at end of file
diff --git a/apps/platform/package.json b/apps/platform/package.json
index 5063912c..489c3b26 100644
--- a/apps/platform/package.json
+++ b/apps/platform/package.json
@@ -9,22 +9,24 @@
"clean": "rm -rf .turbo && rm -rf node_modules && rm -rf dist"
},
"dependencies": {
- "@coral-xyz/anchor": "^0.27.0",
+ "@coral-xyz/anchor": "^0.31.1",
+ "@solana/spl-token": "^0.4.13",
+ "@solana/web3.js": "^1.98.2",
"@preact/signals-react": "^1.3.8",
"@react-three/drei": "^9.89.0",
"@react-three/fiber": "^8.15.11",
- "@solana/spl-token": "^0.3.8",
- "@solana/wallet-adapter-react": "^0.15.35",
+ "@solana/wallet-adapter-react": "^0.15.39",
"@solana/wallet-adapter-react-ui": "^0.9.34",
"@solana/wallet-adapter-wallets": "^0.19.18",
- "@solana/web3.js": "^1.93.0",
"@vercel/kv": "^3.0.0",
"buffer": "^6.0.3",
+ "@gamba-labs/multiplayer-sdk": "workspace:*",
"gamba-core-v2": "workspace:*",
"gamba-react-ui-v2": "workspace:*",
"gamba-react-v2": "workspace:*",
"html2canvas": "^1.4.1",
- "matter-js": "^0.19.0",
+ "matter-js": "^0.20.0",
+ "framer-motion": "^12.16.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-router-dom": "^6.10.0",
@@ -34,8 +36,8 @@
"zustand": "^4.4.1"
},
"devDependencies": {
- "@types/react": "^18.2.22",
- "@types/react-dom": "^18.0.11",
+ "@types/react": "^18.2.13",
+ "@types/react-dom": "^18.2.4",
"@types/matter-js": "^0.19.5",
"@types/three": "^0.161.2",
"@vitejs/plugin-react": "^3.1.0",
diff --git a/apps/platform/public/games/jackpot.png b/apps/platform/public/games/jackpot.png
new file mode 100644
index 00000000..1df54646
Binary files /dev/null and b/apps/platform/public/games/jackpot.png differ
diff --git a/apps/platform/public/games/plinkorace.png b/apps/platform/public/games/plinkorace.png
new file mode 100644
index 00000000..24b953af
Binary files /dev/null and b/apps/platform/public/games/plinkorace.png differ
diff --git a/apps/platform/src/App.tsx b/apps/platform/src/App.tsx
index 17065735..cc423d79 100644
--- a/apps/platform/src/App.tsx
+++ b/apps/platform/src/App.tsx
@@ -1,20 +1,27 @@
+import React from 'react'
+import { Route, Routes, useLocation } from 'react-router-dom'
import { useWalletModal } from '@solana/wallet-adapter-react-ui'
import { GambaUi } from 'gamba-react-ui-v2'
import { useTransactionError } from 'gamba-react-v2'
-import React from 'react'
-import { Route, Routes, useLocation } from 'react-router-dom'
+
import { Modal } from './components/Modal'
import { TOS_HTML, ENABLE_TROLLBOX } from './constants'
import { useToast } from './hooks/useToast'
import { useUserStore } from './hooks/useUserStore'
+
import Dashboard from './sections/Dashboard/Dashboard'
import Game from './sections/Game/Game'
import Header from './sections/Header'
import RecentPlays from './sections/RecentPlays/RecentPlays'
import Toasts from './sections/Toasts'
-import { MainWrapper, TosInner, TosWrapper } from './styles'
import TrollBox from './components/TrollBox'
+import { MainWrapper, TosInner, TosWrapper } from './styles'
+
+/* -------------------------------------------------------------------------- */
+/* Helpers */
+/* -------------------------------------------------------------------------- */
+
function ScrollToTop() {
const { pathname } = useLocation()
React.useEffect(() => window.scrollTo(0, 0), [pathname])
@@ -23,63 +30,65 @@ function ScrollToTop() {
function ErrorHandler() {
const walletModal = useWalletModal()
- const toast = useToast()
- const [error, setError] = React.useState()
-
- useTransactionError(
- (error) => {
- if (error.message === 'NOT_CONNECTED') {
- walletModal.setVisible(true)
- return
- }
- toast({ title: '❌ Transaction error', description: error.error?.errorMessage ?? error.message })
- },
- )
+ const toast = useToast()
- return (
- <>
- {error && (
- setError(undefined)}>
- Error occured
- {error.message}
-
- )}
- >
- )
+ // React‑state not needed; let Toasts surface details
+ useTransactionError((err) => {
+ if (err.message === 'NOT_CONNECTED') {
+ walletModal.setVisible(true)
+ } else {
+ toast({
+ title: '❌ Transaction error',
+ description: err.error?.errorMessage ?? err.message,
+ })
+ }
+ })
+
+ return null
}
+/* -------------------------------------------------------------------------- */
+/* App */
+/* -------------------------------------------------------------------------- */
+
export default function App() {
- const newcomer = useUserStore((state) => state.newcomer)
- const set = useUserStore((state) => state.set)
+ const newcomer = useUserStore((s) => s.newcomer)
+ const set = useUserStore((s) => s.set)
return (
<>
+ {/* onboarding / ToS */}
{newcomer && (
Welcome
-
- By playing on our platform, you confirm your compliance.
-
+ By playing on our platform, you confirm your compliance.
set({ newcomer: false })}>
Acknowledge
)}
+
+
+
- } />
- } />
+ {/* Normal landing page always shows Dashboard (with optional inline game) */}
+ } />
+ {/* Dedicated game pages */}
+ } />
+
Recent Plays
+
{ENABLE_TROLLBOX && }
>
)
diff --git a/apps/platform/src/constants.ts b/apps/platform/src/constants.ts
index d112d032..ca1a62db 100644
--- a/apps/platform/src/constants.ts
+++ b/apps/platform/src/constants.ts
@@ -16,10 +16,12 @@ export const EXPLORER_URL = 'https://explorer.gamba.so'
export const PLATFORM_SHARABLE_URL = 'play.gamba.so'
// Creator fee (in %)
-export const PLATFORM_CREATOR_FEE = 0.01 // 1% !!max 5%!!
+export const PLATFORM_CREATOR_FEE = 0.01 // 1% !!max 7%!!
+
+export const MULTIPLAYER_FEE = 0.01 // 1%
// Jackpot fee (in %)
-export const PLATFORM_JACKPOT_FEE = 0.001 // 0.1%
+export const PLATFORM_JACKPOT_FEE = 0.001 // 0.1%, not jackpot game specific, but platform wide
// Referral fee (in %)
export const PLATFORM_REFERRAL_FEE = 0.0025 // 0.25%
@@ -109,3 +111,8 @@ export const TOKEN_METADATA_FETCHER = (
export const ENABLE_LEADERBOARD = true
export const ENABLE_TROLLBOX = false // Requires setup in vercel (check tutorial in discord)
+
+/** If true, the featured game is fully playable inline on the dashboard */
+export const FEATURED_GAME_INLINE = false
+export const FEATURED_GAME_ID: string | undefined = 'jackpot' // ← put game id or leave undefined
+
diff --git a/apps/platform/src/games/Jackpot/Coinfall.tsx b/apps/platform/src/games/Jackpot/Coinfall.tsx
new file mode 100644
index 00000000..b012aac3
--- /dev/null
+++ b/apps/platform/src/games/Jackpot/Coinfall.tsx
@@ -0,0 +1,178 @@
+import React, { useRef, useEffect } from 'react';
+import styled from 'styled-components';
+import * as Matter from 'matter-js';
+import { IdlAccounts, web3 } from '@coral-xyz/anchor';
+import type { Multiplayer } from '@gamba-labs/multiplayer-sdk';
+import { useSound } from 'gamba-react-ui-v2';
+import joinSnd from './sounds/join.mp3';
+
+const Container = styled.div`
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ z-index: 0;
+ pointer-events: none;
+ overflow: hidden;
+`;
+
+type Player = IdlAccounts['game']['players'][number];
+
+interface CoinfallsProps {
+ players: Player[];
+}
+
+function usePrevious(value: T) {
+ const ref = useRef();
+ useEffect(() => {
+ ref.current = value;
+ });
+ return ref.current;
+}
+
+const getRadiusForWager = (wagerLamports: number) => {
+ const minWager = 0.01 * web3.LAMPORTS_PER_SOL;
+ const maxWager = 5 * web3.LAMPORTS_PER_SOL;
+ const minRadius = 10;
+ const maxRadius = 100;
+
+ if (wagerLamports <= minWager) return minRadius;
+ if (wagerLamports >= maxWager) return maxRadius;
+
+ const pct = (wagerLamports - minWager) / (maxWager - minWager);
+ return minRadius + pct * (maxRadius - minRadius);
+};
+
+export function Coinfalls({ players }: CoinfallsProps) {
+ const containerRef = useRef(null);
+ const engineRef = useRef();
+ const prevPlayers = usePrevious(players) ?? [];
+ const { play: playJoin, sounds } = useSound({ join: joinSnd });
+
+ // SETUP: engine, renderer, ground, background-spawner
+ useEffect(() => {
+ const engine = Matter.Engine.create({
+ gravity: { y: 0.6 },
+ enableSleeping: true,
+ });
+ engineRef.current = engine;
+
+ const container = containerRef.current;
+ if (!container) return;
+
+ // renderer
+ const render = Matter.Render.create({
+ element: container,
+ engine,
+ options: {
+ width: container.clientWidth,
+ height: container.clientHeight,
+ wireframes: false,
+ background: 'transparent',
+ },
+ });
+
+ const runner = Matter.Runner.create();
+ Matter.Runner.run(runner, engine);
+ Matter.Render.run(render);
+
+ // ground (invisible)
+ const width = container.clientWidth;
+ const height = container.clientHeight;
+ const thickness = 100;
+ const ground = Matter.Bodies.rectangle(
+ width/2, height + thickness/2,
+ width*2, thickness,
+ { isStatic: true, render: { visible: false } }
+ );
+ Matter.World.add(engine.world, ground);
+
+ // tint everything gold + semi-transparent
+ Matter.Events.on(engine, 'afterUpdate', () => {
+ engine.world.bodies.forEach(body => {
+ if (!body.isStatic) {
+ body.render.fillStyle = '#FFD700';
+ body.render.opacity = 0.4;
+ }
+ });
+ });
+
+ // handle window resize
+ const handleResize = () => {
+ if (!container || !render.canvas) return;
+ render.canvas.width = container.clientWidth;
+ render.canvas.height = container.clientHeight;
+ Matter.Body.setPosition(ground, {
+ x: container.clientWidth/2,
+ y: container.clientHeight + thickness/2,
+ });
+ };
+ window.addEventListener('resize', handleResize);
+
+ // BACKGROUND SPAWNER: tiny non‑colliding coins
+ const bgInterval = setInterval(() => {
+ const w = container.clientWidth;
+ const h = container.clientHeight;
+ const x = Math.random() * w;
+ const r = 6 + Math.random() * 4; // 6–10 px
+
+ const smallCoin = Matter.Bodies.circle(x, -40, r, {
+ isSensor: true, // no collision physics
+ collisionFilter: { mask: 0 },
+ render: { fillStyle: '#FFD700', opacity: 0.15 },
+ restitution: 0.2,
+ friction: 0.02,
+ });
+
+ Matter.World.add(engine.world, smallCoin);
+
+ // cleanup after it falls past view
+ setTimeout(() => {
+ Matter.World.remove(engine.world, smallCoin);
+ }, 5000);
+ }, 4000);
+
+ // teardown
+ return () => {
+ clearInterval(bgInterval);
+ window.removeEventListener('resize', handleResize);
+ Matter.Runner.stop(runner);
+ Matter.Render.stop(render);
+ Matter.Engine.clear(engine);
+ if (render.canvas && render.canvas.parentNode) {
+ render.canvas.parentNode.removeChild(render.canvas);
+ }
+ };
+ }, []); // run once
+
+ // PLAYER SPAWNER: when new players appear
+ useEffect(() => {
+ const engine = engineRef.current;
+ const container = containerRef.current;
+ if (!engine || !container) return;
+
+ if (players.length > prevPlayers.length) {
+ const prevKeys = new Set(prevPlayers.map(p => p.user.toBase58()));
+ const newPlayers = players.filter(p => !prevKeys.has(p.user.toBase58()));
+
+ newPlayers.forEach(player => {
+ const w = container.clientWidth;
+ const r = getRadiusForWager(player.wager.toNumber());
+ const x = w * 0.2 + Math.random() * (w * 0.6);
+
+ const coin = Matter.Bodies.circle(x, -30, r, {
+ restitution: 0.5,
+ friction: 0.1,
+ render: { fillStyle: '#FFD700' },
+ });
+ Matter.World.add(engine.world, coin);
+
+ // play join sound per new player (gated by readiness)
+ if (sounds.join?.ready) playJoin('join');
+ });
+ }
+ }, [players, prevPlayers]);
+
+ return ;
+}
diff --git a/apps/platform/src/games/Jackpot/Countdown.tsx b/apps/platform/src/games/Jackpot/Countdown.tsx
new file mode 100644
index 00000000..671f05b1
--- /dev/null
+++ b/apps/platform/src/games/Jackpot/Countdown.tsx
@@ -0,0 +1,95 @@
+import React, { useEffect, useRef, useState } from 'react'
+import styled from 'styled-components'
+
+const Wrapper = styled.div`
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ margin: 10px 0;
+ width: 100%;
+`
+
+const Time = styled.div`
+ font-size: 2.5rem;
+ color: #fff;
+ font-weight: bold;
+ text-shadow: 0 0 10px rgba(255, 255, 255, 0.5);
+ font-variant-numeric: tabular-nums;
+`
+
+const ProgressBar = styled.div`
+ width: 100%;
+ max-width: 500px;
+ height: 10px;
+ background: #2c2c54;
+ border-radius: 5px;
+ margin-top: 6px;
+ overflow: hidden;
+`
+
+const Progress = styled.div`
+ height: 100%;
+ background: linear-gradient(90deg, #f39c12, #f1c40f);
+ border-radius: 5px;
+ transition: width 0.5s cubic-bezier(0.25, 1, 0.5, 1);
+`
+
+interface CountdownProps {
+ creationTimestamp: number
+ softExpiration: number
+ onComplete: () => void
+}
+
+export const Countdown: React.FC = ({
+ creationTimestamp,
+ softExpiration,
+ onComplete,
+}) => {
+ // total window
+ const totalWindowRef = useRef(Math.max(softExpiration - creationTimestamp, 0))
+ // time left until soft
+ const [timeLeft, setTimeLeft] = useState(Math.max(softExpiration - Date.now(), 0))
+
+ // reset when timestamps change
+ useEffect(() => {
+ totalWindowRef.current = Math.max(softExpiration - creationTimestamp, 0)
+ setTimeLeft(Math.max(softExpiration - Date.now(), 0))
+ }, [creationTimestamp, softExpiration])
+
+ // ticking
+ const firedRef = useRef(false)
+ useEffect(() => {
+ if (timeLeft <= 0) {
+ if (!firedRef.current) {
+ firedRef.current = true
+ onComplete()
+ }
+ return
+ }
+ const id = setInterval(() => {
+ const rem = Math.max(softExpiration - Date.now(), 0)
+ setTimeLeft(rem)
+ }, 500)
+ return () => clearInterval(id)
+ }, [softExpiration, timeLeft, onComplete])
+
+ // formatting
+ const pad = (n: number) => (n < 10 ? `0${n}` : `${n}`)
+ const totalSec = Math.ceil(timeLeft / 1000)
+ const m = Math.floor(totalSec / 60)
+ const s = totalSec % 60
+
+ // progress %
+ const pct = totalWindowRef.current > 0
+ ? Math.min(100, ((totalWindowRef.current - timeLeft) / totalWindowRef.current) * 100)
+ : 0
+
+ return (
+
+
+
+
+
+
+ )
+}
diff --git a/apps/platform/src/games/Jackpot/Jackpot.styles.ts b/apps/platform/src/games/Jackpot/Jackpot.styles.ts
new file mode 100644
index 00000000..fc8262df
--- /dev/null
+++ b/apps/platform/src/games/Jackpot/Jackpot.styles.ts
@@ -0,0 +1,133 @@
+import styled from 'styled-components'
+import { motion } from 'framer-motion'
+
+export const ScreenLayout = styled.div`
+ width: 100%;
+ max-width: 1200px;
+ margin: 0 auto;
+ padding: 15px;
+ display: flex;
+ flex-direction: column;
+ gap: 15px;
+`
+
+export const PageLayout = styled.div`
+ width: 100%;
+ display: grid;
+ grid-template-columns: 200px 1fr 200px;
+ grid-template-rows: auto;
+ grid-template-areas: "topplayers game recentgames";
+ gap: 15px;
+
+ @media (max-width: 900px) {
+ grid-template-columns: 1fr;
+ grid-template-areas: "game";
+ }
+`
+
+export const GameContainer = styled.div`
+ grid-area: game;
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: flex-start; /* header sticks to top */
+ padding: 20px;
+ background: #1a1a2e;
+ border-radius: 20px;
+ box-shadow: 0 5px 20px rgba(0, 0, 0, 0.4);
+ overflow: hidden;
+ z-index: 1;
+ width: 100%;
+ height: 420px;
+`
+
+export const TopPlayersSidebar = styled.div`
+ grid-area: topplayers;
+`
+
+export const RecentGamesSidebar = styled.div`
+ grid-area: recentgames;
+`
+
+export const RecentPlayersContainer = styled.div`
+ width: 100%;
+`
+
+export const TopPlayersOverlay = styled.div`
+ position: absolute;
+ top: 10px;
+ left: 10px;
+ z-index: 10;
+ max-width: 180px;
+`
+
+export const MainContent = styled.div`
+ position: relative;
+ z-index: 2;
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ flex: 1; /* this plus CenterBlock layout will animate */
+`
+
+/* now uses motion.div so layout shifts get smoothed */
+export const CenterBlock = styled(motion.div)`
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+`
+
+export const Loading = styled.div`
+ font-size: 1.2rem;
+ color: #e0e0e0;
+`
+
+export const Header = styled.div`
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ width: 100%;
+ margin-bottom: 10px;
+`
+
+export const Title = styled.h2`
+ font-size: 1.5rem;
+ color: #fff;
+ margin: 0;
+`
+
+export const Badge = styled.span<{
+ status: 'waiting' | 'live' | 'settled';
+}>`
+ padding: 6px 12px;
+ border-radius: 10px;
+ font-size: 0.9rem;
+ background: ${({ status }) =>
+ status === 'waiting'
+ ? '#f39c12'
+ : status === 'live'
+ ? '#2ecc71'
+ : '#3498db'};
+ color: #fff;
+ font-weight: bold;
+`
+
+export const TestButton = styled.button`
+ background: #f39c12;
+ color: white;
+ border: none;
+ padding: 8px 16px;
+ border-radius: 8px;
+ cursor: pointer;
+ font-weight: bold;
+ margin-bottom: 10px;
+ align-self: center;
+
+ &:hover {
+ background: #e67e22;
+ }
+`
diff --git a/apps/platform/src/games/Jackpot/MyStats.tsx b/apps/platform/src/games/Jackpot/MyStats.tsx
new file mode 100644
index 00000000..aeb5f88a
--- /dev/null
+++ b/apps/platform/src/games/Jackpot/MyStats.tsx
@@ -0,0 +1,108 @@
+import React, { useEffect, useRef } from 'react'
+import styled from 'styled-components'
+import { motion, AnimatePresence, animate } from 'framer-motion'
+
+function AnimatedNumber({ value }: { value: number }) {
+ const ref = useRef(null)
+
+ useEffect(() => {
+ const node = ref.current
+ if (!node) return
+
+ const controls = animate(
+ Number(node.textContent) || 0,
+ value,
+ {
+ duration: 0.5,
+ ease: 'easeOut',
+ onUpdate(latest) {
+ node.textContent = latest.toFixed(2)
+ },
+ }
+ )
+ return () => controls.stop()
+ }, [value])
+
+ return
+}
+
+const Wrap = styled(motion.div)`
+ display: flex;
+ gap: 20px;
+ margin-top: 10px;
+ justify-content: center;
+ color: #fff;
+ flex-wrap: wrap;
+ text-align: center;
+`
+
+const Stat = styled(motion.div)`
+ background: #23233b;
+ padding: 10px 14px;
+ border-radius: 12px;
+ min-width: 120px;
+ line-height: 1.2;
+ font-size: 0.9rem;
+ border: 1px solid #4a4a7c;
+
+ & > div:first-child {
+ opacity: 0.7;
+ font-size: 0.8rem;
+ }
+ & > div:last-child {
+ font-weight: bold;
+ color: #f1c40f;
+ }
+`
+
+const containerVariants = {
+ hidden: { opacity: 0, scale: 0.95 },
+ visible: {
+ opacity: 1,
+ scale: 1,
+ transition: {
+ type: 'spring',
+ stiffness: 500,
+ damping: 30,
+ staggerChildren: 0.1,
+ },
+ },
+}
+
+const itemVariants = {
+ hidden: { opacity: 0, y: 20 },
+ visible: { opacity: 1, y: 0 },
+}
+
+interface Props {
+ betSOL: number
+ chancePct: number
+}
+
+export function MyStats({ betSOL, chancePct }: Props) {
+ return (
+
+ {/* keyed so AnimatePresence can detect mount/unmount if needed */}
+
+
+ My Bet
+
+
+
+ Chance
+
+
+
+
+ )
+}
diff --git a/apps/platform/src/games/Jackpot/Pot.tsx b/apps/platform/src/games/Jackpot/Pot.tsx
new file mode 100644
index 00000000..2524af59
--- /dev/null
+++ b/apps/platform/src/games/Jackpot/Pot.tsx
@@ -0,0 +1,66 @@
+import React, { useEffect, useRef } from 'react'
+import styled from 'styled-components'
+import { animate } from 'framer-motion'
+
+function AnimatedNumber({ value }: { value: number }) {
+ const ref = useRef(null)
+
+ useEffect(() => {
+ const node = ref.current
+ if (!node) return
+
+ const controls = animate(
+ Number(node.textContent) || 0,
+ value,
+ {
+ duration: 0.5,
+ ease: 'easeOut',
+ onUpdate(latest) {
+ node.textContent = latest.toFixed(2)
+ },
+ }
+ )
+ return () => controls.stop()
+ }, [value])
+
+ return
+}
+
+const Wrapper = styled.div`
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ margin: 15px 0;
+`
+
+const Label = styled.div`
+ font-size: 1rem;
+ color: #e0e0e0;
+`
+
+const Value = styled.div`
+ font-size: 3rem;
+ line-height: 1.1;
+ color: #f1c40f;
+ font-weight: bold;
+ text-shadow: 0 0 15px #f1c40f;
+
+ @media (max-width: 900px) {
+ font-size: 2.5rem;
+ }
+`
+
+interface PotProps {
+ totalPot: number
+}
+
+export function Pot({ totalPot }: PotProps) {
+ return (
+
+
+
+ SOL
+
+
+ )
+}
diff --git a/apps/platform/src/games/Jackpot/RecentGames.tsx b/apps/platform/src/games/Jackpot/RecentGames.tsx
new file mode 100644
index 00000000..861c9a6d
--- /dev/null
+++ b/apps/platform/src/games/Jackpot/RecentGames.tsx
@@ -0,0 +1,148 @@
+import React from 'react'
+import styled from 'styled-components'
+import { motion, AnimatePresence } from 'framer-motion'
+import { LAMPORTS_PER_SOL } from '@solana/web3.js'
+import { useRecentMultiplayerEvents } from 'gamba-react-v2'
+import { DESIRED_CREATOR, DESIRED_MAX_PLAYERS } from './config'
+import { ParsedEvent } from '@gamba-labs/multiplayer-sdk'
+
+const Container = styled.div`
+ background: #23233b;
+ border-radius: 15px;
+ padding: 15px;
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+ position: relative;
+`
+const Header = styled.header`
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ margin-bottom: 10px;
+ flex-shrink: 0;
+ h3 { margin: 0; font-size: 1rem; color: #fff; }
+`
+const List = styled.ul`
+ list-style: none;
+ margin: 0; padding: 0;
+ overflow-y: auto; flex-grow: 1;
+ &::-webkit-scrollbar { width: 0; background: transparent; }
+`
+const GameItem = styled(motion.li)`
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ background: #2c2c54;
+ padding: 4px 8px;
+ border-radius: 8px;
+ border: 1px solid #4a4a7c;
+ margin-bottom: 4px;
+ font-size: 0.8rem;
+ white-space: nowrap;
+`
+const GameId = styled.div` font-family: monospace; color: #a9a9b8; flex: 0 0 auto; `
+const PotSize = styled.div` flex: 1 1 auto; text-align: center; color: #e0e0e0; `
+const Multiplier = styled.div`
+ font-family: monospace;
+ text-align: right;
+ color: #2ecc71;
+ font-weight: bold;
+ flex: 0 0 auto;
+`
+const EmptyState = styled.div`
+ display: flex; align-items: center; justify-content: center;
+ height: 100%; color: #a9a9b8; font-size: 0.9rem;
+`
+const Fade = styled.div`
+ position: absolute;
+ bottom: 15px; left: 15px; right: 15px;
+ height: 40px;
+ pointer-events: none;
+ background: linear-gradient(rgba(35,35,59,0), rgba(35,35,59,1));
+`
+
+const toNum = (x: any): number =>
+ typeof x === 'number'
+ ? x
+ : typeof x === 'bigint'
+ ? Number(x)
+ : x?.toNumber
+ ? x.toNumber()
+ : Number(x)
+
+const toStr = (x: any): string =>
+ typeof x === 'string'
+ ? x
+ : x?.toString
+ ? x.toString()
+ : String(x)
+
+const fmt2 = (n: number) =>
+ n.toLocaleString(undefined, {
+ minimumFractionDigits: 2,
+ maximumFractionDigits: 2,
+ })
+
+export function RecentGames() {
+ const { events, loading } = useRecentMultiplayerEvents(
+ 'winnersSelected',
+ 20,
+ 0,
+ )
+
+ const filtered = React.useMemo<
+ ParsedEvent<'winnersSelected'>[]
+ >(() => {
+ return events.filter(ev =>
+ ev.data.gameMaker.equals(DESIRED_CREATOR) &&
+ ev.data.maxPlayers === DESIRED_MAX_PLAYERS
+ )
+ }, [events])
+
+ return (
+
+
+
+
+ {filtered.length > 0 ? (
+ filtered.map(ev => {
+ const { gameId, totalWager, payouts, winnerWagers } =
+ ev.data
+ const potSOL = toNum(totalWager) / LAMPORTS_PER_SOL
+
+ let mul = 0
+ if (winnerWagers?.[0] && payouts?.[0]) {
+ const bet = toNum(winnerWagers[0]) / LAMPORTS_PER_SOL
+ const pay = toNum(payouts[0]) / LAMPORTS_PER_SOL
+ mul = bet > 0 ? pay / bet : 0
+ }
+
+ return (
+
+ #{toStr(gameId)}
+ {fmt2(potSOL)} SOL
+ ×{fmt2(mul)}
+
+ )
+ })
+ ) : (
+
+ {loading ? 'Loading…' : 'No recent games'}
+
+ )}
+
+
+
+
+ )
+}
+
+export default RecentGames
diff --git a/apps/platform/src/games/Jackpot/RecentPlayers.tsx b/apps/platform/src/games/Jackpot/RecentPlayers.tsx
new file mode 100644
index 00000000..b7cc98de
--- /dev/null
+++ b/apps/platform/src/games/Jackpot/RecentPlayers.tsx
@@ -0,0 +1,127 @@
+import React, { useMemo } from 'react'
+import styled from 'styled-components'
+import { AnimatePresence, motion } from 'framer-motion'
+import { LAMPORTS_PER_SOL } from '@solana/web3.js'
+import type { IdlAccounts } from '@coral-xyz/anchor'
+import type { Multiplayer } from '@gamba-labs/multiplayer-sdk'
+
+const Container = styled.div`
+ background: #23233b;
+ border-radius: 15px;
+ padding: 15px;
+
+ min-height: 120px;
+`
+
+const Title = styled.h3`
+ margin: 0 0 10px 0;
+ color: #fff;
+ font-size: 1rem;
+ text-align: center;
+`
+
+const List = styled.ul`
+ list-style: none;
+ padding: 0;
+ margin: 0;
+ display: flex;
+ gap: 10px;
+ overflow-x: auto;
+ padding-bottom: 10px; /* For scrollbar spacing */
+
+ &::-webkit-scrollbar {
+ height: 4px;
+ }
+ &::-webkit-scrollbar-thumb {
+ background: #4a4a7c;
+ border-radius: 2px;
+ }
+ &::-webkit-scrollbar-track {
+ background: transparent;
+ }
+
+ & > li {
+ background: #2c2c54;
+ padding: 8px;
+ border-radius: 10px;
+ border: 1px solid #4a4a7c;
+ flex-shrink: 0;
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ min-width: 130px;
+ }
+`
+
+const Avatar = styled.div`
+ width: 30px;
+ height: 30px;
+ border-radius: 50%;
+ background: #4a4a7c;
+ flex-shrink: 0;
+`
+
+const PlayerInfo = styled.div`
+ display: flex;
+ flex-direction: column;
+ overflow: hidden;
+`
+
+const PlayerAddress = styled.div`
+ font-size: 0.8rem;
+ color: #e0e0e0;
+ font-family: monospace;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ overflow: hidden;
+`
+
+const PlayerWager = styled.div`
+ font-size: 0.75rem;
+ color: #2ecc71;
+ font-weight: bold;
+`
+
+type Player = IdlAccounts['game']['players'][number]
+
+interface RecentPlayersProps {
+ players: Player[]
+}
+
+export function RecentPlayers({ players }: RecentPlayersProps) {
+ const recentPlayers = useMemo(() => {
+ return [...players].reverse()
+ }, [players])
+
+ const shorten = (str: string) => `${str.slice(0, 4)}...`
+
+ return (
+
+ Recent Players
+
+
+ {recentPlayers.map((player) => (
+
+
+
+
+ {shorten(player.user.toBase58())}
+
+
+ {(player.wager.toNumber() / LAMPORTS_PER_SOL).toFixed(2)} SOL
+
+
+
+ ))}
+
+
+
+ )
+}
diff --git a/apps/platform/src/games/Jackpot/TopPlayers.tsx b/apps/platform/src/games/Jackpot/TopPlayers.tsx
new file mode 100644
index 00000000..deb84beb
--- /dev/null
+++ b/apps/platform/src/games/Jackpot/TopPlayers.tsx
@@ -0,0 +1,210 @@
+import React, { useMemo, useState, useEffect } from 'react'
+import styled, { css } from 'styled-components'
+import type { IdlAccounts } from '@coral-xyz/anchor'
+import type { Multiplayer } from '@gamba-labs/multiplayer-sdk'
+import { LAMPORTS_PER_SOL } from '@solana/web3.js'
+
+type Player = IdlAccounts['game']['players'][number]
+
+interface TopPlayersProps {
+ players: Player[]
+ totalPot: number
+ $isOverlay?: boolean
+}
+
+const COMPACT_BREAKPOINT = 900
+
+function useIsCompact(): boolean {
+ const [isCompact, setIsCompact] = useState(
+ () =>
+ typeof window !== 'undefined' &&
+ window.innerWidth <= COMPACT_BREAKPOINT
+ )
+ useEffect(() => {
+ const onResize = () =>
+ setIsCompact(window.innerWidth <= COMPACT_BREAKPOINT)
+ window.addEventListener('resize', onResize)
+ return () => window.removeEventListener('resize', onResize)
+ }, [])
+ return isCompact
+}
+
+const Container = styled.div<{ $isOverlay: boolean }>`
+ position: relative;
+ ${({ $isOverlay }) =>
+ $isOverlay
+ ? css`
+ background: rgba(35, 35, 59, 0.8);
+ backdrop-filter: blur(5px);
+ border: 1px solid #4a4a7c;
+ border-radius: 15px;
+ padding: 10px;
+ `
+ : css`
+ background: #23233b;
+ border-radius: 15px;
+ padding: 15px;
+ `}
+ height: 420px;
+ overflow: hidden;
+
+ @media (max-width: ${COMPACT_BREAKPOINT}px) {
+ background: rgba(35, 35, 59, 0.5);
+ backdrop-filter: blur(5px);
+ border: 1px solid #4a4a7c;
+ padding: 8px;
+ height: auto;
+ }
+`
+
+const Title = styled.h3`
+ margin: 0 0 10px;
+ color: #fff;
+ font-size: 1rem;
+ text-align: center;
+
+ @media (max-width: ${COMPACT_BREAKPOINT}px) {
+ display: none;
+ }
+`
+
+const List = styled.ul`
+ list-style: none;
+ margin: 0;
+ padding: 0;
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+`
+
+const PlayerItem = styled.li`
+ display: flex;
+ align-items: center;
+ background: #2c2c54;
+ padding: 8px;
+ border-radius: 8px;
+ border: 1px solid #4a4a7c;
+
+ @media (max-width: ${COMPACT_BREAKPOINT}px) {
+ padding: 4px;
+ border-radius: 6px;
+ }
+`
+
+const PlayerRank = styled.div`
+ font-size: 0.9rem;
+ font-weight: bold;
+ color: #f39c12;
+ margin-right: 10px;
+ min-width: 24px;
+ text-align: center;
+
+ @media (max-width: ${COMPACT_BREAKPOINT}px) {
+ font-size: 0.8rem;
+ margin-right: 6px;
+ min-width: 20px;
+ }
+`
+
+const PlayerInfo = styled.div`
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ overflow: hidden;
+`
+
+const PlayerAddress = styled.div`
+ font-size: 0.8rem;
+ color: #e0e0e0;
+ font-family: monospace;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ overflow: hidden;
+
+ @media (max-width: ${COMPACT_BREAKPOINT}px) {
+ font-size: 0.7rem;
+ }
+`
+
+const PlayerWager = styled.div`
+ font-size: 0.8rem;
+ color: #2ecc71;
+ font-weight: bold;
+ margin-top: 2px;
+
+ /* compact */
+ @media (max-width: ${COMPACT_BREAKPOINT}px) {
+ font-size: 0.7rem;
+ margin-top: 1px;
+ }
+`
+
+const Fade = styled.div`
+ content: '';
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ width: 100%;
+ height: 40px;
+ pointer-events: none;
+ background: linear-gradient(
+ rgba(35, 35, 59, 0),
+ rgba(35, 35, 59, 1)
+ );
+
+ /* hide on compact */
+ @media (max-width: ${COMPACT_BREAKPOINT}px) {
+ display: none;
+ }
+`
+
+export function TopPlayers({
+ players,
+ totalPot,
+ $isOverlay = false,
+}: TopPlayersProps) {
+ const isCompact = useIsCompact()
+
+ // sort & limit count based on layout
+ const sorted = useMemo(() => {
+ const all = [...players].sort(
+ (a, b) => b.wager.toNumber() - a.wager.toNumber()
+ )
+ const maxCount = isCompact ? 3 : 7
+ return all.slice(0, maxCount)
+ }, [players, isCompact])
+
+ const shorten = (addr: string) =>
+ `${addr.slice(0, 4)}…${addr.slice(-4)}`
+
+ return (
+
+ {!$isOverlay && Top Players}
+
+
+ {sorted.map((p, i) => {
+ const sol = p.wager.toNumber() / LAMPORTS_PER_SOL
+ const pct = totalPot
+ ? (p.wager.toNumber() / totalPot) * 100
+ : 0
+
+ return (
+
+ #{i + 1}
+
+
+ {shorten(p.user.toBase58())}
+
+
+ {sol.toFixed(2)} SOL • {pct.toFixed(1)} %
+
+
+
+ )
+ })}
+
+
+ {!isCompact && players.length > 8 && }
+
+ )
+}
diff --git a/apps/platform/src/games/Jackpot/Waiting.tsx b/apps/platform/src/games/Jackpot/Waiting.tsx
new file mode 100644
index 00000000..4ccc46aa
--- /dev/null
+++ b/apps/platform/src/games/Jackpot/Waiting.tsx
@@ -0,0 +1,39 @@
+import React from 'react'
+import styled from 'styled-components'
+import { motion } from 'framer-motion'
+
+const Overlay = styled.div`
+ position: absolute;
+ inset: 0; /* stretch to all edges */
+ display: flex;
+ flex-direction: column;
+ justify-content: center; /* vertical centre */
+ align-items: center; /* horizontal centre */
+ pointer-events: none; /* clicks pass through */
+ color: #a9a9b8;
+`
+
+const Ghost = styled.div`
+ font-size: 5rem;
+ filter: drop-shadow(0 5px 15px rgba(0,0,0,0.3));
+`
+
+const Text = styled.div`
+ margin-top: 1rem;
+ font-size: 1.2rem;
+ font-weight: bold;
+`
+
+export function Waiting() {
+ return (
+
+
+ 👻
+
+ Waiting for game…
+
+ )
+}
diff --git a/apps/platform/src/games/Jackpot/WinnerAnimation.tsx b/apps/platform/src/games/Jackpot/WinnerAnimation.tsx
new file mode 100644
index 00000000..39c8718a
--- /dev/null
+++ b/apps/platform/src/games/Jackpot/WinnerAnimation.tsx
@@ -0,0 +1,248 @@
+import React, {
+ useState,
+ useEffect,
+ useLayoutEffect,
+ useRef,
+} from 'react'
+import styled, { keyframes, css } from 'styled-components'
+import { motion, AnimatePresence } from 'framer-motion'
+import type { IdlAccounts } from '@coral-xyz/anchor'
+import type { Multiplayer } from '@gamba-labs/multiplayer-sdk'
+import type { PublicKey } from '@solana/web3.js'
+import { useSound } from 'gamba-react-ui-v2'
+import tickSnd from './sounds/tick.mp3'
+import winSnd from './sounds/win.mp3'
+
+const winnerGlow = keyframes`
+ 0%,100%{box-shadow:0 0 15px 5px rgba(46,204,113,.7);transform:scale(1.1)}
+ 50% {box-shadow:0 0 30px 10px rgba(46,204,113,1);transform:scale(1.15)}
+`
+
+const Wrapper = styled(motion.div)`
+ position:absolute;inset:0;
+ display:flex;flex-direction:column;justify-content:center;align-items:center;
+ background:rgba(26,26,46,.9);backdrop-filter:blur(5px);z-index:100;
+`
+
+const ReelContainer = styled.div`
+ position:relative;
+ width:100%;
+ max-width:80vw;
+
+ overflow-x:hidden; /* hide left / right bleed */
+ overflow-y:visible; /* BUT let the glow breathe vertically */
+
+ padding:40px 0; /* extra headroom above + below cards */
+
+ mask-image:linear-gradient(
+ to right,
+ transparent,
+ black 20%,
+ black 80%,
+ transparent
+ );
+`
+
+const Pointer = styled.div`
+ position:absolute;top:40px;left:50%;transform:translateX(-50%);
+ width:4px;height:calc(100% - 80px); /* compensate for 40 px padding */
+ background:#f39c12;box-shadow:0 0 10px #f39c12;border-radius:2px;z-index:2;
+`
+
+const PlayerReel = styled(motion.div)`display:flex;`
+
+const PlayerCard = styled.div<{ $isWinner:boolean;$isYou:boolean }>`
+ position:relative;flex-shrink:0;
+ width:100px;height:120px;margin:0 5px;
+ display:flex;flex-direction:column;align-items:center;justify-content:center;
+
+ background:#2c2c54;border:2px solid #4a4a7c;border-radius:10px;transition:.3s;
+
+ ${({$isYou,$isWinner})=>$isYou&&!$isWinner&&css`
+ border-color:#3498db;box-shadow:0 0 10px 2px rgba(52,152,219,.6);`}
+
+ ${({$isWinner})=>$isWinner&&css`
+ border-color:#2ecc71;animation:${winnerGlow} 1.5s ease-in-out infinite;`}
+`
+
+const YouBadge = styled.div`position:absolute;top:-10px;padding:2px 8px;font-size:.7rem;font-weight:700;color:#fff;background:#3498db;border-radius:6px;`
+const Avatar = styled.div`width:50px;height:50px;border-radius:50%;background:#4a4a7c;margin-bottom:10px;`
+const PlayerAddress = styled.div`font-size:.8rem;color:#e0e0e0;font-family:monospace;`
+const BottomBar = styled.div`height:3rem;display:flex;align-items:center;justify-content:center;`
+const WinnerText = styled(motion.div)`font-size:1.5rem;color:#fff;font-weight:bold;text-shadow:0 0 10px #2ecc71;`
+
+type Player = IdlAccounts['game']['players'][number]
+
+const TARGET = 100
+const short = (s:string)=>`${s.slice(0,4)}…`
+
+function buildReel(players:Player[], winnerIdx:number):Player[]{
+ if(!players.length) return []
+
+ const total = players.reduce((s,p)=>s+p.wager.toNumber(),0)
+ const TICKETS = 40
+ const pool:Player[]=[]
+ players.forEach(p=>{
+ const share = p.wager.toNumber()/total
+ const cnt = Math.max(1,Math.round(share*TICKETS))
+ for(let i=0;i0;i--){
+ const j=Math.floor(Math.random()*(i+1))
+ ;[pool[i],pool[j]]=[pool[j],pool[i]]
+ }
+
+ const reel:Player[]=Array.from({length:TARGET*2},(_,i)=>pool[i%pool.length])
+ reel[TARGET]=players[winnerIdx]||players[0]
+ return reel
+}
+
+export const WinnerAnimation:React.FC<{
+ players:Player[];winnerIndexes:number[];
+ currentUser?:PublicKey|null;
+ onClose?:()=>void;
+}> = ({ players, winnerIndexes, currentUser, onClose }) => {
+ if(!players.length||!winnerIndexes.length) return null
+ const winnerIdx = winnerIndexes[0]
+
+ const frozenPlayers=useRef([])
+ const frozenReel =useRef([])
+ if(!frozenReel.current.length){
+ frozenPlayers.current=[...players]
+ frozenReel.current =buildReel(frozenPlayers.current,winnerIdx)
+ }
+
+ const reel=frozenReel.current
+ const snap=frozenPlayers.current
+
+ const [closing,setClosing]=useState(false)
+ const [spinDone,setSpinDone]=useState(false)
+ const [winner,setWinner]=useState(null)
+ const [offset,setOffset]=useState(0)
+ const reelEl=useRef(null)
+ const centersRef = useRef([])
+ const lastTickIndexRef = useRef(null)
+
+ useLayoutEffect(()=>{
+ const el=reelEl.current
+ if(!el) return
+ const card = el.children[TARGET] as HTMLElement|undefined
+ if(card) setOffset(-(card.offsetLeft+card.offsetWidth/2 - el.clientWidth/2))
+
+ // precompute card center positions for ticking
+ const centers: number[] = []
+ for (let i = 0; i < el.children.length; i++) {
+ const c = el.children[i] as HTMLElement
+ centers.push(c.offsetLeft + c.offsetWidth / 2)
+ }
+ centersRef.current = centers
+ // initialize last tick index at starting position
+ const pointerX = el.clientWidth / 2
+ let nearest = 0, best = Infinity
+ centers.forEach((cx, idx) => {
+ const d = Math.abs(cx - pointerX)
+ if (d < best) { best = d; nearest = idx }
+ })
+ lastTickIndexRef.current = nearest
+ },[])
+
+ useEffect(()=>{
+ if(!spinDone) return
+ const t=setTimeout(()=>setWinner(snap[winnerIdx]),1500)
+ return()=>clearTimeout(t)
+ },[spinDone,snap,winnerIdx])
+
+ useEffect(()=>{
+ if(!winner) return
+ const t=setTimeout(()=>setClosing(true),3000)
+ return()=>clearTimeout(t)
+ },[winner])
+
+ // tick & win sounds
+ const { play: playSfx, sounds: sfx } = useSound({ tick: tickSnd, win: winSnd })
+
+ // play win SFX if the connected wallet is the winner
+ useEffect(()=>{
+ if (!winner || !currentUser) return
+ if (winner.user.equals(currentUser)) {
+ try { if (sfx.win?.ready) playSfx('win') } catch {}
+ }
+ },[winner, currentUser, sfx, playSfx])
+
+ useEffect(()=>{if(closing) onClose?.()},[closing,onClose])
+
+ const winnerPk=winner?.user.toBase58()??''
+ const mePk =currentUser?.toBase58()??''
+
+ // tick sound handled via onUpdate
+
+ return(
+
+ {!closing&&(
+
+
+
+ {
+ const el = reelEl.current
+ if (!el || typeof x !== 'number') return
+ const pointerX = -x + el.clientWidth / 2
+ const centers = centersRef.current
+ if (!centers.length) return
+ let nearest = 0, best = Infinity
+ for (let i = 0; i < centers.length; i++) {
+ const d = Math.abs(centers[i] - pointerX)
+ if (d < best) { best = d; nearest = i }
+ }
+ if (nearest !== lastTickIndexRef.current) {
+ lastTickIndexRef.current = nearest
+ if (sfx.tick?.ready) playSfx('tick')
+ }
+ }}
+ onAnimationComplete={()=>setSpinDone(true)}
+ >
+ {reel.map((p,i)=>{
+ const addr = p.user.toBase58()
+ const win = addr===winnerPk && i===TARGET
+ const you = addr===mePk
+ return(
+
+ {you&&!win&&YOU}
+
+ {short(addr)}
+
+ )
+ })}
+
+
+
+
+
+ {winner&&(
+
+ Winner: {short(winnerPk)}
+
+ )}
+
+
+
+ )}
+
+ )
+}
+
+export default WinnerAnimation
diff --git a/apps/platform/src/games/Jackpot/config.ts b/apps/platform/src/games/Jackpot/config.ts
new file mode 100644
index 00000000..bc0f0a5b
--- /dev/null
+++ b/apps/platform/src/games/Jackpot/config.ts
@@ -0,0 +1,9 @@
+import { PublicKey } from '@solana/web3.js';
+import { WRAPPED_SOL_MINT } from '@gamba-labs/multiplayer-sdk';
+
+//gamba creator bot adddress
+export const DESIRED_CREATOR = new PublicKey('GamermKQpHDVzw8BwuQnj6XSYXqvwCFu64k9cn88umqn');
+
+export const DESIRED_MAX_PLAYERS = 999;
+export const DESIRED_WINNERS_TARGET = 1;
+export const DESIRED_MINT = WRAPPED_SOL_MINT;
diff --git a/apps/platform/src/games/Jackpot/index.tsx b/apps/platform/src/games/Jackpot/index.tsx
new file mode 100644
index 00000000..743531a0
--- /dev/null
+++ b/apps/platform/src/games/Jackpot/index.tsx
@@ -0,0 +1,227 @@
+import React, { useEffect, useMemo, useRef, useState } from 'react'
+import { LAMPORTS_PER_SOL } from '@solana/web3.js'
+import { GambaUi, Multiplayer } from 'gamba-react-ui-v2'
+import { useGame, useSpecificGames } from 'gamba-react-v2'
+import { useWallet } from '@solana/wallet-adapter-react'
+import { BPS_PER_WHOLE } from 'gamba-core-v2'
+
+import { Countdown } from './Countdown'
+import { Pot } from './Pot'
+import { WinnerAnimation } from './WinnerAnimation'
+import { Coinfalls } from './Coinfall'
+import { TopPlayers } from './TopPlayers'
+import { RecentPlayers } from './RecentPlayers'
+import { RecentGames } from './RecentGames'
+import { Waiting } from './Waiting'
+import { MyStats } from './MyStats'
+
+import { DESIRED_CREATOR, DESIRED_MAX_PLAYERS, DESIRED_WINNERS_TARGET, DESIRED_MINT } from './config'
+import {
+ PLATFORM_CREATOR_ADDRESS,
+ MULTIPLAYER_FEE,
+ PLATFORM_REFERRAL_FEE, // referral %
+} from '../../constants'
+import * as S from './Jackpot.styles'
+
+// Responsive media query hook
+const useMediaQuery = (q: string) => {
+ const [m, setM] = useState(matchMedia(q).matches)
+ useEffect(() => {
+ const mm = matchMedia(q)
+ const h = () => setM(mm.matches)
+ mm.addEventListener('change', h)
+ return () => mm.removeEventListener('change', h)
+ }, [q])
+ return m
+}
+
+// Component
+export default function Jackpot() {
+ const isSmall = useMediaQuery('(max-width: 900px)')
+ const { publicKey: walletKey } = useWallet()
+
+ // Discover games (no auto polling)
+ const {
+ games, loading: gamesLoading, refresh: refreshGames,
+ } = useSpecificGames({
+ creator: DESIRED_CREATOR,
+ maxPlayers: DESIRED_MAX_PLAYERS,
+ winnersTarget: DESIRED_WINNERS_TARGET,
+ mint: DESIRED_MINT,
+ } as any, 0)
+
+ // Track last consumed gameId
+ const lastGameIdRef = useRef(null)
+
+ // Use first fresh game (skip previously consumed)
+ const freshGames = games.filter(
+ g => g.account.gameId.toNumber() !== lastGameIdRef.current,
+ )
+ const topGame = freshGames[0] ?? null
+
+ // Live subscription
+ const liveGame = useGame(topGame?.publicKey ?? null).game
+
+ // Phase handling
+ type Phase = 'playing' | 'animation' | 'waiting'
+ const [phase, setPhase] = useState('waiting')
+
+ // Set phase based on on-chain state
+ useEffect(() => {
+ if (liveGame && liveGame.state.waiting) setPhase('playing')
+ if (liveGame && liveGame.state.playing) setPhase('playing')
+ if (liveGame && liveGame.state.settled) setPhase('animation')
+ }, [liveGame])
+
+ // Poll while waiting only
+ useEffect(() => {
+ if (phase !== 'waiting') return
+ refreshGames() // kick off immediately
+ const id = setInterval(refreshGames, 5000)
+ return () => clearInterval(id)
+ }, [phase, refreshGames])
+
+ // After animation, mark game as consumed
+ const handleAnimationDone = () => {
+ if (liveGame) lastGameIdRef.current = liveGame.gameId.toNumber()
+ setPhase('waiting')
+ }
+
+ // Derived helpers
+ const players = liveGame?.players ?? []
+ const totalPotLamports = players.reduce((s, p) => s + p.wager.toNumber(), 0)
+ const waitingForPlayers= !!liveGame?.state.waiting
+ const settled = !!liveGame?.state.settled
+
+ const youJoined = useMemo(
+ () => !!walletKey && players.some(p => p.user.equals(walletKey)),
+ [walletKey, players],
+ )
+ const myEntry = players.find(p => walletKey && p.user.equals(walletKey))
+ const myBetLamports = myEntry?.wager.toNumber() ?? 0
+ const myChancePct = totalPotLamports
+ ? (myBetLamports / totalPotLamports) * 100
+ : 0
+
+ // Timestamps for progress bar
+ const creationMs = liveGame ? Number(liveGame.creationTimestamp) * 1e3 : 0
+ const softMs = liveGame ? Number(liveGame.softExpirationTimestamp) * 1e3 : 0
+ const totalDur = Math.max(softMs - creationMs, 0)
+
+ // Render
+ return (
+ <>
+
+
+
+
+ {!isSmall && (
+
+
+
+ )}
+
+
+
+ {liveGame && }
+
+ {isSmall && players.length > 0 && (
+
+
+
+ )}
+
+
+ {!liveGame && (
+
+
+
+ )}
+
+ {liveGame && (
+ <>
+
+ Game #{liveGame.gameId.toString()}
+
+ {waitingForPlayers ? 'Waiting'
+ : settled ? 'Settled'
+ : 'Live'}
+
+
+
+ {totalDur > 0 && (
+ {}}
+ />
+ )}
+
+
+
+ {phase === 'animation' && (
+
+ )}
+
+
+
+ {myEntry && (
+
+ )}
+
+ >
+ )}
+
+
+
+ {!isSmall && (
+
+
+
+ )}
+
+
+
+
+
+
+
+
+
+ {phase === 'playing' && waitingForPlayers && !youJoined && topGame && (
+
+ )}
+ {phase === 'playing' && waitingForPlayers && youJoined && topGame && (
+
+ )}
+
+ >
+ )
+}
diff --git a/apps/platform/src/games/Jackpot/sounds/join.mp3 b/apps/platform/src/games/Jackpot/sounds/join.mp3
new file mode 100644
index 00000000..bc66d0dc
Binary files /dev/null and b/apps/platform/src/games/Jackpot/sounds/join.mp3 differ
diff --git a/apps/platform/src/games/Jackpot/sounds/tick.mp3 b/apps/platform/src/games/Jackpot/sounds/tick.mp3
new file mode 100644
index 00000000..91068afe
Binary files /dev/null and b/apps/platform/src/games/Jackpot/sounds/tick.mp3 differ
diff --git a/apps/platform/src/games/Jackpot/sounds/win.mp3 b/apps/platform/src/games/Jackpot/sounds/win.mp3
new file mode 100644
index 00000000..c9f58b9b
Binary files /dev/null and b/apps/platform/src/games/Jackpot/sounds/win.mp3 differ
diff --git a/apps/platform/src/games/Plinko/game.ts b/apps/platform/src/games/Plinko/game.ts
index dd3161df..1033c9bb 100644
--- a/apps/platform/src/games/Plinko/game.ts
+++ b/apps/platform/src/games/Plinko/game.ts
@@ -1,326 +1,356 @@
-import Matter from 'matter-js'
+import Matter from "matter-js";
-const WIDTH = 700
-const HEIGHT = 700
+const WIDTH = 700;
+const HEIGHT = 700;
+const SIMULATIONS = 50;
-const SIMULATIONS = 100
-export const PLINKO_RAIUS = 9
-export const PEG_RADIUS = 11
-const RESTISTUTION = .4
-const GRAVITY = 1
-const SPAWN_OFFSET_RANGE = 10
+export const PLINKO_RAIUS = 9;
+export const PEG_RADIUS = 11;
+const RESTITUTION = 0.4;
+const GRAVITY = 1;
+const SPAWN_OFFSET_RANGE = 10;
-export const bucketWallHeight = 60
-export const bucketHeight = bucketWallHeight
-export const barrierHeight = bucketWallHeight * 1.2
-export const barrierWidth = 4
+export const bucketWallHeight = 60;
+export const bucketHeight = bucketWallHeight;
+export const barrierHeight = bucketWallHeight * 1.2;
+export const barrierWidth = 4;
interface PlinkoContactEvent {
- plinko?: Matter.Body
- peg?: Matter.Body
- bucket?: Matter.Body
- barrier?: Matter.Body
+ plinko?: Matter.Body;
+ peg?: Matter.Body;
+ bucket?: Matter.Body;
+ barrier?: Matter.Body;
}
export interface PlinkoProps {
- multipliers: number[]
- onContact: (contact: PlinkoContactEvent) => void
- rows: number
+ multipliers: number[];
+ onContact: (contact: PlinkoContactEvent) => void;
+ rows: number;
}
interface SimulationResult {
- bucketIndex: number
- plinkoIndex: number
- path: {x:number,y:number}[]
- collisions: { frame: number, event: PlinkoContactEvent }[]
+ bucketIndex: number;
+ plinkoIndex: number;
+ path: Float32Array; // dense [x0,y0,x1,y1…]
+ collisions: { frame: number; event: PlinkoContactEvent }[];
}
export class Plinko {
- width = WIDTH
- height = HEIGHT
+ width = WIDTH;
+ height = HEIGHT;
private engine = Matter.Engine.create({
gravity: { y: GRAVITY },
timing: { timeScale: 1 },
- })
-
- private runner = Matter.Runner.create()
- private props: PlinkoProps
- private ballComposite = Matter.Composite.create()
- private bucketComposite = Matter.Composite.create()
- private startPositions: number[]
- private currentPath: {x:number,y:number}[] | null = null
- private replayCollisions: { frame: number, event: PlinkoContactEvent }[] = []
- private currentFrame: number = 0
- private replayBall: Matter.Body | null = null
- private animationId: number | null = null
- private visualizePath: boolean = false
-
- setVisualizePath(enabled: boolean) {
- this.visualizePath = enabled
+ });
+
+ // keep isFixed:true so tick(engine,1) is deterministic
+ private runner = Matter.Runner.create({ isFixed: true });
+
+ private props: PlinkoProps;
+ private ballComposite = Matter.Composite.create();
+ private bucketComposite = Matter.Composite.create();
+ private startPositions: number[];
+ private currentPath: Float32Array | null = null;
+ private replayCollisions: { frame: number; event: PlinkoContactEvent }[] = [];
+ private currentFrame = 0;
+ private replayBall: Matter.Body | null = null;
+ private animationId: number | null = null;
+ private visualizePath = false;
+
+ setVisualizePath(on: boolean) {
+ this.visualizePath = on;
+ }
+
+ constructor(props: PlinkoProps) {
+ this.props = props;
+ // pre-compute 50 random X-offsets in ±5px
+ this.startPositions = Array.from({ length: SIMULATIONS }).map(() =>
+ Matter.Common.random(-SPAWN_OFFSET_RANGE / 2, SPAWN_OFFSET_RANGE / 2)
+ );
+
+ // build peg grid
+ const rowSize = this.height / (props.rows + 2);
+ const pegs = Array.from({ length: props.rows })
+ .flatMap((_, row, all) => {
+ const cols = row + 1;
+ const rowW = (this.width * row) / (all.length - 1);
+ const spacing = cols === 1 ? 0 : rowW / (cols - 1);
+ return Array.from({ length: cols }).map((_, col) => {
+ const x = this.width / 2 - rowW / 2 + spacing * col;
+ const y = rowSize * row + rowSize / 2;
+ return Matter.Bodies.circle(x, y, PEG_RADIUS, {
+ isStatic: true,
+ label: "Peg",
+ plugin: { pegIndex: row * cols + col },
+ });
+ });
+ })
+ .slice(1);
+
+ Matter.Composite.add(this.bucketComposite, this.makeBuckets());
+ Matter.Composite.add(this.engine.world, [
+ ...pegs,
+ this.ballComposite,
+ this.bucketComposite,
+ ]);
}
private makeBuckets() {
- const unique = Array.from(new Set(this.props.multipliers))
- const secondHalf = [...unique].slice(1)
- const firstHalf = [...secondHalf].reverse()
- const center = [unique[0], unique[0], unique[0]]
- const buckets = [...firstHalf, ...center, ...secondHalf]
- const numBuckets = buckets.length
- const bucketWidth = this.width / numBuckets
- const barriers = Array.from({ length: numBuckets + 1 }).map((_, i) => {
- const x = i * bucketWidth
- return Matter.Bodies.rectangle(x, this.height - barrierHeight / 2, barrierWidth, barrierHeight, {
+ const unique = Array.from(new Set(this.props.multipliers));
+ const secondHalf = unique.slice(1);
+ const firstHalf = [...secondHalf].reverse();
+ const center = [unique[0], unique[0], unique[0]];
+ const layout = [...firstHalf, ...center, ...secondHalf];
+ const w = this.width / layout.length;
+
+ const barriers = Array.from({ length: layout.length + 1 }).map((_, i) =>
+ Matter.Bodies.rectangle(i * w, this.height - barrierHeight / 2, barrierWidth, barrierHeight, {
isStatic: true,
- label: 'Barrier',
+ label: "Barrier",
chamfer: { radius: 2 },
})
- })
- const sensors = buckets.map((bucketMultiplier, bucketIndex) => {
- const x = bucketIndex * bucketWidth + bucketWidth / 2
- return Matter.Bodies.rectangle(x, this.height - bucketHeight / 2, bucketWidth - barrierWidth, bucketHeight, {
- isStatic: true,
- isSensor: true,
- label: 'Bucket',
- plugin: {
- bucketIndex,
- bucketMultiplier,
- },
- })
- })
+ );
+
+ const sensors = layout.map((m, idx) =>
+ Matter.Bodies.rectangle(
+ idx * w + w / 2,
+ this.height - bucketHeight / 2,
+ w - barrierWidth,
+ bucketHeight,
+ {
+ isStatic: true,
+ isSensor: true,
+ label: "Bucket",
+ plugin: { bucketIndex: idx, bucketMultiplier: m },
+ }
+ )
+ );
- return [...sensors, ...barriers]
+ return [...sensors, ...barriers];
}
- private makePlinko = (offsetX: number, index: number) => {
- const x = this.width / 2 + offsetX
- const y = -10
- return Matter.Bodies.circle(x, y, PLINKO_RAIUS, {
- restitution: RESTISTUTION,
+ private makePlinko = (offsetX: number, index: number) =>
+ Matter.Bodies.circle(this.width / 2 + offsetX, -10, PLINKO_RAIUS, {
+ restitution: RESTITUTION,
collisionFilter: { group: -6969 },
- label: 'Plinko',
+ label: "Plinko",
plugin: { startPositionIndex: index },
- })
+ });
+
+ getBodies() {
+ return Matter.Composite.allBodies(this.engine.world);
}
single() {
- Matter.Events.off(this.engine, 'collisionStart', this.collisionHandler)
- Matter.Runner.stop(this.runner)
- Matter.Events.on(this.engine, 'collisionStart', this.collisionHandler)
+ Matter.Events.off(this.engine, "collisionStart", this.collisionHandler);
+ Matter.Runner.stop(this.runner);
+ Matter.Events.on(this.engine, "collisionStart", this.collisionHandler);
Matter.Composite.add(
this.ballComposite,
- this.makePlinko(Matter.Common.random(-SPAWN_OFFSET_RANGE, SPAWN_OFFSET_RANGE), 0),
- )
- Matter.Runner.run(this.runner, this.engine)
+ this.makePlinko(Matter.Common.random(-SPAWN_OFFSET_RANGE, SPAWN_OFFSET_RANGE), 0)
+ );
+ Matter.Runner.run(this.runner, this.engine);
}
cleanup() {
- Matter.World.clear(this.engine.world, false)
- Matter.Engine.clear(this.engine)
- if (this.animationId !== null) {
- cancelAnimationFrame(this.animationId)
- this.animationId = null
- }
- }
-
- private makePlinkos() {
- return this.startPositions.map(this.makePlinko)
+ Matter.World.clear(this.engine.world, false);
+ Matter.Engine.clear(this.engine);
+ if (this.animationId !== null) cancelAnimationFrame(this.animationId);
+ this.animationId = null;
}
- getBodies() {
- return Matter.Composite.allBodies(this.engine.world)
- }
-
- constructor(props: PlinkoProps) {
- this.props = props
- this.startPositions = Array.from({ length: SIMULATIONS }).map(() => Matter.Common.random(-SPAWN_OFFSET_RANGE / 2, SPAWN_OFFSET_RANGE / 2))
-
- const rowSize = this.height / (this.props.rows + 2)
- const pegs = Array.from({ length: this.props.rows })
- .flatMap((_, row, jarr) => {
- const cols = (1 + row)
- const rowWidth = this.width * (row / (jarr.length - 1))
- const colSpacing = cols === 1 ? 0 : rowWidth / (cols - 1)
- return Array.from({ length: cols })
- .map((_, column, arr) => {
- const x = this.width / 2 - rowWidth / 2 + colSpacing * column
- const y = rowSize * row + rowSize / 2
- return Matter.Bodies.circle(x, y, PEG_RADIUS, {
- isStatic: true,
- label: 'Peg',
- plugin: { pegIndex: row * arr.length + column },
- })
- })
- }).slice(1)
-
+ reset() {
+ Matter.Runner.stop(this.runner);
+ Matter.Composite.clear(this.ballComposite, false);
Matter.Composite.add(
- this.bucketComposite,
- this.makeBuckets(),
- )
-
- Matter.Composite.add(this.engine.world, [
- ...pegs,
this.ballComposite,
- this.bucketComposite,
- ])
+ this.startPositions.map(this.makePlinko)
+ );
}
- reset() {
- Matter.Runner.stop(this.runner)
- Matter.Composite.clear(this.ballComposite, false)
- Matter.Composite.add(this.ballComposite, this.makePlinkos())
- }
+ /** Simulate up to 1 000 steps, recording *all* paths until the very first
+ * ball hits the target bucket, then stop. */
+ private simulate(desiredBucketIndex: number): SimulationResult | null {
+ this.reset();
- private recordContactEvent(event: Matter.IEventCollision, frame: number, collisions: { frame: number, event: PlinkoContactEvent }[]) {
- for (const pair of event.pairs) {
- const contactEvent: PlinkoContactEvent = {}
- const assignBody = (key: keyof PlinkoContactEvent, label: string) => {
- if (pair.bodyA.label === label) contactEvent[key] = pair.bodyA
- if (pair.bodyB.label === label) contactEvent[key] = pair.bodyB
- }
- assignBody('peg', 'Peg')
- assignBody('bucket', 'Bucket')
- assignBody('barrier', 'Barrier')
- assignBody('plinko', 'Plinko')
+ // per-ball path buffers
+ const paths: number[][] = this.startPositions.map(() => []);
+ // all collisions, to be filtered
+ const allCollisions: { frame: number; event: PlinkoContactEvent }[] = [];
+ let chosenIndex: number | null = null;
+ let frame = 0;
- if (contactEvent.peg || contactEvent.bucket || contactEvent.barrier || contactEvent.plinko) {
- collisions.push({ frame, event: contactEvent })
+ const simHandler = (ev: Matter.IEventCollision) => {
+ // record every collision
+ this.recordContactEvent(ev, frame, allCollisions);
+
+ // detect the *first* bucket hit
+ for (const p of ev.pairs) {
+ const A = p.bodyA, B = p.bodyB;
+ if (
+ (A.label === "Plinko" && B.label === "Bucket" && B.plugin.bucketIndex === desiredBucketIndex) ||
+ (B.label === "Plinko" && A.label === "Bucket" && A.plugin.bucketIndex === desiredBucketIndex)
+ ) {
+ chosenIndex = (A.label === "Plinko" ? A : B).plugin.startPositionIndex;
+ break;
+ }
}
- }
- }
+ };
- simulate(desiredBucketIndex: number) {
- const results: Omit[] = []
- const paths: {x:number,y:number}[][] = this.startPositions.map(() => [])
- const allCollisions: { frame: number, event: PlinkoContactEvent }[] = []
+ Matter.Events.on(this.engine, "collisionStart", simHandler);
- let simFrame = 0
- const simHandler = (ev: Matter.IEventCollision) => {
- this.recordContactEvent(ev, simFrame, allCollisions)
- }
+ // run up to 1 000 ms-ticks or until chosen ball leaves bottom
+ for (; frame < 1000; frame++) {
+ Matter.Runner.tick(this.runner, this.engine, 1);
- Matter.Events.on(this.engine, 'collisionStart', simHandler)
- this.reset()
-
- for (let i = 0; i < 1000; i++) {
- simFrame = i
- Matter.Runner.tick(this.runner, this.engine, 1)
- const bodies = Matter.Composite.allBodies(this.ballComposite)
- bodies.forEach((b) => {
- if (b.label === 'Plinko') {
- const idx = b.plugin.startPositionIndex
- paths[idx].push({ x: b.position.x, y: b.position.y })
+ // record position for every ball this frame
+ for (const b of this.ballComposite.bodies) {
+ if (b.label === "Plinko") {
+ const idx = b.plugin.startPositionIndex;
+ paths[idx].push(b.position.x, b.position.y);
}
- })
- }
-
- Matter.Events.off(this.engine, 'collisionStart', simHandler)
- Matter.Runner.stop(this.runner)
- Matter.Composite.clear(this.ballComposite, false)
+ }
- const bucketHits: { [plinkoIndex: number]: number } = {}
- allCollisions.forEach(({frame, event}) => {
- if (event.plinko && event.bucket) {
- const plinkoIndex = event.plinko.plugin.startPositionIndex
- if (bucketHits[plinkoIndex] === undefined) {
- bucketHits[plinkoIndex] = event.bucket.plugin.bucketIndex
+ // once we know which ball and it has dropped below view, stop
+ if (chosenIndex !== null) {
+ const winBall = this.ballComposite.bodies.find(
+ (b) => b.plugin.startPositionIndex === chosenIndex
+ )!;
+ if (winBall.position.y > this.height) {
+ frame++;
+ break;
}
}
- })
-
- const finalResults = []
- for (let i=0; i bucketIndex === desiredBucketIndex)
+ Matter.Events.off(this.engine, "collisionStart", simHandler);
+ Matter.Runner.stop(this.runner);
+ Matter.Composite.clear(this.ballComposite, false);
+
+ if (chosenIndex === null) return null;
+
+ // build a typed array for the winner’s full path
+ const winnerPath = new Float32Array(paths[chosenIndex]);
+
+ // filter out only this ball’s collisions
+ const winnerCollisions = allCollisions.filter(
+ (c) => c.event.plinko?.plugin.startPositionIndex === chosenIndex
+ );
+
+ return {
+ bucketIndex: desiredBucketIndex,
+ plinkoIndex: chosenIndex,
+ path: winnerPath,
+ collisions: winnerCollisions,
+ };
}
- collisionHandler = (event: Matter.IEventCollision) => {
- const contactEvent: PlinkoContactEvent = {}
- for (const pair of event.pairs) {
- const assignBody = (key: keyof PlinkoContactEvent, label: string) => {
- if (pair.bodyA.label === label) contactEvent[key] = pair.bodyA
- if (pair.bodyB.label === label) contactEvent[key] = pair.bodyB
+ private recordContactEvent(
+ ev: Matter.IEventCollision,
+ frame: number,
+ list: { frame: number; event: PlinkoContactEvent }[],
+ onlyForPlinko?: number
+ ) {
+ for (const p of ev.pairs) {
+ const evt: PlinkoContactEvent = {};
+ const tag = (k: keyof PlinkoContactEvent, lbl: string) => {
+ if (p.bodyA.label === lbl) evt[k] = p.bodyA;
+ if (p.bodyB.label === lbl) evt[k] = p.bodyB;
+ };
+ tag("peg", "Peg");
+ tag("barrier", "Barrier");
+ tag("bucket", "Bucket");
+ tag("plinko", "Plinko");
+
+ if (
+ evt.plinko &&
+ (onlyForPlinko === undefined ||
+ evt.plinko.plugin.startPositionIndex === onlyForPlinko)
+ ) {
+ list.push({ frame, event: evt });
}
- assignBody('peg', 'Peg')
- assignBody('bucket', 'Bucket')
- assignBody('barrier', 'Barrier')
- assignBody('plinko', 'Plinko')
}
- this.props.onContact && this.props.onContact(contactEvent)
- }
-
- runAll() {
- Matter.Events.off(this.engine, 'collisionStart', this.collisionHandler)
- Matter.Runner.stop(this.runner)
- Matter.Composite.clear(this.ballComposite, false)
- Matter.Events.on(this.engine, 'collisionStart', this.collisionHandler)
- Matter.Composite.add(
- this.ballComposite,
- this.makePlinkos(),
- )
- Matter.Runner.run(this.runner, this.engine)
}
run(desiredMultiplier: number) {
- Matter.Events.off(this.engine, 'collisionStart', this.collisionHandler)
+ // pick a bucket with matching multiplier
const bucket = Matter.Common.choose(
- this.bucketComposite.bodies.filter((x) => x.plugin.bucketMultiplier === desiredMultiplier),
- )
- const candidates = this.simulate(bucket.plugin.bucketIndex)
- if (!candidates.length) throw new Error('Failed to simulate desired outcome')
-
- const chosen = Matter.Common.choose(candidates)
+ this.bucketComposite.bodies.filter(
+ (b) => b.plugin.bucketMultiplier === desiredMultiplier
+ )
+ );
+ const sim = this.simulate(bucket.plugin.bucketIndex);
+ if (!sim) throw new Error("Failed to simulate desired outcome");
if (this.visualizePath) {
- console.log("Chosen path:", chosen.path)
+ console.log("Simulation frames:", sim.path.length / 2);
}
- this.currentPath = chosen.path
- this.currentFrame = 0
-
- const chosenIndex = chosen.plinkoIndex
- const chosenCollisions = chosen.collisions.filter(({event}) => {
- return event.plinko && event.plinko.plugin.startPositionIndex === chosenIndex
- })
- this.replayCollisions = chosenCollisions
+ this.currentPath = sim.path;
+ this.currentFrame = 0;
+ this.replayCollisions = sim.collisions;
- const ball = this.makePlinko(this.startPositions[chosenIndex], chosenIndex)
- Matter.Composite.add(this.ballComposite, ball)
- this.replayBall = ball
+ // spawn the live ball at the same start offset
+ const liveBall = this.makePlinko(
+ this.startPositions[sim.plinkoIndex],
+ sim.plinkoIndex
+ );
+ Matter.Composite.add(this.ballComposite, liveBall);
+ this.replayBall = liveBall;
- Matter.Runner.stop(this.runner)
- this.startReplayAnimation()
+ // purely positional replay—no physics
+ this.startReplayAnimation();
}
private startReplayAnimation() {
- if (this.animationId !== null) {
- cancelAnimationFrame(this.animationId)
- }
- const animate = () => {
- if (!this.currentPath || !this.replayBall) return
- if (this.currentFrame >= this.currentPath.length) {
- return
- }
- const pos = this.currentPath[this.currentFrame]
- Matter.Body.setPosition(this.replayBall, { x: pos.x, y: pos.y })
+ if (this.animationId !== null) cancelAnimationFrame(this.animationId);
- const frameCollisions = this.replayCollisions.filter(c => c.frame === this.currentFrame)
- frameCollisions.forEach(({event}) => {
- this.props.onContact(event)
- })
+ const step = () => {
+ if (!this.currentPath || !this.replayBall) return;
+ const totalFrames = this.currentPath.length / 2;
+ if (this.currentFrame >= totalFrames) return;
+
+ const x = this.currentPath[this.currentFrame * 2];
+ const y = this.currentPath[this.currentFrame * 2 + 1];
+ Matter.Body.setPosition(this.replayBall, { x, y });
- this.currentFrame++
- this.animationId = requestAnimationFrame(animate)
+ // fire any collisions for this frame
+ this.replayCollisions
+ .filter((c) => c.frame === this.currentFrame)
+ .forEach(({ event }) => this.props.onContact(event));
+
+ this.currentFrame++;
+ this.animationId = requestAnimationFrame(step);
+ };
+
+ this.animationId = requestAnimationFrame(step);
+ }
+
+ collisionHandler = (ev: Matter.IEventCollision) => {
+ const evt: PlinkoContactEvent = {};
+ for (const p of ev.pairs) {
+ const tag = (k: keyof PlinkoContactEvent, lbl: string) => {
+ if (p.bodyA.label === lbl) evt[k] = p.bodyA;
+ if (p.bodyB.label === lbl) evt[k] = p.bodyB;
+ };
+ tag("peg", "Peg");
+ tag("barrier", "Barrier");
+ tag("bucket", "Bucket");
+ tag("plinko", "Plinko");
}
- this.animationId = requestAnimationFrame(animate)
+ this.props.onContact(evt);
+ };
+
+ runAll() {
+ Matter.Events.off(this.engine, "collisionStart", this.collisionHandler);
+ Matter.Runner.stop(this.runner);
+ Matter.Composite.clear(this.ballComposite, false);
+ Matter.Events.on(this.engine, "collisionStart", this.collisionHandler);
+ Matter.Composite.add(
+ this.ballComposite,
+ this.startPositions.map(this.makePlinko)
+ );
+ Matter.Runner.run(this.runner, this.engine);
}
}
diff --git a/apps/platform/src/games/PlinkoRace/board/Board.tsx b/apps/platform/src/games/PlinkoRace/board/Board.tsx
new file mode 100644
index 00000000..229d0e76
--- /dev/null
+++ b/apps/platform/src/games/PlinkoRace/board/Board.tsx
@@ -0,0 +1,294 @@
+import React, { useMemo, useEffect, useState, useRef } from 'react'
+import { GambaUi } from 'gamba-react-ui-v2'
+import { useWallet } from '@solana/wallet-adapter-react'
+import { PublicKey } from '@solana/web3.js'
+import { useMultiPlinko } from '../hooks/useMultiPlinko'
+import {
+ BUCKET_DEFS, BucketType, DYNAMIC_SEQUENCE,
+ DYNAMIC_EXTRA_MULT, CENTER_BUCKET,
+} from '../engine/constants'
+import { PlayerInfo } from '../engine/types'
+import BoardHUD, { HudMessage, HudPayload } from './BoardHUD'
+import BoardRenderer from './BoardRenderer'
+import Scoreboard from './Scoreboard'
+import { makeRng } from '../engine/deterministic'
+
+import extraBallSnd from '../sounds/extraball.mp3'
+import readyGoSnd from '../sounds/readygo.mp3'
+import fallSnd from '../sounds/fall.mp3'
+import bigComboSnd from '../sounds/bigcombo.mp3'
+import finishSnd from '../sounds/finsh.mp3'
+import ouchSnd from '../sounds/ouch.mp3'
+
+type Particle = { x:number; y:number; size:number; opacity:number; life:number; vx:number; vy:number }
+type LerpState = { px:number; py:number }
+
+export default function Board({
+ players,
+ winnerIdx,
+ metadata = {},
+ youIndexOverride,
+ gamePk,
+ targetPoints = 100,
+ payouts,
+ onFinished,
+}: {
+ players: PublicKey[]
+ winnerIdx: number | null
+ metadata?: Record
+ youIndexOverride?: number
+ gamePk: string
+ targetPoints?: number
+ payouts?: number[]
+ onFinished?: () => void
+}) {
+ const roster: PlayerInfo[] = useMemo(() => {
+ const DISTINCT_COLORS = [
+ '#e6194B', // red
+ '#3cb44b', // green
+ '#ffe119', // yellow
+ '#4363d8', // blue
+ '#f58231', // orange
+ '#911eb4', // purple
+ '#46f0f0', // cyan
+ '#f032e6', // magenta
+ '#bcf60c', // lime
+ '#fabebe', // pink
+ '#008080', // teal
+ '#e6beff', // lavender
+ '#9a6324', // brown
+ '#fffac8', // beige
+ '#800000', // maroon
+ '#aaffc3', // mint
+ '#808000', // olive
+ '#ffd8b1', // apricot
+ '#000075', // navy
+ '#a9a9a9', // gray
+ ] as const
+ return players.map((p, i) => ({
+ id: p.toBase58(),
+ color: DISTINCT_COLORS[i % DISTINCT_COLORS.length],
+ }))
+ }, [players])
+ const { publicKey } = useWallet()
+ const youIdx = useMemo(
+ () => youIndexOverride ?? roster.findIndex(r => r.id === publicKey?.toBase58()),
+ [roster, publicKey, youIndexOverride]
+ )
+
+ const { engine, recordRace, replayRace } = useMultiPlinko(roster, gamePk)
+ const [scores, setScores] = useState([])
+ const [mults, setMults] = useState([])
+ const [dynModes, setDynModes] = useState([])
+ const [started, setStarted] = useState(false)
+ const [patternOffsets, setPatternOffsets] = useState([])
+ const [finished, setFinished] = useState(false)
+ const [hud, setHud] = useState(null)
+ const [popups, setPopups] = useState<{ bucketIndex:number; value:number; life:number; y:number }[]>([])
+
+ const showHud = (text: HudMessage) => {
+ setHud({ text, key: Date.now() })
+ }
+
+ const bucketAnim = useRef>({}).current
+ const pegAnim = useRef>({}).current
+ const particles = useRef([]).current
+ const arrowPos = useRef
+ }
+ >
+
+ {/* force the same 600px height as your regular */}
+
+
+
+ {/* controls + play button */}
+
+
+
+
+
+
+
+ )
+}
diff --git a/apps/platform/src/sections/Dashboard/GameCard.tsx b/apps/platform/src/sections/Dashboard/GameCard.tsx
index ea03493a..71a0856a 100644
--- a/apps/platform/src/sections/Dashboard/GameCard.tsx
+++ b/apps/platform/src/sections/Dashboard/GameCard.tsx
@@ -1,120 +1,127 @@
-import { GameBundle } from 'gamba-react-ui-v2'
-import React from 'react'
-import { NavLink, useLocation } from 'react-router-dom'
-import styled, { keyframes } from 'styled-components'
+// src/components/GameCard.tsx
+import React from 'react';
+import { GameBundle } from 'gamba-react-ui-v2';
+import { NavLink, useLocation } from 'react-router-dom';
+import styled, { keyframes } from 'styled-components';
const tileAnimation = keyframes`
- 0% {
- background-position: -100px 100px;
- }
- 100% {
- background-position: 100px -100px;
- }
-`
+ 0% { background-position: -100px 100px; }
+ 100% { background-position: 100px -100px; }
+`;
-const StyledGameCard = styled(NavLink)<{$small: boolean, $background: string}>`
- width: 100%;
-
- @media (min-width: 800px) {
- width: 100%;
- }
+const StyledGameCard = styled(NavLink)<{ $small: boolean; $background: string }>`
+ position: relative;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ overflow: hidden;
+ pointer-events: auto; /* if you need clicks */
- aspect-ratio: ${(props) => props.$small ? '1/.5' : '1/.6'};
+ width: 100%;
+ aspect-ratio: ${({ $small }) => ($small ? '1/.5' : '1/.6')};
+ background: ${({ $background }) => $background};
background-size: cover;
+ background-position: center;
border-radius: 10px;
-
- color: white;
text-decoration: none;
+ color: white;
+ font-weight: bold;
font-size: 24px;
+ transition: transform 0.2s ease;
- transition: transform .2s ease;
- /* border-bottom: 2px solid #00000033; */
-
- & > .background {
+ & > .background,
+ & > .image {
position: absolute;
- left: 0;
top: 0;
+ left: 0;
width: 100%;
height: 100%;
- background-size: 100%;
- background-position: center;
+ transition: transform 0.2s ease, opacity 0.3s ease;
+ }
+
+ & > .background {
background-image: url(/stuff.png);
+ background-size: 100%;
background-repeat: repeat;
- transition: transform .2s ease, opacity .3s;
animation: ${tileAnimation} 5s linear infinite;
opacity: 0;
}
& > .image {
- position: absolute;
- left: 0;
- top: 0;
- width: 100%;
- height: 100%;
background-size: 90% auto;
background-position: center;
background-repeat: no-repeat;
- transform: scale(.9);
- transition: transform .2s ease;
+ transform: scale(0.9);
+ }
+
+ & > .play {
+ position: absolute;
+ bottom: 5px;
+ right: 5px;
+ padding: 5px 10px;
+ font-size: 14px;
+ background: rgba(0, 0, 0, 0.4);
+ border-radius: 5px;
+ text-transform: uppercase;
+ opacity: 0;
+ backdrop-filter: blur(20px);
+ transition: opacity 0.2s ease;
}
&:hover {
transform: scale(1.01);
- .image {
+ outline: 5px solid rgba(149, 100, 255, 0.2);
+ outline-offset: 0;
+
+ & > .background {
+ opacity: 0.35;
+ }
+ & > .image {
transform: scale(1);
}
-
- .background {
- opacity: .35;
+ & > .play {
+ opacity: 1;
}
}
+`;
- position: relative;
- transform: scale(1);
- background: ${(props) => props.$background};
- max-height: 100%;
- overflow: hidden;
- display: flex;
- justify-content: center;
- align-items: center;
- flex-grow: 0;
- flex-shrink: 0;
- background-size: 100% auto;
- background-position: center;
+// New badge for the “VS” tag (or any other tag you choose)
+const Tag = styled.div`
+ position: absolute;
+ top: 8px;
+ left: 8px;
+ padding: 2px 6px;
+ font-size: 12px;
font-weight: bold;
- .play {
- font-size: 14px;
- border-radius: 5px;
- padding: 5px 10px;
- background: #00000066;
- position: absolute;
- right: 5px;
- bottom: 5px;
- opacity: 0;
- text-transform: uppercase;
+ background: rgba(0, 0, 0, 0.6);
+ color: #fff;
+ border-radius: 4px;
+ text-transform: uppercase;
+ z-index: 2;
+`;
- backdrop-filter: blur(20px);
- }
- &:hover .play {
- opacity: 1;
- }
- &:hover {
- outline: #9564ff33 solid 5px;
- outline-offset: 0px;
- }
-`
+export function GameCard({
+ game,
+}: {
+ game: GameBundle & { meta: { tag?: string; [key: string]: any } };
+}) {
+ const small = useLocation().pathname !== '/';
-export function GameCard({ game }: {game: GameBundle}) {
- const small = useLocation().pathname !== '/'
return (
+ {/* render the VS badge if present */}
+ {game.meta.tag && {game.meta.tag}}
+
-
+
Play {game.meta.name}
- )
+ );
}
diff --git a/apps/platform/src/sections/Dashboard/WelcomeBanner.tsx b/apps/platform/src/sections/Dashboard/WelcomeBanner.tsx
index 7115cad3..90dfdc06 100644
--- a/apps/platform/src/sections/Dashboard/WelcomeBanner.tsx
+++ b/apps/platform/src/sections/Dashboard/WelcomeBanner.tsx
@@ -1,136 +1,137 @@
-import { useWallet } from '@solana/wallet-adapter-react'
-import { useWalletModal } from '@solana/wallet-adapter-react-ui'
-import React from 'react'
-import styled from 'styled-components'
-import { useUserStore } from '../../hooks/useUserStore'
-
-const Buttons = styled.div`
- overflow: hidden;
+import { useWallet } from '@solana/wallet-adapter-react';
+import { useWalletModal } from '@solana/wallet-adapter-react-ui';
+import React from 'react';
+import styled from 'styled-components';
+import { useUserStore } from '../../hooks/useUserStore';
+
+const WelcomeWrapper = styled.div`
+ /* Animations */
+ @keyframes welcome-fade-in {
+ from { opacity: 0; }
+ to { opacity: 1; }
+ }
+
+ @keyframes backgroundGradient {
+ 0% { background-position: 0% 50%; }
+ 50% { background-position: 100% 50%; }
+ 100% { background-position: 0% 50%; }
+ }
+
+ /* Styling */
+ background: linear-gradient(-45deg, #ffb07c, #ff3e88, #2969ff, #ef3cff, #ff3c87);
+ background-size: 300% 300%;
+ animation: welcome-fade-in 0.5s ease, backgroundGradient 30s ease infinite;
+ border-radius: 12px; /* Slightly larger radius for a modern look */
+ padding: 24px; /* Consistent padding */
display: flex;
flex-direction: column;
- justify-content: space-between;
- align-items: center;
- gap: 10px;
+ gap: 24px; /* Consistent gap */
+ text-align: center;
+ filter: drop-shadow(0 4px 3px rgba(0,0,0,.07)) drop-shadow(0 2px 2px rgba(0,0,0,.06));
+ /* Desktop styles using a min-width media query */
@media (min-width: 800px) {
- height: 100%;
+ display: grid;
+ grid-template-columns: 2fr 1fr;
+ align-items: center;
+ text-align: left;
+ padding: 40px;
+ gap: 40px;
}
+`;
- @media (max-width: 800px) {
- display: flex;
- flex-direction: row;
- justify-content: space-between;
- width: 100%;
- padding-top: 0!important;
+const WelcomeContent = styled.div`
+ h1 {
+ font-size: 1.75rem; /* Responsive font size */
+ margin: 0 0 8px 0;
+ color: #ffffff;
}
- & > button {
- border: none;
- width: 100%;
- border-radius: 10px;
- padding: 10px;
- background: #ffffffdf;
- transition: background-color .2s ease;
- color: black;
- cursor: pointer;
- &:hover {
- background: white;
- }
- }
-`
-
-const Welcome = styled.div`
- @keyframes welcome-fade-in {
- from {
- opacity: 0;
- }
- to {
- opacity: 1;
- }
+ p {
+ font-size: 1rem;
+ color: #ffffffd1;
+ margin: 0;
}
- @keyframes backgroundGradient {
- 0% {
- background-position: 0% 50%;
- }
- 50% {
- background-position: 100% 50%;
+ @media (min-width: 800px) {
+ h1 {
+ font-size: 2.25rem;
}
- 100% {
- background-position: 0% 50%;
+ p {
+ font-size: 1.125rem;
}
}
+`;
- background: linear-gradient(-45deg, #ffb07c, #ff3e88, #2969ff, #ef3cff, #ff3c87);
- background-size: 300% 300%;
- animation: welcome-fade-in .5s ease, backgroundGradient 30s ease infinite;
- border-radius: 10px;
- position: relative;
- overflow: hidden;
+const ButtonGroup = styled.div`
display: flex;
- align-items: center;
- justify-content: center;
- flex-direction: column;
- padding: 20px;
- filter: drop-shadow(0 4px 3px rgba(0,0,0,.07)) drop-shadow(0 2px 2px rgba(0,0,0,.06));
+ flex-wrap: wrap; /* Allows buttons to wrap onto the next line */
+ gap: 12px; /* Space between buttons */
+ justify-content: center; /* Center buttons on mobile */
- & img {
- animation-duration: 5s;
- animation-iteration-count: infinite;
- animation-timing-function: ease-in-out;
- width: 100px;
- height: 100px;
- top: 0;
- right: 0;
- &:nth-child(1) {animation-delay: 0s;}
- &:nth-child(2) {animation-delay: 1s;}
+ @media (min-width: 800px) {
+ flex-direction: column;
+ justify-content: flex-start;
}
+`;
- & > div {
- padding: 0px;
- filter: drop-shadow(0 4px 3px rgba(0,0,0,.07)) drop-shadow(0 2px 2px rgba(0,0,0,.06));
+const ActionButton = styled.button`
+ /* Base styles */
+ border: none;
+ border-radius: 10px;
+ padding: 12px 20px;
+ font-size: 0.9rem;
+ font-weight: 600;
+ background: #ffffffdf;
+ color: black;
+ cursor: pointer;
+ transition: background-color 0.2s ease, transform 0.2s ease;
+ flex-grow: 1; /* Allows buttons to share space on mobile */
+ text-align: center;
+
+ &:hover {
+ background: white;
+ transform: translateY(-2px); /* Subtle hover effect */
}
+ /* On desktop, buttons take full width of their container */
@media (min-width: 800px) {
- display: grid;
- grid-template-columns: 2fr 1fr;
- padding: 0;
- & > div {
- padding: 40px;
- }
+ width: 100%;
+ flex-grow: 0; /* Reset flex-grow */
}
-`
+`;
export function WelcomeBanner() {
- const wallet = useWallet()
- const walletModal = useWalletModal()
- const store = useUserStore()
- const copyInvite = () => {
- store.set({ userModal: true })
+ const wallet = useWallet();
+ const walletModal = useWalletModal();
+ const { set: setUserModal } = useUserStore(); // Destructure for cleaner access
+
+ const handleCopyInvite = () => {
+ setUserModal({ userModal: true });
if (!wallet.connected) {
- walletModal.setVisible(true)
+ walletModal.setVisible(true);
}
- }
+ };
+
+ const openLink = (url) => () => window.open(url, '_blank', 'noopener,noreferrer');
return (
-
-
+
+
Welcome to Gamba v2 👋
-
- A fair, simple and decentralized casino on Solana.
-
-
-
-
-
-
-
-
- )
-}
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/apps/platform/src/sections/Game/Game.styles.ts b/apps/platform/src/sections/Game/Game.styles.ts
index e9ed77ce..bae4b8e4 100644
--- a/apps/platform/src/sections/Game/Game.styles.ts
+++ b/apps/platform/src/sections/Game/Game.styles.ts
@@ -1,15 +1,10 @@
+// src/sections/Game/Game.styles.ts
import styled, { css, keyframes } from 'styled-components'
const splashAnimation = keyframes`
- 0% {
- opacity: 1;
- }
- 30%, 75% {
- opacity: 1;
- }
- 100% {
- opacity: 0;
- }
+ 0% { opacity: 1; }
+ 30%, 75% { opacity: 1; }
+ 100% { opacity: 0; }
`
export const loadingAnimation = keyframes`
@@ -32,19 +27,15 @@ export const SettingControls = styled.div`
transition: opacity .2s;
padding: 5px;
text-shadow: 0 0 1px #00000066;
- &:hover {
- opacity: 1;
- }
+ &:hover { opacity: 1; }
}
`
export const Splash = styled.div`
pointer-events: none;
position: absolute;
- left: 0;
- top: 0;
- width: 100%;
- height: 100%;
+ left: 0; top: 0;
+ width: 100%; height: 100%;
opacity: 0;
animation: ${splashAnimation} .75s ease;
display: flex;
@@ -64,28 +55,21 @@ export const Screen = styled.div`
overflow: hidden;
transition: height .2s ease;
height: 600px;
- @media (max-width: 700px) {
- height: 600px;
- }
+ @media (max-width: 700px) { height: 600px; }
`
export const IconButton = styled.button`
background: none;
border: none;
- padding: 0;
- width: 50px;
- padding: 10px;
- justify-content: center;
- align-items: center;
+ padding: 0 10px;
display: flex;
- margin: 0;
+ align-items: center;
+ justify-content: center;
cursor: pointer;
font-size: 16px;
border-radius: 10px;
color: white;
- &:hover {
- background: #ffffff22;
- }
+ &:hover { background: #ffffff22; }
`
export const StyledLoadingIndicator = styled.div<{$active: boolean}>`
@@ -97,15 +81,12 @@ export const StyledLoadingIndicator = styled.div<{$active: boolean}>`
&:after {
content: " ";
position: absolute;
- width: 25%;
- height: 100%;
+ width: 25%; height: 100%;
animation: ${loadingAnimation} ease infinite .5s;
opacity: 0;
background: #9564ff;
transition: opacity .5s;
- ${(props) => props.$active && css`
- opacity: 1;
- `}
+ ${(props) => props.$active && css`opacity: 1;`}
}
`
@@ -117,39 +98,35 @@ export const Controls = styled.div`
border-radius: 10px;
z-index: 6;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ gap: 20px;
+
@media (max-width: 800px) {
- padding: 10px;
- display: flex;
flex-direction: column;
gap: 10px;
+ padding: 10px;
}
@media (min-width: 800px) {
- display: flex;
- gap: 20px;
- align-items: center;
height: 80px;
}
`
export const MetaControls = styled.div`
position: absolute;
- bottom: 0;
- left: 0;
+ bottom: 0; left: 0;
width: 100%;
padding: 10px;
display: flex;
- justify-content: left;
- align-items: left;
+ align-items: center;
+ gap: 10px;
z-index: 6;
`
export const spinnerAnimation = keyframes`
- from {
- transform: rotate(0deg);
- }
- to {
- transform: rotate(360deg);
- }
+ from { transform: rotate(0deg); }
+ to { transform: rotate(360deg); }
`
export const Spinner = styled.div<{$small?: boolean}>`
@@ -158,13 +135,12 @@ export const Spinner = styled.div<{$small?: boolean}>`
--color: white;
animation: ${spinnerAnimation} 1s ease infinite;
transform: translateZ(0);
-
border-top: var(--spinner-border) solid var(--color);
border-right: var(--spinner-border) solid var(--color);
border-bottom: var(--spinner-border) solid var(--color);
border-left: var(--spinner-border) solid transparent;
background: transparent;
height: var(--spinner-size);
- aspect-ratio: 1 / 1;
+ aspect-ratio: 1/1;
border-radius: 50%;
`
diff --git a/apps/platform/src/sections/Game/Game.tsx b/apps/platform/src/sections/Game/Game.tsx
index 34b57569..8a265825 100644
--- a/apps/platform/src/sections/Game/Game.tsx
+++ b/apps/platform/src/sections/Game/Game.tsx
@@ -1,6 +1,9 @@
-import { GambaUi, useSoundStore } from 'gamba-react-ui-v2'
+// src/sections/Game/Game.tsx
import React from 'react'
import { useParams } from 'react-router-dom'
+import { GambaUi, useSoundStore } from 'gamba-react-ui-v2'
+import { useTransactionError } from 'gamba-react-v2'
+
import { Icon } from '../../components/Icon'
import { Modal } from '../../components/Modal'
import { GAMES } from '../../games'
@@ -13,111 +16,82 @@ import { TransactionModal } from './TransactionModal'
function CustomError() {
return (
- <>
-
-
- 😭 Oh no!
- Something went wrong
-
-
- >
+
+
+ 😭 Oh no!
+ Something went wrong
+
+
)
}
-/**
- * A renderer component to display the contents of the loaded GambaUi.Game
- * Screen
- * Controls
- */
function CustomRenderer() {
const { game } = GambaUi.useGame()
const [info, setInfo] = React.useState(false)
const [provablyFair, setProvablyFair] = React.useState(false)
const soundStore = useSoundStore()
- const firstTimePlaying = useUserStore((state) => !state.gamesPlayed.includes(game.id))
- const markGameAsPlayed = useUserStore((state) => () => state.markGameAsPlayed(game.id, true))
+ const firstTimePlaying = useUserStore(s => !s.gamesPlayed.includes(game.id))
+ const markGameAsPlayed = useUserStore(s => () => s.markGameAsPlayed(game.id, true))
const [ready, setReady] = React.useState(false)
const [txModal, setTxModal] = React.useState(false)
- // const loading = useLoadingState()
+ const loading = useLoadingState()
- React.useEffect(
- () => {
- const timeout = setTimeout(() => {
- setReady(true)
- }, 750)
- return () => clearTimeout(timeout)
- },
- [],
- )
+ React.useEffect(() => {
+ const t = setTimeout(() => setReady(true), 750)
+ return () => clearTimeout(t)
+ }, [])
- React.useEffect(
- () => {
- const timeout = setTimeout(() => {
- setInfo(firstTimePlaying)
- }, 1000)
- return () => clearTimeout(timeout)
- },
- [firstTimePlaying],
- )
+ React.useEffect(() => {
+ const t = setTimeout(() => setInfo(firstTimePlaying), 1000)
+ return () => clearTimeout(t)
+ }, [firstTimePlaying])
const closeInfo = () => {
markGameAsPlayed()
setInfo(false)
}
+ // global transaction errors
+ useTransactionError(err => {
+ if (err.message === 'NOT_CONNECTED') return
+ // you might want to show a toast here
+ })
+
return (
<>
{info && (
-
+
{game.meta.description}
-
- Play
-
+ Play
)}
- {provablyFair && (
- setProvablyFair(false)} />
- )}
- {txModal && (
- setTxModal(false)} />
- )}
+ {provablyFair && setProvablyFair(false)} />}
+ {txModal && setTxModal(false)} />}
+
-
-
-
+
{ready && }
+
- {/*
- setTxModal(true)}>
- {loading === -1 ? (
-
- ) : (
-
- )}
-
-
*/}
- setInfo(true)}>
-
-
- setProvablyFair(true)}>
-
-
+ setInfo(true)}>
+ setProvablyFair(true)}>
soundStore.set(soundStore.volume ? 0 : .5)}>
{soundStore.volume ? : }
+
+
+ {/* ← No inner wrapper—controls & play buttons are centered by Controls */}
-
-
-
-
+
+
>
@@ -126,16 +100,12 @@ function CustomRenderer() {
export default function Game() {
const { gameId } = useParams()
- const game = GAMES.find((x) => x.id === gameId)
+ const game = GAMES.find(g => g.id === gameId)
return (
<>
{game ? (
- }
- children={}
- />
+ } children={} />
) : (
Game not found! 👎
)}
diff --git a/apps/platform/src/sections/Game/LoadingBar.tsx b/apps/platform/src/sections/Game/LoadingBar.tsx
index dba1260b..cf0f9a98 100644
--- a/apps/platform/src/sections/Game/LoadingBar.tsx
+++ b/apps/platform/src/sections/Game/LoadingBar.tsx
@@ -1,95 +1,88 @@
import { decodeGame, getGameAddress } from 'gamba-core-v2'
import { useAccount, useTransactionStore, useWalletAddress } from 'gamba-react-v2'
-import React from 'react'
+import React, { useMemo } from 'react'
import styled, { css, keyframes } from 'styled-components'
-const StyledLoadingThingy = styled.div`
- position: relative;
+const Container = styled.div`
display: flex;
width: 100%;
gap: 5px;
`
-export const loadingAnimation = keyframes`
- 0%, 100% { opacity: .6 }
- 50% { opacity: .8 }
+const pulse = keyframes`
+ 0%, 100% { opacity: 0.6 }
+ 50% { opacity: 0.8 }
`
-const StyledLoadingBar = styled.div<{$state: 'finished' | 'loading' | 'none'}>`
- position: relative;
- width: 100%;
- border-radius: 10px;
+const Bar = styled.div<{$state: 'none' | 'loading' | 'finished'}>`
flex-grow: 1;
- background: var(--gamba-ui-primary-color);
- color: black;
- padding: 0 10px;
- font-size: 12px;
height: 6px;
- font-weight: bold;
- opacity: .2;
- ${(props) => props.$state === 'loading' && css`
- animation: ${loadingAnimation} ease infinite 1s;
- `}
- ${(props) => props.$state === 'finished' && css`
- opacity: .8;
- `}
- &:after {
- content: " ";
- position: absolute;
- width: 25%;
- height: 100%;
- transition: opacity .5s;
- }
+ border-radius: 10px;
+ background: var(--gamba-ui-primary-color);
+ opacity: 0.2;
+
+ ${({ $state }) =>
+ $state === 'loading' &&
+ css`
+ animation: ${pulse} 1s ease infinite;
+ `}
+
+ ${({ $state }) =>
+ $state === 'finished' &&
+ css`
+ opacity: 0.8;
+ `}
`
-const steps = [
- 'Signing',
- 'Sending',
- 'Settling',
-]
-
-export function useLoadingState() {
- const userAddress = useWalletAddress()
- const game = useAccount(getGameAddress(userAddress), decodeGame)
- const txStore = useTransactionStore()
- const step = (
- () => {
- if (txStore.label !== 'play') {
- return -1
- }
- if (game?.status.resultRequested) {
- return 2
- }
- if (txStore.state === 'processing' || txStore.state === 'sending') {
- return 1
- }
- if (txStore.state === 'simulating' || txStore.state === 'signing') {
- return 0
- }
- return -1
- }
- )()
-
- return step
+const steps = ['Signing', 'Sending', 'Settling'] as const
+
+export function useLoadingState(): Array<'none' | 'loading' | 'finished'> {
+ const user = useWalletAddress()
+ const tx = useTransactionStore()
+ const game = useAccount(getGameAddress(user), decodeGame)
+
+ const status = useMemo(
+ () => (game?.status ? Object.keys(game.status)[0] : null),
+ [game?.status]
+ )
+
+ const states: Array<'none' | 'loading' | 'finished'> = ['none', 'none', 'none']
+
+ if (tx.label !== 'play') return states
+
+ if (tx.state === 'simulating' || tx.state === 'signing') {
+ states[0] = 'loading'
+ return states
+ }
+
+ if (tx.state === 'processing' || tx.state === 'sending') {
+ states[0] = 'finished'
+ states[1] = 'loading'
+ return states
+ }
+
+ if (tx.state === 'confirming' || status === 'ResultRequested') {
+ states[0] = 'finished'
+ states[1] = 'finished'
+ states[2] = 'loading'
+ return states
+ }
+
+ if (status === 'Ready') {
+ return states
+ }
+
+ return states
}
export function LoadingBar() {
- const step = useLoadingState()
+ const states = useLoadingState()
return (
-
-
-
- {steps
- .map((__, i) => (
- i ? 'finished' : 'none'}
- />
- ),
- )}
-
-
-
+
+ {states.map((state, i) => (
+
+ ))}
+
)
}
diff --git a/apps/platform/src/sections/RecentPlays/RecentPlays.tsx b/apps/platform/src/sections/RecentPlays/RecentPlays.tsx
index 70d1a553..9503e7ed 100644
--- a/apps/platform/src/sections/RecentPlays/RecentPlays.tsx
+++ b/apps/platform/src/sections/RecentPlays/RecentPlays.tsx
@@ -1,30 +1,27 @@
+// apps/platform/src/sections/RecentPlays/RecentPlays.tsx
+import React from 'react'
import { BPS_PER_WHOLE, GambaTransaction } from 'gamba-core-v2'
import { GambaUi, TokenValue, useTokenMeta } from 'gamba-react-ui-v2'
-import React from 'react'
-import { EXPLORER_URL, PLATFORM_CREATOR_ADDRESS } from '../../constants'
import { useMediaQuery } from '../../hooks/useMediaQuery'
import { extractMetadata } from '../../utils'
+import { EXPLORER_URL, PLATFORM_CREATOR_ADDRESS } from '../../constants'
import { Container, Jackpot, Profit, Recent, Skeleton } from './RecentPlays.styles'
import { ShareModal } from './ShareModal'
import { useRecentPlays } from './useRecentPlays'
-function TimeDiff({ time, suffix = 'ago' }: {time: number, suffix?: string}) {
- const diff = (Date.now() - time)
+function TimeDiff({ time, suffix = 'ago' }: { time: number; suffix?: string }) {
+ const diff = Date.now() - time
return React.useMemo(() => {
- const seconds = Math.floor(diff / 1000)
- const minutes = Math.floor(seconds / 60)
- const hours = Math.floor(minutes / 60)
- if (hours >= 1) {
- return hours + 'h ' + suffix
- }
- if (minutes >= 1) {
- return minutes + 'm ' + suffix
- }
+ const sec = Math.floor(diff / 1000)
+ const min = Math.floor(sec / 60)
+ const hrs = Math.floor(min / 60)
+ if (hrs >= 1) return `${hrs}h ${suffix}`
+ if (min >= 1) return `${min}m ${suffix}`
return 'Just now'
- }, [diff])
+ }, [diff, suffix])
}
-function RecentPlay({ event }: {event: GambaTransaction<'GameSettled'>}) {
+function RecentPlay({ event }: { event: GambaTransaction<'GameSettled'> }) {
const data = event.data
const token = useTokenMeta(data.tokenMint)
const md = useMediaQuery('md')
@@ -40,28 +37,18 @@ function RecentPlay({ event }: {event: GambaTransaction<'GameSettled'>}) {
<>
- {data.user.toBase58().substring(0, 4)}...
+ {data.user.toBase58().slice(0, 4)}…
{md && (profit >= 0 ? ' won ' : ' lost ')}
0}>
- {/* {(token.usdPrice * profit / (10 ** token.decimals)).toLocaleString()} USD */}
-
- {md && (
- <>
- {profit > 0 && (
-
- ({multiplier.toFixed(2)}x)
-
- )}
- {data.jackpotPayoutToUser.toNumber() > 0 && (
-
- +
-
- )}
- >
+ {md && profit > 0 && ({multiplier.toFixed(2)}x)
}
+ {md && data.jackpotPayoutToUser.toNumber() > 0 && (
+
+ +
+
)}
>
)
@@ -77,20 +64,21 @@ export default function RecentPlays() {
{selectedGame && (
setSelectedGame(undefined)} />
)}
- {!events.length && Array.from({ length: 10 }).map((_, i) => (
-
+ {!events.length && Array.from({ length: 10 }).map((_, i) => )}
+ {events.map((tx) => (
+ setSelectedGame(tx)}>
+
+
+
+
+
))}
- {events.map(
- (tx) => (
- setSelectedGame(tx)}>
-
-
-
-
-
- ),
- )}
- window.open(`${EXPLORER_URL}/platform/${PLATFORM_CREATOR_ADDRESS.toString()}`)}>
+
+ window.open(`${EXPLORER_URL}/platform/${PLATFORM_CREATOR_ADDRESS.toString()}`)
+ }
+ >
🚀 Explorer
diff --git a/apps/platform/src/sections/RecentPlays/ShareModal.tsx b/apps/platform/src/sections/RecentPlays/ShareModal.tsx
index 2554942f..440db3c8 100644
--- a/apps/platform/src/sections/RecentPlays/ShareModal.tsx
+++ b/apps/platform/src/sections/RecentPlays/ShareModal.tsx
@@ -76,4 +76,4 @@ export function ShareModal({ event, onClose }: {event: GambaTransaction<'GameSet
)
-}
+}
\ No newline at end of file
diff --git a/apps/platform/src/sections/RecentPlays/useRecentPlays.ts b/apps/platform/src/sections/RecentPlays/useRecentPlays.ts
index a75ade1f..31b7ad17 100644
--- a/apps/platform/src/sections/RecentPlays/useRecentPlays.ts
+++ b/apps/platform/src/sections/RecentPlays/useRecentPlays.ts
@@ -1,5 +1,11 @@
+// apps/platform/src/sections/RecentPlays/useRecentPlays.ts
+
import { GambaTransaction } from 'gamba-core-v2'
-import { useGambaEventListener, useGambaEvents, useWalletAddress } from 'gamba-react-v2'
+import {
+ useWalletAddress,
+ useGambaEvents,
+ useGambaEventListener,
+} from 'gamba-react-v2'
import React from 'react'
import { useLocation } from 'react-router-dom'
import { PLATFORM_CREATOR_ADDRESS } from '../../constants'
@@ -10,40 +16,71 @@ interface Params {
export function useRecentPlays(params: Params = {}) {
const { showAllPlatforms = false } = params
- const location = useLocation()
+ const location = useLocation()
const userAddress = useWalletAddress()
- // Fetch previous events
- const previousEvents = useGambaEvents(
+ // 1) Historical events via lightweight fetchRecentLogs under the hood
+ const previousEvents = useGambaEvents<'GameSettled'>(
'GameSettled',
- { address: !showAllPlatforms ? PLATFORM_CREATOR_ADDRESS : undefined },
+ {
+ address: !showAllPlatforms
+ ? PLATFORM_CREATOR_ADDRESS
+ : undefined,
+ signatureLimit: 30,
+ },
)
- const [newEvents, setEvents] = React.useState[]>([])
+ // 2) State for live events
+ const [liveEvents, setLiveEvents] = React.useState<
+ GambaTransaction<'GameSettled'>[]
+ >([])
- // Listen for new events
- useGambaEventListener(
+ // 3) Refs for up-to-date filter values
+ const showAllRef = React.useRef(showAllPlatforms)
+ const userRef = React.useRef(userAddress)
+ const pathRef = React.useRef(location.pathname)
+ React.useEffect(() => {
+ showAllRef.current = showAllPlatforms
+ }, [showAllPlatforms])
+ React.useEffect(() => {
+ userRef.current = userAddress
+ }, [userAddress])
+ React.useEffect(() => {
+ pathRef.current = location.pathname
+ }, [location.pathname])
+
+ // 4) Live subscription via the single‐argument callback signature
+ useGambaEventListener<'GameSettled'>(
'GameSettled',
- (event) => {
- // Ignore events that occured on another platform
- if (!showAllPlatforms && !event.data.creator.equals(PLATFORM_CREATOR_ADDRESS)) return
- // Set a delay on games with suspenseful reveal
- const delay = event.data.user.equals(userAddress) && ['plinko', 'slots'].some((x) => location.pathname.includes(x)) ? 3000 : 1
- setTimeout(
- () => {
- setEvents((events) => [event, ...events])
- },
- delay,
+ (evt) => {
+ const { data, signature } = evt
+
+ // Platform filter
+ if (
+ !showAllRef.current &&
+ !data.creator.equals(PLATFORM_CREATOR_ADDRESS)
+ ) {
+ return
+ }
+
+ // Optional suspense delay for user’s own plays
+ const isUserGame = data.user.equals(userRef.current)
+ const inSuspense = ['plinko', 'slots'].some((p) =>
+ pathRef.current.includes(p),
)
+ const delay = isUserGame && inSuspense ? 3000 : 1
+
+ setTimeout(() => {
+ setLiveEvents((all) => [evt, ...all])
+ }, delay)
},
- [location.pathname, userAddress, showAllPlatforms],
+ // re-subscribe whenever these change
+ [showAllPlatforms, userAddress, location.pathname],
)
- // Merge previous & new events
+ // 5) Merge & return
return React.useMemo(
- () => {
- return [...newEvents, ...previousEvents]
- },
- [newEvents, previousEvents],
+ () => [...liveEvents, ...previousEvents],
+ [liveEvents, previousEvents],
)
}
diff --git a/apps/platform/src/sections/TokenSelect.tsx b/apps/platform/src/sections/TokenSelect.tsx
index 14a3cc3d..46e3ad98 100644
--- a/apps/platform/src/sections/TokenSelect.tsx
+++ b/apps/platform/src/sections/TokenSelect.tsx
@@ -58,6 +58,8 @@ function TokenSelectItem({ mint }: {mint: PublicKey}) {
export default function TokenSelect() {
const [visible, setVisible] = React.useState(false)
const [warning, setWarning] = React.useState(false)
+ // Allow real plays override via query param/localStorage for deployed testing
+ const [allowRealPlays, setAllowRealPlays] = React.useState(false)
const context = React.useContext(GambaPlatformContext)
const selectedToken = useCurrentToken()
const userStore = useUserStore()
@@ -70,13 +72,25 @@ export default function TokenSelect() {
}
}, [])
+ // Read real-play override – enables SOL selection on deployed builds when needed
+ useEffect(() => {
+ try {
+ const params = new URLSearchParams(window.location.search)
+ const q = params.get('allowReal') || params.get('real') || params.get('realplays')
+ if (q != null) {
+ const v = q === '1' || q === 'true'
+ localStorage.setItem('allowRealPlays', v ? '1' : '0')
+ }
+ const saved = localStorage.getItem('allowRealPlays')
+ setAllowRealPlays(saved === '1')
+ } catch {}
+ }, [])
+
const selectPool = (pool: PoolToken) => {
setVisible(false)
// Check if platform has real plays disabled
- if (
- import.meta.env.VITE_REAL_PLAYS_DISABLED &&
- !pool.token.equals(FAKE_TOKEN_MINT)
- ) {
+ const realDisabled = Boolean(import.meta.env.VITE_REAL_PLAYS_DISABLED) && !allowRealPlays
+ if (realDisabled && !pool.token.equals(FAKE_TOKEN_MINT)) {
setWarning(true)
return
}
@@ -120,7 +134,8 @@ export default function TokenSelect() {
)}
- {POOLS.map((pool, i) => (
+ {/* Mount balances for list items only when dropdown is visible to avoid unnecessary watchers */}
+ {visible && POOLS.map((pool, i) => (
selectPool(pool)} key={i}>
diff --git a/apps/platform/vite.config.ts b/apps/platform/vite.config.ts
index 25da10bb..8dc8c87f 100644
--- a/apps/platform/vite.config.ts
+++ b/apps/platform/vite.config.ts
@@ -1,15 +1,25 @@
-import react from '@vitejs/plugin-react'
+// vite.config.ts
import { defineConfig } from 'vite'
+import react from '@vitejs/plugin-react'
+import path from 'path'
-const ENV_PREFIX = ['VITE_']
-
-export default defineConfig(() => ({
- envPrefix: ENV_PREFIX,
+export default defineConfig({
server: { port: 4001, host: false },
+ envPrefix: ['VITE_'],
assetsInclude: ['**/*.glb'],
define: { 'process.env.ANCHOR_BROWSER': true },
- resolve: { alias: { crypto: 'crypto-browserify' } },
+ resolve: {
+ alias: {
+ // === POINT TO THE PACKAGE FOLDERS, NOT entry files ===
+ react: path.resolve(__dirname, 'node_modules/react'),
+ 'react-dom': path.resolve(__dirname, 'node_modules/react-dom'),
+
+ // your other alias stays the same
+ crypto: 'crypto-browserify',
+ },
+ dedupe: ['react', 'react-dom']
+ },
plugins: [
react({ jsxRuntime: 'classic' }),
],
-}))
+})
diff --git a/apps/website/next.config.js b/apps/website/next.config.js
index 7a721617..28e3cdcd 100644
--- a/apps/website/next.config.js
+++ b/apps/website/next.config.js
@@ -1,8 +1,9 @@
-const withNextra = require('nextra')({
+const nextraPkg = require('nextra');
+const withNextra = (nextraPkg.default || nextraPkg)({
theme: 'nextra-theme-docs',
themeConfig: './theme.config.tsx',
defaultShowCopyCode: true,
-})
+});
-module.exports = withNextra()
+module.exports = withNextra();
diff --git a/apps/website/package.json b/apps/website/package.json
index 8851d744..834b5e13 100644
--- a/apps/website/package.json
+++ b/apps/website/package.json
@@ -23,8 +23,8 @@
"bright": "^0.8.2",
"clsx": "^2.1.1",
"next": "^13.0.6",
- "nextra": "latest",
- "nextra-theme-docs": "latest",
+ "nextra": "^3.3.1",
+ "nextra-theme-docs": "^3.3.1",
"react": "^18.3.1",
"react-code-blocks": "0.0.9-0",
"react-dom": "^18.3.1",
@@ -35,6 +35,6 @@
},
"devDependencies": {
"@types/node": "18.11.10",
- "@types/react": "18.2.21"
+ "@types/react": "^18.2.13"
}
}
diff --git a/apps/website/pages/_app.mdx b/apps/website/pages/_app.tsx
similarity index 100%
rename from apps/website/pages/_app.mdx
rename to apps/website/pages/_app.tsx
diff --git a/apps/website/pages/_meta.json b/apps/website/pages/_meta.json
deleted file mode 100644
index a2cedcdc..00000000
--- a/apps/website/pages/_meta.json
+++ /dev/null
@@ -1,22 +0,0 @@
-{
- "games": {
- "type": "page",
- "title": "Built on Gamba",
- "display": "hidden",
- "theme": {
- "layout": "raw"
- }
- },
- "index": {
- "type": "page",
- "title": "Gamba",
- "display": "hidden",
- "theme": {
- "layout": "raw"
- }
- },
- "docs": {
- "type": "page",
- "title": "Documentation"
- }
-}
diff --git a/apps/website/pages/_meta.ts b/apps/website/pages/_meta.ts
new file mode 100644
index 00000000..e8938e7f
--- /dev/null
+++ b/apps/website/pages/_meta.ts
@@ -0,0 +1,23 @@
+// apps/website/pages/_meta.ts
+export default {
+ games: {
+ type: 'page',
+ title: 'Built on Gamba',
+ display: 'hidden',
+ theme: {
+ layout: 'raw'
+ }
+ },
+ index: {
+ type: 'page',
+ title: 'Gamba',
+ display: 'hidden',
+ theme: {
+ layout: 'raw'
+ }
+ },
+ docs: {
+ type: 'page',
+ title: 'Documentation'
+ }
+}
diff --git a/apps/website/pages/docs/_meta.json b/apps/website/pages/docs/_meta.json
deleted file mode 100644
index 4577d420..00000000
--- a/apps/website/pages/docs/_meta.json
+++ /dev/null
@@ -1,26 +0,0 @@
-{
- "index": "👋 Welcome",
- "-- Introduction": {
- "type": "separator",
- "title": "Introduction"
- },
- "apps": "🎰 Apps",
- "pools": "🏦 Pools",
- "games": "🎲 Games",
- "explorer": "🔎 Explorer",
- "examples": "👨💻 Examples",
- "dao": "🏛️ DAO (Soon)",
- "-- Development": {
- "type": "separator",
- "title": "Development"
- },
- "get-started": "🚀 Get Started",
- "packages": "📦 Packages",
- "-- SDK Docs": {
- "type": "separator",
- "title": "SDK Docs"
- },
- "gamba-core-v2": "🔧 gamba-core-v2",
- "gamba-react-v2": "🧩 gamba-react-v2",
- "gamba-react-ui-v2": "🎨 gamba-react-ui-v2"
-}
diff --git a/apps/website/pages/docs/_meta.ts b/apps/website/pages/docs/_meta.ts
new file mode 100644
index 00000000..b5012c8b
--- /dev/null
+++ b/apps/website/pages/docs/_meta.ts
@@ -0,0 +1,26 @@
+export default {
+ index: '👋 Welcome',
+ '-- Introduction': {
+ type: 'separator',
+ title: 'Introduction'
+ },
+ apps: '🎰 Apps',
+ pools: '🏦 Pools',
+ games: '🎲 Games',
+ multiplayer: '🤝 Multiplayer',
+ explorer: '🔎 Explorer',
+ examples: '👨💻 Examples',
+ '-- Development': {
+ type: 'separator',
+ title: 'Development'
+ },
+ 'get-started': '🚀 Get Started',
+ packages: '📦 Packages',
+ '-- SDK Docs': {
+ type: 'separator',
+ title: 'SDK Docs'
+ },
+ 'gamba-core-v2': '🔧 gamba-core-v2',
+ 'gamba-react-v2': '🧩 gamba-react-v2',
+ 'gamba-react-ui-v2': '🎨 gamba-react-ui-v2'
+}
\ No newline at end of file
diff --git a/apps/website/pages/docs/apps.mdx b/apps/website/pages/docs/apps.mdx
index 567fddaa..b9c506f2 100644
--- a/apps/website/pages/docs/apps.mdx
+++ b/apps/website/pages/docs/apps.mdx
@@ -1,4 +1,4 @@
-import { Card, Cards } from "nextra-theme-docs";
+import { Cards } from "nextra/components";
# 🎰 Apps
@@ -15,5 +15,5 @@ Explore our list of [Apps](https://explorer.gamba.so/platforms) or discover how
If you're interested in hosting your own App, visit our templates page to kickstart your journey.
-
+
diff --git a/apps/website/pages/docs/examples.mdx b/apps/website/pages/docs/examples.mdx
index f8ea34b3..234c65e2 100644
--- a/apps/website/pages/docs/examples.mdx
+++ b/apps/website/pages/docs/examples.mdx
@@ -1,4 +1,4 @@
-import { Card, Cards } from "nextra-theme-docs";
+import { Cards } from "nextra/components";
import { projects } from "../../components/projects";
# 👨💻 Examples
@@ -6,71 +6,11 @@ import { projects } from "../../components/projects";
Projects built on Gamba
- {projects.map(({ name, link, thumbnail }) => (
-
- {
- e.target.style.backgroundColor = "rgba(0, 0, 0, 0)";
- }}
- onMouseLeave={(e) => {
- e.target.style.backgroundColor = "rgba(0, 0, 0, 0.5)";
- }}
- />
-
-
+ {projects.map(({ name, link }) => (
+
))}
-
-
+
+
+
diff --git a/apps/website/pages/docs/explorer.mdx b/apps/website/pages/docs/explorer.mdx
index f876d88a..bdea78b0 100644
--- a/apps/website/pages/docs/explorer.mdx
+++ b/apps/website/pages/docs/explorer.mdx
@@ -1,5 +1,4 @@
-import { Card, Cards } from "nextra-theme-docs";
-import { Tabs } from "nextra/components";
+import { Tabs, Cards } from "nextra/components";
# 🔎 Explorer
@@ -10,9 +9,9 @@ The [explorer](https://explorer.gamba.so) is a comprehensive dashboard and manag
- **DAO**: Manage the DAO and distribution of fees
-
-
-
+
+
+
## Transactions
@@ -45,7 +44,7 @@ Provides access to detailed logs of all transaction events for audit and analysi
-
+
## Pools
@@ -57,8 +56,8 @@ The "Pools" section enables users to monitor and manage liquidity pools effectiv
- **Metrics & Data**: View all pools and their stats/activity.
-
-
+
+
## Platforms
@@ -67,5 +66,5 @@ The "Platforms" section enables users to view gamba transactions:
- **Metrics & Data**: View detailed analytics for each gaming platform and their stats/activity.
-
+
diff --git a/apps/website/pages/docs/gamba-core-v2.mdx b/apps/website/pages/docs/gamba-core-v2.mdx
index 0e464c46..dbf5fbd9 100644
--- a/apps/website/pages/docs/gamba-core-v2.mdx
+++ b/apps/website/pages/docs/gamba-core-v2.mdx
@@ -1,5 +1,5 @@
-import { Callout } from "nextra-theme-docs";
+import { Callout } from "nextra/components";
# 🔧 `gamba-core-v2`
diff --git a/apps/website/pages/docs/gamba-core-v2/_meta.json b/apps/website/pages/docs/gamba-core-v2/_meta.json
deleted file mode 100644
index ef0bbfee..00000000
--- a/apps/website/pages/docs/gamba-core-v2/_meta.json
+++ /dev/null
@@ -1,35 +0,0 @@
-{
- "gamba-providers": {
- "title": "🛠️ GambaProviders",
- "href": "/docs/gamba-core-v2#gambaproviders"
- },
- "transaction-utilities": {
- "title": "🔄 Transaction Utilities",
- "href": "/docs/gamba-core-v2#transactionutilities"
- },
- "decoding-helpers": {
- "title": "🧩 Decoding Helpers",
- "href": "/docs/gamba-core-v2#decodinghelpers"
- },
- "game-logic": {
- "title": "🎮 Game Logic",
- "href": "/docs/gamba-core-v2#gamelogic"
- },
- "constants": {
- "title": "🔑 Constants",
- "href": "/docs/gamba-core-v2#constants"
- },
- "seed-address-generators": {
- "title": "🗺️ Seed Address Generators",
- "href": "/docs/gamba-core-v2#seedaddressgenerators"
- },
- "utilities": {
- "title": "📊 Utilities",
- "href": "/docs/gamba-core-v2#utilities"
- },
- "types": {
- "title": "📜 Types",
- "href": "/docs/gamba-core-v2#types"
- }
- }
-
\ No newline at end of file
diff --git a/apps/website/pages/docs/gamba-core-v2/_meta.ts b/apps/website/pages/docs/gamba-core-v2/_meta.ts
new file mode 100644
index 00000000..59fe006d
--- /dev/null
+++ b/apps/website/pages/docs/gamba-core-v2/_meta.ts
@@ -0,0 +1,34 @@
+export default {
+ 'gamba-providers': {
+ title: '🛠️ GambaProviders',
+ href: '/docs/gamba-core-v2#gambaproviders'
+ },
+ 'transaction-utilities': {
+ title: '🔄 Transaction Utilities',
+ href: '/docs/gamba-core-v2#transactionutilities'
+ },
+ 'decoding-helpers': {
+ title: '🧩 Decoding Helpers',
+ href: '/docs/gamba-core-v2#decodinghelpers'
+ },
+ 'game-logic': {
+ title: '🎮 Game Logic',
+ href: '/docs/gamba-core-v2#gamelogic'
+ },
+ constants: {
+ title: '🔑 Constants',
+ href: '/docs/gamba-core-v2#constants'
+ },
+ 'seed-address-generators': {
+ title: '🗺️ Seed Address Generators',
+ href: '/docs/gamba-core-v2#seedaddressgenerators'
+ },
+ utilities: {
+ title: '📊 Utilities',
+ href: '/docs/gamba-core-v2#utilities'
+ },
+ types: {
+ title: '📜 Types',
+ href: '/docs/gamba-core-v2#types'
+ }
+}
diff --git a/apps/website/pages/docs/gamba-core-v2/constants/_meta.json b/apps/website/pages/docs/gamba-core-v2/constants/_meta.json
deleted file mode 100644
index ee71054e..00000000
--- a/apps/website/pages/docs/gamba-core-v2/constants/_meta.json
+++ /dev/null
@@ -1,51 +0,0 @@
-{
- "program-id": {
- "title": "PROGRAM_ID",
- "href": "/docs/gamba-core-v2#programid"
- },
- "system-program": {
- "title": "SYSTEM_PROGRAM",
- "href": "/docs/gamba-core-v2#systemprogram"
- },
- "gamba-state-seed": {
- "title": "GAMBA_STATE_SEED",
- "href": "/docs/gamba-core-v2#gambastateseed"
- },
- "game-seed": {
- "title": "GAME_SEED",
- "href": "/docs/gamba-core-v2#gameseed"
- },
- "player-seed": {
- "title": "PLAYER_SEED",
- "href": "/docs/gamba-core-v2#playerseed"
- },
- "pool-seed": {
- "title": "POOL_SEED",
- "href": "/docs/gamba-core-v2#poolseed"
- },
- "pool-ata-seed": {
- "title": "POOL_ATA_SEED",
- "href": "/docs/gamba-core-v2#poolataseed"
- },
- "pool-jackpot-seed": {
- "title": "POOL_JACKPOT_SEED",
- "href": "/docs/gamba-core-v2#pooljackpotseed"
- },
- "pool-bonus-underlying-ta-seed": {
- "title": "POOL_BONUS_UNDERLYING_TA_SEED",
- "href": "/docs/gamba-core-v2#poolbonusunderlyingtaseed"
- },
- "pool-bonus-mint-seed": {
- "title": "POOL_BONUS_MINT_SEED",
- "href": "/docs/gamba-core-v2#poolbonusmintseed"
- },
- "pool-lp-mint-seed": {
- "title": "POOL_LP_MINT_SEED",
- "href": "/docs/gamba-core-v2#poollp-mintseed"
- },
- "bps-per-whole": {
- "title": "BPS_PER_WHOLE",
- "href": "/docs/gamba-core-v2#bpsperwhole"
- }
- }
-
\ No newline at end of file
diff --git a/apps/website/pages/docs/gamba-core-v2/constants/_meta.ts b/apps/website/pages/docs/gamba-core-v2/constants/_meta.ts
new file mode 100644
index 00000000..a0401ee3
--- /dev/null
+++ b/apps/website/pages/docs/gamba-core-v2/constants/_meta.ts
@@ -0,0 +1,50 @@
+export default {
+ 'program-id': {
+ title: 'PROGRAM_ID',
+ href: '/docs/gamba-core-v2#programid'
+ },
+ 'system-program': {
+ title: 'SYSTEM_PROGRAM',
+ href: '/docs/gamba-core-v2#systemprogram'
+ },
+ 'gamba-state-seed': {
+ title: 'GAMBA_STATE_SEED',
+ href: '/docs/gamba-core-v2#gambastateseed'
+ },
+ 'game-seed': {
+ title: 'GAME_SEED',
+ href: '/docs/gamba-core-v2#gameseed'
+ },
+ 'player-seed': {
+ title: 'PLAYER_SEED',
+ href: '/docs/gamba-core-v2#playerseed'
+ },
+ 'pool-seed': {
+ title: 'POOL_SEED',
+ href: '/docs/gamba-core-v2#poolseed'
+ },
+ 'pool-ata-seed': {
+ title: 'POOL_ATA_SEED',
+ href: '/docs/gamba-core-v2#poolataseed'
+ },
+ 'pool-jackpot-seed': {
+ title: 'POOL_JACKPOT_SEED',
+ href: '/docs/gamba-core-v2#pooljackpotseed'
+ },
+ 'pool-bonus-underlying-ta-seed': {
+ title: 'POOL_BONUS_UNDERLYING_TA_SEED',
+ href: '/docs/gamba-core-v2#poolbonusunderlyingtaseed'
+ },
+ 'pool-bonus-mint-seed': {
+ title: 'POOL_BONUS_MINT_SEED',
+ href: '/docs/gamba-core-v2#poolbonusmintseed'
+ },
+ 'pool-lp-mint-seed': {
+ title: 'POOL_LP_MINT_SEED',
+ href: '/docs/gamba-core-v2#poollp-mintseed'
+ },
+ 'bps-per-whole': {
+ title: 'BPS_PER_WHOLE',
+ href: '/docs/gamba-core-v2#bpsperwhole'
+ }
+}
\ No newline at end of file
diff --git a/apps/website/pages/docs/gamba-core-v2/decoding-helpers/_meta.json b/apps/website/pages/docs/gamba-core-v2/decoding-helpers/_meta.json
deleted file mode 100644
index 10760028..00000000
--- a/apps/website/pages/docs/gamba-core-v2/decoding-helpers/_meta.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
- "decode-ata": {
- "title": "decodeAta",
- "href": "/docs/gamba-core-v2#decodeata"
- },
- "decode-player": {
- "title": "decodePlayer",
- "href": "/docs/gamba-core-v2#decodeplayer"
- },
- "decode-game": {
- "title": "decodeGame",
- "href": "/docs/gamba-core-v2#decodegame"
- },
- "decode-pool": {
- "title": "decodePool",
- "href": "/docs/gamba-core-v2#decodepool"
- },
- "decode-gamba-state": {
- "title": "decodeGambaState",
- "href": "/docs/gamba-core-v2#decodegambastate"
- }
- }
-
\ No newline at end of file
diff --git a/apps/website/pages/docs/gamba-core-v2/decoding-helpers/_meta.ts b/apps/website/pages/docs/gamba-core-v2/decoding-helpers/_meta.ts
new file mode 100644
index 00000000..ad8a38fb
--- /dev/null
+++ b/apps/website/pages/docs/gamba-core-v2/decoding-helpers/_meta.ts
@@ -0,0 +1,22 @@
+export default {
+ 'decode-ata': {
+ title: 'decodeAta',
+ href: '/docs/gamba-core-v2#decodeata'
+ },
+ 'decode-player': {
+ title: 'decodePlayer',
+ href: '/docs/gamba-core-v2#decodeplayer'
+ },
+ 'decode-game': {
+ title: 'decodeGame',
+ href: '/docs/gamba-core-v2#decodegame'
+ },
+ 'decode-pool': {
+ title: 'decodePool',
+ href: '/docs/gamba-core-v2#decodepool'
+ },
+ 'decode-gamba-state': {
+ title: 'decodeGambaState',
+ href: '/docs/gamba-core-v2#decodegambastate'
+ }
+}
\ No newline at end of file
diff --git a/apps/website/pages/docs/gamba-core-v2/gamba-providers/_meta.json b/apps/website/pages/docs/gamba-core-v2/gamba-providers/_meta.json
deleted file mode 100644
index 1f096857..00000000
--- a/apps/website/pages/docs/gamba-core-v2/gamba-providers/_meta.json
+++ /dev/null
@@ -1,11 +0,0 @@
-{
- "gamba-provider": {
- "title": "GambaProvider",
- "href": "/docs/gamba-core-v2#gambaprovider"
- },
- "gamba-provider-wallet": {
- "title": "GambaProviderWallet",
- "href": "/docs/gamba-core-v2#gambaproviderwallet"
- }
- }
-
\ No newline at end of file
diff --git a/apps/website/pages/docs/gamba-core-v2/gamba-providers/_meta.ts b/apps/website/pages/docs/gamba-core-v2/gamba-providers/_meta.ts
new file mode 100644
index 00000000..a7bf421b
--- /dev/null
+++ b/apps/website/pages/docs/gamba-core-v2/gamba-providers/_meta.ts
@@ -0,0 +1,10 @@
+export default {
+ 'gamba-provider': {
+ title: 'GambaProvider',
+ href: '/docs/gamba-core-v2#gambaprovider'
+ },
+ 'gamba-provider-wallet': {
+ title: 'GambaProviderWallet',
+ href: '/docs/gamba-core-v2#gambaproviderwallet'
+ }
+}
diff --git a/apps/website/pages/docs/gamba-core-v2/game-logic/_meta.json b/apps/website/pages/docs/gamba-core-v2/game-logic/_meta.json
deleted file mode 100644
index 1c94cb57..00000000
--- a/apps/website/pages/docs/gamba-core-v2/game-logic/_meta.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
- "get-game-hash": {
- "title": "getGameHash",
- "href": "/docs/gamba-core-v2#getgamehash"
- },
- "get-result-number": {
- "title": "getResultNumber",
- "href": "/docs/gamba-core-v2#getresultnumber"
- },
- "parse-result": {
- "title": "parseResult",
- "href": "/docs/gamba-core-v2#parseresult"
- },
- "get-next-result": {
- "title": "getNextResult",
- "href": "/docs/gamba-core-v2#getnextresult"
- },
- "hmac256": {
- "title": "hmac256",
- "href": "/docs/gamba-core-v2#hmac256"
- }
- }
-
\ No newline at end of file
diff --git a/apps/website/pages/docs/gamba-core-v2/game-logic/_meta.ts b/apps/website/pages/docs/gamba-core-v2/game-logic/_meta.ts
new file mode 100644
index 00000000..6a01ba05
--- /dev/null
+++ b/apps/website/pages/docs/gamba-core-v2/game-logic/_meta.ts
@@ -0,0 +1,22 @@
+export default {
+ 'get-game-hash': {
+ title: 'getGameHash',
+ href: '/docs/gamba-core-v2#getgamehash'
+ },
+ 'get-result-number': {
+ title: 'getResultNumber',
+ href: '/docs/gamba-core-v2#getresultnumber'
+ },
+ 'parse-result': {
+ title: 'parseResult',
+ href: '/docs/gamba-core-v2#parseresult'
+ },
+ 'get-next-result': {
+ title: 'getNextResult',
+ href: '/docs/gamba-core-v2#getnextresult'
+ },
+ 'hmac256': {
+ title: 'hmac256',
+ href: '/docs/gamba-core-v2#hmac256'
+ }
+}
diff --git a/apps/website/pages/docs/gamba-core-v2/seed-address-generators/_meta.json b/apps/website/pages/docs/gamba-core-v2/seed-address-generators/_meta.json
deleted file mode 100644
index ae6f1af9..00000000
--- a/apps/website/pages/docs/gamba-core-v2/seed-address-generators/_meta.json
+++ /dev/null
@@ -1,66 +0,0 @@
-{
- "get-pda-address": {
- "title": "getPdaAddress",
- "href": "/docs/gamba-core-v2#getpdaaddress"
- },
- "get-pool-address": {
- "title": "getPoolAddress",
- "href": "/docs/gamba-core-v2#getpooladdress"
- },
- "get-gamba-state-address": {
- "title": "getGambaStateAddress",
- "href": "/docs/gamba-core-v2#getgambastateaddress"
- },
- "get-player-address": {
- "title": "getPlayerAddress",
- "href": "/docs/gamba-core-v2#getplayeraddress"
- },
- "get-game-address": {
- "title": "getGameAddress",
- "href": "/docs/gamba-core-v2#getgameaddress"
- },
- "get-pool-lp-address": {
- "title": "getPoolLpAddress",
- "href": "/docs/gamba-core-v2#getpoollpaddress"
- },
- "get-pool-bonus-address": {
- "title": "getPoolBonusAddress",
- "href": "/docs/gamba-core-v2#getpoolbonusaddress"
- },
- "get-pool-underlying-token-account-address": {
- "title": "getPoolUnderlyingTokenAccountAddress",
- "href": "/docs/gamba-core-v2#getpoolunderlyingtokenaccountaddress"
- },
- "get-pool-jackpot-token-account-address": {
- "title": "getPoolJackpotTokenAccountAddress",
- "href": "/docs/gamba-core-v2#getpooljackpottokenaccountaddress"
- },
- "get-pool-bonus-underlying-token-account-address": {
- "title": "getPoolBonusUnderlyingTokenAccountAddress",
- "href": "/docs/gamba-core-v2#getpoolbonusunderlyingtokenaccountaddress"
- },
- "get-user-underlying-ata": {
- "title": "getUserUnderlyingAta",
- "href": "/docs/gamba-core-v2#getuserunderlyingata"
- },
- "get-player-underlying-ata": {
- "title": "getPlayerUnderlyingAta",
- "href": "/docs/gamba-core-v2#getplayerunderlyingata"
- },
- "get-user-bonus-ata-for-pool": {
- "title": "getUserBonusAtaForPool",
- "href": "/docs/gamba-core-v2#getuserbonusataforpool"
- },
- "get-user-lp-ata-for-pool": {
- "title": "getUserLpAtaForPool",
- "href": "/docs/gamba-core-v2#getuserlpataforpool"
- },
- "get-player-bonus-ata-for-pool": {
- "title": "getPlayerBonusAtaForPool",
- "href": "/docs/gamba-core-v2#getplayerbonusataforpool"
- },
- "get-user-wsol-account": {
- "title": "getUserWsolAccount",
- "href": "/docs/gamba-core-v2#getuserwsolaccount"
- }
- }
\ No newline at end of file
diff --git a/apps/website/pages/docs/gamba-core-v2/seed-address-generators/_meta.ts b/apps/website/pages/docs/gamba-core-v2/seed-address-generators/_meta.ts
new file mode 100644
index 00000000..32e6f5db
--- /dev/null
+++ b/apps/website/pages/docs/gamba-core-v2/seed-address-generators/_meta.ts
@@ -0,0 +1,67 @@
+// apps/website/pages/docs/gamba-core-v2/seed-address-generators/_meta.ts
+export default {
+ 'get-pda-address': {
+ title: 'getPdaAddress',
+ href: '/docs/gamba-core-v2#getpdaaddress'
+ },
+ 'get-pool-address': {
+ title: 'getPoolAddress',
+ href: '/docs/gamba-core-v2#getpooladdress'
+ },
+ 'get-gamba-state-address': {
+ title: 'getGambaStateAddress',
+ href: '/docs/gamba-core-v2#getgambastateaddress'
+ },
+ 'get-player-address': {
+ title: 'getPlayerAddress',
+ href: '/docs/gamba-core-v2#getplayeraddress'
+ },
+ 'get-game-address': {
+ title: 'getGameAddress',
+ href: '/docs/gamba-core-v2#getgameaddress'
+ },
+ 'get-pool-lp-address': {
+ title: 'getPoolLpAddress',
+ href: '/docs/gamba-core-v2#getpoollpaddress'
+ },
+ 'get-pool-bonus-address': {
+ title: 'getPoolBonusAddress',
+ href: '/docs/gamba-core-v2#getpoolbonusaddress'
+ },
+ 'get-pool-underlying-token-account-address': {
+ title: 'getPoolUnderlyingTokenAccountAddress',
+ href: '/docs/gamba-core-v2#getpoolunderlyingtokenaccountaddress'
+ },
+ 'get-pool-jackpot-token-account-address': {
+ title: 'getPoolJackpotTokenAccountAddress',
+ href: '/docs/gamba-core-v2#getpooljackpottokenaccountaddress'
+ },
+ 'get-pool-bonus-underlying-token-account-address': {
+ title: 'getPoolBonusUnderlyingTokenAccountAddress',
+ href: '/docs/gamba-core-v2#getpoolbonusunderlyingtokenaccountaddress'
+ },
+ 'get-user-underlying-ata': {
+ title: 'getUserUnderlyingAta',
+ href: '/docs/gamba-core-v2#getuserunderlyingata'
+ },
+ 'get-player-underlying-ata': {
+ title: 'getPlayerUnderlyingAta',
+ href: '/docs/gamba-core-v2#getplayerunderlyingata'
+ },
+ 'get-user-bonus-ata-for-pool': {
+ title: 'getUserBonusAtaForPool',
+ href: '/docs/gamba-core-v2#getuserbonusataforpool'
+ },
+ 'get-user-lp-ata-for-pool': {
+ title: 'getUserLpAtaForPool',
+ href: '/docs/gamba-core-v2#getuserlpataforpool'
+ },
+ 'get-player-bonus-ata-for-pool': {
+ title: 'getPlayerBonusAtaForPool',
+ href: '/docs/gamba-core-v2#getplayerbonusataforpool'
+ },
+ 'get-user-wsol-account': {
+ title: 'getUserWsolAccount',
+ href: '/docs/gamba-core-v2#getuserwsolaccount'
+ }
+}
diff --git a/apps/website/pages/docs/gamba-core-v2/transaction-utilities/_meta.json b/apps/website/pages/docs/gamba-core-v2/transaction-utilities/_meta.json
deleted file mode 100644
index 66c7788b..00000000
--- a/apps/website/pages/docs/gamba-core-v2/transaction-utilities/_meta.json
+++ /dev/null
@@ -1,19 +0,0 @@
-{
- "parse-transaction-events": {
- "title": "parseTransactionEvents",
- "href": "/docs/gamba-core-v2#parsetransactionevents"
- },
- "parse-gamba-transaction": {
- "title": "parseGambaTransaction",
- "href": "/docs/gamba-core-v2#parsegambatransaction"
- },
- "fetch-gamba-transactions-from-signatures": {
- "title": "fetchGambaTransactionsFromSignatures",
- "href": "/docs/gamba-core-v2#fetchgambatransactionsfromsignatures"
- },
- "fetch-gamba-transactions": {
- "title": "fetchGambaTransactions",
- "href": "/docs/gamba-core-v2#fetchgambatransactions"
- }
- }
-
\ No newline at end of file
diff --git a/apps/website/pages/docs/gamba-core-v2/transaction-utilities/_meta.ts b/apps/website/pages/docs/gamba-core-v2/transaction-utilities/_meta.ts
new file mode 100644
index 00000000..936a750b
--- /dev/null
+++ b/apps/website/pages/docs/gamba-core-v2/transaction-utilities/_meta.ts
@@ -0,0 +1,18 @@
+export default {
+ 'parse-transaction-events': {
+ title: 'parseTransactionEvents',
+ href: '/docs/gamba-core-v2#parsetransactionevents'
+ },
+ 'parse-gamba-transaction': {
+ title: 'parseGambaTransaction',
+ href: '/docs/gamba-core-v2#parsegambatransaction'
+ },
+ 'fetch-gamba-transactions-from-signatures': {
+ title: 'fetchGambaTransactionsFromSignatures',
+ href: '/docs/gamba-core-v2#fetchgambatransactionsfromsignatures'
+ },
+ 'fetch-gamba-transactions': {
+ title: 'fetchGambaTransactions',
+ href: '/docs/gamba-core-v2#fetchgambatransactions'
+ }
+}
\ No newline at end of file
diff --git a/apps/website/pages/docs/gamba-core-v2/types/_meta.json b/apps/website/pages/docs/gamba-core-v2/types/_meta.json
deleted file mode 100644
index eeff9514..00000000
--- a/apps/website/pages/docs/gamba-core-v2/types/_meta.json
+++ /dev/null
@@ -1,43 +0,0 @@
-{
- "gamba": {
- "title": "Gamba",
- "href": "/docs/gamba-core-v2#gamba"
- },
- "gamba-event-type": {
- "title": "GambaEventType",
- "href": "/docs/gamba-core-v2#gambaeventtype"
- },
- "gamba-event": {
- "title": "GambaEvent",
- "href": "/docs/gamba-core-v2#gambaevent"
- },
- "any-gamba-event": {
- "title": "AnyGambaEvent",
- "href": "/docs/gamba-core-v2#anygambaevent"
- },
- "gamba-transaction": {
- "title": "GambaTransaction",
- "href": "/docs/gamba-core-v2#gambatransaction"
- },
- "gamba-state": {
- "title": "GambaState",
- "href": "/docs/gamba-core-v2#gambastate"
- },
- "game-state": {
- "title": "GameState",
- "href": "/docs/gamba-core-v2#gamestate"
- },
- "player-state": {
- "title": "PlayerState",
- "href": "/docs/gamba-core-v2#playerstate"
- },
- "pool-state": {
- "title": "PoolState",
- "href": "/docs/gamba-core-v2#poolstate"
- },
- "game-result": {
- "title": "GameResult",
- "href": "/docs/gamba-core-v2#gameresult"
- }
- }
-
\ No newline at end of file
diff --git a/apps/website/pages/docs/gamba-core-v2/types/_meta.ts b/apps/website/pages/docs/gamba-core-v2/types/_meta.ts
new file mode 100644
index 00000000..19abd82c
--- /dev/null
+++ b/apps/website/pages/docs/gamba-core-v2/types/_meta.ts
@@ -0,0 +1,43 @@
+// apps/website/pages/docs/gamba-core-v2/_sidebar.ts
+export default {
+ gamba: {
+ title: 'Gamba',
+ href: '/docs/gamba-core-v2#gamba'
+ },
+ 'gamba-event-type': {
+ title: 'GambaEventType',
+ href: '/docs/gamba-core-v2#gambaeventtype'
+ },
+ 'gamba-event': {
+ title: 'GambaEvent',
+ href: '/docs/gamba-core-v2#gambaevent'
+ },
+ 'any-gamba-event': {
+ title: 'AnyGambaEvent',
+ href: '/docs/gamba-core-v2#anygambaevent'
+ },
+ 'gamba-transaction': {
+ title: 'GambaTransaction',
+ href: '/docs/gamba-core-v2#gambatransaction'
+ },
+ 'gamba-state': {
+ title: 'GambaState',
+ href: '/docs/gamba-core-v2#gambastate'
+ },
+ 'game-state': {
+ title: 'GameState',
+ href: '/docs/gamba-core-v2#gamestate'
+ },
+ 'player-state': {
+ title: 'PlayerState',
+ href: '/docs/gamba-core-v2#playerstate'
+ },
+ 'pool-state': {
+ title: 'PoolState',
+ href: '/docs/gamba-core-v2#poolstate'
+ },
+ 'game-result': {
+ title: 'GameResult',
+ href: '/docs/gamba-core-v2#gameresult'
+ }
+}
diff --git a/apps/website/pages/docs/gamba-core-v2/utilities/_meta.json b/apps/website/pages/docs/gamba-core-v2/utilities/_meta.json
deleted file mode 100644
index ecb217a8..00000000
--- a/apps/website/pages/docs/gamba-core-v2/utilities/_meta.json
+++ /dev/null
@@ -1,19 +0,0 @@
-{
- "basis-points": {
- "title": "basisPoints",
- "href": "/docs/gamba-core-v2#basispoints"
- },
- "wrap-sol": {
- "title": "wrapSol",
- "href": "/docs/gamba-core-v2#wrapsol"
- },
- "unwrap-sol": {
- "title": "unwrapSol",
- "href": "/docs/gamba-core-v2#unwrapsol"
- },
- "is-native-mint": {
- "title": "isNativeMint",
- "href": "/docs/gamba-core-v2#isnativemint"
- }
- }
-
\ No newline at end of file
diff --git a/apps/website/pages/docs/gamba-core-v2/utilities/_meta.ts b/apps/website/pages/docs/gamba-core-v2/utilities/_meta.ts
new file mode 100644
index 00000000..45085d4e
--- /dev/null
+++ b/apps/website/pages/docs/gamba-core-v2/utilities/_meta.ts
@@ -0,0 +1,19 @@
+// apps/website/pages/docs/gamba-core-v2/utilities/_meta.ts
+export default {
+ 'basis-points': {
+ title: 'basisPoints',
+ href: '/docs/gamba-core-v2#basispoints'
+ },
+ 'wrap-sol': {
+ title: 'wrapSol',
+ href: '/docs/gamba-core-v2#wrapsol'
+ },
+ 'unwrap-sol': {
+ title: 'unwrapSol',
+ href: '/docs/gamba-core-v2#unwrapsol'
+ },
+ 'is-native-mint': {
+ title: 'isNativeMint',
+ href: '/docs/gamba-core-v2#isnativemint'
+ }
+}
diff --git a/apps/website/pages/docs/gamba-react-ui-v2.mdx b/apps/website/pages/docs/gamba-react-ui-v2.mdx
index e705ea68..178a8540 100644
--- a/apps/website/pages/docs/gamba-react-ui-v2.mdx
+++ b/apps/website/pages/docs/gamba-react-ui-v2.mdx
@@ -1,4 +1,4 @@
-import { Callout } from "nextra-theme-docs";
+import { Callout } from "nextra/components";
# 🎨 `gamba-react-ui-v2`
diff --git a/apps/website/pages/docs/gamba-react-ui-v2/_meta.json b/apps/website/pages/docs/gamba-react-ui-v2/_meta.json
deleted file mode 100644
index 8a34e031..00000000
--- a/apps/website/pages/docs/gamba-react-ui-v2/_meta.json
+++ /dev/null
@@ -1,18 +0,0 @@
-{
- "components": {
- "title": "🖥️ Components",
- "href": "/docs/gamba-react-ui-v2#components"
- },
- "utilities-and-hooks": {
- "title": "🛠️ Utilities and Hooks",
- "href": "/docs/gamba-react-ui-v2#utilities-and-hooks"
- },
- "contexts": {
- "title": "🔄 Contexts",
- "href": "/docs/gamba-react-ui-v2#contexts"
- },
- "hooks": {
- "title": "🏷️ Token Metadata",
- "href": "/docs/gamba-react-ui-v2#token-metadata"
- }
-}
diff --git a/apps/website/pages/docs/gamba-react-ui-v2/_meta.ts b/apps/website/pages/docs/gamba-react-ui-v2/_meta.ts
new file mode 100644
index 00000000..1ac9e26f
--- /dev/null
+++ b/apps/website/pages/docs/gamba-react-ui-v2/_meta.ts
@@ -0,0 +1,18 @@
+export default {
+ components: {
+ title: '🖥️ Components',
+ href: '/docs/gamba-react-ui-v2#components'
+ },
+ 'utilities-and-hooks': {
+ title: '🛠️ Utilities and Hooks',
+ href: '/docs/gamba-react-ui-v2#utilities-and-hooks'
+ },
+ contexts: {
+ title: '🔄 Contexts',
+ href: '/docs/gamba-react-ui-v2#contexts'
+ },
+ hooks: {
+ title: '🏷️ Token Metadata',
+ href: '/docs/gamba-react-ui-v2#token-metadata'
+ }
+}
diff --git a/apps/website/pages/docs/gamba-react-ui-v2/components/_meta.json b/apps/website/pages/docs/gamba-react-ui-v2/components/_meta.json
deleted file mode 100644
index bbc3032a..00000000
--- a/apps/website/pages/docs/gamba-react-ui-v2/components/_meta.json
+++ /dev/null
@@ -1,58 +0,0 @@
-{
- "gambauibutton": {
- "title": "GambaUi.Button",
- "href": "/docs/gamba-react-ui-v2#gambauibutton"
- },
- "gambauiplaybutton": {
- "title": "GambaUi.PlayButton",
- "href": "/docs/gamba-react-ui-v2#gambauiplaybutton"
- },
- "gambauiwagerinput": {
- "title": "GambaUi.WagerInput",
- "href": "/docs/gamba-react-ui-v2#gambauiwagerinput"
- },
- "gambauiwagerselect": {
- "title": "GambaUi.WagerSelect",
- "href": "/docs/gamba-react-ui-v2#gambauiwagerselect"
- },
- "gambauiswitch": {
- "title": "GambaUi.Switch",
- "href": "/docs/gamba-react-ui-v2#gambauiswitch"
- },
- "gambauiselect": {
- "title": "GambaUi.Select",
- "href": "/docs/gamba-react-ui-v2#gambauiselect"
- },
- "gambauitextinput": {
- "title": "GambaUi.TextInput",
- "href": "/docs/gamba-react-ui-v2#gambauitextinput"
- },
- "gambauieffecttest": {
- "title": "GambaUi.EffectTest",
- "href": "/docs/gamba-react-ui-v2#gambauieffecttest"
- },
- "gambauiresponsivesize": {
- "title": "GambaUi.ResponsiveSize",
- "href": "/docs/gamba-react-ui-v2#gambauiresponsivesize"
- },
- "gambauiportal": {
- "title": "GambaUi.Portal",
- "href": "/docs/gamba-react-ui-v2#gambauiportal"
- },
- "gambauiportaltarget": {
- "title": "GambaUi.PortalTarget",
- "href": "/docs/gamba-react-ui-v2#gambauiportaltarget"
- },
- "gambauigame": {
- "title": "GambaUi.Game",
- "href": "/docs/gamba-react-ui-v2#gambauigame"
- },
- "gambauicanvas": {
- "title": "GambaUi.Canvas",
- "href": "/docs/gamba-react-ui-v2#gambauicanvas"
- },
- "gambauitokenvalue": {
- "title": "GambaUi.TokenValue",
- "href": "/docs/gamba-react-ui-v2#gambauitokenvalue"
- }
- }
\ No newline at end of file
diff --git a/apps/website/pages/docs/gamba-react-ui-v2/components/_meta.ts b/apps/website/pages/docs/gamba-react-ui-v2/components/_meta.ts
new file mode 100644
index 00000000..8eb74af2
--- /dev/null
+++ b/apps/website/pages/docs/gamba-react-ui-v2/components/_meta.ts
@@ -0,0 +1,58 @@
+export default {
+ gambauibutton: {
+ title: 'GambaUi.Button',
+ href: '/docs/gamba-react-ui-v2#gambauibutton'
+ },
+ gambauiplaybutton: {
+ title: 'GambaUi.PlayButton',
+ href: '/docs/gamba-react-ui-v2#gambauiplaybutton'
+ },
+ gambauiwagerinput: {
+ title: 'GambaUi.WagerInput',
+ href: '/docs/gamba-react-ui-v2#gambauiwagerinput'
+ },
+ gambauiwagerselect: {
+ title: 'GambaUi.WagerSelect',
+ href: '/docs/gamba-react-ui-v2#gambauiwagerselect'
+ },
+ gambauiswitch: {
+ title: 'GambaUi.Switch',
+ href: '/docs/gamba-react-ui-v2#gambauiswitch'
+ },
+ gambauiselect: {
+ title: 'GambaUi.Select',
+ href: '/docs/gamba-react-ui-v2#gambauiselect'
+ },
+ gambauitextinput: {
+ title: 'GambaUi.TextInput',
+ href: '/docs/gamba-react-ui-v2#gambauitextinput'
+ },
+ gambauieffecttest: {
+ title: 'GambaUi.EffectTest',
+ href: '/docs/gamba-react-ui-v2#gambauieffecttest'
+ },
+ gambauiresponsivesize: {
+ title: 'GambaUi.ResponsiveSize',
+ href: '/docs/gamba-react-ui-v2#gambauiresponsivesize'
+ },
+ gambauiportal: {
+ title: 'GambaUi.Portal',
+ href: '/docs/gamba-react-ui-v2#gambauiportal'
+ },
+ gambauiportaltarget: {
+ title: 'GambaUi.PortalTarget',
+ href: '/docs/gamba-react-ui-v2#gambauiportaltarget'
+ },
+ gambauigame: {
+ title: 'GambaUi.Game',
+ href: '/docs/gamba-react-ui-v2#gambauigame'
+ },
+ gambauicanvas: {
+ title: 'GambaUi.Canvas',
+ href: '/docs/gamba-react-ui-v2#gambauicanvas'
+ },
+ gambauitokenvalue: {
+ title: 'GambaUi.TokenValue',
+ href: '/docs/gamba-react-ui-v2#gambauitokenvalue'
+ }
+}
\ No newline at end of file
diff --git a/apps/website/pages/docs/gamba-react-ui-v2/contexts/_meta.json b/apps/website/pages/docs/gamba-react-ui-v2/contexts/_meta.json
deleted file mode 100644
index 5094d898..00000000
--- a/apps/website/pages/docs/gamba-react-ui-v2/contexts/_meta.json
+++ /dev/null
@@ -1,11 +0,0 @@
-{
- "gambaplatformcontext": {
- "title": "GambaPlatformContext",
- "href": "/docs/gamba-react-ui-v2#gambaplatformcontext"
- },
- "gamecontext": {
- "title": "GameContext",
- "href": "/docs/gamba-react-ui-v2#gamecontext"
- }
- }
-
\ No newline at end of file
diff --git a/apps/website/pages/docs/gamba-react-ui-v2/contexts/_meta.ts b/apps/website/pages/docs/gamba-react-ui-v2/contexts/_meta.ts
new file mode 100644
index 00000000..f6f0b6ac
--- /dev/null
+++ b/apps/website/pages/docs/gamba-react-ui-v2/contexts/_meta.ts
@@ -0,0 +1,10 @@
+export default {
+ gambaplatformcontext: {
+ title: 'GambaPlatformContext',
+ href: '/docs/gamba-react-ui-v2#gambaplatformcontext'
+ },
+ gamecontext: {
+ title: 'GameContext',
+ href: '/docs/gamba-react-ui-v2#gamecontext'
+ }
+}
\ No newline at end of file
diff --git a/apps/website/pages/docs/gamba-react-ui-v2/hooks/_meta.json b/apps/website/pages/docs/gamba-react-ui-v2/hooks/_meta.json
deleted file mode 100644
index bf041066..00000000
--- a/apps/website/pages/docs/gamba-react-ui-v2/hooks/_meta.json
+++ /dev/null
@@ -1,11 +0,0 @@
-{
- "tokenmeta": {
- "title": "TokenMeta",
- "href": "/docs/gamba-react-ui-v2#tokenmeta"
- },
- "makeheliustokenfetcher": {
- "title": "makeHeliusTokenFetcher",
- "href": "/docs/gamba-react-ui-v2#makeheliustokenfetcher"
- }
- }
-
\ No newline at end of file
diff --git a/apps/website/pages/docs/gamba-react-ui-v2/hooks/_meta.ts b/apps/website/pages/docs/gamba-react-ui-v2/hooks/_meta.ts
new file mode 100644
index 00000000..e9fc9c4b
--- /dev/null
+++ b/apps/website/pages/docs/gamba-react-ui-v2/hooks/_meta.ts
@@ -0,0 +1,10 @@
+export default {
+ tokenmeta: {
+ title: 'TokenMeta',
+ href: '/docs/gamba-react-ui-v2#tokenmeta'
+ },
+ makeheliustokenfetcher: {
+ title: 'makeHeliusTokenFetcher',
+ href: '/docs/gamba-react-ui-v2#makeheliustokenfetcher'
+ }
+}
\ No newline at end of file
diff --git a/apps/website/pages/docs/gamba-react-ui-v2/utilities-and-hooks/_meta.json b/apps/website/pages/docs/gamba-react-ui-v2/utilities-and-hooks/_meta.json
deleted file mode 100644
index d158c323..00000000
--- a/apps/website/pages/docs/gamba-react-ui-v2/utilities-and-hooks/_meta.json
+++ /dev/null
@@ -1,55 +0,0 @@
-{
- "usegame": {
- "title": "useGame",
- "href": "/docs/gamba-react-ui-v2#usegame"
- },
- "usegambaplatformcontext": {
- "title": "useGambaPlatformContext",
- "href": "/docs/gamba-react-ui-v2#usegambaplatformcontext"
- },
- "usesound": {
- "title": "useSound",
- "href": "/docs/gamba-react-ui-v2#usesound"
- },
- "usecurrenttoken": {
- "title": "useCurrentToken",
- "href": "/docs/gamba-react-ui-v2#usecurrenttoken"
- },
- "usetokenbalance": {
- "title": "useTokenBalance",
- "href": "/docs/gamba-react-ui-v2#usetokenbalance"
- },
- "useuserbalance": {
- "title": "useUserBalance",
- "href": "/docs/gamba-react-ui-v2#useuserbalance"
- },
- "usewagerinput": {
- "title": "useWagerInput",
- "href": "/docs/gamba-react-ui-v2#usewagerinput"
- },
- "usenextfakeresult": {
- "title": "useNextFakeResult",
- "href": "/docs/gamba-react-ui-v2#usenextfakeresult"
- },
- "usefakeaccountstore": {
- "title": "useFakeAccountStore",
- "href": "/docs/gamba-react-ui-v2#usefakeaccountstore"
- },
- "usegambaaudiostore": {
- "title": "useGambaAudioStore",
- "href": "/docs/gamba-react-ui-v2#usegambaaudiostore"
- },
- "usetokenlist": {
- "title": "useTokenList",
- "href": "/docs/gamba-react-ui-v2#usetokenlist"
- },
- "gambastandardtokens": {
- "title": "GambaStandardTokens",
- "href": "/docs/gamba-react-ui-v2#gambastandardtokens"
- },
- "usecurrentpool": {
- "title": "useCurrentPool",
- "href": "/docs/gamba-react-ui-v2#usecurrentpool"
- }
- }
-
\ No newline at end of file
diff --git a/apps/website/pages/docs/gamba-react-ui-v2/utilities-and-hooks/_meta.ts b/apps/website/pages/docs/gamba-react-ui-v2/utilities-and-hooks/_meta.ts
new file mode 100644
index 00000000..375aedbe
--- /dev/null
+++ b/apps/website/pages/docs/gamba-react-ui-v2/utilities-and-hooks/_meta.ts
@@ -0,0 +1,54 @@
+export default {
+ usegame: {
+ title: 'useGame',
+ href: '/docs/gamba-react-ui-v2#usegame'
+ },
+ usegambaplatformcontext: {
+ title: 'useGambaPlatformContext',
+ href: '/docs/gamba-react-ui-v2#usegambaplatformcontext'
+ },
+ usesound: {
+ title: 'useSound',
+ href: '/docs/gamba-react-ui-v2#usesound'
+ },
+ usecurrenttoken: {
+ title: 'useCurrentToken',
+ href: '/docs/gamba-react-ui-v2#usecurrenttoken'
+ },
+ usetokenbalance: {
+ title: 'useTokenBalance',
+ href: '/docs/gamba-react-ui-v2#usetokenbalance'
+ },
+ useuserbalance: {
+ title: 'useUserBalance',
+ href: '/docs/gamba-react-ui-v2#useuserbalance'
+ },
+ usewagerinput: {
+ title: 'useWagerInput',
+ href: '/docs/gamba-react-ui-v2#usewagerinput'
+ },
+ usenextfakeresult: {
+ title: 'useNextFakeResult',
+ href: '/docs/gamba-react-ui-v2#usenextfakeresult'
+ },
+ usefakeaccountstore: {
+ title: 'useFakeAccountStore',
+ href: '/docs/gamba-react-ui-v2#usefakeaccountstore'
+ },
+ usegambaaudiostore: {
+ title: 'useGambaAudioStore',
+ href: '/docs/gamba-react-ui-v2#usegambaaudiostore'
+ },
+ usetokenlist: {
+ title: 'useTokenList',
+ href: '/docs/gamba-react-ui-v2#usetokenlist'
+ },
+ gambastandardtokens: {
+ title: 'GambaStandardTokens',
+ href: '/docs/gamba-react-ui-v2#gambastandardtokens'
+ },
+ usecurrentpool: {
+ title: 'useCurrentPool',
+ href: '/docs/gamba-react-ui-v2#usecurrentpool'
+ }
+}
diff --git a/apps/website/pages/docs/gamba-react-v2.mdx b/apps/website/pages/docs/gamba-react-v2.mdx
index 926051af..35374797 100644
--- a/apps/website/pages/docs/gamba-react-v2.mdx
+++ b/apps/website/pages/docs/gamba-react-v2.mdx
@@ -1,4 +1,4 @@
-import { Callout } from "nextra-theme-docs";
+import { Callout } from "nextra/components";
# 🧩 `gamba-react-v2`
diff --git a/apps/website/pages/docs/gamba-react-v2/_meta.json b/apps/website/pages/docs/gamba-react-v2/_meta.json
deleted file mode 100644
index 9d0bd1be..00000000
--- a/apps/website/pages/docs/gamba-react-v2/_meta.json
+++ /dev/null
@@ -1,19 +0,0 @@
-{
- "contexts": {
- "title": "🔄 Contexts",
- "href": "/docs/gamba-react-v2#contexts"
- },
- "types": {
- "title": "📜 Types",
- "href": "/docs/gamba-react-v2#types"
- },
- "methodsandhooks": {
- "title": "🔍 Methods and Hooks",
- "href": "/docs/gamba-react-v2#methodsandhooks"
- },
- "utilitiesandhooks": {
- "title": "⚙️ Utilities and Functions",
- "href": "/docs/gamba-react-v2#utilitiesandhooks"
- }
- }
-
\ No newline at end of file
diff --git a/apps/website/pages/docs/gamba-react-v2/_meta.ts b/apps/website/pages/docs/gamba-react-v2/_meta.ts
new file mode 100644
index 00000000..122ebd71
--- /dev/null
+++ b/apps/website/pages/docs/gamba-react-v2/_meta.ts
@@ -0,0 +1,19 @@
+// apps/website/pages/docs/gamba-react-v2/_sidebar.ts
+export default {
+ contexts: {
+ title: '🔄 Contexts',
+ href: '/docs/gamba-react-v2#contexts'
+ },
+ types: {
+ title: '📜 Types',
+ href: '/docs/gamba-react-v2#types'
+ },
+ methodsandhooks: {
+ title: '🔍 Methods and Hooks',
+ href: '/docs/gamba-react-v2#methodsandhooks'
+ },
+ utilitiesandhooks: {
+ title: '⚙️ Utilities and Functions',
+ href: '/docs/gamba-react-v2#utilitiesandhooks'
+ }
+}
diff --git a/apps/website/pages/docs/gamba-react-v2/contexts/_meta.json b/apps/website/pages/docs/gamba-react-v2/contexts/_meta.json
deleted file mode 100644
index 92b98493..00000000
--- a/apps/website/pages/docs/gamba-react-v2/contexts/_meta.json
+++ /dev/null
@@ -1,19 +0,0 @@
-{
- "sendtransactioncontext": {
- "title": "SendTransactionContext",
- "href": "/docs/gamba-react-v2#sendtransactioncontext"
- },
- "gambacontext": {
- "title": "GambaContext",
- "href": "/docs/gamba-react-v2#gambacontext"
- },
- "sendtransactionprovider": {
- "title": "SendTransactionProvider",
- "href": "/docs/gamba-react-v2#sendtransactionprovider"
- },
- "gambaprovider": {
- "title": "GambaProvider",
- "href": "/docs/gamba-react-v2#gambaprovider"
- }
- }
-
\ No newline at end of file
diff --git a/apps/website/pages/docs/gamba-react-v2/contexts/_meta.ts b/apps/website/pages/docs/gamba-react-v2/contexts/_meta.ts
new file mode 100644
index 00000000..073ab8dc
--- /dev/null
+++ b/apps/website/pages/docs/gamba-react-v2/contexts/_meta.ts
@@ -0,0 +1,19 @@
+
+export default {
+ sendtransactioncontext: {
+ title: 'SendTransactionContext',
+ href: '/docs/gamba-react-v2#sendtransactioncontext'
+ },
+ gambacontext: {
+ title: 'GambaContext',
+ href: '/docs/gamba-react-v2#gambacontext'
+ },
+ sendtransactionprovider: {
+ title: 'SendTransactionProvider',
+ href: '/docs/gamba-react-v2#sendtransactionprovider'
+ },
+ gambaprovider: {
+ title: 'GambaProvider',
+ href: '/docs/gamba-react-v2#gambaprovider'
+ }
+}
diff --git a/apps/website/pages/docs/gamba-react-v2/methodsandhooks/_meta.json b/apps/website/pages/docs/gamba-react-v2/methodsandhooks/_meta.json
deleted file mode 100644
index 8ba8de58..00000000
--- a/apps/website/pages/docs/gamba-react-v2/methodsandhooks/_meta.json
+++ /dev/null
@@ -1,62 +0,0 @@
-{
- "useaccount": {
- "title": "useAccount",
- "href": "/docs/gamba-react-v2#useaccount"
- },
- "usewalletaddress": {
- "title": "useWalletAddress",
- "href": "/docs/gamba-react-v2#usewalletaddress"
- },
- "usebalance": {
- "title": "useBalance",
- "href": "/docs/gamba-react-v2#usebalance"
- },
- "usetransactionerror": {
- "title": "useTransactionError",
- "href": "/docs/gamba-react-v2#usetransactionerror"
- },
- "usesendtransaction": {
- "title": "useSendTransaction",
- "href": "/docs/gamba-react-v2#usesendtransaction"
- },
- "usegambaplay": {
- "title": "useGambaPlay",
- "href": "/docs/gamba-react-v2#usegambaplay"
- },
- "usenextresult": {
- "title": "useNextResult",
- "href": "/docs/gamba-react-v2#usenextresult"
- },
- "usegamba": {
- "title": "useGamba",
- "href": "/docs/gamba-react-v2#usegamba"
- },
- "usegambaeventlistener": {
- "title": "useGambaEventListener",
- "href": "/docs/gamba-react-v2#usegambaeventlistener"
- },
- "usegambaevents": {
- "title": "useGambaEvents",
- "href": "/docs/gamba-react-v2#usegambaevents"
- },
- "usepool": {
- "title": "usePool",
- "href": "/docs/gamba-react-v2#usepool"
- },
- "usetransactionstore": {
- "title": "useTransactionStore",
- "href": "/docs/gamba-react-v2#usetransactionstore"
- },
- "usegambaprovider": {
- "title": "useGambaProvider",
- "href": "/docs/gamba-react-v2#usegambaprovider"
- },
- "usegambaprogram": {
- "title": "useGambaProgram",
- "href": "/docs/gamba-react-v2#usegambaprogram"
- },
- "usegambacomponent": {
- "title": "useGambaComponent",
- "href": "/docs/gamba-react-v2#usegambacomponent"
- }
- }
\ No newline at end of file
diff --git a/apps/website/pages/docs/gamba-react-v2/methodsandhooks/_meta.ts b/apps/website/pages/docs/gamba-react-v2/methodsandhooks/_meta.ts
new file mode 100644
index 00000000..d8720145
--- /dev/null
+++ b/apps/website/pages/docs/gamba-react-v2/methodsandhooks/_meta.ts
@@ -0,0 +1,63 @@
+
+export default {
+ useaccount: {
+ title: 'useAccount',
+ href: '/docs/gamba-react-v2#useaccount'
+ },
+ usewalletaddress: {
+ title: 'useWalletAddress',
+ href: '/docs/gamba-react-v2#usewalletaddress'
+ },
+ usebalance: {
+ title: 'useBalance',
+ href: '/docs/gamba-react-v2#usebalance'
+ },
+ usetransactionerror: {
+ title: 'useTransactionError',
+ href: '/docs/gamba-react-v2#usetransactionerror'
+ },
+ usesendtransaction: {
+ title: 'useSendTransaction',
+ href: '/docs/gamba-react-v2#usesendtransaction'
+ },
+ usegambaplay: {
+ title: 'useGambaPlay',
+ href: '/docs/gamba-react-v2#usegambaplay'
+ },
+ usenextresult: {
+ title: 'useNextResult',
+ href: '/docs/gamba-react-v2#usenextresult'
+ },
+ usegamba: {
+ title: 'useGamba',
+ href: '/docs/gamba-react-v2#usegamba'
+ },
+ usegambaeventlistener: {
+ title: 'useGambaEventListener',
+ href: '/docs/gamba-react-v2#usegambaeventlistener'
+ },
+ usegambaevents: {
+ title: 'useGambaEvents',
+ href: '/docs/gamba-react-v2#usegambaevents'
+ },
+ usepool: {
+ title: 'usePool',
+ href: '/docs/gamba-react-v2#usepool'
+ },
+ usetransactionstore: {
+ title: 'useTransactionStore',
+ href: '/docs/gamba-react-v2#usetransactionstore'
+ },
+ usegambaprovider: {
+ title: 'useGambaProvider',
+ href: '/docs/gamba-react-v2#usegambaprovider'
+ },
+ usegambaprogram: {
+ title: 'useGambaProgram',
+ href: '/docs/gamba-react-v2#usegambaprogram'
+ },
+ usegambacomponent: {
+ title: 'useGambaComponent',
+ href: '/docs/gamba-react-v2#usegambacomponent'
+ }
+}
diff --git a/apps/website/pages/docs/gamba-react-v2/types/_meta.json b/apps/website/pages/docs/gamba-react-v2/types/_meta.json
deleted file mode 100644
index a533a9ff..00000000
--- a/apps/website/pages/docs/gamba-react-v2/types/_meta.json
+++ /dev/null
@@ -1,46 +0,0 @@
-{
- "gambaeventtype": {
- "title": "GambaEventType",
- "href": "/docs/gamba-react-v2#gambaeventtype"
- },
- "gambatransaction": {
- "title": "GambaTransaction",
- "href": "/docs/gamba-react-v2#gambatransaction"
- },
- "gameresult": {
- "title": "GameResult",
- "href": "/docs/gamba-react-v2#gameresult"
- },
- "gambaplayinput": {
- "title": "GambaPlayInput",
- "href": "/docs/gamba-react-v2#gambaplayinput"
- },
- "sendtransactionoptions": {
- "title": "SendTransactionOptions",
- "href": "/docs/gamba-react-v2#sendtransactionoptions"
- },
- "sendtransactionprops": {
- "title": "SendTransactionProps",
- "href": "/docs/gamba-react-v2#sendtransactionprops"
- },
- "gambaproviderprops": {
- "title": "GambaProviderProps",
- "href": "/docs/gamba-react-v2#gambaproviderprops"
- },
- "transactionstore": {
- "title": "TransactionStore",
- "href": "/docs/gamba-react-v2#transactionstore"
- },
- "usegambaeventsparams": {
- "title": "UseGambaEventsParams",
- "href": "/docs/gamba-react-v2#usegambaeventsparams"
- },
- "uipoolstate": {
- "title": "UiPoolState",
- "href": "/docs/gamba-react-v2#uipoolstate"
- },
- "gambaplugininput": {
- "title": "GambaPluginInput",
- "href": "/docs/gamba-react-v2#gambaplugininput"
- }
- }
\ No newline at end of file
diff --git a/apps/website/pages/docs/gamba-react-v2/types/_meta.ts b/apps/website/pages/docs/gamba-react-v2/types/_meta.ts
new file mode 100644
index 00000000..59a787a7
--- /dev/null
+++ b/apps/website/pages/docs/gamba-react-v2/types/_meta.ts
@@ -0,0 +1,47 @@
+// apps/website/pages/docs/gamba-react-v2/types/_meta.ts
+export default {
+ gambaeventtype: {
+ title: 'GambaEventType',
+ href: '/docs/gamba-react-v2#gambaeventtype'
+ },
+ gambatransaction: {
+ title: 'GambaTransaction',
+ href: '/docs/gamba-react-v2#gambatransaction'
+ },
+ gameresult: {
+ title: 'GameResult',
+ href: '/docs/gamba-react-v2#gameresult'
+ },
+ gambaplayinput: {
+ title: 'GambaPlayInput',
+ href: '/docs/gamba-react-v2#gambaplayinput'
+ },
+ sendtransactionoptions: {
+ title: 'SendTransactionOptions',
+ href: '/docs/gamba-react-v2#sendtransactionoptions'
+ },
+ sendtransactionprops: {
+ title: 'SendTransactionProps',
+ href: '/docs/gamba-react-v2#sendtransactionprops'
+ },
+ gambaproviderprops: {
+ title: 'GambaProviderProps',
+ href: '/docs/gamba-react-v2#gambaproviderprops'
+ },
+ transactionstore: {
+ title: 'TransactionStore',
+ href: '/docs/gamba-react-v2#transactionstore'
+ },
+ usegambaeventsparams: {
+ title: 'UseGambaEventsParams',
+ href: '/docs/gamba-react-v2#usegambaeventsparams'
+ },
+ uipoolstate: {
+ title: 'UiPoolState',
+ href: '/docs/gamba-react-v2#uipoolstate'
+ },
+ gambaplugininput: {
+ title: 'GambaPluginInput',
+ href: '/docs/gamba-react-v2#gambaplugininput'
+ }
+}
diff --git a/apps/website/pages/docs/gamba-react-v2/utilitiesandhooks/_meta.json b/apps/website/pages/docs/gamba-react-v2/utilitiesandhooks/_meta.json
deleted file mode 100644
index 867f83d9..00000000
--- a/apps/website/pages/docs/gamba-react-v2/utilitiesandhooks/_meta.json
+++ /dev/null
@@ -1,10 +0,0 @@
-{
- "throwtransactionerror": {
- "title": "throwTransactionError",
- "href": "/docs/gamba-react-v2#throwtransactionerror"
- },
- "createcustomfeeplugin": {
- "title": "createCustomFeePlugin",
- "href": "/docs/gamba-react-v2#createcustomfeeplugin"
- }
- }
\ No newline at end of file
diff --git a/apps/website/pages/docs/gamba-react-v2/utilitiesandhooks/_meta.ts b/apps/website/pages/docs/gamba-react-v2/utilitiesandhooks/_meta.ts
new file mode 100644
index 00000000..3e0ebdea
--- /dev/null
+++ b/apps/website/pages/docs/gamba-react-v2/utilitiesandhooks/_meta.ts
@@ -0,0 +1,11 @@
+
+export default {
+ throwtransactionerror: {
+ title: 'throwTransactionError',
+ href: '/docs/gamba-react-v2#throwtransactionerror'
+ },
+ createcustomfeeplugin: {
+ title: 'createCustomFeePlugin',
+ href: '/docs/gamba-react-v2#createcustomfeeplugin'
+ }
+}
diff --git a/apps/website/pages/docs/games.mdx b/apps/website/pages/docs/games.mdx
index 8e3a507a..79b59f33 100644
--- a/apps/website/pages/docs/games.mdx
+++ b/apps/website/pages/docs/games.mdx
@@ -1,5 +1,5 @@
import Simulator from "../../components/docs/games-simulator";
-import { Callout } from "nextra-theme-docs";
+import { Callout } from "nextra/components";
import { Steps } from "nextra/components";
import { Tabs } from "nextra/components";
diff --git a/apps/website/pages/docs/get-started.mdx b/apps/website/pages/docs/get-started.mdx
index d4ec448a..d2175116 100644
--- a/apps/website/pages/docs/get-started.mdx
+++ b/apps/website/pages/docs/get-started.mdx
@@ -1,63 +1,18 @@
-import { Callout, Card, Cards } from "nextra-theme-docs";
-import { Steps } from "nextra/components";
+import { Cards } from "nextra/components";
# 🚀 Get Started
Get started with Gamba by using a template for quick setup
-
- <>
- 
-
- VITE
-
- >
-
-
- <>
- 
-
- NEXT.ᴊs
-
- >
-
-
+
+
+
Start scratch
-
-
-
+
+
+
\ No newline at end of file
diff --git a/apps/website/pages/docs/get-started/_meta.json b/apps/website/pages/docs/get-started/_meta.json
deleted file mode 100644
index b93cb90d..00000000
--- a/apps/website/pages/docs/get-started/_meta.json
+++ /dev/null
@@ -1,14 +0,0 @@
-{
- "vite": {
- "title": "⚡ Vite",
- "href": "/docs/get-started/vite"
- },
- "nextjs": {
- "title": "⚛️ NEXT.ᴊs",
- "href": "/docs/get-started/nextjs"
- },
- "manual": {
- "title": "📖 Manual",
- "href": "/docs/get-started/manual"
- }
-}
diff --git a/apps/website/pages/docs/get-started/_meta.ts b/apps/website/pages/docs/get-started/_meta.ts
new file mode 100644
index 00000000..c5876326
--- /dev/null
+++ b/apps/website/pages/docs/get-started/_meta.ts
@@ -0,0 +1,15 @@
+// apps/website/pages/docs/get-started/_sidebar.ts
+export default {
+ vite: {
+ title: '⚡ Vite',
+ href: '/docs/get-started/vite'
+ },
+ nextjs: {
+ title: '⚛️ NEXT.ᴊs',
+ href: '/docs/get-started/nextjs'
+ },
+ manual: {
+ title: '📖 Manual',
+ href: '/docs/get-started/manual'
+ }
+}
diff --git a/apps/website/pages/docs/get-started/manual.mdx b/apps/website/pages/docs/get-started/manual.mdx
index 1eab14e2..1e7bc8dc 100644
--- a/apps/website/pages/docs/get-started/manual.mdx
+++ b/apps/website/pages/docs/get-started/manual.mdx
@@ -1,4 +1,4 @@
-import { Callout, Card, Cards } from "nextra-theme-docs";
+import { Callout } from "nextra/components";
import { Steps } from "nextra/components";
# 📖 Manual
diff --git a/apps/website/pages/docs/get-started/vite.mdx b/apps/website/pages/docs/get-started/vite.mdx
index dd5f39db..d8635e93 100644
--- a/apps/website/pages/docs/get-started/vite.mdx
+++ b/apps/website/pages/docs/get-started/vite.mdx
@@ -1,5 +1,5 @@
import { Steps } from "nextra/components";
-import { Callout } from "nextra-theme-docs";
+import { Callout } from "nextra/components";
# ⚡ Vite
diff --git a/apps/website/pages/docs/index.mdx b/apps/website/pages/docs/index.mdx
index 248303f8..e1bd6549 100644
--- a/apps/website/pages/docs/index.mdx
+++ b/apps/website/pages/docs/index.mdx
@@ -1,15 +1,11 @@
-import { Callout, Card, Cards } from 'nextra-theme-docs';
-
# 👋 Welcome to Gamba
Gamba is a decentralized gambleFi protocol for on-chain degeneracy on Solana.
## Quickstart
-Skip to the good stuff
+Skip to the good stuff:
+
+- [Get Started](/docs/get-started)
-
-
- <>>
-
-
+

diff --git a/apps/website/pages/docs/multiplayer.mdx b/apps/website/pages/docs/multiplayer.mdx
new file mode 100644
index 00000000..8487e7bb
--- /dev/null
+++ b/apps/website/pages/docs/multiplayer.mdx
@@ -0,0 +1,132 @@
+# 🤝 Multiplayer
+
+Build multiplayer games on Gamba using the Multiplayer SDK. This page covers how to create a game and how players join, with the exact parameters you need.
+
+## Install
+
+```bash
+pnpm add @gamba-labs/multiplayer-sdk @coral-xyz/anchor @solana/web3.js @solana/spl-token
+```
+
+## Create Game
+
+Function: `createGameIx(provider, params)`
+
+- Automatically chooses native SOL vs SPL flow based on `params.accounts.mint` (`WRAPPED_SOL_MINT` = native)
+- Returns a `TransactionInstruction`
+
+### Params
+
+```ts
+type CreateGameParams = {
+ preAllocPlayers: number
+ maxPlayers: number
+ numTeams: number
+ winnersTarget: number
+ wagerType: number
+ payoutType: number
+ wager: BN | number
+ softDuration: BN | number
+ hardDuration: BN | number
+ gameSeed: BN | number
+ minBet: BN | number
+ maxBet: BN | number
+ accounts: {
+ gameMaker: web3.PublicKey
+ mint: web3.PublicKey
+ }
+}
+```
+
+Wager types: `sameWager`, `customWager`, `betRange` (as numeric enum in the IDL). If using `betRange`, UI should clamp inputs between `minBet` and `maxBet`.
+
+Payout types: `same`, `exponentialDecay` (as numeric enum in the IDL).
+
+### Example
+
+```ts
+import { AnchorProvider, BN, web3 } from '@coral-xyz/anchor'
+import { createGameIx, WRAPPED_SOL_MINT } from '@gamba-labs/multiplayer-sdk'
+
+const provider = AnchorProvider.env()
+const walletPubkey = provider.wallet.publicKey
+
+const ix = await createGameIx(provider, {
+ preAllocPlayers: 100,
+ maxPlayers: 1000,
+ numTeams: 1,
+ winnersTarget: 1,
+ wagerType: 0, // e.g. sameWager
+ payoutType: 0, // e.g. same
+ wager: new BN(1_000_000), // 0.001 in token base units
+ softDuration: new BN(60), // seconds
+ hardDuration: new BN(300), // seconds
+ gameSeed: new BN(Date.now()),
+ minBet: new BN(1_000_000),
+ maxBet: new BN(1_000_000),
+ accounts: {
+ gameMaker: walletPubkey,
+ mint: WRAPPED_SOL_MINT, // or any SPL mint
+ },
+})
+
+const tx = new web3.Transaction().add(ix)
+await provider.sendAndConfirm(tx)
+```
+
+## Join Game
+
+Function: `joinGameIx(provider, params)`
+
+- Handles native SOL or SPL automatically
+- Returns a `TransactionInstruction`
+
+### Params
+
+```ts
+type JoinGameParams = {
+ creatorFeeBps: number
+ wager: BN | number
+ team?: number
+ playerMeta?: Buffer | Uint8Array
+ accounts: {
+ gameAccount: web3.PublicKey
+ mint: web3.PublicKey
+ playerAccount: web3.PublicKey
+ creatorAddress: web3.PublicKey
+ }
+}
+```
+
+### Example
+
+```ts
+import { AnchorProvider, BN, web3 } from '@coral-xyz/anchor'
+import { joinGameIx, WRAPPED_SOL_MINT } from '@gamba-labs/multiplayer-sdk'
+
+const provider = AnchorProvider.env()
+const walletPubkey = provider.wallet.publicKey
+
+const ix = await joinGameIx(provider, {
+ creatorFeeBps: 200, // 2%
+ wager: new BN(1_000_000),
+ team: 0, // optional
+ // playerMeta: new Uint8Array(32), // optional 32 bytes
+ accounts: {
+ gameAccount: new web3.PublicKey('
'),
+ mint: WRAPPED_SOL_MINT, // or the SPL mint used by the game
+ playerAccount: walletPubkey,
+ creatorAddress: walletPubkey, // referral / creator
+ },
+})
+
+const tx = new web3.Transaction().add(ix)
+await provider.sendAndConfirm(tx)
+```
+
+## Notes
+
+- For SPL mints, associated token accounts are derived lazily by the SDK and created if missing.
+- `gameSeed` should be unique per game; a timestamp or counter works well.
+- For `betRange`, ensure your UI clamps any wager changes within `[minBet, maxBet]`.
+
diff --git a/apps/website/pages/docs/pools.mdx b/apps/website/pages/docs/pools.mdx
index 3a971a27..48fb15b9 100644
--- a/apps/website/pages/docs/pools.mdx
+++ b/apps/website/pages/docs/pools.mdx
@@ -1,20 +1,20 @@
-import { Card, Cards } from "nextra-theme-docs";
+import { Cards } from "nextra/components";
# 🏦 Pools
Gamba liquidity pools act as the foundation for bets placed by players through various [frontend apps](/docs/apps), resembling the house in a traditional casino. However, unlike a traditional setup, Gamba offers the advantage of inclusivity, allowing anyone to participate and share in the profits.
-
+
+
+
## Creating a Pool
Creating a Gamba pool for any custom Solana token is a straightforward process, accessible via the explorer UI or by depositing into an existing pool. Moreover, pools can be effortlessly managed and interacted with through the Gamba SDK.
-
-
-
+
## LP Token
diff --git a/apps/website/pages/docs/templates.mdx b/apps/website/pages/docs/templates.mdx
index 59aba8b4..c8f2b647 100644
--- a/apps/website/pages/docs/templates.mdx
+++ b/apps/website/pages/docs/templates.mdx
@@ -1,11 +1,11 @@
-import { Callout, Card, Cards } from "nextra-theme-docs";
+import { Callout, Cards } from "nextra/components";
# 📄 Templates
Gamba starter templates
- VITE
>
-
-
+ NEXT.ᴊs
>
-
+
diff --git a/apps/website/theme.config.tsx b/apps/website/theme.config.tsx
index fcf3a5a1..67cea8d5 100644
--- a/apps/website/theme.config.tsx
+++ b/apps/website/theme.config.tsx
@@ -1,30 +1,47 @@
-import { DocsThemeConfig, useConfig } from 'nextra-theme-docs'
+// apps/website/theme.config.tsx
+import type { DocsThemeConfig } from 'nextra-theme-docs'
+import { useConfig } from 'nextra-theme-docs'
import { useRouter } from 'next/router'
const config: DocsThemeConfig = {
- logo: (<>
>),
+ logo: (
+ <>
+
+ >
+ ),
+
project: { link: 'https://github.com/gamba-labs' },
chat: { link: 'https://discord.gg/xjBsW3e8fK' },
- docsRepositoryBase: 'https://github.com/gamba-labs/gamba/tree/docs/apps/website',
+
+ docsRepositoryBase:
+ 'https://github.com/gamba-labs/gamba/tree/docs/apps/website',
+
nextThemes: {
defaultTheme: 'dark',
forcedTheme: 'dark',
},
+
themeSwitch: { component: () => null },
- footer: { text: 'Gamba ©©©©©©©©' },
+
+ footer: {
+ content: `Gamba © ${new Date().getFullYear()}`,
+ },
+
head: () => {
- const { asPath, pathname } = useRouter()
+ const { asPath } = useRouter()
const { frontMatter } = useConfig()
+
const ogConfig = {
title: 'Gamba',
- description: 'Build your own web3 games with Gamba, a decentralized betting platform on Solana',
+ description:
+ 'Build your own web3 games with Gamba, a decentralized betting platform on Solana',
author: { twitter: 'gambalabs' },
favicon: '/gamba.svg',
}
- const favicon = String(ogConfig.favicon)
- const title = String(frontMatter.title || ogConfig.title)
- const description = String(frontMatter.description || ogConfig.description)
+
+ const title = frontMatter.title ?? ogConfig.title
+ const description = frontMatter.description ?? ogConfig.description
const canonical = new URL(asPath, 'https://gamba.so').toString()
const image = 'https://www.gamba.so/og.png'
@@ -35,13 +52,16 @@ const config: DocsThemeConfig = {
+
-
+
+
-
-
+
+
+
@@ -57,19 +77,17 @@ const config: DocsThemeConfig = {
>
)
},
- sidebar: { toggleButton: true, defaultMenuCollapseLevel: 1 },
- useNextSeoProps() {
- const { asPath } = useRouter()
-
- if (['/', '/docs'].includes(asPath)) {
- return { titleTemplate: 'Gamba' }
- }
- return { titleTemplate: '%s | Gamba' }
+ sidebar: {
+ toggleButton: true,
+ defaultMenuCollapseLevel: 1,
},
- primaryHue: {
- light: 270,
- dark: 204,
+
+ color: {
+ hue: {
+ light: 270,
+ dark: 204,
+ },
},
}
diff --git a/apps/website/tsconfig.json b/apps/website/tsconfig.json
index 1563f3e8..620349f3 100644
--- a/apps/website/tsconfig.json
+++ b/apps/website/tsconfig.json
@@ -15,6 +15,6 @@
"isolatedModules": true,
"jsx": "preserve"
},
- "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "pages/index.mdx", "pages/games.mdx"],
"exclude": ["node_modules"]
}
diff --git a/package.json b/package.json
index 8eeb2dd0..3d79ccd4 100644
--- a/package.json
+++ b/package.json
@@ -1,5 +1,8 @@
{
"private": true,
+ "engines": {
+ "node": ">=22 <23"
+ },
"scripts": {
"build": "turbo run build",
"dev": "turbo run dev --filter=!gamba-api --no-cache --continue",
@@ -17,7 +20,11 @@
"packageManager": "pnpm@10.12.4",
"pnpm": {
"overrides": {
+ "react": "18.3.1",
+ "react-dom": "18.3.1",
+ "@solana/web3.js": "1.98.2",
+ "@solana/spl-token": "0.4.13",
"@solana/wallet-adapter-react": "^0.15.39"
}
}
-}
+}
\ No newline at end of file
diff --git a/packages/core/package.json b/packages/core/package.json
index 3313a0f7..9dc69d75 100644
--- a/packages/core/package.json
+++ b/packages/core/package.json
@@ -1,38 +1,37 @@
{
"name": "gamba-core-v2",
- "private": false,
"version": "0.4.1",
+ "private": false,
"main": "dist/index.js",
"module": "dist/index.mjs",
"types": "dist/index.d.ts",
"sideEffects": false,
- "files": [
- "dist/**"
- ],
- "publishConfig": {
- "access": "public"
- },
+ "files": ["dist/**"],
"scripts": {
- "dev": "tsup src/index.ts --watch --format cjs,esm --dts",
+ "dev": "tsup src/index.ts --watch --format cjs,esm --dts",
"build": "tsup src/index.ts --format cjs,esm --dts",
- "lint": "tsc",
- "clean": "rm -rf .turbo && rm -rf node_modules && rm -rf dist"
+ "lint": "tsc",
+ "clean": "rm -rf .turbo node_modules dist"
},
"dependencies": {
- "@coral-xyz/anchor": "^0.27.0",
- "@solana/spl-token": "^0.3.8",
- "@solana/web3.js": "^1.93.0"
+ "@coral-xyz/anchor": "^0.31.1",
+ "@solana/spl-token": "^0.4.13",
+ "@solana/web3.js": "^1.98.2",
+ "buffer": "^6.0.3"
+ },
+ "peerDependencies": {
+ "@coral-xyz/anchor": "^0.31.1",
+ "@solana/spl-token": "^0.4.13",
+ "@solana/web3.js": "^1.98.2"
},
"devDependencies": {
- "eslint": "^8.48.0",
- "tsup": "^7.2.0",
- "typescript": "^5.2.2"
+ "@types/node": "^24.0.10",
+ "eslint": "^9.30.1",
+ "tsup": "^8.5.0",
+ "typescript": "^5.2.2"
},
- "peerDependencies": {
- "@coral-xyz/anchor": "^0.27.0",
- "@solana/web3.js": "^1.93.0"
+ "publishConfig": {
+ "access": "public"
},
- "keywords": [],
- "author": "",
"license": "MIT"
}
diff --git a/packages/core/src/GambaProvider.ts b/packages/core/src/GambaProvider.ts
index 9003f180..ba04a65a 100644
--- a/packages/core/src/GambaProvider.ts
+++ b/packages/core/src/GambaProvider.ts
@@ -1,15 +1,16 @@
import * as anchor from '@coral-xyz/anchor'
+import { Buffer } from 'buffer'
import NodeWallet from '@coral-xyz/anchor/dist/cjs/nodewallet'
import { ASSOCIATED_TOKEN_PROGRAM_ID, TOKEN_PROGRAM_ID, getAssociatedTokenAddressSync } from '@solana/spl-token'
import { AddressLookupTableProgram, ConfirmOptions, Connection, Keypair, PublicKey, SYSVAR_RENT_PUBKEY, SystemProgram } from '@solana/web3.js'
import { PROGRAM_ID } from './constants'
-import { Gamba as GambaIdl, IDL } from './idl'
-import { getGambaStateAddress, getGameAddress, getPlayerAddress, getPoolAddress, getPoolBonusAddress, getPoolLpAddress, getPoolUnderlyingTokenAccountAddress } from './pdas'
+import { Gamba, IDL } from './idl'
+import { getGambaStateAddress, getGameAddress, getPlayerAddress, getPoolAddress, getPoolBonusAddress, getPoolLpAddress, getPoolUnderlyingTokenAccountAddress, getPoolBonusUnderlyingTokenAccountAddress, getPoolJackpotTokenAccountAddress } from './pdas'
import { GambaProviderWallet } from './types'
import { basisPoints } from './utils'
export class GambaProvider {
- gambaProgram: anchor.Program
+ gambaProgram: anchor.Program
anchorProvider: anchor.AnchorProvider
wallet: GambaProviderWallet
@@ -25,7 +26,7 @@ export class GambaProvider {
wallet,
opts,
)
- this.gambaProgram = new anchor.Program(IDL, PROGRAM_ID, this.anchorProvider)
+ this.gambaProgram = new anchor.Program(IDL, this.anchorProvider)
this.wallet = wallet
}
@@ -51,192 +52,242 @@ export class GambaProvider {
* @param slot The slot to use for the lookup table instruction
* @returns Multiple TransactionInstruction in an array
*/
- createPool(underlyingTokenMint: PublicKey, authority: PublicKey, slot: number) {
- const gambaStateAta = getAssociatedTokenAddressSync(
- underlyingTokenMint,
- getGambaStateAddress(),
- true,
+ createPool(
+ underlyingTokenMint: PublicKey,
+ authority: PublicKey,
+ slot: number,
+ ) {
+ // … compute all your PDAs exactly as before …
+ const pool = getPoolAddress(underlyingTokenMint, authority)
+ const poolUnderlyingTA = getPoolUnderlyingTokenAccountAddress(pool)
+ const [poolBonusUnderlyingTA] = PublicKey.findProgramAddressSync(
+ [Buffer.from('POOL_BONUS_UNDERLYING_TA'), pool.toBuffer()],
+ PROGRAM_ID,
+ )
+ const gamba_state = getGambaStateAddress()
+ const gambaStateAta = getAssociatedTokenAddressSync(underlyingTokenMint, gamba_state, true)
+ const poolJackpotTA = PublicKey.findProgramAddressSync(
+ [Buffer.from('POOL_JACKPOT'), pool.toBuffer()],
+ PROGRAM_ID,
+ )[0]
+ const lpMint = getPoolLpAddress(pool)
+ const bonusMint = getPoolBonusAddress(pool)
+ const TOKEN_METADATA = new PublicKey('metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s')
+ const METADATA_SEED = 'metadata'
+ const [lpMintMetadata] = PublicKey.findProgramAddressSync(
+ [Buffer.from(METADATA_SEED), TOKEN_METADATA.toBuffer(), lpMint.toBuffer()],
+ TOKEN_METADATA,
+ )
+ const [bonusMintMetadata] = PublicKey.findProgramAddressSync(
+ [Buffer.from(METADATA_SEED), TOKEN_METADATA.toBuffer(), bonusMint.toBuffer()],
+ TOKEN_METADATA,
)
- const METADATA_SEED = 'metadata'
- const TOKEN_METADATA_PROGRAM_ID = new PublicKey('metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s')
- const pool = getPoolAddress(underlyingTokenMint, authority)
- const lpMint = getPoolLpAddress(pool)
- const bonusMint = getPoolBonusAddress(pool)
- const poolUnderlyingTokenAccount = getPoolUnderlyingTokenAccountAddress(pool)
- const [poolBonusUnderlyingTokenAccount] = PublicKey.findProgramAddressSync([Buffer.from('POOL_BONUS_UNDERLYING_TA'), pool.toBuffer()], PROGRAM_ID)
- const [lpMintMetadata] = PublicKey.findProgramAddressSync([Buffer.from(METADATA_SEED), TOKEN_METADATA_PROGRAM_ID.toBuffer(), lpMint.toBuffer()], TOKEN_METADATA_PROGRAM_ID)
- const [bonusMintMetadata] = PublicKey.findProgramAddressSync([Buffer.from(METADATA_SEED), TOKEN_METADATA_PROGRAM_ID.toBuffer(), bonusMint.toBuffer()], TOKEN_METADATA_PROGRAM_ID)
-
- //more addresses for lookup table
- const gamba_state = getGambaStateAddress()
- const poolJackpotTokenAccount = PublicKey.findProgramAddressSync([Buffer.from('POOL_JACKPOT'), pool.toBuffer()], PROGRAM_ID)[0]
- const [lookupTableInst, lookupTableAddress] = AddressLookupTableProgram.createLookupTable({
- authority: this.wallet.publicKey,
- payer: this.wallet.publicKey,
+ const [lutCreateIx, lutAddress] = AddressLookupTableProgram.createLookupTable({
+ authority: this.user,
+ payer: this.user,
recentSlot: slot - 1,
})
- const addAddressesInstruction = AddressLookupTableProgram.extendLookupTable({
- payer: this.wallet.publicKey,
- authority: this.wallet.publicKey,
- lookupTable: lookupTableAddress,
+ const lutExtendIx = AddressLookupTableProgram.extendLookupTable({
+ payer: this.user,
+ authority: this.user,
+ lookupTable: lutAddress,
addresses: [
- pool,
- underlyingTokenMint,
- poolUnderlyingTokenAccount,
- poolBonusUnderlyingTokenAccount,
- gamba_state,
- gambaStateAta,
- bonusMint,
- poolJackpotTokenAccount,
+ pool, underlyingTokenMint, poolUnderlyingTA,
+ poolBonusUnderlyingTA, gamba_state, gambaStateAta,
+ bonusMint, poolJackpotTA,
],
})
- const freezeInstruction = AddressLookupTableProgram.freezeLookupTable({
- authority: this.wallet.publicKey,
- lookupTable: lookupTableAddress,
+ const lutFreezeIx = AddressLookupTableProgram.freezeLookupTable({
+ authority: this.user,
+ lookupTable: lutAddress,
})
- const createPoolInstruction = this.gambaProgram.methods
- .poolInitialize(authority, lookupTableAddress)
- .accounts({
- initializer: this.wallet.publicKey,
- gambaState: getGambaStateAddress(),
- underlyingTokenMint: underlyingTokenMint,
- pool,
- poolUnderlyingTokenAccount,
- poolBonusUnderlyingTokenAccount,
- gambaStateAta,
- lpMint,
- lpMintMetadata,
- bonusMint,
- bonusMintMetadata,
- associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,
- tokenProgram: TOKEN_PROGRAM_ID,
- systemProgram: SystemProgram.programId,
- rent: SYSVAR_RENT_PUBKEY,
- tokenMetadataProgram: TOKEN_METADATA_PROGRAM_ID,
- })
+ // ——— HERE is the switch to accountsPartial ———
+ const accs: Record = {
+ initializer: this.user,
+ gambaState: gamba_state,
+ underlyingTokenMint,
+ pool,
+ poolUnderlyingTokenAccount: poolUnderlyingTA,
+ poolBonusUnderlyingTokenAccount: poolBonusUnderlyingTA,
+ gambaStateAta,
+ lpMint,
+ lpMintMetadata,
+ bonusMint,
+ bonusMintMetadata,
+ associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,
+ tokenProgram: TOKEN_PROGRAM_ID,
+ systemProgram: SystemProgram.programId,
+ rent: SYSVAR_RENT_PUBKEY,
+ tokenMetadataProgram: TOKEN_METADATA,
+ }
+
+ const createPoolIx = this.gambaProgram.methods
+ .poolInitialize(authority, lutAddress)
+ .accountsPartial(accs as any)
.instruction()
- return [lookupTableInst, addAddressesInstruction, freezeInstruction, createPoolInstruction]
+ return [lutCreateIx, lutExtendIx, lutFreezeIx, createPoolIx]
}
/**
- *
- * @param pool The pool to deposit to
- * @param underlyingTokenMint Token to deposit (Has to be the same as pool.underlyingTokenMint)
- * @param amount Amount of tokens to deposit
- */
+ *
+ * @param pool The pool to deposit to
+ * @param underlyingTokenMint Token to deposit (Has to be the same as pool.underlyingTokenMint)
+ * @param amount Amount of tokens to deposit
+ */
depositToPool(
pool: PublicKey,
underlyingTokenMint: PublicKey,
- amount: bigint | number,
+ amount: number | bigint,
) {
+ // PDAs
const poolUnderlyingTokenAccount = getPoolUnderlyingTokenAccountAddress(pool)
- const poolLpMint = getPoolLpAddress(pool)
+ const poolLpMint = getPoolLpAddress(pool)
+ const gambaState = getGambaStateAddress()
+ // User ATAs
const userUnderlyingAta = getAssociatedTokenAddressSync(
underlyingTokenMint,
this.wallet.publicKey,
)
-
const userLpAta = getAssociatedTokenAddressSync(
poolLpMint,
this.wallet.publicKey,
)
+ // build a loose map of all accounts
+ const accs: Record = {
+ user: this.wallet.publicKey,
+ gambaState,
+ pool,
+ underlyingTokenMint,
+ poolUnderlyingTokenAccount,
+ lpMint: poolLpMint,
+ userUnderlyingAta,
+ userLpAta,
+ associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,
+ tokenProgram: TOKEN_PROGRAM_ID,
+ systemProgram: SystemProgram.programId,
+ }
+
return this.gambaProgram.methods
.poolDeposit(new anchor.BN(amount))
- .accounts({
- pool,
- underlyingTokenMint,
- poolUnderlyingTokenAccount,
- userUnderlyingAta,
- userLpAta,
- })
+ .accountsPartial(accs as any)
.instruction()
}
/**
- *
- * @param pool The pool to withdraw from
- * @param underlyingTokenMint Token to withdraw (Has to be the same as pool.underlyingTokenMint)
- * @param amount Amount of tokens to withdraw
- */
+ *
+ * @param pool The pool to withdraw from
+ * @param underlyingTokenMint Token to withdraw (Has to be the same as pool.underlyingTokenMint)
+ * @param amount Amount of tokens to withdraw
+ */
withdrawFromPool(
pool: PublicKey,
underlyingTokenMint: PublicKey,
- amount: bigint | number,
+ amount: number | bigint,
) {
- const poolUnderlyingTokenAccount = getPoolUnderlyingTokenAccountAddress(pool)
- const poolLpMint = getPoolLpAddress(pool)
-
+ const poolUnderlyingTA = getPoolUnderlyingTokenAccountAddress(pool)
+ const poolLpMint = getPoolLpAddress(pool)
+ const gambaState = getGambaStateAddress()
const userUnderlyingAta = getAssociatedTokenAddressSync(
underlyingTokenMint,
this.wallet.publicKey,
)
-
const userLpAta = getAssociatedTokenAddressSync(
poolLpMint,
this.wallet.publicKey,
)
+ const accs: Record = {
+ user: this.wallet.publicKey,
+ gambaState,
+ pool,
+ underlyingTokenMint,
+ poolUnderlyingTokenAccount: poolUnderlyingTA,
+ lpMint: poolLpMint,
+ userUnderlyingAta,
+ userLpAta,
+ associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,
+ tokenProgram: TOKEN_PROGRAM_ID,
+ systemProgram: SystemProgram.programId,
+ }
+
return this.gambaProgram.methods
.poolWithdraw(new anchor.BN(amount))
- .accounts({
- pool,
- underlyingTokenMint,
- poolUnderlyingTokenAccount,
- userUnderlyingAta,
- userLpAta,
- })
+ .accountsPartial(accs as any)
.instruction()
}
/**
* Mints bonus tokens that can be used as free plays in the pool
* @param pool Pool to mint bonus tokens for
- * @param underlyingTokenMint Token to mint bonus tokens for (Has to be equal to pool.underlyingTokenMint)
+ * @param underlyingTokenMint Token to mint bonus tokens for
* @param amount Amount of bonus tokens to mint
*/
mintBonusTokens(
pool: PublicKey,
underlyingTokenMint: PublicKey,
- amount: bigint | number,
+ amount: number | bigint,
) {
- const poolBonusMint = getPoolBonusAddress(pool)
-
+ const bonusMint = getPoolBonusAddress(pool)
+ const gambaState = getGambaStateAddress()
const userUnderlyingAta = getAssociatedTokenAddressSync(
underlyingTokenMint,
this.wallet.publicKey,
)
-
- const userBonusAta = getAssociatedTokenAddressSync(
- poolBonusMint,
+ const userBonusAta = getAssociatedTokenAddressSync(
+ bonusMint,
this.wallet.publicKey,
)
+ const poolBonusUnderlyingTA = getPoolBonusUnderlyingTokenAccountAddress(pool)
+ const poolJackpotTA = getPoolJackpotTokenAccountAddress(pool)
+
+ const accs: Record = {
+ user: this.wallet.publicKey,
+ gambaState: gambaState,
+ pool: pool,
+ underlyingTokenMint: underlyingTokenMint,
+ bonusMint: bonusMint,
+ userUnderlyingAta: userUnderlyingAta,
+ userBonusAta: userBonusAta,
+ poolBonusUnderlyingTokenAccount: poolBonusUnderlyingTA,
+ poolJackpotTokenAccount: poolJackpotTA,
+ associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,
+ tokenProgram: TOKEN_PROGRAM_ID,
+ systemProgram: SystemProgram.programId,
+ rent: SYSVAR_RENT_PUBKEY,
+ }
+
return this.gambaProgram.methods
.poolMintBonusTokens(new anchor.BN(amount))
- .accounts({
- pool,
- user: this.wallet.publicKey,
- underlyingTokenMint,
- userUnderlyingAta,
- userBonusAta,
- })
+ .accountsPartial(accs as any)
.instruction()
}
+
/**
* Initializes an associated Player account for the connected wallet
*/
createPlayer() {
+ const player = getPlayerAddress(this.wallet.publicKey)
+ const game = getGameAddress(this.wallet.publicKey)
+
+ const accs: Record = {
+ player,
+ game,
+ user: this.wallet.publicKey,
+ systemProgram: SystemProgram.programId,
+ }
+
return this.gambaProgram.methods
.playerInitialize()
- .accounts({})
+ .accountsPartial(accs as any)
.instruction()
}
@@ -244,10 +295,18 @@ export class GambaProvider {
* Closes the associated Player account for the connected wallet
*/
closePlayer() {
- const gameAddress = getGameAddress(this.user)
+ const player = getPlayerAddress(this.wallet.publicKey)
+ const game = getGameAddress(this.wallet.publicKey)
+
+ const accs = {
+ player,
+ game,
+ user: this.wallet.publicKey,
+ }
+
return this.gambaProgram.methods
.playerClose()
- .accounts({ game: gameAddress })
+ .accountsPartial(accs as any)
.instruction()
}
@@ -261,20 +320,20 @@ export class GambaProvider {
creatorFee: number,
jackpotFee: number,
metadata: string,
- useBonus: boolean,
+ useBonus = false,
) {
- const player = getPlayerAddress(this.user)
+ const player = getPlayerAddress(this.wallet.publicKey)
+ const game = getGameAddress(this.wallet.publicKey)
+ const gambaState = getGambaStateAddress()
const userUnderlyingAta = getAssociatedTokenAddressSync(
underlyingTokenMint,
- this.user,
+ this.wallet.publicKey,
)
-
const creatorAta = getAssociatedTokenAddressSync(
underlyingTokenMint,
creator,
)
-
const playerAta = getAssociatedTokenAddressSync(
underlyingTokenMint,
player,
@@ -284,15 +343,39 @@ export class GambaProvider {
const bonusMint = getPoolBonusAddress(pool)
const userBonusAta = getAssociatedTokenAddressSync(
bonusMint,
- this.user,
+ this.wallet.publicKey,
)
-
const playerBonusAta = getAssociatedTokenAddressSync(
bonusMint,
- getPlayerAddress(this.user),
+ player,
true,
)
+ const poolJackpotTA = PublicKey.findProgramAddressSync(
+ [Buffer.from('POOL_JACKPOT'), pool.toBuffer()],
+ PROGRAM_ID,
+ )[0]
+
+ const accs: Record = {
+ user: this.wallet.publicKey,
+ player,
+ game,
+ gambaState,
+ pool,
+ underlyingTokenMint,
+ bonusTokenMint: bonusMint,
+ userUnderlyingAta,
+ creator,
+ creatorAta,
+ playerAta,
+ playerBonusAta: useBonus ? playerBonusAta : null,
+ userBonusAta: useBonus ? userBonusAta : null,
+ poolJackpotTokenAccount: poolJackpotTA,
+ systemProgram: SystemProgram.programId,
+ tokenProgram: TOKEN_PROGRAM_ID,
+ associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,
+ }
+
return this.gambaProgram.methods
.playGame(
new anchor.BN(wager),
@@ -302,16 +385,7 @@ export class GambaProvider {
basisPoints(jackpotFee),
metadata,
)
- .accounts({
- pool,
- userUnderlyingAta,
- underlyingTokenMint,
- creator,
- creatorAta,
- playerAta,
- playerBonusAta: useBonus ? playerBonusAta : null,
- userBonusAta: useBonus ? userBonusAta : null,
- })
+ .accountsPartial(accs as any)
.instruction()
}
}
diff --git a/packages/core/src/decoders.ts b/packages/core/src/decoders.ts
index 75ff1032..cb833aae 100644
--- a/packages/core/src/decoders.ts
+++ b/packages/core/src/decoders.ts
@@ -1,31 +1,56 @@
import { BorshAccountsCoder, IdlAccounts } from '@coral-xyz/anchor'
import { AccountLayout } from '@solana/spl-token'
-import { AccountInfo } from '@solana/web3.js'
-import { GambaIdl } from '.'
+import type { AccountInfo } from '@solana/web3.js'
+import type { GambaIdl } from '.'
import { IDL } from './idl'
const accountsCoder = new BorshAccountsCoder(IDL)
-const decodeAccount = (accountName: string, info: AccountInfo | null) => {
- if (!info?.data?.length)
- return null
+/** Decode any account by its Anchor name, returning `T | null` */
+function decodeAccount(
+ accountName: string,
+ info: AccountInfo | null,
+): T | null {
+ if (!info?.data?.length) return null
return accountsCoder.decode(accountName, info.data)
}
-export const decodeAta = (acc: AccountInfo | null) => {
+/** Standard SPL‐Token ATA decoder */
+export function decodeAta(
+ acc: AccountInfo | null
+): ReturnType | null {
if (!acc) return null
return AccountLayout.decode(acc.data)
}
type GambaAccounts = IdlAccounts
-const makeDecoder = (accountName: N) => {
- return (info: AccountInfo | null) => {
- return decodeAccount(accountName, info) as GambaAccounts[N] | null
- }
+/** Factory for a strongly-typed Anchor account decoder */
+function makeDecoder<
+ N extends Extract
+>(
+ accountName: N
+): (info: AccountInfo | null) => GambaAccounts[N] | null {
+ return (info) => decodeAccount(accountName, info)
}
-export const decodePlayer = makeDecoder('player')
-export const decodeGame = makeDecoder('game')
-export const decodePool = makeDecoder('pool')
-export const decodeGambaState = makeDecoder('gambaState')
+// ─── THESE NAMES MUST MATCH YOUR IDL ACCOUNTS[].name ──────────────────────────
+export const decodePlayer: (
+ info: AccountInfo | null
+) => GambaAccounts['Player'] | null =
+ makeDecoder('Player')
+
+export const decodeGame: (
+ info: AccountInfo | null
+) => GambaAccounts['Game'] | null =
+ makeDecoder('Game')
+
+export const decodePool: (
+ info: AccountInfo | null
+) => GambaAccounts['Pool'] | null =
+ makeDecoder('Pool')
+
+export const decodeGambaState: (
+ info: AccountInfo | null
+) => GambaAccounts['GambaState'] | null =
+ makeDecoder('GambaState')
diff --git a/packages/core/src/events.ts b/packages/core/src/events.ts
index 50705930..7af9ddbe 100644
--- a/packages/core/src/events.ts
+++ b/packages/core/src/events.ts
@@ -1,6 +1,20 @@
+// packages/core/src/events.ts
+
import { BorshCoder, EventParser } from '@coral-xyz/anchor'
-import { Connection, ParsedTransactionWithMeta, PublicKey, SignaturesForAddressOptions } from '@solana/web3.js'
-import { AnyGambaEvent, GambaEvent, GambaEventType, IDL, PROGRAM_ID } from '.'
+import {
+ Connection,
+ ParsedTransactionWithMeta,
+ PublicKey,
+ ConfirmedSignatureInfo,
+ SignaturesForAddressOptions,
+} from '@solana/web3.js'
+import {
+ AnyGambaEvent,
+ GambaEvent,
+ GambaEventType,
+ IDL,
+ PROGRAM_ID,
+} from '.'
export type GambaTransaction = {
signature: string
@@ -9,72 +23,142 @@ export type GambaTransaction = {
data: GambaEvent['data']
}
-const eventParser = new EventParser(PROGRAM_ID, new BorshCoder(IDL))
+const coder = new BorshCoder(IDL)
+const parser = new EventParser(PROGRAM_ID, coder)
-/**
- * Extracts events from transaction logs
- */
-export const parseTransactionEvents = (logs: string[]) => {
+/** Extract Anchor events from raw logs */
+export function parseTransactionEvents(logs: string[]): AnyGambaEvent[] {
try {
- const parsedEvents: AnyGambaEvent[] = []
- const events = eventParser.parseLogs(logs) as any as AnyGambaEvent[]
- for (const event of events) {
- parsedEvents.push(event)
- }
- return parsedEvents
+ return parser.parseLogs(logs) as any as AnyGambaEvent[]
} catch {
return []
}
}
-/**
- * Extracts events from a transaction
+/**
+ * 🔴 @deprecated — use `fetchRecentLogs` for real blockTime timestamps
*/
-export const parseGambaTransaction = (
+export function parseGambaTransaction(
transaction: ParsedTransactionWithMeta,
-) => {
+): GambaTransaction[] {
+ const blockTime = transaction.blockTime ?? 0
const logs = transaction.meta?.logMessages ?? []
- const events = parseTransactionEvents(logs)
-
- return events.map((event) => {
- return {
- signature: transaction.transaction.signatures[0],
- time: (transaction.blockTime ?? 0) * 1000,
- name: event.name,
- data: event.data,
- } as GambaTransaction<'GameSettled'> | GambaTransaction<'PoolChange'>
- })
+ const evts = parseTransactionEvents(logs)
+ return evts.map((ev) => ({
+ signature: transaction.transaction.signatures[0],
+ time: blockTime * 1000,
+ // cast string → literal type
+ name: ev.name as GambaEventType,
+ data: ev.data,
+ }))
}
+/** 🔴 @deprecated — replaced by `fetchRecentLogs` */
export async function fetchGambaTransactionsFromSignatures(
connection: Connection,
signatures: string[],
-) {
- const transactions = (await connection.getParsedTransactions(
+): Promise[]> {
+ const txns = await connection.getParsedTransactions(
signatures,
- {
- maxSupportedTransactionVersion: 0,
- commitment: 'confirmed',
- },
- )).flatMap((x) => x ? [x] : [])
-
- return transactions.flatMap(parseGambaTransaction)
+ { maxSupportedTransactionVersion: 0, commitment: 'confirmed' },
+ )
+ return txns.flatMap((tx) => (tx ? parseGambaTransaction(tx) : []))
}
-/**
- * Fetches recent Gamba events
- */
+/** 🔴 @deprecated — replaced by `fetchRecentLogs` */
export async function fetchGambaTransactions(
connection: Connection,
address: PublicKey,
options: SignaturesForAddressOptions,
-) {
- const signatureInfo = await connection.getSignaturesForAddress(
+): Promise[]> {
+ const sigInfos = await connection.getSignaturesForAddress(
address,
options,
'confirmed',
)
- const events = await fetchGambaTransactionsFromSignatures(connection, signatureInfo.map((x) => x.signature))
+ const sigs = sigInfos.map((s) => s.signature)
+ return fetchGambaTransactionsFromSignatures(connection, sigs)
+}
+
+// ─── NEW, lightweight history API ──────────────────────────────────────
- return events
+/**
+ * Fetch recent events using only program logs, but still pull full transactions
+ * so we can surface their on‐chain blockTime accurately.
+ */
+export async function fetchRecentLogs(
+ connection: Connection,
+ address: PublicKey = PROGRAM_ID,
+ limit = 30,
+): Promise[]> {
+ // 1) get latest signatures
+ const infos: ConfirmedSignatureInfo[] = await connection.getSignaturesForAddress(
+ address,
+ { limit },
+ 'confirmed',
+ )
+ const sigs = infos.map((i) => i.signature)
+
+ // 2) fetch full parsed transactions
+ const txns = await connection.getParsedTransactions(sigs, {
+ maxSupportedTransactionVersion: 0,
+ commitment: 'confirmed',
+ })
+
+ // 3) extract events with real timestamps
+ const out: GambaTransaction[] = []
+ for (const tx of txns) {
+ if (!tx) continue
+ const ts = (tx.blockTime ?? Math.floor(Date.now() / 1000)) * 1000
+ const evts = parseTransactionEvents(tx.meta?.logMessages ?? [])
+ for (const ev of evts) {
+ out.push({
+ signature: tx.transaction.signatures[0],
+ time: ts,
+ name: ev.name as GambaEventType,
+ data: ev.data as any,
+ })
+ }
+ }
+ return out
+}
+
+/**
+ * Subscribe to raw program logs, parse Anchor events, and invoke callback
+ */
+export function subscribeGambaLogs(
+ connection: Connection,
+ address: PublicKey = PROGRAM_ID,
+ callback: (evt: GambaTransaction) => void,
+): number {
+ const connAny = connection as any
+ const subId: number = connAny.onLogs(
+ address,
+ (logInfo: { signature?: string; logs: string[] }) => {
+ if (!logInfo.signature) return
+ const now = Date.now()
+ const evts = parseTransactionEvents(logInfo.logs)
+ for (const ev of evts) {
+ callback({
+ signature: logInfo.signature!,
+ time: now,
+ name: ev.name as GambaEventType,
+ data: ev.data as any,
+ })
+ }
+ },
+ 'confirmed',
+ )
+ return subId
+}
+
+/** Unsubscribe from `subscribeGambaLogs` */
+export function unsubscribeGambaLogs(
+ connection: Connection,
+ subId: number,
+) {
+ const connAny = connection as any
+ if (typeof connAny.removeOnLogsListener === 'function') {
+ connAny.removeOnLogsListener(subId)
+ }
}
diff --git a/packages/core/src/idl.ts b/packages/core/src/idl.ts
index 1198cd09..2ce18379 100644
--- a/packages/core/src/idl.ts
+++ b/packages/core/src/idl.ts
@@ -1,55 +1,101 @@
export type Gamba = {
- 'version': '0.1.0',
- 'name': 'gamba',
+ 'address': 'Gamba2hK6KV3quKq854B3sQG1WMdq3zgQLPKqyK4qS18',
+ 'metadata': {
+ 'name': 'gamba',
+ 'version': '0.1.0',
+ 'spec': '0.1.0'
+ },
'instructions': [
{
'name': 'gambaInitialize',
+ 'discriminator': [
+ 255,
+ 140,
+ 190,
+ 102,
+ 152,
+ 30,
+ 179,
+ 112
+ ],
'accounts': [
{
'name': 'initializer',
- 'isMut': true,
- 'isSigner': true
+ 'writable': true,
+ 'signer': true
},
{
'name': 'gambaState',
- 'isMut': true,
- 'isSigner': false,
+ 'writable': true,
'pda': {
'seeds': [
{
'kind': 'const',
- 'type': 'string',
- 'value': 'GAMBA_STATE'
+ 'value': [
+ 34,
+ 71,
+ 65,
+ 77,
+ 66,
+ 65,
+ 95,
+ 83,
+ 84,
+ 65,
+ 84,
+ 69,
+ 34
+ ]
}
]
}
},
{
- 'name': 'systemProgram',
- 'isMut': false,
- 'isSigner': false
+ 'name': 'systemProgram'
}
],
'args': []
},
{
'name': 'gambaSetAuthority',
+ 'discriminator': [
+ 60,
+ 11,
+ 159,
+ 59,
+ 150,
+ 12,
+ 106,
+ 78
+ ],
'accounts': [
{
'name': 'authority',
- 'isMut': true,
- 'isSigner': true
+ 'writable': true,
+ 'signer': true
},
{
'name': 'gambaState',
- 'isMut': true,
- 'isSigner': false,
+ 'writable': true,
'pda': {
'seeds': [
{
'kind': 'const',
- 'type': 'string',
- 'value': 'GAMBA_STATE'
+ 'value': [
+ 34,
+ 71,
+ 65,
+ 77,
+ 66,
+ 65,
+ 95,
+ 83,
+ 84,
+ 65,
+ 84,
+ 69,
+ 34
+ ]
}
]
},
@@ -61,28 +107,50 @@ export type Gamba = {
'args': [
{
'name': 'authority',
- 'type': 'publicKey'
+ 'type': 'pubkey'
}
]
},
{
'name': 'gambaSetConfig',
+ 'discriminator': [
+ 205,
+ 11,
+ 209,
+ 24,
+ 204,
+ 47,
+ 25,
+ 186
+ ],
'accounts': [
{
'name': 'authority',
- 'isMut': true,
- 'isSigner': true
+ 'writable': true,
+ 'signer': true
},
{
'name': 'gambaState',
- 'isMut': true,
- 'isSigner': false,
+ 'writable': true,
'pda': {
'seeds': [
{
'kind': 'const',
- 'type': 'string',
- 'value': 'GAMBA_STATE'
+ 'value': [
+ 34,
+ 71,
+ 65,
+ 77,
+ 66,
+ 65,
+ 95,
+ 83,
+ 84,
+ 65,
+ 84,
+ 69,
+ 34
+ ]
}
]
},
@@ -94,7 +162,7 @@ export type Gamba = {
'args': [
{
'name': 'rngAddress',
- 'type': 'publicKey'
+ 'type': 'pubkey'
},
{
'name': 'gambaFee',
@@ -166,57 +234,80 @@ export type Gamba = {
},
{
'name': 'distributionRecipient',
- 'type': 'publicKey'
+ 'type': 'pubkey'
}
]
},
{
'name': 'poolInitialize',
+ 'discriminator': [
+ 37,
+ 10,
+ 195,
+ 69,
+ 4,
+ 213,
+ 88,
+ 173
+ ],
'accounts': [
{
'name': 'initializer',
- 'isMut': true,
- 'isSigner': true
+ 'writable': true,
+ 'signer': true
},
{
'name': 'gambaState',
- 'isMut': true,
- 'isSigner': false,
+ 'writable': true,
'pda': {
'seeds': [
{
'kind': 'const',
- 'type': 'string',
- 'value': 'GAMBA_STATE'
+ 'value': [
+ 34,
+ 71,
+ 65,
+ 77,
+ 66,
+ 65,
+ 95,
+ 83,
+ 84,
+ 65,
+ 84,
+ 69,
+ 34
+ ]
}
]
}
},
{
- 'name': 'underlyingTokenMint',
- 'isMut': false,
- 'isSigner': false
+ 'name': 'underlyingTokenMint'
},
{
'name': 'pool',
- 'isMut': true,
- 'isSigner': false,
+ 'writable': true,
'pda': {
'seeds': [
{
'kind': 'const',
- 'type': 'string',
- 'value': 'POOL'
+ 'value': [
+ 34,
+ 80,
+ 79,
+ 79,
+ 76,
+ 34
+ ]
},
{
'kind': 'account',
- 'type': 'publicKey',
- 'account': 'Mint',
- 'path': 'underlying_token_mint'
+ 'path': 'underlying_token_mint',
+ 'account': 'Mint'
},
{
'kind': 'arg',
- 'type': 'publicKey',
'path': 'pool_authority'
}
]
@@ -224,252 +315,340 @@ export type Gamba = {
},
{
'name': 'poolUnderlyingTokenAccount',
- 'isMut': true,
- 'isSigner': false,
+ 'writable': true,
'pda': {
'seeds': [
{
'kind': 'const',
- 'type': 'string',
- 'value': 'POOL_ATA'
+ 'value': [
+ 34,
+ 80,
+ 79,
+ 79,
+ 76,
+ 95,
+ 65,
+ 84,
+ 65,
+ 34
+ ]
},
{
'kind': 'account',
- 'type': 'publicKey',
- 'account': 'Pool',
- 'path': 'pool'
+ 'path': 'pool',
+ 'account': 'Pool'
}
]
}
},
{
'name': 'poolBonusUnderlyingTokenAccount',
- 'isMut': true,
- 'isSigner': false,
+ 'writable': true,
'pda': {
'seeds': [
{
'kind': 'const',
- 'type': 'string',
- 'value': 'POOL_BONUS_UNDERLYING_TA'
+ 'value': [
+ 34,
+ 80,
+ 79,
+ 79,
+ 76,
+ 95,
+ 66,
+ 79,
+ 78,
+ 85,
+ 83,
+ 95,
+ 85,
+ 78,
+ 68,
+ 69,
+ 82,
+ 76,
+ 89,
+ 73,
+ 78,
+ 71,
+ 95,
+ 84,
+ 65,
+ 34
+ ]
},
{
'kind': 'account',
- 'type': 'publicKey',
- 'account': 'Pool',
- 'path': 'pool'
+ 'path': 'pool',
+ 'account': 'Pool'
}
]
}
},
{
'name': 'poolJackpotTokenAccount',
- 'isMut': true,
- 'isSigner': false,
+ 'writable': true,
'pda': {
'seeds': [
{
'kind': 'const',
- 'type': 'string',
- 'value': 'POOL_JACKPOT'
+ 'value': [
+ 34,
+ 80,
+ 79,
+ 79,
+ 76,
+ 95,
+ 74,
+ 65,
+ 67,
+ 75,
+ 80,
+ 79,
+ 84,
+ 34
+ ]
},
{
'kind': 'account',
- 'type': 'publicKey',
- 'account': 'Pool',
- 'path': 'pool'
+ 'path': 'pool',
+ 'account': 'Pool'
}
]
}
},
{
'name': 'gambaStateAta',
- 'isMut': true,
- 'isSigner': false
+ 'writable': true
},
{
'name': 'lpMint',
- 'isMut': true,
- 'isSigner': false,
+ 'writable': true,
'pda': {
'seeds': [
{
'kind': 'const',
- 'type': 'string',
- 'value': 'POOL_LP_MINT'
+ 'value': [
+ 34,
+ 80,
+ 79,
+ 79,
+ 76,
+ 95,
+ 76,
+ 80,
+ 95,
+ 77,
+ 73,
+ 78,
+ 84,
+ 34
+ ]
},
{
'kind': 'account',
- 'type': 'publicKey',
- 'account': 'Pool',
- 'path': 'pool'
+ 'path': 'pool',
+ 'account': 'Pool'
}
]
}
},
{
'name': 'lpMintMetadata',
- 'isMut': true,
- 'isSigner': false
+ 'writable': true
},
{
'name': 'bonusMint',
- 'isMut': true,
- 'isSigner': false,
+ 'writable': true,
'pda': {
'seeds': [
{
'kind': 'const',
- 'type': 'string',
- 'value': 'POOL_BONUS_MINT'
+ 'value': [
+ 34,
+ 80,
+ 79,
+ 79,
+ 76,
+ 95,
+ 66,
+ 79,
+ 78,
+ 85,
+ 83,
+ 95,
+ 77,
+ 73,
+ 78,
+ 84,
+ 34
+ ]
},
{
'kind': 'account',
- 'type': 'publicKey',
- 'account': 'Pool',
- 'path': 'pool'
+ 'path': 'pool',
+ 'account': 'Pool'
}
]
}
},
{
'name': 'bonusMintMetadata',
- 'isMut': true,
- 'isSigner': false
+ 'writable': true
},
{
- 'name': 'associatedTokenProgram',
- 'isMut': false,
- 'isSigner': false
+ 'name': 'associatedTokenProgram'
},
{
- 'name': 'tokenProgram',
- 'isMut': false,
- 'isSigner': false
+ 'name': 'tokenProgram'
},
{
- 'name': 'systemProgram',
- 'isMut': false,
- 'isSigner': false
+ 'name': 'systemProgram'
},
{
- 'name': 'rent',
- 'isMut': false,
- 'isSigner': false
+ 'name': 'rent'
},
{
- 'name': 'tokenMetadataProgram',
- 'isMut': false,
- 'isSigner': false
+ 'name': 'tokenMetadataProgram'
}
],
'args': [
{
'name': 'poolAuthority',
- 'type': 'publicKey'
+ 'type': 'pubkey'
},
{
'name': 'lookupAddress',
- 'type': 'publicKey'
+ 'type': 'pubkey'
}
]
},
{
'name': 'poolDeposit',
+ 'discriminator': [
+ 26,
+ 109,
+ 164,
+ 79,
+ 207,
+ 145,
+ 204,
+ 217
+ ],
'accounts': [
{
'name': 'user',
- 'isMut': true,
- 'isSigner': true
+ 'writable': true,
+ 'signer': true
},
{
'name': 'gambaState',
- 'isMut': false,
- 'isSigner': false,
'pda': {
'seeds': [
{
'kind': 'const',
- 'type': 'string',
- 'value': 'GAMBA_STATE'
+ 'value': [
+ 34,
+ 71,
+ 65,
+ 77,
+ 66,
+ 65,
+ 95,
+ 83,
+ 84,
+ 65,
+ 84,
+ 69,
+ 34
+ ]
}
]
}
},
{
'name': 'pool',
- 'isMut': true,
- 'isSigner': false
+ 'writable': true
},
{
'name': 'poolUnderlyingTokenAccount',
- 'isMut': true,
- 'isSigner': false,
+ 'writable': true,
'pda': {
'seeds': [
{
'kind': 'const',
- 'type': 'string',
- 'value': 'POOL_ATA'
+ 'value': [
+ 34,
+ 80,
+ 79,
+ 79,
+ 76,
+ 95,
+ 65,
+ 84,
+ 65,
+ 34
+ ]
},
{
'kind': 'account',
- 'type': 'publicKey',
- 'account': 'Pool',
- 'path': 'pool'
+ 'path': 'pool',
+ 'account': 'Pool'
}
]
}
},
{
'name': 'lpMint',
- 'isMut': true,
- 'isSigner': false,
+ 'writable': true,
'pda': {
'seeds': [
{
'kind': 'const',
- 'type': 'string',
- 'value': 'POOL_LP_MINT'
+ 'value': [
+ 34,
+ 80,
+ 79,
+ 79,
+ 76,
+ 95,
+ 76,
+ 80,
+ 95,
+ 77,
+ 73,
+ 78,
+ 84,
+ 34
+ ]
},
{
'kind': 'account',
- 'type': 'publicKey',
- 'account': 'Pool',
- 'path': 'pool'
+ 'path': 'pool',
+ 'account': 'Pool'
}
]
}
},
{
- 'name': 'underlyingTokenMint',
- 'isMut': false,
- 'isSigner': false
+ 'name': 'underlyingTokenMint'
},
{
'name': 'userUnderlyingAta',
- 'isMut': true,
- 'isSigner': false
+ 'writable': true
},
{
'name': 'userLpAta',
- 'isMut': true,
- 'isSigner': false
+ 'writable': true
},
{
- 'name': 'associatedTokenProgram',
- 'isMut': false,
- 'isSigner': false
+ 'name': 'associatedTokenProgram'
},
{
- 'name': 'tokenProgram',
- 'isMut': false,
- 'isSigner': false
+ 'name': 'tokenProgram'
},
{
- 'name': 'systemProgram',
- 'isMut': false,
- 'isSigner': false
+ 'name': 'systemProgram'
}
],
'args': [
@@ -481,100 +660,130 @@ export type Gamba = {
},
{
'name': 'poolWithdraw',
+ 'discriminator': [
+ 50,
+ 1,
+ 23,
+ 25,
+ 135,
+ 221,
+ 159,
+ 182
+ ],
'accounts': [
{
'name': 'user',
- 'isMut': true,
- 'isSigner': true
+ 'writable': true,
+ 'signer': true
},
{
'name': 'pool',
- 'isMut': true,
- 'isSigner': false
+ 'writable': true
},
{
'name': 'poolUnderlyingTokenAccount',
- 'isMut': true,
- 'isSigner': false,
+ 'writable': true,
'pda': {
'seeds': [
{
'kind': 'const',
- 'type': 'string',
- 'value': 'POOL_ATA'
+ 'value': [
+ 34,
+ 80,
+ 79,
+ 79,
+ 76,
+ 95,
+ 65,
+ 84,
+ 65,
+ 34
+ ]
},
{
'kind': 'account',
- 'type': 'publicKey',
- 'account': 'Pool',
- 'path': 'pool'
+ 'path': 'pool',
+ 'account': 'Pool'
}
]
}
},
{
'name': 'lpMint',
- 'isMut': true,
- 'isSigner': false,
+ 'writable': true,
'pda': {
'seeds': [
{
'kind': 'const',
- 'type': 'string',
- 'value': 'POOL_LP_MINT'
+ 'value': [
+ 34,
+ 80,
+ 79,
+ 79,
+ 76,
+ 95,
+ 76,
+ 80,
+ 95,
+ 77,
+ 73,
+ 78,
+ 84,
+ 34
+ ]
},
{
'kind': 'account',
- 'type': 'publicKey',
- 'account': 'Pool',
- 'path': 'pool'
+ 'path': 'pool',
+ 'account': 'Pool'
}
]
}
},
{
- 'name': 'underlyingTokenMint',
- 'isMut': false,
- 'isSigner': false
+ 'name': 'underlyingTokenMint'
},
{
'name': 'userUnderlyingAta',
- 'isMut': true,
- 'isSigner': false
+ 'writable': true
},
{
'name': 'userLpAta',
- 'isMut': true,
- 'isSigner': false
+ 'writable': true
},
{
'name': 'gambaState',
- 'isMut': false,
- 'isSigner': false,
'pda': {
'seeds': [
{
'kind': 'const',
- 'type': 'string',
- 'value': 'GAMBA_STATE'
+ 'value': [
+ 34,
+ 71,
+ 65,
+ 77,
+ 66,
+ 65,
+ 95,
+ 83,
+ 84,
+ 65,
+ 84,
+ 69,
+ 34
+ ]
}
]
}
},
{
- 'name': 'associatedTokenProgram',
- 'isMut': false,
- 'isSigner': false
+ 'name': 'associatedTokenProgram'
},
{
- 'name': 'tokenProgram',
- 'isMut': false,
- 'isSigner': false
+ 'name': 'tokenProgram'
},
{
- 'name': 'systemProgram',
- 'isMut': false,
- 'isSigner': false
+ 'name': 'systemProgram'
}
],
'args': [
@@ -586,120 +795,180 @@ export type Gamba = {
},
{
'name': 'poolMintBonusTokens',
+ 'discriminator': [
+ 105,
+ 130,
+ 72,
+ 25,
+ 88,
+ 185,
+ 100,
+ 55
+ ],
'accounts': [
{
'name': 'user',
- 'isMut': true,
- 'isSigner': true
+ 'writable': true,
+ 'signer': true
},
{
- 'name': 'pool',
- 'isMut': false,
- 'isSigner': false
+ 'name': 'pool'
},
{
'name': 'gambaState',
- 'isMut': false,
- 'isSigner': false,
'pda': {
'seeds': [
{
'kind': 'const',
- 'type': 'string',
- 'value': 'GAMBA_STATE'
+ 'value': [
+ 34,
+ 71,
+ 65,
+ 77,
+ 66,
+ 65,
+ 95,
+ 83,
+ 84,
+ 65,
+ 84,
+ 69,
+ 34
+ ]
}
]
}
},
{
- 'name': 'underlyingTokenMint',
- 'isMut': false,
- 'isSigner': false
+ 'name': 'underlyingTokenMint'
},
{
'name': 'poolBonusUnderlyingTokenAccount',
- 'isMut': true,
- 'isSigner': false,
+ 'writable': true,
'pda': {
'seeds': [
{
'kind': 'const',
- 'type': 'string',
- 'value': 'POOL_BONUS_UNDERLYING_TA'
+ 'value': [
+ 34,
+ 80,
+ 79,
+ 79,
+ 76,
+ 95,
+ 66,
+ 79,
+ 78,
+ 85,
+ 83,
+ 95,
+ 85,
+ 78,
+ 68,
+ 69,
+ 82,
+ 76,
+ 89,
+ 73,
+ 78,
+ 71,
+ 95,
+ 84,
+ 65,
+ 34
+ ]
},
{
'kind': 'account',
- 'type': 'publicKey',
- 'account': 'Pool',
- 'path': 'pool'
+ 'path': 'pool',
+ 'account': 'Pool'
}
]
}
},
{
'name': 'bonusMint',
- 'isMut': true,
- 'isSigner': false,
+ 'writable': true,
'pda': {
'seeds': [
{
'kind': 'const',
- 'type': 'string',
- 'value': 'POOL_BONUS_MINT'
+ 'value': [
+ 34,
+ 80,
+ 79,
+ 79,
+ 76,
+ 95,
+ 66,
+ 79,
+ 78,
+ 85,
+ 83,
+ 95,
+ 77,
+ 73,
+ 78,
+ 84,
+ 34
+ ]
},
{
'kind': 'account',
- 'type': 'publicKey',
- 'account': 'Pool',
- 'path': 'pool'
+ 'path': 'pool',
+ 'account': 'Pool'
}
]
}
},
{
'name': 'poolJackpotTokenAccount',
- 'isMut': true,
- 'isSigner': false,
+ 'writable': true,
'pda': {
'seeds': [
{
'kind': 'const',
- 'type': 'string',
- 'value': 'POOL_JACKPOT'
+ 'value': [
+ 34,
+ 80,
+ 79,
+ 79,
+ 76,
+ 95,
+ 74,
+ 65,
+ 67,
+ 75,
+ 80,
+ 79,
+ 84,
+ 34
+ ]
},
{
'kind': 'account',
- 'type': 'publicKey',
- 'account': 'Pool',
- 'path': 'pool'
+ 'path': 'pool',
+ 'account': 'Pool'
}
]
}
},
{
'name': 'userUnderlyingAta',
- 'isMut': true,
- 'isSigner': false
+ 'writable': true
},
{
'name': 'userBonusAta',
- 'isMut': true,
- 'isSigner': false
+ 'writable': true
},
{
- 'name': 'associatedTokenProgram',
- 'isMut': false,
- 'isSigner': false
+ 'name': 'associatedTokenProgram'
},
{
- 'name': 'tokenProgram',
- 'isMut': false,
- 'isSigner': false
+ 'name': 'tokenProgram'
},
{
- 'name': 'systemProgram',
- 'isMut': false,
- 'isSigner': false
+ 'name': 'systemProgram'
}
],
'args': [
@@ -711,30 +980,50 @@ export type Gamba = {
},
{
'name': 'poolAuthorityConfig',
+ 'discriminator': [
+ 58,
+ 12,
+ 184,
+ 118,
+ 14,
+ 99,
+ 110,
+ 17
+ ],
'accounts': [
{
'name': 'user',
- 'isMut': true,
- 'isSigner': true
+ 'writable': true,
+ 'signer': true
},
{
'name': 'gambaState',
- 'isMut': false,
- 'isSigner': false,
'pda': {
'seeds': [
{
'kind': 'const',
- 'type': 'string',
- 'value': 'GAMBA_STATE'
+ 'value': [
+ 34,
+ 71,
+ 65,
+ 77,
+ 66,
+ 65,
+ 95,
+ 83,
+ 84,
+ 65,
+ 84,
+ 69,
+ 34
+ ]
}
]
}
},
{
'name': 'pool',
- 'isMut': true,
- 'isSigner': false
+ 'writable': true
}
],
'args': [
@@ -772,36 +1061,56 @@ export type Gamba = {
},
{
'name': 'depositWhitelistAddress',
- 'type': 'publicKey'
+ 'type': 'pubkey'
}
]
},
{
'name': 'poolGambaConfig',
+ 'discriminator': [
+ 197,
+ 177,
+ 234,
+ 111,
+ 246,
+ 248,
+ 20,
+ 155
+ ],
'accounts': [
{
'name': 'user',
- 'isMut': true,
- 'isSigner': true
+ 'writable': true,
+ 'signer': true
},
{
'name': 'gambaState',
- 'isMut': false,
- 'isSigner': false,
'pda': {
'seeds': [
{
'kind': 'const',
- 'type': 'string',
- 'value': 'GAMBA_STATE'
+ 'value': [
+ 34,
+ 71,
+ 65,
+ 77,
+ 66,
+ 65,
+ 95,
+ 83,
+ 84,
+ 65,
+ 84,
+ 69,
+ 34
+ ]
}
]
}
},
{
'name': 'pool',
- 'isMut': true,
- 'isSigner': false
+ 'writable': true
}
],
'args': [
@@ -821,21 +1130,37 @@ export type Gamba = {
},
{
'name': 'playerInitialize',
+ 'discriminator': [
+ 213,
+ 160,
+ 145,
+ 88,
+ 197,
+ 68,
+ 63,
+ 150
+ ],
'accounts': [
{
'name': 'player',
- 'isMut': true,
- 'isSigner': false,
+ 'writable': true,
'pda': {
'seeds': [
{
'kind': 'const',
- 'type': 'string',
- 'value': 'PLAYER'
+ 'value': [
+ 34,
+ 80,
+ 76,
+ 65,
+ 89,
+ 69,
+ 82,
+ 34
+ ]
},
{
'kind': 'account',
- 'type': 'publicKey',
'path': 'user'
}
]
@@ -843,18 +1168,22 @@ export type Gamba = {
},
{
'name': 'game',
- 'isMut': true,
- 'isSigner': false,
+ 'writable': true,
'pda': {
'seeds': [
{
'kind': 'const',
- 'type': 'string',
- 'value': 'GAME'
+ 'value': [
+ 34,
+ 71,
+ 65,
+ 77,
+ 69,
+ 34
+ ]
},
{
'kind': 'account',
- 'type': 'publicKey',
'path': 'user'
}
]
@@ -862,39 +1191,53 @@ export type Gamba = {
},
{
'name': 'user',
- 'isMut': true,
- 'isSigner': true
+ 'writable': true,
+ 'signer': true
},
{
- 'name': 'systemProgram',
- 'isMut': false,
- 'isSigner': false
+ 'name': 'systemProgram'
}
],
'args': []
},
{
'name': 'playGame',
+ 'discriminator': [
+ 37,
+ 88,
+ 207,
+ 85,
+ 42,
+ 144,
+ 122,
+ 197
+ ],
'accounts': [
{
'name': 'user',
- 'isMut': true,
- 'isSigner': true
+ 'writable': true,
+ 'signer': true
},
{
'name': 'player',
- 'isMut': true,
- 'isSigner': false,
+ 'writable': true,
'pda': {
'seeds': [
{
'kind': 'const',
- 'type': 'string',
- 'value': 'PLAYER'
+ 'value': [
+ 34,
+ 80,
+ 76,
+ 65,
+ 89,
+ 69,
+ 82,
+ 34
+ ]
},
{
'kind': 'account',
- 'type': 'publicKey',
'path': 'user'
}
]
@@ -902,18 +1245,22 @@ export type Gamba = {
},
{
'name': 'game',
- 'isMut': true,
- 'isSigner': false,
+ 'writable': true,
'pda': {
'seeds': [
{
'kind': 'const',
- 'type': 'string',
- 'value': 'GAME'
+ 'value': [
+ 34,
+ 71,
+ 65,
+ 77,
+ 69,
+ 34
+ ]
},
{
'kind': 'account',
- 'type': 'publicKey',
'path': 'user'
}
]
@@ -921,114 +1268,136 @@ export type Gamba = {
},
{
'name': 'pool',
- 'isMut': true,
- 'isSigner': false
+ 'writable': true
},
{
- 'name': 'underlyingTokenMint',
- 'isMut': false,
- 'isSigner': false
+ 'name': 'underlyingTokenMint'
},
{
'name': 'bonusTokenMint',
- 'isMut': false,
- 'isSigner': false,
'pda': {
'seeds': [
{
'kind': 'const',
- 'type': 'string',
- 'value': 'POOL_BONUS_MINT'
+ 'value': [
+ 34,
+ 80,
+ 79,
+ 79,
+ 76,
+ 95,
+ 66,
+ 79,
+ 78,
+ 85,
+ 83,
+ 95,
+ 77,
+ 73,
+ 78,
+ 84,
+ 34
+ ]
},
{
'kind': 'account',
- 'type': 'publicKey',
- 'account': 'Pool',
- 'path': 'pool'
+ 'path': 'pool',
+ 'account': 'Pool'
}
]
}
},
{
'name': 'userUnderlyingAta',
- 'isMut': true,
- 'isSigner': false
+ 'writable': true
},
{
- 'name': 'creator',
- 'isMut': false,
- 'isSigner': false
+ 'name': 'creator'
},
{
'name': 'creatorAta',
- 'isMut': true,
- 'isSigner': false
+ 'writable': true
},
{
'name': 'playerAta',
- 'isMut': true,
- 'isSigner': false
+ 'writable': true
},
{
'name': 'playerBonusAta',
- 'isMut': true,
- 'isSigner': false,
- 'isOptional': true
+ 'writable': true,
+ 'optional': true
},
{
'name': 'userBonusAta',
- 'isMut': true,
- 'isSigner': false,
- 'isOptional': true
+ 'writable': true,
+ 'optional': true
},
{
'name': 'gambaState',
- 'isMut': true,
- 'isSigner': false,
+ 'writable': true,
'pda': {
'seeds': [
{
'kind': 'const',
- 'type': 'string',
- 'value': 'GAMBA_STATE'
+ 'value': [
+ 34,
+ 71,
+ 65,
+ 77,
+ 66,
+ 65,
+ 95,
+ 83,
+ 84,
+ 65,
+ 84,
+ 69,
+ 34
+ ]
}
]
}
},
{
'name': 'poolJackpotTokenAccount',
- 'isMut': true,
- 'isSigner': false,
+ 'writable': true,
'pda': {
'seeds': [
{
'kind': 'const',
- 'type': 'string',
- 'value': 'POOL_JACKPOT'
+ 'value': [
+ 34,
+ 80,
+ 79,
+ 79,
+ 76,
+ 95,
+ 74,
+ 65,
+ 67,
+ 75,
+ 80,
+ 79,
+ 84,
+ 34
+ ]
},
{
'kind': 'account',
- 'type': 'publicKey',
- 'account': 'Pool',
- 'path': 'pool'
+ 'path': 'pool',
+ 'account': 'Pool'
}
]
}
},
{
- 'name': 'systemProgram',
- 'isMut': false,
- 'isSigner': false
+ 'name': 'systemProgram'
},
{
- 'name': 'tokenProgram',
- 'isMut': false,
- 'isSigner': false
+ 'name': 'tokenProgram'
},
{
- 'name': 'associatedTokenProgram',
- 'isMut': false,
- 'isSigner': false
+ 'name': 'associatedTokenProgram'
}
],
'args': [
@@ -1062,26 +1431,42 @@ export type Gamba = {
},
{
'name': 'playerClose',
+ 'discriminator': [
+ 26,
+ 155,
+ 61,
+ 179,
+ 53,
+ 157,
+ 80,
+ 30
+ ],
'accounts': [
{
'name': 'user',
- 'isMut': true,
- 'isSigner': true
+ 'writable': true,
+ 'signer': true
},
{
'name': 'player',
- 'isMut': true,
- 'isSigner': false,
+ 'writable': true,
'pda': {
'seeds': [
{
'kind': 'const',
- 'type': 'string',
- 'value': 'PLAYER'
+ 'value': [
+ 34,
+ 80,
+ 76,
+ 65,
+ 89,
+ 69,
+ 82,
+ 34
+ ]
},
{
'kind': 'account',
- 'type': 'publicKey',
'path': 'user'
}
]
@@ -1089,18 +1474,22 @@ export type Gamba = {
},
{
'name': 'game',
- 'isMut': true,
- 'isSigner': false,
+ 'writable': true,
'pda': {
'seeds': [
{
'kind': 'const',
- 'type': 'string',
- 'value': 'GAME'
+ 'value': [
+ 34,
+ 71,
+ 65,
+ 77,
+ 69,
+ 34
+ ]
},
{
'kind': 'account',
- 'type': 'publicKey',
'path': 'user'
}
]
@@ -1111,31 +1500,45 @@ export type Gamba = {
},
{
'name': 'playerClaim',
+ 'discriminator': [
+ 188,
+ 220,
+ 237,
+ 31,
+ 181,
+ 18,
+ 85,
+ 45
+ ],
'accounts': [
{
'name': 'user',
- 'isMut': true,
- 'isSigner': true
+ 'writable': true,
+ 'signer': true
},
{
- 'name': 'underlyingTokenMint',
- 'isMut': false,
- 'isSigner': false
+ 'name': 'underlyingTokenMint'
},
{
'name': 'player',
- 'isMut': true,
- 'isSigner': false,
+ 'writable': true,
'pda': {
'seeds': [
{
'kind': 'const',
- 'type': 'string',
- 'value': 'PLAYER'
+ 'value': [
+ 34,
+ 80,
+ 76,
+ 65,
+ 89,
+ 69,
+ 82,
+ 34
+ ]
},
{
'kind': 'account',
- 'type': 'publicKey',
'path': 'user'
}
]
@@ -1143,18 +1546,22 @@ export type Gamba = {
},
{
'name': 'game',
- 'isMut': true,
- 'isSigner': false,
+ 'writable': true,
'pda': {
'seeds': [
{
'kind': 'const',
- 'type': 'string',
- 'value': 'GAME'
+ 'value': [
+ 34,
+ 71,
+ 65,
+ 77,
+ 69,
+ 34
+ ]
},
{
'kind': 'account',
- 'type': 'publicKey',
'path': 'user'
}
]
@@ -1162,59 +1569,66 @@ export type Gamba = {
},
{
'name': 'playerAta',
- 'isMut': true,
- 'isSigner': false
+ 'writable': true
},
{
'name': 'userUnderlyingAta',
- 'isMut': true,
- 'isSigner': false
+ 'writable': true
},
{
- 'name': 'systemProgram',
- 'isMut': false,
- 'isSigner': false
+ 'name': 'systemProgram'
},
{
- 'name': 'tokenProgram',
- 'isMut': false,
- 'isSigner': false
+ 'name': 'tokenProgram'
},
{
- 'name': 'associatedTokenProgram',
- 'isMut': false,
- 'isSigner': false
+ 'name': 'associatedTokenProgram'
}
],
'args': []
},
{
'name': 'rngSettle',
+ 'discriminator': [
+ 23,
+ 35,
+ 236,
+ 185,
+ 14,
+ 171,
+ 26,
+ 222
+ ],
'accounts': [
{
'name': 'rng',
- 'isMut': true,
- 'isSigner': true
+ 'writable': true,
+ 'signer': true
},
{
'name': 'user',
- 'isMut': true,
- 'isSigner': false
+ 'writable': true
},
{
'name': 'player',
- 'isMut': true,
- 'isSigner': false,
+ 'writable': true,
'pda': {
'seeds': [
{
'kind': 'const',
- 'type': 'string',
- 'value': 'PLAYER'
+ 'value': [
+ 34,
+ 80,
+ 76,
+ 65,
+ 89,
+ 69,
+ 82,
+ 34
+ ]
},
{
'kind': 'account',
- 'type': 'publicKey',
'path': 'user'
}
]
@@ -1222,18 +1636,22 @@ export type Gamba = {
},
{
'name': 'game',
- 'isMut': true,
- 'isSigner': false,
+ 'writable': true,
'pda': {
'seeds': [
{
'kind': 'const',
- 'type': 'string',
- 'value': 'GAME'
+ 'value': [
+ 34,
+ 71,
+ 65,
+ 77,
+ 69,
+ 34
+ ]
},
{
'kind': 'account',
- 'type': 'publicKey',
'path': 'user'
}
]
@@ -1241,178 +1659,236 @@ export type Gamba = {
},
{
'name': 'pool',
- 'isMut': true,
- 'isSigner': false
+ 'writable': true
},
{
- 'name': 'underlyingTokenMint',
- 'isMut': false,
- 'isSigner': false
+ 'name': 'underlyingTokenMint'
},
{
'name': 'poolUnderlyingTokenAccount',
- 'isMut': true,
- 'isSigner': false,
+ 'writable': true,
'pda': {
'seeds': [
{
'kind': 'const',
- 'type': 'string',
- 'value': 'POOL_ATA'
+ 'value': [
+ 34,
+ 80,
+ 79,
+ 79,
+ 76,
+ 95,
+ 65,
+ 84,
+ 65,
+ 34
+ ]
},
{
'kind': 'account',
- 'type': 'publicKey',
- 'account': 'Pool',
- 'path': 'pool'
+ 'path': 'pool',
+ 'account': 'Pool'
}
]
}
},
{
'name': 'poolBonusUnderlyingTokenAccount',
- 'isMut': true,
- 'isSigner': false,
+ 'writable': true,
'pda': {
'seeds': [
{
'kind': 'const',
- 'type': 'string',
- 'value': 'POOL_BONUS_UNDERLYING_TA'
+ 'value': [
+ 34,
+ 80,
+ 79,
+ 79,
+ 76,
+ 95,
+ 66,
+ 79,
+ 78,
+ 85,
+ 83,
+ 95,
+ 85,
+ 78,
+ 68,
+ 69,
+ 82,
+ 76,
+ 89,
+ 73,
+ 78,
+ 71,
+ 95,
+ 84,
+ 65,
+ 34
+ ]
},
{
'kind': 'account',
- 'type': 'publicKey',
- 'account': 'Pool',
- 'path': 'pool'
+ 'path': 'pool',
+ 'account': 'Pool'
}
]
}
},
{
'name': 'playerAta',
- 'isMut': true,
- 'isSigner': false
+ 'writable': true
},
{
'name': 'userUnderlyingAta',
- 'isMut': true,
- 'isSigner': false
+ 'writable': true
},
{
'name': 'gambaState',
- 'isMut': false,
- 'isSigner': false,
'pda': {
'seeds': [
{
'kind': 'const',
- 'type': 'string',
- 'value': 'GAMBA_STATE'
+ 'value': [
+ 34,
+ 71,
+ 65,
+ 77,
+ 66,
+ 65,
+ 95,
+ 83,
+ 84,
+ 65,
+ 84,
+ 69,
+ 34
+ ]
}
]
}
},
{
'name': 'gambaStateAta',
- 'isMut': true,
- 'isSigner': false
+ 'writable': true
},
{
- 'name': 'creator',
- 'isMut': false,
- 'isSigner': false
+ 'name': 'creator'
},
{
'name': 'creatorAta',
- 'isMut': true,
- 'isSigner': false
+ 'writable': true
},
{
'name': 'bonusTokenMint',
- 'isMut': true,
- 'isSigner': false,
+ 'writable': true,
'pda': {
'seeds': [
{
'kind': 'const',
- 'type': 'string',
- 'value': 'POOL_BONUS_MINT'
+ 'value': [
+ 34,
+ 80,
+ 79,
+ 79,
+ 76,
+ 95,
+ 66,
+ 79,
+ 78,
+ 85,
+ 83,
+ 95,
+ 77,
+ 73,
+ 78,
+ 84,
+ 34
+ ]
},
{
'kind': 'account',
- 'type': 'publicKey',
- 'account': 'Pool',
- 'path': 'pool'
+ 'path': 'pool',
+ 'account': 'Pool'
}
]
}
},
{
'name': 'playerBonusAta',
- 'isMut': true,
- 'isSigner': false,
- 'isOptional': true
+ 'writable': true,
+ 'optional': true
},
{
'name': 'poolJackpotTokenAccount',
- 'isMut': true,
- 'isSigner': false,
+ 'writable': true,
'pda': {
'seeds': [
{
'kind': 'const',
- 'type': 'string',
- 'value': 'POOL_JACKPOT'
+ 'value': [
+ 34,
+ 80,
+ 79,
+ 79,
+ 76,
+ 95,
+ 74,
+ 65,
+ 67,
+ 75,
+ 80,
+ 79,
+ 84,
+ 34
+ ]
},
{
'kind': 'account',
- 'type': 'publicKey',
- 'account': 'Pool',
- 'path': 'pool'
+ 'path': 'pool',
+ 'account': 'Pool'
}
]
}
},
{
'name': 'escrowTokenAccount',
- 'isMut': true,
- 'isSigner': false,
+ 'writable': true,
'pda': {
'seeds': [
{
'kind': 'const',
- 'type': 'string',
- 'value': 'ESCROW'
+ 'value': [
+ 34,
+ 69,
+ 83,
+ 67,
+ 82,
+ 79,
+ 87,
+ 34
+ ]
},
{
'kind': 'account',
- 'type': 'publicKey',
- 'account': 'Player',
- 'path': 'player'
+ 'path': 'player',
+ 'account': 'Player'
}
]
}
},
{
- 'name': 'systemProgram',
- 'isMut': false,
- 'isSigner': false
+ 'name': 'systemProgram'
},
{
- 'name': 'tokenProgram',
- 'isMut': false,
- 'isSigner': false
+ 'name': 'tokenProgram'
},
{
- 'name': 'associatedTokenProgram',
- 'isMut': false,
- 'isSigner': false
+ 'name': 'associatedTokenProgram'
},
{
- 'name': 'rent',
- 'isMut': false,
- 'isSigner': false
+ 'name': 'rent'
}
],
'args': [
@@ -1428,27 +1904,48 @@ export type Gamba = {
},
{
'name': 'rngProvideHashedSeed',
+ 'discriminator': [
+ 238,
+ 154,
+ 25,
+ 143,
+ 191,
+ 19,
+ 25,
+ 224
+ ],
'accounts': [
{
'name': 'game',
- 'isMut': true,
- 'isSigner': false
+ 'writable': true
},
{
'name': 'rng',
- 'isMut': true,
- 'isSigner': true
+ 'writable': true,
+ 'signer': true
},
{
'name': 'gambaState',
- 'isMut': true,
- 'isSigner': false,
+ 'writable': true,
'pda': {
'seeds': [
{
'kind': 'const',
- 'type': 'string',
- 'value': 'GAMBA_STATE'
+ 'value': [
+ 34,
+ 71,
+ 65,
+ 77,
+ 66,
+ 65,
+ 95,
+ 83,
+ 84,
+ 65,
+ 84,
+ 69,
+ 34
+ ]
}
]
}
@@ -1463,60 +1960,71 @@ export type Gamba = {
},
{
'name': 'distributeFees',
+ 'discriminator': [
+ 120,
+ 56,
+ 27,
+ 7,
+ 53,
+ 176,
+ 113,
+ 186
+ ],
'accounts': [
{
'name': 'signer',
- 'isMut': true,
- 'isSigner': true
+ 'writable': true,
+ 'signer': true
},
{
- 'name': 'underlyingTokenMint',
- 'isMut': false,
- 'isSigner': false
+ 'name': 'underlyingTokenMint'
},
{
'name': 'gambaState',
- 'isMut': true,
- 'isSigner': false,
+ 'writable': true,
'pda': {
'seeds': [
{
'kind': 'const',
- 'type': 'string',
- 'value': 'GAMBA_STATE'
+ 'value': [
+ 34,
+ 71,
+ 65,
+ 77,
+ 66,
+ 65,
+ 95,
+ 83,
+ 84,
+ 65,
+ 84,
+ 69,
+ 34
+ ]
}
]
}
},
{
'name': 'gambaStateAta',
- 'isMut': true,
- 'isSigner': false
+ 'writable': true
},
{
'name': 'distributionRecipient',
- 'isMut': true,
- 'isSigner': false
+ 'writable': true
},
{
'name': 'distributionRecipientAta',
- 'isMut': true,
- 'isSigner': false
+ 'writable': true
},
{
- 'name': 'associatedTokenProgram',
- 'isMut': false,
- 'isSigner': false
+ 'name': 'associatedTokenProgram'
},
{
- 'name': 'tokenProgram',
- 'isMut': false,
- 'isSigner': false
+ 'name': 'tokenProgram'
},
{
- 'name': 'systemProgram',
- 'isMut': false,
- 'isSigner': false
+ 'name': 'systemProgram'
}
],
'args': [
@@ -1529,7 +2037,236 @@ export type Gamba = {
],
'accounts': [
{
- 'name': 'game',
+ 'name': 'Game',
+ 'discriminator': [
+ 27,
+ 90,
+ 166,
+ 125,
+ 74,
+ 100,
+ 121,
+ 18
+ ]
+ },
+ {
+ 'name': 'Player',
+ 'discriminator': [
+ 205,
+ 222,
+ 112,
+ 7,
+ 165,
+ 155,
+ 206,
+ 218
+ ]
+ },
+ {
+ 'name': 'Pool',
+ 'discriminator': [
+ 241,
+ 154,
+ 109,
+ 4,
+ 17,
+ 177,
+ 109,
+ 188
+ ]
+ },
+ {
+ 'name': 'GambaState',
+ 'discriminator': [
+ 142,
+ 203,
+ 14,
+ 224,
+ 153,
+ 118,
+ 52,
+ 200
+ ]
+ }
+ ],
+ 'events': [
+ {
+ 'name': 'GameSettled',
+ 'discriminator': [
+ 63,
+ 109,
+ 128,
+ 85,
+ 229,
+ 63,
+ 167,
+ 176
+ ]
+ },
+ {
+ 'name': 'PoolChange',
+ 'discriminator': [
+ 241,
+ 7,
+ 155,
+ 154,
+ 56,
+ 57,
+ 0,
+ 101
+ ]
+ },
+ {
+ 'name': 'PoolCreated',
+ 'discriminator': [
+ 202,
+ 44,
+ 41,
+ 88,
+ 104,
+ 220,
+ 157,
+ 82
+ ]
+ }
+ ],
+ 'errors': [
+ {
+ 'code': 6000,
+ 'name': 'GenericError',
+ 'msg': 'Something went wrong'
+ },
+ {
+ 'code': 6001,
+ 'name': 'Unauthorized',
+ 'msg': 'Unauthorized'
+ },
+ {
+ 'code': 6002,
+ 'name': 'CustomPoolFeeExceedsLimit',
+ 'msg': 'Custom pool fee cannot exceed 100%'
+ },
+ {
+ 'code': 6003,
+ 'name': 'CustomMaxPayoutExceedsLimit',
+ 'msg': 'Custom max payout cannot exceed 50%'
+ }
+ ],
+ 'types': [
+ {
+ 'name': 'PlayerError',
+ 'type': {
+ 'kind': 'enum',
+ 'variants': [
+ {
+ 'name': 'NotReadyToPlay'
+ },
+ {
+ 'name': 'CreatorFeeTooHigh'
+ },
+ {
+ 'name': 'WagerTooSmall'
+ },
+ {
+ 'name': 'TooFewBetOutcomes'
+ },
+ {
+ 'name': 'TooManyBetOutcomes'
+ },
+ {
+ 'name': 'PlayerAdvantage'
+ },
+ {
+ 'name': 'HouseAdvantageTooHigh'
+ },
+ {
+ 'name': 'MaxPayoutExceeded'
+ }
+ ]
+ }
+ },
+ {
+ 'name': 'RngError',
+ 'type': {
+ 'kind': 'enum',
+ 'variants': [
+ {
+ 'name': 'Generic'
+ },
+ {
+ 'name': 'InitialHashedSeedAlreadyProvided'
+ },
+ {
+ 'name': 'IncorrectRngSeed'
+ },
+ {
+ 'name': 'ResultNotRequested'
+ }
+ ]
+ }
+ },
+ {
+ 'name': 'GambaStateError',
+ 'type': {
+ 'kind': 'enum',
+ 'variants': [
+ {
+ 'name': 'PlaysNotAllowed'
+ },
+ {
+ 'name': 'DepositNotAllowed'
+ },
+ {
+ 'name': 'WithdrawalNotAllowed'
+ },
+ {
+ 'name': 'PoolCreationNotAllowed'
+ },
+ {
+ 'name': 'DepositLimitExceeded'
+ },
+ {
+ 'name': 'DepositWhitelistRequired'
+ }
+ ]
+ }
+ },
+ {
+ 'name': 'PoolAction',
+ 'type': {
+ 'kind': 'enum',
+ 'variants': [
+ {
+ 'name': 'Deposit'
+ },
+ {
+ 'name': 'Withdraw'
+ }
+ ]
+ }
+ },
+ {
+ 'name': 'GameStatus',
+ 'type': {
+ 'kind': 'enum',
+ 'variants': [
+ {
+ 'name': 'None'
+ },
+ {
+ 'name': 'NotInitialized'
+ },
+ {
+ 'name': 'Ready'
+ },
+ {
+ 'name': 'ResultRequested'
+ }
+ ]
+ }
+ },
+ {
+ 'name': 'Game',
'type': {
'kind': 'struct',
'fields': [
@@ -1548,20 +2285,22 @@ export type Gamba = {
},
{
'name': 'user',
- 'type': 'publicKey'
+ 'type': 'pubkey'
},
{
'name': 'tokenMint',
- 'type': 'publicKey'
+ 'type': 'pubkey'
},
{
'name': 'pool',
- 'type': 'publicKey'
+ 'type': 'pubkey'
},
{
'name': 'status',
'type': {
- 'defined': 'GameStatus'
+ 'defined': {
+ 'name': 'GameStatus'
+ }
}
},
{
@@ -1584,7 +2323,7 @@ export type Gamba = {
},
{
'name': 'creator',
- 'type': 'publicKey'
+ 'type': 'pubkey'
},
{
'name': 'creatorMeta',
@@ -1650,7 +2389,7 @@ export type Gamba = {
},
{
'name': 'pointsAuthority',
- 'type': 'publicKey'
+ 'type': 'pubkey'
},
{
'name': 'metadata',
@@ -1660,7 +2399,7 @@ export type Gamba = {
}
},
{
- 'name': 'player',
+ 'name': 'Player',
'type': {
'kind': 'struct',
'fields': [
@@ -1675,7 +2414,7 @@ export type Gamba = {
},
{
'name': 'user',
- 'type': 'publicKey'
+ 'type': 'pubkey'
},
{
'name': 'nonce',
@@ -1685,7 +2424,7 @@ export type Gamba = {
}
},
{
- 'name': 'pool',
+ 'name': 'Pool',
'type': {
'kind': 'struct',
'fields': [
@@ -1700,15 +2439,15 @@ export type Gamba = {
},
{
'name': 'lookupAddress',
- 'type': 'publicKey'
+ 'type': 'pubkey'
},
{
'name': 'poolAuthority',
- 'type': 'publicKey'
+ 'type': 'pubkey'
},
{
'name': 'underlyingTokenMint',
- 'type': 'publicKey'
+ 'type': 'pubkey'
},
{
'name': 'antiSpamFeeExempt',
@@ -1760,7 +2499,7 @@ export type Gamba = {
},
{
'name': 'customBonusTokenMint',
- 'type': 'publicKey'
+ 'type': 'pubkey'
},
{
'name': 'customBonusToken',
@@ -1780,27 +2519,27 @@ export type Gamba = {
},
{
'name': 'depositWhitelistAddress',
- 'type': 'publicKey'
+ 'type': 'pubkey'
}
]
}
},
{
- 'name': 'gambaState',
+ 'name': 'GambaState',
'type': {
'kind': 'struct',
'fields': [
{
'name': 'authority',
- 'type': 'publicKey'
+ 'type': 'pubkey'
},
{
'name': 'rngAddress',
- 'type': 'publicKey'
+ 'type': 'pubkey'
},
{
'name': 'rngAddress2',
- 'type': 'publicKey'
+ 'type': 'pubkey'
},
{
'name': 'antiSpamFee',
@@ -1872,7 +2611,7 @@ export type Gamba = {
},
{
'name': 'distributionRecipient',
- 'type': 'publicKey'
+ 'type': 'pubkey'
},
{
'name': 'bump',
@@ -1885,369 +2624,284 @@ export type Gamba = {
}
]
}
- }
- ],
- 'types': [
+ },
{
- 'name': 'PlayerError',
+ 'name': 'GameSettled',
'type': {
- 'kind': 'enum',
- 'variants': [
+ 'kind': 'struct',
+ 'fields': [
{
- 'name': 'NotReadyToPlay'
+ 'name': 'user',
+ 'type': 'pubkey'
},
{
- 'name': 'CreatorFeeTooHigh'
+ 'name': 'pool',
+ 'type': 'pubkey'
},
{
- 'name': 'WagerTooSmall'
+ 'name': 'tokenMint',
+ 'type': 'pubkey'
},
{
- 'name': 'TooFewBetOutcomes'
+ 'name': 'creator',
+ 'type': 'pubkey'
},
{
- 'name': 'TooManyBetOutcomes'
+ 'name': 'creatorFee',
+ 'type': 'u64'
},
{
- 'name': 'PlayerAdvantage'
+ 'name': 'gambaFee',
+ 'type': 'u64'
},
{
- 'name': 'HouseAdvantageTooHigh'
+ 'name': 'poolFee',
+ 'type': 'u64'
},
{
- 'name': 'MaxPayoutExceeded'
- }
- ]
- }
- },
- {
- 'name': 'RngError',
- 'type': {
- 'kind': 'enum',
- 'variants': [
- {
- 'name': 'Generic'
+ 'name': 'jackpotFee',
+ 'type': 'u64'
},
{
- 'name': 'InitialHashedSeedAlreadyProvided'
+ 'name': 'underlyingUsed',
+ 'type': 'u64'
},
{
- 'name': 'IncorrectRngSeed'
+ 'name': 'bonusUsed',
+ 'type': 'u64'
},
{
- 'name': 'ResultNotRequested'
- }
- ]
- }
- },
- {
- 'name': 'GambaStateError',
- 'type': {
- 'kind': 'enum',
- 'variants': [
- {
- 'name': 'PlaysNotAllowed'
+ 'name': 'wager',
+ 'type': 'u64'
},
{
- 'name': 'DepositNotAllowed'
+ 'name': 'payout',
+ 'type': 'u64'
},
{
- 'name': 'WithdrawalNotAllowed'
+ 'name': 'multiplierBps',
+ 'type': 'u32'
},
{
- 'name': 'PoolCreationNotAllowed'
+ 'name': 'payoutFromBonusPool',
+ 'type': 'u64'
},
{
- 'name': 'DepositLimitExceeded'
+ 'name': 'payoutFromNormalPool',
+ 'type': 'u64'
},
{
- 'name': 'DepositWhitelistRequired'
- }
- ]
- }
- },
- {
- 'name': 'PoolAction',
- 'type': {
- 'kind': 'enum',
- 'variants': [
- {
- 'name': 'Deposit'
+ 'name': 'jackpotProbabilityUbps',
+ 'type': 'u64'
},
{
- 'name': 'Withdraw'
+ 'name': 'jackpotResult',
+ 'type': 'u64'
+ },
+ {
+ 'name': 'nonce',
+ 'type': 'u64'
+ },
+ {
+ 'name': 'clientSeed',
+ 'type': 'string'
+ },
+ {
+ 'name': 'resultIndex',
+ 'type': 'u64'
+ },
+ {
+ 'name': 'bet',
+ 'type': {
+ 'vec': 'u32'
+ }
+ },
+ {
+ 'name': 'jackpotPayoutToUser',
+ 'type': 'u64'
+ },
+ {
+ 'name': 'poolLiquidity',
+ 'type': 'u64'
+ },
+ {
+ 'name': 'rngSeed',
+ 'type': 'string'
+ },
+ {
+ 'name': 'nextRngSeedHashed',
+ 'type': 'string'
+ },
+ {
+ 'name': 'metadata',
+ 'type': 'string'
}
]
}
},
{
- 'name': 'GameStatus',
+ 'name': 'PoolChange',
'type': {
- 'kind': 'enum',
- 'variants': [
+ 'kind': 'struct',
+ 'fields': [
{
- 'name': 'None'
+ 'name': 'user',
+ 'type': 'pubkey'
},
{
- 'name': 'NotInitialized'
+ 'name': 'pool',
+ 'type': 'pubkey'
},
{
- 'name': 'Ready'
+ 'name': 'tokenMint',
+ 'type': 'pubkey'
},
{
- 'name': 'ResultRequested'
+ 'name': 'action',
+ 'type': {
+ 'defined': {
+ 'name': 'PoolAction'
+ }
+ }
+ },
+ {
+ 'name': 'amount',
+ 'type': 'u64'
+ },
+ {
+ 'name': 'postLiquidity',
+ 'type': 'u64'
+ },
+ {
+ 'name': 'lpSupply',
+ 'type': 'u64'
}
]
}
- }
- ],
- 'events': [
- {
- 'name': 'GameSettled',
- 'fields': [
- {
- 'name': 'user',
- 'type': 'publicKey',
- 'index': false
- },
- {
- 'name': 'pool',
- 'type': 'publicKey',
- 'index': false
- },
- {
- 'name': 'tokenMint',
- 'type': 'publicKey',
- 'index': false
- },
- {
- 'name': 'creator',
- 'type': 'publicKey',
- 'index': false
- },
- {
- 'name': 'creatorFee',
- 'type': 'u64',
- 'index': false
- },
- {
- 'name': 'gambaFee',
- 'type': 'u64',
- 'index': false
- },
- {
- 'name': 'poolFee',
- 'type': 'u64',
- 'index': false
- },
- {
- 'name': 'jackpotFee',
- 'type': 'u64',
- 'index': false
- },
- {
- 'name': 'underlyingUsed',
- 'type': 'u64',
- 'index': false
- },
- {
- 'name': 'bonusUsed',
- 'type': 'u64',
- 'index': false
- },
- {
- 'name': 'wager',
- 'type': 'u64',
- 'index': false
- },
- {
- 'name': 'payout',
- 'type': 'u64',
- 'index': false
- },
- {
- 'name': 'multiplierBps',
- 'type': 'u32',
- 'index': false
- },
- {
- 'name': 'payoutFromBonusPool',
- 'type': 'u64',
- 'index': false
- },
- {
- 'name': 'payoutFromNormalPool',
- 'type': 'u64',
- 'index': false
- },
- {
- 'name': 'jackpotProbabilityUbps',
- 'type': 'u64',
- 'index': false
- },
- {
- 'name': 'jackpotResult',
- 'type': 'u64',
- 'index': false
- },
- {
- 'name': 'nonce',
- 'type': 'u64',
- 'index': false
- },
- {
- 'name': 'clientSeed',
- 'type': 'string',
- 'index': false
- },
- {
- 'name': 'resultIndex',
- 'type': 'u64',
- 'index': false
- },
- {
- 'name': 'bet',
- 'type': {
- 'vec': 'u32'
- },
- 'index': false
- },
- {
- 'name': 'jackpotPayoutToUser',
- 'type': 'u64',
- 'index': false
- },
- {
- 'name': 'poolLiquidity',
- 'type': 'u64',
- 'index': false
- },
- {
- 'name': 'rngSeed',
- 'type': 'string',
- 'index': false
- },
- {
- 'name': 'nextRngSeedHashed',
- 'type': 'string',
- 'index': false
- },
- {
- 'name': 'metadata',
- 'type': 'string',
- 'index': false
- }
- ]
},
{
- 'name': 'PoolChange',
- 'fields': [
- {
- 'name': 'user',
- 'type': 'publicKey',
- 'index': false
- },
- {
- 'name': 'pool',
- 'type': 'publicKey',
- 'index': false
- },
- {
- 'name': 'tokenMint',
- 'type': 'publicKey',
- 'index': false
- },
- {
- 'name': 'action',
- 'type': {
- 'defined': 'PoolAction'
+ 'name': 'PoolCreated',
+ 'type': {
+ 'kind': 'struct',
+ 'fields': [
+ {
+ 'name': 'user',
+ 'type': 'pubkey'
},
- 'index': false
- },
- {
- 'name': 'amount',
- 'type': 'u64',
- 'index': false
- },
- {
- 'name': 'postLiquidity',
- 'type': 'u64',
- 'index': false
- },
- {
- 'name': 'lpSupply',
- 'type': 'u64',
- 'index': false
- }
- ]
- }
- ],
- 'errors': [
- {
- 'code': 6000,
- 'name': 'GenericError',
- 'msg': 'Something went wrong'
- },
- {
- 'code': 6001,
- 'name': 'Unauthorized',
- 'msg': 'Unauthorized'
+ {
+ 'name': 'authority',
+ 'type': 'pubkey'
+ },
+ {
+ 'name': 'pool',
+ 'type': 'pubkey'
+ },
+ {
+ 'name': 'tokenMint',
+ 'type': 'pubkey'
+ }
+ ]
+ }
}
]
-};
+}
export const IDL: Gamba = {
- version: '0.1.0',
- name: 'gamba',
+ address: 'Gamba2hK6KV3quKq854B3sQG1WMdq3zgQLPKqyK4qS18',
+ metadata: {
+ name: 'gamba',
+ version: '0.1.0',
+ spec: '0.1.0',
+ },
instructions: [
{
name: 'gambaInitialize',
+ discriminator: [
+ 255,
+ 140,
+ 190,
+ 102,
+ 152,
+ 30,
+ 179,
+ 112,
+ ],
accounts: [
{
name: 'initializer',
- isMut: true,
- isSigner: true,
+ writable: true,
+ signer: true,
},
{
name: 'gambaState',
- isMut: true,
- isSigner: false,
+ writable: true,
pda: {
seeds: [
{
kind: 'const',
- type: 'string',
- value: 'GAMBA_STATE',
+ value: [
+ 34,
+ 71,
+ 65,
+ 77,
+ 66,
+ 65,
+ 95,
+ 83,
+ 84,
+ 65,
+ 84,
+ 69,
+ 34,
+ ],
},
],
},
},
- {
- name: 'systemProgram',
- isMut: false,
- isSigner: false,
- },
+ { name: 'systemProgram' },
],
args: [],
},
{
name: 'gambaSetAuthority',
+ discriminator: [
+ 60,
+ 11,
+ 159,
+ 59,
+ 150,
+ 12,
+ 106,
+ 78,
+ ],
accounts: [
{
name: 'authority',
- isMut: true,
- isSigner: true,
+ writable: true,
+ signer: true,
},
{
name: 'gambaState',
- isMut: true,
- isSigner: false,
+ writable: true,
pda: {
seeds: [
{
kind: 'const',
- type: 'string',
- value: 'GAMBA_STATE',
+ value: [
+ 34,
+ 71,
+ 65,
+ 77,
+ 66,
+ 65,
+ 95,
+ 83,
+ 84,
+ 65,
+ 84,
+ 69,
+ 34,
+ ],
},
],
},
@@ -2259,28 +2913,50 @@ export const IDL: Gamba = {
args: [
{
name: 'authority',
- type: 'publicKey',
+ type: 'pubkey',
},
],
},
{
name: 'gambaSetConfig',
+ discriminator: [
+ 205,
+ 11,
+ 209,
+ 24,
+ 204,
+ 47,
+ 25,
+ 186,
+ ],
accounts: [
{
name: 'authority',
- isMut: true,
- isSigner: true,
+ writable: true,
+ signer: true,
},
{
name: 'gambaState',
- isMut: true,
- isSigner: false,
+ writable: true,
pda: {
seeds: [
{
kind: 'const',
- type: 'string',
- value: 'GAMBA_STATE',
+ value: [
+ 34,
+ 71,
+ 65,
+ 77,
+ 66,
+ 65,
+ 95,
+ 83,
+ 84,
+ 65,
+ 84,
+ 69,
+ 34,
+ ],
},
],
},
@@ -2292,7 +2968,7 @@ export const IDL: Gamba = {
args: [
{
name: 'rngAddress',
- type: 'publicKey',
+ type: 'pubkey',
},
{
name: 'gambaFee',
@@ -2364,57 +3040,78 @@ export const IDL: Gamba = {
},
{
name: 'distributionRecipient',
- type: 'publicKey',
+ type: 'pubkey',
},
],
},
{
name: 'poolInitialize',
+ discriminator: [
+ 37,
+ 10,
+ 195,
+ 69,
+ 4,
+ 213,
+ 88,
+ 173,
+ ],
accounts: [
{
name: 'initializer',
- isMut: true,
- isSigner: true,
+ writable: true,
+ signer: true,
},
{
name: 'gambaState',
- isMut: true,
- isSigner: false,
+ writable: true,
pda: {
seeds: [
{
kind: 'const',
- type: 'string',
- value: 'GAMBA_STATE',
+ value: [
+ 34,
+ 71,
+ 65,
+ 77,
+ 66,
+ 65,
+ 95,
+ 83,
+ 84,
+ 65,
+ 84,
+ 69,
+ 34,
+ ],
},
],
},
},
- {
- name: 'underlyingTokenMint',
- isMut: false,
- isSigner: false,
- },
+ { name: 'underlyingTokenMint' },
{
name: 'pool',
- isMut: true,
- isSigner: false,
+ writable: true,
pda: {
seeds: [
{
kind: 'const',
- type: 'string',
- value: 'POOL',
+ value: [
+ 34,
+ 80,
+ 79,
+ 79,
+ 76,
+ 34,
+ ],
},
{
kind: 'account',
- type: 'publicKey',
- account: 'Mint',
path: 'underlying_token_mint',
+ account: 'Mint',
},
{
kind: 'arg',
- type: 'publicKey',
path: 'pool_authority',
},
],
@@ -2422,253 +3119,323 @@ export const IDL: Gamba = {
},
{
name: 'poolUnderlyingTokenAccount',
- isMut: true,
- isSigner: false,
+ writable: true,
pda: {
seeds: [
{
kind: 'const',
- type: 'string',
- value: 'POOL_ATA',
+ value: [
+ 34,
+ 80,
+ 79,
+ 79,
+ 76,
+ 95,
+ 65,
+ 84,
+ 65,
+ 34,
+ ],
},
{
kind: 'account',
- type: 'publicKey',
- account: 'Pool',
path: 'pool',
+ account: 'Pool',
},
],
},
},
{
name: 'poolBonusUnderlyingTokenAccount',
- isMut: true,
- isSigner: false,
+ writable: true,
pda: {
seeds: [
{
kind: 'const',
- type: 'string',
- value: 'POOL_BONUS_UNDERLYING_TA',
+ value: [
+ 34,
+ 80,
+ 79,
+ 79,
+ 76,
+ 95,
+ 66,
+ 79,
+ 78,
+ 85,
+ 83,
+ 95,
+ 85,
+ 78,
+ 68,
+ 69,
+ 82,
+ 76,
+ 89,
+ 73,
+ 78,
+ 71,
+ 95,
+ 84,
+ 65,
+ 34,
+ ],
},
{
kind: 'account',
- type: 'publicKey',
- account: 'Pool',
path: 'pool',
+ account: 'Pool',
},
],
},
},
{
name: 'poolJackpotTokenAccount',
- isMut: true,
- isSigner: false,
+ writable: true,
pda: {
seeds: [
{
kind: 'const',
- type: 'string',
- value: 'POOL_JACKPOT',
+ value: [
+ 34,
+ 80,
+ 79,
+ 79,
+ 76,
+ 95,
+ 74,
+ 65,
+ 67,
+ 75,
+ 80,
+ 79,
+ 84,
+ 34,
+ ],
},
{
kind: 'account',
- type: 'publicKey',
- account: 'Pool',
path: 'pool',
+ account: 'Pool',
},
],
},
},
{
name: 'gambaStateAta',
- isMut: true,
- isSigner: false,
+ writable: true,
},
{
name: 'lpMint',
- isMut: true,
- isSigner: false,
+ writable: true,
pda: {
seeds: [
{
kind: 'const',
- type: 'string',
- value: 'POOL_LP_MINT',
+ value: [
+ 34,
+ 80,
+ 79,
+ 79,
+ 76,
+ 95,
+ 76,
+ 80,
+ 95,
+ 77,
+ 73,
+ 78,
+ 84,
+ 34,
+ ],
},
{
kind: 'account',
- type: 'publicKey',
- account: 'Pool',
path: 'pool',
+ account: 'Pool',
},
],
},
},
{
name: 'lpMintMetadata',
- isMut: true,
- isSigner: false,
+ writable: true,
},
{
name: 'bonusMint',
- isMut: true,
- isSigner: false,
+ writable: true,
pda: {
seeds: [
{
kind: 'const',
- type: 'string',
- value: 'POOL_BONUS_MINT',
+ value: [
+ 34,
+ 80,
+ 79,
+ 79,
+ 76,
+ 95,
+ 66,
+ 79,
+ 78,
+ 85,
+ 83,
+ 95,
+ 77,
+ 73,
+ 78,
+ 84,
+ 34,
+ ],
},
{
kind: 'account',
- type: 'publicKey',
- account: 'Pool',
path: 'pool',
+ account: 'Pool',
},
],
},
},
{
name: 'bonusMintMetadata',
- isMut: true,
- isSigner: false,
- },
- {
- name: 'associatedTokenProgram',
- isMut: false,
- isSigner: false,
- },
- {
- name: 'tokenProgram',
- isMut: false,
- isSigner: false,
- },
- {
- name: 'systemProgram',
- isMut: false,
- isSigner: false,
- },
- {
- name: 'rent',
- isMut: false,
- isSigner: false,
- },
- {
- name: 'tokenMetadataProgram',
- isMut: false,
- isSigner: false,
+ writable: true,
},
+ { name: 'associatedTokenProgram' },
+ { name: 'tokenProgram' },
+ { name: 'systemProgram' },
+ { name: 'rent' },
+ { name: 'tokenMetadataProgram' },
],
args: [
{
name: 'poolAuthority',
- type: 'publicKey',
+ type: 'pubkey',
},
{
name: 'lookupAddress',
- type: 'publicKey',
+ type: 'pubkey',
},
],
},
{
name: 'poolDeposit',
+ discriminator: [
+ 26,
+ 109,
+ 164,
+ 79,
+ 207,
+ 145,
+ 204,
+ 217,
+ ],
accounts: [
{
name: 'user',
- isMut: true,
- isSigner: true,
+ writable: true,
+ signer: true,
},
{
name: 'gambaState',
- isMut: false,
- isSigner: false,
pda: {
seeds: [
{
kind: 'const',
- type: 'string',
- value: 'GAMBA_STATE',
+ value: [
+ 34,
+ 71,
+ 65,
+ 77,
+ 66,
+ 65,
+ 95,
+ 83,
+ 84,
+ 65,
+ 84,
+ 69,
+ 34,
+ ],
},
],
},
},
{
name: 'pool',
- isMut: true,
- isSigner: false,
+ writable: true,
},
{
name: 'poolUnderlyingTokenAccount',
- isMut: true,
- isSigner: false,
+ writable: true,
pda: {
seeds: [
{
kind: 'const',
- type: 'string',
- value: 'POOL_ATA',
+ value: [
+ 34,
+ 80,
+ 79,
+ 79,
+ 76,
+ 95,
+ 65,
+ 84,
+ 65,
+ 34,
+ ],
},
{
kind: 'account',
- type: 'publicKey',
- account: 'Pool',
path: 'pool',
+ account: 'Pool',
},
],
},
},
{
name: 'lpMint',
- isMut: true,
- isSigner: false,
+ writable: true,
pda: {
seeds: [
{
kind: 'const',
- type: 'string',
- value: 'POOL_LP_MINT',
+ value: [
+ 34,
+ 80,
+ 79,
+ 79,
+ 76,
+ 95,
+ 76,
+ 80,
+ 95,
+ 77,
+ 73,
+ 78,
+ 84,
+ 34,
+ ],
},
{
kind: 'account',
- type: 'publicKey',
- account: 'Pool',
path: 'pool',
+ account: 'Pool',
},
],
},
},
- {
- name: 'underlyingTokenMint',
- isMut: false,
- isSigner: false,
- },
+ { name: 'underlyingTokenMint' },
{
name: 'userUnderlyingAta',
- isMut: true,
- isSigner: false,
+ writable: true,
},
{
name: 'userLpAta',
- isMut: true,
- isSigner: false,
- },
- {
- name: 'associatedTokenProgram',
- isMut: false,
- isSigner: false,
- },
- {
- name: 'tokenProgram',
- isMut: false,
- isSigner: false,
- },
- {
- name: 'systemProgram',
- isMut: false,
- isSigner: false,
+ writable: true,
},
+ { name: 'associatedTokenProgram' },
+ { name: 'tokenProgram' },
+ { name: 'systemProgram' },
],
args: [
{
@@ -2679,101 +3446,123 @@ export const IDL: Gamba = {
},
{
name: 'poolWithdraw',
+ discriminator: [
+ 50,
+ 1,
+ 23,
+ 25,
+ 135,
+ 221,
+ 159,
+ 182,
+ ],
accounts: [
{
name: 'user',
- isMut: true,
- isSigner: true,
+ writable: true,
+ signer: true,
},
{
name: 'pool',
- isMut: true,
- isSigner: false,
+ writable: true,
},
{
name: 'poolUnderlyingTokenAccount',
- isMut: true,
- isSigner: false,
+ writable: true,
pda: {
seeds: [
{
kind: 'const',
- type: 'string',
- value: 'POOL_ATA',
+ value: [
+ 34,
+ 80,
+ 79,
+ 79,
+ 76,
+ 95,
+ 65,
+ 84,
+ 65,
+ 34,
+ ],
},
{
kind: 'account',
- type: 'publicKey',
- account: 'Pool',
path: 'pool',
+ account: 'Pool',
},
],
},
},
{
name: 'lpMint',
- isMut: true,
- isSigner: false,
+ writable: true,
pda: {
seeds: [
{
kind: 'const',
- type: 'string',
- value: 'POOL_LP_MINT',
+ value: [
+ 34,
+ 80,
+ 79,
+ 79,
+ 76,
+ 95,
+ 76,
+ 80,
+ 95,
+ 77,
+ 73,
+ 78,
+ 84,
+ 34,
+ ],
},
{
kind: 'account',
- type: 'publicKey',
- account: 'Pool',
path: 'pool',
+ account: 'Pool',
},
],
},
},
- {
- name: 'underlyingTokenMint',
- isMut: false,
- isSigner: false,
- },
+ { name: 'underlyingTokenMint' },
{
name: 'userUnderlyingAta',
- isMut: true,
- isSigner: false,
+ writable: true,
},
{
name: 'userLpAta',
- isMut: true,
- isSigner: false,
+ writable: true,
},
{
name: 'gambaState',
- isMut: false,
- isSigner: false,
pda: {
seeds: [
{
kind: 'const',
- type: 'string',
- value: 'GAMBA_STATE',
+ value: [
+ 34,
+ 71,
+ 65,
+ 77,
+ 66,
+ 65,
+ 95,
+ 83,
+ 84,
+ 65,
+ 84,
+ 69,
+ 34,
+ ],
},
],
},
},
- {
- name: 'associatedTokenProgram',
- isMut: false,
- isSigner: false,
- },
- {
- name: 'tokenProgram',
- isMut: false,
- isSigner: false,
- },
- {
- name: 'systemProgram',
- isMut: false,
- isSigner: false,
- },
+ { name: 'associatedTokenProgram' },
+ { name: 'tokenProgram' },
+ { name: 'systemProgram' },
],
args: [
{
@@ -2784,121 +3573,171 @@ export const IDL: Gamba = {
},
{
name: 'poolMintBonusTokens',
+ discriminator: [
+ 105,
+ 130,
+ 72,
+ 25,
+ 88,
+ 185,
+ 100,
+ 55,
+ ],
accounts: [
{
name: 'user',
- isMut: true,
- isSigner: true,
- },
- {
- name: 'pool',
- isMut: false,
- isSigner: false,
+ writable: true,
+ signer: true,
},
+ { name: 'pool' },
{
name: 'gambaState',
- isMut: false,
- isSigner: false,
pda: {
seeds: [
{
kind: 'const',
- type: 'string',
- value: 'GAMBA_STATE',
+ value: [
+ 34,
+ 71,
+ 65,
+ 77,
+ 66,
+ 65,
+ 95,
+ 83,
+ 84,
+ 65,
+ 84,
+ 69,
+ 34,
+ ],
},
],
},
},
- {
- name: 'underlyingTokenMint',
- isMut: false,
- isSigner: false,
- },
+ { name: 'underlyingTokenMint' },
{
name: 'poolBonusUnderlyingTokenAccount',
- isMut: true,
- isSigner: false,
+ writable: true,
pda: {
seeds: [
{
kind: 'const',
- type: 'string',
- value: 'POOL_BONUS_UNDERLYING_TA',
+ value: [
+ 34,
+ 80,
+ 79,
+ 79,
+ 76,
+ 95,
+ 66,
+ 79,
+ 78,
+ 85,
+ 83,
+ 95,
+ 85,
+ 78,
+ 68,
+ 69,
+ 82,
+ 76,
+ 89,
+ 73,
+ 78,
+ 71,
+ 95,
+ 84,
+ 65,
+ 34,
+ ],
},
{
kind: 'account',
- type: 'publicKey',
- account: 'Pool',
path: 'pool',
+ account: 'Pool',
},
],
},
},
{
name: 'bonusMint',
- isMut: true,
- isSigner: false,
+ writable: true,
pda: {
seeds: [
{
kind: 'const',
- type: 'string',
- value: 'POOL_BONUS_MINT',
+ value: [
+ 34,
+ 80,
+ 79,
+ 79,
+ 76,
+ 95,
+ 66,
+ 79,
+ 78,
+ 85,
+ 83,
+ 95,
+ 77,
+ 73,
+ 78,
+ 84,
+ 34,
+ ],
},
{
kind: 'account',
- type: 'publicKey',
- account: 'Pool',
path: 'pool',
+ account: 'Pool',
},
],
},
},
{
name: 'poolJackpotTokenAccount',
- isMut: true,
- isSigner: false,
+ writable: true,
pda: {
seeds: [
{
kind: 'const',
- type: 'string',
- value: 'POOL_JACKPOT',
+ value: [
+ 34,
+ 80,
+ 79,
+ 79,
+ 76,
+ 95,
+ 74,
+ 65,
+ 67,
+ 75,
+ 80,
+ 79,
+ 84,
+ 34,
+ ],
},
{
kind: 'account',
- type: 'publicKey',
- account: 'Pool',
path: 'pool',
+ account: 'Pool',
},
],
},
},
{
name: 'userUnderlyingAta',
- isMut: true,
- isSigner: false,
+ writable: true,
},
{
name: 'userBonusAta',
- isMut: true,
- isSigner: false,
- },
- {
- name: 'associatedTokenProgram',
- isMut: false,
- isSigner: false,
- },
- {
- name: 'tokenProgram',
- isMut: false,
- isSigner: false,
- },
- {
- name: 'systemProgram',
- isMut: false,
- isSigner: false,
+ writable: true,
},
+ { name: 'associatedTokenProgram' },
+ { name: 'tokenProgram' },
+ { name: 'systemProgram' },
],
args: [
{
@@ -2909,30 +3748,50 @@ export const IDL: Gamba = {
},
{
name: 'poolAuthorityConfig',
+ discriminator: [
+ 58,
+ 12,
+ 184,
+ 118,
+ 14,
+ 99,
+ 110,
+ 17,
+ ],
accounts: [
{
name: 'user',
- isMut: true,
- isSigner: true,
+ writable: true,
+ signer: true,
},
{
name: 'gambaState',
- isMut: false,
- isSigner: false,
pda: {
seeds: [
{
kind: 'const',
- type: 'string',
- value: 'GAMBA_STATE',
+ value: [
+ 34,
+ 71,
+ 65,
+ 77,
+ 66,
+ 65,
+ 95,
+ 83,
+ 84,
+ 65,
+ 84,
+ 69,
+ 34,
+ ],
},
],
},
},
{
name: 'pool',
- isMut: true,
- isSigner: false,
+ writable: true,
},
],
args: [
@@ -2970,36 +3829,56 @@ export const IDL: Gamba = {
},
{
name: 'depositWhitelistAddress',
- type: 'publicKey',
+ type: 'pubkey',
},
],
},
{
name: 'poolGambaConfig',
+ discriminator: [
+ 197,
+ 177,
+ 234,
+ 111,
+ 246,
+ 248,
+ 20,
+ 155,
+ ],
accounts: [
{
name: 'user',
- isMut: true,
- isSigner: true,
+ writable: true,
+ signer: true,
},
{
name: 'gambaState',
- isMut: false,
- isSigner: false,
pda: {
seeds: [
{
kind: 'const',
- type: 'string',
- value: 'GAMBA_STATE',
+ value: [
+ 34,
+ 71,
+ 65,
+ 77,
+ 66,
+ 65,
+ 95,
+ 83,
+ 84,
+ 65,
+ 84,
+ 69,
+ 34,
+ ],
},
],
},
},
{
name: 'pool',
- isMut: true,
- isSigner: false,
+ writable: true,
},
],
args: [
@@ -3019,21 +3898,37 @@ export const IDL: Gamba = {
},
{
name: 'playerInitialize',
+ discriminator: [
+ 213,
+ 160,
+ 145,
+ 88,
+ 197,
+ 68,
+ 63,
+ 150,
+ ],
accounts: [
{
name: 'player',
- isMut: true,
- isSigner: false,
+ writable: true,
pda: {
seeds: [
{
kind: 'const',
- type: 'string',
- value: 'PLAYER',
+ value: [
+ 34,
+ 80,
+ 76,
+ 65,
+ 89,
+ 69,
+ 82,
+ 34,
+ ],
},
{
kind: 'account',
- type: 'publicKey',
path: 'user',
},
],
@@ -3041,18 +3936,22 @@ export const IDL: Gamba = {
},
{
name: 'game',
- isMut: true,
- isSigner: false,
+ writable: true,
pda: {
seeds: [
{
kind: 'const',
- type: 'string',
- value: 'GAME',
+ value: [
+ 34,
+ 71,
+ 65,
+ 77,
+ 69,
+ 34,
+ ],
},
{
kind: 'account',
- type: 'publicKey',
path: 'user',
},
],
@@ -3060,39 +3959,51 @@ export const IDL: Gamba = {
},
{
name: 'user',
- isMut: true,
- isSigner: true,
- },
- {
- name: 'systemProgram',
- isMut: false,
- isSigner: false,
+ writable: true,
+ signer: true,
},
+ { name: 'systemProgram' },
],
args: [],
},
{
name: 'playGame',
+ discriminator: [
+ 37,
+ 88,
+ 207,
+ 85,
+ 42,
+ 144,
+ 122,
+ 197,
+ ],
accounts: [
{
name: 'user',
- isMut: true,
- isSigner: true,
+ writable: true,
+ signer: true,
},
{
name: 'player',
- isMut: true,
- isSigner: false,
+ writable: true,
pda: {
seeds: [
{
kind: 'const',
- type: 'string',
- value: 'PLAYER',
+ value: [
+ 34,
+ 80,
+ 76,
+ 65,
+ 89,
+ 69,
+ 82,
+ 34,
+ ],
},
{
kind: 'account',
- type: 'publicKey',
path: 'user',
},
],
@@ -3100,18 +4011,22 @@ export const IDL: Gamba = {
},
{
name: 'game',
- isMut: true,
- isSigner: false,
+ writable: true,
pda: {
seeds: [
{
kind: 'const',
- type: 'string',
- value: 'GAME',
+ value: [
+ 34,
+ 71,
+ 65,
+ 77,
+ 69,
+ 34,
+ ],
},
{
kind: 'account',
- type: 'publicKey',
path: 'user',
},
],
@@ -3119,115 +4034,127 @@ export const IDL: Gamba = {
},
{
name: 'pool',
- isMut: true,
- isSigner: false,
- },
- {
- name: 'underlyingTokenMint',
- isMut: false,
- isSigner: false,
+ writable: true,
},
+ { name: 'underlyingTokenMint' },
{
name: 'bonusTokenMint',
- isMut: false,
- isSigner: false,
pda: {
seeds: [
{
kind: 'const',
- type: 'string',
- value: 'POOL_BONUS_MINT',
+ value: [
+ 34,
+ 80,
+ 79,
+ 79,
+ 76,
+ 95,
+ 66,
+ 79,
+ 78,
+ 85,
+ 83,
+ 95,
+ 77,
+ 73,
+ 78,
+ 84,
+ 34,
+ ],
},
{
kind: 'account',
- type: 'publicKey',
- account: 'Pool',
path: 'pool',
+ account: 'Pool',
},
],
},
},
{
name: 'userUnderlyingAta',
- isMut: true,
- isSigner: false,
- },
- {
- name: 'creator',
- isMut: false,
- isSigner: false,
+ writable: true,
},
+ { name: 'creator' },
{
name: 'creatorAta',
- isMut: true,
- isSigner: false,
+ writable: true,
},
{
name: 'playerAta',
- isMut: true,
- isSigner: false,
+ writable: true,
},
{
name: 'playerBonusAta',
- isMut: true,
- isSigner: false,
- isOptional: true,
+ writable: true,
+ optional: true,
},
{
name: 'userBonusAta',
- isMut: true,
- isSigner: false,
- isOptional: true,
+ writable: true,
+ optional: true,
},
{
name: 'gambaState',
- isMut: true,
- isSigner: false,
+ writable: true,
pda: {
seeds: [
{
kind: 'const',
- type: 'string',
- value: 'GAMBA_STATE',
+ value: [
+ 34,
+ 71,
+ 65,
+ 77,
+ 66,
+ 65,
+ 95,
+ 83,
+ 84,
+ 65,
+ 84,
+ 69,
+ 34,
+ ],
},
],
},
},
{
name: 'poolJackpotTokenAccount',
- isMut: true,
- isSigner: false,
+ writable: true,
pda: {
seeds: [
{
kind: 'const',
- type: 'string',
- value: 'POOL_JACKPOT',
+ value: [
+ 34,
+ 80,
+ 79,
+ 79,
+ 76,
+ 95,
+ 74,
+ 65,
+ 67,
+ 75,
+ 80,
+ 79,
+ 84,
+ 34,
+ ],
},
{
kind: 'account',
- type: 'publicKey',
- account: 'Pool',
path: 'pool',
+ account: 'Pool',
},
],
},
},
- {
- name: 'systemProgram',
- isMut: false,
- isSigner: false,
- },
- {
- name: 'tokenProgram',
- isMut: false,
- isSigner: false,
- },
- {
- name: 'associatedTokenProgram',
- isMut: false,
- isSigner: false,
- },
+ { name: 'systemProgram' },
+ { name: 'tokenProgram' },
+ { name: 'associatedTokenProgram' },
],
args: [
{
@@ -3258,26 +4185,42 @@ export const IDL: Gamba = {
},
{
name: 'playerClose',
+ discriminator: [
+ 26,
+ 155,
+ 61,
+ 179,
+ 53,
+ 157,
+ 80,
+ 30,
+ ],
accounts: [
{
name: 'user',
- isMut: true,
- isSigner: true,
+ writable: true,
+ signer: true,
},
{
name: 'player',
- isMut: true,
- isSigner: false,
+ writable: true,
pda: {
seeds: [
{
kind: 'const',
- type: 'string',
- value: 'PLAYER',
+ value: [
+ 34,
+ 80,
+ 76,
+ 65,
+ 89,
+ 69,
+ 82,
+ 34,
+ ],
},
{
kind: 'account',
- type: 'publicKey',
path: 'user',
},
],
@@ -3285,18 +4228,22 @@ export const IDL: Gamba = {
},
{
name: 'game',
- isMut: true,
- isSigner: false,
+ writable: true,
pda: {
seeds: [
{
kind: 'const',
- type: 'string',
- value: 'GAME',
+ value: [
+ 34,
+ 71,
+ 65,
+ 77,
+ 69,
+ 34,
+ ],
},
{
kind: 'account',
- type: 'publicKey',
path: 'user',
},
],
@@ -3307,31 +4254,43 @@ export const IDL: Gamba = {
},
{
name: 'playerClaim',
+ discriminator: [
+ 188,
+ 220,
+ 237,
+ 31,
+ 181,
+ 18,
+ 85,
+ 45,
+ ],
accounts: [
{
name: 'user',
- isMut: true,
- isSigner: true,
- },
- {
- name: 'underlyingTokenMint',
- isMut: false,
- isSigner: false,
+ writable: true,
+ signer: true,
},
+ { name: 'underlyingTokenMint' },
{
name: 'player',
- isMut: true,
- isSigner: false,
+ writable: true,
pda: {
seeds: [
{
kind: 'const',
- type: 'string',
- value: 'PLAYER',
+ value: [
+ 34,
+ 80,
+ 76,
+ 65,
+ 89,
+ 69,
+ 82,
+ 34,
+ ],
},
{
kind: 'account',
- type: 'publicKey',
path: 'user',
},
],
@@ -3339,18 +4298,22 @@ export const IDL: Gamba = {
},
{
name: 'game',
- isMut: true,
- isSigner: false,
+ writable: true,
pda: {
seeds: [
{
kind: 'const',
- type: 'string',
- value: 'GAME',
+ value: [
+ 34,
+ 71,
+ 65,
+ 77,
+ 69,
+ 34,
+ ],
},
{
kind: 'account',
- type: 'publicKey',
path: 'user',
},
],
@@ -3358,59 +4321,60 @@ export const IDL: Gamba = {
},
{
name: 'playerAta',
- isMut: true,
- isSigner: false,
+ writable: true,
},
{
name: 'userUnderlyingAta',
- isMut: true,
- isSigner: false,
- },
- {
- name: 'systemProgram',
- isMut: false,
- isSigner: false,
- },
- {
- name: 'tokenProgram',
- isMut: false,
- isSigner: false,
- },
- {
- name: 'associatedTokenProgram',
- isMut: false,
- isSigner: false,
+ writable: true,
},
+ { name: 'systemProgram' },
+ { name: 'tokenProgram' },
+ { name: 'associatedTokenProgram' },
],
args: [],
},
{
name: 'rngSettle',
+ discriminator: [
+ 23,
+ 35,
+ 236,
+ 185,
+ 14,
+ 171,
+ 26,
+ 222,
+ ],
accounts: [
{
name: 'rng',
- isMut: true,
- isSigner: true,
+ writable: true,
+ signer: true,
},
{
name: 'user',
- isMut: true,
- isSigner: false,
+ writable: true,
},
{
name: 'player',
- isMut: true,
- isSigner: false,
+ writable: true,
pda: {
seeds: [
{
kind: 'const',
- type: 'string',
- value: 'PLAYER',
+ value: [
+ 34,
+ 80,
+ 76,
+ 65,
+ 89,
+ 69,
+ 82,
+ 34,
+ ],
},
{
kind: 'account',
- type: 'publicKey',
path: 'user',
},
],
@@ -3418,18 +4382,22 @@ export const IDL: Gamba = {
},
{
name: 'game',
- isMut: true,
- isSigner: false,
+ writable: true,
pda: {
seeds: [
{
kind: 'const',
- type: 'string',
- value: 'GAME',
+ value: [
+ 34,
+ 71,
+ 65,
+ 77,
+ 69,
+ 34,
+ ],
},
{
kind: 'account',
- type: 'publicKey',
path: 'user',
},
],
@@ -3437,179 +4405,225 @@ export const IDL: Gamba = {
},
{
name: 'pool',
- isMut: true,
- isSigner: false,
- },
- {
- name: 'underlyingTokenMint',
- isMut: false,
- isSigner: false,
+ writable: true,
},
+ { name: 'underlyingTokenMint' },
{
name: 'poolUnderlyingTokenAccount',
- isMut: true,
- isSigner: false,
+ writable: true,
pda: {
seeds: [
{
kind: 'const',
- type: 'string',
- value: 'POOL_ATA',
+ value: [
+ 34,
+ 80,
+ 79,
+ 79,
+ 76,
+ 95,
+ 65,
+ 84,
+ 65,
+ 34,
+ ],
},
{
kind: 'account',
- type: 'publicKey',
- account: 'Pool',
path: 'pool',
+ account: 'Pool',
},
],
},
},
{
name: 'poolBonusUnderlyingTokenAccount',
- isMut: true,
- isSigner: false,
+ writable: true,
pda: {
seeds: [
{
kind: 'const',
- type: 'string',
- value: 'POOL_BONUS_UNDERLYING_TA',
+ value: [
+ 34,
+ 80,
+ 79,
+ 79,
+ 76,
+ 95,
+ 66,
+ 79,
+ 78,
+ 85,
+ 83,
+ 95,
+ 85,
+ 78,
+ 68,
+ 69,
+ 82,
+ 76,
+ 89,
+ 73,
+ 78,
+ 71,
+ 95,
+ 84,
+ 65,
+ 34,
+ ],
},
{
kind: 'account',
- type: 'publicKey',
- account: 'Pool',
path: 'pool',
+ account: 'Pool',
},
],
},
},
{
name: 'playerAta',
- isMut: true,
- isSigner: false,
+ writable: true,
},
{
name: 'userUnderlyingAta',
- isMut: true,
- isSigner: false,
+ writable: true,
},
{
name: 'gambaState',
- isMut: false,
- isSigner: false,
pda: {
seeds: [
{
kind: 'const',
- type: 'string',
- value: 'GAMBA_STATE',
+ value: [
+ 34,
+ 71,
+ 65,
+ 77,
+ 66,
+ 65,
+ 95,
+ 83,
+ 84,
+ 65,
+ 84,
+ 69,
+ 34,
+ ],
},
],
},
},
{
name: 'gambaStateAta',
- isMut: true,
- isSigner: false,
- },
- {
- name: 'creator',
- isMut: false,
- isSigner: false,
+ writable: true,
},
+ { name: 'creator' },
{
name: 'creatorAta',
- isMut: true,
- isSigner: false,
+ writable: true,
},
{
name: 'bonusTokenMint',
- isMut: true,
- isSigner: false,
+ writable: true,
pda: {
seeds: [
{
kind: 'const',
- type: 'string',
- value: 'POOL_BONUS_MINT',
+ value: [
+ 34,
+ 80,
+ 79,
+ 79,
+ 76,
+ 95,
+ 66,
+ 79,
+ 78,
+ 85,
+ 83,
+ 95,
+ 77,
+ 73,
+ 78,
+ 84,
+ 34,
+ ],
},
{
kind: 'account',
- type: 'publicKey',
- account: 'Pool',
path: 'pool',
+ account: 'Pool',
},
],
},
},
{
name: 'playerBonusAta',
- isMut: true,
- isSigner: false,
- isOptional: true,
+ writable: true,
+ optional: true,
},
{
name: 'poolJackpotTokenAccount',
- isMut: true,
- isSigner: false,
+ writable: true,
pda: {
seeds: [
{
kind: 'const',
- type: 'string',
- value: 'POOL_JACKPOT',
+ value: [
+ 34,
+ 80,
+ 79,
+ 79,
+ 76,
+ 95,
+ 74,
+ 65,
+ 67,
+ 75,
+ 80,
+ 79,
+ 84,
+ 34,
+ ],
},
{
kind: 'account',
- type: 'publicKey',
- account: 'Pool',
path: 'pool',
+ account: 'Pool',
},
],
},
},
{
name: 'escrowTokenAccount',
- isMut: true,
- isSigner: false,
+ writable: true,
pda: {
seeds: [
{
kind: 'const',
- type: 'string',
- value: 'ESCROW',
+ value: [
+ 34,
+ 69,
+ 83,
+ 67,
+ 82,
+ 79,
+ 87,
+ 34,
+ ],
},
{
kind: 'account',
- type: 'publicKey',
- account: 'Player',
path: 'player',
+ account: 'Player',
},
],
},
},
- {
- name: 'systemProgram',
- isMut: false,
- isSigner: false,
- },
- {
- name: 'tokenProgram',
- isMut: false,
- isSigner: false,
- },
- {
- name: 'associatedTokenProgram',
- isMut: false,
- isSigner: false,
- },
- {
- name: 'rent',
- isMut: false,
- isSigner: false,
- },
+ { name: 'systemProgram' },
+ { name: 'tokenProgram' },
+ { name: 'associatedTokenProgram' },
+ { name: 'rent' },
],
args: [
{
@@ -3624,27 +4638,48 @@ export const IDL: Gamba = {
},
{
name: 'rngProvideHashedSeed',
+ discriminator: [
+ 238,
+ 154,
+ 25,
+ 143,
+ 191,
+ 19,
+ 25,
+ 224,
+ ],
accounts: [
{
name: 'game',
- isMut: true,
- isSigner: false,
+ writable: true,
},
{
name: 'rng',
- isMut: true,
- isSigner: true,
+ writable: true,
+ signer: true,
},
{
name: 'gambaState',
- isMut: true,
- isSigner: false,
+ writable: true,
pda: {
seeds: [
{
kind: 'const',
- type: 'string',
- value: 'GAMBA_STATE',
+ value: [
+ 34,
+ 71,
+ 65,
+ 77,
+ 66,
+ 65,
+ 95,
+ 83,
+ 84,
+ 65,
+ 84,
+ 69,
+ 34,
+ ],
},
],
},
@@ -3659,61 +4694,64 @@ export const IDL: Gamba = {
},
{
name: 'distributeFees',
+ discriminator: [
+ 120,
+ 56,
+ 27,
+ 7,
+ 53,
+ 176,
+ 113,
+ 186,
+ ],
accounts: [
{
name: 'signer',
- isMut: true,
- isSigner: true,
- },
- {
- name: 'underlyingTokenMint',
- isMut: false,
- isSigner: false,
+ writable: true,
+ signer: true,
},
+ { name: 'underlyingTokenMint' },
{
name: 'gambaState',
- isMut: true,
- isSigner: false,
+ writable: true,
pda: {
seeds: [
{
kind: 'const',
- type: 'string',
- value: 'GAMBA_STATE',
+ value: [
+ 34,
+ 71,
+ 65,
+ 77,
+ 66,
+ 65,
+ 95,
+ 83,
+ 84,
+ 65,
+ 84,
+ 69,
+ 34,
+ ],
},
],
},
},
{
name: 'gambaStateAta',
- isMut: true,
- isSigner: false,
+ writable: true,
},
{
name: 'distributionRecipient',
- isMut: true,
- isSigner: false,
+ writable: true,
},
{
name: 'distributionRecipientAta',
- isMut: true,
- isSigner: false,
- },
- {
- name: 'associatedTokenProgram',
- isMut: false,
- isSigner: false,
- },
- {
- name: 'tokenProgram',
- isMut: false,
- isSigner: false,
- },
- {
- name: 'systemProgram',
- isMut: false,
- isSigner: false,
+ writable: true,
},
+ { name: 'associatedTokenProgram' },
+ { name: 'tokenProgram' },
+ { name: 'systemProgram' },
],
args: [
{
@@ -3725,7 +4763,188 @@ export const IDL: Gamba = {
],
accounts: [
{
- name: 'game',
+ name: 'Game',
+ discriminator: [
+ 27,
+ 90,
+ 166,
+ 125,
+ 74,
+ 100,
+ 121,
+ 18,
+ ],
+ },
+ {
+ name: 'Player',
+ discriminator: [
+ 205,
+ 222,
+ 112,
+ 7,
+ 165,
+ 155,
+ 206,
+ 218,
+ ],
+ },
+ {
+ name: 'Pool',
+ discriminator: [
+ 241,
+ 154,
+ 109,
+ 4,
+ 17,
+ 177,
+ 109,
+ 188,
+ ],
+ },
+ {
+ name: 'GambaState',
+ discriminator: [
+ 142,
+ 203,
+ 14,
+ 224,
+ 153,
+ 118,
+ 52,
+ 200,
+ ],
+ },
+ ],
+ events: [
+ {
+ name: 'GameSettled',
+ discriminator: [
+ 63,
+ 109,
+ 128,
+ 85,
+ 229,
+ 63,
+ 167,
+ 176,
+ ],
+ },
+ {
+ name: 'PoolChange',
+ discriminator: [
+ 241,
+ 7,
+ 155,
+ 154,
+ 56,
+ 57,
+ 0,
+ 101,
+ ],
+ },
+ {
+ name: 'PoolCreated',
+ discriminator: [
+ 202,
+ 44,
+ 41,
+ 88,
+ 104,
+ 220,
+ 157,
+ 82,
+ ],
+ },
+ ],
+ errors: [
+ {
+ code: 6000,
+ name: 'GenericError',
+ msg: 'Something went wrong',
+ },
+ {
+ code: 6001,
+ name: 'Unauthorized',
+ msg: 'Unauthorized',
+ },
+ {
+ code: 6002,
+ name: 'CustomPoolFeeExceedsLimit',
+ msg: 'Custom pool fee cannot exceed 100%',
+ },
+ {
+ code: 6003,
+ name: 'CustomMaxPayoutExceedsLimit',
+ msg: 'Custom max payout cannot exceed 50%',
+ },
+ ],
+ types: [
+ {
+ name: 'PlayerError',
+ type: {
+ kind: 'enum',
+ variants: [
+ { name: 'NotReadyToPlay' },
+ { name: 'CreatorFeeTooHigh' },
+ { name: 'WagerTooSmall' },
+ { name: 'TooFewBetOutcomes' },
+ { name: 'TooManyBetOutcomes' },
+ { name: 'PlayerAdvantage' },
+ { name: 'HouseAdvantageTooHigh' },
+ { name: 'MaxPayoutExceeded' },
+ ],
+ },
+ },
+ {
+ name: 'RngError',
+ type: {
+ kind: 'enum',
+ variants: [
+ { name: 'Generic' },
+ { name: 'InitialHashedSeedAlreadyProvided' },
+ { name: 'IncorrectRngSeed' },
+ { name: 'ResultNotRequested' },
+ ],
+ },
+ },
+ {
+ name: 'GambaStateError',
+ type: {
+ kind: 'enum',
+ variants: [
+ { name: 'PlaysNotAllowed' },
+ { name: 'DepositNotAllowed' },
+ { name: 'WithdrawalNotAllowed' },
+ { name: 'PoolCreationNotAllowed' },
+ { name: 'DepositLimitExceeded' },
+ { name: 'DepositWhitelistRequired' },
+ ],
+ },
+ },
+ {
+ name: 'PoolAction',
+ type: {
+ kind: 'enum',
+ variants: [
+ { name: 'Deposit' },
+ { name: 'Withdraw' },
+ ],
+ },
+ },
+ {
+ name: 'GameStatus',
+ type: {
+ kind: 'enum',
+ variants: [
+ { name: 'None' },
+ { name: 'NotInitialized' },
+ { name: 'Ready' },
+ { name: 'ResultRequested' },
+ ],
+ },
+ },
+ {
+ name: 'Game',
type: {
kind: 'struct',
fields: [
@@ -3744,19 +4963,19 @@ export const IDL: Gamba = {
},
{
name: 'user',
- type: 'publicKey',
+ type: 'pubkey',
},
{
name: 'tokenMint',
- type: 'publicKey',
+ type: 'pubkey',
},
{
name: 'pool',
- type: 'publicKey',
+ type: 'pubkey',
},
{
name: 'status',
- type: { defined: 'GameStatus' },
+ type: { defined: { name: 'GameStatus' } },
},
{
name: 'nextRngSeedHashed',
@@ -3778,7 +4997,7 @@ export const IDL: Gamba = {
},
{
name: 'creator',
- type: 'publicKey',
+ type: 'pubkey',
},
{
name: 'creatorMeta',
@@ -3842,7 +5061,7 @@ export const IDL: Gamba = {
},
{
name: 'pointsAuthority',
- type: 'publicKey',
+ type: 'pubkey',
},
{
name: 'metadata',
@@ -3852,7 +5071,7 @@ export const IDL: Gamba = {
},
},
{
- name: 'player',
+ name: 'Player',
type: {
kind: 'struct',
fields: [
@@ -3867,7 +5086,7 @@ export const IDL: Gamba = {
},
{
name: 'user',
- type: 'publicKey',
+ type: 'pubkey',
},
{
name: 'nonce',
@@ -3877,7 +5096,7 @@ export const IDL: Gamba = {
},
},
{
- name: 'pool',
+ name: 'Pool',
type: {
kind: 'struct',
fields: [
@@ -3892,15 +5111,15 @@ export const IDL: Gamba = {
},
{
name: 'lookupAddress',
- type: 'publicKey',
+ type: 'pubkey',
},
{
name: 'poolAuthority',
- type: 'publicKey',
+ type: 'pubkey',
},
{
name: 'underlyingTokenMint',
- type: 'publicKey',
+ type: 'pubkey',
},
{
name: 'antiSpamFeeExempt',
@@ -3952,7 +5171,7 @@ export const IDL: Gamba = {
},
{
name: 'customBonusTokenMint',
- type: 'publicKey',
+ type: 'pubkey',
},
{
name: 'customBonusToken',
@@ -3972,27 +5191,27 @@ export const IDL: Gamba = {
},
{
name: 'depositWhitelistAddress',
- type: 'publicKey',
+ type: 'pubkey',
},
],
},
},
{
- name: 'gambaState',
+ name: 'GambaState',
type: {
kind: 'struct',
fields: [
{
name: 'authority',
- type: 'publicKey',
+ type: 'pubkey',
},
{
name: 'rngAddress',
- type: 'publicKey',
+ type: 'pubkey',
},
{
name: 'rngAddress2',
- type: 'publicKey',
+ type: 'pubkey',
},
{
name: 'antiSpamFee',
@@ -4064,7 +5283,7 @@ export const IDL: Gamba = {
},
{
name: 'distributionRecipient',
- type: 'publicKey',
+ type: 'pubkey',
},
{
name: 'bump',
@@ -4078,260 +5297,177 @@ export const IDL: Gamba = {
],
},
},
- ],
- types: [
{
- name: 'PlayerError',
- type: {
- kind: 'enum',
- variants: [
- { name: 'NotReadyToPlay' },
- { name: 'CreatorFeeTooHigh' },
- { name: 'WagerTooSmall' },
- { name: 'TooFewBetOutcomes' },
- { name: 'TooManyBetOutcomes' },
- { name: 'PlayerAdvantage' },
- { name: 'HouseAdvantageTooHigh' },
- { name: 'MaxPayoutExceeded' },
- ],
- },
- },
- {
- name: 'RngError',
- type: {
- kind: 'enum',
- variants: [
- { name: 'Generic' },
- { name: 'InitialHashedSeedAlreadyProvided' },
- { name: 'IncorrectRngSeed' },
- { name: 'ResultNotRequested' },
- ],
- },
- },
- {
- name: 'GambaStateError',
+ name: 'GameSettled',
type: {
- kind: 'enum',
- variants: [
- { name: 'PlaysNotAllowed' },
- { name: 'DepositNotAllowed' },
- { name: 'WithdrawalNotAllowed' },
- { name: 'PoolCreationNotAllowed' },
- { name: 'DepositLimitExceeded' },
- { name: 'DepositWhitelistRequired' },
+ kind: 'struct',
+ fields: [
+ {
+ name: 'user',
+ type: 'pubkey',
+ },
+ {
+ name: 'pool',
+ type: 'pubkey',
+ },
+ {
+ name: 'tokenMint',
+ type: 'pubkey',
+ },
+ {
+ name: 'creator',
+ type: 'pubkey',
+ },
+ {
+ name: 'creatorFee',
+ type: 'u64',
+ },
+ {
+ name: 'gambaFee',
+ type: 'u64',
+ },
+ {
+ name: 'poolFee',
+ type: 'u64',
+ },
+ {
+ name: 'jackpotFee',
+ type: 'u64',
+ },
+ {
+ name: 'underlyingUsed',
+ type: 'u64',
+ },
+ {
+ name: 'bonusUsed',
+ type: 'u64',
+ },
+ {
+ name: 'wager',
+ type: 'u64',
+ },
+ {
+ name: 'payout',
+ type: 'u64',
+ },
+ {
+ name: 'multiplierBps',
+ type: 'u32',
+ },
+ {
+ name: 'payoutFromBonusPool',
+ type: 'u64',
+ },
+ {
+ name: 'payoutFromNormalPool',
+ type: 'u64',
+ },
+ {
+ name: 'jackpotProbabilityUbps',
+ type: 'u64',
+ },
+ {
+ name: 'jackpotResult',
+ type: 'u64',
+ },
+ {
+ name: 'nonce',
+ type: 'u64',
+ },
+ {
+ name: 'clientSeed',
+ type: 'string',
+ },
+ {
+ name: 'resultIndex',
+ type: 'u64',
+ },
+ {
+ name: 'bet',
+ type: { vec: 'u32' },
+ },
+ {
+ name: 'jackpotPayoutToUser',
+ type: 'u64',
+ },
+ {
+ name: 'poolLiquidity',
+ type: 'u64',
+ },
+ {
+ name: 'rngSeed',
+ type: 'string',
+ },
+ {
+ name: 'nextRngSeedHashed',
+ type: 'string',
+ },
+ {
+ name: 'metadata',
+ type: 'string',
+ },
],
},
},
{
- name: 'PoolAction',
+ name: 'PoolChange',
type: {
- kind: 'enum',
- variants: [
- { name: 'Deposit' },
- { name: 'Withdraw' },
+ kind: 'struct',
+ fields: [
+ {
+ name: 'user',
+ type: 'pubkey',
+ },
+ {
+ name: 'pool',
+ type: 'pubkey',
+ },
+ {
+ name: 'tokenMint',
+ type: 'pubkey',
+ },
+ {
+ name: 'action',
+ type: { defined: { name: 'PoolAction' } },
+ },
+ {
+ name: 'amount',
+ type: 'u64',
+ },
+ {
+ name: 'postLiquidity',
+ type: 'u64',
+ },
+ {
+ name: 'lpSupply',
+ type: 'u64',
+ },
],
},
},
{
- name: 'GameStatus',
+ name: 'PoolCreated',
type: {
- kind: 'enum',
- variants: [
- { name: 'None' },
- { name: 'NotInitialized' },
- { name: 'Ready' },
- { name: 'ResultRequested' },
+ kind: 'struct',
+ fields: [
+ {
+ name: 'user',
+ type: 'pubkey',
+ },
+ {
+ name: 'authority',
+ type: 'pubkey',
+ },
+ {
+ name: 'pool',
+ type: 'pubkey',
+ },
+ {
+ name: 'tokenMint',
+ type: 'pubkey',
+ },
],
},
},
],
- events: [
- {
- name: 'GameSettled',
- fields: [
- {
- name: 'user',
- type: 'publicKey',
- index: false,
- },
- {
- name: 'pool',
- type: 'publicKey',
- index: false,
- },
- {
- name: 'tokenMint',
- type: 'publicKey',
- index: false,
- },
- {
- name: 'creator',
- type: 'publicKey',
- index: false,
- },
- {
- name: 'creatorFee',
- type: 'u64',
- index: false,
- },
- {
- name: 'gambaFee',
- type: 'u64',
- index: false,
- },
- {
- name: 'poolFee',
- type: 'u64',
- index: false,
- },
- {
- name: 'jackpotFee',
- type: 'u64',
- index: false,
- },
- {
- name: 'underlyingUsed',
- type: 'u64',
- index: false,
- },
- {
- name: 'bonusUsed',
- type: 'u64',
- index: false,
- },
- {
- name: 'wager',
- type: 'u64',
- index: false,
- },
- {
- name: 'payout',
- type: 'u64',
- index: false,
- },
- {
- name: 'multiplierBps',
- type: 'u32',
- index: false,
- },
- {
- name: 'payoutFromBonusPool',
- type: 'u64',
- index: false,
- },
- {
- name: 'payoutFromNormalPool',
- type: 'u64',
- index: false,
- },
- {
- name: 'jackpotProbabilityUbps',
- type: 'u64',
- index: false,
- },
- {
- name: 'jackpotResult',
- type: 'u64',
- index: false,
- },
- {
- name: 'nonce',
- type: 'u64',
- index: false,
- },
- {
- name: 'clientSeed',
- type: 'string',
- index: false,
- },
- {
- name: 'resultIndex',
- type: 'u64',
- index: false,
- },
- {
- name: 'bet',
- type: { vec: 'u32' },
- index: false,
- },
- {
- name: 'jackpotPayoutToUser',
- type: 'u64',
- index: false,
- },
- {
- name: 'poolLiquidity',
- type: 'u64',
- index: false,
- },
- {
- name: 'rngSeed',
- type: 'string',
- index: false,
- },
- {
- name: 'nextRngSeedHashed',
- type: 'string',
- index: false,
- },
- {
- name: 'metadata',
- type: 'string',
- index: false,
- },
- ],
- },
- {
- name: 'PoolChange',
- fields: [
- {
- name: 'user',
- type: 'publicKey',
- index: false,
- },
- {
- name: 'pool',
- type: 'publicKey',
- index: false,
- },
- {
- name: 'tokenMint',
- type: 'publicKey',
- index: false,
- },
- {
- name: 'action',
- type: { defined: 'PoolAction' },
- index: false,
- },
- {
- name: 'amount',
- type: 'u64',
- index: false,
- },
- {
- name: 'postLiquidity',
- type: 'u64',
- index: false,
- },
- {
- name: 'lpSupply',
- type: 'u64',
- index: false,
- },
- ],
- },
- ],
- errors: [
- {
- code: 6000,
- name: 'GenericError',
- msg: 'Something went wrong',
- },
- {
- code: 6001,
- name: 'Unauthorized',
- msg: 'Unauthorized',
- },
- ],
}
diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts
index ab1de751..0e0d6d2d 100644
--- a/packages/core/src/types.ts
+++ b/packages/core/src/types.ts
@@ -9,8 +9,8 @@ export type GambaEvent = {name: string, data: IdlEvent
export type AnyGambaEvent = GambaEvent<'GameSettled'> | GambaEvent<'PoolChange'>
-export type GambaState = IdlAccounts['gambaState']
-export type PlayerState = IdlAccounts['player']
-export type GameState = IdlAccounts['game']
-export type PoolState = IdlAccounts['pool']
+export type GambaState = IdlAccounts['GambaState']
+export type PlayerState = IdlAccounts['Player']
+export type GameState = IdlAccounts['Game']
+export type PoolState = IdlAccounts['Pool']
export type GambaProviderWallet = Omit & {payer?: Keypair}
diff --git a/packages/core/src/utils.ts b/packages/core/src/utils.ts
index 8e8bcd02..a9baf7a1 100644
--- a/packages/core/src/utils.ts
+++ b/packages/core/src/utils.ts
@@ -1,47 +1,77 @@
import { NATIVE_MINT } from '@solana/spl-token'
-import { Connection, PublicKey } from '@solana/web3.js'
-import { BPS_PER_WHOLE, GameState } from '.'
+import { Connection, PublicKey, AccountInfo } from '@solana/web3.js'
+import { BPS_PER_WHOLE } from '.'
import { decodeGame } from './decoders'
import { getGameAddress } from './pdas'
+import { GameState } from './types'
-export const basisPoints = (percent: number) => {
- return Math.round(percent * BPS_PER_WHOLE)
-}
+export const basisPoints = (percent: number) =>
+ Math.round(percent * BPS_PER_WHOLE)
-export const isNativeMint = (pubkey: PublicKey) => NATIVE_MINT.equals(pubkey)
+export const isNativeMint = (pubkey: PublicKey) =>
+ NATIVE_MINT.equals(pubkey)
-export const hmac256 = async (secretKey: string, message: string) => {
- const encoder = new TextEncoder
+export const hmac256 = async (
+ secretKey: string,
+ message: string,
+): Promise => {
+ const encoder = new TextEncoder()
const messageUint8Array = encoder.encode(message)
const keyUint8Array = encoder.encode(secretKey)
- const cryptoKey = await crypto.subtle.importKey('raw', keyUint8Array, { name: 'HMAC', hash: 'SHA-256' }, false, ['sign'])
+ const cryptoKey = await crypto.subtle.importKey(
+ 'raw',
+ keyUint8Array,
+ { name: 'HMAC', hash: 'SHA-256' },
+ false,
+ ['sign'],
+ )
const signature = await crypto.subtle.sign('HMAC', cryptoKey, messageUint8Array)
- return Array.from(new Uint8Array(signature)).map((b) => b.toString(16).padStart(2, '0')).join('')
+ return Array.from(new Uint8Array(signature))
+ .map((b) => b.toString(16).padStart(2, '0'))
+ .join('')
}
-export const getGameHash = (rngSeed: string, clientSeed: string, nonce: number) => {
- return hmac256(rngSeed, [clientSeed, nonce].join('-'))
-}
+export const getGameHash = (rngSeed: string, clientSeed: string, nonce: number) =>
+ hmac256(rngSeed, [clientSeed, nonce].join('-'))
-export const getResultNumber = async (rngSeed: string, clientSeed: string, nonce: number) => {
+export const getResultNumber = async (
+ rngSeed: string,
+ clientSeed: string,
+ nonce: number,
+) => {
const hash = await getGameHash(rngSeed, clientSeed, nonce)
return parseInt(hash.substring(0, 5), 16)
}
-export type GameResult = ReturnType
+// ─── Explicit, portable return type ─────────────────────────────────────────
+export interface GameResult {
+ creator: PublicKey
+ user: PublicKey
+ rngSeed: string
+ clientSeed: string
+ nonce: number
+ bet: number[]
+ resultIndex: number
+ wager: number
+ payout: number
+ profit: number
+ multiplier: number
+ token: PublicKey
+ bonusUsed: number
+ jackpotWin: number
+}
-export const parseResult = (
- state: GameState,
-) => {
- const clientSeed = state.clientSeed
- const bet = state.bet.map((x) => x / BPS_PER_WHOLE)
- const nonce = state.nonce.toNumber() - 1
- const rngSeed = state.rngSeed
+/** Parses a `GameState` into a plain object */
+export const parseResult = (state: GameState): GameResult => {
+ const clientSeed = state.clientSeed
+ const bet = state.bet.map((x) => x / BPS_PER_WHOLE)
+ const nonce = state.nonce.toNumber() - 1
+ const rngSeed = state.rngSeed
const resultIndex = state.result.toNumber()
- const multiplier = bet[resultIndex]
- const wager = state.wager.toNumber()
- const payout = (wager * multiplier)
- const profit = (payout - wager)
+ const multiplier = bet[resultIndex]
+ const wager = state.wager.toNumber()
+ const payout = wager * multiplier
+ const profit = payout - wager
return {
creator: state.creator,
@@ -61,15 +91,16 @@ export const parseResult = (
}
}
+/** Waits for the next game‐account change and resolves with its parsed result */
export async function getNextResult(
connection: Connection,
user: PublicKey,
prevNonce: number,
-) {
- return new Promise((resolve, reject) => {
+): Promise {
+ return new Promise((resolve, reject) => {
const listener = connection.onAccountChange(
getGameAddress(user),
- async (account) => {
+ async (account: AccountInfo) => {
const current = decodeGame(account)
if (!current) {
connection.removeAccountChangeListener(listener)
@@ -77,8 +108,8 @@ export async function getNextResult(
}
if (current.nonce.toNumber() === prevNonce + 1) {
connection.removeAccountChangeListener(listener)
- const result = await parseResult(current)
- return resolve(result)
+ const result = parseResult(current)
+ resolve(result)
}
},
)
diff --git a/packages/multiplayer/package.json b/packages/multiplayer/package.json
new file mode 100644
index 00000000..19d0c231
--- /dev/null
+++ b/packages/multiplayer/package.json
@@ -0,0 +1,42 @@
+{
+ "name": "@gamba-labs/multiplayer-sdk",
+ "version": "0.1.2",
+ "private": false,
+ "description": "Gamba Multiplayer on-chain game helper layer (Anchor 0.31.1)",
+ "main": "dist/index.js",
+ "module": "dist/index.mjs",
+ "types": "dist/index.d.ts",
+ "files": [
+ "dist/**",
+ "idl/**"
+ ],
+ "sideEffects": false,
+ "scripts": {
+ "dev": "tsup src/index.ts --watch --format cjs,esm --dts",
+ "build": "tsup src/index.ts --format cjs,esm --dts",
+ "lint": "tsc --noEmit",
+ "test": "vitest run",
+ "clean": "rm -rf .turbo node_modules dist"
+ },
+ "peerDependencies": {
+ "@coral-xyz/anchor": "^0.31.1",
+ "@solana/web3.js": "^1.98.2",
+ "@solana/spl-token": "^0.4.13"
+ },
+ "devDependencies": {
+ "@types/node": "^24.0.10",
+ "tsup": "^8.5.0",
+ "typescript": "^5.2.2",
+ "vitest": "^1.6.0"
+ },
+ "publishConfig": {
+ "access": "public"
+ },
+ "keywords": [
+ "solana",
+ "anchor",
+ "gamba",
+ "multiplayer"
+ ],
+ "license": "MIT"
+}
diff --git a/packages/multiplayer/src/constants.ts b/packages/multiplayer/src/constants.ts
new file mode 100644
index 00000000..8472f3c1
--- /dev/null
+++ b/packages/multiplayer/src/constants.ts
@@ -0,0 +1,19 @@
+import { AnchorProvider, Program, utils as anchorUtils, web3 } from "@coral-xyz/anchor";
+import rawIdl from "./idl/multiplayer.json" with { type: "json" };
+import type { Multiplayer } from "./types/multiplayer.js";
+
+export const IDL = rawIdl as unknown as Multiplayer;
+export const PROGRAM_ID = new web3.PublicKey(IDL.address);
+
+export const WRAPPED_SOL_MINT = new web3.PublicKey(
+ "So11111111111111111111111111111111111111112",
+);
+
+export const getProgram = (p: AnchorProvider) => new Program(IDL, p);
+
+export function pda(seed: Uint8Array | Buffer | string[]) {
+ return web3.PublicKey.findProgramAddressSync(
+ Array.isArray(seed) ? seed.map(s => typeof s === "string" ? anchorUtils.bytes.utf8.encode(s) : s) : [seed],
+ PROGRAM_ID,
+ )[0];
+}
diff --git a/packages/multiplayer/src/errors.ts b/packages/multiplayer/src/errors.ts
new file mode 100644
index 00000000..427c36eb
--- /dev/null
+++ b/packages/multiplayer/src/errors.ts
@@ -0,0 +1,27 @@
+import rawIdl from "./idl/multiplayer.json" with { type: "json" };
+
+type IdlError = { code: number; name: string; msg: string };
+
+export const ERROR_MAP = new Map(
+ // @ts-ignore: IDL shape includes errors
+ (rawIdl as any).errors.map((e: IdlError) => [e.code, e]),
+);
+
+export function decodeAnchorError(err: unknown): string | null {
+ if (err && typeof err === "object" && "code" in err) {
+ const maybe = ERROR_MAP.get(Number((err as any).code));
+ if (maybe) return `${maybe.name} (${maybe.code}): ${maybe.msg}`;
+ }
+
+ if (typeof err === "string") {
+ const m = err.match(/custom program error: 0x([0-9a-f]+)/i);
+ if (m) {
+ const code = parseInt(m[1], 16);
+ const maybe = ERROR_MAP.get(code);
+ if (maybe) return `${maybe.name} (${maybe.code}): ${maybe.msg}`;
+ }
+ }
+
+ return null;
+}
+
\ No newline at end of file
diff --git a/packages/multiplayer/src/events.ts b/packages/multiplayer/src/events.ts
new file mode 100644
index 00000000..e8a645aa
--- /dev/null
+++ b/packages/multiplayer/src/events.ts
@@ -0,0 +1,112 @@
+import {
+ AnchorProvider,
+ EventParser,
+ IdlEvents,
+ utils as anchorUtils,
+} from "@coral-xyz/anchor";
+import type { IdlAccounts } from "@coral-xyz/anchor";
+import { PublicKey, Finality } from "@solana/web3.js";
+
+import { Multiplayer } from "./types/multiplayer.js";
+import { PROGRAM_ID, getProgram } from "./index.js";
+
+type AllEvents = IdlEvents; // union of all event names
+export type EventName = keyof AllEvents;
+export type EventData = AllEvents[N];
+
+export interface ParsedEvent {
+ data : EventData;
+ signature : string;
+ slot : number;
+ blockTime : number | null;
+}
+
+const FINALITY: Finality = "confirmed";
+
+export async function fetchRecentEvents(
+ provider: AnchorProvider,
+ name: N,
+ howMany = 5,
+): Promise[]> {
+ const { connection } = provider;
+ const program = getProgram(provider);
+
+ const sigs = await connection.getSignaturesForAddress(
+ PROGRAM_ID,
+ { limit: howMany * 10 },
+ FINALITY,
+ );
+
+ const txs = await connection.getTransactions(
+ sigs.map(s => s.signature),
+ {
+ maxSupportedTransactionVersion: 0,
+ commitment: FINALITY,
+ },
+ );
+
+ const parser = new EventParser(PROGRAM_ID, program.coder);
+ const out: ParsedEvent[] = [];
+
+ txs.forEach((tx, i) => {
+ const logs = tx?.meta?.logMessages;
+ if (!logs) return;
+ try {
+ for (const ev of parser.parseLogs(logs)) {
+ if (ev.name === name) {
+ out.push({
+ data: ev.data as EventData,
+ signature: sigs[i].signature,
+ slot: sigs[i].slot,
+ blockTime: sigs[i].blockTime ?? null,
+ });
+ }
+ }
+ } catch {}
+ });
+
+ return out
+ .sort((a, b) => b.slot - a.slot) // newest first
+ .slice(0, howMany);
+}
+
+export const fetchRecentGameCreated = (p: AnchorProvider, n = 5) =>
+ fetchRecentEvents(p, "gameCreated", n);
+
+export const fetchRecentPlayerJoined = (p: AnchorProvider, n = 5) =>
+ fetchRecentEvents(p, "playerJoined", n);
+
+export const fetchRecentPlayerLeft = (p: AnchorProvider, n = 5) =>
+ fetchRecentEvents(p, "playerLeft", n);
+
+export const fetchRecentGameSettledPartial = (p: AnchorProvider, n = 5) =>
+ fetchRecentEvents(p, "gameSettledPartial", n);
+
+export const fetchRecentWinnersSelected = (p: AnchorProvider, n = 5) =>
+ fetchRecentEvents(p, "winnersSelected", n);
+
+export async function fetchRecentSpecificWinners(
+ provider : AnchorProvider,
+ creator : PublicKey,
+ maxPlayers : number,
+ howMany = 8,
+): Promise[]> {
+ const raw = await fetchRecentEvents(provider, "winnersSelected", howMany * 5);
+ if (!raw.length) return [];
+
+ return raw
+ .filter(ev =>
+ ev.data.gameMaker.equals(creator) &&
+ ev.data.maxPlayers === maxPlayers
+ )
+ .slice(0, howMany);
+}
+
+export function getEventDiscriminator(name: N): Uint8Array {
+ const hex = anchorUtils.sha256.hash(`event:${name}`);
+ const bytes = new Uint8Array(8);
+ for (let i = 0; i < 8; i++) {
+ bytes[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
+ }
+ return bytes;
+}
diff --git a/packages/multiplayer/src/fetch.ts b/packages/multiplayer/src/fetch.ts
new file mode 100644
index 00000000..ac3ea13a
--- /dev/null
+++ b/packages/multiplayer/src/fetch.ts
@@ -0,0 +1,137 @@
+/* ------------------------------------------------------------------
+ fetch.ts – tiny helpers around Anchor’s account APIs
+ Anchor v0.31.1 • Solana web3.js v1.98.2
+------------------------------------------------------------------- */
+import type { AnchorProvider, IdlAccounts } from "@coral-xyz/anchor";
+import { BN, utils as anchorUtils, web3 } from "@coral-xyz/anchor";
+import { PublicKey } from "@solana/web3.js";
+
+import type { Multiplayer } from "./types/multiplayer.js";
+import { getProgram, PROGRAM_ID } from "./constants.js";
+import {
+ deriveGamePdaFromSeed,
+ deriveMetadataPda,
+} from "./utils/pda.js";
+
+/** Full shape of a fetched Game account + its public key */
+export type GameAccountFull = {
+ publicKey: PublicKey;
+ account: IdlAccounts["game"];
+};
+
+/**
+ * Fetch all Multiplayer game accounts.
+ */
+export const fetchGames = async (
+ provider: AnchorProvider,
+ filter?: (g: GameAccountFull) => boolean,
+): Promise => {
+ const program = getProgram(provider);
+ const games = await program.account.game.all();
+ return filter ? games.filter(filter) : games;
+};
+
+/** Optional filters for narrowing the Multiplayer games list */
+export type SpecificGameFilters = {
+ creator?: PublicKey;
+ maxPlayers?: number;
+ wagerType?: number; // enum repr as u8
+ payoutType?: number; // enum repr as u8
+ winnersTarget?: number;
+ mint?: PublicKey;
+};
+
+/**
+ * Fetch games by optional filters. Backwards compatible with the old
+ * (provider, creator, maxPlayers) signature.
+ */
+export function fetchSpecificGames(
+ provider: AnchorProvider,
+ filters: SpecificGameFilters,
+): Promise;
+export function fetchSpecificGames(
+ provider: AnchorProvider,
+ creator: PublicKey,
+ maxPlayers: number,
+): Promise;
+export async function fetchSpecificGames(
+ provider: AnchorProvider,
+ arg1: SpecificGameFilters | PublicKey,
+ arg2?: number,
+): Promise {
+ const filters: SpecificGameFilters = arg1 instanceof PublicKey
+ ? { creator: arg1, maxPlayers: arg2 }
+ : (arg1 ?? {});
+
+ return fetchGames(provider, (g) => {
+ const a = g.account as IdlAccounts["game"] & Record;
+ if (filters.creator && !a.gameMaker.equals(filters.creator)) return false;
+ if (filters.maxPlayers != null && a.maxPlayers !== filters.maxPlayers) return false;
+ if (filters.wagerType != null && Number(a.wagerType) !== Number(filters.wagerType)) return false;
+ if (filters.payoutType != null && Number(a.payoutType) !== Number(filters.payoutType)) return false;
+ if (filters.winnersTarget != null && Number(a.winnersTarget) !== Number(filters.winnersTarget)) return false;
+ if (filters.mint != null && !a.mint.equals(filters.mint)) return false;
+ return true;
+ });
+}
+
+/** Shape of the GambaState PDA */
+export type GambaStateFull = {
+ publicKey: PublicKey;
+ account: IdlAccounts["gambaState"];
+};
+
+/**
+ * Fetch the global GambaState PDA.
+ */
+export const fetchGambaState = async (
+ provider: AnchorProvider,
+): Promise => {
+ const program = getProgram(provider);
+ const [gambaStatePk] = PublicKey.findProgramAddressSync(
+ [anchorUtils.bytes.utf8.encode("GAMBA_STATE")],
+ PROGRAM_ID,
+ );
+ const account = await program.account.gambaState.fetch(gambaStatePk);
+ return { publicKey: gambaStatePk, account };
+};
+
+/** Shape of the PlayerMetadataAccount PDA */
+export type MetadataAccountFull = {
+ publicKey: PublicKey;
+ account: IdlAccounts["playerMetadataAccount"];
+};
+
+/**
+ * Fetch the on-chain metadata for all players in a game.
+ */
+export const fetchPlayerMetadata = async (
+ provider: AnchorProvider,
+ gameSeed: BN | number,
+): Promise> => {
+ const program = getProgram(provider);
+
+ // derive the PDAs
+ const gamePda = deriveGamePdaFromSeed(gameSeed);
+ const metaPda = deriveMetadataPda(gamePda);
+
+ // fetch & cast explicitly (parenthesized to avoid JSX/generic parse issues)
+ const metaAccount = (await program
+ .account
+ .playerMetadataAccount
+ .fetch(metaPda)) as IdlAccounts["playerMetadataAccount"];
+
+ const out: Record = {};
+ const entries = (metaAccount as any).entries as Array<{ player: PublicKey; meta: number[] }>;
+
+ for (const { player, meta } of entries) {
+ const buf = Uint8Array.from(meta);
+ // trim trailing zeros
+ let len = buf.length;
+ while (len > 0 && buf[len - 1] === 0) len--;
+ const str = new TextDecoder().decode(buf.slice(0, len));
+ out[player.toBase58()] = str;
+ }
+
+ return out;
+};
diff --git a/packages/multiplayer/src/idl/multiplayer.json b/packages/multiplayer/src/idl/multiplayer.json
new file mode 100644
index 00000000..a3146dfd
--- /dev/null
+++ b/packages/multiplayer/src/idl/multiplayer.json
@@ -0,0 +1,2153 @@
+{
+ "address": "GambaMyTW8C1NSeFrv2c3KfmX1MSDBF6YbxDeb7dBPxM",
+ "metadata": {
+ "name": "multiplayer",
+ "version": "0.1.0",
+ "spec": "0.1.0",
+ "description": "Created with Anchor"
+ },
+ "instructions": [
+ {
+ "name": "create_game_native",
+ "discriminator": [
+ 14,
+ 237,
+ 122,
+ 202,
+ 102,
+ 88,
+ 48,
+ 75
+ ],
+ "accounts": [
+ {
+ "name": "game_account",
+ "docs": [
+ "The on‐chain Game account, PDA = [b\"GAME\", game_seed]."
+ ],
+ "writable": true,
+ "pda": {
+ "seeds": [
+ {
+ "kind": "const",
+ "value": [
+ 71,
+ 65,
+ 77,
+ 69
+ ]
+ },
+ {
+ "kind": "arg",
+ "path": "game_seed"
+ }
+ ]
+ }
+ },
+ {
+ "name": "metadata_account",
+ "docs": [
+ "Always-present metadata PDA, PDA = [b\"METADATA\", game_account.key()]"
+ ],
+ "writable": true,
+ "pda": {
+ "seeds": [
+ {
+ "kind": "const",
+ "value": [
+ 77,
+ 69,
+ 84,
+ 65,
+ 68,
+ 65,
+ 84,
+ 65
+ ]
+ },
+ {
+ "kind": "account",
+ "path": "game_account"
+ }
+ ]
+ }
+ },
+ {
+ "name": "mint",
+ "docs": [
+ "always SOL (placeholder mint)"
+ ]
+ },
+ {
+ "name": "game_maker",
+ "docs": [
+ "creator of the game, pays rent on init"
+ ],
+ "writable": true,
+ "signer": true
+ },
+ {
+ "name": "gamba_state",
+ "docs": [
+ "global config PDA"
+ ],
+ "writable": true,
+ "pda": {
+ "seeds": [
+ {
+ "kind": "const",
+ "value": [
+ 71,
+ 65,
+ 77,
+ 66,
+ 65,
+ 95,
+ 83,
+ 84,
+ 65,
+ 84,
+ 69
+ ]
+ }
+ ]
+ }
+ },
+ {
+ "name": "system_program",
+ "address": "11111111111111111111111111111111"
+ }
+ ],
+ "args": [
+ {
+ "name": "pre_alloc_players",
+ "type": "u16"
+ },
+ {
+ "name": "max_players",
+ "type": "u16"
+ },
+ {
+ "name": "num_teams",
+ "type": "u8"
+ },
+ {
+ "name": "winners_target",
+ "type": "u16"
+ },
+ {
+ "name": "wager_type",
+ "type": "u8"
+ },
+ {
+ "name": "payout_type",
+ "type": "u8"
+ },
+ {
+ "name": "wager",
+ "type": "u64"
+ },
+ {
+ "name": "soft_duration",
+ "type": "i64"
+ },
+ {
+ "name": "hard_duration",
+ "type": "i64"
+ },
+ {
+ "name": "game_seed",
+ "type": "u64"
+ },
+ {
+ "name": "min_bet",
+ "type": "u64"
+ },
+ {
+ "name": "max_bet",
+ "type": "u64"
+ }
+ ]
+ },
+ {
+ "name": "create_game_spl",
+ "discriminator": [
+ 80,
+ 235,
+ 44,
+ 243,
+ 14,
+ 15,
+ 248,
+ 207
+ ],
+ "accounts": [
+ {
+ "name": "game_account",
+ "docs": [
+ "The on-chain Game account, PDA = [b\"GAME\", game_seed]."
+ ],
+ "writable": true,
+ "pda": {
+ "seeds": [
+ {
+ "kind": "const",
+ "value": [
+ 71,
+ 65,
+ 77,
+ 69
+ ]
+ },
+ {
+ "kind": "arg",
+ "path": "game_seed"
+ }
+ ]
+ }
+ },
+ {
+ "name": "metadata_account",
+ "docs": [
+ "Always-present metadata PDA, PDA = [b\"METADATA\", game_account.key()]"
+ ],
+ "writable": true,
+ "pda": {
+ "seeds": [
+ {
+ "kind": "const",
+ "value": [
+ 77,
+ 69,
+ 84,
+ 65,
+ 68,
+ 65,
+ 84,
+ 65
+ ]
+ },
+ {
+ "kind": "account",
+ "path": "game_account"
+ }
+ ]
+ }
+ },
+ {
+ "name": "mint",
+ "docs": [
+ "the SPL mint for wagers"
+ ]
+ },
+ {
+ "name": "game_account_ta_account",
+ "docs": [
+ "escrow token account for this game"
+ ],
+ "writable": true,
+ "pda": {
+ "seeds": [
+ {
+ "kind": "account",
+ "path": "game_account"
+ }
+ ]
+ }
+ },
+ {
+ "name": "game_maker",
+ "writable": true,
+ "signer": true
+ },
+ {
+ "name": "gamba_state",
+ "writable": true,
+ "pda": {
+ "seeds": [
+ {
+ "kind": "const",
+ "value": [
+ 71,
+ 65,
+ 77,
+ 66,
+ 65,
+ 95,
+ 83,
+ 84,
+ 65,
+ 84,
+ 69
+ ]
+ }
+ ]
+ }
+ },
+ {
+ "name": "system_program",
+ "address": "11111111111111111111111111111111"
+ },
+ {
+ "name": "token_program",
+ "address": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
+ },
+ {
+ "name": "associated_token_program",
+ "address": "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"
+ }
+ ],
+ "args": [
+ {
+ "name": "pre_alloc_players",
+ "type": "u16"
+ },
+ {
+ "name": "max_players",
+ "type": "u16"
+ },
+ {
+ "name": "num_teams",
+ "type": "u8"
+ },
+ {
+ "name": "winners_target",
+ "type": "u16"
+ },
+ {
+ "name": "wager_type",
+ "type": "u8"
+ },
+ {
+ "name": "payout_type",
+ "type": "u8"
+ },
+ {
+ "name": "wager",
+ "type": "u64"
+ },
+ {
+ "name": "soft_duration",
+ "type": "i64"
+ },
+ {
+ "name": "hard_duration",
+ "type": "i64"
+ },
+ {
+ "name": "game_seed",
+ "type": "u64"
+ },
+ {
+ "name": "min_bet",
+ "type": "u64"
+ },
+ {
+ "name": "max_bet",
+ "type": "u64"
+ }
+ ]
+ },
+ {
+ "name": "distribute_native",
+ "discriminator": [
+ 32,
+ 200,
+ 172,
+ 49,
+ 57,
+ 234,
+ 137,
+ 89
+ ],
+ "accounts": [
+ {
+ "name": "payer",
+ "docs": [
+ "Tx signer (fees & optional PDA‑close refund go here)"
+ ],
+ "signer": true
+ },
+ {
+ "name": "gamba_state",
+ "docs": [
+ "Global configuration – for fee vault + RNG auth"
+ ],
+ "pda": {
+ "seeds": [
+ {
+ "kind": "const",
+ "value": [
+ 71,
+ 65,
+ 77,
+ 66,
+ 65,
+ 95,
+ 83,
+ 84,
+ 65,
+ 84,
+ 69
+ ]
+ }
+ ]
+ }
+ },
+ {
+ "name": "game_account",
+ "docs": [
+ "Game account (large PDA, parsed manually)"
+ ],
+ "writable": true
+ },
+ {
+ "name": "game_maker",
+ "docs": [
+ "Game‑maker – receives any closing lamports"
+ ],
+ "writable": true
+ },
+ {
+ "name": "gamba_fee_address",
+ "docs": [
+ "Protocol fee vault"
+ ],
+ "writable": true
+ },
+ {
+ "name": "metadata_account",
+ "docs": [
+ "Metadata PDA – to be closed at settlement"
+ ],
+ "writable": true,
+ "pda": {
+ "seeds": [
+ {
+ "kind": "const",
+ "value": [
+ 77,
+ 69,
+ 84,
+ 65,
+ 68,
+ 65,
+ 84,
+ 65
+ ]
+ },
+ {
+ "kind": "account",
+ "path": "game_account"
+ }
+ ]
+ }
+ },
+ {
+ "name": "system_program",
+ "address": "11111111111111111111111111111111"
+ }
+ ],
+ "args": []
+ },
+ {
+ "name": "distribute_spl",
+ "discriminator": [
+ 93,
+ 24,
+ 158,
+ 238,
+ 198,
+ 173,
+ 215,
+ 228
+ ],
+ "accounts": [
+ {
+ "name": "payer",
+ "writable": true,
+ "signer": true
+ },
+ {
+ "name": "gamba_state",
+ "pda": {
+ "seeds": [
+ {
+ "kind": "const",
+ "value": [
+ 71,
+ 65,
+ 77,
+ 66,
+ 65,
+ 95,
+ 83,
+ 84,
+ 65,
+ 84,
+ 69
+ ]
+ }
+ ]
+ }
+ },
+ {
+ "name": "game_account",
+ "docs": [
+ "Raw Game PDA (parsed by offsets)"
+ ],
+ "writable": true
+ },
+ {
+ "name": "metadata_account",
+ "docs": [
+ "Always-present metadata PDA (to be closed on final settlement)"
+ ],
+ "writable": true,
+ "pda": {
+ "seeds": [
+ {
+ "kind": "const",
+ "value": [
+ 77,
+ 69,
+ 84,
+ 65,
+ 68,
+ 65,
+ 84,
+ 65
+ ]
+ },
+ {
+ "kind": "account",
+ "path": "game_account"
+ }
+ ]
+ }
+ },
+ {
+ "name": "game_account_ta",
+ "docs": [
+ "Game escrow ATA (PDA = [game_account])"
+ ],
+ "writable": true
+ },
+ {
+ "name": "mint"
+ },
+ {
+ "name": "gamba_fee_ata",
+ "docs": [
+ "Protocol fee vault ATA for this mint"
+ ],
+ "writable": true,
+ "pda": {
+ "seeds": [
+ {
+ "kind": "account",
+ "path": "gamba_fee_address"
+ },
+ {
+ "kind": "const",
+ "value": [
+ 6,
+ 221,
+ 246,
+ 225,
+ 215,
+ 101,
+ 161,
+ 147,
+ 217,
+ 203,
+ 225,
+ 70,
+ 206,
+ 235,
+ 121,
+ 172,
+ 28,
+ 180,
+ 133,
+ 237,
+ 95,
+ 91,
+ 55,
+ 145,
+ 58,
+ 140,
+ 245,
+ 133,
+ 126,
+ 255,
+ 0,
+ 169
+ ]
+ },
+ {
+ "kind": "account",
+ "path": "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": "gamba_fee_address",
+ "docs": [
+ "Protocol fee vault (owner of `gamba_fee_ata`)"
+ ],
+ "writable": true
+ },
+ {
+ "name": "game_maker",
+ "docs": [
+ "Game‑maker (receives escrow close lamports)"
+ ],
+ "writable": true
+ },
+ {
+ "name": "creator_ata_0",
+ "writable": true,
+ "optional": true
+ },
+ {
+ "name": "creator_ata_1",
+ "writable": true,
+ "optional": true
+ },
+ {
+ "name": "creator_ata_2",
+ "writable": true,
+ "optional": true
+ },
+ {
+ "name": "creator_ata_3",
+ "writable": true,
+ "optional": true
+ },
+ {
+ "name": "creator_ata_4",
+ "writable": true,
+ "optional": true
+ },
+ {
+ "name": "winner_ata_0",
+ "writable": true,
+ "optional": true
+ },
+ {
+ "name": "winner_ata_1",
+ "writable": true,
+ "optional": true
+ },
+ {
+ "name": "winner_ata_2",
+ "writable": true,
+ "optional": true
+ },
+ {
+ "name": "winner_ata_3",
+ "writable": true,
+ "optional": true
+ },
+ {
+ "name": "winner_ata_4",
+ "writable": true,
+ "optional": true
+ },
+ {
+ "name": "winner_ata_5",
+ "writable": true,
+ "optional": true
+ },
+ {
+ "name": "winner_ata_6",
+ "writable": true,
+ "optional": true
+ },
+ {
+ "name": "winner_ata_7",
+ "writable": true,
+ "optional": true
+ },
+ {
+ "name": "winner_ata_8",
+ "writable": true,
+ "optional": true
+ },
+ {
+ "name": "winner_ata_9",
+ "writable": true,
+ "optional": true
+ },
+ {
+ "name": "winner_ata_10",
+ "writable": true,
+ "optional": true
+ },
+ {
+ "name": "winner_ata_11",
+ "writable": true,
+ "optional": true
+ },
+ {
+ "name": "winner_ata_12",
+ "writable": true,
+ "optional": true
+ },
+ {
+ "name": "winner_ata_13",
+ "writable": true,
+ "optional": true
+ },
+ {
+ "name": "winner_ata_14",
+ "writable": true,
+ "optional": true
+ },
+ {
+ "name": "token_program",
+ "address": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
+ },
+ {
+ "name": "associated_token_program",
+ "address": "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"
+ },
+ {
+ "name": "system_program",
+ "address": "11111111111111111111111111111111"
+ }
+ ],
+ "args": []
+ },
+ {
+ "name": "gamba_config",
+ "discriminator": [
+ 232,
+ 208,
+ 249,
+ 92,
+ 159,
+ 187,
+ 21,
+ 254
+ ],
+ "accounts": [
+ {
+ "name": "gamba_state",
+ "writable": true,
+ "pda": {
+ "seeds": [
+ {
+ "kind": "const",
+ "value": [
+ 71,
+ 65,
+ 77,
+ 66,
+ 65,
+ 95,
+ 83,
+ 84,
+ 65,
+ 84,
+ 69
+ ]
+ }
+ ]
+ }
+ },
+ {
+ "name": "authority",
+ "writable": true,
+ "signer": true
+ },
+ {
+ "name": "system_program",
+ "address": "11111111111111111111111111111111"
+ }
+ ],
+ "args": [
+ {
+ "name": "fee_vault",
+ "type": "pubkey"
+ },
+ {
+ "name": "fee_bps",
+ "type": "u32"
+ },
+ {
+ "name": "rng",
+ "type": "pubkey"
+ },
+ {
+ "name": "authority",
+ "type": "pubkey"
+ }
+ ]
+ },
+ {
+ "name": "join_game",
+ "discriminator": [
+ 107,
+ 112,
+ 18,
+ 38,
+ 56,
+ 173,
+ 60,
+ 128
+ ],
+ "accounts": [
+ {
+ "name": "game_account",
+ "docs": [
+ "Game account as raw bytes"
+ ],
+ "writable": true
+ },
+ {
+ "name": "gamba_state",
+ "writable": true,
+ "pda": {
+ "seeds": [
+ {
+ "kind": "const",
+ "value": [
+ 71,
+ 65,
+ 77,
+ 66,
+ 65,
+ 95,
+ 83,
+ 84,
+ 65,
+ 84,
+ 69
+ ]
+ }
+ ]
+ }
+ },
+ {
+ "name": "game_account_ta",
+ "docs": [
+ "Optional escrow token account (only for SPL games)"
+ ],
+ "writable": true,
+ "optional": true,
+ "pda": {
+ "seeds": [
+ {
+ "kind": "account",
+ "path": "game_account"
+ }
+ ]
+ }
+ },
+ {
+ "name": "mint",
+ "docs": [
+ "Mint used for wagers (native SOL or SPL)"
+ ]
+ },
+ {
+ "name": "player_account",
+ "docs": [
+ "Player joining (payer + signer)"
+ ],
+ "writable": true,
+ "signer": true
+ },
+ {
+ "name": "player_ata",
+ "docs": [
+ "Optional player ATA if SPL wager"
+ ],
+ "writable": true,
+ "optional": true,
+ "pda": {
+ "seeds": [
+ {
+ "kind": "account",
+ "path": "player_account"
+ },
+ {
+ "kind": "const",
+ "value": [
+ 6,
+ 221,
+ 246,
+ 225,
+ 215,
+ 101,
+ 161,
+ 147,
+ 217,
+ 203,
+ 225,
+ 70,
+ 206,
+ 235,
+ 121,
+ 172,
+ 28,
+ 180,
+ 133,
+ 237,
+ 95,
+ 91,
+ 55,
+ 145,
+ 58,
+ 140,
+ 245,
+ 133,
+ 126,
+ 255,
+ 0,
+ 169
+ ]
+ },
+ {
+ "kind": "account",
+ "path": "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": "creator_address",
+ "docs": [
+ "Creator/referrer collecting a fee"
+ ]
+ },
+ {
+ "name": "creator_ata",
+ "docs": [
+ "Creator’s ATA (created lazily)"
+ ],
+ "writable": true,
+ "pda": {
+ "seeds": [
+ {
+ "kind": "account",
+ "path": "creator_address"
+ },
+ {
+ "kind": "const",
+ "value": [
+ 6,
+ 221,
+ 246,
+ 225,
+ 215,
+ 101,
+ 161,
+ 147,
+ 217,
+ 203,
+ 225,
+ 70,
+ 206,
+ 235,
+ 121,
+ 172,
+ 28,
+ 180,
+ 133,
+ 237,
+ 95,
+ 91,
+ 55,
+ 145,
+ 58,
+ 140,
+ 245,
+ 133,
+ 126,
+ 255,
+ 0,
+ 169
+ ]
+ },
+ {
+ "kind": "account",
+ "path": "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": "metadata_account",
+ "docs": [
+ "Always-present metadata PDA"
+ ],
+ "writable": true,
+ "pda": {
+ "seeds": [
+ {
+ "kind": "const",
+ "value": [
+ 77,
+ 69,
+ 84,
+ 65,
+ 68,
+ 65,
+ 84,
+ 65
+ ]
+ },
+ {
+ "kind": "account",
+ "path": "game_account"
+ }
+ ]
+ }
+ },
+ {
+ "name": "system_program",
+ "docs": [
+ "Programs"
+ ],
+ "address": "11111111111111111111111111111111"
+ },
+ {
+ "name": "associated_token_program",
+ "address": "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"
+ },
+ {
+ "name": "token_program",
+ "address": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
+ }
+ ],
+ "args": [
+ {
+ "name": "creator_fee_bps",
+ "type": "u32"
+ },
+ {
+ "name": "wager",
+ "type": "u64"
+ },
+ {
+ "name": "team",
+ "type": "u8"
+ },
+ {
+ "name": "player_meta",
+ "type": {
+ "array": [
+ "u8",
+ 32
+ ]
+ }
+ }
+ ]
+ },
+ {
+ "name": "leave_game",
+ "discriminator": [
+ 218,
+ 226,
+ 6,
+ 0,
+ 243,
+ 34,
+ 125,
+ 201
+ ],
+ "accounts": [
+ {
+ "name": "game_account",
+ "docs": [
+ "Game PDA – parsed manually"
+ ],
+ "writable": true
+ },
+ {
+ "name": "mint"
+ },
+ {
+ "name": "player_account",
+ "writable": true,
+ "signer": true
+ },
+ {
+ "name": "player_ata",
+ "writable": true,
+ "optional": true,
+ "pda": {
+ "seeds": [
+ {
+ "kind": "account",
+ "path": "player_account"
+ },
+ {
+ "kind": "const",
+ "value": [
+ 6,
+ 221,
+ 246,
+ 225,
+ 215,
+ 101,
+ 161,
+ 147,
+ 217,
+ 203,
+ 225,
+ 70,
+ 206,
+ 235,
+ 121,
+ 172,
+ 28,
+ 180,
+ 133,
+ 237,
+ 95,
+ 91,
+ 55,
+ 145,
+ 58,
+ 140,
+ 245,
+ 133,
+ 126,
+ 255,
+ 0,
+ 169
+ ]
+ },
+ {
+ "kind": "account",
+ "path": "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": "game_account_ta",
+ "writable": true,
+ "optional": true,
+ "pda": {
+ "seeds": [
+ {
+ "kind": "account",
+ "path": "game_account"
+ }
+ ]
+ }
+ },
+ {
+ "name": "metadata_account",
+ "docs": [
+ "Always-present metadata PDA"
+ ],
+ "writable": true,
+ "pda": {
+ "seeds": [
+ {
+ "kind": "const",
+ "value": [
+ 77,
+ 69,
+ 84,
+ 65,
+ 68,
+ 65,
+ 84,
+ 65
+ ]
+ },
+ {
+ "kind": "account",
+ "path": "game_account"
+ }
+ ]
+ }
+ },
+ {
+ "name": "system_program",
+ "address": "11111111111111111111111111111111"
+ },
+ {
+ "name": "associated_token_program",
+ "address": "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"
+ },
+ {
+ "name": "token_program",
+ "address": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
+ }
+ ],
+ "args": []
+ },
+ {
+ "name": "select_winners",
+ "discriminator": [
+ 80,
+ 100,
+ 28,
+ 131,
+ 83,
+ 199,
+ 222,
+ 80
+ ],
+ "accounts": [
+ {
+ "name": "rng",
+ "docs": [
+ "Authorised RNG bot"
+ ],
+ "writable": true,
+ "signer": true
+ },
+ {
+ "name": "gamba_state",
+ "docs": [
+ "Global config – only used to enforce `rng`"
+ ],
+ "pda": {
+ "seeds": [
+ {
+ "kind": "const",
+ "value": [
+ 71,
+ 65,
+ 77,
+ 66,
+ 65,
+ 95,
+ 83,
+ 84,
+ 65,
+ 84,
+ 69
+ ]
+ }
+ ]
+ }
+ },
+ {
+ "name": "game_account",
+ "docs": [
+ "Raw Game account; we parse fields by hand"
+ ],
+ "writable": true
+ }
+ ],
+ "args": []
+ }
+ ],
+ "accounts": [
+ {
+ "name": "GambaState",
+ "discriminator": [
+ 142,
+ 203,
+ 14,
+ 224,
+ 153,
+ 118,
+ 52,
+ 200
+ ]
+ },
+ {
+ "name": "Game",
+ "discriminator": [
+ 27,
+ 90,
+ 166,
+ 125,
+ 74,
+ 100,
+ 121,
+ 18
+ ]
+ },
+ {
+ "name": "PlayerMetadataAccount",
+ "discriminator": [
+ 204,
+ 224,
+ 199,
+ 121,
+ 70,
+ 159,
+ 53,
+ 55
+ ]
+ }
+ ],
+ "events": [
+ {
+ "name": "GameCreated",
+ "discriminator": [
+ 218,
+ 25,
+ 150,
+ 94,
+ 177,
+ 112,
+ 96,
+ 2
+ ]
+ },
+ {
+ "name": "GameSettledPartial",
+ "discriminator": [
+ 208,
+ 36,
+ 152,
+ 148,
+ 220,
+ 252,
+ 60,
+ 89
+ ]
+ },
+ {
+ "name": "PlayerJoined",
+ "discriminator": [
+ 39,
+ 144,
+ 49,
+ 106,
+ 108,
+ 210,
+ 183,
+ 38
+ ]
+ },
+ {
+ "name": "PlayerLeft",
+ "discriminator": [
+ 7,
+ 106,
+ 62,
+ 150,
+ 175,
+ 170,
+ 96,
+ 84
+ ]
+ },
+ {
+ "name": "WinnersSelected",
+ "discriminator": [
+ 28,
+ 151,
+ 185,
+ 12,
+ 70,
+ 199,
+ 73,
+ 58
+ ]
+ }
+ ],
+ "errors": [
+ {
+ "code": 6000,
+ "name": "PlayerAlreadyInGame",
+ "msg": "Player is already in the game"
+ },
+ {
+ "code": 6001,
+ "name": "PlayerNotInGame",
+ "msg": "Player is not in the game"
+ },
+ {
+ "code": 6002,
+ "name": "GameInProgress",
+ "msg": "Game is already in progress"
+ },
+ {
+ "code": 6003,
+ "name": "InvalidGameAccount",
+ "msg": "Invalid game account"
+ },
+ {
+ "code": 6004,
+ "name": "CannotSettleYet",
+ "msg": "Cannot settle yet"
+ },
+ {
+ "code": 6005,
+ "name": "AuthorityMismatch",
+ "msg": "Signer / authority mismatch"
+ },
+ {
+ "code": 6006,
+ "name": "InvalidInput",
+ "msg": "Invalid input"
+ },
+ {
+ "code": 6007,
+ "name": "AlreadySettled",
+ "msg": "Game already settled"
+ },
+ {
+ "code": 6008,
+ "name": "NumericalOverflow",
+ "msg": "numerical overflow"
+ },
+ {
+ "code": 6009,
+ "name": "CreatorMismatch",
+ "msg": "Creator Missmatch"
+ },
+ {
+ "code": 6010,
+ "name": "PlayerMismatch",
+ "msg": "player rmismatch"
+ },
+ {
+ "code": 6011,
+ "name": "GameFull",
+ "msg": "Game is Full"
+ }
+ ],
+ "types": [
+ {
+ "name": "CreatorPending",
+ "type": {
+ "kind": "struct",
+ "fields": [
+ {
+ "name": "address",
+ "type": "pubkey"
+ },
+ {
+ "name": "amount",
+ "type": "u64"
+ }
+ ]
+ }
+ },
+ {
+ "name": "GambaState",
+ "type": {
+ "kind": "struct",
+ "fields": [
+ {
+ "name": "rng",
+ "type": "pubkey"
+ },
+ {
+ "name": "authority",
+ "type": "pubkey"
+ },
+ {
+ "name": "gamba_fee_address",
+ "type": "pubkey"
+ },
+ {
+ "name": "gamba_fee_bps",
+ "type": "u32"
+ },
+ {
+ "name": "initialized",
+ "type": "bool"
+ },
+ {
+ "name": "game_id",
+ "type": "u64"
+ },
+ {
+ "name": "bump",
+ "type": "u8"
+ }
+ ]
+ }
+ },
+ {
+ "name": "Game",
+ "type": {
+ "kind": "struct",
+ "fields": [
+ {
+ "name": "game_maker",
+ "type": "pubkey"
+ },
+ {
+ "name": "mint",
+ "type": "pubkey"
+ },
+ {
+ "name": "game_type",
+ "type": {
+ "defined": {
+ "name": "GameType"
+ }
+ }
+ },
+ {
+ "name": "wager_type",
+ "type": {
+ "defined": {
+ "name": "WagerType"
+ }
+ }
+ },
+ {
+ "name": "payout_type",
+ "type": {
+ "defined": {
+ "name": "PayoutType"
+ }
+ }
+ },
+ {
+ "name": "max_players",
+ "type": "u16"
+ },
+ {
+ "name": "pre_alloc",
+ "type": "u16"
+ },
+ {
+ "name": "num_teams",
+ "type": "u8"
+ },
+ {
+ "name": "winners_target",
+ "type": "u16"
+ },
+ {
+ "name": "wager",
+ "type": "u64"
+ },
+ {
+ "name": "soft_expiration_timestamp",
+ "type": "i64"
+ },
+ {
+ "name": "hard_expiration_timestamp",
+ "type": "i64"
+ },
+ {
+ "name": "creation_timestamp",
+ "docs": [
+ "New field: when the game was created (unix timestamp)"
+ ],
+ "type": "i64"
+ },
+ {
+ "name": "state",
+ "type": {
+ "defined": {
+ "name": "GameState"
+ }
+ }
+ },
+ {
+ "name": "pending_gamba_fee",
+ "type": "u64"
+ },
+ {
+ "name": "game_id",
+ "type": "u64"
+ },
+ {
+ "name": "game_seed",
+ "docs": [
+ "Seeds randomness for select_winners"
+ ],
+ "type": "u64"
+ },
+ {
+ "name": "min_bet",
+ "type": "u64"
+ },
+ {
+ "name": "max_bet",
+ "type": "u64"
+ },
+ {
+ "name": "bump",
+ "type": "u8"
+ },
+ {
+ "name": "players",
+ "type": {
+ "vec": {
+ "defined": {
+ "name": "Player"
+ }
+ }
+ }
+ },
+ {
+ "name": "winner_indexes",
+ "type": {
+ "vec": "u16"
+ }
+ },
+ {
+ "name": "creators_pending",
+ "type": {
+ "vec": {
+ "defined": {
+ "name": "CreatorPending"
+ }
+ }
+ }
+ }
+ ]
+ }
+ },
+ {
+ "name": "GameCreated",
+ "type": {
+ "kind": "struct",
+ "fields": [
+ {
+ "name": "game_id",
+ "type": "u64"
+ },
+ {
+ "name": "game_account",
+ "type": "pubkey"
+ },
+ {
+ "name": "game_maker",
+ "type": "pubkey"
+ },
+ {
+ "name": "max_players",
+ "type": "u16"
+ },
+ {
+ "name": "num_teams",
+ "type": "u8"
+ },
+ {
+ "name": "game_type",
+ "type": "u8"
+ },
+ {
+ "name": "wager_type",
+ "type": "u8"
+ },
+ {
+ "name": "payout_type",
+ "type": "u8"
+ },
+ {
+ "name": "winners_target",
+ "type": "u16"
+ },
+ {
+ "name": "soft_duration_seconds",
+ "type": "i64"
+ },
+ {
+ "name": "hard_duration_seconds",
+ "type": "i64"
+ },
+ {
+ "name": "soft_expiration_timestamp",
+ "type": "i64"
+ },
+ {
+ "name": "hard_expiration_timestamp",
+ "type": "i64"
+ },
+ {
+ "name": "wager",
+ "type": "u64"
+ },
+ {
+ "name": "creation_timestamp",
+ "type": "i64"
+ },
+ {
+ "name": "game_seed",
+ "type": "u64"
+ },
+ {
+ "name": "min_bet",
+ "type": "u64"
+ },
+ {
+ "name": "max_bet",
+ "type": "u64"
+ }
+ ]
+ }
+ },
+ {
+ "name": "GameSettledPartial",
+ "type": {
+ "kind": "struct",
+ "fields": [
+ {
+ "name": "game_id",
+ "type": "u64"
+ },
+ {
+ "name": "game_account",
+ "type": "pubkey"
+ },
+ {
+ "name": "creators_left",
+ "type": "u32"
+ },
+ {
+ "name": "winners_left",
+ "type": "u32"
+ },
+ {
+ "name": "paid_creators_this_tx",
+ "type": "u32"
+ },
+ {
+ "name": "paid_winners_this_tx",
+ "type": "u32"
+ },
+ {
+ "name": "amount_paid",
+ "type": "u64"
+ }
+ ]
+ }
+ },
+ {
+ "name": "GameState",
+ "repr": {
+ "kind": "rust"
+ },
+ "type": {
+ "kind": "enum",
+ "variants": [
+ {
+ "name": "Waiting"
+ },
+ {
+ "name": "Playing"
+ },
+ {
+ "name": "Settled"
+ }
+ ]
+ }
+ },
+ {
+ "name": "GameType",
+ "repr": {
+ "kind": "rust"
+ },
+ "type": {
+ "kind": "enum",
+ "variants": [
+ {
+ "name": "Individual"
+ },
+ {
+ "name": "Team"
+ }
+ ]
+ }
+ },
+ {
+ "name": "MetadataEntry",
+ "type": {
+ "kind": "struct",
+ "fields": [
+ {
+ "name": "player",
+ "type": "pubkey"
+ },
+ {
+ "name": "meta",
+ "type": {
+ "array": [
+ "u8",
+ 32
+ ]
+ }
+ }
+ ]
+ }
+ },
+ {
+ "name": "PayoutType",
+ "repr": {
+ "kind": "rust"
+ },
+ "type": {
+ "kind": "enum",
+ "variants": [
+ {
+ "name": "Same"
+ },
+ {
+ "name": "ExponentialDecay"
+ }
+ ]
+ }
+ },
+ {
+ "name": "Player",
+ "type": {
+ "kind": "struct",
+ "fields": [
+ {
+ "name": "creator_address",
+ "type": "pubkey"
+ },
+ {
+ "name": "user",
+ "type": "pubkey"
+ },
+ {
+ "name": "creator_fee_amount",
+ "type": "u64"
+ },
+ {
+ "name": "gamba_fee_amount",
+ "type": "u64"
+ },
+ {
+ "name": "wager",
+ "type": "u64"
+ },
+ {
+ "name": "pending_payout",
+ "type": "u64"
+ },
+ {
+ "name": "team",
+ "type": "u8"
+ }
+ ]
+ }
+ },
+ {
+ "name": "PlayerJoined",
+ "type": {
+ "kind": "struct",
+ "fields": [
+ {
+ "name": "game_id",
+ "type": "u64"
+ },
+ {
+ "name": "game_account",
+ "type": "pubkey"
+ },
+ {
+ "name": "player",
+ "type": "pubkey"
+ },
+ {
+ "name": "wager",
+ "type": "u64"
+ },
+ {
+ "name": "creator_fee",
+ "type": "u64"
+ },
+ {
+ "name": "mint",
+ "type": "pubkey"
+ },
+ {
+ "name": "game_type",
+ "type": "u8"
+ },
+ {
+ "name": "team",
+ "type": "u8"
+ }
+ ]
+ }
+ },
+ {
+ "name": "PlayerLeft",
+ "type": {
+ "kind": "struct",
+ "fields": [
+ {
+ "name": "game_id",
+ "type": "u64"
+ },
+ {
+ "name": "game_account",
+ "type": "pubkey"
+ },
+ {
+ "name": "player",
+ "type": "pubkey"
+ }
+ ]
+ }
+ },
+ {
+ "name": "PlayerMetadataAccount",
+ "type": {
+ "kind": "struct",
+ "fields": [
+ {
+ "name": "bump",
+ "docs": [
+ "bump is injected by Anchor (no need to store it yourself)"
+ ],
+ "type": "u8"
+ },
+ {
+ "name": "max_entries",
+ "docs": [
+ "static cap + capacity pointer"
+ ],
+ "type": "u16"
+ },
+ {
+ "name": "pre_alloc",
+ "type": "u16"
+ },
+ {
+ "name": "entries",
+ "docs": [
+ "dynamic, streamed just like your players Vec"
+ ],
+ "type": {
+ "vec": {
+ "defined": {
+ "name": "MetadataEntry"
+ }
+ }
+ }
+ }
+ ]
+ }
+ },
+ {
+ "name": "WagerType",
+ "repr": {
+ "kind": "rust"
+ },
+ "type": {
+ "kind": "enum",
+ "variants": [
+ {
+ "name": "SameWager"
+ },
+ {
+ "name": "CustomWager"
+ },
+ {
+ "name": "BetRange"
+ }
+ ]
+ }
+ },
+ {
+ "name": "WinnersSelected",
+ "type": {
+ "kind": "struct",
+ "fields": [
+ {
+ "name": "game_id",
+ "type": "u64"
+ },
+ {
+ "name": "game_account",
+ "type": "pubkey"
+ },
+ {
+ "name": "game_maker",
+ "type": "pubkey"
+ },
+ {
+ "name": "max_players",
+ "type": "u16"
+ },
+ {
+ "name": "winner_indexes",
+ "type": {
+ "vec": "u16"
+ }
+ },
+ {
+ "name": "winner_wagers",
+ "type": {
+ "vec": "u64"
+ }
+ },
+ {
+ "name": "payouts",
+ "type": {
+ "vec": "u64"
+ }
+ },
+ {
+ "name": "total_wager",
+ "type": "u64"
+ },
+ {
+ "name": "players_sample",
+ "type": {
+ "vec": "pubkey"
+ }
+ }
+ ]
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/packages/multiplayer/src/index.ts b/packages/multiplayer/src/index.ts
new file mode 100644
index 00000000..4e14c18a
--- /dev/null
+++ b/packages/multiplayer/src/index.ts
@@ -0,0 +1,13 @@
+export * from "./constants.js";
+export * from "./errors.js";
+export * from "./instructions/gamba-config.js";
+export * from "./instructions/create-game.js";
+export * from "./instructions/distribute-native.js";
+export * from "./instructions/distribute-spl.js";
+export * from "./instructions/join-game.js";
+export * from "./instructions/leave-game.js";
+export * from "./instructions/select-winners.js";
+export * from "./fetch.js";
+export * from "./events.js";
+export * from "./utils/pda";
+export type { Multiplayer } from "./types/multiplayer.js";
diff --git a/packages/multiplayer/src/instructions/create-game.ts b/packages/multiplayer/src/instructions/create-game.ts
new file mode 100644
index 00000000..7aedbe67
--- /dev/null
+++ b/packages/multiplayer/src/instructions/create-game.ts
@@ -0,0 +1,116 @@
+import {
+ AnchorProvider,
+ BN,
+ web3,
+} from "@coral-xyz/anchor";
+import {
+ WRAPPED_SOL_MINT,
+ getProgram,
+} from "../constants.js";
+import {
+ deriveGambaState,
+ deriveGamePdaFromSeed,
+ deriveMetadataPda,
+ deriveEscrowPda,
+} from "../utils/pda.js";
+
+export interface CreateGameParams {
+ preAllocPlayers: number;
+ maxPlayers: number;
+ numTeams: number;
+ winnersTarget: number;
+ wagerType: number;
+ payoutType: number;
+ wager: BN | number;
+ softDuration: BN | number;
+ hardDuration: BN | number;
+ gameSeed: BN | number;
+ minBet: BN | number;
+ maxBet: BN | number;
+
+ accounts: {
+ gameMaker: web3.PublicKey;
+ mint: web3.PublicKey;
+ };
+}
+
+export const createGameNativeIx = async (
+ provider: AnchorProvider,
+ p: CreateGameParams,
+) => {
+ const program = getProgram(provider);
+ const gambaState = await deriveGambaState();
+ const gamePda = deriveGamePdaFromSeed(p.gameSeed);
+ const metaPda = deriveMetadataPda(gamePda);
+
+ const ix = await program.methods
+ .createGameNative(
+ p.preAllocPlayers,
+ p.maxPlayers,
+ p.numTeams,
+ p.winnersTarget,
+ p.wagerType,
+ p.payoutType,
+ new BN(p.wager),
+ new BN(p.softDuration),
+ new BN(p.hardDuration),
+ new BN(p.gameSeed),
+ new BN(p.minBet),
+ new BN(p.maxBet),
+ )
+ .accounts({
+ gameAccount: gamePda,
+ metadataACcount: metaPda,
+ mint: p.accounts.mint,
+ gameMaker: p.accounts.gameMaker,
+ gambaState,
+ } as any)
+ .instruction();
+
+ return ix;
+};
+
+export const createGameSplIx = async (
+ provider: AnchorProvider,
+ p: CreateGameParams,
+) => {
+ const program = getProgram(provider);
+ const gambaState = await deriveGambaState();
+ const gamePda = deriveGamePdaFromSeed(p.gameSeed);
+ const metaPda = deriveMetadataPda(gamePda);
+ const escrowPda = deriveEscrowPda(gamePda);
+
+ const ix = await program.methods
+ .createGameSpl(
+ p.preAllocPlayers,
+ p.maxPlayers,
+ p.numTeams,
+ p.winnersTarget,
+ p.wagerType,
+ p.payoutType,
+ new BN(p.wager),
+ new BN(p.softDuration),
+ new BN(p.hardDuration),
+ new BN(p.gameSeed),
+ new BN(p.minBet),
+ new BN(p.maxBet),
+ )
+ .accounts({
+ gameAccount: gamePda,
+ metadataACcount: metaPda,
+ mint: p.accounts.mint,
+ gameAccountTaAccount: escrowPda,
+ gameMaker: p.accounts.gameMaker,
+ gambaState,
+ } as any)
+ .instruction();
+
+ return ix;
+};
+export const createGameIx = (
+ provider: AnchorProvider,
+ p: CreateGameParams,
+) =>
+ p.accounts.mint.equals(WRAPPED_SOL_MINT)
+ ? createGameNativeIx(provider, p)
+ : createGameSplIx(provider, p);
diff --git a/packages/multiplayer/src/instructions/distribute-native.ts b/packages/multiplayer/src/instructions/distribute-native.ts
new file mode 100644
index 00000000..d54271da
--- /dev/null
+++ b/packages/multiplayer/src/instructions/distribute-native.ts
@@ -0,0 +1,45 @@
+import { AnchorProvider, web3 } from "@coral-xyz/anchor";
+import { getProgram } from "../constants.js";
+import { deriveGambaState, deriveMetadataPda } from "../utils/pda.js";
+
+export interface DistributeNativeParams {
+ accounts: {
+ payer: web3.PublicKey;
+ gameAccount: web3.PublicKey;
+ gambaFeeAddress: web3.PublicKey;
+ };
+ remaining: web3.PublicKey[];
+}
+
+export const distributeNativeIx = async (
+ provider: AnchorProvider,
+ p: DistributeNativeParams,
+): Promise => {
+ const program = getProgram(provider);
+ const gambaStatePda = deriveGambaState();
+ const metadataAccount = deriveMetadataPda(p.accounts.gameAccount);
+
+ const rawGame = await program.account.game.fetch(p.accounts.gameAccount);
+ const gameMaker = (rawGame as any).gameMaker as web3.PublicKey;
+
+ const rem = p.remaining.map((pk) => ({
+ pubkey: pk,
+ isWritable: true,
+ isSigner: false,
+ }));
+
+ const ix = await program.methods
+ .distributeNative()
+ .accountsPartial({
+ payer: p.accounts.payer,
+ gambaState: gambaStatePda,
+ gameAccount: p.accounts.gameAccount,
+ metadataAccount,
+ gameMaker,
+ gambaFeeAddress: p.accounts.gambaFeeAddress,
+ } as any)
+ .remainingAccounts(rem)
+ .instruction();
+
+ return ix;
+};
diff --git a/packages/multiplayer/src/instructions/distribute-spl.ts b/packages/multiplayer/src/instructions/distribute-spl.ts
new file mode 100644
index 00000000..eb3a632c
--- /dev/null
+++ b/packages/multiplayer/src/instructions/distribute-spl.ts
@@ -0,0 +1,77 @@
+import { AnchorProvider, web3 } from "@coral-xyz/anchor";
+import { getAssociatedTokenAddressSync as getAta } from "@solana/spl-token";
+import { getProgram } from "../constants.js";
+import {
+ deriveGambaState,
+ deriveMetadataPda,
+ deriveEscrowPda,
+} from "../utils/pda.js";
+
+export interface DistributeSplParams {
+ accounts: {
+ payer: web3.PublicKey;
+ gameAccount: web3.PublicKey;
+ gambaFeeAddress: web3.PublicKey;
+ mint: web3.PublicKey;
+ };
+ creators?: web3.PublicKey[]; // ≤ 5 raw owner keys
+ winners?: web3.PublicKey[]; // ≤ 15 raw owner keys
+}
+
+export const distributeSplIx = async (
+ provider: AnchorProvider,
+ p: DistributeSplParams,
+): Promise => {
+ const program = getProgram(provider);
+ const gambaStatePda = deriveGambaState();
+ const metadataAccount = deriveMetadataPda(p.accounts.gameAccount);
+ const gameAccountTa = deriveEscrowPda(p.accounts.gameAccount);
+ const gambaFeeAta = getAta(p.accounts.mint, p.accounts.gambaFeeAddress);
+
+ // fetch gameMaker
+ const rawGame = await program.account.game.fetch(p.accounts.gameAccount);
+ const gameMaker = (rawGame as any).gameMaker as web3.PublicKey;
+
+ // Build the full account map. We'll explicitly set
+ // ALL 20 optional ATA slots—either to an ATA or to null.
+ const accs: Record = {
+ payer: p.accounts.payer,
+ gambaState: gambaStatePda,
+ gameAccount: p.accounts.gameAccount,
+ metadataAccount,
+ gameAccountTa,
+ mint: p.accounts.mint,
+ gambaFeeAta,
+ gambaFeeAddress: p.accounts.gambaFeeAddress,
+ gameMaker,
+ };
+
+ // Helper to fill an ATA slot or null
+ function setAtaSlot(
+ slot: string,
+ ownerKey: web3.PublicKey | undefined,
+ ) {
+ accs[slot] = ownerKey
+ ? getAta(p.accounts.mint, ownerKey)
+ : null;
+ }
+
+ // creators → creatorAta0 … creatorAta4
+ for (let i = 0; i < 5; i++) {
+ setAtaSlot(`creatorAta${i}`, p.creators?.[i]);
+ }
+
+ // winners → winnerAta0 … winnerAta14
+ for (let i = 0; i < 15; i++) {
+ setAtaSlot(`winnerAta${i}`, p.winners?.[i]);
+ }
+
+ // Now that every optional slot is _present_ (maybe null),
+ // the builder will accept it and replace nulls with the sentinel.
+ const ix = await program.methods
+ .distributeSpl()
+ .accountsPartial(accs as any)
+ .instruction();
+
+ return ix;
+};
diff --git a/packages/multiplayer/src/instructions/gamba-config.ts b/packages/multiplayer/src/instructions/gamba-config.ts
new file mode 100644
index 00000000..1fb46772
--- /dev/null
+++ b/packages/multiplayer/src/instructions/gamba-config.ts
@@ -0,0 +1,25 @@
+import { AnchorProvider, web3 } from "@coral-xyz/anchor";
+import { getProgram } from "../constants.js";
+
+
+export interface GambaConfigParams {
+ gambaFeeAddress : web3.PublicKey;
+ gambaFeeBps : number;
+ rng : web3.PublicKey;
+ authority : web3.PublicKey;
+ authoritySigner : web3.PublicKey;
+}
+
+export const gambaConfigIx = async (
+ provider: AnchorProvider,
+ p: GambaConfigParams,
+) => {
+ const program = getProgram(provider);
+ const ix = await program.methods
+ .gambaConfig(p.gambaFeeAddress, p.gambaFeeBps, p.rng, p.authority)
+ .accounts({ authority: p.authoritySigner })
+ .instruction();
+
+ return ix;
+};
+
diff --git a/packages/multiplayer/src/instructions/join-game.ts b/packages/multiplayer/src/instructions/join-game.ts
new file mode 100644
index 00000000..3455bb24
--- /dev/null
+++ b/packages/multiplayer/src/instructions/join-game.ts
@@ -0,0 +1,73 @@
+import { AnchorProvider, BN, utils as anchorUtils, web3 } from "@coral-xyz/anchor";
+import { getAssociatedTokenAddressSync as ata } from "@solana/spl-token";
+import { WRAPPED_SOL_MINT, getProgram } from "../constants.js";
+import {
+ deriveGambaState,
+ deriveMetadataPda,
+ deriveEscrowPda,
+} from "../utils/pda.js";
+
+export interface JoinGameParams {
+ creatorFeeBps: number;
+ wager: BN | number;
+ team?: number;
+ playerMeta?: Buffer | Uint8Array;
+
+ accounts: {
+ gameAccount: web3.PublicKey;
+ mint: web3.PublicKey;
+ playerAccount: web3.PublicKey;
+ creatorAddress: web3.PublicKey;
+ };
+}
+
+export const joinGameIx = async (
+ provider: AnchorProvider,
+ p: JoinGameParams,
+) => {
+ const program = getProgram(provider);
+ const isNative = p.accounts.mint.equals(WRAPPED_SOL_MINT);
+
+ const gambaState = deriveGambaState();
+ const metaPda = deriveMetadataPda(p.accounts.gameAccount);
+ const gameTa = isNative
+ ? null
+ : deriveEscrowPda(p.accounts.gameAccount);
+
+ const playerAta = isNative
+ ? null
+ : ata(p.accounts.mint, p.accounts.playerAccount, false);
+
+ const creatorAta = ata(
+ p.accounts.mint,
+ p.accounts.creatorAddress,
+ true,
+ );
+
+ const teamIndex = p.team ?? 0;
+ const metaBytes = p.playerMeta
+ ? Array.from(p.playerMeta)
+ : [];
+
+ const ix = await program.methods
+ .joinGame(
+ p.creatorFeeBps,
+ new BN(p.wager),
+ teamIndex,
+ metaBytes, // [u8;32] or empty
+ )
+ .accountsPartial({
+ gameAccount: p.accounts.gameAccount,
+ metadataAccount: metaPda,
+ gambaState,
+ gameAccountTa: gameTa,
+ mint: p.accounts.mint,
+ playerAccount: p.accounts.playerAccount,
+ playerAta,
+ creatorAddress: p.accounts.creatorAddress,
+ creatorAta,
+ } as any)
+ .instruction();
+
+ return ix;
+};
diff --git a/packages/multiplayer/src/instructions/leave-game.ts b/packages/multiplayer/src/instructions/leave-game.ts
new file mode 100644
index 00000000..bc75a8f7
--- /dev/null
+++ b/packages/multiplayer/src/instructions/leave-game.ts
@@ -0,0 +1,54 @@
+import {
+ AnchorProvider,
+ utils as anchorUtils,
+ web3,
+} from "@coral-xyz/anchor";
+import { getAssociatedTokenAddressSync as ata } from "@solana/spl-token";
+import {
+ WRAPPED_SOL_MINT,
+ PROGRAM_ID,
+ getProgram,
+} from "../constants.js";
+import {
+ deriveMetadataPda,
+ deriveEscrowPda,
+} from "../utils/pda.js";
+
+export interface LeaveGameParams {
+ accounts: {
+ gameAccount: web3.PublicKey;
+ mint: web3.PublicKey;
+ playerAccount: web3.PublicKey;
+ };
+}
+
+export const leaveGameIx = async (
+ provider: AnchorProvider,
+ p: LeaveGameParams,
+) => {
+ const program = getProgram(provider);
+ const isNative = p.accounts.mint.equals(WRAPPED_SOL_MINT);
+
+ const metaPda = deriveMetadataPda(p.accounts.gameAccount);
+ const gameTa = isNative
+ ? null
+ : deriveEscrowPda(p.accounts.gameAccount);
+
+ const playerAta = isNative
+ ? null
+ : ata(p.accounts.mint, p.accounts.playerAccount, false);
+
+ const ix = await program.methods
+ .leaveGame()
+ .accountsPartial({
+ gameAccount: p.accounts.gameAccount,
+ metadataAccount: metaPda,
+ gameAccountTa: gameTa,
+ mint: p.accounts.mint,
+ playerAccount: p.accounts.playerAccount,
+ playerAta,
+ } as any)
+ .instruction();
+
+ return ix;
+};
diff --git a/packages/multiplayer/src/instructions/select-winners.ts b/packages/multiplayer/src/instructions/select-winners.ts
new file mode 100644
index 00000000..c91a8729
--- /dev/null
+++ b/packages/multiplayer/src/instructions/select-winners.ts
@@ -0,0 +1,25 @@
+import { AnchorProvider, web3 } from "@coral-xyz/anchor";
+import { getProgram } from "../constants.js";
+
+export interface SelectWinnersParams {
+ accounts: {
+ rng : web3.PublicKey;
+ gameAccount : web3.PublicKey;
+ };
+}
+
+export const selectWinnersIx = async (
+ provider: AnchorProvider,
+ p: SelectWinnersParams,
+) => {
+ const program = getProgram(provider);
+ const ix = await program.methods
+ .selectWinners()
+ .accounts({
+ rng : p.accounts.rng,
+ gameAccount : p.accounts.gameAccount,
+ })
+ .instruction();
+
+ return ix;
+};
diff --git a/packages/multiplayer/src/types/multiplayer.ts b/packages/multiplayer/src/types/multiplayer.ts
new file mode 100644
index 00000000..3115a7d4
--- /dev/null
+++ b/packages/multiplayer/src/types/multiplayer.ts
@@ -0,0 +1,2159 @@
+/**
+ * Program IDL in camelCase format in order to be used in JS/TS.
+ *
+ * Note that this is only a type helper and is not the actual IDL. The original
+ * IDL can be found at `target/idl/multiplayer.json`.
+ */
+export type Multiplayer = {
+ "address": "GambaMyTW8C1NSeFrv2c3KfmX1MSDBF6YbxDeb7dBPxM",
+ "metadata": {
+ "name": "multiplayer",
+ "version": "0.1.0",
+ "spec": "0.1.0",
+ "description": "Created with Anchor"
+ },
+ "instructions": [
+ {
+ "name": "createGameNative",
+ "discriminator": [
+ 14,
+ 237,
+ 122,
+ 202,
+ 102,
+ 88,
+ 48,
+ 75
+ ],
+ "accounts": [
+ {
+ "name": "gameAccount",
+ "docs": [
+ "The on‐chain Game account, PDA = [b\"GAME\", game_seed]."
+ ],
+ "writable": true,
+ "pda": {
+ "seeds": [
+ {
+ "kind": "const",
+ "value": [
+ 71,
+ 65,
+ 77,
+ 69
+ ]
+ },
+ {
+ "kind": "arg",
+ "path": "gameSeed"
+ }
+ ]
+ }
+ },
+ {
+ "name": "metadataAccount",
+ "docs": [
+ "Always-present metadata PDA, PDA = [b\"METADATA\", game_account.key()]"
+ ],
+ "writable": true,
+ "pda": {
+ "seeds": [
+ {
+ "kind": "const",
+ "value": [
+ 77,
+ 69,
+ 84,
+ 65,
+ 68,
+ 65,
+ 84,
+ 65
+ ]
+ },
+ {
+ "kind": "account",
+ "path": "gameAccount"
+ }
+ ]
+ }
+ },
+ {
+ "name": "mint",
+ "docs": [
+ "always SOL (placeholder mint)"
+ ]
+ },
+ {
+ "name": "gameMaker",
+ "docs": [
+ "creator of the game, pays rent on init"
+ ],
+ "writable": true,
+ "signer": true
+ },
+ {
+ "name": "gambaState",
+ "docs": [
+ "global config PDA"
+ ],
+ "writable": true,
+ "pda": {
+ "seeds": [
+ {
+ "kind": "const",
+ "value": [
+ 71,
+ 65,
+ 77,
+ 66,
+ 65,
+ 95,
+ 83,
+ 84,
+ 65,
+ 84,
+ 69
+ ]
+ }
+ ]
+ }
+ },
+ {
+ "name": "systemProgram",
+ "address": "11111111111111111111111111111111"
+ }
+ ],
+ "args": [
+ {
+ "name": "preAllocPlayers",
+ "type": "u16"
+ },
+ {
+ "name": "maxPlayers",
+ "type": "u16"
+ },
+ {
+ "name": "numTeams",
+ "type": "u8"
+ },
+ {
+ "name": "winnersTarget",
+ "type": "u16"
+ },
+ {
+ "name": "wagerType",
+ "type": "u8"
+ },
+ {
+ "name": "payoutType",
+ "type": "u8"
+ },
+ {
+ "name": "wager",
+ "type": "u64"
+ },
+ {
+ "name": "softDuration",
+ "type": "i64"
+ },
+ {
+ "name": "hardDuration",
+ "type": "i64"
+ },
+ {
+ "name": "gameSeed",
+ "type": "u64"
+ },
+ {
+ "name": "minBet",
+ "type": "u64"
+ },
+ {
+ "name": "maxBet",
+ "type": "u64"
+ }
+ ]
+ },
+ {
+ "name": "createGameSpl",
+ "discriminator": [
+ 80,
+ 235,
+ 44,
+ 243,
+ 14,
+ 15,
+ 248,
+ 207
+ ],
+ "accounts": [
+ {
+ "name": "gameAccount",
+ "docs": [
+ "The on-chain Game account, PDA = [b\"GAME\", game_seed]."
+ ],
+ "writable": true,
+ "pda": {
+ "seeds": [
+ {
+ "kind": "const",
+ "value": [
+ 71,
+ 65,
+ 77,
+ 69
+ ]
+ },
+ {
+ "kind": "arg",
+ "path": "gameSeed"
+ }
+ ]
+ }
+ },
+ {
+ "name": "metadataAccount",
+ "docs": [
+ "Always-present metadata PDA, PDA = [b\"METADATA\", game_account.key()]"
+ ],
+ "writable": true,
+ "pda": {
+ "seeds": [
+ {
+ "kind": "const",
+ "value": [
+ 77,
+ 69,
+ 84,
+ 65,
+ 68,
+ 65,
+ 84,
+ 65
+ ]
+ },
+ {
+ "kind": "account",
+ "path": "gameAccount"
+ }
+ ]
+ }
+ },
+ {
+ "name": "mint",
+ "docs": [
+ "the SPL mint for wagers"
+ ]
+ },
+ {
+ "name": "gameAccountTaAccount",
+ "docs": [
+ "escrow token account for this game"
+ ],
+ "writable": true,
+ "pda": {
+ "seeds": [
+ {
+ "kind": "account",
+ "path": "gameAccount"
+ }
+ ]
+ }
+ },
+ {
+ "name": "gameMaker",
+ "writable": true,
+ "signer": true
+ },
+ {
+ "name": "gambaState",
+ "writable": true,
+ "pda": {
+ "seeds": [
+ {
+ "kind": "const",
+ "value": [
+ 71,
+ 65,
+ 77,
+ 66,
+ 65,
+ 95,
+ 83,
+ 84,
+ 65,
+ 84,
+ 69
+ ]
+ }
+ ]
+ }
+ },
+ {
+ "name": "systemProgram",
+ "address": "11111111111111111111111111111111"
+ },
+ {
+ "name": "tokenProgram",
+ "address": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
+ },
+ {
+ "name": "associatedTokenProgram",
+ "address": "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"
+ }
+ ],
+ "args": [
+ {
+ "name": "preAllocPlayers",
+ "type": "u16"
+ },
+ {
+ "name": "maxPlayers",
+ "type": "u16"
+ },
+ {
+ "name": "numTeams",
+ "type": "u8"
+ },
+ {
+ "name": "winnersTarget",
+ "type": "u16"
+ },
+ {
+ "name": "wagerType",
+ "type": "u8"
+ },
+ {
+ "name": "payoutType",
+ "type": "u8"
+ },
+ {
+ "name": "wager",
+ "type": "u64"
+ },
+ {
+ "name": "softDuration",
+ "type": "i64"
+ },
+ {
+ "name": "hardDuration",
+ "type": "i64"
+ },
+ {
+ "name": "gameSeed",
+ "type": "u64"
+ },
+ {
+ "name": "minBet",
+ "type": "u64"
+ },
+ {
+ "name": "maxBet",
+ "type": "u64"
+ }
+ ]
+ },
+ {
+ "name": "distributeNative",
+ "discriminator": [
+ 32,
+ 200,
+ 172,
+ 49,
+ 57,
+ 234,
+ 137,
+ 89
+ ],
+ "accounts": [
+ {
+ "name": "payer",
+ "docs": [
+ "Tx signer (fees & optional PDA‑close refund go here)"
+ ],
+ "signer": true
+ },
+ {
+ "name": "gambaState",
+ "docs": [
+ "Global configuration – for fee vault + RNG auth"
+ ],
+ "pda": {
+ "seeds": [
+ {
+ "kind": "const",
+ "value": [
+ 71,
+ 65,
+ 77,
+ 66,
+ 65,
+ 95,
+ 83,
+ 84,
+ 65,
+ 84,
+ 69
+ ]
+ }
+ ]
+ }
+ },
+ {
+ "name": "gameAccount",
+ "docs": [
+ "Game account (large PDA, parsed manually)"
+ ],
+ "writable": true
+ },
+ {
+ "name": "gameMaker",
+ "docs": [
+ "Game‑maker – receives any closing lamports"
+ ],
+ "writable": true
+ },
+ {
+ "name": "gambaFeeAddress",
+ "docs": [
+ "Protocol fee vault"
+ ],
+ "writable": true
+ },
+ {
+ "name": "metadataAccount",
+ "docs": [
+ "Metadata PDA – to be closed at settlement"
+ ],
+ "writable": true,
+ "pda": {
+ "seeds": [
+ {
+ "kind": "const",
+ "value": [
+ 77,
+ 69,
+ 84,
+ 65,
+ 68,
+ 65,
+ 84,
+ 65
+ ]
+ },
+ {
+ "kind": "account",
+ "path": "gameAccount"
+ }
+ ]
+ }
+ },
+ {
+ "name": "systemProgram",
+ "address": "11111111111111111111111111111111"
+ }
+ ],
+ "args": []
+ },
+ {
+ "name": "distributeSpl",
+ "discriminator": [
+ 93,
+ 24,
+ 158,
+ 238,
+ 198,
+ 173,
+ 215,
+ 228
+ ],
+ "accounts": [
+ {
+ "name": "payer",
+ "writable": true,
+ "signer": true
+ },
+ {
+ "name": "gambaState",
+ "pda": {
+ "seeds": [
+ {
+ "kind": "const",
+ "value": [
+ 71,
+ 65,
+ 77,
+ 66,
+ 65,
+ 95,
+ 83,
+ 84,
+ 65,
+ 84,
+ 69
+ ]
+ }
+ ]
+ }
+ },
+ {
+ "name": "gameAccount",
+ "docs": [
+ "Raw Game PDA (parsed by offsets)"
+ ],
+ "writable": true
+ },
+ {
+ "name": "metadataAccount",
+ "docs": [
+ "Always-present metadata PDA (to be closed on final settlement)"
+ ],
+ "writable": true,
+ "pda": {
+ "seeds": [
+ {
+ "kind": "const",
+ "value": [
+ 77,
+ 69,
+ 84,
+ 65,
+ 68,
+ 65,
+ 84,
+ 65
+ ]
+ },
+ {
+ "kind": "account",
+ "path": "gameAccount"
+ }
+ ]
+ }
+ },
+ {
+ "name": "gameAccountTa",
+ "docs": [
+ "Game escrow ATA (PDA = [game_account])"
+ ],
+ "writable": true
+ },
+ {
+ "name": "mint"
+ },
+ {
+ "name": "gambaFeeAta",
+ "docs": [
+ "Protocol fee vault ATA for this mint"
+ ],
+ "writable": true,
+ "pda": {
+ "seeds": [
+ {
+ "kind": "account",
+ "path": "gambaFeeAddress"
+ },
+ {
+ "kind": "const",
+ "value": [
+ 6,
+ 221,
+ 246,
+ 225,
+ 215,
+ 101,
+ 161,
+ 147,
+ 217,
+ 203,
+ 225,
+ 70,
+ 206,
+ 235,
+ 121,
+ 172,
+ 28,
+ 180,
+ 133,
+ 237,
+ 95,
+ 91,
+ 55,
+ 145,
+ 58,
+ 140,
+ 245,
+ 133,
+ 126,
+ 255,
+ 0,
+ 169
+ ]
+ },
+ {
+ "kind": "account",
+ "path": "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": "gambaFeeAddress",
+ "docs": [
+ "Protocol fee vault (owner of `gamba_fee_ata`)"
+ ],
+ "writable": true
+ },
+ {
+ "name": "gameMaker",
+ "docs": [
+ "Game‑maker (receives escrow close lamports)"
+ ],
+ "writable": true
+ },
+ {
+ "name": "creatorAta0",
+ "writable": true,
+ "optional": true
+ },
+ {
+ "name": "creatorAta1",
+ "writable": true,
+ "optional": true
+ },
+ {
+ "name": "creatorAta2",
+ "writable": true,
+ "optional": true
+ },
+ {
+ "name": "creatorAta3",
+ "writable": true,
+ "optional": true
+ },
+ {
+ "name": "creatorAta4",
+ "writable": true,
+ "optional": true
+ },
+ {
+ "name": "winnerAta0",
+ "writable": true,
+ "optional": true
+ },
+ {
+ "name": "winnerAta1",
+ "writable": true,
+ "optional": true
+ },
+ {
+ "name": "winnerAta2",
+ "writable": true,
+ "optional": true
+ },
+ {
+ "name": "winnerAta3",
+ "writable": true,
+ "optional": true
+ },
+ {
+ "name": "winnerAta4",
+ "writable": true,
+ "optional": true
+ },
+ {
+ "name": "winnerAta5",
+ "writable": true,
+ "optional": true
+ },
+ {
+ "name": "winnerAta6",
+ "writable": true,
+ "optional": true
+ },
+ {
+ "name": "winnerAta7",
+ "writable": true,
+ "optional": true
+ },
+ {
+ "name": "winnerAta8",
+ "writable": true,
+ "optional": true
+ },
+ {
+ "name": "winnerAta9",
+ "writable": true,
+ "optional": true
+ },
+ {
+ "name": "winnerAta10",
+ "writable": true,
+ "optional": true
+ },
+ {
+ "name": "winnerAta11",
+ "writable": true,
+ "optional": true
+ },
+ {
+ "name": "winnerAta12",
+ "writable": true,
+ "optional": true
+ },
+ {
+ "name": "winnerAta13",
+ "writable": true,
+ "optional": true
+ },
+ {
+ "name": "winnerAta14",
+ "writable": true,
+ "optional": true
+ },
+ {
+ "name": "tokenProgram",
+ "address": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
+ },
+ {
+ "name": "associatedTokenProgram",
+ "address": "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"
+ },
+ {
+ "name": "systemProgram",
+ "address": "11111111111111111111111111111111"
+ }
+ ],
+ "args": []
+ },
+ {
+ "name": "gambaConfig",
+ "discriminator": [
+ 232,
+ 208,
+ 249,
+ 92,
+ 159,
+ 187,
+ 21,
+ 254
+ ],
+ "accounts": [
+ {
+ "name": "gambaState",
+ "writable": true,
+ "pda": {
+ "seeds": [
+ {
+ "kind": "const",
+ "value": [
+ 71,
+ 65,
+ 77,
+ 66,
+ 65,
+ 95,
+ 83,
+ 84,
+ 65,
+ 84,
+ 69
+ ]
+ }
+ ]
+ }
+ },
+ {
+ "name": "authority",
+ "writable": true,
+ "signer": true
+ },
+ {
+ "name": "systemProgram",
+ "address": "11111111111111111111111111111111"
+ }
+ ],
+ "args": [
+ {
+ "name": "feeVault",
+ "type": "pubkey"
+ },
+ {
+ "name": "feeBps",
+ "type": "u32"
+ },
+ {
+ "name": "rng",
+ "type": "pubkey"
+ },
+ {
+ "name": "authority",
+ "type": "pubkey"
+ }
+ ]
+ },
+ {
+ "name": "joinGame",
+ "discriminator": [
+ 107,
+ 112,
+ 18,
+ 38,
+ 56,
+ 173,
+ 60,
+ 128
+ ],
+ "accounts": [
+ {
+ "name": "gameAccount",
+ "docs": [
+ "Game account as raw bytes"
+ ],
+ "writable": true
+ },
+ {
+ "name": "gambaState",
+ "writable": true,
+ "pda": {
+ "seeds": [
+ {
+ "kind": "const",
+ "value": [
+ 71,
+ 65,
+ 77,
+ 66,
+ 65,
+ 95,
+ 83,
+ 84,
+ 65,
+ 84,
+ 69
+ ]
+ }
+ ]
+ }
+ },
+ {
+ "name": "gameAccountTa",
+ "docs": [
+ "Optional escrow token account (only for SPL games)"
+ ],
+ "writable": true,
+ "optional": true,
+ "pda": {
+ "seeds": [
+ {
+ "kind": "account",
+ "path": "gameAccount"
+ }
+ ]
+ }
+ },
+ {
+ "name": "mint",
+ "docs": [
+ "Mint used for wagers (native SOL or SPL)"
+ ]
+ },
+ {
+ "name": "playerAccount",
+ "docs": [
+ "Player joining (payer + signer)"
+ ],
+ "writable": true,
+ "signer": true
+ },
+ {
+ "name": "playerAta",
+ "docs": [
+ "Optional player ATA if SPL wager"
+ ],
+ "writable": true,
+ "optional": true,
+ "pda": {
+ "seeds": [
+ {
+ "kind": "account",
+ "path": "playerAccount"
+ },
+ {
+ "kind": "const",
+ "value": [
+ 6,
+ 221,
+ 246,
+ 225,
+ 215,
+ 101,
+ 161,
+ 147,
+ 217,
+ 203,
+ 225,
+ 70,
+ 206,
+ 235,
+ 121,
+ 172,
+ 28,
+ 180,
+ 133,
+ 237,
+ 95,
+ 91,
+ 55,
+ 145,
+ 58,
+ 140,
+ 245,
+ 133,
+ 126,
+ 255,
+ 0,
+ 169
+ ]
+ },
+ {
+ "kind": "account",
+ "path": "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": "creatorAddress",
+ "docs": [
+ "Creator/referrer collecting a fee"
+ ]
+ },
+ {
+ "name": "creatorAta",
+ "docs": [
+ "Creator’s ATA (created lazily)"
+ ],
+ "writable": true,
+ "pda": {
+ "seeds": [
+ {
+ "kind": "account",
+ "path": "creatorAddress"
+ },
+ {
+ "kind": "const",
+ "value": [
+ 6,
+ 221,
+ 246,
+ 225,
+ 215,
+ 101,
+ 161,
+ 147,
+ 217,
+ 203,
+ 225,
+ 70,
+ 206,
+ 235,
+ 121,
+ 172,
+ 28,
+ 180,
+ 133,
+ 237,
+ 95,
+ 91,
+ 55,
+ 145,
+ 58,
+ 140,
+ 245,
+ 133,
+ 126,
+ 255,
+ 0,
+ 169
+ ]
+ },
+ {
+ "kind": "account",
+ "path": "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": "metadataAccount",
+ "docs": [
+ "Always-present metadata PDA"
+ ],
+ "writable": true,
+ "pda": {
+ "seeds": [
+ {
+ "kind": "const",
+ "value": [
+ 77,
+ 69,
+ 84,
+ 65,
+ 68,
+ 65,
+ 84,
+ 65
+ ]
+ },
+ {
+ "kind": "account",
+ "path": "gameAccount"
+ }
+ ]
+ }
+ },
+ {
+ "name": "systemProgram",
+ "docs": [
+ "Programs"
+ ],
+ "address": "11111111111111111111111111111111"
+ },
+ {
+ "name": "associatedTokenProgram",
+ "address": "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"
+ },
+ {
+ "name": "tokenProgram",
+ "address": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
+ }
+ ],
+ "args": [
+ {
+ "name": "creatorFeeBps",
+ "type": "u32"
+ },
+ {
+ "name": "wager",
+ "type": "u64"
+ },
+ {
+ "name": "team",
+ "type": "u8"
+ },
+ {
+ "name": "playerMeta",
+ "type": {
+ "array": [
+ "u8",
+ 32
+ ]
+ }
+ }
+ ]
+ },
+ {
+ "name": "leaveGame",
+ "discriminator": [
+ 218,
+ 226,
+ 6,
+ 0,
+ 243,
+ 34,
+ 125,
+ 201
+ ],
+ "accounts": [
+ {
+ "name": "gameAccount",
+ "docs": [
+ "Game PDA – parsed manually"
+ ],
+ "writable": true
+ },
+ {
+ "name": "mint"
+ },
+ {
+ "name": "playerAccount",
+ "writable": true,
+ "signer": true
+ },
+ {
+ "name": "playerAta",
+ "writable": true,
+ "optional": true,
+ "pda": {
+ "seeds": [
+ {
+ "kind": "account",
+ "path": "playerAccount"
+ },
+ {
+ "kind": "const",
+ "value": [
+ 6,
+ 221,
+ 246,
+ 225,
+ 215,
+ 101,
+ 161,
+ 147,
+ 217,
+ 203,
+ 225,
+ 70,
+ 206,
+ 235,
+ 121,
+ 172,
+ 28,
+ 180,
+ 133,
+ 237,
+ 95,
+ 91,
+ 55,
+ 145,
+ 58,
+ 140,
+ 245,
+ 133,
+ 126,
+ 255,
+ 0,
+ 169
+ ]
+ },
+ {
+ "kind": "account",
+ "path": "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": "gameAccountTa",
+ "writable": true,
+ "optional": true,
+ "pda": {
+ "seeds": [
+ {
+ "kind": "account",
+ "path": "gameAccount"
+ }
+ ]
+ }
+ },
+ {
+ "name": "metadataAccount",
+ "docs": [
+ "Always-present metadata PDA"
+ ],
+ "writable": true,
+ "pda": {
+ "seeds": [
+ {
+ "kind": "const",
+ "value": [
+ 77,
+ 69,
+ 84,
+ 65,
+ 68,
+ 65,
+ 84,
+ 65
+ ]
+ },
+ {
+ "kind": "account",
+ "path": "gameAccount"
+ }
+ ]
+ }
+ },
+ {
+ "name": "systemProgram",
+ "address": "11111111111111111111111111111111"
+ },
+ {
+ "name": "associatedTokenProgram",
+ "address": "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"
+ },
+ {
+ "name": "tokenProgram",
+ "address": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
+ }
+ ],
+ "args": []
+ },
+ {
+ "name": "selectWinners",
+ "discriminator": [
+ 80,
+ 100,
+ 28,
+ 131,
+ 83,
+ 199,
+ 222,
+ 80
+ ],
+ "accounts": [
+ {
+ "name": "rng",
+ "docs": [
+ "Authorised RNG bot"
+ ],
+ "writable": true,
+ "signer": true
+ },
+ {
+ "name": "gambaState",
+ "docs": [
+ "Global config – only used to enforce `rng`"
+ ],
+ "pda": {
+ "seeds": [
+ {
+ "kind": "const",
+ "value": [
+ 71,
+ 65,
+ 77,
+ 66,
+ 65,
+ 95,
+ 83,
+ 84,
+ 65,
+ 84,
+ 69
+ ]
+ }
+ ]
+ }
+ },
+ {
+ "name": "gameAccount",
+ "docs": [
+ "Raw Game account; we parse fields by hand"
+ ],
+ "writable": true
+ }
+ ],
+ "args": []
+ }
+ ],
+ "accounts": [
+ {
+ "name": "gambaState",
+ "discriminator": [
+ 142,
+ 203,
+ 14,
+ 224,
+ 153,
+ 118,
+ 52,
+ 200
+ ]
+ },
+ {
+ "name": "game",
+ "discriminator": [
+ 27,
+ 90,
+ 166,
+ 125,
+ 74,
+ 100,
+ 121,
+ 18
+ ]
+ },
+ {
+ "name": "playerMetadataAccount",
+ "discriminator": [
+ 204,
+ 224,
+ 199,
+ 121,
+ 70,
+ 159,
+ 53,
+ 55
+ ]
+ }
+ ],
+ "events": [
+ {
+ "name": "gameCreated",
+ "discriminator": [
+ 218,
+ 25,
+ 150,
+ 94,
+ 177,
+ 112,
+ 96,
+ 2
+ ]
+ },
+ {
+ "name": "gameSettledPartial",
+ "discriminator": [
+ 208,
+ 36,
+ 152,
+ 148,
+ 220,
+ 252,
+ 60,
+ 89
+ ]
+ },
+ {
+ "name": "playerJoined",
+ "discriminator": [
+ 39,
+ 144,
+ 49,
+ 106,
+ 108,
+ 210,
+ 183,
+ 38
+ ]
+ },
+ {
+ "name": "playerLeft",
+ "discriminator": [
+ 7,
+ 106,
+ 62,
+ 150,
+ 175,
+ 170,
+ 96,
+ 84
+ ]
+ },
+ {
+ "name": "winnersSelected",
+ "discriminator": [
+ 28,
+ 151,
+ 185,
+ 12,
+ 70,
+ 199,
+ 73,
+ 58
+ ]
+ }
+ ],
+ "errors": [
+ {
+ "code": 6000,
+ "name": "playerAlreadyInGame",
+ "msg": "Player is already in the game"
+ },
+ {
+ "code": 6001,
+ "name": "playerNotInGame",
+ "msg": "Player is not in the game"
+ },
+ {
+ "code": 6002,
+ "name": "gameInProgress",
+ "msg": "Game is already in progress"
+ },
+ {
+ "code": 6003,
+ "name": "invalidGameAccount",
+ "msg": "Invalid game account"
+ },
+ {
+ "code": 6004,
+ "name": "cannotSettleYet",
+ "msg": "Cannot settle yet"
+ },
+ {
+ "code": 6005,
+ "name": "authorityMismatch",
+ "msg": "Signer / authority mismatch"
+ },
+ {
+ "code": 6006,
+ "name": "invalidInput",
+ "msg": "Invalid input"
+ },
+ {
+ "code": 6007,
+ "name": "alreadySettled",
+ "msg": "Game already settled"
+ },
+ {
+ "code": 6008,
+ "name": "numericalOverflow",
+ "msg": "numerical overflow"
+ },
+ {
+ "code": 6009,
+ "name": "creatorMismatch",
+ "msg": "Creator Missmatch"
+ },
+ {
+ "code": 6010,
+ "name": "playerMismatch",
+ "msg": "player rmismatch"
+ },
+ {
+ "code": 6011,
+ "name": "gameFull",
+ "msg": "Game is Full"
+ }
+ ],
+ "types": [
+ {
+ "name": "creatorPending",
+ "type": {
+ "kind": "struct",
+ "fields": [
+ {
+ "name": "address",
+ "type": "pubkey"
+ },
+ {
+ "name": "amount",
+ "type": "u64"
+ }
+ ]
+ }
+ },
+ {
+ "name": "gambaState",
+ "type": {
+ "kind": "struct",
+ "fields": [
+ {
+ "name": "rng",
+ "type": "pubkey"
+ },
+ {
+ "name": "authority",
+ "type": "pubkey"
+ },
+ {
+ "name": "gambaFeeAddress",
+ "type": "pubkey"
+ },
+ {
+ "name": "gambaFeeBps",
+ "type": "u32"
+ },
+ {
+ "name": "initialized",
+ "type": "bool"
+ },
+ {
+ "name": "gameId",
+ "type": "u64"
+ },
+ {
+ "name": "bump",
+ "type": "u8"
+ }
+ ]
+ }
+ },
+ {
+ "name": "game",
+ "type": {
+ "kind": "struct",
+ "fields": [
+ {
+ "name": "gameMaker",
+ "type": "pubkey"
+ },
+ {
+ "name": "mint",
+ "type": "pubkey"
+ },
+ {
+ "name": "gameType",
+ "type": {
+ "defined": {
+ "name": "gameType"
+ }
+ }
+ },
+ {
+ "name": "wagerType",
+ "type": {
+ "defined": {
+ "name": "wagerType"
+ }
+ }
+ },
+ {
+ "name": "payoutType",
+ "type": {
+ "defined": {
+ "name": "payoutType"
+ }
+ }
+ },
+ {
+ "name": "maxPlayers",
+ "type": "u16"
+ },
+ {
+ "name": "preAlloc",
+ "type": "u16"
+ },
+ {
+ "name": "numTeams",
+ "type": "u8"
+ },
+ {
+ "name": "winnersTarget",
+ "type": "u16"
+ },
+ {
+ "name": "wager",
+ "type": "u64"
+ },
+ {
+ "name": "softExpirationTimestamp",
+ "type": "i64"
+ },
+ {
+ "name": "hardExpirationTimestamp",
+ "type": "i64"
+ },
+ {
+ "name": "creationTimestamp",
+ "docs": [
+ "New field: when the game was created (unix timestamp)"
+ ],
+ "type": "i64"
+ },
+ {
+ "name": "state",
+ "type": {
+ "defined": {
+ "name": "gameState"
+ }
+ }
+ },
+ {
+ "name": "pendingGambaFee",
+ "type": "u64"
+ },
+ {
+ "name": "gameId",
+ "type": "u64"
+ },
+ {
+ "name": "gameSeed",
+ "docs": [
+ "Seeds randomness for select_winners"
+ ],
+ "type": "u64"
+ },
+ {
+ "name": "minBet",
+ "type": "u64"
+ },
+ {
+ "name": "maxBet",
+ "type": "u64"
+ },
+ {
+ "name": "bump",
+ "type": "u8"
+ },
+ {
+ "name": "players",
+ "type": {
+ "vec": {
+ "defined": {
+ "name": "player"
+ }
+ }
+ }
+ },
+ {
+ "name": "winnerIndexes",
+ "type": {
+ "vec": "u16"
+ }
+ },
+ {
+ "name": "creatorsPending",
+ "type": {
+ "vec": {
+ "defined": {
+ "name": "creatorPending"
+ }
+ }
+ }
+ }
+ ]
+ }
+ },
+ {
+ "name": "gameCreated",
+ "type": {
+ "kind": "struct",
+ "fields": [
+ {
+ "name": "gameId",
+ "type": "u64"
+ },
+ {
+ "name": "gameAccount",
+ "type": "pubkey"
+ },
+ {
+ "name": "gameMaker",
+ "type": "pubkey"
+ },
+ {
+ "name": "maxPlayers",
+ "type": "u16"
+ },
+ {
+ "name": "numTeams",
+ "type": "u8"
+ },
+ {
+ "name": "gameType",
+ "type": "u8"
+ },
+ {
+ "name": "wagerType",
+ "type": "u8"
+ },
+ {
+ "name": "payoutType",
+ "type": "u8"
+ },
+ {
+ "name": "winnersTarget",
+ "type": "u16"
+ },
+ {
+ "name": "softDurationSeconds",
+ "type": "i64"
+ },
+ {
+ "name": "hardDurationSeconds",
+ "type": "i64"
+ },
+ {
+ "name": "softExpirationTimestamp",
+ "type": "i64"
+ },
+ {
+ "name": "hardExpirationTimestamp",
+ "type": "i64"
+ },
+ {
+ "name": "wager",
+ "type": "u64"
+ },
+ {
+ "name": "creationTimestamp",
+ "type": "i64"
+ },
+ {
+ "name": "gameSeed",
+ "type": "u64"
+ },
+ {
+ "name": "minBet",
+ "type": "u64"
+ },
+ {
+ "name": "maxBet",
+ "type": "u64"
+ }
+ ]
+ }
+ },
+ {
+ "name": "gameSettledPartial",
+ "type": {
+ "kind": "struct",
+ "fields": [
+ {
+ "name": "gameId",
+ "type": "u64"
+ },
+ {
+ "name": "gameAccount",
+ "type": "pubkey"
+ },
+ {
+ "name": "creatorsLeft",
+ "type": "u32"
+ },
+ {
+ "name": "winnersLeft",
+ "type": "u32"
+ },
+ {
+ "name": "paidCreatorsThisTx",
+ "type": "u32"
+ },
+ {
+ "name": "paidWinnersThisTx",
+ "type": "u32"
+ },
+ {
+ "name": "amountPaid",
+ "type": "u64"
+ }
+ ]
+ }
+ },
+ {
+ "name": "gameState",
+ "repr": {
+ "kind": "rust"
+ },
+ "type": {
+ "kind": "enum",
+ "variants": [
+ {
+ "name": "waiting"
+ },
+ {
+ "name": "playing"
+ },
+ {
+ "name": "settled"
+ }
+ ]
+ }
+ },
+ {
+ "name": "gameType",
+ "repr": {
+ "kind": "rust"
+ },
+ "type": {
+ "kind": "enum",
+ "variants": [
+ {
+ "name": "individual"
+ },
+ {
+ "name": "team"
+ }
+ ]
+ }
+ },
+ {
+ "name": "metadataEntry",
+ "type": {
+ "kind": "struct",
+ "fields": [
+ {
+ "name": "player",
+ "type": "pubkey"
+ },
+ {
+ "name": "meta",
+ "type": {
+ "array": [
+ "u8",
+ 32
+ ]
+ }
+ }
+ ]
+ }
+ },
+ {
+ "name": "payoutType",
+ "repr": {
+ "kind": "rust"
+ },
+ "type": {
+ "kind": "enum",
+ "variants": [
+ {
+ "name": "same"
+ },
+ {
+ "name": "exponentialDecay"
+ }
+ ]
+ }
+ },
+ {
+ "name": "player",
+ "type": {
+ "kind": "struct",
+ "fields": [
+ {
+ "name": "creatorAddress",
+ "type": "pubkey"
+ },
+ {
+ "name": "user",
+ "type": "pubkey"
+ },
+ {
+ "name": "creatorFeeAmount",
+ "type": "u64"
+ },
+ {
+ "name": "gambaFeeAmount",
+ "type": "u64"
+ },
+ {
+ "name": "wager",
+ "type": "u64"
+ },
+ {
+ "name": "pendingPayout",
+ "type": "u64"
+ },
+ {
+ "name": "team",
+ "type": "u8"
+ }
+ ]
+ }
+ },
+ {
+ "name": "playerJoined",
+ "type": {
+ "kind": "struct",
+ "fields": [
+ {
+ "name": "gameId",
+ "type": "u64"
+ },
+ {
+ "name": "gameAccount",
+ "type": "pubkey"
+ },
+ {
+ "name": "player",
+ "type": "pubkey"
+ },
+ {
+ "name": "wager",
+ "type": "u64"
+ },
+ {
+ "name": "creatorFee",
+ "type": "u64"
+ },
+ {
+ "name": "mint",
+ "type": "pubkey"
+ },
+ {
+ "name": "gameType",
+ "type": "u8"
+ },
+ {
+ "name": "team",
+ "type": "u8"
+ }
+ ]
+ }
+ },
+ {
+ "name": "playerLeft",
+ "type": {
+ "kind": "struct",
+ "fields": [
+ {
+ "name": "gameId",
+ "type": "u64"
+ },
+ {
+ "name": "gameAccount",
+ "type": "pubkey"
+ },
+ {
+ "name": "player",
+ "type": "pubkey"
+ }
+ ]
+ }
+ },
+ {
+ "name": "playerMetadataAccount",
+ "type": {
+ "kind": "struct",
+ "fields": [
+ {
+ "name": "bump",
+ "docs": [
+ "bump is injected by Anchor (no need to store it yourself)"
+ ],
+ "type": "u8"
+ },
+ {
+ "name": "maxEntries",
+ "docs": [
+ "static cap + capacity pointer"
+ ],
+ "type": "u16"
+ },
+ {
+ "name": "preAlloc",
+ "type": "u16"
+ },
+ {
+ "name": "entries",
+ "docs": [
+ "dynamic, streamed just like your players Vec"
+ ],
+ "type": {
+ "vec": {
+ "defined": {
+ "name": "metadataEntry"
+ }
+ }
+ }
+ }
+ ]
+ }
+ },
+ {
+ "name": "wagerType",
+ "repr": {
+ "kind": "rust"
+ },
+ "type": {
+ "kind": "enum",
+ "variants": [
+ {
+ "name": "sameWager"
+ },
+ {
+ "name": "customWager"
+ },
+ {
+ "name": "betRange"
+ }
+ ]
+ }
+ },
+ {
+ "name": "winnersSelected",
+ "type": {
+ "kind": "struct",
+ "fields": [
+ {
+ "name": "gameId",
+ "type": "u64"
+ },
+ {
+ "name": "gameAccount",
+ "type": "pubkey"
+ },
+ {
+ "name": "gameMaker",
+ "type": "pubkey"
+ },
+ {
+ "name": "maxPlayers",
+ "type": "u16"
+ },
+ {
+ "name": "winnerIndexes",
+ "type": {
+ "vec": "u16"
+ }
+ },
+ {
+ "name": "winnerWagers",
+ "type": {
+ "vec": "u64"
+ }
+ },
+ {
+ "name": "payouts",
+ "type": {
+ "vec": "u64"
+ }
+ },
+ {
+ "name": "totalWager",
+ "type": "u64"
+ },
+ {
+ "name": "playersSample",
+ "type": {
+ "vec": "pubkey"
+ }
+ }
+ ]
+ }
+ }
+ ]
+};
diff --git a/packages/multiplayer/src/utils/pda.ts b/packages/multiplayer/src/utils/pda.ts
new file mode 100644
index 00000000..378a0b98
--- /dev/null
+++ b/packages/multiplayer/src/utils/pda.ts
@@ -0,0 +1,54 @@
+// src/utils/pda.ts
+
+import { Program, utils as anchorUtils, BN } from "@coral-xyz/anchor";
+import { PublicKey } from "@solana/web3.js";
+import { PROGRAM_ID } from "../constants";
+
+/**
+ * Derive the global config PDA for Gamba:
+ * seeds = [ "GAMBA_STATE" ]
+ */
+export function deriveGambaState(): PublicKey {
+ const [gambaState] = PublicKey.findProgramAddressSync(
+ [anchorUtils.bytes.utf8.encode("GAMBA_STATE")],
+ PROGRAM_ID
+ );
+ return gambaState;
+}
+
+/**
+ * Derive the game PDA from YOUR seed:
+ * seeds = [ "GAME", seed_le_bytes ]
+ */
+export function deriveGamePdaFromSeed(seed: BN | number): PublicKey {
+ const buf = new BN(seed).toArrayLike(Buffer, "le", 8);
+ const [pda] = PublicKey.findProgramAddressSync(
+ [anchorUtils.bytes.utf8.encode("GAME"), buf],
+ PROGRAM_ID
+ );
+ return pda;
+}
+
+/**
+ * Derive the metadata PDA for a given game:
+ * seeds = [ "METADATA", gamePda ]
+ */
+export function deriveMetadataPda(gamePda: PublicKey): PublicKey {
+ const [metaPda] = PublicKey.findProgramAddressSync(
+ [anchorUtils.bytes.utf8.encode("METADATA"), gamePda.toBuffer()],
+ PROGRAM_ID
+ );
+ return metaPda;
+}
+
+/**
+ * Derive the escrow‐ATA PDA for a given game PDA:
+ * seeds = [ gamePda ]
+ */
+export function deriveEscrowPda(gamePda: PublicKey): PublicKey {
+ const [escrowPda] = PublicKey.findProgramAddressSync(
+ [gamePda.toBuffer()],
+ PROGRAM_ID
+ );
+ return escrowPda;
+}
\ No newline at end of file
diff --git a/packages/multiplayer/tsconfig.json b/packages/multiplayer/tsconfig.json
new file mode 100644
index 00000000..911a6fa1
--- /dev/null
+++ b/packages/multiplayer/tsconfig.json
@@ -0,0 +1,21 @@
+{
+ "compilerOptions": {
+ "rootDir": "src",
+ "outDir": "dist",
+ "declaration": true,
+ "declarationMap": true,
+
+ "target": "ES2020",
+ "module": "ESNext",
+ "moduleResolution": "bundler",
+
+ "lib": ["ES2020", "DOM"],
+ "resolveJsonModule": true,
+ "types": ["node"],
+
+ "strict": true,
+ "skipLibCheck": true
+ },
+ "include": ["src/**/*", "idl/**/*"],
+ "exclude": ["dist", "test"]
+}
diff --git a/packages/multiplayer/tsup.config.ts b/packages/multiplayer/tsup.config.ts
new file mode 100644
index 00000000..d7432269
--- /dev/null
+++ b/packages/multiplayer/tsup.config.ts
@@ -0,0 +1,14 @@
+import { defineConfig } from "tsup";
+
+export default defineConfig({
+ entry: ["src/index.ts"],
+ format: ["cjs", "esm"],
+ dts: true,
+ sourcemap: true,
+ clean: true,
+ treeshake: true,
+ external: [
+ "@coral-xyz/anchor",
+ "@solana/web3.js"
+ ]
+});
diff --git a/packages/react-ui/package.json b/packages/react-ui/package.json
index 287a79dd..8a2f15e9 100644
--- a/packages/react-ui/package.json
+++ b/packages/react-ui/package.json
@@ -1,16 +1,11 @@
{
"name": "gamba-react-ui-v2",
- "private": false,
"version": "0.7.1",
+ "private": false,
"main": "dist/index.js",
"module": "dist/index.mjs",
"types": "dist/index.d.ts",
- "files": [
- "dist/**"
- ],
- "publishConfig": {
- "access": "public"
- },
+ "files": ["dist/**"],
"scripts": {
"dev": "tsup src/index.ts --watch --format cjs,esm --dts --external react --external react-dom --external @solana/web3.js --external @solana/spl-token --external @solana/wallet-adapter-react --external @coral-xyz/anchor --external styled-components --external gamba-react-v2 --external gamba-core-v2 --external @gamba-labs/multiplayer-sdk",
"build": "tsup src/index.ts --format cjs,esm --dts --external react --external react-dom --external @solana/web3.js --external @solana/spl-token --external @solana/wallet-adapter-react --external @coral-xyz/anchor --external styled-components --external gamba-react-v2 --external gamba-core-v2 --external @gamba-labs/multiplayer-sdk",
@@ -18,39 +13,40 @@
"clean": "rm -rf .turbo node_modules dist"
},
"dependencies": {
- "@coral-xyz/anchor": "^0.27.0",
"@preact/signals-react": "^1.3.8",
- "@solana/spl-token": "^0.3.8",
- "@solana/web3.js": "^1.93.0",
- "gamba-core-v2": "workspace:*",
- "gamba-react-v2": "workspace:*",
- "react": "^18.3.1",
- "react-dom": "^18.3.1",
- "styled-components": "^6.0.8",
- "tone": "^14.7.77",
- "zustand": "^4.4.3"
+ "gamba-core-v2": "workspace:*",
+ "gamba-react-v2": "workspace:*",
+ "styled-components": "^6.0.8",
+ "tone": "^14.7.77",
+ "zustand": "^4.4.3"
+ },
+ "peerDependencies": {
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1",
+ "@coral-xyz/anchor": "^0.31.1",
+ "@solana/wallet-adapter-react": "^0.15.39",
+ "@solana/web3.js": "^1.98.2",
+ "@solana/spl-token": "^0.4.13",
+ "gamba-core-v2": "*",
+ "gamba-react-v2": "*",
+ "@gamba-labs/multiplayer-sdk": "*",
+ "styled-components": "^6.0.8"
+ },
+ "peerDependenciesMeta": {
+ "@solana/wallet-adapter-react": { "optional": true }
},
"devDependencies": {
"@changesets/cli": "^2.26.2",
+ "@types/node": "^24.0.10",
"@types/react": "^18.2.13",
- "@solana/wallet-adapter-react": "^0.15.35",
- "@types/react-dom": "^18.0.11",
- "assert": "^2.0.0",
- "tsup": "^7.2.0",
- "typescript": "^5.2.2"
+ "@types/react-dom": "^18.2.4",
+ "assert": "^2.0.0",
+ "tsup": "^8.5.0",
+ "typescript": "^5.2.2",
+ "@gamba-labs/multiplayer-sdk": "workspace:*"
},
- "peerDependencies": {
- "@coral-xyz/anchor": "^0.27.0",
- "@solana/wallet-adapter-react": "^0.15.35",
- "@solana/web3.js": "^1.93.0",
- "gamba-core": "*",
- "gamba-react": "*",
- "react": "^18.3.1",
- "react-dom": "^18.3.1"
+ "publishConfig": {
+ "access": "public"
},
- "peerDependenciesMeta": {
- "@solana/wallet-adapter-react": {
- "optional": true
- }
- }
+ "license": "MIT"
}
diff --git a/packages/react-ui/src/GameContext.tsx b/packages/react-ui/src/GameContext.tsx
index e19b4c92..50fa072b 100644
--- a/packages/react-ui/src/GameContext.tsx
+++ b/packages/react-ui/src/GameContext.tsx
@@ -1,4 +1,3 @@
-import { useGamba } from 'gamba-react-v2'
import React from 'react'
import { GameBundle, useGame } from '.'
import { EffectTest } from './EffectTest'
@@ -23,13 +22,15 @@ interface GameProps extends React.PropsWithChildren {
errorFallback?: React.ReactNode
}
-export const GameContext = React.createContext({ game: { id: 'unknown', app: null! } })
+export const GameContext = React.createContext({
+ game: { id: 'unknown', app: null! },
+})
function Game({ game, children, errorFallback }: GameProps) {
return (
- }>
+
@@ -38,15 +39,14 @@ function Game({ game, children, errorFallback }: GameProps) {
)
}
+/**
+ * PlayButton no longer looks at gamba.isPlaying;
+ * it only disables if you pass `disabled={true}` yourself.
+ */
export function PlayButton(props: ButtonProps) {
- const gamba = useGamba()
return (
-
@@ -57,19 +57,16 @@ export const GambaUi = {
useGame,
useSound,
Portal,
- PortalTarget: PortalTarget,
+ PortalTarget,
Effect: EffectTest,
Button,
Game,
Responsive: ResponsiveSize,
Canvas: GambaCanvas,
- WagerInput: WagerInput,
- /**
- * @deprecated Use WagerInput with "options" prop
- */
- WagerSelect: WagerSelect,
- Switch: Switch,
+ WagerInput,
+ WagerSelect,
+ Switch,
PlayButton,
- Select: Select,
- TextInput: TextInput,
+ Select,
+ TextInput,
}
diff --git a/packages/react-ui/src/components/Button.tsx b/packages/react-ui/src/components/Button.tsx
index 5b6da88b..1598f581 100644
--- a/packages/react-ui/src/components/Button.tsx
+++ b/packages/react-ui/src/components/Button.tsx
@@ -3,20 +3,29 @@ import styled, { css } from 'styled-components'
type ButtonSize = 'small' | 'medium' | 'large'
-const StyledButton = styled.button<{$main?: boolean, $size: ButtonSize}>`
+const StyledButton = styled.button<{
+ $main?: boolean
+ $size?: ButtonSize
+}>`
--color: var(--gamba-ui-button-default-color);
--background-color: var(--gamba-ui-button-default-background);
--background-color-hover: var(--gamba-ui-button-default-background-hover);
- ${(props) => props.$main && css`
- --background-color: var(--gamba-ui-button-main-background);
- --color: var(--gamba-ui-button-main-color);
- --background-color-hover: var(--gamba-ui-button-main-background-hover);
- `}
+ ${({ $main }) =>
+ $main &&
+ css`
+ --background-color: var(--gamba-ui-button-main-background);
+ --color: var(--gamba-ui-button-main-color);
+ --background-color-hover: var(--gamba-ui-button-main-background-hover);
+ `}
- ${(props) => css`
- --padding: ${props.$size === 'small' ? '5px' : props.$size === 'medium' ? '10px' : props.$size === 'large' && '15px'};
- `}
+ /* default $size to "medium" */
+ ${({ $size = 'medium' }) =>
+ css`
+ --padding: ${
+ $size === 'small' ? '5px' : $size === 'large' ? '15px' : '10px'
+ };
+ `}
background: var(--background-color);
color: var(--color);
@@ -28,32 +37,44 @@ const StyledButton = styled.button<{$main?: boolean, $size: ButtonSize}>`
border-radius: var(--gamba-ui-border-radius);
padding: var(--padding);
cursor: pointer;
- /* min-width: 100px; */
text-align: center;
- align-items: center;
&:disabled {
cursor: default;
- opacity: .7;
+ opacity: 0.7;
}
`
-export interface ButtonProps extends React.PropsWithChildren {
+export interface ButtonProps {
disabled?: boolean
onClick?: () => void
main?: boolean
size?: ButtonSize
+ children?: React.ReactNode | bigint
}
-export function Button(props: ButtonProps) {
+export function Button({
+ disabled,
+ onClick,
+ main,
+ size,
+ children,
+}: ButtonProps) {
+ // coerce bigint → string
+ const safeChildren =
+ typeof children === 'bigint' ? children.toString() : children
+
+ // cast away the styled-component’s complex signature
+ const SButton: any = StyledButton
+
return (
-
- {props.children}
-
+ {safeChildren}
+
)
}
diff --git a/packages/react-ui/src/components/ResponsiveSize.tsx b/packages/react-ui/src/components/ResponsiveSize.tsx
index b76c6a9f..d61149bd 100644
--- a/packages/react-ui/src/components/ResponsiveSize.tsx
+++ b/packages/react-ui/src/components/ResponsiveSize.tsx
@@ -1,6 +1,12 @@
-import React from 'react'
+import React, {
+ useRef,
+ useLayoutEffect,
+ PropsWithChildren,
+ HTMLAttributes,
+} from 'react'
import styled from 'styled-components'
+// styled wrapper
const Responsive = styled.div`
justify-content: center;
align-items: center;
@@ -13,39 +19,61 @@ const Responsive = styled.div`
top: 0;
`
-interface Props extends React.PropsWithChildren> {
+// Omit 'contentEditable' to avoid the type mismatch
+type DivProps = Omit, 'contentEditable'>
+
+interface Props extends PropsWithChildren {
maxScale?: number
overlay?: boolean
}
-export default function ResponsiveSize({ children, maxScale = 1, overlay, ...props }: Props) {
- const wrapper = React.useRef(null!)
- const inner = React.useRef(null!)
- const content = React.useRef(null!)
+export default function ResponsiveSize({
+ children,
+ maxScale = 1,
+ overlay, // kept for future use
+ ...props
+}: Props) {
+ const wrapperRef = useRef(null)
+ const innerRef = useRef(null)
+ const contentRef = useRef(null)
- React.useLayoutEffect(() => {
+ useLayoutEffect(() => {
let timeout: NodeJS.Timeout
const resize = () => {
- const ww = wrapper.current.clientWidth / (content.current.scrollWidth + 40)
- const hh = wrapper.current.clientHeight / (content.current.clientHeight + 80)
+ if (
+ !wrapperRef.current ||
+ !innerRef.current ||
+ !contentRef.current
+ ) {
+ return
+ }
+ const ww =
+ wrapperRef.current.clientWidth /
+ (contentRef.current.scrollWidth + 40)
+ const hh =
+ wrapperRef.current.clientHeight /
+ (contentRef.current.clientHeight + 80)
const zoom = Math.min(maxScale, ww, hh)
- inner.current.style.transform = 'scale(' + zoom + ')'
+ innerRef.current.style.transform = `scale(${zoom})`
}
+ // observe size changes on the wrapper
const ro = new ResizeObserver(resize)
+ if (wrapperRef.current) {
+ ro.observe(wrapperRef.current)
+ }
- ro.observe(wrapper.current)
-
+ // also debounce window resizes
const resizeHandler = () => {
clearTimeout(timeout)
- timeout = setTimeout(() => {
- resize()
- }, 250)
+ timeout = setTimeout(resize, 250)
}
-
window.addEventListener('resize', resizeHandler)
+ // initial scale
+ resize()
+
return () => {
window.removeEventListener('resize', resizeHandler)
ro.disconnect()
@@ -54,11 +82,9 @@ export default function ResponsiveSize({ children, maxScale = 1, overlay, ...pro
}, [maxScale])
return (
-
-
-
- {children}
-
+
+
)
diff --git a/packages/react-ui/src/components/TextInput.tsx b/packages/react-ui/src/components/TextInput.tsx
index 7e3318bc..04e41442 100644
--- a/packages/react-ui/src/components/TextInput.tsx
+++ b/packages/react-ui/src/components/TextInput.tsx
@@ -17,24 +17,49 @@ const StyledTextInput = styled.input`
&:disabled {
cursor: default;
- opacity: .7;
+ opacity: 0.7;
}
`
-export interface TextInputProps
extends Omit, 'onChange'> {
- disabled?: boolean
- onClick?: () => void
+export interface TextInputProps
+ // remove the problematic props
+ extends Omit<
+ React.InputHTMLAttributes,
+ 'children' | 'onChange' | 'formAction' | 'contentEditable'
+ > {
+ /** current input value */
value: T
+ /** new-value callback */
onChange?: (value: string) => void
+ /** click handler */
+ onClick?: () => void
+ /** disable input */
+ disabled?: boolean
+ /** if true, prevent editing and show value */
+ locked?: boolean
}
-export function TextInput({ onChange, ...props }: TextInputProps) {
+export function TextInput({
+ value,
+ onChange,
+ locked,
+ ...props
+}: TextInputProps) {
+ // cast away the styled-components overload complexity
+ const SInput: any = StyledTextInput
+
return (
- onChange && onChange(evt.target.value)}
- onFocus={(evt) => evt.target.select()}
+ ) =>
+ onChange?.(evt.target.value)
+ }
+ onFocus={(evt: React.FocusEvent) =>
+ evt.target.select()
+ }
+ disabled={props.disabled || locked}
/>
)
}
diff --git a/packages/react-ui/src/components/WagerInput.tsx b/packages/react-ui/src/components/WagerInput.tsx
index ba7b1ef4..2de5db13 100644
--- a/packages/react-ui/src/components/WagerInput.tsx
+++ b/packages/react-ui/src/components/WagerInput.tsx
@@ -1,4 +1,4 @@
-import { useGamba } from 'gamba-react-v2'
+// src/components/WagerInput.tsx
import React, { useRef } from 'react'
import styled, { css } from 'styled-components'
import { useCurrentToken, useFees, useUserBalance } from '../hooks'
@@ -59,11 +59,8 @@ const Flex = styled.button`
const Input = styled.input`
border: none;
- border-radius: 0;
margin: 0;
- padding: 10px;
- padding-left: 0;
- padding-right: 0;
+ padding: 10px 0;
color: var(--gamba-ui-input-color);
background: var(--gamba-ui-input-background);
outline: none;
@@ -74,8 +71,6 @@ const Input = styled.input`
-webkit-appearance: none;
margin: 0;
}
-
- /* Firefox */
&[type=number] {
-moz-appearance: textfield;
}
@@ -83,7 +78,6 @@ const Input = styled.input`
const InputButton = styled.button`
border: none;
- border-radius: 0;
margin: 0;
padding: 2px 10px;
color: var(--gamba-ui-input-color);
@@ -104,14 +98,11 @@ const TokenImage = styled.img`
`
const WagerAmount = styled.div`
- text-wrap: nowrap;
padding: 10px 0;
width: 40px;
-
@media (min-width: 600px) {
width: 100px;
}
-
opacity: .8;
overflow: hidden;
`
@@ -119,6 +110,11 @@ const WagerAmount = styled.div`
export interface WagerInputBaseProps {
value: number
onChange: (value: number) => void
+ /** If provided, the input is locked to this exact lamports value */
+ lockedValue?: number
+ /** Optional lower/upper bounds in lamports (for range wager games) */
+ minValue?: number
+ maxValue?: number
}
export type WagerInputProps = WagerInputBaseProps & {
@@ -128,94 +124,113 @@ export type WagerInputProps = WagerInputBaseProps & {
}
export function WagerInput(props: WagerInputProps) {
- const gamba = useGamba()
- const token = useCurrentToken()
- const [input, setInput] = React.useState('')
- const balance = useUserBalance() // useBalance(walletAddress, token.mint)
- const fees = useFees()
+ const token = useCurrentToken()
+ const balance = useUserBalance()
+ const fees = useFees()
+ const ref = useRef(null!)
const [isEditing, setIsEditing] = React.useState(false)
- const ref = useRef(null!)
+ const [input, setInput] = React.useState('')
- React.useEffect(
- () => {
- props.onChange(token.baseWager)
- },
- [token.mint.toString()],
- )
+ const isLocked = props.lockedValue != null
+ const effectiveValue = isLocked ? (props.lockedValue as number) : props.value
+
+ const clampToBounds = (n: number) => {
+ let x = n
+ if (props.minValue != null) x = Math.max(x, props.minValue)
+ if (props.maxValue != null) x = Math.min(x, props.maxValue)
+ return x
+ }
+
+ // whenever the mint changes, reset back to base wager
+ React.useEffect(() => {
+ props.onChange(token.baseWager)
+ }, [token.mint.toString()])
useOnClickOutside(ref, () => setIsEditing(false))
const startEditInput = () => {
+ if (props.disabled || isLocked) return
if (props.options) {
setIsEditing(!isEditing)
return
}
setIsEditing(true)
- setInput(String(props.value / (10 ** token.decimals)))
+ setInput(String(effectiveValue / 10 ** token.decimals))
}
const apply = () => {
- props.onChange(Number(input) * (10 ** token.decimals))
+ if (isLocked) return
+ const next = Number(input) * 10 ** token.decimals
+ props.onChange(clampToBounds(next))
setIsEditing(false)
}
const x2 = () => {
- const availableBalance = balance.balance + balance.bonusBalance
- const nextValue = Math.max(token.baseWager, (props.value * 2) || token.baseWager)
- props.onChange(Math.max(0, Math.min(nextValue, availableBalance - nextValue * fees)))
+ if (isLocked) return
+ const available = balance.balance + balance.bonusBalance
+ const base = token.baseWager
+ const want = Math.max(base, effectiveValue * 2 || base)
+ const cappedBal = Math.min(want, available - want * fees)
+ const bounded = clampToBounds(cappedBal)
+ props.onChange(bounded)
}
return (
- !gamba.isPlaying && startEditInput()}>
+
- {(!isEditing || props.options) ? (
+ {isLocked || (!isEditing || props.options) ? (
-
+
) : (
setInput(evt.target.value)}
- onKeyDown={(e) => e.code === 'Enter' && apply()}
- onBlur={(evt) => apply()}
- disabled={gamba.isPlaying}
+ onChange={e => setInput(e.target.value)}
+ onKeyDown={e => e.code === 'Enter' && apply()}
+ onBlur={apply}
+ disabled={props.disabled || isLocked}
autoFocus
- onFocus={(e) => e.target.select()}
+ onFocus={e => e.target.select()}
/>
)}
+
{!props.options && (
- props.onChange(props.value / 2)}>
+ props.onChange(clampToBounds(effectiveValue / 2))}
+ >
x.5
-
+
2x
)}
- {props.options && isEditing && (
+
+ {props.options && isEditing && !isLocked && (
- {props.options.map((valueInBaseWager, i) => (
+ {props.options.map((opt, i) => (
{
- props.onChange(valueInBaseWager * token.baseWager)
+ props.onChange(opt * token.baseWager)
setIsEditing(false)
}}
>
-
+
))}
diff --git a/packages/react-ui/src/components/WagerSelect.tsx b/packages/react-ui/src/components/WagerSelect.tsx
index cba1c3cf..d46310c0 100644
--- a/packages/react-ui/src/components/WagerSelect.tsx
+++ b/packages/react-ui/src/components/WagerSelect.tsx
@@ -1,4 +1,3 @@
-import { useGamba } from 'gamba-react-v2'
import React from 'react'
import { Select } from './Select'
import { TokenValue } from './TokenValue'
@@ -8,23 +7,24 @@ export interface WagerSelectProps {
value: number
onChange: (value: number) => void
className?: string
+ disabled?: boolean
}
-/**
- * @deprecated Use WagerInput with "options" prop
- */
-export function WagerSelect(props: WagerSelectProps) {
- const gamba = useGamba()
+export function WagerSelect({
+ options,
+ value,
+ onChange,
+ className,
+ disabled = false,
+}: WagerSelectProps) {
return (