> ## Documentation Index
> Fetch the complete documentation index at: https://docs.magicblock.gg/llms.txt
> Use this file to discover all available pages before exploring further.

# 빠른 시작

> 어떤 Solana 프로그램이든 delegation 기능을 추가하면 Ephemeral Rollups를 지원하도록 업그레이드할 수 있습니다.

***

<Tip>
  **AI 코딩 에이전트로 개발하고 계신가요?** MagicBlock Dev Skill을 설치하면 위임 플로우, Magic Actions, Crank, VRF 등 MagicBlock 전용 개발 패턴을 에이전트에 제공할 수 있습니다.

  Claude Code 빠른 설치:

  ```bash theme={null}
  npx add-skill https://github.com/magicblock-labs/magicblock-dev-skill
  ```

  Cursor, Codex, Windsurf, Cline 등 다른 에이전트를 사용하시나요? 모든 설치 방법은 [AI Dev Skill](/ko/pages/overview/additional-information/ai-dev-skill) 페이지를 참고하세요.
</Tip>

### 빠른 접근

기본 카운터 예제를 확인해 보세요.

<CardGroup cols={2}>
  <Card title="GitHub" icon="anchor" href="https://github.com/magicblock-labs/magicblock-engine-examples/tree/main/anchor-counter" iconType="duotone">
    Anchor 구현
  </Card>

  <Card title="GitHub" icon="react" href="https://github.com/GabrielePicco/ephemeral-counter-ui" iconType="duotone">
    React 구현
  </Card>
</CardGroup>

***

<div
  style={{
position: "relative",
paddingBottom: "56.25%",
height: 0,
overflow: "hidden",
}}
>
  <iframe
    src="https://www.youtube.com/embed/qwu2RBKyFiw?si=PMg-3UbRfvvbrs7C&list=PLWR_ZQiGMS8mIe1kPZe8OfHIbhvZqaM8V"
    title="Build a real-time Anchor Counter on Solana with MagicBlock's Ephemeral Rollup | Tutorial"
    style={{
  position: "absolute",
  top: 0,
  left: 0,
  width: "100%",
  height: "100%",
}}
    frameBorder="0"
    allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
    allowFullScreen
    referrerPolicy="strict-origin-when-cross-origin"
  />
</div>

***

<div id="quickstart" />

## 단계별 가이드

프로그램을 빌드하고 MagicBlock의 Delegation Program `DELeGGvXpWV2fqJUhqcF5ZSYMS4JTLjteaAMARRSaeSh` 을 사용해 delegation hooks를 추가하여 업그레이드하세요。

<Steps>
  <Step title={<a href="#1-write-program">프로그램 작성</a>}>
    평소와 같이 Solana 프로그램을 작성하세요。
  </Step>

  <Step
    title={
  <a href="#2-delegate">
    프로그램에 delegation 및 undelegation hooks 추가
  </a>
}
  >
    CPI hooks를 추가해 상태 계정을 Ephemeral Rollup 세션을 통해 delegate, commit, undelegate 할 수 있게 하세요。

    <Note>
      <p>
        이 공용 검증자들은 개발용으로 지원됩니다. 위임 명령에 해당 ER
        검증자를 반드시 추가하세요.
      </p>

      **메인넷**

      <ul>
        <li>
          아시아 (as.magicblock.app):{" "}
          <code>MAS1Dt9qreoRMQ14YQuhg8UTZMMzDdKhmkZMECCzk57</code>
        </li>

        <li>
          EU (eu.magicblock.app):{" "}
          <code>MEUGGrYPxKk17hCr7wpT6s8dtNokZj5U2L57vjYMS8e</code>
        </li>

        <li>
          미국 (us.magicblock.app):{" "}
          <code>MUS3hc9TCw4cGC12vHNoYcCGzJG1txjgQLZWVoeNHNd</code>
        </li>

        <li>
          TEE (mainnet-tee.magicblock.app):{" "}
          <code>MTEWGuqxUpYZGFJQcp8tLN7x5v9BSeoFHYWQQ3n3xzo</code>
        </li>
      </ul>

      **데브넷**

      <ul>
        <li>
          아시아 (devnet-as.magicblock.app):{" "}
          <code>MAS1Dt9qreoRMQ14YQuhg8UTZMMzDdKhmkZMECCzk57</code>
        </li>

        <li>
          EU (devnet-eu.magicblock.app):{" "}
          <code>MEUGGrYPxKk17hCr7wpT6s8dtNokZj5U2L57vjYMS8e</code>
        </li>

        <li>
          미국 (devnet-us.magicblock.app):{" "}
          <code>MUS3hc9TCw4cGC12vHNoYcCGzJG1txjgQLZWVoeNHNd</code>
        </li>

        <li>
          TEE (devnet-tee.magicblock.app):{" "}
          <code>MTEWGuqxUpYZGFJQcp8tLN7x5v9BSeoFHYWQQ3n3xzo</code>
        </li>
      </ul>

      **로컬넷**

      <ul>
        <li>
          로컬 ER (localhost:7799):{" "}
          <code>mAGicPQYBMvcYveUZA5F5UNNwyHvfYh5xkLS2Fr1mev</code>
        </li>
      </ul>
    </Note>
  </Step>

  <Step title={<a href="#3-deploy">프로그램을 Solana에 배포합니다.</a>}>
    Anchor 또는 Solana CLI를 사용해 프로그램을 Solana에 직접 배포하세요。
  </Step>

  <Step
    title={
  <a href="#4-test">
    delegation 및 실시간 고속 거래 실행 준비
  </a>
}
  >
    SVM RPC 사양을 준수하는 트랜잭션을 수정 없이 온체인과 오프체인에서 전송하세요。
  </Step>
</Steps>

***

<div id="counter-example" />

## Counter 예제

<img
  src="https://mintcdn.com/magicblock-42/iteauKFqxDKE2Vln/images/gifs/counter-420w.gif?s=f6feee67754b5b0b90c94ad159d4e630"
  alt="Counter GIF"
  style={{
width: "100%",
maxWidth: "420px",
height: "auto",
objectFit: "contain",
borderRadius: "8px",
}}
  width="420"
  height="420"
  data-path="images/gifs/counter-420w.gif"
/>

다음 소프트웨어 패키지가 필요할 수 있으며, 다른 버전도 호환될 수 있습니다.

| 소프트웨어      | 버전      | 설치 가이드                                                     |
| ---------- | ------- | ---------------------------------------------------------- |
| **Solana** | 3.1.9   | [Solana 설치](https://docs.anza.xyz/cli/install)             |
| **Rust**   | 1.89.0  | [Rust 설치](https://www.rust-lang.org/tools/install)         |
| **Anchor** | 1.0.2   | [Anchor 설치](https://www.anchor-lang.com/docs/installation) |
| **Node**   | 24.10.0 | [Node 설치](https://nodejs.org/en/download/current)          |

<div id="code-snippets" />

<div id="code-snippets-section" />

### 코드 스니펫

<Tabs>
  <Tab title="1. Write program">
    이 프로그램은 두 가지 주요 instruction을 구현합니다。

    1. `initialize`: 카운터를 0으로 설정
    2. `increment`: 카운터를 1 증가

    이 프로그램은 카운터의 위임과 위임 해제를 위한 전용 instruction도 구현합니다。

    1. `Delegate`: 카운터를 Base Layer에서 ER로 위임 (Base Layer에서 호출)
    2. `CommitAndUndelegate`: 카운터를 ER에서 Base Layer로 동기화하도록 예약하고 ER에서 위임 해제 (ER에서 호출)
    3. `Commit`: 카운터를 ER에서 Base Layer로 동기화하도록 예약 (ER에서 호출)
    4. `Undelegate`:
       * 카운터의 동기화와 위임 해제를 예약 (ER에서 호출)
       * `#[ephemeral]` 로 주입된 callback instruction을 통해 undelegation 트리거 (validator CPI를 통해 Base Layer에서 호출)

    <Note>
      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]`](/ko/pages/ephemeral-rollups-ers/how-to-guide/quickstart#1-write-program) Anchor macro from MagicBlock's Ephemeral Rollup SDK, the undelegation callback discriminator and processor are injected into your program.
    </Note>

    ```rust theme={null}
    #[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
    ```

    특별한 것은 없고, 단순한 Anchor 카운터 프로그램입니다. 차이점은 undelegation을 위한 `ephemeral` macro와 delegation program과 상호작용하는 유용한 로직을 주입하는 `delegate` macro를 추가했다는 점입니다。

    [⬆️ Back to Top](#code-snippets)
  </Tab>

  <Tab title="2. Delegate">
    <Note>
      <p>
        이 공용 검증자들은 개발용으로 지원됩니다. 위임 명령에 해당 ER
        검증자를 반드시 추가하세요.
      </p>

      **메인넷**

      <ul>
        <li>
          아시아 (as.magicblock.app):{" "}
          <code>MAS1Dt9qreoRMQ14YQuhg8UTZMMzDdKhmkZMECCzk57</code>
        </li>

        <li>
          EU (eu.magicblock.app):{" "}
          <code>MEUGGrYPxKk17hCr7wpT6s8dtNokZj5U2L57vjYMS8e</code>
        </li>

        <li>
          미국 (us.magicblock.app):{" "}
          <code>MUS3hc9TCw4cGC12vHNoYcCGzJG1txjgQLZWVoeNHNd</code>
        </li>

        <li>
          TEE (mainnet-tee.magicblock.app):{" "}
          <code>MTEWGuqxUpYZGFJQcp8tLN7x5v9BSeoFHYWQQ3n3xzo</code>
        </li>
      </ul>

      **데브넷**

      <ul>
        <li>
          아시아 (devnet-as.magicblock.app):{" "}
          <code>MAS1Dt9qreoRMQ14YQuhg8UTZMMzDdKhmkZMECCzk57</code>
        </li>

        <li>
          EU (devnet-eu.magicblock.app):{" "}
          <code>MEUGGrYPxKk17hCr7wpT6s8dtNokZj5U2L57vjYMS8e</code>
        </li>

        <li>
          미국 (devnet-us.magicblock.app):{" "}
          <code>MUS3hc9TCw4cGC12vHNoYcCGzJG1txjgQLZWVoeNHNd</code>
        </li>

        <li>
          TEE (devnet-tee.magicblock.app):{" "}
          <code>MTEWGuqxUpYZGFJQcp8tLN7x5v9BSeoFHYWQQ3n3xzo</code>
        </li>
      </ul>

      **로컬넷**

      <ul>
        <li>
          로컬 ER (localhost:7799):{" "}
          <code>mAGicPQYBMvcYveUZA5F5UNNwyHvfYh5xkLS2Fr1mev</code>
        </li>
      </ul>
    </Note>

    1. Anchor 기능이 포함된 `ephemeral-rollups-sdk` 를 프로그램에 추가합니다

    ```bash theme={null}
    cargo add ephemeral-rollups-sdk --features anchor
    ```

    `delegate`, `commit`, `ephemeral`, `DelegateConfig`, `commit_accounts`, `commit_and_undelegate_accounts` 를 import 합니다：

    ```rust theme={null}
    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
      };
    ```

    2. 프로그램에 `delegate` macro와 instruction, `ephemeral` macro, `undelegate` instruction을 추가하세요. auto commit이나 특정 ER validator 같은 delegation config도 지정할 수 있습니다：

    ```rust theme={null}
    /// 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>,
    }
    ```

    ```rust theme={null}
    /// 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(())
    }
    ```

    ```rust theme={null}
    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(())
    }
    ```

    ```rust theme={null}
    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에서 잠겨 있는 상태로 유지됩니다。

    > `Undelegation` 은 `PDA` 의 소유권을 다시 프로그램으로 되돌리는 과정입니다. undelegation 시 상태가 커밋되고 finalization 프로세스가 시작됩니다. 상태 검증이 완료되면 `PDA` 가 잠금 해제되어 base layer에서 평소처럼 사용할 수 있습니다。

    [⬆️ Back to Top](#code-snippets)
  </Tab>

  <Tab title="3. Deploy">
    이제 프로그램 업그레이드가 완료되었습니다! 원하는 cluster로 빌드하고 배포하세요：

    ```bash theme={null}
    anchor build && anchor deploy
    ```

    [⬆️ Back to Top](#code-snippets)
  </Tab>

  <Tab title="4. Test">
    이제 delegation 및 실시간 고속 거래를 실행할 준비가 되었습니다。

    <Note>
      <p>
        이 공용 검증자들은 개발용으로 지원됩니다. 위임 명령에 해당 ER
        검증자를 반드시 추가하세요.
      </p>

      **메인넷**

      <ul>
        <li>
          아시아 (as.magicblock.app):{" "}
          <code>MAS1Dt9qreoRMQ14YQuhg8UTZMMzDdKhmkZMECCzk57</code>
        </li>

        <li>
          EU (eu.magicblock.app):{" "}
          <code>MEUGGrYPxKk17hCr7wpT6s8dtNokZj5U2L57vjYMS8e</code>
        </li>

        <li>
          미국 (us.magicblock.app):{" "}
          <code>MUS3hc9TCw4cGC12vHNoYcCGzJG1txjgQLZWVoeNHNd</code>
        </li>

        <li>
          TEE (mainnet-tee.magicblock.app):{" "}
          <code>MTEWGuqxUpYZGFJQcp8tLN7x5v9BSeoFHYWQQ3n3xzo</code>
        </li>
      </ul>

      **데브넷**

      <ul>
        <li>
          아시아 (devnet-as.magicblock.app):{" "}
          <code>MAS1Dt9qreoRMQ14YQuhg8UTZMMzDdKhmkZMECCzk57</code>
        </li>

        <li>
          EU (devnet-eu.magicblock.app):{" "}
          <code>MEUGGrYPxKk17hCr7wpT6s8dtNokZj5U2L57vjYMS8e</code>
        </li>

        <li>
          미국 (devnet-us.magicblock.app):{" "}
          <code>MUS3hc9TCw4cGC12vHNoYcCGzJG1txjgQLZWVoeNHNd</code>
        </li>

        <li>
          TEE (devnet-tee.magicblock.app):{" "}
          <code>MTEWGuqxUpYZGFJQcp8tLN7x5v9BSeoFHYWQQ3n3xzo</code>
        </li>
      </ul>

      **로컬넷**

      <ul>
        <li>
          로컬 ER (localhost:7799):{" "}
          <code>mAGicPQYBMvcYveUZA5F5UNNwyHvfYh5xkLS2Fr1mev</code>
        </li>
      </ul>
    </Note>

    ```bash theme={null}
    anchor test --skip-build --skip-deploy --skip-local-validator
    ```

    다음 테스트를 실행하세요：

    ```typescript theme={null}
    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);
    ```

    프런트엔드 통합을 더 쉽게 하기 위해 [Magic Router](/ko/pages/ephemeral-rollups-ers/introduction/magic-router)를 만들었습니다. 트랜잭션을 magic router로 직접 보내면, 그것이 [Ephemeral Rollup](/ko/pages/ephemeral-rollups-ers/introduction/ephemeral-rollup)으로 가야 하는지 base layer로 가야 하는지를 자동으로 판단해 줍니다。

    <Note>
      이 공개 RPC 엔드포인트들은 현재 무료이며 개발용으로 지원됩니다:
      <br /> Magic Router Devnet: [https://devnet-router.magicblock.app](https://devnet-router.magicblock.app) <br />
      Solana Devnet: [https://api.devnet.solana.com](https://api.devnet.solana.com) <br />
      ER Devnet: [https://devnet.magicblock.app](https://devnet.magicblock.app) <br />
      TEE Devnet: [https://devnet-tee.magicblock.app/](https://devnet-tee.magicblock.app/) <br />
      자세한 내용은{" "}
      <a href="/ko/pages/ephemeral-rollups-ers/how-to-guide/local-development">여기</a>
      에서 확인하세요.
    </Note>

    [⬆️ Back to Top](#code-snippets)
  </Tab>
</Tabs>

***

<div id="advanced-code-snippets" />

### 고급 코드 스니펫

<Tabs>
  <Tab title="Resize PDA">
    위임된 PDA를 리사이즈할 때：

    * PDA는 새로운 계정 크기에서도 rent-exempt 상태를 유지할 만큼 충분한 lamports를 가지고 있어야 합니다。
    * 추가 lamports가 필요하다면, 차액을 제공하기 위해 **payer account도 위임되어 있어야 합니다**。
    * PDA는 프로그램이 소유하고 있어야 하며, 트랜잭션에는 lamports 전송에 필요한 signer가 포함되어야 합니다。
    * `system_instruction::allocate` 를 사용합니다。

    ```rust theme={null}
    #[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(())
        }
    ```

    [⬆️ Back to Top](#advanced-code-snippets)
  </Tab>

  <Tab title="Magic Router">
    동적으로 트랜잭션을 보내기 전에 먼저 Magic Router 연결을 초기화하세요.

    <Note>
      이 공개 RPC 엔드포인트들은 현재 무료이며 개발용으로 지원됩니다:
      <br /> Magic Router Devnet: [https://devnet-router.magicblock.app](https://devnet-router.magicblock.app) <br />
    </Note>

    트랜잭션 초기화, 전송, 확인에 사용할 선호 SDK를 선택하세요。

    * `ephemeral-rollups-kit` 는 `@solana/kit` 용
    * `ephemeral-rollups-sdk` 는 `@solana/web.js` 용

    <CodeGroup>
      ```typescript Kit theme={null}
      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 }
      );
      ```

      ```typescript Web3.js theme={null}
      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",
      });
      ```
    </CodeGroup>

    [⬆️ Back to Top](#advanced-code-snippets)
  </Tab>

  <Tab title="Magic Action">
    ### 빠른 접근

    <CardGroup cols={2}>
      <Card title="Magic Actions Example" icon="code" href="https://github.com/magicblock-labs/magicblock-engine-examples/tree/main/magic-actions" iconType="duotone">
        GitHub에서 레퍼런스 구현 보기
      </Card>
    </CardGroup>

    Ephemeral Rollup
    (ER) commit 직후 Solana base layer에서 자동으로 실행되는 하나 이상의 instruction을 연결합니다。
    [Magic Action 더 알아보기](/ko/pages/ephemeral-rollups-ers/magic-actions/overview.mdx)

    ### 1) 액션 명령 생성

    `update_leaderboard` 명령은 커밋이 완료된 직후 베이스 레이어에서 실행됩니다. 계정 컨텍스트에 부여된 `#[action]` 어트리뷰트는 이 명령이 커밋 후 액션에서 호출 가능함을 나타냅니다.

    ```rust theme={null}
    // 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 트랜잭션이 베이스 레이어로 확정될 때 두 작업이 함께 적용됩니다.

    ```rust theme={null}
    // 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`에 전달된 순서대로 순차 실행됩니다.

    ```rust theme={null}
    // 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 트랜잭션 안에서 원자적으로 처리됩니다.

    ```rust theme={null}
    // 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()?;
    ```

    [⬆️ Back to Top](#advanced-code-snippets)
  </Tab>

  <Tab title="위임된 계정에 lamports 충전">
    위임된 계정에 lamports를 충전합니다. 트랜잭션은 **base layer**에서 제출되며, Ephemeral SPL Token 프로그램이 일회성 lamports PDA를 통해 위임된 잔고로 lamports를 옮깁니다.

    주요 용도: 위임된 fee payer에 자금을 충당해 기본으로 제공되는 10회의 commit 스폰서십 할당량을 넘어서도 위임된 fee payer 자체가 commit 비용을 지불할 수 있게 합니다.

    참고 사항:

    * 충전할 때마다 `crypto.getRandomValues`로 새로운 32바이트 salt를 생성합니다 — salt를 재사용하면 기존 PDA와 충돌합니다.
    * ER가 아닌 **base layer의 RPC**에 제출합니다.
    * 대상 계정은 이미 위임되어 있어야 합니다.

    ```typescript theme={null}
    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 };
    }
    ```

    [⬆️ Back to Top](#advanced-code-snippets)
  </Tab>

  <Tab title="On-Curve Delegation">
    ### 빠른 접근

    <CardGroup cols={2}>
      <Card title="GitHub" icon="wallet" href="https://github.com/magicblock-labs/magicblock-engine-examples/tree/main/oncurve-delegation" iconType="duotone">
        온커브 위임
      </Card>
    </CardGroup>

    온커브 계정을 위임할 때 필요한 서명자:

    1. 위임할 온커브 계정
    2. 수수료 지불자

    온커브 계정을 위임할 때 필요한 명령:

    1. System Account를 Delegation Program에 할당
    2. Delegation Program으로 위임

    <CodeGroup>
      ```typescript Kit theme={null}
      // 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 }
      );
      ```

      ```typescript Web3.js theme={null}
      // 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,
        }
      );
      ```
    </CodeGroup>

    직접 commit 및 undelegate는 Magic Program을 통해서만 수행합니다.

    <CodeGroup>
      ```typescript Kit theme={null}
      // 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 }
      );
      ```

      ```typescript Web3.js theme={null}
      // 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,
        }
      );
      ```
    </CodeGroup>

    [⬆️ Back to Top](#advanced-code-snippets)
  </Tab>
</Tabs>

***

### 빠른 접근

Private ER, Rust Native 구현, 로컬 개발을 더 알아보세요.

<CardGroup>
  <Card title="Private Ephemeral Rollups (PER)" icon="anchor" href="/ko/pages/private-ephemeral-rollups-pers/how-to-guide/quickstart" iconType="duotone">
    빠른 시작
  </Card>

  <Card title="Rust Native" icon="rust" href="/ko/pages/ephemeral-rollups-ers/how-to-guide/rust-program" iconType="duotone">
    빠른 시작
  </Card>

  <Card title="Guide" icon="computer" href="/ko/pages/ephemeral-rollups-ers/how-to-guide/local-development" iconType="duotone">
    로컬 개발
  </Card>
</CardGroup>

***

## Solana 익스플로러

Solana에서의 트랜잭션과 계정 정보를 확인해 보세요.

<CardGroup cols={2}>
  <Card title="Solana 익스플로러" icon="search" href="https://explorer.solana.com/" iconType="duotone">
    공식 Solana 익스플로러
  </Card>

  <Card title="Solscan" icon="searchengin" href="https://solscan.io/" iconType="duotone">
    Solana 블록체인 살펴보기
  </Card>
</CardGroup>

## Solana RPC 제공업체

기존 RPC 제공업체를 통해 트랜잭션과 요청을 전송하세요.

<CardGroup cols={2}>
  <Card title="Solana" icon="star" href="https://solana.com/docs/references/clusters#on-a-high-level" iconType="duotone">
    Free Public Nodes
  </Card>

  <Card title="Helius" icon="sun" href="https://www.helius.dev/solana-rpc-nodes" iconType="duotone">
    Free Shared Nodes
  </Card>

  <Card title="Triton" icon="crystal-ball" href="https://triton.one/solana" iconType="duotone">
    Dedicated High-Performance Nodes
  </Card>
</CardGroup>

## Solana 검증자 대시보드

Solana 검증자 인프라의 실시간 업데이트를 확인하세요.

<CardGroup cols={2}>
  <Card title="Solana Beach" icon="wave" href="https://solanabeach.io/" iconType="duotone">
    Get Validator Insights
  </Card>

  <Card title="Validators App" icon="cloud-binary" href="https://www.validators.app/" iconType="duotone">
    Discover Validator Metrics
  </Card>
</CardGroup>

## 서버 상태

Solana와 MagicBlock의 서버 상태를 확인해 보세요.

<CardGroup cols={2}>
  <Card title="Solana Status" icon="server" href="https://status.solana.com/" iconType="duotone">
    Subscribe to Solana Server Updates
  </Card>

  <Card title="MagicBlock Status" icon="heart-pulse" href="/ko/pages/overview/additional-information/system-status" iconType="duotone">
    Subscribe to MagicBlock Server Status
  </Card>
</CardGroup>

***

<div id="magicblock-products" />

## MagicBlock 제품

<CardGroup cols={2}>
  <Card title="에페메럴 롤업(ER)" icon="bolt" href="/ko/pages/ephemeral-rollups-ers/how-to-guide/quickstart" iconType="duotone">
    Solana에서 실시간 무수수료 트랜잭션을 안전하게 실행하세요.
  </Card>

  <Card title="프라이빗 에페메럴 롤업(PER)" icon="shield-check" href="/ko/pages/private-ephemeral-rollups-pers/how-to-guide/quickstart" iconType="duotone">
    규정 준수를 유지하면서 민감한 데이터 보호 — Ephemeral Rollups 위에 구축되었습니다.
  </Card>

  <Card title="프라이빗 결제 API" icon="bag-shopping-plus" href="/ko/pages/private-ephemeral-rollups-pers/api-reference/per/introduction" iconType="duotone">
    몇 초 만에 앱에 온체인 비공개 송금을 통합하세요 — 기본적으로 규정 준수.
  </Card>

  <Card title="Solana VRF" icon="dice" href="/ko/pages/verifiable-randomness-functions-vrfs/introduction/solana-vrf" iconType="duotone">
    게임, 추첨, 실시간 앱에 증명 가능하게 공정한 온체인 랜덤니스를 추가하세요.
  </Card>

  <Card title="가격 오라클" icon="waveform" href="/ko/pages/tools/oracle/introduction" iconType="duotone">
    트레이딩과 DeFi를 위한 저지연 온체인 가격 피드에 접근하세요.
  </Card>
</CardGroup>

***
