Jak kopać Malmar Coin?
Kompletny przewodnik kopania MLM na ESP32
Co potrzebujesz?
Płytka ESP32 (WROOM, DevKit, NodeMCU)
Połączenie z internetem
Do wgrania kodu na ESP32
Adres MLM do odbierania nagród
Krok po kroku
Utwórz portfel
Najpierw potrzebujesz adresu portfela, na który będą wpływać wykopane monety.
Utwórz portfelZainstaluj Arduino IDE
Pobierz i zainstaluj Arduino IDE ze strony oficjalnej.
Pobierz Arduino IDEDodaj obsługę ESP32:
- Otwórz Arduino IDE
- Przejdź do File → Preferences
- W polu "Additional Board Manager URLs" wklej:
https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json - Przejdź do Tools → Board → Boards Manager
- Wyszukaj "ESP32" i zainstaluj
Zainstaluj bibliotekę ArduinoJson
Miner wymaga biblioteki ArduinoJson do komunikacji z poolem.
- W Arduino IDE przejdź do Sketch → Include Library → Manage Libraries
- Wyszukaj "ArduinoJson"
- Zainstaluj najnowszą wersję (autor: Benoit Blanchon)
Skopiuj kod minera
Skopiuj poniższy kod i wklej do Arduino IDE.
/*
* MalmarCoin ESP32 Dual-Core Miner
* Wersja: 3.0 - Telemetria, dual-core mining
*
* Wymagane biblioteki (Menedzer bibliotek Arduino):
* - ArduinoJson (Benoit Blanchon)
* - WiFiClientSecure (wbudowana)
*
* Plytka: ESP32 Dev Module
*/
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>
#include "mbedtls/sha256.h"
// ============================================
// KONFIGURACJA — ZMIEN NA SWOJE DANE
// ============================================
const char* WIFI_SSID = "TWOJA_SIEC_WIFI";
const char* WIFI_PASSWORD = "TWOJE_HASLO_WIFI";
const char* HOST = "malmar.portaleai.pl";
const char* WALLET_ADDRESS = "TWOJ_ADRES_PORTFELA_MLM";
const char* DEVICE_NAME = "ESP32-Miner-1";
const char* FIRMWARE_VER = "3.0.0";
// Limit hashrate per rdzen (0 = brak limitu, pelna moc)
const int MAX_HASHRATE_PER_CORE = 0;
// Interwał telemetrii (ms) — wysylaj co 30s niezaleznie od mining
const unsigned long TELEMETRY_INTERVAL = 30000;
#define LED_PIN 2
// ============================================
// ZMIENNE GLOBALNE
// ============================================
String deviceId;
SemaphoreHandle_t xMutex;
volatile bool miningActive = false;
volatile bool solutionFound = false;
volatile unsigned long foundNonce = 0;
String jobId;
String blockTemplate;
int shareDifficulty = 3;
volatile unsigned long hashCount[2] = {0, 0};
volatile unsigned long statsHashCount[2] = {0, 0};
unsigned long statsWindowStart = 0;
unsigned long lastJobTime = 0;
unsigned long lastStatsTime = 0;
unsigned long lastBlinkTime = 0;
unsigned long lastTelemetryTime = 0;
bool ledState = false;
// Liczniki CPU load (FreeRTOS idle hook)
volatile unsigned long idleCount0 = 0, idleCount1 = 0;
unsigned long prevIdle0 = 0, prevIdle1 = 0;
float lastCpuLoad = 0.0f;
TaskHandle_t miningTask0;
TaskHandle_t miningTask1;
// ============================================
// CPU LOAD — FreeRTOS idle hook
// Wlacz w: menuconfig → FreeRTOS → Enable idle task hook
// ============================================
void vApplicationIdleHook() {
if (xPortGetCoreID() == 0) idleCount0++;
else idleCount1++;
}
float getCpuLoad() {
unsigned long d0 = idleCount0 - prevIdle0;
unsigned long d1 = idleCount1 - prevIdle1;
prevIdle0 = idleCount0; prevIdle1 = idleCount1;
float idle = (d0 + d1) / 2000.0f;
lastCpuLoad = max(0.0f, min(100.0f, (1.0f - idle) * 100.0f));
return lastCpuLoad;
}
// ============================================
// SHA256
// ============================================
void computeHash(const char* tpl, unsigned long nonce, char* outHex) {
char input[256];
snprintf(input, sizeof(input), "%s%lu", tpl, nonce);
unsigned char hashBytes[32];
mbedtls_sha256_context ctx;
mbedtls_sha256_init(&ctx);
mbedtls_sha256_starts(&ctx, 0);
mbedtls_sha256_update(&ctx, (const unsigned char*)input, strlen(input));
mbedtls_sha256_finish(&ctx, hashBytes);
mbedtls_sha256_free(&ctx);
for (int i = 0; i < 32; i++) sprintf(outHex + (i*2), "%02x", hashBytes[i]);
outHex[64] = '\0';
}
bool meetsDifficulty(const char* hexHash, int diff) {
for (int i = 0; i < diff; i++) if (hexHash[i] != '0') return false;
return true;
}
// ============================================
// MINING TASK (dual-core)
// ============================================
void miningTaskFunction(void* parameter) {
int coreId = (int)parameter;
char hashBuf[65];
unsigned long rateWindowStart = millis();
unsigned long rateCount = 0;
while (true) {
if (!miningActive || solutionFound || blockTemplate.length() == 0) {
vTaskDelay(pdMS_TO_TICKS(50));
rateWindowStart = millis(); rateCount = 0;
continue;
}
String tpl = blockTemplate;
int diff = shareDifficulty;
unsigned long n = (unsigned long)coreId;
while (miningActive && !solutionFound) {
computeHash(tpl.c_str(), n, hashBuf);
hashCount[coreId]++; statsHashCount[coreId]++; rateCount++;
if (meetsDifficulty(hashBuf, diff)) {
if (xSemaphoreTake(xMutex, portMAX_DELAY)) {
if (!solutionFound) {
solutionFound = true; foundNonce = n;
Serial.printf("[Core %d] FOUND! Nonce:%lu Hash:%.16s...\n", coreId, n, hashBuf);
}
xSemaphoreGive(xMutex);
}
break;
}
n += 2;
if (hashCount[coreId] % 10000 == 0) vTaskDelay(1);
if (MAX_HASHRATE_PER_CORE > 0 && rateCount >= (unsigned long)MAX_HASHRATE_PER_CORE) {
unsigned long elapsed = millis() - rateWindowStart;
if (elapsed < 1000) vTaskDelay(pdMS_TO_TICKS(1000 - elapsed));
rateWindowStart = millis(); rateCount = 0;
}
}
vTaskDelay(pdMS_TO_TICKS(10));
}
}
// ============================================
// HTTPS HELPERS
// ============================================
bool httpsGet(const String& path, String& response) {
WiFiClientSecure client; client.setInsecure();
HTTPClient http;
http.begin(client, HOST, 443, path, true);
http.setTimeout(15000);
int code = http.GET();
if (code == 200) { response = http.getString(); http.end(); return true; }
// Pokaż treść błędu (np. blokada urządzenia)
String body = http.getString();
Serial.printf("GET -> HTTP %d\n", code);
if (body.length() > 0) Serial.printf("Serwer: %.200s\n", body.c_str());
http.end(); return false;
}
bool httpsPost(const String& path, const String& body, String& response) {
WiFiClientSecure client; client.setInsecure();
HTTPClient http;
http.begin(client, HOST, 443, path, true);
http.addHeader("Content-Type", "application/json");
http.setTimeout(15000);
int code = http.POST(body);
if (code == 200) { response = http.getString(); http.end(); return true; }
Serial.printf("POST %s -> %d\n", path.c_str(), code);
http.end(); return false;
}
// ============================================
// GET JOB — z telemetrią w URL
// ============================================
bool getJob() {
if (WiFi.status() != WL_CONNECTED) return false;
float tempC = (temperatureRead() - 32.0f) * 5.0f / 9.0f;
float load = getCpuLoad();
String path = String("/api.php?action=get_job")
+ "&wallet=" + String(WALLET_ADDRESS)
+ "&device_id=" + deviceId
+ "&device_name=" + String(DEVICE_NAME)
// ── Telemetria ──────────────────────────
+ "&model=" + String(ESP.getChipModel())
+ "&rev=" + String(ESP.getChipRevision())
+ "&cores=" + String(ESP.getChipCores())
+ "&fw=" + String(FIRMWARE_VER)
+ "&flash=" + String(ESP.getFlashChipSize() / 1024)
+ "&psram=" + String(ESP.getPsramSize() / 1024)
+ "&temp=" + String(tempC, 1)
+ "&load=" + String(load, 1)
+ "&heap=" + String(ESP.getFreeHeap())
+ "&rssi=" + String(WiFi.RSSI())
+ "&uptime=" + String(millis() / 1000)
+ "&mac=" + WiFi.macAddress();
String response;
if (!httpsGet(path, response)) return false;
// Debug: pokaż odpowiedź serwera gdy błąd parsowania
DynamicJsonDocument doc(1536);
DeserializationError jerr = deserializeJson(doc, response);
if (jerr != DeserializationError::Ok) {
Serial.printf("JSON error (get_job): %s\n", jerr.c_str());
Serial.printf("Response (%d B): %.120s\n", response.length(), response.c_str());
return false;
}
if (!doc["success"]) {
Serial.printf("Blad serwera: %s\n", doc["error"].as<const char*>()); return false;
}
jobId = doc["job_id"].as<String>();
blockTemplate = doc["block_template"].as<String>();
shareDifficulty = doc["difficulty"].as<int>();
Serial.printf("Job: %.8s... diff=%d tpl=%.12s...\n",
jobId.c_str(), shareDifficulty, blockTemplate.c_str());
return true;
}
// ============================================
// TELEMETRIA — osobny POST co TELEMETRY_INTERVAL
// ============================================
void sendTelemetry() {
if (WiFi.status() != WL_CONNECTED) return;
float tempC = (temperatureRead() - 32.0f) * 5.0f / 9.0f;
float load = getCpuLoad();
StaticJsonDocument<320> doc;
doc["device_id"] = deviceId;
doc["wallet"] = WALLET_ADDRESS;
doc["model"] = ESP.getChipModel();
doc["rev"] = ESP.getChipRevision();
doc["cores"] = ESP.getChipCores();
doc["mac"] = WiFi.macAddress();
doc["fw"] = FIRMWARE_VER;
doc["flash"] = ESP.getFlashChipSize() / 1024;
doc["psram"] = ESP.getPsramSize() / 1024;
doc["temp"] = round(tempC * 10.0f) / 10.0f;
doc["load"] = round(load * 10.0f) / 10.0f;
doc["heap"] = ESP.getFreeHeap();
doc["rssi"] = WiFi.RSSI();
doc["uptime"] = millis() / 1000;
String body, response;
serializeJson(doc, body);
if (httpsPost("/api.php?action=telemetry", body, response)) {
Serial.printf("[Tele] temp=%.1f°C load=%.1f%% heap=%d rssi=%d\n",
tempC, load, (int)doc["heap"], (int)doc["rssi"]);
}
}
// ============================================
// SUBMIT SHARE
// ============================================
bool submitShare(unsigned long nonce, float hashrate) {
if (WiFi.status() != WL_CONNECTED) return false;
char hashBuf[65];
computeHash(blockTemplate.c_str(), nonce, hashBuf);
StaticJsonDocument<256> doc;
doc["job_id"] = jobId;
doc["nonce"] = (uint32_t)nonce;
doc["hash"] = hashBuf;
doc["device_id"] = deviceId;
doc["wallet"] = WALLET_ADDRESS;
doc["hashrate"] = (int)hashrate;
String body; serializeJson(doc, body);
String response;
if (!httpsPost("/api.php?action=submit_share", body, response)) return false;
StaticJsonDocument<256> res;
if (deserializeJson(res, response) != DeserializationError::Ok) {
Serial.println("JSON error (submit)"); return false;
}
if (res["accepted"]) {
if (res["waiting"]) {
int waitSec = res["wait_seconds"] | 10;
Serial.printf(">>> Za szybko! Czekam %ds...\n", waitSec);
for (int i = 0; i < waitSec; i++) {
digitalWrite(LED_PIN, HIGH); vTaskDelay(pdMS_TO_TICKS(500));
digitalWrite(LED_PIN, LOW); vTaskDelay(pdMS_TO_TICKS(500));
}
return true;
}
Serial.println(">>> Share zaakceptowany!");
for (int i = 0; i < 3; i++) {
digitalWrite(LED_PIN, HIGH); delay(60);
digitalWrite(LED_PIN, LOW); delay(60);
}
if (res["block_found"]) {
float reward = res["reward"];
Serial.printf("*** BLOK ZNALEZIONY! +%.2f MLM ***\n", reward);
digitalWrite(LED_PIN, HIGH); delay(1500); digitalWrite(LED_PIN, LOW);
}
return true;
}
Serial.printf("Share odrzucony: %s\n", res["error"] | "");
return false;
}
// ============================================
// SETUP
// ============================================
void setup() {
Serial.begin(115200);
delay(1500);
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, HIGH);
Serial.println("\n================================");
Serial.println(" MalmarCoin ESP32 Miner v3.0");
Serial.println("================================");
uint64_t mac = ESP.getEfuseMac();
char macStr[17]; sprintf(macStr, "%016llX", mac);
deviceId = String(macStr);
Serial.printf("Chip: %s rev%d %d rdzenie\n",
ESP.getChipModel(), ESP.getChipRevision(), ESP.getChipCores());
Serial.printf("Flash: %d KB PSRAM: %d KB\n",
ESP.getFlashChipSize()/1024, ESP.getPsramSize()/1024);
Serial.printf("Device: %s (%s)\n", DEVICE_NAME, deviceId.c_str());
Serial.printf("Wallet: %.16s...\n", WALLET_ADDRESS);
Serial.printf("Pool: https://%s\n", HOST);
Serial.printf("\nLaczenie WiFi: %s", WIFI_SSID);
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
int att = 0;
while (WiFi.status() != WL_CONNECTED && att++ < 40) { delay(500); Serial.print("."); }
if (WiFi.status() != WL_CONNECTED) {
Serial.println("\nBrak WiFi! Restart..."); delay(3000); ESP.restart();
}
Serial.printf("\nWiFi OK! IP: %s MAC: %s\n\n",
WiFi.localIP().toString().c_str(), WiFi.macAddress().c_str());
xMutex = xSemaphoreCreateMutex();
xTaskCreatePinnedToCore(miningTaskFunction, "Mine0", 12000, (void*)0, 1, &miningTask0, 0);
xTaskCreatePinnedToCore(miningTaskFunction, "Mine1", 12000, (void*)1, 1, &miningTask1, 1);
digitalWrite(LED_PIN, LOW);
statsWindowStart = millis();
lastStatsTime = millis();
lastTelemetryTime = millis();
Serial.println("Koparka uruchomiona! (v3.0 z telemetria)");
}
// ============================================
// LOOP
// ============================================
void loop() {
if (WiFi.status() != WL_CONNECTED) {
Serial.println("WiFi zerwane, laczenie...");
miningActive = false; WiFi.reconnect();
vTaskDelay(pdMS_TO_TICKS(5000)); return;
}
// Mining
if (solutionFound || !miningActive || millis() - lastJobTime > 60000) {
if (solutionFound) {
float elapsed = (millis() - lastJobTime) / 1000.0f;
float hashrate = elapsed > 0 ? (hashCount[0] + hashCount[1]) / elapsed : 0;
miningActive = false;
bool ok = false;
for (int i = 1; i <= 5 && !ok; i++) {
ok = submitShare(foundNonce, hashrate);
if (!ok) { Serial.printf("Retry %d/5...\n", i); vTaskDelay(pdMS_TO_TICKS(2000)); }
}
solutionFound = false; foundNonce = 0;
hashCount[0] = 0; hashCount[1] = 0;
}
if (getJob()) { miningActive = true; lastJobTime = millis(); }
else vTaskDelay(pdMS_TO_TICKS(5000));
}
// Osobna telemetria co TELEMETRY_INTERVAL
if (millis() - lastTelemetryTime > TELEMETRY_INTERVAL) {
sendTelemetry();
lastTelemetryTime = millis();
}
// Stats w Serial co 10 s
if (millis() - lastStatsTime > 10000) {
unsigned long elapsed = millis() - statsWindowStart;
if (elapsed > 0) {
float hashrate = (statsHashCount[0] + statsHashCount[1]) * 1000.0f / elapsed;
float tempC = (temperatureRead() - 32.0f) * 5.0f / 9.0f;
Serial.printf("[Stats] H/s: %.0f Temp: %.1f°C Load: %.1f%% Heap: %d RSSI: %d\n",
hashrate, tempC, lastCpuLoad, (int)ESP.getFreeHeap(), (int)WiFi.RSSI());
statsHashCount[0] = statsHashCount[1] = 0;
statsWindowStart = millis();
}
lastStatsTime = millis();
}
// LED blink
unsigned long blinkMs = miningActive ? 150 : 1000;
if (millis() - lastBlinkTime >= blinkMs) {
ledState = !ledState;
digitalWrite(LED_PIN, ledState);
lastBlinkTime = millis();
}
vTaskDelay(pdMS_TO_TICKS(100));
}
Skonfiguruj minera
Zmień następujące wartości w kodzie:
WIFI_SSID
Nazwa Twojej sieci WiFi
WIFI_PASSWORD
Hasło do WiFi
WALLET_ADDRESS
Twój adres portfela MLM
POOL_URL
http://malmar.portaleai.pl
DEVICE_NAME
Nazwa urządzenia (opcjonalnie)
Wgraj kod na ESP32
- Podłącz ESP32 kablem USB do komputera
- W Arduino IDE wybierz:
- Tools → Board → ESP32 Dev Module
- Tools → Port → (wybierz port COM ESP32)
- Kliknij przycisk Upload (strzałka w prawo)
- Poczekaj na zakończenie wgrywania
Sprawdź działanie
Otwórz Serial Monitor (Tools → Serial Monitor) i ustaw prędkość na 115200 baud.
Powinieneś zobaczyć:
Twój miner działa! Sprawdź statystyki na:
Informacje o Pool
http://malmar.portaleai.pl
Często zadawane pytania
DEVICE_NAME dla każdego urządzenia.
- Sprawdź siłę sygnału WiFi
- Upewnij się, że serwer jest dostępny
- Sprawdź czy adres portfela jest poprawny
- Zrestartuj ESP32
- Sprawdź Serial Monitor dla błędów
Rozwiązywanie problemów
- Sprawdź nazwę sieci WiFi (SSID) - wielkość liter ma znaczenie
- Sprawdź hasło WiFi
- Upewnij się, że sieć działa na 2.4 GHz (ESP32 nie obsługuje 5 GHz)
- Sprawdź adres POOL_URL
- Upewnij się, że serwer jest dostępny
- Sprawdź poprawność adresu portfela
- To normalne - niektóre share mogą być odrzucone
- Sprawdź czy job nie wygasł (timeout)
- Upewnij się, że zegar ESP32 jest zsynchronizowany
- Upewnij się, że zainstalowałeś ESP32 board
- Zainstaluj bibliotekę ArduinoJson
- Wybierz odpowiednią płytkę (ESP32 Dev Module)
Gotowy do kopania?
Dołącz do społeczności minerów Malmar Coin!