├── setup ├── .env.example ├── src │ ├── helpers │ │ ├── general │ │ │ └── delay.ts │ │ ├── bls │ │ │ ├── getUserRandomBytesAsHex.ts │ │ │ ├── getBLSPublicKey.ts │ │ │ ├── getBLSSecretKey.ts │ │ │ └── generateBLSSig.ts │ │ ├── keypair │ │ │ ├── getAddress.ts │ │ │ └── getKeyPair.ts │ │ ├── cards │ │ │ ├── getPlayerHand.ts │ │ │ └── generateCards.ts │ │ ├── getObject │ │ │ ├── getGameObject.ts │ │ │ ├── getIsHouseAdminCapBurnt.ts │ │ │ └── getHitOrStandRequestForGameAndSum.ts │ │ └── actions │ │ │ ├── withdrawFromHouse.ts │ │ │ ├── topUpHouse.ts │ │ │ ├── doinitialDeal.ts │ │ │ ├── createGameByPlayer.ts │ │ │ ├── initializeHouseData.ts │ │ │ └── playerHitOrStandRequest.ts │ ├── types │ │ ├── HitDoneEvent.ts │ │ ├── Card.ts │ │ ├── HitRequest.ts │ │ ├── ScenarioState.ts │ │ ├── GameMessage.ts │ │ ├── GameOnChain.ts │ │ └── ScenarioStep.ts │ ├── scripts │ │ ├── 03-InitialDeal.ts │ │ ├── 09-TopUpHouse.ts │ │ ├── 10-WithDrawHouse.ts │ │ ├── 05-HouseRespondToHitRequest.ts │ │ ├── 04-PlayerHitRequest.ts │ │ ├── 07-HouseRespondToStandRequest.ts │ │ ├── 06-PlayerStandRequest.ts │ │ ├── 08-GetGameObject.ts │ │ ├── 01-initializeHouseData.ts │ │ └── 02-createGameByPlayer.ts │ ├── scenarioStand.ts │ ├── scenarioHitAndStand.ts │ ├── config.ts │ └── customScenario.ts ├── package.json └── tx_res.json ├── app ├── vercel.json ├── public │ ├── favicon.ico │ ├── Mysten_Labs_Vertical_Logo_Red.png │ ├── general │ │ ├── player.svg │ │ ├── info.svg │ │ ├── empty-hand.svg │ │ ├── dealer.svg │ │ ├── arrow-top-right.svg │ │ ├── background.svg │ │ ├── sui.svg │ │ ├── google.svg │ │ └── plus.svg │ ├── manifest.json │ ├── cards │ │ ├── diamonds │ │ │ ├── diamonds-7.svg │ │ │ ├── diamonds-4.svg │ │ │ ├── diamonds-A.svg │ │ │ ├── diamonds-2.svg │ │ │ ├── diamonds-5.svg │ │ │ ├── diamonds-10.svg │ │ │ ├── diamonds-9.svg │ │ │ ├── diamonds-6.svg │ │ │ ├── diamonds-3.svg │ │ │ └── diamonds-8.svg │ │ ├── hearts │ │ │ ├── hearts-7.svg │ │ │ ├── hearts-4.svg │ │ │ ├── hearts-2.svg │ │ │ ├── hearts-5.svg │ │ │ ├── hearts-10.svg │ │ │ ├── hearts-9.svg │ │ │ ├── hearts-6.svg │ │ │ ├── hearts-3.svg │ │ │ └── hearts-8.svg │ │ ├── spades │ │ │ ├── spades-7.svg │ │ │ ├── spades-4.svg │ │ │ ├── spades-2.svg │ │ │ ├── spades-5.svg │ │ │ ├── spades-10.svg │ │ │ ├── spades-6.svg │ │ │ ├── spades-3.svg │ │ │ ├── spades-9.svg │ │ │ └── spades-8.svg │ │ └── clubs │ │ │ ├── clubs-7.svg │ │ │ ├── clubs-4.svg │ │ │ ├── clubs-2.svg │ │ │ └── clubs-5.svg │ ├── actions │ │ ├── hit.svg │ │ └── stand.svg │ ├── result │ │ └── win-chip.svg │ └── service-worker.js ├── postcss.config.js ├── src │ ├── types │ │ ├── ChildrenProps.ts │ │ └── GameOnChain.ts │ ├── hooks │ │ ├── useSui.ts │ │ ├── useConnectedAccount.ts │ │ ├── useWalletSession.ts │ │ ├── useRegisterServiceWorker.ts │ │ ├── useRequestSui.ts │ │ └── useSponsoredTransaction.ts │ ├── helpers │ │ ├── getMoveTarget.ts │ │ ├── formatString.ts │ │ ├── suiClient.ts │ │ ├── sponsorAndSignTransactionWithApi.ts │ │ ├── getNetworkName.ts │ │ ├── getSuiExplorerLink.ts │ │ └── formatSUIAmount.ts │ ├── lib │ │ └── utils.ts │ ├── app │ │ ├── api │ │ │ ├── EnokiClient.ts │ │ │ ├── helpers │ │ │ │ ├── getAddress.ts │ │ │ │ ├── getKeyPair.ts │ │ │ │ ├── getBLSSecretKey.ts │ │ │ │ └── getGameObject.ts │ │ │ ├── health │ │ │ │ └── route.ts │ │ │ ├── execute │ │ │ │ └── route.ts │ │ │ ├── sponsor │ │ │ │ └── route.ts │ │ │ ├── games │ │ │ │ └── [id] │ │ │ │ │ ├── deal │ │ │ │ │ └── route.ts │ │ │ │ │ ├── hit │ │ │ │ │ └── route.ts │ │ │ │ │ └── stand │ │ │ │ │ └── route.ts │ │ │ ├── utils │ │ │ │ └── sponsorAndSignTransaction.ts │ │ │ └── services │ │ │ │ ├── doInitialDeal.ts │ │ │ │ └── houseHitOrStand.ts │ │ ├── not-found.tsx │ │ ├── new │ │ │ └── page.tsx │ │ ├── layout.tsx │ │ ├── rules │ │ │ └── page.tsx │ │ └── ProvidersAndLayout.tsx │ ├── components │ │ ├── general │ │ │ ├── Spinner.tsx │ │ │ ├── LoadingButton.tsx │ │ │ ├── SuiExplorerLink.tsx │ │ │ ├── SetupGameStepper.tsx │ │ │ └── Balance.tsx │ │ ├── layouts │ │ │ ├── LargeScreenLayout.tsx │ │ │ ├── TopNavbar.tsx │ │ │ └── InfoIcon.tsx │ │ ├── home │ │ │ ├── DealerCards.tsx │ │ │ ├── PlayerCards.tsx │ │ │ ├── RequestSUI.tsx │ │ │ ├── BlackjackBanner.tsx │ │ │ ├── GameActions.tsx │ │ │ ├── SetupGame.tsx │ │ │ ├── StartGame.tsx │ │ │ └── SignInBanner.tsx │ │ └── ui │ │ │ ├── label.tsx │ │ │ ├── textarea.tsx │ │ │ ├── input.tsx │ │ │ ├── tooltip.tsx │ │ │ ├── badge.tsx │ │ │ ├── radio-group.tsx │ │ │ ├── scroll-area.tsx │ │ │ ├── button.tsx │ │ │ ├── card.tsx │ │ │ └── accordion.tsx │ ├── __generated__ │ │ └── blackjack │ │ │ └── deps │ │ │ └── sui │ │ │ ├── object.ts │ │ │ └── balance.ts │ ├── utils │ │ └── getGameObject.ts │ ├── contexts │ │ ├── EnokiWalletProvider.tsx │ │ └── BalanceContext.tsx │ ├── config │ │ └── serverConfig.ts │ └── styles │ │ └── globals.css ├── components.json ├── .gitignore ├── .env ├── sui-codegen.config.ts ├── .env.development.local.example ├── tsconfig.json ├── eslint.config.mjs ├── next.config.js └── package.json ├── sui_blackjack_sequence_diagram.png ├── move └── blackjack │ └── Move.toml ├── .gitignore └── sui_blackjack_sequence_diagram.txt /setup/.env.example: -------------------------------------------------------------------------------- 1 | SUI_NETWORK=https://rpc.testnet.sui.io:443 2 | -------------------------------------------------------------------------------- /app/vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "installCommand": "pnpm install --frozen-lockfile" 3 | } -------------------------------------------------------------------------------- /app/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MystenLabs/blackjack-sui/HEAD/app/public/favicon.ico -------------------------------------------------------------------------------- /app/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | '@tailwindcss/postcss': {}, 4 | }, 5 | } 6 | -------------------------------------------------------------------------------- /setup/src/helpers/general/delay.ts: -------------------------------------------------------------------------------- 1 | export const delay = (ms: number) => new Promise((res) => setTimeout(res, ms)); -------------------------------------------------------------------------------- /app/src/types/ChildrenProps.ts: -------------------------------------------------------------------------------- 1 | export interface ChildrenProps { 2 | children: React.ReactNode | React.ReactNode[]; 3 | } 4 | -------------------------------------------------------------------------------- /sui_blackjack_sequence_diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MystenLabs/blackjack-sui/HEAD/sui_blackjack_sequence_diagram.png -------------------------------------------------------------------------------- /app/public/Mysten_Labs_Vertical_Logo_Red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MystenLabs/blackjack-sui/HEAD/app/public/Mysten_Labs_Vertical_Logo_Red.png -------------------------------------------------------------------------------- /app/src/hooks/useSui.ts: -------------------------------------------------------------------------------- 1 | import { suiClient } from '@/helpers/suiClient'; 2 | 3 | export const useSui = () => { 4 | return { suiClient }; 5 | }; 6 | -------------------------------------------------------------------------------- /setup/src/types/HitDoneEvent.ts: -------------------------------------------------------------------------------- 1 | export interface HitDoneEvent { 2 | game_id: string; 3 | player_cards: string[]; 4 | current_player_hand_sum: string; 5 | } -------------------------------------------------------------------------------- /setup/src/types/Card.ts: -------------------------------------------------------------------------------- 1 | export interface Card { 2 | index: number; 3 | suit: string; 4 | value: string; 5 | } 6 | 7 | export type CardsMap = { [key: number]: Card }; -------------------------------------------------------------------------------- /setup/src/types/HitRequest.ts: -------------------------------------------------------------------------------- 1 | export interface HitOrStandRequest { 2 | id: { 3 | id: string; 4 | }; 5 | game_id: string; 6 | current_player_sum: number; 7 | } -------------------------------------------------------------------------------- /app/src/helpers/getMoveTarget.ts: -------------------------------------------------------------------------------- 1 | 2 | const getMoveTarget = (pkg: string, fun: string) => `${process.env.NEXT_PUBLIC_PACKAGE_ADDRESS}::${pkg}::${fun}`; 3 | 4 | export default getMoveTarget; -------------------------------------------------------------------------------- /app/src/helpers/formatString.ts: -------------------------------------------------------------------------------- 1 | export const formatString = (str: string, length: number) => { 2 | if (str.length <= length) { 3 | return str; 4 | } 5 | return str.slice(0, length) + "..."; 6 | }; 7 | -------------------------------------------------------------------------------- /app/src/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { type ClassValue, clsx } from "clsx" 2 | import { twMerge } from "tailwind-merge" 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)) 6 | } 7 | -------------------------------------------------------------------------------- /move/blackjack/Move.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "blackjack" 3 | version = "0.0.2" 4 | edition = "2024" 5 | 6 | [addresses] 7 | blackjack = "0x0" 8 | sui = "0000000000000000000000000000000000000000000000000000000000000002" 9 | -------------------------------------------------------------------------------- /setup/src/types/ScenarioState.ts: -------------------------------------------------------------------------------- 1 | import { GameOnChain } from "./GameOnChain"; 2 | 3 | export interface ScenarioState { 4 | houseDataId: string | null; 5 | gameId: string | null; 6 | game: GameOnChain | null; 7 | } 8 | -------------------------------------------------------------------------------- /setup/src/types/GameMessage.ts: -------------------------------------------------------------------------------- 1 | export interface GameMessage { 2 | gameId: string; 3 | packageId?: string; 4 | type?: string; 5 | playerCards?: string[]; 6 | playerScore?: string; 7 | requestObjectId?: string; 8 | } -------------------------------------------------------------------------------- /app/src/app/api/EnokiClient.ts: -------------------------------------------------------------------------------- 1 | import serverConfig from "@/config/serverConfig"; 2 | import { EnokiClient } from "@mysten/enoki"; 3 | 4 | export const enokiClient = new EnokiClient({ 5 | apiKey: serverConfig.ENOKI_SECRET_KEY, 6 | }); 7 | -------------------------------------------------------------------------------- /setup/src/helpers/bls/getUserRandomBytesAsHex.ts: -------------------------------------------------------------------------------- 1 | import { bytesToHex, randomBytes } from "@noble/hashes/utils"; 2 | 3 | export const getUserRandomnessAsHexString = () => { 4 | const userRandomness = randomBytes(16); 5 | return bytesToHex(userRandomness); 6 | }; 7 | -------------------------------------------------------------------------------- /app/src/hooks/useConnectedAccount.ts: -------------------------------------------------------------------------------- 1 | import {useCurrentAccount} from "@mysten/dapp-kit"; 2 | 3 | export default function useConnectedAccount() { 4 | const account = useCurrentAccount(); 5 | 6 | if (!account) { 7 | throw new Error('No account is available'); 8 | } 9 | } -------------------------------------------------------------------------------- /app/src/app/api/helpers/getAddress.ts: -------------------------------------------------------------------------------- 1 | import { getKeypair } from "./getKeyPair"; 2 | 3 | export const getAddress = (secretKey: string): string => { 4 | const keypair = getKeypair(secretKey); 5 | const address = keypair.getPublicKey().toSuiAddress(); 6 | return address; 7 | }; 8 | -------------------------------------------------------------------------------- /setup/src/helpers/keypair/getAddress.ts: -------------------------------------------------------------------------------- 1 | import { getKeypair } from "./getKeyPair"; 2 | 3 | export const getAddress = (secretKey: string): string => { 4 | const keypair = getKeypair(secretKey); 5 | const address = keypair.getPublicKey().toSuiAddress(); 6 | return address; 7 | }; 8 | -------------------------------------------------------------------------------- /app/src/app/api/health/route.ts: -------------------------------------------------------------------------------- 1 | import { NextRequest, NextResponse } from "next/server"; 2 | 3 | export const GET = (request: NextRequest) => { 4 | return NextResponse.json( 5 | { 6 | status: "OK", 7 | }, 8 | { 9 | status: 200, 10 | } 11 | ); 12 | }; 13 | -------------------------------------------------------------------------------- /setup/src/types/GameOnChain.ts: -------------------------------------------------------------------------------- 1 | export interface GameOnChain { 2 | id: { 3 | id: string; 4 | }; 5 | counter: number; 6 | dealer_cards: number[]; 7 | dealer_sum: number; 8 | player_cards: number[]; 9 | player_sum: number; 10 | status: number; 11 | total_stake: string; 12 | user_randomness: number[]; 13 | } 14 | -------------------------------------------------------------------------------- /app/src/helpers/suiClient.ts: -------------------------------------------------------------------------------- 1 | import { SuiClient } from '@mysten/sui/client'; 2 | 3 | export const suiClient = new SuiClient({ 4 | url: process.env.NEXT_PUBLIC_SUI_NETWORK!, 5 | mvr: { 6 | overrides: { 7 | packages: { 8 | '@local-pkg/blackjack': process.env.NEXT_PUBLIC_PACKAGE_ADDRESS!, 9 | }, 10 | }, 11 | }, 12 | }); 13 | -------------------------------------------------------------------------------- /app/src/types/GameOnChain.ts: -------------------------------------------------------------------------------- 1 | export interface GameOnChain { 2 | id: { 3 | id: string; 4 | }; 5 | counter: number; 6 | dealer_cards: number[]; 7 | dealer_sum: number; 8 | player: string; 9 | player_cards: number[]; 10 | player_sum: number; 11 | status: number; 12 | total_stake: string; 13 | user_randomness: number[]; 14 | } 15 | -------------------------------------------------------------------------------- /setup/src/helpers/bls/getBLSPublicKey.ts: -------------------------------------------------------------------------------- 1 | import { getBLSSecreyKey } from "./getBLSSecretKey"; 2 | import { bls12_381 } from "@noble/curves/bls12-381"; 3 | 4 | export const getBLSPublicKey = (secretKey: string) => { 5 | const blsSecretKey = getBLSSecreyKey(secretKey); 6 | const blsPublicKey = bls12_381.getPublicKey(blsSecretKey); 7 | return blsPublicKey; 8 | }; 9 | -------------------------------------------------------------------------------- /app/src/app/not-found.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const NotFoundPage = () => { 4 | return ( 5 |
6 |
404
7 |
Page Not Found
8 |
9 | ); 10 | }; 11 | 12 | export default NotFoundPage; 13 | -------------------------------------------------------------------------------- /app/src/helpers/sponsorAndSignTransactionWithApi.ts: -------------------------------------------------------------------------------- 1 | import { SuiClient } from "@mysten/sui/src/client"; 2 | import { Transaction} from "@mysten/sui/src/transactions"; 3 | 4 | interface Args { 5 | tx: Transaction; 6 | suiClient: SuiClient; 7 | sender: string; 8 | } 9 | 10 | export default function sponsorAndSignTransactionWithApi({ tx, suiClient, sender }: Args) { 11 | 12 | 13 | } -------------------------------------------------------------------------------- /app/src/app/api/helpers/getKeyPair.ts: -------------------------------------------------------------------------------- 1 | import { fromB64 } from "@mysten/sui/utils"; 2 | import { Ed25519Keypair } from "@mysten/sui/keypairs/ed25519"; 3 | 4 | export const getKeypair = (secretKey: string) => { 5 | let privateKeyArray = Uint8Array.from(Array.from(fromB64(secretKey))); 6 | const keypairAdmin = Ed25519Keypair.fromSecretKey(privateKeyArray.slice(1)); 7 | return keypairAdmin; 8 | }; 9 | -------------------------------------------------------------------------------- /app/src/helpers/getNetworkName.ts: -------------------------------------------------------------------------------- 1 | export const getNetworkName = () => { 2 | const network = process.env.NEXT_PUBLIC_SUI_NETWORK!; 3 | if (network.includes("mainnet")) { 4 | return "mainnet"; 5 | } 6 | if (network.includes("testnet")) { 7 | return "testnet"; 8 | } 9 | if (network.includes("devnet")) { 10 | return "devnet"; 11 | } 12 | return "localnet"; 13 | }; 14 | -------------------------------------------------------------------------------- /setup/src/helpers/keypair/getKeyPair.ts: -------------------------------------------------------------------------------- 1 | import { fromB64 } from "@mysten/sui/utils"; 2 | import { Ed25519Keypair } from "@mysten/sui/keypairs/ed25519"; 3 | 4 | export const getKeypair = (secretKey: string) => { 5 | let privateKeyArray = Uint8Array.from(Array.from(fromB64(secretKey))); 6 | const keypairAdmin = Ed25519Keypair.fromSecretKey(privateKeyArray.slice(1)); 7 | return keypairAdmin; 8 | }; 9 | -------------------------------------------------------------------------------- /app/components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "default", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.js", 8 | "css": "src/styles/globals.css", 9 | "baseColor": "slate", 10 | "cssVariables": true 11 | }, 12 | "aliases": { 13 | "components": "@/components", 14 | "utils": "@/lib/utils" 15 | } 16 | } -------------------------------------------------------------------------------- /app/src/app/new/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { Spinner } from "@/components/general/Spinner"; 4 | import { useRouter } from "next/navigation"; 5 | import React, { useEffect } from "react"; 6 | 7 | const NewGamePage = () => { 8 | const router = useRouter(); 9 | 10 | useEffect(() => { 11 | router.push("/"); 12 | }, [router]); 13 | 14 | return ; 15 | }; 16 | 17 | export default NewGamePage; -------------------------------------------------------------------------------- /app/src/components/general/Spinner.tsx: -------------------------------------------------------------------------------- 1 | import { Loader2 } from "lucide-react"; 2 | import React from "react"; 3 | 4 | interface SpinnerProps { 5 | className?: string; 6 | } 7 | 8 | export const Spinner = ({ className = "text-primary" }: SpinnerProps) => { 9 | return ( 10 |
11 | 12 |
13 | ); 14 | }; 15 | -------------------------------------------------------------------------------- /setup/src/types/ScenarioStep.ts: -------------------------------------------------------------------------------- 1 | export type ScenarioStep = "initialize" | "initial-deal" | UserScenarioStep; 2 | 3 | export type UserScenarioStep = 4 | | "request-stand" 5 | | "request-hit" 6 | | "house-stand" 7 | | "house-hit"; 8 | 9 | export const isUserScenarioStep = (step: string): step is UserScenarioStep => { 10 | return ["request-stand", "request-hit", "house-stand", "house-hit"].includes( 11 | step 12 | ); 13 | }; 14 | -------------------------------------------------------------------------------- /setup/src/helpers/bls/getBLSSecretKey.ts: -------------------------------------------------------------------------------- 1 | import hkdf from "futoin-hkdf"; 2 | 3 | export const getBLSSecreyKey = (privateKey: string) => { 4 | // initial key material 5 | const ikm = privateKey; 6 | const length = 32; 7 | const salt = "blackjack"; 8 | const info = "bls-signature"; 9 | const hash = 'SHA-256'; 10 | const derivedSecretKey = hkdf(ikm, length, {salt, info, hash}); 11 | return Uint8Array.from(derivedSecretKey); 12 | } -------------------------------------------------------------------------------- /app/src/app/api/helpers/getBLSSecretKey.ts: -------------------------------------------------------------------------------- 1 | import hkdf from "futoin-hkdf"; 2 | 3 | export const getBLSSecreyKey = (privateKey: string) => { 4 | // initial key material 5 | const ikm = privateKey; 6 | const length = 32; 7 | const salt = "blackjack"; 8 | const info = "bls-signature"; 9 | const hash = 'SHA-256'; 10 | const derivedSecretKey = hkdf(ikm, length, {salt, info, hash}); 11 | return Uint8Array.from(derivedSecretKey); 12 | } -------------------------------------------------------------------------------- /setup/src/scripts/03-InitialDeal.ts: -------------------------------------------------------------------------------- 1 | import { SuiClient } from "@mysten/sui/client"; 2 | import { doInitialDeal } from "../helpers/actions/doinitialDeal"; 3 | import { GAME_ID, HOUSE_DATA_ID, SUI_NETWORK } from "../config"; 4 | 5 | if (!GAME_ID) { 6 | throw new Error("GAME_ID is not set in your .env file"); 7 | } 8 | 9 | doInitialDeal({ 10 | suiClient: new SuiClient({ url: SUI_NETWORK }), 11 | gameId: GAME_ID, 12 | houseDataId: HOUSE_DATA_ID!, 13 | }); 14 | -------------------------------------------------------------------------------- /setup/src/scenarioStand.ts: -------------------------------------------------------------------------------- 1 | import { Scenario } from "./helpers/scenario/Scenario"; 2 | import { BJ_PLAYER_SECRET_KEY } from "./config"; 3 | 4 | const run = async () => { 5 | if (!BJ_PLAYER_SECRET_KEY) { 6 | throw new Error("BJ_PLAYER_SECRET_KEY is not set"); 7 | } 8 | 9 | const scenario = new Scenario({ 10 | steps: ["request-stand", "house-stand"], 11 | playerSecretKey: BJ_PLAYER_SECRET_KEY, 12 | }); 13 | 14 | await scenario.run(); 15 | }; 16 | 17 | run(); 18 | -------------------------------------------------------------------------------- /setup/src/helpers/cards/getPlayerHand.ts: -------------------------------------------------------------------------------- 1 | import { Card, CardsMap } from "../../types/Card"; 2 | 3 | interface GetPlayerHandProps { 4 | cardsMap: CardsMap; 5 | playerCards: number[]; 6 | } 7 | export const getPlayerHand = ({ cardsMap, playerCards }: GetPlayerHandProps) => { 8 | const playerInitialHand: Card[] = []; 9 | playerCards.forEach((cardIndex) => { 10 | const card = cardsMap[cardIndex]; 11 | playerInitialHand.push(card); 12 | }); 13 | return playerInitialHand; 14 | }; 15 | -------------------------------------------------------------------------------- /setup/src/scripts/09-TopUpHouse.ts: -------------------------------------------------------------------------------- 1 | import { SuiClient } from "@mysten/sui/client"; 2 | import { SUI_NETWORK, ADMIN_SECRET_KEY, HOUSE_DATA_ID } from "../config"; 3 | import { topUpHouseData } from "../helpers/actions/topUpHouse"; 4 | 5 | const houseTopUp = async () => { 6 | const suiClient = new SuiClient({ url: SUI_NETWORK }); 7 | await topUpHouseData({ 8 | adminSecretKey: ADMIN_SECRET_KEY, 9 | houseDataId: HOUSE_DATA_ID, 10 | suiClient, 11 | }); 12 | }; 13 | 14 | houseTopUp(); 15 | -------------------------------------------------------------------------------- /app/public/general/player.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /setup/src/scripts/10-WithDrawHouse.ts: -------------------------------------------------------------------------------- 1 | import { SuiClient } from "@mysten/sui/client"; 2 | import { ADMIN_SECRET_KEY, HOUSE_DATA_ID, SUI_NETWORK } from "../config"; 3 | import { withdrawFromHouse } from "../helpers/actions/withdrawFromHouse"; 4 | 5 | const withdrawHouse = async () => { 6 | const suiClient = new SuiClient({ url: SUI_NETWORK }); 7 | await withdrawFromHouse({ 8 | suiClient, 9 | houseDataId: HOUSE_DATA_ID, 10 | adminSecretKey: ADMIN_SECRET_KEY, 11 | }); 12 | }; 13 | 14 | withdrawHouse(); 15 | -------------------------------------------------------------------------------- /setup/src/scenarioHitAndStand.ts: -------------------------------------------------------------------------------- 1 | import { Scenario } from "./helpers/scenario/Scenario"; 2 | import { BJ_PLAYER_SECRET_KEY } from "./config"; 3 | 4 | const run = async () => { 5 | 6 | if (!BJ_PLAYER_SECRET_KEY) { 7 | throw new Error("BJ_PLAYER_SECRET_KEY is not set"); 8 | } 9 | 10 | const scenario = new Scenario({ 11 | steps: ["request-hit", "house-hit", "request-stand", "house-stand"], 12 | playerSecretKey: BJ_PLAYER_SECRET_KEY, 13 | }); 14 | 15 | await scenario.run(); 16 | }; 17 | 18 | run(); 19 | -------------------------------------------------------------------------------- /setup/src/scripts/05-HouseRespondToHitRequest.ts: -------------------------------------------------------------------------------- 1 | import { SuiClient } from "@mysten/sui/client"; 2 | import { GAME_ID, HOUSE_DATA_ID, SUI_NETWORK } from "../config"; 3 | import { houseHitOrStand } from "../helpers/actions/houseHitOrStand"; 4 | 5 | const houseExecuteHit = async () => { 6 | const suiClient = new SuiClient({ 7 | url: SUI_NETWORK, 8 | }); 9 | await houseHitOrStand({ 10 | gameId: GAME_ID, 11 | move: "hit", 12 | suiClient, 13 | houseDataId: HOUSE_DATA_ID, 14 | }); 15 | }; 16 | 17 | houseExecuteHit(); 18 | -------------------------------------------------------------------------------- /setup/src/scripts/04-PlayerHitRequest.ts: -------------------------------------------------------------------------------- 1 | import { SuiClient } from "@mysten/sui/client"; 2 | import { doPlayerHitOrStand } from "../helpers/actions/playerHitOrStandRequest"; 3 | import { BJ_PLAYER_SECRET_KEY, GAME_ID, SUI_NETWORK } from "../config"; 4 | 5 | if (!BJ_PLAYER_SECRET_KEY) { 6 | throw new Error("BJ_PLAYER_SECRET_KEY is not set in your .env file"); 7 | } 8 | 9 | doPlayerHitOrStand({ 10 | playerSecretKey: BJ_PLAYER_SECRET_KEY, 11 | suiClient: new SuiClient({ url: SUI_NETWORK }), 12 | gameId: GAME_ID, 13 | move: "hit", 14 | }); 15 | -------------------------------------------------------------------------------- /setup/src/scripts/07-HouseRespondToStandRequest.ts: -------------------------------------------------------------------------------- 1 | import { SuiClient } from "@mysten/sui/client"; 2 | import { GAME_ID, HOUSE_DATA_ID, SUI_NETWORK } from "../config"; 3 | import { houseHitOrStand } from "../helpers/actions/houseHitOrStand"; 4 | 5 | const houseExecuteStand = async () => { 6 | const suiClient = new SuiClient({ 7 | url: SUI_NETWORK, 8 | }); 9 | await houseHitOrStand({ 10 | gameId: GAME_ID, 11 | move: "stand", 12 | suiClient, 13 | houseDataId: HOUSE_DATA_ID, 14 | }); 15 | }; 16 | 17 | houseExecuteStand(); 18 | -------------------------------------------------------------------------------- /app/public/general/info.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/__generated__/blackjack/deps/sui/object.ts: -------------------------------------------------------------------------------- 1 | /************************************************************** 2 | * THIS FILE IS GENERATED AND SHOULD NOT BE MANUALLY MODIFIED * 3 | **************************************************************/ 4 | 5 | 6 | /** Sui object identifiers */ 7 | 8 | import { MoveStruct } from '../../../utils/index.js'; 9 | import { bcs } from '@mysten/sui/bcs'; 10 | const $moduleName = '0x2::object'; 11 | export const UID = new MoveStruct({ name: `${$moduleName}::UID`, fields: { 12 | id: bcs.Address 13 | } }); -------------------------------------------------------------------------------- /setup/src/scripts/06-PlayerStandRequest.ts: -------------------------------------------------------------------------------- 1 | import { SuiClient } from "@mysten/sui/client"; 2 | import { doPlayerHitOrStand } from "../helpers/actions/playerHitOrStandRequest"; 3 | import { BJ_PLAYER_SECRET_KEY, GAME_ID, SUI_NETWORK } from "../config"; 4 | 5 | if (!BJ_PLAYER_SECRET_KEY) { 6 | throw new Error("BJ_PLAYER_SECRET_KEY is not set in your .env file"); 7 | } 8 | 9 | doPlayerHitOrStand({ 10 | playerSecretKey: BJ_PLAYER_SECRET_KEY, 11 | suiClient: new SuiClient({ url: SUI_NETWORK }), 12 | gameId: GAME_ID, 13 | move: "stand", 14 | }); 15 | -------------------------------------------------------------------------------- /setup/src/scripts/08-GetGameObject.ts: -------------------------------------------------------------------------------- 1 | import { SuiClient } from "@mysten/sui/client"; 2 | import { getGameObject } from "../helpers/getObject/getGameObject"; 3 | import { GAME_ID, SUI_NETWORK } from "../config"; 4 | 5 | const getGame = async () => { 6 | const suiClient = new SuiClient({ 7 | url: SUI_NETWORK, 8 | }); 9 | if (!GAME_ID) { 10 | throw new Error("GAME_ID is not set in your .env file"); 11 | } 12 | const game = await getGameObject({ 13 | suiClient, 14 | gameId: GAME_ID, 15 | }); 16 | console.log(game); 17 | }; 18 | 19 | getGame(); 20 | -------------------------------------------------------------------------------- /app/public/general/empty-hand.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env*.local 29 | 30 | # vercel 31 | .vercel 32 | 33 | # typescript 34 | *.tsbuildinfo 35 | next-env.d.ts 36 | 37 | # next-pwa 38 | public/sw.js 39 | public/workbox-* 40 | -------------------------------------------------------------------------------- /app/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "PoC Template", 3 | "short_name": "Mysten Labs NextJS PoC Template", 4 | "description": "A Progressive Web App built with Next.js", 5 | "start_url": "/", 6 | "display": "standalone", 7 | "background_color": "#ffffff", 8 | "theme_color": "#000000", 9 | "icons": [ 10 | { 11 | "src": "/Mysten_Labs_Vertical_Logo_Red.png", 12 | "sizes": "192x192", 13 | "type": "image/png" 14 | }, 15 | { 16 | "src": "/Mysten_Labs_Vertical_Logo_Red.png", 17 | "sizes": "512x512", 18 | "type": "image/png" 19 | } 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /app/.env: -------------------------------------------------------------------------------- 1 | NEXT_PUBLIC_SUI_NETWORK=https://fullnode.testnet.sui.io:443 2 | NEXT_PUBLIC_PACKAGE_ADDRESS=0x78981b5d55ae3a28e5367efef5ae692b17fa274db7302153e6364c1f1960a146 3 | NEXT_PUBLIC_ADMIN_ADDRESS=0xfae46ba2b4147595fae7f968b34f011ac6f6c11bf15027cd9205178843b9feb5 4 | NEXT_PUBLIC_HOUSE_DATA_ID=0x899f05c230edf251f39eb2108ffce645bc73f1ea1541f5196d1c1589569fdfa6 5 | NEXT_PUBLIC_ENOKI_API_KEY=enoki_public_babee84e0efbb7ddb9fa45a6b50b6caf 6 | NEXT_PUBLIC_GOOGLE_CLIENT_ID=960310698970-hok9jcabvmo1nnsd9id0qg5vshqiqncr.apps.googleusercontent.com 7 | NEXT_PUBLIC_SUI_NETWORK_NAME=testnet 8 | ENOKI_SECRET_KEY=sdf 9 | ADMIN_SECRET_KEY=asf -------------------------------------------------------------------------------- /app/src/components/layouts/LargeScreenLayout.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { ChildrenProps } from "@/types/ChildrenProps"; 4 | import React from "react"; 5 | import { InfoIcon } from "./InfoIcon"; 6 | import { TopNavbar } from "./TopNavbar"; 7 | 8 | export const LargeScreenLayout = ({ children }: ChildrenProps) => { 9 | 10 | return ( 11 |
12 | 13 |
14 |
{children}
15 |
16 | 17 |
18 | ); 19 | }; 20 | -------------------------------------------------------------------------------- /setup/src/scripts/01-initializeHouseData.ts: -------------------------------------------------------------------------------- 1 | import { SuiClient } from "@mysten/sui/client"; 2 | import { initializeHouseData } from "../helpers/actions/initializeHouseData"; 3 | import fs from "fs"; 4 | import { SUI_NETWORK } from "../config"; 5 | 6 | const initializeHouse = async () => { 7 | const houseDataId = await initializeHouseData({ 8 | suiClient: new SuiClient({ url: SUI_NETWORK }), 9 | }); 10 | fs.appendFileSync("./.env", `HOUSE_DATA_ID=${houseDataId}\n`); 11 | fs.appendFileSync( 12 | "../app/.env.local", 13 | `NEXT_PUBLIC_HOUSE_DATA_ID=${houseDataId}\n` 14 | ); 15 | }; 16 | 17 | initializeHouse(); 18 | -------------------------------------------------------------------------------- /app/src/components/home/DealerCards.tsx: -------------------------------------------------------------------------------- 1 | import Image from "next/image"; 2 | import React from "react"; 3 | import { CardsHand } from "./CardsHand"; 4 | 5 | interface DealerCardsProps { 6 | cards: number[]; 7 | points: number; 8 | won?: boolean; 9 | lost?: boolean; 10 | } 11 | 12 | export const DealerCards = ({ cards, points, won, lost }: DealerCardsProps) => { 13 | return ( 14 |
15 | dealer 16 | 17 |
18 | ); 19 | }; 20 | -------------------------------------------------------------------------------- /app/src/utils/getGameObject.ts: -------------------------------------------------------------------------------- 1 | import { GameOnChain } from "@/types/GameOnChain"; 2 | import { SuiClient, SuiMoveObject } from "@mysten/sui/client"; 3 | 4 | interface GetGameObjectProps { 5 | suiClient: SuiClient; 6 | gameId: string; 7 | } 8 | export const getGameObject = async ({ 9 | suiClient, 10 | gameId, 11 | }: GetGameObjectProps): Promise => { 12 | const res = await suiClient.getObject({ 13 | id: gameId, 14 | options: { showContent: true }, 15 | }); 16 | const gameObject = res?.data?.content as SuiMoveObject; 17 | const { fields } = gameObject; 18 | return fields as unknown as GameOnChain; 19 | }; 20 | -------------------------------------------------------------------------------- /app/sui-codegen.config.ts: -------------------------------------------------------------------------------- 1 | import type { SuiCodegenConfig } from "@mysten/codegen"; 2 | import { fileURLToPath } from "node:url"; 3 | import path from "node:path"; 4 | 5 | const __filename = fileURLToPath(import.meta.url); // get the resolved path to the file 6 | const __dirname = path.dirname(__filename); // get the name of the directory 7 | 8 | const config: SuiCodegenConfig = { 9 | output: './src/__generated__', 10 | generateSummaries: true, 11 | prune: true, 12 | packages: [ 13 | { 14 | package: '@local-pkg/blackjack', 15 | path: path.join(__dirname, '../move/blackjack'), 16 | }, 17 | ], 18 | }; 19 | 20 | export default config; 21 | -------------------------------------------------------------------------------- /app/src/app/api/helpers/getGameObject.ts: -------------------------------------------------------------------------------- 1 | import { GameOnChain } from "@/types/GameOnChain"; 2 | import { SuiClient, SuiMoveObject } from "@mysten/sui/client"; 3 | 4 | interface GetGameObjectProps { 5 | suiClient: SuiClient; 6 | gameId: string; 7 | } 8 | export const getGameObject = async ({ 9 | suiClient, 10 | gameId, 11 | }: GetGameObjectProps): Promise => { 12 | const res = await suiClient.getObject({ 13 | id: gameId, 14 | options: { showContent: true }, 15 | }); 16 | const gameObject = res?.data?.content as SuiMoveObject; 17 | const { fields } = gameObject; 18 | return fields as unknown as GameOnChain; 19 | }; 20 | -------------------------------------------------------------------------------- /app/src/helpers/getSuiExplorerLink.ts: -------------------------------------------------------------------------------- 1 | import { getNetworkName } from "./getNetworkName"; 2 | 3 | interface GetSuiExplorerLinkProps { 4 | type: "module" | "object" | "address"; 5 | objectId: string; 6 | moduleName?: string; 7 | } 8 | 9 | export const getSuiExplorerLink = ({ 10 | type, 11 | objectId, 12 | moduleName, 13 | }: GetSuiExplorerLinkProps) => { 14 | const URLParams = `${ 15 | type === "module" ? `module=${moduleName}&` : "" 16 | }network=${getNetworkName()}`; 17 | const URLType = type === "module" ? "object" : type; 18 | const href = `https://suiexplorer.com/${URLType}/${objectId}?${URLParams}`; 19 | return href; 20 | }; 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # IDE directories and files 2 | .idea 3 | .vscode 4 | *.suo 5 | *.ntvs* 6 | *.njsproj 7 | *.sln 8 | *.sw? 9 | 10 | # OS generated files 11 | .DS_Store* 12 | 13 | # Logs 14 | logs 15 | *.log 16 | npm-debug.log* 17 | yarn-debug.log* 18 | yarn-error.log* 19 | lerna-debug.log* 20 | pnpm-debug.log* 21 | sui.log* 22 | 23 | # Dependency and build-related directories 24 | node_modules/ 25 | dist/ 26 | build/ 27 | 28 | # Move 29 | move/blackjack/Move.lock 30 | move/blackjack/build 31 | 32 | # env files under the setup directory 33 | setup/*.env 34 | .publish.res.json 35 | .vercel 36 | 37 | # Codegen related stuff 38 | 39 | move/**/package_summaries/ 40 | -------------------------------------------------------------------------------- /setup/src/helpers/getObject/getGameObject.ts: -------------------------------------------------------------------------------- 1 | import { SuiClient, SuiMoveObject } from "@mysten/sui/client"; 2 | import { GameOnChain } from "../../types/GameOnChain"; 3 | 4 | interface GetGameObjectProps { 5 | suiClient: SuiClient; 6 | gameId: string; 7 | } 8 | export const getGameObject = async ({ 9 | suiClient, 10 | gameId, 11 | }: GetGameObjectProps): Promise => { 12 | const res = await suiClient.getObject({ 13 | id: gameId, 14 | options: { showContent: true }, 15 | }); 16 | const gameObject = res?.data?.content as SuiMoveObject; 17 | const { fields } = gameObject; 18 | return fields as unknown as GameOnChain; 19 | }; 20 | -------------------------------------------------------------------------------- /app/public/general/dealer.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/app/api/execute/route.ts: -------------------------------------------------------------------------------- 1 | import { NextRequest, NextResponse } from "next/server"; 2 | import { enokiClient } from "../EnokiClient"; 3 | 4 | export async function POST(req: NextRequest) { 5 | try { 6 | const { digest, signature } = await req.json(); 7 | 8 | const executionResult = await enokiClient.executeSponsoredTransaction({ 9 | digest, 10 | signature, 11 | }); 12 | 13 | return NextResponse.json({ 14 | digest: executionResult.digest, 15 | }); 16 | } catch (error) { 17 | console.error("Execution failed:", error); 18 | return NextResponse.json({ error: "Execution failed" }, { status: 500 }); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/.env.development.local.example: -------------------------------------------------------------------------------- 1 | # Created by Vercel CLI 2 | KV_REST_API_READ_ONLY_TOKEN="your credentials here" 3 | KV_REST_API_TOKEN="your credentials here" 4 | KV_REST_API_URL="your credentials here" 5 | KV_URL="your credentials here" 6 | NX_DAEMON="" 7 | TURBO_REMOTE_ONLY="" 8 | TURBO_RUN_SUMMARY="" 9 | VERCEL="1" 10 | VERCEL_ENV="development" 11 | VERCEL_GIT_COMMIT_AUTHOR_LOGIN="" 12 | VERCEL_GIT_COMMIT_AUTHOR_NAME="" 13 | VERCEL_GIT_COMMIT_MESSAGE="" 14 | VERCEL_GIT_COMMIT_REF="" 15 | VERCEL_GIT_COMMIT_SHA="" 16 | VERCEL_GIT_PREVIOUS_SHA="" 17 | VERCEL_GIT_PROVIDER="" 18 | VERCEL_GIT_PULL_REQUEST_ID="" 19 | VERCEL_GIT_REPO_ID="" 20 | VERCEL_GIT_REPO_OWNER="" 21 | VERCEL_GIT_REPO_SLUG="" 22 | VERCEL_URL="" 23 | -------------------------------------------------------------------------------- /app/src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import "../styles/globals.css"; 2 | import { Inter } from "next/font/google"; 3 | import { ChildrenProps } from "@/types/ChildrenProps"; 4 | import { ProvidersAndLayout } from "./ProvidersAndLayout"; 5 | 6 | const inter = Inter({ subsets: ["latin"] }); 7 | 8 | export default function RootLayout({ children }: ChildrenProps) { 9 | return ( 10 | 11 | 12 | 13 | 14 | 15 | 16 | {children} 17 | 18 | 19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /setup/src/helpers/cards/generateCards.ts: -------------------------------------------------------------------------------- 1 | import { Card } from "../../types/Card"; 2 | 3 | const suits = ["Clubs", "Diamonds", "Hearts", "Spades"]; 4 | const values = [ 5 | "A", 6 | "2", 7 | "3", 8 | "4", 9 | "5", 10 | "6", 11 | "7", 12 | "8", 13 | "9", 14 | "10", 15 | "J", 16 | "Q", 17 | "K", 18 | ]; 19 | 20 | export const generateCards = () => { 21 | let cards: { [key: number]: Card } = {}; 22 | let i: number = 0; 23 | for (const suit of suits) { 24 | for (const value of values) { 25 | const card = { 26 | index: i, 27 | suit: suit, 28 | value: value, 29 | }; 30 | cards[i] = card; 31 | i++; 32 | } 33 | } 34 | return cards; 35 | }; 36 | -------------------------------------------------------------------------------- /setup/src/scripts/02-createGameByPlayer.ts: -------------------------------------------------------------------------------- 1 | import { SuiClient } from "@mysten/sui/client"; 2 | import { createGameByPlayer } from "../helpers/actions/createGameByPlayer"; 3 | import fs from "fs"; 4 | import { BJ_PLAYER_SECRET_KEY, HOUSE_DATA_ID, SUI_NETWORK } from "../config"; 5 | 6 | if (!BJ_PLAYER_SECRET_KEY) { 7 | throw new Error("BJ_PLAYER_SECRET_KEY is not set in your .env file"); 8 | } 9 | 10 | const createGame = async () => { 11 | const gameId = await createGameByPlayer({ 12 | suiClient: new SuiClient({ url: SUI_NETWORK }), 13 | playerSecretKey: BJ_PLAYER_SECRET_KEY, 14 | houseDataId: HOUSE_DATA_ID, 15 | }); 16 | fs.appendFileSync("./.env", `GAME_ID=${gameId}\n`); 17 | }; 18 | 19 | createGame(); 20 | -------------------------------------------------------------------------------- /app/src/__generated__/blackjack/deps/sui/balance.ts: -------------------------------------------------------------------------------- 1 | /************************************************************** 2 | * THIS FILE IS GENERATED AND SHOULD NOT BE MANUALLY MODIFIED * 3 | **************************************************************/ 4 | 5 | 6 | /** 7 | * A storable handler for Balances in general. Is used in the `Coin` module to 8 | * allow balance operations and can be used to implement custom coins with `Supply` 9 | * and `Balance`s. 10 | */ 11 | 12 | import { MoveStruct } from '../../../utils/index.js'; 13 | import { bcs } from '@mysten/sui/bcs'; 14 | const $moduleName = '0x2::balance'; 15 | export const Balance = new MoveStruct({ name: `${$moduleName}::Balance`, fields: { 16 | value: bcs.u64() 17 | } }); -------------------------------------------------------------------------------- /app/public/general/arrow-top-right.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/src/components/general/LoadingButton.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Button, ButtonProps } from "../ui/button"; 3 | import { Spinner } from "./Spinner"; 4 | 5 | interface LoadingButtonProps extends ButtonProps { 6 | isLoading: boolean; 7 | showSpinner?: boolean; 8 | spinnerClassName?: string; 9 | } 10 | 11 | export const LoadingButton = ({ 12 | isLoading, 13 | showSpinner = true, 14 | children, 15 | spinnerClassName, 16 | ...props 17 | }: LoadingButtonProps) => { 18 | return ( 19 | 27 | ); 28 | }; 29 | -------------------------------------------------------------------------------- /app/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "skipDefaultLibCheck": true, 8 | "strict": true, 9 | "forceConsistentCasingInFileNames": true, 10 | "noEmit": true, 11 | "esModuleInterop": true, 12 | "module": "esnext", 13 | "moduleResolution": "node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "jsx": "preserve", 17 | "incremental": true, 18 | "plugins": [ 19 | { 20 | "name": "next" 21 | } 22 | ], 23 | "paths": { 24 | "@/*": ["./src/*"] 25 | } 26 | }, 27 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 28 | "exclude": ["node_modules"] 29 | } 30 | -------------------------------------------------------------------------------- /app/src/components/home/PlayerCards.tsx: -------------------------------------------------------------------------------- 1 | import Image from "next/image"; 2 | import React from "react"; 3 | import { CardsHand } from "./CardsHand"; 4 | 5 | interface PlayerCardsProps { 6 | cards: number[]; 7 | points: number; 8 | won?: boolean; 9 | lost?: boolean; 10 | showIcon?: boolean; 11 | } 12 | 13 | export const PlayerCards = ({ 14 | cards, 15 | points, 16 | won, 17 | lost, 18 | showIcon = true, 19 | }: PlayerCardsProps) => { 20 | return ( 21 |
26 | 27 | {showIcon && ( 28 | player 29 | )} 30 |
31 | ); 32 | }; 33 | -------------------------------------------------------------------------------- /app/src/contexts/EnokiWalletProvider.tsx: -------------------------------------------------------------------------------- 1 | import {useSuiClientContext} from "@mysten/dapp-kit"; 2 | import {PropsWithChildren, useEffect} from "react"; 3 | import {isEnokiNetwork, registerEnokiWallets} from "@mysten/enoki"; 4 | 5 | export default function EnokiWalletProvider({ children }: PropsWithChildren) { 6 | 7 | const { client, network } = useSuiClientContext(); 8 | useEffect(() => { 9 | if (!isEnokiNetwork(network)) return; 10 | 11 | const { unregister } = registerEnokiWallets({ 12 | apiKey: process.env.NEXT_PUBLIC_ENOKI_API_KEY!, 13 | providers: { 14 | google: { 15 | clientId: process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID!, 16 | // TODO Redirect URI? 17 | }, 18 | }, 19 | client: client as never, 20 | network, 21 | }); 22 | 23 | return unregister; 24 | }, [client, network]); 25 | 26 | return children; 27 | } -------------------------------------------------------------------------------- /app/src/components/ui/label.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as LabelPrimitive from "@radix-ui/react-label" 5 | import { cva, type VariantProps } from "class-variance-authority" 6 | 7 | import { cn } from "@/lib/utils" 8 | 9 | const labelVariants = cva( 10 | "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" 11 | ) 12 | 13 | const Label = React.forwardRef< 14 | React.ElementRef, 15 | React.ComponentPropsWithoutRef & 16 | VariantProps 17 | >(({ className, ...props }, ref) => ( 18 | 23 | )) 24 | Label.displayName = LabelPrimitive.Root.displayName 25 | 26 | export { Label } 27 | -------------------------------------------------------------------------------- /app/eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "eslint/config"; 2 | import unusedImports from "eslint-plugin-unused-imports"; 3 | import path from "node:path"; 4 | import { fileURLToPath } from "node:url"; 5 | import js from "@eslint/js"; 6 | import { FlatCompat } from "@eslint/eslintrc"; 7 | 8 | const __filename = fileURLToPath(import.meta.url); 9 | const __dirname = path.dirname(__filename); 10 | 11 | const compat = new FlatCompat({ 12 | baseDirectory: __dirname, 13 | recommendedConfig: js.configs.recommended, 14 | allConfig: js.configs.all 15 | }); 16 | 17 | export default defineConfig([{ 18 | extends: compat.extends("next/core-web-vitals"), 19 | 20 | plugins: { 21 | "unused-imports": unusedImports, 22 | }, 23 | 24 | rules: { 25 | "unused-imports/no-unused-imports": "error", 26 | }, 27 | 28 | ignores: [".next/**/*"], 29 | }]); -------------------------------------------------------------------------------- /setup/src/helpers/getObject/getIsHouseAdminCapBurnt.ts: -------------------------------------------------------------------------------- 1 | import { SuiClient } from "@mysten/sui/client"; 2 | import { getKeypair } from "../keypair/getKeyPair"; 3 | import { ADMIN_SECRET_KEY, PACKAGE_ADDRESS } from "../../config"; 4 | 5 | interface GetIsHouseAdminCapBurntProps { 6 | suiClient: SuiClient; 7 | } 8 | 9 | export const getIsHouseAdminCapBurnt = async ({ 10 | suiClient, 11 | }: GetIsHouseAdminCapBurntProps): Promise => { 12 | const adminKeypair = getKeypair(ADMIN_SECRET_KEY!); 13 | const adminAddress = adminKeypair.getPublicKey().toSuiAddress(); 14 | 15 | return suiClient 16 | .getOwnedObjects({ 17 | owner: adminAddress, 18 | filter: { 19 | StructType: `${PACKAGE_ADDRESS}::single_player_blackjack::HouseAdminCap`, 20 | }, 21 | }) 22 | .then(async (resp) => { 23 | return resp.data.length === 0; 24 | }); 25 | }; 26 | -------------------------------------------------------------------------------- /app/src/components/ui/textarea.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "@/lib/utils" 4 | 5 | export interface TextareaProps 6 | extends React.TextareaHTMLAttributes {} 7 | 8 | const Textarea = React.forwardRef( 9 | ({ className, ...props }, ref) => { 10 | return ( 11 |