diff --git a/Cargo.lock b/Cargo.lock
index ba62ddef50eefaf84463c67904569113bcbc42f2..13e6e51102a2a39da946259403a4dd645ea99971 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -30,6 +30,28 @@ version = "1.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d"
 
+[[package]]
+name = "backtrace"
+version = "0.3.46"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1e692897359247cc6bb902933361652380af0f1b7651ae5c5013407f30e109e"
+dependencies = [
+ "backtrace-sys",
+ "cfg-if",
+ "libc",
+ "rustc-demangle",
+]
+
+[[package]]
+name = "backtrace-sys"
+version = "0.1.37"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "18fbebbe1c9d1f383a9cc7e8ccdb471b91c8d024ee9c2ca5b5346121fe8b4399"
+dependencies = [
+ "cc",
+ "libc",
+]
+
 [[package]]
 name = "base64"
 version = "0.11.0"
@@ -229,15 +251,20 @@ dependencies = [
 
 [[package]]
 name = "duniteroxyde"
-version = "0.2.9"
+version = "0.3.0"
 dependencies = [
  "bincode",
  "bs58",
+ "dubp-pow",
  "dubp-wot",
+ "duniter-common-tools",
  "dup-crypto",
  "flate2",
  "neon",
  "neon-build",
+ "neon-serde",
+ "serde",
+ "serde_json",
 ]
 
 [[package]]
@@ -262,6 +289,16 @@ version = "1.5.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3"
 
+[[package]]
+name = "error-chain"
+version = "0.12.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d371106cc88ffdfb1eabd7111e432da544f16f3e2d7bf1dfe8bf575f1df045cd"
+dependencies = [
+ "backtrace",
+ "version_check",
+]
+
 [[package]]
 name = "flate2"
 version = "1.0.14"
@@ -312,6 +349,12 @@ dependencies = [
  "either",
 ]
 
+[[package]]
+name = "itoa"
+version = "0.4.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8b7a7c0c47db5545ed3fef7468ee7bb5b74691498139e4b3f6a20685dc6dd8e"
+
 [[package]]
 name = "js-sys"
 version = "0.3.37"
@@ -384,9 +427,9 @@ dependencies = [
 
 [[package]]
 name = "neon"
-version = "0.4.0"
+version = "0.3.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6cac4691701b686e6c07b2eb5b51a9f26f5c11179c5d7924b78100dd387fc99d"
+checksum = "53b85accbbd250627f899a6fc1f220bbb4c8c2ff6dc71830dc6b752b39c2eb97"
 dependencies = [
  "cslice",
  "neon-build",
@@ -396,9 +439,9 @@ dependencies = [
 
 [[package]]
 name = "neon-build"
-version = "0.4.0"
+version = "0.3.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f9ed332afd4711b84f4f83d334428a1fd9ce53620b62b87595934297c5ede2ed"
+checksum = "ae406bf1065c4399e69d328a3bd8d4f088f2a205dc3881bf68c0ac775bfef337"
 dependencies = [
  "cfg-if",
  "neon-sys",
@@ -406,24 +449,101 @@ dependencies = [
 
 [[package]]
 name = "neon-runtime"
-version = "0.4.0"
+version = "0.3.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2beea093a60c08463f65e1da4cda68149986f60d8d2177489b44589463c782a6"
+checksum = "d8465ac4ed3f340dead85e053b75a5f639f48ac6343b3523eff90a751758eead"
 dependencies = [
  "cfg-if",
  "neon-sys",
 ]
 
+[[package]]
+name = "neon-serde"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4b45847cc4cec46db1ff2e921cd9948b38afa0c2348c99c06f9ae70406a30b60"
+dependencies = [
+ "error-chain",
+ "neon",
+ "neon-runtime",
+ "num",
+ "serde",
+]
+
 [[package]]
 name = "neon-sys"
-version = "0.4.0"
+version = "0.3.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "69a6c1ba6b926746f4d3f596de18ce49d062d78fd9f35f636080232aa77a0e16"
+checksum = "8ae4cf3871ca5a395077e68144c1754e94e9e1e3329e7f8399d999ca573ed89a"
 dependencies = [
  "cc",
  "regex",
 ]
 
+[[package]]
+name = "num"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8536030f9fea7127f841b45bb6243b27255787fb4eb83958aa1ef9d2fdc0c36"
+dependencies = [
+ "num-complex",
+ "num-integer",
+ "num-iter",
+ "num-rational",
+ "num-traits",
+]
+
+[[package]]
+name = "num-complex"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6b19411a9719e753aff12e5187b74d60d3dc449ec3f4dc21e3989c3f554bc95"
+dependencies = [
+ "autocfg",
+ "num-traits",
+]
+
+[[package]]
+name = "num-integer"
+version = "0.1.42"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f6ea62e9d81a77cd3ee9a2a5b9b609447857f3d358704331e4ef39eb247fcba"
+dependencies = [
+ "autocfg",
+ "num-traits",
+]
+
+[[package]]
+name = "num-iter"
+version = "0.1.40"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dfb0800a0291891dd9f4fe7bd9c19384f98f7fbe0cd0f39a2c6b88b9868bbc00"
+dependencies = [
+ "autocfg",
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
+name = "num-rational"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c000134b5dbf44adc5cb772486d335293351644b801551abe8f75c84cfa4aef"
+dependencies = [
+ "autocfg",
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c62be47e61d1842b9170f0fdeec8eba98e60e90e5446449a0545e5152acd7096"
+dependencies = [
+ "autocfg",
+]
+
 [[package]]
 name = "num_cpus"
 version = "1.13.0"
@@ -577,6 +697,18 @@ dependencies = [
  "winapi 0.3.8",
 ]
 
+[[package]]
+name = "rustc-demangle"
+version = "0.1.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783"
+
+[[package]]
+name = "ryu"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed3d612bc64430efeb3f7ee6ef26d590dce0c43249217bddc62112540c7941e1"
+
 [[package]]
 name = "scopeguard"
 version = "1.1.0"
@@ -618,6 +750,17 @@ dependencies = [
  "syn",
 ]
 
+[[package]]
+name = "serde_json"
+version = "1.0.52"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a7894c8ed05b7a3a279aeb79025fdec1d3158080b75b98a08faf2806bb799edd"
+dependencies = [
+ "itoa",
+ "ryu",
+ "serde",
+]
+
 [[package]]
 name = "shrinkwraprs"
 version = "0.3.0"
@@ -719,6 +862,12 @@ version = "1.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7e33648dd74328e622c7be51f3b40a303c63f93e6fa5f08778b6203a4c25c20f"
 
+[[package]]
+name = "version_check"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "078775d0255232fb988e6fccf26ddc9d1ac274299aaedcedce21c6f72cc533ce"
+
 [[package]]
 name = "wasi"
 version = "0.9.0+wasi-snapshot-preview1"
diff --git a/neon/lib/index.ts b/neon/lib/index.ts
index 48fdad95ce394d4af0b1875f0940595960e531aa..5dec97d8e4f1f977ec6955ab5c64009d27ab4ec6 100644
--- a/neon/lib/index.ts
+++ b/neon/lib/index.ts
@@ -1,3 +1,4 @@
-export { Ed25519Signator, generateRandomSeed, seedToSecretKey, sha256, verify, Wot } from "../native";
+export { Ed25519Signator, generateRandomSeed, seedToSecretKey, sha256, verify, PowConf, PowQuery, ValidProof, Wot } from "../native";
 export { KeyPairBuilder } from "./crypto";
+export { PowCluster } from "./pow";
 export { WotBuilder } from "./wot";
diff --git a/neon/lib/pow.ts b/neon/lib/pow.ts
new file mode 100644
index 0000000000000000000000000000000000000000..fed7a3a697b7dca69b651eefd8d7a64af4178840
--- /dev/null
+++ b/neon/lib/pow.ts
@@ -0,0 +1,95 @@
+import { PowCluster as RustPowCluster } from "../native";
+import { EventEmitter } from 'events';
+import { PowQuery } from "../native/pow";
+
+// The `PowCluster` class provides glue code to abstract the `poll`
+// interface provided by the Neon class. It may be constructed and used
+// as a normal `EventEmitter`, including use by multiple subscribers.
+export class PowCluster extends EventEmitter {
+
+  private logger: any;
+  private rustCluster: RustPowCluster;
+  private isComputing: boolean;
+
+  constructor(logger: any, nbCores: number | null) {
+    super();
+
+    this.logger = logger;
+
+    // Create an instance of the Neon class
+    this.rustCluster = new RustPowCluster(nbCores);
+
+    this.isComputing = false;
+  }
+
+  getWorkersCount() {
+    return this.rustCluster.getWorkersCount();
+  }
+
+  startPow(powQuery: PowQuery, timeout: number): void {
+
+    this.isComputing = true;
+    this.rustCluster.startPow(powQuery);
+
+    setTimeout(() => {
+      if (this.isComputing) {
+        this.isComputing = false;
+        this.emit('proof', null);
+        this.rustCluster.stopPow(false);
+      }
+    }, timeout)
+
+    // The `loop` method is called continuously to receive data from the Rust
+    // work thread.
+    const loop = () => {
+        // Poll for data
+        this.rustCluster.poll((err, powEvent) => {
+          if (err) {
+            this.emit('error', err);
+          } else if (powEvent) {
+            if (powEvent.cancelled) {
+              this.logger.debug("prover: rust pow cluster emit pow cancellation signal");
+              this.emit('proof', null);
+            } else if (powEvent.proof) {
+              this.logger.debug("prover: rust pow cluster emit valid proof");
+              this.emit('proof', powEvent.proof);
+            }
+          } else {
+            // Otherwise, timeout on poll, no data to emit
+    
+            // Schedule the next iteration of the loop. This is performed with
+            // a `setImmediate` to yield to the event loop, to let JS code run
+            // and avoid a stack overflow.
+            setImmediate(loop);
+          }
+        });
+
+        // Stop the receiving loop and shutdown the work thead. However, since
+        // the `poll` method uses a blocking `recv_timeout`, this code will not execute
+        // until either the next event is sent on the channel or a receive
+        // timeout has occurred.
+        if (!this.isComputing) {
+          return;
+        }
+      };
+  
+      // Start the polling loop on next iteration of the JS event loop to prevent zalgo.
+      setImmediate(loop);
+  }
+
+  updateConf(newCpu: number, newSecretKey: string, newPrefix: number): void {
+    this.rustCluster.updateConf(newCpu, newSecretKey, newPrefix);
+  }
+
+  stopPow(notify: boolean): void {
+    this.rustCluster.stopPow(notify);
+    this.isComputing = false;
+  }
+
+  quit(): void {
+    this.rustCluster.quit();
+    this.isComputing = false;
+  }
+}
+
+module.exports = { PowCluster };
diff --git a/neon/native/Cargo.toml b/neon/native/Cargo.toml
index 7331a0a471d4617948e4b9b10b3531428ad46393..89696b9dcd44e86a71b3dd1dc67026a01f5b8c03 100644
--- a/neon/native/Cargo.toml
+++ b/neon/native/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "duniteroxyde"
-version = "0.2.9"
+version = "0.3.0"
 authors = ["librelois <elois@ifee.fr>"]
 license = "AGPL-3.0"
 build = "build.rs"
@@ -12,12 +12,17 @@ name = "duniteroxyde"
 crate-type = ["cdylib"]
 
 [build-dependencies]
-neon-build = "0.4.0"
+neon-build = "0.3.3"
 
 [dependencies]
 bincode = "1.2.1"
 bs58 = "0.3.0"
+duniter-common-tools = { path = "../../rust-libs/common-tools" }
 dup-crypto = { version = "0.15.0", default-features = false, features = ["rand"] }
+dubp-pow = { path="../../rust-libs/pow" }
 dubp-wot = "0.11.0"
 flate2 = "1.0.14"
-neon = "0.4.0"
+neon = "0.3.3"
+neon-serde = "0.3.0"
+serde = { version = "1.0.*", features = ["derive"] }
+serde_json = "1.0.52"
diff --git a/neon/native/index.d.ts b/neon/native/index.d.ts
index e31c8cbdf33858186239385d59905349669bc4d6..476645481f13f72ddf6d8eafca963322e2f71b11 100644
--- a/neon/native/index.d.ts
+++ b/neon/native/index.d.ts
@@ -1,6 +1,7 @@
 /* tslint:disable */
 
 import * as _crypto from './crypto';
+import * as _pow from './pow';
 import * as _wot from './wot';
 
 export import Ed25519Signator = _crypto.Ed25519Signator;
@@ -9,5 +10,10 @@ export import seedToSecretKey = _crypto.seedToSecretKey;
 export import sha256 = _crypto.sha256;
 export import verify = _crypto.verify;
 
+export import PowCluster = _pow.PowCluster;
+export import PowConf = _pow.PowConf;
+export import PowQuery = _pow.PowQuery;
+export import ValidProof = _pow.ValidProof;
+
 export import Wot = _wot.Wot;
 export import DetailedDistance = _wot.DetailedDistance;
diff --git a/neon/native/pow.d.ts b/neon/native/pow.d.ts
new file mode 100644
index 0000000000000000000000000000000000000000..9c3d6df40bff9dc944d55e1237d99b74212b432e
--- /dev/null
+++ b/neon/native/pow.d.ts
@@ -0,0 +1,47 @@
+/* tslint:disable */
+
+import { EventEmitter } from "events";
+
+export class PowCluster extends EventEmitter {
+    constructor(nbCores: number | null)
+
+    getWorkersCount(): number
+
+    startPow(powQuery: PowQuery): void
+
+    poll(callback: (err: any, event: any) => void): void
+
+    updateConf(
+        newCpu: number,
+        newSecretKey: string,
+        newPrefix: number
+    ): void
+
+    stopPow(notifyRequestor: boolean): void
+
+    quit(): void
+}
+
+export class PowConf {
+    cpu: number
+    secretKey: string
+    prefix: number
+}
+
+export class PowQuery {
+    blockInnerHash: string
+    cpu: number
+    diff: number
+    marker: number
+    nbCores?: number
+    prefix: number
+    secretKey: string
+}
+
+export class ValidProof {
+    itersCount: number
+    nonce: number
+    sig: string
+    hash: string
+    workerId: number
+}
diff --git a/neon/native/src/crypto.rs b/neon/native/src/crypto.rs
index 76414e8dfbdc51a91b14a6dc7acf7bef828a2186..c8b8680fee1fc5c6dbdc8e1947a2eb5835599a39 100644
--- a/neon/native/src/crypto.rs
+++ b/neon/native/src/crypto.rs
@@ -92,7 +92,8 @@ declare_types! {
                         .downcast::<JsString>()
                         .or_throw(&mut cx)?
                         .value();
-                        into_neon_res(&mut cx, keypair_from_expanded_base58_secret_key(&expanded_base58_secret_key))
+                    into_neon_res(&mut cx, keypair_from_expanded_base58_secret_key(&expanded_base58_secret_key))
+                        .map(|keypair| keypair.generate_signator())
                 } else if arg0.is_a::<JsBuffer>() {
                     let seed_js_buffer = arg0
                         .downcast::<JsBuffer>()
@@ -135,9 +136,9 @@ declare_types! {
     }
 }
 
-fn keypair_from_expanded_base58_secret_key(
+pub(crate) fn keypair_from_expanded_base58_secret_key(
     expanded_base58_secret_key: &str,
-) -> Result<Ed25519Signator, &'static str> {
+) -> Result<Ed25519KeyPair, &'static str> {
     let bytes = bs58::decode(expanded_base58_secret_key)
         .into_vec()
         .map_err(|_| "fail to decode b58")?;
@@ -152,7 +153,7 @@ fn keypair_from_expanded_base58_secret_key(
     //let expected_pubkey = Ed25519PublicKey::try_from(pubkey_bytes.as_ref());
 
     if keypair.public_key().as_ref()[..32] == pubkey_bytes {
-        Ok(keypair.generate_signator())
+        Ok(keypair)
     } else {
         Err("corrupted keypair")
     }
@@ -200,7 +201,8 @@ mod tests {
         let expanded_base58_secret_key = "51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP";
 
         let signator = keypair_from_expanded_base58_secret_key(expanded_base58_secret_key)
-            .expect("fail to generate keypair");
+            .expect("fail to generate keypair")
+            .generate_signator();
 
         assert_eq!(
             "HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd",
diff --git a/neon/native/src/lib.rs b/neon/native/src/lib.rs
index 94aa1964de9374642d5aff07763705765f6ce4d5..b6eee9ce5cfcbe60c96ed339d7ad7260b2101275 100644
--- a/neon/native/src/lib.rs
+++ b/neon/native/src/lib.rs
@@ -14,10 +14,12 @@
 // along with this program.  If not, see <https://www.gnu.org/licenses/>.
 
 mod crypto;
+mod pow;
 mod wot;
 
 use neon::{prelude::*, register_module};
 
+#[inline]
 fn into_neon_res<'c, C: Context<'c>, T, S: AsRef<str>>(
     context: &mut C,
     rust_result: Result<T, S>,
@@ -28,6 +30,14 @@ fn into_neon_res<'c, C: Context<'c>, T, S: AsRef<str>>(
     }
 }
 
+#[inline]
+fn return_json_obj<'c, C: Context<'c>>(
+    context: &mut C,
+    json: &serde_json::Value,
+) -> NeonResult<Handle<'c, JsValue>> {
+    Ok(neon_serde::to_value(context, json)?.upcast())
+}
+
 register_module!(mut cx, {
     cx.export_function("generateRandomSeed", crate::crypto::generate_random_seed)?;
     cx.export_function(
@@ -37,6 +47,7 @@ register_module!(mut cx, {
     cx.export_function("sha256", crate::crypto::sha256)?;
     cx.export_function("verify", crate::crypto::verify)?;
     cx.export_class::<crate::crypto::JsKeyPair>("Ed25519Signator")?;
+    cx.export_class::<crate::pow::PowCluster>("PowCluster")?;
     cx.export_class::<crate::wot::JsWoT>("Wot")?;
     Ok(())
 });
diff --git a/neon/native/src/pow.rs b/neon/native/src/pow.rs
new file mode 100644
index 0000000000000000000000000000000000000000..be532c27202b38386042a1b6ffe6e4e4062ee2b4
--- /dev/null
+++ b/neon/native/src/pow.rs
@@ -0,0 +1,247 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+use crate::crypto::keypair_from_expanded_base58_secret_key;
+use crate::{into_neon_res, return_json_obj};
+use dubp_pow::{PowCluster as RustPowCluster, PowConf, PowQuery, RequestorMsg, ValidProof};
+use duniter_common_tools::Percent;
+use dup_crypto::hashs::Hash;
+use neon::declare_types;
+use neon::prelude::*;
+use serde::Deserialize;
+use serde_json::json;
+use std::num::NonZeroUsize;
+use std::sync::{Arc, Mutex};
+use std::{
+    sync::mpsc::{channel, Receiver, RecvTimeoutError, Sender},
+    time::Duration,
+};
+
+const POW_WORKERS_NAME: &str = "duniter_pow_w";
+
+#[derive(Debug, Deserialize)]
+#[serde(rename_all = "camelCase")]
+struct PowQueryFromJs {
+    block_inner_hash: String,
+    cpu: u8,
+    diff: usize,
+    #[serde(rename = "secretKey")]
+    expanded_base58_secret_key: String,
+    marker: usize,
+    nb_cores: Option<usize>,
+    prefix: usize,
+}
+
+struct PowTask(Arc<Mutex<Receiver<RequestorMsg>>>);
+
+impl Task for PowTask {
+    type Output = Option<RequestorMsg>;
+    type Error = String;
+    type JsEvent = JsValue;
+
+    fn perform(&self) -> Result<Self::Output, Self::Error> {
+        let rx = self
+            .0
+            .lock()
+            .map_err(|_| "Could not obtain lock on receiver".to_string())?;
+
+        // Attempt to read from the channel. Block for at most 100 ms.
+        match rx.recv_timeout(Duration::from_millis(100)) {
+            Ok(event) => Ok(Some(event)),
+            Err(RecvTimeoutError::Timeout) => Ok(None),
+            Err(RecvTimeoutError::Disconnected) => Err("Failed to receive event".to_string()),
+        }
+    }
+
+    fn complete(
+        self,
+        mut cx: TaskContext,
+        event: Result<Self::Output, Self::Error>,
+    ) -> JsResult<Self::JsEvent> {
+        // Receive the event or return early with the error
+        let event = event.or_else(|err| cx.throw_error(err))?;
+
+        // Timeout occured, return early with `undefined
+        match event {
+            Some(RequestorMsg::FoundValidProof(ValidProof {
+                iters_count,
+                nonce,
+                sig,
+                hash,
+                worker_nonce_area,
+            })) => return_json_obj(
+                &mut cx,
+                &json!({
+                    "cancelled": false,
+                    "proof": {
+                        "itersCount": iters_count,
+                        "nonce": nonce,
+                        "sig": sig.to_string(),
+                        "hash": hash.to_string(),
+                        "workerId": worker_nonce_area.0,
+                    },
+                }),
+            ),
+            Some(RequestorMsg::PowStoppedByCluster) => {
+                return_json_obj(&mut cx, &json!({ "cancelled": true }))
+            }
+            None => Ok(cx.undefined().upcast()),
+        }
+    }
+}
+
+pub struct PowClusterWithChannel {
+    cluster: RustPowCluster,
+    receiver: Arc<Mutex<Receiver<RequestorMsg>>>,
+    sender: Sender<RequestorMsg>,
+}
+
+declare_types! {
+    pub class PowCluster for PowClusterWithChannel {
+        init(mut cx) {
+            let arg0 = cx.argument::<JsValue>(0)?;
+            let max_cores_opt = if arg0.is_a::<JsNumber>() {
+                Some(arg0
+                    .downcast::<JsNumber>()
+                    .or_throw(&mut cx)?
+                    .value()
+                    .trunc() as usize)
+            } else if arg0.is_a::<JsNull>() || arg0.is_a::<JsUndefined>() {
+                None
+            } else {
+                return cx.throw_type_error("arg0 must be a number");
+            };
+
+            let (sender, receiver) = channel();
+
+            match RustPowCluster::init(max_cores_opt, POW_WORKERS_NAME) {
+                Ok(pow_cluster) => Ok(PowClusterWithChannel {
+                    cluster: pow_cluster,
+                    receiver: Arc::new(Mutex::new(receiver)),
+                    sender,
+                }),
+                Err(e) => cx.throw_error(e.to_string())
+            }
+        }
+        method startPow(mut cx) {
+            let arg0 = cx.argument::<JsValue>(0)?;
+            let pow_query_from_js: PowQueryFromJs = neon_serde::from_value(&mut cx, arg0)?;
+
+            //println!("{:?}", pow_query_from_js);
+
+            let block_inner_hash = into_neon_res(
+                &mut cx,
+                Hash::from_hex(&pow_query_from_js.block_inner_hash).map_err(|e| format!("Invalid innerHash: {}", e))
+            )?;
+            let conf = PowConf {
+                cpu: into_neon_res(&mut cx, Percent::new(pow_query_from_js.cpu).map_err(|e| format!("Invalid cpu: {}", e)))?,
+                keypair: into_neon_res(&mut cx, keypair_from_expanded_base58_secret_key(&pow_query_from_js.expanded_base58_secret_key))?,
+                prefix: into_neon_res(&mut cx, NonZeroUsize::new(pow_query_from_js.prefix).ok_or("prefix must not be zero"))?,
+            };
+
+            let mut this = cx.this();
+            let res = {
+                let guard = cx.lock();
+                let mut pow_cluster_with_channel = this.borrow_mut(&guard);
+
+                let pow_query = PowQuery {
+                    block_inner_hash,
+                    conf,
+                    diff: pow_query_from_js.diff,
+                    marker: pow_query_from_js.marker,
+                    requestor_sender: pow_cluster_with_channel.sender.clone(),
+                };
+                pow_cluster_with_channel.cluster.start_pow(&pow_query, pow_query_from_js.nb_cores)
+            };
+
+            into_neon_res(&mut cx, res.map_err(|e| format!("Fail to start pow: {}", e)))?;
+            Ok(cx.undefined().upcast())
+        }
+        // This method should be called by JS to receive data. It accepts a
+        // `function (err, data)` style asynchronous callback. It may be called
+        // in a loop, but care should be taken to only call it once at a time.
+        method poll(mut cx) {
+            // The callback to be executed when data is available
+            let cb = cx.argument::<JsFunction>(0)?;
+            let this = cx.this();
+
+            // Create an asynchronously `PowTask` to receive data
+            let proof_receiver = cx.borrow(&this, |cluster_with_channel| Arc::clone(&cluster_with_channel.receiver));
+            let task = PowTask(proof_receiver);
+
+            // Schedule the task on the `libuv` thread pool
+            task.schedule(cb);
+
+            // The `poll` method does not return any data.
+            Ok(JsUndefined::new().upcast())
+        }
+        method getWorkersCount(mut cx) {
+            let this = cx.this();
+            let workers_count = {
+                let guard = cx.lock();
+                let pow_cluster_with_task = this.borrow(&guard);
+                pow_cluster_with_task.cluster.get_workers_count()
+            };
+
+            Ok(cx.number(workers_count as f64).upcast())
+        }
+        method updateConf(mut cx) {
+            let arg0 = cx.argument::<JsNumber>(0)?.value();
+            let arg1 = cx.argument::<JsString>(1)?.value();
+            let arg2 = cx.argument::<JsNumber>(2)?.value();
+
+            let new_pow_conf = PowConf {
+                cpu: into_neon_res(&mut cx, Percent::new(arg0 as u8).map_err(|e| format!("Invalid cpu: {}", e)))?,
+                keypair: into_neon_res(&mut cx, keypair_from_expanded_base58_secret_key(&arg1))?,
+                prefix: into_neon_res(&mut cx, NonZeroUsize::new(arg2 as usize).ok_or("prefix must not be zero"))?,
+            };
+
+            let this = cx.this();
+            let res = {
+                let guard = cx.lock();
+                let pow_cluster_with_channel = this.borrow(&guard);
+                pow_cluster_with_channel.cluster.change_pow_conf(&new_pow_conf)
+            };
+
+            into_neon_res(&mut cx, res.map_err(|e| format!("Fail to update pow conf: {}", e)))?;
+
+            Ok(cx.undefined().upcast())
+        }
+        method stopPow(mut cx) {
+            let notify_requestor = cx.argument::<JsBoolean>(0)?.value();
+            let this = cx.this();
+            let res = {
+                let guard = cx.lock();
+                let pow_cluster_with_channel = this.borrow(&guard);
+
+                pow_cluster_with_channel.cluster.stop_pow(notify_requestor)
+            };
+
+            into_neon_res(&mut cx, res.map_err(|e| e.to_string()))?;
+
+            Ok(cx.undefined().upcast())
+        }
+        method quit(mut cx) {
+            let mut this = cx.this();
+            {
+                let guard = cx.lock();
+                let mut pow_cluster_with_channel = this.borrow_mut(&guard);
+                pow_cluster_with_channel.cluster.quit_unchecked();
+            }
+
+            Ok(cx.undefined().upcast())
+        }
+    }
+}
diff --git a/package-lock.json b/package-lock.json
index 0361be35c884feef7a1bddb07beb737308517dfe..459b7c2c96e75f38ba2d363abd4e6e4cfa461c9b 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -5486,6 +5486,19 @@
         "os-tmpdir": "^1.0.0"
       }
     },
+    "p-event": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/p-event/-/p-event-4.1.0.tgz",
+      "integrity": "sha512-4vAd06GCsgflX4wHN1JqrMzBh/8QZ4j+rzp0cd2scXRwuBEv+QR3wrVA5aLhWDLw4y2WgDKvzWF3CCLmVM1UgA==",
+      "requires": {
+        "p-timeout": "^2.0.1"
+      }
+    },
+    "p-finally": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz",
+      "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4="
+    },
     "p-limit": {
       "version": "2.2.2",
       "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.2.tgz",
@@ -5511,6 +5524,14 @@
         "aggregate-error": "^3.0.0"
       }
     },
+    "p-timeout": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-2.0.1.tgz",
+      "integrity": "sha512-88em58dDVB/KzPEx1X0N3LwFfYZPyDc4B6eF38M1rk9VTZMbxXXgjugz8mmwpS9Ox4BDZ+t6t3QP5+/gazweIA==",
+      "requires": {
+        "p-finally": "^1.0.0"
+      }
+    },
     "p-try": {
       "version": "2.2.0",
       "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
diff --git a/package.json b/package.json
index 654c92e86ec389e66980f2af21ff2294567434eb..8f10c63ec47f889a72d1e5965a7ba99ed7aec31d 100644
--- a/package.json
+++ b/package.json
@@ -97,6 +97,7 @@
     "node-pre-gyp": "0.14.0",
     "node-uuid": "1.4.8",
     "optimist": "0.6.1",
+    "p-event": "4.1.0",
     "prettier": "^2.0.4",
     "q-io": "1.13.6",
     "querablep": "0.1.0",
diff --git a/test/neon/test_pow.ts b/test/neon/test_pow.ts
new file mode 100644
index 0000000000000000000000000000000000000000..512ee9be65f1f6cf2738cc25518fa5e2badbbc1d
--- /dev/null
+++ b/test/neon/test_pow.ts
@@ -0,0 +1,96 @@
+"use strict";
+
+import * as pEvent from "p-event";
+import { PowCluster } from "../../neon/lib";
+import { assertNotNull, assertNull, assertEqual } from "../integration/tools/test-framework";
+const winston = require('winston')
+
+describe('PoW cluster tests:', function(){
+
+  it('Start pow', async() => {
+    let powCluster = new PowCluster(winston, 1);
+    assertEqual(powCluster.getWorkersCount(), 1);
+    powCluster.startPow({
+        blockInnerHash: '2CF24DBA5FB0A30E26E83B2AC5B9E29E1B161E5C1FA7425E73043362938B9824',
+        cpu: 100,
+        diff: 50,
+        secretKey: '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP',
+        marker: 999,
+        prefix: 4,
+    }, 1000);
+
+    let proofOrNull = await pEvent(powCluster, 'proof');
+    assertNotNull(proofOrNull);
+    console.log(proofOrNull);
+    powCluster.quit();
+  });
+
+  it('Start pow with a single worker', async() => {
+    let powCluster2 = new PowCluster(winston, 2);
+    assertEqual(powCluster2.getWorkersCount(), 2);
+    powCluster2.startPow({
+        blockInnerHash: '2CF24DBA5FB0A30E26E83B2AC5B9E29E1B161E5C1FA7425E73043362938B9824',
+        cpu: 100,
+        diff: 16,
+        secretKey: '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP',
+        marker: 0,
+        nbCores: 1,
+        prefix: 4,
+    }, 1000);
+    let proofOrNull = await pEvent(powCluster2, 'proof');
+    assertNotNull(proofOrNull);
+    assertEqual(proofOrNull.workerId, 0);
+
+    powCluster2.startPow({
+      blockInnerHash: '2CF24DBA5FB0A30E26E83B2AC5B9E29E1B161E5C1FA7425E73043362938B9824',
+      cpu: 100,
+      diff: 16,
+      secretKey: '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP',
+      marker: 1,
+      nbCores: 1,
+      prefix: 4,
+    }, 1000);
+    let proofOrNull2 = await pEvent(powCluster2, 'proof');
+    assertNotNull(proofOrNull2);
+    assertEqual(proofOrNull2.workerId, 0);
+
+    powCluster2.quit();
+  });
+
+  it('Cancel pow and restart', async() => {
+    let powCluster3 = new PowCluster(winston, 1);
+    powCluster3.startPow({
+        blockInnerHash: '2CF24DBA5FB0A30E26E83B2AC5B9E29E1B161E5C1FA7425E73043362938B9824',
+        cpu: 1,
+        diff: 150,
+        secretKey: '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP',
+        marker: 999,
+        prefix: 4,
+    }, 1000);
+
+    await new Promise((res) => setTimeout(res, 20))
+
+    powCluster3.stopPow(true);
+
+    console.log("Wait pow cancellation..");    
+    assertNull(await pEvent(powCluster3, 'proof'));
+
+    console.log("Pow cancelled. Restart pow..");
+
+    powCluster3.startPow({
+      blockInnerHash: '2CF24DBA5FB0A30E26E83B2AC5B9E29E1B161E5C1FA7425E73043362938B9824',
+      cpu: 1,
+      diff: 32,
+      secretKey: '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP',
+      marker: 0,
+      prefix: 8,
+    }, 1000);
+    //await new Promise((res) => setTimeout(res, 20))
+    console.log("Wait proof..");
+    let proofOrNull2 = await pEvent(powCluster3, 'proof');
+    assertNotNull(proofOrNull2);
+    console.log(proofOrNull2);
+    powCluster3.quit();
+  });
+});
+