diff --git a/FLO_webWallet_mainnet.html b/FLO_webWallet_mainnet.html index 72cc5e8..0b7d898 100644 --- a/FLO_webWallet_mainnet.html +++ b/FLO_webWallet_mainnet.html @@ -1,5752 +1,7774 @@ - + -FLO webWallet - - +FLO web wallet + + + - - - - - - -

FLO Web Wallet

- -
- -
- -
-
- + +
+
+ There seems to be a problem connecting to the internet.
-
-
- - - - - - - -
FLO AddressPrivate Key
-
-
- -
-
-
-
- -
-
-
- - -
-
+
+ +
+
+ +
+ + + +
Monitor
+ Monitor FLO data +
+ +
+ + + + + + +
Send
+ Send FLO data +
+
+ + + + + + + +
Generate
+ Generate address +
+
+ + Settings + + + + + +
Settings
+ Settings +
+
+
+ Copied +
+
+
+
+

Add new address to monitoring list

+
+ + +
+
+ + +
+ + +
+
+
+
+

Edit display card

+ + Copy FLO address + + +
+ + + + + + + +

+
+
+ + +
+ + + +
+
+
+
+ + Add new address to monitor + + +
+
+
+ + Go back to monitoring page + + +

+ + Refresh transactions + + +
+ + + + + + +
+ +
+
+
+ + + + + + +
Available balance
+ 0 + +
To send FLO data, make sure you have enough balance.
+
+
+ + + +
+
+
+
+ + +
+
+ + +
+
+ + +
+ +
+
+
+
+ + Go back to monitoring page + + +

Send

+
+
+
+ + +
+ +
+
+
+ + + + + + +

Transaction successful

+

+ +
+
+ +

+
+
+
+
+
+

Dark mode

+
+ + Automatic
+ Dark mode active : 6pm - 6am +
+ +
+
+ Manual
+ Dark mode +
+ +
+
+
+

Clear all local data

+
This will delete all local Web Wallet data like added addresses and locally stored transactions.After clearing local data you may experience slow loading of newly added address, please proceed cautiously!
+ +
+
+

About

+
Version 2.7.2
+ +
Powered by
+ + + + + + + + + + + + + +
+
+
+
+
+
+ -
- - - - -
-
-
- -
-
- - -
-
-
- - - - - - - - - + - - - - - + + Bitcoin.Address.networkVersion = 0x23; // (FLO mainnet 0x23, 35D), (Bitcoin Mainnet, 0x00, 0D) // *this has no effect * + + /** + * Serialize this object as a standard Bitcoin address. + * + * Returns the address as a base58-encoded string in the standardized format. + */ + Bitcoin.Address.prototype.toString = function () { + // Get a copy of the hash + var hash = this.hash.slice(0); + + // Version + hash.unshift(this.version); + var checksum = Crypto.SHA256(Crypto.SHA256(hash, { asBytes: true }), { asBytes: true }); + var bytes = hash.concat(checksum.slice(0, 4)); + return Bitcoin.Base58.encode(bytes); + }; + + Bitcoin.Address.prototype.getHashBase64 = function () { + return Crypto.util.bytesToBase64(this.hash); + }; + + /** + * Parse a Bitcoin address contained in a string. + */ + Bitcoin.Address.decodeString = function (string,version) { + var bytes = Bitcoin.Base58.decode(string); + var hash = bytes.slice(0, 21); + var checksum = Crypto.SHA256(Crypto.SHA256(hash, { asBytes: true }), { asBytes: true }); + + if (checksum[0] != bytes[21] || + checksum[1] != bytes[22] || + checksum[2] != bytes[23] || + checksum[3] != bytes[24]) { + throw "Checksum validation failed!"; + } + + if (version != hash.shift()) { + throw "Version " + hash.shift() + " not supported!"; + } + + return hash; + }; + //https://raw.github.com/bitcoinjs/bitcoinjs-lib/e90780d3d3b8fc0d027d2bcb38b80479902f223e/src/ecdsa.js + Bitcoin.ECDSA = (function () { + var ecparams = EllipticCurve.getSECCurveByName("secp256k1"); + var rng = new SecureRandom(); + + var P_OVER_FOUR = null; + + function implShamirsTrick(P, k, Q, l) { + var m = Math.max(k.bitLength(), l.bitLength()); + var Z = P.add2D(Q); + var R = P.curve.getInfinity(); + + for (var i = m - 1; i >= 0; --i) { + R = R.twice2D(); + + R.z = BigInteger.ONE; + + if (k.testBit(i)) { + if (l.testBit(i)) { + R = R.add2D(Z); + } else { + R = R.add2D(P); + } + } else { + if (l.testBit(i)) { + R = R.add2D(Q); + } + } + } + + return R; + }; + + var ECDSA = { + getBigRandom: function (limit) { + return new BigInteger(limit.bitLength(), rng) + .mod(limit.subtract(BigInteger.ONE)) + .add(BigInteger.ONE); + }, + sign: function (hash, priv) { + var d = priv; + var n = ecparams.getN(); + var e = BigInteger.fromByteArrayUnsigned(hash); + + do { + var k = ECDSA.getBigRandom(n); + var G = ecparams.getG(); + var Q = G.multiply(k); + var r = Q.getX().toBigInteger().mod(n); + } while (r.compareTo(BigInteger.ZERO) <= 0); + + var s = k.modInverse(n).multiply(e.add(d.multiply(r))).mod(n); + + return ECDSA.serializeSig(r, s); + }, + + verify: function (hash, sig, pubkey) { + var r, s; + if (Bitcoin.Util.isArray(sig)) { + var obj = ECDSA.parseSig(sig); + r = obj.r; + s = obj.s; + } else if ("object" === typeof sig && sig.r && sig.s) { + r = sig.r; + s = sig.s; + } else { + throw "Invalid value for signature"; + } + + var Q; + if (pubkey instanceof ec.PointFp) { + Q = pubkey; + } else if (Bitcoin.Util.isArray(pubkey)) { + Q = EllipticCurve.PointFp.decodeFrom(ecparams.getCurve(), pubkey); + } else { + throw "Invalid format for pubkey value, must be byte array or ec.PointFp"; + } + var e = BigInteger.fromByteArrayUnsigned(hash); + + return ECDSA.verifyRaw(e, r, s, Q); + }, + + verifyRaw: function (e, r, s, Q) { + var n = ecparams.getN(); + var G = ecparams.getG(); + + if (r.compareTo(BigInteger.ONE) < 0 || + r.compareTo(n) >= 0) + return false; + + if (s.compareTo(BigInteger.ONE) < 0 || + s.compareTo(n) >= 0) + return false; + + var c = s.modInverse(n); + + var u1 = e.multiply(c).mod(n); + var u2 = r.multiply(c).mod(n); + + // TODO(!!!): For some reason Shamir's trick isn't working with + // signed message verification!? Probably an implementation + // error! + //var point = implShamirsTrick(G, u1, Q, u2); + var point = G.multiply(u1).add(Q.multiply(u2)); + + var v = point.getX().toBigInteger().mod(n); + + return v.equals(r); + }, + + /** + * Serialize a signature into DER format. + * + * Takes two BigIntegers representing r and s and returns a byte array. + */ + serializeSig: function (r, s) { + var rBa = r.toByteArraySigned(); + var sBa = s.toByteArraySigned(); + + var sequence = []; + sequence.push(0x02); // INTEGER + sequence.push(rBa.length); + sequence = sequence.concat(rBa); + + sequence.push(0x02); // INTEGER + sequence.push(sBa.length); + sequence = sequence.concat(sBa); + + sequence.unshift(sequence.length); + sequence.unshift(0x30); // SEQUENCE + + return sequence; + }, + + /** + * Parses a byte array containing a DER-encoded signature. + * + * This function will return an object of the form: + * + * { + * r: BigInteger, + * s: BigInteger + * } + */ + parseSig: function (sig) { + var cursor; + if (sig[0] != 0x30) + throw new Error("Signature not a valid DERSequence"); + + cursor = 2; + if (sig[cursor] != 0x02) + throw new Error("First element in signature must be a DERInteger");; + var rBa = sig.slice(cursor + 2, cursor + 2 + sig[cursor + 1]); + + cursor += 2 + sig[cursor + 1]; + if (sig[cursor] != 0x02) + throw new Error("Second element in signature must be a DERInteger"); + var sBa = sig.slice(cursor + 2, cursor + 2 + sig[cursor + 1]); + + cursor += 2 + sig[cursor + 1]; + + //if (cursor != sig.length) + // throw new Error("Extra bytes in signature"); + + var r = BigInteger.fromByteArrayUnsigned(rBa); + var s = BigInteger.fromByteArrayUnsigned(sBa); + + return { + r: r, + s: s + }; + }, + + parseSigCompact: function (sig) { + if (sig.length !== 65) { + throw "Signature has the wrong length"; + } + + // Signature is prefixed with a type byte storing three bits of + // information. + var i = sig[0] - 27; + if (i < 0 || i > 7) { + throw "Invalid signature type"; + } + + var n = ecparams.getN(); + var r = BigInteger.fromByteArrayUnsigned(sig.slice(1, 33)).mod(n); + var s = BigInteger.fromByteArrayUnsigned(sig.slice(33, 65)).mod(n); + + return { + r: r, + s: s, + i: i + }; + }, + + /** + * Recover a public key from a signature. + * + * See SEC 1: Elliptic Curve Cryptography, section 4.1.6, "Public + * Key Recovery Operation". + * + * http://www.secg.org/download/aid-780/sec1-v2.pdf + */ + recoverPubKey: function (r, s, hash, i) { + // The recovery parameter i has two bits. + i = i & 3; + + // The less significant bit specifies whether the y coordinate + // of the compressed point is even or not. + var isYEven = i & 1; + + // The more significant bit specifies whether we should use the + // first or second candidate key. + var isSecondKey = i >> 1; + + var n = ecparams.getN(); + var G = ecparams.getG(); + var curve = ecparams.getCurve(); + var p = curve.getQ(); + var a = curve.getA().toBigInteger(); + var b = curve.getB().toBigInteger(); + + // We precalculate (p + 1) / 4 where p is if the field order + if (!P_OVER_FOUR) { + P_OVER_FOUR = p.add(BigInteger.ONE).divide(BigInteger.valueOf(4)); + } + + // 1.1 Compute x + var x = isSecondKey ? r.add(n) : r; + + // 1.3 Convert x to point + var alpha = x.multiply(x).multiply(x).add(a.multiply(x)).add(b).mod(p); + var beta = alpha.modPow(P_OVER_FOUR, p); + + var xorOdd = beta.isEven() ? (i % 2) : ((i + 1) % 2); + // If beta is even, but y isn't or vice versa, then convert it, + // otherwise we're done and y == beta. + var y = (beta.isEven() ? !isYEven : isYEven) ? beta : p.subtract(beta); + + // 1.4 Check that nR is at infinity + var R = new EllipticCurve.PointFp(curve, + curve.fromBigInteger(x), + curve.fromBigInteger(y)); + R.validate(); + + // 1.5 Compute e from M + var e = BigInteger.fromByteArrayUnsigned(hash); + var eNeg = BigInteger.ZERO.subtract(e).mod(n); + + // 1.6 Compute Q = r^-1 (sR - eG) + var rInv = r.modInverse(n); + var Q = implShamirsTrick(R, s, G, eNeg).multiply(rInv); + + Q.validate(); + if (!ECDSA.verifyRaw(e, r, s, Q)) { + throw "Pubkey recovery unsuccessful"; + } + + var pubKey = new Bitcoin.ECKey(); + pubKey.pub = Q; + return pubKey; + }, + + /** + * Calculate pubkey extraction parameter. + * + * When extracting a pubkey from a signature, we have to + * distinguish four different cases. Rather than putting this + * burden on the verifier, Bitcoin includes a 2-bit value with the + * signature. + * + * This function simply tries all four cases and returns the value + * that resulted in a successful pubkey recovery. + */ + calcPubkeyRecoveryParam: function (address, r, s, hash) { + for (var i = 0; i < 4; i++) { + try { + var pubkey = Bitcoin.ECDSA.recoverPubKey(r, s, hash, i); + if (pubkey.getBitcoinAddress().toString() == address) { + return i; + } + } catch (e) {} + } + throw "Unable to find valid recovery factor"; + } + }; + + return ECDSA; + })(); + Bitcoin.KeyPool = (function () { + var KeyPool = function () { + this.keyArray = []; + + this.push = function (item) { + if (item == null || item.priv == null) return; + var doAdd = true; + // prevent duplicates from being added to the array + for (var index in this.keyArray) { + var currentItem = this.keyArray[index]; + if (currentItem != null && currentItem.priv != null && item.getBitcoinAddress() == currentItem.getBitcoinAddress()) { + doAdd = false; + break; + } + } + if (doAdd) this.keyArray.push(item); + }; + + this.reset = function () { + this.keyArray = []; + }; + + this.getArray = function () { + // copy array + return this.keyArray.slice(0); + }; + + this.setArray = function (ka) { + this.keyArray = ka; + }; + + this.length = function () { + return this.keyArray.length; + }; + + this.toString = function () { + var keyPoolString = "# = " + this.length() + "\n"; + var pool = this.getArray(); + for (var index in pool) { + var item = pool[index]; + if (Bitcoin.Util.hasMethods(item, 'getBitcoinAddress', 'toString')) { + if (item != null) { + keyPoolString += "\"" + item.getBitcoinAddress() + "\"" + ", \"" + item.toString("wif") + "\"\n"; + } + } + } + + return keyPoolString; + }; + + return this; + }; + + return new KeyPool(); + })(); + + Bitcoin.Bip38Key = (function () { + var Bip38 = function (address, encryptedKey) { + this.address = address; + this.priv = encryptedKey; + }; + + Bip38.prototype.getBitcoinAddress = function () { + return this.address; + }; + + Bip38.prototype.toString = function () { + return this.priv; + }; + + return Bip38; + })(); + + //https://raw.github.com/pointbiz/bitcoinjs-lib/9b2f94a028a7bc9bed94e0722563e9ff1d8e8db8/src/eckey.js + Bitcoin.ECKey = (function () { + var ECDSA = Bitcoin.ECDSA; + var KeyPool = Bitcoin.KeyPool; + var ecparams = EllipticCurve.getSECCurveByName("secp256k1"); + + var ECKey = function (input) { + if (!input) { + // Generate new key + var n = ecparams.getN(); + this.priv = ECDSA.getBigRandom(n); + } else if (input instanceof BigInteger) { + // Input is a private key value + this.priv = input; + } else if (Bitcoin.Util.isArray(input)) { + // Prepend zero byte to prevent interpretation as negative integer + this.priv = BigInteger.fromByteArrayUnsigned(input); + } else if ("string" == typeof input) { + var bytes = null; + try{ + + // This part is edited for FLO. FLO WIF are always compressed WIF. FLO WIF (private key) starts with R for mainnet and c for testnet. + if(((floGlobals.blockchain == "FLO") && /^R[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{51}$/.test(input)) || + ((floGlobals.blockchain == "FLO_TEST") && /^c[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{51}$/.test(input))) { + bytes = ECKey.decodeCompressedWalletImportFormat(input); + this.compressed = true; + }else if (ECKey.isHexFormat(input)) { + bytes = Crypto.util.hexToBytes(input); + } + + + /* + if (ECKey.isWalletImportFormat(input)) { + bytes = ECKey.decodeWalletImportFormat(input); + } else if (ECKey.isCompressedWalletImportFormat(input)) { + bytes = ECKey.decodeCompressedWalletImportFormat(input); + this.compressed = true; + } else if (ECKey.isMiniFormat(input)) { + bytes = Crypto.SHA256(input, { asBytes: true }); + } else if (ECKey.isHexFormat(input)) { + bytes = Crypto.util.hexToBytes(input); + } else if (ECKey.isBase64Format(input)) { + bytes = Crypto.util.base64ToBytes(input); + } + */ + } catch (exc1) { + this.setError(exc1); + } + + if (ECKey.isBase6Format(input)) { + this.priv = new BigInteger(input, 6); + } else if (bytes == null || bytes.length != 32) { + this.priv = null; + } else { + // Prepend zero byte to prevent interpretation as negative integer + this.priv = BigInteger.fromByteArrayUnsigned(bytes); + } + } + + this.compressed = (this.compressed == undefined) ? !!ECKey.compressByDefault : this.compressed; + try { + // check not zero + if (this.priv != null && BigInteger.ZERO.compareTo(this.priv) == 0) this.setError("Error: BigInteger equal to zero."); + // valid range [0x1, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364140]) + var hexKeyRangeLimit = "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364140"; + var rangeLimitBytes = Crypto.util.hexToBytes(hexKeyRangeLimit); + var limitBigInt = BigInteger.fromByteArrayUnsigned(rangeLimitBytes); + if (this.priv != null && limitBigInt.compareTo(this.priv) < 0) this.setError("Error: BigInteger outside of curve range.") + + if (this.priv != null) { + KeyPool.push(this); + } + } catch (exc2) { + this.setError(exc2); + } + }; + + if(floGlobals.blockchain == "FLO") + ECKey.privateKeyPrefix = 0xA3; //(Bitcoin mainnet 0x80 testnet 0xEF) (FLO mainnet 0xA3 163 D) + else if(floGlobals.blockchain == "FLO_TEST") + ECKey.privateKeyPrefix = 0xEF; //FLO testnet + + /** + * Whether public keys should be returned compressed by default. + */ + ECKey.compressByDefault = false; + + /** + * Set whether the public key should be returned compressed or not. + */ + ECKey.prototype.setError = function (err) { + this.error = err; + this.priv = null; + return this; + }; + + /** + * Set whether the public key should be returned compressed or not. + */ + ECKey.prototype.setCompressed = function (v) { + this.compressed = !!v; + if (this.pubPoint) this.pubPoint.compressed = this.compressed; + return this; + }; + + /* + * Return public key as a byte array in DER encoding + */ + ECKey.prototype.getPub = function () { + if (this.compressed) { + if (this.pubComp) return this.pubComp; + return this.pubComp = this.getPubPoint().getEncoded(1); + } else { + if (this.pubUncomp) return this.pubUncomp; + return this.pubUncomp = this.getPubPoint().getEncoded(0); + } + }; + + /** + * Return public point as ECPoint object. + */ + ECKey.prototype.getPubPoint = function () { + if (!this.pubPoint) { + this.pubPoint = ecparams.getG().multiply(this.priv); + this.pubPoint.compressed = this.compressed; + } + return this.pubPoint; + }; + + ECKey.prototype.getPubKeyHex = function () { + if (this.compressed) { + if (this.pubKeyHexComp) return this.pubKeyHexComp; + return this.pubKeyHexComp = Crypto.util.bytesToHex(this.getPub()).toString().toUpperCase(); + } else { + if (this.pubKeyHexUncomp) return this.pubKeyHexUncomp; + return this.pubKeyHexUncomp = Crypto.util.bytesToHex(this.getPub()).toString().toUpperCase(); + } + }; + + /** + * Get the pubKeyHash for this key. + * + * This is calculated as RIPE160(SHA256([encoded pubkey])) and returned as + * a byte array. + */ + ECKey.prototype.getPubKeyHash = function () { + if (this.compressed) { + if (this.pubKeyHashComp) return this.pubKeyHashComp; + return this.pubKeyHashComp = Bitcoin.Util.sha256ripe160(this.getPub()); + } else { + if (this.pubKeyHashUncomp) return this.pubKeyHashUncomp; + return this.pubKeyHashUncomp = Bitcoin.Util.sha256ripe160(this.getPub()); + } + }; + + ECKey.prototype.getBitcoinAddress = function () { + var hash = this.getPubKeyHash(); + var addr = new Bitcoin.Address(hash); + return addr.toString(); + }; + + /* + * Takes a public point as a hex string or byte array + */ + ECKey.prototype.setPub = function (pub) { + // byte array + if (Bitcoin.Util.isArray(pub)) { + pub = Crypto.util.bytesToHex(pub).toString().toUpperCase(); + } + var ecPoint = ecparams.getCurve().decodePointHex(pub); + this.setCompressed(ecPoint.compressed); + this.pubPoint = ecPoint; + return this; + }; + + // Sipa Private Key Wallet Import Format + ECKey.prototype.getBitcoinWalletImportFormat = function () { + var bytes = this.getBitcoinPrivateKeyByteArray(); + if (bytes == null) return ""; + bytes.unshift(ECKey.privateKeyPrefix); // prepend 0x80 byte + if (this.compressed) bytes.push(0x01); // append 0x01 byte for compressed format + var checksum = Crypto.SHA256(Crypto.SHA256(bytes, { asBytes: true }), { asBytes: true }); + bytes = bytes.concat(checksum.slice(0, 4)); + var privWif = Bitcoin.Base58.encode(bytes); + return privWif; + }; + + // Private Key Hex Format + ECKey.prototype.getBitcoinHexFormat = function () { + return Crypto.util.bytesToHex(this.getBitcoinPrivateKeyByteArray()).toString().toUpperCase(); + }; + + // Private Key Base64 Format + ECKey.prototype.getBitcoinBase64Format = function () { + return Crypto.util.bytesToBase64(this.getBitcoinPrivateKeyByteArray()); + }; + + ECKey.prototype.getBitcoinPrivateKeyByteArray = function () { + if (this.priv == null) return null; + // Get a copy of private key as a byte array + var bytes = this.priv.toByteArrayUnsigned(); + // zero pad if private key is less than 32 bytes + while (bytes.length < 32) bytes.unshift(0x00); + return bytes; + }; + + ECKey.prototype.toString = function (format) { + format = format || ""; + if (format.toString().toLowerCase() == "base64" || format.toString().toLowerCase() == "b64") { + return this.getBitcoinBase64Format(); + } + // Wallet Import Format + else if (format.toString().toLowerCase() == "wif") { + return this.getBitcoinWalletImportFormat(); + } + else { + return this.getBitcoinHexFormat(); + } + }; + + ECKey.prototype.sign = function (hash) { + return ECDSA.sign(hash, this.priv); + }; + + ECKey.prototype.verify = function (hash, sig) { + return ECDSA.verify(hash, sig, this.getPub()); + }; + + /** + * Parse a wallet import format private key contained in a string. + */ + ECKey.decodeWalletImportFormat = function (privStr) { + var bytes = Bitcoin.Base58.decode(privStr); + var hash = bytes.slice(0, 33); + var checksum = Crypto.SHA256(Crypto.SHA256(hash, { asBytes: true }), { asBytes: true }); + if (checksum[0] != bytes[33] || + checksum[1] != bytes[34] || + checksum[2] != bytes[35] || + checksum[3] != bytes[36]) { + throw "Checksum validation failed!"; + + } + var version = hash.shift(); + if (version != ECKey.privateKeyPrefix) { + throw "Version " + version + " not supported!"; + } + return hash; + }; + + /** + * Parse a compressed wallet import format private key contained in a string. + */ + ECKey.decodeCompressedWalletImportFormat = function (privStr) { + var bytes = Bitcoin.Base58.decode(privStr); + var hash = bytes.slice(0, 34); + var checksum = Crypto.SHA256(Crypto.SHA256(hash, { asBytes: true }), { asBytes: true }); + if (checksum[0] != bytes[34] || + checksum[1] != bytes[35] || + checksum[2] != bytes[36] || + checksum[3] != bytes[37]) { + throw "Checksum validation failed!"; + } + var version = hash.shift(); + if (version != ECKey.privateKeyPrefix) { + throw "Version " + version + " not supported!"; + } + hash.pop(); + return hash; + }; + + // 64 characters [0-9A-F] + ECKey.isHexFormat = function (key) { + key = key.toString(); + return /^[A-Fa-f0-9]{64}$/.test(key); + }; + + // 51 characters base58, always starts with a '5' + ECKey.isWalletImportFormat = function (key) { + key = key.toString(); + return (ECKey.privateKeyPrefix == 0x80) ? + (/^5[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{50}$/.test(key)) : + (/^R[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{50}$/.test(key)); + }; + + // 52 characters base58 + ECKey.isCompressedWalletImportFormat = function (key) { + key = key.toString(); + return (ECKey.privateKeyPrefix == 0x80) ? + (/^[LK][123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{51}$/.test(key)) : + (/^R[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{51}$/.test(key)); + }; + + // 44 characters + ECKey.isBase64Format = function (key) { + key = key.toString(); + return (/^[ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789=+\/]{44}$/.test(key)); + }; + + // 99 characters, 1=1, if using dice convert 6 to 0 + ECKey.isBase6Format = function (key) { + key = key.toString(); + return (/^[012345]{99}$/.test(key)); + }; + + // 22, 26 or 30 characters, always starts with an 'S' + ECKey.isMiniFormat = function (key) { + key = key.toString(); + var validChars22 = /^S[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{21}$/.test(key); + var validChars26 = /^S[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{25}$/.test(key); + var validChars30 = /^S[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{29}$/.test(key); + var testBytes = Crypto.SHA256(key + "?", { asBytes: true }); + + return ((testBytes[0] === 0x00 || testBytes[0] === 0x01) && (validChars22 || validChars26 || validChars30)); + }; + + return ECKey; + })(); + //https://raw.github.com/bitcoinjs/bitcoinjs-lib/09e8c6e184d6501a0c2c59d73ca64db5c0d3eb95/src/util.js + // Bitcoin utility functions + Bitcoin.Util = { + /** + * Cross-browser compatibility version of Array.isArray. + */ + isArray: Array.isArray || function (o) { + return Object.prototype.toString.call(o) === '[object Array]'; + }, + /** + * Create an array of a certain length filled with a specific value. + */ + makeFilledArray: function (len, val) { + var array = []; + var i = 0; + while (i < len) { + array[i++] = val; + } + return array; + }, + /** + * Turn an integer into a "var_int". + * + * "var_int" is a variable length integer used by Bitcoin's binary format. + * + * Returns a byte array. + */ + numToVarInt: function (i) { + if (i < 0xfd) { + // unsigned char + return [i]; + } else if (i <= 1 << 16) { + // unsigned short (LE) + return [0xfd, i >>> 8, i & 255]; + } else if (i <= 1 << 32) { + // unsigned int (LE) + return [0xfe].concat(Crypto.util.wordsToBytes([i])); + } else { + // unsigned long long (LE) + return [0xff].concat(Crypto.util.wordsToBytes([i >>> 32, i])); + } + }, + /** + * Parse a Bitcoin value byte array, returning a BigInteger. + */ + valueToBigInt: function (valueBuffer) { + if (valueBuffer instanceof BigInteger) return valueBuffer; + + // Prepend zero byte to prevent interpretation as negative integer + return BigInteger.fromByteArrayUnsigned(valueBuffer); + }, + /** + * Format a Bitcoin value as a string. + * + * Takes a BigInteger or byte-array and returns that amount of Bitcoins in a + * nice standard formatting. + * + * Examples: + * 12.3555 + * 0.1234 + * 900.99998888 + * 34.00 + */ + formatValue: function (valueBuffer) { + var value = this.valueToBigInt(valueBuffer).toString(); + var integerPart = value.length > 8 ? value.substr(0, value.length - 8) : '0'; + var decimalPart = value.length > 8 ? value.substr(value.length - 8) : value; + while (decimalPart.length < 8) decimalPart = "0" + decimalPart; + decimalPart = decimalPart.replace(/0*$/, ''); + while (decimalPart.length < 2) decimalPart += "0"; + return integerPart + "." + decimalPart; + }, + /** + * Parse a floating point string as a Bitcoin value. + * + * Keep in mind that parsing user input is messy. You should always display + * the parsed value back to the user to make sure we understood his input + * correctly. + */ + parseValue: function (valueString) { + // TODO: Detect other number formats (e.g. comma as decimal separator) + var valueComp = valueString.split('.'); + var integralPart = valueComp[0]; + var fractionalPart = valueComp[1] || "0"; + while (fractionalPart.length < 8) fractionalPart += "0"; + fractionalPart = fractionalPart.replace(/^0+/g, ''); + var value = BigInteger.valueOf(parseInt(integralPart)); + value = value.multiply(BigInteger.valueOf(100000000)); + value = value.add(BigInteger.valueOf(parseInt(fractionalPart))); + return value; + }, + /** + * Calculate RIPEMD160(SHA256(data)). + * + * Takes an arbitrary byte array as inputs and returns the hash as a byte + * array. + */ + sha256ripe160: function (data) { + return ripemd160(Crypto.SHA256(data, { asBytes: true }), { asBytes: true }); + }, + // double sha256 + dsha256: function (data) { + return Crypto.SHA256(Crypto.SHA256(data, { asBytes: true }), { asBytes: true }); + }, + // duck typing method + hasMethods: function(obj /*, method list as strings */){ + var i = 1, methodName; + while((methodName = arguments[i++])){ + if(typeof obj[methodName] != 'function') { + return false; + } + } + return true; + } + }; + + /* FLO Crypto Operators*/ + const floCrypto = { + + //Generates a new flo ID and returns private-key, public-key and floID + generateNewID: function () { + try { + var key = new Bitcoin.ECKey(false); + key.setCompressed(true); + return { + floID: key.getBitcoinAddress(), + pubKey: key.getPubKeyHex(), + privKey: key.getBitcoinWalletImportFormat() + } + } catch (e) { + console.log(e); + } + }, + + //Returns public-key from private-key + getPubKeyHex: function (privateKeyHex) { + var key = new Bitcoin.ECKey(privateKeyHex); + if (key.priv == null) + return; + key.setCompressed(true); + var pubkeyHex = key.getPubKeyHex(); + return pubkeyHex; + }, + + //Returns flo-ID from public-key + getFloIDfromPubkeyHex: function (pubkeyHex) { + var key = new Bitcoin.ECKey().setPub(pubkeyHex); + var floID = key.getBitcoinAddress(); + return floID; + }, + + //Verify the private-key for the given public-key or flo-ID + verifyPrivKey: function (privateKeyHex, pubKey_floID, isfloID = true) { + try { + var key = new Bitcoin.ECKey(privateKeyHex); + if (key.priv == null) + return false; + key.setCompressed(true); + if (isfloID && pubKey_floID == key.getBitcoinAddress()) + return true; + else if (!isfloID && pubKey_floID == key.getPubKeyHex()) + return true; + else + return false; + } catch (e) { + console.log(e); + } + }, + + //Check if the given Address is valid or not + validateAddr: function (inpAddr) { + try { + var addr = new Bitcoin.Address(inpAddr); + return true; + } catch { + return false; + } + } + } + + /* FLO Blockchain Operator to send/receive data from blockchain using API calls*/ + const floBlockchainAPI = { + + //Promised AJAX function to get data from API + promisedAJAX: function (method, uri) { + return new Promise((resolve, reject) => { + var request = new XMLHttpRequest(); + var url = `${floGlobals.apiURL[floGlobals.blockchain]}/${uri}`; + console.log(url) + request.open(method, url, true); + request.onload = (evt) => { + if (request.readyState == 4 && request.status == 200) + resolve(request.response); + else + reject(request.response); + }; + request.send(); + }); + }, + + //Get balance for the given Address + getBalance: function (addr) { + return new Promise((resolve, reject) => { + this.promisedAJAX("GET", `api/addr/${addr}/balance`).then(balance => { + resolve(balance); + }).catch(error => { + reject(error); + }); + }); + }, + + //Send Tx to blockchain + sendTx: function (senderAddr, receiverAddr, sendAmt, PrivKey, floData = '') { + return new Promise((resolve, reject) => { + if (!floCrypto.validateAddr(senderAddr)) + reject(`Invalid address : ${senderAddr}`); + else if (!floCrypto.validateAddr(receiverAddr)) + reject(`Invalid address : ${receiverAddr}`); + if (PrivKey.length < 1 || !floCrypto.verifyPrivKey(PrivKey, senderAddr)) + reject("Invalid Private key!"); + else if (typeof sendAmt !== 'number' || sendAmt <= 0) + reject(`Invalid sendAmt : ${sendAmt}`); + else { + var trx = bitjs.transaction(); + var utxoAmt = 0.0; + var fee = floGlobals.fee; + this.promisedAJAX("GET", `api/addr/${senderAddr}/utxo`).then(response => { + var utxos = JSON.parse(response); + for (var i = utxos.length - 1; + (i >= 0) && (utxoAmt < sendAmt + fee); i--) { + if (utxos[i].confirmations) { + trx.addinput(utxos[i].txid, utxos[i].vout, utxos[i].scriptPubKey) + utxoAmt += utxos[i].amount; + } else break; + } + if (utxoAmt < sendAmt + fee) + reject("Insufficient balance!"); + else { + trx.addoutput(receiverAddr, sendAmt); + var change = utxoAmt - sendAmt - fee; + if (change > 0) + trx.addoutput(senderAddr, change); + trx.addflodata(floData); + var signedTxHash = trx.sign(PrivKey, 1); + this.broadcastTx(signedTxHash).then(txid => { + resolve(txid) + }).catch(error => { + reject(error); + }); + } + }).catch(error => { + reject(error); + }); + } + }); + }, + + //Broadcast signed Tx in blockchain using API + broadcastTx: function (signedTxHash) { + return new Promise((resolve, reject) => { + var request = new XMLHttpRequest(); + var url = `${floGlobals.apiURL[floGlobals.blockchain]}/api/tx/send`; + if (signedTxHash.length < 1) + reject("Empty Signature"); + else { + var params = `{"rawtx":"${signedTxHash}"}`; + var result; + request.open('POST', url, false); + //Send the proper header information along with the request + request.setRequestHeader('Content-type', 'application/json'); + request.onload = function () { + if (request.readyState == 4 && request.status == 200) { + console.log(request.response); + resolve(JSON.parse(request.response).txid.result); + } else + reject(request.responseText); + } + request.send(params); + } + }) + }, + + //Get new Tx in blockchain since last sync using API + getNewTxs: function(addr, ignoreOld){ + return new Promise((resolve, reject) => { + this.promisedAJAX("GET", `api/addrs/${addr}/txs?from=0&to=1`).then(response => { + var newItems = JSON.parse(response).totalItems - ignoreOld; + this.promisedAJAX("GET", `api/addrs/${addr}/txs?from=0&to=${newItems*2}`).then(response => { + response = JSON.parse(response) + var filteredData = []; + for (i = response.totalItems - ignoreOld - 1; i >=0 ; i--){ + if(response.items[i].isCoinBase){ + var item = { + time : response.items[i].time, + txid : response.items[i].txid, + sender : '(mined)'+response.items[i].vin[0].coinbase, + receiver : addr, + floData : response.items[i].floData + } + }else{ + var item = { + time : response.items[i].time, + txid : response.items[i].txid, + sender : response.items[i].vin[0].addr, + receiver : response.items[i].vout[0].scriptPubKey.addresses[0], + floData : response.items[i].floData + } + } + filteredData.push(item); + } + resolve({totalItems:response.totalItems , items:filteredData}); + }).catch(error => { + reject(error); + }); + }).catch(error => { + reject(error); + }); + }); + } + } + + /* Compact IndexedDB operations */ + + window.indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB; + window.IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction || window.msIDBTransaction; + window.IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange || window.msIDBKeyRange; + + if (!window.indexedDB) + window.alert("Your browser doesn't support a stable version of IndexedDB.") + + const compactIDB = { + + setDefaultDB: function (dbName) { + this.dbName = dbName; + }, + + initDB: function (dbName, objectStores = {}) { + return new Promise((resolve, reject) => { + this.dbName = this.dbName || dbName; + var idb = indexedDB.open(dbName); + idb.onerror = (event) => { + reject("Error in opening IndexedDB!"); + }; + idb.onupgradeneeded = (event) => { + var db = event.target.result; + for (obs in objectStores) { + var objectStore = db.createObjectStore(obs, objectStores[obs].options || {}); + if (objectStores[obs].indexes && typeof objectStores[obs].indexes === 'object') + for (i in objectStores[obs].indexes) + objectStore.createIndex(i, i, objectStores[obs].indexes[i] || {}); + } + } + idb.onsuccess = (event) => { + var db = event.target.result; + if (JSON.stringify(Object.values(db.objectStoreNames).sort()) === JSON.stringify(Object.keys(objectStores).sort())) + resolve("Initiated IndexedDB"); + else + reject("IndexedDB already exist with different ObjectStores!"); + db.close(); + } + }); + }, + + openDB: function (dbName = this.dbName) { + return new Promise((resolve, reject) => { + var idb = indexedDB.open(dbName); + idb.onerror = (event) => reject("Error in opening IndexedDB!"); + idb.onsuccess = (event) => resolve(event.target.result); + }); + }, + + deleteDB: function (dbName = this.dbName) { + return new Promise((resolve, reject) => { + var deleteReq = indexedDB.deleteDatabase(dbName);; + deleteReq.onerror = (event) => reject("Error deleting database!"); + deleteReq.onsuccess = (event) => resolve("Database deleted successfully"); + }); + }, + + writeData: function (obsName, data, key = false, dbName = this.dbName) { + return new Promise((resolve, reject) => { + this.openDB(dbName).then(db => { + var obs = db.transaction(obsName, "readwrite").objectStore(obsName); + let writeReq = (key ? obs.put(data, key) : obs.put(data)); + writeReq.onsuccess = (evt) => resolve(`Write data Successful`); + writeReq.onerror = (evt) => reject( + `Write data unsuccessful [${evt.target.error.name}] ${evt.target.error.message}`); + db.close(); + }).catch(error => reject(error)); + }); + }, + + addData: function (obsName, data, key = false, dbName = this.dbName) { + return new Promise((resolve, reject) => { + this.openDB(dbName).then(db => { + var obs = db.transaction(obsName, "readwrite").objectStore(obsName); + let addReq = (key ? obs.add(data, key) : obs.add(data)); + addReq.onsuccess = (evt) => resolve(`Add data successful`); + addReq.onerror = (evt) => reject( + `Add data unsuccessful [${evt.target.error.name}] ${evt.target.error.message}`); + db.close(); + }).catch(error => reject(error)); + }); + }, + + removeData: function (obsName, key, dbName = this.dbName) { + return new Promise((resolve, reject) => { + this.openDB(dbName).then(db => { + var obs = db.transaction(obsName, "readwrite").objectStore(obsName); + let delReq = obs.delete(key); + delReq.onsuccess = (evt) => resolve(`Removed Data ${key}`); + delReq.onerror = (evt) => reject( + `Remove data unsuccessful [${evt.target.error.name}] ${evt.target.error.message}`); + db.close(); + }).catch(error => reject(error)); + }); + }, + + readData: function (obsName, key, dbName = this.dbName) { + return new Promise((resolve, reject) => { + this.openDB(dbName).then(db => { + var obs = db.transaction(obsName, "readonly").objectStore(obsName); + let getReq = obs.get(key); + getReq.onsuccess = (evt) => resolve(evt.target.result); + getReq.onerror = (evt) => reject( + `Read data unsuccessful [${evt.target.error.name}] ${evt.target.error.message}`); + db.close(); + }).catch(error => reject(error)); + }); + }, + + readAllData: function (obsName, dbName = this.dbName) { + return new Promise((resolve, reject) => { + this.openDB(dbName).then(db => { + var obs = db.transaction(obsName, "readonly").objectStore(obsName); + var tmpResult = {} + let curReq = obs.openCursor(); + curReq.onsuccess = (evt) => { + var cursor = evt.target.result; + if (cursor) { + tmpResult[cursor.primaryKey] = cursor.value; + cursor.continue(); + } else + resolve(tmpResult); + } + curReq.onerror = (evt) => reject( + `Read-All data unsuccessful [${evt.target.error.name}] ${evt.target.error.message}`); + db.close(); + }).catch(error => reject(error)); + }); + }, + + getAllData: function (obsName, dbName = this.dbName) { + return new Promise((resolve, reject) => { + this.openDB(dbName).then(db => { + var obs = db.transaction(obsName, "readonly").objectStore(obsName); + let getReq = obs.getAll(); + getReq.onsuccess = (evt) => resolve(evt.target.result); + getReq.onerror = (evt) => reject( + `Read data unsuccessful [${evt.target.error.name}] ${evt.target.error.message}`); + db.close(); + }).catch(error => reject(error)); + }); + }, + + searchData: function (obsName, patternEval, dbName = this.dbName) { + return new Promise((resolve, reject) => { + this.openDB(dbName).then(db => { + var obs = db.transaction(obsName, "readonly").objectStore(obsName); + var filteredResult = {} + let curReq = obs.openCursor(); + curReq.onsuccess = (evt) => { + var cursor = evt.target.result; + if (cursor) { + if (patternEval(cursor.primaryKey, cursor.value)) + filteredResult[cursor.primaryKey] = cursor.value; + cursor.continue(); + } else + resolve(filteredResult); + } + curReq.onerror = (evt) => reject( + `Search unsuccessful [${evt.target.error.name}] ${evt.target.error.message}`); + db.close(); + }).catch(error => reject(error)); + }); + } + } + + floWebWallet = { + + //generate a new Address triplet : resolves Object(floID,pubKey,privKey) + generateNewAddr: function(){ + return new Promise((resolve,reject) => { + try{ + var triplet = floCrypto.generateNewID(); + resolve(triplet); + }catch(error){ + reject(error); + } + }) + }, + + //recover triplet from given privKey : resolves Object(floID,pubKey,privKey) + recoverAddr: function(privKey){ + return new Promise((resolve,reject) => { + try{ + var triplet = {} + triplet.privKey = privKey; + triplet.pubKey = floCrypto.getPubKeyHex(triplet.privKey); + triplet.floID = floCrypto.getFloIDfromPubkeyHex(triplet.pubKey); + resolve(triplet); + }catch(error){ + reject(error); + } + }) + }, + + //send transaction to the blockchain using API : resolves (txid) + sendTransaction: function(sender,receiver,amount,floData,privKey){ + return new Promise((resolve,reject) => { + floBlockchainAPI.sendTx(sender,receiver, amount, privKey, floData) + .then(txid => resolve(txid)) + .catch(error => reject(error)) + }) + }, + + //sync new transactions from blockchain using API and stores in IDB : resolves Array(newItems) + syncTransactions: function(addr){ + return new Promise((resolve,reject) => { + compactIDB.readData('lastSync',addr).then(lastSync => { + lastSync = lastSync|0; + floBlockchainAPI.getNewTxs(addr,lastSync).then(APIresult => { + compactIDB.readData('transactions',addr).then(IDBresult => { + if (IDBresult === undefined) + var promise1 = compactIDB.addData('transactions',APIresult.items,addr) + else + var promise1 = compactIDB.writeData('transactions',IDBresult.concat(APIresult.items),addr) + var promise2 = compactIDB.writeData('lastSync',APIresult.totalItems,addr) + Promise.all([promise1,promise2]).then(values => resolve(APIresult.items)) + }) + }) + }).catch(error => reject(error)) + }) + }, + + //read transactions stored in IDB : resolves Array(storedItems) + readTransactions: function(addr){ + return new Promise((resolve,reject) => { + compactIDB.readData('transactions',addr) + .then(IDBresult => resolve(IDBresult)) + .catch(error => reject(error)) + }) + }, + + //get address-label pairs from IDB : resolves Object(addr:label) + getLabels: function(){ + return new Promise((resolve,reject) => { + compactIDB.readAllData('labels') + .then(IDBresult => resolve(IDBresult)) + .catch(error => reject(error)) + }) + } + } + + function onLoadStartUp(){ + var IDBObjects = { + lastSync:{}, + labels:{}, + transactions:{} + } + compactIDB.initDB("FLOwebWallet",IDBObjects) + .then(result => console.log(result)) + .catch(error => console.log(error)) + + createAllCards(); + setElements(); + } + + diff --git a/FLO_webWallet_minified.html b/FLO_webWallet_minified.html new file mode 100644 index 0000000..6a7467e --- /dev/null +++ b/FLO_webWallet_minified.html @@ -0,0 +1,18 @@ + + + +FLO web wallet + + + + + + +
There seems to be a problem connecting to the internet.
Monitor
Monitor FLO data
Send
Send FLO data
Generate
Generate address
Settings
Settings
Settings
Copied

Add new address to monitoring list

Edit display card

Copy FLO address

Add new address to monitor
Go back to monitoring page

Refresh transactions
Available balance
0
To send FLO data, make sure you have enough balance.
Go back to monitoring page

Send

Transaction successful



Dark mode

Automatic
Dark mode active : 6pm - 6am
Manual
Dark mode

Clear all local data

This will delete all local Web Wallet data like added addresses and locally stored transactions.After clearing local data you may experience slow loading of newly added address, please proceed cautiously!

About

Version 2.7.2
Powered by
+ + + diff --git a/README.md b/README.md index 3d1dac9..d2cd0bb 100644 --- a/README.md +++ b/README.md @@ -7,44 +7,67 @@ NOTE : Use respective file for mainnet and testnet ## Instructions to use -Note : open the respective html file in browser +Note : Web wallet uses IndedxedDB for storing data, which means data is stored in respective browser you used to open web wallet.Data stored by one browser can't be accessed by other browser. -Choose the function you require. (Click button) - -There are 3 functions : +There are 4 pages in this web app : ---------------------- -### Address Generator -This function can generate new FLO address and secret key pair (or) recover FLO address from given secret key. -1. Click on the 'Generate New Address' button to generate FLO address and secret key pair. +### Monitor FLO Data -(or) -1. Click on the 'Recover FLO Address' button -2. Enter the secret key in the prompt to recover the FLO address. +This apge allows user to view the transactions done by the given address/es. +1. Click/Tap on the '+' floating button at bottom-right hand side, which opens a popup window. + +2. Enter the FLO address that you want to monitor, You can also specify a label to that address which will be displayed as name for that address.If you left label field empty, the default label will be 'Unknown'. + +3. Click/Tap on 'Ok' and the address you added will be displayed on monitoring page as a card.This address is also added to your local database.Once you add an address/label pair, It will stay there untill you clear data(Option available in Settings page). + +4. When you hover mouse pointer on a monitoring card, three dots will appear. clicking on this allows user to edit or remove that address from monitoring list.(On mobile devices this option is always visible) + +5. To see transactions done by any address, click/Tap on respective address card.It will open an follow up window, which displays transactions in messege format. consisting of data sent/received to/from, date of transaction and FLO data. Arrows on messege body indicate direction of transaction i.e. Data is sent or received.On the top of window we have option to go back to monitoring page or refresh the transaction history. ### Send FLO Data -This function can send FLO data and transactions to the blockchain. -1. Enter the sender's address (for multiple address use comma `,`) -2. Click get balance. -3. Balance of the respective address(es) will be displayed -4. Select the address from which you want to send -5. Enter the Receiver's address -6. Enter amount -7. Enter FLO data (if required) -8. Click Send -9. Enter the private key for the address in the prompt -10. The transaction will be sent to the blockchain and returns the txid (returns error if any). + +This page allows user to send FLO data and transactions to the blockchain.To send any data you have to check your balance first, to make sure you have enough balance as each transaction requires 0.0005 FLOs to be valid. + +1. Enter the sender's address +2. Click check balance. +3. Balance of the respective address will be displayed +4. Enter the Receiver's address +5. Enter amount +6. Enter FLO data (if required) +7. Click Send +8. Enter the private key for the address in the prompt +9. The transaction will be sent to the blockchain and returns the txid (returns error if any). Note : Transcation fee is set to 0.0005 -### Monitor FLO Data -This function views the FLO data sent and received by the given address(es). -1. Enter the address to monitor (for multiple address use comma `,`) -2. Click Monitor Data -3. FloData sent and received by the address(es) will be displayed -4. Each address data will be displayed in seperate table. -5. Add/Edit Label using Edit button of Label input field. -6. Refresh and Remove data from view using refresh and remove buttons respectively. +### FLO address generation/recovery page +This page can be used to generate or recover FLO address and private key pairs.This page has two buttons +#### generate new address +As the name implies this button when clicked/tapped generates new FLO address and private key pair.Also there is an one click/tap option that allows user to copy FLO address or private key. + +#### recover FLO address +This button when click/tapped opens a popup, which prompts user to enter private key associated with the FLO address that has to be recovered. Once correct private key is entered corresponding FLO address is displayed again. + +Note : Do not share your private key with anyone and keep it safe. Once lost a private key can't be recovered. + +### Settings page + +This page contains settings and information about webapp. + +#### Dark mode setting + +This section allows user to set color scheme (light/dark) with two options, either automatic or manual. + 1. Automatic - If this toggle is turned on, Dark mode is enabled automatically from 6pm to 6am(Custom timing will be added in future versions). + + 2. Manual - Turning this toggle on/off changes color scheme and stayes in that mode unless changed by user again. + +#### CLear all local data + +This option clears all the data stored in IndexedDB databases from browser. This is same as clearing browser data and cache.After deleting data, it may take more time to load transactions as they are now fetched again from blockchain. Also you have to add addresses to start monitoring again. + +#### About +This section contains information about version and underlying technologies. + Note : For reducing API calls and processing time , monitored data are stored in local browser. While monitoring the same address, the data from browser database (IndexedDB) is used and only the new transactions are obtained using API. -The local data can be cleared by clicking the 'Clear Local Data' button.