EPguy

[솔라나 Dapp 튜토리얼 - 4] Transaction을 생성하고 솔라나 클러스터에 전송하기 본문

개발/Solana Dapp Tutorial

[솔라나 Dapp 튜토리얼 - 4] Transaction을 생성하고 솔라나 클러스터에 전송하기

EPguy 2024. 2. 16. 13:03

이 글에선 Transaction이란 무엇이고, SOL 을 전송하는 Transaction을 만들어 솔라나 클러스트에 전송하는 방법에 대해 설명합니다.

 

1. Transaction 이란?

  • 온체인에 있는 데이터의 수정은 항상 Transaction을 통해서 이루어집니다.
  • Transaction은 Instruction 이라고 하는 명령들로 구성되어 있습니다.
  • Transaction은 원자적이기 때문에 모든 Instruction이 성공해야 실행되며 하나라도 실패시 모든 Transaction은 실패 됩니다.

 

2. Transaction 은 원자적이다.

실제 세상에서 우리가 다른사람에게 이체를 할 때 아래 과정을 거치게 됩니다.

  • 계좌 잔액이 인출된다.
  • 인출된 계좌 잔액을 은행이 다른 사람에게 이체한다.

만약 위 과정에서 내 계좌잔액은 인출이 됐는데, 은행이 다른사람에게 이체하는 과정이 실패한다고 하면 내 돈은 날아 가게 되겠죠. 이 경우 그냥 두 과정 모두 실패되는게 오히려 나을 겁니다.

원자적이란 모든 개별 명령이 성공적으로 실행되거나, 하나라도 실패하면 다른 명령도 실패됨을 뜻합니다.

 

3. Transaction은 Instruction들로 구성되어있다.

Transaction에서 단계별로 실행되는 단위를 Instruction 이라고 합니다. 즉, Instruction은 프로그램이 실행 가능한 최소 단위라고 보시면 됩니다.

각 Instruction은 아래와 같은 내용을 포함합니다.

  • 읽거나 사용해야 할 Account 목록을 받는다. 이 때, 서로 다른 Account에 미치는 Transaction을 병렬로 처리한다.(솔라나가 속도 빠른 이유 중 하나)
  • 이 Instruction을 호출하는 프로그램의 공개 키를 받는다.
  • 사용되는 데이터를 받는다. 이 데이터는 byte array 형태이다.

 

4. SOL Transfer Instruction 생성 후 Transaction 전송 하기

@solana/web3.js 는 Transaction과 Instruction을 만들 수 있는 함수를 제공합니다.

트랜잭션을 생성할 때는 new Transaction() 을 사용해 생성할 수 있습니다.

그리고 add() 함수를 이용해 Transaction에 Instruction을 추가할 수 있습니다.

SOL을 이체하는 Instruction을 만들 땐 SystemProgram.transfer() 를 사용하면 됩니다.

const transaction = new Transaction()

const sendSolInstruction = SystemProgram.transfer({
  fromPubkey: sender,
  toPubkey: recipient,
  lamports: LAMPORTS_PER_SOL * amount
})

transaction.add(sendSolInstruction)

SystemProgram.transfer() 는 다음을 필요로 합니다.

  • SOL을 전송하는 sender 계정의 공개키
  • SOL을 받는 recipient 계정의 공개키
  • 전송할 SOL의 lamports 수량

SystemProgram.transfer() 는 전송자가 받는이에게 SOL을 전송하는 Instruction을 반환합니다.

참고로, SOL을 전송하는 프로그램은 system이라는 주소가 11111111111111111111111111111111인 프로그램입니다.

이렇게 생성한 Instruction을 Transaction에 추가할 수 있습니다.

Instruction을 Transaction에 추가 했다면 클러스터에 전송하고 확인해야합니다.

클러스터에 Transaction을 전송하기 위해선 sendAndConfirmTransaction() 함수를 사용하면 됩니다.

const signature = sendAndConfirmTransaction(
  connection,
  transaction,
  [senderKeypair]
)

sendAndConfirmTransaction() 는 다음과 같은 파라미터를 갖습니다.

  • 연결할 클러스터 connection
  • 전송할 transaction
  • transaction의 서명자 역할을 할 keypair 배열

 

5. Transaction은 수수료를 갖습니다.

transaction 수수료는 솔라나 네트워크를 검증해주는 검증자의 보상으로 지급 됩니다.

트랜잭션 서명자의 첫번째 배열은 트랜잭션 수수료를 지불 할 사람입니다.

만약 이 서명자가 수수료로 낼 SOL이 충분하지 않다면 다음과 같은 에러가 발생합니다.

> Transaction simulation failed: Attempt to debit an account but found no record of a prior credit.

만약 개발하는 과정에서 이 에러가 발생한다면 solana airdrop 1 명령어를 사용하거나, faucet에서 SOL을 에어드랍 받으면됩니다.

 

6. SOL을 전송하는 애플리케이션 개발하기

[솔라나 Dapp 튜토리얼 - 3]을 참고하여 프로젝트 세팅과 .env 파일에 SOL을 전송할 sender 계정의 secret key를 작성해주세요.

cluster connection, receiver 공개키, sender 키페어를 파라미터로 받는 pingProgram() 함수를 만들어 줍니다.

그리고 빈 트랜잭션을 생성 후 System.transfer() 를 사용하여 SOL을 전송하는 Instruction을 만들어 줍니다.

import web3, { Connection, Keypair, LAMPORTS_PER_SOL, SystemProgram, clusterApiUrl, sendAndConfirmTransaction } from "@solana/web3.js";
import "dotenv/config";
import { getKeypairFromEnvironment } from "@solana-developers/node-helpers";

async function pingProgram(connection: web3.Connection, receiver: web3.PublicKey, payer: web3.Keypair) {
    const transaction = new web3.Transaction();

    const sendSolInsturction = SystemProgram.transfer({
        fromPubkey: payer.publicKey,
        toPubkey: receiver,
        lamports: LAMPORTS_PER_SOL * 1
    })
}

그리고 앞에서 배웠던 sendAndConfirmTransaction() 함수를 사용하여 클러스터에 트랜잭션을 전송합니다.

const signature = await sendAndConfirmTransaction(
        connection,
        transaction,
        [payer]
    )

console.log(`signature : ${signature}`)

main 함수를 만들어 pingProgram 함수를 호출해 SOL을 전송하는 로직을 작성하고 실행시켜줍니다.

receiver 같은 경우 편의를 위해 Keypair.generate() 를 사용해 계정을 새로 생성하는 식으로 했습니다.

const main = async () => {
    try {
        const connection = new Connection(clusterApiUrl("devnet"));
        const payer = getKeypairFromEnvironment("SECRET_KEY");
        const receiver = Keypair.generate();
        console.log(` ✅ payer pub key : ${payer.publicKey.toBase58()}`);
        console.log(` ✅ receiver pub key : ${receiver.publicKey.toBase58()}`);

        await pingProgram(connection, receiver.publicKey, payer);
    } catch (err) {
        console.error(err);
    }
}

main();

짠! 실행이 완료되면 아래 로그를 보실 수 있습니다.

 ✅ payer pub key : CS3QU7cdb1ULPCtdc38QRCYfN45GxPV4xADzrWQMvu47
 ✅ receiver pub key : 8GJspk4cg5YppthmBKUhgLq1mxieJjjt1XXz5J1iCJoB
signature : 3LV8gxwZGoacop9qkgoayA9QNXeunY49YAb7E7ZhrcdnU8amS8SqY2orfzpJbEtK6FkNPtAuzvfsYkXyGBv9J51x

SOLSCAN에서 signature hash를 검색해서 트랜잭션 상세정보도 보실 수 있습니다.

 

image