전체 글 보기
6 분 소요

TON 트레이딩 에이전트 만들기: 잔액 조회, 견적, 논커스터디얼 스왑

MCP 기반 TON 트레이딩 에이전트: 에이전트가 잔액을 읽고 확정 견적을 받아 논커스터디얼 방식으로 스왑을 조립하는 방법 — get_balance, get_swap_quote, build_swap_tx.

TON 트레이딩 에이전트trading agent TONMCPTON 스왑논커스터디얼Omniston

모든 TON 트레이딩 에이전트가 처음 부딪히는 문제

AI 에이전트에게 "가격이 떨어지면 50 USDT를 GRAM으로 스왑해줘"라고 요청했는데, 에이전트가 50이 아니라 50,000 USDT짜리 트랜잭션을 보냅니다. TON의 USDT는 decimals = 6인데 (50 USDT = 50,000,000 raw 단위), 에이전트가 0이 아홉 개 붙는 일반 Jetton 기준으로 계산해 금액을 1000배 부풀렸기 때문입니다. 1000배의 오차, 진짜 돈, 되돌릴 수 없는 트랜잭션. 거울처럼 뒤집힌 실수도 뿌리는 같습니다. 에이전트가 잔액에서 원시 값 1000000000을 보고 "10억 USDT를 갖고 계십니다"라고 당당하게 보고하지만, 실제로는 1000 USDT일 뿐입니다.

지어낸 괴담이 아니라, 원시 RPC로 LLM을 TON DeFi에 직접 연결해 본 사람이라면 누구나 밟는 지뢰입니다. 뒤바뀐 decimals, Jetton 지갑 주소 없이는 Jetton 잔액을 읽을 수 없는 문제, 컨텍스트를 타고 새어 나가는 프라이빗 키. 아래에서는 잔액을 읽고, 확정 견적을 받고, 스왑을 조립하면서도 여러분의 키에는 단 한 번도 손대지 않는 TON 트레이딩 에이전트(trading agent TON)를 어떻게 만드는지 설명합니다. 도구는 TON용 hosted MCP 서버인 TONNode입니다.

TON 트레이딩 에이전트란 무엇이고 왜 MCP가 필요한가

트레이딩 에이전트란 사용자의 요청에 따라 네트워크 상태를 읽고 거래를 준비할 줄 아는 LLM(Claude, Cursor, Codex 또는 그 밖의 모든 MCP 클라이언트)입니다. "내 USDT 잔액 보여줘", "500 USDT면 GRAM을 얼마나 받지?", "스왑 만들어줘" 같은 요청 말입니다. 모델 자체는 블록체인을 볼 수 없습니다. 도구가 필요합니다.

바로 그 역할을 하는 것이 MCP(Model Context Protocol), 즉 에이전트가 외부 도구를 호출하는 방식을 규정한 표준입니다. 모델에게 원시 ADNL 요청을 지어내고 BOC 셀을 파싱하는 법을 가르치는 대신, 타입이 정해진 함수 묶음을 건네주는 것입니다. "잔액 줘", "견적 줘", "트랜잭션 조립해줘". TONNode는 이런 도구의 공급원으로 연결되어, 에이전트에게 TON 네트워크를 다루는 함수를 정확히 16개 제공합니다. 읽기, 스왑, 크로스체인, 지갑 생성입니다.

트레이딩 에이전트에게 필요한 것은 그중 다섯 개이고, 다섯 개 모두 무료 플랜에서도 쓸 수 있습니다:

읽기 (get_balance, get_jetton_balance)
   -> decimals 확인 (get_jetton_info)
      -> 견적 (get_swap_quote)
         -> 스왑 조립 (build_swap_tx)
            -> 지갑이 TonConnect로 서명

마지막 단계의 핵심은 이것입니다. 서명하는 주체는 서버가 아니라 사용자의 지갑입니다. TONNode는 서명되지 않은 메시지를 반환합니다. 이게 왜 원칙의 문제인지는 글 끝에서 다루겠습니다.

1단계: 에이전트가 잔액을 읽는다 (get_balance, get_jetton_balance)

무엇이든 스왑하기 전에 에이전트는 자기가 무엇을 가지고 있는지부터 파악해야 합니다. 도구는 두 개입니다.

  • get_balance — 주소의 GRAM 잔액입니다. GRAM은 2026년 6월에 이름이 바뀐 Toncoin이며, 네트워크 자체는 여전히 TON입니다.
  • get_jetton_balance — Jetton 잔액입니다. USDT, NOT, 그 밖의 어떤 토큰이든 상관없습니다. 여기서 묘미는 Jetton 지갑 주소가 온체인에서 계산된다는 점입니다. 소유자 주소와 Jetton 마스터 주소만 넘기면 TONNode가 알아서 Jetton 지갑 주소를 도출해 잔액을 읽습니다. 그 주소를 미리 알아둘 필요도, 어딘가에 저장해둘 필요도 없습니다.

에이전트에게 주는 프롬프트는 말 그대로 이렇게 생겼습니다:

지갑 UQAbc...xyz의 잔액을 확인해줘.
GRAM은 얼마나 있고 USDT는 얼마나 있어?

내부적으로 모델은 네이티브 잔액을 위해 get_balance를, USDT를 위해 get_jetton_balance를 호출합니다. 문제가 하나 있습니다. 돌아오는 값은 아직 "사람이 읽는" 금액이 아니라 raw 단위라는 것입니다. 진짜 중요한 대목은 여기서 시작됩니다. Jetton 지갑 주소로 씨름하지 않고 호출 한 번으로 USDT 잔액을 받는 방법은 따로 정리해두었습니다: /blog/usdt-balance-ton-one-call.

decimals가 전부다: get_jetton_info, 그리고 USDT가 6인 이유

TON의 잔액과 금액은 raw 단위로, 즉 소수부가 없는 정수로 저장됩니다. 사람이 읽을 수 있는 금액을 얻으려면 원시 값을 10^decimals로 나눠야 합니다. 함정은 여기 있습니다. Jetton마다 decimals가 다릅니다.

  • USDTdecimals = 6입니다. 즉 1 USDT = 1 000 000 raw 단위입니다.
  • TON의 대부분의 Jettondecimals = 9입니다(GRAM도 마찬가지). 즉 1 Jetton = 1 000 000 000 raw 단위입니다.

6과 9를 헷갈리면 금액이 정확히 1000배 틀어집니다. 글 첫머리에 나온 그 "10억 USDT"가 바로 이것입니다. 트레이딩 에이전트에게 이는 사소한 겉치레 문제가 아니라 신뢰의 뿌리입니다. 자릿수를 헷갈리는 에이전트에게 거래 조립을 맡길 수는 없습니다.

그래서 파이프라인에 get_jetton_info를 끼워 넣습니다. Jetton의 메타데이터, 즉 이름, 심볼, 발행량, 그리고 무엇보다 중요한 decimals를 돌려주는 도구입니다. 에이전트 내부의 올바른 로직은 이렇습니다:

raw       = get_jetton_balance(...)   // 예를 들어 1000000000
decimals  = get_jetton_info(...)      // USDT라면 → 6
human     = raw / 10 ** decimals      // 1000000000 / 1e6 = 1000 USDT

같은 raw 값이라도 decimals = 9라면 토큰 1개가 됩니다. 차이가 어마어마합니다. decimals를 프롬프트에 하드코딩하지 마시고, 모델이 기억에 의존해 "알아서 추측"하도록 두지도 마십시오. 처음 보는 Jetton에서 반드시 틀립니다. 매번 get_jetton_info에서 가져와 그 자리에서 다시 계산하게 하십시오. 이 함정이 왜 실제로 사람들의 돈을 앗아가는지는 여기서 자세히 다룹니다: /blog/jetton-decimals-ton.

2단계: Omniston을 통한 확정 견적 (get_swap_quote)

잔액을 읽고 올바른 단위로 환산했다면, 이제 에이전트에게 필요한 것은 가격입니다. DeFi에서 "머릿속 대충 시세"는 통하지 않습니다. 유동성은 여러 DEX에 흩어져 있고 환율은 계속 움직이므로, 에이전트는 추측이 아니라 실시간 견적에 기대야 합니다.

get_swap_quoteOmniston 프로토콜을 통해 GRAM ⇄ Jetton 스왑에 대한 확정 견적을 제공합니다. Omniston은 TON 최대 DEX 두 곳, STON.fiDeDust의 유동성을 한 번에 집계합니다. 에이전트가 직접 풀을 조회하고 가격을 비교하고 슬리피지를 계산할 필요가 없습니다. Omniston이 통합된 유동성에서 최적 경로를 돌려줍니다.

견적 줘. 지금 50 USDT로 GRAM을 얼마나 받을 수 있어?

모델은 금액 50000000(앞 단계에서 말한 바로 그 raw 단위)으로 get_swap_quote를 호출해 구체적인 숫자를 받습니다. 입력이 얼마인지, 출력이 얼마인지, 어떤 경로를 타는지, 슬리피지는 얼마인지. 여기가 의사결정 지점입니다. 에이전트에 조건이 걸려 있다면("환율이 X보다 좋을 때만 스왑해") 견적을 임계값과 비교해 계속 진행하거나 다음 반복을 기다립니다. 중요한 점은, 견적은 아직 거래가 아니라는 것입니다. 자금은 전혀 움직이지 않고, 아무것도 서명되지 않습니다. 순수한 시장 읽기입니다.

3단계: 서명되지 않은 스왑 조립 (build_swap_tx)과 지갑에서의 서명

사용자가 견적을 보고 "좋아, 스왑하자"라고 합니다. 에이전트는 build_swap_tx를 호출해 TonConnect에 바로 넘길 수 있는 서명되지 않은 스왑 트랜잭션을 받습니다.

"서명되지 않은"이라는 말을 강조하고 싶습니다. 서버는 올바른 메시지, 즉 수신자 주소, payload, 금액, 경로 파라미터를 조립해 그대로 반환합니다. 그다음 메시지는 사용자의 지갑(Tonkeeper, MyTonWallet 등 TonConnect 호환 지갑이면 무엇이든)으로 넘어가고, 사용자는 자기가 정확히 무엇에 서명하는지 확인한 뒤 직접 승인합니다. 서명을 찍는 것은 서버가 아니라 사용자의 지갑 안에 살고 있는 사용자의 프라이빗 키입니다.

get_balance / get_jetton_balance   →  가진 것을 읽는다
        ↓
get_jetton_info                     →  decimals 확인, 재계산
        ↓
get_swap_quote (Omniston)           →  확정 견적
        ↓
build_swap_tx                       →  서명되지 않은 트랜잭션
        ↓
사용자 지갑 (TonConnect)            →  서명과 전송

각 단계는 명시적인 개별 도구 호출입니다. 에이전트가 돈을 두고 "알아서 마무리"하는 일은 없습니다. 준비는 에이전트가 하되, 결정과 서명은 사람의 몫으로 남습니다. 견적부터 서명까지 논커스터디얼 스왑의 전체 시나리오는 여기서 단계별로 정리했습니다: /blog/agent-swap-ton-noncustodial.

논커스터디얼: 서버가 에이전트의 키를 절대 보관하지 않는 이유

이건 마케팅 문구가 아니라 아키텍처의 경계선입니다. TONNode의 스왑, 크로스체인, 지갑 생성 도구는 엄격하게 논커스터디얼입니다.

  • 서버는 트랜잭션에 절대 서명하지 않습니다.
  • 서버는 프라이빗 키와 자금을 절대 보관하지 않습니다.
  • 서버가 밖으로 내보내는 것은 오직 서명되지 않은 TonConnect 메시지뿐입니다.

이게 왜 하필 트레이딩 에이전트에게 중요할까요? 에이전트는 정의상 돈을 다루고, 정의상 실수할 수 있기 때문입니다. 요청을 잘못 이해하고, 금액을 헷갈리고, 루프에 빠집니다. 키가 서버에 놓여 있고 서버가 직접 서명한다면, 에이전트의 실수는 곧 여러분이 모르는 사이의 자금 손실을 뜻합니다. 논커스터디얼 구조에서 마지막 방어선은 여러분입니다. 여러분의 지갑이 승인하기 전까지는 어떤 트랜잭션도 나가지 않습니다.

TON Foundation의 공식 @ton/mcp와 비교해 보십시오. 이 서버는 커스터디얼 에이전트 지갑입니다. operator 키를 보관하고 직접 서명합니다 (operator 키는 에이전트가, owner 키는 사용자가 갖는 split-key 방식). 나름의 강점이 있습니다. 사람이 개입하지 않는 자율 지출, NFT와 DNS 지원, Foundation 공식이라는 지위. 하지만 신뢰 모델이 다릅니다. 이 모델에서는 에이전트가 실제로 자금을 움직일 수 있습니다. TONNode는 의도적으로 반대편 경계를 택했습니다. 서버는 그 무엇에도 서명하지 않습니다. 두 접근의 상세 비교는 여기에 있습니다: /blog/custodial-vs-noncustodial-mcp.

연결 방법과 5분 만에 시작하기

좋은 소식이 있습니다. 트레이딩 파이프라인을 만드는 데 돈을 낼 필요는 없습니다. 다만 두 가지 무료 경로를 혼동하면 안 됩니다. 쓸 수 있는 도구 세트가 다릅니다.

로컬 퍼블릭 설정읽기 도구 전체(8개: get_masterchain_info, get_balance, get_account_state, get_transactions, run_get_method, get_jetton_balance, parse_address, get_jetton_info)를 제공합니다. npx로 설치하며 키가 필요 없습니다:

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

@tonnode/mcp 패키지는 오픈소스(MIT)이며 npm과 GitHub(tonnode/mcp)에 올라와 있고, 에이전트와 네트워크 사이에 HTTP 레이어를 끼우지 않고 TON 네이티브 ADNL 프로토콜로 동작합니다. 이 설정을 Claude Desktop, Cursor 또는 아무 MCP 클라이언트에 붙여 넣으면 에이전트는 곧바로 잔액을 읽고 get_jetton_info를 호출할 수 있습니다.

반면 견적 자체, 즉 get_swap_quotebuild_swap_tx스왑 그룹에 속하며 hosted 엔드포인트를 통해 동작합니다. 핵심은 이것입니다. 무료 Hobby 키에서도 견적과 스왑 조립을 포함해 16개 도구 전부를 쓸 수 있습니다. 즉 트레이딩 파이프라인 전체(읽기 → 견적 → 스왑)를 무료로 구성할 수 있습니다. 단, 로컬 퍼블릭 설정이 아니라 hosted Hobby 키를 통해서입니다. hosted 엔드포인트 설정은 이렇습니다:

{
  "mcpServers": {
    "ton": {
      "type": "http",
      "url": "https://mcp.tonnode.io/mcp",
      "headers": { "Authorization": "Bearer tn_live_…" }
    }
  }
}

TONNode의 도구는 정확히 16개이고, 모든 플랜에서 16개 전부를 쓸 수 있습니다. 기능이 아니라 처리량에 대해서만 비용을 냅니다:

  • Hobby — 영구 무료, 60 요청/분, 카드 불필요.
  • Pro — 월 $29, 300 요청/분.
  • Scale — 월 $199, 1200 요청/분.

트레이딩 파이프라인을 시작하고 굴려보는 데는 무료 Hobby 키로 충분하고도 남습니다. 분당 60 요청이면 에이전트의 연속 호출로도 넉넉한 양입니다. 카드는 필요 없고, 로그인하면 키가 바로 발급됩니다.

무료 Hobby 키 받기 (60 req/min, 카드 불필요): tonnode.io/dashboard?plan=hobby

16개 도구 전체 목록: tonnode.io/mcp · 요금제: tonnode.io/pricing


다섯 개 도구 — get_balance, get_jetton_balance, get_jetton_info, get_swap_quote, build_swap_tx — 로 파이프라인을 구성하고, decimals를 통제하고, 서명은 지갑에 맡기십시오. 그러면 잔액을 정직하게 읽고, 확정 견적을 받고, 실제 금액으로 스왑을 준비하면서도 남의 키에는 단 한 순간도 접근하지 않는 트레이딩 에이전트가 완성됩니다. TON 트레이딩 에이전트는 바로 그렇게 동작해야 합니다.

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

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