> ## 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 프로그램

> Solana에서 카운터를 위임하고 증가시키는 간단한 Rust 프로그램 작성법 배우기

***

### 빠른 접근

다른 구현의 기본 카운터 예제를 확인해 보세요。

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

  <Card title="GitHub" icon="face-lying" href="https://github.com/magicblock-labs/magicblock-engine-examples/tree/main/pinocchio-counter" iconType="duotone">
    Pinocchio 구현
  </Card>

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

***

<div
  style={{
position: "relative",
paddingBottom: "56.25%",
height: 0,
overflow: "hidden",
}}
>
  <iframe
    src="https://www.youtube.com/embed/b77bwSGDHK0?si=Oknc5f8CuC17WBnV&list=PLWR_ZQiGMS8mIe1kPZe8OfHIbhvZqaM8V"
    title="Build a real-time Rust 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>

***

## 단계별 가이드

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

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

  <Step title={<a href="#2-delegate">Base Layer에서 PDA Delegate 하기</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-commit">ER에서 PDA Commit 하기</a>}>
    Solana CLI를 사용해 프로그램을 Solana에 직접 배포하세요。
  </Step>

  <Step title={<a href="#4-undelegate">ER에서 PDA Undelegate 하기</a>}>
    SVM RPC 사양을 준수하는 트랜잭션을 수정 없이 온체인과 오프체인에서 전송하세요。
  </Step>
</Steps>

***

## Counter 예제

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

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

<div id="code-snippets" />

### 코드 스니펫

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

    1. `InitializeCounter`: 카운터를 초기화하고 0으로 설정 (Base Layer에서 호출)
    2. `IncreaseCounter`: 초기화된 카운터를 X만큼 증가 (Base Layer 또는 ER에서 호출)

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

    1. `Delegate`: 카운터를 Base Layer에서 ER로 위임 (Base Layer에서 호출)
    2. `CommitAndUndelegate`: 카운터를 ER에서 Base Layer로 동기화하도록 예약하고 ER에서 위임 해제 (ER에서 호출)
    3. `Commit`: 카운터를 ER에서 Base Layer로 동기화하도록 예약 (ER에서 호출)
    4. `Undelegate`: Base Layer에서 카운터를 위임 해제 (validator CPI를 통해 Base Layer에서 호출)
    5. `IncrementAndCommit`: 단일 ER 트랜잭션에서 증가와 커밋을 수행 (ER에서 호출)
    6. `IncrementAndUndelegate`: 단일 ER 트랜잭션에서 증가, 커밋, 위임 해제를 수행 (ER에서 호출)

    <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}
    use borsh::BorshDeserialize;
    use solana_program::program_error::ProgramError;

    pub enum ProgramInstruction {
        InitializeCounter,
        IncreaseCounter { increase_by: u64 },
        Delegate,
        CommitAndUndelegate,
        Commit,
        Undelegate { pda_seeds: Vec<Vec<u8>> },
        IncrementAndCommit { increase_by: u64 },
        IncrementAndUndelegate { increase_by: u64 },
    }

    #[derive(BorshDeserialize)]
    struct IncreaseCounterPayload {
        increase_by: u64,
    }

    impl ProgramInstruction {
        pub fn unpack(input: &[u8]) -> Result<Self, ProgramError> {
            // Ensure the input has at least 8 bytes for the variant
            if input.len() < 8 {
                return Err(ProgramError::InvalidInstructionData);
            }

            // Extract the first 8 bytes as variant
            let (ix_discriminator, rest) = input.split_at(8);

            // Match instruction discriminator with process and deserialize payload
            Ok(match ix_discriminator {
                [0, 0, 0, 0, 0, 0, 0, 0] => Self::InitializeCounter,
                [1, 0, 0, 0, 0, 0, 0, 0] => {
                    let payload = IncreaseCounterPayload::try_from_slice(rest)?;
                    Self::IncreaseCounter {
                        increase_by: payload.increase_by,
                    }
                }
                [2, 0, 0, 0, 0, 0, 0, 0] => Self::Delegate,
                [3, 0, 0, 0, 0, 0, 0, 0] => Self::CommitAndUndelegate,
                [4, 0, 0, 0, 0, 0, 0, 0] => Self::Commit,
                [5, 0, 0, 0, 0, 0, 0, 0] => {
                    let payload = IncreaseCounterPayload::try_from_slice(rest)?;
                    Self::IncrementAndCommit {
                        increase_by: payload.increase_by,
                    }
                }
                [6, 0, 0, 0, 0, 0, 0, 0] => {
                    let payload = IncreaseCounterPayload::try_from_slice(rest)?;
                    Self::IncrementAndUndelegate {
                        increase_by: payload.increase_by,
                    }
                }
                [196, 28, 41, 206, 48, 37, 51, 167] => {
                    let pda_seeds: Vec<Vec<u8>> = Vec::<Vec<u8>>::try_from_slice(rest)?;
                    Self::Undelegate { pda_seeds }
                }
                _ => return Err(ProgramError::InvalidInstructionData),
            })
        }
    }
    ```

    <Note>
      "Undelegate" instruction은 정확한 discriminator를 가져야 합니다. 이것은
      사용자가 직접 호출하지 않으며, Base Layer의 validator가 ER에서 계정을
      undelegate 한 뒤 CPI callback으로 프로그램을 호출합니다.
    </Note>

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

  <Tab title="2. Delegate">
    ### Counter PDA 위임하기

    counter PDA를 위임하고 Ephemeral Rollup 세션에서 writable 하게 만들려면, 내부적으로 `delegate_account` 함수를 호출하는 instruction을 추가해야 합니다. `delegate_account` 는 delegation program으로 CPI를 수행하며, 검증이 끝나면 해당 계정의 소유권을 얻게 됩니다。
    이 단계가 끝나면 ephemeral validator가 counter PDA에서 트랜잭션을 처리하고 delegation program을 통해 상태 차이를 제안할 수 있게 됩니다。

    <Card title="트랜잭션 (Base Layer): Delegate" icon="magnifying-glass" href="https://solscan.io/tx/5jUdf5rsfQsbLYAahS9axrnLnEjdbUqtXUmGfgGuS7QqbYJ4FZHgXTNoT1bxPXp7XQu78r8Ebpp1RT2u9V6qsc1r?cluster=devnet" iconType="duotone">
      Solana Explorer에서 트랜잭션 상세 보기
    </Card>

    ```rust theme={null}
    use solana_program::{
        account_info::{next_account_info, AccountInfo},
        entrypoint::ProgramResult,
        msg,
        pubkey::Pubkey,
    };
    use ephemeral_rollups_sdk::cpi::{delegate_account, DelegateAccounts, DelegateConfig};

    // For Base Layer only
    // Set specific validator based on ER, see https://docs.magicblock.gg/pages/get-started/how-integrate-your-program/local-setup
    pub fn process_delegate(_program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult {
        // Get accounts
        let account_info_iter = &mut accounts.iter();
        let initializer = next_account_info(account_info_iter)?;
        let system_program = next_account_info(account_info_iter)?;
        let pda_to_delegate = next_account_info(account_info_iter)?;
        let owner_program = next_account_info(account_info_iter)?;
        let delegation_buffer = next_account_info(account_info_iter)?;
        let delegation_record = next_account_info(account_info_iter)?;
        let delegation_metadata = next_account_info(account_info_iter)?;
        let delegation_program = next_account_info(account_info_iter)?;
        let validator_account = account_info_iter.next();

        // Optional: client-provided validator or default validator
        let validator_pubkey: Option<Pubkey> = validator_account.map(|acc_info| acc_info.key.clone());

        // Prepare counter pda seeds
        let seed_1 = b"counter";
        let seed_2 = initializer.key.as_ref();
        let pda_seeds: &[&[u8]] = &[seed_1, seed_2];

        let delegate_accounts = DelegateAccounts {
            payer: initializer,
            pda: pda_to_delegate,
            owner_program,
            buffer: delegation_buffer,
            delegation_record,
            delegation_metadata,
            delegation_program,
            system_program,
        };

        let delegate_config = DelegateConfig {
            validator: validator_pubkey, // Set delegating ER validator
            ..Default::default()
        };

        delegate_account(delegate_accounts, pda_seeds, delegate_config)?;

        Ok(())
    }
    ```

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

  <Tab title="3. Commit">
    ### PDA가 위임된 상태에서 커밋하기

    ephemeral runtime은 PDA가 위임된 상태에서도 그 상태를 커밋할 수 있게 해줍니다. 이는 `commit_accounts` 함수를 호출하여 수행됩니다。

    <CardGroup cols={2}>
      <Card title="트랜잭션 (ER): Commit" icon="magnifying-glass" href="https://solscan.io/tx/5GiFGyrnJPkEQbhE8EHVYczoj2RPGe1YSnoq1DzcUHAxpyzMUKtxG2Tc7TLkxtcCHr72ftnvmkAVMfecTaf6TCK8?cluster=custom&customUrl=https://devnet.magicblock.app" iconType="duotone">
        Solana Explorer에서 트랜잭션 상세 보기
      </Card>

      <Card title="트랜잭션 (Base layer): Commit" icon="magnifying-glass" href="https://solscan.io/tx/5fHBADq99LAEBzGoeDXGtN3ut8RBf2s5UhbDM1N6TMTpvADNeYLz8e8vinNWj1VhLhrR7UxFoW7bo2u6pBR3YRjj?cluster=devnet" iconType="duotone">
        Solana Explorer에서 트랜잭션 상세 보기
      </Card>
    </CardGroup>

    ```rust theme={null}
    use solana_program::{
        account_info::{next_account_info, AccountInfo},
        entrypoint::ProgramResult,
        msg,
        program_error::ProgramError,
        pubkey::Pubkey,
    };
    use ephemeral_rollups_sdk::ephem::{FoldableIntentBuilder, MagicIntentBundleBuilder};

    // For ER only
    pub fn process_commit(
        _program_id: &Pubkey,
        accounts: &[AccountInfo],
    ) -> ProgramResult {
        // Get accounts
        let account_info_iter = &mut accounts.iter();
        let initializer = next_account_info(account_info_iter)?;
        let counter_account = next_account_info(account_info_iter)?;
        let magic_program = next_account_info(account_info_iter)?;
        let magic_context = next_account_info(account_info_iter)?;

        // Signer should be the same as the initializer
        if !initializer.is_signer {
            msg!("Initializer {} should be the signer", initializer.key);
            return Err(ProgramError::MissingRequiredSignature);
        }

        MagicIntentBundleBuilder::new(
            initializer.clone(),
            magic_context.clone(),
            magic_program.clone(),
        )
        .commit(&[counter_account.clone()])
        .build_and_invoke()?;

        Ok(())
    }
    ```

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

  <Tab title="4. Undelegate">
    ### PDA 위임 해제하기

    PDA의 위임 해제는 어떤 instruction의 일부로 `commit_and_undelegate_accounts` 를 호출하여 수행합니다。
    undelegation은 최신 상태를 커밋하고 PDA의 소유권을 owner program에 반환합니다. 위임 해제와 상태 finalization이 끝나면 validator는 Base Layer에서 `"undelegate"` 로 CPI callback을 생성합니다。

    <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>

    <CardGroup cols={2}>
      <Card title="트랜잭션 (ER): Undelegate" icon="magnifying-glass" href="https://solscan.io/tx/bMN6AhXrGH93Uc6ALibGgjnE39hcnY57mBhYkZ8TRaKxNRvyFaweaQPBmDxPv81cgR47WTTzzhfziTUEgAT8Y5m?cluster=custom&customUrl=https://devnet.magicblock.app" iconType="duotone">
        Solana Explorer에서 트랜잭션 상세 보기
      </Card>

      <Card title="트랜잭션 (Base layer): Undelegate" icon="magnifying-glass" href="https://solscan.io/tx/8JafYWiXmd4CHc2E97WKYnaNPmChZeg8aGYY7UUWaQ7Z54N5WoMcAVivyv2vdn9wKirMkR3y4UcmFPdXYqBtKAa?cluster=devnet" iconType="duotone">
        Solana Explorer에서 트랜잭션 상세 보기
      </Card>
    </CardGroup>

    ```rust theme={null}
    use solana_program::{
        account_info::{next_account_info, AccountInfo},
        entrypoint::ProgramResult,
        msg,
        program_error::ProgramError,
        pubkey::Pubkey,
    };
    use ephemeral_rollups_sdk::cpi::undelegate_account;
    use ephemeral_rollups_sdk::ephem::{FoldableIntentBuilder, MagicIntentBundleBuilder};

    // For ER only
    pub fn process_commit_and_undelegate(
        _program_id: &Pubkey,
        accounts: &[AccountInfo],
    ) -> ProgramResult {
        // Get accounts
        let account_info_iter = &mut accounts.iter();
        let initializer = next_account_info(account_info_iter)?;
        let counter_account = next_account_info(account_info_iter)?;
        let magic_program = next_account_info(account_info_iter)?;
        let magic_context = next_account_info(account_info_iter)?;

        // Signer should be the same as the initializer
        if !initializer.is_signer {
            msg!("Initializer {} should be the signer", initializer.key);
            return Err(ProgramError::MissingRequiredSignature);
        }

        // Commit and undelegate counter_account on ER
        MagicIntentBundleBuilder::new(
            initializer.clone(),
            magic_context.clone(),
            magic_program.clone(),
        )
        .commit_and_undelegate(&[counter_account.clone()])
        .build_and_invoke()?;

        Ok(())
    }

    // For Base Layer CPI callback
    pub fn process_undelegate(
        program_id: &Pubkey,
        accounts: &[AccountInfo],
        pda_seeds: Vec<Vec<u8>>,
    ) -> ProgramResult {
        // Get accounts
        let account_info_iter = &mut accounts.iter();
        let delegated_pda = next_account_info(account_info_iter)?;
        let delegation_buffer = next_account_info(account_info_iter)?;
        let initializer = next_account_info(account_info_iter)?;
        let system_program = next_account_info(account_info_iter)?;

        // CPI on Solana
        undelegate_account(
            delegated_pda,
            program_id,
            delegation_buffer,
            initializer,
            system_program,
            pda_seeds,
        )?;

        Ok(())
    }
    ```

    [⬆️ 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}
    #[derive(BorshSerialize, BorshDeserialize, Debug)]
    pub struct Counter {
        pub count: u64,
    }

    // Resize counter account
    pub fn resize_counter_account(
        counter_acc: &AccountInfo,
        payer: &AccountInfo,
        program_id: &Pubkey,
        new_size: usize,
        bump: u8,
    ) -> ProgramResult {
        let rent = Rent::get()?;
        let lamports_required = rent.minimum_balance(new_size);

        let current_lamports = counter_acc.lamports();
        if lamports_required > current_lamports {
            let lamports_to_add = lamports_required - current_lamports;
            invoke_signed(
                &system_instruction::transfer(
                    &payer.key,
                    &counter_acc.key,
                    lamports_to_add,
                ),
                &[payer.clone(), counter_acc.clone()],
                &[&[COUNTER_SEED, &[bump]]],
            )?;
        }

        // Allocate new size
        invoke_signed(
            &system_instruction::allocate(&counter_acc.key, new_size as u64),
            &[counter_acc.clone()],
            &[&[COUNTER_SEED, &[bump]]],
        )?;

        // Assign back to program
        invoke_signed(
            &system_instruction::assign(&counter_acc.key, program_id),
            &[counter_acc.clone()],
            &[&[COUNTER_SEED, &[bump]]],
        )?;

        msg!("Counter account resized to {} bytes", new_size);
        Ok(())
    }
    ```

    [⬆️ 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>

***

## 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>

***
