Never-ending Pumpkin Mulch

    eth-sci
    TypeScript icon, indicating that this package has built-in type declarations

    1.0.7 • Public • Published

    Ethereum Smart Contract Interface

    A NodeJS library for compiling, deploying, and interacting with smart contracts. It is built on top of the web3 library.

    The sole purpose of this project is to simplify a backend/dAapp development process, as a result, to save tons of time.

    In particular, once ABI is passed to the Interface constructor, it automatically determines all the methods and their types, as well as the events listed within the ABI. From now on, you're able to call the methods as instance.methodName(methodArgs). As for the events, just add 'on' prefix before the event name - for example: instance.onTransfer(callback)

    Table of content:

    Features

    • extremely easy to setup and use
    • suitable for any smart contract
    • all-in-one library, it includes methods for compilation, deployment, and working with contracts methods and events
    • automatic nonce calculation and tracking - no more 'await' or nonce calculation routines before each next transaction
    • gas/eth expenses tracking
    • supports http(s), ws(s), and ipc protocols
    • supports mnemonic and a private key(s) authorization types
    • automatically applies a proper provider depending on the protocol type and/or authorization method (http or wss, mnemonic or private key, etc)
    • allows using custom web3-instances
    • automatically restores WebSocket connections/subscriptions
    • contains a 'retry-on-fail' option for the send-type transactions
    • with the retry option, you're able to use the same wallet in different application simultaneously, no more 'nonce too low' errors
    • it's suitable for both backend and frontend

    Installation

    $ npm install --save eth-sci

    Quick start

    There are two classes are being exposed by the library - Interface and ERC20. The only difference between them is that the ERC20 one is initialized with the standard ERC20 abi, while the Interface expects to get the ABI upon initialization.

    You're still able to pass (upon init) or set (in run-time) any ABI for both.

    Unsigned transactions

    const { ERC20 } = require('eth-sci');
     
    const token = new ERC20(
        'https://mainnet.infura.io/v3/<API_KEY>',
        '0x45F1F44dE1...'
    );
     
    // Resolve promise
    token.totalSupply().then(console.log);
    token.name().then(console.log);
     
    // Callback
    token.symbol((err, res) => console.log(err, res));
     
    // async-await
    const checkBalance = async address => {
        return await token.balanceOf(address);
    };
     
    checkBalance('0x4b32C...').then(console.log);

    Signed transactions

    In order to be able to alter the contract state, you have to pass either a mnemonic, a private key, or an array of keys to the constructor:

    const { ERC20 } = require('eth-sci');
    const mnemonic = '12 words mnemonic';
     
    // 0x-prefix is allowed as well
    const privateKey = '16ac46b....'; // 32-bytes private key;
     
    // an array of private keys - both plain and '0x'-prefixed keys are allowed
    const keysArray = [
        '0xAbc...', // 0x-prefix
        'Bcd...',   // no prefix
        '0xCde'     // 0x-prefix
    ]
     
    const token = new ERC20(
        'https://mainnet.infura.io/v3/<API_KEY>',
        '0x45F1F44dE1...',
        mnemonic // or `privateKey`, or `keysArray`
    );
     
    const transfer = async (callback) => {
        const balance = await token.balanceOf(token.wallet);
        return await token.transfer('0x91a5...', balance);
    };
     
    transfer((err, res) => console.log(`${err ? 'Fail' : 'Success'}`));

    Web3 vs Ethereum Smart Contract Interface

    Here is an implementation of 'transfer' and 'balanceOf' methods using a native Web3 syntax and the Interface's one.

    The constants are the same for both implementations:

    const nodeAddress = 'https://mainnet.infura.io/v3/<API_KEY>';
    const contractAddress = '0xB4239d61FE...';
    const testWallet = '0x47573c6661...';
    const mnemonic = "12 words mnemonic";

    Web3

    const Web3 = require('web3');
    const HDWalletProvider = require('truffle-hdwallet-provider');
    const abi = require('./token-abi');
     
    const provider = new HDWalletProvider(mnemonic, nodeAddress);
    const web3 = new Web3(provider);
     
    const contract = new web3.eth.Contract(abi, contractAddress);
     
    contract.methods.balanceOf(testWallet)
        .call()
        .then(console.log);
     
    web3.eth.getAccounts().then(accounts => {
        contract.methods.transfer(testWallet, 100)
            .send({from: accounts[0]})
            .then(r => {
                provider.engine.stop();
                console.log(r);
            })
    });

    Smart Contract Interface

    const { ERC20 } = require('eth-sci');
     
    const token = new ERC20(nodeAddress, contractAddress, mnemonic);
     
    token.balanceOf(testWallet).then(console.log);
    token.transfer(testWallet, 100).then(console.log);

    Usage

    Following modules are being exported by the library:

    • Interface - general-purpose class. Requires ABI, can be used directly or as a parent class.
    • Web3 - returns the web3 instance activated by a provider. The type of the provider is defined out of the protocol type (web socket, http, ipc). Supports mnemonic and a private key(s) authorization types.
    • ERC20 - derived from the Interface class; can be used for accessing the ERC20 tokens' standard methods (name, symbol, totalSupply, etc.)
    • utils - a set of utils that includes a 'compile' module - it compiles the source code and returns an object containing abi and bytecode
    • setLogger - a function that sets a logger (see below)

    It supports both promises and the async-await calls with or without the callbacks.

    By default, the ERC20 class instance is initialized with a standard ERC20 abi.

    The following methods are supported out of the box:

    • name()
    • symbol()
    • decimals()
    • totalSupply()
    • cap()
    • balanceOf(address)
    • transfer(to, amount)
    • transferFrom(from, to, amount)
    • approve(spender, amount)
    • allowance(owner, spender)
    • mint(to, amount)

    Events:

    • Transfer(from, to, amount)
    • Approval(owner, spender, amount)
    • Mint(to, amount)
    • Burn(burner, amount)

    The constructor parameters:

    Parameter Type Default Required Description
    nodeAddress string null true Ethereum node URI (http://, ipc://, etc)
    contractAddress address null false contract address
    authKey string|hex-string|array of hex-strings null false 12-words mnemonic, a private key or an array of them
    web3Instance Web3 null false a custom Web3 instance
    abi array null true an ABI
    bytecode hex-string null false "0x"-prefixed bytecode

    Instance attributes:

    Attribute Default Description
    w3 - Web3 instance; can be used for direct access to the native Web3 methods and attributes
    gasLimit 6000000 the gasLimit, it's being used as the 'gas' parameter of send-type transactions
    gasUsed undefined the number of gas units used by the latest transaction
    accounts - the list of wallets addresses provided by truffle-hdwallet or custom web3 instance (web3.eth.getAccounts())
    wallet accounts[0] currently active wallet address; it is used as a 'from' parameter
    gasPrice blockChain gasPrice * 1.2 the gasPrice for a particular transaction
    address - address of the contract
    abi - contract's ABI
    txManager TransactionManager the transaction manager class instance

    Setters and getters

    at - setter; sets an address at this.contract.options.address

    const token = new ERC20('https://mainnet.infura.io/v3/<API_KEY>');
    token.at('0xB8c77482e45F1F44dE1745F52C74426C631bDD52');
    token.name().then(console.log); // VeChain Token

    wallet - setter/getter; getter returns a currently active wallet address, setter accepts a wallet index and sets the address accordingly

    console.log(token.accounts); // ['0xAbc..', '0xBcd...', '0xCde', ...., '0xfab']
    console.log(token.wallet); // '0xAbc..'
     
    token.wallet = 2;
    console.log(token.wallet); // '0xCde'

    gasPrice - setter/getter; getter returns a value in WEI, setter accepts it in gWEI; The default value in null which means that it will be calculated upon a transaction as gas price provided by the blockchain multiplied by 1.2:

    console.log(token.gasPrice); // null
     
    token.gasPrice = 4.567;      // value in gWei
    console.log(token.gasPrice); // 4567000000

    abi - setter/getter; getter returns this.contract.options.jsonInterface, setter sets the ABI at the same path; can be used for changing ABI dynamically:

     const { ERC20 } = require('eth-sci');
     const token = new ERC20(nodeAddress, contractAddress, mnemonic);
     token.addAdmin('0xAbc...'); // TypeError: token.addAdmin is not a function
     
     // a new function defenition
     const addAdmin = {
         "constant": false,
         "inputs": [{ "name": "_newAdminAddress", "type": "address" }],
         "name": "addAdmin",
         "outputs": [{ "name": "", "type": "bool" }],
         "payable": false,
         "stateMutability": "nonpayable",
         "type": "function"
     };
     
     // adding new function to the ABI
     token.abi = [...token.abi, addAdmin];
     
     // calling the method
     token.addAdmin('0xAbc...', (err, res) => {
         if(err) console.log(err);
         else console.log('Success')
     }); // Success

    Compilation and deployment

    The compiler is an async function exported by the utils module while the deploy is the method provided by the Interface class.

    Compiler Parameters

    The compiler requires two arguments:

    • the source code (required)
    • callback (optional)
    const fs = require('fs');
    const { utils } = require('eth-sci');
     
    const sourceCode = fs.readFileSync('./contract.sol', 'utf-8');
    utils.compile(sourceCode[, callback]);
    Returns

    The compiler returns an object with the following structure:

    {
        Contract1 : { abi: [...], bytecode: "0x..." },
        Contract2 : { abi: [...], bytecode: "0x..." },
        ...
        ContractN : { abi: [...], bytecode: "0x..." }
    }
    

    Deploy

    The deploy method accepts an optional object and optional callback function.

    contract.deploy([options][, callback]);

    Parameters

    • options (Object, optional):
      • args (Array, optional): arguments to be passed to the smart contract constructor
      • nonce (Number, optional): nonce to use for the transaction
      • from (String, optional): an address the transaction will be sent from
      • gasPrice (Number, optional): the gas price in wei to use for transactions
    • callback (Function, optional): will be fired with the result of the deployment - an instance of the Interface class as the second argument or an error object as the first one

    Compilation and deployment example


    For example, we're about to deploy this contract:

    contract ERC20Token {
        uint public totalSupply;
        uint public decimals;
        string public name;
        string public symbol;
        ...
    }
    
    contract Ownable is ERC20Token{
        address public owner;
    
        modifier auth() {
            require(msg.sender == owner);
            _;
        }
    
        constructor() public {
            owner = msg.sender;
        }
        ...
    }
    
    contract Token is Ownable {
        constructor(
            string memory _name,
            string memory _symbol,
            uint _decimals,
            uint _totalSupply
        ) public {
            name = _name;
            symbol = _symbol;
            decimals = _decimals;
            totalSupply = _totalSupply;
        }
        ...
    }
    
    
    Passing arguments to the contract

    In the example above, the Token contract constructor requires 4 positional arguments:

    • name
    • symbol
    • decimals
    • totalSupply

    Those parameters should be passed as args key of the options object

    const contractArguments = [name, symbol, decimals, totalSupply];
    const options = {args: contractArguments};
    ...
    contract.deploy(options);
     
    Deploying

    In order to compile and deploy the 'Token' contract:

    const fs = require('fs');
    const { utils, Interface } = require('eth-sci');
     
    const source = fs.readFileSync('./contract.sol', 'utf-8');
    const mnemonic = "12 words mnemonic...";
    const nodeAddress = 'https://mainnet.infura.io/v3/<API_KEY>';
     
    // Smart Contract arguments should be passed as an array
    const contractArguments = ["Token Name", "TKN", 18, "100000000000000000000"];
    const options = {args: contractArguments};
     
    // Compilation
    const compile = async (sourceCode) => {
        const compiled =  await utils.compile(sourceCode);
        return compiled.Token;
    };
     
    // The 'compile' function returns an object with two key-value pairs: { abi: [], bytecode: "0x..." }
    const deploy = async ({ abi, bytecode }) => {
        const contract = new Interface(nodeAddress, null, mnemonic, null, abi, bytecode);
     
        // Smart contract arguments are included to the 'options' object as { args: [] }
        await contract.deploy(options);
        return contract;
    };
     
    const getInfo = async (contract) => {
        return {
            address: contract.address,
            name: await contract.name(),
            symbol: await contract.symbol(),
            decimals: await contract.decimals(),
            totalSupply: await contract.totalSupply()
        }
    };
     
    compile(source)
        .then(deploy)
        .then(getInfo)
        .then(console.log);
     
    /* The output:
    { address: '0x4aA3852beCfD117f19FEdF3d0d61678f5B3e5103',
      name: 'Token Name',
      symbol: 'TKN',
      decimals: '18',
      totalSupply: '100000000000000000000' }
    **/

    Once the contract is deployed, the contract instance gets the address automatically, so it's completely ready to work.

    Customize web3 parameters

    It is possible to replace any parameter used by the underlying web3 provider - i.e. nonce, data, gasPrice, gas, from, and value. Just pass an object with key-value pairs as the last argument (if there is no callback), or right before the callback:

    const params = {
        from: token.accounts[1],  // sender's address
        gas: '40000',             // gas limit
        gasPrice: '5200000000'    // gas price in Wei
        nonce: 101                // set nonce
    };
     
    token.tokenMethod('0xAbc', 100, params).then(...);
     
    // using a callback
    token.tokenMethod('0xAbc', 100, params, (err, res) => {
        // your code here
    });
     
    // call-type methods work the same
    token.someRestrictedMethod({from: token.accounts[2]}).then(r => {
        // your code here
    });

    The last line of the example above is equal to:

    token.wallet = 2;
    token.someRestrictedMethod().then(...);

    See the sendTransaction and methods.myMethod.call methods description.

    Using a custom web3 instance

    There is a static method - web3. It accepts:

    • web3-instance (required)
    • contract address (optionally)
    • abi (required)
    • bytecode (optionally)

    Can be used in mocha test framework:

    const Web3 = require('web3');
    const ganache = require('ganache-cli');
    const { Interface } = require('eth-sci');
    const { abi, bytecode } = require('./compiled.json');
     
    const web3 = new Web3(ganache.provider());
     
    const token = Interface.web3(web3, null, abi, bytecode);

    Runtime events

    There are three types of events are being emitted upon sending a transaction:

    • transactionHash
    • receipt
    • error
    contract.myMethod(methodArguments)
        .on('transactionHash', h => console.log(`TxHash: ${h}`)
        .on('receipt', r => console.log(`Receipt: ${r}`)
        .on('error', e => console.log(`Error: ${e}`)
        .then(() => console.log('Done')
        .catch(() => console.log('Fail')

    More info

    Listening for realtime events

    There are two mandatory requirements:

    • the instance must be initialized with 'ws' or 'wss' address
    • the callback is strictly required

    For any event that defined in ABI of the smart contract, add an 'on'-prefix before the event name and pass optional parameters and a callback. For example, there is a smart contract that contains:

    event MyEvent(address indexed _addr, uint _value);
    
    function myFunc(address _addr, uint _value) public returns(bool) {
        ...
        emit MyEvent(_addr, _value);
        ...
    }
    

    Subscribe to 'MyEvent':

    const contract = new Interface('wss://mainnet.infura.io/ws/v3/<API_KEY>', '0xAbC...');
    contract.onMyEvent({}, (err, res) => { });

    The callback is called each time the MyEvent is fired;

    More info

    Retry-on-fail

    Options

    For any send-type transaction, it is possible to define a 'retryOptions' object with the following parameters:

    • retry (Number, optional): Integer, the maximum number of retries. Default value: 3
    • gasPrice (String, optional): the initial value of the gasPrice in Wei. Default value: web3.eth.getGasPrice() * 1.2
    • delay (Number, optional): the delay in seconds before each next attempt. Default value: 10
    • verify (Function, optional): a function that will be executed before each next retry. Default value: null
    • incBase (Number, optional): the base of the exponential expression that defines the gasPrice on each next retry. Default value: 1

    The verify function (if defined) will get all the arguments that were passed to the original contract's method.

    If the incBase > 1, the gasPrice will be increased on each next iteration as gasPrice = gasPriceBase * incBase ** count, where the gasPriceBase is either retryOptions.gasPrice, this.gasPrice, or the gasPrice provided by the web3.eth.getGasPrice() * 1.2 method:

        async getGasPrice(multiplier) {
            multiplier = multiplier || 1;
            const gasPrice = await this.w3.eth.getGasPrice();
            return Math.ceil(gasPrice * multiplier);
        }
     
        async sendWithRetry() {
            ...
            let gasPrice = retryOptions.gasPrice || this.gasPrice || await this.getGasPrice(1.2);
            ...
        }

    Gas price calculation

    If the initial gasPrice is 3gWei, and the incBase has a value of 1.3, the gasPrice will be changed as

    retry # evaluation gasPrice
    0 3 * 1.3^0 3
    1 3 * 1.3^1 3.9
    2 3 * 1.3^2 5.07
    3 3 * 1.3^3 6.591
    4 3 * 1.3^4 8.5683

    Example

    A contract:

    ...
        mapping (address => bool) public users;
     
        function addUser(address _user) public {
            require(!users[_user]);
            users[_user] = true;
        }
     
        function isUser(address _user) public {
            return users[_user];
        }
    ...

    In the example above, an address can be set as 'user' only once. Every subsequent attempt will fail. By default, web3 has a timeout - the transaction will be rejected by web3 if it hasn't been mined in 750 seconds. But, as a matter of fact, the timeout doesn't mean that the transaction has not been mined - timeout could be caused by network latency, bugs, etc. In this case, if the retryOptions is defined with no verify function, the library will be trying to send the transaction until the maximum number of retries is reached. In order to eliminate such behavior, define the verify function as follows:

    const options = {
        verify: async user => await contract.isUser(user)
    }
     
    contract.addUser('0xABC...', { retryOptions: options });

    Once the very first transaction is failed and the delay period has expired, the code will call the verify function and:

    • will return the result if verify returned true;
    • will resend a transaction otherwise

    Transaction manager

    The transaction manager tracks nonces, keeps the transaction history, total and per-transaction gas and eth expenses, defines the transaction types, sends transactions to the blockchain, etc. It can be accessed via txManager property of the Interface instance.

    Nonce calculation

    The transaction manager is tracking nonces for every wallet in use. Based on the local history and the information provided by the blockchain (getTransactionCount), it calculate a nonce and automatically adds it to the transaction parameters. However, it's possible to set the nonce manually.

    Transaction queue

    There is a limit of 100 transactions that were sent to the blockchain but haven't been mined yet. Once the limit is reached, all subsequent transactions will be queued until a free slot appeared. The manager checks the number of submitted transactions every minute.

    Methods and attributes

    • getTxStat() - returns an object with key-value pairs:
      • submitted - the number of transactions that were sent to the blockchain but haven't been mined yet
      • pending - pending - the number of transactions that have been passed to the txManager but haven't been sent to the blockchain
      • failed - the number of failed transactions
      • confirmed - the number of confirmed transaction (i.e. mined ones)
      • retries - the total number of retries attempted for failed transactions
      • totalGasUsed - the total number of 'gas' units spent
      • totalEthSpent - the total amount of ETH spent
    • totalGasUsed - see above
    • totalEthSpent - see above

    Expenses tracking

    The totalEthSpent is being increased as totalEthSpent += gasPrice * gasUsed.

    gaUsed is an Interface instance property; it indicates the number of gas units that have been used by the latest transaction.

    Examples

    token.gasPrice = 10;  // set a higher gasPrice to get the transaction mined faster
    const testWallet = '0xABC...';
     
    const test = async () => {
        const promises = [];
     
        await token.transfer(testWallet, 100);
        // print the number of gas that was used by the transaction above
        console.log(token.gasUsed);
     
        console.log(JSON.stringify(token.txManager.getTxStat()));
     
     
        // send 5 transaction in one batch, all of them will be (most probably) mined within a single block
        for(let i=0; i<5; i++) {
            promises.push(new Promise(async (resolve, reject) => {
                await token.transfer(testWallet, 100);
                console.log(JSON.stringify(token.txManager.getTxStat()));
                resolve();
            }));
        }
     
        await Promise.all(promises)
    };
     
    test();

    The output:

    37186
    {"submitted":0,"pending":0,"failed":0,"confirmed":1,"retries":0,"totalGasUsed":"37186","totalEthSpent":"0.00037186"}
    {"submitted":4,"pending":0,"failed":0,"confirmed":2,"retries":0,"totalGasUsed":"74372","totalEthSpent":"0.00074372"}
    {"submitted":3,"pending":0,"failed":0,"confirmed":3,"retries":0,"totalGasUsed":"111558","totalEthSpent":"0.0011155800000000001"}
    {"submitted":2,"pending":0,"failed":0,"confirmed":4,"retries":0,"totalGasUsed":"148744","totalEthSpent":"0.00148744"}
    {"submitted":1,"pending":0,"failed":0,"confirmed":5,"retries":0,"totalGasUsed":"185930","totalEthSpent":"0.0018593"}
    {"submitted":0,"pending":0,"failed":0,"confirmed":6,"retries":0,"totalGasUsed":"223116","totalEthSpent":"0.00223116"}

    There are the methods for retrieving individual items of the stat. All of them accept an optional argument - an address of wallet the transactions have been sent from:

    • getFailedTransactions([address])
    • getConfirmedTransactions([address])
    • getPendingTransactions([address])
    • getSubmittedTransactions([address])
    token.gasPrice = 10;
     
    const getStat = address => token.txManager.getConfirmedTransactions(address).length;
     
    const test = async () => {
        await token.transfer(testWallet, 100);
        console.log(token.wallet, getStat(token.wallet));
     
        token.wallet = 1;
        await token.transfer(testWallet, 100);
        console.log(token.wallet, getStat(token.wallet));
        console.log('Total: ', getStat())
    };
     
    test();

    The output:

    0xc9E6D574... 1
    0xa9C5Eb93... 1
    Total: 2
    

    Logging

    The library contains a built-in logger represented as a simple wrapper of the console.log. It prints a comprehensive information about each transaction as well as the accumulated statistic of all the transactions performed since app has been started.

    Log format:

    [timestamp] [loglevel] logmessage

    For each send-type transaction, the logger fires the following messages:

    • (before sending) prints accumulated stat about all the transactions
    • (before sending) prints the unique transaction id and the entire information about the input parameters
    • a transaction hash once tx is sent
    • execution status - CONFIRMED or FAILED
    • updated information that includes a block number, a duration, the amount of gas used, etc.
    [2019-01-16T05:51:40.870Z] [debug] {"submitted":2,"pending":0,"failed":0,"confirmed":1,"retries":0,"totalGasUsed":"4698743","totalEthSpent":"0.004359"}
    [2019-01-16T05:51:41.014Z] [debug] updateTx[0]: 3038736358014506 -> {"id":3038736358014506,"from":"0x095e15c...", ..., "status":"submitted", ...}
    [2019-01-16T05:51:41.132Z] [debug] transactionHash: 3038736358014506 -> 0x404d1cb...
    [2019-01-16T05:51:48.318Z] [debug] submitTx: CONFIRMED - 3038736358014506
    [2019-01-16T05:51:48.319Z] [debug] updateTx[0]: 3038736358014506 -> {..., "status":"confirmed", ...}
    

    Enabling built-in logger

    To activate the logger, set the LOG_LEVEL environment variable to debug.

    Supported log-levels

    The logger uses the following log levels:

    • error
    • warn
    • info
    • debug

    Log message formats

    Transaction log:
    • id: Number - a unique ID of the transaction
    • from: String - the address the transaction was sent from
    • method: String method name
    • methodArgs: Array - the list of arguments that was passed to the smart contract method
    • options: Object - transaction options
      • from: String - the address the transaction was sent from
      • gas: String - gas limit (hex)
      • gasPrice: String - gas price (hex)
      • nonce: String - nonce (hex)
      • data: String - data (hex)
      • value: String - amount of ETH that was sent
      • to: String - smart contract address },
    • txType: String - transaction type (send, call)
    • time: Number - epoch timestamp (when the tx was submitted)
    • status: String - tx status (submitted, failed, confirmed)
    • nonce: Number - nonce
    • lastUpdate: Number - epoch timestamp (when the tx was updated)
    • duration: Number - time (in seconds) that passed before tx got mined
    • txHash: String - tx hash
    • blockNumber: Number - the block the tx was included to
    • gasUsed: Number - the amount of gas that was spent for this transaction
    • totalGasUsed: String - total amount of gas (since the application start)
    WebSocket log:
    [2019-01-16T05:50:38.804Z] [info] [1] WebSocket - connected to "wss://wss.endpoint.uri"
    [2019-01-16T05:51:40.762Z] [debug] [1] [0x1FE10f0B...] -> subscribed to Transfer
    ...
    [2019-01-16T05:54:30.267Z] [error] [1] WebSocket - connection error "wss://wss.endpoint.uri"
    [2019-01-16T09:59:45.871Z] [info] [1] WebSocket - connected to "wss://wss.endpoint.uri"
    [2019-01-16T09:59:45.873Z] [debug] [1] [0x1FE10f0B...] Restoring the "onTransfer" subscription...
    
    Deployment log:
    [2019-01-16T07:00:42.419Z] [debug]  Tx hash: 0x48276e05...       <--- transaction hash
    [2019-01-16T07:00:47.570Z] [debug]  address 0x2aa09C6...         <--- contract address
    [2019-01-16T07:00:47.577Z] [debug] {"deploy":{"gasUsed":25...}}  <--- tx expenses
    [2019-01-16T07:00:47.636Z] [debug] updateTx[0]: 324... -> {"method":"deploy", ...}   <- tx stat
    

    Passing you own logger

    There is the only one requirement - the logger must support these methods:

    • error
    • warn
    • info
    • debug

    If the logger conforms the requirements, import the 'setLogger' method and pass your logger as an argument.

    const { setLogger } = require('eth-sci');
    const myLogger = ...;
    ...
    setLogger(myLogger);

    Custom logger example

    logger.js

    const winston = require('winston');
    const path = require('path');
    const { combine, timestamp, printf } = winston.format;
     
    module.exports = (logLevel, filename, logDir) => {
        logDir = logDir || './log';
        filename = filename || 'combined.log';
        const logFormat = printf(info => {
            return `[${info.timestamp}] [${process.pid}] [${info.level}]: ${info.message}`;
        });
     
        return winston.createLogger({
            transports : [
                new winston.transports.File({
                    level: logLevel || 'error',
                    format: combine(
                        timestamp({format: 'YYYY-MM-DD HH:mm:ss.SSS'}),
                        logFormat
                    ),
                    filename: path.join(logDir, filename),
                    handleExceptions: true
                }),
            ],
            exitOnError: false
        });
    };

    By default, winston supports the required log levels, so all we need to do is:

    const { ..., setLogger } = require('eth-sci');
    const myLogger = require('./logger');
     
    const logger = myLogger('debug');
    setLogger(logger);
    ...

    Accessing the underlying Web3 instance

    All the methods/attribute that is being provided by the Web3 library are accessible through the 'w3' attribute.

    const { Interface } = require('eth-sci');
    const contractAbi = require('./contract-abi.json');
    ...
    const contract = new Interface(nodeAddress, contractAddress, mnemonic, null, contractAbi);
     
    const web3 = contract.w3;
     
    // get a transaction receipt
    web3.eth.getTransactionReceipt("0x...").then(...);
     
    // get a nonce at block 7063638
    web3.eth.getTransactionCount("0xABC...", 7063638).then(...);
     
    //get gasPrice
    web3.eth.getGasPrice().then(...);
     
    // send 1 ETH
    web3.eth.sendTransaction({
        from: contract.accounts[0],
        to: "0xABC...",
        value: contract.w3.utils.toWei(1, 'ether');
    });
     
    // accessing events - we3.eth.Contract().events are binded to this.events;
    contract.events.MyEvent([options][, callback])
     

    See the official web3.js documentation

    Limitations

    • the library does not support 'HTTP Basic Authentication' for the web3 lib. Feel free to contact me or make a pool request. As a workaround, you may create a native Web3 class instance and pass it to the library constructor. More info

    Troubleshooting

    Transactions are too slow

    Increase a gas price:

    const { ERC20 } = require('eth-sci');
    const token = new ERC20(...);
    ...
    token.gasPrice = 3; // in gWei
    ...

    Error: Exceeds block gas limit

    Decrease the gasLimit:

    const { ERC20 } = require('eth-sci');
    const token = new ERC20(...);
    ...
    token.gasLimit = '3000000'
    ...

    Error: Cannot find module 'ethereumjs-wallet/hdkey'

    $ npm uninstall ethereumjs-wallet
    $ npm install ethereumjs-wallet@0.6.0

    The compiler fails with 'Source file requires different compiler version' error

    First of all, stay on top of things and don't use obsolete technologies, consider to align your project in accordance with the most recent requirements.

    The 'compile' module uses 'solc' version 0.5.x. If your pragma parameter is set to something like '^0.4.23', plese try to change it to '>=0.4.23'.

    Also, check the Solidity v0.5.0 Breaking Changes list.

    License

    GPL-2.0

    Install

    npm i eth-sci

    DownloadsWeekly Downloads

    10

    Version

    1.0.7

    License

    GPL-2.0

    Unpacked Size

    116 kB

    Total Files

    13

    Last publish

    Collaborators

    • amekh