30#include "mbedtls/sha256.h"
34static const char *
const TAG =
"tron_rpc";
38#define RESP_BUF_SIZE 3072U
39#define RESP_LOG_MAX 120
42#define RAW_BYTES_MAX (TRON_RAW_HEX_MAX / 2U)
53 return (n < 10U) ?
static_cast<char>(
'0' + n)
54 :
static_cast<char>(
'a' + n - 10U);
59 if ((c >=
'0') && (c <=
'9')) {
return c -
'0'; }
60 if ((c >=
'a') && (c <=
'f')) {
return (c -
'a') + 10; }
61 if ((c >=
'A') && (c <=
'F')) {
return (c -
'A') + 10; }
73static size_t hex_to_bytes(
const char *hex, uint8_t *out,
size_t out_size)
75 const size_t len = strlen(hex);
76 if ((len == 0U) || ((len % 2U) != 0U) || ((len / 2U) > out_size)) {
79 for (
size_t i = 0U; i < (len / 2U); i++) {
82 if ((hi < 0) || (lo < 0)) {
return 0U; }
83 out[i] =
static_cast<uint8_t
>((hi << 4) | lo);
89static void bytes_to_hex(
const uint8_t *data,
size_t len,
char *out)
91 for (
size_t i = 0U; i < len; i++) {
92 out[i * 2U] =
nibble_hex((data[i] >> 4U) & 0x0FU);
93 out[i * 2U + 1U] =
nibble_hex(data[i] & 0x0FU);
99static bool tron_post(
const char *path,
const char *body,
100 char *resp,
size_t resp_size)
103 ESP_LOGE(
TAG,
"tron_rpc_init not called");
107 int k = snprintf(url,
sizeof(url),
"%s%s",
s_base_url, path);
108 if ((k <= 0) || (
static_cast<size_t>(k) >=
sizeof(url))) {
return false; }
120 if ((gettimeofday(&tv, NULL) != 0) || (tv.tv_sec < 1600000000)) {
return 0U; }
121 return (
static_cast<uint64_t
>(tv.tv_sec) * 1000U) +
122 (
static_cast<uint64_t
>(tv.tv_usec) / 1000U);
143 const cJSON *txid = cJSON_GetObjectItemCaseSensitive(txobj,
"txID");
144 const cJSON *raw = cJSON_GetObjectItemCaseSensitive(txobj,
"raw_data_hex");
145 if (!cJSON_IsString(txid) || (txid->valuestring == NULL) ||
146 !cJSON_IsString(raw) || (raw->valuestring == NULL)) {
149 if (strlen(txid->valuestring) != 64U) {
150 ESP_LOGE(
TAG,
"create: txID length %u",
151 (
unsigned)strlen(txid->valuestring));
155 ESP_LOGE(
TAG,
"create: raw_data too long (%u)",
156 (
unsigned)strlen(raw->valuestring));
161 size_t raw_len =
hex_to_bytes(raw->valuestring, raw_bytes,
164 ESP_LOGE(
TAG,
"create: raw_data not hex");
169 if (mbedtls_sha256(raw_bytes, raw_len, digest, 0) != 0) {
170 ESP_LOGE(
TAG,
"create: sha256 failed");
175 if (strcasecmp(digest_hex, txid->valuestring) != 0) {
176 ESP_LOGE(
TAG,
"create: txID is not sha256(raw_data) - refusing");
180 (void)memcpy(out->
txid, digest,
sizeof(out->
txid));
182 (void)snprintf(out->
raw_hex,
sizeof(out->
raw_hex),
"%s", raw->valuestring);
213 if (!cJSON_IsNumber(n) || (n->valuedouble <= 0.0)) {
return 0U; }
214 if (n->valuedouble >= 18446744073709551615.0) {
return UINT64_MAX; }
215 return (uint64_t)n->valuedouble;
220 if ((owner_hex == NULL) || (sun_out == NULL)) {
return false; }
223 (void)snprintf(body,
sizeof(body),
224 "{\"address\":\"%s\",\"visible\":false}", owner_hex);
227 if (!
tron_post(
"/wallet/getaccount", body, resp,
sizeof(resp))) {
231 cJSON *root = cJSON_Parse(resp);
236 *sun_out =
json_u64(cJSON_GetObjectItemCaseSensitive(root,
"balance"));
243 if ((owner_hex == NULL) || (energy_out == NULL)) {
return false; }
246 (void)snprintf(body,
sizeof(body),
247 "{\"address\":\"%s\",\"visible\":false}", owner_hex);
250 if (!
tron_post(
"/wallet/getaccountresource", body, resp,
sizeof(resp))) {
254 cJSON *root = cJSON_Parse(resp);
256 ESP_LOGE(
TAG,
"getaccountresource: not JSON: %.*s",
RESP_LOG_MAX, resp);
259 const uint64_t limit =
260 json_u64(cJSON_GetObjectItemCaseSensitive(root,
"EnergyLimit"));
261 const uint64_t used =
262 json_u64(cJSON_GetObjectItemCaseSensitive(root,
"EnergyUsed"));
265 *energy_out = (limit > used) ? (limit - used) : 0U;
272 if ((owner_hex == NULL) || (contract_hex == NULL) || (units_out == NULL)) {
280 ESP_LOGE(
TAG,
"balanceOf: owner is not %u hex chars",
284 const char *owner20 = owner_hex + 2;
287 (void)snprintf(body,
sizeof(body),
288 "{\"owner_address\":\"%s\",\"contract_address\":\"%s\","
289 "\"function_selector\":\"balanceOf(address)\","
290 "\"parameter\":\"000000000000000000000000%s\","
291 "\"visible\":false}",
292 owner_hex, contract_hex, owner20);
295 if (!
tron_post(
"/wallet/triggerconstantcontract", body,
296 resp,
sizeof(resp))) {
300 cJSON *root = cJSON_Parse(resp);
306 cJSON_GetObjectItemCaseSensitive(root,
"constant_result");
307 const cJSON *first = cJSON_IsArray(arr) ? cJSON_GetArrayItem(arr, 0) : NULL;
310 if (cJSON_IsString(first) && (first->valuestring != NULL)) {
311 const size_t n = strlen(first->valuestring);
316 if ((n > 0U) && (n <= 64U)) {
318 (void)snprintf(hex,
sizeof(hex),
"0x%s", first->valuestring);
323 ESP_LOGE(
TAG,
"balanceOf: no usable constant_result: %.*s",
332 if ((contract_hex == NULL) || (dec_out == NULL) ||
338 int k = snprintf(body,
sizeof(body),
339 "{\"owner_address\":\"%s\",\"contract_address\":\"%s\","
340 "\"function_selector\":\"decimals()\",\"visible\":false}",
341 contract_hex, contract_hex);
342 if ((k <= 0) || (
static_cast<size_t>(k) >=
sizeof(body))) {
return false; }
345 if (!
tron_post(
"/wallet/triggerconstantcontract", body, resp,
sizeof(resp))) {
348 cJSON *root = cJSON_Parse(resp);
349 if (root == NULL) {
return false; }
350 const cJSON *arr = cJSON_GetObjectItemCaseSensitive(root,
"constant_result");
351 const cJSON *first = cJSON_IsArray(arr) ? cJSON_GetArrayItem(arr, 0) : NULL;
353 if (cJSON_IsString(first) && (first->valuestring != NULL) &&
354 (strlen(first->valuestring) > 0U) && (strlen(first->valuestring) <= 64U)) {
356 (void)snprintf(hex,
sizeof(hex),
"0x%s", first->valuestring);
369 if ((owner_hex == NULL) || (to_hex == NULL) || (out == NULL) ||
370 (amount_sun == 0U)) {
375 int k = snprintf(body,
sizeof(body),
376 "{\"owner_address\":\"%s\",\"to_address\":\"%s\","
377 "\"amount\":%" PRIu64
"}",
378 owner_hex, to_hex, amount_sun);
379 if ((k <= 0) || (
static_cast<size_t>(k) >=
sizeof(body))) {
return false; }
382 if (!
tron_post(
"/wallet/createtransaction", body, resp,
sizeof(resp))) {
386 cJSON *root = cJSON_Parse(resp);
400 ESP_LOGE(
TAG,
"create: raw_data does not match the requested transfer");
403 ESP_LOGI(
TAG,
"Tron tx %s (%" PRIu64
" sun)", out->
txid_hex,
412 const char *contract_hex,
413 const char *to_hex, uint64_t amount,
414 uint64_t fee_limit_sun,
417 if ((owner_hex == NULL) || (contract_hex == NULL) || (to_hex == NULL) ||
418 (out == NULL) || (amount == 0U) || (fee_limit_sun == 0U)) {
426 ESP_LOGE(
TAG,
"trc20: bad recipient");
431 int k = snprintf(body,
sizeof(body),
432 "{\"owner_address\":\"%s\",\"contract_address\":\"%s\","
433 "\"function_selector\":\"transfer(address,uint256)\","
434 "\"parameter\":\"%s\",\"call_value\":0,"
435 "\"fee_limit\":%" PRIu64
"}",
436 owner_hex, contract_hex, param, fee_limit_sun);
437 if ((k <= 0) || (
static_cast<size_t>(k) >=
sizeof(body))) {
return false; }
440 if (!
tron_post(
"/wallet/triggersmartcontract", body, resp,
sizeof(resp))) {
444 cJSON *root = cJSON_Parse(resp);
453 const cJSON *txobj = cJSON_GetObjectItemCaseSensitive(root,
"transaction");
458 amount, fee_limit_sun,
now_ms(),
460 ESP_LOGE(
TAG,
"trc20: raw_data does not match the requested transfer");
463 ESP_LOGI(
TAG,
"Tron TRC-20 tx %s (%" PRIu64
" units of %s)",
464 out->
txid_hex, amount, contract_hex);
473 if ((tx == NULL) || (sig == NULL)) {
return false; }
481 envelope,
sizeof(envelope)) == 0U) {
482 ESP_LOGE(
TAG,
"broadcast: envelope build failed");
486 char body[
sizeof(envelope) + 32U];
487 int k = snprintf(body,
sizeof(body),
"{\"transaction\":\"%s\"}", envelope);
488 if ((k <= 0) || (
static_cast<size_t>(k) >=
sizeof(body))) {
return false; }
491 if (!
tron_post(
"/wallet/broadcasthex", body, resp,
sizeof(resp))) {
496 cJSON *root = cJSON_Parse(resp);
501 const cJSON *result = cJSON_GetObjectItemCaseSensitive(root,
"result");
502 ok = cJSON_IsTrue(result);
516 int k = snprintf(body,
sizeof(body),
"{\"value\":\"%s\"}", txid_hex);
517 if ((k <= 0) || (
static_cast<size_t>(k) >=
sizeof(body))) {
522 if (!
tron_post(
"/wallet/gettransactioninfobyid", body,
523 resp,
sizeof(resp))) {
527 cJSON *root = cJSON_Parse(resp);
528 if (!cJSON_IsObject(root)) {
535 const cJSON *result = cJSON_GetObjectItemCaseSensitive(root,
"result");
536 const cJSON *block = cJSON_GetObjectItemCaseSensitive(root,
"blockNumber");
540 const cJSON *rcpt = cJSON_GetObjectItemCaseSensitive(root,
"receipt");
541 const cJSON *rres = cJSON_IsObject(rcpt)
542 ? cJSON_GetObjectItemCaseSensitive(rcpt,
"result")
544 if (cJSON_IsString(result) && (result->valuestring != NULL) &&
545 (strcmp(result->valuestring,
"FAILED") == 0)) {
547 }
else if (cJSON_IsString(rres) && (rres->valuestring != NULL) &&
548 (strcmp(rres->valuestring,
"SUCCESS") != 0)) {
549 ESP_LOGE(
TAG,
"receipt: contract result %s", rres->valuestring);
551 }
else if (cJSON_IsNumber(block)) {
JSON-RPC response parsing helpers — kept in their own unit (cJSON + CW_Utils only,...
static bool eth_json_hex_quantity(const char *hex, uint64_t *out)
Parse a JSON-RPC QUANTITY ("0x0", "0x1a", 64 hex chars) into a uint64, saturating instead of wrapping...
static const char *const TAG
static const char * s_ca_cert
bool https_post_json(const char *url, const char *body, char *resp_buf, size_t resp_buf_size, const char *user, const char *pass, const char *ca_pem)
POST a JSON body over HTTPS and read the response.
One HTTPS JSON POST, shared by every RPC client in the firmware.
static esp_err_t ok(httpd_req_t *req, const char *msg)
200 with a plain-text message.
A created-and-verified transfer, ready to sign and broadcast.
char raw_hex[TRON_RAW_HEX_MAX]
static uint64_t json_u64(const cJSON *n)
A cJSON number as a uint64, clamped at both ends.
bool tron_rpc_get_trc20_balance(const char *owner_hex, const char *contract_hex, uint64_t *units_out)
Read an account's TRC-20 balance via a constant balanceOf call.
bool tron_rpc_get_balance(const char *owner_hex, uint64_t *sun_out)
Read an account's TRX balance, in sun.
bool tron_rpc_create_trc20_transfer(const char *owner_hex, const char *contract_hex, const char *to_hex, uint64_t amount, uint64_t fee_limit_sun, tron_tx_ctx_t *out)
Create a TRC-20 transfer (USDT, USDC, …) and verify what the node serialised for us.
static uint64_t now_ms(void)
Unix time in ms, 0 if the clock is unset (the checks then refuse).
bool tron_rpc_get_energy(const char *owner_hex, uint64_t *energy_out)
Read the energy an account still has available.
void tron_rpc_set_ca_cert(const char *ca_pem)
Optional: pin the Tron endpoint's TLS certificate.
bool tron_rpc_broadcast(const tron_tx_ctx_t *tx, const uint8_t sig[65])
Broadcast a created transfer with its 65-byte signature.
static int nibble_val(char c)
static void bytes_to_hex(const uint8_t *data, size_t len, char *out)
Hex-encode len bytes into out (NUL-terminated).
static bool tron_post(const char *path, const char *body, char *resp, size_t resp_size)
POST body to path under the configured base URL.
static bool tx_ctx_from_json(const cJSON *txobj, tron_tx_ctx_t *out)
Take a node-built transaction object apart into a tron_tx_ctx_t.
bool tron_rpc_create_transfer(const char *owner_hex, const char *to_hex, uint64_t amount_sun, tron_tx_ctx_t *out)
Create a TRX transfer and verify what the node serialised for us.
tron_receipt_t tron_rpc_get_receipt(const char *txid_hex)
Poll a broadcast transaction's receipt (one shot).
static char nibble_hex(unsigned n)
static size_t hex_to_bytes(const char *hex, uint8_t *out, size_t out_size)
Decode a hex string into bytes.
static const char * s_base_url
void tron_rpc_init(const char *base_url)
Set the Tron HTTP API base URL (no trailing slash).
bool tron_rpc_get_trc20_decimals(const char *contract_hex, uint64_t *dec_out)
Read a TRC-20 contract's decimals() — see eth_rpc_get_token_decimals for why it has to be 6.
Tron HTTP API client (Nile testnet): build a TRX transfer, broadcast it signed, poll its receipt.
#define TRON_RAW_HEX_MAX
Hex capacity for a raw_data protobuf.
tron_receipt_t
Outcome of one gettransactioninfobyid poll.
bool tron_tx_contract_ok(const char *raw_data_hex, const char *owner_hex, const char *to_hex, uint64_t amount_sun, uint64_t now_ms, uint64_t *expiration_ms)
Verify that a node-serialised raw_data really transfers what we asked.
bool tron_tx_trc20_ok(const char *raw_data_hex, const char *owner_hex, const char *contract_hex, const char *to_hex, uint64_t amount, uint64_t fee_limit_sun, uint64_t now_ms, uint64_t *expiration_ms)
Verify that a node-serialised raw_data really is the TRC-20 transfer we asked for.
size_t tron_trc20_param_hex(const char *to_hex, uint64_t amount, char *out, size_t out_size)
ABI-encode the arguments of transfer(address,uint256), hex.
size_t tron_tx_envelope_hex(const char *raw_data_hex, const char *sig_hex, char *out, size_t out_size)
Build the hex of a signed Transaction protobuf for broadcasthex.
Tron transaction hex helpers — protobuf varints, the TransferContract integrity check and the signed-...
#define TRON_ADDR_HEX_LEN
Hex length of a Tron address (21 bytes: 0x41 || 20-byte key hash).
#define TRON_SIG_HEX_LEN
Hex length of a Tron signature (r || s || v).
#define TRON_TRC20_PARAM_HEX_LEN
Hex length of the two ABI argument words that follow the selector.