diff --git a/roll-dice/Cargo.lock b/roll-dice/Cargo.lock
index 5db6f67..8fb2c90 100644
--- a/roll-dice/Cargo.lock
+++ b/roll-dice/Cargo.lock
@@ -263,6 +263,12 @@ version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
+[[package]]
+name = "base64ct"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
+
[[package]]
name = "bincode"
version = "1.3.3"
@@ -561,10 +567,29 @@ checksum = "1567ae90eac4338ad3d87228bd357b540142af5edbc333c73f06c74cd2bf336f"
dependencies = [
"anchor-lang",
"borsh 0.10.4",
- "ephemeral-rollups-sdk-attribute-commit",
- "ephemeral-rollups-sdk-attribute-delegate",
- "ephemeral-rollups-sdk-attribute-ephemeral",
+ "ephemeral-rollups-sdk-attribute-commit 0.2.5",
+ "ephemeral-rollups-sdk-attribute-delegate 0.2.5",
+ "ephemeral-rollups-sdk-attribute-ephemeral 0.2.5",
+ "solana-program",
+]
+
+[[package]]
+name = "ephemeral-rollups-sdk"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf068e9dc9c71ba2c0e1c3e1cc14ed633428274589f20483ea6680353cf81c00"
+dependencies = [
+ "anchor-lang",
+ "base64ct",
+ "borsh 1.5.7",
+ "ephemeral-rollups-sdk-attribute-commit 0.4.1",
+ "ephemeral-rollups-sdk-attribute-delegate 0.4.1",
+ "ephemeral-rollups-sdk-attribute-ephemeral 0.4.1",
+ "getrandom 0.2.16",
+ "magicblock-delegation-program",
+ "magicblock-magic-program-api",
"solana-program",
+ "solana-system-interface",
]
[[package]]
@@ -577,6 +602,16 @@ dependencies = [
"syn 1.0.109",
]
+[[package]]
+name = "ephemeral-rollups-sdk-attribute-commit"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a5d2622e5f61947543f6f1668434529dc29ac54e9d8512c7bf4f38d67c139485"
+dependencies = [
+ "quote",
+ "syn 1.0.109",
+]
+
[[package]]
name = "ephemeral-rollups-sdk-attribute-delegate"
version = "0.2.5"
@@ -588,6 +623,17 @@ dependencies = [
"syn 1.0.109",
]
+[[package]]
+name = "ephemeral-rollups-sdk-attribute-delegate"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "540da5e9c176532fa92f60fd74915d09cc694bf2849be0cc079ec30499f94f49"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
[[package]]
name = "ephemeral-rollups-sdk-attribute-ephemeral"
version = "0.2.5"
@@ -599,6 +645,17 @@ dependencies = [
"syn 1.0.109",
]
+[[package]]
+name = "ephemeral-rollups-sdk-attribute-ephemeral"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a8d85afb0d8559775441afcafe3b121a629f80ef702678e7daac663e25500ed"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
[[package]]
name = "ephemeral-vrf-sdk"
version = "0.2.1"
@@ -822,6 +879,30 @@ version = "0.4.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
+[[package]]
+name = "magicblock-delegation-program"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d0e25f9e37194cc27c0f1d3dbc00e83795f7f4012f1a99c20bc557bdeb62e13e"
+dependencies = [
+ "borsh 1.5.7",
+ "bytemuck",
+ "num_enum",
+ "solana-program",
+ "static_assertions",
+]
+
+[[package]]
+name = "magicblock-magic-program-api"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1076e02cb9260b2400e8b7b21d6c5839e6054a0f5b9d75378811c66fa04d8d40"
+dependencies = [
+ "bincode",
+ "serde",
+ "solana-program",
+]
+
[[package]]
name = "memchr"
version = "2.7.4"
@@ -876,6 +957,28 @@ dependencies = [
"autocfg",
]
+[[package]]
+name = "num_enum"
+version = "0.7.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1207a7e20ad57b847bbddc6776b968420d38292bbfe2089accff5e19e82454c"
+dependencies = [
+ "num_enum_derive",
+ "rustversion",
+]
+
+[[package]]
+name = "num_enum_derive"
+version = "0.7.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7"
+dependencies = [
+ "proc-macro-crate 3.3.0",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.101",
+]
+
[[package]]
name = "once_cell"
version = "1.21.3"
@@ -1070,7 +1173,7 @@ name = "roll-dice"
version = "0.1.0"
dependencies = [
"anchor-lang",
- "ephemeral-rollups-sdk",
+ "ephemeral-rollups-sdk 0.2.5",
"ephemeral-vrf-sdk",
]
@@ -1079,7 +1182,7 @@ name = "roll-dice-delegated"
version = "0.1.0"
dependencies = [
"anchor-lang",
- "ephemeral-rollups-sdk",
+ "ephemeral-rollups-sdk 0.4.1",
"ephemeral-vrf-sdk",
]
@@ -2019,6 +2122,12 @@ dependencies = [
"solana-system-interface",
]
+[[package]]
+name = "static_assertions"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
+
[[package]]
name = "subtle"
version = "2.6.1"
diff --git a/roll-dice/README.md b/roll-dice/README.md
index c19e279..d9bf99c 100644
--- a/roll-dice/README.md
+++ b/roll-dice/README.md
@@ -10,7 +10,7 @@ Simple dice rolling application using Ephemeral Rollups to demonstrate using a v
## Software Packages
-This program has utilized the following sofware packages.
+This program has utilized the following software packages.
| Software | Version | Installation Guide |
| ---------- | ------- | --------------------------------------------------------------- |
diff --git a/roll-dice/app/app/delegated/page.tsx b/roll-dice/app/app/delegated/page.tsx
new file mode 100644
index 0000000..169d7bf
--- /dev/null
+++ b/roll-dice/app/app/delegated/page.tsx
@@ -0,0 +1,1043 @@
+"use client"
+
+import { useCallback, useEffect, useRef, useState } from "react"
+import * as anchor from "@coral-xyz/anchor"
+import {
+ Connection,
+ Keypair,
+ PublicKey,
+} from "@solana/web3.js"
+import { ConnectionMagicRouter } from "@magicblock-labs/ephemeral-rollups-sdk"
+import Dice from "@/components/dice"
+import SolanaAddress from "@/components/solana-address"
+import {
+ Table,
+ TableBody,
+ TableCell,
+ TableHead,
+ TableHeader,
+ TableRow,
+} from "@/components/ui/table"
+import { Badge } from "@/components/ui/badge"
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
+import {
+ Accordion,
+ AccordionContent,
+ AccordionItem,
+ AccordionTrigger,
+} from "@/components/ui/accordion"
+import { Copy, Check } from "lucide-react"
+import {
+ PROGRAM_ID,
+ PLAYER_SEED,
+ ORACLE_QUEUE,
+ BASE_ENDPOINT,
+ PLAYER_STORAGE_KEY,
+ BLOCKHASH_REFRESH_INTERVAL_MS,
+ ROLL_TIMEOUT_MS,
+ ROLL_ANIMATION_INTERVAL_MS,
+} from "@/lib/config"
+import {
+ walletAdapterFrom,
+ loadOrCreateKeypair,
+ ensureFunds,
+ fetchAndCacheBlockhash,
+ getCachedBlockhash,
+ checkDelegationStatus,
+} from "@/lib/solana-utils"
+import type { RollEntry, CachedBlockhash } from "@/lib/types"
+
+const derivePlayerPda = (user: PublicKey) =>
+ PublicKey.findProgramAddressSync([Buffer.from(PLAYER_SEED), user.toBuffer()], PROGRAM_ID)[0]
+
+const MiniDice = ({ value }: { value: number | null }) => {
+ if (value === null) return -
+
+ const safeValue = Math.min(Math.max(1, value), 6)
+ const dotSize = "w-1.5 h-1.5"
+
+ return (
+
+
+ {safeValue === 1 && (
+
+ )}
+ {safeValue === 2 && (
+
+ )}
+ {safeValue === 3 && (
+
+ )}
+ {safeValue === 4 && (
+
+ )}
+ {safeValue === 5 && (
+
+ )}
+ {safeValue === 6 && (
+
+ )}
+
+
+ )
+}
+
+export default function DiceRollerDelegated() {
+ const [diceValue, setDiceValue] = useState(1)
+ const [isRolling, setIsRolling] = useState(false)
+ const [isInitialized, setIsInitialized] = useState(false)
+ const [isDelegated, setIsDelegated] = useState(false)
+ const [isDelegating, setIsDelegating] = useState(false)
+ const [isUndelegating, setIsUndelegating] = useState(false)
+ const [rollHistory, setRollHistory] = useState([])
+ const [timerTick, setTimerTick] = useState(0)
+ const [playerAccountData, setPlayerAccountData] = useState<{ lastResult: number; rollnum: number } | null>(null)
+ const [playerPda, setPlayerPda] = useState(null)
+ const [copied, setCopied] = useState(false)
+ const [ephemeralEndpoint, setEphemeralEndpoint] = useState(null)
+
+ const previousDiceValueRef = useRef(1)
+ const programRef = useRef(null)
+ const ephemeralProgramRef = useRef(null)
+ const connectionRef = useRef(null)
+ const ephemeralConnectionRef = useRef(null)
+ const routerConnectionRef = useRef(null)
+ const playerPdaRef = useRef(null)
+ const subscriptionIdRef = useRef(null)
+ const rollIntervalRef = useRef(null)
+ const timerIntervalRef = useRef(null)
+ const timeoutRef = useRef(null)
+ const blockhashIntervalRef = useRef(null)
+ const delegationPollIntervalRef = useRef(null)
+ const playerKeypairRef = useRef(null)
+ const cachedBaseBlockhashRef = useRef(null)
+ const cachedEphemeralBlockhashRef = useRef(null)
+
+ const clearAllIntervals = useCallback(() => {
+ if (rollIntervalRef.current) {
+ clearInterval(rollIntervalRef.current)
+ rollIntervalRef.current = null
+ }
+ if (timerIntervalRef.current) {
+ clearInterval(timerIntervalRef.current)
+ timerIntervalRef.current = null
+ }
+ if (timeoutRef.current) {
+ clearTimeout(timeoutRef.current)
+ timeoutRef.current = null
+ }
+ // Note: blockhashIntervalRef is NOT cleared here - it should run continuously
+ if (delegationPollIntervalRef.current) {
+ clearInterval(delegationPollIntervalRef.current)
+ delegationPollIntervalRef.current = null
+ }
+ }, [])
+
+
+ const getBlockhashAsync = useCallback(async (connection: Connection, isEphemeral: boolean): Promise => {
+ const cacheRef = isEphemeral ? cachedEphemeralBlockhashRef : cachedBaseBlockhashRef
+ const cached = getCachedBlockhash(connection, cacheRef)
+ if (cached) return cached
+ const { blockhash } = await connection.getLatestBlockhash()
+ return blockhash
+ }, [])
+
+ const refreshDelegationStatus = useCallback(async () => {
+ if (!connectionRef.current || !playerPdaRef.current) return false
+ const delegated = await checkDelegationStatus(connectionRef.current, playerPdaRef.current)
+ setIsDelegated(delegated)
+ return delegated
+ }, [])
+
+ const updateEphemeralConnectionToValidator = useCallback(async (validatorFqdn: string) => {
+ if (!playerKeypairRef.current || !playerPdaRef.current || !programRef.current) return
+
+ // Convert https:// to wss:// for WebSocket endpoint
+ const ephemeralWsEndpoint = validatorFqdn.replace(/^https:\/\//, "wss://").replace(/^http:\/\//, "ws://")
+ const newEphemeralConnection = new Connection(validatorFqdn, {
+ wsEndpoint: ephemeralWsEndpoint,
+ commitment: "processed",
+ })
+
+ // Clean up old subscription
+ if (subscriptionIdRef.current !== null && ephemeralConnectionRef.current) {
+ await ephemeralConnectionRef.current.removeAccountChangeListener(subscriptionIdRef.current).catch(console.error)
+ }
+
+ ephemeralConnectionRef.current = newEphemeralConnection
+ setEphemeralEndpoint(validatorFqdn)
+
+ // Recreate ephemeral program with new connection
+ const idl = await anchor.Program.fetchIdl(PROGRAM_ID, programRef.current.provider)
+ if (!idl) throw new Error("IDL not found")
+
+ const ephemeralProvider = new anchor.AnchorProvider(
+ newEphemeralConnection,
+ walletAdapterFrom(playerKeypairRef.current),
+ anchor.AnchorProvider.defaultOptions()
+ )
+ ephemeralProgramRef.current = new anchor.Program(idl, ephemeralProvider)
+
+ // Recreate subscription with new connection
+ subscriptionIdRef.current = newEphemeralConnection.onAccountChange(
+ playerPdaRef.current,
+ (accountInfo) => {
+ if (!ephemeralProgramRef.current || !accountInfo?.data) return
+ try {
+ const player = ephemeralProgramRef.current.coder.accounts.decode("player", accountInfo.data)
+ const newValue = Number(player.lastResult)
+ setPlayerAccountData({ lastResult: newValue, rollnum: Number(player.rollnum) })
+ if (newValue > 0) {
+ setDiceValue(newValue)
+ previousDiceValueRef.current = newValue
+ }
+ setRollHistory(prev => {
+ const idx = prev.findIndex(entry => entry.isPending)
+ if (idx === -1) return prev
+ const updated = [...prev]
+ updated[idx] = { ...updated[idx], value: newValue, endTime: Date.now(), isPending: false }
+ setIsRolling(false)
+ clearAllIntervals()
+ return updated
+ })
+ } catch (error) {
+ console.error("[WebSocket] Failed to decode player account:", error)
+ }
+ },
+ { commitment: "processed" }
+ )
+
+ // Fetch blockhash for new connection
+ await fetchAndCacheBlockhash(newEphemeralConnection, cachedEphemeralBlockhashRef)
+ }, [clearAllIntervals])
+
+ const sendBackgroundRoll = useCallback(async () => {
+ if (!ephemeralProgramRef.current || !playerKeypairRef.current || !playerPdaRef.current || !ephemeralConnectionRef.current) return
+
+ try {
+ const randomValue = Math.floor(Math.random() * 6) + 1
+ const [tx, blockhash] = await Promise.all([
+ ephemeralProgramRef.current.methods.rollDiceDelegated(randomValue).accounts({
+ payer: playerKeypairRef.current.publicKey,
+ player: playerPdaRef.current,
+ oracleQueue: ORACLE_QUEUE,
+ }).transaction(),
+ getBlockhashAsync(ephemeralConnectionRef.current, true)
+ ])
+
+ tx.recentBlockhash = blockhash
+ tx.feePayer = playerKeypairRef.current.publicKey
+ tx.sign(playerKeypairRef.current)
+
+ ephemeralConnectionRef.current.sendRawTransaction(tx.serialize(), { skipPreflight: true })
+ fetchAndCacheBlockhash(ephemeralConnectionRef.current, cachedEphemeralBlockhashRef).catch(console.error)
+ } catch (error) {
+ console.error("[BackgroundRoll] Error:", error)
+ }
+ }, [getBlockhashAsync])
+
+ const initializeProgram = useCallback(async () => {
+ if (typeof window === "undefined") return
+ try {
+ const connection = new Connection(BASE_ENDPOINT, "confirmed")
+ connectionRef.current = connection
+
+ if (!playerKeypairRef.current) {
+ playerKeypairRef.current = loadOrCreateKeypair(PLAYER_STORAGE_KEY)
+ }
+
+ await ensureFunds(connection, playerKeypairRef.current)
+
+ const provider = new anchor.AnchorProvider(
+ connection,
+ walletAdapterFrom(playerKeypairRef.current),
+ anchor.AnchorProvider.defaultOptions()
+ )
+
+ const idl = await anchor.Program.fetchIdl(PROGRAM_ID, provider)
+ if (!idl) throw new Error("IDL not found")
+
+ const program = new anchor.Program(idl, provider)
+ programRef.current = program
+
+ const playerPk = derivePlayerPda(playerKeypairRef.current.publicKey)
+ playerPdaRef.current = playerPk
+ setPlayerPda(playerPk)
+
+ let account = await connection.getAccountInfo(playerPk)
+ if (!account) {
+ await program.methods.initialize().rpc()
+ account = await connection.getAccountInfo(playerPk)
+ }
+ if (account) {
+ try {
+ const player = program.coder.accounts.decode("player", account.data)
+ const initialValue = player.lastResult || 1
+ setDiceValue(initialValue)
+ previousDiceValueRef.current = initialValue
+ setPlayerAccountData({
+ lastResult: Number(player.lastResult),
+ rollnum: Number(player.rollnum),
+ })
+ } catch (error) {
+ console.error("Failed to decode player on init:", error)
+ }
+ }
+
+ const routerEndpoint = process.env.NEXT_PUBLIC_ROUTER_ENDPOINT || "https://devnet-router.magicblock.app"
+ const routerWsEndpoint = process.env.NEXT_PUBLIC_ROUTER_WS_ENDPOINT || "wss://devnet-router.magicblock.app"
+ const routerConnection = new ConnectionMagicRouter(routerEndpoint, {
+ wsEndpoint: routerWsEndpoint,
+ })
+ routerConnectionRef.current = routerConnection
+
+ const ephemeralEndpoint = process.env.NEXT_PUBLIC_EPHEMERAL_PROVIDER_ENDPOINT || "https://devnet.magicblock.app"
+ setEphemeralEndpoint(ephemeralEndpoint)
+ const ephemeralWsEndpoint = process.env.NEXT_PUBLIC_EPHEMERAL_WS_ENDPOINT || "wss://devnet.magicblock.app"
+ const ephemeralConnection = new Connection(ephemeralEndpoint, {
+ wsEndpoint: ephemeralWsEndpoint,
+ commitment: "processed",
+ })
+ ephemeralConnectionRef.current = ephemeralConnection
+ const ephemeralProvider = new anchor.AnchorProvider(
+ ephemeralConnection,
+ walletAdapterFrom(playerKeypairRef.current),
+ anchor.AnchorProvider.defaultOptions()
+ )
+ ephemeralProgramRef.current = new anchor.Program(idl, ephemeralProvider)
+
+ if (subscriptionIdRef.current !== null && ephemeralConnection) {
+ await ephemeralConnection.removeAccountChangeListener(subscriptionIdRef.current).catch(console.error)
+ }
+ if (ephemeralConnection && playerPk) {
+ subscriptionIdRef.current = ephemeralConnection.onAccountChange(
+ playerPk,
+ (accountInfo) => {
+ if (!ephemeralProgramRef.current || !accountInfo?.data) return
+ try {
+ const player = ephemeralProgramRef.current.coder.accounts.decode("player", accountInfo.data)
+ const newValue = Number(player.lastResult)
+ setPlayerAccountData({ lastResult: newValue, rollnum: Number(player.rollnum) })
+ if (newValue > 0) {
+ setDiceValue(newValue)
+ previousDiceValueRef.current = newValue
+ }
+ setRollHistory(prev => {
+ const idx = prev.findIndex(entry => entry.isPending)
+ if (idx === -1) return prev
+ const updated = [...prev]
+ updated[idx] = { ...updated[idx], value: newValue, endTime: Date.now(), isPending: false }
+ setIsRolling(false)
+ clearAllIntervals()
+ return updated
+ })
+ } catch (error) {
+ console.error("[WebSocket] Failed to decode player account:", error)
+ }
+ },
+ { commitment: "processed" }
+ )
+ }
+
+ const isDelegated = await refreshDelegationStatus()
+
+ // If already delegated, update ephemeral connection to nearest validator
+ if (isDelegated && routerConnectionRef.current) {
+ try {
+ const validatorResult = await routerConnectionRef.current.getClosestValidator()
+ console.log("getClosestValidator result on init:", validatorResult)
+
+ if (validatorResult.fqdn) {
+ await updateEphemeralConnectionToValidator(validatorResult.fqdn)
+ }
+ } catch (error) {
+ console.error("Failed to update ephemeral connection to nearest validator:", error)
+ // Continue with default ephemeral connection if update fails
+ await fetchAndCacheBlockhash(ephemeralConnection, cachedEphemeralBlockhashRef)
+ }
+ } else if (!isDelegated && routerConnectionRef.current) {
+ // Automatically delegate on startup if not already delegated
+ setIsDelegating(true)
+ try {
+ const validatorResult = await routerConnectionRef.current.getClosestValidator()
+ console.log("getClosestValidator result on init:", validatorResult)
+
+ const validatorIdentity = validatorResult.identity
+ const validatorFqdn = validatorResult.fqdn
+
+ if (!validatorIdentity || !validatorFqdn) {
+ throw new Error("Validator identity or fqdn not found in getClosestValidator response")
+ }
+
+ await updateEphemeralConnectionToValidator(validatorFqdn)
+
+ await ensureFunds(connection, playerKeypairRef.current)
+ const validatorPubkey = new PublicKey(validatorIdentity)
+ await program.methods
+ .delegate({
+ commitFrequencyMs: 30000,
+ validator: validatorPubkey,
+ })
+ .rpc()
+
+ // Poll every second until delegation succeeds
+ if (delegationPollIntervalRef.current) {
+ clearInterval(delegationPollIntervalRef.current)
+ }
+
+ delegationPollIntervalRef.current = setInterval(async () => {
+ const delegated = await refreshDelegationStatus()
+ if (delegated) {
+ if (delegationPollIntervalRef.current) {
+ clearInterval(delegationPollIntervalRef.current)
+ delegationPollIntervalRef.current = null
+ }
+ setIsDelegating(false)
+ // Send background roll immediately after successful delegation
+ sendBackgroundRoll().catch(console.error)
+ }
+ }, 1000)
+ } catch (error) {
+ console.error("Automatic delegation failed on startup:", error)
+ if (delegationPollIntervalRef.current) {
+ clearInterval(delegationPollIntervalRef.current)
+ delegationPollIntervalRef.current = null
+ }
+ setIsDelegating(false)
+ await fetchAndCacheBlockhash(connection, cachedBaseBlockhashRef)
+ if (ephemeralConnection) {
+ await fetchAndCacheBlockhash(ephemeralConnection, cachedEphemeralBlockhashRef)
+ }
+ }
+ } else {
+ await fetchAndCacheBlockhash(connection, cachedBaseBlockhashRef)
+ if (ephemeralConnection) {
+ await fetchAndCacheBlockhash(ephemeralConnection, cachedEphemeralBlockhashRef)
+ }
+ }
+
+ // Clear any existing interval before creating a new one
+ if (blockhashIntervalRef.current) {
+ clearInterval(blockhashIntervalRef.current)
+ }
+
+ // Start continuous blockhash refresh - this runs every 20 seconds regardless of other activity
+ blockhashIntervalRef.current = setInterval(() => {
+ if (connectionRef.current) {
+ fetchAndCacheBlockhash(connectionRef.current, cachedBaseBlockhashRef).catch(console.error)
+ }
+ if (ephemeralConnectionRef.current) {
+ fetchAndCacheBlockhash(ephemeralConnectionRef.current, cachedEphemeralBlockhashRef).catch(console.error)
+ }
+ }, BLOCKHASH_REFRESH_INTERVAL_MS)
+
+ setIsInitialized(true)
+ } catch (error) {
+ console.error("Failed to initialize delegated dice:", error)
+ setIsInitialized(false)
+ }
+ }, [refreshDelegationStatus, updateEphemeralConnectionToValidator, sendBackgroundRoll])
+
+ useEffect(() => {
+ initializeProgram()
+
+ return () => {
+ clearAllIntervals()
+ // Clean up blockhash refresh interval on unmount
+ if (blockhashIntervalRef.current) {
+ clearInterval(blockhashIntervalRef.current)
+ blockhashIntervalRef.current = null
+ }
+ // Clean up subscription
+ if (subscriptionIdRef.current !== null && ephemeralConnectionRef.current) {
+ ephemeralConnectionRef.current.removeAccountChangeListener(subscriptionIdRef.current).catch(console.error)
+ subscriptionIdRef.current = null
+ }
+ }
+ }, [clearAllIntervals, initializeProgram])
+
+ const handleDelegateToValidator = useCallback(async (validatorIdentity: string, validatorFqdn: string) => {
+ if (
+ !programRef.current ||
+ !connectionRef.current ||
+ !playerKeypairRef.current ||
+ !playerPdaRef.current
+ )
+ return
+ if (isDelegated) return
+
+ setIsDelegating(true)
+ try {
+ const connection = connectionRef.current
+ const playerKeypair = playerKeypairRef.current
+
+ const validatorPubkey = new PublicKey(validatorIdentity)
+
+ // Update ephemeral connection to use the fqdn
+ await updateEphemeralConnectionToValidator(validatorFqdn)
+
+ await ensureFunds(connection, playerKeypair)
+ await programRef.current.methods
+ .delegate({
+ commitFrequencyMs: 30000,
+ validator: validatorPubkey,
+ })
+ .rpc()
+
+ // Poll every second until delegation succeeds
+ if (delegationPollIntervalRef.current) {
+ clearInterval(delegationPollIntervalRef.current)
+ }
+
+ delegationPollIntervalRef.current = setInterval(async () => {
+ const delegated = await refreshDelegationStatus()
+ if (delegated) {
+ if (delegationPollIntervalRef.current) {
+ clearInterval(delegationPollIntervalRef.current)
+ delegationPollIntervalRef.current = null
+ }
+ setIsDelegating(false)
+ // Send background roll immediately after successful delegation
+ sendBackgroundRoll().catch(console.error)
+ }
+ }, 1000)
+ } catch (error) {
+ console.error("Delegation failed:", error)
+ if (delegationPollIntervalRef.current) {
+ clearInterval(delegationPollIntervalRef.current)
+ delegationPollIntervalRef.current = null
+ }
+ setIsDelegating(false)
+ }
+ }, [isDelegated, refreshDelegationStatus, clearAllIntervals, updateEphemeralConnectionToValidator, sendBackgroundRoll])
+
+ const handleDelegate = useCallback(async () => {
+ if (
+ !programRef.current ||
+ !connectionRef.current ||
+ !playerKeypairRef.current ||
+ !playerPdaRef.current ||
+ !routerConnectionRef.current
+ )
+ return
+ if (isDelegated) return
+
+ setIsDelegating(true)
+ try {
+ const connection = connectionRef.current
+ const playerKeypair = playerKeypairRef.current
+
+ // Get closest validator
+ const validatorResult = await routerConnectionRef.current.getClosestValidator()
+ console.log("getClosestValidator result:", validatorResult)
+
+ const validatorIdentity = validatorResult.identity
+ const validatorFqdn = validatorResult.fqdn
+
+ if (!validatorIdentity || !validatorFqdn) {
+ throw new Error("Validator identity or fqdn not found in getClosestValidator response")
+ }
+
+ await handleDelegateToValidator(validatorIdentity, validatorFqdn)
+ } catch (error) {
+ console.error("Delegation failed:", error)
+ if (delegationPollIntervalRef.current) {
+ clearInterval(delegationPollIntervalRef.current)
+ delegationPollIntervalRef.current = null
+ }
+ setIsDelegating(false)
+ }
+ }, [isDelegated, handleDelegateToValidator])
+
+ const handleUndelegate = useCallback(async () => {
+ if (
+ !programRef.current ||
+ !playerKeypairRef.current ||
+ !playerPdaRef.current
+ )
+ return
+ if (!isDelegated) return
+
+ setIsUndelegating(true)
+ try {
+ // Store refs in local variables for TypeScript
+ const playerKeypair = playerKeypairRef.current
+ const playerPda = playerPdaRef.current
+ const program = programRef.current
+
+ if (!playerKeypair || !playerPda || !program) {
+ throw new Error("Required refs not available")
+ }
+
+ // List of all known validator endpoints
+ const validatorEndpoints = [
+ "https://devnet-us.magicblock.app",
+ "https://devnet-as.magicblock.app",
+ "https://devnet-eu.magicblock.app",
+ ]
+
+ // Fetch IDL once
+ const idl = await anchor.Program.fetchIdl(PROGRAM_ID, program.provider)
+ if (!idl) throw new Error("IDL not found")
+
+ // Send undelegate RPC to all validator endpoints
+ const undelegatePromises = validatorEndpoints.map(async (endpoint) => {
+ try {
+ const wsEndpoint = endpoint.replace(/^https:\/\//, "wss://").replace(/^http:\/\//, "ws://")
+ const connection = new Connection(endpoint, {
+ wsEndpoint,
+ commitment: "processed",
+ })
+
+ const provider = new anchor.AnchorProvider(
+ connection,
+ walletAdapterFrom(playerKeypair),
+ anchor.AnchorProvider.defaultOptions()
+ )
+
+ const ephemeralProgram = new anchor.Program(idl, provider)
+
+ return await ephemeralProgram.methods
+ .undelegate()
+ .accounts({
+ payer: playerKeypair.publicKey,
+ user: playerPda,
+ })
+ .rpc()
+ } catch (error) {
+ console.warn(`Undelegation failed for ${endpoint}:`, error)
+ throw error
+ }
+ })
+
+ // Wait for all attempts (at least one should succeed)
+ const results = await Promise.allSettled(undelegatePromises)
+ const successful = results.filter(r => r.status === "fulfilled")
+
+ if (successful.length === 0) {
+ throw new Error("All undelegation attempts failed")
+ }
+
+ console.log(`Undelegation succeeded on ${successful.length} endpoint(s)`)
+
+ // Poll every second until undelegation succeeds
+ if (delegationPollIntervalRef.current) {
+ clearInterval(delegationPollIntervalRef.current)
+ }
+
+ delegationPollIntervalRef.current = setInterval(async () => {
+ const delegated = await refreshDelegationStatus()
+ if (!delegated) {
+ if (delegationPollIntervalRef.current) {
+ clearInterval(delegationPollIntervalRef.current)
+ delegationPollIntervalRef.current = null
+ }
+ setIsUndelegating(false)
+ }
+ }, 1000)
+ } catch (error) {
+ console.error("Undelegation failed:", error)
+ if (delegationPollIntervalRef.current) {
+ clearInterval(delegationPollIntervalRef.current)
+ delegationPollIntervalRef.current = null
+ }
+ setIsUndelegating(false)
+ }
+ }, [isDelegated, refreshDelegationStatus])
+
+ const handleRollDice = useCallback(async () => {
+ if (isRolling || !isInitialized || !isDelegated) return
+ if (!ephemeralProgramRef.current || !playerKeypairRef.current || !playerPdaRef.current) return
+
+ console.log("[RollDice] Starting roll")
+ setIsRolling(true)
+ clearAllIntervals()
+
+ rollIntervalRef.current = setInterval(() => {
+ setDiceValue(Math.floor(Math.random() * 6) + 1)
+ }, ROLL_ANIMATION_INTERVAL_MS)
+
+ // Create pending roll history entry (startTime will be set when transaction is sent)
+ setRollHistory(prev => {
+ const newEntry = {
+ value: null,
+ startTime: Date.now(), // Temporary placeholder
+ endTime: null,
+ isPending: true,
+ }
+ return [newEntry, ...prev]
+ })
+
+ timerIntervalRef.current = setInterval(() => {
+ setRollHistory(prev => {
+ const hasPending = prev.some(entry => entry.isPending)
+ if (!hasPending) {
+ if (timerIntervalRef.current) {
+ clearInterval(timerIntervalRef.current)
+ timerIntervalRef.current = null
+ }
+ return prev
+ }
+ setTimerTick(t => t + 1)
+ return prev
+ })
+ }, 10)
+
+ timeoutRef.current = setTimeout(() => {
+ setRollHistory(prev => {
+ if (!prev.some(entry => entry.isPending)) return prev
+ clearAllIntervals()
+ setIsRolling(false)
+ return prev.map(entry => entry.isPending ? { ...entry, isPending: false } : entry)
+ })
+ }, ROLL_TIMEOUT_MS)
+
+ try {
+ const randomValue = Math.floor(Math.random() * 6) + 1
+ const connection = ephemeralConnectionRef.current!
+ const [tx, blockhash] = await Promise.all([
+ ephemeralProgramRef.current.methods.rollDiceDelegated(randomValue).accounts({
+ payer: playerKeypairRef.current.publicKey,
+ player: playerPdaRef.current,
+ oracleQueue: ORACLE_QUEUE,
+ }).transaction(),
+ getBlockhashAsync(connection, true)
+ ])
+
+ tx.recentBlockhash = blockhash
+ tx.feePayer = playerKeypairRef.current.publicKey
+ tx.sign(playerKeypairRef.current)
+
+ const transactionStartTime = Date.now()
+ const sendPromise = connection.sendRawTransaction(tx.serialize(), { skipPreflight: true })
+
+ sendPromise.then((signature) => {
+ setRollHistory(prev => {
+ const updated = [...prev]
+ // First try to find a pending entry without a value (normal case)
+ let targetIndex = updated.findIndex(entry => entry.isPending && entry.value === null)
+ // If not found, try to find a pending entry without a signature (account updated first)
+ if (targetIndex === -1) {
+ targetIndex = updated.findIndex(entry => entry.isPending && !entry.signature)
+ }
+ // If still not found, find the most recent entry without a signature (account updated and no longer pending)
+ // Since entries are added to the front, the first match is the most recent
+ if (targetIndex === -1) {
+ targetIndex = updated.findIndex(entry => !entry.signature)
+ }
+ if (targetIndex !== -1) {
+ updated[targetIndex].startTime = transactionStartTime
+ updated[targetIndex].signature = signature
+ }
+ return updated
+ })
+ }).catch((error) => {
+ console.error("[RollDice] Transaction send error:", error)
+ clearAllIntervals()
+ setIsRolling(false)
+ setRollHistory(prev => prev.filter(entry => !entry.isPending))
+ })
+
+ fetchAndCacheBlockhash(connection, cachedEphemeralBlockhashRef).catch(console.error)
+ } catch (error) {
+ clearAllIntervals()
+ console.error("Error rolling dice:", error)
+ setIsRolling(false)
+ setRollHistory(prev => prev.filter(entry => !entry.isPending))
+ }
+ }, [clearAllIntervals, isDelegated, isInitialized, isRolling, getBlockhashAsync])
+
+ const copyToClipboard = async (text: string) => {
+ try {
+ await navigator.clipboard.writeText(text)
+ setCopied(true)
+ setTimeout(() => setCopied(false), 2000)
+ } catch (error) {
+ console.error("Failed to copy:", error)
+ }
+ }
+
+ const handleGetClosestValidator = useCallback(async () => {
+ if (!routerConnectionRef.current) {
+ console.error("Router connection not initialized")
+ return
+ }
+ try {
+ const result = await routerConnectionRef.current.getClosestValidator()
+ console.log("getClosestValidator result:", result)
+ } catch (error) {
+ console.error("Failed to get closest validator:", error)
+ }
+ }, [])
+
+ const formatAddress = (addr: string) => {
+ if (!addr) return ""
+ return `${addr.substring(0, 8)}...${addr.substring(addr.length - 8)}`
+ }
+
+ const shortenSignature = (signature: string) => {
+ if (!signature) return ""
+ return `${signature.substring(0, 4)}...${signature.substring(signature.length - 4)}`
+ }
+
+ const getExplorerUrl = (signature: string) => {
+ if (ephemeralEndpoint) {
+ const encodedUrl = encodeURIComponent(ephemeralEndpoint)
+ return `https://explorer.solana.com/tx/${signature}?cluster=custom&customUrl=${encodedUrl}`
+ }
+ return `https://explorer.solana.com/tx/${signature}?cluster=devnet`
+ }
+
+ return (
+
+
+
+ {playerPda && (
+
+
+
+
+ Debug
+
+
+
+
+
PDA Address
+
copyToClipboard(playerPda.toBase58())}
+ >
+ {formatAddress(playerPda.toBase58())}
+ {copied ? : }
+
+
+ {ephemeralEndpoint && (
+
+
Ephemeral Connection
+
{ephemeralEndpoint}
+
+ )}
+
+
+
+
+
+
+
+
+
+ {playerAccountData && (
+
+
+
Last Result
+
{playerAccountData.lastResult}
+
+
+
Roll Count
+
{playerAccountData.rollnum}
+
+
+ )}
+
+
+
+
+
+ )}
+
+
+
+
+
+
+
+ {isDelegated ? "Delegated" : "Undelegated"}
+
+ {!isDelegated && (
+
+ )}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Value
+ Time
+
+
+
+ {rollHistory.length === 0 ? (
+
+
+ No rolls yet
+
+
+ ) : (
+ rollHistory.slice(0, 10).map((entry, index) => {
+ const elapsed = entry.isPending
+ ? Date.now() - entry.startTime
+ : entry.endTime
+ ? entry.endTime - entry.startTime
+ : 0
+ const formattedTime = `${elapsed.toString().padStart(6, '\u00A0')}ms${entry.isPending ? '...' : ''}`
+ return (
+
+
+
+
+
+ {formattedTime}
+
+
+ )
+ })
+ )}
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/roll-dice/app/app/globals.css b/roll-dice/app/app/globals.css
index ac68442..ff60b82 100644
--- a/roll-dice/app/app/globals.css
+++ b/roll-dice/app/app/globals.css
@@ -92,3 +92,26 @@ body {
@apply bg-background text-foreground;
}
}
+
+.custom-scrollbar::-webkit-scrollbar {
+ width: 8px;
+}
+
+.custom-scrollbar::-webkit-scrollbar-track {
+ background: #f1f1f1;
+ border-radius: 4px;
+}
+
+.custom-scrollbar::-webkit-scrollbar-thumb {
+ background: #888;
+ border-radius: 4px;
+}
+
+.custom-scrollbar::-webkit-scrollbar-thumb:hover {
+ background: #555;
+}
+
+.custom-scrollbar {
+ scrollbar-width: thin;
+ scrollbar-color: #888 #f1f1f1;
+}
diff --git a/roll-dice/app/app/page.tsx b/roll-dice/app/app/page.tsx
index 4b74f88..ee63f5c 100644
--- a/roll-dice/app/app/page.tsx
+++ b/roll-dice/app/app/page.tsx
@@ -1,25 +1,25 @@
"use client"
import { useState, useCallback, useEffect, useRef } from "react"
-import Dice from "@/components/dice"
-import SolanaAddress from "@/components/solana-address"
-// @ts-ignore
import * as anchor from "@coral-xyz/anchor"
-// @ts-ignore
-import {Connection, Keypair, PublicKey, Transaction, VersionedTransaction} from "@solana/web3.js"
+import { Connection, PublicKey } from "@solana/web3.js"
import { useToast } from "@/hooks/use-toast"
-
-// Program ID for the dice game
-const PROGRAM_ID = new anchor.web3.PublicKey("8xgZ1hY7TnVZ4Bbh7v552Rs3BZMSq3LisyWckkBsNLP")
+import Dice from "@/components/dice"
+import SolanaAddress from "@/components/solana-address"
+import { PROGRAM_ID_STANDARD, PLAYER_SEED, BASE_ENDPOINT, PLAYER_STORAGE_KEY } from "@/lib/config"
+import { walletAdapterFrom, loadOrCreateKeypair } from "@/lib/solana-utils"
export default function DiceRoller() {
const [diceValue, setDiceValue] = useState(1)
const [isRolling, setIsRolling] = useState(false)
const [isInitialized, setIsInitialized] = useState(false)
- const [key, setKey] = useState(0) // Used to force re-render the component
+ const [key, setKey] = useState(0)
+
const programRef = useRef(null)
const subscriptionIdRef = useRef(null)
const rollIntervalRef = useRef(null)
+ const timeoutRef = useRef(null)
+
const { toast } = useToast()
// Clear the rolling animation interval
@@ -30,91 +30,61 @@ export default function DiceRoller() {
}
}
+ const clearAllIntervals = useCallback(() => {
+ clearRollInterval()
+ if (timeoutRef.current) {
+ clearTimeout(timeoutRef.current)
+ timeoutRef.current = null
+ }
+ }, [])
+
const initializeProgram = async () => {
try {
- // Get or create keypair
- let storedKeypair = localStorage.getItem("solanaKeypair")
- let keypair: Keypair
-
- const connection = new Connection("https://rpc.magicblock.app/devnet", "confirmed")
+ const keypair = loadOrCreateKeypair(PLAYER_STORAGE_KEY)
+ const connection = new Connection(BASE_ENDPOINT, "confirmed")
- if (storedKeypair) {
- const secretKey = Uint8Array.from(JSON.parse(storedKeypair))
- keypair = Keypair.fromSecretKey(secretKey)
- } else {
- keypair = Keypair.generate()
- localStorage.setItem("solanaKeypair", JSON.stringify(Array.from(keypair.secretKey)))
- }
-
- // Create the provider
const provider = new anchor.AnchorProvider(
- connection,
- {
- publicKey: keypair.publicKey,
- signTransaction: async (transaction: T): Promise => {
- // @ts-ignore
- transaction.sign(keypair)
- return transaction
- },
- signAllTransactions: async (transactions: T[]): Promise => {
- for (const tx of transactions) {
- // @ts-ignore
- tx.sign(keypair)
- }
- return transactions
- },
- },
- anchor.AnchorProvider.defaultOptions()
+ connection,
+ walletAdapterFrom(keypair),
+ anchor.AnchorProvider.defaultOptions()
)
- // User
- console.log("User: ", keypair.publicKey.toBase58())
-
- // Fetch the IDL
- const idl = await anchor.Program.fetchIdl(PROGRAM_ID, provider)
+ const idl = await anchor.Program.fetchIdl(PROGRAM_ID_STANDARD, provider)
if (!idl) throw new Error("IDL not found")
- // Create the program instance
const program = new anchor.Program(idl, provider)
programRef.current = program
- console.log("Program instance created successfully: ", program.programId.toBase58())
-
- // Initialize the program
- const playerPk = PublicKey.findProgramAddressSync([Buffer.from("playerd"), provider.publicKey.toBytes()], program.programId)[0];
- let account = await connection.getAccountInfo(playerPk);
- // @ts-ignore
- if(!account || !account.data || account.data.length === 0) {
- console.log("Player account not found, creating new one...")
- const tx = await program.methods.initialize().rpc()
- console.log("User initialized with tx:", tx)
- }else{
- const ply = program.coder.accounts.decode("player", account.data)
- console.log("Player account:", playerPk.toBase58(), "lastResult:", ply.lastResult)
- setDiceValue(ply.lastResult)
+ const playerPk = PublicKey.findProgramAddressSync(
+ [Buffer.from(PLAYER_SEED), provider.publicKey.toBytes()],
+ program.programId
+ )[0]
+
+ let account = await connection.getAccountInfo(playerPk)
+ if (!account || !account.data || account.data.length === 0) {
+ await program.methods.initialize().rpc()
+ } else {
+ const player = program.coder.accounts.decode("player", account.data)
+ setDiceValue(player.lastResult)
}
- // Subscribe to account changes
if (subscriptionIdRef.current !== null) {
- await connection.removeAccountChangeListener(subscriptionIdRef.current);
+ await connection.removeAccountChangeListener(subscriptionIdRef.current)
}
subscriptionIdRef.current = connection.onAccountChange(
- playerPk,
- // @ts-ignore
- (accountInfo) => {
- const player = program.coder.accounts.decode("player", accountInfo.data)
- console.log("Player account changed:", player)
- setDiceValue(player.lastResult)
- setIsRolling(false)
- clearRollInterval()
- },
- {commitment: "processed"}
- );
-
- // Set initialization as successful
- setIsInitialized(true)
+ playerPk,
+ (accountInfo) => {
+ const player = program.coder.accounts.decode("player", accountInfo.data)
+ const newValue = Number(player.lastResult)
+ setDiceValue(newValue)
+ setIsRolling(false)
+ clearAllIntervals()
+ },
+ { commitment: "processed" }
+ )
+ setIsInitialized(true)
} catch (error) {
console.error("Failed to initialize program:", error)
setIsInitialized(false)
@@ -129,30 +99,19 @@ export default function DiceRoller() {
useEffect(() => {
initializeProgram()
- // Cleanup function
return () => {
- clearRollInterval()
- // Clean up subscription
+ clearAllIntervals()
if (subscriptionIdRef.current !== null) {
- const connection = new Connection("https://rpc.magicblock.app/devnet", "confirmed")
+ const connection = new Connection(BASE_ENDPOINT, "confirmed")
connection.removeAccountChangeListener(subscriptionIdRef.current).catch(console.error)
}
}
- }, [toast, key]) // Add key as dependency to re-run when key changes
+ }, [toast, key])
const handleBalanceChange = useCallback((newBalance: number) => {
- console.log("Balance changed:", newBalance)
-
- // If not initialized, try to initialize again or force component reload
if (!isInitialized) {
- console.log("Not initialized, attempting to reinitialize...")
-
- // Option 1: Call initializeProgram again
initializeProgram()
-
- // Option 2: Force component reload by changing the key
setKey(prevKey => prevKey + 1)
-
toast({
title: "Reinitializing",
description: "Balance changed, attempting to reinitialize the program",
@@ -161,61 +120,48 @@ export default function DiceRoller() {
}, [isInitialized, toast])
const handleRollDice = useCallback(async () => {
- if (isRolling || !isInitialized) return;
+ if (isRolling || !isInitialized) return
setIsRolling(true)
-
- // Clear any existing interval
clearRollInterval()
- if (programRef.current) {
- try {
- const tx = await programRef.current.methods.rollDice(Math.floor(Math.random() * 6) + 1).rpc()
- console.log("Dice rolled on-chain with tx:", tx)
-
- toast({
- title: "Dice Rolled",
- description: `Result: TX: ${tx.slice(0, 8)}...`,
- })
-
- // Simulate rolling animation by changing values rapidly
- rollIntervalRef.current = setInterval(() => {
- setDiceValue(Math.floor(Math.random() * 6) + 1)
- }, 100)
-
- // Add a timeout to stop rolling after 10 seconds if still rolling
- setTimeout(() => {
- if (isRolling) {
- console.log("Rolling timeout reached (10s), stopping animation")
- setIsRolling(false)
- clearRollInterval()
- toast({
- title: "Notice",
- description: "Dice roll is taking longer than expected. Check transaction status in explorer.",
- variant: "destructive",
- })
- }
- }, 10000)
-
-
- } catch (error) {
- console.error("Error rolling dice:", error)
- toast({
- title: "Error",
- description: "Failed to roll dice",
- variant: "destructive",
- })
- setIsRolling(false)
- clearRollInterval()
- }
- } else {
- console.error("Program not initialized")
+ if (!programRef.current) {
toast({
title: "Error",
description: "Program not initialized",
variant: "destructive",
})
setIsRolling(false)
+ return
+ }
+
+ try {
+ await programRef.current.methods.rollDice(Math.floor(Math.random() * 6) + 1).rpc()
+
+ rollIntervalRef.current = setInterval(() => {
+ setDiceValue(Math.floor(Math.random() * 6) + 1)
+ }, 100)
+
+ setTimeout(() => {
+ if (isRolling) {
+ setIsRolling(false)
+ clearRollInterval()
+ toast({
+ title: "Notice",
+ description: "Dice roll is taking longer than expected. Check transaction status in explorer.",
+ variant: "destructive",
+ })
+ }
+ }, 10000)
+ } catch (error) {
+ console.error("Error rolling dice:", error)
+ toast({
+ title: "Error",
+ description: "Failed to roll dice",
+ variant: "destructive",
+ })
+ setIsRolling(false)
+ clearRollInterval()
}
}, [isRolling, isInitialized, toast])
diff --git a/roll-dice/app/lib/config.ts b/roll-dice/app/lib/config.ts
new file mode 100644
index 0000000..7c5cbf9
--- /dev/null
+++ b/roll-dice/app/lib/config.ts
@@ -0,0 +1,15 @@
+import { PublicKey } from "@solana/web3.js"
+
+export const PROGRAM_ID = new PublicKey("5bPwgoPWz274NKgThcnPas2Mv4rSknu9JrbxzFVqU5gY")
+export const PROGRAM_ID_STANDARD = new PublicKey("8xgZ1hY7TnVZ4Bbh7v552Rs3BZMSq3LisyWckkBsNLP")
+export const PLAYER_SEED = "playerd"
+export const ORACLE_QUEUE = new PublicKey("5hBR571xnXppuCPveTrctfTU7tJLSN94nq7kv7FRK5Tc")
+export const BASE_ENDPOINT = "https://rpc.magicblock.app/devnet"
+export const PLAYER_STORAGE_KEY = "solanaKeypair"
+export const PAYER_STORAGE_KEY = "delegatePayerKeypair"
+export const MIN_BALANCE_LAMPORTS = 0.05
+export const BLOCKHASH_CACHE_MAX_AGE_MS = 30000
+export const BLOCKHASH_REFRESH_INTERVAL_MS = 15000
+export const ROLL_TIMEOUT_MS = 2000
+export const ROLL_ANIMATION_INTERVAL_MS = 100
+
diff --git a/roll-dice/app/lib/delegate-instruction.ts b/roll-dice/app/lib/delegate-instruction.ts
new file mode 100644
index 0000000..5422e54
--- /dev/null
+++ b/roll-dice/app/lib/delegate-instruction.ts
@@ -0,0 +1,106 @@
+import * as beet from "@metaplex-foundation/beet";
+import * as web3 from "@solana/web3.js";
+import {
+ DELEGATION_PROGRAM_ID,
+ delegationRecordPdaFromDelegatedAccount,
+ delegationMetadataPdaFromDelegatedAccount,
+ delegateBufferPdaFromDelegatedAccountAndOwnerProgram,
+} from "@magicblock-labs/ephemeral-rollups-sdk";
+
+const delegateStruct = new beet.FixableBeetArgsStruct(
+ [
+ ["instructionDiscriminator", beet.uniformFixedSizeArray(beet.u8, 8)],
+ ["commit_frequency_ms", beet.u32],
+ ["seeds", beet.array(beet.array(beet.u8))],
+ ["validator", beet.coption(beet.uniformFixedSizeArray(beet.u8, 32))],
+ ],
+ "DelegateInstructionArgs"
+);
+
+const delegateInstructionDiscriminator = [0, 0, 0, 0, 0, 0, 0, 0];
+
+export interface CreateDelegateInstructionAccounts {
+ payer: web3.PublicKey;
+ delegatedAccount: web3.PublicKey;
+ ownerProgram: web3.PublicKey;
+ delegationRecord?: web3.PublicKey;
+ delegationMetadata?: web3.PublicKey;
+ systemProgram?: web3.PublicKey;
+ validator?: web3.PublicKey;
+}
+
+export interface CreateDelegateInstructionArgs {
+ commit_frequency_ms?: number;
+ seeds?: number[][];
+}
+
+export function createDelegateInstruction(
+ accounts: CreateDelegateInstructionAccounts,
+ args?: CreateDelegateInstructionArgs,
+ programId: web3.PublicKey = DELEGATION_PROGRAM_ID
+): web3.TransactionInstruction {
+ const delegateBufferPda = delegateBufferPdaFromDelegatedAccountAndOwnerProgram(
+ accounts.delegatedAccount,
+ accounts.ownerProgram
+ );
+ const delegationRecordPda = delegationRecordPdaFromDelegatedAccount(
+ accounts.delegatedAccount
+ );
+ const delegationMetadataPda = delegationMetadataPdaFromDelegatedAccount(
+ accounts.delegatedAccount
+ );
+
+ args = args ?? {
+ commit_frequency_ms: 4294967295,
+ seeds: [],
+ };
+
+ const keys = [
+ { pubkey: accounts.payer, isWritable: false, isSigner: true },
+ { pubkey: accounts.delegatedAccount, isWritable: true, isSigner: true },
+ { pubkey: accounts.ownerProgram, isWritable: false, isSigner: false },
+ {
+ pubkey: delegateBufferPda,
+ isWritable: true,
+ isSigner: false,
+ },
+ {
+ pubkey: accounts.delegationRecord ?? delegationRecordPda,
+ isWritable: true,
+ isSigner: false,
+ },
+ {
+ pubkey: accounts.delegationMetadata ?? delegationMetadataPda,
+ isWritable: true,
+ isSigner: false,
+ },
+ {
+ pubkey: accounts.systemProgram ?? web3.SystemProgram.programId,
+ isWritable: false,
+ isSigner: false,
+ },
+ ...(accounts.validator
+ ? [
+ {
+ pubkey: accounts.validator,
+ isWritable: false,
+ isSigner: false,
+ },
+ ]
+ : []),
+ ];
+
+ const [data] = delegateStruct.serialize({
+ instructionDiscriminator: delegateInstructionDiscriminator,
+ commit_frequency_ms: args.commit_frequency_ms,
+ seeds: args.seeds.map((seed) => seed.map(Number)),
+ validator: accounts.validator ? accounts.validator.toBytes() : null,
+ });
+
+ return new web3.TransactionInstruction({
+ programId,
+ keys,
+ data,
+ });
+}
+
diff --git a/roll-dice/app/lib/solana-utils.ts b/roll-dice/app/lib/solana-utils.ts
new file mode 100644
index 0000000..82fa6a3
--- /dev/null
+++ b/roll-dice/app/lib/solana-utils.ts
@@ -0,0 +1,79 @@
+import { Keypair, PublicKey, Transaction, VersionedTransaction, Connection, LAMPORTS_PER_SOL } from "@solana/web3.js"
+import { DELEGATION_PROGRAM_ID } from "@magicblock-labs/ephemeral-rollups-sdk"
+import { MIN_BALANCE_LAMPORTS, BLOCKHASH_CACHE_MAX_AGE_MS } from "./config"
+import type { CachedBlockhash } from "./types"
+
+export const walletAdapterFrom = (keypair: Keypair) => ({
+ publicKey: keypair.publicKey,
+ async signTransaction(transaction: T): Promise {
+ // @ts-ignore - Transaction and VersionedTransaction have different sign signatures
+ transaction.sign(keypair)
+ return transaction
+ },
+ async signAllTransactions(transactions: T[]): Promise {
+ // @ts-ignore - Transaction and VersionedTransaction have different sign signatures
+ transactions.forEach(tx => tx.sign(keypair))
+ return transactions
+ },
+})
+
+export const loadOrCreateKeypair = (storageKey: string): Keypair => {
+ if (typeof window === "undefined") return Keypair.generate()
+ const stored = window.localStorage.getItem(storageKey)
+ if (stored) {
+ return Keypair.fromSecretKey(Uint8Array.from(JSON.parse(stored)))
+ }
+ const generated = Keypair.generate()
+ window.localStorage.setItem(storageKey, JSON.stringify(Array.from(generated.secretKey)))
+ return generated
+}
+
+export const ensureFunds = async (connection: Connection, keypair: Keypair): Promise => {
+ const balance = await connection.getBalance(keypair.publicKey)
+ if (balance < MIN_BALANCE_LAMPORTS * LAMPORTS_PER_SOL) {
+ const signature = await connection.requestAirdrop(keypair.publicKey, LAMPORTS_PER_SOL)
+ await connection.confirmTransaction(signature, "confirmed")
+ }
+}
+
+export const fetchAndCacheBlockhash = async (
+ connection: Connection,
+ cacheRef: { current: CachedBlockhash | null }
+): Promise => {
+ try {
+ const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash()
+ cacheRef.current = {
+ blockhash,
+ lastValidBlockHeight,
+ timestamp: Date.now(),
+ }
+ } catch (error) {
+ console.error("Failed to fetch blockhash:", error)
+ }
+}
+
+export const getCachedBlockhash = (
+ connection: Connection,
+ cacheRef: { current: CachedBlockhash | null }
+): string | null => {
+ const cached = cacheRef.current
+ if (!cached) return null
+
+ const age = Date.now() - cached.timestamp
+ if (age > BLOCKHASH_CACHE_MAX_AGE_MS) {
+ // Trigger refresh in background but don't return stale blockhash
+ fetchAndCacheBlockhash(connection, cacheRef).catch(console.error)
+ return null
+ }
+
+ return cached.blockhash
+}
+
+export const checkDelegationStatus = async (
+ connection: Connection,
+ accountPubkey: PublicKey
+): Promise => {
+ const accountInfo = await connection.getAccountInfo(accountPubkey)
+ return !!accountInfo && accountInfo.owner.equals(DELEGATION_PROGRAM_ID)
+}
+
diff --git a/roll-dice/app/lib/types.ts b/roll-dice/app/lib/types.ts
new file mode 100644
index 0000000..71a86fd
--- /dev/null
+++ b/roll-dice/app/lib/types.ts
@@ -0,0 +1,14 @@
+export type RollEntry = {
+ value: number | null
+ startTime: number
+ endTime: number | null
+ isPending: boolean
+ signature?: string
+}
+
+export type CachedBlockhash = {
+ blockhash: string
+ lastValidBlockHeight: number
+ timestamp: number
+}
+
diff --git a/roll-dice/app/package.json b/roll-dice/app/package.json
index 48569e7..cd1a076 100644
--- a/roll-dice/app/package.json
+++ b/roll-dice/app/package.json
@@ -9,7 +9,10 @@
"lint": "next lint"
},
"dependencies": {
+ "@coral-xyz/anchor": "^0.31.0",
"@hookform/resolvers": "^3.9.1",
+ "@magicblock-labs/ephemeral-rollups-sdk": "^0.4.1",
+ "@metaplex-foundation/beet": "^0.7.2",
"@radix-ui/react-accordion": "^1.2.2",
"@radix-ui/react-alert-dialog": "^1.1.4",
"@radix-ui/react-aspect-ratio": "^1.1.1",
@@ -38,7 +41,6 @@
"@radix-ui/react-toggle-group": "^1.1.1",
"@radix-ui/react-tooltip": "^1.1.6",
"@solana/web3.js": "latest",
- "@coral-xyz/anchor": "^0.31.0",
"autoprefixer": "^10.4.20",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
diff --git a/roll-dice/app/yarn.lock b/roll-dice/app/yarn.lock
index 15879bd..134efb3 100644
--- a/roll-dice/app/yarn.lock
+++ b/roll-dice/app/yarn.lock
@@ -351,6 +351,28 @@
"@jridgewell/resolve-uri" "^3.1.0"
"@jridgewell/sourcemap-codec" "^1.4.14"
+"@magicblock-labs/ephemeral-rollups-sdk@^0.4.1":
+ version "0.4.1"
+ resolved "https://registry.yarnpkg.com/@magicblock-labs/ephemeral-rollups-sdk/-/ephemeral-rollups-sdk-0.4.1.tgz#2fba50160e6a279c0eaaa02bac50e48ee8781e54"
+ integrity sha512-Te/8DIddisjci1nhUNM24EtfS2Hk3h4PXjYtGQV/MsIGq5S+Zwd04Sop8q5h05GHYbvzMAG1TqDPIArPBOBzVA==
+ dependencies:
+ "@metaplex-foundation/beet" "^0.7.2"
+ "@phala/dcap-qvl-web" "^0.2.7"
+ "@solana/web3.js" "^1.98.0"
+ bs58 "^6.0.0"
+ rpc-websockets "^9.0.4"
+ typescript "^5.3.0"
+
+"@metaplex-foundation/beet@^0.7.2":
+ version "0.7.2"
+ resolved "https://registry.yarnpkg.com/@metaplex-foundation/beet/-/beet-0.7.2.tgz#fa4726e4cfd4fb6fed6cddc9b5213c1c2a2d0b77"
+ integrity sha512-K+g3WhyFxKPc0xIvcIjNyV1eaTVJTiuaHZpig7Xx0MuYRMoJLLvhLTnUXhFdR5Tu2l2QSyKwfyXDgZlzhULqFg==
+ dependencies:
+ ansicolors "^0.3.2"
+ assert "^2.1.0"
+ bn.js "^5.2.0"
+ debug "^4.3.3"
+
"@napi-rs/wasm-runtime@^0.2.7":
version "0.2.7"
resolved "https://registry.yarnpkg.com/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.7.tgz#288f03812a408bc53c2c3686c65f38fe90f295eb"
@@ -450,6 +472,11 @@
resolved "https://registry.yarnpkg.com/@nolyfill/is-core-module/-/is-core-module-1.0.39.tgz#3dc35ba0f1e66b403c00b39344f870298ebb1c8e"
integrity sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==
+"@phala/dcap-qvl-web@^0.2.7":
+ version "0.2.7"
+ resolved "https://registry.yarnpkg.com/@phala/dcap-qvl-web/-/dcap-qvl-web-0.2.7.tgz#d7a03b059a201355262ca9c1bb6c77a1c22472dd"
+ integrity sha512-OgDIN8ZRsLg0dJgUAk0HCXMjkAmrif7p0C+P74YrtxgE/8fNSFpqNDjVW3mCVB2Q/V7X6mUhbEQWa5wJmM9OSQ==
+
"@pkgjs/parseargs@^0.11.0":
version "0.11.0"
resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33"
@@ -1057,6 +1084,29 @@
dependencies:
buffer "~6.0.3"
+"@solana/codecs-core@2.3.0":
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/@solana/codecs-core/-/codecs-core-2.3.0.tgz#6bf2bb565cb1ae880f8018635c92f751465d8695"
+ integrity sha512-oG+VZzN6YhBHIoSKgS5ESM9VIGzhWjEHEGNPSibiDTxFhsFWxNaz8LbMDPjBUE69r9wmdGLkrQ+wVPbnJcZPvw==
+ dependencies:
+ "@solana/errors" "2.3.0"
+
+"@solana/codecs-numbers@^2.1.0":
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/@solana/codecs-numbers/-/codecs-numbers-2.3.0.tgz#ac7e7f38aaf7fcd22ce2061fbdcd625e73828dc6"
+ integrity sha512-jFvvwKJKffvG7Iz9dmN51OGB7JBcy2CJ6Xf3NqD/VP90xak66m/Lg48T01u5IQ/hc15mChVHiBm+HHuOFDUrQg==
+ dependencies:
+ "@solana/codecs-core" "2.3.0"
+ "@solana/errors" "2.3.0"
+
+"@solana/errors@2.3.0":
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/@solana/errors/-/errors-2.3.0.tgz#4ac9380343dbeffb9dffbcb77c28d0e457c5fa31"
+ integrity sha512-66RI9MAbwYV0UtP7kGcTBVLxJgUxoZGm8Fbc0ah+lGiAw17Gugco6+9GrJCV83VyF2mDWyYnYM9qdI3yjgpnaQ==
+ dependencies:
+ chalk "^5.4.1"
+ commander "^14.0.0"
+
"@solana/web3.js@^1.69.0", "@solana/web3.js@latest":
version "1.98.0"
resolved "https://registry.yarnpkg.com/@solana/web3.js/-/web3.js-1.98.0.tgz#21ecfe8198c10831df6f0cfde7f68370d0405917"
@@ -1078,6 +1128,27 @@
rpc-websockets "^9.0.2"
superstruct "^2.0.2"
+"@solana/web3.js@^1.98.0":
+ version "1.98.4"
+ resolved "https://registry.yarnpkg.com/@solana/web3.js/-/web3.js-1.98.4.tgz#df51d78be9d865181ec5138b4e699d48e6895bbe"
+ integrity sha512-vv9lfnvjUsRiq//+j5pBdXig0IQdtzA0BRZ3bXEP4KaIyF1CcaydWqgyzQgfZMNIsWNWmG+AUHwPy4AHOD6gpw==
+ dependencies:
+ "@babel/runtime" "^7.25.0"
+ "@noble/curves" "^1.4.2"
+ "@noble/hashes" "^1.4.0"
+ "@solana/buffer-layout" "^4.0.1"
+ "@solana/codecs-numbers" "^2.1.0"
+ agentkeepalive "^4.5.0"
+ bn.js "^5.2.1"
+ borsh "^0.7.0"
+ bs58 "^4.0.1"
+ buffer "6.0.3"
+ fast-stable-stringify "^1.0.0"
+ jayson "^4.1.1"
+ node-fetch "^2.7.0"
+ rpc-websockets "^9.0.2"
+ superstruct "^2.0.2"
+
"@swc/counter@0.1.3":
version "0.1.3"
resolved "https://registry.yarnpkg.com/@swc/counter/-/counter-0.1.3.tgz#cc7463bd02949611c6329596fccd2b0ec782b0e9"
@@ -1428,6 +1499,11 @@ ansi-styles@^6.1.0:
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5"
integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==
+ansicolors@^0.3.2:
+ version "0.3.2"
+ resolved "https://registry.yarnpkg.com/ansicolors/-/ansicolors-0.3.2.tgz#665597de86a9ffe3aa9bfbe6cae5c6ea426b4979"
+ integrity sha512-QXu7BPrP29VllRxH8GwB7x5iX5qWKAAMLqKQGWTeLWVlNHNOpVMJ91dsxQAIWXpjuW5wqvxu3Jd/nRjrJ+0pqg==
+
any-promise@^1.0.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f"
@@ -1552,6 +1628,17 @@ arraybuffer.prototype.slice@^1.0.4:
get-intrinsic "^1.2.6"
is-array-buffer "^3.0.4"
+assert@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/assert/-/assert-2.1.0.tgz#6d92a238d05dc02e7427c881fb8be81c8448b2dd"
+ integrity sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw==
+ dependencies:
+ call-bind "^1.0.2"
+ is-nan "^1.3.2"
+ object-is "^1.1.5"
+ object.assign "^4.1.4"
+ util "^0.12.5"
+
ast-types-flow@^0.0.8:
version "0.0.8"
resolved "https://registry.yarnpkg.com/ast-types-flow/-/ast-types-flow-0.0.8.tgz#0a85e1c92695769ac13a428bb653e7538bea27d6"
@@ -1603,6 +1690,11 @@ base-x@^3.0.2:
dependencies:
safe-buffer "^5.0.1"
+base-x@^5.0.0:
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/base-x/-/base-x-5.0.1.tgz#16bf35254be1df8aca15e36b7c1dda74b2aa6b03"
+ integrity sha512-M7uio8Zt++eg3jPj+rHMfCC+IuygQHHCOU+IYsVtik6FWjuYpVt/+MRKcgsAMHh8mMFAwnB+Bs+mTrFiXjMzKg==
+
base64-js@^1.3.1:
version "1.5.1"
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
@@ -1680,6 +1772,13 @@ bs58@^4.0.0, bs58@^4.0.1:
dependencies:
base-x "^3.0.2"
+bs58@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/bs58/-/bs58-6.0.0.tgz#a2cda0130558535dd281a2f8697df79caaf425d8"
+ integrity sha512-PD0wEnEYg6ijszw/u8s+iI3H17cTymlrwkKhDhPZq+Sokl3AU4htyBFTjAeNAlCCmg0f53g6ih3jATyCKftTfw==
+ dependencies:
+ base-x "^5.0.0"
+
buffer-layout@^1.2.0, buffer-layout@^1.2.2:
version "1.2.2"
resolved "https://registry.yarnpkg.com/buffer-layout/-/buffer-layout-1.2.2.tgz#b9814e7c7235783085f9ca4966a0cfff112259d5"
@@ -1715,7 +1814,7 @@ call-bind-apply-helpers@^1.0.0, call-bind-apply-helpers@^1.0.1, call-bind-apply-
es-errors "^1.3.0"
function-bind "^1.1.2"
-call-bind@^1.0.7, call-bind@^1.0.8:
+call-bind@^1.0.0, call-bind@^1.0.2, call-bind@^1.0.7, call-bind@^1.0.8:
version "1.0.8"
resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.8.tgz#0736a9660f537e3388826f440d5ec45f744eaa4c"
integrity sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==
@@ -1761,6 +1860,11 @@ chalk@^4.0.0:
ansi-styles "^4.1.0"
supports-color "^7.1.0"
+chalk@^5.4.1:
+ version "5.6.2"
+ resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.6.2.tgz#b1238b6e23ea337af71c7f8a295db5af0c158aea"
+ integrity sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==
+
chokidar@^3.6.0:
version "3.6.0"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b"
@@ -1831,6 +1935,11 @@ color@^4.2.3:
color-convert "^2.0.1"
color-string "^1.9.0"
+commander@^14.0.0:
+ version "14.0.2"
+ resolved "https://registry.yarnpkg.com/commander/-/commander-14.0.2.tgz#b71fd37fe4069e4c3c7c13925252ada4eba14e8e"
+ integrity sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==
+
commander@^2.20.3:
version "2.20.3"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
@@ -1994,6 +2103,13 @@ debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@^4.4.0:
dependencies:
ms "^2.1.3"
+debug@^4.3.3:
+ version "4.4.3"
+ resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a"
+ integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==
+ dependencies:
+ ms "^2.1.3"
+
decimal.js-light@^2.4.1:
version "2.5.1"
resolved "https://registry.yarnpkg.com/decimal.js-light/-/decimal.js-light-2.5.1.tgz#134fd32508f19e208f4fb2f8dac0d2626a867934"
@@ -2626,6 +2742,11 @@ functions-have-names@^1.2.3:
resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834"
integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==
+generator-function@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/generator-function/-/generator-function-2.0.1.tgz#0e75dd410d1243687a0ba2e951b94eedb8f737a2"
+ integrity sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==
+
get-intrinsic@^1.2.4, get-intrinsic@^1.2.5, get-intrinsic@^1.2.6, get-intrinsic@^1.2.7, get-intrinsic@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01"
@@ -2793,6 +2914,11 @@ imurmurhash@^0.1.4:
resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==
+inherits@^2.0.3:
+ version "2.0.4"
+ resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
+ integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
+
input-otp@1.4.1:
version "1.4.1"
resolved "https://registry.yarnpkg.com/input-otp/-/input-otp-1.4.1.tgz#bc22e68b14b1667219d54adf74243e37ea79cf84"
@@ -2812,6 +2938,14 @@ internal-slot@^1.1.0:
resolved "https://registry.yarnpkg.com/internmap/-/internmap-2.0.3.tgz#6685f23755e43c524e251d29cbc97248e3061009"
integrity sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==
+is-arguments@^1.0.4:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.2.0.tgz#ad58c6aecf563b78ef2bf04df540da8f5d7d8e1b"
+ integrity sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==
+ dependencies:
+ call-bound "^1.0.2"
+ has-tostringtag "^1.0.2"
+
is-array-buffer@^3.0.4, is-array-buffer@^3.0.5:
version "3.0.5"
resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.5.tgz#65742e1e687bd2cc666253068fd8707fe4d44280"
@@ -2922,6 +3056,17 @@ is-generator-function@^1.0.10:
has-tostringtag "^1.0.2"
safe-regex-test "^1.1.0"
+is-generator-function@^1.0.7:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.1.2.tgz#ae3b61e3d5ea4e4839b90bad22b02335051a17d5"
+ integrity sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==
+ dependencies:
+ call-bound "^1.0.4"
+ generator-function "^2.0.0"
+ get-proto "^1.0.1"
+ has-tostringtag "^1.0.2"
+ safe-regex-test "^1.1.0"
+
is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1:
version "4.0.3"
resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084"
@@ -2934,6 +3079,14 @@ is-map@^2.0.3:
resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.3.tgz#ede96b7fe1e270b3c4465e3a465658764926d62e"
integrity sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==
+is-nan@^1.3.2:
+ version "1.3.2"
+ resolved "https://registry.yarnpkg.com/is-nan/-/is-nan-1.3.2.tgz#043a54adea31748b55b6cd4e09aadafa69bd9e1d"
+ integrity sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==
+ dependencies:
+ call-bind "^1.0.0"
+ define-properties "^1.1.3"
+
is-number-object@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.1.1.tgz#144b21e95a1bc148205dcc2814a9134ec41b2541"
@@ -2986,7 +3139,7 @@ is-symbol@^1.0.4, is-symbol@^1.1.1:
has-symbols "^1.1.0"
safe-regex-test "^1.1.0"
-is-typed-array@^1.1.13, is-typed-array@^1.1.14, is-typed-array@^1.1.15:
+is-typed-array@^1.1.13, is-typed-array@^1.1.14, is-typed-array@^1.1.15, is-typed-array@^1.1.3:
version "1.1.15"
resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.15.tgz#4bfb4a45b61cee83a5a46fba778e4e8d59c0ce0b"
integrity sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==
@@ -3333,6 +3486,14 @@ object-inspect@^1.13.3:
resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.4.tgz#8375265e21bc20d0fa582c22e1b13485d6e00213"
integrity sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==
+object-is@^1.1.5:
+ version "1.1.6"
+ resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.6.tgz#1a6a53aed2dd8f7e6775ff870bea58545956ab07"
+ integrity sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==
+ dependencies:
+ call-bind "^1.0.7"
+ define-properties "^1.2.1"
+
object-keys@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e"
@@ -3778,6 +3939,22 @@ rpc-websockets@^9.0.2:
bufferutil "^4.0.1"
utf-8-validate "^5.0.2"
+rpc-websockets@^9.0.4:
+ version "9.3.1"
+ resolved "https://registry.yarnpkg.com/rpc-websockets/-/rpc-websockets-9.3.1.tgz#d817a59d812f68bae1215740a3f78fcdd3813698"
+ integrity sha512-bY6a+i/lEtBJ/mUxwsCTgevoV1P0foXTVA7UoThzaIWbM+3NDqorf8NBWs5DmqKTFeA1IoNzgvkWjFCPgnzUiQ==
+ dependencies:
+ "@swc/helpers" "^0.5.11"
+ "@types/uuid" "^8.3.4"
+ "@types/ws" "^8.2.2"
+ buffer "^6.0.3"
+ eventemitter3 "^5.0.1"
+ uuid "^8.3.2"
+ ws "^8.5.0"
+ optionalDependencies:
+ bufferutil "^4.0.1"
+ utf-8-validate "^5.0.2"
+
run-parallel@^1.1.9:
version "1.2.0"
resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee"
@@ -4319,6 +4496,11 @@ typescript@^5:
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.8.2.tgz#8170b3702f74b79db2e5a96207c15e65807999e4"
integrity sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==
+typescript@^5.3.0:
+ version "5.9.3"
+ resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.9.3.tgz#5b4f59e15310ab17a216f5d6cf53ee476ede670f"
+ integrity sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==
+
unbox-primitive@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.1.0.tgz#8d9d2c9edeea8460c7f35033a88867944934d1e2"
@@ -4402,6 +4584,17 @@ util-deprecate@^1.0.2:
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==
+util@^0.12.5:
+ version "0.12.5"
+ resolved "https://registry.yarnpkg.com/util/-/util-0.12.5.tgz#5f17a6059b73db61a875668781a1c2b136bd6fbc"
+ integrity sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==
+ dependencies:
+ inherits "^2.0.3"
+ is-arguments "^1.0.4"
+ is-generator-function "^1.0.7"
+ is-typed-array "^1.1.3"
+ which-typed-array "^1.1.2"
+
uuid@^8.3.2:
version "8.3.2"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
@@ -4487,7 +4680,7 @@ which-collection@^1.0.2:
is-weakmap "^2.0.2"
is-weakset "^2.0.3"
-which-typed-array@^1.1.16, which-typed-array@^1.1.18:
+which-typed-array@^1.1.16, which-typed-array@^1.1.18, which-typed-array@^1.1.2:
version "1.1.19"
resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.19.tgz#df03842e870b6b88e117524a4b364b6fc689f956"
integrity sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==
diff --git a/roll-dice/package.json b/roll-dice/package.json
index 38f386e..2cc8963 100644
--- a/roll-dice/package.json
+++ b/roll-dice/package.json
@@ -5,7 +5,8 @@
"lint": "prettier */*.js \"*/**/*{.js,.ts}\" --check"
},
"dependencies": {
- "@coral-xyz/anchor": "0.32.1"
+ "@coral-xyz/anchor": "0.32.1",
+ "@magicblock-labs/ephemeral-rollups-sdk": "^0.4.1"
},
"devDependencies": {
"@types/bn.js": "^5.1.0",
diff --git a/roll-dice/programs/roll-dice-delegated/Cargo.toml b/roll-dice/programs/roll-dice-delegated/Cargo.toml
index f900b9f..96cb568 100644
--- a/roll-dice/programs/roll-dice-delegated/Cargo.toml
+++ b/roll-dice/programs/roll-dice-delegated/Cargo.toml
@@ -22,6 +22,6 @@ custom-panic = []
[dependencies]
anchor-lang = { version = "0.32.1", features = ["init-if-needed"] }
-ephemeral-rollups-sdk = { version = "0.2.5", features = ["anchor"] }
-ephemeral-vrf-sdk = {version = "0.2.1", features = ["anchor"]}
+ephemeral-rollups-sdk = { version = "0.4.1", features = ["anchor"] }
+ephemeral-vrf-sdk = {version = "0.1.2", features = ["anchor"]}
diff --git a/roll-dice/programs/roll-dice-delegated/src/lib.rs b/roll-dice/programs/roll-dice-delegated/src/lib.rs
index 476a44f..aaf71ce 100644
--- a/roll-dice/programs/roll-dice-delegated/src/lib.rs
+++ b/roll-dice/programs/roll-dice-delegated/src/lib.rs
@@ -1,12 +1,13 @@
use anchor_lang::prelude::*;
use ephemeral_vrf_sdk::anchor::vrf;
use ephemeral_vrf_sdk::instructions::{create_request_randomness_ix, RequestRandomnessParams};
+use ephemeral_vrf_sdk::types::SerializableAccountMeta;
use ephemeral_rollups_sdk::anchor::{commit, delegate, ephemeral};
use ephemeral_rollups_sdk::cpi::DelegateConfig;
use ephemeral_rollups_sdk::ephem::{commit_and_undelegate_accounts};
-declare_id!("8QudyDCGXZw8jJnV7zAm5Fsr1Suztg6Nu5YCgAf2fuWj");
+declare_id!("5bPwgoPWz274NKgThcnPas2Mv4rSknu9JrbxzFVqU5gY");
pub const PLAYER: &[u8] = b"playerd";
@@ -33,7 +34,11 @@ pub mod random_dice_delegated {
callback_program_id: ID,
callback_discriminator: instruction::CallbackRollDiceSimple::DISCRIMINATOR.to_vec(),
caller_seed: [client_seed; 32],
- accounts_metas: None,
+ accounts_metas: Some(vec![SerializableAccountMeta {
+ pubkey: ctx.accounts.player.key(),
+ is_signer: false,
+ is_writable: true,
+ }]),
..Default::default()
});
ctx.accounts
@@ -42,20 +47,28 @@ pub mod random_dice_delegated {
}
pub fn callback_roll_dice_simple(
- _ctx: Context,
+ ctx: Context,
randomness: [u8; 32],
) -> Result<()> {
let rnd_u8 = ephemeral_vrf_sdk::rnd::random_u8_with_range(&randomness, 1, 6);
msg!("Consuming random number: {:?}", rnd_u8);
+ player.rollnum = player.rollnum.saturating_add(1);
+ msg!("Roll number: {:?}", player.rollnum);
+ let player = &mut ctx.accounts.player;
+ player.last_result = rnd_u8;
Ok(())
}
// Delegate the player account to use the VRF in the ephemeral rollups
- pub fn delegate(ctx: Context) -> Result<()> {
+ pub fn delegate(ctx: Context, params: DelegateParams) -> Result<()> {
+ let config = DelegateConfig {
+ commit_frequency_ms: params.commit_frequency_ms,
+ validator: params.validator,
+ };
ctx.accounts.delegate_player(
&ctx.accounts.user,
&[PLAYER, &ctx.accounts.user.key().to_bytes().as_slice()],
- DelegateConfig::default(),
+ config,
)?;
Ok(())
}
@@ -76,7 +89,7 @@ pub mod random_dice_delegated {
pub struct Initialize<'info> {
#[account(mut)]
pub payer: Signer<'info>,
- #[account(init_if_needed, payer = payer, space = 8 + 1, seeds = [PLAYER, payer.key().to_bytes().as_slice()], bump)]
+ #[account(init_if_needed, payer = payer, space = 8 + 2, seeds = [PLAYER, payer.key().to_bytes().as_slice()], bump)]
pub player: Account<'info, Player>,
pub system_program: Program<'info, System>,
}
@@ -121,6 +134,8 @@ pub struct CallbackRollDiceSimpleCtx<'info> {
/// enforcing the callback is executed by the VRF program trough CPI
#[account(address = ephemeral_vrf_sdk::consts::VRF_PROGRAM_IDENTITY)]
pub vrf_program_identity: Signer<'info>,
+ #[account(mut)]
+ pub player: Account<'info, Player>,
}
#[delegate]
@@ -145,4 +160,11 @@ pub struct Undelegate<'info> {
#[account]
pub struct Player {
pub last_result: u8,
+ pub rollnum: u8,
+}
+
+#[derive(AnchorSerialize, AnchorDeserialize, Clone)]
+pub struct DelegateParams {
+ pub commit_frequency_ms: u32,
+ pub validator: Option,
}
\ No newline at end of file
diff --git a/roll-dice/tests/roll-dice-delegated.ts b/roll-dice/tests/roll-dice-delegated.ts
index 787631b..02dc07b 100644
--- a/roll-dice/tests/roll-dice-delegated.ts
+++ b/roll-dice/tests/roll-dice-delegated.ts
@@ -1,7 +1,17 @@
import * as anchor from "@coral-xyz/anchor";
import {Program} from "@coral-xyz/anchor";
-import { LAMPORTS_PER_SOL } from "@solana/web3.js";
+import { LAMPORTS_PER_SOL, Keypair, SystemProgram, Transaction, PublicKey } from "@solana/web3.js";
import { RandomDiceDelegated } from "../target/types/random_dice_delegated";
+import * as crypto from "crypto";
+import {
+ DELEGATION_PROGRAM_ID,
+ delegationRecordPdaFromDelegatedAccount,
+ delegationMetadataPdaFromDelegatedAccount,
+ delegateBufferPdaFromDelegatedAccountAndOwnerProgram,
+ createDelegateInstruction,
+ MAGIC_CONTEXT_ID,
+ MAGIC_PROGRAM_ID,
+} from "@magicblock-labs/ephemeral-rollups-sdk";
describe("roll-dice-delegated", () => {
// Configure the client to use the local cluster.
@@ -12,7 +22,7 @@ describe("roll-dice-delegated", () => {
const providerEphemeralRollup = new anchor.AnchorProvider(
new anchor.web3.Connection(
- process.env.PROVIDER_ENDPOINT || "https://devnet.magicblock.app/",
+ process.env.PROVIDER_ENDPOINT || "https://devnet-as.magicblock.app/",
{
wsEndpoint: process.env.WS_ENDPOINT || "wss://devnet.magicblock.app/",
}
@@ -21,33 +31,172 @@ describe("roll-dice-delegated", () => {
);
const ephemeralProgram = new Program(program.idl, providerEphemeralRollup);
- console.log("Base Layer Connection: ", provider.connection._rpcEndpoint);
- console.log("Ephemeral Rollup Connection: ", providerEphemeralRollup.connection._rpcEndpoint);
- console.log(`Current SOL Public Key: ${anchor.Wallet.local().publicKey}`)
+ const payer = anchor.Wallet.local().publicKey;
+
+ const delegatedPayerKeypair = Keypair.generate();
+
+ const [playerPda] = anchor.web3.PublicKey.findProgramAddressSync(
+ [Buffer.from("playerd"), delegatedPayerKeypair.publicKey.toBuffer()],
+ program.programId
+ );
+
+ console.log("Base Layer Connection: ", provider.connection.rpcEndpoint);
+ console.log("Ephemeral Rollup Connection: ", providerEphemeralRollup.connection.rpcEndpoint);
+ console.log(`Current SOL Public Key: ${payer}`)
+ console.log("Player PDA: ", playerPda.toString());
+ console.log("Delegated Payer Public Key: ", delegatedPayerKeypair.publicKey.toString());
before(async function () {
const balance = await provider.connection.getBalance(anchor.Wallet.local().publicKey)
console.log('Current balance is', balance / LAMPORTS_PER_SOL, ' SOL','\n')
+
+ const transferIx = SystemProgram.transfer({
+ fromPubkey: provider.wallet.publicKey,
+ toPubkey: delegatedPayerKeypair.publicKey,
+ lamports: 0.1 * LAMPORTS_PER_SOL,
+ });
+ const tx = new Transaction().add(transferIx);
+ await provider.sendAndConfirm(tx);
+ console.log("Transferred 0.1 SOL to delegated payer keypair");
})
it("Initialized player!", async () => {
- const tx = await program.methods.initialize().rpc();
+ const tx = await program.methods
+ .initialize()
+ .accounts({
+ payer: delegatedPayerKeypair.publicKey,
+ })
+ .signers([delegatedPayerKeypair])
+ .rpc();
console.log("Your transaction signature", tx);
});
it("Delegate Roll Dice!", async () => {
- const tx = await program.methods.delegate().rpc();
+ const tx = await program.methods
+ .delegate()
+ .accounts({
+ user: delegatedPayerKeypair.publicKey,
+ })
+ .signers([delegatedPayerKeypair])
+ .rpc();
console.log("Your transaction signature", tx);
});
- it("Do Roll Dice Delegated!", async () => {
- const tx = await ephemeralProgram.methods.rollDiceDelegated(0).rpc();
+ it("Delegate on-curve account", async () => {
+ const delegatedAccount = delegatedPayerKeypair.publicKey;
+
+ const assignIx = SystemProgram.assign({
+ accountPubkey: delegatedAccount,
+ programId: DELEGATION_PROGRAM_ID,
+ });
+ const assignTxHash = await provider.sendAndConfirm(new Transaction().add(assignIx), [delegatedPayerKeypair]);
+ console.log("Assign transaction signature:", assignTxHash);
+
+ const delegateIx = createDelegateInstruction({
+ payer: provider.wallet.publicKey,
+ delegatedAccount: delegatedAccount,
+ ownerProgram: SystemProgram.programId,
+ });
+ const delegateTxHash = await provider.sendAndConfirm(new Transaction().add(delegateIx), [provider.wallet.payer,delegatedPayerKeypair]);
+ console.log("Delegate transaction signature:", delegateTxHash);
+
+ await new Promise(resolve => setTimeout(resolve, 5000));
+ });
+
+ it("Do Roll Dice Delegated with delegated payer!", async () => {
+ // Create a wallet from the delegated keypair for the ephemeral provider
+ const delegatedWallet = new anchor.Wallet(delegatedPayerKeypair);
+ const ephemeralProviderWithDelegatedPayer = new anchor.AnchorProvider(
+ providerEphemeralRollup.connection,
+ delegatedWallet,
+ {}
+ );
+ const ephemeralProgramWithDelegatedPayer = new Program(program.idl, ephemeralProviderWithDelegatedPayer);
+
+ const tx = await ephemeralProgramWithDelegatedPayer.methods
+ .rollDiceDelegated(1)
+ .accounts({
+ payer: delegatedPayerKeypair.publicKey,
+ player: playerPda,
+ })
+ .rpc();
console.log("Your transaction signature", tx);
+ await new Promise(resolve => setTimeout(resolve, 5000));
});
it("Undelegate Roll Dice!", async () => {
- const tx = await ephemeralProgram.methods.undelegate().rpc();
+ const delegatedWallet = new anchor.Wallet(delegatedPayerKeypair);
+ const ephemeralProviderWithDelegatedPayer = new anchor.AnchorProvider(
+ providerEphemeralRollup.connection,
+ delegatedWallet,
+ {}
+ );
+ const ephemeralProgramWithDelegatedPayer = new Program(program.idl, ephemeralProviderWithDelegatedPayer);
+
+ const tx = await ephemeralProgramWithDelegatedPayer.methods
+ .undelegate()
+ .accounts({
+ payer: delegatedPayerKeypair.publicKey,
+ })
+ .rpc();
console.log("Your transaction signature", tx);
});
+ xit("Undelegate on-curve account", async () => {
+ const delegatedAccount = delegatedPayerKeypair.publicKey;
+ const undelegateDiscriminator = crypto
+ .createHash("sha256")
+ .update("global:undelegate")
+ .digest()
+ .slice(0, 8);
+
+ // For on-curve accounts, seeds is empty vec
+ const seedsLength = Buffer.allocUnsafe(4);
+ seedsLength.writeUInt32LE(0, 0); // empty vec length
+
+ const instructionData = Buffer.concat([
+ undelegateDiscriminator,
+ seedsLength,
+ ]);
+
+ const delegationRecordPda = delegationRecordPdaFromDelegatedAccount(delegatedAccount);
+ const delegationMetadataPda = delegationMetadataPdaFromDelegatedAccount(delegatedAccount);
+
+ const undelegateAccounts = [
+ { pubkey: providerEphemeralRollup.wallet.publicKey, isSigner: true, isWritable: true }, // payer
+ { pubkey: delegatedAccount, isSigner: false, isWritable: true }, // delegated_account
+ { pubkey: SystemProgram.programId, isSigner: false, isWritable: false }, // owner_program (system for on-curve)
+ { pubkey: delegationRecordPda, isSigner: false, isWritable: true }, // delegation_record
+ { pubkey: delegationMetadataPda, isSigner: false, isWritable: true }, // delegation_metadata
+ { pubkey: DELEGATION_PROGRAM_ID, isSigner: false, isWritable: false }, // delegation_program
+ { pubkey: MAGIC_PROGRAM_ID, isSigner: false, isWritable: false }, // magic_program
+ { pubkey: MAGIC_CONTEXT_ID, isSigner: false, isWritable: true }, // magic_context
+ ];
+
+ const undelegateIx = new anchor.web3.TransactionInstruction({
+ keys: undelegateAccounts,
+ programId: DELEGATION_PROGRAM_ID,
+ data: instructionData,
+ });
+
+ const undelegateTx = new Transaction().add(undelegateIx);
+ undelegateTx.feePayer = providerEphemeralRollup.wallet.publicKey;
+ undelegateTx.recentBlockhash = (await providerEphemeralRollup.connection.getLatestBlockhash()).blockhash;
+ const undelegateTxHash = await providerEphemeralRollup.sendAndConfirm(undelegateTx);
+ console.log("Undelegate transaction signature:", undelegateTxHash);
+
+ // After undelegation, reassign the account back to system program
+ const reassignIx = SystemProgram.assign({
+ accountPubkey: delegatedAccount,
+ programId: SystemProgram.programId,
+ });
+
+ const reassignTx = new Transaction().add(reassignIx);
+ reassignTx.feePayer = provider.wallet.publicKey;
+ reassignTx.recentBlockhash = (await provider.connection.getLatestBlockhash()).blockhash;
+ reassignTx.sign(delegatedPayerKeypair);
+ const reassignTxHash = await provider.sendAndConfirm(reassignTx, [delegatedPayerKeypair]);
+ console.log("Reassign transaction signature:", reassignTxHash);
+ });
+
});
diff --git a/roll-dice/vercel.json b/roll-dice/vercel.json
new file mode 100644
index 0000000..9ffc824
--- /dev/null
+++ b/roll-dice/vercel.json
@@ -0,0 +1,5 @@
+{
+ "buildCommand": "cd app && npm install && npm run build",
+ "outputDirectory": "app/.next"
+}
+
diff --git a/roll-dice/yarn.lock b/roll-dice/yarn.lock
index 2c20b26..11e25c2 100644
--- a/roll-dice/yarn.lock
+++ b/roll-dice/yarn.lock
@@ -41,6 +41,28 @@
bn.js "^5.1.2"
buffer-layout "^1.2.0"
+"@magicblock-labs/ephemeral-rollups-sdk@^0.4.1":
+ version "0.4.1"
+ resolved "https://registry.yarnpkg.com/@magicblock-labs/ephemeral-rollups-sdk/-/ephemeral-rollups-sdk-0.4.1.tgz#2fba50160e6a279c0eaaa02bac50e48ee8781e54"
+ integrity sha512-Te/8DIddisjci1nhUNM24EtfS2Hk3h4PXjYtGQV/MsIGq5S+Zwd04Sop8q5h05GHYbvzMAG1TqDPIArPBOBzVA==
+ dependencies:
+ "@metaplex-foundation/beet" "^0.7.2"
+ "@phala/dcap-qvl-web" "^0.2.7"
+ "@solana/web3.js" "^1.98.0"
+ bs58 "^6.0.0"
+ rpc-websockets "^9.0.4"
+ typescript "^5.3.0"
+
+"@metaplex-foundation/beet@^0.7.2":
+ version "0.7.2"
+ resolved "https://registry.yarnpkg.com/@metaplex-foundation/beet/-/beet-0.7.2.tgz#fa4726e4cfd4fb6fed6cddc9b5213c1c2a2d0b77"
+ integrity sha512-K+g3WhyFxKPc0xIvcIjNyV1eaTVJTiuaHZpig7Xx0MuYRMoJLLvhLTnUXhFdR5Tu2l2QSyKwfyXDgZlzhULqFg==
+ dependencies:
+ ansicolors "^0.3.2"
+ assert "^2.1.0"
+ bn.js "^5.2.0"
+ debug "^4.3.3"
+
"@noble/curves@^1.4.2":
version "1.8.1"
resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.8.1.tgz#19bc3970e205c99e4bdb1c64a4785706bce497ff"
@@ -53,6 +75,11 @@
resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.7.1.tgz#5738f6d765710921e7a751e00c20ae091ed8db0f"
integrity sha512-B8XBPsn4vT/KJAGqDzbwztd+6Yte3P4V7iafm24bxgDe/mlRuK6xmWPuCNrKt2vDafZ8MfJLlchDG/vYafQEjQ==
+"@phala/dcap-qvl-web@^0.2.7":
+ version "0.2.7"
+ resolved "https://registry.yarnpkg.com/@phala/dcap-qvl-web/-/dcap-qvl-web-0.2.7.tgz#d7a03b059a201355262ca9c1bb6c77a1c22472dd"
+ integrity sha512-OgDIN8ZRsLg0dJgUAk0HCXMjkAmrif7p0C+P74YrtxgE/8fNSFpqNDjVW3mCVB2Q/V7X6mUhbEQWa5wJmM9OSQ==
+
"@solana/buffer-layout@^4.0.1":
version "4.0.1"
resolved "https://registry.yarnpkg.com/@solana/buffer-layout/-/buffer-layout-4.0.1.tgz#b996235eaec15b1e0b5092a8ed6028df77fa6c15"
@@ -60,6 +87,29 @@
dependencies:
buffer "~6.0.3"
+"@solana/codecs-core@2.3.0":
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/@solana/codecs-core/-/codecs-core-2.3.0.tgz#6bf2bb565cb1ae880f8018635c92f751465d8695"
+ integrity sha512-oG+VZzN6YhBHIoSKgS5ESM9VIGzhWjEHEGNPSibiDTxFhsFWxNaz8LbMDPjBUE69r9wmdGLkrQ+wVPbnJcZPvw==
+ dependencies:
+ "@solana/errors" "2.3.0"
+
+"@solana/codecs-numbers@^2.1.0":
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/@solana/codecs-numbers/-/codecs-numbers-2.3.0.tgz#ac7e7f38aaf7fcd22ce2061fbdcd625e73828dc6"
+ integrity sha512-jFvvwKJKffvG7Iz9dmN51OGB7JBcy2CJ6Xf3NqD/VP90xak66m/Lg48T01u5IQ/hc15mChVHiBm+HHuOFDUrQg==
+ dependencies:
+ "@solana/codecs-core" "2.3.0"
+ "@solana/errors" "2.3.0"
+
+"@solana/errors@2.3.0":
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/@solana/errors/-/errors-2.3.0.tgz#4ac9380343dbeffb9dffbcb77c28d0e457c5fa31"
+ integrity sha512-66RI9MAbwYV0UtP7kGcTBVLxJgUxoZGm8Fbc0ah+lGiAw17Gugco6+9GrJCV83VyF2mDWyYnYM9qdI3yjgpnaQ==
+ dependencies:
+ chalk "^5.4.1"
+ commander "^14.0.0"
+
"@solana/web3.js@^1.69.0":
version "1.98.0"
resolved "https://registry.yarnpkg.com/@solana/web3.js/-/web3.js-1.98.0.tgz#21ecfe8198c10831df6f0cfde7f68370d0405917"
@@ -81,6 +131,27 @@
rpc-websockets "^9.0.2"
superstruct "^2.0.2"
+"@solana/web3.js@^1.98.0":
+ version "1.98.4"
+ resolved "https://registry.yarnpkg.com/@solana/web3.js/-/web3.js-1.98.4.tgz#df51d78be9d865181ec5138b4e699d48e6895bbe"
+ integrity sha512-vv9lfnvjUsRiq//+j5pBdXig0IQdtzA0BRZ3bXEP4KaIyF1CcaydWqgyzQgfZMNIsWNWmG+AUHwPy4AHOD6gpw==
+ dependencies:
+ "@babel/runtime" "^7.25.0"
+ "@noble/curves" "^1.4.2"
+ "@noble/hashes" "^1.4.0"
+ "@solana/buffer-layout" "^4.0.1"
+ "@solana/codecs-numbers" "^2.1.0"
+ agentkeepalive "^4.5.0"
+ bn.js "^5.2.1"
+ borsh "^0.7.0"
+ bs58 "^4.0.1"
+ buffer "6.0.3"
+ fast-stable-stringify "^1.0.0"
+ jayson "^4.1.1"
+ node-fetch "^2.7.0"
+ rpc-websockets "^9.0.2"
+ superstruct "^2.0.2"
+
"@swc/helpers@^0.5.11":
version "0.5.15"
resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.5.15.tgz#79efab344c5819ecf83a43f3f9f811fc84b516d7"
@@ -185,6 +256,11 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0:
dependencies:
color-convert "^2.0.1"
+ansicolors@^0.3.2:
+ version "0.3.2"
+ resolved "https://registry.yarnpkg.com/ansicolors/-/ansicolors-0.3.2.tgz#665597de86a9ffe3aa9bfbe6cae5c6ea426b4979"
+ integrity sha512-QXu7BPrP29VllRxH8GwB7x5iX5qWKAAMLqKQGWTeLWVlNHNOpVMJ91dsxQAIWXpjuW5wqvxu3Jd/nRjrJ+0pqg==
+
anymatch@~3.1.2:
version "3.1.3"
resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e"
@@ -203,11 +279,29 @@ arrify@^1.0.0:
resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d"
integrity sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==
+assert@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/assert/-/assert-2.1.0.tgz#6d92a238d05dc02e7427c881fb8be81c8448b2dd"
+ integrity sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw==
+ dependencies:
+ call-bind "^1.0.2"
+ is-nan "^1.3.2"
+ object-is "^1.1.5"
+ object.assign "^4.1.4"
+ util "^0.12.5"
+
assertion-error@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b"
integrity sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==
+available-typed-arrays@^1.0.7:
+ version "1.0.7"
+ resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz#a5cc375d6a03c2efc87a553f3e0b1522def14846"
+ integrity sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==
+ dependencies:
+ possible-typed-array-names "^1.0.0"
+
balanced-match@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
@@ -220,6 +314,11 @@ base-x@^3.0.2:
dependencies:
safe-buffer "^5.0.1"
+base-x@^5.0.0:
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/base-x/-/base-x-5.0.1.tgz#16bf35254be1df8aca15e36b7c1dda74b2aa6b03"
+ integrity sha512-M7uio8Zt++eg3jPj+rHMfCC+IuygQHHCOU+IYsVtik6FWjuYpVt/+MRKcgsAMHh8mMFAwnB+Bs+mTrFiXjMzKg==
+
base64-js@^1.3.1:
version "1.5.1"
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
@@ -285,6 +384,13 @@ bs58@^4.0.0, bs58@^4.0.1:
dependencies:
base-x "^3.0.2"
+bs58@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/bs58/-/bs58-6.0.0.tgz#a2cda0130558535dd281a2f8697df79caaf425d8"
+ integrity sha512-PD0wEnEYg6ijszw/u8s+iI3H17cTymlrwkKhDhPZq+Sokl3AU4htyBFTjAeNAlCCmg0f53g6ih3jATyCKftTfw==
+ dependencies:
+ base-x "^5.0.0"
+
buffer-from@^1.0.0, buffer-from@^1.1.0:
version "1.1.2"
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5"
@@ -310,6 +416,32 @@ bufferutil@^4.0.1:
dependencies:
node-gyp-build "^4.3.0"
+call-bind-apply-helpers@^1.0.0, call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz#4b5428c222be985d79c3d82657479dbe0b59b2d6"
+ integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==
+ dependencies:
+ es-errors "^1.3.0"
+ function-bind "^1.1.2"
+
+call-bind@^1.0.0, call-bind@^1.0.2, call-bind@^1.0.7, call-bind@^1.0.8:
+ version "1.0.8"
+ resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.8.tgz#0736a9660f537e3388826f440d5ec45f744eaa4c"
+ integrity sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==
+ dependencies:
+ call-bind-apply-helpers "^1.0.0"
+ es-define-property "^1.0.0"
+ get-intrinsic "^1.2.4"
+ set-function-length "^1.2.2"
+
+call-bound@^1.0.2, call-bound@^1.0.3, call-bound@^1.0.4:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/call-bound/-/call-bound-1.0.4.tgz#238de935d2a2a692928c538c7ccfa91067fd062a"
+ integrity sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==
+ dependencies:
+ call-bind-apply-helpers "^1.0.2"
+ get-intrinsic "^1.3.0"
+
camelcase@^6.0.0, camelcase@^6.3.0:
version "6.3.0"
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a"
@@ -336,6 +468,11 @@ chalk@^4.1.0:
ansi-styles "^4.1.0"
supports-color "^7.1.0"
+chalk@^5.4.1:
+ version "5.6.2"
+ resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.6.2.tgz#b1238b6e23ea337af71c7f8a295db5af0c158aea"
+ integrity sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==
+
check-error@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.3.tgz#a6502e4312a7ee969f646e83bb3ddd56281bd694"
@@ -379,6 +516,11 @@ color-name@~1.1.4:
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
+commander@^14.0.0:
+ version "14.0.2"
+ resolved "https://registry.yarnpkg.com/commander/-/commander-14.0.2.tgz#b71fd37fe4069e4c3c7c13925252ada4eba14e8e"
+ integrity sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==
+
commander@^2.20.3:
version "2.20.3"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
@@ -403,6 +545,13 @@ debug@4.3.3:
dependencies:
ms "2.1.2"
+debug@^4.3.3:
+ version "4.4.3"
+ resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a"
+ integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==
+ dependencies:
+ ms "^2.1.3"
+
decamelize@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-4.0.0.tgz#aa472d7bf660eb15f3494efd531cab7f2a709837"
@@ -415,6 +564,24 @@ deep-eql@^4.1.3:
dependencies:
type-detect "^4.0.0"
+define-data-property@^1.0.1, define-data-property@^1.1.4:
+ version "1.1.4"
+ resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e"
+ integrity sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==
+ dependencies:
+ es-define-property "^1.0.0"
+ es-errors "^1.3.0"
+ gopd "^1.0.1"
+
+define-properties@^1.1.3, define-properties@^1.2.1:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.1.tgz#10781cc616eb951a80a034bafcaa7377f6af2b6c"
+ integrity sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==
+ dependencies:
+ define-data-property "^1.0.1"
+ has-property-descriptors "^1.0.0"
+ object-keys "^1.1.1"
+
delay@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/delay/-/delay-5.0.0.tgz#137045ef1b96e5071060dd5be60bf9334436bd1d"
@@ -430,11 +597,37 @@ diff@^3.1.0:
resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12"
integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==
+dunder-proto@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a"
+ integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==
+ dependencies:
+ call-bind-apply-helpers "^1.0.1"
+ es-errors "^1.3.0"
+ gopd "^1.2.0"
+
emoji-regex@^8.0.0:
version "8.0.0"
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==
+es-define-property@^1.0.0, es-define-property@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa"
+ integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==
+
+es-errors@^1.3.0:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f"
+ integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==
+
+es-object-atoms@^1.0.0, es-object-atoms@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz#1c4f2c4837327597ce69d2ca190a7fdd172338c1"
+ integrity sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==
+ dependencies:
+ es-errors "^1.3.0"
+
es6-promise@^4.0.3:
version "4.2.8"
resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a"
@@ -502,6 +695,13 @@ flat@^5.0.2:
resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241"
integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==
+for-each@^0.3.5:
+ version "0.3.5"
+ resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.5.tgz#d650688027826920feeb0af747ee7b9421a41d47"
+ integrity sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==
+ dependencies:
+ is-callable "^1.2.7"
+
fs.realpath@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
@@ -512,6 +712,16 @@ fsevents@~2.3.2:
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6"
integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==
+function-bind@^1.1.2:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c"
+ integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==
+
+generator-function@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/generator-function/-/generator-function-2.0.1.tgz#0e75dd410d1243687a0ba2e951b94eedb8f737a2"
+ integrity sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==
+
get-caller-file@^2.0.5:
version "2.0.5"
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
@@ -522,6 +732,30 @@ get-func-name@^2.0.1, get-func-name@^2.0.2:
resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.2.tgz#0d7cf20cd13fda808669ffa88f4ffc7a3943fc41"
integrity sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==
+get-intrinsic@^1.2.4, get-intrinsic@^1.3.0:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01"
+ integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==
+ dependencies:
+ call-bind-apply-helpers "^1.0.2"
+ es-define-property "^1.0.1"
+ es-errors "^1.3.0"
+ es-object-atoms "^1.1.1"
+ function-bind "^1.1.2"
+ get-proto "^1.0.1"
+ gopd "^1.2.0"
+ has-symbols "^1.1.0"
+ hasown "^2.0.2"
+ math-intrinsics "^1.1.0"
+
+get-proto@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1"
+ integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==
+ dependencies:
+ dunder-proto "^1.0.1"
+ es-object-atoms "^1.0.0"
+
glob-parent@~5.1.2:
version "5.1.2"
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
@@ -541,6 +775,11 @@ glob@7.2.0:
once "^1.3.0"
path-is-absolute "^1.0.0"
+gopd@^1.0.1, gopd@^1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1"
+ integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==
+
growl@1.10.5:
version "1.10.5"
resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e"
@@ -551,6 +790,32 @@ has-flag@^4.0.0:
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b"
integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
+has-property-descriptors@^1.0.0, has-property-descriptors@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz#963ed7d071dc7bf5f084c5bfbe0d1b6222586854"
+ integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==
+ dependencies:
+ es-define-property "^1.0.0"
+
+has-symbols@^1.0.3, has-symbols@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338"
+ integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==
+
+has-tostringtag@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc"
+ integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==
+ dependencies:
+ has-symbols "^1.0.3"
+
+hasown@^2.0.2:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003"
+ integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==
+ dependencies:
+ function-bind "^1.1.2"
+
he@1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
@@ -576,11 +841,19 @@ inflight@^1.0.4:
once "^1.3.0"
wrappy "1"
-inherits@2:
+inherits@2, inherits@^2.0.3:
version "2.0.4"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
+is-arguments@^1.0.4:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.2.0.tgz#ad58c6aecf563b78ef2bf04df540da8f5d7d8e1b"
+ integrity sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==
+ dependencies:
+ call-bound "^1.0.2"
+ has-tostringtag "^1.0.2"
+
is-binary-path@~2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09"
@@ -588,6 +861,11 @@ is-binary-path@~2.1.0:
dependencies:
binary-extensions "^2.0.0"
+is-callable@^1.2.7:
+ version "1.2.7"
+ resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055"
+ integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==
+
is-extglob@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
@@ -598,6 +876,17 @@ is-fullwidth-code-point@^3.0.0:
resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d"
integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==
+is-generator-function@^1.0.7:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.1.2.tgz#ae3b61e3d5ea4e4839b90bad22b02335051a17d5"
+ integrity sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==
+ dependencies:
+ call-bound "^1.0.4"
+ generator-function "^2.0.0"
+ get-proto "^1.0.1"
+ has-tostringtag "^1.0.2"
+ safe-regex-test "^1.1.0"
+
is-glob@^4.0.1, is-glob@~4.0.1:
version "4.0.3"
resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084"
@@ -605,6 +894,14 @@ is-glob@^4.0.1, is-glob@~4.0.1:
dependencies:
is-extglob "^2.1.1"
+is-nan@^1.3.2:
+ version "1.3.2"
+ resolved "https://registry.yarnpkg.com/is-nan/-/is-nan-1.3.2.tgz#043a54adea31748b55b6cd4e09aadafa69bd9e1d"
+ integrity sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==
+ dependencies:
+ call-bind "^1.0.0"
+ define-properties "^1.1.3"
+
is-number@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
@@ -615,6 +912,23 @@ is-plain-obj@^2.1.0:
resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287"
integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==
+is-regex@^1.2.1:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.2.1.tgz#76d70a3ed10ef9be48eb577887d74205bf0cad22"
+ integrity sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==
+ dependencies:
+ call-bound "^1.0.2"
+ gopd "^1.2.0"
+ has-tostringtag "^1.0.2"
+ hasown "^2.0.2"
+
+is-typed-array@^1.1.3:
+ version "1.1.15"
+ resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.15.tgz#4bfb4a45b61cee83a5a46fba778e4e8d59c0ce0b"
+ integrity sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==
+ dependencies:
+ which-typed-array "^1.1.16"
+
is-unicode-supported@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7"
@@ -699,6 +1013,11 @@ make-error@^1.1.1:
resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2"
integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==
+math-intrinsics@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9"
+ integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==
+
minimatch@4.2.1:
version "4.2.1"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-4.2.1.tgz#40d9d511a46bdc4e563c22c3080cde9c0d8299b4"
@@ -760,7 +1079,7 @@ ms@2.1.2:
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
-ms@2.1.3, ms@^2.0.0:
+ms@2.1.3, ms@^2.0.0, ms@^2.1.3:
version "2.1.3"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
@@ -787,6 +1106,31 @@ normalize-path@^3.0.0, normalize-path@~3.0.0:
resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
+object-is@^1.1.5:
+ version "1.1.6"
+ resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.6.tgz#1a6a53aed2dd8f7e6775ff870bea58545956ab07"
+ integrity sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==
+ dependencies:
+ call-bind "^1.0.7"
+ define-properties "^1.2.1"
+
+object-keys@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e"
+ integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==
+
+object.assign@^4.1.4:
+ version "4.1.7"
+ resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.7.tgz#8c14ca1a424c6a561b0bb2a22f66f5049a945d3d"
+ integrity sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==
+ dependencies:
+ call-bind "^1.0.8"
+ call-bound "^1.0.3"
+ define-properties "^1.2.1"
+ es-object-atoms "^1.0.0"
+ has-symbols "^1.1.0"
+ object-keys "^1.1.1"
+
once@^1.3.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
@@ -833,6 +1177,11 @@ picomatch@^2.0.4, picomatch@^2.2.1:
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
+possible-typed-array-names@^1.0.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz#93e3582bc0e5426586d9d07b79ee40fc841de4ae"
+ integrity sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==
+
prettier@^2.6.2:
version "2.8.8"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da"
@@ -878,11 +1227,36 @@ rpc-websockets@^9.0.2:
bufferutil "^4.0.1"
utf-8-validate "^5.0.2"
+rpc-websockets@^9.0.4:
+ version "9.3.1"
+ resolved "https://registry.yarnpkg.com/rpc-websockets/-/rpc-websockets-9.3.1.tgz#d817a59d812f68bae1215740a3f78fcdd3813698"
+ integrity sha512-bY6a+i/lEtBJ/mUxwsCTgevoV1P0foXTVA7UoThzaIWbM+3NDqorf8NBWs5DmqKTFeA1IoNzgvkWjFCPgnzUiQ==
+ dependencies:
+ "@swc/helpers" "^0.5.11"
+ "@types/uuid" "^8.3.4"
+ "@types/ws" "^8.2.2"
+ buffer "^6.0.3"
+ eventemitter3 "^5.0.1"
+ uuid "^8.3.2"
+ ws "^8.5.0"
+ optionalDependencies:
+ bufferutil "^4.0.1"
+ utf-8-validate "^5.0.2"
+
safe-buffer@^5.0.1, safe-buffer@^5.1.0:
version "5.2.1"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
+safe-regex-test@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.1.0.tgz#7f87dfb67a3150782eaaf18583ff5d1711ac10c1"
+ integrity sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==
+ dependencies:
+ call-bound "^1.0.2"
+ es-errors "^1.3.0"
+ is-regex "^1.2.1"
+
serialize-javascript@6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.0.tgz#efae5d88f45d7924141da8b5c3a7a7e663fefeb8"
@@ -890,6 +1264,18 @@ serialize-javascript@6.0.0:
dependencies:
randombytes "^2.1.0"
+set-function-length@^1.2.2:
+ version "1.2.2"
+ resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449"
+ integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==
+ dependencies:
+ define-data-property "^1.1.4"
+ es-errors "^1.3.0"
+ function-bind "^1.1.2"
+ get-intrinsic "^1.2.4"
+ gopd "^1.0.1"
+ has-property-descriptors "^1.0.2"
+
source-map-support@^0.5.6:
version "0.5.21"
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f"
@@ -1023,6 +1409,11 @@ type-detect@^4.0.0, type-detect@^4.1.0:
resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.1.0.tgz#deb2453e8f08dcae7ae98c626b13dddb0155906c"
integrity sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==
+typescript@^5.3.0:
+ version "5.9.3"
+ resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.9.3.tgz#5b4f59e15310ab17a216f5d6cf53ee476ede670f"
+ integrity sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==
+
typescript@^5.7.3:
version "5.8.2"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.8.2.tgz#8170b3702f74b79db2e5a96207c15e65807999e4"
@@ -1040,6 +1431,17 @@ utf-8-validate@^5.0.2:
dependencies:
node-gyp-build "^4.3.0"
+util@^0.12.5:
+ version "0.12.5"
+ resolved "https://registry.yarnpkg.com/util/-/util-0.12.5.tgz#5f17a6059b73db61a875668781a1c2b136bd6fbc"
+ integrity sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==
+ dependencies:
+ inherits "^2.0.3"
+ is-arguments "^1.0.4"
+ is-generator-function "^1.0.7"
+ is-typed-array "^1.1.3"
+ which-typed-array "^1.1.2"
+
uuid@^8.3.2:
version "8.3.2"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
@@ -1058,6 +1460,19 @@ whatwg-url@^5.0.0:
tr46 "~0.0.3"
webidl-conversions "^3.0.0"
+which-typed-array@^1.1.16, which-typed-array@^1.1.2:
+ version "1.1.19"
+ resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.19.tgz#df03842e870b6b88e117524a4b364b6fc689f956"
+ integrity sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==
+ dependencies:
+ available-typed-arrays "^1.0.7"
+ call-bind "^1.0.8"
+ call-bound "^1.0.4"
+ for-each "^0.3.5"
+ get-proto "^1.0.1"
+ gopd "^1.2.0"
+ has-tostringtag "^1.0.2"
+
which@2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1"