cryptnox-pos 1.0.0
Standalone USDC payment terminal firmware (ESP32 + Cryptnox smart card)
Loading...
Searching...
No Matches
money.h
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
19
20#ifndef MONEY_H
21#define MONEY_H
22
23#include <inttypes.h>
24#include <stdbool.h>
25#include <stddef.h>
26#include <stdint.h>
27#include <stdio.h>
28#include <string.h>
29
30#include "CW_Utils.h" /* secure_wipe / safe_memcpy (CODING_RULES §1.4) */
31#include "eth_addr.h" /* ETH_ADDR_LEN */
32
33/******************************************************************
34 * Keypad amounts (ui.cpp)
35 ******************************************************************/
36
37/* 9999.99 of any asset: plenty for a counter terminal, and it keeps the
38 * figure, the cents and the asset selector inside the amount row. */
39#define AMOUNT_CENTS_MAX 999999ULL /* 9999.99 */
40/* The same ceiling in 6-decimal base units. */
41#define AMOUNT_UNITS_MAX (AMOUNT_CENTS_MAX * 10000ULL)
42
44static inline uint64_t amount_key_digit(uint64_t cents, unsigned digit, uint64_t cap)
45{
46 const uint64_t n = (cents * 10ULL) + static_cast<uint64_t>(digit);
47 return (n <= cap) ? n : cents;
48}
49
51static inline uint64_t amount_key_00(uint64_t cents, uint64_t cap)
52{
53 const uint64_t n = cents * 100ULL;
54 return (n > cap) ? cap : n;
55}
56
58static inline uint64_t amount_key_back(uint64_t cents)
59{
60 return cents / 10ULL;
61}
62
64static inline uint64_t amount_cents_to_units(uint64_t cents)
65{
66 return cents * 10000ULL;
67}
68
70static inline void amount_format(uint64_t units, char *out, size_t n)
71{
72 uint64_t whole = units / 1000000ULL;
73 uint64_t cents = (units % 1000000ULL) / 10000ULL;
74 snprintf(out, n, "%" PRIu64 ".%02" PRIu64, whole, cents);
75}
76
77/******************************************************************
78 * Units, wei and fees (main.cpp)
79 ******************************************************************/
80
82#define WEI_LEN 32U
83
85static inline void wei_from_u64(uint64_t v, uint8_t out[WEI_LEN])
86{
87 for (size_t i = 0U; i < WEI_LEN; i++) {
88 out[(WEI_LEN - 1U) - i] =
89 (i < 8U) ? static_cast<uint8_t>((v >> (8U * i)) & 0xFFU) : 0U;
90 }
91}
92
95static inline void wei_add(uint8_t a[WEI_LEN], const uint8_t b[WEI_LEN])
96{
97 unsigned carry = 0U;
98 for (size_t i = WEI_LEN; i > 0U; i--) {
99 const unsigned s = static_cast<unsigned>(a[i - 1U]) + b[i - 1U] + carry;
100 a[i - 1U] = static_cast<uint8_t>(s & 0xFFU);
101 carry = s >> 8;
102 }
103}
104
106static inline bool wei_lt(const uint8_t a[WEI_LEN], const uint8_t b[WEI_LEN])
107{
108 return memcmp(a, b, WEI_LEN) < 0;
109}
110
122static inline bool evm_units_to_wei(uint64_t units, uint8_t wei[WEI_LEN])
123{
124 if (units > AMOUNT_UNITS_MAX) { return false; }
125 const uint64_t x = units * 1000000ULL; /* < 2^54 */
126 uint8_t lo[WEI_LEN];
127 uint8_t hi[WEI_LEN];
128 wei_from_u64((x & 0xFFFFFFFFULL) * 1000000ULL, lo); /* < 2^52 */
129 wei_from_u64((x >> 32) * 1000000ULL, hi); /* < 2^42 */
130 /* hi << 32: four bytes towards the front */
131 (void)memmove(hi, hi + 4U, WEI_LEN - 4U);
132 (void)memset(hi + (WEI_LEN - 4U), 0, 4U);
133 wei_add(lo, hi);
134 (void)CW_Utils::safe_memcpy(wei, WEI_LEN, lo, WEI_LEN);
135 return true;
136}
137
149static inline void evm_fees_from_gwei(uint32_t max_gwei, uint32_t prio_gwei,
150 bool polygon, uint32_t floor_gwei,
151 uint64_t *max_fee, uint64_t *prio_fee)
152{
153 /* The user edits Gwei, so scale to wei. Keep the tip <= the cap or the tx
154 * is malformed. */
155 uint64_t max_fee_wei = (uint64_t)max_gwei * 1000000000ULL;
156 uint64_t prio_fee_wei = (uint64_t)prio_gwei * 1000000000ULL;
157 if (prio_fee_wei > max_fee_wei) { prio_fee_wei = max_fee_wei; }
158 /* Polygon drops a transfer whose tip is under ~25 Gwei, and the fee knobs are
159 * shared with Ethereum where 20 is right. Raise the floor here rather than
160 * asking the operator to retune the Tx tab every time they switch networks —
161 * and lift the cap with it, or the clamp above would only put it back. */
162 if (polygon) {
163 const uint64_t floor_wei = (uint64_t)floor_gwei * 1000000000ULL;
164 if (prio_fee_wei < floor_wei) { prio_fee_wei = floor_wei; }
165 if (max_fee_wei < prio_fee_wei) { max_fee_wei = prio_fee_wei; }
166 }
167 *max_fee = max_fee_wei;
168 *prio_fee = prio_fee_wei;
169}
170
178
187static inline evm_funds_t evm_funds_check(bool native,
188 const uint8_t have_wei[WEI_LEN],
189 uint64_t gas_cost, uint64_t units)
190{
191 uint8_t need[WEI_LEN];
192 wei_from_u64(gas_cost, need);
193 if (wei_lt(have_wei, need)) { return EVM_FUNDS_SHORT_GAS; }
194 if (!native) { return EVM_FUNDS_OK; }
195 uint8_t value_wei[WEI_LEN];
196 if (!evm_units_to_wei(units, value_wei)) { return EVM_FUNDS_UNKNOWN; }
197 /* Under 2^64 + 2^74: the uint256 sum cannot wrap. */
198 wei_add(need, value_wei);
199 if (wei_lt(have_wei, need)) { return EVM_FUNDS_SHORT_VALUE; }
200 return EVM_FUNDS_OK;
201}
202
207static inline void fmt_coin(char *out, size_t n, uint64_t v, unsigned dec,
208 const char *coin)
209{
210 uint64_t div = 1U;
211 for (unsigned i = 6U; i < dec; i++) { div *= 10U; }
212 const uint64_t micro = (v / div) + (((v % div) != 0U) ? 1U : 0U);
213 char frac[8];
214 (void)snprintf(frac, sizeof(frac), "%06" PRIu64, micro % 1000000U);
215 size_t f = strlen(frac);
216 while ((f > 0U) && (frac[f - 1U] == '0')) { frac[--f] = '\0'; }
217 (void)snprintf(out, n, "%" PRIu64 "%s%s %s", micro / 1000000U,
218 (f > 0U) ? "." : "", frac, coin);
219}
220
221/******************************************************************
222 * USDC transfer calldata (main.cpp)
223 ******************************************************************/
224
225/* ── ERC-20 transfer(address,uint256) selector + calldata ── */
226static const uint8_t TRANSFER_SELECTOR[4] = { 0xa9U, 0x05U, 0x9cU, 0xbbU };
227#define ABI_SELECTOR_LEN 4U /* transfer(address,uint256) selector */
228#define ABI_WORD_LEN 32U /* one ABI-encoded argument word */
229#define USDC_CALLDATA_LEN (ABI_SELECTOR_LEN + (2U * ABI_WORD_LEN)) /* 68 */
230#define ABI_TO_OFFSET (ABI_SELECTOR_LEN + (ABI_WORD_LEN - ETH_ADDR_LEN))
231
246static inline void build_usdc_calldata(uint8_t out[USDC_CALLDATA_LEN],
247 const uint8_t to[ETH_ADDR_LEN],
248 uint64_t amount)
249{
250 CW_Utils::secure_wipe(out, USDC_CALLDATA_LEN);
251 (void)CW_Utils::safe_memcpy(out, USDC_CALLDATA_LEN,
253 (void)CW_Utils::safe_memcpy(out + ABI_TO_OFFSET,
255 to, ETH_ADDR_LEN);
256
257 size_t j;
258 for (j = 0U; j < sizeof(amount); j++) {
259 out[(USDC_CALLDATA_LEN - 1U) - j] =
260 static_cast<uint8_t>((amount >> (8U * j)) & 0xFFU);
261 }
262}
263
264#endif /* MONEY_H */
Hex Ethereum-address parsing — a pure, dependency-free unit so it can be unit-tested and fuzzed on th...
#define ETH_ADDR_LEN
Length of a raw (binary) Ethereum address, in bytes.
Definition eth_addr.h:25
static const uint8_t TRANSFER_SELECTOR[4]
Definition money.h:226
#define WEI_LEN
How wei is carried: a uint256, 32 bytes big-endian, as on chain.
Definition money.h:82
static void wei_add(uint8_t a[WEI_LEN], const uint8_t b[WEI_LEN])
a += b. A carry out of the top byte is dropped; every caller here stays below 2^80.
Definition money.h:95
#define AMOUNT_UNITS_MAX
Definition money.h:41
static void wei_from_u64(uint64_t v, uint8_t out[WEI_LEN])
v as a uint256.
Definition money.h:85
static evm_funds_t evm_funds_check(bool native, const uint8_t have_wei[WEI_LEN], uint64_t gas_cost, uint64_t units)
Can have_wei pay for this sale?
Definition money.h:187
static void evm_fees_from_gwei(uint32_t max_gwei, uint32_t prio_gwei, bool polygon, uint32_t floor_gwei, uint64_t *max_fee, uint64_t *prio_fee)
The EIP-1559 fees one EVM sale will offer, in wei per gas, from the operator's Gwei settings.
Definition money.h:149
#define ABI_TO_OFFSET
Definition money.h:230
static void fmt_coin(char *out, size_t n, uint64_t v, unsigned dec, const char *coin)
"0.0013 ETH": v base units of a dec-decimal coin, to six places, rounded UP — it is a ceiling,...
Definition money.h:207
static bool wei_lt(const uint8_t a[WEI_LEN], const uint8_t b[WEI_LEN])
a < b. Equal-length big-endian, so byte order is numeric order.
Definition money.h:106
#define ABI_SELECTOR_LEN
Definition money.h:227
evm_funds_t
What the pre-flight balance check concluded.
Definition money.h:172
@ EVM_FUNDS_OK
Definition money.h:173
@ EVM_FUNDS_UNKNOWN
Definition money.h:176
@ EVM_FUNDS_SHORT_VALUE
Definition money.h:175
@ EVM_FUNDS_SHORT_GAS
Definition money.h:174
static uint64_t amount_key_00(uint64_t cents, uint64_t cap)
The "00" key: two zeroes shifted in, clamped to cap.
Definition money.h:51
static void build_usdc_calldata(uint8_t out[USDC_CALLDATA_LEN], const uint8_t to[ETH_ADDR_LEN], uint64_t amount)
Build the 68-byte ABI-encoded calldata for a USDC transfer call.
Definition money.h:246
static uint64_t amount_key_back(uint64_t cents)
The backspace key: the last digit dropped.
Definition money.h:58
static void amount_format(uint64_t units, char *out, size_t n)
"12.50": 6-decimal base units to two places, truncated.
Definition money.h:70
static uint64_t amount_key_digit(uint64_t cents, unsigned digit, uint64_t cap)
A digit shifted in from the right; refused whole if it passes cap.
Definition money.h:44
static uint64_t amount_cents_to_units(uint64_t cents)
Keypad cents to 6-decimal base units.
Definition money.h:64
#define USDC_CALLDATA_LEN
Definition money.h:229
static bool evm_units_to_wei(uint64_t units, uint8_t wei[WEI_LEN])
6-decimal keypad units -> wei, for the 18-decimal coins only.
Definition money.h:122