Finance
Financial applications typically need functionality like secure storage for storing balances and transaction history, networking for interacting with blockchain (1) or other financial APIs, cryptography for secure signing and verifying transactions, and configuration management for handling user settings.
Of course, we should add highly accurate numerical calculations as an essential feature.
Libraries
Many Boost libraries should assist you in building a finance app, here are some to consider:
-
Boost.Uuid : A useful utility library for generating unique identifiers.
-
Boost.Serialization : To save and restore data.
-
Boost.Hash2 : Boost.Hash2 : Hashing algorithms play a crucial role in financial applications by ensuring data integrity, authentication, and security in various transactions and records. Hashing enables fingerprinting of transactions, invoices, and records to detect duplicate or modified entries. Boost.Hash2 is a new library, first available in Boost version 1.88.
-
Boost.Interprocess : Allows for shared memory communication and synchronization between processes. It’s useful for creating shared memory regions, handling inter-process communication, managing shared objects, and synchronizing processes.
-
Boost.Multiprecision : For extended precision arithmetic.
-
Boost.Asio : If your app has network-related features, and its hard to envisage a finance app that does not, this library provides a consistent asynchronous model for network programming.
-
Boost.Beast : Built on top of Boost.Asio this library provides implementations of HTTP and WebSocket. These are common protocols for network programming.
-
Boost.Json : An efficient library for parsing, serializing, and manipulating JSON data. This is useful specifically in client-server communication and web services. Also, if you are working with large JSON payloads, there is support for incremental parsing, so you can feed data to the parser as it arrives over the network.
-
Boost.ProgramOptions : Allows program options to be defined, with types and default values, and their values to be retrieved from the command line, from config files, and programmatically.
-
Boost.DateTime or Boost.Chrono: If you need to timestamp changes or edits, or if you’re implementing a version history feature, these libraries provide the required functions.
-
Boost.PropertyTree : Provides a hierarchical data structure for representing and manipulating structured data, such as XML, JSON, INI, or property list formats. You can use it to parse, validate, and sanitize input data in various formats, ensuring that it conforms to expected schema or constraints before further processing.
- Notes
-
The code in this tutorial was written and tested using Microsoft Visual Studio (Visual C++ 2022, Console App project) with Boost version 1.88.0.
In Visual Studio, the C++ Language Standard (in the Project Properties C++ Language section) has been set to
ISO C17 Standard (/std:c17). The libsodium library has been installed with the following Git Bash commands:git clone https://github.com/Microsoft/vcpkg.git cd vcpkg ./bootstrap-vcpkg.bat ./vcpkg install libsodium ./vcpkg integrate installFinally, the location of the libsodium headers has been added to the Additional Include Libraries section of the Project Properties.
Signing Transactions
Encrypting and securing transactions, one of the first tasks developers of a financial app should investigate, is a never-ending journey.
Public-key cryptography, also known as asymmetric cryptography, was first conceptualized in the 1970s by Whitfield Diffie and Martin Hellman, who introduced the idea of key pairs — one public and one private — for secure communication. This breakthrough solved a major problem in cryptography: how to securely share encryption keys over an untrusted channel. Shortly after, Rivest, Shamir, and Adleman developed the RSA algorithm, which became one of the earliest widely used public-key systems, relying on the mathematical difficulty of factoring large prime numbers.
As cryptography evolved, new methods based on elliptic curves gained popularity due to their superior security per bit compared to RSA. The ECDSA or Elliptic Curve Digital Signature Algorithm (2) emerged as a widely adopted standard, particularly in financial applications and cryptocurrencies like Bitcoin. However, ECDSA has limitations in security and performance, leading to the development of EdDSA or Edwards-curve Digital Signature Algorithm (3), which offers improved speed, security, and resistance to side-channel attacks. The most well-known variant, Ed25519, is now favored in many modern cryptographic applications for its efficiency and robust security guarantees.
If you are writing a serious financial app, then you could also research Schnorr Signatures. Unlike ECDSA, where an attacker can slightly modify a valid signature to create a new one, Schnorr Signatures prevent this, improving security in blockchain applications. And in addition, ECDSA (and all elliptic curve cryptography) and possibly Ed25519 can be broken by large-scale quantum computers - Post-Quantum Cryptography (PQC) is designed to resist this by replacing these schemes with lattice-based, hash-based, multivariate, and code-based cryptography - and is a currently developing story!
The following sample shows signing of a transaction, using Ed25519 public/private key signing with the libsodium library, supported by Boost.Uuid and Boost.Serialization.
#include <sodium.h>
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/serialization/string.hpp>
#include <boost/uuid/uuid.hpp>
#include <boost/uuid/uuid_generators.hpp>
#include <boost/uuid/uuid_io.hpp>
#include <iostream>
#include <sstream>
// Transaction Structure
struct Transaction {
std::string sender;
std::string receiver;
double amount;
boost::uuids::uuid tx_id;
template<class Archive>
void serialize(Archive& ar, const unsigned int) {
ar& sender;
ar& receiver;
ar& amount;
ar& tx_id;
}
};
// Serialization Utility
std::string serialize_transaction(const Transaction& tx) {
std::ostringstream oss;
boost::archive::text_oarchive oa(oss);
oa << tx;
return oss.str();
}
// Signing and Verifying
bool sign_transaction(const std::string& serialized_tx,
std::array<unsigned char, crypto_sign_BYTES>& signature,
const std::array<unsigned char, crypto_sign_SECRETKEYBYTES>& sk) {
return crypto_sign_detached(signature.data(),
nullptr,
reinterpret_cast<const unsigned char*>(serialized_tx.data()),
serialized_tx.size(),
sk.data()) == 0;
}
bool verify_transaction(const std::string& serialized_tx,
const std::array<unsigned char, crypto_sign_BYTES>& signature,
const std::array<unsigned char, crypto_sign_PUBLICKEYBYTES>& pk) {
return crypto_sign_verify_detached(signature.data(),
reinterpret_cast<const unsigned char*>(serialized_tx.data()),
serialized_tx.size(),
pk.data()) == 0;
}
// Main Flow
int main() {
if (sodium_init() < 0) {
std::cerr << "Failed to initialize libsodium\n";
return 1;
}
// Key pair
std::array<unsigned char, crypto_sign_PUBLICKEYBYTES> pk;
std::array<unsigned char, crypto_sign_SECRETKEYBYTES> sk;
crypto_sign_keypair(pk.data(), sk.data());
// Create a sample transaction
Transaction tx{
"alice@example.com",
"bob@example.com",
100.5,
boost::uuids::random_generator()()
};
// Serialize the transaction
std::string serialized = serialize_transaction(tx);
// Sign it
std::array<unsigned char, crypto_sign_BYTES> signature;
if (!sign_transaction(serialized, signature, sk)) {
std::cerr << "Signing failed.\n";
return 1;
}
std::cout << "Transaction signed with Ed25519.\n";
std::cout << "Transaction ID: " << tx.tx_id << "\n";
// Verify the signature
if (verify_transaction(serialized, signature, pk)) {
std::cout << "Signature verified\n";
}
else {
std::cout << "Signature verification failed\n";
}
return 0;
}
Run the program and you should get a verified signature:
Transaction signed with Ed25519.
Transaction ID: 77f104be-815d-4800-b424-244d0e8ee7c0
Signature verified
Sample Wallet with Ed25519 Signing
Let’s start with a simple wallet that enables transactions from a sender to a receiver, using Ed25519 signing as before.
#pragma once
#include <sodium.h>
#include <iostream>
#include <fstream>
#include <sstream>
#include <boost/uuid/uuid.hpp>
#include <boost/uuid/random_generator.hpp>
#include <boost/uuid/uuid_io.hpp>
#include <boost/serialization/string.hpp>
#include <boost/serialization/array.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
class Wallet {
public:
Wallet() {
crypto_sign_keypair(public_key.data(), private_key.data());
id = boost::uuids::to_string(boost::uuids::random_generator()());
}
std::string get_id() const { return id; }
const std::array<unsigned char, crypto_sign_PUBLICKEYBYTES>& pubkey() const { return public_key; }
const std::array<unsigned char, crypto_sign_SECRETKEYBYTES>& privkey() const { return private_key; }
private:
std::string id;
std::array<unsigned char, crypto_sign_PUBLICKEYBYTES> public_key{};
std::array<unsigned char, crypto_sign_SECRETKEYBYTES> private_key{};
};
struct Transaction {
std::string sender_id;
std::string recipient_id;
double amount;
std::array<unsigned char, crypto_sign_BYTES> signature{};
Transaction() = default;
Transaction(const std::string& sender, const std::string& recipient, double amt)
: sender_id(sender), recipient_id(recipient), amount(amt) {
}
std::string serialize_data() const {
std::ostringstream oss;
oss << sender_id << "|" << recipient_id << "|" << std::fixed << std::setprecision(2) << amount;
return oss.str();
}
void sign(const std::array<unsigned char, crypto_sign_SECRETKEYBYTES>& sk) {
std::string msg = serialize_data();
crypto_sign_detached(signature.data(),
nullptr,
reinterpret_cast<const unsigned char*>(msg.data()),
msg.size(),
sk.data());
}
bool verify(const std::array<unsigned char, crypto_sign_PUBLICKEYBYTES>& pk) const {
std::string msg = serialize_data();
return crypto_sign_verify_detached(signature.data(),
reinterpret_cast<const unsigned char*>(msg.data()),
msg.size(),
pk.data()) == 0;
}
private:
friend class boost::serialization::access;
template<class Archive>
void serialize(Archive& ar, const unsigned int) {
ar& sender_id;
ar& recipient_id;
ar& amount;
ar& signature;
}
};
int main() {
if (sodium_init() < 0) {
std::cerr << "libsodium initialization failed\n";
return 1;
}
Wallet sender;
Wallet recipient;
std::cout << "Created wallets:\n";
std::cout << "Sender ID: " << sender.get_id() << "\n";
std::cout << "Recipient ID: " << recipient.get_id() << "\n";
Transaction tx(sender.get_id(), recipient.get_id(), 123.45);
tx.sign(sender.privkey());
if (tx.verify(sender.pubkey())) {
std::cout << "Signature verified successfully.\n";
}
else {
std::cout << "Signature verification failed.\n";
}
// Block the write code to ensure the write buffers are flushed
{
std::ofstream ofs("transactions.dat");
boost::archive::text_oarchive oa(ofs);
oa << tx;
}
std::cout << "Transaction saved.\n";
Transaction loaded_tx;
std::ifstream ifs("transactions.dat");
boost::archive::text_iarchive ia(ifs);
ia >> loaded_tx;
std::cout << "Loaded transaction: " << loaded_tx.sender_id << " -> "
<< loaded_tx.recipient_id << ", $" << loaded_tx.amount << "\n";
}
A sample run of the program might be:
Created wallets:
Sender ID: 782ded6c-4eda-47d6-a62c-d87bcf2c8f79
Recipient ID: 48ff8851-f638-4bfc-ad39-33547d82fbcb
Signature verified successfully.
Transaction saved.
Loaded transaction: 782ded6c-4eda-47d6-a62c-d87bcf2c8f79 -> 48ff8851-f638-4bfc-ad39-33547d82fbcb, $123.45
Wallet with Foreign Currency Exchange
For a more functional wallet, we should add foreign currency exchange. In this sample the rates are hard-coded, however the Next Steps section includes information on how to access live currency exchange rates.
Sender:
Recipient:
#include <sodium.h>
#include <iostream>
#include <fstream>
#include <sstream>
#include <boost/uuid/uuid.hpp>
#include <boost/uuid/random_generator.hpp>
#include <boost/uuid/uuid_io.hpp>
#include <boost/serialization/string.hpp>
#include <boost/serialization/map.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
enum class Currency { USD, EUR, JPY };
inline std::string to_string(Currency c) {
switch (c) {
case Currency::USD: return "USD";
case Currency::EUR: return "EUR";
case Currency::JPY: return "JPY";
}
return "UNK";
}
inline Currency from_string(const std::string& s) {
if (s == "USD") return Currency::USD;
if (s == "EUR") return Currency::EUR;
if (s == "JPY") return Currency::JPY;
throw std::runtime_error("Unknown currency");
}
static const std::map<std::pair<Currency, Currency>, double> exchange_rates = {
{{Currency::USD, Currency::EUR}, 0.91},
{{Currency::EUR, Currency::USD}, 1.10},
{{Currency::USD, Currency::JPY}, 146.0},
{{Currency::JPY, Currency::USD}, 0.0068},
{{Currency::EUR, Currency::JPY}, 160.0},
{{Currency::JPY, Currency::EUR}, 0.0062}
};
class Wallet {
public:
Wallet(double usd, double eur, double jpy) {
crypto_sign_keypair(public_key.data(), private_key.data());
id = boost::uuids::to_string(boost::uuids::random_generator()());
balances[Currency::USD] = usd;
balances[Currency::EUR] = eur;
balances[Currency::JPY] = jpy;
}
void credit(Currency cur, double amt) {
balances[cur] += amt;
}
bool debit(Currency cur, double amt) {
if (balances[cur] >= amt) {
balances[cur] -= amt;
return true;
}
return false;
}
void print_balances() const {
std::cout << "\nBalances for " << id << ":\n";
for (const auto& [cur, val] : balances) {
std::cout << " " << to_string(cur) << ": " << val << "\n";
}
}
std::string get_id() const { return id; }
const std::array<unsigned char, crypto_sign_PUBLICKEYBYTES>& pubkey() const { return public_key; }
const std::array<unsigned char, crypto_sign_SECRETKEYBYTES>& privkey() const { return private_key; }
private:
std::string id;
std::array<unsigned char, crypto_sign_PUBLICKEYBYTES> public_key{};
std::array<unsigned char, crypto_sign_SECRETKEYBYTES> private_key{};
std::map<Currency, double> balances;
};
struct Transaction {
std::string sender_id;
std::string recipient_id;
double amount;
Currency sender_currency;
Currency recipient_currency;
std::array<unsigned char, crypto_sign_BYTES> signature{};
Transaction() = default;
Transaction(const std::string& s, const std::string& r, double amt, Currency scur, Currency rcur)
: sender_id(s), recipient_id(r), amount(amt), sender_currency(scur), recipient_currency(rcur) {
}
std::string serialize_data() const {
std::ostringstream oss;
oss << sender_id << "|" << recipient_id << "|" << std::fixed << std::setprecision(2)
<< amount << "|" << to_string(sender_currency) << "|" << to_string(recipient_currency);
return oss.str();
}
double converted_amount() const {
if (sender_currency == recipient_currency) return amount;
auto it = exchange_rates.find({ sender_currency, recipient_currency });
if (it != exchange_rates.end()) return amount * it->second;
throw std::runtime_error("No exchange rate available.");
}
void sign(const std::array<unsigned char, crypto_sign_SECRETKEYBYTES>& sk) {
std::string msg = serialize_data();
crypto_sign_detached(signature.data(), nullptr,
reinterpret_cast<const unsigned char*>(msg.data()),
msg.size(), sk.data());
}
bool verify(const std::array<unsigned char, crypto_sign_PUBLICKEYBYTES>& pk) const {
std::string msg = serialize_data();
return crypto_sign_verify_detached(signature.data(),
reinterpret_cast<const unsigned char*>(msg.data()),
msg.size(), pk.data()) == 0;
}
private:
friend class boost::serialization::access;
template<class Archive>
void serialize(Archive& ar, const unsigned int) {
ar& sender_id& recipient_id& amount;
std::string scur = to_string(sender_currency);
std::string rcur = to_string(recipient_currency);
ar& scur& rcur;
sender_currency = from_string(scur);
recipient_currency = from_string(rcur);
ar& signature;
}
};
void make_transaction(Wallet &sender, Wallet &recipient, double amount, Currency scur, Currency rcur)
{
Transaction tx(sender.get_id(), recipient.get_id(), amount, scur, rcur);
tx.sign(sender.privkey());
if (tx.verify(sender.pubkey())) {
std::cout << "\nTransaction verified.\n";
if (sender.debit(tx.sender_currency, tx.amount)) {
recipient.credit(tx.recipient_currency, tx.converted_amount());
}
else {
std::cout << "Sender has insufficient funds.\n";
}
}
else {
std::cout << "Transaction verification failed.\n";
}
sender.print_balances();
recipient.print_balances();
}
int main() {
if (sodium_init() < 0) {
std::cerr << "libsodium initialization failed\n";
return 1;
}
Wallet sender = Wallet(500, 300, 5000);
Wallet recipient = Wallet(0, 0, 0);
std::cout << "Created wallets:\n";
std::cout << "Sender ID: " << sender.get_id() << "\n";
std::cout << "Recipient ID: " << recipient.get_id() << "\n";
make_transaction(sender, recipient, 100.0, Currency::EUR, Currency::USD);
make_transaction(sender, recipient, 2500.0, Currency::JPY, Currency::USD);
make_transaction(recipient, sender, 50.0, Currency::USD, Currency::EUR);
return 0;
}
- Note
-
Wallets contain three amounts, one for each currency. If a transaction is made from a currency the Wallet does not have enough of, amounts are not transferred from the other currencies to cover it. On receipt of a transaction, the amounts are converted to the specified currency.
Run the program:
Created wallets:
Sender ID: 48b711fd-8197-4e39-bbaf-06e931ab9cc3
Recipient ID: fdda420d-820a-4670-99a9-3320864ef990
Transaction verified.
Balances for 48b711fd-8197-4e39-bbaf-06e931ab9cc3:
USD: 500
EUR: 200
JPY: 5000
Balances for fdda420d-820a-4670-99a9-3320864ef990:
USD: 110
EUR: 0
JPY: 0
Transaction verified.
Balances for 48b711fd-8197-4e39-bbaf-06e931ab9cc3:
USD: 500
EUR: 200
JPY: 2500
Balances for fdda420d-820a-4670-99a9-3320864ef990:
USD: 127
EUR: 0
JPY: 0
Transaction verified.
Balances for fdda420d-820a-4670-99a9-3320864ef990:
USD: 77
EUR: 0
JPY: 0
Balances for 48b711fd-8197-4e39-bbaf-06e931ab9cc3:
USD: 500
EUR: 245.5
JPY: 2500
Get Realtime Currency Exchange Rates
Let’s use the features of Boost.Beast to access real exchange rates, then use Boost.Json to parse the response. The following example demonstrates how to:
-
Resolve a hostname to an IP address (DNS)
-
Establish a TCP connection
-
Establish a TLS/SSL encrypted connection (HTTPS)
-
Send an HTTP GET request
-
Receive an HTTP response
-
Parse JSON using Boost.JSON
-
Extract an exchange rate
#include <boost/asio/ssl.hpp>
#include <boost/beast.hpp>
#include <boost/json.hpp>
#include <iostream>
// Common namespace aliases used in Beast examples.
namespace asio = boost::asio;
namespace ssl = asio::ssl;
namespace beast = boost::beast;
namespace http = beast::http;
namespace json = boost::json;
// Convenience alias for TCP networking.
using tcp = asio::ip::tcp;
int main()
{
try
{
//------------------------------------------------------------------
// Step 1: Define the currency conversion we want.
//------------------------------------------------------------------
std::string from = "USD";
std::string to = "GBP";
double amount = 100.0;
//------------------------------------------------------------------
// Step 2: Define the remote web server.
//
// host:
// DNS name of the server.
// port:
// 443 is the standard HTTPS port.
//------------------------------------------------------------------
std::string host = "api.frankfurter.dev";
std::string port = "443";
//------------------------------------------------------------------
// Build the HTTP URL path.
//
// Result:
// /v1/latest?base=<from>&symbols=<to>
//------------------------------------------------------------------
std::string target =
"/v1/latest?base=" + from +
"&symbols=" + to;
//------------------------------------------------------------------
// Step 3: Create the Asio I/O context.
//
// The io_context owns the operating system networking resources.
// Almost every Asio program starts by creating one.
//------------------------------------------------------------------
asio::io_context ioc;
//------------------------------------------------------------------
// Step 4: Create an SSL/TLS context.
//
// This object stores TLS configuration:
// - certificates
// - trusted certificate authorities
// - protocol versions
// - verification settings
//------------------------------------------------------------------
ssl::context ctx(ssl::context::tls_client);
//------------------------------------------------------------------
// Load system default certificate locations.
//
// On Windows this often isn't sufficient by itself,
// but it doesn't hurt to call it.
//------------------------------------------------------------------
ctx.set_default_verify_paths();
//------------------------------------------------------------------
// Step 5: Create a DNS resolver.
//
// DNS converts:
// api.frankfurter.dev
// into one or more IP addresses.
//------------------------------------------------------------------
tcp::resolver resolver(ioc);
//------------------------------------------------------------------
// Step 6: Create the HTTPS stream.
//
// Layer stack:
// ssl::stream
// ↓
// beast::tcp_stream
// ↓
// socket
//
// The TCP stream performs network I/O. The SSL stream adds encryption.
//------------------------------------------------------------------
ssl::stream<beast::tcp_stream> stream(ioc, ctx);
//------------------------------------------------------------------
// Step 7: Load trusted certificate authorities.
//
// This file contains a list of trusted root certificates.
//
// During the TLS handshake the server presents a certificate.
// OpenSSL verifies that the certificate chains back to one
// of these trusted authorities.
//------------------------------------------------------------------
ctx.load_verify_file(
"<YOUR PATH>\\cacert.pem");
//------------------------------------------------------------------
// Require certificate verification.
//
// Production code should verify certificates.
//------------------------------------------------------------------
stream.set_verify_mode(ssl::verify_peer);
//------------------------------------------------------------------
// Step 8: DNS resolution.
//
// Example:
// api.frankfurter.dev
//
// might resolve to:
// 104.21.xx.xx
// 172.67.xx.xx
//------------------------------------------------------------------
auto endpoints = resolver.resolve(host, port);
//------------------------------------------------------------------
// Step 9: Establish the TCP connection.
//
// At this point:
// Client <----TCP----> Server
//
// There is NOT yet any encryption.
//------------------------------------------------------------------
beast::get_lowest_layer(stream).connect(endpoints);
//------------------------------------------------------------------
// Step 10: Configure SNI (Server Name Indication).
//
// Many HTTPS servers host multiple websites on the same IP.
// SNI tells the server which hostname we want.
// Modern HTTPS connections often fail without this.
//------------------------------------------------------------------
if (!SSL_set_tlsext_host_name(
stream.native_handle(),
host.c_str()))
{
throw beast::system_error(
beast::error_code(
static_cast<int>(::ERR_get_error()),
asio::error::get_ssl_category()));
}
//------------------------------------------------------------------
// Step 11: Perform TLS handshake.
//
// During this phase:
// - encryption algorithms are negotiated
// - certificates are exchanged
// - certificates are verified
// - session keys are created
//
// After this succeeds, all traffic is encrypted.
//------------------------------------------------------------------
stream.handshake(ssl::stream_base::client);
//------------------------------------------------------------------
// Step 12: Build an HTTP GET request.
//
// HTTP version 11 = HTTP/1.1
//------------------------------------------------------------------
http::request<http::empty_body> req{
http::verb::get,
target,
11
};
req.set(http::field::host, host);
req.set(http::field::user_agent,
"Boost.Beast Exchange Demo");
//------------------------------------------------------------------
// Step 13: Send the HTTP request.
//
// Browser equivalent:
// GET /v1/latest?... HTTP/1.1
// Host: api.frankfurter.dev
//------------------------------------------------------------------
http::write(stream, req);
//------------------------------------------------------------------
// Step 14: Receive the HTTP response.
//------------------------------------------------------------------
beast::flat_buffer buffer;
http::response<http::string_body> res;
http::read(stream, buffer, res);
std::cout
<< "HTTP Status: "
<< res.result_int()
<< "\n\n";
//------------------------------------------------------------------
// Step 15: Parse JSON.
//
// Example response:
// {
// "rates":
// {
// "EUR": 0.87
// }
// }
//------------------------------------------------------------------
json::value jv = json::parse(res.body());
auto const& obj = jv.as_object();
//------------------------------------------------------------------
// Extract:
//
// rates -> EUR
//------------------------------------------------------------------
double rate =
obj.at("rates")
.as_object()
.at(to)
.as_double();
//------------------------------------------------------------------
// Convert the amount.
//------------------------------------------------------------------
double converted = amount * rate;
std::cout
<< amount << ' '
<< from
<< " = "
<< converted << ' '
<< to
<< "\n";
//------------------------------------------------------------------
// Step 16: Gracefully shut down TLS.
//
// Some servers close the connection without sending the
// TLS close_notify message. OpenSSL reports this as stream_truncated.
//
// For simple HTTPS clients this is commonly ignored.
//------------------------------------------------------------------
beast::error_code ec;
stream.shutdown(ec);
if (ec == asio::ssl::error::stream_truncated)
{
ec = {};
}
if (ec == asio::error::eof)
{
ec = {};
}
if (ec)
{
throw beast::system_error(ec);
}
}
catch (std::exception const& e)
{
std::cerr
<< "Error: "
<< e.what()
<< "\n";
}
}
- Note
-
If you do not have a certificate file installed (cacert.pem), remove the line that refers to it, and change
stream.set_verify_mode(ssl::verify_peer);tostream.set_verify_mode(ssl::verify_none);. Though for production code security certificates are important!
If you run the code, you should get something like:
HTTP Status: 200
100 USD = 74.605 GBP
Next Steps
Consider adding a user interface to the wallet to allow you to make deposits, withdrawals, transfers, and balance checks. And add the realtime access to exchange rates to your wallet.
For some ideas on how to expand this app with remote access, refer to the samples in Networking.
Footnotes
(1) Blockchain : A technology that provides a decentralized, tamper-resistant ledger that ensures transparency, security, and trust in digital transactions. By distributing records across a network of nodes and using cryptographic techniques, like hashing and digital signatures, blockchain eliminates the need for intermediaries, reducing fraud and operational costs. Its applications extend beyond cryptocurrencies to areas such as supply chain tracking, smart contracts, secure identity verification, and financial services. The immutability and auditability make it particularly valuable for industries requiring verifiable and trustless interactions, though challenges like scalability and energy consumption remain areas of active development.
(2) ECDSA : An Elliptic Curve Digital Signature Algorithm creates a public and private key pair. ECDSA provides a variant of digital signature algorithms that use elliptic-curve cryptography to provide an additional level of complexity to the private key. However, care should be taken when implementing this algorithm - in particular, high-quality randomness in signatures is an absolutely essential.
(3) Ed25519 : A high-performance, secure, and efficient public-key signature algorithm based on the Edwards-curve Digital Signature Algorithm (EdDSA), specifically designed for the Curve25519 elliptic curve. It offers 128-bit security, is resistant to side-channel attacks, and provides fast signing and verification speeds while maintaining small key and signature sizes (32-byte public keys and 64-byte signatures). Unlike ECDSA, Ed25519 does not require a secure random k value for signing, eliminating a major source of vulnerabilities. Widely adopted in cryptographic protocols like SSH, TLS, and cryptocurrency systems, Ed25519 is favored for its robustness, simplicity, and efficiency in modern security applications.