AI 코딩 에이전트로 개발하고 계신가요? MagicBlock Dev Skill을 설치하면 위임 플로우, Magic Actions, Crank, VRF 등 MagicBlock 전용 개발 패턴을 에이전트에 제공할 수 있습니다.Claude Code 빠른 설치:Cursor, Codex, Windsurf, Cline 등 다른 에이전트를 사용하시나요? 모든 설치 방법은 AI Dev Skill 페이지를 참고하세요.
npx add-skill https://github.com/magicblock-labs/magicblock-dev-skill
빠른 접근
기본 카운터 예제를 확인해 보세요.GitHub
Anchor 구현
GitHub
React 구현
단계별 가이드
프로그램을 빌드하고 MagicBlock의 Delegation ProgramDELeGGvXpWV2fqJUhqcF5ZSYMS4JTLjteaAMARRSaeSh 을 사용해 delegation hooks를 추가하여 업그레이드하세요。
1
평소와 같이 Solana 프로그램을 작성하세요。
2
프로그램에 delegation 및 undelegation hooks 추가
CPI hooks를 추가해 상태 계정을 Ephemeral Rollup 세션을 통해 delegate, commit, undelegate 할 수 있게 하세요。
이 공용 검증자들은 개발용으로 지원됩니다. 위임 명령에 해당 ER 검증자를 반드시 추가하세요.
메인넷- 아시아 (as.magicblock.app):
MAS1Dt9qreoRMQ14YQuhg8UTZMMzDdKhmkZMECCzk57 - EU (eu.magicblock.app):
MEUGGrYPxKk17hCr7wpT6s8dtNokZj5U2L57vjYMS8e - 미국 (us.magicblock.app):
MUS3hc9TCw4cGC12vHNoYcCGzJG1txjgQLZWVoeNHNd - TEE (mainnet-tee.magicblock.app):
MTEWGuqxUpYZGFJQcp8tLN7x5v9BSeoFHYWQQ3n3xzo
- 아시아 (devnet-as.magicblock.app):
MAS1Dt9qreoRMQ14YQuhg8UTZMMzDdKhmkZMECCzk57 - EU (devnet-eu.magicblock.app):
MEUGGrYPxKk17hCr7wpT6s8dtNokZj5U2L57vjYMS8e - 미국 (devnet-us.magicblock.app):
MUS3hc9TCw4cGC12vHNoYcCGzJG1txjgQLZWVoeNHNd - TEE (devnet-tee.magicblock.app):
MTEWGuqxUpYZGFJQcp8tLN7x5v9BSeoFHYWQQ3n3xzo
- 로컬 ER (localhost:7799):
mAGicPQYBMvcYveUZA5F5UNNwyHvfYh5xkLS2Fr1mev
3
Anchor 또는 Solana CLI를 사용해 프로그램을 Solana에 직접 배포하세요。
4
SVM RPC 사양을 준수하는 트랜잭션을 수정 없이 온체인과 오프체인에서 전송하세요。
Counter 예제

코드 스니펫
- 1. Write program
- 2. Delegate
- 3. Deploy
- 4. Test
이 프로그램은 두 가지 주요 instruction을 구현합니다。특별한 것은 없고, 단순한 Anchor 카운터 프로그램입니다. 차이점은 undelegation을 위한
initialize: 카운터를 0으로 설정increment: 카운터를 1 증가
Delegate: 카운터를 Base Layer에서 ER로 위임 (Base Layer에서 호출)CommitAndUndelegate: 카운터를 ER에서 Base Layer로 동기화하도록 예약하고 ER에서 위임 해제 (ER에서 호출)Commit: 카운터를 ER에서 Base Layer로 동기화하도록 예약 (ER에서 호출)Undelegate:- 카운터의 동기화와 위임 해제를 예약 (ER에서 호출)
#[ephemeral]로 주입된 callback instruction을 통해 undelegation 트리거 (validator CPI를 통해 Base Layer에서 호출)
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 public_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<()> {
// ...
}
/// Manually commit the counter state in the Ephemeral Rollup session.
pub fn commit(ctx: Context<IncrementAndCommit>) -> Result<()> {
// ...
}
/// Increment the counter and commit in the same instruction.
pub fn increment_and_commit(ctx: Context<IncrementAndCommit>) -> Result<()> {
// ...
}
/// Undelegate the account from the delegation program.
pub fn undelegate(ctx: Context<IncrementAndCommit>) -> Result<()> {
// ...
}
}
pub const COUNTER_SEED: &[u8] = b"counter";
/// Context for initializing counter
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(init_if_needed, payer = user, space = 8 + 8, seeds = [COUNTER_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 = [COUNTER_SEED], bump)]
pub counter: Account<'info, Counter>,
}
/// Counter struct
#[account]
pub struct Counter {
pub count: u64,
}
/// Other context for delegation
ephemeral macro와 delegation program과 상호작용하는 유용한 로직을 주입하는 delegate macro를 추가했다는 점입니다。⬆️ Back to Top이 공용 검증자들은 개발용으로 지원됩니다. 위임 명령에 해당 ER 검증자를 반드시 추가하세요.
메인넷- 아시아 (as.magicblock.app):
MAS1Dt9qreoRMQ14YQuhg8UTZMMzDdKhmkZMECCzk57 - EU (eu.magicblock.app):
MEUGGrYPxKk17hCr7wpT6s8dtNokZj5U2L57vjYMS8e - 미국 (us.magicblock.app):
MUS3hc9TCw4cGC12vHNoYcCGzJG1txjgQLZWVoeNHNd - TEE (mainnet-tee.magicblock.app):
MTEWGuqxUpYZGFJQcp8tLN7x5v9BSeoFHYWQQ3n3xzo
- 아시아 (devnet-as.magicblock.app):
MAS1Dt9qreoRMQ14YQuhg8UTZMMzDdKhmkZMECCzk57 - EU (devnet-eu.magicblock.app):
MEUGGrYPxKk17hCr7wpT6s8dtNokZj5U2L57vjYMS8e - 미국 (devnet-us.magicblock.app):
MUS3hc9TCw4cGC12vHNoYcCGzJG1txjgQLZWVoeNHNd - TEE (devnet-tee.magicblock.app):
MTEWGuqxUpYZGFJQcp8tLN7x5v9BSeoFHYWQQ3n3xzo
- 로컬 ER (localhost:7799):
mAGicPQYBMvcYveUZA5F5UNNwyHvfYh5xkLS2Fr1mev
- Anchor 기능이 포함된
ephemeral-rollups-sdk를 프로그램에 추가합니다
cargo add ephemeral-rollups-sdk --features anchor
delegate, commit, ephemeral, DelegateConfig, commit_accounts, commit_and_undelegate_accounts 를 import 합니다: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
};
- 프로그램에
delegatemacro와 instruction,ephemeralmacro,undelegateinstruction을 추가하세요. auto commit이나 특정 ER validator 같은 delegation config도 지정할 수 있습니다:
/// Add delegate function to the context
#[delegate]
#[derive(Accounts)]
pub struct DelegateInput<'info> {
pub payer: Signer<'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-setup
pub fn delegate(ctx: Context<DelegateInput>) -> Result<()> {
ctx.accounts.delegate_pda(
&ctx.accounts.payer,
&[COUNTER_SEED],
DelegateConfig {
// Optionally set a specific validator from the first remaining account
validator: ctx.remaining_accounts.first().map(|acc| acc.key()),
..Default::default()
},
)?;
Ok(())
}
use ephemeral_rollups_sdk::ephem::MagicIntentBundleBuilder;
/// Manually commit the counter state in the Ephemeral Rollup session.
pub fn commit(ctx: Context<IncrementAndCommit>) -> Result<()> {
MagicIntentBundleBuilder::new(
ctx.accounts.payer.to_account_info(),
ctx.accounts.magic_context.to_account_info(),
ctx.accounts.magic_program.to_account_info(),
)
.commit(&[ctx.accounts.counter.to_account_info()])
.build_and_invoke()?;
Ok(())
}
/// Increment the counter and commit the new state in the same instruction.
pub fn increment_and_commit(ctx: Context<IncrementAndCommit>) -> Result<()> {
let counter = &mut ctx.accounts.counter;
counter.count += 1;
// Serialize the Anchor account before the CPI sees it
counter.exit(&crate::ID)?;
MagicIntentBundleBuilder::new(
ctx.accounts.payer.to_account_info(),
ctx.accounts.magic_context.to_account_info(),
ctx.accounts.magic_program.to_account_info(),
)
.commit(&[ctx.accounts.counter.to_account_info()])
.build_and_invoke()?;
Ok(())
}
use ephemeral_rollups_sdk::ephem::MagicIntentBundleBuilder;
/// Undelegate the account from the delegation program.
/// Commits the latest state and returns ownership of the PDA back to the owner program.
pub fn undelegate(ctx: Context<IncrementAndCommit>) -> Result<()> {
MagicIntentBundleBuilder::new(
ctx.accounts.payer.to_account_info(),
ctx.accounts.magic_context.to_account_info(),
ctx.accounts.magic_program.to_account_info(),
)
.commit_and_undelegate(&[ctx.accounts.counter.to_account_info()])
.build_and_invoke()?;
Ok(())
}
Delegation은 프로그램의 하나 이상의PDA소유권을 delegation program으로 이전하는 과정입니다. 그러면 Ephemeral Validators가 이PDA를 사용해 SVM runtime에서 트랜잭션을 실행할 수 있습니다.
Commit은PDA의 상태를 ER에서 base layer로 업데이트하는 과정입니다. finalization 이후에도PDA는 base layer에서 잠겨 있는 상태로 유지됩니다。
⬆️ Back to TopUndelegation은PDA의 소유권을 다시 프로그램으로 되돌리는 과정입니다. undelegation 시 상태가 커밋되고 finalization 프로세스가 시작됩니다. 상태 검증이 완료되면PDA가 잠금 해제되어 base layer에서 평소처럼 사용할 수 있습니다。
이제 delegation 및 실시간 고속 거래를 실행할 준비가 되었습니다。다음 테스트를 실행하세요:프런트엔드 통합을 더 쉽게 하기 위해 Magic Router를 만들었습니다. 트랜잭션을 magic router로 직접 보내면, 그것이 Ephemeral Rollup으로 가야 하는지 base layer로 가야 하는지를 자동으로 판단해 줍니다。⬆️ Back to Top
이 공용 검증자들은 개발용으로 지원됩니다. 위임 명령에 해당 ER 검증자를 반드시 추가하세요.
메인넷- 아시아 (as.magicblock.app):
MAS1Dt9qreoRMQ14YQuhg8UTZMMzDdKhmkZMECCzk57 - EU (eu.magicblock.app):
MEUGGrYPxKk17hCr7wpT6s8dtNokZj5U2L57vjYMS8e - 미국 (us.magicblock.app):
MUS3hc9TCw4cGC12vHNoYcCGzJG1txjgQLZWVoeNHNd - TEE (mainnet-tee.magicblock.app):
MTEWGuqxUpYZGFJQcp8tLN7x5v9BSeoFHYWQQ3n3xzo
- 아시아 (devnet-as.magicblock.app):
MAS1Dt9qreoRMQ14YQuhg8UTZMMzDdKhmkZMECCzk57 - EU (devnet-eu.magicblock.app):
MEUGGrYPxKk17hCr7wpT6s8dtNokZj5U2L57vjYMS8e - 미국 (devnet-us.magicblock.app):
MUS3hc9TCw4cGC12vHNoYcCGzJG1txjgQLZWVoeNHNd - TEE (devnet-tee.magicblock.app):
MTEWGuqxUpYZGFJQcp8tLN7x5v9BSeoFHYWQQ3n3xzo
- 로컬 ER (localhost:7799):
mAGicPQYBMvcYveUZA5F5UNNwyHvfYh5xkLS2Fr1mev
anchor test --skip-build --skip-deploy --skip-local-validator
const COUNTER_SEED = "counter";
// Set Anchor providers
const provider = new anchor.AnchorProvider(
new anchor.web3.Connection(
process.env.PROVIDER_ENDPOINT || "https://api.devnet.solana.com",
{
wsEndpoint: process.env.PROVIDER_WS_ENDPOINT || undefined,
commitment: "confirmed",
},
),
anchor.Wallet.local(),
);
anchor.setProvider(provider);
const providerEphemeralRollup = new anchor.AnchorProvider(
new anchor.web3.Connection(
process.env.EPHEMERAL_PROVIDER_ENDPOINT ||
"https://devnet-as.magicblock.app/",
{
wsEndpoint:
process.env.EPHEMERAL_WS_ENDPOINT || "wss://devnet-as.magicblock.app/",
commitment: "confirmed",
},
),
anchor.Wallet.local(),
);
// Set program and PDA
const program = anchor.workspace.PublicCounter as Program<PublicCounter>;
const [counterPDA] = anchor.web3.PublicKey.findProgramAddressSync(
[Buffer.from(COUNTER_SEED)],
program.programId,
);
// Initialize counter on base layer
let initTx = await program.methods
.initialize()
.accounts({
user: provider.wallet.publicKey,
})
.transaction();
const initTxHash = await provider.sendAndConfirm(initTx, [
provider.wallet.payer,
]);
// Increment counter on base layer
let incBaseTx = await program.methods
.increment()
.accounts({
counter: counterPDA,
})
.transaction();
const incBaseTxHash = await provider.sendAndConfirm(incBaseTx, [
provider.wallet.payer,
]);
// Delegate counter to ER
// Pin a specific validator by passing it in remaining_accounts
const ER_VALIDATOR = new anchor.web3.PublicKey(
"MAS1Dt9qreoRMQ14YQuhg8UTZMMzDdKhmkZMECCzk57", // Asia ER validator
);
let delTx = await program.methods
.delegate()
.accounts({
payer: provider.wallet.publicKey,
pda: counterPDA,
})
.remainingAccounts([
{ pubkey: ER_VALIDATOR, isSigner: false, isWritable: false },
])
.transaction();
const delTxHash = await provider.sendAndConfirm(delTx, [
provider.wallet.payer,
]);
// Increment counter in real time on ER
let incErTx = await program.methods
.increment()
.accounts({
counter: counterPDA,
})
.transaction();
incErTx.feePayer = providerEphemeralRollup.wallet.publicKey;
incErTx.recentBlockhash = (
await providerEphemeralRollup.connection.getLatestBlockhash()
).blockhash;
incErTx = await providerEphemeralRollup.wallet.signTransaction(incErTx);
const incErTxHash = await providerEphemeralRollup.sendAndConfirm(incErTx);
// Commit and undelegate counter from ER back to base layer
let undelTx = await program.methods
.undelegate()
.accounts({
payer: providerEphemeralRollup.wallet.publicKey,
})
.transaction();
undelTx.feePayer = providerEphemeralRollup.wallet.publicKey;
undelTx.recentBlockhash = (
await providerEphemeralRollup.connection.getLatestBlockhash()
).blockhash;
undelTx = await providerEphemeralRollup.wallet.signTransaction(undelTx);
const undelTxHash = await providerEphemeralRollup.sendAndConfirm(undelTx);
이 공개 RPC 엔드포인트들은 현재 무료이며 개발용으로 지원됩니다:
Magic Router Devnet: https://devnet-router.magicblock.app
Solana Devnet: https://api.devnet.solana.com
ER Devnet: https://devnet.magicblock.app
TEE Devnet: https://devnet-tee.magicblock.app/
자세한 내용은 여기 에서 확인하세요.
Magic Router Devnet: https://devnet-router.magicblock.app
Solana Devnet: https://api.devnet.solana.com
ER Devnet: https://devnet.magicblock.app
TEE Devnet: https://devnet-tee.magicblock.app/
자세한 내용은 여기 에서 확인하세요.
고급 코드 스니펫
- Resize PDA
- Magic Router
- Magic Action
- 위임된 계정에 lamports 충전
- On-Curve Delegation
위임된 PDA를 리사이즈할 때:⬆️ Back to Top
- PDA는 새로운 계정 크기에서도 rent-exempt 상태를 유지할 만큼 충분한 lamports를 가지고 있어야 합니다。
- 추가 lamports가 필요하다면, 차액을 제공하기 위해 payer account도 위임되어 있어야 합니다。
- PDA는 프로그램이 소유하고 있어야 하며, 트랜잭션에는 lamports 전송에 필요한 signer가 포함되어야 합니다。
system_instruction::allocate를 사용합니다。
#[account]
pub struct Counter {
pub count: u64,
pub extra_data: Vec<u8>,
}
#[derive(Accounts)]
pub struct ResizeCounter<'info> {
#[account(mut)]
pub counter: Account<'info, Counter>,
#[account(mut)]
pub payer: Signer<'info>,
pub system_program: Program<'info, System>,
}
// Resize the counter (e.g., to store more extra_data)
pub fn resize_counter(ctx: Context<ResizeCounter>, new_size: usize) -> Result<()> {
let account_to_resize = &mut ctx.accounts.counter.to_account_info();
let payer = &mut ctx.accounts.payer.to_account_info();
// Calculate rent-exemption for the new size
let rent = Rent::get()?;
let min_balance = rent.minimum_balance(new_size);
// Top up lamports if needed
let current_lamports = **account_to_resize.lamports.borrow();
if current_lamports < min_balance {
let to_transfer = min_balance - current_lamports;
**payer.try_borrow_mut_lamports()? -= to_transfer;
**account_to_resize.try_borrow_mut_lamports()? += to_transfer;
}
// Resize account
account_to_resize.resize(new_size)?;
Ok(())
}
동적으로 트랜잭션을 보내기 전에 먼저 Magic Router 연결을 초기화하세요.트랜잭션 초기화, 전송, 확인에 사용할 선호 SDK를 선택하세요。⬆️ Back to Top
이 공개 RPC 엔드포인트들은 현재 무료이며 개발용으로 지원됩니다:
Magic Router Devnet: https://devnet-router.magicblock.app
Magic Router Devnet: https://devnet-router.magicblock.app
ephemeral-rollups-kit는@solana/kit용ephemeral-rollups-sdk는@solana/web.js용
import { Connection } from "@magicblock-labs/ephemeral-rollups-kit";
// Initialize connection
const connection = await Connection.create(
"https://devnet-router.magicblock.app",
"wss://devnet-router.magicblock.app"
);
// ... create transaction
// Send and confirm transaction
const txHash = await connection.sendAndConfirmTransaction(
transactionMessage,
[userKeypair],
{ commitment: "confirmed", skipPreflight: true }
);
import { sendAndConfirmTransaction } from "@solana/web3.js";
import { ConnectionMagicRouter } from "@magicblock-labs/ephemeral-rollups-sdk";
// Initialize connection
const connection = new ConnectionMagicRouter(
"https://devnet-router.magicblock.app/",
{ wsEndpoint: "wss://devnet-router.magicblock.app/" }
);
// ... create transaction
// Send and confirm transaction
const txHash = await sendAndConfirmTransaction(connection, tx, [payer], {
skipPreflight: true,
commitment: "confirmed",
});
빠른 접근
Magic Actions Example
GitHub에서 레퍼런스 구현 보기
1) 액션 명령 생성
update_leaderboard 명령은 커밋이 완료된 직후 베이스 레이어에서 실행됩니다. 계정 컨텍스트에 부여된 #[action] 어트리뷰트는 이 명령이 커밋 후 액션에서 호출 가능함을 나타냅니다.// program instruction
pub fn update_leaderboard(ctx: Context<UpdateLeaderboard>) -> Result<()> {
let leaderboard = &mut ctx.accounts.leaderboard;
let counter_info = &mut ctx.accounts.counter.to_account_info();
let mut data: &[u8] = &counter_info.try_borrow_data()?;
let counter = Counter::try_deserialize(&mut data)?;
if counter.count > leaderboard.high_score {
leaderboard.high_score = counter.count;
}
msg!(
"Leaderboard updated! High score: {}",
leaderboard.high_score
);
Ok(())
}
// instruction context
#[action]
#[derive(Accounts)]
pub struct UpdateLeaderboard<'info> {
#[account(mut, seeds = [LEADERBOARD_SEED], bump)]
pub leaderboard: Account<'info, Leaderboard>,
/// CHECK: PDA owner depends on: 1) Delegated: Delegation Program; 2) Undelegated: Your program ID
pub counter: UncheckedAccount<'info>,
}
2) 액션이 포함된 커밋 명령 구성
commit_and_update_leaderboard 커밋 명령은 ER에서 실행됩니다. MagicIntentBundleBuilder를 사용해 커밋과 커밋 후 액션을 모두 magic_context에 예약하며 — ER 트랜잭션이 베이스 레이어로 확정될 때 두 작업이 함께 적용됩니다.// commit action instruction on ER
pub fn commit_and_update_leaderboard(ctx: Context<CommitAndUpdateLeaderboard>) -> Result<()> {
// Build the post-commit action that updates the leaderboard on base layer
let instruction_data =
anchor_lang::InstructionData::data(&crate::instruction::UpdateLeaderboard {});
let action_args = ActionArgs::new(instruction_data);
let action_accounts = vec![
ShortAccountMeta {
pubkey: ctx.accounts.leaderboard.key(),
is_writable: true,
},
ShortAccountMeta {
pubkey: ctx.accounts.counter.key(),
is_writable: false,
},
];
let action = CallHandler {
destination_program: crate::ID,
accounts: action_accounts,
args: action_args,
// Signer that pays transaction fees for the action from its escrow PDA
escrow_authority: ctx.accounts.payer.to_account_info(),
compute_units: 200_000,
};
// Schedule commit + post-commit action on magic_context
MagicIntentBundleBuilder::new(
ctx.accounts.payer.to_account_info(),
ctx.accounts.magic_context.to_account_info(),
ctx.accounts.magic_program.to_account_info(),
)
.commit(&[ctx.accounts.counter.to_account_info()])
.add_post_commit_actions([action])
.build_and_invoke()?;
Ok(())
}
// commit action context on ER
#[commit]
#[derive(Accounts)]
pub struct CommitAndUpdateLeaderboard<'info> {
#[account(mut)]
pub payer: Signer<'info>,
#[account(mut, seeds = [COUNTER_SEED], bump)]
pub counter: Account<'info, Counter>,
/// CHECK: Leaderboard PDA - not mut here, writable set in handler
#[account(seeds = [LEADERBOARD_SEED], bump)]
pub leaderboard: UncheckedAccount<'info>,
/// CHECK: Your program ID
pub program_id: AccountInfo<'info>,
}
여러 액션 실행
여러 계정을 커밋하고 한 번의 호출로 여러 액션을 연결할 수 있습니다. 액션은add_post_commit_actions에 전달된 순서대로 순차 실행됩니다.// Chain several actions — they execute sequentially on base layer after the commit lands.
MagicIntentBundleBuilder::new(
ctx.accounts.payer.to_account_info(),
ctx.accounts.magic_context.to_account_info(),
ctx.accounts.magic_program.to_account_info(),
)
.commit(&[
ctx.accounts.counter.to_account_info(),
// ... additional committed accounts
])
.add_post_commit_actions([action_1, action_2, action_3])
.build_and_invoke()?;
위임 해제와 함께 액션 실행
액션은 위임 해제에도 연결할 수 있습니다 — counter 커밋, 위임 해제, 액션 실행이 모두 동일한 ER 트랜잭션 안에서 원자적으로 처리됩니다.// Commit, undelegate, AND execute actions — all atomically on base layer after the ER transaction seals.
MagicIntentBundleBuilder::new(
ctx.accounts.payer.to_account_info(),
ctx.accounts.magic_context.to_account_info(),
ctx.accounts.magic_program.to_account_info(),
)
.commit_and_undelegate(&[ctx.accounts.counter.to_account_info()])
.add_post_commit_actions([action])
.build_and_invoke()?;
위임된 계정에 lamports를 충전합니다. 트랜잭션은 base layer에서 제출되며, Ephemeral SPL Token 프로그램이 일회성 lamports PDA를 통해 위임된 잔고로 lamports를 옮깁니다.주요 용도: 위임된 fee payer에 자금을 충당해 기본으로 제공되는 10회의 commit 스폰서십 할당량을 넘어서도 위임된 fee payer 자체가 commit 비용을 지불할 수 있게 합니다.참고 사항:⬆️ Back to Top
- 충전할 때마다
crypto.getRandomValues로 새로운 32바이트 salt를 생성합니다 — salt를 재사용하면 기존 PDA와 충돌합니다. - ER가 아닌 base layer의 RPC에 제출합니다.
- 대상 계정은 이미 위임되어 있어야 합니다.
import {
Connection,
Keypair,
PublicKey,
Transaction,
sendAndConfirmTransaction,
} from "@solana/web3.js";
import {
lamportsDelegatedTransferIx,
deriveLamportsPda,
} from "@magicblock-labs/ephemeral-rollups-sdk";
/**
* Top up a delegated account with lamports.
*
* The transaction is submitted on the BASE LAYER. The Ephemeral SPL Token
* program creates a single-use lamports PDA, funds it from the payer, and
* delegates it so the ER credits the destination's delegated balance.
*/
async function topUpDelegatedAccount(
connection: Connection, // base-layer connection
payer: Keypair,
destination: PublicKey, // delegated account to top up
amountLamports: bigint,
) {
// Generate a fresh 32-byte salt per top-up.
// Re-using a salt collides with an existing lamports PDA and the call fails.
const salt = crypto.getRandomValues(new Uint8Array(32));
const [lamportsPda] = deriveLamportsPda(payer.publicKey, destination, salt);
const ix = await lamportsDelegatedTransferIx(
payer.publicKey,
destination,
amountLamports,
salt,
);
const tx = new Transaction().add(ix);
tx.feePayer = payer.publicKey;
// CRITICAL: send to the base-layer RPC, not the ER.
const sig = await sendAndConfirmTransaction(connection, tx, [payer], {
commitment: "confirmed",
skipPreflight: true,
});
return { sig, lamportsPda };
}
빠른 접근
GitHub
온커브 위임
- 위임할 온커브 계정
- 수수료 지불자
- System Account를 Delegation Program에 할당
- Delegation Program으로 위임
// Create assign instruction
// The on-curve account must sign this instruction to change its owner
const accountSigner = await cryptoKeyPairToTransactionSigner(userKeypair);
const delegationProgramAddress = address(DELEGATION_PROGRAM_ID.toString());
const assignInstruction = getAssignInstruction({
account: accountSigner,
programAddress: delegationProgramAddress,
});
// Create delegate instruction
const delegateInstruction = await createDelegateInstruction({
payer: feePayerAddress,
delegatedAccount: userAddress,
ownerProgram: ownerProgramAddress,
validator: validatorAddress,
});
// Prepare transaction
const transactionMessage = pipe(
createTransactionMessage({ version: 0 }),
(tx) => setTransactionMessageFeePayer(feePayerAddress, tx),
(tx) =>
appendTransactionMessageInstructions(
[assignInstruction, delegateInstruction],
tx
)
);
// Send and confirm transaction (fee payer need to sign, on-curve account cannot be signer since delegated)
const txHash = await connection.sendAndConfirmTransaction(
transactionMessage,
[userKeypair, feePayerKeypair],
{ commitment: "confirmed", skipPreflight: true }
);
// Create assign instruction
const assignInstruction = SystemProgram.assign({
accountPubkey: userPubkey,
programId: DELEGATION_PROGRAM_ID,
});
// Create delegate instruction
const delegateInstruction = createDelegateInstruction({
payer: feePayerKeypair.publicKey,
delegatedAccount: userPubkey,
ownerProgram: ownerProgram,
validator: validator,
});
// Create and send transaction (fee payer need to sign, on-curve account cannot be signer since delegated)
const tx = new Transaction().add(assignInstruction, delegateInstruction);
tx.feePayer = feePayerKeypair.publicKey;
const txSignature = await sendAndConfirmTransaction(
connectionBaseLayer,
tx,
[userKeypair, feePayerKeypair],
{
skipPreflight: true,
}
);
// Create commit and undelegate instruction
const commitAndUndelegateInstruction = createCommitAndUndelegateInstruction(
userAddress,
[userAddress]
);
// Prepare transaction
const transactionMessage = pipe(
createTransactionMessage({ version: 0 }),
(tx) => setTransactionMessageFeePayer(feePayerAddress, tx),
(tx) =>
appendTransactionMessageInstructions([commitAndUndelegateInstruction], tx)
);
// Send and confirm transaction on ephemeral connection
const txHash = await ephemeralConnection.sendAndConfirmTransaction(
transactionMessage,
[userKeypair, feePayerKeypair],
{ commitment: "confirmed", skipPreflight: true }
);
// Create commit and undelegate instruction
const commitAndUndelegateInstruction = createCommitAndUndelegateInstruction(
userPubkey,
[userPubkey]
);
// Send and confirm transaction on ephemeral connection
const tx = new Transaction().add(commitAndUndelegateInstruction);
tx.feePayer = feePayerKeypair.publicKey;
const txSignature = await sendAndConfirmTransaction(
ephemeralConnection,
tx,
[userKeypair, feePayerKeypair],
{
skipPreflight: true,
}
);
빠른 접근
Private ER, Rust Native 구현, 로컬 개발을 더 알아보세요.Private Ephemeral Rollups (PER)
빠른 시작
Rust Native
빠른 시작
Guide
로컬 개발
Solana 익스플로러
Solana에서의 트랜잭션과 계정 정보를 확인해 보세요.Solana 익스플로러
공식 Solana 익스플로러
Solscan
Solana 블록체인 살펴보기
Solana RPC 제공업체
기존 RPC 제공업체를 통해 트랜잭션과 요청을 전송하세요.Solana
Free Public Nodes
Helius
Free Shared Nodes
Triton
Dedicated High-Performance Nodes
Solana 검증자 대시보드
Solana 검증자 인프라의 실시간 업데이트를 확인하세요.Solana Beach
Get Validator Insights
Validators App
Discover Validator Metrics
서버 상태
Solana와 MagicBlock의 서버 상태를 확인해 보세요.Solana Status
Subscribe to Solana Server Updates
MagicBlock Status
Subscribe to MagicBlock Server Status
MagicBlock 제품
에페메럴 롤업(ER)
Solana에서 실시간 무수수료 트랜잭션을 안전하게 실행하세요.
프라이빗 에페메럴 롤업(PER)
규정 준수를 유지하면서 민감한 데이터 보호 — Ephemeral Rollups 위에 구축되었습니다.
프라이빗 결제 API
몇 초 만에 앱에 온체인 비공개 송금을 통합하세요 — 기본적으로 규정 준수.
Solana VRF
게임, 추첨, 실시간 앱에 증명 가능하게 공정한 온체인 랜덤니스를 추가하세요.
가격 오라클
트레이딩과 DeFi를 위한 저지연 온체인 가격 피드에 접근하세요.

