跳到主要内容

开发者

TRONAgg Radar 在工作台中能做的一切,你的后端都可以通过 API 完成:在创建地址时添加地址、为每条提醒接收已签名的 Webhook、在地址就绪时进行归集,以及重放你错过的任何内容。你无需运行 TRON 节点,也无需轮询区块。

  • 基础 URL:https://tronagg.ai/api/v1/watch
  • 身份验证:每个请求都在 X-API-Key 请求头中携带你的 API 密钥。在工作台 → API 密钥中创建,并将其保存在你的服务器上。
  • 单位:TRX 和价格为整数 SUN(1 TRX = 1,000,000 SUN)。USDT 金额为六位小数的十进制字符串("1250.000000")。能量为整数。时间戳包含时区。
  • 参考:每个端点及其参数、响应和错误都在 API 参考 → TRONAgg Radar 中。

集成的整体流程​

  1. 创建一个场景,只需一次,可在工作台中创建,也可使用 POST /scenarios。它决定 Radar 执行什么操作以及提醒发送到哪里。
  2. 在创建地址时添加地址:POST /addresses,每次调用最多 1,000 个,并附上你自己的 external_id。
  3. 接收 Webhook。 验证签名,按信封 id 去重,迅速返回 2xx。
  4. 在 address.sweep.ready 时归集。 按 episode_id 让你的归集保持幂等。
  5. 在任何停机之后使用 GET /events?since_id= 重放,并在为客户入账之前使用 updated_since 跟踪确认状态。
端点作用
GET /status访问权限、额度、计数、链数据新鲜度(live 或 delayed)以及今日的能量花费。在获得访问权限之前也可调用。
GET /scenarios、POST /scenarios列出和创建场景。
GET、PATCH、DELETE /scenarios/{id}读取、更改或删除单个场景。
POST /addresses/check预览哪些地址是新地址、已在监控或无效。不做任何更改。
POST /addresses开始监控最多 1,000 个地址。
GET /addresses带筛选(needs_attention、not_ready、waiting_to_sweep、energy_paused)、搜索和游标分页的列表。
POST /addresses/bulk移动、暂停添加能量、恢复或停止监控最多 1,000 个地址。
GET、PATCH、DELETE /addresses/{address}读取单个地址,更改其标签、外部 ID、场景或能量暂停状态,或停止监控它。
POST /addresses/{address}/reviews将安全事项或问题事项标记为已查看。
GET /addresses/{address}/energy/quote、POST /addresses/{address}/energy先报价,再为该地址的下一笔转账购买能量。
GET /events事件流:使用 since_id 重放,使用 updated_since 获取确认状态变化。
GET /events/{id}、POST /events/{id}/redeliver单个事件及 Radar 的能量决策和每一次投递;重新发送该事件。
GET /pricing当前一次能量添加的费用,以及今日的价格区间。

快速入门​

此示例会注册一个地址、验证 Radar 的 Webhook、响应 address.sweep.ready,并重放可能错过的事件。你需要:

  • 一个 API 密钥;
  • 一个场景:在 TRONAgg Radar → 场景中创建,或使用下面的调用创建;
  • 一个在 TRONAgg Radar → 提醒中连接的 Webhook,以及在显示时复制的签名密钥。生产环境中 Webhook 必须是公开的 HTTPS URL;示例在本地监听。
export TRONAGG_API_KEY='your-api-key'
curl -sS -X POST https://tronagg.ai/api/v1/watch/scenarios \
-H "X-API-Key: $TRONAGG_API_KEY" -H 'Content-Type: application/json' \
-d '{"name":"Deposit addresses","action":"add_on_deposit","threshold":"100","energy_preset":"65k","daily_budget_sun":50000000}'
# 从响应中复制 "id",或使用现有场景的 id。
export RADAR_SCENARIO_ID='scenario-uuid-from-response'
export RADAR_ADDRESS='your-base58check-tron-address'
export RADAR_WEBHOOK_SECRET='secret-shown-once-when-connecting-the-webhook'

两个程序都把去重集合和重放游标保存在内存中,因此无需任何依赖即可运行。生产环境中,请把它们保存在你的数据库中:每处理完一页就持久化游标。只在 onReady / on_ready 中、在完成你自己的检查之后,并且仅当场景的能量预设与收款方相符时(65,000 覆盖已持有 USDT 的收款方),才签名并发送归集交易。

JavaScript(Node 22+)​

保存为 radar.mjs,然后运行 node radar.mjs。

radar.mjs
import { createHmac, timingSafeEqual } from 'node:crypto';
import { createServer } from 'node:http';

const root = 'https://tronagg.ai/api/v1/watch';
const key = process.env.TRONAGG_API_KEY;
const secret = process.env.RADAR_WEBHOOK_SECRET;
const address = process.env.RADAR_ADDRESS;
const scenarioId = process.env.RADAR_SCENARIO_ID;
if (![key, secret, address, scenarioId].every(Boolean)) throw new Error('Set the four Radar environment variables');
const seenIds = new Set();
const handledEpisodes = new Set();
let sinceId = Number(process.env.RADAR_SINCE_ID || 0);

async function api(path, options = {}) {
const res = await fetch(root + path, { ...options, headers: { 'X-API-Key': key, 'Content-Type': 'application/json', ...options.headers } });
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
return res.json();
}

function onReady(data) {
if (handledEpisodes.has(data.episode_id)) return; // 每笔入账只归集一次
handledEpisodes.add(data.episode_id);
console.log(`Ready to sweep ${data.display_name} (${data.address}): ${data.balance} USDT, Energy ${data.energy_available}, expires ${data.expires_at}`);
// 在检查目标地址和你的业务规则之后,在这里把你自己的归集加入队列。
}

function verify(timestamp, signature, rawBody) {
const sentAt = Number(timestamp);
if (!Number.isFinite(sentAt) || Math.abs(Date.now() / 1000 - sentAt) > 300) return false;
if (typeof signature !== 'string' || !/^v1=[0-9a-f]{64}$/i.test(signature)) return false;
const expected = createHmac('sha256', secret).update(`${timestamp}.`).update(rawBody).digest();
const supplied = Buffer.from(signature.slice(3), 'hex');
return supplied.length === expected.length && timingSafeEqual(supplied, expected);
}

// 不带 updatedSince:sinceId 之后的新事件。带 updatedSince:自该时间以来的确认状态变化。
async function replay(updatedSince) {
const nextWindow = new Date().toISOString(); // 在拉取第一页之前记录
let cursor = updatedSince ? 0 : sinceId;
while (true) {
const query = new URLSearchParams({ limit: '500', since_id: String(cursor) });
if (updatedSince) query.set('updated_since', updatedSince);
const { events, next_since_id } = await api(`/events?${query}`);
for (const event of events) {
if (event.type === 'address.sweep.ready') onReady(event.data);
console.log('event', event.id, event.type, event.finality);
}
const next = next_since_id ?? cursor;
if (!updatedSince) sinceId = next; // 每处理完一页就持久化
if (events.length < 500 || next === cursor) break;
cursor = next; // 分页期间保持 updatedSince 不变
}
return updatedSince ? nextWindow : undefined; // 仅在所有分页都成功后持久化
}

await api('/addresses', { method: 'POST', body: JSON.stringify({ scenario_id: scenarioId, existing: 'skip', items: [{ address, label: 'deposit-0042', external_id: 'invoice-4821' }] }) });
await replay();

createServer(async (req, res) => {
if (req.method !== 'POST' || req.url !== '/radar') { res.writeHead(404).end(); return; }
try {
const chunks = [];
for await (const chunk of req) chunks.push(chunk);
const raw = Buffer.concat(chunks);
if (!verify(req.headers['x-tronagg-timestamp'], req.headers['x-tronagg-signature'], raw)) { res.writeHead(401).end(); return; }
const event = JSON.parse(raw.toString('utf8'));
if (!seenIds.has(event.id)) {
seenIds.add(event.id);
if (event.type === 'address.sweep.ready') onReady(event.data);
}
res.writeHead(204).end();
} catch (error) { console.error(error); res.writeHead(500).end(); }
}).listen(8080, '127.0.0.1', () => console.log('Listening on http://127.0.0.1:8080/radar'));

Python(3.11+)​

保存为 radar.py,然后运行 python3 radar.py。它只使用标准库。

radar.py
import hashlib, hmac, json, os, time
from datetime import datetime, timezone
from http.server import BaseHTTPRequestHandler, HTTPServer
from urllib.parse import urlencode
from urllib.request import Request, urlopen

ROOT = 'https://tronagg.ai/api/v1/watch'
KEY = os.environ['TRONAGG_API_KEY']
SECRET = os.environ['RADAR_WEBHOOK_SECRET'].encode()
ADDRESS = os.environ['RADAR_ADDRESS']
SCENARIO_ID = os.environ['RADAR_SCENARIO_ID']
seen_ids, handled_episodes = set(), set()
since_id = int(os.getenv('RADAR_SINCE_ID', '0'))

def api(path, payload=None):
body = json.dumps(payload).encode() if payload is not None else None
req = Request(ROOT + path, data=body, headers={'X-API-Key': KEY, 'Content-Type': 'application/json'}, method='POST' if body else 'GET')
with urlopen(req, timeout=10) as response:
return json.load(response)

def on_ready(data):
if data['episode_id'] in handled_episodes: # 每笔入账只归集一次
return
handled_episodes.add(data['episode_id'])
print('Ready to sweep', data['display_name'], data['address'], data['balance'], 'USDT, Energy', data['energy_available'], 'expires', data['expires_at'])
# 在检查目标地址和你的业务规则之后,在这里把你自己的归集加入队列。

def verify(timestamp, signature, raw):
if not timestamp or not signature or not signature.startswith('v1='):
return False
try:
if abs(time.time() - int(timestamp)) > 300:
return False
except ValueError:
return False
expected = hmac.new(SECRET, timestamp.encode() + b'.' + raw, hashlib.sha256).hexdigest()
return hmac.compare_digest('v1=' + expected, signature)

def replay(updated_since=None):
"""不带 updated_since:since_id 之后的新事件。带上它:自该时间以来的确认状态变化。"""
global since_id
next_window = datetime.now(timezone.utc).isoformat() # 在拉取第一页之前记录
cursor = 0 if updated_since else since_id
while True:
params = {'limit': 500, 'since_id': cursor}
if updated_since:
params['updated_since'] = updated_since
result = api('/events?' + urlencode(params))
for event in result['events']:
if event['type'] == 'address.sweep.ready':
on_ready(event['data'])
print('event', event['id'], event['type'], event['finality'])
next_cursor = result.get('next_since_id') or cursor
if not updated_since:
since_id = next_cursor # 每处理完一页就持久化
if len(result['events']) < 500 or next_cursor == cursor:
break
cursor = next_cursor # 分页期间保持 updated_since 不变
return next_window if updated_since else None # 仅在所有分页都成功后持久化

class Handler(BaseHTTPRequestHandler):
def do_POST(self):
if self.path != '/radar':
self.send_error(404)
return
raw = self.rfile.read(int(self.headers.get('Content-Length', '0')))
if not verify(self.headers.get('x-tronagg-timestamp'), self.headers.get('x-tronagg-signature'), raw):
self.send_error(401)
return
try:
event = json.loads(raw)
if event['id'] not in seen_ids:
seen_ids.add(event['id'])
if event['type'] == 'address.sweep.ready':
on_ready(event['data'])
except Exception as exc:
print('Radar webhook processing failed:', repr(exc))
self.send_error(500)
return
self.send_response(204)
self.end_headers()

api('/addresses', {'scenario_id': SCENARIO_ID, 'existing': 'skip', 'items': [{'address': ADDRESS, 'label': 'deposit-0042', 'external_id': 'invoice-4821'}]})
replay()
HTTPServer(('127.0.0.1', 8080), Handler).serve_forever()

Webhook​

在 TRONAgg Radar → 提醒中连接 Webhook,或使用 POST /webhooks。对于每条提醒,Radar 会向地址所在场景中接收该提醒分组的每个 Webhook 发送一个 HTTPS POST。Webhook 从不并入汇总,在 Telegram 中将地址静音也不会影响 Webhook。

每次投递都是一个 JSON 信封:

{
"id": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"type": "address.usdt.received",
"created_at": "2026-10-04T18:05:18.123456+00:00",
"audience": "customer",
"data": { }
}

并带有以下请求头:

请求头值
X-TronAgg-Event事件的 type。
X-TronAgg-Delivery投递 ID,在重试之间保持不变。
X-TronAgg-Timestamp本次尝试签名时的 Unix 秒数。
X-TronAgg-Signaturev1=<hex>:使用 Webhook 签名密钥对 {timestamp}.{raw body} 计算的 HMAC-SHA256。

验证每一次投递​

  1. 在进行任何 JSON 解析之前,获取原始请求字节。
  2. 使用你的签名密钥,对 X-TronAgg-Timestamp、一个点号和原始请求体计算 HMAC-SHA256。以恒定时间将其与 v1= 之后的十六进制值进行比较。
  3. 拒绝与你的时钟相差超过 5 分钟的时间戳。每次重试都会使用新的时间戳重新签名,因此真实的投递总能通过验证。

快速入门中的 verify 函数正是这样做的。适用于任何 TRONAgg Webhook 的同样检查见 Webhook 事件。

投递规则​

  • 在 10 秒内返回 2xx,耗时的工作放在响应之后进行。不会跟随重定向。
  • 重试:投递失败后最多再重试 4 次,分别在每次失败后约 1 分钟、5 分钟、30 分钟和 2 小时进行,之后放弃。请使用事件流补齐。
  • 重复投递在所难免。 按信封 id 去重。如果你同时使用 Webhook 和重放,还要按 episode_id 让你的归集保持幂等。
  • 不保证顺序。 使用 data.event_id(随时间递增)或 block_number 对同一地址的事件排序。
  • 忽略你不认识的类型,并仍然返回 2xx:以后可能会新增事件。
  • 使用 POST /events/{id}/redeliver 重新发送单个事件,可以发送到某一个渠道,或发送到地址当前场景的各个渠道。每个事件最多重新发送 5 次,每个账户每小时最多 60 次。

各个 ID 的区别​

ID位置用途
信封 id(UUID)Webhook 请求体对重试的投递去重。
data.event_id(整数)Webhook data;等于 GET /events 中事件的 id在事件流中查找该事件、将其标记为已查看、重新发送。
data.episode_id(UUID)address.sweep.ready让每笔入账的归集只执行一次。
data.action_id(UUID)能量事件与 GET /events/{id} 中 Radar 的能量决策相对应。

Webhook 负载​

一次完整的 address.sweep.ready 投递(示例值):

{
"id": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"type": "address.sweep.ready",
"created_at": "2026-10-04T18:05:18.123456+00:00",
"audience": "customer",
"data": {
"address_id": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5c",
"address": "TXKc3d5gEqvKEayX9LySXNS5RLf4qDDEb1",
"display_name": "deposit-0042",
"label": "deposit-0042",
"external_id": "invoice-4821",
"scenario_id": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5d",
"scenario_name": "Deposit addresses",
"event_id": 123,
"tx_id": "a91853964b278f64a91853964b278f64a91853964b278f64a91853964b278f64",
"block_number": 86792808,
"block_time": "2026-10-04T18:05:15Z",
"finality": "head",
"episode_id": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5e",
"balance": "1250.000000",
"energy_available": 65000,
"energy_required": 64285,
"expires_at": "2026-10-04T19:05:15Z",
"source": "radar"
}
}

每个事件都有的字段​

字段类型含义
address_idUUID 字符串Radar 为你所监控地址分配的 ID。
address字符串该地址,Base58Check 格式(T…)。
display_name字符串标签;没有标签时为缩略地址。
label、external_id字符串或 null你的标签和你自己的参考编号。
scenario_id、scenario_nameUUID 字符串、字符串该地址所在的场景。
event_id整数该事件在事件流中的 ID。
tx_id、block_number、block_time、finality字符串、整数、UTC 时间戳、head / solid / reverted事件背后的交易。

关于链上字段:由交易引起的事件会带有全部四个字段。来自 Radar 自身检查的事件(例如在例行检查中发现的能量不足)带有这些键,但值为 null;block_number 可能是最新区块高度。能量事件只有在某笔交易触发了该决策时才包含这些字段。address.energy.expired 不包含其中任何一个。finality 从 head 开始;参见确认状态。

USDT 金额是六位小数的字符串,TRX 金额是整数 SUN,能量是整数。标记为可为空的字段始终存在。

所有事件​

分组事件额外的 data 字段(示例值)触发时机
入账与转账address.usdt.received{"asset":"USDT","direction":"in","amount":"1250.000000","counterparty":"TYqHsaXKuUutMYADYKF15hVfVAYfmpggvo","balance":"1250.000000"}USDT(官方合约)转入,1 USDT 起。如果转账后未能立即读取余额,balance 可能为 null。
入账与转账address.usdt.sent{"asset":"USDT","direction":"out","amount":"1250.000000","counterparty":"TWd4WrZ9wn84f5x1hZhL4DHvk738ns5jwb","balance":"0.000000"}USDT 转出,1 USDT 起。使用了 Radar 能量的归集之后可能会增加 saved_sun:估算未燃烧的 TRX,单位为 SUN。
入账与转账address.trx.received{"asset":"TRX","direction":"in","amount":10000000,"amount_trx":"10.000000","counterparty":"TYqHsaXKuUutMYADYKF15hVfVAYfmpggvo","balance":12000000}TRX 转入,10 TRX 起,包括智能合约发送到该地址的 TRX(内部转账)。amount 和可为空的 balance 单位为 SUN;amount_trx 是六位小数的字符串。
入账与转账address.trx.sent{"asset":"TRX","direction":"out","amount":10000000,"amount_trx":"10.000000","counterparty":"TWd4WrZ9wn84f5x1hZhL4DHvk738ns5jwb","balance":2000000}TRX 转出,10 TRX 起,包括随智能合约调用发送的 TRX。
能量添加address.sweep.ready{"episode_id":"0190f7a4-0000-7000-8000-000000000001","balance":"1250.000000","energy_available":65000,"energy_required":64285,"expires_at":"2026-10-04T19:05:15Z","source":"radar"}一笔等待中的入账可以归集:拥有足够完成一笔预设转账的能量、未被冻结、能够支付带宽。每笔入账一次。source 为 radar(Radar 添加了能量)或 existing(能量原本就在;此时 expires_at 为 null)。
能量添加address.energy.charged{"action_id":"0190f7a4-0000-7000-8000-000000000002","order_id":"0190f7a4-0000-7000-8000-000000000003","energy":65000,"total_price_sun":1755000,"delegation_txid":"a91853964b278f64a91853964b278f64a91853964b278f64a91853964b278f64","expires_at":"2026-10-04T19:05:15Z"}Radar 添加的能量已到达该地址。total_price_sun 是实际支付的金额。order_id、delegation_txid 和 expires_at 可为空。
能量添加address.charge.skipped{"action_id":"0190f7a4-0000-7000-8000-000000000004","reason":"budget"}未添加能量。reason:radar_disabled、actions_disabled、energy_paused、outside_hours、frozen、contract、autoenergy_active、no_bandwidth、budget、limit、insufficient_balance。每个场景和每种原因每小时最多一次。
能量添加address.charge.failed{"action_id":"0190f7a4-0000-7000-8000-000000000005","reason":"order_failed"}能量订单失败。reason:purchase_failed 或 order_failed。
能量添加address.energy.expired{"action_id":"0190f7a4-0000-7000-8000-000000000006","order_id":"0190f7a4-0000-7000-8000-000000000007","energy":65000,"expires_at":"2026-10-04T19:05:15Z"}Radar 添加的能量在入账被归集之前已过期。order_id、energy 和 expires_at 可为空。
问题address.energy.insufficient{"energy_available":12400,"energy_required":64285,"deficit":63999}能量不足以完成下一笔预设转账。缺口为 energy_required − energy_available;deficit 是 Radar 为此会下的订单量:向上取整到下一个 1,000,且不低于最低订单量。对于关闭了能量警告的仅发送提醒场景不发送。
问题address.trx.low{"trx_balance_sun":0,"needed_sun":345000,"reason":"bandwidth"}TRX 不足以支付下一笔转账的带宽或多签费用。reason:bandwidth 或 multisig。对于关闭了能量警告的仅发送提醒场景不发送。
问题address.trx.burned{"trx_burned_sun":6428500,"energy_used":64285,"would_cost_sun":1755000}该地址的一笔交易燃烧了 TRX 来获取能量。would_cost_sun 是现在从 Radar 获取这些能量的费用:无法读取价格时为 null,无需下单时为 0。
问题address.tx.failed{"reason":"out_of_energy","fee_limit_sun":1000000,"energy_required":64285}该地址的一笔交易失败。reason:out_of_energy 或 reverted;对于 reverted,fee_limit_sun 和 energy_required 为 null:更多的能量也无济于事。
问题address.usdt.low{"balance":"40.000000","threshold":"100.000000"}USDT 余额降至场景的 USDT 余额偏低提醒以下。
安全address.permissions.changed{"owner_before":{"threshold":1,"keys":[{"address":"TEAmEwsUMT9JkgtUmCG3eZrCbmkXRjA3Mc","weight":1}]},"owner_after":{"threshold":2,"keys":[{"address":"TEAmEwsUMT9JkgtUmCG3eZrCbmkXRjA3Mc","weight":1},{"address":"TXk8rQSAvPvBBNtqSoY6nCfsXWCSSpTVQF","weight":1}]},"active_changed":false}账户权限已变更,在变更后第一时间报告。owner_before 和 owner_after 可能为 null;没有更早的快照时,active_changed 为 null。从不合并。
安全address.usdt.approval{"spender":"TXk8rQSAvPvBBNtqSoY6nCfsXWCSSpTVQF","amount":"1000000.000000","unlimited":false}设置了 USDT 授权额度。授权额度达到或超过 2²⁵⁵ 时 unlimited 为 true;无限额授权从不合并。
安全address.usdt.frozen{"frozen":true}发行方冻结(true)或解冻(false)了该地址的 USDT。从不合并。
安全address.poisoning.suspected{"lookalike_of":"TWd4WrZ9wn84f5x1hZhL4DHvk738ns5jwb","counterparty":"TWd4Wr7Lq2Xc9Tp3Vb8Nm1Kd6Hs4qn5jwb","direction":"in","asset":null,"token":"TFakeUSDTq8ZY4pL8otSzgjLj6tR7NHqje","amount":null}一笔涉及已知地址仿冒地址的转账。direction:in 或 out。对于 USDT 或 TRX,asset 有值,token 为 null,amount 是六位小数的字符串;对于其他代币,asset 和 amount 为 null,token 为其合约地址(或 null)。

有些决策会被记录但不发送事件:当地址已经就绪(already_ready)、该入账已不再需要能量(no_longer_needed),或地址已被暂停、移动或移除(scenario_changed)时,Radar 不会发送 address.charge.skipped。这些决策仍会出现在 GET /events/{id} 的 Action 中。

繁忙的 Telegram 聊天可能会收到一条文本汇总,而不是单独的消息。汇总永远不是 Webhook 事件:Webhook 始终单独接收每个事件。

重放与确认状态​

GET /events 是一个持久的事件流,包含你的地址在最近 90 天内的每个事件,与你的 Webhook 收到的事件相同。用它在停机后补齐事件,并了解事件何时被最终确认。

使用 since_id 补齐​

curl -sS "https://tronagg.ai/api/v1/watch/events?since_id=0&limit=500" -H "X-API-Key: $TRONAGG_API_KEY"
{
"events": [
{
"id": 124,
"type": "address.usdt.received",
"address": "TXKc3d5gEqvKEayX9LySXNS5RLf4qDDEb1",
"tx_id": "c3f1a0d29e7b4c5a8d6e0f1b2a3c4d5e6f708192a3b4c5d6e7f8091a2b3c4d5e",
"block_number": 86792801,
"block_time": "2026-10-04T18:04:54Z",
"created_at": "2026-10-04T18:04:57Z",
"finality": "solid",
"finality_updated_at": "2026-10-04T18:05:51Z",
"late": false,
"data": {
"address_id": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5c",
"address": "TXKc3d5gEqvKEayX9LySXNS5RLf4qDDEb1",
"display_name": "deposit-0042",
"label": "deposit-0042",
"external_id": "invoice-4821",
"scenario_id": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5d",
"scenario_name": "Deposit addresses",
"event_id": 124,
"tx_id": "c3f1a0d29e7b4c5a8d6e0f1b2a3c4d5e6f708192a3b4c5d6e7f8091a2b3c4d5e",
"block_number": 86792801,
"block_time": "2026-10-04T18:04:54Z",
"finality": "head",
"asset": "USDT",
"direction": "in",
"amount": "1250.000000",
"counterparty": "TYqHsaXKuUutMYADYKF15hVfVAYfmpggvo",
"balance": "1250.000000"
},
"review": null
}
],
"next_since_id": 124
}
  • 事件按从旧到新排列,从你发送的 since_id 之后开始。
  • 处理完一页后保存 next_since_id,并将其作为下一次的 since_id 发送。没有新事件时,它就是你发送的 since_id。
  • 持续分页,直到某一页的事件数少于你的 limit(最多 500)。
  • 使用 address 或 kind(一种事件类型,例如 address.sweep.ready)缩小事件流范围。
  • data 是你的 Webhook 收到的对象,保持发送时的原样:其中的 finality 保持当时的状态,而事件自身的 finality 显示当前状态。late: true 表示 Radar 读取该区块时已落后于链超过 100 个区块(约五分钟),例如在故障之后。

使用 updated_since 跟踪确认状态​

Radar 根据最新区块发送提醒,因此每个事件都以 finality: "head" 开始。当区块变为不可逆时,事件变为 solid。在网络丢弃该区块的罕见情况下,它会变为 reverted。状态变化时 Radar 不会发送 Webhook。请主动查询:

curl -sS "https://tronagg.ai/api/v1/watch/events?updated_since=2026-10-04T18:00:00Z&since_id=0&limit=500" -H "X-API-Key: $TRONAGG_API_KEY"

它会返回确认状态在 updated_since 当时或之后发生变化的事件,并附上新的 finality 和 finality_updated_at。按计划执行时:

  1. 在拉取第一页之前,记下当前时间:它就是你下一次的 updated_since。
  2. 在使用 since_id(从 0 开始)分页期间,保持 updated_since 不变。
  3. 只有在所有分页都成功之后,才保存第 1 步记下的时间。分页期间发生的变化会在下一轮再次出现,因此请以幂等方式处理它们。

updated_since 必须包含时区。快速入门中的 replay(updatedSince) / replay(updated_since) 函数实现了这个循环。

一个可行的规则: 将 head 视为“已看到付款”,并在事件变为 solid 时为客户入账。reverted 事件在链上从未发生过。如果你自己的归集已经发出,那是你自己的另一笔交易,回滚不会撤销它。

从你的代码中添加能量​

为地址的下一笔转账购买能量,适用于任何场景,包括 alert_only:

# 1. 报价。不会购买任何东西。
curl -sS "https://tronagg.ai/api/v1/watch/addresses/$RADAR_ADDRESS/energy/quote?energy_preset=131k" \
-H "X-API-Key: $TRONAGG_API_KEY"
# 示例响应:{"energy":131000,"total_price_sun":3537000,"activation_fee_sun":0}

# 2. 购买,以报价的总额作为你的上限:total_price_sun + activation_fee_sun。
curl -sS -X POST "https://tronagg.ai/api/v1/watch/addresses/$RADAR_ADDRESS/energy" \
-H "X-API-Key: $TRONAGG_API_KEY" -H 'Content-Type: application/json' \
-d '{"energy_preset":"131k","max_total_price_sun":3537000}'
  • energy_preset 为 65k 或 131k。省略时使用场景的预设。
  • 报价只针对缺少的能量,按当前小时的价格计算;activation_fee_sun 是尚未激活地址的一次性激活费。
  • max_total_price_sun 是可选的。提供时,如果最新价格高于你的上限,会返回 409 quote_expired 并附上新的 quote,且不会购买。不提供时,按实时价格执行,并受场景的每日预算和你的余额限制。
  • 响应为 Action:status 为 reserved、ordered 或 delivered,包含已下单的能量、价格,以及交付后的 ready_at 和 expires_at。能量到达时随后会发送 address.energy.charged Webhook。
  • 它计入场景的 daily_budget_sun,不计入 times_per_address_day,并遵守场景的工作时间。alert_only 场景从不自行购买,也没有预算:在这类场景中,你的上限和余额是仅有的限制,因此请发送 max_total_price_sun。Radar 会先在链上检查该地址。
  • 拒绝情况:409 already_ready、in_flight、budget_exhausted、frozen、energy_paused、outside_hours、contract、no_bandwidth、autoenergy_active、quote_expired;400 insufficient_balance,附带以 SUN 为单位的 current_balance 和 required_amount。

这永远不会转移 USDT,也不会签署任何内容:你的归集仍由你自己发送。

错误​

Radar 的拒绝响应只有一种结构。请根据 error 分支处理,而不要依赖 message:

{ "detail": { "error": "radar_limit_exceeded", "message": "This change would exceed your Radar address limit", "kind": "with_energy", "used": 1000, "max": 1000 } }
状态码error何时出现
401(纯字符串 detail)API 密钥缺失或无效。
403radar_access_required既没有 Google 登录,也还没有 1 TRX 的充值。包含 required_deposit_sun 和 allowed_auth_methods。
404radar_disabled此账户无法使用 TRONAgg Radar。
404address_not_found、scenario_not_found、event_not_found、destination_not_found在你自己的地址、场景、事件或渠道中未找到。
409radar_limit_exceeded此更改超出你的额度。包含 kind(watch_only 或 with_energy)、used 和 max。
409scenario_name_taken、external_id_taken、scenario_has_addresses、address_not_watched名称或外部 ID 已被使用;删除仍有地址的场景但未提供 move_to;为你已不再监控的地址重新发送事件但未指定渠道。
409、400添加能量的拒绝情况参见从你的代码中添加能量。
422invalid_*、empty_batch、batch_too_large、scenario_requiredRadar 永远无法接受的输入,并由 field 指明具体字段,例如 invalid_address、invalid_threshold、invalid_energy_preset、invalid_energy_alerts、invalid_daily_budget、invalid_active_hours、invalid_cursor、invalid_timestamp。
422(数组 detail)请求与结构定义不符,例如未知字段或类型错误的值。
429redelivery_rate_limited、redelivery_backlogged重新发送次数过多,或仍有 200 条 Webhook 投递在排队。
429(纯字符串 detail)每个账户每分钟超过 1,000 次请求。请等待 Retry-After 秒。

批量调用(POST /addresses、POST /addresses/bulk)会在单项结果中报告有问题的项,因此批次中的其余项仍会继续处理。每个端点的完整列表见 API 参考;整个 API 共享的约定见错误与约定。