일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- Vue.js
- rust 기초
- ethers v6
- ethers
- 깃허브명령어
- chainlink 설명
- 컨트렉트 동일한 함수이름 호출
- 러스트 기초 학습
- 러스트 기초
- 스마트컨트렉트 함수이름 중복 호출
- nestjs 튜토리얼
- ambiguous function description
- 스마트컨트렉트프록시
- 스마트 컨트렉트 함수이름 중복
- 머신러닝기초
- 컨트렉트 배포 자동화
- 체인의정석
- nest.js설명
- ethers typescript
- Vue
- vue기초
- multicall
- SBT표준
- 러스트기초
- ethers websocket
- 스마트컨트렉트테스트
- ethers type
- git rebase
- 스마트컨트렉트 예약어 함수이름 중복
- 프록시배포구조
- Today
- Total
체인의정석
블록체인에서 발생한 이벤트 데이터 정리해서 엑셀 파일로 만들기 (ethers, hardhat, excel) 본문
블록체인에서 발생한 이벤트 데이터 정리해서 엑셀 파일로 만들기 (ethers, hardhat, excel)
체인의정석 2023. 7. 10. 14:15실제로 실무를 하다보면 블록체인 서비스의 데이터를 종합하여 보고해야 하는 일이 빈번히 일어난다.
백엔드 개발과 더불어 지속적으로 들어오는 업무이므로, 뭔가 자체적인 db를 쌓기에는 반복적이지 않고 엑셀로 결과물을 정리해서 비 개발직군 분들에게 넘겨줘야 할 경우를 생각하여 프로그램을 설계해 봤다.
STEP 1. 다양한 레포지토리의 컨트렉트를 하나의 경로에서 모두 관리하기
먼저 가장 귀찮았던 것은 매 컨트렉트 마다 계속해서 경로를 생성해서 처리해줘야하는 부분이였다.
이 경우 생각보다 매우 쉬운 방법이 있다.
바로 hardhat의 특징 상 기본 경로 구조 자체가 contracts에 들어가 있기 때문에
contracts 폴더의 하위 디렉터리로 각 레포지토리를 넣어주면 되는 것이였다.
예를 들어 하나의 defi 서비스라면
contracts - lending , swap, staking
이런 식으로 서로 다른 레포지토리에 있는 contracts 부분만 가져와서
랜딩 레포 -> contracts/leding에 소스 코드 넣기
스테이킹 레포 -> contracts/staking에 소스 코드 넣기
이런 식으로 진행을 해주면 된다.
그리고 만약 각 코드별로 버전이 다르다면 hardhat config.ts 에서 다음과 같이 넣어주면 된다.
const config: HardhatUserConfig = {
solidity: {
compilers: [
{version: "0.8.11"},
{version: "0.8.10"},
{version: "0.8.0"},
{version: "0.8.2"},
{version: "0.8.9"}
],
settings: {
optimizer: {
enabled: true,
runs: 200,
},
},
},
paths: {
},
이제 npx hardhat compile 한방이면 각 레포지토리의 컨트렉트들이 한꺼번에 컴파일 되게 된다.
STEP 2. const 값 설정하기
이제 요청이 들어오는 컨트렉트 주소, 조회하는 블록 번호 등의 공통 변수를 뽑아주고 이를 const에 넣어주는 부분이 필요하다.
import { mainnetAddress } from "../../mainnetAddress";
export const tokenA = mainnetAddress.tokenA;
이런식으로 typescript에서는 export const를 이용해서 const 파일에 기본 값들을 정의해준다.
만약 같은 작업을 여러 토큰에 대해서 하는것과 같이 반복이 있다면 여기서 배열을 정의해 준다.
나는 여기서 객체 형태로 정의한 후에 여기에 CA주소 및 파일 명을 넣어두었다.
STEP 3. const에서 값 불러와서 이벤트 로그 불러오기
const fs = require('fs');
export async function getAllInput(contract_address: string, to_address: string) {
const exampleContract = await ethers.getContractAt("exampleContract", contract_address);
const filter = exampleContract.filters.Transfer(null, to_address, null);
const res = await exampleContract.queryFilter(filter,0, toBN);
const results : any = []
for(let i of res) {
const result : any = {}
result.txhash = i.transactionHash;
result.from = i.args.from;
result.to = i.args.to;
reasult.value = i.args.value.toString();
results.push(result);
}
return results;
}
여기선 간단하게 queryFilter를 사용하였는데 queryFilter가 조금 불편한 점은 이벤트가 오버로딩이 된 경우
const filter = exampleContract.filters["Transfer(address,address,uint256)"];
이런식으로 해주게 되는데 이렇게 되면 필터링이 안되고 그냥 나오게 된다.
따라서 이를 일일히 필터링 해주고 싶다면 위의 for 문 위에다가 그냥 조건절을 달아주는게 더 편하다.
const fs = require('fs');
export async function getAllInput(contract_address: string, to_address: string) {
const exampleContract = await ethers.getContractAt("exampleContract", contract_address);
const filter = exampleContract.filters.Transfer(null, to_address, null);
const res = await exampleContract.queryFilter(filter,0, toBN);
const results : any = []
for(let i of res) {
if(i.event == "Transfer" && i.args.from == from_address) {
const result : any = {}
result.txhash = i.transactionHash;
result.from = i.args.from;
result.to = i.args.to;
reasult.value = i.args.value.toString();
results.push(result);
}
}
return results;
}
요런식으로 해주어서 필터링 하면 사용은 가능하다.
마지막으로 만약 이것도 귀찮다면 그냥 abi가 정의된 부분에서 event를 내가 원하는 이벤트만 남기고 뽑아내면 내가 원하는 이벤트만 나오는 방법도 있다.
STEP 4. json 파일 만들기
import { info_list } from "./const"
import { getAllInput } from "./functions"
import fs from 'fs';
for(const info of info_list) {
getAllInput(info.example_address, info.to_address)
.then(async(answerArr_in) => {
await fs.writeFileSync(
`${__dirname}/output/ExampleLog_${info.to_address}.json`,
JSON.stringify(answerArr_in, null, 1)
)
})
}
위와 같이 현재 경로에서 output 폴더를 만든 후에 json 파일로 만들어 주기 위해서 데이터를 뽑아내는 부분과 이를 동기화 시킨 json파일을 만드는 부분을 구분하여 .then으로 데이터를 뽑아내고 이를 리턴받아서 json으로 넣어주는 부분을 생성한다.
STEP 5. excel 파일로 만들기
exceljs 모듈을 사용해서 json 파일을 excel 파일로 만들어 준다. 이때 worksheet를 정의하면 여러 json 파일을 받아와서 각각 다른 워크시트로 하나의 excel 파일로 한번에 처리가 가능하다.
import Excel from 'exceljs';
import path from 'path';
import fs from 'fs';
import { info_list } from './const';
type Info = {
address: string,
amount: string,
}
export const exportUserFile = async (dir_in: string, dir_out: string, file_name: string) => {
const datas_in: Array<Info> = JSON.parse(fs.readFileSync(dir_in,'utf-8'));
const datas_out: Array<Info> = JSON.parse(fs.readFileSync(dir_out,'utf-8'));
const workbook = new Excel.Workbook();
let worksheet_in = workbook.addWorksheet(`sheet name 1`);
for(let data of datas_in) {
worksheet_in.columns = [
{ key: 'txhash', header: 'txhash'},
{ key: 'amount', header: 'amount' }
];
worksheet_in.addRow(data);
}
let worksheet_out = workbook.addWorksheet(`sheet name 2`);
for(let data of datas_out) {
worksheet_out.columns = [
{ key: 'txhash', header: 'txhash'},
{ key: 'amount', header: 'amount' }
];
worksheet_out.addRow(data);
}
const exportPath = path.resolve(__dirname, `./output_${file_name}.xlsx`);
await workbook.xlsx.writeFile(exportPath);
};
for(const info of info_list) {
exportUserFile(
`${__dirname}/output/example1_${info.address}.json`,
`${__dirname}/output/example2_${info.address}.json`,
info.token_name
)
}
다음과 같이 하면 각각 2개의 워크시트를 첫번째, 두번째 경로의 json 파일로 부터 읽어서 엑셀 파일을 뽑아낼 수 있다.
오류와 해결
위와 같은 프로그램 작성 시 timeout error가 발생하였는데 아래 포스팅에서 정리해 두었다.
https://it-timehacker.tistory.com/435
'블록체인 > Ethers & web3' 카테고리의 다른 글
ethers & typechain 사용해서 타입 정의하기 (0) | 2023.07.20 |
---|---|
hardhat, ethers, typescript 에서 서명정보 및 공개키 등록 및 가져오기 (getSigners, getContractAt) (0) | 2023.07.20 |
hardhat 에서 HeadersTimeoutError: Headers Timeout Error 해결 법 (0) | 2023.07.10 |
ethers에서 함수를 못찾을 때 , 오버로딩 문제 체크해 보기 (0) | 2023.05.23 |
ethers에서 블록태그(block tag) 사용하기 (0) | 2023.05.23 |