33static const char *
const TAG =
"eth_rpc";
36#define RESP_BUF_SIZE 1024U
39#define HEX_PER_BYTE 2U
43#define RESP_LOG_MAX 80
47#define NONCE_MAX 0xFFFFFFFFULL
50#define RESULT_STR_MAX 80U
77static bool do_post(
const char *body,
char *resp_buf,
size_t resp_buf_size)
95 return (n < 10U) ?
static_cast<char>(
'0' + n)
96 :
static_cast<char>(
'a' + n - 10U);
109 for (i = 0U; i < len; i++) {
135 if (addr == NULL) {
return false; }
136 if ((addr[0] !=
'0') || ((addr[1] !=
'x') && (addr[1] !=
'X'))) {
139 if (strlen(addr) != 42U) {
return false; }
160 (void)snprintf(body,
sizeof(body),
161 "{\"jsonrpc\":\"2.0\",\"method\":\"eth_getTransactionCount\","
162 "\"params\":[\"%s\",\"latest\"],\"id\":1}",
166 if (!
do_post(body, resp,
sizeof(resp))) {
175 if (strncmp(result,
"0x", 2U) != 0) {
181 uint64_t nonce = strtoull(result + 2, &end, 16);
182 if ((end == (result + 2)) || (*end !=
'\0')) {
188 ESP_LOGE(
TAG,
"nonce: out of range: %" PRIu64, nonce);
193 ESP_LOGI(
TAG,
"Nonce: %" PRIu64, nonce);
206 if ((p != NULL) && (p[0] ==
'0') && ((p[1] ==
'x') || (p[1] ==
'X'))) {
214 if ((wei_out == NULL) || (
s_from_addr == NULL)) {
return false; }
217 (void)snprintf(body,
sizeof(body),
218 "{\"jsonrpc\":\"2.0\",\"method\":\"eth_getBalance\","
219 "\"params\":[\"%s\",\"latest\"],\"id\":4}",
223 if (!
do_post(body, resp,
sizeof(resp))) {
233 ESP_LOGE(
TAG,
"balance: malformed quantity: %.*s",
RESP_LOG_MAX, result);
241 if ((token_addr == NULL) || (units_out == NULL) || (
s_from_addr == NULL)) {
250 if (strlen(from_hex) != 40U) {
251 ESP_LOGE(
TAG,
"token balance: from_addr is not 20 bytes");
257 (void)snprintf(body,
sizeof(body),
258 "{\"jsonrpc\":\"2.0\",\"method\":\"eth_call\","
259 "\"params\":[{\"to\":\"%s\",\"data\":\"0x70a08231"
260 "000000000000000000000000%s\"},\"latest\"],\"id\":5}",
261 token_addr, from_hex);
264 if (!
do_post(body, resp,
sizeof(resp))) {
270 ESP_LOGE(
TAG,
"token balance: no result in: %.*s",
RESP_LOG_MAX, resp);
279 ESP_LOGE(
TAG,
"token balance: malformed quantity: %.*s",
287 char *tx_hash_out,
size_t tx_hash_max,
288 char *err_out,
size_t err_max)
294 if ((err_out != NULL) && (err_max > 0U)) { err_out[0] =
'\0'; }
298 char *tx_hex =
static_cast<char *
>(malloc(hex_str_size));
299 if (tx_hex == NULL) {
return false; }
304 tx_hex[hex_str_size - 1U] =
'\0';
307 size_t body_size = hex_str_size + 128U;
308 char *body =
static_cast<char *
>(malloc(body_size));
309 if (body == NULL) { free(tx_hex);
return false; }
311 (void)snprintf(body, body_size,
312 "{\"jsonrpc\":\"2.0\",\"method\":\"eth_sendRawTransaction\","
313 "\"params\":[\"%s\"],\"id\":2}",
318 bool ok =
do_post(body, resp,
sizeof(resp));
321 if (!
ok) {
return false; }
331 if ((err_out != NULL) && (err_max > 0U)) {
336 if (strncmp(result,
"0x", 2U) != 0) {
337 ESP_LOGE(
TAG,
"send_raw_tx: result not a hash: %.*s",
342 size_t hash_len = strlen(result);
343 if ((hash_len + 1U) > tx_hash_max) {
return false; }
345 (void)CW_Utils::safe_memcpy(
reinterpret_cast<uint8_t *
>(tx_hash_out),
347 reinterpret_cast<const uint8_t *
>(result),
349 tx_hash_out[hash_len] =
'\0';
350 ESP_LOGI(
TAG,
"Tx hash: %s", tx_hash_out);
356 if ((token_addr == NULL) || (dec_out == NULL)) {
return false; }
358 int k = snprintf(body,
sizeof(body),
359 "{\"jsonrpc\":\"2.0\",\"method\":\"eth_call\","
360 "\"params\":[{\"to\":\"%s\",\"data\":\"0x313ce567\"},"
361 "\"latest\"],\"id\":6}",
363 if ((k <= 0) || (
static_cast<size_t>(k) >=
sizeof(body))) {
return false; }
366 if (!
do_post(body, resp,
sizeof(resp))) {
return false; }
379 return (node_err != NULL) &&
380 ((strstr(node_err,
"already known") != NULL) ||
381 (strstr(node_err,
"known transaction") != NULL) ||
382 (strstr(node_err,
"ALREADY_EXISTS") != NULL));
389 (void)snprintf(body,
sizeof(body),
390 "{\"jsonrpc\":\"2.0\",\"method\":\"eth_getTransactionReceipt\","
391 "\"params\":[\"%s\"],\"id\":3}",
397 const size_t resp_size = 4096U;
398 char *resp =
static_cast<char *
>(malloc(resp_size));
402 if (
do_post(body, resp, resp_size)) {
bool eth_json_error_message(const char *resp, char *out, size_t out_size)
Extract the JSON-RPC error.message from a response body.
bool eth_json_result_string(const char *resp, char *out, size_t out_size)
Extract the top-level "result" string from a JSON-RPC response.
eth_json_receipt_t eth_json_receipt_check(const char *resp, const eth_receipt_expect_t *want)
Classify a receipt AND check it is the payment that was asked for.
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...
@ ETH_JSON_RECEIPT_REVERTED
@ ETH_JSON_RECEIPT_PENDING
@ ETH_JSON_RECEIPT_SUCCESS
@ ETH_JSON_RECEIPT_MISMATCH
static bool do_post(const char *body, char *resp_buf, size_t resp_buf_size)
POST a JSON-RPC body to the configured endpoint over HTTPS.
static const char * s_api_secret
static const char * s_from_addr
eth_rpc_receipt_result_t eth_rpc_get_tx_receipt(const eth_receipt_expect_t *want)
Poll the receipt of a broadcast transaction (one shot).
bool eth_rpc_get_nonce(uint64_t *nonce_out)
Fetch the confirmed transaction count (nonce) for from_addr.
static const char *const TAG
static const char * s_rpc_url
void eth_rpc_init(const char *rpc_url, const char *from_addr)
Set the RPC URL and the from-address used for nonce queries and ecrecover comparison.
void eth_rpc_set_auth(const char *project_id, const char *api_secret)
Optional: set Infura-style HTTP Basic Auth credentials.
static char hex_nibble(uint8_t n)
Convert a nibble value to its lowercase ASCII hex digit.
static const char * s_project_id
static void bytes_to_hex(const uint8_t *data, size_t len, char *out)
Hex-encode a byte buffer (lowercase, no prefix, no NUL).
bool eth_rpc_get_token_decimals(const char *token_addr, uint64_t *dec_out)
Read an ERC-20 contract's decimals().
bool eth_rpc_get_balance(uint64_t *wei_out)
Fetch the native balance of from_addr, in wei.
bool eth_rpc_get_token_balance(const char *token_addr, uint64_t *units_out)
Fetch from_addr's balance of an ERC-20, via balanceOf over eth_call.
bool eth_rpc_send_raw_tx(const uint8_t *tx, size_t tx_len, char *tx_hash_out, size_t tx_hash_max, char *err_out, size_t err_max)
Broadcast a raw signed transaction (type-prefixed RLP bytes).
bool eth_rpc_err_already_known(const char *node_err)
true if a broadcast error message means the node already HAS this transaction ("already known",...
static const char * from_no_prefix(void)
The configured from-address with any "0x" prefix removed.
void eth_rpc_set_ca_cert(const char *ca_pem)
Optional: pin the RPC endpoint's TLS certificate.
static const char * s_ca_cert
static char s_from_buf[43]
bool eth_rpc_set_from(const char *addr)
Replace the from-address — the account a sale spends from.
Ethereum JSON-RPC client over HTTPS (nonce / ecrecover parity / raw-tx broadcast / receipt polling)....
eth_rpc_receipt_result_t
Outcome of one eth_getTransactionReceipt poll.
@ ETH_RPC_RECEIPT_RPC_ERROR
@ ETH_RPC_RECEIPT_MISMATCH
@ ETH_RPC_RECEIPT_REVERTED
@ ETH_RPC_RECEIPT_PENDING
@ ETH_RPC_RECEIPT_SUCCESS
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.
What a receipt must show for the payment to count — see eth_json_receipt_check.