TON 지갑 생성: generate_wallet 한 번으로 v4, v5, highload까지
generate_wallet 호출 한 번으로 TON 지갑을 프로그래밍 방식으로 만드는 법. v3r2, v4, v5r1, highload 버전과 니모닉·키·주소, 그리고 첫 입금을 안전하게 받는 방법까지.
프로모션 코드 500개를 발행했는데 지급 건마다 별도의 TON 지갑이 필요합니다. 또는 즉석에서 "핫" 지갑을 만들어 입금을 받고 뭔가를 처리하는 AI 에이전트를 개발하는 중일 수도 있습니다. 인터넷에서 제일 먼저 나오는 것은 산더미 같은 SDK 예제입니다. @ton/ton을 붙이고, WalletContractV4와 WalletContractV5R1이 뭐가 다른지 파악하고, mnemonicNew()를 직접 호출하고, 키를 유도하고, 주소를 계산하고, highload의 subwallet_id도 잊지 말아야 합니다. 정작 여러분의 과제와는 아무 상관 없는 인프라 코드에 반나절이 날아갑니다. 게다가 지갑을 만드는 주체가 AI 에이전트라면 에이전트가 스스로 해낼 수 있어야 합니다. 여러분이 암호 로직을 직접 심어 넣지 않고도 말입니다.
아래에서는 generate_wallet 도구 호출 한 번으로 TON 지갑을 프로그래밍 방식으로 생성(generate wallet)하는 법, v3r2와 v4, v5r1, highload_v3 버전의 차이, 그리고 bounceable 주소에 얽힌 전형적인 실수로 첫 입금을 날리지 않는 법을 다룹니다.
TON 지갑을 프로그래밍 방식으로 만드는 이유(그리고 SDK를 쓰지 않는 이유)
SDK로 직접 만드는 방식은 첫 프로덕션 전까지는 아무 문제가 없습니다. 규모가 커지면 문제가 드러납니다.
- 버전. TON에는 "지갑"이 하나만 있는 것이 아니라 여러 개의 컨트랙트가 있습니다. v3r2, v4, v5r1, highload가 각각 다른 로직을 갖고, 같은 키를 쓰더라도 주소가 서로 다릅니다. 버전을 잘못 고르면 엉뚱한 주소를 받게 됩니다.
- 주소 형식. 같은 지갑이 EQ와 UQ 두 가지 형태를 가집니다. 입금용 주소를 알려줄 때 이 둘을 헷갈리면 돈이 그대로 "튕겨" 돌아갑니다(자세한 내용은 아래에서).
- AI 에이전트. 에이전트(Claude, Cursor, ChatGPT/Codex)에게는 "라이브러리를 쥐여줄" 수 없습니다. 설명만 보고 호출할 수 있는 도구가 필요합니다. MCP(Model Context Protocol)를 쓰면 에이전트가
generate_wallet을 평범한 함수처럼 호출해 구조화된 응답을 그대로 받습니다. 버전과 키, 주소 형식을 다루는 로직은 MCP 서버 쪽에 있습니다. - 폴리글랏 스택. Go와 Python, Node에 TON SDK를 각각 유지하고 싶지 않다면, MCP가 언어를 가리지 않는 단일 인터페이스를 제공합니다.
generate_wallet은 TON용 hosted MCP 서버인 TONNode의 16개 도구 중 하나입니다. 하는 일은 딱 하나, 새 지갑을 만들어 그 지갑을 소유하는 데 필요한 모든 것을 여러분에게 넘겨주는 것입니다. 이 접근 방식이 처음이라면 TON MCP 가이드부터 보시기 바랍니다.
generate_wallet 한 번 호출로 정확히 무엇이 돌아오는가
generate_wallet은 새 TON 지갑을 만들고, 그 지갑을 소유하고 복구하는 데 필요한 것을 한 세트로 반환합니다.
- 니모닉(시드 문구) — 사람이 읽을 수 있는 단어 묶음으로, 나머지 전부가 여기서 결정론적으로 유도됩니다.
- 프라이빗 키 — 트랜잭션 서명용입니다.
- 퍼블릭 키
- 지갑 주소 — TON 표준 형식으로 제공됩니다.
에이전트에게 주는 프롬프트는 말 그대로 이렇습니다.
generate_wallet으로 v5r1 버전의 새 TON 지갑을 만들고
주소와 니모닉을 보여주세요.
에이전트는 버전 파라미터와 함께 도구를 호출하고 대략 이런 구조를 돌려줍니다(값은 예시이며, TON의 Ed25519 키는 Ethereum의 0x 접두사가 없는 평범한 hex입니다).
{
"version": "v5r1",
"mnemonic": ["word1", "word2", "...", "word24"],
"public_key": "e3f1a2…",
"private_key": "9b7c4d…",
"address": {
"bounceable": "EQ…",
"non_bounceable": "UQ…",
"raw": "0:abcd…"
}
}
SDK도, 손으로 하는 키 유도 작업도 없습니다. 이걸 어디에 보관할지는 이제 여러분이 정하면 됩니다. 핵심은 이것입니다. 서버는 이 니모닉과 키를 자기 쪽에 남기지 않습니다. 논커스터디얼에 관해서는 아래 섹션에서 다룹니다.
지갑 버전 v3r2, v4, v5r1, highload_v3 중 무엇을 고를까
generate_wallet은 네 가지 지갑 컨트랙트 버전을 지원합니다. "최신이 곧 최선"이라는 뜻이 아닙니다. 버전마다 자기 쓰임새가 따로 있습니다.
v5r1 (W5) — 신규 프로젝트의 기본값
가장 최신 버전입니다. 확장(extension)과 gasless 시나리오를 지원합니다. 사용자를 대신해 제3자 릴레이어가 수수료를 낼 수 있다는 뜻입니다. 이건 v5 컨트랙트 자체의 성질이지 TONNode가 제공하는 서비스가 아닙니다. 서버는 그런 지갑을 만들어 줄 뿐, gasless 릴레이어를 제공하지는 않습니다. 현대적인 에이전트를 처음부터 새로 만드는 중이고 굳이 highload가 필요한 경우가 아니라면 v5r1을 고르십시오.
v4 — 플러그인을 지원하는 버전
직전 세대의 주류 버전입니다. 플러그인을 지원하므로 지갑에 확장 컨트랙트를 붙일 수 있습니다(예를 들어 구독이나 예약 결제용으로). 오랫동안 사실상의 표준이었고 지갑과 서비스 전반에서 폭넓게 지원됩니다. v4의 플러그인 모델에 의존하는 부분이 있다면 v4를 쓰십시오.
v3r2 — 단순한 기본 지갑
확장도 플러그인도 없는 최소 컨트랙트입니다. 동작이 예측 가능하고 가스가 저렴합니다. 로직이라고는 "받고 보내는" 것이 전부인 운영용 주소에 어울립니다.
highload_v3 — 대량 지급용
별도의 부류입니다. 높은 처리량을 위한 지갑으로, 외부 메시지 하나가 다수의 전송을 실어 나를 수 있습니다. 배치 지급, 에어드롭, 리워드 배포, 결제 게이트웨이, 거래소 출금에 쓰는 것이 바로 이 지갑입니다. 그 효율의 대가로 메시지를 특수한 방식(query_id / expiration)으로 관리하기 때문에 조심해서 다뤄야 합니다. 파라미터 보관에 관한 섹션을 참고하십시오.
| 버전 | 언제 고를까 |
|---|---|
v5r1 |
신규 프로젝트, 확장, gasless |
v4 |
플러그인(구독 등)이 필요할 때, 최대 호환성 |
v3r2 |
군더더기 없는 단순 운영 지갑 |
highload_v3 |
대량/배치 지급, 높은 throughput |
지급용 프롬프트 예시입니다.
배치 지급용 highload_v3 지갑을 생성하고
니모닉과 키, 주소를 반환하세요.
첫 입금: 왜 non-bounceable UQ 주소로 보내야 하는가
지갑을 만든 뒤 가장 흔하고 가장 억울한 실수가 첫 입금을 날리는 것입니다. 원리는 이렇습니다. 갓 만들어진 지갑은 아직 블록체인에 배포되지 않은 상태입니다. 컨트랙트 코드를 배포시키는 첫 전송이 도착하기 전까지는 그 주소에 컨트랙트가 물리적으로 존재하지 않습니다.
TON의 주소에는 "bounceable" 플래그가 있습니다.
- EQ (bounceable) — 주소에 살아 있는 컨트랙트가 없으면 네트워크가 전송을 발신자에게 되튕겨(bounce) 돌려보냅니다. 새로 만든 지갑이 정확히 이 경우에 해당합니다.
- UQ (non-bounceable) — 컨트랙트가 아직 배포되지 않았더라도 전송이 주소에 "달라붙습니다". 첫 입금에 필요한 것이 바로 이것입니다.
규칙은 단순합니다. 새 지갑을 채우는 첫 전송은 UQ 주소로 보내십시오. EQ로 보내면 되돌아오고, 여러분은 왜 잔액이 비어 있는지 한참 헤매게 됩니다. 지갑이 첫 발신 트랜잭션을 보내 배포되고 나면 그때부터는 bounceable 형태를 마음 놓고 써도 됩니다.
UQ 형태를 확실하게 얻는 방법은 무엇일까요? 오프라인 도구 parse_address를 쓰면 됩니다. 네트워크에 접속하지 않고 EQ/UQ/raw 형식을 변환하고 검증합니다.
parse_address로 이 주소를 첫 입금용
non-bounceable UQ 형태로 변환하세요
입금 후에는 get_account_state로 상태를 확인하십시오. 계정 상태(uninitialized / active)와 플래그, 마지막 트랜잭션을 보여줍니다.
UQ…에 대해 get_account_state를 확인해 주세요 — 지갑이
배포됐는지, 잔액은 얼마인지
컨트랙트가 활성화되기 전까지는 입금이 아직 지갑을 "일으켜 세우지" 못했다는 사실을 상태값이 알려줍니다. 형식에 대한 자세한 분석은 EQ/UQ/raw 주소 형식 글에 정리해 두었습니다.
highload 지갑: subwallet_id와 timeout은 직접 기록해 두십시오
highload_v3을 고른 분들을 위한 별도의 경고입니다. 여기서는 니모닉만으로는 작동하는 지갑을 제대로 복원하고 그 동작을 그대로 재현하기에 부족합니다. highload 컨트랙트에는 키 말고도 생성 시점에 정해져 initial data(state init)에 포함되는 파라미터가 두 개 더 있습니다.
subwallet_id— 서브지갑 식별자입니다(하나의 시드 문구에서 서로 다른 주소를 여러 개 만들 수 있게 해 줍니다).timeout— 외부 메시지의 유효 기간이며,query_id와 만료 로직이 이 값 위에 세워져 있습니다.
중요한 점이 있습니다. generate_wallet이 돌려주는 것은 니모닉과 키, 주소입니다. 반면 subwallet_id와 timeout은 highload 지갑을 구성하는 파라미터로서 여러분이 직접 고르고 직접 고정하는 값입니다. 지갑이 어떤 값으로 생성됐는지 기록해 두는 책임은 다름 아닌 여러분에게 있습니다.
highload 지갑은 timeout을 기준으로 어떤 메시지가 아직 "살아 있는지"를 추적합니다. subwallet_id와 timeout 없이 시드 문구만으로 지갑을 복원하면 이렇게 됩니다.
- 엉뚱한 주소를 얻게 됩니다.
subwallet_id와timeout은 둘 다 state init에 들어가므로, 둘 중 어느 하나만 바뀌어도 컨트랙트의 initial data가 달라지고 따라서 주소도 달라집니다. - 메시지 만료와 중복 제거 로직을 올바르게 재현하지 못하고, 발신 전송 집계가 깨질 위험을 떠안게 됩니다.
실무 규칙. highload_v3이라면 subwallet_id와 timeout을 니모닉 바로 옆에, 지갑 하나에 대한 단일 레코드의 일부로 저장하십시오. 선택적 메타데이터가 아니라 지갑 정체성의 일부입니다.
# 시크릿 저장 예시
mnemonic: "word1 word2 … word24"
version: "highload_v3"
subwallet_id: <생성할 때 여러분이 지정한 값>
timeout: <생성할 때 여러분이 지정한 값>
v3r2/v4/v5r1에는 이런 요구사항이 없습니다. 니모닉과 버전만 알면 충분합니다.
논커스터디얼: 서버는 키를 보관하지도, 서명하지도 않습니다
지갑이 "서버 어딘가에서" 만들어질 때 가장 중요한 질문은 이것입니다. 결국 키는 누가 소유하는가? 이 질문에 대한 답은 명확합니다.
generate_wallet은 철저히 논커스터디얼입니다.
- 서버는 프라이빗 키와 니모닉, 자금을 절대 보관하지 않습니다.
- 서버는 여러분을 대신해 트랜잭션에 절대 서명하지 않습니다.
- 생성된 지갑, 즉 니모닉과 키와 주소는 응답으로 전부 여러분에게 넘어가며 서버 쪽에는 저장되지 않습니다.
남의 시드 문구가 담긴 데이터베이스도 없고, 여러분 대신 무언가에 서명하는 operator 키도 없습니다. 본질적으로 generate_wallet은 결정론적 생성을 감싼 편리한 래퍼입니다. 암호 연산이 일어나고, 결과는 여러분에게 전달되고, 서버에는 아무것도 남지 않습니다. 서비스가 키(또는 그 일부)를 쥔 채 직접 트랜잭션에 서명하는 커스터디얼 에이전트 지갑과는 근본적으로 다른 지점입니다. 그 차이는 따로 다뤘습니다. 커스터디얼 MCP와 논커스터디얼 MCP를 보십시오.
실무적 결론입니다. generate_wallet의 응답은 곧바로 여러분의 안전한 저장소에 보관하십시오. 같은 지갑을 서버가 두 번째로 "꺼내주는" 일은 없습니다. 서버는 그것을 기억하지 않으며 "고객지원을 통한" 복구도 불가능합니다. 이건 버그가 아니라 기능입니다.
연결하고 첫 지갑을 만드는 법
연결은 1분이면 끝납니다. npx로 @tonnode/mcp 패키지를 로컬에서 무료로 띄우면 되고, 읽기 도구 전체는 키 없이도 쓸 수 있습니다.
MCP 클라이언트 설정입니다(Claude Desktop, Cursor, 그 밖의 모든 MCP 클라이언트).
{
"mcpServers": {
"ton": {
"command": "npx",
"args": ["-y", "@tonnode/mcp"]
}
}
}
@tonnode/mcp 패키지는 오픈소스(MIT)로 npm과 GitHub(tonnode/mcp)에 올라와 있고, 여러분의 에이전트와 네트워크 사이에 HTTP 레이어를 끼우지 않은 채 TON 네이티브 ADNL 프로토콜로 동작합니다. 클라이언트를 재시작하면 generate_wallet, parse_address, get_account_state를 포함한 16개 도구 전부를 에이전트가 쓸 수 있게 됩니다. 특정 클라이언트에 붙이는 방법은 Claude와 Cursor를 TON에 연결하기 안내에 있습니다.
generate_wallet은 16개 도구 세트에 포함되어 있고 무료 Hobby를 비롯한 모든 요금제에서 쓸 수 있습니다. Hobby는 분당 60요청이며 카드가 필요 없습니다. 무료 키는 로그인 직후 바로 발급됩니다. 여러분이 지불하는 것은 도구 이용권이 아니라 처리량뿐입니다.
다음은 "만들고 안전하게 채우기" 시나리오 전체를 담당하는 세 가지 도구입니다.
generate_wallet— 원하는 버전의 지갑을 만들고 니모닉과 키, 주소를 받습니다.parse_address— 첫 입금용 UQ 형태 주소를 얻습니다(오프라인).get_account_state— 입금으로 컨트랙트가 배포됐는지 확인합니다.
에이전트에게 줄 전체 프롬프트 예시입니다.
generate_wallet으로 v5r1 지갑을 만드세요. 그다음
parse_address로 첫 입금에 쓸 UQ 주소를 알려주세요.
제가 코인을 보내고 나면 get_account_state로
지갑이 배포됐는지 확인해 주세요.
hosted 키가 필요한 경우
로컬 npx 실행은 공개 설정을 사용하며 개발 용도로는 더할 나위 없습니다. 부하가 걸린 상황에서 보장된 처리량이 필요하다면(앞서 말한 highload 대량 지급 같은 경우) 자신의 키로 hosted 엔드포인트를 연결하십시오.
{
"mcpServers": {
"ton": {
"type": "http",
"url": "https://mcp.tonnode.io/mcp",
"headers": { "Authorization": "Bearer tn_live_…" }
}
}
}
정리
generate_wallet은 호출 한 번으로 TON 지갑을 만들고 니모닉과 프라이빗/퍼블릭 키, 주소를 반환합니다. SDK를 손으로 조립하는 것이 아니라 버전만 골라 한 번 호출하면 끝입니다.- 신규 프로젝트의 기본값은 v5r1, 대량 지급은 highload_v3, 플러그인이 필요하면 v4, 단순한 지갑이면 v3r2입니다.
- 첫 입금은 non-bounceable UQ 주소로 보내십시오. 아니면 되튕겨 나갑니다. UQ 형태는
parse_address가 주고, 상태는get_account_state가 확인합니다. - highload라면
subwallet_id와timeout두 값을 직접 고정해 니모닉과 함께 보관하십시오. 둘 다 state init에 들어가며 주소에 영향을 줍니다. - 서버는 논커스터디얼입니다. 키도 자금도 보관하지 않고 서명하지도 않습니다. 키는 여러분의 것입니다.
무료 Hobby 키를 받아 generate_wallet으로 첫 지갑을 몇 분 만에 만들어 보십시오 — tonnode.io/dashboard?plan=hobby.