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

# 프로그램 테스트하기

> 간단한 Rust 프로그램을 테스트하는 방법 배우기

***

### 빠른 접근

바로 코드로 들어가고 싶다면:

<CardGroup cols={2}>
  <Card title="GitHub" icon="js" href="https://github.com/magicblock-labs/magicblock-engine-examples/blob/main/rust-counter/tests" iconType="duotone">
    Rust Counter 테스트
  </Card>

  <Card title="GitHub" icon="js" href="https://github.com/magicblock-labs/magicblock-engine-examples/tree/main/pinocchio-counter/tests" iconType="duotone">
    Pinocchio Counter 테스트
  </Card>

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

***

## 단계별 가이드

프로그램의 delegation 및 undelegation instruction을 호출하는 유효한 트랜잭션을 구성하세요。
이 프로젝트의 전체 테스트는 [Typescript 테스트 스크립트](https://github.com/magicblock-labs/magicblock-engine-examples/blob/main/rust-counter/tests) 에서 확인할 수 있습니다。

<Steps>
  <Step title={<a href="#1-connection">SDK 가져오기 및 Connection 초기화</a>}>
    connection과 계정 설정
  </Step>

  <Step title={<a href="#2-delegate">Base Layer에서 PDA Delegate 하기</a>}>
    Base Layer에서 상태 계정을 위임하는 CPI hook 테스트

    <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>}>
    ER에서 상태 계정을 커밋하는 CPI hook 테스트
  </Step>

  <Step title={<a href="#4-undelegate">ER에서 PDA Undelegate 하기</a>}>
    ER에서 상태 계정을 위임 해제하는 CPI hook 테스트
  </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. Connection">
    ### SDK 가져오기 및 Connection 생성

    `@magicblock-labs/ephemeral-rollups-sdk` 또는 `@magicblock-labs/ephemeral-rollups-kit` 를 가져오고, 테스트와 트랜잭션 전송 전에 connection을 초기화합니다。

    <CodeGroup>
      ```bash kit theme={null}
      yarn add @magicblock-labs/ephemeral-rollups-kit@latest
      ```

      ```bash web3.js theme={null}
      yarn add @magicblock-labs/ephemeral-rollups-sdk@latest
      ```
    </CodeGroup>

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

    <CodeGroup>
      ```typescript Kit theme={null}
      import {
        Instruction,
        getAddressEncoder,
        getProgramDerivedAddress,
        AccountRole,
        createKeyPairFromBytes,
        getAddressFromPublicKey,
        address,
        createTransactionMessage,
        appendTransactionMessageInstructions,
        pipe,
        setTransactionMessageFeePayer,
      } from "@solana/kit";

      import {
        Connection,
        DELEGATION_PROGRAM_ID,
        delegationRecordPdaFromDelegatedAccount,
        delegationMetadataPdaFromDelegatedAccount,
        delegateBufferPdaFromDelegatedAccountAndOwnerProgram,
        MAGIC_CONTEXT_ID,
        MAGIC_PROGRAM_ID,
      } from "@magicblock-labs/ephemeral-rollups-kit";

      // Set up a base and ephemeral connection (alternatively use router, see Magic Router)
      const connection = await Connection.create(
        process.env.PROVIDER_ENDPOINT || "https://api.devnet.solana.com",
        process.env.WS_ENDPOINT || "wss://api.devnet.solana.com"
      );
      const ephemeralConnection = await Connection.create(
        process.env.EPHEMERAL_PROVIDER_ENDPOINT || "https://devnet-as.magicblock.app",
        process.env.EPHEMERAL_WS_ENDPOINT || "wss://devnet-as.magicblock.app"
      );

      // Prepare user
      const userKeypair = await initializeSolSignerKeypair();
      const userPubkey = await getAddressFromPublicKey(userKeypair.publicKey);

      // Get PDA
      const addressEncoder = getAddressEncoder();
      const [counterPda, bump] = await getProgramDerivedAddress({
        programAddress: PROGRAM_ID,
        seeds: [Buffer.from("counter"), addressEncoder.encode(userPubkey)],
      });
      ```

      ```typescript Web3.js theme={null}
      import {
        Keypair,
        PublicKey,
        SystemProgram,
        Transaction,
        TransactionInstruction,
        Connection,
        sendAndConfirmTransaction,
      } from "@solana/web3.js";

      import {
        DELEGATION_PROGRAM_ID,
        delegationRecordPdaFromDelegatedAccount,
        delegationMetadataPdaFromDelegatedAccount,
        delegateBufferPdaFromDelegatedAccountAndOwnerProgram,
        MAGIC_CONTEXT_ID,
        MAGIC_PROGRAM_ID,
        GetCommitmentSignature,
      } from "@magicblock-labs/ephemeral-rollups-sdk";

      // Set up a base and ephemeral connection (alternatively use router, see Magic Router)
      const connectionBaseLayer = new Connection(
        process.env.PROVIDER_ENDPOINT || "https://api.devnet.solana.com",
        { wsEndpoint: process.env.WS_ENDPOINT || "wss://api.devnet.solana.com" }
      );
      const connectionEphemeralRollup = new Connection(
        process.env.EPHEMERAL_PROVIDER_ENDPOINT ||
          "https://devnet-as.magicblock.app/",
        {
          wsEndpoint:
            process.env.EPHEMERAL_WS_ENDPOINT || "wss://devnet-as.magicblock.app/",
        }
      );

      // Create user keypair and airdrop SOL if needed
      const userKeypair = initializeSolSignerKeypair();

      // Get pda
      let [counterPda, bump] = PublicKey.findProgramAddressSync(
        [Buffer.from("counter"), userKeypair.publicKey.toBuffer()],
        PROGRAM_ID
      );
      ```
    </CodeGroup>

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

  <Tab title="2. Delegate">
    ### `delegation` 트랜잭션 테스트

    계정의 순서와 속성이 올바르고, 프로그램의 `delegation` instruction discriminator를 가진 instruction을 만드세요。 그런 다음 해당 instruction을 포함한 트랜잭션을 Base Layer(Solana) 네트워크로 보냅니다。

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

    <CodeGroup>
      ```typescript Kit theme={null}
      // "Delegate" transaction

      // Add local validator identity to the remaining accounts if running on localnet
      const remainingAccounts =
        connection.clusterUrlHttp.includes("localhost") ||
        connection.clusterUrlHttp.includes("127.0.0.1")
          ? [
              {
                address: address("mAGicPQYBMvcYveUZA5F5UNNwyHvfYh5xkLS2Fr1mev"),
                role: AccountRole.READONLY,
              },
            ]
          : [];
      const accounts = [
        { address: userPubkey, role: AccountRole.WRITABLE_SIGNER },
        { address: SYSTEM_PROGRAM_ADDRESS, role: AccountRole.READONLY },
        { address: counterPda, role: AccountRole.WRITABLE },
        { address: PROGRAM_ID, role: AccountRole.READONLY },
        {
          address: await delegateBufferPdaFromDelegatedAccountAndOwnerProgram(
            counterPda,
            PROGRAM_ID
          ),
          role: AccountRole.WRITABLE,
        },
        {
          address: await delegationRecordPdaFromDelegatedAccount(counterPda),
          role: AccountRole.WRITABLE,
        },
        {
          address: await delegationMetadataPdaFromDelegatedAccount(counterPda),
          role: AccountRole.WRITABLE,
        },
        { address: DELEGATION_PROGRAM_ID, role: AccountRole.READONLY },
        ...remainingAccounts,
      ];
      const serializedInstructionData = Buffer.from(
        CounterInstruction.Delegate,
        "hex"
      );
      const delegateIx: Instruction = {
        accounts,
        programAddress: PROGRAM_ID,
        data: serializedInstructionData,
      };
      const transactionMessage = pipe(
        createTransactionMessage({ version: 0 }),
        (tx) => setTransactionMessageFeePayer(userPubkey, tx),
        (tx) => appendTransactionMessageInstructions([delegateIx], tx)
      );

      // Send and confirm transaction on base layer
      const txHash = await connection.sendAndConfirmTransaction(
        transactionMessage,
        [userKeypair],
        { commitment: "confirmed", skipPreflight: true }
      );
      ```

      ```typescript Web3.js theme={null}
      // "Delegate" transaction

      // Add local validator identity to the remaining accounts if running on localnet
      const remainingAccounts =
        connectionEphemeralRollup.rpcEndpoint.includes("localhost") ||
        connectionEphemeralRollup.rpcEndpoint.includes("127.0.0.1")
          ? [
              {
                pubkey: new PublicKey("mAGicPQYBMvcYveUZA5F5UNNwyHvfYh5xkLS2Fr1mev"),
                isSigner: false,
                isWritable: false,
              },
            ]
          : [];

      const tx = new web3.Transaction();
      const keys = [
        // Initializer
        {
          pubkey: userKeypair.publicKey,
          isSigner: true,
          isWritable: true,
        },
        // System Program
        {
          pubkey: web3.SystemProgram.programId,
          isSigner: false,
          isWritable: false,
        },
        // Counter Account
        {
          pubkey: counterPda,
          isSigner: false,
          isWritable: true,
        },
        // Owner Program
        {
          pubkey: PROGRAM_ID,
          isSigner: false,
          isWritable: false,
        },
        // Delegation Buffer
        {
          pubkey: getDelegationBufferPda(counterPda, PROGRAM_ID),
          isSigner: false,
          isWritable: true,
        },
        // Delegation Record
        {
          pubkey: getDelegationRecordPda(counterPda),
          isSigner: false,
          isWritable: true,
        },
        // Delegation Metadata
        {
          pubkey: getDelegationMetadataPda(counterPda),
          isSigner: false,
          isWritable: true,
        },
        // Delegation Program
        {
          pubkey: DELEGATION_PROGRAM_ID,
          isSigner: false,
          isWritable: false,
        },
        // ER Validator
        ...remainingAccounts,
      ];
      const serializedInstructionData = Buffer.from(
        CounterInstruction.Delegate,
        "hex"
      );
      const delegateIx = new web3.TransactionInstruction({
        keys: keys,
        programId: PROGRAM_ID,
        data: serializedInstructionData,
      });
      tx.add(delegateIx);

      // Send and confirm transaction to Base Layer
      const txHash = await sendAndConfirmTransaction(
        connectionBaseLayer,
        tx,
        [userKeypair],
        {
          skipPreflight: true,
          commitment: "confirmed",
        }
      );
      ```
    </CodeGroup>

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

  <Tab title="3. Commit">
    ### `commit` 트랜잭션 테스트

    계정의 순서와 속성이 올바르고, 프로그램의 `commit` instruction discriminator를 가진 instruction을 만드세요。 그런 다음 해당 instruction을 포함한 트랜잭션을 ER 네트워크로 보냅니다。

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

    <CodeGroup>
      ```typescript Kit theme={null}
      // "Commit" transaction

      const accounts = [
        { address: userPubkey, role: AccountRole.WRITABLE_SIGNER },
        { address: counterPda, role: AccountRole.WRITABLE },
        { address: address(MAGIC_PROGRAM_ID.toString()), role: AccountRole.READONLY },
        { address: address(MAGIC_CONTEXT_ID.toString()), role: AccountRole.WRITABLE },
      ];
      const serializedInstructionData = Buffer.from(CounterInstruction.Commit, "hex");
      const commitIx: Instruction = {
        accounts,
        programAddress: PROGRAM_ID,
        data: serializedInstructionData,
      };
      const transactionMessage = pipe(
        createTransactionMessage({ version: 0 }),
        (tx) => setTransactionMessageFeePayer(userPubkey, tx),
        (tx) => appendTransactionMessageInstructions([commitIx], tx)
      );

      // Send and confirm transaction on ER
      const txHash = await ephemeralConnection.sendAndConfirmTransaction(
        transactionMessage,
        [userKeypair],
        { commitment: "confirmed", skipPreflight: true }
      );
      ```

      ```typescript Web3.js theme={null}
      // "Commit" transaction

      const tx = new web3.Transaction();
      const keys = [
        // Initializer
        {
          pubkey: userKeypair.publicKey,
          isSigner: true,
          isWritable: true,
        },
        // Counter Account
        {
          pubkey: counterPda,
          isSigner: false,
          isWritable: true,
        },
        // Magic Program
        {
          pubkey: MAGIC_PROGRAM_ID,
          isSigner: false,
          isWritable: false,
        },
        // Magic Context
        {
          pubkey: MAGIC_CONTEXT_ID,
          isSigner: false,
          isWritable: true,
        },
      ];
      const serializedInstructionData = Buffer.from(CounterInstruction.Commit, "hex");
      const commitIx = new web3.TransactionInstruction({
        keys: keys,
        programId: PROGRAM_ID,
        data: serializedInstructionData,
      });
      tx.add(commitIx);

      // Send and confirm transaction to ER
      const txHash = await sendAndConfirmTransaction(
        connectionEphemeralRollup,
        tx,
        [userKeypair],
        {
          skipPreflight: true,
          commitment: "confirmed",
        }
      );
      ```
    </CodeGroup>

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

  <Tab title="4. Undelegate">
    ### `undelegation` 트랜잭션 테스트

    계정의 순서와 속성이 올바르고, 프로그램의 `undelegation` instruction discriminator를 가진 instruction을 만드세요。 그런 다음 해당 instruction을 포함한 트랜잭션을 ER 네트워크로 보냅니다。

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

    <CodeGroup>
      ```typescript Kit theme={null}
      // "Undelegate" transaction

      const accounts = [
        { address: userPubkey, role: AccountRole.WRITABLE_SIGNER },
        { address: counterPda, role: AccountRole.WRITABLE },
        { address: address(MAGIC_PROGRAM_ID.toString()), role: AccountRole.READONLY },
        { address: address(MAGIC_CONTEXT_ID.toString()), role: AccountRole.WRITABLE },
      ];
      const serializedInstructionData = Buffer.from(
        CounterInstruction.CommitAndUndelegate,
        "hex"
      );
      const undelegateIx: Instruction = {
        accounts,
        programAddress: PROGRAM_ID,
        data: serializedInstructionData,
      };
      const transactionMessage = pipe(
        createTransactionMessage({ version: 0 }),
        (tx) => setTransactionMessageFeePayer(userPubkey, tx),
        (tx) => appendTransactionMessageInstructions([undelegateIx], tx)
      );

      // Send and confirm transaction on ER
      const txHash = await ephemeralConnection.sendAndConfirmTransaction(
        transactionMessage,
        [userKeypair],
        { commitment: "confirmed", skipPreflight: true }
      );
      ```

      ```typescript Web3.js theme={null}
      // "Undelegate" transaction

      const tx = new web3.Transaction();
      const keys = [
        // Initializer
        {
          pubkey: userKeypair.publicKey,
          isSigner: true,
          isWritable: true,
        },
        // Counter Account
        {
          pubkey: counterPda,
          isSigner: false,
          isWritable: true,
        },
        // Magic Program
        {
          pubkey: MAGIC_PROGRAM_ID,
          isSigner: false,
          isWritable: false,
        },
        // Magic Context
        {
          pubkey: MAGIC_CONTEXT_ID,
          isSigner: false,
          isWritable: true,
        },
      ];
      const serializedInstructionData = Buffer.from(
        CounterInstruction.CommitAndUndelegate,
        "hex"
      );
      const undelegateIx = new web3.TransactionInstruction({
        keys: keys,
        programId: PROGRAM_ID,
        data: serializedInstructionData,
      });
      tx.add(undelegateIx);

      // Send and confirm transaction to ER. Afterwards CPI callback will be triggered to "Undelegate" instruction of your program on the Base Layer.
      const txHash = await sendAndConfirmTransaction(
        connectionEphemeralRollup,
        tx,
        [userKeypair],
        {
          skipPreflight: true,
          commitment: "confirmed",
        }
      );
      ```
    </CodeGroup>

    [⬆️ Back to Top](#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>

***
