Jak kopać Malmar Coin?
Kompletny przewodnik kopania MLM na ESP32
Flash przez przeglądarkę — bez Arduino IDE
Podłącz ESP32 kablem USB, kliknij „Flashuj", wpisz dane WiFi — gotowe. Zero kompilowania, zero bibliotek, 2 minuty.
Wolisz starą drogę? Pełna instrukcja Arduino IDE poniżej — działa, ale trzeba zainstalować środowisko i biblioteki.
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.3.1
*
* 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.3.1";
const int MAX_HASHRATE_PER_CORE = 0;
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;
char jobId[64] = {0};
char blockTemplate[128] = {0};
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;
TaskHandle_t miningTask0;
TaskHandle_t miningTask1;
// ============================================
// TEMPERATURA
// ============================================
float readCpuTempC() {
float raw = temperatureRead();
if (raw > 120.0f) return (raw - 32.0f) * 5.0f / 9.0f;
return raw;
}
// ============================================
// CPU LOAD — pomiar przez licznik idle taska
// Osobny task niskiego priorytetu zlicza ile razy
// zdąży się wykonać — im mniej, tym większe obciążenie
// ============================================
volatile unsigned long idleCounter = 0;
volatile float cpuLoadPercent = 0.0f;
unsigned long idleBaseline = 0; // wartość przy 0% obciążeniu
void cpuMeasureTask(void* param) {
// Kalibracja — zmierz baseline przez 1s przy bezczynności
unsigned long start = millis();
unsigned long cnt = 0;
while (millis() - start < 1000) { cnt++; taskYIELD(); }
idleBaseline = cnt > 0 ? cnt : 1;
while (true) {
unsigned long t0 = millis();
unsigned long c0 = 0;
while (millis() - t0 < 1000) { c0++; taskYIELD(); }
float ratio = (float)c0 / (float)idleBaseline;
cpuLoadPercent = max(0.0f, min(100.0f, (1.0f - ratio) * 100.0f));
idleCounter++;
}
}
// ============================================
// SHA256 — każdy task ma WŁASNY kontekst (FIX!)
// Nie używamy globalnego sha_ctx — race condition!
// ============================================
void computeHash(mbedtls_sha256_context* ctx,
const char* tpl, unsigned long nonce, char* outHex) {
char input[200];
int len = snprintf(input, sizeof(input), "%s%lu", tpl, nonce);
unsigned char hashBytes[32];
mbedtls_sha256_starts(ctx, 0);
mbedtls_sha256_update(ctx, (const unsigned char*)input, len);
mbedtls_sha256_finish(ctx, hashBytes);
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)
// Każdy task ma lokalny sha_ctx — thread safe!
// ============================================
void miningTaskFunction(void* parameter) {
int coreId = (int)parameter;
char hashBuf[65];
// Lokalny kontekst SHA — NIE współdzielony (FIX race condition)
mbedtls_sha256_context localCtx;
mbedtls_sha256_init(&localCtx);
unsigned long rateWindowStart = millis();
unsigned long rateCount = 0;
unsigned long hashIter = 0;
while (true) {
if (!miningActive || solutionFound || blockTemplate[0] == '\0') {
vTaskDelay(pdMS_TO_TICKS(50));
rateWindowStart = millis();
rateCount = 0;
hashIter = 0;
continue;
}
char tpl[128];
strncpy(tpl, blockTemplate, sizeof(tpl) - 1);
tpl[sizeof(tpl) - 1] = '\0';
int diff = shareDifficulty;
unsigned long n = (unsigned long)coreId; // Core0: 0,2,4... Core1: 1,3,5...
while (miningActive && !solutionFound) {
computeHash(&localCtx, tpl, n, hashBuf);
hashCount[coreId]++;
statsHashCount[coreId]++;
rateCount++;
hashIter++;
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;
// Yield co 10000 hashów — nie blokuj schedulera
if (hashIter % 10000 == 0) {
vTaskDelay(1);
hashIter = 0;
}
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
// ============================================
bool httpsGet(const String& path, String& response) {
WiFiClientSecure client;
client.setInsecure();
client.setTimeout(15);
HTTPClient http;
http.setReuse(false);
http.begin(client, HOST, 443, path, true);
http.setTimeout(15000);
int code = http.GET();
bool ok = false;
if (code == 200) {
response = http.getString();
ok = true;
} else {
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();
client.stop();
return ok;
}
bool httpsPost(const String& path, const String& body, String& response) {
WiFiClientSecure client;
client.setInsecure();
client.setTimeout(15);
HTTPClient http;
http.setReuse(false);
http.begin(client, HOST, 443, path, true);
http.addHeader("Content-Type", "application/json");
http.setTimeout(15000);
int code = http.POST(body);
bool ok = false;
if (code == 200) {
response = http.getString();
ok = true;
} else {
Serial.printf("POST %s -> %d\n", path.c_str(), code);
}
http.end();
client.stop();
return ok;
}
// ============================================
// GET JOB
// ============================================
bool getJob() {
if (WiFi.status() != WL_CONNECTED) return false;
float tempC = readCpuTempC();
String path = String("/api.php?action=get_job")
+ "&wallet=" + String(WALLET_ADDRESS)
+ "&device_id=" + deviceId
+ "&device_name=" + String(DEVICE_NAME)
+ "&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((int)cpuLoadPercent)
+ "&heap=" + String(ESP.getFreeHeap())
+ "&rssi=" + String(WiFi.RSSI())
+ "&uptime=" + String(millis() / 1000)
+ "&mac=" + WiFi.macAddress();
String response;
if (!httpsGet(path, response)) return false;
StaticJsonDocument<1536> doc;
DeserializationError jerr = deserializeJson(doc, response);
if (jerr != DeserializationError::Ok) {
Serial.printf("JSON error (get_job): %s\n", jerr.c_str());
Serial.printf("Response: %.120s\n", response.c_str());
return false;
}
if (!doc["success"]) {
Serial.printf("Blad: %s\n", doc["error"].as<const char*>());
return false;
}
strncpy(jobId, doc["job_id"].as<const char*>(), sizeof(jobId) - 1);
strncpy(blockTemplate, doc["block_template"].as<const char*>(), sizeof(blockTemplate) - 1);
jobId[sizeof(jobId) - 1] = '\0';
blockTemplate[sizeof(blockTemplate) - 1] = '\0';
shareDifficulty = doc["difficulty"].as<int>();
Serial.printf("Job: %.8s... diff=%d\n", jobId, shareDifficulty);
return true;
}
// ============================================
// TELEMETRIA
// ============================================
void sendTelemetry() {
if (WiFi.status() != WL_CONNECTED) return;
float tempC = readCpuTempC();
StaticJsonDocument<384> 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(cpuLoadPercent * 10.0f) / 10.0f;
doc["heap"] = (int)ESP.getFreeHeap();
doc["rssi"] = (int)WiFi.RSSI();
doc["uptime"] = millis() / 1000;
String body, response;
serializeJson(doc, body);
if (httpsPost("/api.php?action=telemetry", body, response)) {
Serial.printf("[Tele] temp=%.1fC heap=%d rssi=%d\n",
tempC, (int)doc["heap"], (int)doc["rssi"]);
}
}
// ============================================
// SUBMIT SHARE
// ============================================
bool submitShare(unsigned long nonce, float hashrate) {
if (WiFi.status() != WL_CONNECTED) return false;
// Lokalny kontekst SHA do weryfikacji
char hashBuf[65];
mbedtls_sha256_context verifyCtx;
mbedtls_sha256_init(&verifyCtx);
computeHash(&verifyCtx, blockTemplate, nonce, hashBuf);
mbedtls_sha256_free(&verifyCtx);
StaticJsonDocument<512> 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, response;
serializeJson(doc, body);
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 maks 3s...\n");
vTaskDelay(pdMS_TO_TICKS(min((long)waitSec * 1000L, 3000L)));
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"]) {
Serial.printf("*** BLOK! +%.2f MLM ***\n", res["reward"].as<float>());
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.3.1");
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("Heap: %d B free\n", ESP.getFreeHeap());
Serial.printf("Device: %s\n", DEVICE_NAME);
Serial.printf("Wallet: %.16s...\n", WALLET_ADDRESS);
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 RSSI: %d dBm\n\n",
WiFi.localIP().toString().c_str(), WiFi.RSSI());
xMutex = xSemaphoreCreateMutex();
// Pomiar CPU load (niski priorytet, Core 0)
xTaskCreatePinnedToCore(cpuMeasureTask, "CpuMsr", 2048, NULL, 0, NULL, 0);
xTaskCreatePinnedToCore(miningTaskFunction, "Mine0", 16000, (void*)0, 1, &miningTask0, 0);
xTaskCreatePinnedToCore(miningTaskFunction, "Mine1", 16000, (void*)1, 1, &miningTask1, 1);
digitalWrite(LED_PIN, LOW);
statsWindowStart = millis();
lastStatsTime = millis();
lastTelemetryTime = millis();
Serial.println("Koparka uruchomiona! (v3.3.1)");
}
// ============================================
// LOOP
// ============================================
void loop() {
if (WiFi.status() != WL_CONNECTED) {
Serial.println("WiFi zerwane...");
miningActive = false;
WiFi.reconnect();
delay(5000);
return;
}
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); delay(2000); }
}
solutionFound = false;
foundNonce = 0;
hashCount[0] = 0;
hashCount[1] = 0;
}
if (getJob()) { miningActive = true; lastJobTime = millis(); }
else delay(5000);
}
if (millis() - lastTelemetryTime > TELEMETRY_INTERVAL) {
sendTelemetry();
lastTelemetryTime = millis();
}
if (millis() - lastStatsTime > 10000) {
unsigned long elapsed = millis() - statsWindowStart;
if (elapsed > 0) {
float hr = (statsHashCount[0] + statsHashCount[1]) * 1000.0f / elapsed;
int heap = (int)ESP.getFreeHeap();
Serial.printf("[Stats] %.0f H/s Temp:%.1fC CPU:%.0f%% Heap:%d RSSI:%d\n",
hr, readCpuTempC(), cpuLoadPercent, heap, (int)WiFi.RSSI());
if (heap < 30000) {
Serial.printf("[WARN] Malo pamieci: %d B! Restart\n", heap);
delay(500); ESP.restart();
}
statsHashCount[0] = 0;
statsHashCount[1] = 0;
statsWindowStart = millis();
}
lastStatsTime = millis();
}
unsigned long blinkMs = miningActive ? 150 : 1000;
if (millis() - lastBlinkTime >= blinkMs) {
ledState = !ledState;
digitalWrite(LED_PIN, ledState);
lastBlinkTime = millis();
}
delay(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!