전체 글 보기
6 분 소요

TON 주소 형식 EQ, UQ, raw와 첫 입금이 되돌아오는 이유

TON 주소 형식 EQ, UQ, raw의 차이와 bounceable 대 non-bounceable 플래그, 그리고 새 지갑으로 보낸 첫 입금이 왜 발신자에게 되돌아오는지 정리했습니다.

TONTON 주소bounceableEQ UQ rawparse_address첫 입금

개발자가 새 지갑을 만들고, 주소를 복사하고, 가스용으로 첫 5 GRAM을 보냅니다 — 그런데 1분 뒤 코인이 발신 지갑으로 되돌아옵니다. 새 주소의 잔액은 0입니다. 에러도 없고 "reverted"도 없으며 트랜잭션은 성공적으로 처리되었습니다. 그저 돈이… 튕겨 나온 것입니다. TON 채팅방에서는 이미 고전이 된 사연입니다. "내 주소로 보냈는데 도착하지 않았다." 그리고 원인은 거의 언제나 하나입니다 — 주소 형식을 혼동했고 bounceable 플래그를 잘못 썼다는 것입니다.

TON의 주소 형식인 EQ, UQ, raw가 무엇인지, bounceable과 non-bounceable이 어떻게 다른지, 그리고 새 지갑으로 보낸 첫 입금이 왜 발신자에게 되돌아가는지를 사실에 근거해 정리해보겠습니다. 덧붙여 SDK도 노드도 띄우지 않고 AI 에이전트에서 도구 호출 한 번으로 이 모든 것을 확인하는 방법도 보여드리겠습니다.

TON 주소의 세 가지 형식: raw, EQ, UQ의 차이

TON의 모든 주소는 내부적으로 동일한 것을 담고 있습니다. 워크체인 번호와 컨트랙트 state init의 256비트 해시입니다. 이것이 바로 raw 형식입니다.

0:83dfd552e63729b472fcbcc8c45ebcc6691702558b68ec7527e1ba403a0f31a8

콜론 왼쪽은 워크체인(보통 0은 베이스체인, -1은 마스터체인), 오른쪽은 hex로 표기한 256비트 해시입니다. 정직하고 모호함이 없는 형식이지만 체크섬이 없습니다. 한 글자만 잘못 입력해도 겉보기에는 멀쩡한 다른 주소가 되어버립니다. 컨트랙트의 get 메서드와 저수준 도구들이 보통 기대하는 것이 이 raw 형식이며, 사람에게는 거의 노출되지 않습니다.

두 번째 표현은 user-friendly 형식입니다. 지갑과 익스플로러에서 보게 되는 그 48자짜리 base64url 문자열입니다.

EQCD39VS5jcptHL8vMjEXrzGaRcCVYto7HUn4bpAOg8xqB2N     (bounceable)
UQCD39VS5jcptHL8vMjEXrzGaRcCVYto7HUn4bpAOg8xqEBI     (non-bounceable)

여기에는 동일한 "워크체인 + 해시" 쌍에 더해 플래그 바이트와 끝부분의 CRC16 체크섬이 들어 있습니다. CRC는 오타를 잡아줍니다. 망가진 주소는 전송 전에 검증 단계에서 그냥 걸러집니다. 그리고 바로 이 플래그 바이트에서 EQUQ 접두사가 나옵니다.

먼저 확실히 짚고 넘어가야 할 핵심은 이것입니다.

  • rawuser-friendly는 같은 주소를 적는 두 가지 방식입니다.
  • EQUQ는 같은 user-friendly 주소의 두 변형이며, 정확히 플래그 하나만 다릅니다.

즉 한 지갑의 EQ…UQ…동일한 raw 해시, 동일한 컨트랙트, 동일한 잔액을 가리킵니다. 서로 다른 두 지갑도, 서로 다른 두 계정도 아닙니다. 차이는 발신자의 의도를 나타내는 비트 하나뿐입니다.

Bounceable(EQ) 대 non-bounceable(UQ): 플래그의 의미

user-friendly 주소 안의 그 플래그 바이트가 접두사를 결정합니다.

플래그 태그 접두사 (mainnet) 접두사 (testnet)
bounceable 0x11 EQ kQ
non-bounceable 0x51 UQ 0Q

테스트넷에서는 태그에 0x80이 더해집니다. 여기서 저 낯선 kQ0Q가 나오는 것입니다. 다만 우리에게 중요한 것은 태그 산술이 아니라 플래그의 의미입니다.

Bounceable(EQ)은 문자 그대로 이런 뜻입니다. "수신 측에서 뭔가 잘못되면 코인을 나에게 되돌려 달라." 이는 스마트 컨트랙트를 위한 안전장치입니다. 돈을 처리해야 할 컨트랙트에 자금을 보냈는데 그 컨트랙트가 에러로 실패했거나, 아직 배포되지 않았거나, 가스가 부족했다면 — 코인이 그냥 허공에 묶여 있기를 원하지는 않을 것입니다. bounce 메커니즘이 수수료를 제하고 발신자에게 돌려줍니다.

Non-bounceable(UQ)은 정반대입니다. "무슨 일이 있든 전달하고 그대로 두라." 반환은 없고 코인은 그냥 주소에 쌓입니다.

사람 사이의 일상적인 송금에서는 이 차이가 눈에 띄지 않습니다 — 딱 한 가지 경우를 제외하면 말입니다.

배포되지 않은 지갑으로 보낸 첫 입금이 "튕기는" 이유

바로 여기에 함정이 있습니다. TON에서 지갑 주소는 지갑 컨트랙트가 실제로 블록체인에 배포되기 전에 이미 존재합니다. 주소는 컨트랙트 코드와 초기 데이터(공개키)로부터 결정론적으로 계산된 해시이므로, 니모닉을 생성한 직후 완전히 오프라인 상태에서도 알 수 있습니다. 하지만 해당 주소에서 첫 트랜잭션이 일어나기 전까지 계정은 uninit(초기화되지 않음) 상태에 있습니다. 주소는 있지만 그 위에 컨트랙트 코드는 없습니다.

이제 bounceable 전송에서 어떤 일이 벌어지는지 보십시오.

  1. 이 uninit 주소로 EQ 형식(bounceable)의 송금을 보냅니다.
  2. 네트워크가 코인을 전달하고 수신 컨트랙트를 "호출"하려 시도합니다. 받을 주체가 없습니다 — 그 주소에 컨트랙트가 없기 때문입니다.
  3. bounce가 작동합니다. 전달에 실패했으므로 코인은 수수료를 제하고 발신자에게 되돌아갑니다.

결과가 바로 그 "튕김"입니다. 트랜잭션은 성공적으로 처리되었지만 새 지갑의 잔액은 0으로 남았고, 돈은 온 곳으로 되돌아갔습니다. 아무도 훔쳐 가지 않았고 네트워크는 설계대로 정확히 동작했습니다 — 다만 bounceable 형식을 쓰지 말아야 할 곳에 쓴 것뿐입니다.

같은 송금을 UQ 형식(non-bounceable)으로 보내면 어떻게 될까요?

  1. 네트워크가 uninit 주소로 코인 전달을 시도합니다.
  2. 플래그로 bounce가 꺼져 있으므로 되돌릴 필요가 없습니다.
  3. 컨트랙트가 아직 배포되지 않았는데도 코인은 주소에 그대로 남습니다.

지갑이 충전되었습니다. 나중에 이 지갑에서 첫 발신 트랜잭션을 보낼 때 컨트랙트 코드가 함께 배포되고, 계정은 active 상태가 됩니다. 그 시점부터는 bounceable을 포함한 어떤 송금이든 문제없이 받습니다. UQ 규칙이 결정적으로 중요한 것은 아직 배포되지 않은 지갑의 최초 충전 한 번뿐입니다.

좋은 소식은 생태계가 이 구멍을 사용자 대신 조금씩 메워주고 있다는 점입니다. v5r1 지갑과 여러 클라이언트는 기본적으로 주소를 non-bounceable(UQ) 형태로 보여줍니다. 초심자가 첫 입금을 잃지 않게 하려는 것입니다. 하지만 주소를 프로그램으로 다루기 시작하는 순간 — 백엔드에서, 스크립트에서, AI 에이전트에서 — 플래그에 대한 책임은 다시 여러분에게 돌아옵니다.

첫 송금을 제대로 보내는 법: EQ 대신 UQ

실무 규칙은 한 줄로 요약됩니다.

새(uninit) 지갑으로 보내는 첫 입금은 언제나 UQ로. 그다음부터는 아무래도 괜찮습니다.

사용자에게 자금을 보내는 서비스라면 다음 규칙을 그대로 적용하면 됩니다.

  • 수신자 주소가 uninit 상태 → UQ(non-bounceable)로 보냅니다.
  • 주소가 activeEQ(bounceable)로 보내도 됩니다.
  • 스마트 컨트랙트(DEX, Jetton 민터, 에스크로)로 보내는 경우 → EQ로 보내야 실패 시 돈이 돌아옵니다.

문제는 EQ와 UQ가 눈으로 보기에 접두사 한 글자만 다르고, uninit/active 상태는 주소만 봐서는 전혀 알 수 없다는 점입니다. 두 검사 모두 프로그램으로 해야 합니다. 그리고 여기서 프로젝트에 @ton/ton을 끌어들이고 프로바이더를 띄우고 셀을 파싱할 필요는 없습니다. 두 질문 모두 AI 에이전트가 알아서 호출하는 MCP 서버 도구 두 개로 해결됩니다.

지갑 생성과 최초 충전이라는 주제가 아직 낯설다면 TON 지갑 생성 가이드에 별도의 설명이 있습니다.

parse_address: 호출 한 번으로 오프라인에서 주소 변환하고 검증하기

parse_address는 TON용 hosted MCP 서버인 TONNode의 오프라인 도구입니다. 주소를 디코딩하고 형식을 다시 계산하고 CRC16을 검증하지만, 네트워크에는 전혀 접근하지 않습니다. 노드도 키도 필요 없습니다. 문자열을 다루는 순수한 수학 연산이라 즉시 끝나고 사용량 한도도 소모하지 않습니다.

이 도구가 할 수 있는 일은 다음과 같습니다.

  • EQ ⇄ UQ ⇄ raw를 어느 방향으로든 변환합니다.
  • 주소의 유효성을 검사합니다(체크섬이 맞는지 확인).
  • bounceable 플래그와 워크체인 번호를 보여줍니다.

MCP(Model Context Protocol)는 AI 에이전트(Claude, Cursor, ChatGPT/Codex 및 모든 MCP 클라이언트)가 도구를 호출하는 방식을 정한 표준입니다. MCP 서버는 클라이언트 설정에 항목 하나를 추가하면 연결됩니다. 읽기 도구 전체를 포함한 로컬 무료 버전은 이렇습니다.

{
  "mcpServers": {
    "ton": {
      "command": "npx",
      "args": ["-y", "@tonnode/mcp"]
    }
  }
}

그다음부터는 에이전트에게 평범한 프롬프트만 주면 됩니다.

EQCD39VS5jcptHL8vMjEXrzGaRcCVYto7HUn4bpAOg8xqB2N 주소를 non-bounceable(UQ) 형식과 raw 형식으로 보여줘. 체크섬이 유효한지도 확인해줘.

에이전트는 parse_address를 호출해 같은 지갑의 UQ 버전과 raw 해시, 그리고 유효성 상태를 돌려줍니다. 손으로 base64url을 주무를 일도, 48자 문자열에서 오타를 낼 위험도 없습니다.

get_account_state: 보내기 전에 지갑이 배포되었는지 확인하는 법

형식만으로 끝나지 않습니다. 주소가 active인지 uninit인지도 알아야 합니다. 이것은 온체인 질문이며, 여기에 답하는 것이 get_account_state입니다. 이 도구는 계정 상태와 플래그, 마지막 트랜잭션 정보를 돌려줍니다.

첫 송금 전에 적용할 논리는 이렇습니다.

  • get_account_stateuninit을 반환 → 주소가 아직 배포되지 않음 → UQ로 보냅니다.
  • active를 반환 → 지갑이 배포됨 → EQ로 안심하고 보내도 됩니다.

에이전트에게 줄 프롬프트입니다.

get_account_state로 UQCD39VS5jcptHL8vMjEXrzGaRcCVYto7HUn4bpAOg8xqEBI 주소가 배포되었는지 확인해줘. 상태가 uninit이면 첫 입금은 UQ 형식으로 보내야 한다고 알려줘.

전체 조합은 이렇게 됩니다. generate_wallet이 지갑(v3r2/v4/v5r1/highload_v3 버전)을 만들어 주소를 돌려주는데, 생성 시점에 이 주소는 반드시 uninit입니다. 네트워크가 아직 이 주소를 모르기 때문입니다. 따라서 첫 입금은 무조건 UQ로 보내야 합니다. 보내기 전에 get_account_state로 주소가 정말 uninit인지 확인하고, parse_address로 non-bounceable 표현을 제대로 골랐는지 보장합니다. 충전 후에는 get_balance를 호출해 코인이 튕겨 나가지 않고 주소에 잘 들어왔는지 확인하면 편리합니다.

생성과 관련해 중요한 점이 있습니다. generate_wallet은 니모닉과 키, 주소를 사용자에게 돌려줍니다. 서버는 이를 저장하지 않으며 여러분을 대신해 아무것도 서명하지 않습니다. 이것이 논커스터디얼(non-custodial) 방식이고, 스왑과 크로스체인, 지갑 관련 도구 전부에 동일하게 적용됩니다.

용어에 대한 짧은 보충입니다. GRAM은 이름이 바뀐 Toncoin이며(2026년 6월 변경), 네트워크 자체는 여전히 TON입니다.

체크리스트: TON에서 첫 입금을 잃지 않는 법

새 지갑으로 첫 송금을 할 때마다 적용할 짧은 절차입니다.

  1. 주소를 생성했다면(generate_wallet 또는 직접 쓰는 SDK) — 기본적으로 uninit이라고 간주하십시오.
  2. 상태를 확인하십시오get_account_stateuninit인지 active인지 봅니다.
  3. uninit이라면parse_address로 수신자 주소를 UQ로 변환하고 첫 입금은 오직 그 주소로만 보냅니다.
  4. active라면EQ도 괜찮습니다. 컨트랙트라면 오히려 그쪽이 낫습니다.
  5. 스마트 컨트랙트로 보낼 때는 항상 bounceable(EQ)로 보내야 실패 시 돈이 돌아옵니다.
  6. 충전 후에는 get_balance로 확인하십시오. 코인은 튕겨 나가지 않고 주소에 들어와 있어야 합니다.
  7. 기억하십시오. EQUQ는 같은 지갑(같은 raw 해시)입니다. 여러분이 고르는 것은 주소가 아니라 전달 실패 시의 동작입니다.

핵심 검사 두 가지, 즉 parse_address(오프라인)와 get_account_state(온체인)는 자체 인프라 없이도 바로 쓸 수 있습니다. 무료 Hobby 키(60 요청/분, 카드 불필요)를 받아서 두 도구를 AI 에이전트에서 곧바로 호출해보십시오: https://tonnode.io/dashboard?plan=hobby.

이후에는 같은 방식으로 인접한 과제들도 편하게 풀 수 있습니다. TON에서 수신 USDT 결제 감지하기, 호출 한 번으로 USDT 잔액 읽기, SDK 없이 컨트랙트 get 메서드 호출하기 같은 것들입니다. 주소 형식은 나머지 모든 것이 올라서는 토대입니다. EQ/UQ를 한 번 제대로 이해해두면 "튕겨 나간" 입금이 다시는 여러분을 당황하게 만들지 않을 것입니다.

에이전트에게 TON 접근 권한을 주세요

16가지 MCP 도구: 읽기, 논커스터디얼 스왑, 크로스체인, 지갑. 무료 요금제 60 요청/분, 카드 불필요.