본문으로 건너뛰기
전체 글

멀티체인 API에서 같게 만들 것과 남겨 둘 차이

· 약 11분
Bankware Global Engineering

업무 화면에는 “A가 B에게 10개를 보낸다”는 송금 기능이 있다. 처음에는 한 블록체인만 연결했지만, 다른 체인을 사용하는 고객에게도 같은 업무를 제공해야 한다. 체인 선택 항목을 하나 추가하면 끝날까?

보내는 사람과 받는 사람, 자산과 수량을 확인하는 일은 비슷하다. 그러나 어떤 체인은 토큰 계약을 호출하고, 어떤 체인은 아직 쓰지 않은 자산 묶음을 소비한다. 수취인의 지갑 주소 외에 토큰 계정이나 태그가 필요하기도 한다. 이 차이를 알아야 같은 송금 화면에서 무엇을 감추고 무엇을 보여 줄지 정할 수 있다.

BXB의 EVM·Cardano·Solana·XRPL 전송 구현을 바탕으로 그 경계를 살펴보자. 네 체인에서 각각 10단위를 보내는 설명용 예제를 사용한다. 체인 사이에서 같은 자산을 옮기는 브리지 거래는 다루지 않는다.

같은 숫자로 시작하는 네 개의 독립 송금​

첫 번째 예제는 EVM 네트워크의 ERC-20 토큰 E, 두 번째는 Cardano의 native token C, 세 번째는 Solana의 기본 Token Program으로 발행한 토큰 S다. native token은 Cardano 원장이 직접 수량을 관리하는 사용자 정의 자산이며, 기본 코인 ADA와 구별한다. 네 번째 예제는 XRP Ledger, 줄여서 XRPL의 기본 자산 XRP를 보낸다.

각 예제에서 A에게 송금용으로 배정한 자산은 100단위, B에게 배정한 자산은 0단위다. 10단위를 한 번 보내면 이 송금분은 A 90, B 10이 된다. E·C·S는 서로 다른 토큰이고, 각 체인의 A·B도 독립된 발신자와 수취인이다. 가격이나 교환 비율을 비교하는 숫자가 아니다.

E와 S는 소수 자릿수가 0인 토큰으로 정하고, C도 원장의 정수 1단위를 화면의 1개로 표시한다. 따라서 세 토큰의 전송 수량은 모두 정수 10이다. XRP는 10 XRP를 보내며, 요청에서 사용하는 가장 작은 단위인 drop으로는 10000000이다. 1 XRP는 100만 drops다.

발행·소각·토큰 전송 수수료와 다른 동시 거래는 없다고 가정한다. 체인 수수료와 필요한 최소 잔액은 미리 준비한다. 특히 XRP는 송금 자산과 수수료 자산이 같으므로, 앞의 100과 0은 준비금·수수료를 제외한 송금분이다. 실제 XRP 계정 전체 잔액은 뒤에서 따로 계산한다. 세 토큰의 100과 0은 해당 토큰 잔액이며, ETH·ADA·SOL 보유량은 포함하지 않는다.

이제 업무 요청의 공통 내용을 정리할 수 있다. 다음은 설명용 업무 모델이며 모든 BXB API가 받는 동일한 JSON 형식은 아니다.

업무가 정해야 할 것예제의 내용함께 보존할 의미
실행 위치선택한 하나의 네트워크운영망·시험망까지 구별
보내는 주체와 목적지A에서 B로서명 권한과 수취 정보
자산과 수량E·C·S 10개 또는 XRP 10자산 식별자와 수량 단위
요청의 연결 정보이번 한 번의 송금후속 거래 조회와 업무 기록

자산 이름만 같다고 같은 자산은 아니다. 업무 모델에는 네트워크와 그 안의 자산 식별자가 함께 있어야 한다. “10”이라는 숫자도 표시 수량인지 원장의 정수 수량인지 정하지 않으면 실행할 수 없다. 소수 자릿수가 6인 토큰으로 예제를 바꾼다면 화면의 10개를 10000000으로 바꾸는 단계부터 필요하다.

공통 진입점 뒤에서 체인에 맞는 역할을 고른다​

BXB 구현에서는 체인 계열을 구별하는 namespace를 사용한다. 이를 네트워크 연결, 지갑 관리, 요청을 전달하는 프록시와 API 문서 생성 등의 구현을 고르는 기준으로 삼는다. 같은 역할을 맡는 구성요소라도 EVM용, Cardano용, Solana용, XRPL용이 각각 존재하는 구조다.

namespace는 “어떤 규칙의 구현을 사용할까”에 답하고, 선택한 네트워크는 “어느 원장에 실행할까”에 답한다. EVM 계열을 골랐다는 사실만으로 실제 체인 하나가 결정되지는 않는다. 업무가 지정한 네트워크와 등록한 자산을 바탕으로 그 체인에 맞는 요청 구성과 노드 연결이 이어져야 한다.

이 구조가 줄이는 것은 연계 코드의 반복이다. 업무 서버는 고객의 송금 의도를 접수하고, 허용된 주체인지 확인하고, 결과를 업무 기록에 연결하는 흐름을 유지할 수 있다. 체인 어댑터는 그 의도를 실행할 거래와 서명 대상, 조회 요청으로 바꾼다. 공통 역할을 나누었다고 각 역할의 입력과 결과까지 모두 같아지는 것은 아니다.

예를 들어 EVM의 토큰 자산은 계약 주소로 찾고, Cardano native token은 발행 정책을 식별하는 policy ID와 asset name의 조합으로 찾는다. Solana의 토큰 종류는 mint라는 자산 계정의 주소로 식별한다. XRPL의 이번 자산은 XRP다. 업무 시스템이 이를 공통 자산 ID로 관리하더라도 어댑터에는 원래 식별 정보가 필요하다. 체인 선택만 바꿔 이전 체인의 주소나 자산 정보를 그대로 재사용할 수 없는 이유다.

EVM에서는 토큰 계약을 호출하고 거래 순서와 가스비를 준비한다​

E를 10개 보내려면 A가 E의 토큰 계약에 transfer(B, 10)을 요청한다. 일반적인 ERC-20 전송의 수취인 B와 수량은 함수의 인자다. 거래 자체가 향하는 주소는 E의 계약 주소다. B에게 기본 코인 ETH 10개를 보내는 거래와 구별해야 한다.

BXB의 EVM 프록시는 계약의 함수 형식을 기술한 ABI를 바탕으로 요청 인자를 호출 데이터로 인코딩한다. 일반 서명 경로는 그 데이터를 담은 거래를 만들고 서명해 제출한다. 컨트랙트를 REST API로 연결하는 과정에서 설명한 변환이 이 예제에서는 토큰의 transfer 호출에 적용되는 것이다.

이때 A의 nonce와 가스 조건이 추가된다. nonce는 이 네트워크에서 A가 보내는 거래의 순서를 나타내는 공개 값이다. 가스 한도와 수수료 조건은 계약 실행에 쓸 자원과 비용을 정한다. 확인한 BXB의 일반 EVM 경로도 발신자의 nonce를 조회하고 가스를 추정해 이 필드들을 채운다. Ethereum의 거래 설명은 호출 데이터와 nonce·가스 필드가 하나의 서명 대상에 들어가는 관계를 보여 준다.

여기서는 A가 직접 서명하고 기본 코인으로 가스비도 낸다고 가정한다. E를 100개 갖고 있어도 가스비가 부족하면 이 조건의 거래를 실행할 수 없다. 반대로 거래가 의도대로 성공했다면 E 잔액은 A 90개, B 10개이고, A의 기본 코인 잔액에서는 실행 수수료가 따로 빠진다. 토큰 수량과 실행 비용을 나누어 확인해야 송금 화면의 “보유량 100개”가 무엇을 뜻하는지 설명할 수 있다.

Cardano에서는 쓸 자산 묶음과 새로 만들 출력을 고른다​

Cardano의 C 100개가 A의 한 UTXO에 들어 있다고 하자. UTXO는 이전 거래가 만들었고 아직 소비되지 않은 출력이다. 새 거래는 이를 입력으로 소비하고, B에게 C 10개가 담긴 출력과 A에게 C 90개가 돌아오는 출력을 만든다. 기존 묶음 안에서 10개만 떼어 내고 나머지를 그대로 두는 방식이 아니다. Cardano의 UTXO 설명은 입력을 통째로 소비하고 새 출력을 만드는 원리를 다룬다.

이 예제의 입력에는 충분한 ADA도 함께 있다고 가정한다. B의 C 10개 출력과 A의 C 90개 잔돈 출력에는 각각 필요한 최소 ADA가 붙어야 한다. 네트워크 수수료도 ADA로 낸다. 토큰 수량은 100 = 10 + 90으로 보존되고, 입력 ADA는 B의 출력에 붙은 ADA와 A에게 돌아오는 ADA, 수수료의 합으로 나뉜다. 따라서 B에게 간 ADA는 단순히 소모된 수수료가 아니라 B의 출력에 남는 자산이다.

최소 ADA는 “토큰 한 개당 고정 비용”이 아니다. 출력이 차지하는 크기와 프로토콜 조건에 따라 달라지며, 수취 출력뿐 아니라 잔돈 출력도 이 조건을 만족해야 한다. Cardano의 연계 문서가 이 최소값과 출력 구성의 관계를 설명한다.

확인한 BXB의 native token 전송 경로는 자산 식별자와 양의 정수 수량으로 수취 출력을 지정하고, 거래 구성 라이브러리를 통해 입력 선택·출력 구성과 서명·제출을 진행한다. 이 경로는 토큰의 일반 전송을 원장의 자산 이동으로 다룬다. EVM의 transfer 함수 호출을 그대로 번역하지 않는다.

업무 요청은 여전히 “B에게 C 10개”다. 어댑터가 해결할 질문은 사용할 수 있는 UTXO가 무엇이며, 필요한 토큰·ADA와 잔돈 출력을 어떻게 구성할 것인가다. 다른 거래가 같은 UTXO를 먼저 소비했다면 입력을 다시 검토해야 한다. 이 차이는 EVM의 nonce를 다른 이름으로 바꾸는 것만으로 표현할 수 없다.

Solana에서는 수취인의 토큰 계정과 거래의 유효 범위를 준비한다​

Solana에서 S는 mint 주소로 식별되고, A와 B의 S 잔액은 각각의 토큰 계정에 기록된다. 지갑 주소가 토큰을 보낼 권한을 나타내는 주소라면, 토큰 계정은 특정 mint의 수량을 담는 곳이다. 한 지갑이 여러 종류의 토큰을 받으려면 종류별 토큰 계정이 필요하다.

확인한 BXB의 일반 토큰 전송은 A와 B의 associated token account, 줄여서 ATA를 사용한다. ATA는 지갑·mint·Token Program을 기준으로 주소를 정할 수 있는 토큰 계정이다. 어댑터는 B의 지갑 주소에서 수취 ATA를 구하고, 필요하면 생성할 수 있는 명령을 전송과 같은 거래에 넣는다. Solana의 토큰 계정 설명은 지갑과 mint, ATA의 관계 및 계정 생성에 필요한 최소 잔액을 설명한다.

예제에서는 기본 Token Program의 S를 transferChecked로 전송한다. 이 명령은 전송 수량과 함께 mint 및 소수 자릿수를 검사하는 형태다. BXB는 등록된 자산의 소수 자릿수를 사용하고, 수량은 원장의 정수 단위로 받는다. S는 소수 자릿수가 0이므로 전송 수량 10으로 A의 S 계정 100개가 90개, B의 S 계정 0개가 10개가 된다.

A는 거래 수수료를 낼 SOL을 갖고 있어야 한다. B의 ATA를 새로 만들어야 한다면, 이 경로에서 생성 비용을 부담하는 A에게 계정의 최소 잔액을 마련할 SOL도 필요하다. 그 최소 잔액은 계정에 남는 금액이므로, 네트워크 수수료와 같은 항목으로 전부 소비됐다고 기록해서는 안 된다. 예제에서 두 토큰 계정은 이미 존재한다고 두면 새 계정 생성 자금 없이 토큰 이동과 거래 수수료를 구분할 수 있다.

거래를 제출할 때에는 recent blockhash도 필요하다. 최근 블록의 해시를 넣어 해당 거래가 유효하게 처리될 범위를 정한다. 확인한 전송 경로는 이 값을 조회해 서명·제출에 사용한다. Solana 거래 구조는 명령과 서명, 최근 blockhash의 관계를 설명한다. 이 일반 경로의 거래를 오래 보관했다가 보내려면 blockhash의 유효성도 살펴야 한다. 송금의 수량이 맞는지와 서명한 거래가 아직 제출 가능한지는 서로 다른 검사다.

XRPL에서는 XRP 금액과 순서, 수취 조건을 함께 담는다​

이번 XRPL 예제는 XRP를 직접 보내는 Payment다. 확인한 BXB 자산·결제 경로가 다루는 native XRP를 선택했다. XRPL 자체가 제공하는 다른 자산이나 결제 방식까지 이 구현의 지원 범위로 넓혀 설명하지 않는다. XRPL의 Payment 문서에서 직접 XRP 결제는 지정한 XRP 금액을 수취인에게 전달하는 유형이다.

BXB는 수취 주소와 drops 단위의 금액을 읽고, 발신 계정의 sequence와 수수료, 거래가 포함될 수 있는 마지막 원장 번호를 결제에 넣는다. sequence는 발신 계정의 거래 순서를 다루고, 마지막 원장 번호는 유효 기간의 상한을 둔다. 각 필드의 역할은 XRPL 거래 공통 필드에 정의돼 있다. 순서를 나타낸다는 점은 EVM nonce와 닮았지만, 거래 형식과 제출·확인 절차까지 같다는 뜻은 아니다.

목적지에 따라 destination tag도 필요하다. 하나의 수취 주소로 여러 고객의 입금을 받는 서비스라면, 태그가 어느 고객에게 입금을 배정할지 알려 줄 수 있다. 수취 계정이 태그를 필수로 설정하면 태그를 빠뜨린 결제는 거절된다. BXB 경로도 이 수취 조건을 사전에 확인한다. destination tag의 역할은 주소 안에서 고객을 구분하는 업무 정보이므로, 어댑터가 nonce처럼 임의로 만들어 채울 수 없다.

이제 XRP 잔액을 닫아 보자. A와 B는 이미 활성화됐고 각자의 필요 준비금을 갖고 있다고 가정한다. A는 여기에 송금분 100 XRP와 이번 거래의 실제 수수료만큼을 더 갖고, B의 송금분은 0 XRP다. 10 XRP 전송이 성공하면 A에게는 준비금과 90 XRP, B에게는 준비금과 10 XRP가 남는다. 별도로 마련한 수수료는 소모된다. 준비금과 송금분을 다른 계정에 보관한다는 뜻은 아니며, 하나의 XRP 잔액을 설명을 위해 나눈 것이다.

따라서 총잔액이 정확히 100 XRP인 계정에서 10 XRP를 보낸 결과를 90 XRP라고 쓰면 수수료가 빠진 계산이다. 계정이 유지해야 할 준비금도 고려해야 한다. XRPL의 준비금 설명에 따르면 필요한 금액은 네트워크와 계정이 소유한 원장 객체 등에 영향을 받는다. 고정된 준비금 숫자를 모든 계정의 전송 가능 금액에 적용할 수는 없다.

같은 업무 결과를 제공하려면 지원 범위와 완료 기준도 남겨야 한다​

네 전송의 공통 목표는 수취인에게 정해진 자산 10단위를 전달하고 그 결과를 업무 요청에 연결하는 것이다. 그 목표를 위해 체인별 차이를 제거하는 대신, 각 차이를 처리할 담당을 분명히 할 수 있다. nonce·입력 UTXO·recent blockhash·sequence는 실행을 구성하는 쪽에서 다룬다. 수취 태그처럼 업무가 알고 있는 정보와, 추가로 필요한 기본 자산 비용은 요청 단계까지 전달되어야 한다.

지원 범위도 같은 방식으로 드러내야 한다. BXB의 Solana 토큰 경로는 mint의 확장 기능을 보고 제공할 명령을 정한다. 해석하지 못하는 확장이 있으면 읽기와 쓰기의 지원 범위를 구분한다. 이것은 Solana가 그 자산을 전송할 수 없다는 뜻이 아니라, 현재 어댑터가 이해하는 명령의 범위다. 체인 이름을 하나 지원한다고 그 체인의 모든 자산·모든 기능·동일한 API 주소를 지원하는 것은 아니다.

결과를 공통 화면으로 묶을 때에도 “성공” 한 단어만 남기면 곤란하다. 다음은 업무 연계를 위해 구분할 설명용 단계다. 모든 BXB 경로가 이 이름의 공통 상태를 반환한다는 뜻은 아니다.

업무가 알고 싶은 단계확인할 내용예제에서의 판단
제출 전 거절필요한 정보·자산·권한이 충분한가전송을 시작하지 않음
제출 후 확인 중어느 네트워크의 어떤 거래를 추적하는가아직 90과 10을 확정하지 않음
업무 완료의도한 이동과 필요한 확정 조건을 확인했는가송금분 A 90, B 10과 비용을 기록

체인별 완료 근거도 보존해야 한다. 예를 들어 EVM의 실행 결과를 담은 receipt와 Solana의 확인 상태는 같은 응답 형식이 아니다. 확인한 Solana 대기 경로는 confirmed 또는 finalized 상태를 받아들인다. 이 둘을 구별할 필요가 있는 업무라면 “응답을 받았다”만으로 최종 완료를 정하지 않아야 한다. 공통 업무 상태와 그 판단에 사용한 체인별 근거를 함께 두는 것은 이런 차이를 처리하기 위한 설계 기준이다.

응답을 놓친 경우에도 이 연결은 필요하다. 송금 타임아웃과 재시도에서 설명했듯이, 기존 거래를 조회하는 일과 새 거래를 만드는 일은 구분해야 한다. 여기에 각 체인의 거래 순서·사용한 입력·유효 기간이 더해지므로, 동일한 재시도 동작을 모든 체인에 적용할 수는 없다.

멀티체인 API의 공통화는 업무가 반복해서 묻는 질문을 안정적으로 유지하는 데서 시작한다. 누가 어떤 자산을 누구에게 얼마나 보내려 했고, 그 의도는 어떤 거래와 결과로 이어졌는가. 그 답을 설명할 수 있도록 체인별 자산 식별, 비용, 실행 조건과 확인 근거를 남겨 두는 것이 어댑터의 역할이다.