coin.js

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;