POW Distribution
PoW or Proof-of-Work is a mechanism which allows SKALE chain owner to mine sufficient amount of sFUEL to any address to send transaction, if address doesn’t have enough amount of sFUEL.
PoW Usage
- The client generates a unique gasPrice value based on the transaction data
- The client sends transaction with a unique gasPrice value and the required gasAmount, which was calculated during the algorithm.
- GasPrice and gasAmount of this transaction will be checked on the SKALE chain side by the same check in the algorithm.
- Transaction is executed, even without sFUEL in the client’s account.
PoW algorithm
- The client generates a random 256-bit X by Formula which is equal a unique gasPrice value
- Check whether X is allow to run transaction
a. If not, then back to the first step
b. If the condition is met - send the transaction with gasPrice = X and gasAmount = freeGas(X)
PoW sFUEL Distribution
- Get a wallet to sign transactions - a random generated one can be used since PoW based transactions don’t require signer to have sFUEL
- Create a transaction to call sFUEL distribution contract function
- Unique gasPrice function generated - based on: signer address, signer nonce and gas
- Send transaction with the unique GasPrice
Available On-Chain sFUEL Faucets
Chain | Mainnet Address | Testnet Address | Function Signature |
---|---|---|---|
Europa | 0x2B267A3e49b351DEdac892400a530ABb2f899d23 | 0x366727B410fE55774C8b0B5b5A6E2d74199a088A | 0x0c11dedd |
Calypso | 0x02891b34B7911A9C68e82C193cd7A6fBf0c3b30A | 0x62Fe932FF26e0087Ae383f6080bd2Ed481bA5A8A | 0x0c11dedd |
Nebula | 0x5a6869ef5b81DCb58EBF51b8F893c31f5AFE3Fa8 | 0x000E9c53C4e2e21F5063f2e232d0AA907318dccb | 0x0c11dedd |
Titan | 0xa5C297dF8f8386E4b940D61EF9A8f2bB367a6fAB | 0x08f98Af60eb83C18184231591A8F89577E46A4B9 | 0x0c11dedd |
Implementation Example
-
Random signer generation
import { JsonRpcProvider, Wallet } from "ethers";const provider = new JsonRpcProvider(chain?.chainInfo?.[isMainnet ? "mainnet" : "testnet"]?.rpcUrl);const randomWallet = Wallet.createRandom(provider); -
Request creation to call sFUEL distribution contract function
const nonce = await provider.getTransactionCount(randomWallet.address);const functionSignature = key === "europa" && isMainnet ? "0x6a627842" : "0x0c11dedd";const { duration, gasPrice } = await miner.mineGasForTransaction(nonce, 100_000, randomWallet.address);const request = {to: chain?.chainInfo?.[isMainnet ? "mainnet" : "testnet"].proofOfWork,data: `${functionSignature}000000000000000000000000${address.substring(2)}`,gasLimit: 100_000,gasPrice} -
Unique Gas Price Generation
import { isHexString, getNumber, randomBytes, keccak256, toBeHex, toBigInt, ethers } from 'ethers'export default class Miner {public static MAX_NUMBER = ethers.MaxUint256;public async mineGasForTransaction(nonce: string | number,gas: string | number,from: string): Promise<{duration: number;gasPrice: bigint;}> {let address = fromnonce = isHexString(nonce) ? getNumber(nonce) : (nonce as number)gas = isHexString(gas) ? getNumber(gas) : (gas as number)return await this.mineFreeGas(gas as number, address, nonce as number)}public async mineFreeGas(gasAmount: number, address: string, nonce: number): Promise<{duration: number;gasPrice: bigint;}> {let nonceHash = toBigInt(keccak256(toBeHex(nonce, 32)));let addressHash = toBigInt(keccak256(address));let nonceAddressXOR = nonceHash ^ addressHash;let divConstant = Miner.MAX_NUMBER;let candidate: Uint8Array;let iterations = 0;const start = performance.now();while (true) {candidate = randomBytes(32)let candidateHash = toBigInt(keccak256(candidate));let resultHash = nonceAddressXOR ^ candidateHash;let externalGas = divConstant / resultHash;if (externalGas >= gasAmount) {break;}// every 2k iterations, yield to the event loopif (iterations++ % 1_000 === 0) {await new Promise<void>((resolve) => setTimeout(resolve, 0));}}const end = performance.now();return {duration: start - end,gasPrice: toBigInt(candidate)}}} -
Call sFUEL distribution contract with unique Gas Price
const response = await randomWallet.sendTransaction(request);await provider.waitForTransaction(response.hash, 1);await new Promise<void>((resolve) => setTimeout(resolve, 1000));
-
Random signer generation
import SkalePowMiner from "@skaleproject/pow";import BN from "bn.js";import Web3 from "web3";this.provider = new Web3(params.rpcUrl);let wallet = this.provider.eth.accounts.create(); -
Request creation to call sFUEL distribution contract function
const to = "0x_sfuel_contract_address";let nonce = await this.provider.eth.getTransactionCount(wallet.address);let gas: number = params.gas ?? 100000;const mineFreeGasResult = await this.miner.mineGasForTransaction(nonce, gas, wallet.address)const functionSignature = key === "europa" && isMainnet ? "0x6a627842" : "0x0c11dedd";const request = {from: wallet.address,to,data: `${functionSignature}000000000000000000000000${address.substring(2)}`,nonce,gasPrice: mineFreeGasResult} -
Unique Gas Price Generation
import BN from "bn.js";import { isHex, hexToNumber, soliditySha3 } from "web3-utils";import * as crypto from "crypto";interface Params {difficulty?: BN;}export default class SkalePowMiner {public difficulty: BN = new BN(1);constructor(params?: Params) {if (params && params.difficulty) this.difficulty = params.difficulty;}public async mineGasForTransaction(nonce: string | number, gas: string | number, from: string, bytes?: string) : Promise<any> {let address = from;nonce = isHex(nonce) ? hexToNumber(nonce) : nonce as number;gas = isHex(gas) ? hexToNumber(gas) : gas as number;return await this.mineFreeGas(gas as number, address, nonce as number, bytes);}public async mineFreeGas(gasAmount: number, address: string, nonce: number, bytes?: string) {let nonceHash = new BN((soliditySha3(nonce) as string).slice(2), 16);let addressHash = new BN((soliditySha3(address) as string).slice(2), 16);let nonceAddressXOR = nonceHash.xor(addressHash)let maxNumber = new BN(2).pow(new BN(256)).sub(new BN(1));let divConstant = maxNumber.div(this.difficulty);let candidate: BN;let _bytes: string;let iterations = 0while (true) {_bytes = crypto.randomBytes(32).toString("hex");candidate = new BN(bytes ?? _bytes, 16);let candidateHash = new BN((soliditySha3(candidate) as string).slice(2), 16);let resultHash = nonceAddressXOR.xor(candidateHash);let externalGas = divConstant.div(resultHash).toNumber();if (externalGas >= gasAmount) {break;}// every 2k iterations, yield to the event loopif (iterations++ % 2_000 === 0) {await new Promise<void>((resolve) => setTimeout(resolve, 0));}}return candidate.toString();}} -
Call sFUEL distribution contract with unique Gas Price
const tx = await this.provider.eth.sendTransaction(request);
-
Random signer generation
const { JsonRpcProvider, Wallet } = require("ethers");const provider = new JsonRpcProvider(chain.chainInfo[isMainnet ? "mainnet" : "testnet"].rpcUrl);const randomWallet = Wallet.createRandom(provider); -
Request creation to call sFUEL distribution contract function
const nonce = await provider.getTransactionCount(randomWallet.address);const functionSignature = key === "europa" && isMainnet ? "0x6a627842" : "0x0c11dedd";const { duration, gasPrice } = await miner.mineGasForTransaction(nonce, 100_000, randomWallet.address);const request = {to: chain.chainInfo[isMainnet ? "mainnet" : "testnet"].proofOfWork,data: `${functionSignature}000000000000000000000000${address.substring(2)}`,gasLimit: 100_000,gasPrice}; -
Unique Gas Price Generation
const { isHexString, getNumber, randomBytes, keccak256, toBeHex, toBigInt, ethers } = require('ethers');class Miner {static MAX_NUMBER = ethers.MaxUint256;async mineGasForTransaction(nonce, gas, from) {let address = from;nonce = isHexString(nonce) ? getNumber(nonce) : nonce;gas = isHexString(gas) ? getNumber(gas) : gas;return await this.mineFreeGas(gas, address, nonce);}async mineFreeGas(gasAmount, address, nonce) {let nonceHash = toBigInt(keccak256(toBeHex(nonce, 32)));let addressHash = toBigInt(keccak256(address));let nonceAddressXOR = nonceHash ^ addressHash;let divConstant = Miner.MAX_NUMBER;let candidate;let iterations = 0;const start = performance.now();while (true) {candidate = randomBytes(32);let candidateHash = toBigInt(keccak256(candidate));let resultHash = nonceAddressXOR ^ candidateHash;let externalGas = divConstant / resultHash;if (externalGas >= gasAmount) {break;}// every 2k iterations, yield to the event loopif (iterations++ % 1_000 === 0) {await new Promise((resolve) => setTimeout(resolve, 0));}}const end = performance.now();return {duration: start - end,gasPrice: toBigInt(candidate)};}}module.exports = Miner; -
Call sFUEL distribution contract with unique Gas Price
const response = await randomWallet.sendTransaction(request);await provider.waitForTransaction(response.hash, 1);await new Promise((resolve) => setTimeout(resolve, 1000));
-
Random signer generation
import SkalePowMiner from "@skaleproject/pow";import BN from "bn.js";import Web3 from "web3";this.provider = new Web3(params.rpcUrl);let wallet = this.provider.eth.accounts.create(); -
Request creation to call sFUEL distribution contract function
const to = "0x_sfuel_contract_address";let nonce = await this.provider.eth.getTransactionCount(wallet.address);let gas = params.gas ?? 100000;const mineFreeGasResult = await this.miner.mineGasForTransaction(nonce, gas, wallet.address)const functionSignature = key === "europa" && isMainnet ? "0x6a627842" : "0x0c11dedd";const request = {from: wallet.address,to,data: `${functionSignature}000000000000000000000000${address.substring(2)}`,nonce,gasPrice: mineFreeGasResult} -
Unique Gas Price Generation
const BN = require("bn.js");const crypto = require("crypto")const DIFFICULTY = new BN(1);async function mineGasForTransaction(web3, tx) {if(tx.from === undefined || tx.nonce === undefined) {throw new Error("Not enough fields for mining gas (from, nonce)")}if (!tx.gas) {tx.gas = await web3.eth.estimateGas(tx)}let address = tx.fromlet nonce = web3.utils.isHex(tx.nonce) ? web3.utils.hexToNumber(tx.nonce) : tx.nonce;let gas = web3.utils.isHex(tx.gas) ? web3.utils.hexToNumber(tx.gas) : tx.gas;tx.gasPrice = mineFreeGas(gas, address, nonce, web3);}function mineFreeGas(gasAmount, address, nonce, web3) {console.log('Mining free gas: ', gasAmount);let nonceHash = new BN(web3.utils.soliditySha3(nonce).slice(2), 16)let addressHash = new BN(web3.utils.soliditySha3(address).slice(2), 16)let nonceAddressXOR = nonceHash.xor(addressHash)let maxNumber = new BN(2).pow(new BN(256)).sub(new BN(1));let divConstant = maxNumber.div(DIFFICULTY);let candidate;while (true){candidate = new BN(crypto.randomBytes(32).toString('hex'), 16);let candidateHash = new BN(web3.utils.soliditySha3(candidate).slice(2), 16);let resultHash = nonceAddressXOR.xor(candidateHash);let externalGas = divConstant.div(resultHash).toNumber();if (externalGas >= gasAmount) {break;}}return candidate.toString();}exports.mineGasForTransaction = mineGasForTransaction; -
Call sFUEL distribution contract with unique Gas Price
const tx = await this.provider.eth.sendTransaction(request);
-
Random signer generation
using System.Collections;using System.Collections.Generic;using UnityEngine;using Nethereum.Web3.Accounts;using System;public class AnonymousWallet: WalletPow{public AnonymousWallet(Chains chain): base(GenerateRandomAccount(), chain){}public static Account GenerateRandomAccount(){var ecKey = Nethereum.Signer.EthECKey.GenerateKey();byte[] privateKey = ecKey.GetPrivateKeyAsBytes();string hexString = BitConverter.ToString(privateKey).Replace("-", string.Empty);return new Account(hexString);}} -
Request creation to call sFUEL distribution contract function
using System.Threading.Tasks;using Nethereum.Web3;using Nethereum.RPC.Eth.DTOs;using Nethereum.Hex.HexTypes;using System.Numerics;using Nethereum.Web3.Accounts;public class WalletPow{public Miner miner_PoW;private Account account;private Web3 web3;// Scriptable object created within the chain detailsprivate Chains current_chain;public WalletPow(Account new_account, Chains chain){account = new_account;current_chain = chain;#if UNITY_EDITORweb3 = new Web3(account, current_chain.rpc);#elseweb3 = new Web3(account, new UnityWebRequestRpcTaskClient(new Uri(current_chain.rpc)));#endif}public async Task<TransactionInput> CreateTransactionInput(string receiver_address){web3.TransactionReceiptPolling.SetPollingRetryIntervalInMilliseconds(300);web3.TransactionManager.UseLegacyAsDefault = true;string data = current_chain.functionSignature + "000000000000000000000000" + receiver_address;var transactionInput = new TransactionInput(){From = account.Address,To = current_chain.address,Nonce = await web3.Eth.Transactions.GetTransactionCount.SendRequestAsync(account.Address),Gas = new HexBigInteger("10000"),Value = new HexBigInteger("0"),Data = data};return transactionInput;}public async Task<TransactionReceipt> sFUEL_Distribution(string receiver_address){var transactionInput = await CreateTransactionInput(receiver_address);string unique_gas_price = await miner_PoW.POW_Caller(current_chain, transactionInput);transactionInput.GasPrice = new HexBigInteger(BigInteger.Parse(unique_gas_price));return await web3.Eth.TransactionManager.SendTransactionAndWaitForReceiptAsync(transactionInput);}} -
Unique Gas Price Generation
using System;using System.Numerics;using System.Security.Cryptography;using System.Threading.Tasks;using Nethereum.Hex.HexConvertors.Extensions;using Nethereum.RPC.Eth.DTOs;using Nethereum.Util;using Nethereum.Web3;using UnityEngine;using BNSharp;using Nethereum.ABI.Encoders;using Nethereum.ABI;using Nethereum.Unity.Rpc;using System.Collections;public class Miner: MonoBehaviour{/*** Receives the transaction data used to generate the unique gas price*/public IEnumerator MineGasForTransaction(System.Action<string> callback,Chains schain, TransactionInput tx){if (tx.From == null || tx.Nonce == null){throw new ArgumentException("Not enough fields for mining gas (from, nonce)");}var address = tx.From;var nonce = (long)(tx.Nonce).Value;var gas = (long)(tx.Gas).Value;string result = null;StartCoroutine(MineFreeGas((value) =>{result = value;}, gas, address, nonce));while (result == null){yield return null;}callback(result);}/*** Unique Gas Price Generator*/public IEnumerator MineFreeGas(System.Action<string> callback,long gasAmount, string address,long nonce){BN bn_nonce_hash = new BN(GetSoliditySha3(nonce),16);BN bn_address_hash = new BN(GetSoliditySha3(address.HexToByteArray()), 16);BN nonceAddressXOR = bn_nonce_hash.Xor(bn_address_hash);BN maxNumber = new BN(2).Pow(new BN(256)).Sub(new BN(1));BN DIFFICULTY = new BN(1);BN divConstant = maxNumber.Div(DIFFICULTY);BN candidate = new BN(0);double iterations = 0;while (true){var candidateBytes = new byte[32];using (var random = new RNGCryptoServiceProvider()){random.GetBytes(candidateBytes);}string hexString = BitConverter.ToString(candidateBytes).Replace("-","").ToLowerInvariant();candidate = new BN(hexString, 16);BigInteger candidate_to_bi = BigInteger.Parse(candidate.ToString());BN candidateHash = new BN(GetSoliditySha3(candidate_to_bi),16);BN resultHash = nonceAddressXOR.Xor(candidateHash);long externalGas = divConstant.Div(resultHash).ToNumber();if (externalGas >= gasAmount){break;}if (iterations++ % 1_00 == 0){yield return null;}}callback(candidate.ToString());}public async Task<string> POW_Caller(Chains schain, TransactionInput tx){string result = null;StartCoroutine(MineGasForTransaction((result_) =>{result = result_;}, schain, tx));while (result == null){await Task.Yield(); // Yield to the main Unity thread}return result;}private string GetSoliditySha3(object val){var abiEncode = new ABIEncode();var result = abiEncode.GetSha3ABIEncodedPacked(val);return result.ToHex();}} -
Call sFUEL distribution contract with unique Gas Price
AnonymousWallet wallet = new AnonymousWallet(currentChain);string sFUEL_receiver_address = "0x_some_address";TransactionReceipt transactionReceipt = await wallet.sFUEL_Distribution(sFUEL_receiver_address);
The code below can be found on the skale.dart repo.
-
Random signer generation
import "dart:math";import "package:skale/src/pow/wallet.dart";import "package:web3dart/credentials.dart";class AnonymousParams extends WalletParams {AnonymousParams(String rpcUrl, BigInt? difficulty): super(difficulty,rpcUrl: rpcUrl,privateKey: EthPrivateKey.createRandom(Random.secure()));}class AnonymousPow extends WalletPow {AnonymousPow(AnonymousParams params) : super(params);} -
Request creation to call sFUEL distribution contract function
import "dart:math";import "dart:typed_data";import "package:http/http.dart";import "package:web3dart/credentials.dart";import "package:web3dart/web3dart.dart"show EthPrivateKey, EtherAmount, EtherUnit, Transaction, Wallet, Web3Client;import "./miner.dart";class WalletParams extends SkalePowMinerParams {final String rpcUrl;final EthPrivateKey privateKey;WalletParams(BigInt? difficulty,{required this.rpcUrl, required this.privateKey}): super(difficulty);}class TransactionParams {EthereumAddress to;Uint8List data;int? gas;Uint8List? bytes;TransactionParams({required this.to, required this.data, this.gas, this.bytes});}class WalletPow extends SkalePowMiner {late Wallet wallet;late Web3Client client;WalletPow(WalletParams params) : super(params) {client = Web3Client(params.rpcUrl, Client());wallet = Wallet.createNew(params.privateKey, "", Random.secure());}Future<String> send(TransactionParams params) async {int nonce = await client.getTransactionCount(wallet.privateKey.address);BigInt gasHash = await mineFreeGas(params.gas ?? 100000, wallet.privateKey.address, nonce, params.bytes);Transaction tx = Transaction(from: wallet.privateKey.address,to: params.to,data: params.data,gasPrice: EtherAmount.fromBigInt(EtherUnit.wei, gasHash));return await client.sendTransaction(wallet.privateKey, tx,chainId: null, fetchChainIdFromNetworkId: true);}} -
Unique Gas Price Generation
import 'dart:typed_data';import 'package:eth_sig_util/eth_sig_util.dart';import 'package:skale/src/utils/shared.dart';import 'package:web3dart/crypto.dart';import 'package:web3dart/web3dart.dart';class SkalePowMinerParams {late BigInt difficulty;SkalePowMinerParams(BigInt? optionalDifficulty) {difficulty = optionalDifficulty ?? BigInt.one;}}class SkalePowMiner {BigInt difficulty = BigInt.one;SkalePowMiner(SkalePowMinerParams? params) {if (params != null) difficulty = params.difficulty;}Future<BigInt> mineGasForTransaction(int nonce, int gas, EthereumAddress from, Uint8List? bytes) async {return mineFreeGas(gas, from, nonce, bytes);}Future<BigInt> mineFreeGas(int gasAmount, EthereumAddress from, int nonce, Uint8List? bytes) async {final int gasAmountInt = gasAmount.toInt();final BigInt nonceHash =bytesToInt(AbiUtil.soliditySHA3(["uint256"], [nonce]));final BigInt addressHash =bytesToInt(AbiUtil.soliditySHA3(["address"], [from.addressBytes]));final BigInt nonceAddressXOR = nonceHash ^ addressHash;final BigInt maxNumber = getMaxNumber();final BigInt divConstant = BigInt.from(maxNumber / difficulty);BigInt candidate;int iterations = 0;while (true) {candidate =bytesToInt(padUint8ListTo32(bytes ?? randomBytes(32, secure: true)));BigInt candidateHash =bytesToInt(AbiUtil.soliditySHA3(["uint256"], [candidate]));BigInt resultHash = nonceAddressXOR ^ candidateHash;double externalGas = divConstant / resultHash;if (externalGas >= gasAmountInt) {break;}if (iterations++ % 2000 == 0) {await Future.delayed(Duration.zero);}}return candidate;}} -
Call sFUEL distribution contract with unique Gas Price
import 'package:skale/skale.dart';import 'package:web3dart/credentials.dart';import 'package:web3dart/crypto.dart';Future<void> main() async {AnonymousPow pow = AnonymousPow(AnonymousParams("https://staging-v3.skalenodes.com/v1/staging-utter-unripe-menkar",null));await pow.send(TransactionParams(to: EthereumAddress.fromHex("0xa9eC34461791162Cae8c312C4237C9ddd1D64336"),data: hexToBytes("0x0c11dedd000000000000000000000000Da11eC5944D960008A3184Cc7F4A9C001b3B2Cff")));}