Skip to content
Open
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
10 changes: 5 additions & 5 deletions skill/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: magicblock
description: MagicBlock Ephemeral Rollups development patterns for Solana. Covers delegation/undelegation flows, dual-connection architecture (base layer + ER), cranks for scheduled tasks, VRF for verifiable randomness, and TypeScript/Anchor integration. Use for high-performance gaming, real-time apps, and fast transaction throughput on Solana.
description: MagicBlock Ephemeral Rollups development patterns for Solana. Covers delegation/undelegation flows, dual-connection architecture (base layer + ER), cranks for scheduled tasks, VRF for verifiable randomness, and TypeScript integration. Supports both Anchor and Pinocchio frameworks. Use for high-performance gaming, real-time apps, and fast transaction throughput on Solana.
user-invocable: true
---

Expand Down Expand Up @@ -34,10 +34,9 @@ Use this Skill when the user asks for:

## Default stack decisions (opinionated)

1) **Programs: Anchor with ephemeral-rollups-sdk**
- Use `ephemeral-rollups-sdk` with Anchor features
- Apply `#[ephemeral]` macro before `#[program]`
- Use `#[delegate]` and `#[commit]` macros for delegation contexts
1) **Programs: Anchor or Pinocchio with ephemeral-rollups SDK**
- **Anchor**: Use `ephemeral-rollups-sdk` with `#[ephemeral]`, `#[delegate]`, and `#[commit]` macros
- **Pinocchio**: Use `ephemeral-rollups-pinocchio` with explicit account slicing and manual dispatch

2) **Dual Connections**
- Base layer connection for initialization and delegation
Expand Down Expand Up @@ -68,6 +67,7 @@ Always be explicit about:
- PDA seeds matching between delegate call and account definition
- Using `skipPreflight: true` for ER transactions
- Waiting for state propagation after delegate/undelegate
- Only marking delegated accounts as writable in ER instructions — non-delegated accounts must not be writable

### 4. Add appropriate features
- Cranks for recurring automated transactions
Expand Down
177 changes: 167 additions & 10 deletions skill/delegation.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Delegation Patterns (Rust Programs)

## Rust Program Setup
## Anchor Framework

### Dependencies

Expand Down Expand Up @@ -30,7 +30,7 @@ pub mod my_program {
}
```

## Delegate Instruction
### Delegate Instruction

```rust
pub fn delegate(ctx: Context<DelegateInput>, uid: String) -> Result<()> {
Expand All @@ -55,7 +55,7 @@ pub struct DelegateInput<'info> {
}
```

## Undelegate Instruction
### Undelegate Instruction

```rust
pub fn undelegate(ctx: Context<Undelegate>) -> Result<()> {
Expand All @@ -78,7 +78,7 @@ pub struct Undelegate<'info> {
}
```

## Commit Without Undelegating
### Commit Without Undelegating

```rust
pub fn commit(ctx: Context<CommitState>) -> Result<()> {
Expand All @@ -92,16 +92,166 @@ pub fn commit(ctx: Context<CommitState>) -> Result<()> {
}
```

## Common Gotchas
### Anchor Gotchas

### Method Name Convention
#### Method Name Convention
The delegate method is auto-generated as `delegate_<field_name>`:
```rust
pub my_account: AccountInfo<'info>, // => ctx.accounts.delegate_my_account()
```

#### Don't use `Account<>` in delegate context
Use `AccountInfo` with `del` constraint instead.

#### Don't skip the `#[commit]` macro
Required for undelegate context.

Comment on lines +97 to +108
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Add blank lines around headings and code fences for markdown compliance.

The markdown linter flags missing blank lines around several headings (lines 97, 103, 106) and a code fence (line 99). While this doesn't affect rendering in most viewers, it's good practice to follow markdown conventions for consistency across the documentation.

📝 Suggested formatting fixes
 ### Anchor Gotchas
 
 #### Method Name Convention
+
 The delegate method is auto-generated as `delegate_<field_name>`:
+
 ```rust
 pub my_account: AccountInfo<'info>,  // => ctx.accounts.delegate_my_account()

Don't use Account<> in delegate context

Use AccountInfo with del constraint instead.

Don't skip the #[commit] macro

Required for undelegate context.

</details>

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 markdownlint-cli2 (0.20.0)</summary>

[warning] 97-97: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)

---

[warning] 99-99: Fenced code blocks should be surrounded by blank lines

(MD031, blanks-around-fences)

---

[warning] 103-103: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)

---

[warning] 106-106: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)

</details>

</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

In @skill/delegation.md around lines 97 - 108, Add blank lines before and after
the "Method Name Convention" heading, the Rust code fence that shows the
delegate example (pub my_account: AccountInfo<'info>, // =>
ctx.accounts.delegate_my_account()), and before the "Don't use Account<> in
delegate context" and "Don't skip the #[commit] macro" headings so the
markdown linter stops flagging missing surrounding blank lines; ensure there is
an empty line above and below each heading and the fenced code block to comply
with Markdown conventions.


</details>

<!-- fingerprinting:phantom:triton:puma -->

<!-- This is an auto-generated comment by CodeRabbit -->

## Pinocchio Framework

### Dependencies

```toml
# Cargo.toml
[dependencies]
pinocchio = { version = "0.10.2", features = ["cpi", "copy"] }
pinocchio-log = { version = "0.5" }
pinocchio-system = { version = "0.5" }
ephemeral-rollups-pinocchio = { version = "0.8.5" }
```
Comment on lines +113 to +120
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

pinocchio rust crate latest version 2025 2026 crates.io

💡 Result:

As of February 7, 2026, the latest pinocchio Rust crate version on crates.io is 0.10.1. [1][2]


🌐 Web query:

pinocchio-log pinocchio-system ephemeral-rollups-pinocchio latest versions

💡 Result:

Latest published crate versions (per docs.rs “latest” / crate page):

  • pinocchio-log: 0.5.1 [1]
  • pinocchio-system: 0.5.0 [2]
  • ephemeral-rollups-pinocchio: 0.8.4 [3]

Sources: [1] pinocchio-log docs.rs, [2] pinocchio-system docs.rs, [3] ephemeral-rollups-pinocchio docs.rs.


🌐 Web query:

pinocchio 0.10.2 crate features cpi copy deprecated

💡 Result:

As of February 7, 2026, I don’t see a pinocchio crate release 0.10.2 on crates.io; the latest published version appears to be 0.10.1 (published ~4 weeks ago). [1]

In the current docs for pinocchio (latest = 0.10.1), the Cargo features are:

  • cpi: enables cross-program invocation (CPI) helpers and related instruction/signer types. Not shown as deprecated. [2]
  • copy: enables deriving Copy for types (and enables copy on some re-exports). Not shown as deprecated. [2]

What is deprecated is the pinocchio::program module (deprecated since 0.8.0) with guidance to use the cpi module instead—this is about a module path, not the cpi feature flag. [3]

Sources: [1] [2] [3]


Update Cargo.toml to use valid published versions.

  • pinocchio version 0.10.2 does not exist; latest published is 0.10.1
  • ephemeral-rollups-pinocchio version 0.8.5 does not exist; latest published is 0.8.4
  • pinocchio-log should be updated to 0.5.1 (latest)

The cpi and copy features remain valid and are not deprecated. Correct the versions to ensure the documentation reflects resolvable dependencies:

[dependencies]
pinocchio = { version = "0.10.1", features = ["cpi", "copy"] }
pinocchio-log = { version = "0.5.1" }
pinocchio-system = { version = "0.5" }
ephemeral-rollups-pinocchio = { version = "0.8.4" }
🤖 Prompt for AI Agents
In `@skill/delegation.md` around lines 113 - 120, Update the dependency versions
in the Cargo manifest snippet so they match published releases: change pinocchio
from "0.10.2" to "0.10.1", pinocchio-log from "0.5" to "0.5.1", and
ephemeral-rollups-pinocchio from "0.8.5" to "0.8.4" while leaving pinocchio's
features ["cpi","copy"] and pinocchio-system = "0.5" unchanged; ensure the
updated versions replace the entries for pinocchio, pinocchio-log, and
ephemeral-rollups-pinocchio in the shown Cargo.toml block.


### Imports

```rust
use ephemeral_rollups_pinocchio::instruction::delegate_account;
use ephemeral_rollups_pinocchio::instruction::{
commit_accounts, commit_and_undelegate_accounts, undelegate,
};
use ephemeral_rollups_pinocchio::types::DelegateConfig;
```

### Program Setup

No macros needed. Pinocchio uses explicit account slicing and manual instruction dispatch instead of Anchor's `#[program]`/`#[ephemeral]` macros.

### Delegate Instruction

```rust
pub fn process_delegate(
_program_id: &Address,
accounts: &[AccountView],
bump: u8,
) -> ProgramResult {
let [payer, pda_to_delegate, owner_program, delegation_buffer,
delegation_record, delegation_metadata, _delegation_program,
system_program, rest @ ..] = accounts
else {
return Err(ProgramError::NotEnoughAccountKeys);
};
let validator = rest.first().map(|account| *account.address());
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Document when the optional validator account should be provided.

The code extracts an optional validator from remaining accounts but doesn't explain when this should be included or what it's used for. Users following this example may not know whether to pass this account or leave it empty.

📖 Suggested documentation addition

Add a comment above line 150:

    } else {
        return Err(ProgramError::NotEnoughAccountKeys);
    };
+   // Optional: provide a validator account to enforce validation on the ER
    let validator = rest.first().map(|account| *account.address());
🤖 Prompt for AI Agents
In `@skill/delegation.md` at line 150, Add documentation above the optional
validator extraction (the line using rest.first().map to set validator)
explaining when callers should include this extra account and what it does;
e.g., state that the validator is optional and should be provided only when you
want an external validator to enforce checks on the ER (otherwise leave it
empty), mention expected signer/ownership requirements and that absence means no
external validation is performed; update the comment near the validator variable
(referencing validator and rest.first().map(|account| *account.address())) to
clearly describe purpose and usage.


let seeds: &[&[u8]] = &[b"seed", payer.address().as_ref()];
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Seeds differ between Anchor and Pinocchio examples - clarify the pattern.

The Anchor example uses &[b"seed", uid.as_bytes()] while the Pinocchio example uses &[b"seed", payer.address().as_ref()]. This inconsistency may confuse users about which seed pattern to follow. If this is intentional (different use cases), consider adding a comment explaining why the seed patterns differ.

💡 Suggested clarification
+   // Seeds for PDA derivation (adjust based on your program's needs)
+   // Example: using payer address as seed component
    let seeds: &[&[u8]] = &[b"seed", payer.address().as_ref()];
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let seeds: &[&[u8]] = &[b"seed", payer.address().as_ref()];
// Seeds for PDA derivation (adjust based on your program's needs)
// Example: using payer address as seed component
let seeds: &[&[u8]] = &[b"seed", payer.address().as_ref()];
🤖 Prompt for AI Agents
In `@skill/delegation.md` at line 152, The seed usage is inconsistent between the
Anchor example (using uid.as_bytes() in the seeds array) and the Pinocchio
example (using payer.address().as_ref()), so update the documentation around the
seeds declaration (the lines referencing seeds, uid, and payer.address()) to
either unify the pattern or add a concise explanatory comment: state that Anchor
examples use a user-specific UID byte slice (uid.as_bytes()) for PDA derivation
while the Pinocchio example demonstrates a payer-address-based seed
(payer.address().as_ref()) for a different use case; reference the symbols
seeds, uid, and payer.address() in the comment so readers understand which
pattern to choose.


delegate_account(
&[
payer,
pda_to_delegate,
owner_program,
delegation_buffer,
delegation_record,
delegation_metadata,
system_program,
],
seeds,
bump,
DelegateConfig {
validator,
..Default::default()
},
)?;

Ok(())
}
```

### Undelegate Instruction

```rust
pub fn process_undelegate(
_program_id: &Address,
accounts: &[AccountView],
) -> ProgramResult {
let [payer, my_account, magic_program, magic_context] = accounts else {
return Err(ProgramError::NotEnoughAccountKeys);
};

if !payer.is_signer() {
return Err(ProgramError::MissingRequiredSignature);
}

commit_and_undelegate_accounts(
payer,
&[*my_account],
magic_context,
magic_program,
)?;

Ok(())
}

// REQUIRED: Handle the undelegation callback from the delegation program
pub fn process_undelegation_callback(
program_id: &Address,
accounts: &[AccountView],
ix_data: &[u8],
) -> ProgramResult {
let [delegated_acc, buffer_acc, payer, _system_program, ..] = accounts else {
return Err(ProgramError::NotEnoughAccountKeys);
};
undelegate(delegated_acc, program_id, buffer_acc, payer, ix_data)?;
Ok(())
}
```

### Commit Without Undelegating

```rust
pub fn process_commit(
_program_id: &Address,
accounts: &[AccountView],
) -> ProgramResult {
let [payer, my_account, magic_program, magic_context] = accounts else {
return Err(ProgramError::NotEnoughAccountKeys);
};

if !payer.is_signer() {
return Err(ProgramError::MissingRequiredSignature);
}

commit_accounts(
payer,
&[*my_account],
magic_context,
magic_program,
)?;

Ok(())
}
```

### Pinocchio Gotchas

#### Undelegation Callback Required
You must implement a `process_undelegation_callback` handler that calls `undelegate()` — the delegation program invokes this on your program when undelegating.

#### Copy Semantics for Account References
`commit_accounts` and `commit_and_undelegate_accounts` take `&[AccountView]` (copied), not references like Anchor's `&AccountInfo`. This requires the `copy` feature on the `pinocchio` crate.

Comment on lines +241 to +248
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Use the same callback name as the example (undelegation_callback).
The gotcha says process_undelegation_callback, but the earlier example defines undelegation_callback. Align the naming to avoid confusion.

✏️ Suggested wording fix
-You must implement a `process_undelegation_callback` handler that calls `undelegate()` — the delegation program invokes this on your program when undelegating.
+You must implement an `undelegation_callback` handler that calls `undelegate()` — the delegation program invokes this on your program when undelegating.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
### Pinocchio Gotchas
#### Undelegation Callback Required
You must implement a `process_undelegation_callback` handler that calls `undelegate()` — the delegation program invokes this on your program when undelegating.
#### Copy Semantics for Account References
`commit_accounts` and `commit_and_undelegate_accounts` take `&[AccountView]` (copied), not references like Anchor's `&AccountInfo`. This requires the `copy` feature on the `pinocchio` crate.
### Pinocchio Gotchas
#### Undelegation Callback Required
You must implement an `undelegation_callback` handler that calls `undelegate()` — the delegation program invokes this on your program when undelegating.
#### Copy Semantics for Account References
`commit_accounts` and `commit_and_undelegate_accounts` take `&[AccountView]` (copied), not references like Anchor's `&AccountInfo`. This requires the `copy` feature on the `pinocchio` crate.
🤖 Prompt for AI Agents
In `@skill/delegation.md` around lines 241 - 248, The docs currently mention
process_undelegation_callback but the example and callback name used elsewhere
is undelegation_callback; update the text so the callback name is
consistent—either rename the example to process_undelegation_callback or
(preferably) change the doc text to reference undelegation_callback so it
matches the example and any code that calls undelegation_callback; also keep the
adjacent note about calling undelegate() and the copy-semantics note for
commit_accounts / commit_and_undelegate_accounts unchanged.

## Common Gotchas

### PDA Seeds Must Match
Seeds in delegate instruction must exactly match account definition:
Seeds in delegate instruction must exactly match account definition.

**Anchor:**
```rust
#[account(mut, del, seeds = [b"tomo", uid.as_bytes()], bump)]
pub tomo: AccountInfo<'info>,
Expand All @@ -110,6 +260,15 @@ pub tomo: AccountInfo<'info>,
ctx.accounts.delegate_tomo(&payer, &[b"tomo", uid.as_bytes()], config)?;
```

**Pinocchio:**
```rust
// Seeds used to derive the PDA
let seeds: &[&[u8]] = &[b"seed", payer.address().as_ref()];

// delegate_account call - seeds must match the PDA derivation
delegate_account(&[...], seeds, bump, delegate_config)?;
```

### Account Owner Changes on Delegation
```
Not delegated: account.owner == YOUR_PROGRAM_ID
Expand All @@ -128,6 +287,4 @@ Delegated: account.owner == DELEGATION_PROGRAM_ID
### Don'ts
- Don't send delegate tx to ER - Delegation always goes to base layer
- Don't send operations to base layer - Delegated account ops go to ER
- Don't forget the `#[ephemeral]` macro - Required on program module
- Don't use `Account<>` in delegate context - Use `AccountInfo` with `del` constraint
- Don't skip the `#[commit]` macro - Required for undelegate context
- Don't forget the `#[ephemeral]` macro - Required on program module (Anchor)