var globalVar = require("global").libcoinconst;
var libraries = globalVar.libraries;
var config = globalVar.config;
const price = require('crypto-price');
var keccak = require("eth-lib/lib/hash")
var Error = globalVar.Error;
const keccak256 = keccak.keccak256
const Bytes = require("eth-lib/lib/bytes");
const ethCrypto = require('eth-crypto');
/** @classdesc A class that can represent a coin or token. E.g. new xxx.BCH(privateKey);
* @class Coin
* @hideconstructor
*/
class Coin {
initGlobal(){
var _this=this;
var promises =[];
globalVar[this.coin].blocks ? (()=>{_this.blocks=globalVar[_this.coin].blocks})() : promises.push(new Promise((resolve,reject)=>{_this.block(10).live().then((blocks)=>{globalVar[_this.coin].blocks=_this.blocks=blocks; resolve()})}));
globalVar[_this.coin].fee ? (()=>{_this.fee=globalVar[_this.fee].fee})() : promises.push(new Promise((resolve,reject)=>{_this.fee(10).live().then((fee)=>{globalVar[_this.coin].fee=_this.fee=fee; resolve()}) }));
this._initGlobal ? promises.push(new Promise((resolve,reject)=>{_this._initGlobal(); resolve();})) : null;
console.log("Got these promises",promises);
return promises;
}
/**
Connect to the WebSocket network for your blockchain, and assign global variables to track its last 10 blocks in `.Blocks` and its fee/gas-price in `.Fee`. Note that balances, history, and other information is not yet tracked.
*/
async build(doPromises){
console.log("Build is called");
if(this._build) await this._build();
doPromises = doPromises || false;
return doPromises ? await Promise.all(this.initGlobal()) : this.initGlobal();
}
/**
Performs the functions in .coin, as well as assigning the token information (decimals, symbol, etc) to your token structure. Note that balances, history, and other information is not yet tracked.
@param {string} TokenID The address of the genesis Tx or similar structure that uniquely identifies a token.
*/
async token(tokenId){
var _this=this;
this.isToken = true;
return await Promise.all(this.coin(false).concat(new Promise((resolve,reject)=>{_this._token ? _this._token(tokenId).then(resolve) : resolve()})));
}
/**
* @private
*/
constructor(coin,config){
config = config || {};
if(typeof config=="string")config ={"privateKey":config}
this.coin = this.symbol = coin.toLowerCase();
this.isToken = false;
this.stableBlockNum = 6; // how many blocks when it is trusted
this.Cascade = Cascade;
var _this=this;
["balance","block","coinBalance","fee","history","price","stableBlock","tokenBalance","transaction"].forEach((e)=>{
["get","on","off","live"].forEach((e2)=>{
_this[e2+e.charAt(0).toUpperCase()+e.substring(1,e.length)] = ()=>{return _this[e]()[e2]()}
});
});
}
get latestBlock(){return this.blocks.length>0 ? this.blocks[0] : (()=>{throw new Error('Please init with .coin() chain in ending')})();}
get blockHeight(){return this.blocks.length>0 ? this.blocks[0].number : (()=>{throw new Error('Please init with .coin() chain ending')})();}
get blocks(){return globalVar[this.coin].blocks?globalVar[this.coin].blocks:(()=>{throw new Error('Please init with .coin() chain ending')})();}
/**
* Get the USD price, or other fiat price, of a coin or token. May not work for some tokens. Needs to be turned into Live structure.
* @param {string} ["USD"] - The fiat to compare your coin or token to
* @param {string} ["COIN"] - The symbol of the coin or token (will default to token symbol / coin.
*/
async price (fiat,coin){coin = coin || this.symbol || this.coin; return new this.Cascade({"gs":(coin==this.symbol || coin==this.coin),"parent":this},"price",fiat,this.coin || this.symbol);}
/**
* Build, send, or sign a transaction. For 'on', 'get', and 'is' methods, the number of confirmations of a transaction hash is calculated.
* @param {object} TxJSON A JSON structure / dictionary / unordered list. Required keys: toAddress, amount (in decimals, will be converted to wei / satoshi / tokenDecimals when returned). Optional: privateKey(string) --> if you don't provide this, the one for your current coin/token will be used. data(json-object or string)--> data to embed in the blockchain, can create large transactions on btc /bch. .hash --> May contain a hash structure
*/
async transaction(inputTx){return new this.Cascade({"gs":false,"parent":this},"transaction",inputTx);}
/**
* Get the balance of the given coin or token. Internally, these functions call `tokenBalance` or `coinBalance`, depending on if the object is a coin or token. You might find it useful to refer to `tokenBalance` and `coinBalance` individually. Returns a Live Structure
* @param {string} [Address] The address of the given coin or token. If none are provided, the instances address is used.
*/
balance(address){ return this.isToken ? this.tokenBalance(address) : this.coinBalance(address); }
tokenBalance(address){address = address || this.auth.address; return this.auth.isTokenAddress(address) ? new this.Cascade({"gs":address==this.auth.address,"parent":this},"tokenBalance",address) : (()=>{throw new Error('Not a token address') })()}
coinBalance(address){address = address || this.auth.address; if(this.auth.isCoinAddress(address)){ return new this.Cascade({"gs":address==this.auth.address,"parent":this},"coinBalance",address)} else throw new Error('Not a coin address') }
/**
* List all transactions for an address
* @param {string} [Address] A cryptocurrency address, or the instance's address is used
*/
history(address){address = address || this.auth.address; return new this.Cascade({"gs":address==this.auth.address,"parent":this},"history",address);}
/**
* The current fee, or gas price, for your coin is always stored in the .fee constant. If you'd like to get the average fee over a greater number of blocks, you can do that here. By default, this will update the .fee constant for this coin instance.
*/
fee(change){ return new this.Cascade({"gs":change || true,"parent":this},"fee",this.stableBlockNum*10); }
/**
* Get a number of very recent blocks in an array. The first element of the array is the oldest block.
* @param {integer} [lookback] The number of blocks to return from the most recent. Default: 0
* @param {boolean} [onlyOldest] If true, returns only the oldest of these blocks as an object
* @returns {array} Blocks Returns an array, unless onlyOldest is selected, then a block object is returned.
*/
block(lookback){
lookback = lookback || 0; return new this.Cascade({"gs":false,"global":true,"parent":this},"block",lookback)
}
//get block(){return globalVar[this.coin].blocks ? (globalVar[this.coin].blocks.length > 0 ? globalVar[this.coin].blocks[0] : (()=>{throw new Error('Please init with .coin()')})()) : (()=>{throw new Error('Please init with .coin()')})()}
/**
* Get the latest stable block (one that is unlikely to be lost due to re-org / "uncle").
* @returns {object} Block A block object with <tt>number</tt>, <tt>hash</tt>, and <tt>timestamp</tt> (server time in seconds).
*/
stableBlock(){ return new this.Cascade({"gs":false,"global":true,"parent":this},"block",this.stableBlockNum)}
}
/** @classdesc TL;DR: For a function named `balance()` call `getBalance()` on its parent to execute the function. Otherwise a Cascade (chainable function) structure is returned.
* Advanced:
* A Live structure makes use of underlying websocket APIs.
* 1. Chaining .get() after .balance(), an HTTP request to get the balance given the provided arguments. Example: .balance(address).get(); Equivalent: .getBalance()address;
* 2. Chaning .on(callback) after .balance() provides a Websocket subscription where new data is provided to the 'callback' function as it is received. Example: .balance(address).on(console.log). Equivalent: .onBalance(address,console.log);
* 3. Chaining .live() to .balance() returns a pointer to a <tt>live</tt> variable. When you assign this live variable to one of your object's keys, the object's key is updated with new data automatically as it comes in. Example: user.bal = BCH.balance(address).live(); // user.bal is always updated with latest data. However, if you were to assign this value to a non-object (like var user = xx.live()), it wouldn't work because javascript pointers only work when assigned as a key to existing objects and not newly initialized variables.
* 4. Chaining .off() to .balance() should turn off the .balance() object, but only if you've saved the original object returned by the .balance() that you called 'on' on.
* 5. The 'transaction' function also includes chains like .build(), .sign(), and .send(). It expects an object for these inputs. Its .get(), .live(), .on() functions expects an object with 'hash' as a key or a string.
* LibCoin is a wrapper over other cryptocurrency libraries, which frequently have syntax that differs most greatly in relation to websockets and HTTP requests. Our becomes much smaller using Cascade varaibles.
* @class Cascade
* @hideconstructor
*/
class Cascade {
constructor(config,query,...args){
this.parent=config.parent;
var parent = this.parent;
this.query=query;this.args = args;this.last=null;
var _this=this;
if(config.gs&&false){
Object.defineProperty(parent,query,{
get:()=>_this.last,
set:()=>{throw 'Cant assign name to a live property property, sorry';}
})
}
this.turnedOff=false;
this.Get = class Get{
constructor(parent,query,...args){return this[query] ? this[query](...args) : this._(query,...args)}
}
this.On = class On {
constructor(parent,callback,query,...args){
return this[query] ? this[query](callback,...args) : this.__(callback,query,...args);
}
arrayToKeep(num) {
return this.data.length > num ? this.data.slice(this.data.length-num,this.data.length) : this.data;
}
__(callback,query,...args){
this._((data)=>{
if(this.numToKeep){
if(!this.data)this.data=[];
this.data.concat(typeof data == "array" ? data : [].concat(data)); // concat or push? i think concat ? depends if a string ? mightve overthought this line sorry
this.data=this.arrayToKeep(this.numToKeep);
}else this.data=data;
callback(this.data);
},query,...args);
}
}
this.Off = class Off{
constructor(query,...args){ return this[query] ? this[query](...args) : this._(query,...args)}
}
this.Build = class Build{
constructor(query,...args){
return this[query] ? this[query](...args) : this._(query,...args)
}
async ransaction(...args){
var inputTx = args;
if(typeof inputTx!="object")throw new Error('You called a function that was expecting an object as an inputTx');
["toAddress","amount"].forEach((e,i,a)=>{if(typeof inputTx[e]=="undefined")throw 'Missing '+e+' from inputTx structure';});
inputTx.privateKey = inputTx.privateKey || this.parent.auth.privateKey || (()=>{throw 'Missing privateKey from inputTx structure and coin object'})();
inputTx.fromAddress= inputTx.privateKey!=this.parent.auth.privateKey ? this.parent.auth.toAddress(inputTx.privateKey) : this.parent.auth.address;
if(inputTx.data)inputTx.data = typeof inputTx.data=="string" ? inputTx.data : JSON.stringify(inputTx.data);
inputTx.unsigned = await this._transaction(inputTx);
return inputTx;
}
_(query){throw new Error('Query not found')}
}
this.Sign = class Sign{
constructor(query,...args){return this[query] ? this[query](...args) : this._(query,...args)}
async transaction(...args){
var inputTx = args;
if(!inputTx.unsigned)inputTx = await this.buildTransaction(inputTx);
inputTx.privateKey = inputTx.privateKey || this.parent.auth.privateKey || (()=>{throw 'Missing privateKey from inputTx structure and coin object'})();
inputTx.signed = this._transaction(inputTx);
return inputTx;
}
_(query){throw new Error('Query not found')}
}
this.Send = class Send{
constructor(query,...args){return this[query] ? this[query](...args) : this._(query,...args)}
async transaction(...args){
var inputTx = args
return await this._transaction(inputTx.signed ? inputTx : (await this.signTransaction(inputTx)));
}
_(query){throw new Error('Query not found')}
}
}
async get(){return this.last = await new this.Get(this.parent,this.query,this.args)}
async on(callback){
this.callback = callback;
var _this=this;
return await new this.On((data)=>{_this.callback(_this.last = data)},this.query,this.args)
}
off(){this.turnedOff=true; this.callback = function(){}; return new this.Off(this.parent,this.query,this.args); }
async live(callback){
var obj = {"is": await this.get(this.query,this.args) }
var onstruct;
onstruct = this.on((data)=>{callback(obj.is = data)});
if(onstruct.numToKeep){
onstruct.data = [].concat(obj.live); // initialize with the array'd getData if needed
}
return obj;
}
}
/** @classdesc A class for creating new identities, signing, and verifying messages. Each Coin or Token comes with this functionality under its .auth structure.
* @class
* @hideconstructor
*/
class Auth {
constructor(parent,config){
config = config || {};
var _this=this;
this.messageTimeLimit = config.messageTimeLimit || -1; //60*60*1000; // 1 hr for messages
this.messageSplitter = "_*_*_*"
if(config.address)this.address=config.address;
if(config.privateKey)this.setIdentity(config.privateKey)
this.messagePrefix = "";
this.parent=parent;
}
/**
* Create very random entropy with a length of at least 256 bytes.
*/
createEntropy(string){
var outerHex = string || "";
while(outerHex.length < 256){
if(!string){
const innerHex = keccak256(Bytes.concat(Bytes.random(32), Bytes.random(32)));
const middleHex = Bytes.concat(Bytes.concat(Bytes.random(32), innerHex), Bytes.random(32));
outerHex+=keccak256(middleHex);
}else outerHex+=string;
}
return outerHex;
}
/**
* Determine if a coin or token address is valid.
* @param {string} InputString A string of anything.
* @returns {bool} IsAddress whether the InputString is an address not.
*/
isAddress(address){
if(parent.isToken)return this.isTokenAddress(address);
else return this.isCoinAddress(address);
}
/**
* Determine if an address is valid for the given coin. As of now this is only necessary because of SLP tokens.
* @param {string} Address A cryptocurrency address.
* @returns {string} Address A cryptocurrency address.
*/
isCoinAddress(address){return this._isCoinAddress ? this._isCoinAddress(address) : this._isAddress(address)}
/**
* Determine if a token address is valid for the given token. As of now this is only applicable to SLP tokens.
* @param {string} Address A cryptocurrency address.
* @returns {string} Address A cryptocurrency address.
*/
isTokenAddress(address){return this._isTokenAddress ? this._isTokenAddress(address) : this._isAddress(address)}
/**
* Set the identity for the given coin or token instance. Input can be a private key in WIF format (bch, btc, eos), an Ethereum private key (begins with 0x, is ___ characters), or a string of text (brain wallet). Provide no input to assign a random identity.
* @param {string} [Private_Key] Optionally provide a string with a private key
*/
setIdentity(string){
var identity = this.createIdentity(string);
for(var key in identity)this[key] = identity[key];
}
/**
* Create an identity, but only return its address and private key. Input can be a private key in WIF format (bch, btc, eos), an Ethereum private key (begins with 0x, is ___ characters), or a string of text (brain wallet). Provide no input to assign a random identity.
* @param {string} [Private_Key] Optionally provide a string with a private key
* @returns {object} Identity An identity object with a private key, address, and encryptionPublicKey. Share the encryptionPublicKey with those who'd like to send you an encrypted message.
*/
createIdentity(string){
// string can be a private key or nothing
var identity = this._createIdentity(string || this.createEntropy());
identity.encryptionPublicKey = ethCrypto.publicKeyByPrivateKey(new Buffer(identity.privateKey).toString('hex'))
return identity;
}
/**
* Do not use yet - Encrypt a message to another user's pubkey. Use this.auth.pubkey.
* @param {object} Message The message to encrypt
* @param {string} [PubKey] The pubkey that owns the private key who will decrypt it.
* @returns {string} Encrypted The encrypted object as a string
*/
async encrypt(message,pubKey){
message = this.genMessage(message);
var encrypted = await ethCrypto.encryptWithPublicKey(pubKey,message);
encrypted = ethCrypto.cipher.stringify(encrypted);
if(encrypted.substring(0,2)=="0x")encrypted=encrypted.substring(2,encrypted.length);
encrypted=_this.encode(encrypted);
return encrypted
}
/**
* Do not use yet - Decrypt a message using a private key.
* @param {string} Encrypted The message to decrypt
* @param {string} [Private Key] The key to use when decrypting
* @returns {object} Decrypted The object that was decrypted
* @returns {number} TimeSince The number of milliseconds that has passed the message was signed (allegedly).
*/
async decrypt(encrypted,privateKey){
privateKey = new Buffer(privateKey || this.auth.privateKey).toString('hex');
if(encrypted.substring(0,2)=="0x")encrypted=encrypted.substring(2,encrypted.length); //removes 0x
encrypted = ethCrypto.cipher.parse(encrypted);
var decrypted = await ethCrypto.decryptWithPrivateKey(privateKey,encrypted)
return this.parseMessage(decrypted);
}
/**
* Do not use yet - Sign a message using a unique prefix generated by this library.
* @param {string/object} Message The message to sign
* @param {string} [Private_Key] An optional private key. If none are provided, the instance's private key is used.
* @returns {object} SigObject An object that contains the message, signature, and address that signed it.
*/
sign(message,privateKey){
message = this.genMessage(message || "");
privateKey = privateKey || this.privateKey || (()=>{throw new Error('Need a private key to sign')})();
return this._sign(message,privateKey);
}
/**
* Do not use yet - Verify a message signed from this library.
* @param {string} Message The timestamped message to verify
* @param {string} Signature The signature to verify
* @param {string} Address The address who may have signed the message
* @returns {boolean} Verified Whether the signature matches the address given
* @returns {object} The original message without the timestamp & coin-specific and library-specific nonces
* @returns {number} The number of milliseconds since the message was signed (allegedly)
*/
verify(message,signature,address){
var {originalMessage, timeSince} = this.parseMessage(message);
var verified = this._verify(message,signature,address);
return {verified,originalMessage,timeSince}
}
/**
* @ignore
*/
parseMessage(message){
message=message.split(this.messageSplitter);
if(message.length < 2){
throw "This message was not signed by this library.";
}
else if(this.timeLimit < 0 || message[2] > (new Date().getTime() - _this.messageTimeLimit))return JSON.parse(message),(new Date().getTime() - message[1]);
else throw 'Message signed too long ago';
}
/**
* Do not use yet - Generate a message that a user can sign in another wallet software and have it be analyzed in this library.
* @param {object} Message The message to stringify and append a timestamp to
* @returns {string} SerializedMessage The stringified message containing coin-specific prefix, library-specific splitters, and a timestamp nonce.
*/
genMessage(message){
return this.messagePrefix + splitter+JSON.stringify(message)+splitter+new Date().getTime();
}
sha256(string){return libraries.sha256(string);}
}
module.exports.Coin = Coin;
module.exports.Auth = Auth;
module.exports.Cascade = Cascade;