Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -24,6 +26,7 @@ Core feature demos like delegation, randomness and privacy - the place to start.
<img src="https://img.shields.io/badge/Anchor-5243AA?style=flat-square" alt="Anchor"/>
</p>
<p><em>Counter program in Anchor.</em></p>
<p>🌐 <a href="https://counter-example.magicblock.app">Live demo</a></p>
</blockquote>
</td>
<td valign="top" width="33%">
Expand Down Expand Up @@ -54,6 +57,7 @@ Core feature demos like delegation, randomness and privacy - the place to start.
<img src="https://img.shields.io/badge/Privacy-9333ea?style=flat-square" alt="Privacy"/>
</p>
<p><em>Anchor counter with permissions.</em></p>
<p>🌐 <a href="https://private-counter-example.magicblock.app">Live demo</a></p>
</blockquote>
</td>
<td valign="top" width="33%">
Expand All @@ -74,6 +78,7 @@ Core feature demos like delegation, randomness and privacy - the place to start.
<img src="https://img.shields.io/badge/SPL%20Token-9945FF?style=flat-square" alt="Tokens"/>
</p>
<p><em>SPL token delegation, ER transfers, and withdrawal back to base layer.</em></p>
<p>🌐 <a href="https://spl-demo.magicblock.app">Live demo</a></p>
</blockquote>
</td>
</tr>
Expand All @@ -86,6 +91,7 @@ Core feature demos like delegation, randomness and privacy - the place to start.
<img src="https://img.shields.io/badge/VRF-4f46e5?style=flat-square" alt="VRF"/>
</p>
<p><em>Dice roll using a verifiable random function (VRF) on the ER.</em></p>
<p>🌐 <a href="https://roll-dice.magicblock.app">Live demo</a></p>
</blockquote>
</td>
<td valign="top" width="33%">
Expand Down Expand Up @@ -180,6 +186,7 @@ Focused demos of individual capabilities — on-curve delegation, actions, ephem
<img src="https://img.shields.io/badge/Session%20Keys-6366f1?style=flat-square" alt="Session Keys"/>
</p>
<p><em>Counter using gpl-session keys for delegated-signer auth on both base chain and ER.</em></p>
<p>🌐 <a href="https://counter-session-keys-example.magicblock.app">Live demo</a></p>
</blockquote>
</td>
</tr>
Expand All @@ -193,6 +200,7 @@ Focused demos of individual capabilities — on-curve delegation, actions, ephem
<img src="https://img.shields.io/badge/Session%20Keys-6366f1?style=flat-square" alt="Session Keys"/>
</p>
<p><em>Up/down SPL-token prediction flow using session keys and an ER price oracle.</em></p>
<p>🌐 <a href="https://binary-prediction.magicblock.app">Live demo</a></p>
</blockquote>
</td>
<td valign="top" width="33%">
Expand Down Expand Up @@ -234,6 +242,7 @@ End-to-end app templates you can fork and build on.
<img src="https://img.shields.io/badge/VRF-4f46e5?style=flat-square" alt="VRF"/>
</p>
<p><em>Rewards distribution program using delegated VRF.</em></p>
<p>🌐 <a href="https://magicblock-rewards-dashboard.vercel.app">Live demo</a></p>
</blockquote>
</td>
<td valign="top" width="33%">
Expand All @@ -244,6 +253,7 @@ End-to-end app templates you can fork and build on.
<img src="https://img.shields.io/badge/Privacy-9333ea?style=flat-square" alt="Privacy"/>
</p>
<p><em>Two-player RPS with hidden moves on the ER until reveal.</em></p>
<p>🌐 <a href="https://rps-example.magicblock.app">Live demo</a></p>
</blockquote>
</td>
</tr>
Expand All @@ -262,6 +272,21 @@ End-to-end app templates you can fork and build on.
</tr>
</table>

## 🌐 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:
Expand Down
2 changes: 2 additions & 0 deletions binary-prediction/anchor/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions counter/anchor/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 2 additions & 0 deletions private-counter/anchor/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand Down
2 changes: 2 additions & 0 deletions rewards-delegated-vrf/anchor/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
6 changes: 1 addition & 5 deletions rewards-delegated-vrf/anchor/dashboard/lib/clusterContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down Expand Up @@ -161,4 +158,3 @@ export function getDefaultSolanaEndpoint(): string {
export function getDefaultMagicBlockErEndpoint(): string {
return CLUSTER_CONFIG["https://devnet-as.magicblock.app/"].endpoint;
}

2 changes: 2 additions & 0 deletions rock-paper-scissor/anchor/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion roll-dice/anchor/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ The dice account is delegated to the Ephemeral Rollup. On the ER, the program re

<img width="508" alt="Screenshot 2025-03-27 at 18 48 50" src="https://github.com/user-attachments/assets/8b67fd33-c9b4-48f1-9a1a-92a9e8d74111" />

[https://roll-dice-demo.vercel.app/](https://roll-dice-demo.vercel.app/)
🌐 **Live demo:** https://roll-dice.magicblock.app

## Software Packages

Expand Down
107 changes: 57 additions & 50 deletions roll-dice/anchor/app/lib/roll-result.test.ts
Original file line number Diff line number Diff line change
@@ -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<Parameters<typeof shouldCompleteRoll>[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);
});
});
41 changes: 22 additions & 19 deletions roll-dice/anchor/app/lib/roll-result.ts
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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 (
Expand All @@ -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
);
}
2 changes: 2 additions & 0 deletions session-keys/anchor/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 2 additions & 0 deletions spl-tokens/anchor/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading