Skip to main content

Fetch your Wallet Balance

Poll your wallet balance from your microcontroller and react when it changes - the foundation for every "do something physical when something happens on-chain" project in this section.

Setting up the API​

We use the Koios /account_info endpoint. It's a POST that takes your stake address in the request body.

Get your stake address from Yoroi:

  1. Open the Yoroi extension.
  2. Confirm Preprod (orange banner at top).
  3. Go to Wallet → Receive.
  4. Copy your stake address from the rewards section - it starts with stake_test1....

You can test the endpoint from Insomnia or Postman with this body:

{
"_stake_addresses": [
"stake_test1urq4rcynzj4uxqc74c852zky7wa6epgmn9r6k3j3gv7502q8jks0l"
]
}

Response (truncated):

[
{
"stake_address": "stake_test1uz22g9jd408t8zq8g3dw8p60qla49qjgvhh7z74kjpvuyrctlwf4m",
"status": "registered",
"delegated_pool": null,
"delegated_drep": "drep1ytesfw7n2pq5ys2rk0m7fxxd2dyagf820wy24d82rdd9yxqfm4qjg",
"total_balance": "10497440929",
"utxo": "10497440929",
"rewards": "0",
"withdrawals": "0",
"rewards_available": "0",
"deposit": "2000000",
"reserves": "0",
"treasury": "0",
"proposal_refund": "0"
}
]

Key fields:

  • stake_address - your stake address.
  • status - registration status.
  • total_balance - total balance in lovelace (1 tADA = 1,000,000 lovelace).
  • utxo - total UTxO value in lovelace.
  • rewards_available - rewards you can withdraw.
  • delegated_pool - pool ID if delegated.
  • delegated_drep - DRep ID for governance.

Fetching the balance from the ESP32​

The sketch will:

  1. Connect to WiFi.
  2. POST to Koios with your stake address.
  3. Parse the response and extract total_balance.
  4. Display the balance via the serial monitor.
// Include necessary libraries for WiFi, HTTP requests, and JSON parsing
#include <WiFi.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>

// WiFi credentials - replace with your network details
const char* ssid = "Your SSID";
const char* password = "Your Password";

// Koios API endpoint for fetching account information
const char* apiUrl = "https://preprod.koios.rest/api/v1/account_info";

// Your Cardano stake address (Preprod Testnet)
String stakeAddress = "stake_test1...";

// Variables for timing balance checks
unsigned long lastCheck = 0; // Timestamp of last balance check
const unsigned long checkInterval = 30000; // Check every 30 seconds (30000 milliseconds)

// Store previous balance to detect changes
float previousBalance = 0;

void setup() {
// Initialize serial communication for debugging (115200 baud rate)
Serial.begin(115200);

// Start WiFi connection
WiFi.begin(ssid, password);

// Wait until WiFi is connected
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.println("Connecting to WiFi...");
}

// Print connection confirmation and IP address
Serial.println("Connected to WiFi");
Serial.print("IP address: ");
Serial.println(WiFi.localIP());

// Perform initial balance check on startup
fetchStakeBalance();
}

void loop() {
// Check if WiFi connection is still active
if (WiFi.status() != WL_CONNECTED) {
Serial.println("WiFi connection lost. Reconnecting...");
WiFi.reconnect();

// Wait for reconnection
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.print(".");
}
Serial.println("Reconnected!");
}

// Get current time in milliseconds
unsigned long currentMillis = millis();

// Check if enough time has passed since last check
if (currentMillis - lastCheck >= checkInterval) {
fetchStakeBalance();
lastCheck = currentMillis; // Update last check timestamp
}
}

void fetchStakeBalance() {
// Only proceed if WiFi is connected
if (WiFi.status() == WL_CONNECTED) {
HTTPClient http;

// Initialize HTTP client with API URL
http.begin(apiUrl);

// Set content type header for JSON request
http.addHeader("Content-Type", "application/json");

// Create JSON payload with stake address
// Koios API expects stake addresses in an array under "_stake_addresses" key
String jsonPayload = "{\\"";
jsonPayload += "_stake_addresses";
jsonPayload += "\\":[\\"";
jsonPayload += stakeAddress;
jsonPayload += "\\"]}";

// Send POST request and get response code
int httpResponseCode = http.POST(jsonPayload);

// Check if request was successful (response code > 0)
if (httpResponseCode > 0) {
// Get response body as string
String response = http.getString();
Serial.println("HTTP Response Code: " + String(httpResponseCode));

// Create JSON document to parse response (2048 bytes buffer)
DynamicJsonDocument doc(2048);
DeserializationError error = deserializeJson(doc, response);

// Check if JSON parsing was successful
if (!error) {
// Verify response is an array with at least one element
if (doc.is<JsonArray>() && doc.size() > 0) {
// Get first account info object from array
JsonObject accountInfo = doc[0];

// Extract total balance as string (Koios returns balance as string)
// total_balance includes delegated amount + rewards
const char* balanceStr = accountInfo["total_balance"];

// Convert string to long long (for large Lovelace values)
long long balanceLovelace = 0;
if (balanceStr != nullptr) {
balanceLovelace = atoll(balanceStr);
}

// Convert from Lovelace (smallest unit) to tADA (test ADA)
// 1 tADA = 1,000,000 Lovelace
float balance = balanceLovelace / 1000000.0;

// Print account information
Serial.println("Stake Address: " + String(accountInfo["stake_address"].as<const char*>()));
Serial.println("Total Balance: " + String(balance, 6) + " tADA");

// Check if balance has changed since last check
if (balance != previousBalance) {
if (balance > previousBalance) {
Serial.println("✓ Balance increased!");
} else {
Serial.println("✓ Balance decreased!");
}
// Update previous balance for next comparison
previousBalance = balance;
}
}
} else {
// Print error if JSON parsing failed
Serial.print("JSON parsing failed: ");
Serial.println(error.c_str());
}
} else {
// Print error if HTTP request failed
Serial.println("Error in HTTP request");
Serial.println("HTTP Response Code: " + String(httpResponseCode));
}

// Close HTTP connection
http.end();
} else {
Serial.println("WiFi not connected");
}
}

Source: Workshop-02/examples/wallet-balance/wallet-balance.ino

Update WiFi credentials and your stake address before uploading.

Listening for changes​

The sketch already detects balance changes by storing the previous balance and comparing on each poll. From there you can hang any side-effect off the change:

  • Light up an LED when funds arrive.
  • Buzz a buzzer.
  • Update a display (next lesson).
  • Trigger a relay (lesson 3 of this workshop).
CardanoThings PingPong wallet

Need an easy way to test the change-detection loop? Send tADA to the CardanoThings PingPong wallet - it auto-refunds your transaction (minus fees) within ~60 seconds, so you can trigger the change handler repeatedly.

Address: addr_test1qpvla0l6zgkl4ufzur0wal0uny5lyqsg4rw7g6gxj08lzacth0hnd66lz6uqqz7kwkmx07xyppsk2cddvxnqvfd05reqf7p26w

Preprod-only.

Alternative APIs​

Koios is free and open-source, but there are alternatives.

Blockfrost​

Blockfrost is a popular Cardano API with free and paid tiers. It uses a simpler GET request and requires an API key. Sign up at blockfrost.io, create a Preprod project, and grab the key.

The Blockfrost response shape is slightly different:

{
"stake_address": "stake_test1ux3g2c9dx2nhhehyrezyxpkstartcqmu9hk63qgfkccw5rqttygt7",
"active": true,
"active_epoch": 412,
"controlled_amount": "619154618165",
"rewards_sum": "319154618165",
"withdrawals_sum": "12125369253",
"reserves_sum": "319154618165",
"treasury_sum": "12000000",
"withdrawable_amount": "319154618165",
"pool_id": "pool1pu5jlj4q9w9jlxeu370a3c9myx47md5j5m2str0naunn2q3lkdy",
"drep_id": "drep15cfxz9exyn5rx0807zvxfrvslrjqfchrd4d47kv9e0f46uedqtc"
}
// Include necessary libraries for WiFi, HTTP requests, and JSON parsing
#include <WiFi.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>

// WiFi credentials - replace with your network details
const char* ssid = "Your SSID";
const char* password = "Your Password";

// Blockfrost API endpoint (Preprod Testnet)
// Note: Blockfrost uses GET requests with stake address in URL path
const char* apiUrl = "https://cardano-preprod.blockfrost.io/api/v0/accounts/";

// Your Blockfrost API key (get free key from blockfrost.io)
const char* apiKey = "your-blockfrost-api-key";

// Your Cardano stake address (Preprod Testnet)
String stakeAddress = "stake_test1...";

// Variables for timing balance checks
unsigned long lastCheck = 0; // Timestamp of last balance check
const unsigned long checkInterval = 30000; // Check every 30 seconds

void setup() {
// Initialize serial communication for debugging
Serial.begin(115200);

// Start WiFi connection
WiFi.begin(ssid, password);

// Wait until WiFi is connected
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.println("Connecting to WiFi...");
}
Serial.println("Connected to WiFi");
}

void loop() {
// Check if WiFi connection is still active
if (WiFi.status() != WL_CONNECTED) {
WiFi.reconnect();
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
}
}

// Get current time in milliseconds
unsigned long currentMillis = millis();

// Check if enough time has passed since last check
if (currentMillis - lastCheck >= checkInterval) {
fetchStakeBalance();
lastCheck = currentMillis; // Update last check timestamp
}
}

void fetchStakeBalance() {
// Only proceed if WiFi is connected
if (WiFi.status() == WL_CONNECTED) {
HTTPClient http;

// Build full URL by appending stake address to base URL
String fullUrl = apiUrl + stakeAddress;

// Initialize HTTP client with full URL
http.begin(fullUrl);

// Blockfrost requires API key in "project_id" header
http.addHeader("project_id", apiKey);

// Send GET request (Blockfrost uses GET, not POST like Koios)
int httpResponseCode = http.GET();

// Check if request was successful
if (httpResponseCode > 0) {
// Get response body as string
String response = http.getString();

// Create JSON document to parse response (1024 bytes buffer)
DynamicJsonDocument doc(1024);
DeserializationError error = deserializeJson(doc, response);

// Check if JSON parsing was successful
if (!error) {
// Extract controlled_amount as string (Blockfrost returns balance as string)
// controlled_amount is the total balance including delegated amount and rewards
const char* balanceStr = doc["controlled_amount"];

// Convert string to long long (for large Lovelace values)
long long balanceLovelace = 0;
if (balanceStr != nullptr) {
balanceLovelace = atoll(balanceStr);
}

// Convert from Lovelace to tADA (test ADA) - 1 tADA = 1,000,000 Lovelace
float balance = balanceLovelace / 1000000.0;

// Print account information
Serial.println("Stake Address: " + String(doc["stake_address"].as<const char*>()));
Serial.println("Total Balance: " + String(balance, 6) + " tADA");
}
}

// Close HTTP connection
http.end();
}
}

Source: Workshop-02/examples/wallet-balance-blockfrost/wallet-balance-blockfrost.ino

PlatformIO + Blockfrost SDK

If you use PlatformIO, Blockfrost ships an official Arduino SDK that handles auth and serialization: github.com/blockfrost/blockfrost-arduino.

Maestro​

Maestro is another Cardano API provider with a free tier and similar coverage to Blockfrost and Koios.

Dolos​

You can also self-host with TxPipe Dolos, which exposes a Mini Blockfrost API over your own node.

Further Resources​


Adapted from the CardanoThings workshop series, originally produced under Project Catalyst Fund 11. Source code: github.com/CardanoThings/Workshops/Workshop-02.