├── 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 |