UNPKG

62 kBJavaScriptView Raw
1const _ = require('lodash')
2const CoinKey = require('@scrypta/coinkey')
3const crypto = require('crypto')
4const CryptoJS = require('crypto-js')
5const secp256k1 = require('secp256k1')
6const cs = require('coinstring')
7const axios = require('axios')
8const Trx = require('./trx/trx')
9const ScryptaDB = require('./db')
10const NodeRSA = require('node-rsa');
11const { sum, round, subtract } = require('mathjs')
12const { captureRejectionSymbol } = require('events')
13const { isUndefined } = require('lodash')
14
15const lyraInfo = {
16 mainnet: {
17 private: 0xae,
18 public: 0x30,
19 scripthash: 0x0d
20 },
21 testnet: {
22 private: 0xae,
23 public: 0x7f,
24 scripthash: 0x13
25 }
26}
27
28global['io'] = { server: null, client: null, sockets: {} }
29global['nodes'] = {}
30global['connected'] = {}
31global['cache'] = []
32
33module.exports = class ScryptaCore {
34 constructor(isBrowser = false) {
35 this.RAWsAPIKey = ''
36 this.PubAddress = ''
37 this.mainnetIdaNodes = ['https://idanodejs01.scryptachain.org', 'https://idanodejs02.scryptachain.org', 'https://idanodejs03.scryptachain.org', 'https://idanodejs04.scryptachain.org', 'https://idanodejs05.scryptachain.org', 'https://idanodejs06.scryptachain.org']
38 this.testnetIdaNodes = ['https://testnet.scryptachain.org']
39 this.staticnodes = false
40 this.debug = false
41 this.MAX_OPRETURN = 7500
42 this.testnet = false
43 this.portP2P = 42226
44 this.sidechain = ''
45 this.idanode = ''
46 this.isBrowser = isBrowser
47 this.math = {}
48 this.math.sum = sum
49 this.math.round = round
50 this.math.subtract = subtract
51
52 if (isBrowser) {
53 this.importBrowserSID()
54 }
55 this.clearCache()
56 }
57
58 //IDANODE FUNCTIONS
59 returnNodes() {
60 const app = this
61 return new Promise(async response => {
62 if (this.staticnodes === false) {
63 if (this.testnet === true) {
64 response(app.testnetIdaNodes)
65 } else {
66 const db = new ScryptaDB(app.isBrowser)
67 let idanodes = await db.get('nodes')
68 try {
69 let nodes_git = await axios.get('https://raw.githubusercontent.com/scryptachain/scrypta-idanode-network/master/peers')
70 let raw_nodes = nodes_git.data.split("\n")
71 let nodes = []
72 for (let x in raw_nodes) {
73 let node = raw_nodes[x].split(':')
74 let url = 'https://idanodejs' + node[0] + '.scryptachain.org'
75 await db.put('nodes', url)
76 nodes.push(url)
77 }
78 response(nodes)
79 } catch (e) {
80 if (idanodes.length > 0) {
81 response(idanodes)
82 } else {
83 // FALLBACK TO STATIC NODES IF GIT FAILS AND DB IS EMPTY
84 response(app.mainnetIdaNodes)
85 }
86 }
87 }
88 } else {
89 if(this.testnet === false){
90 response(app.mainnetIdaNodes)
91 }else{
92 response(app.testnetIdaNodes)
93 }
94 }
95 })
96 }
97
98 post(endpoint, params, node = '') {
99 const app = this
100 return new Promise(async response => {
101 if (node === '') {
102 node = await app.connectNode()
103 }
104 let res
105 try{
106 res = await axios.post(node + endpoint, params, { timeout: 20000 }).catch(err => {
107 console.log("ERROR ON IDANODE " + node + ": " + err.message)
108 response(false)
109 })
110 }catch(e){
111 node = await app.connectNode()
112 res = await axios.post(node + endpoint, params, { timeout: 20000 }).catch(err => {
113 console.log("ERROR ON IDANODE " + node + ": " + err.message)
114 response(false)
115 })
116 }
117
118 if(res !== undefined && res.data !== undefined){
119 response(res.data)
120 }else{
121 console.log("ERROR ON IDANODE " + node + ": " + err.message)
122 response(false)
123 }
124 })
125 }
126
127 get(endpoint, node = '') {
128 const app = this
129 return new Promise(async response => {
130 if (node === '') {
131 node = await app.connectNode()
132 }
133 let res
134 try{
135 res = await axios.get(node + endpoint, { timeout: 20000 }).catch(err => {
136 console.log("ERROR ON IDANODE " + node + ": " + err.message)
137 response(false)
138 })
139 }catch(e){
140 node = await app.connectNode()
141 res = await axios.get(node + endpoint, { timeout: 20000 }).catch(err => {
142 console.log("ERROR ON IDANODE " + node + ": " + err.message)
143 response(false)
144 })
145 }
146 if (res !== undefined && res.data !== undefined) {
147 response(res.data)
148 }else{
149 response(false)
150 }
151 })
152 }
153
154 testnet(value = true) {
155 this.testnet = value
156 }
157
158 async checkNode(node) {
159 return new Promise(response => {
160 axios.get(node + '/wallet/getinfo', { timeout: 20000 }).catch(err => {
161 response(false)
162 }).then(result => {
163 response(result)
164 })
165 })
166 }
167
168 async connectNode() {
169 const app = this
170 return new Promise(async response => {
171 if (app.idanode === '') {
172 let connected = false
173 if(app.debug === true){
174 console.log('CONNECTING TO FIRST AVAILABLE IDANODE')
175 }
176 while (connected === false) {
177 let node = await this.returnFirstNode()
178 if (node !== false) {
179 connected = true
180 app.idanode = node
181 if(app.debug === true){
182 console.log('CONNECTED TO ' + app.idanode)
183 }
184 response(node)
185 }
186 }
187 } else {
188 let check = await app.checkNode(app.idanode)
189 if (check !== false && check.data.toindex <= 1) {
190 if(app.debug === true){
191 console.log('CONNECTED IDANODE ' + app.idanode + ' STILL VALID')
192 }
193 response(app.idanode)
194 } else {
195 app.idanode = ''
196 let connected = false
197 if(app.debug === true){
198 console.log('CONNECTED IDANODE ' + app.idanode + ' NOT VALID ANYMORE, CONNECTING TO NEW IDANODE')
199 }
200 while (connected === false) {
201 let node = await this.returnFirstNode()
202 if (node !== false) {
203 connected = true
204 app.idanode = node
205 response(node)
206 }
207 }
208 }
209 }
210 })
211 }
212
213 async returnLastChecksum(version) {
214 const app = this
215 return new Promise(async response => {
216 const db = new ScryptaDB(app.isBrowser)
217 let last = await db.get('checksums', 'version', version)
218 if (last === false) {
219 try {
220 let checksums_git = await axios.get('https://raw.githubusercontent.com/scryptachain/scrypta-idanodejs/master/checksum')
221 let checksums = checksums_git.data.split("\n")
222 for (let x in checksums) {
223 let checksum = checksums[x].split(':')
224 if (checksum[0] === version) {
225 await db.put('checksums', {
226 version: version,
227 checksum: checksum[1]
228 })
229 response(checksum[1])
230 }
231 }
232 } catch (e) {
233 console.log(e)
234 response(false)
235 }
236 } else {
237 response(last.checksum)
238 }
239 })
240 }
241
242 async returnFirstNode() {
243 const app = this
244 return new Promise(async response => {
245 let nodes = await this.returnNodes()
246 var checknodes = this.shuffle(nodes)
247 let connected = false
248 for (var i = 0; i < checknodes.length; i++) {
249 try {
250 axios.get(checknodes[i] + '/wallet/getinfo', { timeout: 20000 }).then(async check => {
251 let checksum = await app.returnLastChecksum(check.data.version)
252 let isValid = true
253 if (checksum !== false) {
254 if (check.data.checksum !== checksum) {
255 isValid = false
256 }
257 }
258 if (check.data.blocks !== undefined && connected === false && check.data.toindex === 0 && isValid) {
259 connected = true
260 if (check.config.url !== undefined) {
261 response(check.config.url.replace('/wallet/getinfo', ''))
262 }
263 }
264 }).catch(err => {
265 response(false)
266 })
267 } catch (err) {
268 // console.log(err)
269 }
270 }
271 setTimeout(function () {
272 if (connected === false) {
273 response(false)
274 }
275 }, 1500)
276 })
277 }
278
279 //CACHE FUNCTIONS
280 async clearCache(force = false) {
281 const app = this
282 return new Promise(async response => {
283 const db = new ScryptaDB(app.isBrowser)
284 if (force) {
285 await db.destroy('sxidcache')
286 await db.destroy('txidcache')
287 }
288 await db.destroy('usxocache')
289 await db.destroy('utxocache')
290 response(true)
291 })
292 }
293
294 async returnTXIDCache() {
295 const app = this
296 return new Promise(async response => {
297 const db = new ScryptaDB(app.isBrowser)
298 let cache = await db.get('txidcache')
299 response(cache)
300 })
301 }
302
303 async pushTXIDtoCache(txid) {
304 const app = this
305 return new Promise(async response => {
306 const db = new ScryptaDB(app.isBrowser)
307 await db.put('txidcache', txid)
308 response(true)
309 })
310 }
311
312 async returnUTXOCache() {
313 const app = this
314 return new Promise(async response => {
315 const db = new ScryptaDB(app.isBrowser)
316 let cache = await db.get('utxocache')
317 response(cache)
318 })
319 }
320
321 async pushUTXOtoCache(utxo) {
322 const app = this
323 return new Promise(async response => {
324 const db = new ScryptaDB(app.isBrowser)
325 await db.put('utxocache', utxo)
326 response(true)
327 })
328 }
329
330 async returnSXIDCache() {
331 const app = this
332 return new Promise(async response => {
333 const db = new ScryptaDB(app.isBrowser)
334 let cache = await db.get('sxidcache')
335 response(cache)
336 })
337 }
338
339 async pushSXIDtoCache(sxid) {
340 const app = this
341 return new Promise(async response => {
342 const db = new ScryptaDB(app.isBrowser)
343 await db.put('sxidcache', sxid)
344 response(true)
345 })
346 }
347
348 async returnUSXOCache() {
349 const app = this
350 return new Promise(async response => {
351 const db = new ScryptaDB(app.isBrowser)
352 let cache = await db.get('usxocache')
353 response(cache)
354 })
355 }
356
357 async pushUSXOtoCache(usxo) {
358 const app = this
359 return new Promise(async response => {
360 const db = new ScryptaDB(app.isBrowser)
361 await db.put('usxocache', usxo)
362 response(true)
363 })
364 }
365
366 // UTILITIES FUNCTION
367 sleep(ms) {
368 return new Promise(resolve => setTimeout(resolve, ms));
369 }
370
371 //CRYPT AND ENCRYPT FUNCTIONS
372 async cryptData(data, password) {
373 return new Promise(response => {
374 let iv = crypto.randomBytes(16)
375 let key = crypto.createHash('sha256').update(String(password)).digest('base64').substr(0, 32)
376 let cipher = crypto.createCipheriv('aes-256-ctr', key, iv);
377 let encrypted = cipher.update(data);
378 encrypted = Buffer.concat([encrypted, cipher.final()]);
379 let hex = iv.toString('hex') + '*' + encrypted.toString('hex')
380 response(hex)
381 })
382 }
383
384 async decryptData(data, password, buffer = false) {
385 return new Promise(response => {
386 try {
387 if(data.indexOf('*') === -1){
388 // MAINTAIN FALLBACK TO OLD ENCRYPTED WALLETS
389 var decipher = crypto.createDecipher('aes-256-cbc', password)
390 var dec = decipher.update(data, 'hex', 'utf8')
391 dec += decipher.final('utf8')
392 response(dec)
393 }else{
394 let textParts = data.split('*');
395 let iv = Buffer.from(textParts.shift(), 'hex')
396 let encryptedText = Buffer.from(textParts.join('*'), 'hex')
397 let key = crypto.createHash('sha256').update(String(password)).digest('base64').substr(0, 32)
398 let decipher = crypto.createDecipheriv('aes-256-ctr', key, iv)
399 let decrypted = decipher.update(encryptedText)
400 decrypted = Buffer.concat([decrypted, decipher.final()])
401 if(buffer === false){
402 response(decrypted.toString())
403 }else{
404 response(decrypted)
405 }
406 }
407 } catch (e) {
408 response(false)
409 }
410 })
411 }
412
413 // DEPRECATED
414 async cryptFile(file, password) {
415 return new Promise(response => {
416
417 const reader = new FileReader();
418 reader.onload = function () {
419 var buf = Buffer(reader.result)
420 var cipher = crypto.createCipher('aes-256-cbc', password)
421 var crypted = Buffer.concat([cipher.update(buf), cipher.final()])
422 response(crypted.toString('hex'))
423 };
424
425 reader.readAsArrayBuffer(file);
426 })
427 }
428
429 // DEPRECATED
430 async decryptFile(file, password) {
431 return new Promise(response => {
432 try {
433 let buf = Buffer(file, 'hex')
434 var decipher = crypto.createDecipher('aes-256-cbc', password)
435 var decrypted = Buffer.concat([decipher.update(buf), decipher.final()])
436 response(decrypted)
437 } catch (e) {
438 response(false)
439 }
440 })
441 }
442
443 //ADDRESS MANAGEMENT
444 async createAddress(password, saveKey = true) {
445 // LYRA WALLET
446 let params = lyraInfo.mainnet
447 if (this.testnet === true) {
448 params = lyraInfo.testnet
449 }
450 var ck = new CoinKey.createRandom(params)
451
452 var lyrapub = ck.publicAddress;
453 var lyraprv = ck.privateWif;
454 var lyrakey = ck.publicKey.toString('hex');
455
456 var wallet = {
457 prv: lyraprv,
458 key: lyrakey
459 }
460
461 var walletstore = await this.buildWallet(password, lyrapub, wallet, saveKey)
462
463 var response = {
464 pub: lyrapub,
465 key: lyrakey,
466 prv: lyraprv,
467 walletstore: walletstore
468 }
469 return response;
470 }
471
472 async buildWallet(password, pub, wallet, saveKey) {
473 const app = this
474 const db = new ScryptaDB(app.isBrowser)
475 return new Promise(async response => {
476
477 let wallethex = await this.cryptData(JSON.stringify(wallet), password)
478 var walletstore = pub + ':' + wallethex;
479
480 if (saveKey === true) {
481 let check = await db.get('wallet', 'address', pub)
482 if (!check) {
483 await db.put('wallet', {
484 address: pub,
485 wallet: walletstore
486 })
487 } else {
488 await db.update('wallet', 'address', pub, {
489 address: pub,
490 wallet: walletstore
491 })
492 }
493 }
494
495 response(walletstore)
496 })
497 }
498
499 async initAddress(address) {
500 const app = this
501 const node = await app.connectNode();
502 const response = await axios.post(node + '/init', { address: address, airdrop: true })
503 return response;
504 }
505
506 async getPublicKey(privateWif) {
507 var ck = new CoinKey.fromWif(privateWif);
508 var pubkey = ck.publicKey.toString('hex');
509 return pubkey;
510 }
511
512 async getAddressFromPubKey(pubKey) {
513 return new Promise(response => {
514 let params = lyraInfo.mainnet
515 if (this.testnet === true) {
516 params = lyraInfo.testnet
517 }
518 let pubkeybuffer = Buffer.from(pubKey, 'hex')
519 var sha = crypto.createHash('sha256').update(pubkeybuffer).digest()
520 let pubKeyHash = crypto.createHash('rmd160').update(sha).digest()
521 var hash160Buf = Buffer.from(pubKeyHash, 'hex')
522 response(cs.encode(hash160Buf, params.public))
523 })
524 }
525
526 async importBrowserSID() {
527 const app = this
528 const db = new ScryptaDB(app.isBrowser)
529 if (app.isBrowser) {
530 let SID = localStorage.getItem('SID')
531 if (SID !== null) {
532 let SIDS = SID.split(':')
533 let check = await db.get('wallet', 'address', SIDS[0])
534 if (!check) {
535 await db.put('wallet', {
536 address: SIDS[0],
537 wallet: SIDS[0] + ':' + SIDS[1]
538 })
539 }
540 }
541 }
542 }
543
544
545 importPrivateKey(key, password) {
546 return new Promise(async response => {
547 let lyrakey = await this.getPublicKey(key)
548 let lyrapub = await this.getAddressFromPubKey(lyrakey)
549
550 var wallet = {
551 prv: key,
552 key: lyrakey
553 }
554 var walletstore = await this.buildWallet(password, lyrapub, wallet, true)
555
556 response({
557 pub: lyrapub,
558 key: lyrakey,
559 prv: key,
560 walletstore: walletstore
561 })
562 })
563 }
564
565 returnKey(address) {
566 const app = this
567 return new Promise(async response => {
568 if (address.length === 34) {
569 const db = new ScryptaDB(app.isBrowser)
570 let doc = await db.get('wallet', 'address', address)
571 if (doc !== undefined) {
572 response(doc.wallet)
573 } else {
574 response(false)
575 }
576 } else {
577 response(address)
578 }
579 })
580 }
581
582 async readKey(password, key) {
583 let wallet = await this.returnKey(key)
584 if (wallet !== false) {
585 if (password !== '') {
586 var SIDS = key.split(':');
587 try {
588 let decrypted = await this.decryptData(SIDS[1], password)
589 return Promise.resolve(JSON.parse(decrypted));
590 } catch (ex) {
591 // console.log('WRONG PASSWORD')
592 return Promise.resolve(false);
593 }
594 }
595 } else {
596 return false
597 }
598 }
599
600 //TRANSACTIONS FUNCTIONS
601 async listUnspent(address) {
602 const app = this
603 const node = await app.connectNode();
604 var unspent = await app.get('/unspent/' + address)
605 return unspent.unspent
606 }
607
608 async sendRawTransaction(rawtransaction) {
609 const app = this
610 var txid = await app.post('/sendrawtransaction',
611 { rawtransaction: rawtransaction }
612 ).catch(function (err) {
613 console.log(err)
614 })
615 return txid.data
616 }
617
618 async decodeRawTransaction(rawtransaction) {
619 const app = this
620 const node = await app.connectNode();
621 if (node !== undefined) {
622 var transaction = await axios.post(
623 node + '/decoderawtransaction',
624 { rawtransaction: rawtransaction }
625 ).catch(function (err) {
626 console.log(err)
627 })
628 return transaction.data.transaction
629 } else {
630 return Promise.resolve(false)
631 }
632 }
633
634 async build(key, password, send = false, to, amount, metadata = '', fees = 0.001) {
635 var SID = key;
636 var MAX_OPRETURN = this.MAX_OPRETURN
637 if (password !== '') {
638 var SIDS = SID.split(':');
639 try {
640 let decrypted = await this.decryptData(SIDS[1], password)
641 decrypted = JSON.parse(decrypted)
642 var trx = Trx.transaction();
643 var from = SIDS[0]
644 var unspent = []
645 var inputs = []
646 var cache = await this.returnUTXOCache()
647 if (cache !== undefined && cache.length > 0) {
648 for (var x = 0; x < cache.length; x++) {
649 if(this.debug){
650 console.log(cache[x])
651 }
652 if(cache[x].address === SIDS[0]){
653 unspent.push(cache[x])
654 }
655 }
656 }
657 var listunspent = await this.listUnspent(from)
658 for (var x = 0; x < listunspent.length; x++) {
659 unspent.push(listunspent[x])
660 }
661 if(this.debug){
662 console.log('UNSPENT', unspent, unspent.length)
663 }
664 if (unspent.length > 0) {
665 var inputamount = 0;
666 var amountneed = amount + fees;
667 for (var i = 0; i < unspent.length; i++) {
668 if (inputamount <= amountneed) {
669 var txid = unspent[i]['txid'];
670 var index = unspent[i]['vout'];
671 var script = unspent[i]['scriptPubKey'];
672 var cache = await this.returnTXIDCache()
673 if (cache.indexOf(txid + ':' + index) === -1 && inputs.indexOf(txid + ':' + index) === -1) {
674 trx.addinput(txid, index, script);
675 inputamount += unspent[i]['amount']
676 inputs.push(txid + ':' + index)
677 }else if(this.debug){
678 console.log('INPUT ALREADY IN CACHE ' + txid + ':' + index)
679 }
680 }
681 }
682 if (inputamount >= amountneed) {
683 var change = inputamount - amountneed;
684 if (amount > 0.00001) {
685 trx.addoutput(to, amount);
686 }
687 if (change > 0.00001) {
688 trx.addoutput(from, change);
689 }
690 if (metadata !== '') {
691 if (metadata.length <= MAX_OPRETURN) {
692 //console.log('ADDING METADATA TO TX', metadata)
693 trx.addmetadata(metadata);
694 } else {
695 //console.log('METADATA TOO LONG')
696 }
697 }
698 var wif = decrypted.prv;
699 var signed = trx.sign(wif, 1);
700 if (send === false) {
701 return Promise.resolve({
702 inputs: inputs,
703 signed: signed
704 });
705 } else {
706 var txid = await this.sendRawTransaction(signed)
707 if(this.debug){
708 console.log(txid)
709 }
710 if (txid !== undefined && txid !== null && txid.length === 64) {
711 for (let i in inputs) {
712 await this.pushTXIDtoCache(inputs[i])
713 }
714 // console.log("TX SENT: " + txid)
715 return Promise.resolve(txid)
716 }else{
717 return Promise.resolve(false) //NOT ENOUGH FUNDS
718 }
719 }
720 } else {
721 if(this.debug){ console.log('NOT ENOUGH FUNDS') }
722 return Promise.resolve(false) //NOT ENOUGH FUNDS
723 }
724 } else {
725 // console.log('NO UNSPENTS')
726 return Promise.resolve(false) //NOT ENOUGH FUNDS
727 }
728 } catch (error) {
729 console.log(error)
730 return Promise.resolve(false);
731 }
732 }
733 }
734
735 async send(key, password, to, amount, metadata = '') {
736 let wallet = await this.returnKey(key)
737 if (wallet !== false) {
738 if (password !== '' && to !== '') {
739 var SIDS = wallet.split(':');
740 try {
741 let decrypted = await this.decryptData(SIDS[1], password)
742 if(decrypted !== false){
743 var txid = ''
744 var i = 0
745 var rawtransaction
746 while (txid !== null && txid !== undefined && txid.length !== 64) {
747 var fees = 0.001 + (i / 1000)
748 rawtransaction = await this.build(wallet, password, false, to, amount, metadata, fees)
749 if(rawtransaction === false){
750 Promise.resolve(false)
751 }
752 txid = await this.sendRawTransaction(rawtransaction.signed)
753 if(this.debug){
754 console.log(rawtransaction, txid)
755 }
756 if (txid !== null && txid !== false && txid.length === 64) {
757 for (let i in rawtransaction.inputs) {
758 await this.pushTXIDtoCache(rawtransaction.inputs[i])
759 }
760 //Storing UTXO to cache
761 var decoded = await this.decodeRawTransaction(rawtransaction.signed)
762 if (decoded.vout[1].scriptPubKey.addresses !== undefined) {
763 let unspent = {
764 txid: decoded.txid,
765 vout: 1,
766 address: decoded.vout[1].scriptPubKey.addresses[0],
767 scriptPubKey: decoded.vout[1].scriptPubKey.hex,
768 amount: decoded.vout[1].value
769 }
770 await this.pushUTXOtoCache(unspent)
771 }
772 } else {
773 txid = null
774 }
775 i++;
776 }
777 return Promise.resolve(txid)
778 }else{
779 return Promise.resolve(false)
780 }
781 } catch (e) {
782 return Promise.resolve(false)
783 }
784 } else {
785 return false
786 }
787 } else {
788 return false
789 }
790 }
791
792 // PLANUM FUNCTIONS
793 usePlanum(sidechain) {
794 const app = this
795 app.sidechain = sidechain
796 }
797
798 async listPlanumUnspent(address) {
799 return new Promise(async response => {
800 const app = this
801 let unspent = []
802
803 // PUSHING LOCAL CACHE
804 var cache = await this.returnUSXOCache()
805 if (cache !== undefined && cache.length > 0) {
806 for (var x = 0; x < cache.length; x++) {
807 unspent.push(cache[x])
808 }
809 }
810
811 if (app.sidechain !== '') {
812 let unspentnode = await app.post('/sidechain/listunspent', { sidechain_address: app.sidechain, dapp_address: address })
813 if (unspentnode.unspent !== undefined) {
814 for (let x in unspentnode.unspent) {
815 unspent.push(unspentnode.unspent[x])
816 }
817 response(unspent)
818 } else {
819 response(false)
820 }
821 } else {
822 response(false)
823 }
824 })
825 }
826
827 async sendPlanumAsset(key, password, to, amount, changeaddress = '', memo = '') {
828 const app = this
829 let wallet = await this.returnKey(key)
830 if (wallet !== false) {
831 if (password !== '' && to !== '') {
832 var SIDS = wallet.split(':');
833 let decrypted
834 try {
835 decrypted = await this.decryptData(SIDS[1], password)
836 decrypted = JSON.parse(decrypted)
837 } catch (e) {
838 return false
839 }
840 if(decrypted.prv !== undefined){
841 const address = SIDS[0]
842 var sxid = ''
843 let unspent = await app.listPlanumUnspent(address)
844 let check_sidechain = await app.post('/sidechain/get', { sidechain_address: app.sidechain })
845 let sidechainObj = check_sidechain.sidechain[0]
846 const decimals = sidechainObj.data.genesis.decimals
847 if (unspent.length > 0) {
848 let inputs = []
849 let outputs = {}
850 let amountinput = 0
851 amount = app.math.round(amount, decimals)
852 let usedtx = []
853 let checkto = await app.get('/validate/' + to)
854 if (checkto.data.isvalid === false) {
855 return Promise.resolve(false)
856 }
857
858 for (let i in unspent) {
859 if (amountinput < amount) {
860 delete unspent[i]._id
861 delete unspent[i].sidechain
862 delete unspent[i].address
863 delete unspent[i].block
864 delete unspent[i].redeemblock
865 delete unspent[i].redeemed
866 let cache = await this.returnSXIDCache()
867 if (cache.indexOf(unspent[i].sxid + ':' + unspent[i].vout) === -1) {
868 inputs.push(unspent[i])
869 usedtx.push(unspent[i].sxid + ':' + unspent[i].vout)
870 let toadd = app.math.round(unspent[i].amount, decimals)
871 amountinput = app.math.sum(amountinput, toadd)
872 amountinput = app.math.round(amountinput, decimals)
873 }
874 }
875 }
876
877 let totaloutputs = 0
878 amountinput = app.math.round(amountinput, decimals)
879 amount = app.math.round(amount, decimals)
880 if (amountinput >= amount) {
881
882 if (to === sidechainObj.address && sidechainObj.data.burnable === false) {
883
884 return Promise.resolve(false)
885
886 } else {
887
888 outputs[to] = amount
889 totaloutputs = app.math.sum(totaloutputs, amount)
890
891 let change = app.math.subtract(amountinput, amount)
892 change = app.math.round(change, decimals)
893
894 if (to !== address) {
895 if (change > 0 && changeaddress === '') {
896 outputs[address] = change
897 totaloutputs = app.math.sum(totaloutputs, change)
898 } else if (change > 0 && changeaddress !== '') {
899 // CHECK IF CHANGE ADDRESS IS VALID
900 let checkchange = await app.get('validate/' + change)
901 if (checkchange.data.isvalid === true) {
902 outputs[changeaddress] = change
903 totaloutputs = app.math.sum(totaloutputs, change)
904 } else {
905 // IF NOT, SEND TO MAIN ADDRESS
906 outputs[address] = change
907 totaloutputs = app.math.sum(totaloutputs, change)
908 }
909 }
910 } else {
911 if (change > 0) {
912 outputs[address] = app.math.sum(change, amount)
913 outputs[address] = app.math.round(outputs[address], sidechainObj.data.genesis.decimals)
914 totaloutputs = app.math.sum(totaloutputs, change)
915 }
916 }
917
918 totaloutputs = app.math.round(totaloutputs, sidechainObj.data.genesis.decimals)
919
920 if (inputs.length > 0 && totaloutputs > 0) {
921 let transaction = {}
922 transaction["sidechain"] = app.sidechain
923 transaction["inputs"] = inputs
924 transaction["outputs"] = outputs
925 transaction["memo"] = memo
926 transaction["time"] = new Date().getTime()
927
928 let signtx = await app.signMessage(decrypted.prv, JSON.stringify(transaction))
929
930 let tx = {
931 transaction: transaction,
932 signature: signtx.signature,
933 pubkey: decrypted.key,
934 sxid: signtx.hash
935 }
936
937 let validatetransaction = await app.post('/sidechain/validate',
938 {
939 transaction: tx,
940 address: address,
941 sxid: signtx.hash,
942 signature: signtx.signature,
943 pubkey: decrypted.key
944 }
945 )
946
947 if (validatetransaction.errors === undefined && validatetransaction.valid === true && signtx.hash !== undefined) {
948 let sent = false
949 let txs = []
950 let retry = 0
951 while (sent === false) {
952 let written = await app.write(key, password, JSON.stringify(tx), '', '', 'chain://')
953 if (written !== false && written.txs !== undefined && written.txs.length >= 1 && written.txs[0] !== null) {
954 for (let x in usedtx) {
955 await app.pushSXIDtoCache(usedtx[x])
956 }
957 let vout = 0
958 for (let x in outputs) {
959 let unspent = {
960 sxid: tx.sxid,
961 vout: vout,
962 address: x,
963 amount: outputs[x],
964 sidechain: tx.transaction['sidechain']
965 }
966 if (unspent.address === address) {
967 await app.pushUSXOtoCache(unspent)
968 }
969 vout++
970 }
971 sent = true
972 txs = written.txs
973 } else {
974 retry++
975 await app.sleep(2000)
976 }
977 if (retry > 10) {
978 sent = true
979 }
980 }
981 if (txs.length >= 1) {
982 return Promise.resolve(tx.sxid)
983 } else {
984 return Promise.resolve(false)
985 }
986 } else {
987 return Promise.resolve(false)
988 }
989
990 } else {
991 return Promise.resolve(false)
992 }
993 }
994 } else {
995 return Promise.resolve(false)
996 }
997 } else {
998 // console.log('NO UNSPENT')
999 return false
1000 }
1001 }else{
1002 return false
1003 }
1004 } else {
1005 return false
1006 }
1007 } else {
1008 return false
1009 }
1010 }
1011
1012 //PROGRESSIVE DATA MANAGEMENT
1013 async write(key, password, metadata, collection = '', refID = '', protocol = '', uuid = '') {
1014 if (password !== '' && metadata !== '') {
1015 let wallet = await this.returnKey(key)
1016 if (wallet !== false) {
1017 var SIDS = wallet.split(':');
1018 var MAX_OPRETURN = this.MAX_OPRETURN
1019 try {
1020 //console.log('WRITING TO BLOCKCHAIN')
1021 let decrypted = await this.decryptData(SIDS[1], password)
1022 if(decrypted !== false){
1023 let address = SIDS[0]
1024
1025 if (uuid === '') {
1026 var Uuid = require('uuid/v4')
1027 uuid = Uuid().replace(new RegExp('-', 'g'), '.')
1028 }
1029
1030 if (collection !== '') {
1031 collection = '!*!' + collection
1032 } else {
1033 collection = '!*!'
1034 }
1035
1036 if (refID !== '') {
1037 refID = '!*!' + refID
1038 } else {
1039 refID = '!*!'
1040 }
1041
1042 if (protocol !== '') {
1043 protocol = '!*!' + protocol
1044 } else {
1045 protocol = '!*!'
1046 }
1047
1048 var dataToWrite = '*!*' + uuid + collection + refID + protocol + '*=>' + metadata + '*!*'
1049 if (dataToWrite.length <= MAX_OPRETURN) {
1050 var txid = ''
1051 var i = 0
1052 var totalfees = 0
1053 var retries = 0
1054 while (txid.length !== 64) {
1055 var fees = 0.001 + (i / 1000)
1056 var rawtransaction = await this.build(wallet, password, false, address, 0, dataToWrite, fees)
1057 if(this.debug){
1058 console.log(rawtransaction.signed)
1059 }
1060 if (rawtransaction.signed !== false) {
1061 txid = await this.sendRawTransaction(rawtransaction.signed)
1062 if(this.debug){
1063 console.log(txid)
1064 }
1065 if (txid !== undefined && txid !== null && txid.length === 64) {
1066 totalfees += fees
1067 for (let i in rawtransaction.inputs) {
1068 await this.pushTXIDtoCache(rawtransaction.inputs[i])
1069 }
1070 //Storing UTXO to cache
1071 var decoded = await this.decodeRawTransaction(rawtransaction.signed)
1072 if (decoded.vout[0].scriptPubKey.addresses !== undefined) {
1073 let unspent = {
1074 txid: decoded.txid,
1075 vout: 0,
1076 address: decoded.vout[0].scriptPubKey.addresses[0],
1077 scriptPubKey: decoded.vout[0].scriptPubKey.hex,
1078 amount: decoded.vout[0].value
1079 }
1080 await this.pushUTXOtoCache(unspent)
1081 }
1082 }else{
1083 txid = ''
1084 }
1085 }
1086 i++;
1087 retries ++;
1088 if(retries > 9){
1089 txid = '0000000000000000000000000000000000000000000000000000000000000000'
1090 }
1091 }
1092
1093 if(txid !== '0000000000000000000000000000000000000000000000000000000000000000'){
1094 return Promise.resolve({
1095 uuid: uuid,
1096 address: wallet,
1097 fees: totalfees,
1098 collection: collection.replace('!*!', ''),
1099 refID: refID.replace('!*!', ''),
1100 protocol: protocol.replace('!*!', ''),
1101 dimension: dataToWrite.length,
1102 chunks: 1,
1103 stored: dataToWrite,
1104 txs: [txid]
1105 })
1106 }else{
1107 return Promise.resolve(false)
1108 }
1109
1110 } else {
1111
1112 var txs = []
1113 var chunklength = MAX_OPRETURN - 6
1114 var chunkdatalimit = chunklength - 3
1115 var dataToWriteLength = dataToWrite.length
1116 var nchunks = Math.ceil(dataToWriteLength / chunklength)
1117 var last = nchunks - 1
1118 var chunks = []
1119
1120 for (var i = 0; i < nchunks; i++) {
1121 var start = i * chunklength
1122 var end = start + chunklength
1123 var chunk = dataToWrite.substring(start, end)
1124
1125 if (i === 0) {
1126 var startnext = (i + 1) * chunklength
1127 var endnext = startnext + chunklength
1128 var prevref = ''
1129 var nextref = dataToWrite.substring(startnext, endnext).substring(0, 3)
1130 } else if (i === last) {
1131 var startprev = (i - 1) * chunklength
1132 var endprev = startprev + chunklength
1133 var nextref = ''
1134 var prevref = dataToWrite.substr(startprev, endprev).substr(chunkdatalimit, 3)
1135 } else {
1136 var sni = i + 1
1137 var startnext = sni * chunklength
1138 var endnext = startnext + chunklength
1139 var nextref = dataToWrite.substring(startnext, endnext).substring(0, 3)
1140 var spi = i - 1
1141 var startprev = spi * chunklength
1142 var endprev = startprev + chunklength
1143 var prevref = dataToWrite.substr(startprev, endprev).substr(chunkdatalimit, 3)
1144 }
1145 chunk = prevref + chunk + nextref
1146 chunks.push(chunk)
1147 }
1148
1149 var totalfees = 0
1150
1151 for (var cix = 0; cix < chunks.length; cix++) {
1152 var txid = ''
1153 var i = 0
1154 var rawtransaction
1155 while (txid !== null && txid !== undefined && txid.length !== 64) {
1156 var fees = 0.001 + (i / 1000)
1157 //console.log('STORING CHUNK #' + cix, chunks[cix])
1158 rawtransaction = await this.build(wallet, password, false, address, 0, chunks[cix], fees)
1159 txid = await this.sendRawTransaction(rawtransaction.signed)
1160 //console.log(txid)
1161 if (txid !== null && txid !== false && txid.length === 64) {
1162 for (let i in rawtransaction.inputs) {
1163 await this.pushTXIDtoCache(rawtransaction.inputs[i])
1164 }
1165 totalfees += fees
1166 txs.push(txid)
1167 //Storing UTXO to cache
1168 var decoded = await this.decodeRawTransaction(rawtransaction.signed)
1169 if (decoded.vout[0].scriptPubKey.addresses !== undefined) {
1170 let unspent = {
1171 txid: decoded.txid,
1172 vout: 0,
1173 address: decoded.vout[0].scriptPubKey.addresses[0],
1174 scriptPubKey: decoded.vout[0].scriptPubKey.hex,
1175 amount: decoded.vout[0].value
1176 }
1177 await this.pushUTXOtoCache(unspent)
1178 }
1179 } else {
1180 txid = null
1181 }
1182 i++;
1183 }
1184 }
1185
1186 return Promise.resolve({
1187 uuid: uuid,
1188 address: wallet,
1189 fees: totalfees,
1190 collection: collection.replace('!*!', ''),
1191 refID: refID.replace('!*!', ''),
1192 protocol: protocol.replace('!*!', ''),
1193 dimension: dataToWrite.length,
1194 chunks: nchunks,
1195 stored: dataToWrite,
1196 txs: txs
1197 })
1198
1199 }
1200 }else{
1201 return Promise.resolve(false);
1202 }
1203 } catch (error) {
1204 console.log(error)
1205 return Promise.resolve(false);
1206 }
1207 } else {
1208 return false
1209 }
1210 }
1211 }
1212
1213 async update(key, password, metadata, collection = '', refID = '', protocol = '', uuid) {
1214 return new Promise(response => {
1215 if (uuid !== undefined) {
1216 let written = this.write(key, password, metadata, collection, refID, protocol, uuid)
1217 response(written)
1218 } else {
1219 response(false)
1220 }
1221 })
1222 }
1223
1224 async invalidate(key, password, uuid) {
1225 return new Promise(response => {
1226 if (uuid !== undefined) {
1227 let metadata = 'END'
1228 let written = this.write(key, password, metadata, '', '', '', uuid)
1229 response(written)
1230 } else {
1231 response(false)
1232 }
1233 })
1234 }
1235
1236 //SIGNING FUNCTIONS
1237 async signMessage(key, message) {
1238 return new Promise(response => {
1239 //CREATING CK OBJECT
1240 let params = lyraInfo.mainnet
1241 if (this.testnet === true) {
1242 params = lyraInfo.testnet
1243 }
1244 var ck = CoinKey.fromWif(key, params);
1245 //CREATE HASH FROM MESSAGE
1246 let hash = CryptoJS.SHA256(message);
1247 let msg = Buffer.from(hash.toString(CryptoJS.enc.Hex), 'hex');
1248 //GETTING PUBKEY FROM PRIVATEKEY
1249 let privKey = ck.privateKey
1250 //SIGN MESSAGE
1251 const sigObj = secp256k1.sign(msg, privKey)
1252 const pubKey = secp256k1.publicKeyCreate(privKey)
1253
1254 response({
1255 message: message,
1256 hash: hash.toString(CryptoJS.enc.Hex),
1257 signature: sigObj.signature.toString('hex'),
1258 pubkey: pubKey.toString('hex'),
1259 address: ck.publicAddress
1260 })
1261 })
1262 }
1263
1264 async verifyMessage(pubkey, signature, message) {
1265 return new Promise(async response => {
1266 //CREATE HASH FROM MESSAGE
1267 let hash = CryptoJS.SHA256(message);
1268 let msg = Buffer.from(hash.toString(CryptoJS.enc.Hex), 'hex')
1269 //VERIFY MESSAGE
1270 let buf = Buffer.from(signature, 'hex')
1271 let pubKey = Buffer.from(pubkey, 'hex')
1272 let verified = secp256k1.verify(msg, buf, pubKey)
1273 let address = await this.getAddressFromPubKey(pubkey)
1274 if (verified === true) {
1275 response({
1276 address: address,
1277 pubkey: pubkey,
1278 signature: signature,
1279 hash: hash.toString(CryptoJS.enc.Hex),
1280 message: message,
1281 })
1282 } else {
1283 response(false)
1284 }
1285 })
1286 }
1287
1288 // P2P FUNCTIONALITIES
1289
1290 async connectP2P(callback) {
1291 const app = this
1292 let nodes = await this.returnNodes()
1293 const db = new ScryptaDB(app.isBrowser)
1294
1295 for (let x in nodes) {
1296 let node = nodes[x]
1297 let check = await app.checkNode(node)
1298 if (check !== false) {
1299 let ready = await app.get('/wallet/getinfo', node)
1300 if(ready.blocks !== undefined){
1301 try{
1302 console.log('Bootstrap connection to ' + node.replace('https://', 'https://p2p.'))
1303 global['nodes'][node] = require('socket.io-client')(node.replace('https://', 'https://p2p.'), { reconnect: true })
1304 global['nodes'][node].on('connect', function () {
1305 console.log('Connected to peer: ' + global['nodes'][node].io.uri)
1306 global['connected'][node] = true
1307 })
1308 global['nodes'][node].on('disconnect', function () {
1309 // console.log('Disconnected from peer: ' + global['nodes'][node].io.uri)
1310 global['connected'][node] = false
1311 })
1312
1313 //PROTOCOLS
1314 global['nodes'][node].on('message', async function (data) {
1315 if(data.pubkey === undefined && data.pubKey !== undefined){
1316 data.pubkey = data.pubKey
1317 }
1318 let verified = await app.verifyMessage(data.pubkey, data.signature, data.message)
1319 if (verified !== false && global['cache'].indexOf(data.signature) === -1) {
1320 global['cache'].push(data.signature)
1321 let check = await db.get('messages', 'signature', data.signature)
1322 if (!check) {
1323 await db.put('messages', {
1324 signature: data.signature,
1325 message: data.message,
1326 pubkey: data.pubKey,
1327 address: data.address
1328 }).catch(err => {
1329 // console.log(err)
1330 }).then(success => {
1331 callback(data)
1332 })
1333 }
1334 }
1335 })
1336 }catch(e){
1337 console.log("CAN'T CONNECT TO " + node)
1338 }
1339 }
1340 }
1341 }
1342 }
1343
1344 async broadcast(key, password, protocol, message, socketID = '', nodeID = '') {
1345 const app = this
1346 key = await this.returnKey(key)
1347 let wallet = await this.readKey(password, key)
1348 if (wallet !== false) {
1349 let signed = await app.signMessage(wallet.prv, message)
1350
1351 return new Promise(async response => {
1352 if (nodeID === '') {
1353 for (let id in global['nodes']) {
1354 global['nodes'][id].emit(protocol, signed)
1355 }
1356 } else {
1357 if (global['nodes'][nodeID]) {
1358 global['nodes'][nodeID].emit(protocol, signed)
1359 }
1360 }
1361 response(message)
1362 })
1363 }
1364 }
1365
1366 // IDENTITIES FUNCTIONS
1367 returnIdentities() {
1368 const app = this
1369 return new Promise(response => {
1370 const db = new ScryptaDB(app.isBrowser)
1371 let wallet = db.get('wallet')
1372 response(wallet)
1373 })
1374 }
1375
1376 returnIdentity(address) {
1377 const app = this
1378 return new Promise(response => {
1379 const db = new ScryptaDB(app.isBrowser)
1380 let wallet = db.get('wallet', 'address', address)
1381 if (wallet !== false) {
1382 response(wallet)
1383 } else {
1384 response(false)
1385 }
1386 })
1387 }
1388
1389 createRSAKeys(address, password) {
1390 const app = this
1391 return new Promise(async response => {
1392 let wallet = await app.returnKey(address)
1393 const db = new ScryptaDB(app.isBrowser)
1394 let SIDS = wallet.split(':')
1395 let stored = await db.get('wallet', 'address', SIDS[0])
1396 if (stored.rsa === undefined) {
1397 let key = await app.readKey(password, wallet)
1398 if (key !== false) {
1399 const key = new NodeRSA({ b: 2048 });
1400 let pub = key.exportKey('pkcs8-public-pem');
1401 let prv = key.exportKey('pkcs8-pem');
1402
1403 let prvhex = await this.cryptData(prv, password)
1404 let checkdecryption = await this.decryptData(prvhex, password)
1405 if (checkdecryption === prv) {
1406 stored.rsa = {
1407 pub: pub,
1408 prv: prvhex
1409 }
1410 await db.update('wallet', 'address', stored.address, stored)
1411 response(true)
1412 } else {
1413 response(false)
1414 }
1415 } else {
1416 response(false)
1417 }
1418 } else {
1419 response(wallet.rsa)
1420 }
1421 })
1422 }
1423
1424 setDefaultIdentity(address) {
1425 const app = this
1426 return new Promise(async response => {
1427 const db = new ScryptaDB(app.isBrowser)
1428 let wallet = await db.get('wallet', 'address', address)
1429 if (wallet !== false && wallet !== null) {
1430 if (app.isBrowser) {
1431 // console.log(wallet)
1432 localStorage.setItem('SID', wallet.wallet)
1433 response(true)
1434 } else {
1435 await db.destroy('identity')
1436 await db.put('identity', wallet)
1437 response(true)
1438 }
1439 } else {
1440 response(false)
1441 }
1442 })
1443 }
1444
1445 returnDefaultIdentity() {
1446 const app = this
1447 return new Promise(async response => {
1448 const db = new ScryptaDB(app.isBrowser)
1449 if (app.isBrowser) {
1450 if (localStorage.getItem('SID') !== null) {
1451 response(localStorage.getItem('SID'))
1452 } else {
1453 let wallet = await db.get('wallet')
1454 if (wallet !== false && wallet[0] !== undefined) {
1455 localStorage.setItem('SID', wallet[0].wallet)
1456 response(wallet)
1457 } else {
1458 response(false)
1459 }
1460 }
1461 } else {
1462 let wallet = await db.get('identity')
1463 if (wallet !== false && wallet[0] !== undefined) {
1464 response(wallet[0])
1465 } else {
1466 response(false)
1467 }
1468 }
1469 })
1470 }
1471
1472 fetchIdentities(address) {
1473 return new Promise(async response => {
1474 const app = this
1475 if (wallet !== false) {
1476 let identities = app.post('/read', { dapp_address: address, protocol: 'I://' }).catch(err => {
1477 response(err)
1478 })
1479 response(identities.data)
1480 } else {
1481 response(false)
1482 }
1483 })
1484 }
1485
1486 shuffle(array) {
1487 var currentIndex = array.length, temporaryValue, randomIndex;
1488
1489 while (0 !== currentIndex) {
1490
1491 randomIndex = Math.floor(Math.random() * currentIndex);
1492 currentIndex -= 1;
1493
1494 temporaryValue = array[currentIndex];
1495 array[currentIndex] = array[randomIndex];
1496 array[randomIndex] = temporaryValue;
1497 }
1498
1499 return array;
1500 }
1501}