├── template ├── config.json ├── .gitignore ├── interface │ ├── domain.js │ └── helloworld.js ├── init.js ├── model │ └── domain.js ├── contract │ └── domain.js ├── dapp.json ├── genesis.json └── public │ └── index.html ├── bin └── asch-cli ├── .gitignore ├── helpers ├── account.js ├── api.js ├── dapp.js └── block.js ├── .travis.yml ├── config.json ├── index.js ├── LICENSE ├── package.json ├── lib ├── dapptransactions.js ├── crypto.js └── transactions.js ├── contract-example.js ├── plugins ├── crypto.js ├── contract.js ├── misc.js ├── chain.js └── api.js └── README.md /template/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "secrets": [ 3 | 4 | ] 5 | } -------------------------------------------------------------------------------- /bin/asch-cli: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | require('../index.js'); 4 | -------------------------------------------------------------------------------- /template/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .DS_Store 3 | .idea/ 4 | build 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea/ 3 | *.log 4 | genesisBlock.json 5 | node_modules 6 | package-lock.json 7 | genesis.json 8 | dapp.json 9 | tmp 10 | -------------------------------------------------------------------------------- /template/interface/domain.js: -------------------------------------------------------------------------------- 1 | app.route.get('/domain/:domain', async function (req) { 2 | return await app.model.Domain.findOne({domain: req.params.domain}) 3 | }) 4 | 5 | app.route.get('/domain/suffix/:suffix', async function (req) { 6 | return await app.model.Domain.findAll({suffix: req.params.suffix}) 7 | }) -------------------------------------------------------------------------------- /template/init.js: -------------------------------------------------------------------------------- 1 | module.exports = async function () { 2 | console.log('enter dapp init') 3 | 4 | app.registerContract(1000, 'domain.register') 5 | app.registerContract(1001, 'domain.set_ip') 6 | 7 | 8 | app.events.on('newBlock', (block) => { 9 | console.log('new block received', block.height) 10 | }) 11 | } -------------------------------------------------------------------------------- /helpers/account.js: -------------------------------------------------------------------------------- 1 | var crypto = require('../lib/crypto.js'); 2 | 3 | module.exports = { 4 | account: function (secret) { 5 | var kp = crypto.keypair(secret); 6 | var address = crypto.getAddress(new Buffer(kp.publicKey, 'hex')); 7 | 8 | return { 9 | keypair: kp, 10 | address: address, 11 | secret : secret 12 | } 13 | }, 14 | 15 | isValidSecret: crypto.isValidSecret 16 | } 17 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | os: 2 | - linux 3 | language: node_js 4 | env: 5 | - CXX=g++-4.8 6 | node_js: 7 | - "6.0.0" 8 | - "7" 9 | - "8" 10 | notifications: 11 | recipients: 12 | - sqf1225@foxmail.com 13 | - i@yanyiwu.com 14 | email: 15 | on_success: change 16 | on_failure: always 17 | addons: 18 | apt: 19 | sources: 20 | - ubuntu-toolchain-r-test 21 | packages: 22 | - g++-4.8 23 | -------------------------------------------------------------------------------- /template/interface/helloworld.js: -------------------------------------------------------------------------------- 1 | app.route.get('/helloworld', async function (req) { 2 | return { message: 'helloworld' } 3 | }) 4 | 5 | app.route.put('/ping/:seq', async function (req) { 6 | return { pong: Number(req.params.seq) + 1 } 7 | }) 8 | 9 | app.route.post('/sleep', async function (req, cb) { 10 | if (!req.query || !req.query.seconds) { 11 | throw new Error('Invalid params') 12 | } 13 | await new Promise(resolve => setTimeout(resolve, Math.floor(Number(req.query.seconds) * 1000))) 14 | }) -------------------------------------------------------------------------------- /template/model/domain.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: 'domains', 3 | fields: [ 4 | { 5 | name: 'domain', 6 | type: 'String', 7 | length: 256, 8 | not_null: true, 9 | index: true 10 | }, 11 | { 12 | name: 'ip', 13 | type: 'String', 14 | length: 15 15 | }, 16 | { 17 | name: 'owner', 18 | type: 'String', 19 | length: 50, 20 | not_null: true, 21 | }, 22 | { 23 | name: 'suffix', 24 | type: 'String', 25 | length: 10, 26 | not_null: true, 27 | index: true 28 | } 29 | ] 30 | } -------------------------------------------------------------------------------- /template/contract/domain.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | register: async function(domain) { 3 | app.sdb.lock('domain.register@' + domain) 4 | let exists = await app.model.Domain.exists({ domain }) 5 | if (exists) return 'Domain already registered' 6 | app.sdb.create('Domain', { 7 | domain, 8 | owner: this.trs.senderId, 9 | suffix: domain.split('.').pop() 10 | }) 11 | }, 12 | set_ip: async function(domain, ip) { 13 | app.sdb.lock('domain.register@' + domain) 14 | let exists = await app.model.Domain.exists({ domain }) 15 | if (!exists) return 'Domain not exists' 16 | app.sdb.update('Domain', { ip }, { domain }) 17 | } 18 | } -------------------------------------------------------------------------------- /template/dapp.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "asch-dapp-helloworld", 3 | "link": "https://github.com/AschPlatform/asch-dapp-helloworld/archive/master.zip", 4 | "category": 1, 5 | "description": "A hello world demo for asch dapp", 6 | "tags": "asch,dapp,demo", 7 | "icon": "http://o7dyh3w0x.bkt.clouddn.com/hello.png", 8 | "type": 0, 9 | "delegates": [ 10 | "a518e4390512e43d71503a02c9912413db6a9ffac4cbefdcd25b8fa2a1d5ca27", 11 | "c7dee266d5c85bf19da8fab1efc93204fed7b35538a3618d7f6a12d022498cab", 12 | "9cac187d70713b33cc4a9bf3ff4c004bfca94802aed4a32e2f23ed662161ea50", 13 | "01944ce58570592250f509214d29171a84f0f9c15129dbea070251512a08f5cc", 14 | "f31d61066c902bebc80155fed318200ffbcfc97792511ed18d85bd5af666639f" 15 | ], 16 | "unlockDelegates": 3 17 | } -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "port": 7000, 3 | "address": "0.0.0.0", 4 | "serveHttpAPI": true, 5 | "serveHttpWallet": true, 6 | "version": "0.1.1", 7 | "fileLogLevel": "info", 8 | "consoleLogLevel": "log", 9 | "sharePort": true, 10 | "api": { 11 | "access": { 12 | "whiteList": [] 13 | } 14 | }, 15 | "peers": { 16 | "list": [], 17 | "blackList": [], 18 | "options": { 19 | "timeout": 20000 20 | } 21 | }, 22 | "forging": { 23 | "secret": [ 24 | ], 25 | "access": { 26 | "whiteList": [ 27 | "127.0.0.1" 28 | ] 29 | } 30 | }, 31 | "loading": { 32 | "verifyOnLoading": true, 33 | "loadPerIteration": 5000 34 | }, 35 | "ssl": { 36 | "enabled": false, 37 | "options": { 38 | "port": 443, 39 | "address": "0.0.0.0", 40 | "key": "./ssl/server.key", 41 | "cert": "./ssl/server.crt" 42 | } 43 | }, 44 | "dapp": { 45 | "autoexec": [ 46 | ] 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 2 | var program = require("commander"); 3 | 4 | var fs = require("fs"); 5 | var path = require("path"); 6 | var package = require('./package.json'); 7 | 8 | function main() { 9 | var default_host = process.env.ASCH_HOST || '127.0.0.1'; 10 | var default_port = process.env.ASCH_PORT || 4096; 11 | program.version(package.version) 12 | .option('-H, --host ', 'Specify the hostname or ip of the node, default: ' + default_host, default_host) 13 | .option('-P, --port ', 'Specify the port of the node, default: ' + default_port, default_port) 14 | .option('-M, --main', 'Specify the mainnet, default: false') 15 | 16 | var plugins = fs.readdirSync(path.join(__dirname, 'plugins')); 17 | plugins.forEach(function (el) { 18 | if (el.endsWith('js')) { 19 | require('./plugins/' + el)(program); 20 | } 21 | }); 22 | 23 | if (!process.argv.slice(2).length) { 24 | program.outputHelp(); 25 | } 26 | program.parse(process.argv); 27 | } 28 | 29 | main(); 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Asch 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /template/genesis.json: -------------------------------------------------------------------------------- 1 | { 2 | "delegate": "8065a105c785a08757727fded3a06f8f312e73ad40f1f3502e0232ea42e67efd", 3 | "height": 1, 4 | "pointId": null, 5 | "pointHeight": null, 6 | "transactions": [ 7 | { 8 | "fee": "0", 9 | "timestamp": 0, 10 | "senderPublicKey": "4ff4b8a05503c9189d0c20e56714c38cf07efce3eccb63e22ef2afa0066ef2d4", 11 | "type": 3, 12 | "args": [ 13 | "CNY", 14 | "100000000000000", 15 | "A8QCwz5Vs77UGX9YqBg9kJ6AZmsXQBC8vj" 16 | ], 17 | "signature": "4304867b5dd59cdc2020807754ee93147ba77a3b9fe3ffe6bade14953f75b1c17d607a8c031c2c99301eccb2838ad51d7b87498f4bc9b1f3d473c7217f268f0e", 18 | "id": "3f0314d3301c4470dd54e856d6628760ec39eb14c1eb8d36020279df88a6385f" 19 | } 20 | ], 21 | "timestamp": 0, 22 | "payloadLength": 93, 23 | "payloadHash": "0cfee92c58c330fc1bf89c716bef5da4ed9f5a48b8956c6a3b451ee778bd77f3", 24 | "count": 1, 25 | "signature": "8a1dc59ef0b63e6ad614fbb00cafa8be1d063c030290084933e33549b286c040d6fc9f7e02bfa029e1def1462fad7bfc0973be0d9a6627c923fa607baf3e6109", 26 | "id": "1381a4c89c68c106b38d26b98a77d6809b8e7c773ddbdfd55ec8c8c1869e2512" 27 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "asch-cli", 3 | "preferGlobal": true, 4 | "version": "1.4.0", 5 | "description": "Command line interface for bootstrapping and managing asch dapps", 6 | "main": "index.js", 7 | "scripts": { 8 | "test": "echo \"Error: no test specified\" && exit 0" 9 | }, 10 | "keywords": [ 11 | "asch", 12 | "dapps", 13 | "cli" 14 | ], 15 | "bin": { 16 | "asch-cli": "./bin/asch-cli" 17 | }, 18 | "author": "Qingfeng Shan ", 19 | "license": "MIT", 20 | "dependencies": { 21 | "asch-js": "*", 22 | "async": "1.5.2", 23 | "bitcore-mnemonic": "1.0.1", 24 | "browserify-bignum": "1.3.0-2", 25 | "bytebuffer": "^4.0.0", 26 | "commander": "^2.8.1", 27 | "crypto-browserify": "^3.9.14", 28 | "fs-extra": "0.30.0", 29 | "gift": "0.6.1", 30 | "inquirer": "^3.3.0", 31 | "js-nacl": "0.6.0", 32 | "request": "^2.62.0", 33 | "rmdir": "^1.1.0", 34 | "shelljs": "^0.8.1", 35 | "valid-url": "1.0.9" 36 | }, 37 | "bugs": { 38 | "url": "https://github.com/AschPlatform/asch-cli/issues" 39 | }, 40 | "repository": { 41 | "type": "git", 42 | "url": "https://github.com/AschPlatform/asch-cli.git" 43 | }, 44 | "homepage": "https://github.com/AschPlatform/asch-cli" 45 | } 46 | -------------------------------------------------------------------------------- /lib/dapptransactions.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var util = require('util'); 3 | var ByteBuffer = require('bytebuffer'); 4 | var crypto = require('./crypto.js'); 5 | var bignum = require('browserify-bignum'); 6 | 7 | var bytesTypes = { 8 | 2: function (trs) { 9 | try { 10 | var buf = new Buffer(trs.asset.delegates.list.join(","), 'utf8'); 11 | } catch (e) { 12 | throw Error(e.toString()); 13 | } 14 | 15 | return buf; 16 | } 17 | } 18 | 19 | function getTransactionBytes(trs, skipSignature) { 20 | 21 | try { 22 | var bb = new ByteBuffer(1, true); 23 | bb.writeInt(trs.timestamp); 24 | bb.writeString(trs.fee) 25 | 26 | var senderPublicKeyBuffer = new Buffer(trs.senderPublicKey, 'hex'); 27 | for (var i = 0; i < senderPublicKeyBuffer.length; i++) { 28 | bb.writeByte(senderPublicKeyBuffer[i]); 29 | } 30 | 31 | bb.writeInt(trs.type) 32 | 33 | assert(Array.isArray(trs.args)) 34 | bb.writeString(JSON.stringify(trs.args)) 35 | 36 | if (!skipSignature && trs.signature) { 37 | var signatureBuffer = new Buffer(trs.signature, 'hex'); 38 | for (var i = 0; i < signatureBuffer.length; i++) { 39 | bb.writeByte(signatureBuffer[i]); 40 | } 41 | } 42 | 43 | bb.flip(); 44 | } catch (e) { 45 | throw Error(e.toString()); 46 | } 47 | return bb.toBuffer(); 48 | } 49 | 50 | module.exports = { 51 | getTransactionBytes: getTransactionBytes 52 | } 53 | -------------------------------------------------------------------------------- /contract-example.js: -------------------------------------------------------------------------------- 1 | var TransactionTypes = require("../helpers/transaction-types.js"); 2 | 3 | var private = {}, self = null, 4 | library = null, modules = null; 5 | 6 | function ExampleContract(cb, _library) { 7 | self = this; 8 | library = _library; 9 | cb(null, self); 10 | } 11 | 12 | ExampleContract.prototype.create = function (data, trs) { 13 | return trs; 14 | } 15 | 16 | ExampleContract.prototype.calculateFee = function (trs) { 17 | return 0; 18 | } 19 | 20 | ExampleContract.prototype.verify = function (trs, sender, cb, scope) { 21 | setImmediate(cb, null, trs); 22 | } 23 | 24 | ExampleContract.prototype.getBytes = function (trs) { 25 | return null; 26 | } 27 | 28 | ExampleContract.prototype.apply = function (trs, sender, cb, scope) { 29 | setImmediate(cb); 30 | } 31 | 32 | ExampleContract.prototype.undo = function (trs, sender, cb, scope) { 33 | setImmediate(cb); 34 | } 35 | 36 | ExampleContract.prototype.applyUnconfirmed = function (trs, sender, cb, scope) { 37 | setImmediate(cb); 38 | } 39 | 40 | ExampleContract.prototype.undoUnconfirmed = function (trs, sender, cb, scope) { 41 | setImmediate(cb); 42 | } 43 | 44 | ExampleContract.prototype.ready = function (trs, sender, cb, scope) { 45 | setImmediate(cb); 46 | } 47 | 48 | ExampleContract.prototype.save = function (trs, cb) { 49 | setImmediate(cb); 50 | } 51 | 52 | ExampleContract.prototype.dbRead = function (row) { 53 | return null; 54 | } 55 | 56 | ExampleContract.prototype.normalize = function (asset, cb) { 57 | setImmediate(cb); 58 | } 59 | 60 | ExampleContract.prototype.onBind = function (_modules) { 61 | modules = _modules; 62 | modules.logic.transaction.attachAssetType(__TYPE__, self); 63 | } 64 | 65 | module.exports = ExampleContract; 66 | -------------------------------------------------------------------------------- /plugins/crypto.js: -------------------------------------------------------------------------------- 1 | var inquirer = require("inquirer"); 2 | var cryptoLib = require("../lib/crypto.js"); 3 | var accountHelper = require("../helpers/account.js"); 4 | 5 | async function genPubkey() { 6 | let result = await inquirer.prompt([ 7 | { 8 | type: "password", 9 | name: "secret", 10 | message: "Enter secret of your testnet account" 11 | } 12 | ]); 13 | var account = accountHelper.account(result.secret.trim()); 14 | console.log("Public key: " + account.keypair.publicKey); 15 | console.log("Address: " + account.address); 16 | } 17 | 18 | async function genAccount() { 19 | let result = await inquirer.prompt([ 20 | { 21 | type: "input", 22 | name: "amount", 23 | message: "Enter number of accounts to generate" 24 | } 25 | ]); 26 | var n = parseInt(result.amount); 27 | var accounts = []; 28 | 29 | for (var i = 0; i < n; i++) { 30 | var a = accountHelper.account(cryptoLib.generateSecret()); 31 | accounts.push({ 32 | address: a.address, 33 | secret: a.secret, 34 | publicKey: a.keypair.publicKey 35 | }); 36 | } 37 | console.log(accounts); 38 | console.log("Done"); 39 | } 40 | 41 | module.exports = function (program) { 42 | program 43 | .command("crypto") 44 | .description("crypto operations") 45 | .option("-p, --pubkey", "generate public key from secret") 46 | .option("-g, --generate", "generate random accounts") 47 | .action(function (options) { 48 | (async function () { 49 | try { 50 | if (options.pubkey) { 51 | genPubkey(); 52 | } else if (options.generate) { 53 | genAccount(); 54 | } else { 55 | console.log("'node crypto -h' to get help"); 56 | } 57 | } catch (e) { 58 | console.log(e) 59 | } 60 | })() 61 | }); 62 | } -------------------------------------------------------------------------------- /lib/crypto.js: -------------------------------------------------------------------------------- 1 | var nacl_factory = require('js-nacl'); 2 | var crypto = require('crypto-browserify'); 3 | var bignum = require('browserify-bignum'); 4 | var Mnemonic = require('bitcore-mnemonic'); 5 | var nacl = nacl_factory.instantiate(); 6 | var AschJS = require('asch-js') 7 | 8 | var randomString = function (max) { 9 | var text = ""; 10 | var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789#$%^&*@"; 11 | 12 | for (var i = 0; i < max; i++) { 13 | text += possible.charAt(Math.floor(Math.random() * possible.length)); 14 | } 15 | 16 | return text; 17 | } 18 | 19 | var keypair = function (secret) { 20 | var hash = crypto.createHash('sha256').update(secret, 'utf8').digest(); 21 | var kp = nacl.crypto_sign_keypair_from_seed(hash); 22 | 23 | var keypair = { 24 | publicKey: new Buffer(kp.signPk).toString('hex'), 25 | privateKey: new Buffer(kp.signSk).toString('hex') 26 | } 27 | 28 | return keypair; 29 | } 30 | 31 | var sign = function (keypair, data) { 32 | var hash = crypto.createHash('sha256').update(data).digest(); 33 | var signature = nacl.crypto_sign_detached(hash, new Buffer(keypair.privateKey, 'hex')); 34 | return new Buffer(signature).toString('hex'); 35 | } 36 | 37 | var getId = function (data) { 38 | var hash = crypto.createHash('sha256').update(data).digest(); 39 | return hash.toString('hex'); 40 | } 41 | 42 | function generateSecret() { 43 | return new Mnemonic(Mnemonic.Words.ENGLISH).toString(); 44 | } 45 | 46 | function isValidSecret(secret) { 47 | return Mnemonic.isValid(secret); 48 | } 49 | 50 | function getAddress(publicKey) { 51 | return AschJS.crypto.getAddress(publicKey) 52 | } 53 | 54 | module.exports = { 55 | keypair: keypair, 56 | sign: sign, 57 | getId: getId, 58 | randomString: randomString, 59 | generateSecret: generateSecret, 60 | isValidSecret: isValidSecret, 61 | getAddress: getAddress 62 | } 63 | -------------------------------------------------------------------------------- /helpers/api.js: -------------------------------------------------------------------------------- 1 | var request = require('request'); 2 | 3 | function resultHandler(cb) { 4 | return function (err, resp, body) { 5 | if (err) { 6 | cb("Request error: " + err); 7 | } else if (resp.statusCode != 200) { 8 | var msg = "Unexpected status code: " + resp.statusCode; 9 | if (body.error) { 10 | msg += ", "; 11 | msg += body.error; 12 | } 13 | cb(msg); 14 | } else { 15 | if (!body.success) { 16 | cb("Server error: " + (body.error || body.message)); 17 | } else { 18 | cb(null, body); 19 | } 20 | } 21 | } 22 | } 23 | 24 | function Api(options) { 25 | this.options = options || {}; 26 | this.mainnet = this.options.mainnet; 27 | this.host = this.options.host || "127.0.0.1"; 28 | this.port = this.options.port || (this.mainnet ? 8192 : 4096); 29 | this.baseUrl = "http://" + this.host + ":" + this.port; 30 | this.magic = this.mainnet ? '5f5b3cf5' : '594fe0f3'; 31 | } 32 | 33 | Api.prototype.get = function (path, params, cb) { 34 | var qs = null; 35 | if (typeof params === 'function') { 36 | cb = params; 37 | } else { 38 | qs = params; 39 | } 40 | request({ 41 | method: "GET", 42 | url: this.baseUrl + path, 43 | json: true, 44 | qs: qs 45 | }, resultHandler(cb)); 46 | } 47 | 48 | Api.prototype.put = function (path, data, cb) { 49 | request({ 50 | method: "PUT", 51 | url: this.baseUrl + path, 52 | json: data 53 | }, resultHandler(cb)); 54 | } 55 | 56 | Api.prototype.post = function (path, data, cb) { 57 | request({ 58 | method: "POST", 59 | url: this.baseUrl + path, 60 | json: data 61 | }, resultHandler(cb)); 62 | } 63 | 64 | Api.prototype.broadcastTransaction = function (trs, cb) { 65 | request({ 66 | method: "POST", 67 | url: this.baseUrl + "/peer/transactions", 68 | // TODO magic should be read from a config file or options 69 | headers: { 70 | magic: this.magic, 71 | version: "" 72 | }, 73 | json: { 74 | transaction: trs 75 | } 76 | }, resultHandler(cb)); 77 | } 78 | 79 | module.exports = Api; 80 | -------------------------------------------------------------------------------- /helpers/dapp.js: -------------------------------------------------------------------------------- 1 | var cryptoLib = require('../lib/crypto.js'); 2 | var ByteBuffer = require('bytebuffer'); 3 | var bignum = require('browserify-bignum'); 4 | var crypto = require('crypto'); 5 | var dappTransactionsLib = require('../lib/dapptransactions.js'); 6 | var accounts = require('./account.js'); 7 | 8 | function getBytes(block, skipSignature) { 9 | var size = 8 + 4 + 4 + 4 + 32 + 32 + 8 + 4 + 4 + 64; 10 | 11 | var bb = new ByteBuffer(size, true); 12 | 13 | bb.writeString(block.prevBlockId || '0') 14 | 15 | bb.writeLong(block.height); 16 | bb.writeInt(block.timestamp); 17 | bb.writeInt(block.payloadLength); 18 | 19 | var ph = new Buffer(block.payloadHash, 'hex'); 20 | for (var i = 0; i < ph.length; i++) { 21 | bb.writeByte(ph[i]); 22 | } 23 | 24 | var pb = new Buffer(block.delegate, 'hex'); 25 | for (var i = 0; i < pb.length; i++) { 26 | bb.writeByte(pb[i]); 27 | } 28 | 29 | bb.writeString(block.pointId || '0') 30 | 31 | bb.writeLong(block.pointHeight || 0); 32 | 33 | bb.writeInt(block.count); 34 | 35 | if (!skipSignature && block.signature) { 36 | var pb = new Buffer(block.signature, 'hex'); 37 | for (var i = 0; i < pb.length; i++) { 38 | bb.writeByte(pb[i]); 39 | } 40 | } 41 | 42 | bb.flip(); 43 | var b = bb.toBuffer(); 44 | 45 | return b; 46 | } 47 | 48 | module.exports = { 49 | new: function (genesisAccount, publicKeys, assetInfo) { 50 | var sender = accounts.account(cryptoLib.generateSecret()); 51 | 52 | var block = { 53 | delegate: genesisAccount.keypair.publicKey, 54 | height: 1, 55 | pointId: null, 56 | pointHeight: null, 57 | transactions: [], 58 | timestamp: 0, 59 | payloadLength: 0, 60 | payloadHash: crypto.createHash('sha256') 61 | } 62 | 63 | if (assetInfo) { 64 | var assetTrs = { 65 | fee: '0', 66 | timestamp: 0, 67 | senderPublicKey: sender.keypair.publicKey, 68 | type: 3, 69 | args: [ 70 | assetInfo.name, 71 | String(Number(assetInfo.amount) * Math.pow(10, assetInfo.precision)), 72 | genesisAccount.address 73 | ] 74 | } 75 | bytes = dappTransactionsLib.getTransactionBytes(assetTrs); 76 | assetTrs.signature = cryptoLib.sign(sender.keypair, bytes); 77 | block.payloadLength += bytes.length; 78 | block.payloadHash.update(bytes); 79 | 80 | bytes = dappTransactionsLib.getTransactionBytes(assetTrs); 81 | assetTrs.id = cryptoLib.getId(bytes); 82 | block.transactions.push(assetTrs); 83 | } 84 | block.count = block.transactions.length; 85 | 86 | block.payloadHash = block.payloadHash.digest().toString('hex'); 87 | bytes = getBytes(block); 88 | block.signature = cryptoLib.sign(genesisAccount.keypair, bytes); 89 | bytes = getBytes(block); 90 | block.id = cryptoLib.getId(bytes); 91 | 92 | return block; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /lib/transactions.js: -------------------------------------------------------------------------------- 1 | var util = require('util'); 2 | var ByteBuffer = require('bytebuffer'); 3 | var crypto = require('./crypto.js'); 4 | var bignum = require('browserify-bignum'); 5 | 6 | var bytesTypes = { 7 | 2: function (trs) { 8 | try { 9 | var buf = new Buffer(trs.asset.delegate.username, 'utf8'); 10 | } catch (e) { 11 | throw Error(e.toString()); 12 | } 13 | 14 | return buf; 15 | }, 16 | 17 | 3: function (trs) { 18 | try { 19 | var buf = trs.asset.vote.votes ? new Buffer(trs.asset.vote.votes.join(''), 'utf8') : null; 20 | } catch (e) { 21 | throw Error(e.toString()); 22 | } 23 | 24 | return buf; 25 | }, 26 | 27 | 5: function (trs) { 28 | try { 29 | var buf = new Buffer([]); 30 | var nameBuf = new Buffer(trs.asset.dapp.name, 'utf8'); 31 | buf = Buffer.concat([buf, nameBuf]); 32 | 33 | if (trs.asset.dapp.description) { 34 | var descriptionBuf = new Buffer(trs.asset.dapp.description, 'utf8'); 35 | buf = Buffer.concat([buf, descriptionBuf]); 36 | } 37 | 38 | if (trs.asset.dapp.git) { 39 | buf = Buffer.concat([buf, new Buffer(trs.asset.dapp.git, 'utf8')]); 40 | } 41 | 42 | var bb = new ByteBuffer(4 + 4, true); 43 | bb.writeInt(trs.asset.dapp.type); 44 | bb.writeInt(trs.asset.dapp.category); 45 | bb.flip(); 46 | 47 | buf = Buffer.concat([buf, bb.toBuffer()]); 48 | } catch (e) { 49 | throw Error(e.toString()); 50 | } 51 | 52 | return buf; 53 | } 54 | } 55 | 56 | function getTransactionBytes(trs, skipSignature, skipSecondSignature) { 57 | var bb = new ByteBuffer(1, true); 58 | bb.writeInt(trs.type); 59 | bb.writeInt(trs.timestamp); 60 | bb.writeLong(trs.fee); 61 | bb.writeString(trs.senderId); 62 | 63 | if (trs.message) bb.writeString(trs.message); 64 | if (trs.args) { 65 | let args 66 | if (Array.isArray(trs.args)) { 67 | args = JSON.stringify(trs.args) 68 | } else if (typeof trs.args === 'string') { 69 | args = trs.args 70 | } else { 71 | throw new Error('Invalid transaction args') 72 | } 73 | bb.writeString(args) 74 | } 75 | 76 | if (!skipSignature && trs.signatures) { 77 | for (let signature of trs.signatures) { 78 | var signatureBuffer = new Buffer(signature, 'hex'); 79 | for (var i = 0; i < signatureBuffer.length; i++) { 80 | bb.writeByte(signatureBuffer[i]); 81 | } 82 | } 83 | } 84 | 85 | if (!skipSecondSignature && trs.signSignature) { 86 | var signSignatureBuffer = new Buffer(trs.signSignature, 'hex'); 87 | for (var i = 0; i < signSignatureBuffer.length; i++) { 88 | bb.writeByte(signSignatureBuffer[i]); 89 | } 90 | } 91 | 92 | bb.flip(); 93 | 94 | return bb.toBuffer(); 95 | } 96 | 97 | module.exports = { 98 | getTransactionBytes: getTransactionBytes 99 | } 100 | -------------------------------------------------------------------------------- /plugins/contract.js: -------------------------------------------------------------------------------- 1 | 2 | var inquirer = require("inquirer"); 3 | var fs = require("fs"); 4 | var path = require("path"); 5 | 6 | var contractsPath = path.join(".", "modules", "contracts"); 7 | 8 | function addContract() { 9 | try { 10 | var filenames = fs.readdirSync(contractsPath); 11 | inquirer.prompt([ 12 | { 13 | type: "input", 14 | name: "filename", 15 | message: "Contract file name (without .js)" 16 | } 17 | ], function (result) { 18 | var name = result.filename; 19 | var type = filenames.length; 20 | var filename = result.filename + ".js"; 21 | 22 | var className = ''; 23 | for (var i = 0; i < name.length; ++i) { 24 | className += (i==0 ? name[i].toUpperCase() : name[i]); 25 | } 26 | var exampleContract = fs.readFileSync(path.join(__dirname, "..", "contract-example.js"), "utf8"); 27 | exampleContract = exampleContract.replace(/ExampleContract/g, className); 28 | exampleContract = exampleContract.replace(/__TYPE__/g, 'TransactionTypes.' + name.toUpperCase()); 29 | fs.writeFileSync(path.join(contractsPath, filename), exampleContract, "utf8"); 30 | 31 | var typesFile = path.resolve("./modules/helpers/transaction-types.js"); 32 | var transactionTypes = require(typesFile); 33 | transactionTypes[name.toUpperCase()] = type; 34 | fs.writeFileSync(typesFile, 'module.exports = ' + JSON.stringify(transactionTypes, null, 2), "utf8"); 35 | 36 | console.log("New contract created: " + ("./contracts/" + filename)); 37 | console.log("Updating contracts list"); 38 | 39 | var text = fs.readFileSync(path.join(".", "modules.full.json"), "utf8"); 40 | var modules = JSON.parse(text); 41 | var contractName = "contracts/" + name; 42 | var dappPathConfig = "./" + path.join(contractsPath, filename); 43 | 44 | modules[contractName] = dappPathConfig; 45 | modules = JSON.stringify(modules, false, 2); 46 | 47 | fs.writeFileSync(path.join(".", "modules.full.json"), modules, "utf8"); 48 | console.log("Done"); 49 | }); 50 | } catch (e) { 51 | console.log(e); 52 | } 53 | } 54 | 55 | function deleteContract() { 56 | inquirer.prompt([ 57 | { 58 | type: "input", 59 | name: "filename", 60 | message: "Contract file name (without .js)" 61 | } 62 | ], function (result) { 63 | var name = result.filename; 64 | var type = filenames.length + 1; 65 | var filename = result.filename + ".js"; 66 | 67 | var contractPath = path.join(contractsPath, filename); 68 | var exists = fs.existsSync(contractPath); 69 | if (!exists) { 70 | return console.log("Contract not found: " + contractPath); 71 | } 72 | try { 73 | fs.unlinkSync(contractPath); 74 | console.log("Contract removed"); 75 | console.log("Updating contracts list"); 76 | 77 | var text = fs.readFileSync(path.join(".", "modules.full.json"), "utf8"); 78 | var modules = JSON.parse(text); 79 | var name = "contracts/" + name; 80 | delete modules[name]; 81 | modules = JSON.stringify(modules, false, 2); 82 | fs.writeFileSync(path.join(".", "modules.full.json"), modules, "utf8"); 83 | console.log("Done"); 84 | } catch (e) { 85 | console.log(e); 86 | } 87 | }); 88 | } 89 | 90 | module.exports = function (program) { 91 | program 92 | .command("contract") 93 | .description("contract operations") 94 | .option("-a, --add", "add new contract") 95 | .option("-d, --delete", "delete contract") 96 | .action(function (options) { 97 | var exist = fs.existsSync(contractsPath); 98 | if (exist) { 99 | if (options.add) { 100 | addContract(); 101 | } else if (options.delete) { 102 | deleteContract(); 103 | } else { 104 | console.log("'node contract -h' to get help"); 105 | } 106 | } else { 107 | return console.log("./modules/contracts path not found, please change directory to your dapp folder"); 108 | } 109 | }); 110 | } 111 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/AschPlatform/asch-cli.png?branch=master)](https://travis-ci.org/AschPlatform/asch-cli) 2 | [![Author](https://img.shields.io/badge/author-@AschPlatform-blue.svg?style=flat)](http://github.com/AschPlatform) 3 | [![License](https://img.shields.io/badge/license-MIT-yellow.svg?style=flat)](http://AschPlatform.mit-license.org) 4 | [![NpmDownload Status](http://img.shields.io/npm/dm/asch-cli.svg)](https://www.npmjs.org/package/asch-cli) 5 | [![NPM Version](https://img.shields.io/npm/v/asch-cli.svg?style=flat)](https://www.npmjs.org/package/asch-cli) 6 | - - - 7 | 8 | # Asch Client 9 | 10 | A command line interface for bootstrapping and managing [Asch](https://github.com/AschPlatform) blockchain apps. 11 | 12 | ## Installation 13 | 14 | 由于依赖的inquirer模块在低版本node下存在bug 15 | 最新的dapps系列子命令要求node版本号为v8.4.0以上 16 | 17 | ``` 18 | npm install -g asch-cli 19 | ``` 20 | 21 | 如在某些Linux发行版运行 asch-cli 报类似错“/usr/bin/env: ‘node’: No such file or directory”,即node版本过低或缺少node,可先执行: 22 | 23 | ``` 24 | npm install -g n 25 | n stable 26 | ``` 27 | 28 | ## Usage 29 | 30 | ``` 31 | ./bin/asch-cli --help 32 | 33 | Usage: asch-cli [options] [command] 34 | 35 | 36 | Commands: 37 | 38 | getheight get block height 39 | getblockstatus get block status 40 | openaccount [secret] open your account and get the infomation by secret 41 | openaccountbypublickey [publickey] open your account and get the infomation by publickey 42 | getbalance [address] get balance by address 43 | getaccount [address] get account by address 44 | getvoteddelegates [options] [address] get delegates voted by address 45 | getdelegatescount get delegates count 46 | getdelegates [options] get delegates 47 | getvoters [publicKey] get voters of a delegate by public key 48 | getdelegatebypublickey [publicKey] get delegate by public key 49 | getdelegatebyusername [username] get delegate by username 50 | getblocks [options] get blocks 51 | getblockbyid [id] get block by id 52 | getblockbyheight [height] get block by height 53 | getpeers [options] get peers 54 | getunconfirmedtransactions [options] get unconfirmed transactions 55 | gettransactions [options] get transactions 56 | gettransaction [id] get transactions 57 | sendmoney [options] send money to some address 58 | registerdelegate [options] register delegate 59 | upvote [options] vote for delegates 60 | downvote [options] cancel vote for delegates 61 | setsecondsecret [options] set second secret 62 | registerdapp [options] register a dapp 63 | contract [options] contract operations 64 | crypto [options] crypto operations 65 | dapps [options] manage your dapps 66 | creategenesis [options] create genesis block 67 | peerstat analyze block height of all peers 68 | delegatestat analyze delegates status 69 | ipstat analyze peer ip info 70 | 71 | Options: 72 | 73 | -h, --help output usage information 74 | -V, --version output the version number 75 | -H, --host Specify the hostname or ip of the node, default: 127.0.0.1 76 | -P, --port Specify the port of the node, default: 4096 77 | -M, --main Specify the mainnet, default: false 78 | ``` 79 | 80 | ## Detailed asch-cli Documentation 81 | 82 | * [Chinese](https://github.com/AschPlatform/asch-docs/blob/master/cli_usage/zh-cn.md) 83 | * [English](https://github.com/AschPlatform/asch-docs/blob/master/cli_usage/en.md) 84 | * [German](https://github.com/AschPlatform/asch-docs/blob/master/cli_usage/de.md) 85 | -------------------------------------------------------------------------------- /helpers/block.js: -------------------------------------------------------------------------------- 1 | var crypto = require('crypto'); 2 | var fs = require('fs'); 3 | var cryptoLib = require('../lib/crypto.js'); 4 | var transactionsLib = require('../lib/transactions.js'); 5 | var accounts = require('./account.js'); 6 | var ByteBuffer = require('bytebuffer'); 7 | 8 | var sender = accounts.account(cryptoLib.generateSecret()); 9 | 10 | function getBytes(block, skipSignature) { 11 | var size = 4 + 4 + 8 + 4 + 8 + 8 + 8 + 4 + 32 + 32 + 64; 12 | 13 | var bb = new ByteBuffer(size, true); 14 | bb.writeInt(block.version); 15 | bb.writeInt(block.timestamp); 16 | bb.writeLong(block.height); 17 | bb.writeInt(block.count); 18 | bb.writeLong(block.fees); 19 | bb.writeLong(block.reward); 20 | bb.writeString(block.delegate); 21 | 22 | if (block.previousBlock) { 23 | bb.writeString(block.previousBlock) 24 | } else { 25 | bb.writeString('0') 26 | } 27 | 28 | var payloadHashBuffer = new Buffer(block.payloadHash, 'hex'); 29 | for (var i = 0; i < payloadHashBuffer.length; i++) { 30 | bb.writeByte(payloadHashBuffer[i]); 31 | } 32 | 33 | 34 | if (!skipSignature && block.signature) { 35 | var signatureBuffer = new Buffer(block.signature, 'hex'); 36 | for (var i = 0; i < signatureBuffer.length; i++) { 37 | bb.writeByte(signatureBuffer[i]); 38 | } 39 | } 40 | 41 | bb.flip(); 42 | var b = bb.toBuffer(); 43 | 44 | return b; 45 | } 46 | 47 | function signTransaction(trs, keypair) { 48 | let bytes = transactionsLib.getTransactionBytes(trs) 49 | trs.signatures.push(cryptoLib.sign(sender.keypair, bytes)) 50 | bytes = transactionsLib.getTransactionBytes(trs) 51 | trs.id = cryptoLib.getId(bytes) 52 | return trs 53 | } 54 | 55 | module.exports = { 56 | getBytes: getBytes, 57 | new: function (genesisAccount, dapp, accountsFile) { 58 | var payloadLength = 0, 59 | payloadHash = crypto.createHash('sha256'), 60 | transactions = [], 61 | totalAmount = 0, 62 | delegates = []; 63 | 64 | // fund recipient account 65 | if (accountsFile && fs.existsSync(accountsFile)) { 66 | var lines = fs.readFileSync(accountsFile, 'utf8').split('\n'); 67 | for (var i in lines) { 68 | var parts = lines[i].split('\t'); 69 | if (parts.length != 2) { 70 | console.error('Invalid recipient balance format'); 71 | process.exit(1); 72 | } 73 | var amount = String(Number(parts[1]) * 100000000) 74 | var trs = { 75 | type: 1, 76 | fee: 0, 77 | timestamp: 0, 78 | senderId: sender.address, 79 | senderPublicKey: sender.keypair.publicKey, 80 | signatures: [], 81 | message: '', 82 | args: [Number(amount), parts[0]] 83 | }; 84 | 85 | transactions.push(signTransaction(trs, sender.keypair)); 86 | } 87 | } else { 88 | var balanceTransaction = { 89 | type: 1, 90 | fee: 0, 91 | timestamp: 0, 92 | senderId: sender.address, 93 | senderPublicKey: sender.keypair.publicKey, 94 | signatures: [], 95 | message: '', 96 | args: [10000000000000000, genesisAccount.address] 97 | }; 98 | 99 | transactions.push(signTransaction(balanceTransaction, sender.keypair)); 100 | } 101 | 102 | // make delegates 103 | for (var i = 0; i < 101; i++) { 104 | var delegate = accounts.account(cryptoLib.generateSecret()); 105 | 106 | var username = "asch_g" + (i + 1); 107 | delegate.name = username 108 | delegates.push(delegate); 109 | 110 | var nameTrs = { 111 | type: 2, 112 | fee: 0, 113 | timestamp: 0, 114 | senderId: delegate.address, 115 | senderPublicKey: delegate.keypair.publicKey, 116 | signatures: [], 117 | args: [username], 118 | message: '' 119 | } 120 | var delegateTrs = { 121 | type: 10, 122 | fee: 0, 123 | timestamp: 0, 124 | senderId: delegate.address, 125 | senderPublicKey: delegate.keypair.publicKey, 126 | signatures: [], 127 | message: '' 128 | } 129 | 130 | transactions.push(signTransaction(nameTrs, delegate.keypair)); 131 | transactions.push(signTransaction(delegateTrs, delegate.keypair)); 132 | } 133 | 134 | // make votes 135 | var delegateNames = delegates.map(function (delegate) { 136 | return delegate.name; 137 | }); 138 | 139 | var voteTransaction = { 140 | type: 11, 141 | fee: 0, 142 | timestamp: 0, 143 | senderId: genesisAccount.address, 144 | senderPublicKey: genesisAccount.keypair.publicKey, 145 | signatures: [], 146 | args: [delegateNames.join(',')], 147 | message: '' 148 | } 149 | 150 | transactions.forEach(function (tx) { 151 | bytes = transactionsLib.getTransactionBytes(tx); 152 | payloadLength += bytes.length; 153 | payloadHash.update(bytes); 154 | }); 155 | 156 | payloadHash = payloadHash.digest(); 157 | 158 | var block = { 159 | version: 0, 160 | payloadHash: payloadHash.toString('hex'), 161 | timestamp: 0, 162 | previousBlock: null, 163 | delegate: sender.keypair.publicKey, 164 | transactions: transactions, 165 | height: 0, 166 | count: transactions.length, 167 | fees: 0, 168 | reward: 0, 169 | }; 170 | 171 | bytes = getBytes(block); 172 | block.signature = cryptoLib.sign(sender.keypair, bytes); 173 | bytes = getBytes(block); 174 | block.id = cryptoLib.getId(bytes); 175 | 176 | return { 177 | block: block, 178 | delegates: delegates 179 | }; 180 | }, 181 | 182 | from: function (genesisBlock, genesisAccount, dapp) { 183 | for (var i in genesisBlock.transactions) { 184 | var tx = genesisBlock.transactions[i]; 185 | 186 | if (tx.type == 5) { 187 | if (tx.asset.dapp.name == dapp.name) { 188 | throw new Error("DApp with name '" + dapp.name + "' already exists in genesis block"); 189 | } 190 | 191 | if (tx.asset.dapp.git == dapp.git) { 192 | throw new Error("DApp with git '" + dapp.git + "' already exists in genesis block"); 193 | } 194 | 195 | if (tx.asset.dapp.link == dapp.link) { 196 | throw new Error("DApp with link '" + dapp.link + "' already exists in genesis block"); 197 | } 198 | } 199 | } 200 | 201 | var dappTransaction = { 202 | type: 5, 203 | amount: 0, 204 | fee: 0, 205 | timestamp: 0, 206 | senderId: genesisAccount.address, 207 | senderPublicKey: genesisAccount.keypair.publicKey, 208 | asset: { 209 | dapp: dapp 210 | } 211 | }; 212 | 213 | var bytes = transactionsLib.getTransactionBytes(dappTransaction); 214 | dappTransaction.signature = cryptoLib.sign(genesisAccount.keypair, bytes); 215 | bytes = transactionsLib.getTransactionBytes(dappTransaction); 216 | dappTransaction.id = cryptoLib.getId(bytes); 217 | 218 | genesisBlock.payloadLength += bytes.length; 219 | var payloadHash = crypto.createHash('sha256').update(new Buffer(genesisBlock.payloadHash, 'hex')); 220 | payloadHash.update(bytes); 221 | genesisBlock.payloadHash = payloadHash.digest().toString('hex'); 222 | 223 | genesisBlock.transactions.push(dappTransaction); 224 | genesisBlock.numberOfTransactions += 1; 225 | genesisBlock.generatorPublicKey = sender.keypair.publicKey; 226 | 227 | bytes = getBytes(genesisBlock); 228 | genesisBlock.blockSignature = cryptoLib.sign(sender.keypair, bytes); 229 | bytes = getBytes(genesisBlock); 230 | genesisBlock.id = cryptoLib.getId(bytes); 231 | 232 | return { 233 | block: genesisBlock, 234 | dapp: dappTransaction 235 | }; 236 | } 237 | } 238 | -------------------------------------------------------------------------------- /plugins/misc.js: -------------------------------------------------------------------------------- 1 | var fs = require("fs"); 2 | var async = require("async"); 3 | var request = require("request"); 4 | var accountHelper = require("../helpers/account.js"); 5 | var blockHelper = require("../helpers/block.js"); 6 | var cryptoLib = require("../lib/crypto.js"); 7 | var dappHelper = require("../helpers/dapp.js"); 8 | var Api = require('../helpers/api.js'); 9 | var AschUtils = require('asch-js').utils; 10 | 11 | var globalOptions; 12 | 13 | function getApi() { 14 | return new Api({host: globalOptions.host, port: globalOptions.port, mainnet: !!globalOptions.main}); 15 | } 16 | 17 | function writeFileSync(file, obj) { 18 | var content = (typeof obj === "string" ? obj : JSON.stringify(obj, null, 2)); 19 | fs.writeFileSync(file, content, "utf8"); 20 | } 21 | 22 | function appendFileSync(file, obj) { 23 | var content = (typeof obj === "string" ? obj : JSON.stringify(obj, null, 2)); 24 | fs.appendFileSync(file, content, "utf8"); 25 | } 26 | 27 | function genGenesisBlock(options) { 28 | var genesisAccount = accountHelper.account(cryptoLib.generateSecret()); 29 | var newBlockInfo = blockHelper.new(genesisAccount, null, options.file); 30 | var delegateSecrets = newBlockInfo.delegates.map(function(i) { 31 | return i.secret; 32 | }); 33 | writeFileSync("./genesisBlock.json", newBlockInfo.block); 34 | 35 | var logFile = "./genGenesisBlock.log"; 36 | writeFileSync(logFile, "genesis account:\n"); 37 | appendFileSync(logFile, genesisAccount); 38 | appendFileSync(logFile, "\ndelegates secrets:\n"); 39 | appendFileSync(logFile, delegateSecrets); 40 | console.log('New genesis block and related account has been created, please see the two file: genesisBlock.json and genGenesisBlock.log'); 41 | } 42 | 43 | function peerstat() { 44 | var api = getApi(); 45 | api.get('/api/peers/', {}, function (err, result) { 46 | if (err) { 47 | console.log('Failed to get peers', err); 48 | return; 49 | } 50 | async.map(result.peers, function (peer, next) { 51 | new Api({host: peer.ip, port: peer.port}).get('/api/blocks/getHeight', function (err, result) { 52 | if (err) { 53 | console.log('%s:%d %s %d', peer.ip, peer.port, peer.version, err); 54 | next(null, {peer: peer, error: err}); 55 | } else { 56 | console.log('%s:%d %s %d', peer.ip, peer.port, peer.version, result.height); 57 | next(null, {peer: peer, height: result.height}); 58 | } 59 | }); 60 | }, function (err, results) { 61 | var heightMap = {}; 62 | var errorMap = {}; 63 | for (var i = 0; i < results.length; ++i) { 64 | var item = results[i]; 65 | if (item.error) { 66 | if (!errorMap[item.error]) { 67 | errorMap[item.error] = []; 68 | } 69 | errorMap[item.error].push(item.peer); 70 | } else { 71 | if (!heightMap[item.height]) { 72 | heightMap[item.height] = []; 73 | } 74 | heightMap[item.height].push(item.peer); 75 | } 76 | } 77 | var normalList = []; 78 | var errList = []; 79 | for (var k in heightMap) { 80 | normalList.push({peers: heightMap[k], height: k}); 81 | } 82 | for (var k in errorMap) { 83 | errList.push({peers: errorMap[k], error: k}); 84 | } 85 | normalList.sort(function (l, r) { 86 | return r.height - l.height; 87 | }); 88 | 89 | function joinPeerAddrs(peers) { 90 | var peerAddrs = []; 91 | peers.forEach(function (p) { 92 | peerAddrs.push(p.ip + ':' + p.port); 93 | }); 94 | return peerAddrs.join(','); 95 | } 96 | console.log('======================================'); 97 | for (var i = 0; i < normalList.length; ++i) { 98 | var item = normalList[i]; 99 | if (i == 0) { 100 | console.log(item.peers.length + ' height: ' + item.height); 101 | } else { 102 | console.log(item.peers.length + ' height: ' + item.height, joinPeerAddrs(item.peers)); 103 | } 104 | } 105 | for (var i = 0; i < errList.length; ++i) { 106 | var item = errList[i]; 107 | console.log(item.peers.length + ' error: ' + item.error, joinPeerAddrs(item.peers)); 108 | } 109 | }); 110 | }); 111 | } 112 | 113 | function delegatestat() { 114 | var api = getApi(); 115 | api.get('/api/delegates', {}, function (err, result) { 116 | if (err) { 117 | console.log('Failed to get delegates', err); 118 | return; 119 | } 120 | async.map(result.delegates, function (delegate, next) { 121 | var params = { 122 | generatorPublicKey: delegate.publicKey, 123 | limit: 1, 124 | offset: 0, 125 | orderBy: 'height:desc' 126 | }; 127 | api.get('/api/blocks', params, function (err, result) { 128 | if (err) { 129 | next(err); 130 | } else { 131 | next(null, {delegate: delegate, block: result.blocks[0]}); 132 | } 133 | }); 134 | }, function (err, delegates) { 135 | if (err) { 136 | console.log('Failed to get forged block', err); 137 | return; 138 | } 139 | delegates = delegates.sort(function (l, r) { 140 | if (!l.block) { 141 | return -1; 142 | } 143 | if (!r.block) { 144 | return 1; 145 | } 146 | return l.block.timestamp - r.block.timestamp; 147 | }); 148 | console.log("name\taddress\trate\tapproval\tproductivity\tproduced\tbalance\theight\tid\ttime"); 149 | for (var i in delegates) { 150 | var d = delegates[i].delegate; 151 | var b = delegates[i].block; 152 | console.log('%s\t%s\t%d\t%s%%\t%s%%\t%d\t%d\t%s\t%s\t%s(%s)', 153 | d.username, 154 | d.address, 155 | d.rate, 156 | d.approval, 157 | d.productivity, 158 | d.producedblocks, 159 | d.balance / 100000000, 160 | b ? b.height : '', 161 | b ? b.id : '', 162 | AschUtils.format.fullTimestamp(b ? b.timestamp : ''), 163 | AschUtils.format.timeAgo(b ? b.timestamp : '')); 164 | } 165 | }); 166 | }); 167 | } 168 | 169 | function ipstat() { 170 | var api = getApi(); 171 | api.get('/api/peers/', {}, function (err, result) { 172 | if (err) { 173 | console.log('Failed to get peers', err); 174 | return; 175 | } 176 | async.mapLimit(result.peers, 5, function (peer, next) { 177 | var url = 'http://ip.taobao.com/service/getIpInfo.php?ip=' + peer.ip; 178 | request(url, function (err, resp, body) { 179 | if (err || resp.statusCode != 200) { 180 | console.error('Failed to get ip info:', err); 181 | next(null, {}); 182 | } else { 183 | next(null, JSON.parse(body).data); 184 | } 185 | }); 186 | }, function (err, ips) { 187 | for (var i = 0; i < ips.length; ++i) { 188 | var ip = ips[i]; 189 | if (ip.country_id) { 190 | console.log('%s\t%s', ip.country, ip.country_id); 191 | } 192 | } 193 | }); 194 | }); 195 | } 196 | 197 | module.exports = function(program) { 198 | globalOptions = program; 199 | 200 | program 201 | .command("creategenesis") 202 | .description("create genesis block") 203 | .option("-f, --file ", "genesis accounts balance file") 204 | .action(genGenesisBlock); 205 | 206 | program 207 | .command("peerstat") 208 | .description("analyze block height of all peers") 209 | .action(peerstat); 210 | 211 | program 212 | .command("delegatestat") 213 | .description("analyze delegates status") 214 | .action(delegatestat); 215 | 216 | program 217 | .command("ipstat") 218 | .description("analyze peer ip info") 219 | .action(ipstat); 220 | } -------------------------------------------------------------------------------- /template/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Asch DApp Example 1 - hello world 8 | 9 | 10 | 166 | 167 | 168 |

Asch DApp Example 1 - hello world

169 |
170 | 171 | 172 |
173 | 200 | 201 | 202 | 203 | -------------------------------------------------------------------------------- /plugins/chain.js: -------------------------------------------------------------------------------- 1 | 2 | var inquirer = require("inquirer"); 3 | var gift = require("gift"); 4 | var fs = require("fs"); 5 | var shell = require("shelljs"); 6 | var async = require('async'); 7 | var path = require("path"); 8 | var request = require("request"); 9 | var valid_url = require("valid-url"); 10 | var AschJS = require('asch-js'); 11 | var accountHelper = require("../helpers/account.js"); 12 | var blockHelper = require("../helpers/block.js"); 13 | var dappHelper = require("../helpers/dapp.js"); 14 | var Api = require("../helpers/api.js"); 15 | 16 | var templatePath = path.join(__dirname, "..", "template"); 17 | 18 | function bip39Validator(input) { 19 | var done = this.async(); 20 | 21 | if (!accountHelper.isValidSecret(input)) { 22 | done("Secret is not validated by BIP39"); 23 | return; 24 | } 25 | 26 | done(null, true); 27 | } 28 | 29 | function assetNameValidator(input) { 30 | let done = this.async() 31 | if (!input || !/^[A-Z]{3,6}$/.test(input)) { 32 | return done('Invalid currency symbol') 33 | } 34 | done(null, true) 35 | } 36 | 37 | function amountValidator(input) { 38 | let done = this.async() 39 | if (!/^[1-9][0-9]*$/.test(input)) { 40 | return done('Amount should be integer') 41 | } 42 | done(null, true) 43 | } 44 | 45 | function precisionValidator(input) { 46 | let done = this.async() 47 | let precision = Number(input) 48 | if (!Number.isInteger(precision) || precision < 0 || precision > 16) { 49 | return done('Precision is between 0 and 16') 50 | } 51 | done(null, true) 52 | } 53 | 54 | async function prompt(question) { 55 | if (Array.isArray(question)) { 56 | return await inquirer.prompt(question) 57 | } else { 58 | let answer = await inquirer.prompt([question]) 59 | return answer[question.name] 60 | } 61 | } 62 | 63 | async function createChainMetaFile() { 64 | let answer = await prompt([ 65 | { 66 | type: "input", 67 | name: "name", 68 | message: "Enter chain name", 69 | required: true, 70 | validate: function (value) { 71 | var done = this.async(); 72 | if (value.length == 0) { 73 | done("Chain name is too short, minimum is 1 character"); 74 | return; 75 | } 76 | if (value.length > 32) { 77 | done("Chain name is too long, maximum is 32 characters"); 78 | return; 79 | } 80 | return done(null, true) 81 | } 82 | }, 83 | { 84 | type: "input", 85 | name: "desc", 86 | message: "Enter chain description", 87 | validate: function (value) { 88 | var done = this.async(); 89 | 90 | if (value.length > 160) { 91 | done("Chain description is too long, maximum is 160 characters"); 92 | return; 93 | } 94 | 95 | return done(null, true); 96 | } 97 | }, 98 | { 99 | type: "input", 100 | name: "link", 101 | message: "Enter chain link", 102 | required: true, 103 | validate: function (value) { 104 | var done = this.async(); 105 | 106 | if (!valid_url.isUri(value)) { 107 | done("Invalid chain link, must be a valid url"); 108 | return; 109 | } 110 | if (value.indexOf(".zip") != value.length - 4) { 111 | done("Invalid chain link, does not link to zip file"); 112 | return; 113 | } 114 | if (value.length > 160) { 115 | return done("Chain link is too long, maximum is 160 characters"); 116 | } 117 | 118 | return done(null, true); 119 | } 120 | }, 121 | { 122 | type: "input", 123 | name: "icon", 124 | message: "Enter chain icon url", 125 | validate: function (value) { 126 | var done = this.async(); 127 | 128 | if (!valid_url.isUri(value)) { 129 | return done("Invalid chain icon, must be a valid url"); 130 | } 131 | var extname = path.extname(value); 132 | if (['.png', '.jpg', '.jpeg'].indexOf(extname) == -1) { 133 | return done("Invalid chain icon file type"); 134 | } 135 | if (value.length > 160) { 136 | return done("Chain icon url is too long, maximum is 160 characters"); 137 | } 138 | 139 | return done(null, true); 140 | } 141 | }, 142 | { 143 | type: "input", 144 | name: "delegates", 145 | message: "Enter public keys of chain delegates - hex array, use ',' for separator", 146 | validate: function (value) { 147 | var done = this.async(); 148 | 149 | var publicKeys = value.split(","); 150 | 151 | if (publicKeys.length == 0) { 152 | done("Chain requires at least 1 delegate public key"); 153 | return; 154 | } 155 | 156 | for (var i in publicKeys) { 157 | try { 158 | var b = new Buffer(publicKeys[i], "hex"); 159 | if (b.length != 32) { 160 | done("Invalid public key: " + publicKeys[i]); 161 | return; 162 | } 163 | } catch (e) { 164 | done("Invalid hex for public key: " + publicKeys[i]); 165 | return; 166 | } 167 | } 168 | done(null, true); 169 | } 170 | }, 171 | { 172 | type: "input", 173 | name: "unlockDelegates", 174 | message: "How many delegates are needed to unlock asset of a chain?", 175 | validate: function (value) { 176 | var done = this.async(); 177 | var n = Number(value); 178 | if (!Number.isInteger(n) || n < 3 || n > 101) { 179 | return done("Invalid unlockDelegates"); 180 | } 181 | done(null, true); 182 | } 183 | } 184 | ]) 185 | var chainMetaInfo = { 186 | name: answer.name, 187 | link: answer.link, 188 | desc: answer.desc || "", 189 | icon: answer.icon || "", 190 | delegates: answer.delegates.split(","), 191 | unlockDelegates: Number(answer.unlockDelegates) 192 | } 193 | var chainMetaJson = JSON.stringify(chainMetaInfo, null, 2); 194 | fs.writeFileSync("./chain.json", chainMetaJson, "utf8"); 195 | console.log("Chain meta information is saved to ./chain.json ..."); 196 | } 197 | 198 | async function createChain() { 199 | console.log('Copying template to the current directory ...') 200 | shell.cp('-R', templatePath + '/*', '.') 201 | await createChainMetaFile() 202 | } 203 | 204 | async function depositChain() { 205 | let result = await inquirer.prompt([ 206 | { 207 | type: "password", 208 | name: "secret", 209 | message: "Enter secret", 210 | validate: bip39Validator, 211 | required: true 212 | }, 213 | { 214 | type: "input", 215 | name: "amount", 216 | message: "Enter amount", 217 | validate: function (value) { 218 | return !isNaN(parseInt(value)); 219 | }, 220 | required: true 221 | }, 222 | { 223 | type: "input", 224 | name: "chain", 225 | message: "chain name", 226 | required: true 227 | }, 228 | { 229 | type: "input", 230 | name: "secondSecret", 231 | message: "Enter secondary secret (if defined)", 232 | validate: function (message) { 233 | return message.length < 100; 234 | }, 235 | required: false 236 | } 237 | ]); 238 | 239 | 240 | var realAmount = parseFloat((parseInt(result.amount) * 100000000).toFixed(0)); 241 | var body = { 242 | secret: result.secret, 243 | chain: result.chain, 244 | amount: realAmount 245 | }; 246 | 247 | if (result.secondSecret && result.secondSecret.length > 0) { 248 | body.secondSecret = result.secondSecret; 249 | } 250 | 251 | let hostResult = await inquirer.prompt([ 252 | { 253 | type: "input", 254 | name: "host", 255 | message: "Host and port", 256 | default: "localhost:4096", 257 | required: true 258 | } 259 | ]); 260 | 261 | request({ 262 | url: "http://" + hostResult.host + "/api/chains/transaction", 263 | method: "put", 264 | json: true, 265 | body: body 266 | }, function (err, resp, body) { 267 | 268 | if (err) { 269 | return console.log(err.toString()); 270 | } 271 | 272 | if (body.success) { 273 | console.log(body.transactionId); 274 | return; 275 | } else { 276 | return console.log(body.error); 277 | } 278 | }); 279 | } 280 | 281 | async function withdrawalChain() { 282 | let result = await inquirer.prompt([ 283 | { 284 | type: "password", 285 | name: "secret", 286 | message: "Enter secret", 287 | validate: bip39Validator, 288 | required: true 289 | }, 290 | { 291 | type: "input", 292 | name: "amount", 293 | message: "Amount", 294 | validate: function (value) { 295 | return !isNaN(parseInt(value)); 296 | }, 297 | required: true 298 | }, 299 | { 300 | type: "input", 301 | name: "chain", 302 | message: "Enter chain name", 303 | validate: function (value) { 304 | var isAddress = /^[0-9]+$/g; 305 | return isAddress.test(value); 306 | }, 307 | required: true 308 | }]); 309 | 310 | var body = { 311 | secret: result.secret, 312 | amount: Number(result.amount) 313 | }; 314 | 315 | request({ 316 | url: "http://localhost:4096/api/chains/" + result.chain + "/api/withdrawal", 317 | method: "post", 318 | json: true, 319 | body: body 320 | }, function (err, resp, body) { 321 | if (err) { 322 | return console.log(err.toString()); 323 | } 324 | 325 | if (body.success) { 326 | console.log(body.transactionId); 327 | } else { 328 | return console.log(body.error); 329 | } 330 | }); 331 | } 332 | 333 | async function uninstallChain() { 334 | let result = await inquirer.prompt([ 335 | { 336 | type: "input", 337 | name: "chain", 338 | message: "Enter chain name", 339 | validate: function (value) { 340 | return value.length > 0 && value.length < 100; 341 | }, 342 | required: true 343 | }, 344 | { 345 | type: "input", 346 | name: "host", 347 | message: "Host and port", 348 | default: "localhost:4096", 349 | required: true 350 | }, 351 | { 352 | type: "password", 353 | name: "masterpassword", 354 | message: "Enter chain master password", 355 | required: true 356 | }]); 357 | 358 | var body = { 359 | id: String(result.chain), 360 | master: String(result.masterpassword) 361 | }; 362 | 363 | request({ 364 | url: "http://" + result.host + "/api/chains/uninstall", 365 | method: "post", 366 | json: true, 367 | body: body 368 | }, function (err, resp, body) { 369 | if (err) { 370 | return console.log(err.toString()); 371 | } 372 | 373 | if (body.success) { 374 | console.log("Done!"); 375 | } else { 376 | return console.log(body.error); 377 | } 378 | }); 379 | } 380 | 381 | async function installChain() { 382 | let result = await inquirer.prompt([ 383 | { 384 | type: "input", 385 | name: "chain", 386 | message: "Enter chain name", 387 | validate: function (value) { 388 | return value.length > 0 && value.length < 100; 389 | }, 390 | required: true 391 | }, 392 | { 393 | type: "input", 394 | name: "host", 395 | message: "Host and port", 396 | default: "localhost:4096", 397 | required: true 398 | }, 399 | { 400 | type: "input", 401 | name: "masterpassword", 402 | message: "Enter chain master password", 403 | required: true 404 | }]); 405 | 406 | var body = { 407 | name: String(result.chain), 408 | master: String(result.masterpassword) 409 | }; 410 | 411 | request({ 412 | url: "http://" + result.host + "/api/chains/install", 413 | method: "post", 414 | json: true, 415 | body: body 416 | }, function (err, resp, body) { 417 | if (err) { 418 | return console.log(err.toString()); 419 | } 420 | 421 | if (body.success) { 422 | console.log("Done!", body.path); 423 | } else { 424 | return console.log(body.error); 425 | } 426 | }); 427 | } 428 | 429 | async function createGenesisBlock() { 430 | var genesisSecret = await prompt({ 431 | type: "password", 432 | name: "genesisSecret", 433 | message: "Enter master secret of your genesis account", 434 | validate: bip39Validator 435 | }) 436 | 437 | var wantInbuiltAsset = await inquirer.prompt({ 438 | type: "confirm", 439 | name: "wantInbuiltAsset", 440 | message: "Do you want publish a inbuilt asset in this chain?", 441 | default: false 442 | }) 443 | 444 | var assetInfo = null 445 | if (wantInbuiltAsset.wantInbuiltAsset) { 446 | var name = await prompt({ 447 | type: "input", 448 | name: "assetName", 449 | message: "Enter asset name, for example: BTC, CNY, USD, MYASSET", 450 | validate: assetNameValidator 451 | }) 452 | var amount = await prompt({ 453 | type: "input", 454 | name: "assetAmount", 455 | message: "Enter asset total amount", 456 | validate: amountValidator 457 | }) 458 | var precision = await prompt({ 459 | type: "input", 460 | name: "assetPrecison", 461 | message: "Enter asset precision", 462 | validate: precisionValidator 463 | }) 464 | assetInfo = { 465 | name: name, 466 | amount: amount, 467 | precision: precision 468 | } 469 | } 470 | 471 | var account = accountHelper.account(genesisSecret) 472 | var chainBlock = dappHelper.new(account, null, assetInfo); 473 | var chainGenesisBlockJson = JSON.stringify(chainBlock, null, 2); 474 | fs.writeFileSync('genesis.json', chainGenesisBlockJson, "utf8"); 475 | console.log("New genesis block is created at: ./genesis.json"); 476 | } 477 | 478 | module.exports = function (program) { 479 | program 480 | .command("chain") 481 | .description("manage your chains") 482 | .option("-c, --create", "create new chain") 483 | .option("-d, --deposit", "deposit funds to chain") 484 | .option("-w, --withdrawal", "withdraw funds from chain") 485 | .option("-i, --install", "install chain") 486 | .option("-u, --uninstall", "uninstall chain") 487 | .option("-g, --genesis", "create genesis block") 488 | .action(function (options) { 489 | (async function () { 490 | try { 491 | if (options.create) { 492 | createChain(); 493 | } else if (options.deposit) { 494 | depositChain(); 495 | } else if (options.withdrawal) { 496 | withdrawalChain(); 497 | } else if (options.install) { 498 | installChain(); 499 | } else if (options.uninstall) { 500 | uninstallChain(); 501 | } else if (options.genesis) { 502 | createGenesisBlock() 503 | } else { 504 | console.log("'node chain -h' to get help"); 505 | } 506 | } catch (e) { 507 | console.error(e) 508 | } 509 | })() 510 | }); 511 | } -------------------------------------------------------------------------------- /plugins/api.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var crypto = require('crypto'); 3 | var aschJS = require('asch-js'); 4 | var Api = require('../helpers/api.js'); 5 | var blockHelper = require('../helpers/block.js'); 6 | var cryptoLib = require('../lib/crypto.js'); 7 | 8 | var globalOptions; 9 | 10 | function getApi() { 11 | return new Api({host: globalOptions.host, port: globalOptions.port, mainnet: !!globalOptions.main}); 12 | } 13 | 14 | function pretty(obj) { 15 | return JSON.stringify(obj, null, 2); 16 | } 17 | 18 | function openAccount(secret) { 19 | getApi().post('/api/accounts/open', {secret: secret}, function (err, result) { 20 | console.log(err || pretty(result.account)); 21 | }); 22 | } 23 | 24 | function openAccountByPublicKey(publicKey) { 25 | getApi().post('/api/accounts/open2', {publicKey: publicKey}, function (err, result) { 26 | console.log(err || pretty(result.account)); 27 | }); 28 | } 29 | 30 | function getHeight() { 31 | getApi().get('/api/blocks/getHeight', function (err, result) { 32 | console.log(err || result.height); 33 | }); 34 | } 35 | 36 | function getBlockStatus() { 37 | getApi().get('/api/blocks/getStatus', function (err, result) { 38 | console.log(err || pretty(result)); 39 | }); 40 | } 41 | 42 | function getBalance(address) { 43 | var params = {address: address}; 44 | getApi().get('/api/accounts/getBalance', params, function (err, result) { 45 | console.log(err || result.balance); 46 | }); 47 | } 48 | 49 | function getAccount(address) { 50 | var params = {address: address}; 51 | getApi().get('/api/accounts/', params, function (err, result) { 52 | console.log(err || pretty(result)); 53 | }); 54 | } 55 | 56 | function getVotedDelegates(address, options) { 57 | var params = { 58 | address: address, 59 | limit: options.limit, 60 | offset: options.offset 61 | }; 62 | getApi().get('/api/accounts/delegates', params, function (err, result) { 63 | console.log(err || result); 64 | }); 65 | } 66 | 67 | function getDelegates(options) { 68 | var params = { 69 | limit: options.limit, 70 | offset: options.offset, 71 | orderBy: options.sort || "rate:asc" 72 | }; 73 | getApi().get('/api/delegates/', params, function (err, result) { 74 | console.log(err || pretty(result.delegates)); 75 | }); 76 | } 77 | 78 | function getDelegatesCount() { 79 | getApi().get('/api/delegates/count', function (err, result) { 80 | console.log(err || result.count); 81 | }); 82 | } 83 | 84 | function getVoters(publicKey) { 85 | var params = {publicKey: publicKey}; 86 | getApi().get('/api/delegates/voters', params, function (err, result) { 87 | console.log(err || pretty(result.accounts)); 88 | }); 89 | } 90 | 91 | function getDelegateByPublicKey(publicKey) { 92 | var params = {publicKey: publicKey}; 93 | getApi().get('/api/delegates/get', params, function (err, result) { 94 | console.log(err || pretty(result.delegate)); 95 | }); 96 | } 97 | 98 | function getDelegateByUsername(username) { 99 | var params = {username: username}; 100 | getApi().get('/api/delegates/get', params, function (err, result) { 101 | console.log(err || pretty(result.delegate)); 102 | }); 103 | } 104 | 105 | function getBlocks(options) { 106 | var params = { 107 | limit: options.limit, 108 | orderBy: options.sort, 109 | offset: options.offset, 110 | totalAmount: options.totalAmount, 111 | totalFee: options.totalFee, 112 | reward: options.reward, 113 | generatorPublicKey: options.generatorPublicKey 114 | }; 115 | getApi().get('/api/blocks/', params, function (err, result) { 116 | console.log(err || pretty(result)); 117 | }); 118 | } 119 | 120 | function getBlockById(id) { 121 | var params = {id: id}; 122 | getApi().get('/api/blocks/get', params, function (err, result) { 123 | console.log(err || pretty(result.block)); 124 | }); 125 | } 126 | 127 | function getBlockByHeight(height) { 128 | var params = {height: height}; 129 | getApi().get('/api/blocks/get', params, function (err, result) { 130 | console.log(err || pretty(result.block)); 131 | }); 132 | } 133 | 134 | function getPeers(options) { 135 | var params = { 136 | limit: options.limit, 137 | orderBy: options.sort, 138 | offset: options.offset, 139 | state: options.state, 140 | os: options.os, 141 | port: options.port, 142 | version: options.version 143 | }; 144 | // var liskOptions = {host:'login.lisk.io', port:80}; 145 | getApi().get('/api/peers/', params, function (err, result) { 146 | console.log(err || pretty(result.peers)); 147 | }); 148 | } 149 | 150 | function getUnconfirmedTransactions(options) { 151 | var params = { 152 | senderPublicKey: options.key, 153 | address: options.address 154 | }; 155 | getApi().get('/api/transactions/unconfirmed', params, function (err, result) { 156 | console.log(err || pretty(result.transactions)); 157 | }); 158 | } 159 | 160 | function getTransactions(options) { 161 | var params = { 162 | blockId: options.blockId, 163 | limit: options.limit, 164 | orderBy: options.sort, 165 | offset: options.offset, 166 | type: options.type, 167 | senderPublicKey: options.senderPublicKey, 168 | senderId: options.senderId, 169 | recipientId: options.recipientId, 170 | amount: options.amount, 171 | fee: options.fee, 172 | message: options.message 173 | }; 174 | getApi().get('/api/transactions/', params, function (err, result) { 175 | console.log(err || pretty(result.transactions)); 176 | }); 177 | } 178 | 179 | function getTransaction(id) { 180 | var params = {id: id}; 181 | getApi().get('/api/transactions/get', params, function (err, result) { 182 | console.log(err || pretty(result.transaction)); 183 | }); 184 | } 185 | 186 | function sendMoney(options) { 187 | // var params = { 188 | // secret: options.secret, 189 | // secondSecret: options.secondSecret, 190 | // recipientId: options.to, 191 | // amount: Number(options.amount) 192 | // }; 193 | // getApi().put('/api/transactions/', params, function (err, result) { 194 | // console.log(err || result); 195 | // }); 196 | var trs = aschJS.transaction.createTransactionEx({ 197 | type: 1, 198 | fee: Number(options.fee) || 10000000, 199 | message: options.message, 200 | secret: options.secret, 201 | secondSecret: options.secondSecret, 202 | args: [ 203 | options.amount, 204 | options.to 205 | ] 206 | }); 207 | getApi().broadcastTransaction(trs, function (err, result) { 208 | console.log(err || result.transactionId) 209 | }); 210 | } 211 | 212 | function sendAsset(options) { 213 | var trs = aschJS.uia.createTransfer( 214 | options.currency, 215 | options.amount, 216 | options.to, 217 | options.message, 218 | options.secret, 219 | options.secondSecret 220 | ); 221 | getApi().broadcastTransaction(trs, function (err, result) { 222 | console.log(err || result.transactionId) 223 | }); 224 | } 225 | 226 | function registerDelegate(options) { 227 | // var params = { 228 | // secret: options.secret, 229 | // username: options.username, 230 | // secondSecret: options.secondSecret, 231 | // }; 232 | // getApi().put('/api/delegates/', params, function (err, result) { 233 | // console.log(err || result); 234 | // }); 235 | var trs = aschJS.delegate.createDelegate( 236 | options.username, 237 | options.secret, 238 | options.secondSecret 239 | ); 240 | getApi().broadcastTransaction(trs, function (err, result) { 241 | console.log(err || result.transactionId) 242 | }); 243 | } 244 | 245 | function vote(secret, publicKeys, op, secondSecret) { 246 | var votes = publicKeys.split(',').map(function (el) { 247 | return op + el; 248 | }); 249 | var trs = aschJS.vote.createVote( 250 | votes, 251 | secret, 252 | secondSecret 253 | ); 254 | getApi().broadcastTransaction(trs, function (err, result) { 255 | console.log(err || result.transactionId) 256 | }); 257 | } 258 | 259 | function listdiffvotes(options) { 260 | var params = {username: options.username}; 261 | getApi().get('/api/delegates/get', params, function (err, result) { 262 | var publicKey = result.delegate.publicKey; 263 | var params = { 264 | address: result.delegate.address, 265 | limit: options.limit || 101, 266 | offset: options.offset || 0, 267 | }; 268 | getApi().get('/api/accounts/delegates', params, function (err, result) { 269 | var names_a = []; 270 | for (var i = 0; i < result.delegates.length; ++i) { 271 | names_a[i] = result.delegates[i].username; 272 | } 273 | var a = new Set(names_a); 274 | var params = {publicKey: publicKey}; 275 | getApi().get('/api/delegates/voters', params, function (err, result) { 276 | var names_b = []; 277 | for (var i = 0; i < result.accounts.length; ++i) { 278 | names_b[i] = result.accounts[i].username; 279 | } 280 | var b = new Set(names_b); 281 | var diffab = [...a].filter(x => !b.has(x)); 282 | var diffba = [...b].filter(x => !a.has(x)); 283 | console.log('you voted but doesn\'t vote you: \n\t', JSON.stringify(diffab)); 284 | console.log('\nvoted you but you don\'t voted: \n\t', JSON.stringify(diffba)); 285 | }); 286 | }); 287 | }); 288 | } 289 | 290 | function upvote(options) { 291 | vote(options.secret, options.publicKeys, '+', options.secondSecret); 292 | } 293 | 294 | function downvote(options) { 295 | vote(options.secret, options.publicKeys, '-', options.secondSecret); 296 | } 297 | 298 | function setSecondSecret(options) { 299 | var trs = aschJS.basic.setSecondSecret(options.secret, options.secondSecret); 300 | getApi().broadcastTransaction(trs, function (err, result) { 301 | console.log(err || result.transactionId) 302 | }); 303 | } 304 | 305 | function registerChain(options) { 306 | if (!options.metafile || !fs.existsSync(options.metafile)) { 307 | console.error("Error: invalid params, dapp meta file must exists"); 308 | return; 309 | } 310 | var dapp = JSON.parse(fs.readFileSync(options.metafile, 'utf8')); 311 | var trs = aschJS.transaction.createTransactionEx({ 312 | type: 200, 313 | fee: 100 * 100000000, 314 | secret: options.secret, 315 | secondSecret: options.secondSecret, 316 | args: [ 317 | dapp.name, 318 | dapp.desc, 319 | dapp.link, 320 | dapp.icon, 321 | dapp.delegates, 322 | dapp.unlockDelegates, 323 | ] 324 | }); 325 | getApi().broadcastTransaction(trs, function (err, result) { 326 | console.log(err || result.transactionId) 327 | }); 328 | } 329 | 330 | function deposit(options) { 331 | var trs = aschJS.transfer.createInTransfer(options.dapp, options.currency, options.amount, options.secret, options.secondSecret) 332 | getApi().broadcastTransaction(trs, function (err, result) { 333 | console.log(err || result.transactionId) 334 | }); 335 | } 336 | 337 | function dappTransaction(options) { 338 | var trs = aschJS.dapp.createInnerTransaction({ 339 | fee: options.fee, 340 | type: Number(options.type), 341 | args: JSON.parse(options.args) 342 | }, options.secret) 343 | getApi().put('/api/dapps/' + options.dapp + '/transactions/signed', { transaction: trs }, function (err, result) { 344 | console.log(err || result.transactionId) 345 | }); 346 | } 347 | 348 | function transaction(options) { 349 | var trs = aschJS.transaction.createTransactionEx({ 350 | type: Number(options.type), 351 | fee: Number(options.fee) || 10000000, 352 | message: options.message, 353 | secret: options.secret, 354 | secondSecret: options.secondSecret, 355 | args: JSON.parse(options.args) 356 | }) 357 | getApi().broadcastTransaction(trs, function (err, result) { 358 | console.log(err || result.transactionId) 359 | }); 360 | } 361 | 362 | function lock(options) { 363 | var trs = aschJS.transaction.createLock(options.height, options.secret, options.secondSecret) 364 | getApi().broadcastTransaction(trs, function (err, result) { 365 | console.log(err || result.transactionId) 366 | }); 367 | } 368 | 369 | function getFullBlockById(id) { 370 | getApi().get('/api/blocks/full?id=' + id, function (err, result) { 371 | console.log(err || pretty(result.block)) 372 | }) 373 | } 374 | 375 | function getFullBlockByHeight(height) { 376 | getApi().get('/api/blocks/full?height=' + height, function (err, result) { 377 | console.log(err || pretty(result.block)) 378 | }) 379 | } 380 | 381 | function getTransactionBytes(options) { 382 | try { 383 | var trs = JSON.parse(fs.readFileSync(options.file)) 384 | } catch (e) { 385 | console.log('Invalid transaction format') 386 | return 387 | } 388 | console.log(aschJS.crypto.getBytes(trs, true, true).toString('hex')) 389 | } 390 | 391 | function getTransactionId(options) { 392 | try { 393 | var trs = JSON.parse(fs.readFileSync(options.file)) 394 | } catch (e) { 395 | console.log('Invalid transaction format') 396 | return 397 | } 398 | console.log(aschJS.crypto.getId(trs)) 399 | } 400 | 401 | function getBlockPayloadHash(options) { 402 | try { 403 | var block = JSON.parse(fs.readFileSync(options.file)) 404 | } catch (e) { 405 | console.log('Invalid transaction format') 406 | return 407 | } 408 | var payloadHash = crypto.createHash('sha256'); 409 | for (let i = 0; i < block.transactions.length; ++i) { 410 | payloadHash.update(aschJS.crypto.getBytes(block.transactions[i])) 411 | } 412 | console.log(payloadHash.digest().toString('hex')) 413 | } 414 | 415 | function getBlockBytes(options) { 416 | try { 417 | var block = JSON.parse(fs.readFileSync(options.file)) 418 | } catch (e) { 419 | console.log('Invalid transaction format') 420 | return 421 | } 422 | console.log(blockHelper.getBytes(block, true).toString('hex')) 423 | } 424 | 425 | function getBlockId(options) { 426 | try { 427 | var block = JSON.parse(fs.readFileSync(options.file)) 428 | } catch (e) { 429 | console.log('Invalid transaction format') 430 | return 431 | } 432 | var bytes = blockHelper.getBytes(block) 433 | console.log(cryptoLib.getId(bytes)) 434 | } 435 | 436 | function verifyBytes(options) { 437 | console.log(aschJS.crypto.verifyBytes(options.bytes, options.signature, options.publicKey)) 438 | } 439 | 440 | module.exports = function(program) { 441 | globalOptions = program; 442 | 443 | program 444 | .command("getheight") 445 | .description("get block height") 446 | .action(getHeight); 447 | 448 | program 449 | .command("getblockstatus") 450 | .description("get block status") 451 | .action(getBlockStatus); 452 | 453 | program 454 | .command("openaccount [secret]") 455 | .description("open your account and get the infomation by secret") 456 | .action(openAccount); 457 | 458 | program 459 | .command("openaccountbypublickey [publickey]") 460 | .description("open your account and get the infomation by publickey") 461 | .action(openAccountByPublicKey); 462 | 463 | program 464 | .command("getbalance [address]") 465 | .description("get balance by address") 466 | .action(getBalance); 467 | 468 | program 469 | .command("getaccount [address]") 470 | .description("get account by address") 471 | .action(getAccount); 472 | 473 | program 474 | .command("getvoteddelegates [address]") 475 | .description("get delegates voted by address") 476 | .option("-o, --offset ", "") 477 | .option("-l, --limit ", "") 478 | .action(getVotedDelegates); 479 | 480 | program 481 | .command("getdelegatescount") 482 | .description("get delegates count") 483 | .action(getDelegatesCount); 484 | 485 | program 486 | .command("getdelegates") 487 | .description("get delegates") 488 | .option("-o, --offset ", "") 489 | .option("-l, --limit ", "") 490 | .option("-s, --sort ", "rate:asc, vote:desc, ...") 491 | .action(getDelegates); 492 | 493 | program 494 | .command("getvoters [publicKey]") 495 | .description("get voters of a delegate by public key") 496 | .action(getVoters); 497 | 498 | program 499 | .command("getdelegatebypublickey [publicKey]") 500 | .description("get delegate by public key") 501 | .action(getDelegateByPublicKey); 502 | 503 | program 504 | .command("getdelegatebyusername [username]") 505 | .description("get delegate by username") 506 | .action(getDelegateByUsername); 507 | 508 | program 509 | .command("getblocks") 510 | .description("get blocks") 511 | .option("-o, --offset ", "") 512 | .option("-l, --limit ", "") 513 | .option("-r, --reward ", "") 514 | .option("-f, --totalFee ", "") 515 | .option("-a, --totalAmount ", "") 516 | .option("-g, --generatorPublicKey ", "") 517 | .option("-s, --sort ", "height:asc, totalAmount:asc, totalFee:asc") 518 | .action(getBlocks); 519 | 520 | program 521 | .command("getblockbyid [id]") 522 | .description("get block by id") 523 | .action(getBlockById); 524 | 525 | program 526 | .command("getblockbyheight [height]") 527 | .description("get block by height") 528 | .action(getBlockByHeight); 529 | 530 | program 531 | .command("getpeers") 532 | .description("get peers") 533 | .option("-o, --offset ", "") 534 | .option("-l, --limit ", "") 535 | .option("-t, --state ", " 0 ~ 3") 536 | .option("-s, --sort ", "") 537 | .option("-v, --version ", "") 538 | .option("-p, --port ", "") 539 | .option("--os ", "") 540 | .action(getPeers); 541 | 542 | program 543 | .command("getunconfirmedtransactions") 544 | .description("get unconfirmed transactions") 545 | .option("-k, --key ", "") 546 | .option("-a, --address
", "") 547 | .action(getUnconfirmedTransactions); 548 | 549 | program 550 | .command("gettransactions") 551 | .description("get transactions") 552 | .option("-b, --blockId ", "") 553 | .option("-o, --offset ", "") 554 | .option("-l, --limit ", "") 555 | .option("-t, --type ", "transaction type") 556 | .option("-s, --sort ", "") 557 | .option("-a, --amount ", "") 558 | .option("-f, --fee ", "") 559 | .option("-m, --message ", "") 560 | .option("--senderPublicKey ", "") 561 | .option("--senderId ", "") 562 | .option("--recipientId ", "") 563 | .action(getTransactions); 564 | 565 | program 566 | .command("gettransaction [id]") 567 | .description("get transactions") 568 | .action(getTransaction); 569 | 570 | program 571 | .command("sendmoney") 572 | .description("send money to some address") 573 | .option("-e, --secret ", "") 574 | .option("-s, --secondSecret ", "") 575 | .option("-a, --amount ", "") 576 | .option("-f, --fee ", "") 577 | .option("-t, --to
", "") 578 | .option("-m, --message ", "") 579 | .action(sendMoney); 580 | 581 | program 582 | .command("sendasset") 583 | .description("send asset to some address") 584 | .option("-e, --secret ", "") 585 | .option("-s, --secondSecret ", "") 586 | .option("-c, --currency ", "") 587 | .option("-a, --amount ", "") 588 | .option("-t, --to
", "") 589 | .option("-m, --message ", "") 590 | .action(sendAsset); 591 | 592 | program 593 | .command("registerdelegate") 594 | .description("register delegate") 595 | .option("-e, --secret ", "") 596 | .option("-s, --secondSecret ", "") 597 | .option("-u, --username ", "") 598 | .action(registerDelegate); 599 | 600 | program 601 | .command("listdiffvotes") 602 | .description("list the votes each other") 603 | .option("-u, --username ", "", process.env.ASCH_USER) 604 | .action(listdiffvotes); 605 | 606 | program 607 | .command("upvote") 608 | .description("vote for delegates") 609 | .option("-e, --secret ", "") 610 | .option("-s, --secondSecret ", "") 611 | .option("-p, --publicKeys ", "") 612 | .action(upvote); 613 | 614 | program 615 | .command("downvote") 616 | .description("cancel vote for delegates") 617 | .option("-e, --secret ", "") 618 | .option("-s, --secondSecret ", "") 619 | .option("-p, --publicKeys ", "") 620 | .action(downvote); 621 | 622 | program 623 | .command("setsecondsecret") 624 | .description("set second secret") 625 | .option("-e, --secret ", "") 626 | .option("-s, --secondSecret ", "") 627 | .action(setSecondSecret); 628 | 629 | program 630 | .command("registerchain") 631 | .description("register a sidechain") 632 | .option("-e, --secret ", "") 633 | .option("-s, --secondSecret ", "") 634 | .option("-f, --metafile ", "dapp meta file") 635 | .action(registerChain); 636 | 637 | program 638 | .command("deposit") 639 | .description("deposit assets to an app") 640 | .option("-e, --secret ", "") 641 | .option("-s, --secondSecret ", "") 642 | .option("-d, --dapp ", "dapp id that you want to deposit") 643 | .option("-c, --currency ", "deposit currency") 644 | .option("-a, --amount ", "deposit amount") 645 | .action(deposit); 646 | 647 | program 648 | .command("dapptransaction") 649 | .description("create a dapp transaction") 650 | .option("-e, --secret ", "") 651 | .option("-d, --dapp ", "dapp id") 652 | .option("-t, --type ", "transaction type") 653 | .option("-a, --args ", "json array format") 654 | .option("-f, --fee ", "transaction fee") 655 | .action(dappTransaction); 656 | 657 | program 658 | .command("lock") 659 | .description("lock account transfer") 660 | .option("-e, --secret ", "") 661 | .option("-s, --secondSecret ", "") 662 | .option("-h, --height ", "lock height") 663 | .action(lock); 664 | 665 | program 666 | .command("getfullblockbyid [id]") 667 | .description("get full block by block id") 668 | .action(getFullBlockById); 669 | 670 | program 671 | .command("getfullblockbyheight [height]") 672 | .description("get full block by block height") 673 | .action(getFullBlockByHeight); 674 | 675 | program 676 | .command("gettransactionbytes") 677 | .description("get transaction bytes") 678 | .option("-f, --file ", "transaction file") 679 | .action(getTransactionBytes) 680 | 681 | program 682 | .command("gettransactionid") 683 | .description("get transaction id") 684 | .option("-f, --file ", "transaction file") 685 | .action(getTransactionId) 686 | 687 | program 688 | .command("getblockbytes") 689 | .description("get block bytes") 690 | .option("-f, --file ", "block file") 691 | .action(getBlockBytes) 692 | 693 | program 694 | .command("getblockpayloadhash") 695 | .description("get block bytes") 696 | .option("-f, --file ", "block file") 697 | .action(getBlockPayloadHash) 698 | 699 | program 700 | .command("getblockid") 701 | .description("get block id") 702 | .option("-f, --file ", "block file") 703 | .action(getBlockId) 704 | 705 | program 706 | .command("verifybytes") 707 | .description("verify bytes/signature/publickey") 708 | .option("-b, --bytes ", "transaction or block bytes") 709 | .option("-s, --signature ", "transaction or block signature") 710 | .option("-p, --publicKey ", "signer public key") 711 | .action(verifyBytes) 712 | 713 | program 714 | .command("transaction") 715 | .description("create a transaction in mainchain") 716 | .option("-e, --secret ", "") 717 | .option("-s, --secondSecret ", "") 718 | .option("-t, --type ", "transaction type") 719 | .option("-a, --args ", "json array format") 720 | .option("-m, --message ", "") 721 | .option("-f, --fee ", "transaction fee") 722 | .action(transaction); 723 | } 724 | --------------------------------------------------------------------------------