Source: index.js

/**
 * Ethereum Blockchain Wallet implementing Green Energy semantics for Corrently based decentralized capacity market.
 *
 * @link https://corrently.com/
 * @module CorrentlyWallet
 */

const ethers = require('ethers');
const request = require('request');

if (typeof ethers.providers.getDefaultProvider === 'undefined') {
  if (typeof ethers.getDefaultProvider !== 'undefined') {
    ethers.providers.getDefaultProvider = ethers.getDefaultProvider;
  }
}
/**
 * @const CORRENTLY
 * @desc Core Constants for semantics used in decentralized capacity market
 */
ethers.CORRENTLY = {
  ERC20ABI: require('./ERC20ABI.json'),
  STROMKONTOABI: require('./STROMKONTOABI.json'),
  CORI_ADDRESS: '0x725b190bc077ffde17cf549aa8ba25e298550b18',
  API: 'https://api.corrently.io/core/',
};

/**
 * @function CorrentlyAccount
  *@desc Digital twin of consensus driven account identified by address
 * @param {string} address Address of a twinable account
 * @return {Object} Digital twin identified by address
 */
ethers.CorrentlyAccount = function(address) {
  return new Promise(function(resolve, reject) {
    ethers.utils._retrieveCoriAccount(address).then(function(twin) {
      twin.getCoriEquity = function() {
        const cori_contract = new ethers.Contract(ethers.CORRENTLY.CORI_ADDRESS, ethers.CORRENTLY.ERC20ABI, ethers.providers.getDefaultProvider('homestead'));

        return new Promise(function(resolve2, reject) {
          cori_contract.balanceOf(address).then(function(balance) {
            resolve2(balance / 100);
          });
        });
      };
      twin.getMetas = function() {
        return new Promise(function(resolve2, reject2) {
          const options = {
            url: ethers.CORRENTLY.API + 'meta?account=' + address,
            timeout: 20000,
          };
          request(options, function(e, r, b) {
            let results = JSON.parse(b);
            resolve2(results.result);
          });
        });
      };
      twin.getStromkonto = function() {
        return ethers.Stromkonto(address);
      };
      resolve(twin);
    });
  });
};

/**
 * @function CorrentlyIoT
  *@desc IoT Wrapper to Corrently-IoT implementation
 * @param {string} address Address of a thing
 * @return {number} Value of thing
 */
ethers.CorrentlyIoT = function(address) {
  return new Promise(function(resolve, reject) {
    const options = {
      url: ethers.CORRENTLY.API + 'iot?account=' + address,
      timeout: 20000,
    };
    request(options, function(e, r, b) {
      let results = JSON.parse(b);
      resolve(results.result.value);
    });
  });
};

/**
 * @function deleteData
  *@desc GDPR compliance to delete personal and private data from OTC transactions
 * @param {string} address Address of wallet
 */
ethers.Wallet.prototype.deleteData = function(address) {
  let parent = this;
  return new Promise(function(resolve, reject) {
    let transaction = {};
    transaction.timeStamp = new Date().getTime();

    let hash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes(JSON.stringify(transaction)));
    parent.signMessage(hash).then(function(signature) {
      const options = {
        url: ethers.CORRENTLY.API + 'deleteTwin?&signature=' + signature + '&hash=' + hash + '&transaction=' + encodeURI(JSON.stringify(transaction)),
        timeout: 20000,
      };
      request(options, function(e, r, b) {
        let results = JSON.parse(b);
        resolve(results);
      });
    });
  });
};

/**
 * @function buyCapacity
  *@desc OTC buy capacity from market
 * @param {string} asset Address Contract to buy from
 * @param {number} quantity Amount of capacity to buy
 */
ethers.Wallet.prototype.buyCapacity = function(asset, quantity, funding) {
  if ((typeof asset === 'object') && (typeof asset.assset !== 'undefined')) {
    asset = asset.asset;
  }
  let parent = this;
  return new Promise(function(resolve, reject) {
    parent._retrieveCoriAccount().then(function(account) {
      ethers.Market().then(function(market) {
        let transaction = {};
        transaction.cori = quantity;
        transaction.timeStamp = new Date().getTime();
        transaction.asset = asset;
        transaction.eth = 0;
        transaction.nonce = account.txs.length;
        if ((typeof funding !== 'undefined') && (funding !== null)) {
          transaction.funding = funding;
        }
        let hash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes(JSON.stringify(transaction)));
        parent.signMessage(hash).then(function(signature) {
          delete parent.twin;
          const options = {
            url: ethers.CORRENTLY.API + 'signedTransaction?transaction=' + encodeURI(JSON.stringify(transaction)) + '&hash=' + hash + '&signature=' + signature,
            timeout: 20000,
          };
          request(options, function(e, r, b) {
            let results = JSON.parse(b);
            resolve(results.result);
          });
        });
      });
    });
  });
};

/**
 * Link confirmed consumption source to wallet.
 * Any provider or authority might create new demand links. Typical use of this function is after receiving
 * a demandLink from a provider/utility.
 *
 * @function linkDemand
 * @param {string} ethereumAddress Address to link with
 */
ethers.Wallet.prototype.linkDemand = function(ethereumAddress) {
  let parent = this;
  return new Promise(function(resolve, reject) {
    let transaction = {};
    transaction.link = ethereumAddress;
    let hash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes(JSON.stringify(transaction)));
    parent.signMessage(hash).then(function(signature) {
      const options = {
        url: ethers.CORRENTLY.API + 'link?transaction=' + encodeURI(JSON.stringify(transaction)) + '&hash=' + hash + '&signature=' + signature,
        timeout: 20000,
      };
      request(options, function(e, r, b) {
        let results = JSON.parse(b);
        resolve(results.result);
      });
    });
  });
};

/**
 * Set Key Value MetaInformation for account
 * Allows to associate signed meta information to an account which becomes publicly available
 *
 * @function setMeta
 * @param {string} key Of Meta Date
 * @param {string} value Of Meta Date
 */
ethers.Wallet.prototype.setMeta = function(key, value) {
  let parent = this;
  let _key = key;
  let _value = value;
  return new Promise(function(resolve, reject) {
    let transaction = {};
    let hash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes(JSON.stringify(transaction)));
    parent.signMessage(hash).then(function(signature) {
      const options = {
        url: ethers.CORRENTLY.API + 'meta?account=' + parent.address + '&key=' + encodeURI(_key) + '&value=' + encodeURI(_value) + '&hash=' + hash + '&signature=' + signature,
        timeout: 20000,
      };
      request(options, function(e, r, b) {
        let results = JSON.parse(b);
        resolve(results.result);
      });
    });
  });
};

/**
 * Request new Demand Link - This method might be used to tell an energy provider to publish a new deman link for this account.
 * Typical usage is to set email and provider in options. The given energy provider will get in contact with you to negotiate an offer.
 * As soon as an energy contract is in place a demanLink will be published and could be used with the function `linkDemand`.
 *
 * Typical Options:
 *  - email: communication address for contract, offer negotiation. Only allowed to be used for this Communication
 *  - provider: Might be 'stromdao' as contact persion (request will be routed to this provider)
 *  - yearlyDemand: Kilo-Watt-Hours per Year requested
 *  - address: Geo-Coded Address for point of consumption
 *
 * @function newDemand
 * @param {object} options Options required for energy  provider to create a demandLink
 */
ethers.Wallet.prototype.newDemand = function(data) {
  let parent = this;
  return new Promise(function(resolve, reject) {
    let email = data.email;
    delete data.email;
    let transaction = {};
    if (typeof data.provider === 'undefined') data.provider = 'STROMDAO';
    transaction.email = email;
    transaction.options = JSON.stringify(data);
    let hash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes(JSON.stringify(transaction)));
    parent.signMessage(hash).then(function(signature) {
      const options = {
        url: ethers.CORRENTLY.API + 'requestLink?transaction=' + encodeURI(JSON.stringify(transaction)) + '&hash=' + hash + '&signature=' + signature,
        timeout: 20000,
      };
      request(options, function(e, r, b) {
        let results = JSON.parse(b);
        resolve(results.result);
      });
    });
  });
};

/**
 * @function transferCapacity
  *@desc Transfer generation capacity to another ethereum account
 * @param {string} ethereumAddress Address to receive capacity
 * @param {number} kilowatthours Kilo-Watt-Hours per year to transfer
 */
ethers.Wallet.prototype.transferCapacity = function(ethereumAddress, kilowatthours) {
  let parent = this;
  return new Promise(function(resolve, reject) {
    const cori_contract = new ethers.Contract(ethers.CORRENTLY.CORI_ADDRESS, ethers.CORRENTLY.ERC20ABI, parent);
    cori_contract.transfer(ethereumAddress, Math.round(kilowatthours * 100)).then(function(tx) {
      resolve(tx);
    }).catch(function(e) {
      reject(e);
    });
  });
};

ethers.Wallet.prototype.transferCORI = ethers.Wallet.prototype.transferCapacity;

/**
 * @function deletePending
  *@desc Delete a pending transaction from q
 * @param {string} nonce nonce of a pending transaction
 */
ethers.Wallet.prototype.deletePending = function(nonce) {
  let parent = this;
  return new Promise(function(resolve, reject) {
    parent._retrieveCoriAccount().then(function(account) {
      let transaction = {};
      transaction.nonce = nonce;

      let hash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes(JSON.stringify(transaction)));
      parent.signMessage(hash).then(function(signature) {
        delete parent.twin;
        const options = {
          url: ethers.CORRENTLY.API + 'deletePending?transaction=' + encodeURI(JSON.stringify(transaction)) + '&hash=' + hash + '&signature=' + signature,
          timeout: 20000,
        };
        request(options, function(e, r, b) {
          let results = JSON.parse(b);
          resolve(results.result);
        });
      });
    });
  });
};

ethers.Wallet.prototype._retrieveCoriAccount = function() {
  let parent = this;
  return new Promise(function(resolve, reject) {
    if (typeof parent.account === 'undefined') {
      ethers.CorrentlyAccount(parent.address).then(function(twin) {
        parent.twin = twin;
        resolve(twin);
      });
    } else {
      resolve(parent.twin);
    }
  });
};

ethers.utils._retrieveCoriAccount = function(address) {
  return new Promise(function(resolve, reject) {
    request(ethers.CORRENTLY.API + 'accountInfo?account=' + address, function(e, r, b) {
      resolve(JSON.parse(b).result);
    });
  });
};

/**
 * @function Market
  *@desc Retrieve market data OTC (Over the counter trade) as provided by Corrently Corp
 * @return {Object} Market data of all assets
 */
ethers.Market = function() {
  return new Promise(function(resolve, reject) {
    request(ethers.CORRENTLY.API + 'market', function(e, r, b) {
      resolve(JSON.parse(b).results);
    });
  });
};

/**
 * Retrieve Performance profile of given asset.
 * Corrently has a day based performance monitoring for assets on the market.
 * This is subject to be changed in later releases to merge with Performance package
 *
 * @function performance
 * @param {string} asset transaction hash of asset contract setup
 * @return {Object} Performance data as returned by asset schema
 */
ethers.Market.performance = function(asset) {
  return new Promise(function(resolve, reject) {
    request(ethers.CORRENTLY.API + 'assetPerformance?asset=' + asset, function(e, r, b) {
      resolve(JSON.parse(b).results);
    });
  });
};

/**
 * Retrieve Performance profile of given asset Metering ID.
 * Note: This is not standarized at the moment and schema is subject to be changed.
 *
 * @function Performance
 * @param {string} meterid unique id to dispatch
 * @return {Object} Performance data as given by meter schema
 */
ethers.Performance = function(meterid) {
  return new Promise(function(resolve, reject) {
    request(ethers.CORRENTLY.API + 'performance?meterid=' + meterid, function(e, r, b) {
      resolve(JSON.parse(b));
    });
  });
};

/**
 * Retrieve Stromkonto Balances for given account.
 * Note: This does not retrieve data from BLC - instead it is using Corrently service
 *
 * @function Stromkonto
 * @param {string} meterid unique id to dispatch
 * @return {Object} Performance data as given by meter schema
 */
ethers.Stromkonto = function(account) {
  return new Promise(function(resolve, reject) {
    request(ethers.CORRENTLY.API + 'stromkonto?account=' + account, function(e, r, b) {
      let res = JSON.parse(b);
      res.transactions = function() {
        return new Promise(function(resolve2, reject2) {
          let provider = new ethers.providers.JsonRpcProvider('https://node.corrently.io/', { chainId: 42 });
          provider.getBlockNumber().then(function(latest_block) {
            let mytxs = [];
            provider.getLogs({
              address: '0x8e93e70d8ac18dbaa38dd557acd4901f843e04e3',
              fromBlock: latest_block - 15000,
              topics: ['0x1a71774309711c9c0f58692353c6a0789dbdc71f63e2e42a190ab9bc03f79250'],
            }).then(function(l) {
              l = l.reverse();
              for (var i = 0; i < l.length; i++) {
                let item = l[i];
                item.data = item.data.substr(2);
                item.from = '0x' + item.data.substr(24, 40);
                item.to = '0x' + item.data.substr(88, 40);
                item.value = parseInt(item.data.substr(128, 64), 16) / 100000;
                item.base = parseInt(item.data.substr(192, 64), 16) / 1000;
                l[i] = item;
                if ((item.from === ('' + account).toLowerCase()) || (item.to === ('' + account).toLowerCase())) {
                  if ((item.from === ('' + account).toLowerCase())) {
                    item.peer = item.to;
                    item.value_abs = item.value * -1;
                    item.base_abs = item.base * -1;
                  } else {
                    item.peer = item.from;
                    item.value_abs = item.value;
                    item.base_abs = item.base;
                  }
                  item.value_abs = (item.value_abs.toFixed(5) + '').replace('.', ',');
                  item.base_abs = (item.base_abs.toFixed(3) + '').replace('.', ',');
                  if (item.peer === '0x445c1e284c15a50a69fe7d6dcd9fba3b938b52bb') item.base_abs = '';
                  mytxs.push(item);
                }
              }
              resolve2(mytxs);
            });
          });
        });
      };

      resolve(res);
    });
  });
};

export default ethers;