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

# Solana 트랜잭션 최적화 가이드

> Solana 트랜잭션을 최적화하여 확인 지연 시간을 최소화하고 전달율을 최대화하세요. 우선 수수료, 계산 유닛, 모범 사례에 대해 알아보세요.

Solana에서 트랜잭션을 전송하는 주요 방법은 두 가지입니다:

1. [스테이킹 연결](https://www.helius.dev/staked-connections) 사용 (기본값)
2. [Sender](https://www.helius.dev/sender)와 같은 전문 진입 서비스 사용 (추천)

이 문서는 모든 Helius 유료 플랜의 기본 방법인 스테이킹 연결 사용을 위한 트랜잭션 최적화 모범 사례를 다룹니다.

스테이킹 연결은 비즈니스에 대한 지연 시간이 중요하지 않은 사용 사례(예: 결제, 지갑, 소셜 앱 등)에 적합합니다.

고급 트레이더(예: propAMM, 스나이퍼, 카피 트레이더, 청산 봇, 차익 거래)라면 초저지연 트랜잭션 진입 서비스를 찾고 있다면 [Sender 튜토리얼](/docs/ko/sending-transactions/sender)을 읽어보세요.

## 요약

Helius의 스테이킹 연결은 100% 트랜잭션 전달을 보장하며 확인 시간을 최소화합니다. 스테이킹 연결로 트랜잭션 도착률을 최적화하기 위해 다음 모범 사례를 권장합니다:

* “confirmed” 커밋을 사용하여 [최신 블록해시](/docs/ko/rpc/guides/getlatestblockhash) 가져오기
* [우선 수수료](/docs/ko/sending-transactions/optimizing-transactions#올바른-우선-수수료-설정)를 추가하고 동적으로 계산
* 계산 유닛(CU) 사용 최적화
* `maxRetries`를 0으로 설정하고 강력한 재시도 로직 구현
* `skipPreflight`를 `true`로 설정하여 전송 (선택 사항)

<Tip>
  더 깊이 알고 싶으신가요? 이 [블로그 게시물](https://www.helius.dev/blog/how-to-land-transactions-on-solana)에서 모든 기본 사항을 다룹니다.
</Tip>

## 트레이더를 위한 권장 최적화

지연 시간에 민감한 거래 사용 사례의 경우, [Sender 사용](/docs/ko/sending-transactions/sender)을 권장합니다.

그러나 스테이킹 연결을 사용하고 가능한 최저 지연 시간으로 설정을 최적화하려면 위에서 언급한 모범 사례를 적용하는 것 외에도 다음 최적화를 권장합니다:

* 클라이언트 서버(트랜잭션을 전송하는 기기)는 미국 동부 또는 서유럽에 위치해야 합니다.
* Helius 트랜잭션 전송 서버와 협력하려면 FRA 또는 PIT를 선택하세요.
* 검증자 네트워크에서 멀리 떨어진 지역(LATAM, 남아프리카 등)에서 전송하는 것을 피하세요.
* 말단 지연 시간을 최소화하기 위해 Helius 지역 캐시를 예열하세요.
* 지역별로 하나의 예열 스레드만 필요하며 그 이상은 효과가 없습니다.
* [`getHealth`](/docs/ko/rpc/guides/gethealth) RPC 호출을 트랜잭션을 전송하는 것과 동일한 엔드포인트 및 API 키를 사용하여 매초 한 번씩 전송하세요.

이러한 이점은 경험 있는 트레이더에게만 눈에 띌 것입니다. 일반 앱 개발자에게는 아래의 스마트 트랜잭션 전송 섹션의 지침을 따르는 것이 좋습니다.

<Tip>
  [Raw Shreds (UDP)](/docs/ko/shred-delivery/raw-shreds)로 온체인 트랜잭션 데이터를 최대한 빨리 가져오세요. [Helius 대시보드에서 구독](https://dashboard.helius.dev/shred-delivery-seats).
</Tip>

## 스마트 트랜잭션 전송

Helius [Node.js](/docs/ko/sending-transactions/optimizing-transactions#nodejs-sdk) 및 [Rust](/docs/ko/sending-transactions/optimizing-transactions#rust-sdk) SDK는 스마트 트랜잭션을 보낼 수 있습니다. 이 새로운 방법은 최적화된 트랜잭션을 구축하고 전송하며 확인 상태를 처리합니다.

사용자는 트랜잭션 발송 옵션을 구성할 수 있으며, 트랜잭션이 사전 비행 검사를 건너뛸지 여부를 선택할 수 있습니다.

기본적으로 사용자는 자신이 실행하려는 키페어와 지침을 제공해야 하며 나머지는 우리가 처리합니다.

우리는 다음을 수행합니다:

* 최신 블록해시 가져오기
* 초기 트랜잭션 구축
* 사용된 계산 유닛(CU)을 가져오기 위해 초기 트랜잭션을 시뮬레이션
* 앞 단계에서 소비된 CU에 약간의 여유를 두어 CU 한도 설정
* [우선 수수료 API](/docs/ko/priority-fee-api)에서 Helius 추천 우선 수수료 가져오기
* 우선 수수료(CU당 마이크로 라모프)로 Helius 추천 수수료 설정
* 추천 수수료가 몇 초 내에 변경될 경우를 대비하여 작은 버퍼 수수료 추가
* 최적화된 트랜잭션 구축 및 전송
* 성공 시 트랜잭션 서명 반환

<Note>
  스테이킹 연결에 권장값(또는 그 이상)을 요구하는 것은 Helius가 고품질의 트랜잭션을 보내며 검증자가 속도를 제한하지 않도록 보장합니다.
</Note>

이 방법은 Solana에서 트랜잭션을 구축, 전송, 착지하는 가장 쉬운 방법입니다.

Helius 추천 수수료를 사용하여, Helius 사용자가 [표준 유료 플랜](/docs/ko/billing/plans) 중 하나로 전송하는 트랜잭션은 스테이킹 연결을 통해 라우팅되어 거의 100%의 트랜잭션 전달과 최소한의 지연 시간이 보장됩니다.

### Node.js SDK

`sendSmartTransaction` 메서드는 [Helius Node.js SDK](/docs/ko/sdks)에서 [버전 >= 1.3.2](https://www.npmjs.com/package/helius-sdk)에서 사용할 수 있습니다. 최신 버전의 SDK로 업데이트하려면 `npm update helius-sdk`를 실행하세요.

이 예제는 선택한 계정으로 SOL을 전송합니다. `sendSmartTransaction`를 사용하여 사전 비행 검사를 건너뛰지 않는 최적화된 트랜잭션을 전송합니다:

```typescript theme={"system"}
import { Helius } from "helius-sdk";
import {
  Keypair,
  SystemProgram,
  LAMPORTS_PER_SOL,
  TransactionInstruction,
} from "@solana/web3.js";

const helius = new Helius("YOUR_API_KEY");
const fromKeypair = /* Your keypair goes here */;
const fromPubkey = fromKeypair.publicKey;
const toPubkey = /* The person we're sending 0.5 SOL to */;

const instructions: TransactionInstruction[] = [
  SystemProgram.transfer({
    fromPubkey: fromPubkey,
    toPubkey: toPubkey,
    lamports: 0.5 * LAMPORTS_PER_SOL, 
  }),
];

const transactionSignature = await helius.rpc.sendSmartTransaction(instructions, [fromKeypair]);
console.log(`Successful transfer: ${transactionSignature}`);
```

### Rust SDK

`send_smart_transaction` 메서드는 [Rust SDK](/docs/ko/sdks)에서 [버전 >= 0.1.5](https://docs.rs/helius/latest/helius/)에서 사용할 수 있습니다. 최신 버전의 SDK로 업데이트하려면 `cargo update helius`를 실행하세요.

다음 예제는 0.01 SOL을 선택한 계정으로 전송합니다.

`send_smart_transaction`를 활용하여 사전 비행 검사를 건너뛰고 필요한 경우 두 번 재시도하는 최적화된 트랜잭션을 전송합니다:

```rust theme={"system"}
use helius::types::*;
use helius::Helius;
use solana_sdk::{
    pubkey::Pubkey,
    signature::Keypair,
    system_instruction
};

#[tokio::main]
async fn main() {
    let api_key: &str = "YOUR_API_KEY";
    let cluster: Cluster = Cluster::MainnetBeta;
    let helius: Helius = Helius::new(api_key, cluster).unwrap();
    
    let from_keypair: Keypair = /* Your keypair goes here */;
    let from_pubkey: Pubkey = from_keypair.pubkey();
    let to_pubkey: Pubkey = /* The person we're sending 0.01 SOL to */;

    // Create a simple instruction (transfer 0.01 SOL from from_pubkey to to_pubkey)
    let transfer_amount = 100_000; // 0.01 SOL in lamports
    let instruction = system_instruction::transfer(&from_pubkey, &to_pubkey, transfer_amount);

    // Create the SmartTransactionConfig
    let config = SmartTransactionConfig {
        instructions,
        signers: vec![&from_keypair],
        send_options: RpcSendTransactionConfig {
            skip_preflight: true,
            preflight_commitment: None,
            encoding: None,
            max_retries: Some(2),
            min_context_slot: None,
        },
        lookup_tables: None,
    };

    // Send the optimized transaction
    match helius.send_smart_transaction(config).await {
        Ok(signature) => {
            println!("Transaction sent successfully: {}", signature);
        }
        Err(e) => {
            eprintln!("Failed to send transaction: {:?}", e);
        }
    }
}
```

## SDK 없이 트랜잭션 보내기

하나의 SDK로 스마트 트랜잭션을 전송하는 것을 권장하지만, SDK를 사용하지 않고도 동일한 기능을 구현할 수 있습니다.

Node.js SDK와 Rust SDK는 모두 오픈 소스이므로, 스마트 트랜잭션 발송 기능의 기본 코드는 언제든지 볼 수 있습니다.

### 초기 트랜잭션 준비 및 구축

먼저 초기 트랜잭션을 준비하고 구축하세요. 여기에는 지침 세트로 새 트랜잭션을 생성하고, 최신 블록해시를 추가하며, 수수료 지불자를 할당하는 것이 포함됩니다.

버전이 있는 트랜잭션의 경우 `TransactionMessage`를 생성하고 조회 테이블이 있는 경우 컴파일하세요.

그런 다음 새 버전 트랜잭션을 생성하고 서명하세요. 이는 트랜잭션을 시뮬레이션할 때 필수적입니다. 트랜잭션은 서명되어야 합니다.

예를 들어 버전이 있는 트랜잭션을 준비하려면:

```typescript theme={"system"}
// Prepare your instructions and set them to an instructions variable
// The payerKey is the public key that will be paying for this transaction
// Prepare your lookup tables and set them to a lookupTables variable
let recentBlockhash = (await this.connection.getLatestBlockhash()).blockhash;
const v0Message = new TransactionMessage({
    instructions: instructions,
    payerKey: pubKey,
    recentBlockhash: recentBlockhash,
}).compileToV0Message(lookupTables);
versionedTransaction = new VersionedTransaction(v0Message);
versionedTransaction.sign([fromKeypair]);
```

### 트랜잭션의 계산 유닛 (CU) 사용 최적화

[트랜잭션의 계산 유닛 (CU) 사용 최적화](https://www.helius.dev/blog/optimizing-solana-programs)하기 위해, `simulateTransaction` RPC 메서드를 사용하여 트랜잭션을 시뮬레이션할 수 있습니다.

[트랜잭션 시뮬레이션](/docs/ko/api-reference/rpc/http/simulatetransaction)은 사용된 CU의 양을 반환하므로 이 값을 사용하여 우리의 계산 한도를 적절히 설정할 수 있습니다.

먼저 원하는 지침과 1.4m CU로 계산 한도를 설정하는 지침을 추가하여 시험 트랜잭션을 사용해 보는 것이 좋습니다.

이렇게 해야 트랜잭션 시뮬레이션이 성공적으로 완료됩니다.

예를 들어:

```typescript theme={"system"}
const testInstructions = [
    ComputeBudgetProgram.setComputeUnitLimit({ units: 1_400_000 }),
    ...instructions,
];

const testTransaction = new VersionedTransaction(
    new TransactionMessage({
        instructions: testInstructions,
        payerKey: payer,
        recentBlockhash: (await this.connection.getLatestBlockhash()).blockhash,
    }).compileToV0Message(lookupTables)
);

const rpcResponse = await this.connection.simulateTransaction(testTransaction, {
    replaceRecentBlockhash: true,
    sigVerify: false,
});

const unitsConsumed = rpcResponse.value.unitsConsumed;
```

트랜잭션이 문제없이 실행되도록 하기 위해 약간의 여유를 추가하는 것도 권장됩니다. 다음과 같이 설정하세요:

```typescript theme={"system"}
let customersCU = Math.ceil(unitsConsumed * 1.1);
```

그런 다음 이 값을 계산 유닛 한도로 설정하는 지침을 만들고 이를 지침 배열에 추가하세요:

```typescript theme={"system"}
const computeUnitIx = ComputeBudgetProgram.setComputeUnitLimit({
    units: customersCU
});
instructions.push(computeUnitIx);
```

### 트랜잭션 직렬화 및 인코딩

이 과정은 비교적 간단합니다.

먼저 트랜잭션을 직렬화하려면, Transaction 및 VersionedTransaction 유형 모두에 `.serialize()` 메서드가 있습니다. 그런 다음 [bs58 패키지](https://www.npmjs.com/package/bs58)를 사용하여 트랜잭션을 인코딩하세요.

코드 조각은 `bs58.encode(txt.serialize());`과 같아야 합니다.

### 올바른 우선 수수료 설정

먼저 [Priority Fee API](/docs/ko/priority-fee-api)를 사용하여 우선 수수료를 추정하세요. 트랜잭션을 전달하고 권장 매개변수를 통해 Helius 추천 수수료를 받기를 원합니다:

```typescript theme={"system"}
const response = await fetch(HeliusURL, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
        jsonrpc: "2.0",
        id: "1",
        method: "getPriorityFeeEstimate",
        params: [
            {
                transaction: bs58.encode(versionedTransaction), // Pass the serialized transaction in
                options: { recommended: true },
            },
        ],   
    }),
});

const data = await response.json();
const priorityFeeRecommendation = data.result.priorityFeeEstimate;
```

그런 다음 이 값을 컴퓨팅 유닛 가격으로 설정하는 지침을 생성하고 이전 지침에 추가하세요:

```typescript theme={"system"}
const computeBudgetIx = ComputeBudgetProgram.setComputeUnitPrice({
    microLamports: priorityFeeRecommendation,
});

instructions.push(computeBudgetIx);
```

### 최적화된 트랜잭션 구축 및 전송

이 단계는 거의 첫 단계의 반복입니다. 그러나 초기 지침 배열은 계산 유닛 한도 및 가격을 최적으로 설정하기 위해 두 개의 지침을 추가하기 위해 변경되었습니다.

이제 트랜잭션을 보내세요.

사전 비행 검사를 사용 여부 및 기타 전송 옵션을 변경 여부는 중요하지 않습니다 — 트랜잭션은 모든 유료 플랜에 대해 우리의 스테이킹 연결을 통해 라우팅됩니다.

### 트랜잭션의 상태를 폴링 및 재방송

<Warning>
  스테이킹 연결이 트랜잭션을 리더에게 직접 전달하더라도 [Banking Stage](https://www.helius.dev/blog/solana-virtual-machine#the-banking-stage)에서 트랜잭션이 삭제될 가능성이 여전히 있습니다. 사용자가 RPC에 의존하여 트랜잭션을 자동 재시도하는 대신 자체 재방송 로직을 사용하는 것이 좋습니다.
</Warning>

[`sendTransaction` RPC 메서드](/docs/ko/api-reference/rpc/http/sendtransaction)에는 RPC의 기본 재시도 로직을 재정의할 수 있는 `maxRetries` 매개변수가 있어서 개발자가 재시도 프로세스를 보다 제어할 수 있습니다.

[`getLatestBlockhash`](/docs/ko/rpc/guides/getlatestblockhash)를 통해 현재 블록해시를 가져오고 `lastValidBlockHeight`를 저장하며 블록해시가 만료될 때까지 트랜잭션을 재시도하는 것이 일반적인 패턴입니다.

블록해시가 더 이상 유효하지 않을 때만 트랜잭션에 다시 서명해야 하며, 그렇지 않을 경우 두 트랜잭션 모두 네트워크에 의해 수락될 수 있습니다.

트랜잭션이 전송된 후, 네트워크가 이를 처리하고 확인했는지 상태를 확인하여 다시 시도하기 전에 그 상태를 폴링하는 것이 중요합니다. [`getSignatureStatuses` RPC 메서드](/docs/ko/rpc/guides/getsignaturestatuses)를 사용하여 여러 트랜잭션의 확인 상태를 확인하세요.

@solana/web3.js SDK에도 여러 서명의 현재 상태를 가져올 수 있는 `getSignatureStatuses` 메서드가 `Connection` 클래스에 있습니다.

### sendSmartTransaction의 폴링 및 재방송 처리 방식

`sendSmartTransaction` 메서드는 60초의 타임아웃 기간을 가집니다. 블록해시는 150 슬롯 동안 유효하며, 이상적인 400ms 슬롯을 가정하면 트랜잭션 블록해시는 1분 후에 무효화된다고 합리적으로 가정할 수 있습니다.

이 메서드는 트랜잭션을 전송하고 이 타임아웃 기간을 사용하여 서명을 폴링합니다:

```typescript theme={"system"}
try {
   // Create a smart transaction
   const transaction = await this.createSmartTransaction(instructions, signers, lookupTables, sendOptions);
  
   const timeout = 60000;
   const startTime = Date.now();
   let txtSig;
  
   while (Date.now() - startTime < timeout) {
     try {
       txtSig = await this.connection.sendRawTransaction(transaction.serialize(), {
         skipPreflight: sendOptions.skipPreflight,
         ...sendOptions,
       });
  
       return await this.pollTransactionConfirmation(txtSig);
     } catch (error) {
       continue;
     }
   }
} catch (error) {
   throw new Error(`Error sending smart transaction: ${error}`);
}
```

`txtSig`는 방금 전송된 트랜잭션의 서명으로 설정됩니다.

그런 다음 `pollTransactionConfirmation()` 메서드를 사용하여 트랜잭션의 확인 상태를 폴링합니다. 이 메서드는 최대 3번, 5초마다 트랜잭션의 상태를 확인합니다.

이 시간 내에 트랜잭션이 확인되지 않으면 오류가 반환됩니다:

```typescript theme={"system"}
async pollTransactionConfirmation(txtSig: TransactionSignature): Promise<TransactionSignature> {
    // 15 second timeout
    const timeout = 15000;
    // 5 second retry interval
    const interval = 5000;
    let elapsed = 0;

    return new Promise<TransactionSignature>((resolve, reject) => {
      const intervalId = setInterval(async () => {
        elapsed += interval;

        if (elapsed >= timeout) {
          clearInterval(intervalId);
          reject(new Error(`Transaction ${txtSig}'s confirmation timed out`));
        }

        const status = await this.connection.getSignatureStatuses([txtSig]);

        if (status?.value[0]?.confirmationStatus === "confirmed") {
          clearInterval(intervalId);
          resolve(txtSig);
        }
      }, interval);
   });
}
```

```typescript theme={"system"}
async pollTransactionConfirmation(txtSig: TransactionSignature): Promise<TransactionSignature> {
    // 15 second timeout
    const timeout = 15000;
    // 5 second retry interval
    const interval = 5000;
    let elapsed = 0;

    return new Promise<TransactionSignature>((resolve, reject) => {
      const intervalId = setInterval(async () => {
        elapsed += interval;

        if (elapsed >= timeout) {
          clearInterval(intervalId);
          reject(new Error(`Transaction ${txtSig}'s confirmation timed out`));
        }

        const status = await this.connection.getSignatureStatuses([txtSig]);

        if (status?.value[0]?.confirmationStatus === "confirmed") {
          clearInterval(intervalId);
          resolve(txtSig);
        }
      }, interval);
   });
}
```
