2024. 7. 11. 07:50ㆍUX Engineer 이야기
들어가며
블록체인의 컨트랙트에서 전송을 감지하고, 어떻게 처리하는지 알아보자.
처음의 궁금증은 지갑에서 보이는 기본 토큰의 보유 수량을 어떻게 업데이트하는지에 대한 고민부터 시작되었다.
다른 사람이 내 지갑으로 토큰을 전송한 걸 감지해서 보여줄까? 에 대한 고민이었는데, 어떻게 하면 더 효율적으로 보유 수량을 업데이트할 수 있는지 알아보고, 찾아낸 방법을 소개하고자 한다.
단순한 해결 법
최초 고민 시에 내가 아는 지식은 블록체인 내에서 내가 가진 토큰의 수량을 조회하고 보여주는 게 끝이었다.
이런 지식 속에서 처음 생각난 해결법은 주기적으로 내가 가진 토큰의 수량을 조회하여 보여주는 방식이 생각났지만 동시에 너무나도 비효율적인 방법이라는 생각이 듦과 동시에 누가 생각하더라도 이런 비효율적인 방법보다 더욱 효율적인 방법이 있을 거라는 생각과 함께 지금 시작하는 나보다도 더 오랫동안 블록체인 관련 지식을 쌓고 경험이 있는 사람들이 먼저 이런 생각을 하고 해결했을 거란 믿음, 그리고 여러 가지 블록체인 관련 라이브러리에 관련한 기능이 있을 거라는 생각에 다른 방법을 찾기로 했다.
구글 서치와, Ethers.js 문서를 찾아보는 와중에 필터(filter)라는 기능을 발견하였다. 생각했던 대로 컨트랙트에 내가 원하는 이벤트의 필터를 걸어 해당 이벤트를 감지할 수 있는 기능이었다.
필터(Filter)란?
먼저 Ethers.js는 이더리움 블록체인과 상호작용을 하는 강력한 자바스크립트 라이브러리로, 개발자들이 스마트 컨트랙트와 쉽게 통신할 수 있도록 도와주는 라이브러리다. 이 라이브러리의 핵심 기능 중 하나인 필터는 블록체인에서 발생하는 다양한 이벤트와 트랜잭션을 탐지하고 추적하는 데 사용되는 매우 중요하고 활용도가 높다.
필터는 블록체인 네트워크에서 특정 조건에 맞는 데이터를 실시간으로 감지하고, 이를 개발자가 정의한 방식으로 처리할 수 있도록 하는 기능으로서 이를 통해 개발자는 블록체인에서 발생하는 방대한 데이터를 효과적으로 관리하고, 필요한 정보만을 추출하여 애플리케이션에 활용할 수 있다.
필터의 역할과 중요성
필터는 블록체인 애플리케이션에서 다음과 같은 중요한 역할을 수행한다.
- 이벤트 추적: 스마트 컨트랙트 내에서 발생하는 특정 이벤트를 실시간으로 추적한다. 이는 사용자가 특정 조건이 충족될 때 자동으로 알림을 받을 수 있도록 하며, 스마트 컨트랙트의 상태 변화에 즉각적으로 대응할 수 있게 한다.
- 데이터 분석: 블록체인 상의 트랜잭션 로그를 분석하여 특정 패턴이나 조건에 맞는 데이터를 필터링한다. 이를 통해 불필요한 데이터를 제외하고, 중요한 데이터만을 집중적으로 분석할 수 있다.
- 실시간 모니터링: 네트워크 상의 트랜잭션이나 블록 생성 등의 활동을 실시간으로 모니터링하여, 네트워크 상태를 지속적으로 파악하고 관리할 수 있게 한다. 이는 네트워크의 성능 최적화와 보안 강화에 중요한 역할을 한다.
필터의 조건과 종류
필터는 다음과 같은 요소들로 구성되며, 특정 조건을 설정하여 블록체인 네트워크에서 발생하는 이벤트를 감지한다.
- 주소(Address): 특정 스마트 컨트랙트의 주소를 지정하여 해당 주소에서 발생하는 이벤트만을 필터링한다.
- 토픽(Topics): 이벤트 시그니처와 관련된 토픽을 설정하여 특정 이벤트를 감지한다. 각 토픽은 이벤트의 매개변수와 연관되어 있으며, 이를 통해 더 세밀한 필터링이 가능하다.
- 블록 범위(Block Range): 특정 블록 범위 내에서 발생한 이벤트를 필터링하며, 이를 통해 과거의 특정 블록에서 발생한 이벤트를 조회할 수 있다.
그리고, 필터를 적용할 수 있는 경우는 다음과 같다.
- 블록 필터(Block Filter): 새로운 블록이 생성될 때마다 이를 감지하여 처리. 이는 네트워크의 블록 생성 주기를 모니터링하는 데 유용하다.
- 트랜잭션 필터(Transaction Filter): 새로운 트랜잭션이 발생할 때 이를 감지하여 처리한다. 특정 주소에서 발생하는 트랜잭션을 모니터링하거나, 특정 조건을 만족하는 트랜잭션을 추적하는 데 사용한다.
- 이벤트 필터(Event Filter): 스마트 컨트랙트 내에서 발생하는 특정 이벤트를 감지합니다. 이는 이벤트 로그를 통해 컨트랙트의 상태 변화를 실시간으로 추적하고, 필요한 조치를 즉각적으로 취할 수 있게 한다.
필터를 적용할 수 있는 경우를 보면 내가 만난 문제를 해결하기 위해 트랜잭션 필터를 사용하는 게 좋다고 판단하였고, 이를 적용하기 위해 시도를 하였다.
그러나 생각처럼 간단하고, 빠르게 적용하지는 못했는데, 이유는 워낙에 빠르게 변화하는 블록체인 세계라서 그런지 Ethers.js도 업데이트가 빠르게 되었고, 찾아내는 정보들은 대다수 이전 버전의 내용이기도 했다.
필터 자체의 사용하는 방법은 크게 변한 거 같진 않았는데, 이를 사용하기 위한 Ethers.js의 내장 함수가 바뀐 부분들이 있어 바로 적용하기는 힘들었는데, 이는 Ethers.js의 공식 문서를 참고하면서 적용해 보았다.
실제 적용
적용할 토큰은 2개의 토큰이었는데, 하나는 지갑을 만들 때 활용하고자 만들었던 openZeppelin으로 만든 테스트 토큰이었고, 하나는 다른 프로젝트에서 만든 토큰이었다.
우선은 2개의 토큰 ABI를 가지고, 토큰의 이벤트 시그니처를 만든 후 필터 설정부터 하였으나 하나의 토큰이 잘 작동하면 다른 하나의 토큰의 필터는 작동하지 않았고, 반대의 경우도 마찬가지였다.
이때까지만 해도, 2개의 토큰은 ERC-20의 표준을 준수하고, 컨트랙트의 전송함수 또한 동일하다고 생각했었으나 아무래도 이상해서 ABI를 좀 더 살펴보았다. 이때 두 토큰의 전송 함수에서 차이점이 있는 것을 발견하였다.
다음은 첫 번째 토큰의 ABI 중 Transfer 부분이다.
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "from",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "to",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "Transfer",
"type": "event"
}
다음은 두 번째 토큰 ABI의 Transfer 부분이다.
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "from",
"type": "address"
},
{
"indexed": false,
"internalType": "address",
"name": "to",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "value",
"type": "uint256"
}
],
"name": "Transfer",
"type": "event"
}
두 토큰의 Transfer 함수는 동일한 파라미터를 가지고 있지만, indexed 부분이 다른 점을 발견하였다. 그러나 이 부분으로 인해 설정한 필터가 작동하는 방식이 왜 다른지에 대해서는 이해할 수 없었다.
두 토큰의 컨트랙트 소스코드에 어떤 차이점이 있는지는 Solidity 코드를 확인할 수 없어 내가 가진 정보 안에서는 indexed만이 유일하게 문제를 발생시키는 부분으로 보였다.
결과적으로 문제를 해결한 나의 코드는 다음과 같다.
// Ethereum 네트워크에 연결
const provider = new ethers.JsonRpcProvider(PROVIDER);
// 토큰의 이벤트 시그니처 정의
const transferEventSignature =
token.symbol === 'TOKEN1'
? 'Transfer(address,uint256)' // TOKEN1 토큰의 이벤트 시그니처
: 'Transfer(address,address,uint256)'; // TOKEN2 토큰의 이벤트 시그니처
// 보낸 이벤트를 감지하기 위한 필터 설정
const sendFilter =
token.symbol === 'TOKEN1'
? {
address: token.address,
}
: {
address: token.address,
topics: [
ethers.id(transferEventSignature),
ethers.zeroPadValue(userAddress, 32), // 32바이트의 고정된 길이로 반환
null,
],
};
// 전송 받은 이벤트를 감지하기 위한 필터 설정
const receiveFilter =
token.symbol === 'TOKEN1'
? {
address: token.address,
}
: {
address: token.address,
topics: [
ethers.id(transferEventSignature),
null,
ethers.zeroPadValue(userAddress, 32), // 32바이트의 고정된 길이로 반환
],
};
provider.on(sendFilter, handleEvent);
provider.on(receiveFilter, handleEvent);
여기에서 전송 이벤트를 감지하고, 실행되는 handleEvent는 이벤트가 감지되면 실행되는 함수이다.
이 함수에서 단순하게 해당 토큰의 보유 수량을 조회해도 되며, 이벤트의 TransactionHash를 추출하여, 영수증을 조회하여 해당 토큰의 전송에 대한 로그를 쌓을 수 있다.
필터의 활용
Ethers.js의 필터를 활용하는 방법은 여러 가지가 있을 것으로 생각이 된다.
물론, 이미 활용하고 있는 애플리케이션도 많지만, 아직은 모든 애플리케이션에서 제대로 활용하고 있지는 않는 거 같다. 이 필터를 잘 활용하면 이전보다 더욱 좋은 사용자 경험도 얻을 수 있으며, 기존의 애플리케이션보다 더욱 편리한 결과물을 만들어 낼 수 있을 거 같다.
아마도 다음과 같은 부분에서 이 필터가 잘 활용될 수 있지 않을까?
- 실시간 잔액 업데이트: 사용자가 자신의 지갑에서 토큰 전송을 감지하고 실시간으로 잔액을 업데이트할 수 있도록 하면, 사용자 경험이 크게 향상될 수 있다. 예를 들어, 사용자가 자신의 지갑 앱을 열었을 때, 다른 사용자가 토큰을 전송하면 즉시 잔액이 업데이트되는 기능을 제공할 수 있다. 이를 통해 사용자는 항상 최신 잔액을 확인할 수 있으며, 추가적인 업데이트 버튼 클릭 없이도 최신 정보를 얻을 수 있다.
- 알림 기능: 전송 이벤트를 감지하여 사용자가 토큰을 전송받았을 때 실시간 알림을 제공할 수 있다. 이는 이메일, SMS 또는 앱 푸시 알림을 통해 사용자에게 알릴 수 있다. 이렇게 하면 사용자는 토큰을 받았을 때 즉시 알림을 받고, 필요한 조치를 취할 수 있다. 예를 들어, 거래소에서 토큰을 전송받았을 때 즉시 거래를 진행하거나, 친구로부터 받은 토큰을 확인할 수 있다.
- 거래 기록 및 분석: 전송 이벤트를 감지하여 사용자의 거래 기록을 자동으로 업데이트하고, 이를 분석하여 유용한 정보를 제공할 수 있다. 사용자는 자신의 거래 내역을 쉽게 확인할 수 있으며, 이를 통해 재정 상태를 파악하거나, 특정 기간 동안의 거래 패턴을 분석할 수 있다. 또한, 특정 조건이 충족되었을 때 자동으로 보고서를 생성하여 사용자가 필요할 때 참고할 수 있도록 할 수도 있을 거 같다.
- 스마트 컨트랙트 상호작용 자동화: 사용자가 특정 이벤트에 따라 자동으로 스마트 컨트랙트와 상호작용하도록 설정할 수 있다. 예를 들어, 사용자가 특정 조건이 충족되었을 때 자동으로 다른 스마트 컨트랙트와 상호작용하여 토큰을 전송하거나, 특정 서비스를 호출하는 기능을 제공할 수 있다. 이를 통해 사용자는 반복적인 작업을 자동화하여 편리하게 이용할 수 있는 환경을 만들 수 있다.
마치며
Ethers.js를 활용하여 블록체인 네트워크에서 토큰 전송을 감지하고 이를 처리하는 방법에 대해 알아보았다. 전송 이벤트를 감지하기 위해 필터를 설정하는 과정에서 발생한 문제를 해결하기 위해 두 개의 토큰의 ABI를 비교하고, 각 토큰의 이벤트 시그니처 차이를 파악하여 두 토큰의 이벤트 시그니처를 달리함으로써 문제를 해결하였다.
아직은 이 차이로 인한 문제인지 아니면 잘못된 ERC-20 표준으로 만들어진 토큰 자체의 차이로 인한 문제인지는 모르겠으나. HTML과, Javascript와 같이 표준을 지키면 동일한 방식으로 사용할 수 있는 부분을 좀 멀리 돌아온 것 같은 느낌이었다.
이 글에서는 단순히 이벤트 필터만을 이야기하였으나, 두 토큰의 잔액 조회와 전송 등 많은 부분에서 동일한 ERC-20 토큰이지만 다른 로직을 작성해야 하는 번거로움이 있었다. 개인적으로는 이런 경험에서 또다시 표준의 중요성을 느꼈고, 조금은 과장되지만 이전 IE의 독자적인 HTML 태그와 기능들에 대한 좋지 않은 추억이 생각나는 시간이었다.
블록체인 개발은 빠르게 변화하는 기술 환경에서 항상 최신 정보를 참고하며 지속적으로 학습해야 하는 분야인 거 같다. Ethers.js와 같은 라이브러리를 활용하면 복잡한 블록체인 상호작용을 더욱 쉽게 구현할 수 있지만, 각 프로젝트의 특성에 맞게 최적화하는 과정 또한 느꼈으며, 앞으로 이런 경험을 바탕으로 더 효율적이고 안정적인 애플리케이션을 개발할 수 있는 계기가 되지 않을까.
더 자세한 기술 정보를 보려면 XE 기술 블로그 원문(링크)을 확인하세요.