계정주 확인 자동화
사용자가 타 거래소에 보유 중인 디지털 자산을 업비트로 입금 시 자금세탁을 방지하기 위해 가상자산사업자가 계정주 동일 여부를 확인해야 합니다. 업비트 API를 사용해 자동으로 계정주 확인을 하는 방법을 학습합니다.
전체 코드 예제는 Recipes 메뉴에서 확인하실 수 있습니다.
위 버튼을 클릭하면 본 튜토리얼의 전체 코드 Recipe 페이지로 이동합니다.
시작하기
디지털 자산 입금은 다음과 같은 순서로 진행됩니다.
- 업비트에서 디지털 자산을 입금받을 수 있는 입금 주소를 생성합니다.
- 타 거래소에 보유 중인 디지털 자산을 업비트의 입금 주소로 전송합니다.
- 수신자와 송신자 계정주 일치 여부를 확인합니다.
- 확인이 완료된 후, 입금 반영 여부를 확인합니다.
이 가이드에서는 업비트 API를 사용해 위와 동일한 기능을 구현하고 타 거래소에서 업비트로 입금 시 자동으로 트래블룰 검증을 진행하는 방법을 안내합니다.
100만 원 이상의 디지털 자산을 입금하는 경우 트래블룰 적용 대상이며 송금인, 수취인 일치 여부를 검증하고 확인된 사용자의 입금 건만 정상적으로 반영합니다. 트래블룰에 대한 자세한 사항은 아래 링크를 통해 확인할 수 있습니다.
업비트 고객센터 > 트래블룰 알아보기
인증 안내
입금 관리 API는 Exchange API로, 인증 대상입니다. 인증 문서와 아래 레시피를 참고하여 API 호출 시 인증 헤더를 추가하시기 바랍니다.
입금 주소 생성
타 거래소에서 출금한 자산을 업비트로 입금받기 위해서는 입금 주소가 필요합니다. 입금 주소 생성 요청 API를 호출하여 지정한 디지털 자산의 입금 주소를 생성하는 함수를 구현합니다.
def create_deposit_address(currency: str, net_type: str) -> Mapping:
body = {
"currency": currency,
"net_type": net_type
}
query_string = _build_query_string(body)
jwt_token = _create_jwt(access_key, secret_key, query_string)
url = "https://api.upbit.com/v1/deposits/generate_coin_address"
headers = {
"Authorization": "Bearer {jwt_token}".format(jwt_token=jwt_token),
"Content-Type": "application/json"
}
response = requests.post(url, headers=headers, json=body).json()
return response
해당 API는 비동기로 처리되기 때문에 최초 실행 시 "생성 중" 응답을 확인할 수 있습니다.
{
"success": true,
"message": "USDT 입금주소를 생성중입니다."
}
일정 시간 이후 해당 함수를 다시 한 번 호출하거나 이미 입금 주소가 존재하는 경우, 다음과 같이 생성된 입금 주소가 반환됩니다.
{
"currency": "USDT",
"net_type": "TRX",
"deposit_address": "<your_deposit_address>",
"secondary_address": None
}
타 거래소에서 업비트로 입금
타 거래소에서 업비트 입금 주소로 디지털 자산을 입금합니다.
단일 입금 조회 API를 호출하여 특정 입금 건의 정보를 조회하는 함수를 구현합니다. 타 거래소에서 업비트로 디지털 자산을 출금할 때 받은 UUID 또는 TxID 로 해당 입금 건의 상태를 조회할 수 있습니다.
- UUID로 특정 입금 건 정보 조회
def get_deposit_by_uuid(uuid: str) -> Mapping:
params = {
"uuid": uuid
}
query_string = _build_query_string(params)
jwt_token = _create_jwt(access_key, secret_key, query_string)
url = "https://api.upbit.com/v1/deposit"
headers = {
"Authorization": "Bearer {jwt_token}".format(jwt_token=jwt_token),
"Content-Type": "application/json"
}
response = requests.get(url, headers=headers, params=params).json()
return response
- TxID로 특정 입금 건 정보 조회
def get_deposit_by_txid(txid: str) -> Mapping:
params = {
"txid": txid
}
query_string = _build_query_string(params)
jwt_token = _create_jwt(access_key, secret_key, query_string)
url = "https://api.upbit.com/v1/deposit"
headers = {
"Authorization": "Bearer {jwt_token}".format(jwt_token=jwt_token),
"Content-Type": "application/json"
}
response = requests.get(url, headers=headers, params=params).json()
return response
입금 정보의 state 필드의 값이 TRAVEL_RULE_SUSPECTED인 경우 트래블룰 계정주 확인 진행이 필요한 상태입니다.
{
"type": "deposit",
"uuid": "<your_deposit_uuid>",
"currency": "USDT",
"net_type": "TRX",
"txid": "<your_deposit_txid>",
"state": "TRAVEL_RULE_SUSPECTED",
"created_at": "2025-07-04T15:00:02+09:00",
"done_at": "2025-07-04T15:00:48+09:00",
"amount": "2500.363189",
"fee": "0.0",
"transaction_type": "default"
}
계정주 확인
트래블룰 계정주 확인 서비스 지원 거래소 목록 조회
트래블룰 계정주 확인 서비스를 요청하기 위해서는 상대 거래소 UUID가 필요합니다. 아래 함수를 구현하여 계정주 확인 서비스 지원 거래소 목록 조회 API를 호출한 뒤 거래소의 이름에 상응하는 UUID를 조회할 수 있습니다.
def get_vasp_uuid(vasp_name: str) -> str:
params = {
"vasp_name": vasp_name
}
query_string = _build_query_string(params)
jwt_token = _create_jwt(access_key, secret_key, query_string)
url = "https://api.upbit.com/v1/travel_rule/vasps"
headers = {
"Authorization": "Bearer {jwt_token}".format(jwt_token=jwt_token),
"Content-Type": "application/json"
}
response = requests.get(url, headers=headers, params=params).json()
vasp_uuid = next((item.get('vasp_uuid') for item in response if item.get('vasp_name') == vasp_name), None)
if vasp_uuid is None:
raise ValueError("{vasp_name} is NOT_FOUND".format(vasp_name=vasp_name))
return vasp_uuid
계정주 확인 요청
트래블룰 검증을 실행하는 함수를 구현합니다. 입금한 거래소의 UUID와 입금 건에 대한 UUID 혹은 TxID로 검증을 실행합니다.
- UUID로 트래블룰 검증 - 입금 UUID로 계정주 검증 요청
def verify_travel_rule_by_uuid(deposit_uuid: str, vasp_uuid: str) -> str:
params = {
"deposit_uuid": deposit_uuid,
"vasp_uuid": vasp_uuid
}
query_string = _build_query_string(params)
jwt_token = _create_jwt(access_key, secret_key, query_string)
url = "https://api.upbit.com/v1/travel_rule/deposit/uuid"
headers = {
"Authorization": "Bearer {jwt_token}".format(jwt_token=jwt_token),
"Content-Type": "application/json"
}
response = requests.post(url, headers=headers, json=params).json()
verification_result = response.get('verification_result')
if verification_result is None:
raise ValueError("Please check the response. {response}".format(response=response))
else:
return verification_result
- TxID로 트래블룰 검증 - 입금 TxID로 계정주 검증 요청
def verify_travel_rule_by_txid(deposit_txid: str, vasp_uuid: str, currency: str, net_type: str) -> str:
params = {
"txid": deposit_txid,
"vasp_uuid": vasp_uuid,
"currency": currency,
"net_type": net_type
}
query_string = _build_query_string(params)
jwt_token = _create_jwt(access_key, secret_key, query_string)
url = "https://api.upbit.com/v1/travel_rule/deposit/txid"
headers = {
"Authorization": "Bearer {jwt_token}".format(jwt_token=jwt_token),
"Content-Type": "application/json"
}
response = requests.post(url, headers=headers, json=params).json()
verification_result = response.get('verification_result')
if verification_result is None:
raise ValueError("Please check the response. {response}".format(response=response))
else:
return verification_result
함수 실행 시 다음과 같이 검증에 대한 결과를 확인할 수 있습니다.
{
"deposit_uuid": "00000000-0000-0000-0000-000000000000",
"verification_result": "verified",
"deposit_state": "PROCESSING"
}
입금 반영 확인
트래블룰 검증을 완료한 후, 앞서 구현한 입금 정보 조회 함수로 입금 건의 상태를 확인합니다.
{
"type": "deposit",
"uuid": "<your deposit uuid>",
"currency": "USDT",
"net_type": "TRX",
"txid": "<your deposit transaction txid>",
"state": "ACCEPTED",
"created_at": "2025-07-04T15:00:02+09:00",
"done_at": "2025-07-04T15:00:48+09:00",
"amount": "2500.363189",
"fee": "0.0",
"transaction_type": "default"
}
전체 코드
계정주를 자동으로 확인하는 전체 코드는 다음과 같습니다.
from urllib.parse import unquote, urlencode
from typing import Any
from collections.abc import Mapping
import hashlib
import uuid
import jwt # PyJWT
import requests
def create_deposit_address(currency: str, net_type: str) -> Mapping:
body = {
"currency": currency,
"net_type": net_type
}
query_string = _build_query_string(body)
jwt_token = _create_jwt(access_key, secret_key, query_string)
url = "https://api.upbit.com/v1/deposits/generate_coin_address"
headers = {
"Authorization": "Bearer {jwt_token}".format(jwt_token=jwt_token),
"Content-Type": "application/json"
}
response = requests.post(url, headers=headers, json=body).json()
return response
def get_deposit_by_uuid(uuid: str) -> Mapping:
params = {
"uuid": uuid
}
query_string = _build_query_string(params)
jwt_token = _create_jwt(access_key, secret_key, query_string)
url = "https://api.upbit.com/v1/deposit"
headers = {
"Authorization": "Bearer {jwt_token}".format(jwt_token=jwt_token),
"Content-Type": "application/json"
}
response = requests.get(url, headers=headers, params=params).json()
return response
def get_deposit_by_txid(txid: str) -> Mapping:
params = {
"txid": txid
}
query_string = _build_query_string(params)
jwt_token = _create_jwt(access_key, secret_key, query_string)
url = "https://api.upbit.com/v1/deposit"
headers = {
"Authorization": "Bearer {jwt_token}".format(jwt_token=jwt_token),
"Content-Type": "application/json"
}
response = requests.get(url, headers=headers, params=params).json()
return response
def get_vasp_uuid(vasp_name: str) -> str:
params = {
"vasp_name": vasp_name
}
query_string = _build_query_string(params)
jwt_token = _create_jwt(access_key, secret_key, query_string)
url = "https://api.upbit.com/v1/travel_rule/vasps"
headers = {
"Authorization": "Bearer {jwt_token}".format(jwt_token=jwt_token),
"Content-Type": "application/json"
}
response = requests.get(url, headers=headers, params=params).json()
vasp_uuid = next((item.get('vasp_uuid') for item in response if item.get('vasp_name') == vasp_name), None)
if vasp_uuid is None:
raise ValueError("{vasp_name} is NOT_FOUND".format(vasp_name=vasp_name))
return vasp_uuid
def verify_travel_rule_by_uuid(deposit_uuid: str, vasp_uuid: str) -> str:
params = {
"deposit_uuid": deposit_uuid,
"vasp_uuid": vasp_uuid
}
query_string = _build_query_string(params)
jwt_token = _create_jwt(access_key, secret_key, query_string)
url = "https://api.upbit.com/v1/travel_rule/deposit/uuid"
headers = {
"Authorization": "Bearer {jwt_token}".format(jwt_token=jwt_token),
"Content-Type": "application/json"
}
response = requests.post(url, headers=headers, json=params).json()
verification_result = response.get('verification_result')
if verification_result is None:
raise ValueError("Please check the response. {response}".format(response=response))
else:
return verification_result
def verify_travel_rule_by_txid(deposit_txid: str, vasp_uuid: str, currency: str, net_type: str) -> str:
params = {
"txid": deposit_txid,
"vasp_uuid": vasp_uuid,
"currency": currency,
"net_type": net_type
}
query_string = _build_query_string(params)
jwt_token = _create_jwt(access_key, secret_key, query_string)
url = "https://api.upbit.com/v1/travel_rule/deposit/txid"
headers = {
"Authorization": "Bearer {jwt_token}".format(jwt_token=jwt_token),
"Content-Type": "application/json"
}
response = requests.post(url, headers=headers, json=params).json()
verification_result = response.get('verification_result')
if verification_result is None:
raise ValueError("Please check the response. {response}".format(response=response))
else:
return verification_result
# 주석을 해제하고 실행할 경우 트래블룰 검증 프로세스가 실행될 수 있습니다. 실행 전 다시 한 번 확인해 주시기 바랍니다.
# if __name__ == "__main__":
# ####### uuid 기반 트래블룰 검증 ########
# currency = "<Enter_your_currency>"
# net_type = "<Enter_your_net_type>"
# deposit_address_dict = create_deposit_address(currency=currency, net_type=net_type)
# deposit_address = deposit_address_dict.get('deposit_address')
# if deposit_address is not None:
# deposit_uuid = "<Enter_your_deposit_uuid>"
# deposit_state = get_deposit_by_uuid(deposit_uuid)['state']
# print(deposit_state)
# if deposit_state == "ACCEPTED":
# vasp_uuid = get_vasp_uuid("바이낸스")
# result = verify_travel_rule_by_uuid(deposit_uuid=deposit_uuid, vasp_uuid=vasp_uuid)
# if result == "verified":
# print(get_deposit_by_uuid(deposit_uuid))
# else:
# raise ValueError("Check the travel rule verification result.")
# else:
# raise ValueError("This deposit does not require verification.")
# else:
# raise ValueError("Check the deposit address.")
# ######## txid 기반 트래블룰 검증 ########
# currency = "<Enter_your_currency>"
# net_type = "<Enter_your_net_type>"
# deposit_address_dict = create_deposit_address(currency=currency, net_type=net_type)
# deposit_address = deposit_address_dict.get('deposit_address')
# if deposit_address is not None:
# deposit_txid = "<Enter_your_deposit_txid>"
# deposit_state = get_deposit_by_txid(deposit_txid)['state']
# if deposit_state == "TRAVEL_RULE_SUSPECTED":
# vasp_uuid = get_vasp_uuid("바이낸스")
# result = verify_travel_rule_by_txid(deposit_txid=deposit_txid, vasp_uuid=vasp_uuid, currency=currency, net_type=net_type)
# if result == "verified":
# print(get_deposit_by_txid(deposit_txid))
# else:
# raise ValueError("Check the travel rule verification result.")
# else:
# raise ValueError("This deposit does not require verification.")
# else:
# raise ValueError("Check the deposit address.")
Updated 5 days ago