The program implements specific instructions for delegating and undelegating the counter:
Delegate: Delegates counter from Base Layer to ER (called on Base Layer)
CommitAndUndelegate: Schedules sync of counter from ER to Base Layer, and undelegates counter on ER (called on ER)
Commit: Schedules sync of counter from ER to Base Layer (called on ER)
Undelegate:
Schedules sync and undelegation of counter (called on ER)
Undelegation triggered through callback instruction injected through #[ephemeral] (called on Base Layer through validator CPI)
The undelegation callback discriminator [196, 28, 41, 206, 48, 37, 51, 167]
and its instruction processor must be specified in your program. This
instruction triggered by Delegation Program reverts account ownership on the
Base Layer after calling undelegation on ER.With [#ephemeral] Anchor macro from MagicBlock’s Ephemeral Rollup SDK, the undelegation callback discriminator and processor are injected into your program.
#[ephemeral]#[program]pub mod anchor_counter { use super::*; /// Initialize the counter. pub fn initialize(ctx: Context<Initialize>) -> Result<()> { let counter = &mut ctx.accounts.counter; counter.count = 0; Ok(()) } /// Increment the counter. pub fn increment(ctx: Context<Increment>) -> Result<()> { let counter = &mut ctx.accounts.counter; counter.count += 1; Ok(()) } /// Delegate the account to the delegation program /// Set specific validator based on ER, see https://docs.magicblock.gg/pages/get-started/how-integrate-your-program/local-setup pub fn delegate(ctx: Context<DelegateInput>) -> Result<()> { // ... } /// Increment the counter and manually commit the account in the Ephemeral Rollup session. pub fn increment_and_commit(ctx: Context<IncrementAndCommit>) -> Result<()> { // ... } /// Undelegate the account from the delegation program pub fn undelegate(ctx: Context<IncrementAndCommit>) -> Result<()> { // ... }}/// Context for initializing counter#[derive(Accounts)]pub struct Initialize<'info> { #[account(init_if_needed, payer = user, space = 8 + 8, seeds = [TEST_PDA_SEED], bump)] pub counter: Account<'info, Counter>, #[account(mut)] pub user: Signer<'info>, pub system_program: Program<'info, System>,}/// Context for incrementing counter#[derive(Accounts)]pub struct Increment<'info> { #[account(mut, seeds = [TEST_PDA_SEED], bump)] pub counter: Account<'info, Counter>,}/// Counter struct#[account]pub struct Counter { pub count: u64,}/// Other context for delegation
Nothing special here, just a simple Anchor program that increments a counter. The only difference is that we’re adding the ephemeral macro for undelegation and delegate macro to inject some useful logic to interact with the delegation program.⬆️ Back to Top
These public validators are supported for development. Make sure to add the
specific ER validator in your delegation instruction:
Mainnet
Asia (as.magicblock.app):
MAS1Dt9qreoRMQ14YQuhg8UTZMMzDdKhmkZMECCzk57
EU (eu.magicblock.app):
MEUGGrYPxKk17hCr7wpT6s8dtNokZj5U2L57vjYMS8e
US (us.magicblock.app):
MUS3hc9TCw4cGC12vHNoYcCGzJG1txjgQLZWVoeNHNd
TEE (mainnet-tee.magicblock.app):
MTEWGuqxUpYZGFJQcp8tLN7x5v9BSeoFHYWQQ3n3xzo
Devnet
Asia (devnet-as.magicblock.app):
MAS1Dt9qreoRMQ14YQuhg8UTZMMzDdKhmkZMECCzk57
EU (devnet-eu.magicblock.app):
MEUGGrYPxKk17hCr7wpT6s8dtNokZj5U2L57vjYMS8e
US (devnet-us.magicblock.app):
MUS3hc9TCw4cGC12vHNoYcCGzJG1txjgQLZWVoeNHNd
TEE (tee.magicblock.app):
FnE6VJT5QNZdedZPnCoLsARgBwoE6DeJNjBs2H1gySXA
Localnet
Local ER (localhost:7799):
mAGicPQYBMvcYveUZA5F5UNNwyHvfYh5xkLS2Fr1mev
Add ephemeral-rollups-sdk with Anchor features to your program
cargo add ephemeral-rollups-sdk --features anchor
Import delegate, commit, ephemeral, DelegateConfig, commit_accounts and commit_and_undelegate_accounts:
use ephemeral_rollups_sdk::anchor::{ commit, delegate, ephemeral};use ephemeral_rollups_sdk::cpi::DelegateConfig;use ephemeral_rollups_sdk::ephem::{ commit_accounts, commit_and_undelegate_accounts };
Add delegate macro and instruction, ephemeral macro, and undelegate instruction to your program. Specify your preferred delegation config such as auto commits and specific ER validator:
/// Add delegate function to the context#[delegate]#[derive(Accounts)]pub struct DelegateInput<'info> { pub payer: Signer<'info>, /// CHECK: Checked by the delegate program pub validator: Option<AccountInfo<'info>>, /// CHECK The pda to delegate #[account(mut, del)] pub pda: AccountInfo<'info>,}
/// Delegate the account to the delegation program/// Set specific validator based on ER, see https://docs.magicblock.gg/pages/get-started/how-integrate-your-program/local-setuppub fn delegate(ctx: Context<DelegateInput>) -> Result<()> { ctx.accounts.delegate_pda( &ctx.accounts.payer, &[TEST_PDA_SEED], DelegateConfig { // Optionally set a specific validator from the first remaining account validator: ctx.remaining_accounts.first().map(|acc| acc.key()), ..Default::default() }, )?; Ok(())}
/// Increment the counter and manually commit the account in the Ephemeral Rollup session.pub fn increment_and_commit(ctx: Context<IncrementAndCommit>) -> Result<()> { let counter = &mut ctx.accounts.counter; counter.count += 1; commit_accounts( &ctx.accounts.payer, vec![&ctx.accounts.counter.to_account_info()], &ctx.accounts.magic_context, &ctx.accounts.magic_program, )?; Ok(())}
/// Undelegate the account from the delegation programpub fn undelegate(ctx: Context<IncrementAndCommit>) -> Result<()> { commit_and_undelegate_accounts( &ctx.accounts.payer, vec![&ctx.accounts.counter.to_account_info()], &ctx.accounts.magic_context, &ctx.accounts.magic_program, )?; Ok(())}
Delegation is the process of transferring ownership of one or more of your program’s PDAs to the delegation program. Ephemeral Validators will then be able to use the PDAs to perform transactions in the SVM runtime.
Commit is the process of updating the state of the PDAs from ER to the base layer. After the finalization process, the PDAs remain locked on base layer.
Undelegation is the process of transferring ownership of the PDAs back to your program. On undelegation, the state is committed and it trigger the finalization process. Once state it validated, the PDAs are unlocked and can be used as normal on base layer.
Ready to execute transactions for delegation and real-time speed.
These public validators are supported for development. Make sure to add the
specific ER validator in your delegation instruction:
Mainnet
Asia (as.magicblock.app):
MAS1Dt9qreoRMQ14YQuhg8UTZMMzDdKhmkZMECCzk57
EU (eu.magicblock.app):
MEUGGrYPxKk17hCr7wpT6s8dtNokZj5U2L57vjYMS8e
US (us.magicblock.app):
MUS3hc9TCw4cGC12vHNoYcCGzJG1txjgQLZWVoeNHNd
TEE (mainnet-tee.magicblock.app):
MTEWGuqxUpYZGFJQcp8tLN7x5v9BSeoFHYWQQ3n3xzo
Devnet
Asia (devnet-as.magicblock.app):
MAS1Dt9qreoRMQ14YQuhg8UTZMMzDdKhmkZMECCzk57
EU (devnet-eu.magicblock.app):
MEUGGrYPxKk17hCr7wpT6s8dtNokZj5U2L57vjYMS8e
US (devnet-us.magicblock.app):
MUS3hc9TCw4cGC12vHNoYcCGzJG1txjgQLZWVoeNHNd
TEE (tee.magicblock.app):
FnE6VJT5QNZdedZPnCoLsARgBwoE6DeJNjBs2H1gySXA
Localnet
Local ER (localhost:7799):
mAGicPQYBMvcYveUZA5F5UNNwyHvfYh5xkLS2Fr1mev
anchor test --skip-build --skip-deploy --skip-local-validator
Run the following test:
// Set ER Validatorconst ER_VALIDATOR = new web3.PublicKey( "MAS1Dt9qreoRMQ14YQuhg8UTZMMzDdKhmkZMECCzk57"); // Asia ER Validator// Set Anchor providersconst provider = anchor.AnchorProvider.env();anchor.setProvider(provider);const providerEphemeralRollup = new anchor.AnchorProvider( new anchor.web3.Connection("https://devnet-as.magicblock.app/", { wsEndpoint: "wss://devnet.magicblock.app/", }), anchor.Wallet.local());// Set program and pdaconst program = anchor.workspace.AnchorCounter as Program<AnchorCounter>;const [pda] = anchor.web3.PublicKey.findProgramAddressSync( [Buffer.from(SEED_TEST_PDA)], program.programId);// Initialize Counter on Base Layerlet initTx = await program.methods .increment() .accounts({ counter: pda, }) .transaction();initTx.feePayer = provider.wallet.publicKey;initTx.recentBlockhash = ( await provider.connection.getLatestBlockhash()).blockhash;initTx = await provider.connection.signTransaction(initTx);const dinitTxHash = await provider.sendAndConfirm(initTx);// Delegate Counter on Base Layer to ERlet delTx = await program.methods .increment() .accounts({ payer: provider.wallet.publicKey, validator: ER_VALIDATOR, pda: pda, }) .transaction();delTx.feePayer = provider.connection.wallet.publicKey;delTx.recentBlockhash = ( await provider.connection.getLatestBlockhash()).blockhash;delTx = await provider.wallet.signTransaction(delTx);const delTxHash = await provider.sendAndConfirm(delTx);// Increment Counter in real-time on ERlet incTx = await program.methods .increment() .accounts({ counter: pda, }) .transaction();incTx.feePayer = providerEphemeralRollup.wallet.publicKey;incTx.recentBlockhash = ( await providerEphemeralRollup.connection.getLatestBlockhash()).blockhash;incTx = await providerEphemeralRollup.wallet.signTransaction(incTx);const incTxHash = await providerEphemeralRollup.sendAndConfirm(incTx);
To make it easier to integrate via the frontend, we created the Magic Router. You send transactions directly to the magic router, and we can determine for you whether it should be routed to the Ephemeral Rollup or base layer.
Attach one or more instructions that run automatically on the Solana base layer immediately after an Ephemeral Rollup
(ER) commit.
Learn more about Magic Action