web3js 트랙잭션
2022.07.10
1. 트랜잭션
-
send transaction
블록체인 네트워크의 상태를 변경하는 트랜잭션
네트워크에 브로드캐스트(송신 호스트가 전송한 데이터를 네트워크에 연결된 모든 호스트에 전송하는 방식 ex;p2p)가 되고 채굴자가 처리하며 블록체인에 게시될 수 있음
다른 계정에 영향을 미치며, 블록체인 상태를 업데이트하는 쓰기 작업
채굴자가 트랜잭션을 블록에 포함하지 않을 수 있기 때문에 비동기식 (ex 가스비가 너무 낮을 경우)
-
call transaction
상태를 변경하지 않는 read-only
블록체인에서 아무것도 브로드캐스트 하거나 게시하지않고 로컬 호출
동기식이면 컨트랙트 함수의 반환값이 즉시 반환
1.1.트랜잭션 구성 요소
1.1.1. gasPrice
- 가스 가격
- transaction 처리에 영향을 미침
1.1.2. gas
- 가스 최대 값 (Gas Limit)
1.1.3. nonce
- 발신 EOA에 의해 발행되며, 메시지 재사용 방지하는데 사용
- 모든 트랜잭션은 일회성이고 고유하고 nonce는 계정에서 보내는 트랜잭션에 할당된 번호 trnasaction를 전송시 nonce는 1씩 증가하며 계정에서 유일하기 때문에 동일한 논스가 존재 하지 않음
- 논스는 중복되지 않고 순차적이기 때문에, 같은 논스로 여러 트랜잭션이 전소되었다면 가장 높은 가스비를 지불한 트랜잭션이 처리 → 이중 지불 문제 방지
Send Transaction
-
Web3js 기본값 세팅
import Web3 from 'web3'; interface settingsTypes { RPC_URL: string; WALLET_ADDRESS: string; PRIVATE_KEY: string; ABI: string; CONTRACT_ADDRESS: string; } // 전송에 사용할 지갑 주소 const address = settings.WALLET_ADDRESS; // 컨트랙트 abi const abi = require(`./ABI/${settings.ABI}`); // 컨트랙트 주소 const contractAddress = settings.CONTRACT_ADDRESS;
-
Web3 Instance 생성
const provider = new Web3.providers.HttpProvider(settings.RPC_URL); const web3 = new Web3(provider);
-
지갑에 Private Key 추가 (서명에 사용)
web3.eth.accounts.wallet.add(settings.PRIVATE_KEY);
-
Contract Instance 생성
// 위의 1번에서 가져온 Contract Address와 abi const CONTRACT_INSTANCE = new web3.eth.Contract(abi, contractAddress);
-
Transaction Option값 설정
5.1. GasPrice : 거래에 사용할 가스량
// 최근에 사용한 가스 값 가져오기 const gasPrice = await web3.eth.getGasPrice().then(currentGasPrice => { return web3.utils.toHex(Math.round(parseInt(currentGasPrice))); });
5.2. gas: 최대로 사용할 가스 제한량
// 블록체인 노드가 보낼 트랜잭션에서 사용될 예측 가스량을 반환 해준다. let gas: string; if (params) { gas = await CONTRACT_INSTANCE.methods[method](...params) .estimateGas({ from: address, }) .then((gas: any) => { return gas; }) .catch((err: any) => { return err.message; }); } else { gas = await CONTRACT_INSTANCE.methods[method]() .estimateGas({ from: address, }) .then((gas: any) => { return gas; }) .catch((err: any) => { return err.message; }); }
5.3. nonce: 트랜잭션 수
기본 값으로 nonce가 부여 되지만 이전 nonce번호가 부여된 트랜잭션이 체결 되기 전에는 같은 nonce가 부여 되기 때문에 다음과 같이 pending처리를 해줌
const nonce = await web3.eth.getTransactionCount(address, 'pending');
-
트랜잭션 전송
let receipt: any; if (params) { receipt = await CONTRACT_INSTANCE.methods[method](...params).send({ from: address, gas: gas, gasPrice: gasPrice, nonce: nonce, }); } else { receipt = await CONTRACT_INSTANCE.methods[method]().send({ from: address, gas: gas, gasPrice: gasPrice, nonce: nonce, }); }
-
전체 코드
import 'dotenv/config'; import Web3 from 'web3'; interface settingsTypes { RPC_URL: string; WALLET_ADDRESS: string; PRIVATE_KEY: string; ABI: string; CONTRACT_ADDRESS: string; } const settings: settingsTypes = { RPC_URL: process.env.RPC_URL || '', WALLET_ADDRESS: process.env.WALLET_ADDRESS || '', PRIVATE_KEY: process.env.PRIVATE_KEY || '', ABI: process.env.ABI || '', CONTRACT_ADDRESS: process.env.CONTRACT_ADDRESS || '', }; /** * Transaction Send * @param {string} method transaction을 보내려 하는 메서드 이름 * @param {string[]} params transaction을 보내려하는 파라미터값 * @returns transaction을 보낸 후 반환 결과값 */ export async function TransactionSend(method: string, params?: string[]) { /** 초기값 세팅 */ const address = settings.WALLET_ADDRESS; const abi = require(`./ABI/${settings.ABI}`); const contractAddress = settings.CONTRACT_ADDRESS; /** web3 instance 생성 */ const provider = new Web3.providers.HttpProvider(settings.RPC_URL); const web3 = new Web3(provider); /** 지갑에 Private Key 추가 */ web3.eth.accounts.wallet.add(settings.PRIVATE_KEY); /** Contract Instance 생성 */ const CONTRACT_INSTANCE = new web3.eth.Contract(abi, contractAddress); /** transaction option값 설정 */ // gasPrice: 거래에 사용할 가스량 const gasPrice = await web3.eth.getGasPrice().then(currentGasPrice => { return web3.utils.toHex(Math.round(parseInt(currentGasPrice))); }); // gas: 최대로 사용할 가스 제한 let gas: string; if (params) { gas = await CONTRACT_INSTANCE.methods[method](...params) .estimateGas({ from: address, }) .then((gas: any) => { return gas; }) .catch((err: any) => { return err.message; }); } else { gas = await CONTRACT_INSTANCE.methods[method]() .estimateGas({ from: address, }) .then((gas: any) => { return gas; }) .catch((err: any) => { return err.message; }); } // nonce: 트랜잭션 수 // pending : 이미 정의된 nonce 번호에 대해 pending const nonce = await web3.eth.getTransactionCount(address, 'pending'); /** transaction 전송 */ let receipt: any; if (params) { receipt = await CONTRACT_INSTANCE.methods[method](...params).send({ from: address, gas: gas, gasPrice: gasPrice, nonce: nonce, }); } else { receipt = await CONTRACT_INSTANCE.methods[method]().send({ from: address, gas: gas, gasPrice: gasPrice, nonce: nonce, }); } return receipt; }
실행
const to = '[송신할 주소]'; const amount = '[전송할 값]'; const web3 = new Web3(); // 단위 변환 const amountWei = web3.utils.toWei(amount, 'ether'); const sendParams = [to, amountWei]; /** parmas가 있는 send Transaction */ const sendResult1 = await TransactionSend('mint', sendParams); /** parmas가 없는 send Transaction */ const sendResult2 = await TransactionSend('counter'); console.log('=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-'); console.log('\n send result1 : ', sendResult1); console.log('\n send result2 : ', sendResult2);
-
실행 결과
Call Transaction
-
Web3js 기본값 세팅
import Web3 from 'web3'; interface settingsTypes { RPC_URL: string; WALLET_ADDRESS: string; PRIVATE_KEY: string; ABI: string; CONTRACT_ADDRESS: string; } // 전송에 사용할 지갑 주소 const address = settings.WALLET_ADDRESS; // 컨트랙트 abi const abi = require(`./ABI/${settings.ABI}`); // 컨트랙트 주소 const contractAddress = settings.CONTRACT_ADDRESS;
-
Web3 Instance 생성
const provider = new Web3.providers.HttpProvider(settings.RPC_URL); const web3 = new Web3(provider);
-
지갑에 Private Key 추가 (서명에 사용)
web3.eth.accounts.wallet.add(settings.PRIVATE_KEY);
-
Contract Instance 생성
// 위의 1번에서 가져온 Contract Address와 abi const CONTRACT_INSTANCE = new web3.eth.Contract(abi, contractAddress);
-
트랜잭션 호출
let receipt: any; if (params) { receipt = await CONTRACT_INSTANCE.methods[method](...params).call({ from: address, }); } else { receipt = await CONTRACT_INSTANCE.methods[method]().call({ from: address, }); }
-
전체 코드
import 'dotenv/config'; import Web3 from 'web3'; interface settingsTypes { RPC_URL: string; WALLET_ADDRESS: string; PRIVATE_KEY: string; ABI: string; CONTRACT_ADDRESS: string; } const settings: settingsTypes = { RPC_URL: process.env.RPC_URL || '', WALLET_ADDRESS: process.env.WALLET_ADDRESS || '', PRIVATE_KEY: process.env.PRIVATE_KEY || '', ABI: process.env.ABI || '', CONTRACT_ADDRESS: process.env.CONTRACT_ADDRESS || '', }; /** * Transaction Call * @param {string} method transaction을 보내려 하는 메서드 이름 * @param {string[]} params transaction을 보내려하는 파라미터값 * @returns transaction을 보낸 후 반환 결과값 */ export async function TransactionCall(method: string, params?: string[]) { /** 초기값 세팅 */ const address = settings.WALLET_ADDRESS; const abi = require(`./ABI/${settings.ABI}`); const contractAddress = settings.CONTRACT_ADDRESS; /** web3 instance 생성 */ const provider = new Web3.providers.HttpProvider(settings.RPC_URL); const web3 = new Web3(provider); /** 지갑에 Private Key 추가 */ web3.eth.accounts.wallet.add(settings.PRIVATE_KEY); /** Contract Instance 생성 */ const CONTRACT_INSTANCE = new web3.eth.Contract(abi, contractAddress); /** transaction 호출 */ let receipt: any; if (params) { receipt = await CONTRACT_INSTANCE.methods[method](...params).call({ from: address, }); } else { receipt = await CONTRACT_INSTANCE.methods[method]().call({ from: address }); } return receipt; }
-
실행 결과