RSS듀오랩스

블록체인 트랜잭션을 만들고 서명해 전송하는 과정

블록체인 지갑에서 전송 버튼을 누른 뒤 발생하는 일은 단순한 API 요청과 다릅니다. 앱은 사용자의 의도를 트랜잭션 데이터로 만들고, 현재 네트워크 상태에 맞춰 수수료와 nonce를 채우고, 개인키로 서명한 결과를 노드에 전파해야 합니다.

노드가 트랜잭션 해시를 반환한 뒤에도 처리는 끝나지 않습니다. 거래가 블록에 포함됐는지, 실행에 성공했는지, 충분히 확정됐는지까지 추적해야 사용자가 보는 전송 상태가 완성됩니다.

사용자 입력과 서명 데이터 사이에 검증 계층이 필요합니다

사용자는 보통 수신 주소, 자산과 금액을 입력합니다. 앱은 이 값을 바로 직렬화하지 않고 현재 네트워크의 규칙에 맞는지 먼저 확인해야 합니다.

  • 수신 주소의 형식과 체크섬이 유효한지 확인합니다.
  • 선택한 자산이 현재 네트워크에 존재하는지 확인합니다.
  • 표시 금액을 최소 단위 정수로 정확하게 변환합니다.
  • 잔액이 전송 금액과 예상 수수료를 감당하는지 확인합니다.
  • 컨트랙트 호출이라면 대상 주소와 호출 데이터를 분리해 검증합니다.

네이티브 자산 전송과 토큰 전송도 구조가 다릅니다. EVM 호환 네트워크에서 네이티브 자산은 트랜잭션의 value에 금액을 넣지만, ERC-20 토큰은 토큰 컨트랙트에 transfer 호출 데이터를 보냅니다. 화면에서는 모두 전송으로 보이지만 실제 서명 데이터는 같지 않습니다.

nonce는 계정별 트랜잭션 순서를 나타냅니다

EVM 계열에서 nonce는 해당 계정이 보낸 트랜잭션의 순서를 나타냅니다. 같은 계정에서 동일한 nonce를 가진 거래가 경쟁하면 하나가 다른 하나를 교체할 수 있고, 앞선 nonce가 오래 대기하면 뒤의 거래도 함께 지연될 수 있습니다.

새 트랜잭션을 만들 때는 일반적으로 대기 중인 거래를 포함한 nonce를 조회합니다. 하지만 여러 기기나 여러 화면에서 동시에 전송하면 조회 직후 값이 달라질 수 있습니다. 지갑 내부에는 계정과 네트워크를 기준으로 nonce 사용을 직렬화하는 계층이 필요합니다.

네트워크 nonce 조회

로컬 대기 거래의 nonce와 비교

사용 가능한 다음 nonce 예약

서명과 전파

실패 시 예약 해제 또는 상태 재조회

네트워크 오류가 발생했다고 같은 트랜잭션을 새 nonce로 무조건 다시 만들면 중복 전송이 생길 수 있습니다. 먼저 기존 해시와 nonce가 네트워크에 존재하는지 확인해야 합니다.

수수료는 전송 직전에 계산합니다

블록체인 수수료는 네트워크 혼잡도와 실행할 연산량에 따라 달라집니다. 너무 낮으면 오랫동안 포함되지 않고, 지나치게 높으면 사용자가 불필요한 비용을 부담합니다.

EIP-1559를 사용하는 네트워크에서는 기본 수수료와 우선 수수료를 바탕으로 maxFeePerGasmaxPriorityFeePerGas를 구성합니다. 컨트랙트 호출에는 가스 추정도 필요합니다. 다만 가스 추정 성공이 실제 실행 성공을 보장하지는 않습니다. 추정 이후 컨트랙트 상태가 바뀌거나 사용자의 잔액이 달라질 수 있기 때문입니다.

수수료 화면에는 다음 값을 구분해 보여 주는 것이 좋습니다.

  • 네트워크가 제안한 예상 수수료
  • 사용자가 지불할 수 있는 최대 수수료
  • 실제 블록 포함 후 사용된 수수료

빠름, 보통, 느림 같은 표현만 제공하면 사용자가 비용을 비교하기 어렵습니다. 자산 단위와 법정화폐 환산값을 함께 표시하되, 환산값에는 시세 기준 시각을 표시해야 합니다.

서명 전에는 실행 결과를 가능한 범위에서 확인합니다

컨트랙트 트랜잭션은 수신 주소와 금액만으로 의미를 파악하기 어렵습니다. 가능하다면 읽기 전용 호출이나 시뮬레이션으로 실행 실패 여부를 확인하고, ABI를 알고 있는 호출은 함수와 인자를 사람이 읽을 수 있는 형태로 해석합니다.

토큰 사용 권한을 부여하는 approve 호출처럼 자산을 바로 보내지 않지만 큰 권한을 만드는 트랜잭션도 있습니다. 이 경우 승인 대상, 토큰과 한도를 확인 화면에 명확히 표시해야 합니다. 무제한 승인이라면 일반 금액 승인과 다른 경고가 필요합니다.

시뮬레이션 결과는 참고 정보이며 최종 보증은 아닙니다. 서명과 블록 포함 사이에 원장 상태가 달라질 수 있으므로, 화면에서는 예상 결과와 실제 확정 결과를 구분해야 합니다.

개인키는 기기 안에서 서명에만 사용합니다

검증을 마친 트랜잭션은 네트워크 규칙에 맞게 직렬화하고 해시한 뒤 개인키로 서명합니다. 비수탁형 지갑이라면 이 과정은 기기 안에서 수행되고, 서버에는 개인키나 복구 문구가 아니라 서명된 원시 트랜잭션만 전달됩니다.

서명 데이터에는 현재 네트워크를 식별할 수 있는 값이 포함돼야 합니다. EVM 트랜잭션의 chain ID는 다른 네트워크에서 같은 서명이 재사용되는 위험을 줄입니다. 앱 화면에서 선택한 네트워크와 실제 서명 데이터의 chain ID가 일치하는지 서명 직전에 다시 확인하는 편이 안전합니다.

확인 화면과 서명 모듈이 서로 다른 데이터 객체를 만들지 않도록 하는 것도 중요합니다. 사용자가 확인한 값을 불변 트랜잭션 모델로 만들고, 그 모델을 그대로 직렬화 단계에 전달하면 표시값과 실제 서명값이 달라지는 문제를 줄일 수 있습니다.

전파 성공과 거래 성공은 다른 상태입니다

서명된 트랜잭션을 RPC 노드에 보내면 노드는 형식과 기본 조건을 확인한 뒤 트랜잭션 해시를 반환합니다. 이 시점은 거래 완료가 아니라 네트워크 추적을 시작할 수 있는 시점입니다.

작성됨 → 서명됨 → 전파됨 → 블록 포함 → 실행 성공 또는 실패 → 확정

앱은 트랜잭션 해시로 영수증을 조회하고, 영수증의 실행 상태와 포함된 블록을 확인해야 합니다. 컨트랙트 실행이 실패해도 블록에 포함될 수 있으며 이 경우 가스는 소비될 수 있습니다. 단순히 영수증이 존재한다는 이유로 성공으로 표시해서는 안 됩니다.

확정 기준은 네트워크 특성에 따라 다릅니다. 한 블록에 포함된 즉시 완료로 표시할지, 추가 블록을 기다릴지는 서비스 정책으로 분리해야 합니다. 화면에는 대기, 포함, 확정과 실패 상태를 사용자가 구분할 수 있게 보여 줍니다.

재시도는 같은 작업을 두 번 보내지 않게 설계합니다

모바일 네트워크에서는 요청이 서버에 도착했지만 응답만 사라지는 상황이 발생합니다. 전파 API가 시간 초과됐다고 즉시 새 트랜잭션을 만들면 동일한 의도가 두 번 실행될 수 있습니다.

동일한 원시 트랜잭션 바이트는 동일한 해시를 갖습니다. 응답을 받지 못했을 때는 먼저 로컬에서 계산한 해시로 네트워크 상태를 조회하고, 필요하면 동일한 원시 트랜잭션을 다시 전파합니다. 새 nonce와 새 서명은 사용자가 명시적으로 재전송하거나 교체 거래를 선택했을 때 만들어야 합니다.

대기 거래를 빠르게 처리하기 위한 수수료 인상도 별도 흐름으로 다뤄야 합니다. 같은 nonce를 유지하면서 수수료 조건을 높인 교체 거래를 만들고, 기존 해시와 새 해시의 관계를 로컬에 기록해야 화면에서 중복 거래처럼 보이지 않습니다.

트랜잭션은 하나의 상태 기계로 관리합니다

블록체인 전송의 핵심은 서명 함수 한 번이 아니라 입력부터 확정까지 이어지는 상태 관리입니다. 주소와 금액을 검증하고, nonce와 수수료를 채우고, 사용자가 확인한 데이터에 로컬 서명을 적용하고, 전파 후 실제 실행 결과를 추적해야 합니다.

이 과정을 명시적인 상태 기계로 구성하면 앱 종료와 재시작, 네트워크 전환, RPC 시간 초과와 교체 거래도 같은 규칙으로 복구할 수 있습니다. 사용자는 전송 버튼 하나를 보지만, 지갑은 그 뒤에서 자산 이동의 의도와 네트워크 결과를 끝까지 연결해야 합니다.

마지막 수정:

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