diff --git a/README.md b/README.md
index bd7aa98..e3b757e 100644
--- a/README.md
+++ b/README.md
@@ -9,6 +9,8 @@ Read more about Ephemeral Rollups [here](https://docs.magicblock.gg/EphemeralRol
> To view integrated demos for specific usecases, please look at [MagicBlock Starter Kits](https://github.com/magicblock-labs/starter-kits).
+Examples with a deployed frontend link to their **🌐 Live demo** below — see [Live Demos](#-live-demos) for the full list.
+
## 👷 Examples
### 🧱 Basic Examples
@@ -24,6 +26,7 @@ Core feature demos like delegation, randomness and privacy - the place to start.
Counter program in Anchor.
+🌐 Live demo
@@ -54,6 +57,7 @@ Core feature demos like delegation, randomness and privacy - the place to start.
Anchor counter with permissions.
+🌐 Live demo
|
@@ -74,6 +78,7 @@ Core feature demos like delegation, randomness and privacy - the place to start.
SPL token delegation, ER transfers, and withdrawal back to base layer.
+🌐 Live demo
|
@@ -86,6 +91,7 @@ Core feature demos like delegation, randomness and privacy - the place to start.
Dice roll using a verifiable random function (VRF) on the ER.
+🌐 Live demo
@@ -180,6 +186,7 @@ Focused demos of individual capabilities — on-curve delegation, actions, ephem
Counter using gpl-session keys for delegated-signer auth on both base chain and ER.
+🌐 Live demo
|
@@ -193,6 +200,7 @@ Focused demos of individual capabilities — on-curve delegation, actions, ephem
Up/down SPL-token prediction flow using session keys and an ER price oracle.
+🌐 Live demo
@@ -234,6 +242,7 @@ End-to-end app templates you can fork and build on.
Rewards distribution program using delegated VRF.
+🌐 Live demo
|
@@ -244,6 +253,7 @@ End-to-end app templates you can fork and build on.
Two-player RPS with hidden moves on the ER until reveal.
+🌐 Live demo
|
@@ -262,6 +272,21 @@ End-to-end app templates you can fork and build on.
+## 🌐 Live Demos
+
+Deployed frontends for the examples that ship a UI.
+
+| Example | Live demo |
+| ------- | --------- |
+| [Counter / Anchor](./counter/anchor/) | https://counter-example.magicblock.app |
+| [Private Counter / Anchor](./private-counter/anchor/) | https://private-counter-example.magicblock.app |
+| [SPL Tokens / Anchor](./spl-tokens/anchor/) | https://spl-demo.magicblock.app |
+| [Roll Dice / Anchor](./roll-dice/anchor/) | https://roll-dice.magicblock.app |
+| [Session Keys / Anchor](./session-keys/anchor/) | https://counter-session-keys-example.magicblock.app |
+| [Binary Prediction / Anchor](./binary-prediction/anchor/) | https://binary-prediction.magicblock.app |
+| [Rewards (Delegated VRF) / Anchor](./rewards-delegated-vrf/anchor/) | https://magicblock-rewards-dashboard.vercel.app |
+| [Rock Paper Scissor / Anchor](./rock-paper-scissor/anchor/) | https://rps-example.magicblock.app |
+
## Testing
To run local tests for any example project, use the following steps:
diff --git a/binary-prediction/anchor/README.md b/binary-prediction/anchor/README.md
index b2ba3b6..715ad88 100644
--- a/binary-prediction/anchor/README.md
+++ b/binary-prediction/anchor/README.md
@@ -2,6 +2,8 @@
Anchor example for a binary up/down prediction flow on MagicBlock Ephemeral Rollups.
+🌐 **Live demo:** https://binary-prediction.magicblock.app
+
The example shows a user staking SPL tokens on a directional price move. The program reads a
MagicBlock `ephemeral-oracle` price feed on the ER, snapshots the opening price at bet time, and
settles against the same feed after a short per-bet expiry. Correct calls pay the pool's configured
diff --git a/counter/anchor/README.md b/counter/anchor/README.md
index da83d0c..5fceb5a 100644
--- a/counter/anchor/README.md
+++ b/counter/anchor/README.md
@@ -2,6 +2,8 @@
Simple counter program using Anchor and Ephemeral Rollups.
+🌐 **Live demo:** https://counter-example.magicblock.app
+
## Software Packages
This program has utilized the following software packages.
diff --git a/private-counter/anchor/README.md b/private-counter/anchor/README.md
index d36e4eb..f0a1189 100644
--- a/private-counter/anchor/README.md
+++ b/private-counter/anchor/README.md
@@ -2,6 +2,8 @@
Counter program using Anchor, Ephemeral Rollups, and **private ephemeral permissions**. The counter PDA is delegated to the ER; access to delegated state is gated by an on-rollup permission account that only the counter authority (and configured members) can use.
+🌐 **Live demo:** https://private-counter-example.magicblock.app
+
## Overview
| Layer | What happens |
diff --git a/rewards-delegated-vrf/anchor/README.md b/rewards-delegated-vrf/anchor/README.md
index 091e0bf..7030be0 100644
--- a/rewards-delegated-vrf/anchor/README.md
+++ b/rewards-delegated-vrf/anchor/README.md
@@ -2,6 +2,8 @@
Anchor rewards program that mints random rewards on an Ephemeral Rollup using MagicBlock's verifiable random function (VRF).
+🌐 **Live demo (rewards dashboard):** https://magicblock-rewards-dashboard.vercel.app
+
## VRF Flow
A reward account is delegated to the Ephemeral Rollup. On the ER, the program requests randomness from the VRF oracle; the oracle fulfills the request by invoking the program's callback instruction with verified random bytes, which the program uses to determine the reward. State is then committed back to the base layer.
diff --git a/rewards-delegated-vrf/anchor/dashboard/lib/clusterContext.ts b/rewards-delegated-vrf/anchor/dashboard/lib/clusterContext.ts
index 0508c9d..560ab55 100644
--- a/rewards-delegated-vrf/anchor/dashboard/lib/clusterContext.ts
+++ b/rewards-delegated-vrf/anchor/dashboard/lib/clusterContext.ts
@@ -3,10 +3,7 @@
* Provides cluster information and utilities for the application
*/
-import {
- SOLANA_DEVNET_ENDPOINT,
- SOLANA_MAINNET_ENDPOINT,
-} from "./endpoints";
+import { SOLANA_DEVNET_ENDPOINT, SOLANA_MAINNET_ENDPOINT } from "./endpoints";
export { getBaseLayerSolanaEndpoint } from "./endpoints";
@@ -161,4 +158,3 @@ export function getDefaultSolanaEndpoint(): string {
export function getDefaultMagicBlockErEndpoint(): string {
return CLUSTER_CONFIG["https://devnet-as.magicblock.app/"].endpoint;
}
-
diff --git a/rock-paper-scissor/anchor/README.md b/rock-paper-scissor/anchor/README.md
index 8ff5edc..72c72dd 100644
--- a/rock-paper-scissor/anchor/README.md
+++ b/rock-paper-scissor/anchor/README.md
@@ -2,6 +2,8 @@
A confidential Rock Paper Scissor game built on Solana using Anchor and MagicBlock's Ephemeral Rollups SDK. This example demonstrates how to implement a two-player game with hidden choices that remain private during gameplay until the winner is revealed.
+🌐 **Live demo:** https://rps-example.magicblock.app
+
## Overview
This project showcases:
diff --git a/roll-dice/anchor/README.md b/roll-dice/anchor/README.md
index 4cce3b2..35c0881 100644
--- a/roll-dice/anchor/README.md
+++ b/roll-dice/anchor/README.md
@@ -10,7 +10,7 @@ The dice account is delegated to the Ephemeral Rollup. On the ER, the program re
-[https://roll-dice-demo.vercel.app/](https://roll-dice-demo.vercel.app/)
+🌐 **Live demo:** https://roll-dice.magicblock.app
## Software Packages
diff --git a/roll-dice/anchor/app/lib/roll-result.test.ts b/roll-dice/anchor/app/lib/roll-result.test.ts
index eb453df..1422b9e 100644
--- a/roll-dice/anchor/app/lib/roll-result.test.ts
+++ b/roll-dice/anchor/app/lib/roll-result.test.ts
@@ -1,75 +1,82 @@
-import assert from "node:assert/strict"
-import { describe, it } from "node:test"
+import assert from "node:assert/strict";
+import { describe, it } from "node:test";
import {
requiresLiveRollCorrelation,
shouldCompleteRoll,
type RollResultSource,
-} from "./roll-result.ts"
+} from "./roll-result.ts";
const completion = (
overrides: Partial[0]> = {},
-) => shouldCompleteRoll({
- source: "subscription",
- isPending: true,
- activeGeneration: 4,
- startRollnum: 12,
- newRollnum: 13,
- newValue: 6,
- hasRequestSignature: true,
- requestSlot: null,
- observedSlot: 101,
- ...overrides,
-})
+) =>
+ shouldCompleteRoll({
+ source: "subscription",
+ isPending: true,
+ activeGeneration: 4,
+ startRollnum: 12,
+ newRollnum: 13,
+ newValue: 6,
+ hasRequestSignature: true,
+ requestSlot: null,
+ observedSlot: 101,
+ ...overrides,
+ });
describe("shouldCompleteRoll", () => {
it("uses a rollnum-advancing account subscription as the fast path", () => {
- assert.equal(completion(), true)
- })
+ assert.equal(completion(), true);
+ });
it("rejects stale, unchanged, and pre-request subscription updates", () => {
- assert.equal(completion({ newRollnum: 12 }), false)
- assert.equal(completion({ newRollnum: 11 }), false)
- assert.equal(completion({ hasRequestSignature: false }), false)
- assert.equal(completion({ startRollnum: null }), false)
- assert.equal(completion({ requestSlot: 102, observedSlot: 101 }), false)
- assert.equal(completion({ requestSlot: 101, observedSlot: 101 }), true)
- })
+ assert.equal(completion({ newRollnum: 12 }), false);
+ assert.equal(completion({ newRollnum: 11 }), false);
+ assert.equal(completion({ hasRequestSignature: false }), false);
+ assert.equal(completion({ startRollnum: null }), false);
+ assert.equal(completion({ requestSlot: 102, observedSlot: 101 }), false);
+ assert.equal(completion({ requestSlot: 101, observedSlot: 101 }), true);
+ });
it("does not let sync, polling, or warm-up updates complete a user roll", () => {
for (const source of ["sync", "poll"] satisfies RollResultSource[]) {
- assert.equal(completion({ source }), false)
+ assert.equal(completion({ source }), false);
}
- assert.equal(completion({ isPending: false }), false)
- })
+ assert.equal(completion({ isPending: false }), false);
+ });
it("keeps a saturated counter on the correlated callback path", () => {
- assert.equal(completion({ startRollnum: 255, newRollnum: 255 }), false)
- })
+ assert.equal(completion({ startRollnum: 255, newRollnum: 255 }), false);
+ });
it("requires the callback to match the active roll generation", () => {
- assert.equal(completion({
- source: "callback",
- observedGeneration: 4,
- startRollnum: 255,
- newRollnum: 255,
- }), true)
- assert.equal(completion({
- source: "callback",
- observedGeneration: 3,
- startRollnum: 255,
- newRollnum: 255,
- }), false)
- })
-})
+ assert.equal(
+ completion({
+ source: "callback",
+ observedGeneration: 4,
+ startRollnum: 255,
+ newRollnum: 255,
+ }),
+ true,
+ );
+ assert.equal(
+ completion({
+ source: "callback",
+ observedGeneration: 3,
+ startRollnum: 255,
+ newRollnum: 255,
+ }),
+ false,
+ );
+ });
+});
describe("requiresLiveRollCorrelation", () => {
it("keeps ordinary single-in-flight rolls on the account-only hot path", () => {
- assert.equal(requiresLiveRollCorrelation(96, 1), false)
- })
+ assert.equal(requiresLiveRollCorrelation(96, 1), false);
+ });
it("enables live correlation for ambiguous account updates", () => {
- assert.equal(requiresLiveRollCorrelation(null, 1), true)
- assert.equal(requiresLiveRollCorrelation(255, 1), true)
- assert.equal(requiresLiveRollCorrelation(96, 2), true)
- })
-})
+ assert.equal(requiresLiveRollCorrelation(null, 1), true);
+ assert.equal(requiresLiveRollCorrelation(255, 1), true);
+ assert.equal(requiresLiveRollCorrelation(96, 2), true);
+ });
+});
diff --git a/roll-dice/anchor/app/lib/roll-result.ts b/roll-dice/anchor/app/lib/roll-result.ts
index 1018264..f407100 100644
--- a/roll-dice/anchor/app/lib/roll-result.ts
+++ b/roll-dice/anchor/app/lib/roll-result.ts
@@ -1,26 +1,26 @@
-export type RollResultSource = "subscription" | "sync" | "poll" | "callback"
+export type RollResultSource = "subscription" | "sync" | "poll" | "callback";
export function requiresLiveRollCorrelation(
startRollnum: number | null,
unavailableClientSeeds: number,
): boolean {
- return startRollnum === null ||
- startRollnum >= 255 ||
- unavailableClientSeeds > 1
+ return (
+ startRollnum === null || startRollnum >= 255 || unavailableClientSeeds > 1
+ );
}
type RollResultCompletion = {
- source: RollResultSource
- isPending: boolean
- activeGeneration: number
- observedGeneration?: number
- startRollnum: number | null
- newRollnum: number
- newValue: number
- hasRequestSignature: boolean
- requestSlot: number | null
- observedSlot?: number
-}
+ source: RollResultSource;
+ isPending: boolean;
+ activeGeneration: number;
+ observedGeneration?: number;
+ startRollnum: number | null;
+ newRollnum: number;
+ newValue: number;
+ hasRequestSignature: boolean;
+ requestSlot: number | null;
+ observedSlot?: number;
+};
/**
* Account subscriptions are the lowest-latency signal, but the player account
@@ -43,10 +43,10 @@ export function shouldCompleteRoll({
requestSlot,
observedSlot,
}: RollResultCompletion): boolean {
- if (!isPending || newValue <= 0) return false
+ if (!isPending || newValue <= 0) return false;
if (source === "callback") {
- return observedGeneration === activeGeneration
+ return observedGeneration === activeGeneration;
}
if (
@@ -55,9 +55,12 @@ export function shouldCompleteRoll({
startRollnum === null ||
startRollnum >= 255 ||
newRollnum <= startRollnum
- ) return false
+ )
+ return false;
- return requestSlot === null ||
+ return (
+ requestSlot === null ||
observedSlot === undefined ||
observedSlot >= requestSlot
+ );
}
diff --git a/session-keys/anchor/README.md b/session-keys/anchor/README.md
index 36ce09d..c803d25 100644
--- a/session-keys/anchor/README.md
+++ b/session-keys/anchor/README.md
@@ -2,6 +2,8 @@
Counter program using Anchor and Ephemeral Rollups, authorized with session keys so a temporary signer can act on behalf of the counter authority without re-signing every transaction.
+🌐 **Live demo:** https://counter-session-keys-example.magicblock.app
+
## Software Packages
This program has utilized the following software packages.
diff --git a/spl-tokens/anchor/README.md b/spl-tokens/anchor/README.md
index 25f556b..4f2b7b9 100644
--- a/spl-tokens/anchor/README.md
+++ b/spl-tokens/anchor/README.md
@@ -6,6 +6,8 @@ balances back to the base layer.
Includes a React UI in [`app/`](./app/README.md) for interactive demos.
+🌐 **Live demo:** https://spl-demo.magicblock.app
+
This folder does **not** implement the Ephemeral SPL Token program itself. The
local test harness preloads that program at
`SPLxh1LVZzEkX99H6rqYizhytLWPZVV296zyYDPagv2` from