1 |
|
2 | var inquirer = require("inquirer");
|
3 | var gift = require("gift");
|
4 | var fs = require("fs");
|
5 | var async = require('async');
|
6 | var path = require("path");
|
7 | var rmdir = require("rmdir");
|
8 | var npm = require("npm");
|
9 | var request = require("request");
|
10 | var valid_url = require("valid-url");
|
11 | var fsExtra = require('fs-extra');
|
12 | var AschJS = require('asch-js');
|
13 | var accountHelper = require("../helpers/account.js");
|
14 | var blockHelper = require("../helpers/block.js");
|
15 | var dappHelper = require("../helpers/dapp.js");
|
16 | var Api = require("../helpers/api.js");
|
17 |
|
18 | var templatePath = path.join(__dirname, "..", "template");
|
19 |
|
20 | var dappCategories = [
|
21 | "Common",
|
22 | "Business",
|
23 | "Social",
|
24 | "Education",
|
25 | "Entertainment",
|
26 | "News",
|
27 | "Life",
|
28 | "Utilities",
|
29 | "Games"
|
30 | ];
|
31 | function addDapp() {
|
32 | var account;
|
33 | var secondSecret;
|
34 | var dappParams;
|
35 | var dappTrs;
|
36 | var dappBlock;
|
37 | var dappsPath = path.join(".", "dapps");
|
38 | var dappPath;
|
39 | var assetInfo;
|
40 | async.series([
|
41 | function(next) {
|
42 | inquirer.prompt([
|
43 | {
|
44 | type: "password",
|
45 | name: "secret",
|
46 | message: "Enter secret of your testnet account",
|
47 | validate: function (value) {
|
48 | var done = this.async();
|
49 |
|
50 | if (!accountHelper.isValidSecret(value)) {
|
51 | done("Secret is not validated by BIP39");
|
52 | return;
|
53 | }
|
54 |
|
55 | done(true);
|
56 | }
|
57 | },
|
58 | {
|
59 | type: "password",
|
60 | name: "secondSecret",
|
61 | message: "Enter second secret of your testnet account if you have"
|
62 | }
|
63 | ], function (result) {
|
64 | account = accountHelper.account(result.secret);
|
65 | secondSecret = result.secondSecret;
|
66 | next();
|
67 | })
|
68 | },
|
69 | function(next) {
|
70 | inquirer.prompt([
|
71 | {
|
72 | type: "input",
|
73 | name: "name",
|
74 | message: "Enter DApp name",
|
75 | required: true,
|
76 | validate: function (value) {
|
77 | var done = this.async();
|
78 |
|
79 | if (value.length == 0) {
|
80 | done("DApp name is too short, minimum is 1 character");
|
81 | return;
|
82 | }
|
83 |
|
84 | if (value.length > 32) {
|
85 | done("DApp name is too long, maximum is 32 characters");
|
86 | return;
|
87 | }
|
88 |
|
89 | return done(true)
|
90 | }
|
91 | },
|
92 | {
|
93 | type: "input",
|
94 | name: "description",
|
95 | message: "Enter DApp description",
|
96 | validate: function (value) {
|
97 | var done = this.async();
|
98 |
|
99 | if (value.length > 160) {
|
100 | done("DApp description is too long, maximum is 160 characters");
|
101 | return;
|
102 | }
|
103 |
|
104 | return done(true);
|
105 | }
|
106 | },
|
107 | {
|
108 | type: "input",
|
109 | name: "tags",
|
110 | message: "Enter DApp tags",
|
111 | validate: function (value) {
|
112 | var done = this.async();
|
113 |
|
114 | if (value.length > 160) {
|
115 | done("DApp tags is too long, maximum is 160 characters");
|
116 | return;
|
117 | }
|
118 |
|
119 | return done(true);
|
120 | }
|
121 | },
|
122 | {
|
123 | type: "rawlist",
|
124 | name: "category",
|
125 | required: true,
|
126 | message: "Choose DApp category",
|
127 | choices: dappCategories
|
128 | },
|
129 | {
|
130 | type: "input",
|
131 | name: "link",
|
132 | message: "Enter DApp link",
|
133 | required: true,
|
134 | validate: function (value) {
|
135 | var done = this.async();
|
136 |
|
137 | if (!valid_url.isUri(value)) {
|
138 | done("Invalid DApp link, must be a valid url");
|
139 | return;
|
140 | }
|
141 | if (value.indexOf(".zip") != value.length - 4) {
|
142 | done("Invalid DApp link, does not link to zip file");
|
143 | return;
|
144 | }
|
145 | if (value.length > 160) {
|
146 | return done("DApp link is too long, maximum is 160 characters");
|
147 | }
|
148 |
|
149 | return done(true);
|
150 | }
|
151 | },
|
152 | {
|
153 | type: "input",
|
154 | name: "icon",
|
155 | message: "Enter DApp icon url",
|
156 | validate: function (value) {
|
157 | var done = this.async();
|
158 |
|
159 | if (!valid_url.isUri(value)) {
|
160 | return done("Invalid DApp icon, must be a valid url");
|
161 | }
|
162 | var extname = path.extname(value);
|
163 | if (['.png', '.jpg', '.jpeg'].indexOf(extname) == -1) {
|
164 | return done("Invalid DApp icon file type");
|
165 | }
|
166 | if (value.length > 160) {
|
167 | return done("DApp icon url is too long, maximum is 160 characters");
|
168 | }
|
169 |
|
170 | return done(true);
|
171 | }
|
172 | }
|
173 | ], function (result) {
|
174 | if (!result.name || !result.link || !result.category) {
|
175 | return next(new Error('invalid dapp params'));
|
176 | }
|
177 | dappParams = {
|
178 | name: result.name,
|
179 | link: result.link,
|
180 | category: dappCategories.indexOf(result.category) + 1,
|
181 | description: result.description || "",
|
182 | tags: result.tags || "",
|
183 | icon: result.icon || "",
|
184 | type: 0
|
185 | };
|
186 | dappTrs = AschJS.dapp.createDapp(account.secret, secondSecret, dappParams);
|
187 | console.log("Generate dapp transaction", dappTrs);
|
188 | next();
|
189 | });
|
190 | },
|
191 | function(next) {
|
192 | inquirer.prompt([
|
193 | {
|
194 | type: "confirm",
|
195 | name: "inbuiltAsset",
|
196 | message: "Do you want publish a inbuilt asset in this dapp?",
|
197 | default: false
|
198 | }
|
199 | ], function (result) {
|
200 | if (!result.inbuiltAsset) {
|
201 | return next();
|
202 | }
|
203 | inquirer.prompt([
|
204 | {
|
205 | type: "input",
|
206 | name: "assetName",
|
207 | message: "Enter asset name, for example: BTC, CNY, USD, MYASSET",
|
208 | default: ''
|
209 | },
|
210 | {
|
211 | type: "input",
|
212 | name: "assetAmount",
|
213 | message: "Enter asset total amount",
|
214 | default: 1000000
|
215 | }
|
216 | ], function (result) {
|
217 | if (!result.assetName || result.assetName === 'XAS') {
|
218 | return next('invalid inbuilt asset name');
|
219 | }
|
220 | var assetAmount = Number(result.assetAmount);
|
221 | if (!assetAmount || isNaN(assetAmount) || assetAmount < 0) {
|
222 | return next('invalid inbuilt asset amount');
|
223 | }
|
224 | assetInfo = {
|
225 | name: result.assetName,
|
226 | amount: assetAmount
|
227 | };
|
228 | next();
|
229 | });
|
230 | });
|
231 | },
|
232 | function(next) {
|
233 | inquirer.prompt([
|
234 | {
|
235 | type: "input",
|
236 | name: "publicKeys",
|
237 | message: "Enter public keys of dapp forgers - hex array, use ',' for separator",
|
238 | default: account.keypair.publicKey,
|
239 | validate: function (value) {
|
240 | var done = this.async();
|
241 |
|
242 | var publicKeys = value.split(",");
|
243 |
|
244 | if (publicKeys.length == 0) {
|
245 | done("DApp requires at least 1 public key");
|
246 | return;
|
247 | }
|
248 |
|
249 | for (var i in publicKeys) {
|
250 | try {
|
251 | var b = new Buffer(publicKeys[i], "hex");
|
252 | if (b.length != 32) {
|
253 | done("Invalid public key: " + publicKeys[i]);
|
254 | return;
|
255 | }
|
256 | } catch (e) {
|
257 | done("Invalid hex for public key: " + publicKeys[i]);
|
258 | return;
|
259 | }
|
260 | }
|
261 | done(true);
|
262 | }
|
263 | }
|
264 | ], function (result) {
|
265 | if (!result.publicKeys) {
|
266 | return next("invalid dapp forger public keys");
|
267 | }
|
268 | console.log("Creating DApp genesis block");
|
269 | dappBlock = dappHelper.new(account, result.publicKeys.split(","), assetInfo);
|
270 | next();
|
271 | });
|
272 | },
|
273 | function(next) {
|
274 | console.log("Fetching Asch Dapps SDK");
|
275 | fs.exists(dappsPath, function (exists) {
|
276 | if (!exists) {
|
277 | fs.mkdir(dappsPath, next);
|
278 | } else {
|
279 | next();
|
280 | }
|
281 | });
|
282 | },
|
283 | function(next) {
|
284 | dappPath = path.join(dappsPath, dappTrs.id);
|
285 | fsExtra.copy(templatePath, dappPath, {clobber: true}, next);
|
286 | },
|
287 | function(next) {
|
288 | console.log("Saving genesis block");
|
289 | var genesisBlockJson = JSON.stringify(dappBlock, null, 2);
|
290 | fs.writeFile(path.join(dappPath, "genesis.json"), genesisBlockJson, "utf8", next);
|
291 | },
|
292 | function(next) {
|
293 | console.log("Saving dapp meta information");
|
294 | var dappParamsJson = JSON.stringify(dappParams, null, 2);
|
295 | fs.writeFile(path.join(dappPath, "dapp.json"), dappParamsJson, "utf8", next);
|
296 | },
|
297 | function (next) {
|
298 | console.log("Registering dapp in localnet");
|
299 | var api = new Api({port: 4096});
|
300 | api.broadcastTransaction(dappTrs, function (err) {
|
301 | if (err) {
|
302 | next("Failed to register dapp: " + err);
|
303 | } else {
|
304 | next();
|
305 | }
|
306 | });
|
307 | }
|
308 | ], function(err) {
|
309 | if (err) {
|
310 | console.log(err);
|
311 | } else {
|
312 | setTimeout(function () {
|
313 | console.log("Done (DApp id is " + dappTrs.id + ")");
|
314 | }, 10000);
|
315 | }
|
316 | });
|
317 | }
|
318 |
|
319 | function changeDapp() {
|
320 | inquirer.prompt([
|
321 | {
|
322 | type: "confirm",
|
323 | name: "confirmed",
|
324 | message: "Existing blockchain will be replaced, are you sure?",
|
325 | default: false
|
326 | }
|
327 | ], function (result) {
|
328 | if (result.confirmed) {
|
329 | inquirer.prompt([
|
330 | {
|
331 | type: "password",
|
332 | name: "secret",
|
333 | message: "Enter secret of your testnet account",
|
334 | validate: function (value) {
|
335 | var done = this.async();
|
336 | if (!accountHelper.isValidSecret(value)) {
|
337 | done("Secret is not validated by BIP39");
|
338 | return;
|
339 | }
|
340 | done(true);
|
341 | }
|
342 | },
|
343 | {
|
344 | type: "input",
|
345 | name: "dappId",
|
346 | message: "Enter DApp id (folder name of dapp)",
|
347 | required: true
|
348 | },
|
349 | ], function (result) {
|
350 | var account = accountHelper.account(result.secret);
|
351 | var dappId = result.dappId;
|
352 | var publicKeys = [];
|
353 |
|
354 | var dappPath = path.join(".", "dapps", dappId);
|
355 | var dappGenesis = JSON.parse(fs.readFileSync(path.join(dappPath, "genesis.json"), "utf8"));
|
356 | inquirer.prompt([
|
357 | {
|
358 | type: "confirm",
|
359 | name: "inbuiltAsset",
|
360 | message: "Do you want publish a inbuilt asset in this dapp?",
|
361 | default: false
|
362 | }
|
363 | ], function (result) {
|
364 | var assetInfo;
|
365 | if (result.inbuiltAsset) {
|
366 | inquirer.prompt([
|
367 | {
|
368 | type: "input",
|
369 | name: "assetName",
|
370 | message: "Enter asset name, for example: BTC, CNY, USD, MYASSET",
|
371 | default: ''
|
372 | },
|
373 | {
|
374 | type: "input",
|
375 | name: "assetAmount",
|
376 | message: "Enter asset total amount",
|
377 | default: 1000000
|
378 | }
|
379 | ], function (result) {
|
380 | if (!result.assetName || result.assetName === 'XAS') {
|
381 | return next('invalid inbuilt asset name');
|
382 | }
|
383 | var assetAmount = Number(result.assetAmount);
|
384 | if (!assetAmount || isNaN(assetAmount) || assetAmount < 0) {
|
385 | return next('invalid inbuilt asset amount');
|
386 | }
|
387 | assetInfo = {
|
388 | name: result.assetName,
|
389 | amount: assetAmount
|
390 | };
|
391 | });
|
392 | }
|
393 |
|
394 | inquirer.prompt([
|
395 | {
|
396 | type: "confirm",
|
397 | name: "confirmed",
|
398 | message: "Continue with exists forgers public keys",
|
399 | required: true,
|
400 | }], function (result) {
|
401 | if (result.confirmed) {
|
402 | publicKeys = dappGenesis.delegates;
|
403 | }
|
404 |
|
405 | inquirer.prompt([
|
406 | {
|
407 | type: "input",
|
408 | name: "publicKeys",
|
409 | message: "Enter public keys of dapp forgers - hex array, use ',' for separator",
|
410 | default: account.keypair.publicKey,
|
411 | validate: function (value) {
|
412 | var done = this.async();
|
413 |
|
414 | var publicKeys = value.split(",");
|
415 |
|
416 | if (publicKeys.length == 0) {
|
417 | done("DApp requires at least 1 public key");
|
418 | return;
|
419 | }
|
420 |
|
421 | for (var i in publicKeys) {
|
422 | try {
|
423 | var b = new Buffer(publicKeys[i], "hex");
|
424 | if (b.length != 32) {
|
425 | done("Invalid public key: " + publicKeys[i]);
|
426 | return;
|
427 | }
|
428 | } catch (e) {
|
429 | done("Invalid hex for public key: " + publicKeys[i]);
|
430 | return;
|
431 | }
|
432 | }
|
433 |
|
434 | done(true);
|
435 | }
|
436 | }
|
437 | ], function (result) {
|
438 | console.log("Creating DApp genesis block");
|
439 |
|
440 | var dappBlock = dappHelper.new(account, result.publicKeys.split(","), assetInfo);
|
441 | var dappGenesisBlockJson = JSON.stringify(dappBlock, null, 2);
|
442 |
|
443 | try {
|
444 | fs.writeFileSync(path.join(dappPath, "genesis.json"), dappGenesisBlockJson, "utf8");
|
445 | } catch (e) {
|
446 | return console.log(err);
|
447 | }
|
448 |
|
449 | console.log("Done");
|
450 | });
|
451 | });
|
452 | });
|
453 | });
|
454 | }
|
455 | });
|
456 | }
|
457 |
|
458 | function depositDapp() {
|
459 | inquirer.prompt([
|
460 | {
|
461 | type: "password",
|
462 | name: "secret",
|
463 | message: "Enter secret",
|
464 | validate: function (value) {
|
465 | return value.length > 0 && value.length < 100;
|
466 | },
|
467 | required: true
|
468 | },
|
469 | {
|
470 | type: "input",
|
471 | name: "amount",
|
472 | message: "Enter amount",
|
473 | validate: function (value) {
|
474 | return !isNaN(parseInt(value));
|
475 | },
|
476 | required: true
|
477 | },
|
478 | {
|
479 | type: "input",
|
480 | name: "dappId",
|
481 | message: "DApp Id",
|
482 | required: true
|
483 | },
|
484 | {
|
485 | type: "input",
|
486 | name: "secondSecret",
|
487 | message: "Enter secondary secret (if defined)",
|
488 | validate: function (value) {
|
489 | return value.length < 100;
|
490 | },
|
491 | required: false
|
492 | }
|
493 | ], function (result) {
|
494 | var realAmount = parseFloat((parseInt(result.amount) * 100000000).toFixed(0));
|
495 | var body = {
|
496 | secret: result.secret,
|
497 | dappId: result.dappId,
|
498 | amount: realAmount
|
499 | };
|
500 |
|
501 | if (result.secondSecret && result.secondSecret.length > 0) {
|
502 | body.secondSecret = result.secondSecret;
|
503 | }
|
504 |
|
505 | inquirer.prompt([
|
506 | {
|
507 | type: "input",
|
508 | name: "host",
|
509 | message: "Host and port",
|
510 | default: "localhost:4096",
|
511 | required: true
|
512 | }
|
513 | ], function (result) {
|
514 | request({
|
515 | url: "http://" + result.host + "/api/dapps/transaction",
|
516 | method: "put",
|
517 | json: true,
|
518 | body: body
|
519 | }, function (err, resp, body) {
|
520 | console.log(err, body);
|
521 | if (err) {
|
522 | return console.log(err.toString());
|
523 | }
|
524 |
|
525 | if (body.success) {
|
526 | console.log(body.transactionId);
|
527 | return;
|
528 | } else {
|
529 | return console.log(body.error);
|
530 | }
|
531 | });
|
532 | });
|
533 | });
|
534 | }
|
535 |
|
536 | function withdrawalDapp() {
|
537 | inquirer.prompt([
|
538 | {
|
539 | type: "password",
|
540 | name: "secret",
|
541 | message: "Enter secret",
|
542 | validate: function (value) {
|
543 | return value.length > 0 && value.length < 100;
|
544 | },
|
545 | required: true
|
546 | },
|
547 | {
|
548 | type: "input",
|
549 | name: "amount",
|
550 | message: "Amount",
|
551 | validate: function (value) {
|
552 | return !isNaN(parseInt(value));
|
553 | },
|
554 | required: true
|
555 | },
|
556 | {
|
557 | type: "input",
|
558 | name: "dappId",
|
559 | message: "Enter DApp id",
|
560 | validate: function (value) {
|
561 | var isAddress = /^[0-9]+$/g;
|
562 | return isAddress.test(value);
|
563 | },
|
564 | required: true
|
565 | }], function (result) {
|
566 |
|
567 | var body = {
|
568 | secret: result.secret,
|
569 | amount: Number(result.amount)
|
570 | };
|
571 |
|
572 | request({
|
573 | url: "http://localhost:4096/api/dapps/" + result.dappId + "/api/withdrawal",
|
574 | method: "post",
|
575 | json: true,
|
576 | body: body
|
577 | }, function (err, resp, body) {
|
578 | if (err) {
|
579 | return console.log(err.toString());
|
580 | }
|
581 |
|
582 | if (body.success) {
|
583 | console.log(body.transactionId);
|
584 | } else {
|
585 | return console.log(body.error);
|
586 | }
|
587 | });
|
588 | });
|
589 | }
|
590 |
|
591 | function uninstallDapp() {
|
592 | inquirer.prompt([
|
593 | {
|
594 | type: "input",
|
595 | name: "dappId",
|
596 | message: "Enter dapp id",
|
597 | validate: function (value) {
|
598 | return value.length > 0 && value.length < 100;
|
599 | },
|
600 | required: true
|
601 | },
|
602 | {
|
603 | type: "input",
|
604 | name: "host",
|
605 | message: "Host and port",
|
606 | default: "localhost:4096",
|
607 | required: true
|
608 | },
|
609 | {
|
610 | type: "password",
|
611 | name: "masterpassword",
|
612 | message: "Enter dapp master password",
|
613 | required: true
|
614 | }], function (result) {
|
615 |
|
616 | var body = {
|
617 | id: String(result.dappId),
|
618 | master: String(result.masterpassword)
|
619 | };
|
620 |
|
621 | request({
|
622 | url: "http://" + result.host + "/api/dapps/uninstall",
|
623 | method: "post",
|
624 | json: true,
|
625 | body: body
|
626 | }, function (err, resp, body) {
|
627 | if (err) {
|
628 | return console.log(err.toString());
|
629 | }
|
630 |
|
631 | if (body.success) {
|
632 | console.log("Done!");
|
633 | } else {
|
634 | return console.log(body.error);
|
635 | }
|
636 | });
|
637 | });
|
638 | }
|
639 |
|
640 | function installDapp() {
|
641 | inquirer.prompt([
|
642 | {
|
643 | type: "input",
|
644 | name: "dappId",
|
645 | message: "Enter dapp id",
|
646 | validate: function (value) {
|
647 | return value.length > 0 && value.length < 100;
|
648 | },
|
649 | required: true
|
650 | },
|
651 | {
|
652 | type: "input",
|
653 | name: "host",
|
654 | message: "Host and port",
|
655 | default: "localhost:4096",
|
656 | required: true
|
657 | },
|
658 | {
|
659 | type: "password",
|
660 | name: "masterpassword",
|
661 | message: "Enter dapp master password",
|
662 | required: true
|
663 | }], function (result) {
|
664 |
|
665 | var body = {
|
666 | id: String(result.dappId),
|
667 | master: String(result.masterpassword)
|
668 | };
|
669 |
|
670 | request({
|
671 | url: "http://" + result.host + "/api/dapps/install",
|
672 | method: "post",
|
673 | json: true,
|
674 | body: body
|
675 | }, function (err, resp, body) {
|
676 | if (err) {
|
677 | return console.log(err.toString());
|
678 | }
|
679 |
|
680 | if (body.success) {
|
681 | console.log("Done!", body.path);
|
682 | } else {
|
683 | return console.log(body.error);
|
684 | }
|
685 | });
|
686 | });
|
687 | }
|
688 |
|
689 | module.exports = function (program) {
|
690 | program
|
691 | .command("dapps")
|
692 | .description("manage your dapps")
|
693 | .option("-a, --add", "add new dapp")
|
694 | .option("-c, --change", "change dapp genesis block")
|
695 | .option("-d, --deposit", "deposit funds to dapp")
|
696 | .option("-w, --withdrawal", "withdraw funds from dapp")
|
697 | .option("-i, --install", "install dapp")
|
698 | .option("-u, --uninstall", "uninstall dapp")
|
699 | .action(function (options) {
|
700 | if (options.add) {
|
701 | addDapp();
|
702 | } else if (options.change) {
|
703 | changeDapp();
|
704 | } else if (options.deposit) {
|
705 | depositDapp();
|
706 | } else if (options.withdrawal) {
|
707 | withdrawalDapp();
|
708 | } else if (options.install) {
|
709 | installDapp();
|
710 | } else if (options.uninstall) {
|
711 | uninstallDapp();
|
712 | } else {
|
713 | console.log("'node dapps -h' to get help");
|
714 | }
|
715 | });
|
716 | } |
\ | No newline at end of file |