Balance USDT en TON — cómo leerlo con una sola llamada
Cómo obtener el balance USDT de una wallet TON con una llamada a get_jetton_balance — la jetton wallet se calcula on-chain, sin indexador propio.
La wallet del agente está abierta, get_balance devolvió honestamente unos pocos GRAM — y de USDT, cero. Aunque sabes con certeza que a esa dirección llegaron stablecoins. ¿Te suena? La wallet parece vacía cuando en realidad tiene 500 USDT. No es un bug ni dinero perdido — simplemente le preguntaste al contrato equivocado. Y justo ahí es donde fallan la mayoría de las primeras integraciones de un agente de IA con TON: el agente lee la dirección que no es.
Por qué el balance de USDT no es el balance de la wallet TON
En Ethereum estás acostumbrado a que un token ERC-20 «vive en la dirección»: el contrato del token guarda una tabla address → balance, y para conocer el balance consultas ese único contrato. En TON el modelo es otro, y esa es la primera fuente de confusión.
La moneda nativa GRAM (el antiguo Toncoin; la red se sigue llamando TON) sí vive directamente en el smart contract de tu wallet — get_balance la lee con una sola consulta al estado de la cuenta. Pero USDT es un jetton (el estándar de TON para tokens fungibles). Y el balance de un jetton no se guarda en tu wallet principal, sino en un pequeño contrato aparte: la jetton wallet.
Una analogía: tu wallet TON principal eres tú como persona. Y la jetton wallet de USDT es una cuenta a tu nombre abierta por un banco concreto (el contrato maestro de USDT). Preguntarle a la persona «cuántos dólares tengo» no tiene sentido — el dinero está en la cuenta, no en el bolsillo. Cada par «propietario + jetton» tiene su propia jetton wallet: una dirección para USDT, otra para NOT, una tercera para cualquier otro jetton.
La buena noticia: la dirección de esa jetton wallet no es aleatoria. Se deriva de forma determinista a partir de dos cosas:
- la dirección del propietario (tu wallet TON normal,
EQ…/UQ…); - la dirección del contrato maestro del jetton (el jetton master — para USDT es un contrato fijo).
La mala noticia: para calcular esa dirección de verdad hay que ir al contrato maestro, invocar su método get y después leer el estado de la jetton wallet. A mano son varios pasos, y es exactamente ahí donde tropiezan las integraciones.
get_jetton_balance: una llamada en lugar de un indexador
Normalmente este problema se resuelve de una de dos maneras, y ambas son incómodas:
- Indexador propio. Levantar un nodo, indexar las transferencias de jettons en una base de datos, mantenerla al día. Caro y frágil para obtener un solo número, y los datos siempre van un poco por detrás de la cadena.
- API HTTP pública. Choca rápido con los límites: sin clave es del orden de una petición por segundo, y al superarlos recibes un honesto
HTTP 429 Too Many Requests. Además dependes de la indexación de terceros y de su profundidad histórica.
La herramienta get_jetton_balance de TONNode elimina ambos problemas. Le pasas:
- la dirección del propietario — la wallet TON normal del usuario (
EQ…/UQ…); - el identificador del jetton — por ejemplo, USDT.
A partir de ahí el servidor hace todo el trabajo on-chain:
- invoca el método get del contrato maestro del jetton, que a partir de la dirección del propietario devuelve la dirección de su jetton wallet (esa derivación determinista);
- lee el balance de esa jetton wallet y te lo devuelve.
Sin indexadores de terceros, sin bases de datos, sin desincronización — solo lectura directa del estado de la red a través de los métodos get de los contratos.
TONNode es un servidor MCP hosted para TON. MCP (Model Context Protocol) es el estándar con el que los agentes de IA (Claude, Cursor, ChatGPT/Codex y cualquier cliente MCP) invocan herramientas externas. Es decir, get_jetton_balance no es una línea en tu código, sino una herramienta que el agente ejecuta por sí mismo cuando el usuario pregunta por un balance. Por debajo, el paquete @tonnode/mcp (open source, MIT) habla el protocolo nativo ADNL de TON, sin capas HTTP intermedias — el agente se comunica con la red directamente, no a través de otra pasarela REST más.
Ejemplo: el prompt al agente y qué devuelve
La conexión es local, sin clave y sin tarjeta. Añade esto a la config de tu cliente MCP (Claude Desktop, Cursor, cualquier cliente compatible con MCP):
{
"mcpServers": {
"ton": {
"command": "npx",
"args": ["-y", "@tonnode/mcp"]
}
}
}
Después basta con un prompt normal en lenguaje natural:
¿Cuánto USDT hay en la wallet
UQAbc…xyz? Devuélveme el valor legible para humanos.
El agente elegirá por sí solo la herramienta get_jetton_balance, le pasará la dirección del propietario y el jetton USDT, y el servidor devolverá el balance — pero no en los «dólares» de siempre, sino en unidades raw (las unidades mínimas indivisibles del jetton). Junto con el balance, la respuesta incluye la dirección de la jetton wallet que el servidor calculó on-chain — útil si después quieres seguir las transferencias de ese contrato en concreto. El balance en nuestro ejemplo es un valor del tipo 12500000.
Pero 12500000 no son 12.5 millones de USDT. Aquí empieza la segunda trampa, en la que es muy fácil equivocarse.
Unidades raw y decimals: por qué USDT tiene 6 y no 9
Las blockchains no trabajan con fracciones. Todas las cantidades se guardan como enteros en unidades raw, y el valor «humano» se obtiene dividiendo entre 10^decimals, donde decimals es una propiedad de cada jetton. Es como centavos y dólares: a bajo nivel todo va en centavos, y decimals te dice dónde poner el punto decimal.
El matiz clave de TON: cada jetton tiene su propio número de decimals.
- El GRAM nativo y la mayoría de los jettons de TON tienen
decimals = 9. - Pero USDT en TON tiene
decimals = 6.
Por eso el mismo valor raw significa cantidades completamente distintas. Recalculemos nuestro ejemplo como corresponde:
raw = 12500000
decimals = 6 // precisamente para USDT
human = 12500000 / 10^6 = 12500000 / 1_000_000 = 12.5 USDT
12.5 USDT, no 12.5 millones. Si por inercia hubieras dividido entre 10^9 (como con un jetton corriente), habrías obtenido 0.0125 — mil veces menos de lo real. El mismo número, tres órdenes de magnitud de diferencia. Exactamente así se pierde dinero en las integraciones ingenuas: un divisor 10^9 hardcodeado para todo por igual. Por eso nunca hardcodees los decimals — tómalos de los metadatos del propio jetton.
get_jetton_info: de dónde sacar los decimals y los metadatos del jetton
Para no adivinar, existe la herramienta gemela: get_jetton_info. Lee el contrato maestro del jetton y devuelve sus metadatos:
- nombre (name);
- símbolo (symbol);
- decimals — el número con el que se construye el divisor;
- emisión (total supply).
El flujo fiable para un agente son dos llamadas: si el jetton es desconocido, primero get_jetton_info para conocer los decimals, después get_jetton_balance para obtener el balance raw, y solo entonces la división raw / 10^decimals para mostrarlo.
1) get_jetton_info(USDT) -> decimals = 6
2) get_jetton_balance(owner, USDT) -> raw = 12500000
3) human = 12500000 / 10^6 -> 12.5 USDT
El prompt se puede formular de modo que el agente encadene esos pasos por sí mismo:
Obtén los decimals de USDT con get_jetton_info, luego el balance de la wallet
UQAbc…xyzcon get_jetton_balance y convierte el raw a un número legible para humanos.
Ese orden es obligatorio si trabajas con jettons arbitrarios y no solo con USDT: con un token desconocido no sabes de antemano si tiene 6 decimals o 9. Para USDT, decimals = 6 es una constante, pero el hábito de sacarla de get_jetton_info te salvará en cuanto aparezca el primer jetton no estándar. Este mismo truco está en la base de la detección de pagos entrantes de USDT en TON — ahí los decimals son críticos para no confundir 1 USDT con polvo microscópico.
parse_address: validar la dirección offline antes de la consulta
Otra causa frecuente del balance «en cero» es una dirección de propietario mal escrita. Los usuarios mandan direcciones en formatos distintos: EQ… (bounceable), UQ… (non-bounceable), raw (0:…). Antes de pedir el balance conviene normalizar la dirección con parse_address — funciona offline (sin tocar la red): acepta la dirección en cualquiera de los formatos, la convierte a los tres (EQ/UQ/raw) y, de entrada, te dice si es válida.
Es barato, instantáneo y elimina toda una clase de errores del tipo «balance 0 porque la dirección no era la correcta» — sobre todo si la dirección viene de una fuente poco fiable, de la entrada del usuario o de un chat.
Cómo conectarlo: gratis en local o hosted
Tres herramientas — parse_address, get_jetton_info, get_jetton_balance — dan una respuesta completa y honesta a la pregunta «cuánto USDT hay en la wallet» sin un solo indexador de terceros.
Gratis en local
El paquete @tonnode/mcp es open source (MIT) y está en npm y GitHub. La misma config pública del ejemplo de arriba (npx -y @tonnode/mcp) te da el conjunto completo de herramientas de lectura, incluidas get_jetton_balance, get_jetton_info y parse_address — no hace falta una clave aparte.
Reinicias el cliente — y el agente ya lee balances de jettons por ADNL. Por qué un agente necesita su propio servidor MCP y no una pasarela pública con límites lo desglosamos en la nota sobre MCP para agentes de IA en TON. Y dónde termina la config pública gratuita y por qué — en el análisis de los límites de los liteservers públicos de TON.
Hosted — cuando necesitas más capacidad
Cuando el rate local se queda corto (bots, backends, carga de producción), pasas al endpoint hosted con tu propia clave. El conjunto de herramientas es idéntico en todos los planes — las 16, incluido el bloque de lectura; las tarifas solo se diferencian en la capacidad garantizada:
- Hobby — gratis para siempre, 60 peticiones/min;
- Pro — $29/mes, 300 peticiones/min;
- Scale — $199/mes, 1200 peticiones/min.
La conexión hosted solo se diferencia en que apuntas al endpoint compartido con tu clave:
{
"mcpServers": {
"ton": {
"type": "http",
"url": "https://mcp.tonnode.io/mcp",
"headers": { "Authorization": "Bearer tn_live_…" }
}
}
}
Si estás montando un dashboard, un bot de consulta de balances o un agente que consulta direcciones con frecuencia — pide una clave para no chocar con los límites de las API públicas ni llevarte un 429 en el peor momento posible.
En resumen
- USDT en TON es un jetton, y su balance vive en una jetton wallet aparte, no en la dirección principal.
get_jetton_balancecalcula por sí solo la dirección de la jetton wallet on-chain y lee el balance — no necesitas indexador ni base de datos propia.- El balance llega en unidades raw; para mostrarlo divide entre
10^decimals. - USDT tiene
decimals = 6(divisor1_000_000); la mayoría de los jettons,9. No lo hardcodees — tómalo deget_jetton_info. - Todo el flujo se puede probar gratis:
npx -y @tonnode/mcp, config pública, conjunto de lectura completo.
La clave gratuita Hobby — 60 peticiones/min, sin tarjeta — se emite nada más iniciar sesión: obtener la clave. Es más que suficiente para ejecutar la cadena parse_address → get_jetton_info → get_jetton_balance sobre una wallet real y comprobar que 12500000 raw se convierten exactamente en 12.5 USDT — y no en 12.5 millones ni en 0.0125.
Dale a tu agente acceso a TON
16 herramientas MCP: lectura, swaps no custodiales, cross-chain y billeteras. Plan gratuito — 60 req/min, sin tarjeta.