RSS듀오랩스

블록체인 지갑의 잔액과 토큰 내역을 동기화하는 구조

블록체인 지갑의 잔액 화면은 서버 데이터베이스의 한 행을 읽어 보여 주는 화면이 아닙니다. 공개 주소를 기준으로 여러 블록체인 상태를 조회하고, 토큰 단위와 거래 확정 상태를 해석해 만든 하나의 읽기 모델입니다.

네트워크는 계속 새 블록을 만들고, 사용자가 전송한 거래는 대기 상태에서 포함 또는 실패 상태로 바뀝니다. 앱이 이 변화를 안정적으로 보여 주려면 RPC 호출 몇 개보다 데이터의 기준 시점과 갱신 정책을 먼저 설계해야 합니다.

지갑 화면은 블록체인 상태의 투영입니다

지갑이 표시하는 대표적인 데이터는 다음과 같습니다.

  • 네이티브 자산 잔액
  • 토큰별 잔액과 소수점 단위
  • 전송 및 수신 내역
  • 대기 중인 트랜잭션
  • 현재 블록 높이와 확정 상태
  • 토큰 이름, 심볼과 이미지 같은 메타데이터

이 정보가 모두 같은 출처에서 나오지는 않습니다. 노드 RPC는 현재 상태를 읽는 데 적합하지만, 특정 주소의 전체 거래 내역을 검색하는 기능은 제한적일 수 있습니다. 거래 내역과 토큰 목록은 별도의 인덱서나 서비스 API가 더 효율적인 경우가 많습니다.

따라서 지갑의 동기화 계층은 RPC 응답, 인덱서 데이터와 로컬 캐시를 하나의 화면 모델로 조합해야 합니다.

RPC와 인덱서는 역할이 다릅니다

EVM 호환 네트워크를 예로 들면 네이티브 자산 잔액은 eth_getBalance로 조회할 수 있습니다. ERC-20 토큰 잔액은 토큰 컨트랙트의 balanceOfeth_call로 실행해 확인합니다. 네트워크마다 메서드와 데이터 형식은 달라질 수 있지만, 공개 주소로 원장 상태를 읽는다는 원리는 같습니다.

반면 주소별 거래 내역은 여러 블록과 이벤트 로그를 역방향으로 검색해야 합니다. 모바일 앱이 RPC 노드에서 매번 직접 계산하면 호출량과 응답 시간이 커집니다. 이때 인덱서는 블록을 미리 분석해 주소 중심의 조회 결과를 제공합니다.

RPC 노드
  네이티브 잔액, 컨트랙트 상태, 블록 높이

인덱서
  주소별 거래 내역, 토큰 전송 이벤트

메타데이터 서비스
  토큰 이름, 심볼, 이미지와 검증 정보

로컬 저장소
  마지막 정상 결과, 대기 중인 거래, 갱신 기준점

출처를 분리하면 한 서비스가 느려져도 전체 화면을 비우지 않고, 마지막 정상 데이터와 현재 갱신 상태를 함께 보여 줄 수 있습니다.

캐시 키에는 주소와 네트워크가 모두 들어가야 합니다

같은 주소 문자열이라도 네트워크가 다르면 잔액과 토큰 목록은 전혀 다릅니다. 캐시를 주소만으로 구분하면 메인넷에서 테스트넷 잔액이 보이거나, 네트워크 전환 직후 이전 토큰 목록이 잠시 노출될 수 있습니다.

최소한 다음 값이 캐시 경계에 포함돼야 합니다.

chainId + accountAddress + assetIdentifier

토큰의 식별자에는 컨트랙트 주소가 포함됩니다. 심볼만으로 토큰을 구분하면 이름이 같은 다른 자산을 합칠 수 있습니다. 대소문자 정규화 규칙과 체크섬 주소 처리도 조회와 표시 단계에서 일관되게 적용해야 합니다.

계정을 바꾸거나 네트워크를 전환할 때는 기존 요청도 취소하거나 결과 적용 조건을 검사해야 합니다. 늦게 도착한 이전 네트워크 응답이 현재 화면을 덮어쓰는 경쟁 상태가 생길 수 있기 때문입니다.

폴링과 WebSocket은 함께 사용할 수 있습니다

앱이 열려 있는 동안 일정 간격으로 잔액을 다시 조회하는 폴링은 단순하고 복구하기 쉽습니다. 하지만 간격이 짧으면 RPC 호출이 많아지고, 길면 입금 반영이 늦어집니다.

WebSocket으로 새 블록 헤더를 구독하면 블록이 생성된 시점에 맞춰 필요한 데이터만 갱신할 수 있습니다. 다만 모바일 네트워크에서는 백그라운드 전환, Wi-Fi와 셀룰러 이동, 절전 정책 때문에 연결이 자주 끊깁니다. WebSocket 이벤트만 신뢰하면 끊어진 동안 생성된 블록을 놓칠 수 있습니다.

안정적인 구조는 두 방식을 조합합니다.

  1. 화면 진입 시 RPC로 기준 상태를 조회합니다.
  2. 연결 중에는 새 블록 이벤트를 받아 변경 가능성이 있는 데이터를 갱신합니다.
  3. 앱 복귀나 재연결 시 마지막 블록 높이부터 현재 높이까지 공백을 확인합니다.
  4. 이벤트가 오래 없으면 느린 주기의 폴링으로 상태를 보정합니다.

실시간 구독은 갱신 신호로 사용하고, 최종 상태는 다시 조회해 확정하는 편이 안전합니다.

정수 잔액과 표시 금액을 분리해야 합니다

블록체인 잔액은 보통 최소 단위의 큰 정수로 전달됩니다. 화면에 표시할 때 토큰의 decimals를 적용하지만, 내부 계산까지 부동소수점으로 바꾸면 반올림 오차가 생길 수 있습니다.

원본 잔액은 큰 정수나 정밀도 손실이 없는 문자열로 유지하고, 표시 단계에서만 소수점 위치를 계산해야 합니다. 사용자가 입력한 전송 금액도 같은 규칙으로 최소 단위 정수로 변환한 뒤 범위와 잔액을 검증합니다.

토큰 메타데이터가 없거나 잘못된 경우도 고려해야 합니다. decimals, symbol, name 호출이 실패할 수 있고, 악성 토큰이 혼동을 유도하는 이름을 반환할 수도 있습니다. 검증되지 않은 토큰은 컨트랙트 주소를 함께 보여 주고 이미지와 이름을 신뢰 정보처럼 사용하지 않는 편이 좋습니다.

트랜잭션 해시는 완료 상태가 아닙니다

트랜잭션을 네트워크에 전송해 해시를 받았다는 것은 노드가 요청을 받아들였다는 뜻에 가깝습니다. 아직 블록에 포함되지 않았고, 수수료 조건이나 nonce 충돌로 오래 대기하거나 교체될 수 있습니다.

지갑에서는 최소한 다음 상태를 구분해야 합니다.

  • 생성됨: 로컬에서 서명했지만 전파되지 않은 상태
  • 대기 중: 네트워크에 전파됐지만 블록에 포함되지 않은 상태
  • 포함됨: 특정 블록의 영수증이 확인된 상태
  • 확정됨: 서비스 정책상 필요한 블록 수가 추가된 상태
  • 실패함: 실행 영수증이 실패를 나타내는 상태
  • 교체 또는 제외됨: 같은 nonce의 다른 거래로 대체되거나 더 이상 조회되지 않는 상태

전송 직후에는 로컬의 대기 거래를 화면에 먼저 합치고, 이후 노드와 인덱서 결과로 상태를 갱신할 수 있습니다. 이때 트랜잭션 해시와 nonce를 함께 저장하면 교체 거래를 추적하기 쉬워집니다.

실패할 때 마지막 정상 상태를 설명해야 합니다

RPC 오류가 발생했다고 잔액을 0으로 표시하면 사용자는 자산이 사라졌다고 느낄 수 있습니다. 마지막으로 성공한 잔액을 유지하면서 갱신 시각과 연결 상태를 별도로 보여 주는 편이 안전합니다.

재시도에는 지수 백오프와 상한을 두고, 앱이 백그라운드일 때 불필요한 요청을 줄여야 합니다. 여러 RPC 엔드포인트를 사용할 경우에는 블록 높이 차이와 체인 ID를 확인해 잘못된 네트워크 응답을 섞지 않도록 합니다.

동기화의 핵심은 일관된 기준점입니다

지갑 동기화는 최대한 자주 API를 호출하는 문제가 아닙니다. 어떤 네트워크와 계정을 보고 있는지, 데이터가 어느 블록을 기준으로 하는지, 대기 중인 거래를 어떻게 합칠지를 일관되게 관리하는 문제입니다.

RPC는 현재 원장 상태를 제공하고, 인덱서는 검색 가능한 이력을 제공하며, 로컬 저장소는 화면의 연속성을 만듭니다. 세 계층의 역할을 분리하면 네트워크가 불안정한 모바일 환경에서도 잔액과 거래 상태를 예측 가능하게 보여 줄 수 있습니다.

마지막 수정:

공유하실 때는 출처(Duolabs)와 원문 주소를 표시해 주세요.