cryptnox-pos 1.0.0
Standalone USDC payment terminal firmware (ESP32 + Cryptnox smart card)
Loading...
Searching...
No Matches
tron_rpc.cpp
Go to the documentation of this file.
1/*
2 * SPDX-License-Identifier: LGPL-3.0-or-later
3 * Copyright (c) 2026 Cryptnox SA
4 */
5
10
11/******************************************************************
12 * 1. Included files
13 ******************************************************************/
14
15#include "tron_rpc.h"
16#include "tron_tx.h"
17#include "eth_json.h" /* eth_json_hex_quantity — one saturating hex parser */
18#include "https_post.h"
19
20#include <stdio.h>
21#include <string.h>
22#include <strings.h> /* strcasecmp */
23#include <inttypes.h>
24
25/* CW_Utils.h pulls in Arduino.h; keep it ahead of any lwip-including header
26 * (see the note in https_post.cpp). */
27#include "CW_Utils.h"
28
29#include "esp_log.h"
30#include "mbedtls/sha256.h"
31#include <sys/time.h> /* gettimeofday — raw_data expiration bound */
32#include "cJSON.h"
33
34static const char *const TAG = "tron_rpc";
35
36/* Responses echo the whole transaction, raw_data expanded as JSON; a TRX
37 * transfer needs ~1 KB and a TriggerSmartContract roughly twice that. */
38#define RESP_BUF_SIZE 3072U
39#define RESP_LOG_MAX 120
40
41/* raw_data is at most TRON_RAW_HEX_MAX/2 bytes. */
42#define RAW_BYTES_MAX (TRON_RAW_HEX_MAX / 2U)
43
44static const char *s_base_url = NULL;
45static const char *s_ca_cert = NULL; /* pinned cert; NULL = CA bundle */
46
47/******************************************************************
48 * 2. Local helpers
49 ******************************************************************/
50
51static char nibble_hex(unsigned n)
52{
53 return (n < 10U) ? static_cast<char>('0' + n)
54 : static_cast<char>('a' + n - 10U);
55}
56
57static int nibble_val(char c)
58{
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; }
62 return -1;
63}
64
73static size_t hex_to_bytes(const char *hex, uint8_t *out, size_t out_size)
74{
75 const size_t len = strlen(hex);
76 if ((len == 0U) || ((len % 2U) != 0U) || ((len / 2U) > out_size)) {
77 return 0U;
78 }
79 for (size_t i = 0U; i < (len / 2U); i++) {
80 int hi = nibble_val(hex[i * 2U]);
81 int lo = nibble_val(hex[i * 2U + 1U]);
82 if ((hi < 0) || (lo < 0)) { return 0U; }
83 out[i] = static_cast<uint8_t>((hi << 4) | lo);
84 }
85 return len / 2U;
86}
87
89static void bytes_to_hex(const uint8_t *data, size_t len, char *out)
90{
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);
94 }
95 out[len * 2U] = '\0';
96}
97
99static bool tron_post(const char *path, const char *body,
100 char *resp, size_t resp_size)
101{
102 if (s_base_url == NULL) {
103 ESP_LOGE(TAG, "tron_rpc_init not called");
104 return false;
105 }
106 char url[128];
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; }
109
110 /* No auth — TronGrid's public endpoint needs none for these calls. The cert
111 * is pinned only if the integrator supplied one; unpinned falls back to the
112 * CA bundle, and tron_tx_contract_ok is what makes that survivable. */
113 return https_post_json(url, body, resp, resp_size, NULL, NULL, s_ca_cert);
114}
115
117static uint64_t now_ms(void)
118{
119 struct timeval tv;
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);
123}
124
141static bool tx_ctx_from_json(const cJSON *txobj, tron_tx_ctx_t *out)
142{
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)) {
147 return false;
148 }
149 if (strlen(txid->valuestring) != 64U) {
150 ESP_LOGE(TAG, "create: txID length %u",
151 (unsigned)strlen(txid->valuestring));
152 return false;
153 }
154 if (strlen(raw->valuestring) >= TRON_RAW_HEX_MAX) {
155 ESP_LOGE(TAG, "create: raw_data too long (%u)",
156 (unsigned)strlen(raw->valuestring));
157 return false;
158 }
159
160 uint8_t raw_bytes[RAW_BYTES_MAX];
161 size_t raw_len = hex_to_bytes(raw->valuestring, raw_bytes,
162 sizeof(raw_bytes));
163 if (raw_len == 0U) {
164 ESP_LOGE(TAG, "create: raw_data not hex");
165 return false;
166 }
167
168 uint8_t digest[32];
169 if (mbedtls_sha256(raw_bytes, raw_len, digest, 0) != 0) {
170 ESP_LOGE(TAG, "create: sha256 failed");
171 return false;
172 }
173 char digest_hex[65];
174 bytes_to_hex(digest, sizeof(digest), digest_hex);
175 if (strcasecmp(digest_hex, txid->valuestring) != 0) {
176 ESP_LOGE(TAG, "create: txID is not sha256(raw_data) - refusing");
177 return false;
178 }
179
180 (void)memcpy(out->txid, digest, sizeof(out->txid));
181 (void)snprintf(out->txid_hex, sizeof(out->txid_hex), "%s", digest_hex);
182 (void)snprintf(out->raw_hex, sizeof(out->raw_hex), "%s", raw->valuestring);
183 return true;
184}
185
186/******************************************************************
187 * 3. Public API
188 ******************************************************************/
189
190void tron_rpc_init(const char *base_url)
191{
192 s_base_url = base_url;
193}
194
195void tron_rpc_set_ca_cert(const char *ca_pem)
196{
197 s_ca_cert = ca_pem;
198}
199
211static uint64_t json_u64(const cJSON *n)
212{
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;
216}
217
218bool tron_rpc_get_balance(const char *owner_hex, uint64_t *sun_out)
219{
220 if ((owner_hex == NULL) || (sun_out == NULL)) { return false; }
221
222 char body[96];
223 (void)snprintf(body, sizeof(body),
224 "{\"address\":\"%s\",\"visible\":false}", owner_hex);
225
226 char resp[RESP_BUF_SIZE];
227 if (!tron_post("/wallet/getaccount", body, resp, sizeof(resp))) {
228 return false;
229 }
230
231 cJSON *root = cJSON_Parse(resp);
232 if (root == NULL) {
233 ESP_LOGE(TAG, "getaccount: not JSON: %.*s", RESP_LOG_MAX, resp);
234 return false;
235 }
236 *sun_out = json_u64(cJSON_GetObjectItemCaseSensitive(root, "balance"));
237 cJSON_Delete(root);
238 return true;
239}
240
241bool tron_rpc_get_energy(const char *owner_hex, uint64_t *energy_out)
242{
243 if ((owner_hex == NULL) || (energy_out == NULL)) { return false; }
244
245 char body[96];
246 (void)snprintf(body, sizeof(body),
247 "{\"address\":\"%s\",\"visible\":false}", owner_hex);
248
249 char resp[RESP_BUF_SIZE];
250 if (!tron_post("/wallet/getaccountresource", body, resp, sizeof(resp))) {
251 return false;
252 }
253
254 cJSON *root = cJSON_Parse(resp);
255 if (root == NULL) {
256 ESP_LOGE(TAG, "getaccountresource: not JSON: %.*s", RESP_LOG_MAX, resp);
257 return false;
258 }
259 const uint64_t limit =
260 json_u64(cJSON_GetObjectItemCaseSensitive(root, "EnergyLimit"));
261 const uint64_t used =
262 json_u64(cJSON_GetObjectItemCaseSensitive(root, "EnergyUsed"));
263 cJSON_Delete(root);
264
265 *energy_out = (limit > used) ? (limit - used) : 0U;
266 return true;
267}
268
269bool tron_rpc_get_trc20_balance(const char *owner_hex, const char *contract_hex,
270 uint64_t *units_out)
271{
272 if ((owner_hex == NULL) || (contract_hex == NULL) || (units_out == NULL)) {
273 return false;
274 }
275 /* The ABI argument is the 20-byte address left-padded to 32, so Tron's "41"
276 * prefix comes off first. Leaving it on would shift the padding by a byte
277 * and ask the contract about a different account — which it would answer,
278 * and the answer would be about somebody else. */
279 if (strlen(owner_hex) != TRON_ADDR_HEX_LEN) {
280 ESP_LOGE(TAG, "balanceOf: owner is not %u hex chars",
281 (unsigned)TRON_ADDR_HEX_LEN);
282 return false;
283 }
284 const char *owner20 = owner_hex + 2;
285
286 char body[320];
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);
293
294 char resp[RESP_BUF_SIZE];
295 if (!tron_post("/wallet/triggerconstantcontract", body,
296 resp, sizeof(resp))) {
297 return false;
298 }
299
300 cJSON *root = cJSON_Parse(resp);
301 if (root == NULL) {
302 ESP_LOGE(TAG, "balanceOf: not JSON: %.*s", RESP_LOG_MAX, resp);
303 return false;
304 }
305 const cJSON *arr =
306 cJSON_GetObjectItemCaseSensitive(root, "constant_result");
307 const cJSON *first = cJSON_IsArray(arr) ? cJSON_GetArrayItem(arr, 0) : NULL;
308
309 bool ok = false;
310 if (cJSON_IsString(first) && (first->valuestring != NULL)) {
311 const size_t n = strlen(first->valuestring);
312 /* A uint256 comes back as 64 characters. Anything longer is not a
313 * number this can read — and must not be truncated into one, because a
314 * padded value truncated from the right loses its significant digits
315 * and would report a funded account as nearly empty. */
316 if ((n > 0U) && (n <= 64U)) {
317 char hex[80];
318 (void)snprintf(hex, sizeof(hex), "0x%s", first->valuestring);
319 ok = eth_json_hex_quantity(hex, units_out);
320 }
321 }
322 if (!ok) {
323 ESP_LOGE(TAG, "balanceOf: no usable constant_result: %.*s",
324 RESP_LOG_MAX, resp);
325 }
326 cJSON_Delete(root);
327 return ok;
328}
329
330bool tron_rpc_get_trc20_decimals(const char *contract_hex, uint64_t *dec_out)
331{
332 if ((contract_hex == NULL) || (dec_out == NULL) ||
333 (strlen(contract_hex) != TRON_ADDR_HEX_LEN)) {
334 return false;
335 }
336 /* A constant call needs some owner; the contract itself will do. */
337 char body[256];
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; }
343
344 char resp[RESP_BUF_SIZE];
345 if (!tron_post("/wallet/triggerconstantcontract", body, resp, sizeof(resp))) {
346 return false;
347 }
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;
352 bool ok = false;
353 if (cJSON_IsString(first) && (first->valuestring != NULL) &&
354 (strlen(first->valuestring) > 0U) && (strlen(first->valuestring) <= 64U)) {
355 char hex[80];
356 (void)snprintf(hex, sizeof(hex), "0x%s", first->valuestring);
357 ok = eth_json_hex_quantity(hex, dec_out);
358 }
359 if (!ok) {
360 ESP_LOGE(TAG, "decimals: no usable answer: %.*s", RESP_LOG_MAX, resp);
361 }
362 cJSON_Delete(root);
363 return ok;
364}
365
366bool tron_rpc_create_transfer(const char *owner_hex, const char *to_hex,
367 uint64_t amount_sun, tron_tx_ctx_t *out)
368{
369 if ((owner_hex == NULL) || (to_hex == NULL) || (out == NULL) ||
370 (amount_sun == 0U)) {
371 return false;
372 }
373
374 char body[192];
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; }
380
381 char resp[RESP_BUF_SIZE];
382 if (!tron_post("/wallet/createtransaction", body, resp, sizeof(resp))) {
383 return false;
384 }
385
386 cJSON *root = cJSON_Parse(resp);
387 if (root == NULL) {
388 ESP_LOGE(TAG, "create: not JSON: %.*s", RESP_LOG_MAX, resp);
389 return false;
390 }
391
392 /* Tron reports bad parameters as {"Error":"..."} with HTTP 200. */
393 bool ok = tx_ctx_from_json(root, out);
394 if (!ok) {
395 ESP_LOGE(TAG, "create: unusable answer: %.*s", RESP_LOG_MAX, resp);
396 } else if (!tron_tx_contract_ok(out->raw_hex, owner_hex, to_hex,
397 amount_sun, now_ms(),
398 &out->expiration_ms)) {
399 /* The node is a trust boundary — see tron_tx.h. */
400 ESP_LOGE(TAG, "create: raw_data does not match the requested transfer");
401 ok = false;
402 } else {
403 ESP_LOGI(TAG, "Tron tx %s (%" PRIu64 " sun)", out->txid_hex,
404 amount_sun);
405 }
406
407 cJSON_Delete(root);
408 return ok;
409}
410
411bool tron_rpc_create_trc20_transfer(const char *owner_hex,
412 const char *contract_hex,
413 const char *to_hex, uint64_t amount,
414 uint64_t fee_limit_sun,
415 tron_tx_ctx_t *out)
416{
417 if ((owner_hex == NULL) || (contract_hex == NULL) || (to_hex == NULL) ||
418 (out == NULL) || (amount == 0U) || (fee_limit_sun == 0U)) {
419 return false;
420 }
421
422 /* Build the calldata arguments ourselves rather than let the node infer
423 * them: these same bytes are what tron_tx_trc20_ok looks for afterwards. */
424 char param[TRON_TRC20_PARAM_HEX_LEN + 1U];
425 if (tron_trc20_param_hex(to_hex, amount, param, sizeof(param)) == 0U) {
426 ESP_LOGE(TAG, "trc20: bad recipient");
427 return false;
428 }
429
430 char body[512];
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; }
438
439 char resp[RESP_BUF_SIZE];
440 if (!tron_post("/wallet/triggersmartcontract", body, resp, sizeof(resp))) {
441 return false;
442 }
443
444 cJSON *root = cJSON_Parse(resp);
445 if (root == NULL) {
446 ESP_LOGE(TAG, "trc20: not JSON: %.*s", RESP_LOG_MAX, resp);
447 return false;
448 }
449
450 /* Unlike createtransaction, this one nests the transaction one level down;
451 * a rejected call ({"result":{"code":"CONTRACT_VALIDATE_ERROR",...}})
452 * simply has no "transaction" member. */
453 const cJSON *txobj = cJSON_GetObjectItemCaseSensitive(root, "transaction");
454 bool ok = cJSON_IsObject(txobj) && tx_ctx_from_json(txobj, out);
455 if (!ok) {
456 ESP_LOGE(TAG, "trc20: unusable answer: %.*s", RESP_LOG_MAX, resp);
457 } else if (!tron_tx_trc20_ok(out->raw_hex, owner_hex, contract_hex, to_hex,
458 amount, fee_limit_sun, now_ms(),
459 &out->expiration_ms)) {
460 ESP_LOGE(TAG, "trc20: raw_data does not match the requested transfer");
461 ok = false;
462 } else {
463 ESP_LOGI(TAG, "Tron TRC-20 tx %s (%" PRIu64 " units of %s)",
464 out->txid_hex, amount, contract_hex);
465 }
466
467 cJSON_Delete(root);
468 return ok;
469}
470
471bool tron_rpc_broadcast(const tron_tx_ctx_t *tx, const uint8_t sig[65])
472{
473 if ((tx == NULL) || (sig == NULL)) { return false; }
474
475 char sig_hex[TRON_SIG_HEX_LEN + 1U];
476 bytes_to_hex(sig, 65U, sig_hex);
477
478 /* "0a" + varint len + raw_data + "1241" + signature */
479 char envelope[TRON_RAW_HEX_MAX + TRON_SIG_HEX_LEN + 16U];
480 if (tron_tx_envelope_hex(tx->raw_hex, sig_hex,
481 envelope, sizeof(envelope)) == 0U) {
482 ESP_LOGE(TAG, "broadcast: envelope build failed");
483 return false;
484 }
485
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; }
489
490 char resp[RESP_BUF_SIZE];
491 if (!tron_post("/wallet/broadcasthex", body, resp, sizeof(resp))) {
492 return false;
493 }
494
495 bool ok = false;
496 cJSON *root = cJSON_Parse(resp);
497 if (root == NULL) {
498 ESP_LOGE(TAG, "broadcast: not JSON: %.*s", RESP_LOG_MAX, resp);
499 return false;
500 }
501 const cJSON *result = cJSON_GetObjectItemCaseSensitive(root, "result");
502 ok = cJSON_IsTrue(result);
503 if (!ok) {
504 /* code/message carry the reason (SIGERROR, BANDWIDTH_ERROR, ...). */
505 ESP_LOGE(TAG, "broadcast rejected: %.*s", RESP_LOG_MAX, resp);
506 }
507 cJSON_Delete(root);
508 return ok;
509}
510
512{
513 if (txid_hex == NULL) { return TRON_RECEIPT_RPC_ERROR; }
514
515 char body[96];
516 int k = snprintf(body, sizeof(body), "{\"value\":\"%s\"}", txid_hex);
517 if ((k <= 0) || (static_cast<size_t>(k) >= sizeof(body))) {
519 }
520
521 char resp[RESP_BUF_SIZE];
522 if (!tron_post("/wallet/gettransactioninfobyid", body,
523 resp, sizeof(resp))) {
525 }
526
527 cJSON *root = cJSON_Parse(resp);
528 if (!cJSON_IsObject(root)) {
529 cJSON_Delete(root);
531 }
532
533 /* Not in a block yet: the node answers with an empty object. */
535 const cJSON *result = cJSON_GetObjectItemCaseSensitive(root, "result");
536 const cJSON *block = cJSON_GetObjectItemCaseSensitive(root, "blockNumber");
537 /* A TRC-20 call that ran out of energy or reverted is mined but did not
538 * move any tokens; receipt.result carries that verdict (OUT_OF_ENERGY,
539 * REVERT, ...) and only "SUCCESS" means the operator got paid. */
540 const cJSON *rcpt = cJSON_GetObjectItemCaseSensitive(root, "receipt");
541 const cJSON *rres = cJSON_IsObject(rcpt)
542 ? cJSON_GetObjectItemCaseSensitive(rcpt, "result")
543 : NULL;
544 if (cJSON_IsString(result) && (result->valuestring != NULL) &&
545 (strcmp(result->valuestring, "FAILED") == 0)) {
546 verdict = TRON_RECEIPT_FAILED;
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);
550 verdict = TRON_RECEIPT_FAILED;
551 } else if (cJSON_IsNumber(block)) {
552 verdict = TRON_RECEIPT_SUCCESS;
553 }
554 cJSON_Delete(root);
555 return verdict;
556}
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...
Definition eth_json.h:135
static const char *const TAG
Definition eth_rpc.cpp:33
#define RESP_BUF_SIZE
Definition eth_rpc.cpp:36
static const char * s_ca_cert
Definition eth_rpc.cpp:60
#define RESP_LOG_MAX
Definition eth_rpc.cpp:43
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.
Definition tron_rpc.h:38
uint8_t txid[32]
Definition tron_rpc.h:39
char txid_hex[65]
Definition tron_rpc.h:40
char raw_hex[TRON_RAW_HEX_MAX]
Definition tron_rpc.h:41
uint64_t expiration_ms
Definition tron_rpc.h:42
#define RAW_BYTES_MAX
Definition tron_rpc.cpp:42
static uint64_t json_u64(const cJSON *n)
A cJSON number as a uint64, clamped at both ends.
Definition tron_rpc.cpp:211
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.
Definition tron_rpc.cpp:269
bool tron_rpc_get_balance(const char *owner_hex, uint64_t *sun_out)
Read an account's TRX balance, in sun.
Definition tron_rpc.cpp:218
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.
Definition tron_rpc.cpp:411
static uint64_t now_ms(void)
Unix time in ms, 0 if the clock is unset (the checks then refuse).
Definition tron_rpc.cpp:117
bool tron_rpc_get_energy(const char *owner_hex, uint64_t *energy_out)
Read the energy an account still has available.
Definition tron_rpc.cpp:241
void tron_rpc_set_ca_cert(const char *ca_pem)
Optional: pin the Tron endpoint's TLS certificate.
Definition tron_rpc.cpp:195
bool tron_rpc_broadcast(const tron_tx_ctx_t *tx, const uint8_t sig[65])
Broadcast a created transfer with its 65-byte signature.
Definition tron_rpc.cpp:471
static int nibble_val(char c)
Definition tron_rpc.cpp:57
static void bytes_to_hex(const uint8_t *data, size_t len, char *out)
Hex-encode len bytes into out (NUL-terminated).
Definition tron_rpc.cpp:89
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.
Definition tron_rpc.cpp:99
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.
Definition tron_rpc.cpp:141
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.
Definition tron_rpc.cpp:366
tron_receipt_t tron_rpc_get_receipt(const char *txid_hex)
Poll a broadcast transaction's receipt (one shot).
Definition tron_rpc.cpp:511
static char nibble_hex(unsigned n)
Definition tron_rpc.cpp:51
static size_t hex_to_bytes(const char *hex, uint8_t *out, size_t out_size)
Decode a hex string into bytes.
Definition tron_rpc.cpp:73
static const char * s_base_url
Definition tron_rpc.cpp:44
void tron_rpc_init(const char *base_url)
Set the Tron HTTP API base URL (no trailing slash).
Definition tron_rpc.cpp:190
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.
Definition tron_rpc.cpp:330
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.
Definition tron_rpc.h:35
tron_receipt_t
Outcome of one gettransactioninfobyid poll.
Definition tron_rpc.h:47
@ TRON_RECEIPT_FAILED
Definition tron_rpc.h:50
@ TRON_RECEIPT_RPC_ERROR
Definition tron_rpc.h:51
@ TRON_RECEIPT_SUCCESS
Definition tron_rpc.h:49
@ TRON_RECEIPT_PENDING
Definition tron_rpc.h:48
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.
Definition tron_tx.cpp:316
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.
Definition tron_tx.cpp:365
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.
Definition tron_tx.cpp:341
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.
Definition tron_tx.cpp:406
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).
Definition tron_tx.h:38
#define TRON_SIG_HEX_LEN
Hex length of a Tron signature (r || s || v).
Definition tron_tx.h:41
#define TRON_TRC20_PARAM_HEX_LEN
Hex length of the two ABI argument words that follow the selector.
Definition tron_tx.h:47