正在使用 AI 编程助手开发? 安装 MagicBlock Dev Skill,为你的助手提供 MagicBlock 专属开发模式——委托流程、Magic Actions、Crank、VRF 等。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 完成升级:
按照你平常的方式编写 Solana 程序。
在程序中添加 delegation 和 undelegation hooks
添加 CPI hooks,使状态账户能够在 Ephemeral Rollup 会话中完成 delegate、commit 和 undelegate。
这些公共验证器可用于开发环境。请确保在你的委托指令中添加对应的 ER 验证器:
主网- 亚洲 (as.magicblock.app):
MAS1Dt9qreoRMQ14YQuhg8UTZMMzDdKhmkZMECCzk57 - 欧盟 (eu.magicblock.app):
MEUGGrYPxKk17hCr7wpT6s8dtNokZj5U2L57vjYMS8e - 美国 (us.magicblock.app):
MUS3hc9TCw4cGC12vHNoYcCGzJG1txjgQLZWVoeNHNd - TEE (mainnet-tee.magicblock.app):
MTEWGuqxUpYZGFJQcp8tLN7x5v9BSeoFHYWQQ3n3xzo
- 亚洲 (devnet-as.magicblock.app):
MAS1Dt9qreoRMQ14YQuhg8UTZMMzDdKhmkZMECCzk57 - 欧盟 (devnet-eu.magicblock.app):
MEUGGrYPxKk17hCr7wpT6s8dtNokZj5U2L57vjYMS8e - 美国 (devnet-us.magicblock.app):
MUS3hc9TCw4cGC12vHNoYcCGzJG1txjgQLZWVoeNHNd - TEE (devnet-tee.magicblock.app):
MTEWGuqxUpYZGFJQcp8tLN7x5v9BSeoFHYWQQ3n3xzo
- 本地 ER (localhost:7799):
mAGicPQYBMvcYveUZA5F5UNNwyHvfYh5xkLS2Fr1mev
使用 Anchor 或 Solana CLI 直接将程序部署到 Solana 上。
在链上和链下发送无需修改且符合 SVM RPC 规范的交易。
Counter 示例

代码片段
- 1. Write program
- 2. Delegate
- 3. Deploy
- 4. Test
该程序实现了两个主要 instruction:这里没有什么特别之处,只是一个简单的 Anchor 计数器程序。唯一的区别是我们加入了用于 undelegation 的
initialize:将计数器设为 0increment:将计数器加 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.magicblock.app):
MEUGGrYPxKk17hCr7wpT6s8dtNokZj5U2L57vjYMS8e - 美国 (us.magicblock.app):
MUS3hc9TCw4cGC12vHNoYcCGzJG1txjgQLZWVoeNHNd - TEE (mainnet-tee.magicblock.app):
MTEWGuqxUpYZGFJQcp8tLN7x5v9BSeoFHYWQQ3n3xzo
- 亚洲 (devnet-as.magicblock.app):
MAS1Dt9qreoRMQ14YQuhg8UTZMMzDdKhmkZMECCzk57 - 欧盟 (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: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。你还可以指定希望使用的 delegation 配置,例如自动 commit 和特定 ER validator:
/// 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 就可以在 SVM runtime 中使用这些PDA执行交易。
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.magicblock.app):
MEUGGrYPxKk17hCr7wpT6s8dtNokZj5U2L57vjYMS8e - 美国 (us.magicblock.app):
MUS3hc9TCw4cGC12vHNoYcCGzJG1txjgQLZWVoeNHNd - TEE (mainnet-tee.magicblock.app):
MTEWGuqxUpYZGFJQcp8tLN7x5v9BSeoFHYWQQ3n3xzo
- 亚洲 (devnet-as.magicblock.app):
MAS1Dt9qreoRMQ14YQuhg8UTZMMzDdKhmkZMECCzk57 - 欧盟 (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
- 为已委托账户补充余额
- On-Curve Delegation
当调整已委托 PDA 的大小时:⬆️ Back to Top
- PDA 必须有足够的 lamports,才能在新账户大小下继续保持 rent-exempt。
- 如果需要额外 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/kitephemeral-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 }
);
快速入口
Magic Actions Example
在 GitHub 上查看参考实现
1) 创建 action 指令
update_leaderboard 指令会在提交完成后立即于基础层执行。其账户上下文上的 #[action] 属性将其标记为可由提交后 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) 构建带有 action 的提交指令
commit_and_update_leaderboard 提交指令在 ER 上运行。它使用 MagicIntentBundleBuilder 将提交和提交后 action 一起调度到 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>,
}
执行多个 action
你可以一次提交多个账户并串联多个 action。Action 按其传入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()?;
解除委托时执行 action
Action 也可以串联到解除委托上 —— counter 提交、解除委托以及 action 全部在同一个 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 转送到目标账户在 ER 上的委托余额。常见使用场景:给被委托的 fee payer 不断补充余额,以便在超过默认 10 次 commit 赞助额度后,该 PDA 仍能继续为自己的 commit 付费。要点:⬆️ Back to Top
- 每次补充都需通过
crypto.getRandomValues生成一个全新的 32 字节 salt —— 重复使用会与现有 PDA 冲突。 - 提交到 base-layer RPC,而不是 ER。
- 目标账户必须已被委托。
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 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 }
);
快速入口
进一步了解 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 产品
Ephemeral Rollup(ER)
在 Solana 上安全执行实时、零手续费交易。
Private Ephemeral Rollup(PER)
在合规的前提下保护敏感数据——基于 Ephemeral Rollups 构建。
私密支付 API
几秒钟为你的应用集成链上私密转账——默认合规。
Solana VRF
为游戏、抽奖和实时应用添加可证明公平的链上随机数。
价格预言机
获取适用于交易和 DeFi 的低延迟链上价格数据。

